|
|
@@ -262,13 +262,18 @@
|
|
|
<!-- 我的要素(已确认的实体) -->
|
|
|
<div class="element-section">
|
|
|
<div class="element-header">
|
|
|
- <span class="element-title">
|
|
|
+ <span class="element-title" @click="showElementsModal = true" style="cursor: pointer;" title="点击管理所有要素">
|
|
|
🏷️ 我的要素
|
|
|
<span class="element-count">({{ myEntities?.length || 0 }})</span>
|
|
|
</span>
|
|
|
- <el-button size="small" :icon="Plus" @click="showAddVariableDialog = true">
|
|
|
- 添加
|
|
|
- </el-button>
|
|
|
+ <div class="header-actions">
|
|
|
+ <el-button size="small" text @click="showElementsModal = true" title="管理要素">
|
|
|
+ 管理
|
|
|
+ </el-button>
|
|
|
+ <el-button size="small" type="primary" :icon="Plus" @click="showAddVariableDialog = true">
|
|
|
+ 添加
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
<!-- 搜索和筛选 -->
|
|
|
<div class="element-filter" v-if="myEntities && myEntities.length > 0">
|
|
|
@@ -628,6 +633,111 @@
|
|
|
</template>
|
|
|
</el-dialog>
|
|
|
|
|
|
+ <!-- 报告要素管理弹窗 -->
|
|
|
+ <el-dialog
|
|
|
+ v-model="showElementsModal"
|
|
|
+ title="报告要素"
|
|
|
+ width="1100"
|
|
|
+ class="elements-modal"
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ >
|
|
|
+ <div class="elements-modal-content">
|
|
|
+ <!-- 搜索栏 -->
|
|
|
+ <div class="elements-search">
|
|
|
+ <el-input
|
|
|
+ v-model="elementsSearchKeyword"
|
|
|
+ placeholder="搜索要素名称 / 类型..."
|
|
|
+ :prefix-icon="Search"
|
|
|
+ clearable
|
|
|
+ />
|
|
|
+ <div class="elements-type-filter">
|
|
|
+ <el-radio-group v-model="elementsTypeFilter" size="small">
|
|
|
+ <el-radio-button value="">全部</el-radio-button>
|
|
|
+ <el-radio-button value="dynamic">动态要素</el-radio-button>
|
|
|
+ <el-radio-button value="static">静态要素</el-radio-button>
|
|
|
+ </el-radio-group>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 要素表格 -->
|
|
|
+ <div class="elements-table-wrap">
|
|
|
+ <el-table
|
|
|
+ :data="paginatedElements"
|
|
|
+ style="width: 100%"
|
|
|
+ max-height="400"
|
|
|
+ stripe
|
|
|
+ >
|
|
|
+ <el-table-column prop="name" label="名称" width="140">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span class="element-name">{{ row.text || row.name }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="description" label="描述" width="160">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span class="element-desc">{{ row.description || '-' }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="dataType" label="类型" width="80">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-tag size="small" :type="getDataTypeTagType(row.dataType)">
|
|
|
+ {{ row.dataType || '文本' }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="elementType" label="要素类型" width="100">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-tag size="small" :type="row.isDynamic ? 'warning' : 'info'">
|
|
|
+ {{ row.isDynamic ? '动态' : '静态' }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="originalValue" label="原值" width="120">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span class="original-value">{{ row.originalValue || row.text || '-' }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="newValue" label="新值" width="140">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-input
|
|
|
+ v-model="row.newValue"
|
|
|
+ size="small"
|
|
|
+ placeholder="输入新值"
|
|
|
+ @change="onElementValueChange(row)"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="source" label="填充源" width="140">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span class="element-source">{{ row.source || row.sourceFile || '文档' }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" width="80" fixed="right">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-button size="small" :icon="Delete" circle @click="deleteElement(row)" title="删除" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 分页 -->
|
|
|
+ <div class="elements-pagination">
|
|
|
+ <el-pagination
|
|
|
+ v-model:current-page="elementsCurrentPage"
|
|
|
+ v-model:page-size="elementsPageSize"
|
|
|
+ :page-sizes="[10, 20, 50]"
|
|
|
+ :total="filteredElementsList.length"
|
|
|
+ layout="total, sizes, prev, pager, next"
|
|
|
+ small
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="showElementsModal = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="saveAllElements" :loading="savingElements">保存全部</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
<!-- 知识图谱弹窗 -->
|
|
|
<el-dialog v-model="showGraphModal" title="🔗 知识图谱" width="900">
|
|
|
<div class="graph-container">
|
|
|
@@ -1490,6 +1600,120 @@ const selectionRange = ref(null)
|
|
|
// 知识图谱
|
|
|
const showGraphModal = ref(false)
|
|
|
|
|
|
+// 报告要素管理弹窗
|
|
|
+const showElementsModal = ref(false)
|
|
|
+const elementsSearchKeyword = ref('')
|
|
|
+const elementsTypeFilter = ref('')
|
|
|
+const elementsCurrentPage = ref(1)
|
|
|
+const elementsPageSize = ref(10)
|
|
|
+const savingElements = ref(false)
|
|
|
+
|
|
|
+// 计算属性:过滤后的要素列表(用于弹窗)
|
|
|
+const filteredElementsList = computed(() => {
|
|
|
+ let list = myEntities.value || []
|
|
|
+
|
|
|
+ // 按类型筛选
|
|
|
+ if (elementsTypeFilter.value === 'dynamic') {
|
|
|
+ list = list.filter(e => e.isDynamic)
|
|
|
+ } else if (elementsTypeFilter.value === 'static') {
|
|
|
+ list = list.filter(e => !e.isDynamic)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 按关键词搜索
|
|
|
+ if (elementsSearchKeyword.value) {
|
|
|
+ const keyword = elementsSearchKeyword.value.toLowerCase()
|
|
|
+ list = list.filter(e =>
|
|
|
+ (e.text || e.name || '').toLowerCase().includes(keyword) ||
|
|
|
+ (e.type || '').toLowerCase().includes(keyword) ||
|
|
|
+ (e.description || '').toLowerCase().includes(keyword)
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ return list
|
|
|
+})
|
|
|
+
|
|
|
+// 计算属性:分页后的要素列表
|
|
|
+const paginatedElements = computed(() => {
|
|
|
+ const start = (elementsCurrentPage.value - 1) * elementsPageSize.value
|
|
|
+ const end = start + elementsPageSize.value
|
|
|
+ return filteredElementsList.value.slice(start, end)
|
|
|
+})
|
|
|
+
|
|
|
+// 获取数据类型对应的标签类型
|
|
|
+function getDataTypeTagType(dataType) {
|
|
|
+ const typeMap = {
|
|
|
+ '文本': '',
|
|
|
+ '金额': 'success',
|
|
|
+ '日期': 'warning',
|
|
|
+ '数字': 'info',
|
|
|
+ '百分比': 'danger'
|
|
|
+ }
|
|
|
+ return typeMap[dataType] || ''
|
|
|
+}
|
|
|
+
|
|
|
+// 要素值变更
|
|
|
+function onElementValueChange(element) {
|
|
|
+ // 标记为已修改
|
|
|
+ element.isModified = true
|
|
|
+}
|
|
|
+
|
|
|
+// 删除要素
|
|
|
+function deleteElement(element) {
|
|
|
+ ElMessageBox.confirm(
|
|
|
+ `确定要删除要素「${element.text || element.name}」吗?`,
|
|
|
+ '删除确认',
|
|
|
+ {
|
|
|
+ confirmButtonText: '删除',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning'
|
|
|
+ }
|
|
|
+ ).then(() => {
|
|
|
+ const index = entities.value.findIndex(e => e.id === element.id)
|
|
|
+ if (index !== -1) {
|
|
|
+ entities.value.splice(index, 1)
|
|
|
+ }
|
|
|
+ ElMessage.success('已删除')
|
|
|
+ refreshDocumentHighlight()
|
|
|
+ }).catch(() => {})
|
|
|
+}
|
|
|
+
|
|
|
+// 保存所有要素
|
|
|
+async function saveAllElements() {
|
|
|
+ savingElements.value = true
|
|
|
+ try {
|
|
|
+ // 找出所有被修改的要素
|
|
|
+ const modifiedElements = filteredElementsList.value.filter(e => e.isModified)
|
|
|
+
|
|
|
+ if (modifiedElements.length === 0) {
|
|
|
+ ElMessage.info('没有需要保存的修改')
|
|
|
+ showElementsModal.value = false
|
|
|
+ savingElements.value = false
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // TODO: 调用 API 保存修改
|
|
|
+ // await Promise.all(modifiedElements.map(e => variableApi.update(e.id, e)))
|
|
|
+
|
|
|
+ // 清除修改标记
|
|
|
+ modifiedElements.forEach(e => {
|
|
|
+ e.isModified = false
|
|
|
+ // 如果有新值,更新文本
|
|
|
+ if (e.newValue && e.newValue !== e.text) {
|
|
|
+ e.text = e.newValue
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ ElMessage.success(`已保存 ${modifiedElements.length} 个要素`)
|
|
|
+ refreshDocumentHighlight()
|
|
|
+ showElementsModal.value = false
|
|
|
+ } catch (error) {
|
|
|
+ console.error('保存要素失败:', error)
|
|
|
+ ElMessage.error('保存失败')
|
|
|
+ } finally {
|
|
|
+ savingElements.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
// 实体编辑弹窗
|
|
|
const showEntityEditModal = ref(false)
|
|
|
const editingEntity = ref(null)
|
|
|
@@ -4732,6 +4956,85 @@ onUnmounted(() => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// ==========================================
|
|
|
+// 报告要素管理弹窗样式
|
|
|
+// ==========================================
|
|
|
+.elements-modal {
|
|
|
+ :deep(.el-dialog__header) {
|
|
|
+ padding: 16px 20px;
|
|
|
+ border-bottom: 1px solid var(--border);
|
|
|
+ margin-right: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-dialog__body) {
|
|
|
+ padding: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-dialog__footer) {
|
|
|
+ padding: 12px 20px;
|
|
|
+ border-top: 1px solid var(--border);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.elements-modal-content {
|
|
|
+ .elements-search {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 16px;
|
|
|
+ padding: 16px 20px;
|
|
|
+ border-bottom: 1px solid var(--border);
|
|
|
+ background: var(--bg);
|
|
|
+
|
|
|
+ .el-input {
|
|
|
+ max-width: 300px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .elements-table-wrap {
|
|
|
+ padding: 0;
|
|
|
+
|
|
|
+ :deep(.el-table) {
|
|
|
+ .element-name {
|
|
|
+ font-weight: 500;
|
|
|
+ color: var(--text-1);
|
|
|
+ }
|
|
|
+
|
|
|
+ .element-desc {
|
|
|
+ color: var(--text-3);
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .original-value {
|
|
|
+ color: var(--text-2);
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .element-source {
|
|
|
+ color: var(--primary);
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-input__wrapper {
|
|
|
+ box-shadow: none;
|
|
|
+ background: var(--bg);
|
|
|
+ border-radius: var(--radius-sm);
|
|
|
+
|
|
|
+ &:hover, &.is-focus {
|
|
|
+ background: var(--white);
|
|
|
+ box-shadow: 0 0 0 1px var(--primary);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .elements-pagination {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ padding: 12px 20px;
|
|
|
+ border-top: 1px solid var(--border);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
// ==========================================
|
|
|
// 新建报告对话框样式
|
|
|
// ==========================================
|