feat(w05): Task 7b —— 件① 补「新建芯片类型」,零平台改动

类型下拉里没有合适的,从前只有一句 guide 文字,没有可落笔的地方(parts.chip_type 全无
编辑面;parts.type_param 有个「加一行」但零行时 ViewContainer.addRow 找不到 tr:last-of-type
可克隆,按钮到不了岸)。

查证 Step 1 的坎(autoKeys 跨行自增键联动通不通)后确认:同一次保存请求内没有机制把父行
刚铸出来的自增键传给同批的子行,这条联动本身不需要通 —— 改成两步流程绕开(新建类型先保存,
type_id 铸出来后立刻出现在 data-options 现读的下拉里;定义核心参数字段时按类型名称选,
不必照抄编号)。

深挖过程中揪出两个从未被触发过的平台坑,根因都是 parts.chip_type.type_id 是本仓唯一
「auto:true 又是 integer」的主键:① resolveRowKey 的「③ 续号」步骤只认 isSeq(declared
type)不认 auto,会抢在 writeEditedRows 的 autoKeys() 前面把新行按草稿行数续号,撞上种子
数据 type_id=1/2(把「DC-DC 降压稳压器」悄悄改名);② autoKeys() 补零的字符串('03')经
引用列的 coerceToColumn(_,'integer') 会被 Number() 削掉前导零,refersTo/data-by 的字符串
比对全部失配。两处坑统一改声明(type_id 自身 + 五处引用列共 6 处,integer→text)一次性
绕开,desk.ts/ViewContainer.tsx/view-render.ts 零改动。

实现:parts.chip_type 新增可编辑表(动态行 + 一条不受 data-rows 管辖的静态种子行,
data-edit 两处写法保证 collect()/.on 切换与 renderRows 的 editable 检测都够得着);
parts.type_param 的 type_id 列从裸文本输入换成下拉(选项现读 parts.chip_type);两张表
各配一条静态种子行解决零行加不出第一行的问题。9 条新测试 + 3 条既有断言更新,变异验证
逐条附证据(拿掉种子行 / 拿掉 text 声明 / 拿掉下拉分别验证目标测试变红,过程中发现并修正
一条子串包含式断言测不出「只剩一份」的退化)。packageVersion 2.0.0→2.1.0。

pnpm gates 退出码 0(2410 passed | 34 skipped);E04/I01 包内测试同批全绿(92/92、55/55)—
本任务未改共享面,理论上不需要专门回归,仍按纪律核过。

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 23:19:55 +08:00
co-authored by Claude Opus 5
parent a408542bed
commit d9bcce9516
3 changed files with 243 additions and 44 deletions
@@ -54,7 +54,7 @@ export const manifest: ActivityManifest = {
activityId: ACTIVITY_ID,
name: 'ACT-WORK-05 · 差异评估',
/* 重写不是改 —— 同版重装是 no-op(静默丢失,踩过) */
packageVersion: '2.0.0',
packageVersion: '2.1.0',
category: '需求分析与方案匹配',
sdkVersion: '0.1.0-m5',
checksum: 'dev-checksum-part-substitution-assessment',
@@ -219,7 +219,29 @@ export const manifest: ActivityManifest = {
kind: 'master',
label: '芯片类型字典',
fields: [
col({ name: 'type_id', type: 'integer', label: '类型编号', primaryKey: true, auto: true }),
/*
* **`type_id` 声明成 `text` 而不是 `integer`**(Task 7b,2026-08-29,件① 补「新建类型」
* 逼出来的两处平台级发现,记在这里免得下一个人重踩):
* ①`resolveRowKey`(`apps/api/src/app/routes/desk.ts`)的「③ 续号」只认 `isSeq`
* (declared type 是 integer/number),不看 `auto` —— 这个类型碰巧是**唯一**一个
* `auto:true` 又是 `integer` 的主键,于是它抢在 `writeEditedRows` 的 `autoKeys()`
* (真正的续号器,数official+草稿叠加、不会撞号)前面,自己按「本单草稿有几行」
* 铸了个从 1 起的号——种子已经占了 `type_id=1/2`,人第一次新建类型就会撞车,
* 把种子的「DC-DC 降压稳压器」悄悄改名。②即便①不发生,`autoKeys()` 的续号是
* 两位补零字符串(`'03'`),而**引用它的那几列**(`parts.profile.chip_type_id` 等)
* 若声明成 `integer`,写入时会被 `coerceToColumn` 按 `Number()` 解析掉前导零——
* `'03'` 存成 `parts.chip_type.type_id`、`3` 存成 `parts.profile.chip_type_id`,
* `refersTo`/`data-by` 那几处按 `String(...)` 比对的查找会全部失配(`'03' !== '3'`),
* 写入当场被 `refersTo` 拒、或下拉/查表显示不出人刚建的类型。
* **两处病根都是「integer 类型的自增列」这个此前从未出现过的组合触发的**——本仓
* 唯一先例 `contact_code` 一直是 `text`。改成 `text`(此列与下面 5 处同引用列一起改,
* 保持同一条身份链路全程一种类型)对存储没有影响(台账是 JSONB,没有物理列类型),
* 只是把逻辑类型改得诚实;S-FEEL 那几处只做等值/去重比较,不做数值运算,不受影响
* (已逐处核对:distinct 计数 / lookup·take / refersTo 皆按字符串比,`>` 只用在
* `distinct_chip_type_id` 这个计数列上,不用在值本身)。查证过程详见
* Task 7b 报告(`.superpowers/sdd/2026-08-29-act-work-05-rewrite/task-7b-report.md`)。
*/
col({ name: 'type_id', type: 'text', label: '类型编号', primaryKey: true, auto: true }),
col({ name: 'type_name', type: 'text', label: '类型名称', required: true }),
col({ name: 'description', type: 'text', label: '类型说明', expect: true }),
],
@@ -230,7 +252,8 @@ export const manifest: ActivityManifest = {
kind: 'master',
label: '芯片类型核心参数字段',
fields: [
col({ name: 'type_id', type: 'integer', label: '所属类型', primaryKey: true, refersTo: 'parts.chip_type.type_id' }),
/* 与 `parts.chip_type.type_id` 同一条身份链路,同一个理由改成 text——见上面那条大注释 */
col({ name: 'type_id', type: 'text', label: '所属类型', primaryKey: true, refersTo: 'parts.chip_type.type_id' }),
col({ name: 'param_name', type: 'text', label: '参数名', primaryKey: true }),
/* AI 分析时读它 —— 这一格说清「这个参数是什么意思」,是 S2 唯一一次 analyze 出场的入参之一 */
col({ name: 'param_desc', type: 'text', label: '参数说明', required: true }),
@@ -254,7 +277,8 @@ export const manifest: ActivityManifest = {
label: '芯片主数据(P-08)',
fields: [
col({ name: 'internal_pn', type: 'text', label: '内部料号', primaryKey: true }),
col({ name: 'chip_type_id', type: 'integer', label: '芯片类型', required: true, refersTo: 'parts.chip_type.type_id' }),
/* 与 `parts.chip_type.type_id` 同一条身份链路,同一个理由改成 text —— 见该字段的大注释 */
col({ name: 'chip_type_id', type: 'text', label: '芯片类型', required: true, refersTo: 'parts.chip_type.type_id' }),
col({ name: 'brand_name', type: 'text', label: '品牌名称', required: true }),
col({ name: 'lifecycle_status', type: 'text', label: '生命周期状态', required: true, tally: ['active', 'nrnd', 'eol', 'discontinued', 'risk'] }),
col({ name: 'specification_description', type: 'text', label: '规格描述', expect: true }),
@@ -314,7 +338,8 @@ export const manifest: ActivityManifest = {
col({ name: 'exists_status', type: 'text', label: '在库状态', required: true, tally: ['found', 'absent'] }),
col({
name: 'chip_type_id',
type: 'integer',
/* 与 `parts.chip_type.type_id` 同一条身份链路,同一个理由改成 text —— 见该字段的大注释 */
type: 'text',
label: '芯片类型',
required: true,
/*
@@ -334,7 +359,8 @@ export const manifest: ActivityManifest = {
label: '本单案卷',
fields: [
col({ name: 'eval_ref', type: 'text', label: '评估案号', primaryKey: true }),
col({ name: 'chip_type_id', type: 'integer', label: '本单锁定的芯片类型' }),
/* 与 `parts.chip_type.type_id` 同一条身份链路,同一个理由改成 text —— 见该字段的大注释 */
col({ name: 'chip_type_id', type: 'text', label: '本单锁定的芯片类型' }),
col({ name: 'gate_reason', type: 'text', label: '入参未就绪原因(件上摆的就是它)' }),
col({ name: 'summary', type: 'text', label: '结论摘要' }),
],
@@ -354,7 +380,8 @@ export const manifest: ActivityManifest = {
col({ name: 'eval_ref', type: 'text', label: '评估案号', required: true }),
col({ name: 'original_internal_pn', type: 'text', label: '原内部料号', required: true, refersTo: 'parts.profile.internal_pn' }),
col({ name: 'candidate_internal_pn', type: 'text', label: '候选内部料号', required: true, refersTo: 'parts.profile.internal_pn' }),
col({ name: 'chip_type_id', type: 'integer', label: '芯片类型', required: true }),
/* 与 `parts.chip_type.type_id` 同一条身份链路,同一个理由改成 text —— 见该字段的大注释 */
col({ name: 'chip_type_id', type: 'text', label: '芯片类型', required: true }),
col({ name: 'recommendation_level', type: 'text', label: '推荐等级', required: true, tally: ['recommended', 'conditional', 'not_recommended'] }),
col({ name: 'recommendation_reason', type: 'text', label: '推荐理由', required: true }),
col({ name: 'difference_summary', type: 'text', label: '差异摘要', required: true }),
@@ -5,10 +5,11 @@
* 差异评估结论审核 / 评审放行与落账确认)按 spec §8 全部作废,S1 那一步不再分成
* 「先核对存在性 → 分别派 P-08/P-10 数据工单 → 再摆评估输入清单」三个局面 ——
* 新的四步案例模型只有一张数据工单卡(`variant: 'data'`),这里对应**一张**新局面
* `input-supplement`(评估输入补全):**三块竖排**(spec §6.1)——
* `input-supplement`(评估输入补全):**四块竖排**(spec §6.1,Task 7b 补第②a 块)——
* ① 要评估什么(两颗料的存在性 + 主数据,`sub_eval.part` 铺行 / `parts.profile` 承载编辑);
* ② 芯片类型(每颗料各自的类型选择,选项从 `parts.chip_type` 现读;
* 下面跟着新建类型时要填的核心参数字段清单 `parts.type_param`,供加行);
* ②a 芯片类型(每颗料各自的类型选择,选项从 `parts.chip_type` 现读;类型下拉里没有合适的
* 就在这里现新建一个,`parts.chip_type` 承载编辑);
* ②b 核心参数字段清单(新建类型时要填的字段清单,`parts.type_param` 承载编辑,供加行);
* ③ 参数值(逐条核心参数的实测 / 规格值,一字段一行,`parts.parameter`)。
* 顶部一条红带摆 S1 配方算好的 `gate_reason`(「还差什么」的一句话,配方 §see case-model.ts
* 的 step④),够不够齐、该不该出现由样式表判(§5「模版不许算」)。
@@ -17,6 +18,17 @@
* 结论审核」一节);`landing-confirm`(件③ 落账确认)已由 Task 9 重做并交付(见文件尾
* 「局面:落账确认」一节)—— 三张件此刻齐了。
*
* **Task 7b(2026-08-29)补②a 块**:件① 交付时「新建类型」只有一句 guide 文字,没有可
* 落笔的地方(类型下拉只能挑**已有**的)。零平台改动补齐(查证与三条候选路见 Task 7b 报告):
* `parts.chip_type` 与 `parts.type_param` 两张表新开局都是零行,`ViewContainer.addRow`
* 靠克隆最后一行添新行、零行时无样可克隆——两张表各加一个**不受 `data-rows` 管辖的
* 静态种子行**(不经 `cell()`/`data-slot`,字面量手写 `data-col` 输入格,详见
* `CHIP_TYPE_TABLE`/`TYPE_PARAM_TABLE` 头注)。查证过程中额外揪出并顺手治死两处平台级
* 潜伏坑(`resolveRowKey` 的续号步骤抢在 `autoKeys()` 续号器前面 / 引用列声明成 `integer`
* 会把补零字符串的前导零削掉致使 `refersTo` 失配)——两者的共同根因是`parts.chip_type.type_id`
* 是本仓**唯一**「auto:true 又是 integer」的主键,改声明成 `text`(与其六处同一身份链路的
* 引用列一起改)后两处坑自然绕开,详见 manifest.ts 该字段的大注释。
*
* 版面语言仍是「搬,不要照着做」(《HTML报告生成规范》§7):`P08_CSS`(逐字节抽自
* `docs/SS02-ACT-WORK-05-html/ACT-WORK-05-数据工单-01-芯片主数据创建.html`,
* 见 `./target-style.ts` 头注)是**唯一**的样式来源 —— 三块内容原来分属 P-08 数据工单 /
@@ -104,6 +116,15 @@ html.editable .jc-gapbar.v-0 { display: none; }
* 用 id 选择器把计数器重新点亮(id 特异度压过 P08_CSS 的类选择器)。
*/
#tbl1.on td.rn .jc-rn-num, #tbl3.on td.rn .jc-rn-num { display: inline; }
/*
* **新建行(Task 7b,件① 补「新建芯片类型」)**:parts.chip_type / parts.type_param
* 两张表新增一条**字面量静态行**(不走 data-rows,不受行数所限,ViewContainer.addRow
* 找不到 tr:last-of-type 可克隆时也照常在——这一条从来不指望被克隆出来,它本身就是种子)。
* 只在编辑态露面:读的时候看见一行「系统生成 / 占位提示」的空壳子是界面撒谎的另一种形状。
* table.on(与其余 .jc-cell/.jc-vcell 同一个开关)已经个别管住了里面每个输入格的显隐,
* 这条补的是**行本身**(#、静态提示文字这类不挂 .jc-cell 的内容不受那条规则约束)。
*/
table:not(.on) tr.jc-row-static { display: none; }
`;
/*
@@ -307,9 +328,66 @@ const PART_TABLE = `<div class="jc-tbl-wrap"><table id="tbl1">
</table></div>`;
/**
* 块② 芯片类型的核心参数字段清单 —— `parts.type_param`,复合主键 `type_id + param_name`。
* **供新建类型时加字段行**(唯一带「+加一行」/删行的一张):类型下拉里没有合适的,
* 人就在这里把新类型的核心参数字段一条条定下来(名字 / 说明 / 硬软约束 / 单位)。
* 块②a 新建芯片类型 —— `parts.chip_type`(Task 7b,2026-08-29 补齐)。类型下拉(块①)里
* 没有合适的,人在这里现造一个:类型名称(必填)+ 说明(建议)。`type_id` 是自增主键
* (`auto:true`,声明改 `text` 的理由见 manifest 那条大注释)—— **不进表单**,自增列
* 不问人(与联系人编码 `contact_code` 同一条纪律,2026-08-02 立);只读态摆 `ro()`,
* 连孪生输入框都不给。
*
* **零行照样加得出第一行**(这是这一块存在的全部理由):`mine:parts.chip_type` 每单
* 开局是零行,`ViewContainer.addRow` 靠克隆 `tr:last-of-type` 添新行,零行时找不到可
* 克隆的样子(查证见 Task 7b 报告)。这里**多一个不受 `data-rows` 管辖的静态 `<tbody>`**,
* 里面字面量摆一行输入格(不经 `cell()`/`data-slot`,直接手写 `<input data-col=…>`)——
* 它不是数据行,不受「零行」影响,永远在那儿;填了按保存,`writeEditedRows` 认得出
* 「主键全空 + 别的列有值」这个形状(`mintable`),转给 `autoKeys()` 续号,和别处的
* `contact_code` 走的是同一条路。填过一次之后 `mine:parts.chip_type` 就有行了,
* 上面那张动态表本身的「+加一行」(参见 addrow('parts.chip_type', …))也就跟着能用。
*
* `data-edit="parts.chip_type"` **两处都写**——外层 `<table>` 一处,内层 `data-rows`
* 那个 `<tbody>` 一处:`renderRows` 判「这组行可编辑吗」只认与 `data-rows` **同一个标签**
* 自带的属性(不看祖先),内层那份是留给它看的;`ViewContainer.collect()`/`.on` 切换按
* `document.querySelector('[data-edit="…"]')` 找**唯一**一个宿主,要同时收到「已有行」
* 与「新建那一行」两处的输入,宿主必须是两者共同的祖先,外层那份是留给它看的。
* `.on` 落点用 `host.closest('table')`——外层就是 `<table>` 自身,`closest` 命中自己,
* 落的还是同一张 `<table>`,与从前只在 `<tbody>` 上挂 `data-edit` 时完全一致,CSS 不必改。
*/
const CHIP_TYPE_TABLE = `<div class="jc-tbl-wrap"><table data-edit="parts.chip_type">
<thead><tr>
<th style="width:38px">#</th>
<th data-h="类型编号">编号</th>
<th class="req" data-h="类型名称">类型名称</th>
<th data-h="类型说明">类型说明</th>
</tr></thead>
<tbody data-rows="mine:parts.chip_type" data-edit="parts.chip_type"><tr>
<td class="rn"><span class="jc-rn-num"></span></td>
<td>${ro('type_id', 'mono')}</td>
<td>${cell('type_name')}</td>
<td>${cell('description')}</td>
</tr></tbody>
<tbody><tr class="jc-row-static">
<td class="rn"><span class="jc-rn-num"></span></td>
<td><span class="jc-vcell ro">系统生成</span></td>
<td><input class="jc-cell" data-col="type_name" placeholder="新类型名称"></td>
<td><input class="jc-cell" data-col="description" placeholder="这个类型是什么(供选型参考)"></td>
</tr></tbody>
</table></div>`;
/**
* 块②b 芯片类型的核心参数字段清单 —— `parts.type_param`,复合主键 `type_id + param_name`。
* **供新建类型时加字段行**:块②a 新建的类型(或已有类型)在这里把核心参数字段一条条
* 定下来(名字 / 说明 / 硬软约束 / 单位)。
*
* **`type_id` 改成下拉(Task 7b)**:从前是裸文本输入(`cell('type_id','mono')`),等于
* 要人记住/照抄一个此刻还不存在、要等保存那一刻才由 `autoKeys()` 铸出来的自增号——
* 那是在问人一件他答不上来的事。改成与块①同款的 `optionSelect`(选项从 `parts.chip_type`
* 现读,人挑的是**类型名称**):块②a 新建的类型一旦保存,这份下拉的选项就会带着它
* (`data-options` 现读,与写死选项那条老毛病同一课)——**先建类型、保存,新类型立刻
* 出现在这份下拉里,不必谁记住那个号**,「新建类型 → 定它的字段」这条链路因此不需要
* 同一次保存里就把父行的自增键传给子行(那条路要基座补一层「跨行自增键联动」,
* 查证过没有必要:换个问法——选下拉而不是记数字——链路一样打通,且零平台改动)。
*
* 新增一行零行也加得出**的静态种子行**(与块②a 同一套手法,详细道理见那边的大注释)——
* 这张表本就是**唯一带「+加一行」/删行的一张**,零行时那颗按钮同样够不到岸,种子行同样补。
*
* **不挂 `id="tbl2"`**:P08_CSS 的 `#tbl2 th:nth-child(n)` 那几条列宽规则是按位置
* (nth-child)写死给旧版 `parts.parameter` 表(所属料号/参数名/值/单位/约束/说明,7 列)调的,
@@ -317,10 +395,10 @@ const PART_TABLE = `<div class="jc-tbl-wrap"><table id="tbl1">
* 位置 5、6 恰好被那两条老规则套反(约束套上「单位」的宽度、参考单位套上「约束」的宽度)。
* 不挂 id 就不会继承这几条位置写死的规则,退回普通 `table`/`th`/`td` 基础样式。
*/
const TYPE_PARAM_TABLE = `<div class="jc-tbl-wrap"><table>
const TYPE_PARAM_TABLE = `<div class="jc-tbl-wrap"><table data-edit="parts.type_param">
<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 class="req" data-h="参数说明">参数说明</th>
<th class="req" data-h="约束类型">约束(硬/软)</th>
@@ -328,12 +406,23 @@ const TYPE_PARAM_TABLE = `<div class="jc-tbl-wrap"><table>
</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>
<span class="jc-vcell"><span data-rows="parts.chip_type" data-by="type_id=type_id"><i data-slot="type_name" data-blank></i></span></span>
${optionSelect('type_id', 'parts.chip_type', 'type_id', 'type_name', true)}
</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>
<tbody><tr class="jc-row-static">
<td class="rn"><span class="jc-rn-num"></span></td>
<td>${optionSelect('type_id', 'parts.chip_type', 'type_id', 'type_name', true)}</td>
<td><input class="jc-cell" data-col="param_name" placeholder="参数名"></td>
<td><input class="jc-cell" data-col="param_desc" placeholder="这个参数为什么要紧"></td>
<td><select class="jc-cell" data-col="constraint_type"><option value=""></option><option value="hard">硬约束</option><option value="soft">软约束</option></select></td>
<td><input class="jc-cell" data-col="param_unit" placeholder="单位(选填)"></td>
</tr></tbody>
</table></div>`;
/**
@@ -390,18 +479,23 @@ export const INPUT_SUPPLEMENT = doc({
'<span class="need">模板定位</span>:本单是「数据工单」——目标台账 = 芯片主数据(P-08 主记录 + 标准化参数子表),核心参数字段清单挂在芯片类型字典上。',
'<span class="need">触发原因</span>:下面【要评估什么】里两行是本次评估要比对的原 PN 与候选 PN —— 至少有一颗不在库、两颗归的类型不一致、或必填格还空着,下面这条红带才会出现。',
'<span class="done">已绑定</span>:两颗料的内部料号已按启动参数铺进下表,不必你重填料号本身,只需核对 / 补齐其余各格;在库的料现值已回填,只是查漏补缺。',
'<span class="need">纪律</span>:两颗料必须归到同一个芯片类型下才谈得上逐项比对;类型下拉里没有合适的就新建一个,并把它的核心参数字段一并定下来;查不到的参数值留空(会被红带点名),不要编一个值顶替。',
'<span class="need">纪律</span>:两颗料必须归到同一个芯片类型下才谈得上逐项比对;类型下拉里没有合适的就在【芯片类型】区新建一个,并把它的核心参数字段一并定下来;查不到的参数值留空(会被红带点名),不要编一个值顶替。',
]) +
gateBar() +
zone('要评估什么', '两颗料的存在性与主数据(每行一颗,人不加行,*为必填列)', ZIC_TABLE) +
editGate('parts.profile', '两颗料的主数据核对 / 补齐,点「编辑」开表') +
gapbar('parts_profile', '必填') +
PART_TABLE +
zone('芯片类型', '两颗料所归类型的核心参数字段清单(供新建类型时补齐,*为必填列)', ZIC_TABLE) +
editGate('parts.type_param', '字段名字 / 说明 / 硬软约束,点「编辑」核对;新建类型用「加一行」补字段') +
zone('芯片类型', '上面下拉选不到合适的类型,就在这里新建一个(选填,*为必填列)', ZIC_TABLE) +
editGate('parts.chip_type', '新建一个类型:表尾那一行填类型名称与说明,保存后立刻出现在上面的下拉里') +
gapbar('parts_chip_type', '必填') +
CHIP_TYPE_TABLE +
addrow('parts.chip_type', '+ 再建一个类型', '两颗料若都要新建、且类型不同,先各自保存,「加一行」能再建一个') +
zone('核心参数字段', '这个类型逐条比对时要看哪些参数(新建类型时必填,*为必填列)', ZIC_TABLE) +
editGate('parts.type_param', '字段名字 / 说明 / 硬软约束,点「编辑」核对;表尾那一行 / 「加一行」补字段') +
gapbar('parts_type_param', '必填') +
TYPE_PARAM_TABLE +
addrow('parts.type_param', '+ 加一行(补一个核心参数字段)', '所属类型编号 / 参数名 / 参数说明 / 约束类型必须写;参考单位选填') +
addrow('parts.type_param', '+ 加一行(补一个核心参数字段)', '所属类型 / 参数名 / 参数说明 / 约束类型必须写;参考单位选填') +
zone('参数值', '逐条核心参数的实测 / 规格值(一字段一行,*为必填列;单位由字典定,这里不改)', ZIC_TABLE) +
editGate('parts.parameter', '逐条参数值核对 / 补齐,点「编辑」开表') +
gapbar('parts_parameter', '必填') +