feat(w05): 件① 评估输入补全 —— 类型下拉从台账现读

芯片类型是人在件上现造的,选项写死等于「只能选出包那天就有的那几种」。
行由系统铺、人不加行:要建哪颗料是启动参数就定死的事,他只填格子。
主键列挂 data-col 当行的钥匙 —— 漏了会把人改的行错配到别行上(2026-08-02 踩过)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
This commit is contained in:
xcs
2026-08-29 18:33:07 +08:00
co-authored by Claude Opus 5
parent b2159022bc
commit c60a2adf43
5 changed files with 281 additions and 632 deletions
@@ -26,7 +26,12 @@ const ITEMS = ['S1_inputs', 'S2_assess', 'S3_review', 'T_release'];
* 入库事实 / 分析结论一落地引擎就自己收步,不再需要人工激活等待。 * 入库事实 / 分析结论一落地引擎就自己收步,不再需要人工激活等待。
*/ */
const NO_DONE_WHEN = new Set(['S3_review', 'T_release']); const NO_DONE_WHEN = new Set(['S3_review', 'T_release']);
const VIEWS = ['p08-master-create', 'p10-document-create', 'eval-input-list', 'difference-review', 'landing-confirm']; /*
* 2026-08-29 Task 7:五张旧局面收成三张 —— `p08-master-create` / `p10-document-create` /
* `eval-input-list` 三张合并成一张 `input-supplement`(Task 7 已交付);
* `difference-review` / `landing-confirm` 由 Task 8 / 9 各自重做(同名)。
*/
const VIEWS = ['input-supplement', 'difference-review', 'landing-confirm'];
/** 运营中心 L2 沙箱里那张关键台账数据看板 —— 不是工单件,只在「都声明了」这一条里出现;自己那组断言在文件尾 */ /** 运营中心 L2 沙箱里那张关键台账数据看板 —— 不是工单件,只在「都声明了」这一条里出现;自己那组断言在文件尾 */
const BOARD_VIEW = 'activity-ledger'; const BOARD_VIEW = 'activity-ledger';
const byAddress = (domain: string, object: string) => manifest.declarations.ledger.find((l) => l.domain === domain && l.object === object); const byAddress = (domain: string, object: string) => manifest.declarations.ledger.find((l) => l.domain === domain && l.object === object);
@@ -99,12 +104,11 @@ describe('清单本身合规', () => {
}); });
/* /*
* SKIP(2026-08-29,Task 2):`assets.viewTemplates` 已按 spec §8 暂时只留 `activity-ledger` * SKIP(2026-08-29,Task 2 → Task 7 部分交付):`input-supplement` 已随 Task 7 补回
* 一张(五张旧局面全部作废,它们绑的是已退休的九步与旧台账地址)—— Task 7–9 会各自加回 * `assets.viewTemplates`(`VIEWS` 已更新),但 `difference-review` / `landing-confirm`
* `input-supplement` / `difference-review` / `landing-confirm` 三张新件,那时再补一条按新 * 两张还在等 Task 8 / 9 —— `new Set(...)` 的整体相等要三张都在才成立,继续跳过。
* 视图集合钉死的断言。这条转性检查在过渡期间的意义不大,先跳过、不删。
*/ */
it.skip('五张工单视图 + 一张运营侧看板都声明了,剧本每一步引用的 view 都在其中', () => { it.skip('三张工单视图 + 一张运营侧看板都声明了,剧本每一步引用的 view 都在其中', () => {
expect(new Set(manifest.assets.viewTemplates.map((v) => v.name))).toEqual(new Set([...VIEWS, BOARD_VIEW])); expect(new Set(manifest.assets.viewTemplates.map((v) => v.name))).toEqual(new Set([...VIEWS, BOARD_VIEW]));
for (const step of manifest.assets.businessModel.steps as ReadonlyArray<{ view?: string }>) { for (const step of manifest.assets.businessModel.steps as ReadonlyArray<{ view?: string }>) {
if (step.view !== undefined) expect(VIEWS, step.view).toContain(step.view); if (step.view !== undefined) expect(VIEWS, step.view).toContain(step.view);
@@ -315,10 +319,14 @@ describe('案例模型与清单的对齐', () => {
describe('报告模版:编辑面与只读面各就各位', () => { describe('报告模版:编辑面与只读面各就各位', () => {
/* /*
* SKIP(2026-08-29,Task 2 → Task 7 补回):`p08-master-create` / `p10-document-create` / * SKIP(2026-08-29,Task 2 → Task 7,复核后维持 skip):`p08-master-create` / `p10-document-create` /
* `eval-input-list` 三张局面已随重写从 `assets.viewTemplates` 撤下(spec §8:五张旧件全部作废, * `eval-input-list` 三张局面已被 Task 7 的 `input-supplement` 一张顶掉,但这条测的形状
* 新的 `input-supplement` 由 Task 7 重做)。`viewHtml()` 现在对这三个名字一律返回空串, * 与新设计不同,不是简单换个 view 名字就能救活:①P-10(`parts.document`)整个不在新
* 下面几条对 HTML 结构的断言无从谈起 —— 跳过、不删,等 Task 7–9 补回视图再验。 * S1 的范围里(本活动不再碰它);②新设计里 `parts.profile` 两行由系统铺、人不加行
* (要建哪颗料是启动参数定死的事),`parts.parameter` 的行集由配方从
* `sub_eval.part × parts.type_param` 派生且 `prune:true`(手加的孤行下一拍就被删)——
* 这两张表都**不给** `data-edit-add`,只有 `parts.type_param`(新建类型的字段清单)才有。
* 新覆盖见下面「件① 评估输入补全」;继续 skip、不改断言内容,留给读的人对照差异。
*/ */
it.skip('P-08 数据工单件的表 1 + 表 2(标准化参数子表)都可加行可编辑:data-edit / data-edit-add 与主键 data-col 都在', () => { it.skip('P-08 数据工单件的表 1 + 表 2(标准化参数子表)都可加行可编辑:data-edit / data-edit-add 与主键 data-col 都在', () => {
const p08 = viewHtml('p08-master-create'); const p08 = viewHtml('p08-master-create');
@@ -350,11 +358,9 @@ describe('报告模版:编辑面与只读面各就各位', () => {
} }
}); });
it('每个可编辑格子的 data-col 都在对应台账对象的声明里(p08-master-create 一屏两张表,两张台账对象的声明并起来看)', () => { it('每个可编辑格子的 data-col 都在对应台账对象的声明里(input-supplement 一屏三张表,三张台账对象的声明并起来看)', () => {
const cases: Array<[string, Array<[string, string]>]> = [ const cases: Array<[string, Array<[string, string]>]> = [
['p08-master-create', [['parts', 'profile'], ['parts', 'parameter']]], ['input-supplement', [['parts', 'profile'], ['parts', 'type_param'], ['parts', 'parameter']]],
['p10-document-create', [['parts', 'document']]],
['eval-input-list', [[D, 'input_gap']]],
]; ];
for (const [view, objects] of cases) { for (const [view, objects] of cases) {
const cols = new Set([...viewHtml(view).matchAll(/data-col="([^"]+)"/g)].map((m) => m[1] ?? '')); const cols = new Set([...viewHtml(view).matchAll(/data-col="([^"]+)"/g)].map((m) => m[1] ?? ''));
@@ -380,8 +386,13 @@ describe('报告模版:编辑面与只读面各就各位', () => {
await expect(host.base.interaction.pushHtml('<div><script>x()</script></div>')).rejects.toThrow(/无脚本执行权/); await expect(host.base.interaction.pushHtml('<div><script>x()</script></div>')).rejects.toThrow(/无脚本执行权/);
}); });
/* SKIP(2026-08-29,Task 2 → Task 7-9):VIEWS 列的五张局面已全部撤下,`assets.viewTemplates` 现在只有运营侧看板一张 */ /*
it.skip('五张模版的外壳都是搬自目标件的 page/card,不再含 E04/I01 那套视觉底(.proto-root)的痕迹', () => { * SKIP(2026-08-29,Task 2 → Task 7 部分交付):VIEWS 现在是三张新局面的名单,`input-supplement`
* 已随 Task 7 补回、这一条本身可以对它验(外壳确实是 page/card),但 `difference-review` /
* `landing-confirm` 两张还没有(viewHtml 返回空串,`toContain('class="card-head"')` 会假),
* 循环体没法只验一部分 —— 等 Task 8/9 补齐三张再一起解封。
*/
it.skip('三张模版的外壳都是搬自目标件的 page/card,不再含 E04/I01 那套视觉底(.proto-root)的痕迹', () => {
for (const view of VIEWS) { for (const view of VIEWS) {
const html = viewHtml(view); const html = viewHtml(view);
expect(html, view).toContain('class="card-head"'); expect(html, view).toContain('class="card-head"');
@@ -391,6 +402,56 @@ describe('报告模版:编辑面与只读面各就各位', () => {
}); });
}); });
/*
* 件① 评估输入补全(2026-08-29,Task 7):S1 的配方缺什么就发一张数据工单,`view: 'input-supplement'`
* 指向它。三块竖排——要评估什么(两颗料的存在性 + 主数据)/ 芯片类型(选型 + 新建类型时的核心参数
* 字段清单)/ 参数值(逐条实测/规格值);顶部一条红带摆配方算好的 `gate_reason`。
* 复用文件顶部已有的 `viewHtml` 短写(与 brief 草稿里的 `viewOf` 同一件事,不重复定义一个)。
*/
describe('件① 评估输入补全', () => {
const html = viewHtml('input-supplement');
it('在 viewTemplates 里,且 S1 指向它', () => {
expect(html).not.toBe('');
const steps = manifest.assets.businessModel.steps as ReadonlyArray<{ item: string; view?: string }>;
expect(steps.find((s) => s.item === 'S1_inputs')?.view).toBe('input-supplement');
});
it('三块都在,行都绑本单草稿(不是整表)', () => {
expect(html).toContain('data-rows="mine:sub_eval.part"');
expect(html).toContain('data-rows="mine:parts.parameter"');
expect(html).toContain('data-rows="mine:parts.type_param"');
});
it('芯片类型是从台账现读的下拉 —— 写死等于「只能选出包那天就有的那几种」', () => {
expect(html).toContain('data-options="parts.chip_type"');
expect(html).toContain('data-option-value="type_id"');
expect(html).toContain('data-option-label="type_name"');
/* data-options 与 data-slot 不能共存(渲染时 data-slot 会被剥掉)—— 当下值由 data-col 定 */
expect(/data-options="[^"]*"[^>]*data-slot=/.test(html)).toBe(false);
});
it('**主键列必挂 `data-col`** —— 它不是编辑面,是行的钥匙(漏了会把人改的行错配到别行上)', () => {
expect(html).toContain('data-col="internal_pn"');
expect(html).toContain('data-col="param_name"');
});
it('可改的三张表各有一道编辑闸', () => {
for (const address of ['parts.profile', 'parts.type_param', 'parts.parameter']) {
expect(html).toContain(`data-edit="${address}"`);
}
});
it('模版不算不判:没有内联脚本,红带的显隐交给样式表', () => {
expect(html).not.toContain('<script');
expect(html).toContain('data-class-of="gate_reason"');
});
it('表宽超出要能横着滑 —— 不许写成 overflow:hidden(E04/I01 抄错过一次)', () => {
expect(html).not.toMatch(/\.jc-tbl-wrap\s*\{[^}]*overflow\s*:\s*hidden/);
});
});
/* /*
* 2026-08-27 实测账,两条都真截图 + SQL 复核过: * 2026-08-27 实测账,两条都真截图 + SQL 复核过:
* ① **带下划线的枚举值一律显示成「待定」**——平台有两套互相打架的消毒规则: * ① **带下划线的枚举值一律显示成「待定」**——平台有两套互相打架的消毒规则:
@@ -481,17 +542,17 @@ describe.skip('徽标别名与值域相关的坑(2026-08-27 实测账,回归网)
* 两条各配一张回归网,防的是同一类事故再出现在别的字段上。 * 两条各配一张回归网,防的是同一类事故再出现在别的字段上。
*/ */
/* /*
* SKIP(2026-08-29,Task 2 → Task 7 补回):`EDITABLE` 列表指着的三张局面(p08-master-create / * 解封(2026-08-29,Task 7):`EDITABLE` 列表已按新设计换成 `input-supplement` 一张件绑的
* p10-document-create / eval-input-list)与 `sub_eval.input_gap` 都已随重写撤下 —— 这两条 * 三张台账对象(`parts.profile` / `parts.type_param` / `parts.parameter`)—— `p10-document-create`
* Critical 回归网等 Task 7 重做数据工单件时再钉在新件上。 * 与 `sub_eval.input_gap` 两张随重写整个退休,不再有对应条目。这两条 Critical 回归网继续钉在
* 新件上:主键(internal_pn / type_id+param_name)都挂了 `data-col`,必填列都有可填的控件。
*/ */
describe.skip('可编辑件的主键与必填列(2026-08-27 复审:主键丢钥匙 / 必填填不了,两条 Critical 回归)', () => { describe('可编辑件的主键与必填列(2026-08-27 复审:主键丢钥匙 / 必填填不了,两条 Critical 回归)', () => {
/** 三张真能编辑的件与它们各自绑定的台账对象 */ /** input-supplement 一屏三张真能编辑的表与它们各自绑定的台账对象 */
const EDITABLE: ReadonlyArray<{ view: string; domain: string; object: string }> = [ const EDITABLE: ReadonlyArray<{ view: string; domain: string; object: string }> = [
{ view: 'p08-master-create', domain: 'parts', object: 'profile' }, { view: 'input-supplement', domain: 'parts', object: 'profile' },
{ view: 'p08-master-create', domain: 'parts', object: 'parameter' }, { view: 'input-supplement', domain: 'parts', object: 'type_param' },
{ view: 'p10-document-create', domain: 'parts', object: 'document' }, { view: 'input-supplement', domain: 'parts', object: 'parameter' },
{ view: 'eval-input-list', domain: D, object: 'input_gap' },
]; ];
it('每一个主键列都有 data-col —— 它不是编辑面,是行的钥匙,丢了就整行保存不下去', () => { it('每一个主键列都有 data-col —— 它不是编辑面,是行的钥匙,丢了就整行保存不下去', () => {
@@ -240,6 +240,9 @@ export const ASSESSMENT_STEPS = [
item: 'S1_inputs' as const, item: 'S1_inputs' as const,
intent: '核对两颗料在不在库、归没归同一个类型、参数值齐不齐;缺什么就派一张数据工单让人补', intent: '核对两颗料在不在库、归没归同一个类型、参数值齐不齐;缺什么就派一张数据工单让人补',
guide: '', guide: '',
/* Task 7:发给人的数据工单摆的就是这一张 —— 三块竖排(存在性与主数据 / 芯片类型字段 / 参数值),
与 auto.do 第 ⑤ 笔 `card` 的 `gateLedgers` 同源(parts.chip_type / parts.type_param / parts.profile / parts.parameter)。 */
view: 'input-supplement',
writes: ['sub_eval.part', 'sub_eval.case', 'parts.profile', 'parts.parameter'], writes: ['sub_eval.part', 'sub_eval.case', 'parts.profile', 'parts.parameter'],
reads: ['originalInternalPn', 'candidateInternalPn', 'case:openedAt', 'parts.profile', 'parts.parameter', 'parts.chip_type', 'parts.type_param'], reads: ['originalInternalPn', 'candidateInternalPn', 'case:openedAt', 'parts.profile', 'parts.parameter', 'parts.chip_type', 'parts.type_param'],
tools: [], tools: [],
@@ -39,6 +39,7 @@ import type { ActivityManifest } from '@contracts';
import { activityIdSchema } from '@contracts'; import { activityIdSchema } from '@contracts';
import { CMMN_XML, NARRATIVE, ASSESSMENT_PHASES, ASSESSMENT_STEPS } from './case-model.ts'; import { CMMN_XML, NARRATIVE, ASSESSMENT_PHASES, ASSESSMENT_STEPS } from './case-model.ts';
import { LEDGER_BOARD, LEDGER_BOARD_DATA } from './views/ledger-board.ts'; import { LEDGER_BOARD, LEDGER_BOARD_DATA } from './views/ledger-board.ts';
import { INPUT_SUPPLEMENT } from './views/report.ts';
import { ACTIVITY_ID_TEXT, D } from './constants.ts'; import { ACTIVITY_ID_TEXT, D } from './constants.ts';
export { D, RULE_VERSION } from './constants.ts'; export { D, RULE_VERSION } from './constants.ts';
@@ -437,12 +438,17 @@ export const manifest: ActivityManifest = {
/* 三条技能已随重写退役(全程无 agent,没有临场编排可言);skills.ts 的删除排在 Task 10 */ /* 三条技能已随重写退役(全程无 agent,没有临场编排可言);skills.ts 的删除排在 Task 10 */
skills: [], skills: [],
/* /*
* 五张旧局面全部作废(它们绑的九步与旧台账地址已经不存在),**暂时只留运营中心那张看板** —— * 五张旧局面全部作废(它们绑的九步与旧台账地址已经不存在)。**Task 7 已补回第一张**:
* 三张新件(`input-supplement` / `difference-review` / `landing-confirm`)由 Task 7–9 各自加回; * `input-supplement`(评估输入补全,S1 的数据工单,三块竖排 —— 存在性与主数据 /
* 中途(Task 2–Task 9 之间)包不装,不会被静态审拦下。看板自己的 `data` 声明现在也引着已经 * 芯片类型字段 / 参数值)。`difference-review` / `landing-confirm` 两张新件由 Task 8/9
* 不存在的台账地址(`sub_eval.pn_check` 等)——Task 9「运营中心看板重绑」会把它换成新九张表。 * 各自加回;中途(Task 7–Task 9 之间)包不装,不会被静态审拦下。看板自己的 `data` 声明
* 现在也引着已经不存在的台账地址(`sub_eval.pn_check` 等)——Task 9「运营中心看板重绑」
* 会把它换成新九张表。
*/ */
viewTemplates: [{ name: 'activity-ledger', html: LEDGER_BOARD, data: LEDGER_BOARD_DATA }], viewTemplates: [
{ name: 'activity-ledger', html: LEDGER_BOARD, data: LEDGER_BOARD_DATA },
{ name: 'input-supplement', html: INPUT_SUPPLEMENT },
],
cardSchemas: {}, cardSchemas: {},
/* 蒸馏钩子撤空:它是给 agent 在终态回看轨迹用的,而这个活动全程无 agent(AI 只出场一次、答一份结构化判断,没有可供归纳的多轮轨迹) */ /* 蒸馏钩子撤空:它是给 agent 在终态回看轨迹用的,而这个活动全程无 agent(AI 只出场一次、答一份结构化判断,没有可供归纳的多轮轨迹) */
distillHooks: [], distillHooks: [],
@@ -1,32 +1,35 @@
/** /**
* ACT-WORK-05 差异评估的 HTML 报告模版 —— **活动自己持有的资产**,随包版本冻结。 * ACT-WORK-05 差异评估的 HTML 报告模版 —— **活动自己持有的资产**,随包版本冻结。
* *
* 五个局面各一张,版面语言**逐块搬自** `docs/SS02-ACT-WORK-05-html/` 的四份目标件 * **2026-08-29 Task 7 重写**:五张旧局面(P-08 数据工单 / P-10 数据工单 / 评估输入清单 /
* (《HTML报告生成规范》§7:有目标件时「搬」,不要「照着做」——样式表整段搬,标记逐块搬, * 差异评估结论审核 / 评审放行与落账确认)按 spec §8 全部作废,S1 那一步不再分成
* 只把写死的数据换成绑定): * 「先核对存在性 → 分别派 P-08/P-10 数据工单 → 再摆评估输入清单」三个局面 ——
* · `p08-master-create` —— S0d 摆,数据工单(P-08 芯片主数据),搬自「数据工单-01」 * 新的四步案例模型只有一张数据工单卡(`variant: 'data'`),这里对应**一张**新局面
* (2026-08-27:补齐目标件里原本就有、之前漏搬的【创建什么 · 表 2】标准化参数子表 `parts.parameter` * `input-supplement`(评估输入补全):**三块竖排**(spec §6.1)——
* —— 用户核对实跑页面后指出「页面结构必须与目标件一致」,目标件本就是两张表); * ① 要评估什么(两颗料的存在性 + 主数据,`sub_eval.part` 铺行 / `parts.profile` 承载编辑);
* · `p10-document-create` —— S0d 摆,数据工单(P-10 原厂技术资料),**没有自己的目标件** * ② 芯片类型(每颗料各自的类型选择,选项从 `parts.chip_type` 现读;
* (规格里数据工单是「一个统一模板按目标台账实例化」),复用「数据工单-01」的样式表与版面骨架; * 下面跟着新建类型时要填的核心参数字段清单 `parts.type_param`,供加行);
* · `eval-input-list` —— S0/S1 摆,评估输入清单,搬自「执行工单-01」; * ③ 参数值(逐条核心参数的实测 / 规格值,一字段一行,`parts.parameter`)。
* · `difference-review` —— S2/S3/S4 摆,差异评估结论审核,搬自「验收工单-01」; * 顶部一条红带摆 S1 配方算好的 `gate_reason`(「还差什么」的一句话,配方 §see case-model.ts
* · `landing-confirm` —— S5/T_release 摆,评审放行与落账确认,搬自「验收工单-02」。 * 的 step④),够不够齐、该不该出现由样式表判(§5「模版不许算」)。
* *
* 目标件的样式表逐字节存在 `./target-style.ts`(`P08_CSS`/`EXEC_CSS`/`ACC1_CSS`/`ACC2_CSS`, * `difference-review`(件② 差异评估结论审核)与 `landing-confirm`(件④ 评审放行与落账确认)
* 抽取与 diff 验证过程见 task-5-report.md)。**这份文件只做取值绑定,不算不判**; * 由 Task 8 / 9 分别重做(同名新建),这份文件此刻只有一张工单件。
* `BIND_COMMON`/`BIND_P08`/`BIND_EXEC`/`BIND_ACC1`/`BIND_ACC2` 是平台绑定层必须新增的东西 *
* (目标件是写死数据的静态demo,没有「N 行草稿」「编辑态与归档态二选一」这些平台概念): * 版面语言仍是「搬,不要照着做」(《HTML报告生成规范》§7):`P08_CSS`(逐字节抽自
* 缺项红带、编辑闸/增删行按 `html.editable` 收起、序号列换成 CSS 计数器(目标件靠 JS 脚本 * `docs/SS02-ACT-WORK-05-html/ACT-WORK-05-数据工单-01-芯片主数据创建.html`,
* 重排数字,沙箱零脚本改用它)、`data-class-of` 的 `v-*` 别名(枚举列不许摆 ASCII 原值 —— * 见 `./target-style.ts` 头注)是**唯一**的样式来源 —— 三块内容原来分属 P-08 数据工单 /
* 有色徽标的地方原样复制目标件自己那几条颜色声明当 `v-` 版,目标件本来是纯文字的地方 * P-10 数据工单(复用 P08_CSS)/ 执行工单(EXEC_CSS)三张局面,合并成一张之后
* 就只补 `::after` 文案、不额外配色)。 * 统一沿用 P08_CSS 这一套版面语言(EXEC_CSS 随之退休,见 target-style.ts 头注)。
* `BIND_COMMON`/`BIND_P08` 是平台绑定层加的东西(缺项红带、编辑闸/增删行、
* `data-class-of` 的 `v-*` 别名):目标件是写死数据的静态 demo,没有「N 行草稿」
* 「编辑态与归档态二选一」这些平台概念。
*/ */
import { P08_CSS, EXEC_CSS, ACC1_CSS, ACC2_CSS } from './target-style.ts'; import { P08_CSS } from './target-style.ts';
import { D } from '../constants.ts'; import { D } from '../constants.ts';
/* ==================================================================================== /* ====================================================================================
* 绑定层公共开关 —— 五张模版共用,与目标件自己的样式表分开存(那份要保持逐字节可比) * 绑定层公共开关 —— 三张模版共用(此刻只有一张),与目标件自己的样式表分开存(那份要保持逐字节可比)
* ==================================================================================== */ * ==================================================================================== */
const BIND_COMMON = ` const BIND_COMMON = `
/* ── 2026-08-27 用户四连指出的件上毛病,病根三处都在「目标件的样式与平台的编辑态没对上」 ── */ /* ── 2026-08-27 用户四连指出的件上毛病,病根三处都在「目标件的样式与平台的编辑态没对上」 ── */
@@ -71,6 +74,27 @@ html.editable .jc-gapbar {
.jc-gapbar b { font-weight: 800; } .jc-gapbar b { font-weight: 800; }
.jc-gapbar span { color: #9a3b36; } .jc-gapbar span { color: #9a3b36; }
html.editable .jc-gapbar.v-0 { display: none; } html.editable .jc-gapbar.v-0 { display: none; }
/*
* 顶部「还差什么」红带(2026-08-29,Task 7 新增,不是照抄 .jc-gapbar):gate_reason
* 是一句中文自由文本(配方拼的整句话,如「有料号库里查不到 —— 在下面把它的主数据建起来。」),
* 不是像 _missing 那样的整数计数 —— data-class-of 把值消毒成 class 时只留 [a-z0-9-],
* 中文字符全被剥空,v-<token> 永远是空 token(既取不出 v-0 也取不出任何有意义的档位),
* 靠它去挑「空 → 隐藏」这条路本身就走不通(核对过 view-render.ts 的 classToken 实现)。
* 改用更结实的信号:data-slot 配 data-blank 让空值真的渲成空文本节点,CSS 用 :has(:empty)
* 认「这句话是不是空的」—— gate_reason 本身还是照 brief 摆 data-class-of(留作未来若换成
* 枚举型信号时的钩子,且不影响任何断言),但真正管显隐的是下面这条 :empty 规则。
*/
.jc-gate-bar { display: flex; align-items: flex-start; gap: 8px; margin: 0 0 14px; padding: 10px 14px;
border-radius: 9px; background: #fff5f5; border: 1px solid #f3b9b5; color: var(--danger); font-size: 12.5px; line-height: 1.65; }
.jc-gate-bar b { font-weight: 800; }
.jc-gate-bar:has(.jc-gate-text:empty) { display: none; }
/*
* 不给删行的两张表,编辑态里 # 号不能空着:P08_CSS 的 table.on td.rn .jc-rn-num{display:none}
* 是配 .jc-rowdel 一起写的(删行按钮腾出位置)—— 要评估什么(#tbl1)与参数值(#tbl3)两张表
* 行集固定(人不加行、系统按 sub_eval.part 与 parts.type_param 铺),没有删行按钮可换,编辑态里
* 用 id 选择器把计数器重新点亮(id 特异度压过 P08_CSS 的类选择器)。
*/
#tbl1.on td.rn .jc-rn-num, #tbl3.on td.rn .jc-rn-num { display: inline; }
`; `;
/* /*
@@ -101,12 +125,16 @@ const DANGER = 'color: var(--danger); background: var(--danger-soft); border-col
const AMBER = 'color: #8f6a12; background: #fdf3d7; border-color: #ecd9a0;'; const AMBER = 'color: #8f6a12; background: #fdf3d7; border-color: #ecd9a0;';
const MUTED = 'color: var(--ink-3); background: #eef1f6; border-color: var(--line);'; const MUTED = 'color: var(--ink-3); background: #eef1f6; border-color: var(--line);';
/* ==================== 数据工单(P08/P10 共用):徽标别名 ==================== */ /*
* ==================== input-supplement 的徽标别名 ====================
*
* 沿用 P08_CSS 已带的两个基座形状:`.stx`(生命周期状态那一族用的胶囊)与 `.ct`(约束类型)——
* 不新起一个 `.st` 基座类(那是已退休的 EXEC_CSS 定义的,P08_CSS 里没有它的 display/padding
* 这些基础属性,借用类名而没有基座样式,徽标会是一段无样式的裸文字)。
*/
const BIND_P08 = ` const BIND_P08 = `
/* 生命周期 / 资料类型 / 资料状态 三个枚举列共用同一个徽标类 —— 取值域互不重叠(active/nrnd/eol/ /* 生命周期状态(parts.profile)与在库状态(sub_eval.part.exists_status)共用 .stx 胶囊 ——
discontinued/risk 与 datasheet/app_note/… 与 valid/expired/pending/void 三组没有一个词撞车), 取值域互不重叠(active/nrnd/eol/discontinued/risk 与 found/absent 没有一个词撞车)。 */
同一页内不必再分家。配色借的是本设计语言在别的目标件里已经出现过的那几个色(ok/danger/琥珀),
不是新配的颜色。 */
.stx { display: inline-block; font-size: 11px; font-weight: 700; padding: 1px 8px; border-radius: 999px; border: 1px solid transparent; white-space: nowrap; } .stx { display: inline-block; font-size: 11px; font-weight: 700; padding: 1px 8px; border-radius: 999px; border: 1px solid transparent; white-space: nowrap; }
${badgeCss('.stx', [ ${badgeCss('.stx', [
['active', 'Active', OK], ['active', 'Active', OK],
@@ -114,164 +142,19 @@ ${badgeCss('.stx', [
['eol', 'EOL', DANGER], ['eol', 'EOL', DANGER],
['discontinued', '停产', MUTED], ['discontinued', '停产', MUTED],
['risk', '风险', DANGER], ['risk', '风险', DANGER],
['datasheet', 'Datasheet', 'color: var(--ink-2); background: var(--soft); border-color: var(--line);'], ['found', '在库', OK],
['app_note', '应用笔记', 'color: var(--ink-2); background: var(--soft); border-color: var(--line);'], ['absent', '不在库', AMBER],
['reference_design', '参考设计', 'color: var(--ink-2); background: var(--soft); border-color: var(--line);'],
['white_paper', '白皮书', 'color: var(--ink-2); background: var(--soft); border-color: var(--line);'],
['sample_doc', '样片资料', 'color: var(--ink-2); background: var(--soft); border-color: var(--line);'],
['commitment_letter', '承诺函', 'color: var(--ink-2); background: var(--soft); border-color: var(--line);'],
['other', '其他', 'color: var(--ink-2); background: var(--soft); border-color: var(--line);'],
['valid', '有效', OK],
['expired', '过期', DANGER],
['pending', '待核实', AMBER],
['void', '作废', MUTED],
])} ])}
/* /* 约束类型(硬/软)—— parts.type_param.constraint_type,目标件 P08_CSS 已带 .ct 基座形状与这两条颜色声明 */
* 表 2(标准化参数集)的约束类型(硬/软)—— 目标件里这一格是字面类名 .ct.hard / .ct.soft
* (P08_CSS 已带这两条颜色声明,.ct 基座形状也在);平台的 data-class-of 走的是
* v-<token> 别名机制,不是字面枚举值,这里用 badgeCss 生成 .ct.v-hard / .ct.v-soft,
* 颜色原样照抄目标件那两条声明的数值(DANGER/MUTED 与 .ct.hard / .ct.soft 同一份值)。
*/
${badgeCss('.ct', [ ${badgeCss('.ct', [
['hard', '硬约束', DANGER], ['hard', '硬约束', DANGER],
['soft', '软约束', MUTED], ['soft', '软约束', MUTED],
])} ])}
`; /* 角色(原 PN / 候选 PN)是分类标签不是状态,不配色,只补 ::after 文案 */
/* ==================== 执行工单(评估输入清单):徽标别名 ==================== */
const BIND_EXEC = `
/* 候选参数现值(只读参考块):一颗料一张,复用 .jc-tbl-wrap;块头摆料号 / 角色 / 在库状态 */
.prefs { display: block; }
.pref { border: 1px solid var(--line-soft); border-radius: 10px; overflow: hidden; margin-bottom: 12px; }
.pref:last-of-type { margin-bottom: 0; }
.pref-head { display: flex; align-items: center; gap: 10px; padding: 9px 14px; background: var(--soft); border-bottom: 1px solid var(--line); }
.pref-head b { font-size: 13.5px; }
.pref .jc-tbl-wrap { border: 0; border-radius: 0; }
/* 存在性核对的两列(角色 / 在库状态)与评估输入清单的状态列共用目标件自己的 .st 徽标基座 ——
取值域互不重叠(found/absent/created/abandoned 与 gap/default/ok 没有一个词撞车)。
四档配色**原样照抄目标件自己的 .st.bound / .st.ok / .st.miss / .st.def 那四条声明**,只换成 v- 版。 */
${badgeCss('.st', [
['found', '在库', OK],
['absent', '不在库', 'color: var(--human); background: var(--human-soft); border-color: var(--human-line);'],
['created', '本单新建', 'color: var(--brand-deep); background: var(--brand-wash); border-color: var(--brand-soft);'],
['abandoned', '已放弃', MUTED],
['gap', '待补*', 'color: var(--human); background: var(--human-soft); border-color: var(--human-line);'],
['default', '默认·可改', 'color: var(--brand-deep); background: var(--brand-wash); border-color: var(--brand-soft);'],
['ok', '齐备', OK],
])}
/* 角色(原 PN / 候选 PN)是分类标签不是状态,目标件里这类描述文字从不配色,这里只补 ::after 文案 */
${badgeCss('.rl', [ ${badgeCss('.rl', [
['original', '原内部料号'], ['original', '原内部料号'],
['candidate', '候选内部料号'], ['candidate', '候选内部料号'],
])} ])}
/*
* **补充内容按行投影必填**(2026-08-27 实测:11 行全摆红「必填」胶囊,而闸只认 3 行)——
* \`sub_eval.input_gap.supplement\` 的必填是 \`requiredWhen:{status:'gap'}\`,按行不按列;
* 静态 \`class="req"\` 会把每一行都标必填。改成 \`<td data-class-of="status">\` 按本行的
* status 值投影,只在 v-gap 那一档出红胶囊与行首红条,default / ok 两档不出。
*/
td.sup.v-gap > .jc-vcell:empty::after {
content: "必填"; display: inline-flex; align-items: center; font-style: normal; font-size: 10px; font-weight: 700;
line-height: 1; padding: 3px 7px; border-radius: 5px; border: 1px dashed #f3b9b5; color: var(--danger); background: var(--danger-soft);
}
tbody tr:has(td.sup.v-gap > .jc-vcell:empty) td { background: #fffbfb; }
tbody tr:has(td.sup.v-gap > .jc-vcell:empty) td.rn { color: var(--danger); font-weight: 800; }
`;
/* ==================== 验收工单 01(差异评估结论审核):徽标别名 ==================== */
const BIND_ACC1 = `
/* 差异四态与推荐等级**都**落在这一屏(本活动数据面的既有约定 —— 各候选推荐等级摘要与
逐项差异明细同屏摆),两者取值域都含 "conditional",光认 .st 分不清是哪一列,
加 .dst / .rec 两个字段级修饰类各自配色(**原样照抄目标件 .st.ok/.miss/.unknown/.cond
四条声明**,只是同一份颜色现在要接两条不同的人读文案)。 */
${badgeCss('.st.dst', [
['match', 'MATCH', OK],
['mismatch', 'MISMATCH', DANGER],
['unknown', 'UNKNOWN', AMBER],
// 目标件 2026-08-27 改版:CONDITIONAL 从题头红改成品牌蓝(.st.cond),原样照抄
['conditional', 'CONDITIONAL', 'color: var(--brand-deep); background: var(--brand-wash); border-color: var(--brand-soft);'],
])}
/* 推荐程度 pill —— 目标件 .rec.yes / .rec.cond / .rec.no 三条声明原样照抄;台账是四档(备选借条件推荐那档琥珀) */
${badgeCss('.rec', [
['recommended', '推荐', OK],
['conditional', '有条件推荐', 'color: var(--amber); background: var(--amber-soft); border-color: var(--amber-line);'],
['alternate', '备选', 'color: var(--amber); background: var(--amber-soft); border-color: var(--amber-line);'],
['not_recommended', '不推荐', DANGER],
])}
/* 候选块序号(目标件 .cand-idx 写死 1/2)—— 交给 CSS 计数器;块头的风险级别前缀「风险 」也在目标件里 */
.card-bd { counter-reset: cand; }
.cand { counter-increment: cand; }
.cand-idx::before { content: counter(cand); }
.cand-con .risk::before { content: "风险 "; }
/*
* 风险等级(原样照抄目标件 .risk.low/.mid/.hi 三条声明)——加 critical 一档:这一屏「各候选
* 推荐等级」摘要读的是 substitution.relation.risk_level(声明了 low/medium/high/critical
* 四档),差异明细本身的 risk_level 只有三档,共用同一族选择器,多出的第四档不会撞车。
*/
${badgeCss('.risk', [
['low', '低', 'color: var(--ok);'],
['medium', '中', 'color: var(--amber, #8f6a12);'],
['high', '高', 'color: var(--danger);'],
['critical', '危急', 'color: var(--danger); font-weight: 900;'],
])}
/*
* 约束类型(硬/软)—— 目标件的差异明细表里这一列是纯文字,没有配色(数据工单-01 的 .ct.hard/
* .ct.soft 才是这两个值真正的徽标声明,只是它长在另一份目标件里)。这里把那两条颜色声明
* 原样搬过来当 v- 版(数值不改,只换了外壳类名,因为这一页的样式表里从未定义过 .ct)。
*/
.con { display: inline-block; font-size: 11px; font-weight: 700; padding: 1px 8px; border-radius: 999px; border: 1px solid transparent; white-space: nowrap; }
${badgeCss('.con', [
['hard', '硬约束', DANGER],
['soft', '软约束', MUTED],
])}
/* 替代类型 —— 目标件里也是纯文字,不配色,只补文案 */
${badgeCss('.enumtxt', [
['pin_to_pin', 'Pin-to-Pin'],
['functional', '功能替代'],
['parametric', '参数替代'],
['other', '其他'],
])}
`;
/* ==================== 验收工单 02(评审放行与落账确认):徽标别名 ==================== */
const BIND_ACC2 = `
/* 这一屏四个枚举列(推荐等级 / 分析状态 / 需求状态 / 当前阶段)取值域两两不重叠,共用 .st 即可。
推荐等级、分析状态目标件里本就是 .st 徽标(原样照抄 .st.ok/.miss/.unknown/.cond 四条声明);
需求状态 / 当前阶段目标件没有这两列,借同一套配色,不新配颜色。 */
${badgeCss('.st', [
['recommended', '推荐', OK],
['conditional', '条件推荐', 'color: var(--ledger); background: var(--ledger-soft); border-color: var(--ledger-line);'],
['alternate', '备选', AMBER],
['not_recommended', '不推荐', DANGER],
['ai_draft', 'AI 草稿', AMBER],
['fae_confirmed', 'FAE确认', OK],
['pending_customer', '待客户验证', 'color: var(--ledger); background: var(--ledger-soft); border-color: var(--ledger-line);'],
['customer_approved', '客户已批准', OK],
['rejected', '不批准', DANGER],
['void', '作废', MUTED],
['pending', '待处理', MUTED],
['responded', '已响应', 'color: var(--ledger); background: var(--ledger-soft); border-color: var(--ledger-line);'],
['won', '已成交', OK],
['abandoned', '已放弃', DANGER],
['closed', '已关闭', MUTED],
['assessing', '评估中', MUTED],
['gate03', '提交 ACT-GATE-03', OK],
])}
/* 风险等级比验收 01 多一档 critical(P-11 主记录的 risk_level 声明了它,差异明细没有) */
${badgeCss('.risk', [
['low', '低', 'color: var(--ok);'],
['medium', '中', 'color: var(--amber, #8f6a12);'],
['high', '高', 'color: var(--danger);'],
['critical', '危急', 'color: var(--danger); font-weight: 900;'],
])}
/* 替代类型 —— 目标件里是纯文字,不配色,只补文案(与验收 01 同一套值域,各自页面各存一份) */
${badgeCss('.enumtxt', [
['pin_to_pin', 'Pin-to-Pin'],
['functional', '功能替代'],
['parametric', '参数替代'],
['other', '其他'],
])}
`; `;
/** 三层壳照目标件:`<div class="page"><div class="card">`,不再套 `.proto-root`/`.app-shell`/`.jc-stage` /** 三层壳照目标件:`<div class="page"><div class="card">`,不再套 `.proto-root`/`.app-shell`/`.jc-stage`
@@ -314,8 +197,6 @@ const ZIC_TABLE =
/* ==================== 通用绑定小工具 ==================== */ /* ==================== 通用绑定小工具 ==================== */
const zone = (label: string, aside: string, icon = ''): string => `<p class="zone">${icon}<b>【${label}】</b>—— ${aside}</p>`; const zone = (label: string, aside: string, icon = ''): string => `<p class="zone">${icon}<b>【${label}】</b>—— ${aside}</p>`;
const note = (items: readonly string[]): string => `<div class="note">${items.map((p) => `<p>${p}</p>`).join('')}</div>`; const note = (items: readonly string[]): string => `<div class="note">${items.map((p) => `<p>${p}</p>`).join('')}</div>`;
const criteria = (title: string, items: readonly string[]): string =>
`<div class="criteria"><div class="c-title">${title}</div><ul>${items.map((i) => `<li>${i}</li>`).join('')}</ul></div>`;
const flow = (text: string): string => `<div class="flow">${text}</div>`; const flow = (text: string): string => `<div class="flow">${text}</div>`;
const editGate = (address: string, label: string): string => `<div class="jc-editgate" data-edit-for="${address}"> const editGate = (address: string, label: string): string => `<div class="jc-editgate" data-edit-for="${address}">
<span class="jc-editgate-t">${label}</span> <span class="jc-editgate-t">${label}</span>
@@ -345,387 +226,160 @@ const badge = (klass: string, col: string, wrapTwin = false): string => {
const inner = `<span class="${klass}" data-class-of="${col}"></span>`; const inner = `<span class="${klass}" data-class-of="${col}"></span>`;
return wrapTwin ? `<span class="jc-vcell">${inner}</span>` : inner; return wrapTwin ? `<span class="jc-vcell">${inner}</span>` : inner;
}; };
/**
* **下拉的选项从台账现读**(spec §5.3,2026-08-29):`data-options` 与 `data-slot` 不能共存 ——
* 渲染时 `fillOptions` 会把 `data-slot` 一并摘掉(它是给内容文本用的槽位标记,`<select>` 的内容
* 是一串 `<option>`,留着会被当成普通文本槽处理,把刚铺好的选项冲成一行数字/文字)。下拉的
* 「当下值」由 `data-col` 决定(`selectCurrent` 认它),这里不写 `data-slot`。
*/
const optionSelect = (col: string, address: string, valueCol: string, labelCol: string, required = false): string =>
`<select class="jc-cell" data-col="${col}"${required ? ' required' : ''} data-options="${address}" data-option-value="${valueCol}" data-option-label="${labelCol}"></select>`;
/** 顶部「还差什么」红带 —— 见 BIND_COMMON 里 `.jc-gate-bar` 的显隐说明(它不是 gapbar 那种数值判断) */
const gateBar = (): string => `<div class="jc-gate-bar" data-class-of="gate_reason">
<span><b>还差什么</b> —— <span class="jc-gate-text" data-slot="gate_reason" data-blank></span></span>
</div>`;
/* ==================== 局面 0 · 数据工单 P-08(搬自「数据工单-01」)==================== */ /* ==================== 局面:评估输入补全(input-supplement,S1 的数据工单) ==================== */
const P08_TABLE = `<div class="jc-tbl-wrap"><table id="tbl1"> /**
* 块① 要评估什么 —— 两颗料(原 PN / 候选 PN)的存在性与主数据。
*
* **行由 `sub_eval.part` 铺、写向 `parts.profile`**(`data-rows="mine:sub_eval.part"
* data-edit="parts.profile"`,两个不同的台账地址同时出现在一个行容器上,收值的授权面
* 只认 `data-edit`,与行从哪来无关 —— `apps/api/src/app/routes/desk.ts` 的
* `writesOf`/`collect` 按 `data-edit` 地址找容器、按容器内 `data-col` 收值,`data-rows`
* 只管这一屏摆几行、摆谁的)。选它做行源是因为**角色(原/候选)与在库状态**只长在
* `sub_eval.part` 上,而这两格是「要评估什么」的第一句话;`internal_pn` 挂 `data-col`
* 之后,保存时按它认作 `parts.profile` 的主键。**人不加行**(要建哪颗料是启动参数定死的事,
* 见清单 `S1_inputs.reads`),没有「+加一行」/删行按钮 —— #号列的显隐见 BIND_COMMON
* 里 `#tbl1.on td.rn .jc-rn-num` 那条覆盖。
*
* 芯片类型选择器**长在每一行里**(不是页面级单选):`sub_eval.part.chip_type_id` /
* `parts.profile.chip_type_id` 都是**逐颗料各自一份**的字段(`distinct` 判据要求两颗
* 恰好落在同一个值上,前提是它们本就可能不同),写入目标仍是 `parts.profile`。
* 只读态用嵌套 `data-by` 从 `parts.chip_type` 整表按 `type_id` 查出人读的 `type_name`
* (取值,不是算);编辑态换成 `optionSelect`(选项现读)。
*/
const PART_TABLE = `<div class="jc-tbl-wrap"><table id="tbl1">
<thead><tr> <thead><tr>
<th style="width:38px">#</th> <th style="width:38px">#</th>
<th data-h="角色">角色</th>
<th class="req" data-h="内部料号">内部料号</th> <th class="req" data-h="内部料号">内部料号</th>
<th data-h="在库状态">在库状态</th>
<th class="req" data-h="芯片类型">芯片类型</th>
<th class="req" data-h="品牌名称">品牌</th> <th class="req" data-h="品牌名称">品牌</th>
<th data-h="制造商名称">制造商</th>
<th class="req" data-h="规格描述">规格描述</th>
<th class="req" data-h="封装">封装</th>
<th data-h="工艺">工艺</th>
<th class="req" data-h="生命周期状态">生命周期</th> <th class="req" data-h="生命周期状态">生命周期</th>
<th data-h="EOL/PCN状态">EOL/PCN</th> <th data-h="制造商名称">制造商</th>
<th data-h="规格描述">规格描述</th>
<th data-h="Datasheet引用">Datasheet 引用</th> <th data-h="Datasheet引用">Datasheet 引用</th>
<th data-h="Datasheet版本">Datasheet 版本</th> <th data-h="Datasheet版本">Datasheet 版本</th>
<th data-h="适配应用场景">适配场景</th>
</tr></thead> </tr></thead>
<tbody data-rows="mine:parts.profile" data-edit="parts.profile"><tr> <tbody data-rows="mine:${D}.part" data-edit="parts.profile"><tr>
<td class="rn"><span class="jc-rn-num"></span><button class="jc-rowdel" type="button" title="删除此行" data-edit-del="parts.profile">×</button></td> <td class="rn"><span class="jc-rn-num"></span></td>
<td><span class="jc-vcell ro"><span class="rl" data-class-of="role"></span></span></td>
<td>${cell('internal_pn', 'mono')}</td> <td>${cell('internal_pn', 'mono')}</td>
<td><span class="jc-vcell ro">${badge('stx', 'exists_status')}</span></td>
<td>
<span class="jc-vcell"><span data-rows="parts.chip_type" data-by="type_id=chip_type_id"><i data-slot="type_name" data-blank></i></span></span>
${optionSelect('chip_type_id', 'parts.chip_type', 'type_id', 'type_name', true)}
</td>
<td>${cell('brand_name')}</td> <td>${cell('brand_name')}</td>
<td>${badge('stx', 'lifecycle_status', true)}${select('lifecycle_status', [['active', 'Active'], ['nrnd', 'NRND'], ['eol', 'EOL'], ['discontinued', '停产'], ['risk', '风险']], true)}</td>
<td>${cell('manufacturer_name')}</td> <td>${cell('manufacturer_name')}</td>
<td>${cell('specification_description')}</td> <td>${cell('specification_description')}</td>
<td>${cell('package', 'mono')}</td>
<td>${cell('process_technology')}</td>
<td>${badge('stx', 'lifecycle_status', true)}${select('lifecycle_status', [['active', 'Active'], ['nrnd', 'NRND'], ['eol', 'EOL'], ['discontinued', '停产'], ['risk', '风险']], true)}</td>
<td>${cell('eol_pcn_status')}</td>
<td>${cell('datasheet_ref', 'mono')}</td> <td>${cell('datasheet_ref', 'mono')}</td>
<td>${cell('datasheet_version')}</td> <td>${cell('datasheet_version')}</td>
<td>${cell('application_scenarios')}</td>
</tr></tbody> </tr></tbody>
</table></div>`; </table></div>`;
/* /**
* 表 2 · 标准化参数集(P-08 子表 `parts.parameter`)—— 粒度与表 1 不同(一个参数一行, * 块② 芯片类型的核心参数字段清单 —— `parts.type_param`,复合主键 `type_id + param_name`。
* 行数不固定),不能塞进表 1 的一格(《统一方案》§六:重复明细用子表承载), * **供新建类型时加字段行**(唯一带「+加一行」/删行的一张):类型下拉里没有合适的,
* 逐块搬自目标件《数据工单-01》的【创建什么 · 表 2】,`id="tbl2"` 沿用目标件自己的 * 人就在这里把新类型的核心参数字段一条条定下来(名字 / 说明 / 硬软约束 / 单位)。
* 列宽声明(P08_CSS 里 `#tbl2 th:nth-child(n)` 那几条,不必在这里重复)。 *
* 「说明」列是系统只读注释(目标件原话:「说明列为系统注释,无需填写」),不给 data-col。 * **不挂 `id="tbl2"`**:P08_CSS 的 `#tbl2 th:nth-child(n)` 那几条列宽规则是按位置
* (nth-child)写死给旧版 `parts.parameter` 表(所属料号/参数名/值/单位/约束/说明,7 列)调的,
* 这张表列的顺序与语义不同(所属类型编号/参数名/参数说明/约束/参考单位,6 列)——
* 位置 5、6 恰好被那两条老规则套反(约束套上「单位」的宽度、参考单位套上「约束」的宽度)。
* 不挂 id 就不会继承这几条位置写死的规则,退回普通 `table`/`th`/`td` 基础样式。
*/ */
const P08_PARAMS_TABLE = `<div class="jc-tbl-wrap"><table id="tbl2"> const TYPE_PARAM_TABLE = `<div class="jc-tbl-wrap"><table>
<thead><tr> <thead><tr>
<th style="width:38px">#</th> <th style="width:38px">#</th>
<th class="req" data-h="所属料号">所属料号</th> <th class="req" data-h="所属类型">所属类型编号</th>
<th class="req" data-h="参数名">参数名</th> <th class="req" data-h="参数名">参数名</th>
<th class="req" data-h="参数说明">参数说明</th>
<th class="req" data-h="约束类型">约束(硬/软)</th>
<th data-h="参考单位">参考单位</th>
</tr></thead>
<tbody data-rows="mine:parts.type_param" data-edit="parts.type_param"><tr>
<td class="rn"><span class="jc-rn-num"></span><button class="jc-rowdel" type="button" title="删除此行" data-edit-del="parts.type_param">×</button></td>
<td>${cell('type_id', 'mono')}</td>
<td>${cell('param_name')}</td>
<td>${cell('param_desc')}</td>
<td>${badge('ct', 'constraint_type', true)}${select('constraint_type', [['hard', '硬约束'], ['soft', '软约束']], true)}</td>
<td>${cell('param_unit')}</td>
</tr></tbody>
</table></div>`;
/**
* 块③ 参数值 —— `parts.parameter`,一字段一行(行由 S1 配方按 `sub_eval.part × parts.type_param`
* 铺,`prune:true` 会把不属于这个组合的孤行清掉,故这张表也**人不加行**)。
* **只两栏可改**(brief 原话):值 / 说明;`param_unit` 特意留只读 —— 配方每拍都从
* `parts.type_param` 字典**直接复制**这一格(`{ col: 'param_unit' }`,不是自查现值的
* `lookup...else` 那种自保写法),手改了也会在下一拍被字典值覆盖回去,给它配可编辑的
* 输入孪生件只会让人误以为改了真的算数。
*/
const PARAMETER_TABLE = `<div class="jc-tbl-wrap"><table id="tbl3">
<thead><tr>
<th style="width:38px">#</th>
<th data-h="所属料号">所属料号</th>
<th data-h="参数名">参数名</th>
<th class="req" data-h="值">值</th> <th class="req" data-h="值">值</th>
<th data-h="单位">单位</th> <th data-h="单位">单位</th>
<th class="req" data-h="约束类型">约束(硬/软)</th>
<th data-h="说明">说明</th> <th data-h="说明">说明</th>
</tr></thead> </tr></thead>
<tbody data-rows="mine:parts.parameter" data-edit="parts.parameter"><tr> <tbody data-rows="mine:parts.parameter" data-edit="parts.parameter"><tr>
<td class="rn"><span class="jc-rn-num"></span><button class="jc-rowdel" type="button" title="删除此行" data-edit-del="parts.parameter">×</button></td> <td class="rn"><span class="jc-rn-num"></span></td>
<td>${cell('internal_pn', 'mono')}</td> <td>${cell('internal_pn', 'mono')}</td>
<td>${cell('param_name')}</td> <td>${cell('param_name')}</td>
<td>${cell('param_value', 'mono')}</td> <td>${cell('param_value', 'mono')}</td>
<td>${cell('param_unit')}</td> <td>${ro('param_unit')}</td>
<td>${badge('ct', 'constraint_type', true)}${select('constraint_type', [['hard', '硬约束'], ['soft', '软约束']], true)}</td> <td>${cell('param_note')}</td>
<td class="desc">${ro('param_note')}</td>
</tr></tbody> </tr></tbody>
</table></div>`; </table></div>`;
export const P08_MASTER_CREATE = doc({ export const INPUT_SUPPLEMENT = doc({
css: P08_CSS, css: P08_CSS,
alias: BIND_P08, alias: BIND_P08,
badge: '数据', badge: '数据',
hic: HIC, hic: HIC,
tag: '数据工单', tag: '数据工单',
kicker: 'SS02 · ACT-WORK-05 差异评估 · 数据工单 1/2', kicker: 'SS02 · ACT-WORK-05 差异评估 · 数据工单',
title: '芯片主数据创建(P-08)', title: '差异评估的输入还差几格',
step: '步骤 0.5 · 等待数据', step: '步骤 1/4 · 校验入参与补数据',
footTag: '工单类型:数据', footTag: '工单类型:数据',
body: body:
zone('做什么', '批量创建 P-08 主数据记录', ZIC_TASK) + zone('做什么', '核对两颗料在不在库、归没归同一个芯片类型、参数值齐不齐;缺什么就在下面补齐', ZIC_TASK) +
note([ note([
'<span class="need">模板定位</span>:本单是「数据工单」统一模板的实例——<b>一单一类(可多条)</b>:目标台账 = <b>芯片主数据(P-08)</b>;<b>字段即表头、每行一条新记录</b>;标准化参数集是它的结构子表(表 2,一行一参数,按所属料号归属)。', '<span class="need">模板定位</span>:本单是「数据工单」——目标台账 = 芯片主数据(P-08 主记录 + 标准化参数子表),核心参数字段清单挂在芯片类型字典上。',
'<span class="need">触发原因</span>:系统按内部料号查芯片主数据<b>查无记录</b>——是「完全没有」,不是字段缺失(那归执行工单)。', '<span class="need">触发原因</span>:下面【要评估什么】里两行是本次评估要比对的原 PN 与候选 PN —— 至少有一颗不在库、两颗归的类型不一致、或必填格还空着,下面这条红带才会出现。',
'<span class="done">AI 预填</span>:上传了 datasheet 的料号 AI 已按资料预填,标着值的格子请核对;没上传的料号全部手填,同为合法路径。', '<span class="done">已绑定</span>:两颗料的内部料号已按启动参数铺进下表,不必你重填料号本身,只需核对 / 补齐其余各格;在库的料现值已回填,只是查漏补缺。',
'<span class="need">纪律</span>:必填列全齐才允许确认;确认后这批记录进本单草稿,放行时并入正式库并留痕(确认人 = 数据责任人);只建主数据,验证记录等事实类不在此建。', '<span class="need">纪律</span>:两颗料必须归到同一个芯片类型下才谈得上逐项比对;类型下拉里没有合适的就新建一个,并把它的核心参数字段一并定下来;查不到的参数值留空(会被红带点名),不要编一个值顶替。',
]) + ]) +
zone('创建什么 · 表 1', '主数据记录(每行一条,*为必填列)', ZIC_TABLE) + gateBar() +
zone('要评估什么', '两颗料的存在性与主数据(每行一颗,人不加行,*为必填列)', ZIC_TABLE) +
editGate('parts.profile', '两颗料的主数据核对 / 补齐,点「编辑」开表') +
gapbar('parts_profile', '必填') + gapbar('parts_profile', '必填') +
editGate('parts.profile', `<b>待建 <span data-slot="parts_profile_rows"></span> 条记录</b>,点「编辑」核对预填、补齐空缺;「+ 加一行」= 再建一条`) + PART_TABLE +
P08_TABLE + zone('芯片类型', '两颗料所归类型的核心参数字段清单(供新建类型时补齐,*为必填列)', ZIC_TABLE) +
addrow('parts.profile', '+ 加一行(再建一条记录)', '新行的内部料号必须写(它是这一行的钥匙);其余必填列全部填写后才允许确认;删一行 = 该记录不建') + editGate('parts.type_param', '字段名字 / 说明 / 硬软约束,点「编辑」核对;新建类型用「加一行」补字段') +
`<p class="zone tbl-gap">${ZIC_TABLE}<b>【创建什么 · 表 2】</b>—— 标准化参数集(每行一个参数,按所属料号归属,*必填)</p>` + gapbar('parts_type_param', '必填') +
TYPE_PARAM_TABLE +
addrow('parts.type_param', '+ 加一行(补一个核心参数字段)', '所属类型编号 / 参数名 / 参数说明 / 约束类型必须写;参考单位选填') +
zone('参数值', '逐条核心参数的实测 / 规格值(一字段一行,*为必填列;单位由字典定,这里不改)', ZIC_TABLE) +
editGate('parts.parameter', '逐条参数值核对 / 补齐,点「编辑」开表') +
gapbar('parts_parameter', '必填') + gapbar('parts_parameter', '必填') +
editGate('parts.parameter', `<b>已铺 <span data-slot="parts_parameter_rows"></span> 行参数</b>(有 datasheet 的已按资料预填),点「编辑」核对修正;「+ 加一行」补充未列出的参数`) + PARAMETER_TABLE +
P08_PARAMS_TABLE +
addrow('parts.parameter', '+ 加一行(补充参数)', '所属料号 / 参数名 / 值 / 单位 / 约束类型齐全;「未披露」请直接写未披露,将进入 UNKNOWN 并挂验证任务;「说明」列为系统注释,无需填写') +
flow('<b>提交 / 确认:</b>本工单只承载内容;确认请在对话区的【执行卡】中进行。确认后活动自动从「等待数据」唤醒。'), flow('<b>提交 / 确认:</b>本工单只承载内容;确认请在对话区的【执行卡】中进行。确认后活动自动从「等待数据」唤醒。'),
}); });
/* ==================== 局面 0′ · 数据工单 P-10(复用「数据工单-01」的样式与骨架,没有独立目标件)==================== */
const P10_TABLE = `<div class="jc-tbl-wrap"><table>
<thead><tr>
<th style="width:38px">#</th>
<th class="req" data-h="关联料号">关联料号</th>
<th class="req" data-h="资料序号">资料序号</th>
<th class="req" data-h="资料类型">资料类型</th>
<th data-h="品牌名称">品牌</th>
<th class="req" data-h="资料标题">资料标题</th>
<th class="req" data-h="资料版本">资料版本</th>
<th class="req" data-h="文件链接">文件链接</th>
<th data-h="证据定位">证据定位</th>
<th class="req" data-h="资料状态">资料状态</th>
</tr></thead>
<tbody data-rows="mine:parts.document" data-edit="parts.document"><tr>
<td class="rn"><span class="jc-rn-num"></span><button class="jc-rowdel" type="button" title="删除此行" data-edit-del="parts.document">×</button></td>
<td>${cell('internal_pn', 'mono')}</td>
<td>${cell('doc_seq', 'mono')}</td>
<td>${badge('stx', 'document_type', true)}${select('document_type', [['datasheet', 'Datasheet'], ['app_note', '应用笔记'], ['reference_design', '参考设计'], ['white_paper', '白皮书'], ['sample_doc', '样片资料'], ['commitment_letter', '承诺函'], ['other', '其他']], true)}</td>
<td>${cell('brand_name')}</td>
<td>${cell('document_title')}</td>
<td>${cell('document_version')}</td>
<td>${cell('document_ref', 'mono')}</td>
<td>${cell('citation_locator')}</td>
<td>${badge('stx', 'document_status', true)}${select('document_status', [['valid', '有效'], ['expired', '过期'], ['pending', '待核实'], ['void', '作废']], true)}</td>
</tr></tbody>
</table></div>`;
export const P10_DOCUMENT_CREATE = doc({
css: P08_CSS,
alias: BIND_P08,
badge: '数据',
hic: HIC,
tag: '数据工单',
kicker: 'SS02 · ACT-WORK-05 差异评估 · 数据工单 2/2',
title: '原厂技术资料创建(P-10)',
step: '步骤 0.5 · 等待数据',
footTag: '工单类型:数据',
body:
zone('做什么', '为本单新建的料号登记原厂资料(P-10)', ZIC_TASK) +
note([
'<span class="need">模板定位</span>:与 P-08 同一套「数据工单」统一模板——目标台账 = <b>原厂技术资料库(P-10)</b>;一份资料一行,同一料号多份资料序号 01/02… 自己续。',
'<span class="need">触发原因</span>:按内部料号查原厂技术资料库<b>查无记录</b>(P-08 那张先确认过,这一批料号才会走到这里)。',
'<span class="need">为什么要它</span>:差异明细的证据引用要指到具体资料的版本与页码——没有这一行,结论就追溯不到来源。',
]) +
zone('创建什么', '资料记录(每行一条,*为必填列)', ZIC_TABLE) +
gapbar('parts_document', '必填') +
editGate('parts.document', `<b>待建 <span data-slot="parts_document_rows"></span> 条资料记录</b>,点「编辑」补齐`) +
P10_TABLE +
addrow('parts.document', '+ 加一行(再登记一份资料)', '关联料号与序号是这一行的钥匙,新行都要写;必填列全齐才允许确认;删一行 = 该资料不建') +
flow('<b>提交 / 确认:</b>本工单只承载内容;确认请在对话区的【执行卡】中进行。'),
});
/* ==================== 局面 1 · 评估输入清单(搬自「执行工单-01」)==================== */
const PN_CHECK_TABLE = `<div class="jc-tbl-wrap"><table>
<thead><tr>
<th style="width:38px">#</th>
<th data-h="料号">料号</th>
<th data-h="角色">角色</th>
<th data-h="在库状态">在库状态</th>
<th data-h="备注/放弃原因">备注 / 放弃原因</th>
</tr></thead>
<tbody data-rows="mine:${D}.pn_check"><tr>
<td class="rn"><span class="jc-rn-num"></span></td>
<td>${ro('internal_pn', 'mono')}</td>
<td><span class="jc-vcell ro"><span class="rl" data-class-of="role"></span></span></td>
<td>${badge('st', 'exists_status', true)}</td>
<td>${ro('note')} ${ro('abandon_reason')}</td>
</tr></tbody>
</table></div>`;
/** 候选参数现值 —— **只读参考,基座从台账现读**(2026-08-27,用户三次指出「库里明明有值,清单还要填一遍」):
* 从前「待确认」行由模型判「哪个参数原厂未披露」再写进清单 —— 参数值有没有是台账里的事实,不该由模型宣布;
* 现在每颗料一张小表(外层按核对表逐颗复制,里层 `data-by="internal_pn"` 只列它名下的参数行),
* 人看得见现值;未披露的由差异评估判成 UNKNOWN 并挂验证任务。 */
const PARAM_REF = `<article class="prefs" data-rows="mine:${D}.pn_check"><section class="pref">
<div class="pref-head"><b class="mono" data-slot="internal_pn" data-blank></b><span class="rl" data-class-of="role"></span>${badge('st', 'exists_status')}</div>
<div class="jc-tbl-wrap"><table>
<thead><tr><th style="width:38px">#</th><th data-h="参数名">参数名</th><th data-h="值">值</th><th data-h="单位">单位</th><th data-h="约束类型">约束类型</th><th data-h="说明">说明</th></tr></thead>
<tbody data-rows="parts.parameter" data-by="internal_pn"><tr>
<td class="rn"><span class="jc-rn-num"></span></td>
<td>${ro('param_name')}</td>
<td>${ro('param_value', 'mono')}</td>
<td>${ro('param_unit')}</td>
<td><span class="jc-vcell ro"><span class="ct" data-class-of="constraint_type"></span></span></td>
<td>${ro('param_note')}</td>
</tr></tbody>
</table></div>
</section></article>`;
const GAP_TABLE = `<div class="jc-tbl-wrap"><table id="tbl">
<thead><tr>
<th style="width:38px">#</th>
<th style="width:120px" data-h="键">键</th>
<th data-h="输入项">输入项</th>
<th data-h="来源">来源</th>
<th data-h="状态">状态</th>
<th data-h="绑定值/缺口说明">绑定值 / 缺口说明</th>
<th data-h="补充内容">补充内容</th>
</tr></thead>
<tbody data-rows="mine:${D}.input_gap" data-edit="${D}.input_gap"><tr>
<td class="rn"><span class="jc-rn-num"></span><button class="jc-rowdel" type="button" title="删除此行" data-edit-del="${D}.input_gap">×</button></td>
<td>${cell('item_key', 'mono')}</td>
<td>${cell('input_item')}</td>
<td>${cell('source_ledger', 'mono')}</td>
<td>${select('status', [['gap', '待补'], ['default', '默认·可改'], ['ok', '齐备']], true)}${badge('st', 'status')}</td>
<td>${cell('detail')}</td>
<td class="sup" data-class-of="status">${cell('supplement')}</td>
</tr></tbody>
</table></div>`;
export const EVAL_INPUT_LIST = doc({
css: EXEC_CSS,
alias: BIND_EXEC,
badge: '执行',
tag: '执行工单',
kicker: 'SS02 · ACT-WORK-05 差异评估 · 执行工单 1/1',
title: '评估输入与资料补齐确认',
step: '步骤 1/5 · 建立评估上下文',
footTag: '工单类型:执行',
body:
zone('做什么', '启动 4 项已绑定,其余信息在工单里补齐') +
note([
'<span class="done">AI 已完成</span>:系统已按启动传入的 <b>4 项</b>完成绑定——客户、客户需求(新建 P-01 · 版本 V1)、原 PN、候选 PN(数组,逐个评估)——并生成输入快照。',
'<span class="need">需要你补齐</span>:启动时未收的信息(应用场景、BOM 位)<b>直接在下表补</b>;默认值(替代类型 / 规则版本 / 负责人期限)可确认可改。「完全没有」(料号不在库)已由数据工单创建;每颗料的参数现值摆在「候选参数现值」里,写着「未披露」的参数将按 <b>UNKNOWN</b> 进入差异评估并挂验证任务,不用在这里补。',
]) +
zone('料号存在性核对', '只读参考 · 与下表「补充内容」互不冲突') +
PN_CHECK_TABLE +
zone('候选参数现值', '只读参考 · P-08 标准化参数集,一颗料一张,基座从台账现读;写着「未披露」的由差异评估判成 UNKNOWN 并挂验证任务') +
PARAM_REF +
zone('交回什么', '评估输入清单(已绑定项照实核对,缺口项补齐,*为必填)') +
// 红带说「必填格」不说「补充内容」:缺的可能是 AI 该写的输入项(2026-08-27 实测),写死列名会把人指向错的格子
gapbar(`${D}_input_gap`, '必填格') +
editGate(`${D}.input_gap`, `<b>还有 <span data-slot="${D}_input_gap_missing"></span> 项待补</b>,点「编辑」在「补充内容」列填入 —— 已绑定那几行的「输入项/来源/绑定值」抄的是启动入参,发现记录本身有误请退回触发方重新启动,不要在这里改`) +
GAP_TABLE +
addrow(`${D}.input_gap`, '+ 加一行(补充资料项)', '新行的「键」必须写(它是这一行的钥匙,写一个英文短名,如 thermal_note);「输入项」与「补充内容」也是必填;删一行 = 该输入项不适用;补不了的写「留 UNKNOWN」或「挂验证任务」') +
flow(
'<b>审批 / 提交:</b>本工单只承载内容;「提交」请在对话区的【执行卡】中进行。<b>提交后去向:</b>进入步骤 2 · AI 读取与标准化,仍缺失的项将按 UNKNOWN 进入差异评估。<b>注意:</b>启动参数如有误,需退回触发方修订后重新触发,本单不可改。',
),
});
/* ==================== 局面 2 · 差异评估结论审核(搬自「验收工单-01」)==================== */
/** 候选 PN 结论汇总 —— 目标件 2026-08-27 改版:一个候选一条结论(P-11 主表草稿)。
* 品牌不在 relation 上:用嵌套行模版 `data-by` 从 `parts.profile` 整表里挑这一颗料那一行(取值,不是算)。
* 「差异统计(四态)」绑 difference_summary —— S3 剧本让模型按「7 项比对:4 MATCH / 1 MISMATCH / …」的形写它,
* 模版不数四态(数出来的画面一律不做假)。 */
const RELATION_SUMMARY_TABLE = `<div class="tbl-wrap" style="margin-bottom:20px"><table>
<thead><tr>
<th style="width:40px">#</th>
<th data-h="候选PN">候选 PN</th>
<th data-h="品牌">品牌</th>
<th data-h="推荐程度">推荐程度</th>
<th data-h="风险级别">风险级别</th>
<th data-h="差异统计">差异统计(四态)</th>
<th data-h="结论摘要">结论摘要</th>
</tr></thead>
<tbody data-rows="mine:substitution.relation"><tr>
<td class="num"></td>
<td class="cust mono" data-slot="candidate_internal_pn" data-blank></td>
<td><span data-rows="parts.profile" data-by="internal_pn=candidate_internal_pn"><i data-slot="brand_name" data-blank></i></span></td>
<td>${badge('rec', 'recommendation_level')}</td>
<td>${badge('risk', 'risk_level')}</td>
<td class="stat" data-slot="difference_summary" data-blank></td>
<td class="reason" data-slot="recommendation_reason" data-blank></td>
</tr></tbody>
</table></div>`;
/** 差异明细 · 按候选 PN 分组 —— 目标件 2026-08-27 改版:**一个候选一张表**(用户:「存在多个替代 PN 要对比差异时看不清楚」)。
* 外层容器 <article> 按 relation 逐个复制行模版 <section class="cand">,里层 <tbody data-by="substitution_relation_id">
* 只列该候选名下的明细(渲染器 2026-08-27 补的嵌套 + 父键过滤)。容器标签名不得在行模版内部再出现:
* <article> 里是 section / div / table,不撞。 */
const CANDIDATE_SECTIONS = `<article class="cands" data-rows="mine:substitution.relation"><section class="cand">
<div class="cand-head">
<span class="cand-idx"></span>
<span class="cand-name mono" data-slot="candidate_internal_pn" data-blank></span>
<span class="cand-brand" data-rows="parts.profile" data-by="internal_pn=candidate_internal_pn"><i data-slot="brand_name" data-blank></i></span>
<span class="cand-vs">vs 原 PN · <i data-slot="original_internal_pn" data-blank></i></span>
<span class="cand-con">${badge('rec', 'recommendation_level')}${badge('risk', 'risk_level')}</span>
</div>
<div class="tbl-wrap"><table>
<thead><tr><th style="width:40px">#</th><th data-h="参数项">参数项</th><th data-h="原PN值">原 PN 值</th><th data-h="候选PN值">候选 PN 值</th><th data-h="单位">单位</th><th data-h="约束类型">约束类型</th><th data-h="结论">结论</th><th data-h="风险等级">风险等级</th><th data-h="证据引用">证据引用</th><th data-h="AI置信度">AI 置信度</th></tr></thead>
<tbody data-rows="mine:substitution.difference_item" data-by="substitution_relation_id"><tr>
<td class="num"></td>
<td class="cust" data-slot="parameter_name" data-blank></td>
<td class="mono" data-slot="original_value" data-blank></td>
<td class="mono" data-slot="candidate_value" data-blank></td>
<td data-slot="parameter_unit" data-blank></td>
<td><span class="con" data-class-of="constraint_type"></span></td>
<td>${badge('st dst', 'difference_status')}</td>
<td>${badge('risk', 'risk_level')}</td>
<td class="mono" data-slot="evidence_ref" data-blank></td>
<td class="mono" data-slot="confidence" data-blank></td>
</tr></tbody>
</table></div>
</section></article>`;
export const DIFFERENCE_REVIEW = doc({
css: ACC1_CSS,
alias: BIND_ACC1,
badge: '验收',
tag: '验收工单',
kicker: 'SS02 · ACT-WORK-05 差异评估 · 验收工单 1/2',
title: '差异评估结论审核',
step: '步骤 3~4/5 · 逐项差异评估 + FAE/RI 审核',
footTag: '工单类型:验收',
body:
zone('AI 结论说明', '结论是怎么算出来的') +
note([
'<b>AI 已完成的计算</b>:按 GOV 评估规则(锁定版本 <b class="mono" data-slot="rule_version" data-blank></b>),以原 PN <b class="mono" data-slot="original_internal_pn" data-blank></b> 为基准,对 <b data-slot="substitution_relation_rows"></b> 个候选 PN 分别逐项比对,每项产出四态结论——<b>MATCH / MISMATCH / UNKNOWN / CONDITIONAL</b>,并给出风险等级、证据引用与置信度;再按候选汇总出下方的【候选 PN 结论汇总】(一个候选一条结论)。差异明细 = <b>P-11 差异明细子表</b>(一条参数差异一条记录,按候选分组);结论汇总 = <b>P-11 主表草稿</b>。当前均为 AI 草稿,<b>FAE 审核通过前不落账</b>。评估参数可能含本次数据工单创建的主数据(确认人背书;未附 datasheet 的参数项会显式标注「人工录入」)。',
]) +
criteria('判断这份差异评估结论是否可信,请逐项核对', [
'<b>汇总结论与明细一致</b>:结论表中每个候选的推荐程度 / 风险级别,与其下方差异明细表的四态分布吻合(存在 MISMATCH 硬约束或悬空 UNKNOWN 的候选不得给「推荐」);',
'<b>关键参数差异均有明确状态</b>:每个候选的逐项结论无漏项,每个参数都有四态之一的判定;',
'<b>UNKNOWN 项均有去向</b>:每个 UNKNOWN 都挂了验证任务或补来源动作,不允许悬空;',
'<b>证据链完整</b>:每项结论可追溯至 P-08 / P-10 / P-06 的具体记录,或本单数据工单的创建记录(标注:人工录入);硬约束项未经证据支持不得判 MATCH。',
]) +
zone('候选 PN 结论汇总', '每个候选一条结论,内容 = 对应下方明细表的汇总判定(P-11 主表草稿)') +
RELATION_SUMMARY_TABLE +
zone('差异明细 · 按候选 PN 分组', '每个候选一张表,与原 PN 逐项对比(P-11 差异明细子表草稿 · 一条参数一条记录 difference_item_id)') +
CANDIDATE_SECTIONS +
flow(
'<b>审批:</b>本工单只承载内容;「通过 / 驳回」请在对话区的【验收卡】中进行。审核意见与补充条件写入 P-06(验证要求 verification_requirement);<b>通过后去向:</b>进入步骤 5 · 评审放行与落账确认。',
),
});
/* ==================== 局面 3 · 评审放行与落账确认(搬自「验收工单-02」)==================== */
/** 字段:值 两列表 —— 逐字节搬自目标件「拟写 P-11」「拟回写 P-01」的版式(一行一个字段),
* 用 `data-rows` 包一整张小表当行模版,一个候选 / 一条需求各重复出一份,天然支持多候选。 */
const P11_FIELD_TABLE = `<div class="tbl-wrap"><table>
<thead><tr><th style="width:40px">#</th><th style="width:180px" data-h="字段">字段</th><th data-h="拟写入值">拟写入值</th></tr></thead>
<tbody data-rows="mine:substitution.relation">
<tr><td class="num"></td><td class="cust">替代类型</td><td><span class="enumtxt" data-class-of="substitution_type"></span></td></tr>
<tr><td class="num"></td><td class="cust">原内部料号</td><td class="mono" data-slot="original_internal_pn" data-blank></td></tr>
<tr><td class="num"></td><td class="cust">候选内部料号</td><td class="mono" data-slot="candidate_internal_pn" data-blank></td></tr>
<tr><td class="num"></td><td class="cust">推荐等级</td><td>${badge('st', 'recommendation_level')} <span class="reason">(枚举:推荐 / 条件推荐 / 备选 / 不推荐,四态之一)</span></td></tr>
<tr><td class="num"></td><td class="cust">推荐理由</td><td data-slot="recommendation_reason" data-blank></td></tr>
<tr><td class="num"></td><td class="cust">差异摘要</td><td data-slot="difference_summary" data-blank></td></tr>
<tr><td class="num"></td><td class="cust">替代分析报告</td><td class="mono" data-slot="analysis_report_ref" data-blank></td></tr>
<tr><td class="num"></td><td class="cust">分析状态</td><td>${badge('st', 'analysis_status')} <span class="reason">(枚举:AI草稿 / FAE确认 / 待客户验证 / 客户已批准 / 不批准 / 作废)</span></td></tr>
<tr><td class="num"></td><td class="cust">客户批准状态</td><td>未提交 <span class="reason">(由审批流程维护,本活动不产生)</span></td></tr>
<tr><td class="num"></td><td class="cust">风险等级</td><td>${badge('risk', 'risk_level')}</td></tr>
<tr><td class="num"></td><td class="cust">验证要求</td><td class="mono" data-slot="verification_requirement" data-blank></td></tr>
<tr><td class="num"></td><td class="cust">证据可信度</td><td class="mono" data-slot="confidence_score" data-blank></td></tr>
<tr><td class="num"></td><td class="cust">差异规则版本</td><td class="mono" data-slot="rule_version" data-blank></td></tr>
</tbody>
</table></div>`;
const P01_FIELD_TABLE = `<div class="tbl-wrap"><table>
<thead><tr><th style="width:40px">#</th><th style="width:180px" data-h="字段">字段</th><th data-h="拟写入值">拟写入值</th></tr></thead>
<tbody data-rows="mine:demand.request">
<tr><td class="num"></td><td class="cust">风险等级</td><td>${badge('risk', 'risk_level')}</td></tr>
<tr><td class="num"></td><td class="cust">验证要求</td><td class="mono" data-slot="verification_requirement" data-blank></td></tr>
<tr><td class="num"></td><td class="cust">输出链接</td><td class="mono" data-slot="output_ref" data-blank></td></tr>
<tr><td class="num"></td><td class="cust">下一步动作</td><td data-slot="next_action" data-blank></td></tr>
<tr><td class="num"></td><td class="cust">当前阶段</td><td>${badge('st', 'current_stage')}</td></tr>
<tr><td class="num"></td><td class="cust">需求状态</td><td>${badge('st', 'request_status')}</td></tr>
</tbody>
</table></div>`;
export const LANDING_CONFIRM = doc({
css: ACC2_CSS,
alias: BIND_ACC2,
badge: '验收',
tag: '验收工单',
kicker: 'SS02 · ACT-WORK-05 差异评估 · 验收工单 2/2',
title: '评审放行与落账确认',
step: '步骤 5/5 · 提交 ACT-GATE-03',
footTag: '工单类型:验收',
body:
zone('AI 结论说明', '本单确认什么') +
note([
'<b>本活动以 ACT-GATE-03 评审放行为终点。</b>本单确认拟落账内容:评估结论层写 <b>P-11 替代关系主记录</b>(替代类型 / 原内部料号 / 候选内部料号 / 推荐等级 / 推荐理由 / 差异摘要 / 替代分析报告 / 分析状态 / 风险等级 / 验证要求 / 证据可信度 / 差异规则版本),需求状态回写 <b>P-01</b>(风险等级 / 验证要求 / 输出链接 / 下一步动作 / 当前阶段)。<b>客户批准不属于本活动</b>——客户批准状态由审批流程维护,本单只写分析层(分析状态 = FAE 确认),customer_approval_status 保持「未提交」,不由差异评估产生。',
]) +
criteria('放行与落账前,请逐项核对', [
'<b>推荐等级与差异明细一致</b>:差异明细中存在 UNKNOWN / CONDITIONAL 时,推荐等级不得给「推荐」;',
'<b>分析状态与客户批准状态分离</b>:本单只写分析层(分析状态 = FAE 确认),客户批准状态保持「未提交」;',
'<b>GOV 规则版本已锁定</b>:规则版本随结论一并落账;',
'<b>回写 P-01 字段完整</b>:风险等级、验证要求、输出链接、下一步动作、当前阶段五项齐全。',
]) +
zone('拟写 P-11 · 替代关系主记录', '只读展示') +
P11_FIELD_TABLE +
zone('拟回写 P-01 · 需求状态', '只读展示') +
P01_FIELD_TABLE +
flow(
'<b>审批:</b>本工单只承载内容;「通过 / 驳回」请在对话区的【验收卡】中进行。<b>通过后去向:</b>在对话区卡提交 ACT-GATE-03 评审放行;放行后结论供 ACT-WORK-08 证据归集消费。',
),
});
@@ -1,5 +1,5 @@
/** /**
* ACT-WORK-05 差异评估的四份目标件样式表 —— **机械抽取,一个数值都不许改** * ACT-WORK-05 差异评估的目标件样式表 —— **机械抽取,一个数值都不许改**
* (《HTML报告生成规范》§7:有目标件时「搬」,不要「照着做」;样式表整段搬,标记逐块搬)。 * (《HTML报告生成规范》§7:有目标件时「搬」,不要「照着做」;样式表整段搬,标记逐块搬)。
* *
* 来源(`docs/SS02-ACT-WORK-05-html/`): * 来源(`docs/SS02-ACT-WORK-05-html/`):
@@ -7,13 +7,20 @@
* (该文件头部连续放了两个 `<style>` 元素:第一段是基座壳,第二段标着 * (该文件头部连续放了两个 `<style>` 元素:第一段是基座壳,第二段标着
* 「视觉补充段 v3」——`.hic`/`.zic`/`.thd`/`#tbl1`/`#tbl2` 等数据工单专属样式。 * 「视觉补充段 v3」——`.hic`/`.zic`/`.thd`/`#tbl1`/`#tbl2` 等数据工单专属样式。
* 两段按文档原有顺序首尾相接存成一个常量;等价于浏览器解析两个 `<style>` 标签, * 两段按文档原有顺序首尾相接存成一个常量;等价于浏览器解析两个 `<style>` 标签,
* 级联结果与原文件逐字节一致,已用 diff 验证,见 task-5-report.md); * 级联结果与原文件逐字节一致,已用 diff 验证,见 task-5-report.md)——
* · `EXEC_CSS` ← `ACT-WORK-05-执行工单-01-评估输入与资料补齐确认.html` 的 `<style>`; * 2026-08-29 Task 7 起同时是唯一的工单件样式来源:`input-supplement` 一张件
* 顶掉了原来分开的 P-08/P-10/执行工单三张局面,一并沿用这套壳;
* · `ACC1_CSS` ← `ACT-WORK-05-验收工单-01-差异评估结论审核.html` 的 `<style>`; * · `ACC1_CSS` ← `ACT-WORK-05-验收工单-01-差异评估结论审核.html` 的 `<style>`;
* · `ACC2_CSS` ← `ACT-WORK-05-验收工单-02-评审放行与落账确认.html` 的 `<style>`。 * · `ACC2_CSS` ← `ACT-WORK-05-验收工单-02-评审放行与落账确认.html` 的 `<style>`。
* *
* `p10-document-create`(P-10 原厂技术资料创建)**没有自己的目标件**——规格里数据工单是 * `EXEC_CSS`(执行工单-01 的样式)已随 Task 7 删除:它原来专属的 `eval-input-list`
* 「一个统一模板按目标台账实例化」,复用 `P08_CSS`(同一套壳,只是表头列换成 P-10 的字段)。 * 局面已并入 `input-supplement`(改用 `P08_CSS`),此后再没有消费方 —— target-style.ts
* 只留「没有消费方就删」这条规矩下**还有人用**的常量,ACC1_CSS / ACC2_CSS 虽然此刻
* 也没有工单件在用,但被 `activity.test.ts`「两张验收件的主题色」那条测试直接导入校验
* (Task 8 / 9 会把它们重新接回 `difference-review` / `landing-confirm`),留着。
*
* `p10-document-create`(P-10 原厂技术资料创建)原来**没有自己的目标件**、复用 `P08_CSS` ——
* Task 7 重写后 P-10 不再是这个活动的数据工单范围(本活动不碰 P-10),这句历史说明留作背景。
* *
* 抽取用 `sed -n` 按行号截取 `<style>`/`</style>` 之间的原文,再拿抽出来的内容包回 * 抽取用 `sed -n` 按行号截取 `<style>`/`</style>` 之间的原文,再拿抽出来的内容包回
* `<style>…</style>` 与源文件同一行区间做 `diff`,四份全部「零差异」——过程写在 * `<style>…</style>` 与源文件同一行区间做 `diff`,四份全部「零差异」——过程写在
@@ -21,7 +28,7 @@
* *
* 这份文件只装**目标件自己的样式**,不装平台绑定用的开关(三态占位、缺项红带、 * 这份文件只装**目标件自己的样式**,不装平台绑定用的开关(三态占位、缺项红带、
* `data-class-of` 的 `v-*` 别名这类)——那些是本活动自己加出来的东西,活在 * `data-class-of` 的 `v-*` 别名这类)——那些是本活动自己加出来的东西,活在
* `report.ts` 的 `BIND_CSS_*` 常量里,两处不要混。 * `report.ts` 的 `BIND_*` 常量里,两处不要混。
*/ */
export const P08_CSS = ` export const P08_CSS = `
@@ -126,88 +133,6 @@ table.on td>.jc-vcell.ro{display:block}
@media (prefers-reduced-motion: reduce){*{animation:none!important;transition:none!important}} @media (prefers-reduced-motion: reduce){*{animation:none!important;transition:none!important}}
`; `;
export const EXEC_CSS = `
:root{
--ink:#1f2329; --ink-2:#41464e; --ink-3:#646a73; --ink-4:#8f959e;
--line:#dee0e3; --line-soft:#e8eaed; --soft:#f8f9fb;
--brand:#3370ff; --brand-deep:#245bdb; --brand-soft:#e1eaff; --brand-wash:#f0f4ff;
--human:#a9711e; --human-soft:#f6ecd8; --human-line:#e6d3a8;
--ok:#278717; --ok-soft:#e8f8e4; --danger:#d83931; --danger-soft:#feeae9;
--mono:"SF Mono",Menlo,Consolas,monospace;
}
*{box-sizing:border-box}
html,body{margin:0;padding:0}
body{font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;background:#eef1f6;color:var(--ink);font-size:14px;line-height:1.7;-webkit-font-smoothing:antialiased}
.page{margin:0;padding:0}
.card{background:#fff;overflow:hidden;border:0;border-radius:0;box-shadow:none}
.card-head{display:flex;align-items:center;gap:14px;padding:18px 22px;border-bottom:2px solid var(--ink);background:linear-gradient(120deg,#fffaf1 0%,#fff 55%,#fbf6ea 100%)}
.badge{flex:none;width:44px;height:44px;border-radius:10px;display:grid;place-items:center;background:var(--human);color:#fff;font-weight:800;font-size:13px;box-shadow:0 6px 16px rgba(169,113,30,.25)}
.tit{flex:1;min-width:0}
.tit .kick{font-size:11px;letter-spacing:.08em;color:var(--ink-4);font-weight:700}
.tit h1{margin:2px 0 0;font-size:19px;font-weight:800;line-height:1.3}
.tit .sub{font-size:12px;color:var(--ink-3);margin-top:3px}
.side{display:flex;flex-direction:column;align-items:flex-end;gap:6px;flex:none}
.tag{font-size:11px;font-weight:800;padding:2px 10px;border-radius:999px;color:var(--human);background:var(--human-soft);border:1px solid var(--human-line)}
.step{font-size:12px;color:var(--ink-3)}
.deadline{font-size:12px;color:var(--ink-3)}
.deadline b{color:var(--danger)}
.card-bd{padding:20px 22px}
.zone{font-size:11px;font-weight:800;letter-spacing:.12em;color:var(--ink-4);margin:0 0 8px}
.zone b{color:var(--ink-3)}
.note{border:1px solid var(--line);border-left:3px solid var(--human);border-radius:8px;background:var(--soft);padding:14px 16px;margin-bottom:16px}
.note p{margin:0;font-size:13px;color:var(--ink-2)}
.note b{color:var(--ink)}
.note .done{color:var(--ok);font-weight:700}
.note .need{color:var(--human);font-weight:700}
.jc-editgate{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin:14px 0 10px;padding:9px 12px;border-radius:9px;background:var(--soft);border:1px solid var(--line-soft);font-size:12px;color:var(--ink-3)}
.jc-editgate-t{flex:1 1 auto;min-width:0}
.jc-editgate-t b{color:var(--ink-2);font-weight:700}
.jc-egbtn{flex:none;display:inline-flex;align-items:center;gap:5px;cursor:pointer;border:1px solid var(--line);border-radius:7px;padding:6px 13px;font:inherit;font-size:12px;font-weight:700;color:var(--ink-2);background:#fff;transition:border-color .16s,color .16s,background .16s}
.jc-egbtn:hover{border-color:var(--brand);color:var(--brand)}
.jc-egbtn.save{color:#fff;background:var(--human);border-color:var(--human)}
.jc-egbtn.save:hover{filter:brightness(1.06);color:#fff}
.jc-editgate .eg-cancel,.jc-editgate .eg-save{display:none}
.jc-editgate.on{background:var(--human-soft);border-color:var(--human-line);color:#7c4a12}
.jc-editgate.on .eg-open{display:none}
.jc-editgate.on .eg-cancel,.jc-editgate.on .eg-save{display:inline-flex}
.tbl-wrap{border:1px solid var(--line-soft);border-radius:10px;overflow:hidden}
table{width:100%;border-collapse:collapse;font-size:12.5px}
th{text-align:left;background:var(--soft);font-size:11px;letter-spacing:.03em;font-weight:800;color:var(--ink-3);padding:9px 10px;border-bottom:1px solid var(--line)}
th.req::after{content:" *";color:var(--danger);font-size:11px}
td{padding:8px 10px;border-bottom:1px solid var(--line-soft);vertical-align:middle}
tr:last-child td{border-bottom:0}
td.rn{position:relative;width:38px;white-space:nowrap;font-family:var(--mono);font-size:11px;color:var(--ink-4);text-align:center}
.jc-vcell{display:block;font-size:12.5px;color:var(--ink);line-height:1.5;overflow-wrap:anywhere}
.jc-vcell.mono{font-family:var(--mono);font-size:11.5px;color:var(--ink-2)}
.jc-cell{display:none;width:100%;min-width:60px;border:1px solid var(--line);border-radius:6px;padding:6px 8px;font:inherit;font-size:12.5px;color:var(--ink);background:#fff}
.jc-cell.mono{font-family:var(--mono);font-size:11.5px}
.jc-cell:focus{border-color:var(--brand);box-shadow:0 0 0 3px var(--brand-wash);outline:none}
table.on td>.jc-vcell{display:none}
table.on td>.jc-cell{display:block}
td.rn .jc-rowdel{display:none;width:22px;height:22px;margin:0 auto;cursor:pointer;border:1px solid var(--line);border-radius:6px;padding:0;font:inherit;font-size:14px;line-height:1;color:var(--ink-3);background:#fff}
td.rn .jc-rowdel:hover{border-color:var(--danger);color:var(--danger);background:var(--danger-soft)}
table.on td.rn .jc-rn-num{display:none}
table.on td.rn .jc-rowdel{display:block}
.jc-addrow{display:none;margin:8px 0 0;padding:8px 12px;border-radius:9px;background:var(--soft);border:1px dashed var(--line);font-size:12px;color:var(--ink-3)}
.jc-addrow.show{display:flex;align-items:center;gap:8px}
.jc-addrow button{cursor:pointer;border:1px solid var(--brand-soft);border-radius:7px;padding:5px 12px;font:inherit;font-size:12px;font-weight:700;color:var(--brand-deep);background:var(--brand-wash)}
.jc-addrow button:hover{background:var(--brand-soft)}
.jc-addrow em{font-style:normal;font-size:11.5px;color:var(--ink-4)}
.flow{margin-top:16px;padding:9px 12px;border-radius:8px;background:var(--soft);border:1px dashed var(--line);font-size:12px;color:var(--ink-3)}
.flow b{color:var(--ink)}
.card-foot{padding:13px 22px;border-top:1px solid var(--line-soft);font-size:11.5px;color:var(--ink-4);display:flex;justify-content:space-between;flex-wrap:wrap;gap:8px}
@media(max-width:600px){.card-head{flex-wrap:wrap}.side{align-items:flex-start}}
/* 补充:评估输入清单状态标识(仅追加,不改动基件样式) */
.mono{font-family:var(--mono);font-size:12px;color:var(--ink-2)}
.st{display:inline-block;font-size:11px;font-weight:700;padding:1px 8px;border-radius:999px;border:1px solid transparent;white-space:nowrap}
.st.bound{color:var(--ink-3);background:#eef1f6;border-color:var(--line)}
.st.ok{color:var(--ok);background:var(--ok-soft);border-color:#c9ecc2}
.st.miss{color:var(--human);background:var(--human-soft);border-color:var(--human-line)}
.st.def{color:var(--brand-deep);background:var(--brand-wash);border-color:var(--brand-soft)}
tr.ro td{background:var(--soft)}
table.on td>.jc-vcell.ro{display:block}
`;
/* /*
* 两张验收件(ACC1 差异评估结论审核 / ACC2 评审放行与落账确认)的主题色**不再逐字节等于目标件**: * 两张验收件(ACC1 差异评估结论审核 / ACC2 评审放行与落账确认)的主题色**不再逐字节等于目标件**:
* 目标件的题头用 #b23a2e 红(--ledger),2026-08-27 用户定「中央 HTML 模版区域如果是验收工单, * 目标件的题头用 #b23a2e 红(--ledger),2026-08-27 用户定「中央 HTML 模版区域如果是验收工单,