feat(mobile): 登录修复 + 人名字典 + 留言板FAB + 流转算法重构
- login: catch补齐toast,修复密码错误静默失败 - detail: formatUserName渲染当前位置,dictVersion驱动响应式重绘 - detail: 留言板改为悬浮按钮+底部抽屉,未读徽标逻辑 - TaskSwipeCards: lanes终极递归算法,SPAWN不限层级开辟新Tab,先序push修复Tab顺序 - TreeCanvas: 父子相对坐标延伸算法,替代旧版奇偶距离递增
This commit is contained in:
@ -74,7 +74,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
// ============================================================
|
||||
// 🚀 十字星并发拓扑算法
|
||||
// 🚀 父子相对坐标延伸算法
|
||||
// ============================================================
|
||||
treeNodes() {
|
||||
if (!this.product || !this.product.task_tree) return [];
|
||||
@ -93,16 +93,6 @@ export default {
|
||||
|
||||
// 🚀 主干判定
|
||||
const isMainFn = (t) => !t.parent_task_id || t.task_type === 'TRANSFER' || t.task_type === 'RECOVERY';
|
||||
// 🚀 寻根算法
|
||||
const getRootMainId = (t) => {
|
||||
let curr = t;
|
||||
while (curr.parent_task_id && flatMap[curr.parent_task_id]) {
|
||||
const p = flatMap[curr.parent_task_id];
|
||||
if (isMainFn(p)) return p.id;
|
||||
curr = p;
|
||||
}
|
||||
return curr.parent_task_id || curr.id;
|
||||
};
|
||||
|
||||
// 分支编号
|
||||
const branchLabelMap = {};
|
||||
@ -117,16 +107,14 @@ export default {
|
||||
const rootCopies = this.product.task_tree.map(t => flatMap[t.id]).filter(Boolean);
|
||||
rootCopies.forEach(t => { branchLabelMap[t.id] = '主分支'; traverse(t.child_tasks, ''); });
|
||||
|
||||
// 🚀 真实父子分组(按直接 parent_task_id)
|
||||
const subByParent = {};
|
||||
// 🚀 构建真实的全局 childMap(按 parent_task_id)
|
||||
const childMap = {};
|
||||
allTasks.forEach(t => {
|
||||
if (t.parent_task_id) {
|
||||
if (!subByParent[t.parent_task_id]) subByParent[t.parent_task_id] = [];
|
||||
subByParent[t.parent_task_id].push(t);
|
||||
}
|
||||
const pid = t.parent_task_id || '';
|
||||
if (!childMap[pid]) childMap[pid] = [];
|
||||
childMap[pid].push(t);
|
||||
});
|
||||
// 每个 parent 下按时间排序
|
||||
Object.values(subByParent).forEach(arr => arr.sort((a, b) => new Date(a.created_at) - new Date(b.created_at)));
|
||||
Object.values(childMap).forEach(arr => arr.sort((a, b) => new Date(a.created_at) - new Date(b.created_at)));
|
||||
|
||||
// 主管分类
|
||||
const mains = [];
|
||||
@ -135,67 +123,46 @@ export default {
|
||||
|
||||
const nodes = [];
|
||||
|
||||
// 🚀 递归挂载:从父节点向外延伸 X,Y 保持与根主干同高
|
||||
const placeChildren = (parentNode, rootY) => {
|
||||
const children = subByParent[parentNode.id] || [];
|
||||
if (!children.length) return;
|
||||
|
||||
// 父节点是主干 → 交替左右;父节点已有侧向 → 继承方向
|
||||
const parentIsMain = parentNode._isMain;
|
||||
let leftCount = 0, rightCount = 0;
|
||||
|
||||
children.forEach((c) => {
|
||||
let side, childX;
|
||||
if (parentIsMain) {
|
||||
// 主干直系子节点:交替左右,距离递增
|
||||
side = leftCount <= rightCount ? 'left' : 'right';
|
||||
if (side === 'left') leftCount++; else rightCount++;
|
||||
const dist = side === 'left' ? leftCount : rightCount;
|
||||
childX = side === 'left'
|
||||
? CENTER_X - dist * (CARD_W + GAP_X)
|
||||
: CENTER_X + dist * (CARD_W + GAP_X);
|
||||
} else {
|
||||
// 非主干子节点:继承父节点方向,继续向外延伸
|
||||
side = parentNode._side;
|
||||
childX = side === 'left'
|
||||
? parentNode.x - (CARD_W + GAP_X)
|
||||
: parentNode.x + (CARD_W + GAP_X);
|
||||
// 🚀 全新递归放置算法(相对坐标系)
|
||||
const placeChildren = (parentNode, currentSide) => {
|
||||
const children = childMap[parentNode.id] || [];
|
||||
children.forEach((child, index) => {
|
||||
// 主干的第一层协助分支:均衡分发左右
|
||||
let side = currentSide;
|
||||
if (parentNode._isMain) {
|
||||
side = index % 2 === 0 ? 'right' : 'left';
|
||||
}
|
||||
|
||||
// 🚀 核心:永远基于真实父亲 (parentNode) 的坐标向外延伸!
|
||||
const childX = side === 'right'
|
||||
? parentNode.x + CARD_W + GAP_X
|
||||
: parentNode.x - CARD_W - GAP_X;
|
||||
|
||||
// 同级多子节点略微错开 Y 轴防重叠
|
||||
const childY = parentNode.y + (index * (CARD_H + 20));
|
||||
|
||||
const childNode = {
|
||||
...c,
|
||||
x: childX, y: rootY,
|
||||
...child,
|
||||
x: childX, y: childY,
|
||||
_isMain: false, _side: side,
|
||||
_blabel: branchLabelMap[c.id] || '分支',
|
||||
_rootMainY: rootY,
|
||||
_rootMainId: parentIsMain ? parentNode.id : parentNode._rootMainId,
|
||||
_blabel: branchLabelMap[child.id] || '分支',
|
||||
_rootMainId: parentNode._isMain ? parentNode.id : parentNode._rootMainId,
|
||||
};
|
||||
nodes.push(childNode);
|
||||
flatMap[childNode.id] = childNode;
|
||||
|
||||
// 递归挂载孙子节点
|
||||
placeChildren(childNode, rootY);
|
||||
// 带着当前的方向(side)继续递归,保证孙子永远顺着儿子的方向长
|
||||
placeChildren(childNode, side);
|
||||
});
|
||||
};
|
||||
|
||||
// 遍历主干
|
||||
// 🚀 渲染入口
|
||||
mains.forEach((m, mi) => {
|
||||
const rootY = mi * (CARD_H + GAP_Y) * 2;
|
||||
const mainNode = { ...m, x: CENTER_X, y: rootY, _isMain: true, _blabel: '主分支', _rootMainId: m.id };
|
||||
nodes.push(mainNode);
|
||||
flatMap[mainNode.id] = mainNode;
|
||||
|
||||
placeChildren(mainNode, rootY);
|
||||
});
|
||||
|
||||
// 遗留分支(父任务不在任何已渲染节点中)
|
||||
const seenIds = new Set(nodes.map(n => n.id));
|
||||
allTasks.forEach(t => {
|
||||
if (!seenIds.has(t.id) && !isMainFn(t)) {
|
||||
const legacyNode = { ...t, x: CENTER_X + CARD_W + GAP_X, y: nodes.length ? nodes[nodes.length - 1].y + CARD_H + GAP_Y : 0, _isMain: false, _blabel: '分支', _rootMainId: '' };
|
||||
nodes.push(legacyNode);
|
||||
flatMap[legacyNode.id] = legacyNode;
|
||||
}
|
||||
placeChildren(mainNode, null);
|
||||
});
|
||||
|
||||
// 全局偏移
|
||||
|
||||
Reference in New Issue
Block a user