|
|
@@ -42,13 +42,13 @@
|
|
|
<span class="sort-header" @click.stop="onSortClick('name', $event)">名称<span v-if="sortIndex('name') !== null" class="sort-index" :class="sortClass('name') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('name') }}</span>{{ sortIcon('name') }}</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column prop="dimensionCode" label="五维归属" width="100">
|
|
|
+ <el-table-column prop="dimensionCode" label="维度归属" width="110">
|
|
|
<template slot-scope="{ row }">
|
|
|
<span v-if="row.dimensionCode">{{ dimensionLabel(row.dimensionCode) }}</span>
|
|
|
<span v-else class="text-muted">通用</span>
|
|
|
</template>
|
|
|
<template slot="header">
|
|
|
- <span class="sort-header" @click.stop="onSortClick('dimensionCode', $event)">五维归属<span v-if="sortIndex('dimensionCode') !== null" class="sort-index" :class="sortClass('dimensionCode') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('dimensionCode') }}</span>{{ sortIcon('dimensionCode') }}</span>
|
|
|
+ <span class="sort-header" @click.stop="onSortClick('dimensionCode', $event)">维度归属<span v-if="sortIndex('dimensionCode') !== null" class="sort-index" :class="sortClass('dimensionCode') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('dimensionCode') }}</span>{{ sortIcon('dimensionCode') }}</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column prop="sourceKind" label="数据来源" width="120">
|
|
|
@@ -114,14 +114,25 @@
|
|
|
<el-form-item label="名称" required>
|
|
|
<el-input v-model="form.name" />
|
|
|
</el-form-item>
|
|
|
- <el-form-item label="五维归属">
|
|
|
- <el-select v-model="form.dimensionCode" class="form-full" clearable placeholder="不选=通用(不关联具体五维)">
|
|
|
- <el-option label="身" value="body" />
|
|
|
- <el-option label="智" value="mind" />
|
|
|
- <el-option label="富" value="wisdom" />
|
|
|
- <el-option label="行" value="action" />
|
|
|
- <el-option label="心" value="wealth" />
|
|
|
- </el-select>
|
|
|
+ <el-form-item label="维度归属">
|
|
|
+ <!-- P1-T3:从统一维度注册中心树选择归属节点(parentId=节点id,dimensionCode=节点code) -->
|
|
|
+ <div class="dim-tree-box">
|
|
|
+ <el-tree
|
|
|
+ ref="dimTree"
|
|
|
+ :data="dimensionTreeData"
|
|
|
+ :props="{ label: 'label', children: 'children', disabled: 'disabled' }"
|
|
|
+ node-key="id"
|
|
|
+ highlight-current
|
|
|
+ :expand-on-click-node="false"
|
|
|
+ :current-node-key="currentTreeNodeId"
|
|
|
+ @node-click="onDimensionNodeClick"
|
|
|
+ />
|
|
|
+ <div class="dim-tree-selected" v-if="selectedDimPath">
|
|
|
+ 已选:{{ selectedDimPath }}
|
|
|
+ <el-button type="text" size="mini" @click="clearDimension">清除</el-button>
|
|
|
+ </div>
|
|
|
+ <div class="dim-tree-selected text-muted" v-else>不选=通用(不关联具体维度)</div>
|
|
|
+ </div>
|
|
|
</el-form-item>
|
|
|
<el-form-item label="数据来源">
|
|
|
<el-select v-model="form.sourceKind" class="form-full" clearable placeholder="不选=通用">
|
|
|
@@ -162,6 +173,7 @@
|
|
|
<script>
|
|
|
import SortMixin from '@/mixins/SortMixin'
|
|
|
import { listIndicatorDefinitions, createIndicator, updateIndicator, deleteIndicator } from '@/api/indicator'
|
|
|
+import { getDimensionRegistryTree } from '@/api/dimension'
|
|
|
|
|
|
export default {
|
|
|
name: 'IndicatorsIndex',
|
|
|
@@ -190,23 +202,85 @@ export default {
|
|
|
options: '',
|
|
|
unit: '',
|
|
|
dimensionCode: '',
|
|
|
+ parentId: null,
|
|
|
sourceKind: '',
|
|
|
sortOrder: 0,
|
|
|
status: 1
|
|
|
},
|
|
|
- sortableColumns: { id: 'ID', domain: '领域', category: '分类', code: '编码', name: '名称', dataType: '数据类型', options: '可选值', dimensionCode: '五维归属', sourceKind: '数据来源', sortOrder: '排序', status: '状态' }
|
|
|
+ sortableColumns: { id: 'ID', domain: '领域', category: '分类', code: '编码', name: '名称', dataType: '数据类型', options: '可选值', dimensionCode: '维度归属', sourceKind: '数据来源', sortOrder: '排序', status: '状态' },
|
|
|
+ // 统一维度注册中心树(P1-T3):指标维度归属从五维单选改为从注册中心树选择
|
|
|
+ dimensionTreeData: [],
|
|
|
+ currentTreeNodeId: null,
|
|
|
+ selectedDimPath: '',
|
|
|
+ _dimPathMap: {}
|
|
|
}
|
|
|
},
|
|
|
mounted() {
|
|
|
this.loadData()
|
|
|
+ // P1-T3:加载统一维度注册中心树,供维度归属树形选择
|
|
|
+ this.loadDimensionTree()
|
|
|
},
|
|
|
methods: {
|
|
|
loadList() {
|
|
|
this.loadData()
|
|
|
},
|
|
|
dimensionLabel(code) {
|
|
|
- const map = { body: '身', mind: '智', wisdom: '富', action: '行', wealth: '心' }
|
|
|
- return map[code] || code
|
|
|
+ // 注册中心 root code 显示中文名;子维度 code(如 body.growth)直接显示
|
|
|
+ const rootMap = { body: '身体', mind: '心理', wisdom: '智能', action: '行动', wealth: '财富' }
|
|
|
+ return rootMap[code] || code
|
|
|
+ },
|
|
|
+ // P1-T3:加载统一维度注册中心树(GET /api/dimension-registry/tree)
|
|
|
+ loadDimensionTree() {
|
|
|
+ getDimensionRegistryTree().then(res => {
|
|
|
+ if (res.code === 200 && Array.isArray(res.data)) {
|
|
|
+ this.dimensionTreeData = this._mapDimNodes(res.data)
|
|
|
+ this._dimPathMap = {}
|
|
|
+ this._buildDimPathMap(res.data, [])
|
|
|
+ }
|
|
|
+ }).catch(() => {})
|
|
|
+ },
|
|
|
+ _mapDimNodes(nodes) {
|
|
|
+ return (nodes || []).map(n => {
|
|
|
+ return {
|
|
|
+ id: n.id,
|
|
|
+ code: n.code,
|
|
|
+ label: n.name || n.code || '',
|
|
|
+ // P1-T3 仅允许挂接到维度节点(root/sub);指标节点禁选(P2 再放开子树挂接)
|
|
|
+ disabled: n.nodeKind === 'indicator',
|
|
|
+ children: this._mapDimNodes(n.children || [])
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+ _buildDimPathMap(nodes, prefix) {
|
|
|
+ prefix = prefix || []
|
|
|
+ ;(nodes || []).forEach(n => {
|
|
|
+ var path = prefix.concat(n.name || n.code || '')
|
|
|
+ this._dimPathMap[n.id] = path
|
|
|
+ this._buildDimPathMap(n.children || [], path)
|
|
|
+ })
|
|
|
+ },
|
|
|
+ // 树节点点击:设置挂接节点(parentId=节点id,dimensionCode=节点code)
|
|
|
+ onDimensionNodeClick(data, node) {
|
|
|
+ this.form.parentId = data.id
|
|
|
+ this.form.dimensionCode = data.code
|
|
|
+ this.currentTreeNodeId = data.id
|
|
|
+ var labels = []
|
|
|
+ var p = node
|
|
|
+ while (p && p.data) {
|
|
|
+ labels.unshift(p.data.label)
|
|
|
+ p = p.parent
|
|
|
+ }
|
|
|
+ this.selectedDimPath = labels.join(' / ')
|
|
|
+ },
|
|
|
+ // 清除维度归属(=通用,不关联具体维度)
|
|
|
+ clearDimension() {
|
|
|
+ this.form.parentId = null
|
|
|
+ this.form.dimensionCode = ''
|
|
|
+ this.currentTreeNodeId = null
|
|
|
+ this.selectedDimPath = ''
|
|
|
+ if (this.$refs.dimTree) {
|
|
|
+ this.$refs.dimTree.setCurrentKey(null)
|
|
|
+ }
|
|
|
},
|
|
|
sourceKindLabel(kind) {
|
|
|
const map = { report_indicator: '报告指标', bacteria: '细菌/菌群', food_index: '食物指数', dimension_score: '五维得分' }
|
|
|
@@ -248,12 +322,22 @@ export default {
|
|
|
options: '',
|
|
|
unit: '',
|
|
|
dimensionCode: '',
|
|
|
+ parentId: null,
|
|
|
sourceKind: '',
|
|
|
sortOrder: 0,
|
|
|
status: 1
|
|
|
}
|
|
|
}
|
|
|
this.dialogVisible = true
|
|
|
+ // 编辑态:回显已挂接的维度节点(高亮 + 显示路径)
|
|
|
+ var parentId = this.form.parentId
|
|
|
+ this.currentTreeNodeId = parentId || null
|
|
|
+ this.selectedDimPath = (this._dimPathMap[parentId] || []).join(' / ')
|
|
|
+ this.$nextTick(() => {
|
|
|
+ if (this.$refs.dimTree && this.form.parentId) {
|
|
|
+ this.$refs.dimTree.setCurrentKey(this.form.parentId)
|
|
|
+ }
|
|
|
+ })
|
|
|
},
|
|
|
handleSave() {
|
|
|
this.saving = true
|
|
|
@@ -261,6 +345,8 @@ export default {
|
|
|
// 空串归一化为 null,避免数据库存 ''(语义"通用"应存 NULL)
|
|
|
if (payload.dimensionCode === '') payload.dimensionCode = null
|
|
|
if (payload.sourceKind === '') payload.sourceKind = null
|
|
|
+ // parentId 无挂接时置 null(通用/不关联具体维度节点)
|
|
|
+ if (payload.parentId === '' || payload.parentId === undefined) payload.parentId = null
|
|
|
var req = this.form.id ? updateIndicator(payload) : createIndicator(payload)
|
|
|
req.then(res => {
|
|
|
if (res.code === 200) {
|
|
|
@@ -295,4 +381,16 @@ export default {
|
|
|
/* == C 批内联清理工具类 == */
|
|
|
.filter-input-200ml12 { width: 200px; margin-left: 12px; }
|
|
|
.form-full { width: 100%; }
|
|
|
+
|
|
|
+/* P1-T3:维度归属树形选择 */
|
|
|
+.dim-tree-box { width: 100%; }
|
|
|
+.dim-tree-box .el-tree {
|
|
|
+ max-height: 220px;
|
|
|
+ overflow-y: auto;
|
|
|
+ border: 1px solid #ebeef5;
|
|
|
+ border-radius: 4px;
|
|
|
+ padding: 4px;
|
|
|
+}
|
|
|
+.dim-tree-selected { margin-top: 6px; font-size: 12px; color: #606266; }
|
|
|
+.dim-tree-selected .el-button { margin-left: 8px; padding: 0; }
|
|
|
</style>
|