数据工单有了自己的面孔;件上四处样式对齐;缺库不再问人;5173 反代改回本机

用户在工作台上连报六条(2026-08-27):
- 对话区把数据工单摆成「执行工单」→ 契约 ExecutionWorkOrder.variant:'data'(引擎侧与执行工单无异,
  只换面孔),归一层从卡上带下来,web 新增 DataForm(同一套办法、另一副面孔:色/图标/「数据工单」/「确认写入」),
  办结卡与治理台人类工具目录同步;活动的两张数据工单卡骨架标 variant:"data"。
- 表显示不全不能右滑 → 目标件把 overflow-x:auto 写在 .tbl-wrap,平台包装是 .jc-tbl-wrap,谁都没管到;补一条。
- 表头只留中文 → 三张数据工单表去英文列名与 .thd 小字。
- 编辑态内部料号「只读字 + 输入框」并排 → 目标件 #tbl1 .jc-vcell{display:inline}(id 特异度)盖过
  table.on 的隐藏规则,表 1 每格其实都并排,PN 那格恰好有字;用 id 特异度覆盖回去。
- input 与 select 没对齐 → 徽标从前包 .ro 编辑态留着、压在下拉框头上把那格顶低 13px,改成随只读格一起藏;
  input/select 钉同一高度。
- 第一步「派数据工单还是放弃候选」不该问人 → 决策卡退役(覆盖规格「候选 ≥2 可放弃」那一条,用户定),
  查无记录一律自动派;剧本/说明书/测试/e2e 同步(e2e 新增 expectAsks.max 断言「不该问的没问」)。
顺带:5173 的 /api 反代默认指向一条公网隧道(150.158.83.131:46100 → nps → npc → 本机 :8787),
浏览器每次请求先出公网再绕回来;默认改回本机,要远端的显式给 API_PROXY_TARGET。

静态渲染实证:900px 宽的框里横向能滑 200px;编辑态同一行 3 input + 1 select 全部 32px 同一 y;
只读 PN 格 display:none。活动包 1.4.0;2097 测 / typecheck / lint / depcruise 绿。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJcX8A6TWp4GRs41TYryEh
This commit is contained in:
xcs
2026-08-27 12:36:13 +08:00
co-authored by Claude Fable 5
parent 3171d8a864
commit 51c87af583
15 changed files with 197 additions and 116 deletions
@@ -196,8 +196,8 @@ export const NARRATIVE = `§0 开案:**判断候选料号在技术上能不能
**存在性检查与数据工单**:启动时逐个料号在 \`parts.profile\` 里查(原 PN + 每个候选)。查无记录 = 「完全没有」,
要人从零建整条主数据(表 1 主记录 + 表 2 标准化参数子表 \`parts.parameter\`)—— 发**数据工单**(一张单:P-08 一张、P-10 一张);
PN 在库但个别字段缺 = 「有而不全」,归执行工单 01 补。**该不该问人,按候选总数判断,不是按不在库的颗数判断**:
原 PN 缺 = 硬阻塞必须建、不问;候选总数 = 1 时那一颗必须建、不问;候选总数 ≥ 2 时,每一颗不在库的候选都要先问人放弃还是创建。
PN 在库但个别字段缺 = 「有而不全」,归执行工单 01 补。**查无记录的一律派数据工单,不问人**(2026-08-27 用户定:
「只要有缺失就要自动派数据工单,不需要人选择」)—— 原 PN 缺是硬阻塞、候选缺同样直接建,**没有「放弃这颗候选」这个岔路口**。
**库里都有的话这两项的入场前提本就不成立**(此单不走这条),不会出现在你的「当下可为」里,别发空单。
**六步走**:绑定与存在性核对 → (等待数据)→ 人补评估输入 → 标准化对照表 → 逐项评估 → 人审结论(可循环)→ 拟落账 → 人放行。
@@ -219,20 +219,14 @@ PN 在库但个别字段缺 = 「有而不全」,归执行工单 01 补。**该
把九个计划项的全名摆到人脸上,他一个字也用不上(2026-08-27 实测)。
人确认那一下基座已把这一行落进草稿层,**你不许再 write 一遍 customer.profile**。
② 候选不在库、且候选总数 ≥2 时,一颗一张决策卡(按候选数组顺序,一次只发一张;候选总数 = 1 时不问、直接建):
{"kind":"decide","itemId":"S0d_p08_master",
"ask":"候选 <internal_pn> 在芯片主数据里查无记录。是从零建它的主数据(派数据工单),还是本轮放弃这颗候选?",
"options":[{"value":"create","label":"创建主数据(派数据工单)","recommended":true},
{"value":"abandon","label":"放弃这颗候选"}],"allowCustom":true}
\`value\` 只有 \`"create"\` / \`"abandon"\` / \`"custom:"\` 开头三种:\`"custom:"\` 开头按放弃处理、\`note\` 就是放弃原因;
点了 \`"abandon"\` 没写原因,\`abandon_reason\` 写「负责人本轮放弃(未注明原因)」。
② (已退役)候选缺库不再问人 —— 2026-08-27 用户定「只要有缺失就自动派数据工单」;所有查无记录的料号直接进③那张卡。
③ 数据工单 · P-08(所有要建的料号一起,一张单;零输入,填报全在工作区的件上)——
**发这张卡之前,\`parts.profile\` 与 \`parts.parameter\` 必须先有骨架行**(名单里每颗料各一行 + 它的参数各一行)。
不管这一拍是不是顺手把上一步收掉的(压拍常发生,那时这一步的剧本还没摊开),写骨架与发卡都得在同一串里,顺序不能反 ——
件上一行都没有的话,人打开是一张空表、什么也补不了,而这一单的主数据从头到尾不会存在
(实测走到 S2 才被引用检查拦下,那时两张数据工单都已经「办完」了)
{"kind":"human","itemId":"S0d_p08_master","workOrder":"execution","card":{
{"kind":"human","itemId":"S0d_p08_master","workOrder":"execution","card":{"variant":"data",
"title":"数据工单 · 芯片主数据创建(P-08):<料号列表,顿号分隔>",
"ask":"这些料号在芯片主数据里查无记录,请在工作区的件上把整条记录建齐(一颗一行,可加行)。",
"steps":["表 1 每一行:品牌、规格描述、封装、生命周期状态为必填,缺一格确认不了",
@@ -251,7 +245,7 @@ PN 在库但个别字段缺 = 「有而不全」,归执行工单 01 补。**该
**与③同一条:发这张卡之前 \`parts.document\` 必须先有骨架行**(名单里每颗料至少一行,
key 是 {internal_pn, doc_seq:"01"});一行都没有的话人打开是空表、什么也补不了,
提交闸会如实拦住(「这一单还一行都没有」),这一单就卡在这儿了(2026-08-27 实测)。
{"kind":"human","itemId":"S0d_p10_document","workOrder":"execution","card":{
{"kind":"human","itemId":"S0d_p10_document","workOrder":"execution","card":{"variant":"data",
"title":"数据工单 · 原厂技术资料创建(P-10):<料号列表>",
"ask":"请把这些料号的 datasheet / 承诺函等资料记录补进原厂技术资料库(一份资料一行,可加行)。",
"steps":["资料类型、标题、版本、文件链接、资料状态为必填;证据定位写页码或章节,差异评估的证据引用会指向它",
@@ -357,7 +351,7 @@ current_stage = "assessing"。application_scenario / bom_position **这一步不
},
{
item: 'S0d_p08_master',
intent: '料号不在库:派 P-08 数据工单让人从零建芯片主数据 + 标准化参数子表;候选总数 ≥2 才逐颗问放弃还是创建',
intent: '料号不在库:派 P-08 数据工单让人从零建芯片主数据 + 标准化参数子表 —— 查无记录一律派,不问人',
writes: ['parts.profile', 'parts.parameter', 'parts.document', `${D}.pn_check`],
reads: [`${D}.pn_check`, 'candidateInternalPns', 'originalInternalPn', 'datasheets', 'parts.profile', 'parts.parameter', 'parts.document'],
view: 'p08-master-create',
@@ -365,25 +359,11 @@ current_stage = "assessing"。application_scenario / bom_position **这一步不
/* 触达人,不给 doneWhen;人工激活:发卡就算启动 */
guide: `这一项只在 \`${D}.pn_check\` 里有 absent 行时走 —— 都在库时入场前提本就不成立,这一项与下一项 S0d_p10_document 都不会进你的「当下可为」。**先分岔,再动作**:
【先数两个数,再分岔】(先看轨迹:这一项里已经发过哪些卡、人答了什么,【当前这一步】下面那行引擎事实说得清楚)
· **候选总数**:入参 \`candidateInternalPns\` 的长度(\`${D}.pn_check\` 里 role = "candidate" 的行数也是它,不随本单进展变化);
· **不在库的候选数**:\`${D}.pn_check\` 里 role = "candidate" 且 exists_status = "absent" 的行数(这个数会随着人放弃/创建而变小)。
**这里要分开答两个问题,别混成一个**:
**问题一 · 建哪几颗?** 名单 = \`${D}.pn_check\` 里 **exists_status = "absent" 的每一行**,不管它是原 PN 还是候选。
【名单】= \`${D}.pn_check\` 里 **exists_status = "absent" 的每一行**,不管它是原 PN 还是候选 —— **一律建,不问人**
(2026-08-27 用户定:只要有缺失就自动派数据工单;从前那张「派数据工单还是放弃这颗候选」的决策卡已退役,别再发)。
**exists_status = "found" 的一行都不建** —— 那几颗的主数据本来就在库里,给它们派数据工单等于让人把已有的东西再填一遍,
而真正缺的那颗还是没人建、单子照样卡死。名单为空(一行 absent 都没有)这一项根本不会出现在你的「当下可为」里。
**问题二 · 建之前要不要先问人?** 只有 **absent 的候选**才谈得上问,**看的是候选总数,不是不在库的颗数**
——**「不在库的只有一颗」不是跳过决策卡的理由**:
· 原 PN absent → **硬阻塞**,不问人,直接进名单(它是评估基准,没有它整单无从谈起);
· **候选总数 = 1** → 那一颗候选**如果是 absent** 就不许放弃、不问人,直接进名单(候选是 found 的话它压根不在名单里,没有可问的);
· **候选总数 ≥ 2** → **每一颗 absent 的候选都要先发一张决策卡问人**(一次一张,按候选数组顺序;哪怕这一单不在库的候选只有一颗,
只要候选总数 ≥ 2 就要问)照说明书②;人选 "abandon"/"custom:" 的,把那一行 \`${D}.pn_check\` 改成
exists_status = "abandoned" + abandon_reason(同 PK 覆盖写),这颗不再评估;选 "create" 的进「建」的名单。
**至少留一颗候选**:放弃到只剩最后一颗还是 absent 的候选时,那一颗按「候选总数 = 1」办 —— 不再发决策卡问,直接进「建」的名单
(候选全被放弃,存在性核对永远凑不齐、单子会死锁,这一条就是防它)。
**别把同一颗候选问两遍**:决策卡答过就按答案办。
**名单里的料号全放进同一张 P-08 卡**(标题列全料号,件上一颗一行,参数各归各的料号)—— 别一颗一张。
【「建」的名单定了 → 写骨架行,发 P-08 数据工单(一张单,件上两张表)】
1. 给名单里每颗料写一行 \`parts.profile\` 骨架(表 1):key {internal_pn},patch 只带你确有依据的列 —— 有 \`datasheets[].text\` 就从里面读
@@ -414,11 +394,8 @@ current_stage = "assessing"。application_scenario / bom_position **这一步不
门上还有一条逐颗对账(建好几颗就得有几行主数据、几行资料),标了却没建,这一步照样过不去。
【P-08 那张确认回来了】**先看名单里还有没有 absent 的料号**(\`${D}.pn_check\` 现读):
· **还有** → **这一项千万别 finish**。先回到上面【问题二】那一档:候选总数 ≥2 的话,
**这一颗也要先发一张决策卡问人**(照说明书②,人选 create 才建 —— 每一颗 absent 的候选都有被放弃的权利,
第一颗问过了不代表第二颗也问过);人选 create 之后再把它的骨架写好(profile + parameter,有 datasheet 就预填),
发一张 P-08 卡(标题带那一颗的料号)—— 同一个计划项可以先后发好几张卡,一颗一张也行。
原 PN 与「候选总数 = 1」那两档不问,直接建。
· **还有** → **这一项千万别 finish**。把还 absent 的那几颗的骨架写好(profile + parameter,有 datasheet 就预填),
再发一张 P-08 卡(标题列上它们的料号)—— 不问人,直接建;同一个计划项可以先后发好几张卡。
**收掉了就回不去了**:这一项一旦 Completed,再想给它挂第二张卡会被拒「这一步已经办完收掉了」,
而那颗料从此没人建,后面写替代关系时被引用检查逐颗拦下(2026-08-27 实测,一单连拒四次)。
· **一颗都不剩了** → 才走下面这两步。
@@ -89,7 +89,7 @@ export const manifest: ActivityManifest = {
identity: {
activityId: ACTIVITY_ID,
name: 'ACT-WORK-05 · 差异评估',
packageVersion: '1.3.6',
packageVersion: '1.4.0',
category: '需求分析与方案匹配',
sdkVersion: '0.1.0-m5',
checksum: 'dev-checksum-part-substitution-assessment',
@@ -29,6 +29,14 @@ import { D } from '../constants.ts';
* 绑定层公共开关 —— 五张模版共用,与目标件自己的样式表分开存(那份要保持逐字节可比)
* ==================================================================================== */
const BIND_COMMON = `
/* ── 2026-08-27 用户四连指出的件上毛病,病根三处都在「目标件的样式与平台的编辑态没对上」 ── */
/* ① 表宽超出就得能横着滑(E04/I01 08-23 同款):目标件把 overflow-x:auto 写在 .tbl-wrap 上,而平台包装是 .jc-tbl-wrap,谁都没管到 */
.jc-tbl-wrap { overflow-x: auto; overflow-y: hidden; }
/* ② 编辑态只留输入格:目标件 #tbl1 .jc-vcell{display:inline}(id 特异度)盖过了 table.on td>.jc-vcell{display:none},
于是表 1 每格都是「只读字 + 输入框」并排 —— 内部料号那格恰好有字,人看见的就是它 */
#tbl1.on td > .jc-vcell:not(.ro) { display: none; }
/* ③ 一行里的 input 与 select 同高同线:select 天生比 input 矮 4px,编辑态里一排格子高低不齐 */
input.jc-cell, select.jc-cell { height: 32px; box-sizing: border-box; padding-top: 0; padding-bottom: 0; vertical-align: middle; }
/* 只在「待处理」这一态露面;归档 / 验收只读态即便 HTML 里还带着 data-edit 也不摆闸和加行钮
(目标件是静态 demo,不区分这两态;html.editable 是平台在 iframe 根节点上真开的关) */
html:not(.editable) .jc-editgate, html:not(.editable) .jc-addrow { display: none !important; }
@@ -305,10 +313,14 @@ const cell = (col: string, cls = ''): string => `<span class="jc-vcell${cls ===
const ro = (col: string, cls = ''): string => `<span class="jc-vcell ro${cls === '' ? '' : ` ${cls}`}" data-slot="${col}" data-blank></span>`;
const select = (col: string, options: ReadonlyArray<readonly [string, string]>, required = false): string =>
`<select class="jc-cell" data-col="${col}"${required ? ' required' : ''}><option value=""></option>${options.map(([v, l]) => `<option value="${v}">${l}</option>`).join('')}</select>`;
/** 枚举徽标(可改表里用,旁边挨着 `.jc-cell` 孪生件时要包一层 `.ro` 才不被编辑态藏起来) */
const badge = (klass: string, col: string, wrapRo = false): string => {
/**
* 枚举徽标。可改表里挨着 `<select class="jc-cell">` 孪生件时包一层 `.jc-vcell`(不带 `.ro`):
* 看的时候摆徽标,**编辑态随只读格一起藏起来,只留下拉框** —— 从前包的是 `.ro`(编辑态留着),
* 徽标压在下拉框头上把那一格顶低 13px,一排格子高低不齐(2026-08-27 用户指出)。
*/
const badge = (klass: string, col: string, wrapTwin = false): string => {
const inner = `<span class="${klass}" data-class-of="${col}"></span>`;
return wrapRo ? `<span class="jc-vcell ro">${inner}</span>` : inner;
return wrapTwin ? `<span class="jc-vcell">${inner}</span>` : inner;
};
/* ==================== 局面 0 · 数据工单 P-08(搬自「数据工单-01」)==================== */
@@ -316,17 +328,17 @@ const badge = (klass: string, col: string, wrapRo = false): string => {
const P08_TABLE = `<div class="jc-tbl-wrap"><table id="tbl1">
<thead><tr>
<th style="width:38px">#</th>
<th class="req" data-h="内部料号">内部料号 internal_pn<span class="thd">全链路统一基准,不可自由文本</span></th>
<th class="req" data-h="品牌名称">品牌 brand_name<span class="thd">与制造商、供应商分开</span></th>
<th data-h="制造商名称">制造商 manufacturer_name<span class="thd">与品牌不一致时分开记录</span></th>
<th class="req" data-h="规格描述">规格描述 specification_description<span class="thd">按原厂资料口径</span></th>
<th class="req" data-h="封装">封装 package<span class="thd">影响硬约束比对</span></th>
<th data-h="工艺">工艺 process_technology<span class="thd">硬约束比较项</span></th>
<th class="req" data-h="生命周期状态">生命周期 lifecycle_status<span class="thd">Active/NRND/EOL/停产/风险</span></th>
<th data-h="EOL/PCN状态">EOL/PCN eol_pcn_status<span class="thd">通知/影响/生效时间</span></th>
<th data-h="Datasheet引用">Datasheet 引用 datasheet_ref</th>
<th data-h="Datasheet版本">Datasheet 版本 datasheet_version<span class="thd">与引用成对</span></th>
<th data-h="适配应用场景">适配场景 application_scenarios<span class="thd">判断实际适用性</span></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 data-h="EOL/PCN状态">EOL/PCN</th>
<th data-h="Datasheet引用">Datasheet 引用</th>
<th data-h="Datasheet版本">Datasheet 版本</th>
<th data-h="适配应用场景">适配场景</th>
</tr></thead>
<tbody data-rows="mine:parts.profile" 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>
@@ -354,7 +366,7 @@ const P08_TABLE = `<div class="jc-tbl-wrap"><table id="tbl1">
const P08_PARAMS_TABLE = `<div class="jc-tbl-wrap"><table id="tbl2">
<thead><tr>
<th style="width:38px">#</th>
<th class="req" data-h="所属料号">所属料号 internal_pn<span class="thd">归属表 1 哪条记录</span></th>
<th class="req" data-h="所属料号">所属料号</th>
<th class="req" data-h="参数名">参数名</th>
<th class="req" data-h="值">值</th>
<th data-h="单位">单位</th>
@@ -408,15 +420,15 @@ export const P08_MASTER_CREATE = doc({
const P10_TABLE = `<div class="jc-tbl-wrap"><table>
<thead><tr>
<th style="width:38px">#</th>
<th class="req" data-h="关联料号">关联料号 internal_pn<span class="thd">对应 P-08 那条记录</span></th>
<th class="req" data-h="资料序号">资料序号 doc_seq<span class="thd">同一料号下 01/02… 自己续</span></th>
<th class="req" data-h="资料类型">资料类型 document_type</th>
<th data-h="品牌名称">品牌 brand_name</th>
<th class="req" data-h="资料标题">资料标题 document_title</th>
<th class="req" data-h="资料版本">资料版本 document_version</th>
<th class="req" data-h="文件链接">文件链接 document_ref</th>
<th data-h="证据定位">证据定位 citation_locator<span class="thd">页码/章节/表格</span></th>
<th class="req" data-h="资料状态">资料状态 document_status</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>