From f1187218cab48ceb93186b4727a2ee476145071d Mon Sep 17 00:00:00 2001 From: qiaoqichao Date: Wed, 30 Sep 2026 10:59:27 +0800 Subject: [PATCH] =?UTF-8?q?fix(v2):=20=E4=BF=AE=E5=A4=8D=E6=B2=99=E7=9B=98?= =?UTF-8?q?=E9=9D=A2=E6=9D=BF=E5=9C=A8=E7=9C=9F=E6=B8=B2=E6=9F=93=E4=B8=8B?= =?UTF-8?q?=E6=9A=B4=E9=9C=B2=E7=9A=84=E4=B8=89=E5=A4=84=E7=BC=BA=E9=99=B7?= =?UTF-8?q?=EF=BC=88=E6=B5=8F=E8=A7=88=E5=99=A8=E5=AE=9E=E6=B5=8B=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 这些缺陷源码守卫与构建都查不出,只有真正渲染才暴露(正是 F11 要防的情况): 1. 雷达图没有尺寸约束: 只有 viewBox,撑满整个容器(实测约 1000px 高), 且轴上没有维度标签。现按仓库既有约定给显式 width/height 并补轴标签。 2. 首屏不显示演示值:真实运行视图按 AC-30 不携带演示估计,导致首屏显示 "暂不显示",与 13 §7「首屏并列概率(演示值)+ 主审意见」不一致。 现载入演示域运行后取一次基线情景(空选择 → 服务端演示表条目 00), production 域不请求演示表。 3. 未勾选任何假设时也画出实线情景轮廓(等于把基线复制成"所选情景"); 现只在存在被假设的维度时画实线。另外维度阶段补上序号,便于对照图例。 守卫脚本相应补 4 条断言(显式尺寸、轴标签、基线虚线、无勾选不画实线), 并修正被本次改动影响的旧断言。 Co-Authored-By: Claude Code --- scripts/test_prescreen_ui.cjs | 12 +++++++- src/components/xingbang/PrescreenPanel.tsx | 32 +++++++++++++++------- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/scripts/test_prescreen_ui.cjs b/scripts/test_prescreen_ui.cjs index 2d01923..d729948 100644 --- a/scripts/test_prescreen_ui.cjs +++ b/scripts/test_prescreen_ui.cjs @@ -35,7 +35,11 @@ for (const [name, source] of [["panel", panel], ["lib", lib]]) { assert.match(panel, /simulation\.probability_view/, "情景数值必须取自服务端响应"); // 4. 演示值只在演示域显示,且紧邻"人工设定"说明(AC-12/30、DESIGN §38) -assert.match(panel, /demoValue\(run\?\.data_domain/, "演示值必须按数据域判定后才显示"); +assert.match(panel, /const domainName = run\?\.data_domain/, "演示值必须按数据域判定后才显示"); +assert.match(panel, /demoValue\(domainName, simulation\?\.baseline_probability_view \|\| run\?\.probability_view\)/, + "基线演示值必须取自服务端基线视图,不能由前端合成"); +assert.match(panel, /if \(first && first\.data_domain === "demo"\) await applyScenario\(\[\], first\)/, + "首屏演示值只对演示域取值,production 域不得请求演示表"); assert.match(panel, /未完成真实历史校准/, "未校准时必须说明为何不显示概率"); assert.match(panel, /正式审批由人工决定/, "演示值旁必须有用途边界说明"); assert.match(lib, /domain !== "demo"/, "lib 必须在非演示域返回 null"); @@ -49,6 +53,12 @@ assert.match(lib, /EMPTY_DIMENSIONS = "当前方案尚未配置核查维度"/); assert.match(panel, /shown\.layout === "radar" && /, "空维度用空状态,不补六轴"); assert.match(panel, /文字列表与图等价/, "图必须有等价文字列表"); +assert.match(panel, /width=\{\d+\} height=\{\d+\} viewBox=/, "雷达必须给显式尺寸,否则会撑满容器(真渲染验证过的缺陷)"); +assert.match(panel, / item\.assumed\)/, + "没有勾选假设时不得画出实线情景轮廓"); +assert.match(panel, /阶段 \{stage \?\? "—"\} · \{stageName\(stage\)\}/, "维度必须同时给出阶段序号与名称"); // 6. 乱序与过期响应必须丢弃(AC-31) assert.match(panel, /ticket !== seq\.current/, "旧请求晚到必须丢弃"); diff --git a/src/components/xingbang/PrescreenPanel.tsx b/src/components/xingbang/PrescreenPanel.tsx index 9518e01..3a78b4b 100644 --- a/src/components/xingbang/PrescreenPanel.tsx +++ b/src/components/xingbang/PrescreenPanel.tsx @@ -50,6 +50,9 @@ export default function PrescreenPanel({project, request, busy, setBusy, setErro } else { setActions([]); } + // 演示域的基线演示值来自服务端演示表:首屏就要能看到"演示值 + 人工设定说明", + // 而不是等用户勾选后才出现数字(13 §7)。production 域不取,避免越域数值。 + if (first && first.data_domain === "demo") await applyScenario([], first); } catch (e: any) { setError(e.message); } finally { @@ -69,15 +72,16 @@ export default function PrescreenPanel({project, request, busy, setBusy, setErro .catch((e: any) => setError(e.message)); } - async function applyScenario(keys: string[]) { - if (!run) return; + async function applyScenario(keys: string[], target?: RunView | null) { + const current = target || run; + if (!current) return; const ticket = ++seq.current; setBusy(true); setError(""); setNotice(""); setScenarioState(keys.length ? "computing" : "baseline"); try { const result: Simulation = await request( - `${PRESCREEN}${projectPath}/runs/${encodeURIComponent(run.id)}/simulations`, - {assumptions: keys, expected_revision: run.revision}); + `${PRESCREEN}${projectPath}/runs/${encodeURIComponent(current.id)}/simulations`, + {assumptions: keys, expected_revision: current.revision}); if (ticket !== seq.current) return; // 旧请求晚到:丢弃,不覆盖新结果 const returned = (result.assumptions || []).map((item) => item.key).sort().join(","); if (returned !== [...keys].sort().join(",")) return; // 结果与当前选择不一致:丢弃 @@ -111,9 +115,10 @@ export default function PrescreenPanel({project, request, busy, setBusy, setErro opener.current?.focus(); } - const baseline = demoValue(run?.data_domain || "demo", run?.probability_view); - const scenario = simulation && simulation.status === "ready" - ? demoValue(run?.data_domain || "demo", simulation.probability_view) : null; + const domainName = run?.data_domain || "demo"; + const baseline = demoValue(domainName, simulation?.baseline_probability_view || run?.probability_view); + const scenario = simulation && simulation.status === "ready" && selected.length + ? demoValue(domainName, simulation.probability_view) : null; const dimensions = run?.dimension_results || []; // 刻度信息来自呈现层:运行视图只带阶段序数,因此默认同一刻度;方案带 scale_id 时按实际判断。 const shown = presentationFor(dimensions.map((item: any) => ({scale_id: item.scale_id || "stage"}))); @@ -170,7 +175,7 @@ export default function PrescreenPanel({project, request, busy, setBusy, setErro const overlay = scenarioStages.get(item.id); const stage = overlay ? overlay.scenario_stage : item.stage; return

- {item.id} · {stateLabel(item.state)} · 阶段 {stageName(stage)} + {item.id} · {stateLabel(item.state)} · 阶段 {stage ?? "—"} · {stageName(stage)} {overlay?.assumed ? "(本情景假设该维度交叉核验成立;原项目仍待核实)" : ""} {" "}