2026-04-13 17:04:38 +08:00
|
|
|
|
<template>
|
2026-04-15 17:19:56 +08:00
|
|
|
|
<div class="xmind-box" :class="{ 'xmind-box--dashboard': dashboardMode }">
|
|
|
|
|
|
<div class="action-panel">
|
|
|
|
|
|
<el-button type="primary" :size="dashboardMode ? 'mini' : 'small'" icon="el-icon-refresh" @click="handleRefresh">刷新</el-button>
|
2026-04-13 17:04:38 +08:00
|
|
|
|
<!-- <el-button type="primary" icon="el-icon-view" @click="handleRefresh">详情</el-button>
|
|
|
|
|
|
<el-button type="primary" icon="el-icon-edit" @click="handleRefresh">编辑</el-button>
|
|
|
|
|
|
<el-button type="primary" icon="el-icon-folder" @click="previewFiles(currentNode)">文件</el-button>
|
|
|
|
|
|
<el-button type="primary" icon="el-icon-picture" @click="previewImages(currentNode)">图片</el-button> -->
|
|
|
|
|
|
</div>
|
2026-04-15 17:19:56 +08:00
|
|
|
|
<div class="xmind-container" ref="chart" :style="containerStyle"></div>
|
2026-04-13 17:04:38 +08:00
|
|
|
|
<!-- 新增:三级节点点击弹窗-查看完整信息 -->
|
|
|
|
|
|
<el-dialog title="节点详情信息" :visible.sync="dialogVisible" width="1200px" center append-to-body>
|
|
|
|
|
|
<el-form>
|
|
|
|
|
|
<el-row :gutter="10">
|
|
|
|
|
|
<el-col :span="6">
|
|
|
|
|
|
<el-form-item label="进度类别" prop="name">
|
|
|
|
|
|
<el-input v-model="currentNode.tabNode" placeholder="请输入进度类型"></el-input>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-col>
|
|
|
|
|
|
<el-col :span="6">
|
|
|
|
|
|
<el-form-item label="一级分类" prop="firstLevelNode">
|
|
|
|
|
|
<el-input v-model="currentNode.firstLevelNode" placeholder="请输入一级分类"></el-input>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-col>
|
|
|
|
|
|
<el-col :span="12">
|
|
|
|
|
|
<el-form-item label="步骤名称" prop="secondLevelNode">
|
|
|
|
|
|
<el-input v-model="currentNode.secondLevelNode" placeholder="请输入步骤名称"></el-input>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-col>
|
|
|
|
|
|
|
|
|
|
|
|
<el-col :span="12">
|
|
|
|
|
|
<el-form-item label="负责人" prop="nodeHeader">
|
|
|
|
|
|
<el-input v-model="currentNode.nodeHeader" placeholder="请输入负责人"></el-input>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-col>
|
|
|
|
|
|
|
|
|
|
|
|
<el-col :span="12">
|
|
|
|
|
|
<el-form-item label="规格需求" prop="specification">
|
|
|
|
|
|
<el-input v-model="currentNode.specification" placeholder="规格需求"></el-input>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-col>
|
|
|
|
|
|
|
|
|
|
|
|
<el-col :span="24">
|
|
|
|
|
|
<el-form-item label="成果资料" prop="relatedDocs">
|
|
|
|
|
|
<file-upload @success="handleFileSuccess" @delete="handleFileDelete" v-model="currentNode.relatedDocs"
|
|
|
|
|
|
placeholder="成果资料"></file-upload>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-col>
|
|
|
|
|
|
|
|
|
|
|
|
<el-col :span="24">
|
|
|
|
|
|
<el-form-item label="相关图片" prop="relatedImages">
|
|
|
|
|
|
<image-upload v-model="currentNode.relatedImages" placeholder="相关图片"></image-upload>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-col>
|
|
|
|
|
|
|
|
|
|
|
|
<el-col :span="24">
|
|
|
|
|
|
<el-form-item label="需求资料" prop="requirementFile">
|
|
|
|
|
|
<file-upload @success="handleFileSuccess" @delete="handleFileDelete" v-model="currentNode.requirementFile"
|
|
|
|
|
|
placeholder="需求资料"></file-upload>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-col>
|
|
|
|
|
|
</el-row>
|
|
|
|
|
|
</el-form>
|
|
|
|
|
|
<!-- <div class="dialog-content">
|
|
|
|
|
|
<div class="node-title">{{ currentNode.name }}</div>
|
|
|
|
|
|
<div class="node-info-item" v-for="(val, key) in currentNode.value" :key="key">
|
|
|
|
|
|
<span class="label">{{ key }}:</span>
|
|
|
|
|
|
<span class="value">{{ val || '无' }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div> -->
|
|
|
|
|
|
<template #footer>
|
|
|
|
|
|
<span class="dialog-footer">
|
|
|
|
|
|
<el-button type="primary" @click="handleSubmit">提交修改</el-button>
|
|
|
|
|
|
<el-button type="primary" @click="dialogVisible = false">关闭</el-button>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
import { addFileOperationRecord } from '@/api/oa/fileOperationRecord';
|
|
|
|
|
|
import { updateProjectScheduleStep } from "@/api/oa/projectScheduleStep";
|
|
|
|
|
|
import * as echarts from 'echarts';
|
|
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
|
name: "Xmind",
|
|
|
|
|
|
props: {
|
|
|
|
|
|
list: {
|
|
|
|
|
|
type: Array,
|
|
|
|
|
|
default: () => []
|
2026-04-15 17:19:56 +08:00
|
|
|
|
},
|
|
|
|
|
|
/** 容器高度,综合看板等场景可传 100% 以撑满父级 */
|
|
|
|
|
|
height: {
|
|
|
|
|
|
type: String,
|
|
|
|
|
|
default: '800px'
|
|
|
|
|
|
},
|
|
|
|
|
|
/** 综合看板:紧凑、防重叠、三色状态、小圆点 */
|
|
|
|
|
|
dashboardMode: {
|
|
|
|
|
|
type: Boolean,
|
|
|
|
|
|
default: false
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
computed: {
|
|
|
|
|
|
containerStyle () {
|
|
|
|
|
|
return {
|
|
|
|
|
|
width: '100%',
|
|
|
|
|
|
height: this.height,
|
|
|
|
|
|
minHeight: this.dashboardMode ? '300px' : '240px'
|
|
|
|
|
|
}
|
2026-04-13 17:04:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
data () {
|
|
|
|
|
|
return {
|
|
|
|
|
|
chartInstance: null, // 保存图表实例,用于后续重绘/销毁
|
|
|
|
|
|
dialogVisible: false, // 新增:弹窗显示隐藏开关
|
|
|
|
|
|
currentNode: {}, // 新增:存储当前点击的三级节点完整数据
|
|
|
|
|
|
clickEvent: null, // 新增:存储点击事件句柄,用于销毁解绑
|
|
|
|
|
|
users: [],
|
|
|
|
|
|
supplierList: [],
|
|
|
|
|
|
};
|
|
|
|
|
|
},
|
|
|
|
|
|
watch: {
|
|
|
|
|
|
// 监听列表数据变化,自动更新图表
|
|
|
|
|
|
list: {
|
|
|
|
|
|
deep: true,
|
|
|
|
|
|
handler () {
|
|
|
|
|
|
if (this.chartInstance) {
|
|
|
|
|
|
this.initChart();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
mounted () {
|
|
|
|
|
|
this.initChart();
|
2026-04-15 17:19:56 +08:00
|
|
|
|
this.$nextTick(() => this.resizeChart());
|
2026-04-13 17:04:38 +08:00
|
|
|
|
window.addEventListener('resize', this.resizeChart);
|
|
|
|
|
|
},
|
|
|
|
|
|
beforeDestroy () {
|
|
|
|
|
|
window.removeEventListener('resize', this.resizeChart);
|
|
|
|
|
|
// 新增:解绑Echarts点击事件,防止内存泄漏
|
|
|
|
|
|
if (this.chartInstance && this.clickEvent) {
|
|
|
|
|
|
this.chartInstance.off('click', this.clickEvent);
|
|
|
|
|
|
}
|
|
|
|
|
|
// 销毁图表实例,防止内存泄漏
|
|
|
|
|
|
this.chartInstance?.dispose();
|
|
|
|
|
|
},
|
|
|
|
|
|
methods: {
|
|
|
|
|
|
// 优化:增加防抖处理-窗口自适应,避免频繁触发
|
|
|
|
|
|
resizeChart () {
|
|
|
|
|
|
this.chartInstance?.resize()
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
handleSubmit () {
|
|
|
|
|
|
updateProjectScheduleStep(this.currentNode).then(response => {
|
|
|
|
|
|
this.$modal.msgSuccess("修改成功");
|
|
|
|
|
|
this.dialogVisible = false;
|
|
|
|
|
|
this.handleRefresh();
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
handleRefresh () {
|
|
|
|
|
|
this.$emit('refresh')
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
handleFileSuccess (resList, res) {
|
|
|
|
|
|
addFileOperationRecord({
|
|
|
|
|
|
fileId: res.ossId,
|
|
|
|
|
|
fileName: res.name,
|
|
|
|
|
|
type: 1,
|
|
|
|
|
|
projectId: this.currentNode.projectId,
|
|
|
|
|
|
trackId: this.currentNode.trackId,
|
|
|
|
|
|
})
|
|
|
|
|
|
console.log(this.currentNode, this.currentNode.relatedDocs);
|
|
|
|
|
|
updateProjectScheduleStep({
|
|
|
|
|
|
...this.currentNode,
|
|
|
|
|
|
relatedDocs: this.currentNode.relatedDocs,
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
handleFileDelete (res) {
|
|
|
|
|
|
addFileOperationRecord({
|
|
|
|
|
|
fileId: res.ossId,
|
|
|
|
|
|
fileName: res.name,
|
|
|
|
|
|
type: 2,
|
|
|
|
|
|
projectId: this.currentNode.projectId,
|
|
|
|
|
|
trackId: this.currentNode.trackId,
|
|
|
|
|
|
})
|
|
|
|
|
|
console.log(this.currentNode, this.currentNode.relatedDocs);
|
|
|
|
|
|
updateProjectScheduleStep({
|
|
|
|
|
|
...this.currentNode,
|
|
|
|
|
|
relatedDocs: this.currentNode.relatedDocs,
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2026-04-15 17:19:56 +08:00
|
|
|
|
/** 看板长标签按字折行(与折线图区域可读性一致) */
|
|
|
|
|
|
wrapLabelText (text, maxCharsPerLine) {
|
|
|
|
|
|
if (!text) return ''
|
|
|
|
|
|
const max = Math.max(4, maxCharsPerLine || 16)
|
|
|
|
|
|
if (text.length <= max) return text
|
|
|
|
|
|
const lines = []
|
|
|
|
|
|
for (let i = 0; i < text.length; i += max) {
|
|
|
|
|
|
lines.push(text.slice(i, i + max))
|
|
|
|
|
|
}
|
|
|
|
|
|
return lines.join('\n')
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// Tab节点 → 一级节点 → 二级节点(叶子带业务数据)
|
2026-04-13 17:04:38 +08:00
|
|
|
|
transformToTreeData (list) {
|
|
|
|
|
|
if (!list.length) return { name: '暂无项目数据', children: [] };
|
|
|
|
|
|
|
2026-04-15 17:19:56 +08:00
|
|
|
|
const dm = this.dashboardMode
|
2026-04-13 17:04:38 +08:00
|
|
|
|
const projectName = list[0].projectName || '项目进度树图';
|
2026-04-15 17:19:56 +08:00
|
|
|
|
const tabMap = new Map();
|
|
|
|
|
|
|
2026-04-13 17:04:38 +08:00
|
|
|
|
list.forEach(item => {
|
2026-04-15 17:19:56 +08:00
|
|
|
|
const tab = item.tabNode || '默认分组';
|
2026-04-13 17:04:38 +08:00
|
|
|
|
const firstLevel = item.firstLevelNode || '未分类一级节点';
|
2026-04-15 17:19:56 +08:00
|
|
|
|
const secondLevel = item.secondLevelNode || item.stepName || '未命名节点';
|
|
|
|
|
|
const st = Number(item.status);
|
|
|
|
|
|
let statusText = '进行中';
|
|
|
|
|
|
let statusColor = '#E6A23C';
|
|
|
|
|
|
let lineToNode = { color: '#dcdfe6', width: 1.2 };
|
|
|
|
|
|
if (st === 2) {
|
|
|
|
|
|
statusText = '已完成';
|
|
|
|
|
|
statusColor = '#67C23A';
|
|
|
|
|
|
lineToNode = { color: '#67C23A', width: 1.8 };
|
|
|
|
|
|
} else if (st === 0) {
|
|
|
|
|
|
statusText = '未开始';
|
|
|
|
|
|
statusColor = '#909399';
|
|
|
|
|
|
} else if (st === 1) {
|
|
|
|
|
|
statusText = '待验收/进行中';
|
|
|
|
|
|
statusColor = '#E6A23C';
|
|
|
|
|
|
} else if (st === 3) {
|
|
|
|
|
|
statusText = '暂停';
|
|
|
|
|
|
statusColor = '#909399';
|
|
|
|
|
|
}
|
2026-04-13 17:04:38 +08:00
|
|
|
|
|
|
|
|
|
|
const nodeData = {
|
|
|
|
|
|
name: secondLevel,
|
2026-04-15 17:19:56 +08:00
|
|
|
|
/* 看板:小圆点 + 白边,贴近折线图主色风格 */
|
|
|
|
|
|
itemStyle: dm
|
|
|
|
|
|
? { color: statusColor, borderColor: '#ffffff', borderWidth: 1.25, shadowBlur: 3, shadowColor: 'rgba(0,0,0,0.12)' }
|
|
|
|
|
|
: { color: statusColor, borderColor: statusColor },
|
2026-04-13 17:04:38 +08:00
|
|
|
|
label: { color: statusColor },
|
2026-04-15 17:19:56 +08:00
|
|
|
|
lineStyle: lineToNode,
|
2026-04-13 17:04:38 +08:00
|
|
|
|
value: {
|
|
|
|
|
|
...item,
|
2026-04-15 17:19:56 +08:00
|
|
|
|
statusLabel: statusText
|
2026-04-13 17:04:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-04-15 17:19:56 +08:00
|
|
|
|
if (!tabMap.has(tab)) {
|
|
|
|
|
|
tabMap.set(tab, new Map());
|
|
|
|
|
|
}
|
|
|
|
|
|
const firstMap = tabMap.get(tab);
|
|
|
|
|
|
if (!firstMap.has(firstLevel)) {
|
|
|
|
|
|
firstMap.set(firstLevel, []);
|
2026-04-13 17:04:38 +08:00
|
|
|
|
}
|
2026-04-15 17:19:56 +08:00
|
|
|
|
firstMap.get(firstLevel).push(nodeData);
|
2026-04-13 17:04:38 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-04-15 17:19:56 +08:00
|
|
|
|
const treeChildren = Array.from(tabMap).map(([tabName, firstMap]) => ({
|
|
|
|
|
|
name: tabName,
|
|
|
|
|
|
itemStyle: dm
|
|
|
|
|
|
? { color: '#606266', borderColor: '#ffffff', borderWidth: 1, shadowBlur: 2, shadowColor: 'rgba(0,0,0,0.06)' }
|
|
|
|
|
|
: { color: '#606266', borderColor: '#dcdfe6' },
|
|
|
|
|
|
lineStyle: { color: '#c0c4cc', width: 1.2 },
|
|
|
|
|
|
children: Array.from(firstMap).map(([firstName, children]) => ({
|
|
|
|
|
|
name: firstName,
|
|
|
|
|
|
itemStyle: dm
|
|
|
|
|
|
? { color: '#303133', borderColor: '#ffffff', borderWidth: 1, shadowBlur: 2, shadowColor: 'rgba(0,0,0,0.06)' }
|
|
|
|
|
|
: { color: '#303133', borderColor: '#dcdfe6' },
|
|
|
|
|
|
lineStyle: { color: '#c0c4cc', width: 1.2 },
|
|
|
|
|
|
children
|
|
|
|
|
|
}))
|
2026-04-13 17:04:38 +08:00
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: projectName,
|
2026-04-15 17:19:56 +08:00
|
|
|
|
symbolSize: dm ? 7 : undefined,
|
|
|
|
|
|
itemStyle: dm
|
|
|
|
|
|
? { color: '#409eff', borderColor: '#ffffff', borderWidth: 1.5, shadowBlur: 4, shadowColor: 'rgba(64,158,255,0.35)' }
|
|
|
|
|
|
: { color: '#409eff', borderColor: '#409eff' },
|
|
|
|
|
|
lineStyle: { color: '#a0cfff', width: 1.5 },
|
|
|
|
|
|
label: dm ? { distance: 8, fontSize: 11 } : undefined,
|
2026-04-13 17:04:38 +08:00
|
|
|
|
children: treeChildren
|
|
|
|
|
|
};
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化图表
|
|
|
|
|
|
initChart () {
|
|
|
|
|
|
// 初始化图表实例
|
|
|
|
|
|
if (!this.chartInstance) {
|
|
|
|
|
|
this.chartInstance = echarts.init(this.$refs.chart);
|
|
|
|
|
|
}
|
|
|
|
|
|
// 重要:先解绑已有点击事件,防止多次绑定导致弹窗多次触发
|
|
|
|
|
|
if (this.clickEvent) {
|
|
|
|
|
|
this.chartInstance.off('click', this.clickEvent);
|
|
|
|
|
|
}
|
|
|
|
|
|
// 转换数据格式
|
|
|
|
|
|
const treeData = this.transformToTreeData(this.list);
|
2026-04-15 17:19:56 +08:00
|
|
|
|
const dm = this.dashboardMode;
|
|
|
|
|
|
const escapeHtml = (s) => String(s == null ? '' : s)
|
|
|
|
|
|
.replace(/&/g, '&')
|
|
|
|
|
|
.replace(/</g, '<')
|
|
|
|
|
|
.replace(/>/g, '>')
|
|
|
|
|
|
.replace(/"/g, '"')
|
2026-04-13 17:04:38 +08:00
|
|
|
|
const option = {
|
2026-04-15 17:19:56 +08:00
|
|
|
|
backgroundColor: dm ? 'transparent' : undefined,
|
2026-04-13 17:04:38 +08:00
|
|
|
|
tooltip: {
|
|
|
|
|
|
trigger: 'item',
|
2026-04-15 17:19:56 +08:00
|
|
|
|
enterable: true,
|
|
|
|
|
|
confine: true,
|
|
|
|
|
|
extraCssText: 'max-width:420px;white-space:normal;word-break:break-word;border-radius:6px;box-shadow:0 2px 12px rgba(0,0,0,0.08);',
|
|
|
|
|
|
formatter: (params) => {
|
|
|
|
|
|
const data = params.data
|
|
|
|
|
|
const title = (data && data.name) != null ? data.name : (params.name || '')
|
|
|
|
|
|
let tip = `<div style="font-size:13px;font-weight:600;margin-bottom:6px;line-height:1.45;color:#303133">${escapeHtml(title)}</div>`
|
|
|
|
|
|
if (data && data.value) {
|
|
|
|
|
|
const v = data.value
|
|
|
|
|
|
tip += `<div style="font-size:12px;line-height:1.55;color:#606266">负责人:${v.nodeHeader != null && v.nodeHeader !== '' ? escapeHtml(v.nodeHeader) : '无'}</div>`
|
|
|
|
|
|
tip += `<div style="font-size:12px;line-height:1.55;color:#606266">规格需求:${v.specification != null && v.specification !== '' ? escapeHtml(v.specification) : '无'}</div>`
|
|
|
|
|
|
tip += `<div style="font-size:12px;line-height:1.55;color:#606266">状态:${v.statusLabel != null && v.statusLabel !== '' ? escapeHtml(v.statusLabel) : '无'}</div>`
|
|
|
|
|
|
tip += `<div style="font-size:12px;line-height:1.55;color:#606266">计划完成:${v.planEnd != null && v.planEnd !== '' ? escapeHtml(v.planEnd) : '无'}</div>`
|
2026-04-13 17:04:38 +08:00
|
|
|
|
}
|
2026-04-15 17:19:56 +08:00
|
|
|
|
return tip
|
2026-04-13 17:04:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
series: [
|
|
|
|
|
|
{
|
2026-04-15 17:19:56 +08:00
|
|
|
|
type: 'tree',
|
2026-04-13 17:04:38 +08:00
|
|
|
|
data: [treeData],
|
2026-04-15 17:19:56 +08:00
|
|
|
|
/* 与折线图区域一致:留白、白底在容器上 */
|
|
|
|
|
|
...(dm ? { left: '1%', right: '5%', top: '2%', bottom: '2%' } : {}),
|
|
|
|
|
|
symbol: 'circle',
|
|
|
|
|
|
...(dm ? {} : { symbolSize: 6 }),
|
|
|
|
|
|
edgeShape: dm ? 'polyline' : 'curve',
|
|
|
|
|
|
edgeForkPosition: dm ? '74%' : '50%',
|
|
|
|
|
|
orient: 'LR',
|
|
|
|
|
|
initialTreeDepth: 4,
|
|
|
|
|
|
roam: true,
|
|
|
|
|
|
scaleLimit: dm ? { min: 0.22, max: 5 } : undefined,
|
2026-04-13 17:04:38 +08:00
|
|
|
|
label: {
|
|
|
|
|
|
show: true,
|
2026-04-15 17:19:56 +08:00
|
|
|
|
fontSize: dm ? 11 : 12,
|
|
|
|
|
|
fontWeight: 400,
|
|
|
|
|
|
position: 'left',
|
|
|
|
|
|
verticalAlign: 'middle',
|
|
|
|
|
|
...(dm ? { align: 'right' } : {}),
|
|
|
|
|
|
distance: 8,
|
|
|
|
|
|
overflow: 'none',
|
|
|
|
|
|
lineHeight: dm ? 15 : 14,
|
|
|
|
|
|
color: dm ? '#606266' : undefined
|
2026-04-13 17:04:38 +08:00
|
|
|
|
},
|
2026-04-15 17:19:56 +08:00
|
|
|
|
/*
|
|
|
|
|
|
* levels[i]:根下一层起 Tab / 分类 / 叶;缩小状态圆点,叶节点标签放右侧防挤压
|
|
|
|
|
|
*/
|
|
|
|
|
|
levels: dm
|
|
|
|
|
|
? [
|
|
|
|
|
|
{
|
|
|
|
|
|
symbolSize: 6,
|
|
|
|
|
|
itemStyle: { borderWidth: 1, borderColor: '#fff' },
|
|
|
|
|
|
label: {
|
|
|
|
|
|
position: 'left',
|
|
|
|
|
|
distance: 8,
|
|
|
|
|
|
fontSize: 11,
|
|
|
|
|
|
width: 118,
|
|
|
|
|
|
overflow: 'break',
|
|
|
|
|
|
lineHeight: 14,
|
|
|
|
|
|
padding: [2, 6, 2, 6]
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
symbolSize: 5,
|
|
|
|
|
|
itemStyle: { borderWidth: 1, borderColor: '#fff' },
|
|
|
|
|
|
label: {
|
|
|
|
|
|
position: 'left',
|
|
|
|
|
|
distance: 10,
|
|
|
|
|
|
fontSize: 11,
|
|
|
|
|
|
width: 160,
|
|
|
|
|
|
overflow: 'break',
|
|
|
|
|
|
lineHeight: 14,
|
|
|
|
|
|
padding: [2, 8, 2, 6]
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
symbolSize: 4,
|
|
|
|
|
|
itemStyle: { borderWidth: 1, borderColor: '#fff', shadowBlur: 2, shadowColor: 'rgba(0,0,0,0.1)' },
|
|
|
|
|
|
label: {
|
|
|
|
|
|
position: 'right',
|
|
|
|
|
|
verticalAlign: 'middle',
|
|
|
|
|
|
align: 'left',
|
|
|
|
|
|
distance: 12,
|
|
|
|
|
|
fontSize: 11,
|
|
|
|
|
|
width: 232,
|
|
|
|
|
|
overflow: 'break',
|
|
|
|
|
|
lineHeight: 15,
|
|
|
|
|
|
padding: [2, 8, 2, 8],
|
|
|
|
|
|
formatter: (p) => this.wrapLabelText(p.name, 17)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
: [
|
|
|
|
|
|
{ symbolSize: 10 },
|
|
|
|
|
|
{ symbolSize: 7 },
|
|
|
|
|
|
{ symbolSize: 5 },
|
|
|
|
|
|
{ symbolSize: 3 }
|
|
|
|
|
|
],
|
2026-04-13 17:04:38 +08:00
|
|
|
|
lineStyle: {
|
2026-04-15 17:19:56 +08:00
|
|
|
|
width: dm ? 1 : 1.2,
|
|
|
|
|
|
curveness: dm ? 0.1 : 0.3,
|
|
|
|
|
|
color: dm ? '#e4e7ed' : '#ccc'
|
2026-04-13 17:04:38 +08:00
|
|
|
|
},
|
|
|
|
|
|
emphasis: {
|
2026-04-15 17:19:56 +08:00
|
|
|
|
focus: 'descendant',
|
|
|
|
|
|
lineStyle: { width: 2, color: '#409eff' },
|
|
|
|
|
|
itemStyle: dm ? { shadowBlur: 6, shadowColor: 'rgba(64,158,255,0.45)' } : undefined
|
2026-04-13 17:04:38 +08:00
|
|
|
|
},
|
2026-04-15 17:19:56 +08:00
|
|
|
|
expandAndCollapse: true,
|
|
|
|
|
|
animationDuration: 280
|
2026-04-13 17:04:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
};
|
|
|
|
|
|
// 渲染图表
|
|
|
|
|
|
this.chartInstance?.setOption(option, true);
|
|
|
|
|
|
|
|
|
|
|
|
this.clickEvent = (params) => {
|
2026-04-15 17:19:56 +08:00
|
|
|
|
const data = params.data;
|
|
|
|
|
|
if (data && data.value && data.value.trackId) {
|
|
|
|
|
|
this.currentNode = { ...data.value };
|
|
|
|
|
|
this.dialogVisible = true;
|
2026-04-13 17:04:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// 绑定点击事件
|
|
|
|
|
|
this.chartInstance.on('click', this.clickEvent);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.xmind-box {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.action-panel {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 10px;
|
|
|
|
|
|
right: 10px;
|
|
|
|
|
|
z-index: 1000;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.xmind-container {
|
|
|
|
|
|
background: #fafafa;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-15 17:19:56 +08:00
|
|
|
|
.xmind-box--dashboard {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
min-height: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 与综合看板折线图区域一致:白底、细边框、轻圆角 */
|
|
|
|
|
|
.xmind-box--dashboard .xmind-container {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
min-height: 0;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
border: 1px solid #ebeef5;
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.8);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-13 17:04:38 +08:00
|
|
|
|
/* 新增:弹窗内部样式美化 */
|
|
|
|
|
|
:deep(.dialog-content) {
|
|
|
|
|
|
padding: 10px 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.node-title) {
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
color: #1890FF;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
padding: 8px 0;
|
|
|
|
|
|
border-bottom: 1px solid #f0f0f0;
|
|
|
|
|
|
margin-bottom: 15px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.node-info-item) {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
padding: 6px 0;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.label) {
|
|
|
|
|
|
width: 80px;
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.value) {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.dialog-footer) {
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|