feat(w05): 数据工单去表格化 —— 翻面芯片卡(背面参数就地改)+ 基准瓷贴卡;平台多宿主收值

2026-08-31 用户第三轮:「界面上东西太多,做成几个区域;类型与核心参数字段做成和芯片卡
差不多的卡片;芯片卡与它的核心参数值合并 —— 点击翻转,背面写核心参数,悬浮要看得出能翻;
这个界面都不再需要表格来展示数据了」。

视图(主画面零表格,表格只活在新建类型抽屉里 —— 表单要加行,加行机制认 tr):
· 比对基准卡:类型丸 + 描述 + 字段瓷贴(名/硬软徽/单位/说明),与芯片卡同语言;
· 芯片卡翻面:正面主数据、背面本颗芯片的核心参数瓷贴(值必填红 / 说明建议橘)。
  翻转纯 CSS(checkbox + :has,零脚本;checkbox 无 data-col,collect 不认它),
  悬浮出「点击翻面 · 核心参数」提示丸 + 侧倾预告;主数据编辑锁正面、参数编辑自动
  翻背面(reduced-motion 下瞬时切换);说明四条收进折叠。

平台(ui-kit ViewContainer,多宿主收值):「参数值印在每张卡背、就地可改」= 同一张表
的行分居两张卡 —— 一个地址一个宿主装不下。hostsOf(querySelectorAll)+ 收值/开合/
镜像/快照全部按宿主清单走;**格子只归最近的 data-edit 祖先**(不然卡背参数会被主数据
的闸收走并进主数据行 —— collect-nested 钉过的静默丢数据换脸回来)。嵌套 data-rows
不带 data-edit 时行为原样(两条旧钉测不动);addRow 仍单宿主(会加行的表都是单宿主)。

途中踩了三脚都逮回:CSS 注释里写了「data-col=」原文把 ②③ 的「全只读」字面断言踩翻
(选择器与注释都不许出现);.flip-cue i 特异度压过 .cue-back{display:none} 两枚提示
丸同时冒出;host 计数断言错把渲染后的数当模版源码的数。

测:ui-kit multihost 3 条(排除规则变异验证)+ 包内 110(翻面/无表格/瓷贴新锚)+
2528 全仓四门绿;2.9.0 装库 SQL 核过(flip/back_host/ftiles/cue/主画面表数=2);
浏览器实测:点卡真翻(背面 5 瓷贴)、参数编辑自动翻背双卡成表单、LM 全值回显。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
This commit is contained in:
xcs
2026-08-31 15:36:39 +08:00
co-authored by Claude Fable 5
parent 794af58902
commit 66be5bab6e
6 changed files with 308 additions and 124 deletions
@@ -570,7 +570,7 @@ describe('件① 评估输入补全', () => {
expect(html).toContain('data-rows="mine:parts.profile" data-edit="parts.profile"');
expect(html).not.toContain('data-rows="mine:sub_eval.part" data-edit="parts.profile"');
const nested = [...html.matchAll(/<span data-rows="mine:sub_eval\.part" data-by="internal_pn=internal_pn">((?:(?!<\/span>)[\s\S])*?)<\/span>/g)];
expect(nested.length).toBeGreaterThanOrEqual(2);
expect(nested.length).toBeGreaterThanOrEqual(1);
for (const m of nested) expect(m[1] ?? '').not.toContain('data-col');
});
@@ -597,20 +597,50 @@ describe('件① 评估输入补全', () => {
/* 全只读:选类型在档案卡,建类型在抽屉 —— 基准卡上一个编辑面都不许有 */
expect(basis).not.toContain('data-col');
expect(basis).not.toContain('data-edit');
/* 抽屉存在,且两张编辑表都住在里面 */
/* 基准卡不再有表格:类型 + 字段全是瓷贴 */
expect(basis).not.toContain('<table');
expect(basis).toContain('class="ftiles"');
/* 抽屉存在,且两张编辑表都住在里面(说明折叠用的是 lab lab-brief,不会被认成这一个) */
const labAt = html.indexOf('<details class="lab">');
const labEnd = html.indexOf('</details>');
const labEnd = html.indexOf('</details>', labAt);
expect(labAt).toBeGreaterThanOrEqual(0);
expect(html.indexOf('<table data-edit="parts.chip_type">')).toBeGreaterThan(labAt);
expect(html.indexOf('<table data-edit="parts.type_param">')).toBeGreaterThan(labAt);
expect(labEnd).toBeGreaterThan(html.indexOf('<table data-edit="parts.type_param">'));
});
it('三级占位:必填红 / 建议橘 —— 空格子直说要不要填(档案卡与参数值表两处)', () => {
/*
* 2026-08-31 用户第三轮:「芯片卡片需要和该芯片的核心参数值合并…点击后卡片翻转,背面写
* 核心参数,悬浮要看得出可以翻;这个界面都不再需要表格来展示数据」。
* 背面就地可改 = 两张卡背各住一个 parts.parameter 宿主 —— 多宿主收值是同日的平台扩面
* (ViewContainer hostsOf + 格子只归最近的 data-edit 祖先,ui-kit multihost 测试钉着)。
*/
it('翻面卡:背面住着参数宿主、按料过滤(渲染时随行复制成每卡一个 —— 多宿主收值由 ui-kit multihost 测试钉);翻转是无 data-col 的 checkbox;编辑态锁面', () => {
expect((html.match(/data-edit="parts\.parameter"/g) ?? []).length).toBe(1);
expect(html).toContain('data-rows="mine:parts.parameter" data-by="internal_pn=internal_pn" data-edit="parts.parameter"');
const flip = /<input class="flip-tg"[^>]*>/.exec(html)?.[0] ?? '';
expect(flip).not.toBe('');
expect(flip).toContain('type="checkbox"');
expect(flip).not.toContain('data-col');
expect(html).toContain('class="face front"');
expect(html).toContain('class="face back"');
/* 编辑锁面:主数据编辑停正面,参数编辑翻背面 —— 判断全在样式表 */
expect(html).toContain('.dossiers.on .dossier .flip-in{transform:rotateY(0deg)}');
expect(html).toContain('.dossier:has(.pback.on) .flip-in{transform:rotateY(180deg)}');
/* 悬浮提示丸:看得出点击可翻 */
expect(html).toContain('点击翻面');
});
it('主画面无表格:说明折叠之前的正文一张 <table 都没有 —— 表格只活在抽屉里(表单要加行,加行机制认 tr)', () => {
const beforeDrawer = html.slice(0, html.indexOf('<details class="lab">'));
expect(beforeDrawer).not.toContain('<table');
});
it('三级占位:必填红 / 建议橘 —— 空格子直说要不要填(档案卡正面与卡背参数瓷贴两处)', () => {
expect(html).toContain('.fcell.req .jc-vcell:empty::after{content:"必填"');
expect(html).toContain('.fcell.sug .jc-vcell:empty::after{content:"建议"');
expect(html).toContain('td.pv .jc-vcell:empty::after{content:"必填"');
expect(html).toContain('td.sg .jc-vcell:empty::after{content:"建议"');
expect(html).toContain('.ptile .pv .jc-vcell:empty::after{content:"必填"');
expect(html).toContain('.ptile .sg .jc-vcell:empty::after{content:"建议"');
});
it('芯片类型是从台账现读的下拉 —— 写死等于「只能选出包那天就有的那几种」(块①的 chip_type_id 与块②b 的 type_id 都是)', () => {
@@ -769,14 +799,14 @@ describe('件① 评估输入补全', () => {
* 只读态与编辑态下「#」列逐行都是空白。钉死 `.jc-tbl-wrap` 现在也接了这五条规则,
* 且这张件确实只用 `.jc-tbl-wrap`(没有表在用没打这条补丁就会数不出来的 `.tbl-wrap`)。
*/
it('四张表的序号列真的会数:.jc-tbl-wrap 也接了 CSS 计数器(不再只认 .tbl-wrap)', () => {
it('抽屉里两张表的序号列真的会数:.jc-tbl-wrap 也接了 CSS 计数器(不再只认 .tbl-wrap)', () => {
expect(html).toMatch(/\.jc-tbl-wrap\s+table\s*,\s*\.tbl-wrap\s+table\s*\{\s*counter-reset:\s*rn;?\s*\}/);
expect(html).toMatch(/\.jc-tbl-wrap\s+table\s+td\.rn\s*,\s*\.tbl-wrap\s+table\s+td\.rn\s*\{\s*counter-increment:\s*rn;?\s*\}/);
expect(html).toMatch(/\.jc-tbl-wrap\s+table\s+td\.rn\s+\.jc-rn-num::before\s*,\s*\.tbl-wrap\s+table\s+td\.rn\s+\.jc-rn-num::before\s*\{\s*content:\s*counter\(rn\);?\s*\}/);
// 四张:比对基准卡的字段表 + 抽屉里两张编辑表 + 参数值表(2026-08-31 类型/字段并成基准卡后的账)
// 只剩抽屉里两张(2026-08-31 第三轮:基准卡瓷贴化、参数值上卡背 —— 主画面零表格)
expect(html).not.toContain('class="tbl-wrap"');
const jcTblWrapCount = html.match(/class="jc-tbl-wrap"/g)?.length ?? 0;
expect(jcTblWrapCount).toBe(4);
expect(jcTblWrapCount).toBe(2);
});
});