Files
CN_Tool_client/frontend/src/views/tools/mmsMapping/components/JsonMappingTree.vue
yexb ef80aff151 feat(steady): 重构稳态校验功能并优化界面布局
- 更新 API 接口路径从 /steady/data-view/checksquare/* 到 /steady/checksquare/*
- 修改校验任务状态枚举值 FAILED 为 FAIL 并更新相关处理逻辑
- 移除缺失率和最大连续缺失分钟数字段,简化数据完整性计算
- 添加新的创建结果面板组件 ChecksquareCreateResultPanel.vue
- 调整创建对话框布局,采用两行搜索控件设计
- 更新任务表头部按钮文字为"新增"并调整搜索列配置为5列
- 修改详情面板显示开始时间和结束时间字段
- 重构工作台界面布局,使用 flex 布局替代 grid 布局
- 更新设备类型相关 API 接口和数据结构定义
- 添加设备类型字典常量并更新路由配置
- 优化搜索表单展开收起逻辑的计算方式
- 调整创建流程不再轮询获取任务详情,改为直接显示摘要信息
- 更新数据完整性格式化函数参数和调用方式
- 修改创建对话框样式类名和尺寸配置
- 添加设备类型管理相关的接口定义和实现方法
2026-06-15 08:40:44 +08:00

202 lines
4.9 KiB
Vue

<template>
<div class="json-mapping-tree-viewer">
<div class="json-tree-toolbar">
<div class="json-tree-meta">{{ metaText }}</div>
<div class="json-tree-actions">
<slot name="actions" />
<el-button type="primary" plain size="small" :disabled="!rootNode" @click="expandAll">全部展开</el-button>
<el-button plain size="small" :disabled="!rootNode" @click="collapseAll">全部收起</el-button>
<slot name="trailing-actions" />
</div>
</div>
<div v-if="rootNode" class="json-tree-body">
<JsonTreeNode :node="rootNode" :depth="0" :is-last="true" :expanded-keys="expandedKeys" @toggle="toggleNode" />
</div>
<pre v-else class="mapping-json-text">{{ source }}</pre>
</div>
</template>
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import JsonTreeNode, { type JsonTreeNodeModel, type JsonValueType } from './JsonMappingTreeNode.vue'
defineOptions({
name: 'JsonMappingTree'
})
type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue }
const props = defineProps<{
source: string
metaText?: string
}>()
const expandedKeys = ref<Set<string>>(new Set())
const parsedJson = computed<{ valid: true; value: JsonValue } | { valid: false }>(() => {
try {
return {
valid: true,
value: JSON.parse(props.source) as JsonValue
}
} catch {
return {
valid: false
}
}
})
const rootNode = computed(() => {
if (!parsedJson.value.valid) return null
return buildJsonNode(undefined, parsedJson.value.value, '$')
})
watch(
rootNode,
node => {
expandedKeys.value = new Set(node ? collectContainerKeys(node) : [])
},
{ immediate: true }
)
function buildJsonNode(keyName: string | undefined, source: JsonValue, path: string): JsonTreeNodeModel {
if (Array.isArray(source)) {
return {
id: path,
keyName,
openToken: '[',
closeToken: ']',
summary: ` ${source.length}`,
children: source.map((item, index) => buildJsonNode(undefined, item, `${path}/${index}`))
}
}
if (source && typeof source === 'object') {
const entries = Object.entries(source)
return {
id: path,
keyName,
openToken: '{',
closeToken: '}',
summary: ` ${entries.length}`,
children: entries.map(([key, value]) => buildJsonNode(key, value, `${path}/${escapePathKey(key)}`))
}
}
return {
id: path,
keyName,
valueText: formatPrimitiveValue(source),
valueType: getPrimitiveType(source)
}
}
function collectContainerKeys(node: JsonTreeNodeModel): string[] {
if (!node.children) return []
return [node.id, ...node.children.flatMap(collectContainerKeys)]
}
function escapePathKey(key: string) {
return key.replace(/~/g, '~0').replace(/\//g, '~1')
}
function formatPrimitiveValue(source: JsonValue) {
if (typeof source === 'string') return JSON.stringify(source)
if (source === null) return 'null'
return String(source)
}
function getPrimitiveType(source: JsonValue): JsonValueType {
if (source === null) return 'null'
if (typeof source === 'boolean') return 'boolean'
if (typeof source === 'number') return 'number'
return 'string'
}
function toggleNode(id: string) {
const nextKeys = new Set(expandedKeys.value)
if (nextKeys.has(id)) {
nextKeys.delete(id)
} else {
nextKeys.add(id)
}
expandedKeys.value = nextKeys
}
function expandAll() {
if (!rootNode.value) return
expandedKeys.value = new Set(collectContainerKeys(rootNode.value))
}
function collapseAll() {
expandedKeys.value = new Set()
}
</script>
<style scoped lang="scss">
.json-mapping-tree-viewer {
display: flex;
flex: 1;
flex-direction: column;
min-height: 0;
}
.json-tree-toolbar {
display: flex;
align-items: center;
justify-content: flex-end;
min-height: 28px;
gap: 8px;
margin-bottom: 8px;
white-space: nowrap;
}
.json-tree-meta {
flex: 1;
min-width: 0;
overflow: hidden;
color: #64748b;
font-size: 13px;
text-overflow: ellipsis;
}
.json-tree-actions {
display: flex;
flex: 0 0 auto;
align-items: center;
gap: 12px;
}
.json-tree-actions :deep(.el-button + .el-button) {
margin-left: 0;
}
.json-tree-body,
.mapping-json-text {
flex: 1;
min-height: 0;
margin: 0;
padding: 16px;
border: 1px solid #dbe3f0;
border-radius: 10px;
background: #ffffff;
overflow: auto;
font-family: Consolas, 'Courier New', monospace;
font-size: 13px;
line-height: 1.7;
color: #172033;
}
.mapping-json-text {
white-space: pre-wrap;
word-break: break-word;
}
</style>