fix(frontend): 流转蓝图 + 聊天室 + 卡片布局 + 打印功能修复

1. TreeCanvas.vue - Y轴坐标算法重构:
   - calcSubtreeHeight 改为兄弟累加 (reduce) 替代 Math.max,彻底杜绝垂直重叠
   - childNode.y = parentNode.y + CARD_H + VERTICAL_GAP 严格向下延伸
   - 同级子节点按子树高度累加 startY,各占独立 Y 区域
   - 加宽画布默认尺寸 (400→800, padding +300/+400)

2. detail.vue - onShow 生命周期修复:
   - 新增 onShow() 钩子调用 fetchMessages(),解决聊天室退回不刷新问题

3. TaskSwipeCards.vue + detail.vue - CSS 防跳动:
   - .ss-swiper-h / .ss-card-wrapper 添加 min-height: 220px
   - .card 添加 min-height: 120px

4. detail.vue - 移动端打印标签:
   - 新增「🖨️ 打印标签」按钮
   - 双方案 ActionSheet: 网络打印机(后端API) / 蓝牙打印机(ESC/POS)
   - 对接现有 /print/execute 端点
This commit is contained in:
2026-08-11 15:05:39 +08:00
parent 88dc7381f6
commit 30a48d90fc
3 changed files with 72 additions and 17 deletions

View File

@ -74,11 +74,11 @@ export default {
},
computed: {
// ============================================================
// 🚀 父子相对坐标延伸算法
// 🚀 父子相对坐标延伸算法v3 — 无重叠 Y 轴栈式布局)
// ============================================================
treeNodes() {
if (!this.product || !this.product.task_tree) return [];
const CARD_W = 160, CARD_H = 80, GAP_X = 24, GAP_Y = 24;
const CARD_W = 160, CARD_H = 80, GAP_X = 24, VERTICAL_GAP = 28, MAIN_GAP = 40;
const CENTER_X = 0;
// 拍平 + 浅拷贝
@ -121,25 +121,39 @@ export default {
allTasks.forEach(t => { if (isMainFn(t)) mains.push(t); });
mains.sort((a, b) => new Date(a.created_at) - new Date(b.created_at));
// 🚀 预计算每个节点的子树总高度(兄弟累加,用于防重叠占位)
const calcSubtreeHeight = (nodeId) => {
const children = childMap[nodeId] || [];
if (children.length === 0) return CARD_H + VERTICAL_GAP;
const totalChildrenHeight = children.reduce((sum, c) => sum + calcSubtreeHeight(c.id), 0);
return CARD_H + VERTICAL_GAP + totalChildrenHeight;
};
const nodes = [];
// 🚀 全新递归放置算法(相对坐标系)
// 🚀 递归放置算法childNode.y = parentNode.y + CARD_H + VERTICAL_GAP
// 同级子节点按 index 错开 Y 轴,各占其子树高度防止重叠
const placeChildren = (parentNode, currentSide) => {
const children = childMap[parentNode.id] || [];
// 🚀 首个子节点 Y 起始parent.y 正下方
let startY = parentNode.y + CARD_H + VERTICAL_GAP;
children.forEach((child, index) => {
const subtreeH = calcSubtreeHeight(child.id);
// 主干的第一层协助分支:均衡分发左右
let side = currentSide;
if (parentNode._isMain) {
side = index % 2 === 0 ? 'right' : 'left';
}
// 🚀 核心:永远基于真实父亲 (parentNode) 的坐标向外延伸
// 🚀 X 轴:永远基于真实父亲向外延伸
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));
// 🚀 Y 轴:严格基于父节点向下延伸,同级按 index 错开
const childY = startY;
const childNode = {
...child,
@ -151,24 +165,30 @@ export default {
nodes.push(childNode);
flatMap[childNode.id] = childNode;
// 带着当前的方向(side)继续递归,保证孙子永远顺着儿子的方向
// 递归放置孙子节点(沿相同方向
placeChildren(childNode, side);
// 🚀 下一个兄弟节点跳到当前子树高度之后,严禁 Y 坐标重叠
startY += subtreeH + VERTICAL_GAP;
});
};
// 🚀 渲染入口
// 🚀 渲染入口:主干节点也按子树深度排布
let mainStartY = 0;
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 };
const subtreeH = calcSubtreeHeight(m.id);
const mainNode = { ...m, x: CENTER_X, y: mainStartY, _isMain: true, _blabel: '主分支', _rootMainId: m.id };
nodes.push(mainNode);
flatMap[mainNode.id] = mainNode;
placeChildren(mainNode, null);
mainStartY += subtreeH + MAIN_GAP;
});
// 全局偏移
// 全局偏移(给左侧分支预留空间)
const minX = Math.min(...nodes.map(n => n.x));
const minY = Math.min(...nodes.map(n => n.y));
const padding = 40;
nodes.forEach(n => { n.x += Math.abs(minX) + CARD_W * 2 + padding; n.y += padding; });
nodes.forEach(n => { n.x += Math.abs(minX) + CARD_W * 2 + padding; n.y += padding - Math.min(0, minY); });
return nodes;
},
// ============================================================
@ -215,8 +235,8 @@ export default {
return result;
},
// ============================================================
canvasWidth() { const ns = this.treeNodes; if (!ns.length) return 400; return Math.max(...ns.map(n => n.x)) + 200; },
canvasHeight() { const ns = this.treeNodes; if (!ns.length) return 400; return Math.max(...ns.map(n => n.y)) + 200; },
canvasWidth() { const ns = this.treeNodes; if (!ns.length) return 800; return Math.max(...ns.map(n => n.x)) + 400; },
canvasHeight() { const ns = this.treeNodes; if (!ns.length) return 800; return Math.max(...ns.map(n => n.y)) + 300; },
},
methods: {
formatUserName,