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