From 2246b264387b1e1b5211088169a3eaa2aa545670 Mon Sep 17 00:00:00 2001 From: 18628386721 <18628386721@163.com> Date: Tue, 28 Jul 2026 19:44:19 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=89=E9=9D=A2=E6=8B=86=E5=88=86=E2=91=AF:?= =?UTF-8?q?=E6=89=8B=E7=BB=98=E5=9B=BE=E6=B0=B4=E5=B9=B3=E5=B1=85=E4=B8=AD?= =?UTF-8?q?=20=E2=80=94=E2=80=94=20=E7=94=BB=E5=B9=85=E4=B8=8B=E9=99=90?= =?UTF-8?q?=E7=9A=84=E4=BD=99=E7=99=BD=E5=AF=B9=E7=A7=B0=E5=88=86=E5=B8=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 症状(用户报):图保存以后不居中。根因:手绘布局给画幅垫了 1000 的宽度下限, 内容却总从左缘 PAD=20 起 —— 画得比 1000 窄时余白全堆右边,图左贴; 保存时序列化归一化(整体平移到非负)后左贴更明显。 修法:layoutManualFlow 定完画幅宽度后把内容平移到正中,两侧余白对称 (纯呈现层平移,存储坐标不动);纵向保持收顶不居中。 另:用户测试保存产生的 v5 与 v4 几何完全一致,且带着上一轮已拔除的 自动重排 bug 的 id 旋转(保存时浏览器还跑着旧代码)—— 按「图只留 v4」删除。 五闸退出码全过。 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m --- apps/web/src/shared/gov/flow/flow-layout.ts | 24 ++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/apps/web/src/shared/gov/flow/flow-layout.ts b/apps/web/src/shared/gov/flow/flow-layout.ts index f37aa47..9dda365 100644 --- a/apps/web/src/shared/gov/flow/flow-layout.ts +++ b/apps/web/src/shared/gov/flow/flow-layout.ts @@ -428,11 +428,33 @@ function layoutManualFlow(graph: FlowGraph): FlowLayoutResult { maxY = Math.max(maxY, caption.y + 4); } + /* + * 水平居中:画幅有 1000 的下限,内容却总从左缘 PAD 起 —— 画得比 1000 窄时 + * 余白全堆在右边,图看着左贴(保存后经归一化尤其明显)。 + * 定完画幅宽度后把内容平移到正中,两侧余白对称;纵向保持收顶,不居中。 + */ + let cMinX = Infinity; + for (const shape of shapes) cMinX = Math.min(cMinX, shape.x); + for (const connection of connections) for (const p of connection.waypoints) cMinX = Math.min(cMinX, p.x); + for (const caption of captions) cMinX = Math.min(cMinX, caption.x - estTextWidth(caption.text, 10) / 2); + if (!Number.isFinite(cMinX)) cMinX = 0; + const contentW = maxX - cMinX; + const width = Math.max(1000, contentW + 40); + const dxCenter = (width - contentW) / 2 - cMinX; + if (dxCenter !== 0) { + for (const shape of shapes) shape.x += dxCenter; + for (const caption of captions) caption.x += dxCenter; + for (const connection of connections) { + connection.waypoints = connection.waypoints.map((p) => ({ x: p.x + dxCenter, y: p.y })); + if (connection.label) connection.label = { ...connection.label, x: connection.label.x + dxCenter }; + } + } + return { shapes, captions, connections, - width: Math.max(1000, maxX + 40), + width, height: maxY + 44, ghost: false, };