并发协作重构: focusTasks全视野+WBS分支编码+TreeCanvas并发同层布局(串行Y+1/并行同Y)

This commit is contained in:
2026-08-06 10:21:15 +08:00
parent ff3de80c96
commit 11f9f64dc6
2 changed files with 73 additions and 65 deletions

View File

@ -60,79 +60,68 @@ export default {
treeNodes() {
if (!this.product || !this.product.task_tree) return [];
// 1. 拍平所有任务(无视后端的错误嵌套)
const allTasks = [];
const flatten = (tasks) => {
if (!tasks) return;
for (const t of tasks) { allTasks.push(t); flatten(t.child_tasks); }
};
flatten(this.product.task_tree);
// 2. 绝对时间轴排序(强制自上而下流转)
allTasks.sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime());
// 3. 智能分层(判定谁是同行裂变,谁是上下游)
const levels = [];
let currentLevel = [];
let lastTime = null;
allTasks.forEach(t => {
const tTime = new Date(t.created_at).getTime();
if (lastTime === null) {
currentLevel.push(t); lastTime = tTime;
} else {
if (Math.abs(tTime - lastTime) < 2000 ||
(t.parent_task_id && currentLevel.some(c => c.parent_task_id === t.parent_task_id))) {
currentLevel.push(t);
} else {
levels.push(currentLevel);
currentLevel = [t]; lastTime = tTime;
}
}
});
if (currentLevel.length > 0) levels.push(currentLevel);
// 4. 计算 X, Y 坐标
const CARD_W = 320, CARD_H = 460, GAP_X = 80, GAP_Y = 120;
const nodes = [];
nodes._lines = [];
let maxCards = Math.max(...levels.map(l => l.length));
const CENTER_X = (maxCards * CARD_W + (maxCards - 1) * GAP_X) / 2 + 60;
// 按行组织:每行是一个 { y, tasks: [...] }
const rows = [];
levels.forEach((lvlTasks, depth) => {
const y = 40 + depth * (CARD_H + GAP_Y);
const lvlWidth = lvlTasks.length * CARD_W + (lvlTasks.length - 1) * GAP_X;
const startX = CENTER_X - lvlWidth / 2;
lvlTasks.forEach((t, i) => {
nodes.push({ ...t, x: startX + i * (CARD_W + GAP_X), y, _depth: depth });
const layout = (tasks, parentRowIdx, parentX) => {
if (!tasks || !tasks.length) return;
tasks.forEach(t => {
// 判定:parent COMPLETED → 串行 Y+1;parent WIP/PENDING → 并行同Y偏右
const isSerial = !t.parent_task_id || (this.taskStatusMap[t.parent_task_id] === 'COMPLETED');
const rowIdx = isSerial ? (parentRowIdx >= 0 ? parentRowIdx + 1 : rows.length) : parentRowIdx;
while (rows.length <= rowIdx) rows.push({ y: 0, tasks: [] });
const row = rows[rowIdx];
const x = row.tasks.length === 0 ? 40 : row.tasks[row.tasks.length - 1]._xEnd + GAP_X;
const node = { ...t, x, y: 0, _rowIdx: rowIdx, _xEnd: x + CARD_W };
row.tasks.push(node);
nodes.push(node);
// 连线到父节点
if (t.parent_task_id) {
const parent = nodes.find(n => n.id === t.parent_task_id);
if (parent) {
nodes._lines.push({
key: parent.id + '-' + t.id,
x1: parent.x + CARD_W / 2, y1: parent.y + CARD_H,
x2: node.x + CARD_W / 2, y2: node.y,
});
}
}
});
// 递归子任务
tasks.forEach(t => {
if (t.child_tasks && t.child_tasks.length) {
const parentNode = nodes.find(n => n.id === t.id);
const parentRow = parentNode ? parentNode._rowIdx : rows.length - 1;
layout(t.child_tasks, parentRow, parentNode ? parentNode.x : 40);
}
});
};
layout(this.product.task_tree, -1, 40);
// 计算 Y 坐标
let curY = 20;
rows.forEach(row => {
row.y = curY;
row.tasks.forEach(n => { n.y = curY; });
curY += CARD_H + GAP_Y;
});
// 5. 智能连线生成
for (let d = 1; d < levels.length; d++) {
const curLvl = levels[d];
const prevLvl = levels[d - 1];
curLvl.forEach(curr => {
const currNode = nodes.find(n => n.id === curr.id);
let parents = [];
if (curr.parent_task_id) {
parents = nodes.filter(n => n.id === curr.parent_task_id);
}
if (parents.length === 0) {
parents = prevLvl.map(p => nodes.find(n => n.id === p.id));
}
parents.forEach(p => {
nodes._lines.push({
key: p.id + '-' + curr.id,
x1: p.x + CARD_W / 2, y1: p.y + CARD_H,
x2: currNode.x + CARD_W / 2, y2: currNode.y
});
});
});
}
return nodes;
},
taskStatusMap() {
const map = {};
const walk = (tasks) => { if (!tasks) return; tasks.forEach(t => { map[t.id] = t.status; walk(t.child_tasks); }); };
if (this.product) walk(this.product.task_tree);
return map;
},
treeLines() { return this.treeNodes._lines || []; },
canvasWidth() { if (!this.treeNodes.length) return 400; return Math.max(800, Math.max(...this.treeNodes.map(n => n.x)) + 300); },
canvasHeight() { if (!this.treeNodes.length) return 400; return Math.max(800, Math.max(...this.treeNodes.map(n => n.y)) + 300); }