|
|
@ -84,8 +84,8 @@ |
|
|
</div> |
|
|
</div> |
|
|
<div class="filter-btns"> |
|
|
<div class="filter-btns"> |
|
|
<el-button type="primary" class="btn-search-ref" @click="handleNodeSearch">搜索</el-button> |
|
|
<el-button type="primary" class="btn-search-ref" @click="handleNodeSearch">搜索</el-button> |
|
|
<el-button type="primary" class="btn-import" @click="">导入</el-button> |
|
|
<el-button type="primary" class="btn-import" @click="triggerImport('node')">导入</el-button> |
|
|
<el-button type="primary" class="btn-export" @click="">导出</el-button> |
|
|
<el-button type="warning" class="btn-export" @click="handleExport('node')">导出</el-button> |
|
|
<el-button class="btn-orange" @click="openNodeDialog(null)">新增节点</el-button> |
|
|
<el-button class="btn-orange" @click="openNodeDialog(null)">新增节点</el-button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
@ -178,6 +178,8 @@ |
|
|
</div> |
|
|
</div> |
|
|
<div class="filter-btns"> |
|
|
<div class="filter-btns"> |
|
|
<el-button type="primary" class="btn-search-ref" @click="handleRelSearch">查询</el-button> |
|
|
<el-button type="primary" class="btn-search-ref" @click="handleRelSearch">查询</el-button> |
|
|
|
|
|
<el-button type="primary" class="btn-import" @click="triggerImport('rel')">导入</el-button> |
|
|
|
|
|
<el-button type="warning" class="btn-export" @click="handleExport('rel')">导出</el-button> |
|
|
<el-button class="btn-orange" @click="openRelDialog(null)">新增关系</el-button> |
|
|
<el-button class="btn-orange" @click="openRelDialog(null)">新增关系</el-button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
@ -222,7 +224,90 @@ |
|
|
</div> |
|
|
</div> |
|
|
</main> |
|
|
</main> |
|
|
|
|
|
|
|
|
<el-dialog v-model="detailVisible" title="数据详情" style=" border-radius: 7px;" width="550px" destroy-on-close header-class="bold-header"> |
|
|
<input type="file" ref="fileInput" style="display: none" accept=".json" @change="handleFileChange"/> |
|
|
|
|
|
|
|
|
|
|
|
<el-dialog v-model="importDialogVisible" title="批量导入预检报告" width="550px" class="custom-dialog" |
|
|
|
|
|
:close-on-click-modal="false" header-class="bold-header"> |
|
|
|
|
|
<div style="display: flex; gap: 16px; padding: 10px 5px;"> |
|
|
|
|
|
<div style="flex-shrink: 0; padding-top: 2px;"> |
|
|
|
|
|
<el-icon v-if="importStep === 'report'" :size="28" |
|
|
|
|
|
:color="importReport.conflicts.length > 0 ? '#e6a23c' : '#67c23a'"> |
|
|
|
|
|
<WarningFilled v-if="importReport.conflicts.length > 0"/> |
|
|
|
|
|
<SuccessFilled v-else/> |
|
|
|
|
|
</el-icon> |
|
|
|
|
|
<el-icon v-else class="is-loading" :size="28" color="#165dff"> |
|
|
|
|
|
<Loading/> |
|
|
|
|
|
</el-icon> |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<div style="flex: 1;"> |
|
|
|
|
|
<div v-if="importStep === 'checking'"> |
|
|
|
|
|
<p style="margin: 0; font-size: 16px; font-weight: bold; color: #303133;">正在分析数据冲突...</p> |
|
|
|
|
|
<p style="margin: 8px 0 0; font-size: 14px; color: #606266;">系统正在检查 JSON 文件中的重复数据。</p> |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<div v-else-if="importStep === 'report'"> |
|
|
|
|
|
<p style="margin: 0; font-size: 16px; font-weight: bold; color: #303133;"> |
|
|
|
|
|
<span v-if="importReport.invalid.length > 0" style="color: #f56c6c">检测到非法数据</span> |
|
|
|
|
|
<span v-else-if="importReport.conflicts.length > 0" style="color: #e6a23c">检测到数据冲突</span> |
|
|
|
|
|
<span v-else style="color: #67c23a;">预检通过</span> |
|
|
|
|
|
</p> |
|
|
|
|
|
|
|
|
|
|
|
<p style="margin: 8px 0 15px; font-size: 14px; color: #606266; line-height: 1.6;"> |
|
|
|
|
|
本次导入包含 {{ importReport.summary.total }} 条数据。 |
|
|
|
|
|
<span v-if="importReport.invalid.length > 0"> |
|
|
|
|
|
其中 <b style="color: #f56c6c">{{ importReport.invalid.length }}</b> 条数据由于节点不存在无法导入。 |
|
|
|
|
|
</span> |
|
|
|
|
|
<span v-if="importReport.conflicts.length > 0"> |
|
|
|
|
|
另有 <b style="color: #e6a23c">{{ importReport.conflicts.length }}</b> 条已存在。 |
|
|
|
|
|
</span> |
|
|
|
|
|
<span v-if="importReport.summary.valid > 0" style="color: #67c23a;"> |
|
|
|
|
|
可导入 <b>{{ importReport.summary.valid }}</b> 条。 |
|
|
|
|
|
</span> |
|
|
|
|
|
</p> |
|
|
|
|
|
|
|
|
|
|
|
<div style="background: #f8f9fb; padding: 10px; border-radius: 6px; margin-bottom: 15px;"> |
|
|
|
|
|
<span style="font-size: 13px; font-weight: bold; color: #606266; margin-right: 10px;">处理策略:</span> |
|
|
|
|
|
<el-radio-group v-model="importMode" size="small"> |
|
|
|
|
|
<el-radio label="skip">跳过重复</el-radio> |
|
|
|
|
|
<el-radio label="update">覆盖更新</el-radio> |
|
|
|
|
|
</el-radio-group> |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<el-table :data="importReport.conflicts.slice(0, 5)" border size="small" |
|
|
|
|
|
style="width: 100%; font-size: 12px;"> |
|
|
|
|
|
<el-table-column :prop="importType === 'node' ? 'nodeId' : 'source'" |
|
|
|
|
|
:label="importType === 'node' ? 'ID' : '起点'" show-overflow-tooltip/> |
|
|
|
|
|
<el-table-column :prop="importType === 'node' ? 'name' : 'target'" |
|
|
|
|
|
:label="importType === 'node' ? '名称' : '终点'" show-overflow-tooltip/> |
|
|
|
|
|
<el-table-column |
|
|
|
|
|
v-if="importReport.invalid.length > 0" |
|
|
|
|
|
prop="reason" |
|
|
|
|
|
label="异常原因" |
|
|
|
|
|
width="180" |
|
|
|
|
|
style="color: #f56c6c" |
|
|
|
|
|
/> |
|
|
|
|
|
</el-table> |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<div v-else-if="importStep === 'uploading'"> |
|
|
|
|
|
<p style="margin: 0 0 10px; font-weight: bold;">正在写入数据库...</p> |
|
|
|
|
|
<el-progress :percentage="importProgress" :stroke-width="15" striped striped-flow/> |
|
|
|
|
|
</div> |
|
|
|
|
|
</div> |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<template #footer> |
|
|
|
|
|
<div class="dialog-footer-wrap" v-if="importStep === 'report'"> |
|
|
|
|
|
<el-button class="btn-cancel" @click="importDialogVisible = false">取消导入</el-button> |
|
|
|
|
|
<el-button class="btn-confirm" type="primary" @click="startBatchImport" |
|
|
|
|
|
:disabled="importReport.summary.valid === 0" >{{ importReport.summary.valid === 0 ? '无有效数据' : '开始导入' }}</el-button> |
|
|
|
|
|
</div> |
|
|
|
|
|
</template> |
|
|
|
|
|
</el-dialog> |
|
|
|
|
|
|
|
|
|
|
|
<el-dialog v-model="detailVisible" title="数据详情" style="border-radius: 7px;" width="550px" destroy-on-close |
|
|
|
|
|
header-class="bold-header"> |
|
|
<el-descriptions :column="1" border> |
|
|
<el-descriptions :column="1" border> |
|
|
<el-descriptions-item label="系统 ID (ElementId)">{{ currentDetail.id }}</el-descriptions-item> |
|
|
<el-descriptions-item label="系统 ID (ElementId)">{{ currentDetail.id }}</el-descriptions-item> |
|
|
<template v-if="detailType === 'node'"> |
|
|
<template v-if="detailType === 'node'"> |
|
|
@ -248,7 +333,8 @@ |
|
|
<el-input v-model="nodeForm.name" placeholder="请输入实体名称" clearable/> |
|
|
<el-input v-model="nodeForm.name" placeholder="请输入实体名称" clearable/> |
|
|
</el-form-item> |
|
|
</el-form-item> |
|
|
<el-form-item label="标签" required> |
|
|
<el-form-item label="标签" required> |
|
|
<el-select class="label-select" v-model="nodeForm.label" filterable placeholder="请选择标签" style="width: 100%"> |
|
|
<el-select class="label-select" v-model="nodeForm.label" filterable placeholder="请选择标签" |
|
|
|
|
|
style="width: 100%"> |
|
|
<el-option v-for="item in dynamicLabels" :key="item" :label="translateToChinese(item)" :value="item"/> |
|
|
<el-option v-for="item in dynamicLabels" :key="item" :label="translateToChinese(item)" :value="item"/> |
|
|
</el-select> |
|
|
</el-select> |
|
|
</el-form-item> |
|
|
</el-form-item> |
|
|
@ -310,66 +396,44 @@ |
|
|
|
|
|
|
|
|
<script setup> |
|
|
<script setup> |
|
|
import {ref, onMounted, reactive, computed} from 'vue' |
|
|
import {ref, onMounted, reactive, computed} from 'vue' |
|
|
import {ElMessage, ElMessageBox} from 'element-plus' |
|
|
import {ElMessage, ElMessageBox, ElLoading} from 'element-plus' |
|
|
import Menu from '@/components/Menu.vue' |
|
|
import Menu from '@/components/Menu.vue' |
|
|
import { |
|
|
import { |
|
|
getKgStats, getLabels, getRelationshipTypes, getNodeSuggestions, getNodesList, getRelationshipsList, |
|
|
getKgStats, getLabels, getRelationshipTypes, getNodeSuggestions, getNodesList, getRelationshipsList, |
|
|
addNode, updateNode, addRelationship, updateRelationship, deleteNode, deleteRelationship, |
|
|
addNode, updateNode, addRelationship, updateRelationship, deleteNode, deleteRelationship, |
|
|
fixNodeIds |
|
|
fixNodeIds, |
|
|
|
|
|
exportNodes, exportRelationships, |
|
|
|
|
|
precheckNodes, executeImportNodes, |
|
|
|
|
|
precheckRelationships, executeImportRelationships |
|
|
} from '@/api/data' |
|
|
} from '@/api/data' |
|
|
import {preload} from "@/api/graph"; |
|
|
import {preload} from "@/api/graph"; |
|
|
|
|
|
|
|
|
// --- 映射字典 (作为兜底和基础映射) --- |
|
|
// --- 映射字典 --- |
|
|
const CHINESE_TO_ENGLISH_LABEL = { |
|
|
const CHINESE_TO_ENGLISH_LABEL = { |
|
|
"疾病": "Disease", |
|
|
"疾病": "Disease", "症状": "Symptom", "检查项目": "AuxiliaryExamination", "药物": "Drug", "手术": "Operation", |
|
|
"症状": "Symptom", |
|
|
"解剖部位": "CheckSubject", "并发症": "Complication", "诊断": "Diagnosis", "治疗": "Treatment", |
|
|
"检查项目": "AuxiliaryExamination", |
|
|
"辅助治疗": "AdjuvantTherapy", "不良反应": "adverseReactions", "检查": "Check", "部门": "Department", |
|
|
"药物": "Drug", |
|
|
"疾病部位": "DiseaseSite", "相关疾病": "RelatedDisease", "相关症状": "RelatedSymptom", "传播途径": "SpreadWay", |
|
|
"手术": "Operation", |
|
|
"阶段": "Stage", "药物分类": "Subject", "症状与体征": "SymptomAndSign", "治疗方案": "TreatmentPrograms", |
|
|
"解剖部位": "CheckSubject", |
|
|
"类型": "Type", "原因": "Cause", "属性": "Attribute", "指示/适应症": "Indications", "成分": "Ingredients", |
|
|
"并发症": "Complication", |
|
|
"病原学": "Pathogenesis", "病理类型": "PathologicalType", "发病机制": "Pathophysiology", "注意事项": "Precautions", |
|
|
"诊断": "Diagnosis", |
|
|
"预后": "Prognosis", "预后生存时间": "PrognosticSurvivalTime", "疾病比率": "DiseaseRatio", "药物治疗": "DrugTherapy", |
|
|
"治疗": "Treatment", |
|
|
"感染性": "Infectious", "关联实体": "RelatedTo", "人群分组": "MultipleGroups", "发病率": "DiseaseRate" |
|
|
"辅助治疗": "AdjuvantTherapy", |
|
|
|
|
|
"不良反应": "adverseReactions", |
|
|
|
|
|
"检查": "Check", |
|
|
|
|
|
"科室": "Department", |
|
|
|
|
|
"疾病部位": "DiseaseSite", |
|
|
|
|
|
"相关疾病": "RelatedDisease", |
|
|
|
|
|
"相关症状": "RelatedSymptom", |
|
|
|
|
|
"传播途径": "SpreadWay", |
|
|
|
|
|
"阶段": "Stage", |
|
|
|
|
|
"药物分类": "Subject", |
|
|
|
|
|
"症状与体征": "SymptomAndSign", |
|
|
|
|
|
"治疗方案": "TreatmentPrograms", |
|
|
|
|
|
"类型": "Type", |
|
|
|
|
|
"原因": "Cause", |
|
|
|
|
|
"属性": "Attribute", |
|
|
|
|
|
"指示/适应症": "Indications", |
|
|
|
|
|
"成分": "Ingredients", |
|
|
|
|
|
"病原学": "Pathogenesis", |
|
|
|
|
|
"病理类型": "PathologicalType", |
|
|
|
|
|
"发病机制": "Pathophysiology", |
|
|
|
|
|
"注意事项": "Precautions", |
|
|
|
|
|
"预后": "Prognosis", |
|
|
|
|
|
"预后生存时间": "PrognosticSurvivalTime", |
|
|
|
|
|
"疾病比率": "DiseaseRatio", |
|
|
|
|
|
"药物治疗": "DrugTherapy", |
|
|
|
|
|
"感染性": "Infectious", |
|
|
|
|
|
"关联实体":"RelatedTo", |
|
|
|
|
|
"人群分组":"MultipleGroups", |
|
|
|
|
|
"发病率":"DiseaseRate" |
|
|
|
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// 动态获取的关系类型映射 |
|
|
const dynamicRelTypes = ref([]); |
|
|
const dynamicRelTypes = ref([]); |
|
|
const dynamicLabels = ref([]); |
|
|
const dynamicLabels = ref([]); |
|
|
|
|
|
|
|
|
|
|
|
// 核心:翻译字典逻辑 |
|
|
const ENGLISH_TO_CHINESE = computed(() => { |
|
|
const ENGLISH_TO_CHINESE = computed(() => { |
|
|
const map = {}; |
|
|
const map = {}; |
|
|
|
|
|
// 1. 基础字典映射 |
|
|
for (const [chi, eng] of Object.entries(CHINESE_TO_ENGLISH_LABEL)) { |
|
|
for (const [chi, eng] of Object.entries(CHINESE_TO_ENGLISH_LABEL)) { |
|
|
map[eng] = chi; |
|
|
map[eng] = chi; |
|
|
map[eng.toLowerCase()] = chi; |
|
|
map[eng.toLowerCase()] = chi; |
|
|
} |
|
|
} |
|
|
|
|
|
// 2. 数据库动态获取的映射 (优先级更高) |
|
|
dynamicRelTypes.value.forEach(item => { |
|
|
dynamicRelTypes.value.forEach(item => { |
|
|
map[item.type] = item.label; |
|
|
map[item.type] = item.label; |
|
|
}); |
|
|
}); |
|
|
@ -399,6 +463,7 @@ const relTotal = ref(0); |
|
|
const relPage = ref(1); |
|
|
const relPage = ref(1); |
|
|
const relSearch = reactive({source: '', target: '', type: ''}); |
|
|
const relSearch = reactive({source: '', target: '', type: ''}); |
|
|
|
|
|
|
|
|
|
|
|
// --- 弹窗控制 --- |
|
|
const nodeDialogVisible = ref(false); |
|
|
const nodeDialogVisible = ref(false); |
|
|
const relDialogVisible = ref(false); |
|
|
const relDialogVisible = ref(false); |
|
|
const detailVisible = ref(false); |
|
|
const detailVisible = ref(false); |
|
|
@ -406,10 +471,25 @@ const detailType = ref('node'); |
|
|
const currentDetail = ref({}); |
|
|
const currentDetail = ref({}); |
|
|
const isEdit = ref(false); |
|
|
const isEdit = ref(false); |
|
|
|
|
|
|
|
|
|
|
|
// --- 表单数据 --- |
|
|
const nodeForm = reactive({id: '', name: '', label: ''}); |
|
|
const nodeForm = reactive({id: '', name: '', label: ''}); |
|
|
const relForm = reactive({id: '', source: '', target: '', type: '', label: ''}); |
|
|
const relForm = reactive({id: '', source: '', target: '', type: '', label: ''}); |
|
|
|
|
|
|
|
|
// --- 分页切换处理逻辑 --- |
|
|
// --- 批量导入状态 --- |
|
|
|
|
|
const fileInput = ref(null); |
|
|
|
|
|
const importDialogVisible = ref(false); |
|
|
|
|
|
const importType = ref('node'); |
|
|
|
|
|
const importStep = ref('checking'); |
|
|
|
|
|
const importProgress = ref(0); |
|
|
|
|
|
const importMode = ref('skip'); |
|
|
|
|
|
const importRawData = ref([]); |
|
|
|
|
|
const importReport = reactive({ |
|
|
|
|
|
conflicts: [], |
|
|
|
|
|
invalid: [], |
|
|
|
|
|
summary: {total: 0, valid: 0, conflict: 0} |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// 分页处理 |
|
|
const handleNodeSizeChange = (val) => { |
|
|
const handleNodeSizeChange = (val) => { |
|
|
pageSize.value = val; |
|
|
pageSize.value = val; |
|
|
nodePage.value = 1; |
|
|
nodePage.value = 1; |
|
|
@ -424,9 +504,11 @@ const handleRelSizeChange = (val) => { |
|
|
|
|
|
|
|
|
// --- 数据抓取逻辑 --- |
|
|
// --- 数据抓取逻辑 --- |
|
|
const fetchAllMetadata = async () => { |
|
|
const fetchAllMetadata = async () => { |
|
|
|
|
|
// 获取全量节点标签 |
|
|
getLabels().then(res => { |
|
|
getLabels().then(res => { |
|
|
if (res?.code === 200) dynamicLabels.value = res.data; |
|
|
if (res?.code === 200) dynamicLabels.value = res.data; |
|
|
}); |
|
|
}); |
|
|
|
|
|
// 获取全量关系类型 |
|
|
getRelationshipTypes().then(res => { |
|
|
getRelationshipTypes().then(res => { |
|
|
if (res?.code === 200) dynamicRelTypes.value = res.data; |
|
|
if (res?.code === 200) dynamicRelTypes.value = res.data; |
|
|
}); |
|
|
}); |
|
|
@ -460,8 +542,8 @@ const fetchNodes = async () => { |
|
|
label: nodeSearch.label || null |
|
|
label: nodeSearch.label || null |
|
|
}); |
|
|
}); |
|
|
if (res?.code === 200) { |
|
|
if (res?.code === 200) { |
|
|
nodeData.value = res.data.items; |
|
|
nodeData.value = res.data.items || res.data || []; |
|
|
nodeTotal.value = res.data.total; |
|
|
nodeTotal.value = res.count || res.data.total || 0; |
|
|
} |
|
|
} |
|
|
} catch (e) { |
|
|
} catch (e) { |
|
|
ElMessage.error('加载节点失败'); |
|
|
ElMessage.error('加载节点失败'); |
|
|
@ -470,31 +552,52 @@ const fetchNodes = async () => { |
|
|
} |
|
|
} |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 关系列表解析逻辑 |
|
|
|
|
|
*/ |
|
|
const fetchRels = async () => { |
|
|
const fetchRels = async () => { |
|
|
loading.value = true; |
|
|
loading.value = true; |
|
|
try { |
|
|
try { |
|
|
const res = await getRelationshipsList({ |
|
|
const res = await getRelationshipsList({ |
|
|
page: relPage.value, |
|
|
page: relPage.value, pageSize: pageSize.value, |
|
|
pageSize: pageSize.value, |
|
|
source: relSearch.source?.trim() || null, target: relSearch.target?.trim() || null, |
|
|
source: relSearch.source?.trim() || null, |
|
|
type: relSearch.type || null |
|
|
target: relSearch.target?.trim() || null, |
|
|
|
|
|
type: relSearch.type || null // 此时 type 已经是英文 |
|
|
|
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
if (res?.code === 200) { |
|
|
if (res?.code === 200) { |
|
|
relData.value = res.data.items; |
|
|
const rawList = Array.isArray(res.data) ? res.data : (res.data.items || []); |
|
|
relTotal.value = res.data.total; |
|
|
relData.value = rawList.map(pathItem => { |
|
|
|
|
|
const segment = (pathItem.segments && pathItem.segments.length > 0) ? pathItem.segments[0] : null; |
|
|
|
|
|
const relObj = segment ? segment.relationship : null; |
|
|
|
|
|
|
|
|
|
|
|
if (relObj) { |
|
|
|
|
|
return { |
|
|
|
|
|
id: relObj.elementId || relObj.identity, |
|
|
|
|
|
elementId: relObj.elementId, |
|
|
|
|
|
source: pathItem.start?.properties?.name || pathItem.start?.name || '未知', |
|
|
|
|
|
target: pathItem.end?.properties?.name || pathItem.end?.name || '未知', |
|
|
|
|
|
type: relObj.type, |
|
|
|
|
|
label: relObj.properties?.label || relObj.label || relObj.type, |
|
|
|
|
|
properties: relObj.properties || {} |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
return pathItem; |
|
|
|
|
|
}); |
|
|
|
|
|
relTotal.value = res.count !== undefined ? res.count : (res.data.total || 0); |
|
|
} |
|
|
} |
|
|
} catch (e) { |
|
|
} catch (e) { |
|
|
|
|
|
console.error("关系解析异常:", e); |
|
|
ElMessage.error('加载关系失败'); |
|
|
ElMessage.error('加载关系失败'); |
|
|
} finally { |
|
|
} finally { |
|
|
loading.value = false; |
|
|
loading.value = false; |
|
|
} |
|
|
} |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// 弹窗与提交 |
|
|
const openNodeDialog = (row = null) => { |
|
|
const openNodeDialog = (row = null) => { |
|
|
isEdit.value = !!row; |
|
|
isEdit.value = !!row; |
|
|
if (row) { |
|
|
if (row) { |
|
|
Object.assign(nodeForm, {id: row.id, name: row.name, label: row.labels?.[0] || ''}); |
|
|
Object.assign(nodeForm, {id: row.elementId || row.id, name: row.name, label: row.labels?.[0] || ''}); |
|
|
} else { |
|
|
} else { |
|
|
Object.assign(nodeForm, {id: '', name: '', label: ''}); |
|
|
Object.assign(nodeForm, {id: '', name: '', label: ''}); |
|
|
} |
|
|
} |
|
|
@ -515,6 +618,8 @@ const submitNode = async () => { |
|
|
fetchAllMetadata(); // 刷新标签 |
|
|
fetchAllMetadata(); // 刷新标签 |
|
|
preload(); |
|
|
preload(); |
|
|
fetchAllMetadata(); |
|
|
fetchAllMetadata(); |
|
|
|
|
|
fetchAllMetadata(); |
|
|
|
|
|
if (typeof preload === 'function') preload(); |
|
|
} else { |
|
|
} else { |
|
|
ElMessage.error(res?.msg || '操作失败'); |
|
|
ElMessage.error(res?.msg || '操作失败'); |
|
|
} |
|
|
} |
|
|
@ -529,11 +634,11 @@ const openRelDialog = (row = null) => { |
|
|
isEdit.value = !!row; |
|
|
isEdit.value = !!row; |
|
|
if (row) { |
|
|
if (row) { |
|
|
Object.assign(relForm, { |
|
|
Object.assign(relForm, { |
|
|
id: row.id, |
|
|
id: row.elementId || row.id, |
|
|
source: row.source, |
|
|
source: row.source, |
|
|
target: row.target, |
|
|
target: row.target, |
|
|
type: row.type, |
|
|
type: row.type, |
|
|
label: row.label || '' |
|
|
label: row.label || row.type |
|
|
}); |
|
|
}); |
|
|
} else { |
|
|
} else { |
|
|
Object.assign(relForm, {id: '', source: '', target: '', type: '', label: ''}); |
|
|
Object.assign(relForm, {id: '', source: '', target: '', type: '', label: ''}); |
|
|
@ -565,14 +670,13 @@ const submitRel = async () => { |
|
|
|
|
|
|
|
|
const handleDelete = (row, type) => { |
|
|
const handleDelete = (row, type) => { |
|
|
ElMessageBox.confirm('确定要删除吗?', '提示', { |
|
|
ElMessageBox.confirm('确定要删除吗?', '提示', { |
|
|
customClass: 'my-custom-btns', // 自定义类名 |
|
|
confirmButtonText: '确认', cancelButtonText: '取消', type: 'warning' |
|
|
confirmButtonText: '确认', |
|
|
|
|
|
cancelButtonText: '取消' |
|
|
|
|
|
}).then(async () => { |
|
|
}).then(async () => { |
|
|
const res = type === 'node' ? await deleteNode(row.id) : await deleteRelationship(row.id); |
|
|
const targetId = row.elementId || row.id; |
|
|
|
|
|
const res = type === 'node' ? await deleteNode(targetId) : await deleteRelationship(targetId); |
|
|
if (res?.code === 200) { |
|
|
if (res?.code === 200) { |
|
|
ElMessage.success('删除成功'); |
|
|
ElMessage.success('删除成功'); |
|
|
preload(); |
|
|
if (typeof preload === 'function') preload(); |
|
|
type === 'node' ? fetchNodes() : fetchRels(); |
|
|
type === 'node' ? fetchNodes() : fetchRels(); |
|
|
fetchStats(); |
|
|
fetchStats(); |
|
|
fetchAllMetadata(); |
|
|
fetchAllMetadata(); |
|
|
@ -604,18 +708,12 @@ const handleRelSearch = () => { |
|
|
* 重点:将当前 nodeSearch.label 传给后端 |
|
|
* 重点:将当前 nodeSearch.label 传给后端 |
|
|
*/ |
|
|
*/ |
|
|
const queryNodeSearch = async (queryString, cb) => { |
|
|
const queryNodeSearch = async (queryString, cb) => { |
|
|
// 条件拦截:没有标签 且 没有输入内容 |
|
|
const currentLabel = activeName.value === 'first' ? nodeSearch.label : null; |
|
|
if (!nodeSearch.label && !queryString?.trim()) { |
|
|
if (!currentLabel && !queryString?.trim()) return cb([]); |
|
|
return cb([]); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
// 【核心改动】:传递 nodeSearch.label 到后端接口 |
|
|
const res = await getNodeSuggestions(queryString || "", currentLabel); |
|
|
const res = await getNodeSuggestions(queryString || "", nodeSearch.label || null); |
|
|
|
|
|
if (res?.code === 200) { |
|
|
if (res?.code === 200) { |
|
|
// 后端返回的是字符串数组,转成组件需要的对象数组格式 |
|
|
cb((res.data || []).map(n => ({value: n}))); |
|
|
const results = (res.data || []).map(n => ({value: n})); |
|
|
|
|
|
cb(results); |
|
|
|
|
|
} else { |
|
|
} else { |
|
|
cb([]); |
|
|
cb([]); |
|
|
} |
|
|
} |
|
|
@ -625,10 +723,157 @@ const queryNodeSearch = async (queryString, cb) => { |
|
|
} |
|
|
} |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
const handleView = (row, type) => { |
|
|
/** |
|
|
detailType.value = type; |
|
|
* 导出功能 - 增加了二次确认弹窗 |
|
|
currentDetail.value = {...row}; |
|
|
*/ |
|
|
detailVisible.value = true; |
|
|
const handleExport = async (type) => { |
|
|
|
|
|
const typeText = type === 'node' ? '节点' : '关系'; |
|
|
|
|
|
|
|
|
|
|
|
// 1. 弹出确认框防止误触 |
|
|
|
|
|
ElMessageBox.confirm( |
|
|
|
|
|
`确定要导出当前的${typeText}数据吗?若数据量较大可能需要一定处理时间。`, |
|
|
|
|
|
'导出确认', |
|
|
|
|
|
{ |
|
|
|
|
|
confirmButtonText: '立即导出', |
|
|
|
|
|
cancelButtonText: '取消', |
|
|
|
|
|
type: 'info', |
|
|
|
|
|
} |
|
|
|
|
|
).then(async () => { |
|
|
|
|
|
// 2. 用户点击确认后执行 |
|
|
|
|
|
const loadingInstance = ElLoading.service({ |
|
|
|
|
|
text: `正在导出${typeText}数据...`, |
|
|
|
|
|
background: 'rgba(0, 0, 0, 0.7)' |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
let res; |
|
|
|
|
|
let fileName = ''; |
|
|
|
|
|
if (type === 'node') { |
|
|
|
|
|
res = await exportNodes({ |
|
|
|
|
|
name: nodeSearch.name?.trim() || null, |
|
|
|
|
|
label: nodeSearch.label || null |
|
|
|
|
|
}); |
|
|
|
|
|
fileName = `KG_Nodes_${Date.now()}.json`; |
|
|
|
|
|
} else { |
|
|
|
|
|
res = await exportRelationships({ |
|
|
|
|
|
source: relSearch.source?.trim() || null, |
|
|
|
|
|
target: relSearch.target?.trim() || null, |
|
|
|
|
|
type: relSearch.type || null |
|
|
|
|
|
}); |
|
|
|
|
|
fileName = `KG_Rels_${Date.now()}.json`; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (res?.code === 200 && res.data) { |
|
|
|
|
|
const blob = new Blob([JSON.stringify(res.data, null, 2)], {type: "application/json"}); |
|
|
|
|
|
const url = URL.createObjectURL(blob); |
|
|
|
|
|
const link = document.createElement("a"); |
|
|
|
|
|
link.href = url; |
|
|
|
|
|
link.download = fileName; |
|
|
|
|
|
document.body.appendChild(link); // 将链接添加到DOM |
|
|
|
|
|
link.click(); |
|
|
|
|
|
document.body.removeChild(link); // 下载后移除 |
|
|
|
|
|
URL.revokeObjectURL(url); |
|
|
|
|
|
ElMessage.success(`${typeText}导出完成`); |
|
|
|
|
|
} else { |
|
|
|
|
|
ElMessage.error(res?.msg || '导出失败'); |
|
|
|
|
|
} |
|
|
|
|
|
} catch (error) { |
|
|
|
|
|
console.error('导出异常:', error); |
|
|
|
|
|
ElMessage.error('导出异常'); |
|
|
|
|
|
} finally { |
|
|
|
|
|
loadingInstance.close(); |
|
|
|
|
|
} |
|
|
|
|
|
}).catch(() => { |
|
|
|
|
|
// 用户点击取消,不执行导出 |
|
|
|
|
|
}); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// 批量导入逻辑 |
|
|
|
|
|
const triggerImport = (type) => { |
|
|
|
|
|
importType.value = type; |
|
|
|
|
|
if (fileInput.value) fileInput.value.click(); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const handleFileChange = async (e) => { |
|
|
|
|
|
const file = e.target.files[0]; |
|
|
|
|
|
if (!file) return; |
|
|
|
|
|
const reader = new FileReader(); |
|
|
|
|
|
reader.onload = async (event) => { |
|
|
|
|
|
try { |
|
|
|
|
|
const json = JSON.parse(event.target.result); |
|
|
|
|
|
let dataList = []; |
|
|
|
|
|
if (importType.value === 'node') { |
|
|
|
|
|
dataList = Array.isArray(json) ? json : (json.nodes || []); |
|
|
|
|
|
dataList = dataList.map( |
|
|
|
|
|
item => |
|
|
|
|
|
{ |
|
|
|
|
|
// 如果 label 在映射表里有对应的英文,就替换它,否则保持原样 |
|
|
|
|
|
const |
|
|
|
|
|
mappedLabel = CHINESE_TO_ENGLISH_LABEL[item.label] || item.label; |
|
|
|
|
|
return { |
|
|
|
|
|
...item, |
|
|
|
|
|
label: mappedLabel |
|
|
|
|
|
}; |
|
|
|
|
|
}); |
|
|
|
|
|
} else { |
|
|
|
|
|
dataList = Array.isArray(json) ? json : (json.relationships || json.rels || []); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (dataList.length === 0) return ElMessage.error("数据为空"); |
|
|
|
|
|
importRawData.value = dataList; |
|
|
|
|
|
importDialogVisible.value = true; |
|
|
|
|
|
importStep.value = 'checking'; |
|
|
|
|
|
|
|
|
|
|
|
const res = importType.value === 'node' ? await precheckNodes(dataList) : await precheckRelationships(dataList); |
|
|
|
|
|
if (res?.code === 200) { |
|
|
|
|
|
Object.assign(importReport, { |
|
|
|
|
|
conflicts: res.data.conflicts || [], |
|
|
|
|
|
invalid: res.data.invalid || [], |
|
|
|
|
|
summary: res.data.summary || {total: dataList.length} |
|
|
|
|
|
}); |
|
|
|
|
|
importStep.value = 'report'; |
|
|
|
|
|
} else { |
|
|
|
|
|
ElMessage.error(res?.msg || "检查失败"); |
|
|
|
|
|
importDialogVisible.value = false; |
|
|
|
|
|
} |
|
|
|
|
|
} catch (err) { |
|
|
|
|
|
ElMessage.error("文件解析失败"); |
|
|
|
|
|
} finally { |
|
|
|
|
|
e.target.value = ''; |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
reader.readAsText(file); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const startBatchImport = async () => { |
|
|
|
|
|
if (importReport.summary.valid === 0) { |
|
|
|
|
|
ElMessage.error("无有效数据可导入,请检查节点是否存在或数据格式。"); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
importStep.value = 'uploading'; |
|
|
|
|
|
importProgress.value = 0; |
|
|
|
|
|
const total = importRawData.value.length; |
|
|
|
|
|
const chunkSize = 1000; |
|
|
|
|
|
try { |
|
|
|
|
|
for (let i = 0; i < total; i += chunkSize) { |
|
|
|
|
|
const chunk = importRawData.value.slice(i, i + chunkSize); |
|
|
|
|
|
const res = importType.value === 'node' ? await executeImportNodes(chunk, importMode.value) : await executeImportRelationships(chunk, importMode.value); |
|
|
|
|
|
if (res?.code === 200) { |
|
|
|
|
|
importProgress.value = Math.min(Math.round(((i + chunkSize) / total) * 100), 100); |
|
|
|
|
|
} else { |
|
|
|
|
|
throw new Error(res?.msg || "执行失败"); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
ElMessage.success(`导入成功`); |
|
|
|
|
|
importDialogVisible.value = false; |
|
|
|
|
|
fetchStats(); |
|
|
|
|
|
fetchAllMetadata(); |
|
|
|
|
|
if (typeof preload === 'function') preload(); |
|
|
|
|
|
activeName.value === 'first' ? fetchNodes() : fetchRels(); |
|
|
|
|
|
} catch (err) { |
|
|
|
|
|
ElMessageBox.alert(`错误:${err.message}`); |
|
|
|
|
|
importDialogVisible.value = false; |
|
|
|
|
|
} |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
onMounted(() => { |
|
|
onMounted(() => { |
|
|
@ -665,68 +910,233 @@ onMounted(() => { |
|
|
.input-group-inline :deep(.el-select__wrapper){ |
|
|
.input-group-inline :deep(.el-select__wrapper){ |
|
|
box-shadow: none !important; |
|
|
box-shadow: none !important; |
|
|
} |
|
|
} |
|
|
:deep(.input-group-inline .el-autocomplete .el-input__wrapper){ |
|
|
|
|
|
|
|
|
:deep(.input-group-inline .el-autocomplete .el-input__wrapper) { |
|
|
box-shadow: none !important; |
|
|
box-shadow: none !important; |
|
|
} |
|
|
} |
|
|
:deep(.label-select .el-autocomplete .el-input__wrapper.is-focus){ |
|
|
|
|
|
|
|
|
:deep(.label-select .el-autocomplete .el-input__wrapper.is-focus) { |
|
|
box-shadow: none !important; |
|
|
box-shadow: none !important; |
|
|
} |
|
|
} |
|
|
:deep(.label-select .el-autocomplete .el-input__wrapper:hover){ |
|
|
|
|
|
|
|
|
:deep(.label-select .el-autocomplete .el-input__wrapper:hover) { |
|
|
box-shadow: none !important; |
|
|
box-shadow: none !important; |
|
|
} |
|
|
} |
|
|
:deep(.el-form-item__content){box-shadow: 0 0 0 2px #EBF0FF;border: none;border-radius: 5px; } |
|
|
|
|
|
.label-select {box-shadow: 0 0 0 2px #EBF0FF;border: none;border-radius: 5px; } |
|
|
:deep(.el-form-item__content) { |
|
|
.btn-search-ref { background: #165dff !important; border-radius: 8px; height: 38px; } |
|
|
box-shadow: 0 0 0 2px #EBF0FF; |
|
|
|
|
|
border: none; |
|
|
.btn-import { background: rgb(145, 204, 117) !important; border-radius: 8px; height: 38px;border: none } |
|
|
border-radius: 5px; |
|
|
.btn-export { background: rgb(89, 209, 212) !important; border-radius: 8px; height: 38px;border: none } |
|
|
} |
|
|
.btn-orange { background: #ffb142 !important; color: white !important; border-radius: 8px; height: 38px; border: none !important; } |
|
|
|
|
|
.table-compact { box-shadow: 0 4px 20px rgba(22, 93, 255, 0.08); overflow: hidden; } |
|
|
.label-select { |
|
|
.ref-table :deep(.el-table__header) th { background-color: #e8f0ff !important; color: #2869ff; font-weight: 700; } |
|
|
box-shadow: 0 0 0 2px #EBF0FF; |
|
|
.op-group { display: flex; gap: 8px; justify-content: center; } |
|
|
border: none; |
|
|
.ref-op-btn { border: none !important; color: white !important; padding: 6px 14px !important; border-radius: 8px !important; } |
|
|
border-radius: 5px; |
|
|
.ref-op-btn.edit { background-color: #4379ff !important; } |
|
|
} |
|
|
.ref-op-btn.delete { background-color: #ff6060 !important; } |
|
|
|
|
|
.ref-op-btn.view { background-color: #ffb142 !important; } |
|
|
.btn-search-ref { |
|
|
.pagination-footer { margin-top: 20px; display: flex; justify-content: flex-end; } |
|
|
background: #165dff !important; |
|
|
:deep(.bold-header) { margin-right: 0 !important; display: flex !important; justify-content: flex-start !important; } |
|
|
border-radius: 8px; |
|
|
:deep(.bold-header .el-dialog__title) { color: #303133 !important;font-weight: 900 !important;padding:5px;margin-bottom: 10px; font-size: 19px !important;} |
|
|
height: 38px; |
|
|
.custom-form :deep(.el-form-item__label) { color: #606266 !important;font-size: 16px !important; } |
|
|
} |
|
|
.custom-form :deep(.el-input) {box-shadow: 0 0 0 2px #EBF0FF;border: none;border-radius: 5px;} |
|
|
|
|
|
.custom-form :deep(.el-select__wrapper){ |
|
|
.btn-import { |
|
|
|
|
|
background: rgb(145, 204, 117) !important; |
|
|
|
|
|
border-radius: 8px; |
|
|
|
|
|
height: 38px; |
|
|
|
|
|
border: none |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.btn-export { |
|
|
|
|
|
background: rgb(89, 209, 212) !important; |
|
|
|
|
|
border-radius: 8px; |
|
|
|
|
|
height: 38px; |
|
|
|
|
|
border: none |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.btn-orange { |
|
|
|
|
|
background: #ffb142 !important; |
|
|
|
|
|
color: white !important; |
|
|
|
|
|
border-radius: 8px; |
|
|
|
|
|
height: 38px; |
|
|
|
|
|
border: none !important; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.table-compact { |
|
|
|
|
|
box-shadow: 0 4px 20px rgba(22, 93, 255, 0.08); |
|
|
|
|
|
overflow: hidden; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.ref-table :deep(.el-table__header) th { |
|
|
|
|
|
background-color: #e8f0ff !important; |
|
|
|
|
|
color: #2869ff; |
|
|
|
|
|
font-weight: 700; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.op-group { |
|
|
|
|
|
display: flex; |
|
|
|
|
|
gap: 8px; |
|
|
|
|
|
justify-content: center; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.ref-op-btn { |
|
|
|
|
|
border: none !important; |
|
|
|
|
|
color: white !important; |
|
|
|
|
|
padding: 6px 14px !important; |
|
|
|
|
|
border-radius: 8px !important; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.ref-op-btn.edit { |
|
|
|
|
|
background-color: #4379ff !important; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.ref-op-btn.delete { |
|
|
|
|
|
background-color: #ff6060 !important; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.ref-op-btn.view { |
|
|
|
|
|
background-color: #ffb142 !important; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.pagination-footer { |
|
|
|
|
|
margin-top: 20px; |
|
|
|
|
|
display: flex; |
|
|
|
|
|
justify-content: flex-end; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
:deep(.bold-header) { |
|
|
|
|
|
margin-right: 0 !important; |
|
|
|
|
|
display: flex !important; |
|
|
|
|
|
justify-content: flex-start !important; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
:deep(.bold-header .el-dialog__title) { |
|
|
|
|
|
color: #303133 !important; |
|
|
|
|
|
font-weight: 900 !important; |
|
|
|
|
|
padding: 5px; |
|
|
|
|
|
margin-bottom: 10px; |
|
|
|
|
|
font-size: 19px !important; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.custom-form :deep(.el-form-item__label) { |
|
|
|
|
|
color: #606266 !important; |
|
|
|
|
|
font-size: 16px !important; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.custom-form :deep(.el-input) { |
|
|
|
|
|
box-shadow: 0 0 0 2px #EBF0FF; |
|
|
|
|
|
border: none; |
|
|
|
|
|
border-radius: 5px; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.custom-form :deep(.el-select__wrapper) { |
|
|
box-shadow: none !important; |
|
|
box-shadow: none !important; |
|
|
} |
|
|
} |
|
|
.custom-form :deep(.el-input__wrapper){ |
|
|
|
|
|
|
|
|
.custom-form :deep(.el-input__wrapper) { |
|
|
box-shadow: none !important; |
|
|
box-shadow: none !important; |
|
|
} |
|
|
} |
|
|
:deep(.custom-form .el-input__wrapper.is-focus){ |
|
|
|
|
|
|
|
|
:deep(.custom-form .el-input__wrapper.is-focus) { |
|
|
box-shadow: none !important; |
|
|
box-shadow: none !important; |
|
|
} |
|
|
} |
|
|
:deep(.custom-form .el-input__wrapper:hover){ |
|
|
|
|
|
|
|
|
:deep(.custom-form .el-input__wrapper:hover) { |
|
|
box-shadow: none !important; |
|
|
box-shadow: none !important; |
|
|
} |
|
|
} |
|
|
.dialog-footer-wrap { display: flex; justify-content: flex-end; gap: 15px; } |
|
|
|
|
|
.btn-cancel { background-color: #e6e6e6 !important; border: none !important; color: #444 !important; padding: 18px 20px !important; font-weight: 500; } |
|
|
.dialog-footer-wrap { |
|
|
.btn-confirm { background-color: #165dff !important; border: none !important; padding: 18px 20px !important; font-weight: 500; } |
|
|
display: flex; |
|
|
.animate-fade { animation: fadeIn 0.4s ease-out; } |
|
|
justify-content: flex-end; |
|
|
.pagination-custom-text{color: #86909c} |
|
|
gap: 15px; |
|
|
:deep(.el-select__placeholder){color: #606266} |
|
|
} |
|
|
:deep(.el-pagination__sizes .el-select__wrapper){box-shadow: 0 0 0 2px #EBF0FF;} |
|
|
|
|
|
:deep(.el-pagination.is-background .el-pager li.is-active){background-color: #165DFF;} |
|
|
.btn-cancel { |
|
|
|
|
|
background-color: #e6e6e6 !important; |
|
|
|
|
|
border: none !important; |
|
|
|
|
|
color: #444 !important; |
|
|
|
|
|
padding: 18px 20px !important; |
|
|
|
|
|
font-weight: 500; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.btn-confirm { |
|
|
|
|
|
background-color: #165dff !important; |
|
|
|
|
|
border: none !important; |
|
|
|
|
|
padding: 18px 20px !important; |
|
|
|
|
|
font-weight: 500; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.animate-fade { |
|
|
|
|
|
animation: fadeIn 0.4s ease-out; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.pagination-custom-text { |
|
|
|
|
|
color: #86909c |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
:deep(.el-select__placeholder) { |
|
|
|
|
|
color: #606266 |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
:deep(.el-pagination__sizes .el-select__wrapper) { |
|
|
|
|
|
box-shadow: 0 0 0 2px #EBF0FF; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
:deep(.el-pagination.is-background .el-pager li.is-active) { |
|
|
|
|
|
background-color: #165DFF; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
:deep(.el-pager li.is-active) { |
|
|
:deep(.el-pager li.is-active) { |
|
|
color: #fff !important; |
|
|
color: #fff !important; |
|
|
} |
|
|
} |
|
|
:deep(.el-dialog){border-radius: 7px} |
|
|
|
|
|
|
|
|
|
|
|
:deep(.el-input__inner) {color:#606266} |
|
|
:deep(.el-dialog) { |
|
|
:deep(.el-pagination .btn-next:hover){color: #165DFF !important;} |
|
|
border-radius: 7px |
|
|
:deep(.el-pagination .btn-prev:hover){color: #165DFF !important;} |
|
|
} |
|
|
.pagination-footer :deep(.el-pagination .el-input__inner){box-shadow: 0 0 0 2px #EBF0FF;border-radius: 5px; } |
|
|
|
|
|
.pagination-footer :deep(.el-input__wrapper){ box-shadow: none !important;} |
|
|
:deep(.el-input__inner) { |
|
|
@keyframes fadeIn { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } } |
|
|
color: #606266 |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
:deep(.el-pagination .btn-next:hover) { |
|
|
|
|
|
color: #165DFF !important; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
:deep(.el-pagination .btn-prev:hover) { |
|
|
|
|
|
color: #165DFF !important; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
:deep(.custom-dialog .el-dialog__footer) { |
|
|
|
|
|
padding: 15px 25px 20px !important; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
:deep(.custom-dialog .el-dialog__body) { |
|
|
|
|
|
padding: 10px 25px 15px !important; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.pagination-footer :deep(.el-pagination .el-input__inner) { |
|
|
|
|
|
box-shadow: 0 0 0 2px #EBF0FF; |
|
|
|
|
|
border-radius: 5px; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.pagination-footer :deep(.el-input__wrapper) { |
|
|
|
|
|
box-shadow: none !important; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@keyframes fadeIn { |
|
|
|
|
|
from { |
|
|
|
|
|
opacity: 0; |
|
|
|
|
|
transform: translateY(5px); |
|
|
|
|
|
} |
|
|
|
|
|
to { |
|
|
|
|
|
opacity: 1; |
|
|
|
|
|
transform: translateY(0); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
</style> |
|
|
</style> |
|
|
<style> |
|
|
<style> |
|
|
.el-popper .el-select-dropdown__item.is-selected{ |
|
|
.el-popper .el-select-dropdown__item.is-selected { |
|
|
color: #165DFF !important; |
|
|
color: #165DFF !important; |
|
|
font-weight: bold; |
|
|
font-weight: bold; |
|
|
color: #165dff; |
|
|
color: #165dff; |
|
|
@ -803,8 +1213,20 @@ onMounted(() => { |
|
|
border-radius: 8px 8px 0 0; |
|
|
border-radius: 8px 8px 0 0; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.my-custom-btns .el-button:not(.el-button--primary){ background-color: #e6e6e6 !important; border: none !important; color: #444 !important; padding: 18px 20px !important; font-weight: 500; } |
|
|
.my-custom-btns .el-button:not(.el-button--primary) { |
|
|
.my-custom-btns .el-button--primary { background-color: #165dff !important; border: none !important; padding: 18px 20px !important; font-weight: 500; } |
|
|
background-color: #e6e6e6 !important; |
|
|
|
|
|
border: none !important; |
|
|
|
|
|
color: #444 !important; |
|
|
|
|
|
padding: 18px 20px !important; |
|
|
|
|
|
font-weight: 500; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.my-custom-btns .el-button--primary { |
|
|
|
|
|
background-color: #165dff !important; |
|
|
|
|
|
border: none !important; |
|
|
|
|
|
padding: 18px 20px !important; |
|
|
|
|
|
font-weight: 500; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
.data-card-container { |
|
|
.data-card-container { |
|
|
background: #ffffff; |
|
|
background: #ffffff; |
|
|
|