feat(ui): 应用页规范全量重构 + 工作台与流程轨迹优化
build / build (push) Successful in 3m17s

应用页规范(bsch)重构:
- 6 个列表页迁规范骨架: page-wrapper/tabs-wrapper + search-wrap 搜索区(关键字 200px 即输即搜, 去查询按钮) + link-table table-bottom-box 定高表格 + page-panel 分页栏
- 分页列表统一接 TablePageAlovaNew(页长 [10,20,30,40]/customPageCount 记忆/增删智能翻页); dataApi 须返回 Method, 调 .send() 会导致表格静默空数据
- 操作列统一图标式 operatingHandle(Tooltip+fixed right 居中); 各列表补序号列; 危险操作统一 MyConfirm 二次确认
- 弹窗表单必填红星 + 统一提示「请填写红色星号内容!」
- TablePageAlovaNew 补页长记忆与重载后清空勾选; 新增 src/types OperatorEnum

工作台与流程轨迹优化:
- 已办列表去重: 每办件一行, doneCount>1 显示「共办理 N 次」标签
- 新增 TraceTimeline 轨迹时间流(时间升序/kind/轮次, 按人员/科室切换写回 URL), 接入详情/办理页
- 新增流程源码只读抽屉 SourceDrawer, 接入定义列表与图示页
- 办理人映射/流程定义新增「启用」回路(/admin/map/enable, /admin/def/enable)
- 图示视图口径统一 dept/user 两档, 缺省人员视图; 状态列 Tag 色映射统一导出
This commit is contained in:
chenxing
2026-09-18 16:52:40 +08:00
parent 0083246039
commit cdf04f0930
16 changed files with 1172 additions and 481 deletions
+96
View File
@@ -29,6 +29,13 @@ export interface Pos {
/** 拓扑分层布局:入参 {nodes,edges},返回 nodeId → 位置 */
export function layout(data: any): Record<string, Pos> {
const pos = layered(data);
centerParallelGroups(data, pos);
return pos;
}
/** Kahn 分层 + 同列按定义顺序纵排(基础网格,保证无重叠) */
function layered(data: any): Record<string, Pos> {
const nodes: any[] = data?.nodes || [];
const edges: any[] = data?.edges || [];
if (!nodes.length) return {};
@@ -66,3 +73,92 @@ export function layout(data: any): Record<string, Pos> {
});
return pos;
}
/**
* 并行分支组垂直居中:分支不再从 SPLIT 行向下堆叠,而是以 SPLIT 中心线为轴上下对称展开
* (与钉钉/参考稿一致)。SPLIT↔JOIN 配对沿用编辑器口径(各出边可达集求交,取 BFS 序最早的
* PARALLEL_JOIN),组员 = SPLIT 到 JOIN 之间可达的全部节点;JOIN 中线吸附回 SPLIT 中线。
* 内层组先于外层组处理(嵌套并行整体平移不破坏内层居中);结束统一把最顶行抬回 PAD。
*/
function centerParallelGroups(data: any, pos: Record<string, Pos>): void {
const nodes: any[] = data?.nodes || [];
const edges: any[] = data?.edges || [];
const out: Record<string, string[]> = {};
edges.forEach((e) => {
if (pos[e.from] && pos[e.to]) (out[e.from] = out[e.from] || []).push(e.to);
});
const byId: Record<string, any> = {};
nodes.forEach((n) => { byId[n.id] = n; });
const groups = nodes
.filter((n) => n.type === "PARALLEL_SPLIT" && pos[n.id])
.map((split) => {
// BFS 序 + 各分支可达集,用于定位配对 JOIN
const order = new Map<string, number>([[split.id, 0]]);
const queue: string[] = [split.id];
while (queue.length) {
const cur = queue.shift()!;
(out[cur] || []).forEach((to) => {
if (!order.has(to)) {
order.set(to, order.size);
queue.push(to);
}
});
}
const reachSets = (out[split.id] || []).map((head) => {
const set = new Set<string>();
const q: string[] = [head];
while (q.length) {
const cur = q.shift()!;
if (set.has(cur)) continue;
set.add(cur);
(out[cur] || []).forEach((to) => q.push(to));
}
return set;
});
let common = reachSets[0] || new Set<string>();
reachSets.forEach((set) => { common = new Set([...common].filter((x) => set.has(x))); });
const join = nodes
.filter((n) => n.type === "PARALLEL_JOIN" && pos[n.id] && common.has(n.id))
.sort((a, b) => (order.get(a.id) ?? 0) - (order.get(b.id) ?? 0))[0];
if (!join) return null;
// 组员:SPLIT 起步、不穿过 JOIN 的可达集(含嵌套 SPLIT/JOIN)
const members: string[] = [];
const seen = new Set<string>([join.id]);
const q: string[] = [split.id];
while (q.length) {
const cur = q.shift()!;
(out[cur] || []).forEach((to) => {
if (seen.has(to)) return;
seen.add(to);
members.push(to);
q.push(to);
});
}
return { split: split.id, join: join.id, members };
})
.filter(Boolean)
.sort((a: any, b: any) => Math.max(...b.members.map((m: string) => pos[m].depth))
- 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;
g.members.forEach((id: string) => {
top = Math.min(top, pos[id].y);
bottom = Math.max(bottom, pos[id].y + pos[id].h);
});
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);
});
// 上移出界的组把整图平移回 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; });
}
}