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:
@@ -623,7 +623,8 @@ describe('件① 评估输入补全', () => {
|
||||
expect(html).toContain('class="face front"');
|
||||
expect(html).toContain('class="face back"');
|
||||
/* 编辑锁面:主数据编辑停正面,参数编辑翻背面 —— 判断全在样式表 */
|
||||
expect(html).toContain('.dossiers.on .dossier .flip-in{transform:rotateY(0deg)}');
|
||||
/* 按卡锁面(2026-08-31「不要做联动」):.on 由 scopedTargets 落在这张卡的行上 */
|
||||
expect(html).toContain('.dossier.on .flip-in{transform:rotateY(0deg)}');
|
||||
expect(html).toContain('.dossier:has(.pback.on) .flip-in{transform:rotateY(180deg)}');
|
||||
/* 悬浮提示丸:看得出点击可翻 */
|
||||
expect(html).toContain('点击翻面');
|
||||
@@ -713,7 +714,7 @@ describe('件① 评估输入补全', () => {
|
||||
/* 老的闸行不再出现在正文里(样式表里的基座规则给 ② 用,不算) */
|
||||
expect(html.slice(html.indexOf('</style>'))).not.toContain('jc-editgate');
|
||||
/* 笔↔软盘的按面显隐由样式表判 */
|
||||
expect(html).toContain('.dossiers.on .disk-f{display:grid}');
|
||||
expect(html).toContain('.dossier.on .disk-f{display:grid}');
|
||||
expect(html).toContain(".dossier:has(.pback.on) .disk-b{display:grid}");
|
||||
/* 参数瓷贴的输入孪生件带占位提示(渲染器认 data-ph) */
|
||||
expect(html).toContain('data-ph="规格书上的值 · 查不到留空"');
|
||||
@@ -788,6 +789,19 @@ describe('件① 评估输入补全', () => {
|
||||
expect(html).toContain('.jc-gate-bar:has(.jc-gate-text:empty)');
|
||||
});
|
||||
|
||||
/*
|
||||
* 2026-08-31 用户:「页面上所有的问题都合并到最上面的还差什么,不要在某一个板块显示
|
||||
* 类似还差 1 行必填没填这种提示」—— 逐块 gapbar 撤下,点名并进顶条(同一批基座读数,
|
||||
* 数值 0 的那行由样式表自己收起来)。
|
||||
*/
|
||||
it('缺项点名全并进顶条「还差什么」,正文不再有逐块红带', () => {
|
||||
expect(html).toContain('class="jc-gate-sub" data-class-of="parts_profile_missing"');
|
||||
expect(html).toContain('data-slot="parts_profile_missing_keys"');
|
||||
expect(html).toContain('data-slot="parts_parameter_missing_keys"');
|
||||
expect(html).toContain('html.editable .jc-gate-sub.v-0{display:none}');
|
||||
expect(html.slice(html.indexOf('</style>'))).not.toContain('jc-gapbar');
|
||||
});
|
||||
|
||||
it('表宽超出要能横着滑 —— 不许写成 overflow:hidden(E04/I01 抄错过一次)', () => {
|
||||
expect(html).not.toMatch(/\.jc-tbl-wrap\s*\{[^}]*overflow\s*:\s*hidden/);
|
||||
});
|
||||
|
||||
@@ -502,7 +502,7 @@ export const ASSESSMENT_STEPS = [
|
||||
then: '两颗料不是同一个芯片类型,逐项比对无从谈起 —— 换一颗料,或者把归错的那颗类型改对。',
|
||||
else: {
|
||||
when: 'parts_profile_missing + parts_parameter_missing > 0',
|
||||
then: '还有必填格空着 —— 红带点名的那几格填上就能提交。',
|
||||
then: '还有必填格空着 —— 缺哪几格就点名在下面两行,填上就能提交。',
|
||||
else: '',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -54,7 +54,7 @@ export const manifest: ActivityManifest = {
|
||||
activityId: ACTIVITY_ID,
|
||||
name: 'ACT-WORK-05 · 差异评估',
|
||||
/* 重写不是改 —— 同版重装是 no-op(静默丢失,踩过) */
|
||||
packageVersion: '2.10.0',
|
||||
packageVersion: '2.11.0',
|
||||
category: '需求分析与方案匹配',
|
||||
sdkVersion: '0.1.0-m5',
|
||||
checksum: 'dev-checksum-part-substitution-assessment',
|
||||
|
||||
@@ -100,6 +100,8 @@ const optionSelect = (col: string, address: string, valueCol: string, labelCol:
|
||||
*/
|
||||
const gateBar = (): string => `<div class="jc-gate-bar">
|
||||
<span><b>还差什么</b> —— <span class="jc-gate-text" data-slot="gate_reason" data-blank></span></span>
|
||||
<span class="jc-gate-sub" data-class-of="parts_profile_missing">主数据还差 <b data-slot="parts_profile_missing"></b> 行必填 —— <span data-slot="parts_profile_missing_keys"></span></span>
|
||||
<span class="jc-gate-sub" data-class-of="parts_parameter_missing">参数值还差 <b data-slot="parts_parameter_missing"></b> 行必填 —— <span data-slot="parts_parameter_missing_keys"></span></span>
|
||||
</div>`;
|
||||
|
||||
/**
|
||||
@@ -126,6 +128,20 @@ const doc = (input: { accent: string; alias: string; tag: string; kicker: string
|
||||
</footer>
|
||||
</div></div>`;
|
||||
|
||||
/**
|
||||
* 芯片**类型**的图形 —— 层叠双封装(一类而非一颗),别与档案卡的单颗封装共用一枚
|
||||
* (2026-08-31 用户:「芯片类型能不能换一个更贴近芯片类型的 svg,不要和芯片使用同一个图片」)。
|
||||
*/
|
||||
const TYPE_SVG =
|
||||
'<svg class="tsvg" viewBox="0 0 96 72" aria-hidden="true">' +
|
||||
'<g class="t-ghost"><rect class="c-pin" x="13" y="7" width="10" height="4" rx="1.4"/><rect class="c-pin" x="13" y="18" width="10" height="4" rx="1.4"/><rect class="c-pin" x="13" y="29" width="10" height="4" rx="1.4"/><rect class="c-pin" x="79" y="7" width="10" height="4" rx="1.4"/><rect class="c-pin" x="79" y="18" width="10" height="4" rx="1.4"/><rect class="c-pin" x="79" y="29" width="10" height="4" rx="1.4"/><rect class="c-body" x="22" y="2" width="58" height="42" rx="7"/></g>' +
|
||||
'<rect class="c-pin" x="3" y="27" width="11" height="5" rx="1.6"/><rect class="c-pin" x="3" y="39" width="11" height="5" rx="1.6"/><rect class="c-pin" x="3" y="51" width="11" height="5" rx="1.6"/>' +
|
||||
'<rect class="c-pin" x="70" y="27" width="11" height="5" rx="1.6"/><rect class="c-pin" x="70" y="39" width="11" height="5" rx="1.6"/><rect class="c-pin" x="70" y="51" width="11" height="5" rx="1.6"/>' +
|
||||
'<rect class="c-body" x="13" y="22" width="58" height="44" rx="8"/>' +
|
||||
'<rect class="c-die" x="31" y="36" width="22" height="18" rx="3.5"/>' +
|
||||
'<circle class="c-dot" cx="23" cy="30" r="2.3"/>' +
|
||||
'</svg>';
|
||||
|
||||
/** 卡角编辑钮的三枚图标:笔(开编辑)/ 软盘(保存)/ 叉(放弃)—— 2026-08-31 用户定的笔↔软盘交互 */
|
||||
const PEN_SVG =
|
||||
'<svg class="fic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg>';
|
||||
@@ -254,7 +270,7 @@ const DOSSIERS = `<section class="board"><article class="dossiers" data-rows="mi
|
||||
*/
|
||||
const BASIS_CARD = `<article class="basis" data-rows="mine:${D}.case"><section class="basis-in">
|
||||
<header class="basis-hd">
|
||||
${CHIP}
|
||||
${TYPE_SVG}
|
||||
<span class="basis-id">
|
||||
<span class="basis-lb">比对基准 · 芯片类型</span>
|
||||
<span class="basis-chip" data-rows="parts.chip_type" data-by="type_id=chip_type_id"><b data-slot="type_name" data-blank></b><i class="basis-desc" data-slot="description" data-blank></i></span>
|
||||
@@ -294,12 +310,10 @@ export const INPUT_SUPPLEMENT = doc({
|
||||
'<span class="need">纪律</span>:两颗料必须归到同一个芯片类型下才谈得上逐项比对;类型只从字典里选,本活动不新增类型 —— 字典里没有合适的,先请数据管理员补进字典;查不到的参数值留空(会被红带点名),不要编一个值顶替。',
|
||||
]) +
|
||||
`</details>` +
|
||||
zone('芯片档案', '一颗芯片一张卡:正面主数据、背面核心参数值 —— 点卡翻面看另一面;右上角的笔 = 编辑当前这一面,软盘 = 保存', ZIC_TABLE) +
|
||||
gapbar('parts_profile', '必填') +
|
||||
gapbar('parts_parameter', '必填') +
|
||||
DOSSIERS +
|
||||
zone('比对基准', '芯片类型 + 它的核心参数字段 —— 两颗料归到哪个类型,就按这些字段逐项比;字段说明悬浮即看', ZIC_TABLE) +
|
||||
BASIS_CARD +
|
||||
zone('芯片档案', '一颗芯片一张卡:正面主数据、背面核心参数值 —— 点卡翻面看另一面;右上角的笔 = 只编辑这张卡当前这一面,软盘 = 保存', ZIC_TABLE) +
|
||||
DOSSIERS +
|
||||
flow('<b>提交 / 确认:</b>本工单只承载内容;确认请在对话区的【执行卡】中进行。确认后活动自动从「等待数据」唤醒。'),
|
||||
});
|
||||
|
||||
|
||||
@@ -146,9 +146,14 @@ i{font-style:normal}
|
||||
.foot-r{letter-spacing:.04em}
|
||||
|
||||
/* ================= 顶部「还差什么」红带(机制:显隐认 data-blank 的空文本节点) ================= */
|
||||
.jc-gate-bar{display:flex;align-items:flex-start;gap:8px;margin:0 0 16px;padding:11px 14px;border-radius:10px;background:var(--danger-soft);border:1px solid #f5c0ba;color:var(--danger);font-size:12.5px;line-height:1.7}
|
||||
.jc-gate-bar{display:flex;flex-direction:column;gap:5px;margin:0 0 16px;padding:11px 14px;border-radius:10px;background:var(--danger-soft);border:1px solid #f5c0ba;color:var(--danger);font-size:12.5px;line-height:1.7}
|
||||
.jc-gate-bar b{font-weight:800}
|
||||
.jc-gate-bar:has(.jc-gate-text:empty){display:none}
|
||||
/* 逐块的缺项点名并进这一条(2026-08-31 用户:「页面上所有的问题都合并到最上面的还差什么」)——
|
||||
与提交闸同源的基座读数,数值为 0 的那一行自己消失,只在待处理态出现 */
|
||||
.jc-gate-sub{display:none;font-size:12px;color:#a03d34;padding-left:2px}
|
||||
html.editable .jc-gate-sub{display:block}
|
||||
html.editable .jc-gate-sub.v-0{display:none}
|
||||
|
||||
/* 缺项红带 —— 与提交闸同源的必填提示,只在待处理态出现 */
|
||||
.jc-gapbar{display:none}
|
||||
@@ -297,13 +302,14 @@ table.on tr.jc-row-static td{background:var(--acc-wash);border-top:1px dashed va
|
||||
wrapper 不为 :empty,上一条够不着 —— 认裸 class 补同一枚胶囊(单层 :has) */
|
||||
.fcell.req .jc-vcell:has(> [class="stx"])::after{content:"必填";font-size:10.5px;font-weight:800;color:var(--danger);background:var(--danger-soft);border-radius:999px;padding:0 9px}
|
||||
.fcell.sug .jc-vcell:empty::after{content:"建议";font-size:10.5px;font-weight:800;color:var(--amber);background:var(--amber-soft);border-radius:999px;padding:0 9px}
|
||||
/* 档案卡的编辑态 —— **只点亮正面**(.on 落在 data-edit 元素自身;卡背住着另一地址的宿主,
|
||||
不圈 .front 的话参数输入会被主数据的闸顺手点亮) */
|
||||
.dossiers.on .front .jc-vcell{display:none}
|
||||
.dossiers.on .front .jc-vcell.ro{display:block}
|
||||
.dossiers.on .front .jc-cell{display:block}
|
||||
.dossiers.on .front .jc-vcell:empty::after{content:none}
|
||||
.dossiers.on .dz-role{display:flex}
|
||||
/* 档案卡的编辑态 —— **按卡开,只点亮这张卡的正面**(2026-08-31 用户:「各自编辑各自的部分,
|
||||
不要做联动」:.on 由 scopedTargets 落在被点那张卡的行 section 上,不再落整个宿主);
|
||||
只圈 .front 是因为卡背住着另一地址的宿主,不圈的话参数输入会被主数据的编辑顺手点亮 */
|
||||
.dossier.on .front .jc-vcell{display:none}
|
||||
.dossier.on .front .jc-vcell.ro{display:block}
|
||||
.dossier.on .front .jc-cell{display:block}
|
||||
.dossier.on .front .jc-vcell:empty::after{content:none}
|
||||
.dossier.on .dz-role{display:flex}
|
||||
|
||||
/* ── 翻面(纯 CSS:checkbox + :has,零脚本;reduced-motion 下瞬时切换)── */
|
||||
.dossier{position:relative;perspective:1200px;padding:0;background:none;border:0;box-shadow:none}
|
||||
@@ -322,15 +328,15 @@ table.on tr.jc-row-static td{background:var(--acc-wash);border-top:1px dashed va
|
||||
.dossier:has(.flip-tg:checked) .flip-in{transform:rotateY(180deg)}
|
||||
.dossier:has(.flip-tg:checked) .flip-cue .cue-front{display:none}
|
||||
.dossier:has(.flip-tg:checked) .flip-cue .cue-back{display:inline-block}
|
||||
.dossiers.on .dossier .flip-in{transform:rotateY(0deg)}
|
||||
.dossier.on .flip-in{transform:rotateY(0deg)}
|
||||
.dossier:has(.pback.on) .flip-in{transform:rotateY(180deg)}
|
||||
/* 编辑中不许翻(哪面在编辑锁哪面),盖满卡面的点击层与提示丸一起收 */
|
||||
.dossiers.on .flip-hit,.dossier:has(.pback.on) .flip-hit{display:none}
|
||||
.dossiers.on .flip-cue,.dossier:has(.pback.on) .flip-cue{display:none}
|
||||
/* 编辑中的**这张卡**不许翻(哪面在编辑锁哪面),点击层与提示丸一起收;另一张卡不受影响 */
|
||||
.dossier.on .flip-hit,.dossier:has(.pback.on) .flip-hit{display:none}
|
||||
.dossier.on .flip-cue,.dossier:has(.pback.on) .flip-cue{display:none}
|
||||
/* 悬浮侧倾:预告「这张卡点了会翻」—— 只在读态、只在真有悬浮的设备、且不嫌动画的人那里 */
|
||||
@media (hover:hover) and (prefers-reduced-motion: no-preference){
|
||||
.dossiers:not(.on) .dossier:hover:not(:has(.flip-tg:checked)):not(:has(.pback.on)) .flip-in{transform:rotateY(-12deg)}
|
||||
.dossiers:not(.on) .dossier:hover:has(.flip-tg:checked):not(:has(.pback.on)) .flip-in{transform:rotateY(168deg)}
|
||||
.dossier:hover:not(.on):not(:has(.flip-tg:checked)):not(:has(.pback.on)) .flip-in{transform:rotateY(-12deg)}
|
||||
.dossier:hover:not(.on):has(.flip-tg:checked):not(:has(.pback.on)) .flip-in{transform:rotateY(168deg)}
|
||||
}
|
||||
|
||||
/* ── 卡角编辑钮:笔=编辑当前这一面,软盘=保存,叉=放弃(2026-08-31 用户定,顶掉两行编辑闸)──
|
||||
@@ -343,10 +349,11 @@ table.on tr.jc-row-static td{background:var(--acc-wash);border-top:1px dashed va
|
||||
.fab.pen{color:var(--acc-deep)}
|
||||
.fab.disk{background:var(--acc);border-color:var(--acc);color:#fff}
|
||||
.fab.xbtn{color:var(--ink-3)}
|
||||
html.editable .dossiers:not(.on) .dossier:not(:has(.pback.on)):not(:has(.flip-tg:checked)) .pen-f{display:grid}
|
||||
html.editable .dossiers:not(.on) .dossier:has(.flip-tg:checked):not(:has(.pback.on)) .pen-b{display:grid}
|
||||
.dossiers.on .disk-f{display:grid}
|
||||
.dossiers.on .x-f{display:grid}
|
||||
/* 笔:这张卡当下这一面、且这张卡没在编辑 —— 各卡各开,互不联动 */
|
||||
html.editable .dossier:not(.on):not(:has(.pback.on)):not(:has(.flip-tg:checked)) .pen-f{display:grid}
|
||||
html.editable .dossier:not(.on):not(:has(.pback.on)):has(.flip-tg:checked) .pen-b{display:grid}
|
||||
.dossier.on .disk-f{display:grid}
|
||||
.dossier.on .x-f{display:grid}
|
||||
.dossier:has(.pback.on) .disk-b{display:grid}
|
||||
.dossier:has(.pback.on) .x-b{display:grid}
|
||||
|
||||
@@ -384,6 +391,10 @@ html.editable .dossiers:not(.on) .dossier:has(.flip-tg:checked):not(:has(.pback.
|
||||
.basis-desc{font-style:normal;font-size:11.5px;color:var(--ink-3)}
|
||||
.basis-hd:has(.basis-chip:empty)::after{content:"待选择";font-size:11px;font-weight:700;color:var(--amber);background:var(--amber-soft);border:1px dashed #eeda9e;border-radius:999px;padding:1px 10px}
|
||||
.basis-hd .csvg{width:56px;flex:none}
|
||||
/* 类型图:层叠双封装 —— 后面那枚是「同类的别的芯片」,淡出成剪影 */
|
||||
.basis-hd .tsvg{width:60px;flex:none}
|
||||
.tsvg{display:block}
|
||||
.t-ghost{opacity:.32}
|
||||
.basis-id{display:flex;flex-direction:column;gap:5px;min-width:0;flex:1 1 auto}
|
||||
.basis-in:has(.ftiles:empty) .ftiles{display:none}
|
||||
/* 降噪(2026-08-31 用户:「乱糟糟…没必要显示全就不要显示完全,鼠标指上去后再显示」):
|
||||
|
||||
@@ -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