diff --git a/track-uniapp/src/pages/scan/components/TreeCanvas.vue b/track-uniapp/src/pages/scan/components/TreeCanvas.vue index 29118a6..47ad6a9 100644 --- a/track-uniapp/src/pages/scan/components/TreeCanvas.vue +++ b/track-uniapp/src/pages/scan/components/TreeCanvas.vue @@ -83,12 +83,22 @@ export default { const CARD_W = 160, CARD_H = 80, GAP_X = 24, GAP_Y = 24; const CENTER_X = 0; // 中央主干 X - // 拍平 + 深拷贝 + // 🔧 浅拷贝所有 task 对象,避免修改响应式原始数据导致无限渲染循环 const flatMap = {}; const allTasks = []; - const walk = (tasks) => { if (!tasks) return; tasks.forEach(t => { allTasks.push(t); flatMap[t.id] = t; walk(t.child_tasks); }); }; + const walk = (tasks) => { if (!tasks) return; tasks.forEach(t => { const copy = { ...t }; allTasks.push(copy); flatMap[copy.id] = copy; walk(t.child_tasks); }); }; walk(this.product.task_tree); + // 重建父子引用(child_tasks 指向副本) + allTasks.forEach(t => { + if (t.child_tasks && t.child_tasks.length) { + t.child_tasks = t.child_tasks.map(c => flatMap[c.id]).filter(Boolean); + } + if (t.parent_task_id) { + t.parent = flatMap[t.parent_task_id] || null; + } + }); + // 分支编号 const branchLabelMap = {}; const traverse = (tasks, prefix) => { @@ -101,7 +111,9 @@ export default { else { si++; const n = prefix ? prefix + '.' + si : '' + si; branchLabelMap[t.id] = '分支 ' + n; traverse(t.child_tasks, n); } }); }; - this.product.task_tree.forEach(t => { branchLabelMap[t.id] = '主分支'; traverse(t.child_tasks, ''); }); + // 遍历顶层时使用副本引用 + const rootCopies = this.product.task_tree.map(t => flatMap[t.id]).filter(Boolean); + rootCopies.forEach(t => { branchLabelMap[t.id] = '主分支'; traverse(t.child_tasks, ''); }); // 主管分类 const mains = []; const subs = []; const subByParent = {}; @@ -117,25 +129,27 @@ export default { const nodes = []; const childSeen = new Set(); mains.forEach((m, mi) => { - m.x = CENTER_X; m.y = mi * (CARD_H + GAP_Y) * 2; - m._isMain = true; m._blabel = '主分支'; - nodes.push(m); + const node = { ...m, x: CENTER_X, y: mi * (CARD_H + GAP_Y) * 2, _isMain: true, _blabel: '主分支' }; + nodes.push(node); + // 更新 flatMap 引用以便 lines/arrows 计算使用正确的坐标 + flatMap[node.id] = node; const children = subByParent[m.id] || []; children.sort((a, b) => new Date(a.created_at) - new Date(b.created_at)); children.forEach((c, ci) => { const isLeft = ci % 2 === 0; const dist = Math.floor(ci / 2) + 1; - c.y = m.y; - c.x = isLeft ? CENTER_X - dist * (CARD_W + GAP_X) : CENTER_X + dist * (CARD_W + GAP_X); - c._isMain = false; c._blabel = branchLabelMap[c.id] || '分支'; - nodes.push(c); + const childNode = { ...c, y: node.y, + x: isLeft ? CENTER_X - dist * (CARD_W + GAP_X) : CENTER_X + dist * (CARD_W + GAP_X), + _isMain: false, _blabel: branchLabelMap[c.id] || '分支' }; + nodes.push(childNode); + flatMap[childNode.id] = childNode; childSeen.add(c.id); }); }); // 遗留分支(父任务不在 mains 中的) - subs.forEach(s => { if (!childSeen.has(s.id)) { s.x = 200; s.y = nodes.length ? nodes[nodes.length - 1].y + CARD_H + GAP_Y : 0; s._isMain = false; s._blabel = '分支'; nodes.push(s); } }); + subs.forEach(s => { if (!childSeen.has(s.id)) { const legacyNode = { ...s, x: 200, y: nodes.length ? nodes[nodes.length - 1].y + CARD_H + GAP_Y : 0, _isMain: false, _blabel: '分支' }; nodes.push(legacyNode); flatMap[legacyNode.id] = legacyNode; } }); // 加上全局偏移 const padding = 40;