Archived
feat(w05): 按卡独立编辑 + 缺项点名并进顶条 + 区序换回 + 类型层叠图
2026-08-31 用户四连: ① 「各自编辑各自的部分,不要做联动」—— 平台新增 scopedTargets(ui-kit):开合作用域 跟着触发钮走,三档 —— 钮在本地址宿主的行里→只这一行(A 卡的笔只开 A 卡);钮在别的 宿主的行里→行内属于本地址的宿主(这张卡的卡背);外部闸行→全宿主(老行为,② 的闸 不受影响)。收值与保存不随作用域收窄(collect 仍按地址全量,「只写真改了的行」在 服务端把关)。编辑态/锁面/笔盘显隐 CSS 全部改成按卡(.dossier.on);同地址同时只开 一张卡(笔在别卡编辑中自动隐去,快照键按地址,双开会互相踩)。2 测 + 变异验证。 ② 逐块「还差 N 行必填」红带撤下,点名并进顶部「还差什么」—— 同一批基座读数 (_missing/_missing_keys)在顶条出两行子行,数值 0 自己消失;gate_reason 改口。 ③ 比对基准与芯片档案区序换回(基准在前)。 ④ 比对基准换「层叠双封装」SVG(后面那枚淡出成剪影 —— 一类而非一颗),不再与芯片卡 共用同一枚封装图。 测:ui-kit 35(scoped 2 新,变异红过)+ 包内 105 + 2528 全仓;2.11.0 装库五探针核过 (合并条/无旧红带/类型图/按卡 CSS/基准在前);浏览器实测 XL 卡独自翻背进编辑、LM 卡纹丝不动。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
This commit is contained in:
@@ -12,12 +12,13 @@
|
||||
*/
|
||||
// @vitest-environment jsdom
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { collect, mirrorTwins, setOpenIn } from '../src/ViewContainer.tsx';
|
||||
import { collect, mirrorTwins, scopedTargets, setOpenIn } from '../src/ViewContainer.tsx';
|
||||
|
||||
/** 两张芯片卡:正面是主数据格(profile),背面各住一个参数宿主(parameter) */
|
||||
const FLIP_CARDS =
|
||||
'<article data-edit="parts.profile">' +
|
||||
'<section class="dossier">' +
|
||||
'<button class="pen-a" data-edit-open="parts.profile"></button>' +
|
||||
'<div class="front"><input data-col="internal_pn" value="LM2596S-5.0"/><input data-col="brand_name" value="TI"/></div>' +
|
||||
'<div class="back"><section class="pback" data-edit="parts.parameter">' +
|
||||
'<div class="ptile"><input data-col="internal_pn" value="LM2596S-5.0"/><input data-col="param_name" value="输出电压"/><input data-col="param_value" value="5.0"/></div>' +
|
||||
@@ -101,3 +102,28 @@ describe('mirrorTwins:下拉镜像的是选项文案,不是 value(2026-08-31「
|
||||
expect(doc.querySelector('.tsel')?.textContent).toBe('旧名');
|
||||
});
|
||||
});
|
||||
|
||||
describe('scopedTargets:开合作用域跟着触发钮走(2026-08-31「不要做联动,各自编辑各自的部分」)', () => {
|
||||
it('钮在本地址宿主的行里 → 只这一行(A 卡的笔只开 A 卡);钮对别的地址 → 只这张卡里那个宿主', () => {
|
||||
const doc = docOf(FLIP_CARDS);
|
||||
const pen = doc.querySelector('.pen-a') as HTMLElement;
|
||||
// ① profile:钮在 profile 宿主里 → 落到它所在的那一行(第一张卡的 section)
|
||||
const t1 = scopedTargets(doc, 'parts.profile', pen);
|
||||
expect(t1).toHaveLength(1);
|
||||
expect(t1[0]?.className).toBe('dossier');
|
||||
expect(t1[0]?.contains(pen)).toBe(true);
|
||||
// ② parameter:钮不在任何 parameter 宿主里,但在 profile 的行里 → 行内那一个 pback,不是两个
|
||||
const t2 = scopedTargets(doc, 'parts.parameter', pen);
|
||||
expect(t2).toHaveLength(1);
|
||||
expect(t2[0]?.classList.contains('pback')).toBe(true);
|
||||
expect(t1[0]?.contains(t2[0] ?? null)).toBe(true);
|
||||
});
|
||||
|
||||
it('外部闸(不在任何宿主/行里)→ 全部宿主,老行为', () => {
|
||||
const doc = docOf(FLIP_CARDS);
|
||||
const gate = doc.querySelector('[data-edit-for="parts.parameter"]') as HTMLElement;
|
||||
const t = scopedTargets(doc, 'parts.parameter', gate);
|
||||
expect(t).toHaveLength(2);
|
||||
expect(t.every((h) => h.classList.contains('pback'))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -365,15 +365,45 @@ export function addRow(doc: Document, address: string): boolean {
|
||||
}
|
||||
|
||||
/**
|
||||
* 那块内容与它的闸一起开合 —— **同一地址的全部宿主一起**(多宿主时一把闸管一整张表)。
|
||||
* `.on` 落在哪:行容器是表格的落在 `<table>`(老件,样式按 `table.on` 写);
|
||||
* 不是表格的落在 `data-edit` 元素自身(2026-08-02 A2 对决卡立的规矩)。
|
||||
* 那块内容与它的闸一起开合。`.on` 落在哪:行容器是表格的落在 `<table>`(老件,样式按
|
||||
* `table.on` 写);不是表格的落在 `data-edit` 元素自身(2026-08-02 A2 对决卡立的规矩)。
|
||||
* 这一版是**全宿主**开合(外部闸行用它);按了卡上钮的走 `scopedTargets`(见下)。
|
||||
*/
|
||||
export function setOpenIn(doc: Document, address: string, on: boolean): void {
|
||||
doc.querySelector<HTMLElement>(`[data-edit-for="${CSS.escape(address)}"]`)?.classList.toggle('on', on);
|
||||
for (const host of hostsOf(doc, address)) (host.closest('table') ?? host).classList.toggle('on', on);
|
||||
}
|
||||
|
||||
/**
|
||||
* **开合的作用域跟着触发钮走**(2026-08-31 用户:「芯片档案编辑就不能各自编辑各自的部分吗,
|
||||
* 不要做联动」)。三档,从近到远:
|
||||
* ① 钮长在这张表**自己的宿主**里 —— 落到钮所在的**那一行**(宿主的直接子元素;
|
||||
* W05 芯片卡的笔落在这张卡的行上,只这张卡进编辑);行认不出就整个宿主;
|
||||
* ② 钮长在**别的**宿主的某一行里 —— 落到**这一行内部**属于本地址的宿主们
|
||||
* (W05 卡背的参数宿主住在主数据宿主的行里:点这张卡的笔,只这张卡的卡背开);
|
||||
* ③ 都不是(外部闸行,如差异审核件的编辑闸)—— 全部宿主一起,老行为。
|
||||
* 收值与保存**不随作用域收窄**(collect 仍按地址全量;「只写真改了的行」在服务端把关,
|
||||
* 没动的行是 no-op)—— 作用域只管「屏上哪一块进编辑态」这一件事。
|
||||
*/
|
||||
export function scopedTargets(doc: Document, address: string, trigger: Element): HTMLElement[] {
|
||||
const hosts = hostsOf(doc, address);
|
||||
for (const host of hosts) {
|
||||
if (host.contains(trigger)) {
|
||||
const row = rowOf(host, trigger);
|
||||
return [(row as HTMLElement | null) ?? host];
|
||||
}
|
||||
}
|
||||
/* 钮在别的宿主的行里:找出包住钮的那一行(任意 data-edit 宿主的直接子元素),取行内的本地址宿主 */
|
||||
for (let n: Element | null = trigger; n !== null; n = n.parentElement) {
|
||||
const parent = n.parentElement;
|
||||
if (parent !== null && parent.hasAttribute('data-edit')) {
|
||||
const inner = hosts.filter((h) => n !== null && n.contains(h));
|
||||
if (inner.length > 0) return inner;
|
||||
}
|
||||
}
|
||||
return hosts.map((h) => (h.closest('table') ?? h) as HTMLElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* `data-file="域.对象/主键值"` → 取文件要的那两样。
|
||||
*
|
||||
@@ -514,7 +544,10 @@ export function ViewContainer({ html, title = '活动视图', editable = false,
|
||||
* 容器是 `<section>`,从前这里写死 `closest('table')`,非表格容器永远拿不到
|
||||
* `.on`,编辑态整个打不开)。
|
||||
*/
|
||||
const setOpen = (address: string, on: boolean): void => setOpenIn(doc, address, on);
|
||||
const setOpen = (address: string, on: boolean): void => {
|
||||
doc.querySelector<HTMLElement>(`[data-edit-for="${CSS.escape(address)}"]`)?.classList.toggle('on', on);
|
||||
for (const target of scopedTargets(doc, address, hit)) (target.closest('table') ?? target).classList.toggle('on', on);
|
||||
};
|
||||
|
||||
const open = hit.dataset['editOpen'];
|
||||
const cancel = hit.dataset['editCancel'];
|
||||
|
||||
Reference in New Issue
Block a user