feat(diagram): 前端竖排镜像——edgePath/标签锚点方向自适应(发布顺序无关)、名单 2 行 tspan+等N人、补 instance 图例、auto-fit 与 375px 适配
build / build (push) Successful in 3m30s
build / build (push) Successful in 3m30s
This commit is contained in:
+47
-39
@@ -1,23 +1,24 @@
|
||||
/**
|
||||
* 流程图自动布局(从 FlowDiagram.vue 提取的设计器/查看器共用实现):
|
||||
* Kahn 拓扑分层 depth(to)=max(depth(from))+1,从左到右布列、同列按定义顺序纵排。
|
||||
* 设计器(contentJson 无 layout 坐标时算初始坐标)与查看器(无坐标回退)共用同一口径。
|
||||
* 流程图自动布局(查看器在后端未下发 positions 时的灰度兜底):Kahn 拓扑分层
|
||||
* depth(to)=max(depth(from))+1,自上而下布层、同层按定义顺序横排、整层块对竖直中线水平居中。
|
||||
* 与后端 DiagramLayoutService 同规则同常量(2026-10-09 竖排改造镜像);设计器已是钉钉式
|
||||
* 竖向卡片流,不再消费本模块。
|
||||
*/
|
||||
|
||||
export const SLOT_W = 200; // 节点槽位宽
|
||||
export const SLOT_H = 88; // 节点槽位高
|
||||
export const COL_GAP = 72; // 列间距(留给正交折线走线)
|
||||
export const ROW_GAP = 24; // 行间距
|
||||
export const SLOT_W = 280; // 分支槽宽(盒型在槽内水平居中;与后端一致)
|
||||
export const SLOT_H = 100; // 层槽高(盒型在槽内垂直居中;与后端一致)
|
||||
export const GAP_X = 44; // 分支列间距(分支间无需走线空间)
|
||||
export const GAP_Y = 56; // 层间距(留给竖直走线 + 多出口文案)
|
||||
export const PAD = 24;
|
||||
|
||||
export const NODE_BOX: Record<string, { w: number; h: number }> = {
|
||||
START: { w: 104, h: 40 },
|
||||
END: { w: 104, h: 40 },
|
||||
APPROVAL: { w: 184, h: 64 },
|
||||
NOTIFY: { w: 184, h: 64 },
|
||||
PARALLEL_SPLIT: { w: 128, h: 72 },
|
||||
PARALLEL_JOIN: { w: 128, h: 72 },
|
||||
CONDITION: { w: 128, h: 72 },
|
||||
START: { w: 116, h: 44 },
|
||||
END: { w: 116, h: 44 },
|
||||
APPROVAL: { w: 264, h: 76 },
|
||||
NOTIFY: { w: 264, h: 76 },
|
||||
PARALLEL_SPLIT: { w: 132, h: 78 },
|
||||
PARALLEL_JOIN: { w: 132, h: 78 },
|
||||
CONDITION: { w: 132, h: 78 },
|
||||
};
|
||||
|
||||
export interface Pos {
|
||||
@@ -36,7 +37,7 @@ export function layout(data: any): Record<string, Pos> {
|
||||
return pos;
|
||||
}
|
||||
|
||||
/** Kahn 分层 + 同列按定义顺序纵排(基础网格,保证无重叠) */
|
||||
/** Kahn 分层 + 同层按定义顺序横排(基础网格,保证无重叠) */
|
||||
function layered(data: any): Record<string, Pos> {
|
||||
const nodes: any[] = data?.nodes || [];
|
||||
const edges: any[] = data?.edges || [];
|
||||
@@ -56,31 +57,38 @@ function layered(data: any): Record<string, Pos> {
|
||||
if (--inDeg[e.to] === 0) order.push(e.to);
|
||||
});
|
||||
}
|
||||
const rowsIn: Record<number, number> = {};
|
||||
const rowsIn: Record<number, any[]> = {};
|
||||
const pos: Record<string, Pos> = {};
|
||||
nodes.forEach((n) => {
|
||||
const d = depth[n.id] ?? 0;
|
||||
depth[n.id] = d;
|
||||
const row = rowsIn[d] ?? 0;
|
||||
rowsIn[d] = row + 1;
|
||||
const row = (rowsIn[d] = rowsIn[d] || []).length;
|
||||
rowsIn[d].push(n);
|
||||
const box = NODE_BOX[n.type] || NODE_BOX.APPROVAL;
|
||||
pos[n.id] = {
|
||||
x: PAD + d * (SLOT_W + COL_GAP) + (SLOT_W - box.w) / 2,
|
||||
y: PAD + row * (SLOT_H + ROW_GAP) + (SLOT_H - box.h) / 2,
|
||||
x: PAD + row * (SLOT_W + GAP_X) + (SLOT_W - box.w) / 2,
|
||||
y: PAD + d * (SLOT_H + GAP_Y) + (SLOT_H - box.h) / 2,
|
||||
w: box.w,
|
||||
h: box.h,
|
||||
depth: d,
|
||||
row,
|
||||
};
|
||||
});
|
||||
// 整层块对竖直中线水平居中(镜像后端:按实际占宽算块宽,不含尾随 GAP_X)
|
||||
const maxCols = Math.max(...Object.values(rowsIn).map((a) => a.length));
|
||||
const axisW = (maxCols - 1) * (SLOT_W + GAP_X) + SLOT_W;
|
||||
Object.values(rowsIn).forEach((arr) => {
|
||||
const blockW = (arr.length - 1) * (SLOT_W + GAP_X) + SLOT_W;
|
||||
const dx = (axisW - blockW) / 2;
|
||||
arr.forEach((n) => { pos[n.id].x += dx; });
|
||||
});
|
||||
return pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* 并行分支组垂直居中:分支不再从 SPLIT 行向下堆叠,而是以 SPLIT 中心线为轴上下对称展开
|
||||
* (与钉钉/参考稿一致)。SPLIT↔JOIN 配对沿用编辑器口径(各出边可达集求交,取 BFS 序最早的
|
||||
* PARALLEL_JOIN),组员 = SPLIT 到 JOIN 之间可达的全部节点;JOIN 中线吸附回 SPLIT 中线。
|
||||
* 内层组先于外层组处理(嵌套并行整体平移不破坏内层居中);结束统一把最顶行抬回 PAD。
|
||||
* 并行分支组水平居中:分支不再从 SPLIT 行向右堆叠,而是以 SPLIT 竖直中线为轴左右对称展开
|
||||
* (2026-10-09 竖排镜像)。SPLIT↔JOIN 配对沿用编辑器口径(各出边可达集求交,取 BFS 序最早的
|
||||
* PARALLEL_JOIN),组员 = SPLIT 到 JOIN 之间可达的全部节点;JOIN 水平中点吸附回 SPLIT 中线。
|
||||
* 内层组先于外层组处理(嵌套并行整体平移不破坏内层居中);结束统一把最左列推回 PAD。
|
||||
*/
|
||||
function centerParallelGroups(data: any, pos: Record<string, Pos>): void {
|
||||
const nodes: any[] = data?.nodes || [];
|
||||
@@ -144,23 +152,23 @@ function centerParallelGroups(data: any, pos: Record<string, Pos>): void {
|
||||
- Math.max(...a.members.map((m: string) => pos[m].depth)));
|
||||
|
||||
groups.forEach((g: any) => {
|
||||
const axisY = pos[g.split].y + pos[g.split].h / 2;
|
||||
let top = Infinity;
|
||||
let bottom = -Infinity;
|
||||
const axisX = pos[g.split].x + pos[g.split].w / 2;
|
||||
let left = Infinity;
|
||||
let right = -Infinity;
|
||||
g.members.forEach((id: string) => {
|
||||
top = Math.min(top, pos[id].y);
|
||||
bottom = Math.max(bottom, pos[id].y + pos[id].h);
|
||||
left = Math.min(left, pos[id].x);
|
||||
right = Math.max(right, pos[id].x + pos[id].w);
|
||||
});
|
||||
const delta = axisY - (top + bottom) / 2;
|
||||
g.members.forEach((id: string) => { pos[id].y += delta; });
|
||||
pos[g.join].y += axisY - (pos[g.join].y + pos[g.join].h / 2);
|
||||
const delta = axisX - (left + right) / 2;
|
||||
g.members.forEach((id: string) => { pos[id].x += delta; });
|
||||
pos[g.join].x += axisX - (pos[g.join].x + pos[g.join].w / 2);
|
||||
});
|
||||
|
||||
// 上移出界的组把整图平移回 PAD(同 delta 平移不破坏各组居中)
|
||||
let minY = Infinity;
|
||||
Object.values(pos).forEach((p) => { minY = Math.min(minY, p.y); });
|
||||
if (minY < PAD) {
|
||||
const lift = PAD - minY;
|
||||
Object.values(pos).forEach((p) => { p.y += lift; });
|
||||
// 左移出界的组把整图平移回 PAD(同 delta 平移不破坏各组居中)
|
||||
let minX = Infinity;
|
||||
Object.values(pos).forEach((p) => { minX = Math.min(minX, p.x); });
|
||||
if (minX < PAD) {
|
||||
const lift = PAD - minX;
|
||||
Object.values(pos).forEach((p) => { p.x += lift; });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user