资料文件真产出来了;E04/I01 补到验收线,顺手填了几个「墙拦住却不给路」的坑

**资料文件全链**(用户:审核资料这一步要真产出 word/excel/md/html,前端能渲、点资料名能开)
接手时这一步一个文件都产不出来 —— S3 剧本白纸黑字写着「file_ref 本 MVP 阶段可留空」。
文件是台账那一行的投影,不另存一份:
· 声明 `file:{titleColumn,bodyColumn,formatColumn}`,它同时是授权面(没声明就点不开)
· 现造真字节 —— docx/xlsx 是自己打的 OOXML 包(zip-write:store+CRC32,零依赖)
· 读回来渲 —— 解的就是造出来那份字节,渲得出来才证明文件是真的
· 件上 `data-file-of` → 工作区二级界面(Word 纸面/Excel 网格/渲好的 md/沙箱 html)+ 下载原件
放行落地前把每一份冻进 `sys.task_asset_file`(迁移 0029):归档件点开的必须是签字那一刻
那一份,而资料落在主数据上、试跑单不落正式表 —— 不存这一份,收工后一个都点不开。
工作成果里「产出的资料」自成一摞。Markdown 富文本渲染顺带销账(仍零依赖)。

**Harness**
· 画面按「这一拍办完之后」在办的那一步挑 —— 从前拿拍首的局面决定拍尾的事:
  收掉 S4、发出 S5 的卡,而屏上还摆着 S4 的只读件,人无处可填(墙要配路第四次现形)
· 验收「重做」真能退回重做,且打回后台账没动就不许再提交(软硬两段,不锁死单子)
· 立监听的授权面是清单声明;发卡的授权面是调人预算,不是计划项
· 读工具同参重调有兜底;空转写身后还有行动就不废串;finish 的 doneWhen 按写完后的草稿算

**界面**
· 翻件与翻页各归各位(从前二选一,多卡多页时页翻不动);没得翻写「—」不写 0/0
· 工作成果详情能翻份;表宽超出能横滑(E04/I01 抄版式时把 overflow-x 抄丢了)
· 查看面灰底滚过一屏变白 —— 两条独立的路都治了(沙箱内不同色 / 弹性子项不长个)
· 测试台「取不到」不再当成「还活着」

**活动**
E04 关系覆盖、I01 品牌内容各自补到验收线:判据全读基座数、判据指向的东西摆上卡面、
样例满载且名字 ≤14 字;提交前要裁的判据搬进说明书(模型会压拍,压过之后只有说明书还在)。

验收:e2e i01 4/4、e04 3/3 全部 released,资料在办中渲得出、放行后存了档点得开;
门禁 typecheck/lint/depcruise/1628 测全绿。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EEJX8Z7CHejvwmDQyPQuBX
This commit is contained in:
xcs
2026-08-24 10:26:45 +08:00
co-authored by Claude Opus 5
parent 70e83d0b09
commit 0aa2fc11b0
92 changed files with 13984 additions and 370 deletions
@@ -0,0 +1,162 @@
/**
* E04 关系覆盖与关键人经营 · 视觉底(**E04 自己的 PRD 目标件,不是借来的家族版面**)。
*
* 上一版这里写着「E04 没有自己的 PRD 目标件」——那是假的:
* `docs/SS01-客户与市场/e04-html/` 下的四份 HTML(执行工单 1/2、验收工单 1/2)正是
* E04 自己的目标件,颜色变量、卡头渐变、判据框、原生 select/textarea 全部逐字量自那四份文件,
* 不是 A1/A2 那一支「浮起卡片 + 渐变徽章 + 头像签名」的家族版面。
*
* 两处**有意**偏离目标件,原因写在这里(别处再改记得回头看):
* 1. 目标件的必填只在表头打一个「*」;本仓有个更早、已验证过的平台级三档占位系统
* (必填红 / 建议填橘 / 选填灰,2026-08-02 定,`docs/HTML报告生成规范.md` 收录),
* 比「只在表头提一句」更能在空格子上直接指路。两者都留着:表头照抄目标件的「*」,
* 空格子再叠一层三档胶囊——不冲突,目标件的例子本来就没有空格子可比对。
* 2. 候选清单「状态」列:目标件那一列是「正式/试用」(客户主数据的状态),
* 本活动的 `candidate.status` 是另一个真实字段(`pending/covered/excluded`,
* 候选行本轮的覆盖生命周期,`declarations.ledger` 里 tally 着),两者恰好同名不同义。
* 留的是本活动这个——它是真在用的数据,目标件那份是另一件事。
*
* 版面照目标件分两层:这份文件是**底**(配色变量、卡壳、头/尾、判据框、说明框、表格基础排版),
* `report.ts` 里的 `BIND_CSS` 是**面**(哪个值决定哪种颜色/文案——三档占位、缺项红带、
* 状态徽、编辑闸开合、行增删这些「数据驱动的开关」,判断在样式表,取值在模版,两处不许混)。
*/
export const PRD_CSS = `
.proto-root {
--ink:#1f2329; --ink-2:#41464e; --ink-3:#646a73; --ink-4:#8f959e;
--line:#dee0e3; --line-soft:#e8eaed; --soft:#f8f9fb;
--brand:#3370ff; --brand-deep:#245bdb; --brand-soft:#e1eaff; --brand-wash:#f0f4ff;
--human:#a9711e; --human-soft:#f6ecd8; --human-line:#e6d3a8;
--ledger:#b23a2e; --ledger-soft:#f7e4e0; --ledger-line:#ecc5bd;
--ok:#278717; --ok-soft:#e8f8e4;
--danger:#d83931; --danger-soft:#feeae9;
--warn-accent:#a9711e; --warn-soft:#f6ecd8;
--mono: "SF Mono", Menlo, Consolas, monospace;
--t-fast: .16s;
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
color: var(--ink);
font-size: 14px;
line-height: 1.7;
}
.proto-root, .proto-root * { box-sizing: border-box; }
/* ==================== 卡壳(嵌进工作区,不自带浮起——外层已经是一张卡)==================== */
.proto-root .jc-report { background: #fff; overflow: hidden; }
.proto-root .jc-rp-head { display: flex; align-items: center; gap: 14px; padding: 18px 22px; border-bottom: 2px solid var(--ink); }
.proto-root .jc-report.tone-human .jc-rp-head { background: linear-gradient(120deg, #fffaf1 0%, #fff 55%, #fbf6ea 100%); }
.proto-root .jc-report.tone-ledger .jc-rp-head { background: linear-gradient(120deg, #fdf3f1 0%, #fff 55%, #fbf2f0 100%); }
.proto-root .jc-rp-badge {
flex: none; width: 44px; height: 44px; border-radius: 10px;
display: grid; place-items: center; color: #fff; font-weight: 800; font-size: 13px;
}
.proto-root .jc-report.tone-human .jc-rp-badge { background: var(--human); box-shadow: 0 6px 16px rgba(169,113,30,.25); }
.proto-root .jc-report.tone-ledger .jc-rp-badge { background: var(--ledger); box-shadow: 0 6px 16px rgba(178,58,46,.25); }
.proto-root .jc-rp-tit { flex: 1; min-width: 0; }
.proto-root .jc-rp-tit em { font-style: normal; font-size: 11px; letter-spacing: .08em; color: var(--ink-4); font-weight: 700; }
.proto-root .jc-rp-tit h3 { margin: 2px 0 0; font-size: 19px; font-weight: 800; line-height: 1.3; }
.proto-root .jc-rp-sub { font-size: 12px; color: var(--ink-3); margin-top: 3px; }
.proto-root .jc-rp-side { display: flex; flex-direction: column; align-items: flex-end; gap: 6px; flex: none; }
/* 目标件 side 第二行:步骤(静态文案),同目标件 .step 的字号字色 */
.proto-root .jc-rp-step { font-size: 12px; color: var(--ink-3); }
/* 关联任务号:全长 UUID 如实给,排版上截尾 —— 截断是排版,不是数据 */
.proto-root .jc-rp-taskid {
display: inline-block; max-width: 150px; overflow: hidden; text-overflow: ellipsis;
white-space: nowrap; vertical-align: bottom; font-family: var(--mono); font-size: 11px;
}
.proto-root .jc-rp-tag { font-size: 11px; font-weight: 800; padding: 2px 10px; border-radius: 999px; border: 1px solid; }
.proto-root .jc-report.tone-human .jc-rp-tag { color: var(--human); background: var(--human-soft); border-color: var(--human-line); }
.proto-root .jc-report.tone-ledger .jc-rp-tag { color: var(--ledger); background: var(--ledger-soft); border-color: var(--ledger-line); }
.proto-root .jc-rp-wait { font-size: 12px; color: var(--ink-3); }
.proto-root .jc-rp-wait.warn { color: var(--danger); font-weight: 700; }
.proto-root .jc-rp-bd { padding: 20px 22px; }
/* 【做什么】/【交回什么】这类分区小标——括号label + 破折号说明,同一行 */
.proto-root .jc-rp-part { font-size: 11px; font-weight: 800; letter-spacing: .12em; color: var(--ink-4); margin: 20px 0 8px; }
.proto-root .jc-rp-part:first-child { margin-top: 0; }
.proto-root .jc-rp-part b { color: var(--ink-3); }
/* AI 已完成 / 需要你 —— 说明框,左侧色条按 tone 走 */
.proto-root .jc-derive { border: 1px solid var(--line); border-left: 3px solid; border-radius: 8px; background: var(--soft); padding: 14px 16px; margin-bottom: 16px; }
.proto-root .jc-report.tone-human .jc-derive { border-left-color: var(--human); }
.proto-root .jc-report.tone-ledger .jc-derive { border-left-color: var(--ledger); }
.proto-root .jc-derive p { margin: 0; font-size: 13px; color: var(--ink-2); }
.proto-root .jc-derive p + p { margin-top: 6px; }
.proto-root .jc-derive b { color: var(--ink); }
.proto-root .jc-derive .done { color: var(--ok); font-weight: 700; }
.proto-root .jc-report.tone-human .jc-derive .need { color: var(--human); font-weight: 700; }
.proto-root .jc-report.tone-ledger .jc-derive .need { color: var(--ledger); font-weight: 700; }
/* 验收判据框——目标件里两份验收工单同一色(赭红),不随 tone 变 */
.proto-root .jc-criteria { border: 1px solid var(--ledger-line); border-radius: 10px; background: var(--ledger-soft); padding: 13px 16px; margin-bottom: 18px; }
.proto-root .jc-criteria-t { font-size: 11px; font-weight: 800; letter-spacing: .08em; color: var(--ledger); margin-bottom: 7px; }
.proto-root .jc-criteria ul { margin: 0; padding-left: 18px; font-size: 12.5px; color: var(--ink-2); }
.proto-root .jc-criteria li { margin: 4px 0; }
.proto-root .jc-criteria b { color: var(--ink); }
/* 表格基础排版(交互态的显隐开关在 BIND_CSS) */
/*
* **表宽超出就得能横着滑**(2026-08-23 用户:「表格已经显示不全了,不能左右滑动」)。
* 从前这里是 overflow: hidden —— 那是为了让圆角裁住表头,可它同时把超出的列
* **直接裁掉且滑不动**:列一多(或某列为了放下下拉框加宽)就看不全,人还没有任何办法。
* quote-* 各包一直是 overflow-x: auto,这两个新包抄版式时把这一条抄丢了。
* (注释里不写反引号 —— 整段 CSS 是模版字符串,一个反引号就把它截断,本轮踩第三次。)
* 竖向仍 hidden:件是整篇铺开跟着外层滚的,表格自己不该再套一层竖滚动条。
*/
.proto-root .jc-tbl-wrap { border: 1px solid var(--line-soft); border-radius: 10px; overflow-x: auto; overflow-y: hidden; margin-bottom: 4px; }
.proto-root .jc-tbl-in { width: 100%; border-collapse: collapse; font-size: 13px; }
.proto-root .jc-tbl-in th { text-align: left; background: var(--soft); font-size: 11px; letter-spacing: .04em; font-weight: 800; color: var(--ink-3); padding: 9px 11px; border-bottom: 1px solid var(--line); }
.proto-root .jc-tbl-in th.req::after { content: " *"; color: var(--danger); font-size: 11px; }
.proto-root .jc-tbl-in th.sug::after { content: " 建议"; color: var(--warn-accent); font-size: 10px; font-weight: 600; }
.proto-root .jc-tbl-in th.opt::after { content: " 选填"; color: var(--ink-4); font-size: 10px; font-weight: 600; }
.proto-root .jc-tbl-in td { padding: 9px 11px; border-bottom: 1px solid var(--line-soft); vertical-align: top; }
.proto-root .jc-tbl-in tr:last-child td { border-bottom: 0; }
.proto-root .jc-tbl-in td.jc-rn { width: 38px; white-space: nowrap; font-family: var(--mono); font-size: 11px; color: var(--ink-4); text-align: center; position: relative; }
.proto-root .jc-vcell { display: block; font-size: 12.5px; color: var(--ink); line-height: 1.5; overflow-wrap: anywhere; }
.proto-root .jc-vcell.mono { font-family: var(--mono); font-size: 11.5px; color: var(--ink-2); }
.proto-root .jc-vcell.cust { font-weight: 700; }
.proto-root .jc-vcell.dim { color: var(--ink-4); text-decoration: line-through; }
.proto-root .jc-diffnew { font-weight: 800; color: var(--ok); }
.proto-root .jc-cell, .proto-root select.jc-cell, .proto-root textarea.jc-cell {
width: 100%; min-width: 60px; border: 1px solid var(--line); border-radius: 6px;
padding: 6px 8px; font: inherit; font-size: 12.5px; color: var(--ink); background: #fff;
}
/* 多行文本照目标件:最小两行高,竖向可拉 */
.proto-root textarea.jc-cell { min-height: 40px; resize: vertical; }
.proto-root .jc-cell.mono { font-family: var(--mono); font-size: 11.5px; }
.proto-root .jc-cell:focus { border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-wash); outline: none; }
/* 格内次行小字(依据 / 编码)—— 主行之下,不占独立列 */
.proto-root .jc-sub { display: block; font-size: 11px; color: var(--ink-4); margin-top: 2px; line-height: 1.55; }
.proto-root .jc-sub i { font-style: normal; }
/* 取值位(i)全空的次行整行藏掉;直填文本的次行(如对象格的编码)不归这条管 */
.proto-root .jc-sub:has(i):not(:has(i:not(:empty))) { display: none; }
.proto-root .jc-rowdel {
width: 22px; height: 22px; margin: 0 auto; cursor: pointer;
border: 1px solid var(--line); border-radius: 6px; padding: 0;
font: inherit; font-size: 14px; line-height: 1; color: var(--ink-3); background: #fff;
transition: border-color var(--t-fast), color var(--t-fast), background var(--t-fast);
}
.proto-root .jc-rowdel:hover { border-color: var(--danger); color: var(--danger); background: var(--danger-soft); }
.proto-root .jc-addrow em { font-style: normal; font-size: 11.5px; color: var(--ink-4); }
.proto-root .jc-addrow button {
cursor: pointer; border: 1px solid var(--brand-soft); border-radius: 7px; padding: 5px 12px;
font: inherit; font-size: 12px; font-weight: 700; color: var(--brand-deep); background: var(--brand-wash);
}
.proto-root .jc-addrow button:hover { background: var(--brand-soft); }
/* 本工单只承载内容,提交/审批请在对话区——目标件四份文件都有这一条 */
.proto-root .jc-flow { margin-top: 16px; padding: 9px 12px; border-radius: 8px; background: var(--soft); border: 1px dashed var(--line); font-size: 12px; color: var(--ink-3); }
.proto-root .jc-flow b { color: var(--ink); }
.proto-root .jc-rp-foot { padding: 13px 22px; border-top: 1px solid var(--line-soft); font-size: 11.5px; color: var(--ink-4); display: flex; justify-content: space-between; flex-wrap: wrap; gap: 8px; }
.proto-root .jc-rp-foot span:last-child { font-family: var(--mono); }
`;
@@ -0,0 +1,551 @@
/**
* E04 关系覆盖与关键人经营的 HTML 报告模版 —— **活动自己持有的资产**,随包版本冻结。
*
* 四个局面各一张:
* · `coverage-scope` —— S1/S2 摆,候选覆盖客户清单,可编辑(增删改);
* · `coverage-plan` —— S3/S4 摆,关键人经营计划,**只读**(业务说明明文
* "人只做审批,不能修改结论"——通过/驳回的按钮在对话区的决策卡上,不在这份件里);
* · `coverage-receipt` —— S5 摆,执行回执,混排只读(计划动作/对象承继自计划)+
* 可编辑(完成状态/结果摘要/客户反馈/关键人变动四列);
* · `coverage-ledger-update` —— S6/T_release 摆,台账更新明细,**只读**,放行后即归档件。
*
* 版面语言逐字量自 E04 自己的目标件(`docs/SS01-客户与市场/e04-html/` 四份 HTML,
* `prd-style.ts` 头注写了两处有意偏离的地方)。
*
* 取值三纪律:顶层只取汇总列与单值列;逐条走 `mine:crm_coverage.<object>`;
* 列名逐字照 `declarations.ledger` 抄。**模版不算不判** —— 缺必填红带、状态徽、
* 排序位次全部由基座算好的数经 `data-class-of` 投影,判断留在样式表。
*/
import { PRD_CSS } from './prd-style.ts';
const D = 'crm_coverage';
/* ==================== 绑定期补的样式(数据驱动的开关,判断都在这一层)==================== */
const BIND_CSS = `
/* 空占位三级(全局口径):必填红 / 建议填橘 / 选填灰 —— 值真的空才轮到 */
.proto-root .jc-tbl-in td.req > .jc-vcell:empty::after,
.proto-root .jc-tbl-in td.sug > .jc-vcell:empty::after,
.proto-root .jc-tbl-in td.opt > .jc-vcell:empty::after {
display: inline-flex; align-items: center; font-style: normal; font-size: 10px; font-weight: 700;
line-height: 1; padding: 3px 7px; border-radius: 5px; border: 1px dashed;
}
.proto-root .jc-tbl-in td.req > .jc-vcell:empty::after { content: "必填"; color: var(--danger); background: var(--danger-soft); border-color: #f3b6b3; }
.proto-root .jc-tbl-in td.sug > .jc-vcell:empty::after { content: "建议填"; color: var(--warn-accent); background: var(--warn-soft); border-color: #f0cf9a; }
.proto-root .jc-tbl-in td.opt > .jc-vcell:empty::after { content: "选填"; color: var(--ink-4); background: var(--soft); border-color: var(--line); }
/* 归档件 / 只读件里空格子就是「没有」,不再摆「你还能填」的胶囊 */
.proto-root .jc-tbl-in td.dimempty > .jc-vcell:empty::after { content: "—"; color: var(--ink-4); border: 0; background: none; padding: 0; }
/* 缺必填的整行浅红 + 行首红条 —— 由 :has() 认 */
.proto-root .jc-tbl-in tbody tr:has(td.req > .jc-vcell:empty) td { background: #fffbfb; }
.proto-root .jc-tbl-in tbody tr:has(td.req > .jc-vcell:empty):hover td { background: #fff5f5; }
.proto-root .jc-tbl-in tbody tr:has(td.req > .jc-vcell:empty) td.jc-rn { color: var(--danger); font-weight: 800; }
.proto-root .jc-tbl-in tbody tr:has(td.req > .jc-vcell:empty) td.jc-rn::after {
content: ""; position: absolute; left: 0; top: 4px; bottom: 4px; width: 3px; border-radius: 0 3px 3px 0; background: var(--danger);
}
/* 序号由 CSS 计数器排 */
.proto-root .jc-tbl-in tbody { counter-reset: rn; }
.proto-root .jc-tbl-in td.jc-rn::before { counter-increment: rn; content: counter(rn); }
/*
* 就地改:可改格子渲了只读与输入两份,默认锁着,表上加 on 才换孪生件。
*
* **只换真有孪生件的那一份**(2026-08-21 用户送来实测截图查出来的坑):从前认
* 「表上有 on 就把这张表里所有 .jc-vcell 全藏起来」,而 coverage-receipt 混排
* 只读列(计划动作/对象里的联系人姓名——承继自计划,声明明写"改不了")与可改列
* (完成状态等),只读列压根没有 .jc-cell 孪生件可换 —— 一开编辑,那格字直接消失,
* 要退出编辑才重新看得见。**只藏「后面紧跟着一个孪生件」的那一份**:
* 没有孪生件的 .jc-vcell 不属于这道开关,编辑态里也照旧显示。
*/
.proto-root .jc-cell { display: none; }
.proto-root .jc-tbl-in.on td > .jc-vcell:has(+ .jc-cell) { display: none; }
.proto-root .jc-tbl-in.on td > .jc-cell { display: block; }
/* 编辑态下必填空格子:红框 + 整行浅红(由 :placeholder-shown / :invalid 认) */
.proto-root .jc-tbl-in.on td.req > select.jc-cell:invalid,
.proto-root .jc-tbl-in.on td.req > .jc-cell:placeholder-shown { border-color: #f3b6b3; background: #fffafa; }
.proto-root .jc-tbl-in.on tbody tr:has(td.req > .jc-cell:placeholder-shown),
.proto-root .jc-tbl-in.on tbody tr:has(td.req > select.jc-cell:invalid) { background: #fffbfb; }
/* 铺满工作区,卡壳不再自带外边框(外层容器已经是一张卡) */
.jc-stage { max-width: none; padding: 0 0 4px; }
/* 那道闸 —— 默认「已锁定 + 编辑」,开了变成「取消 / 保存」;只在可改屏现身 */
.proto-root .jc-editgate { display: none; }
html.editable .proto-root .jc-editgate {
display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
margin: 14px 0 10px; padding: 9px 12px; border-radius: 9px;
background: var(--soft); border: 1px solid var(--line-soft);
font-size: 12px; color: var(--ink-3);
}
.proto-root .jc-editgate-t { flex: 1 1 auto; min-width: 0; }
.proto-root .jc-editgate-t b { color: var(--ink-2); font-weight: 700; }
.proto-root .jc-egbtn {
flex: none; display: inline-flex; align-items: center; gap: 5px; cursor: pointer;
border: 1px solid var(--line); border-radius: 7px; padding: 6px 13px;
font: inherit; font-size: 12px; font-weight: 700; color: var(--ink-2); background: #fff;
transition: border-color var(--t-fast), color var(--t-fast), background var(--t-fast);
}
.proto-root .jc-egbtn:hover { border-color: var(--brand); color: var(--brand); }
.proto-root .jc-egbtn.save { color: #fff; background: var(--human); border-color: var(--human); }
.proto-root .jc-egbtn.save:hover { filter: brightness(1.06); color: #fff; }
.proto-root .jc-editgate .jc-egbtn.ghost, .proto-root .jc-editgate .jc-egbtn.save { display: none; }
.proto-root .jc-editgate.on { background: var(--human-soft); border-color: var(--human-line); color: #7c4a12; }
.proto-root .jc-editgate.on .jc-egbtn.open { display: none; }
.proto-root .jc-editgate.on .jc-egbtn.ghost, .proto-root .jc-editgate.on .jc-egbtn.save { display: inline-flex; }
.proto-root .jc-editgate.on .jc-editgate-t { visibility: hidden; }
/* 缺必填红带 —— 只在可改屏出,一格不缺自动收(v-0 那一档 display:none) */
.proto-root .jc-gapbar { display: none; }
html.editable .proto-root .jc-gapbar {
display: flex; align-items: flex-start; gap: 8px;
margin: 0 0 10px; padding: 9px 12px; border-radius: 9px;
background: #fff5f5; border: 1px solid #f3b6b3; color: var(--danger);
font-size: 12px; line-height: 1.65;
}
.proto-root .jc-gapbar b { font-weight: 800; }
.proto-root .jc-gapbar span { color: #9a3b36; }
html.editable .proto-root .jc-gapbar.v-0 { display: none; }
/* 那条「怎么改」的提示只在开了闸之后说 */
.proto-root .jc-editbar { display: none; }
.proto-root .jc-editgate.on + .jc-editbar {
display: flex; align-items: flex-start; gap: 8px;
margin: 0 0 10px; padding: 9px 12px; border-radius: 9px;
background: var(--brand-wash); border: 1px dashed var(--brand-soft); color: var(--ink-3);
font-size: 12px; line-height: 1.6;
}
.proto-root .jc-editbar b { color: var(--ink-2); }
/* 增删行 */
.proto-root .jc-rowdel { display: none; }
.proto-root .jc-tbl-in.on td.jc-rn::before { content: none; }
.proto-root .jc-tbl-in.on .jc-rowdel { display: block; }
.proto-root .jc-tbl-in.on tr.jc-row-gone td { background: #fff6f6; opacity: .55; }
.proto-root .jc-tbl-in.on tr.jc-row-gone .jc-cell { text-decoration: line-through; color: var(--ink-3); }
.proto-root .jc-tbl-in.on tr.jc-row-gone .jc-rowdel { border-color: var(--danger); color: var(--danger); background: #fff; }
.proto-root .jc-tbl-in.on tr.jc-row-new td { background: #f2fbf7; }
.proto-root .jc-addrow { display: none; }
html.editable .proto-root .jc-tbl-wrap:has(table.on) + .jc-addrow {
display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
margin: -2px 0 12px; padding: 7px 12px; border-radius: 9px;
background: var(--soft); border: 1px dashed var(--line);
}
/*
* 统一的枚举标签(候选状态 / 优先级 / 完成状态 / 关键人变动 共用同一套机制):
* 值是机器码,人读名与颜色一律由 data-class-of 投影出的 .v-xxx 决定,不写死在取值那一侧。
* 三组值域(pending/covered/excluded、high/medium/low、done/partial/skipped、是/无变动)各自的
* .v-* 类名不会互相碰撞,同一个 .jc-tag 基座够用,不必分三套徽章系统。
*/
.proto-root .jc-tag { font-weight: 800; }
.proto-root .jc-tag::after { content: "待定"; color: var(--ink-4); font-weight: 600; }
.proto-root .jc-tag.v-pending::after { content: "待覆盖"; }
.proto-root .jc-tag.v-pending { color: var(--warn-accent); }
.proto-root .jc-tag.v-covered::after { content: "已覆盖"; }
.proto-root .jc-tag.v-covered { color: var(--ok); }
.proto-root .jc-tag.v-excluded::after { content: "已排除"; }
.proto-root .jc-tag.v-excluded { color: var(--ink-3); }
.proto-root .jc-tag.v-high::after { content: "高"; }
.proto-root .jc-tag.v-high { color: var(--danger); font-family: var(--mono); }
.proto-root .jc-tag.v-medium::after { content: "中"; }
.proto-root .jc-tag.v-medium { color: var(--ink-2); font-family: var(--mono); }
.proto-root .jc-tag.v-low::after { content: "低"; }
.proto-root .jc-tag.v-low { color: var(--ink-4); font-family: var(--mono); }
.proto-root .jc-tag.v-done::after { content: "完成"; }
.proto-root .jc-tag.v-done { color: var(--ok); }
.proto-root .jc-tag.v-partial::after { content: "部分完成"; }
.proto-root .jc-tag.v-partial { color: var(--warn-accent); }
.proto-root .jc-tag.v-skipped::after { content: "未执行"; }
.proto-root .jc-tag.v-skipped { color: var(--ink-3); }
/*
* **值域不能用中文**(2026-08-21 用户送来实测截图查出来的坑):classToken()
* 只放行 ASCII 字母数字与横杠(见 view-render.ts 的注释——属性位插值唯一开放
* 的白名单,宁可没有颜色也不给一个错的钩子),中文字符整段被消毒成空字符串,
* data-class-of="key_person_change" 因此永远长不出 v-是/v-无变动 这两个
* class —— 徽章只会落到默认的「待定」,人怎么选、选了多少次都一样,像是保存没生效。
* 机器值改成 ASCII(changed/unchanged),中文只留在这里的 ::after 与
* option 文字里(人看到的字一个没少)。
*/
.proto-root .jc-tag.v-changed::after { content: "是"; }
.proto-root .jc-tag.v-changed { color: var(--danger); }
.proto-root .jc-tag.v-unchanged::after { content: "无变动"; }
.proto-root .jc-tag.v-unchanged { color: var(--ink-4); }
/*
* **覆盖深度同一坑的另一半**(2026-08-21 用户:「编辑的下拉框是中文,显示是英文」)——
* depth 本身不含中文(说明书写死 ASCII deep/standard/light,不撞 classToken 那道墙),
* 但只读那一份从前是 .jc-vcell 直接回显原值,人看到的是英文单词,跟下拉选项(深度/标准/
* 轻度)对不上。换成同一套 data-class-of + .jc-tag 机制,人读的字上下一致。
*/
/*
* **BT 层级同一坑的第三半**(2026-08-23 用户:「BT 层级下拉框显示不全,
* 都看不到具体选了哪个」)。两处一起错:
* · 列宽只有 80px,而选项文字是「BT-01 · 采购/供应链」—— 下拉被挤扁,选了什么看不见
* (覆盖深度那列因为选项只有两个字,一直没暴露);
* · 只读那一份是 data-slot 直显,存的是 ASCII bt-02,人看到的就是 bt-02 ——
* 与编辑态下拉里写的「BT-02 · 技术」对不上。覆盖深度当初专门修过这个坑,
* BT 层级漏了。现在两列同一套:值仍是 ASCII(台账与 recompute-score 的
* btLevel 入参认它),人读的字由这里出。
*/
.proto-root .jc-tag.v-bt-01::after { content: "BT-01 · 采购/供应链"; }
.proto-root .jc-tag.v-bt-02::after { content: "BT-02 · 技术"; }
.proto-root .jc-tag.v-bt-03::after { content: "BT-03 · 高层"; }
.proto-root .jc-tag.v-bt-01, .proto-root .jc-tag.v-bt-02, .proto-root .jc-tag.v-bt-03 { color: var(--ink-2); font-family: var(--mono); font-size: 11.5px; }
.proto-root .jc-tag.v-deep::after { content: "深度"; }
.proto-root .jc-tag.v-standard::after { content: "标准"; }
.proto-root .jc-tag.v-light::after { content: "轻度"; }
/* 状态徽承继自引擎产物,不进编辑态的显隐开关——on 时也照旧摆着,当「现在是什么」的参照 */
.proto-root .jc-tbl-in.on td > .jc-tag { display: inline-flex; align-items: center; margin-top: 4px; }
/*
* 「本轮触发 / 本轮说明」这类**值还没写下就整行不摆**的说明行:
* 行里所有取值位(b/span/i)都空着 → 整行藏掉 —— 「本轮说明:」后面跟着一片空白
* 比不摆更糟(像是丢了数据)。值一落(S1 写 verdict),行自己现身。
*/
.proto-root .jc-note-line:not(:has(b:not(:empty), span:not(:empty), i:not(:empty))) { display: none; }
`;
/**
* 三层壳照目标件:少一层样式表全失效。
*
* 卡头/页脚的动态位照目标件 §6.2 补齐(2.0.0):收件人(case:owner)、
* **关联任务**(case:taskId,基座注入的任务号)、**步骤**(step,目标件里每份工单
* 写死的静态文案,如「步骤 ① 启动与覆盖范围确认」)、页脚**单号**(round_ref)。
* 目标件还有一格「最迟交回」—— 本仓没有工单期限这个事实,**不摆假数据**,那一格不做。
*/
const doc = (input: {
tone: 'human' | 'ledger';
badge: string;
tag: string;
kicker: string;
title: string;
titleRef: string;
/** 目标件 side 第二行:静态步骤文案(哪份工单盖哪几步是写清单时定死的) */
step: string;
state: string;
body: string;
foot: string;
}): string =>
`<style>${PRD_CSS}${BIND_CSS}</style>
<div class="proto-root"><div class="app-shell"><div class="jc-stage">
<article class="jc-report tone-${input.tone}">
<header class="jc-rp-head">
<div class="jc-rp-badge">${input.badge}</div>
<div class="jc-rp-tit">
<em>${input.kicker}</em>
<h3>${input.title} <span data-slot="${input.titleRef}"></span></h3>
<div class="jc-rp-sub">收件人:<span data-slot="case:owner"></span> · 关联任务:<span class="jc-rp-taskid" data-slot="case:taskId" data-blank></span></div>
</div>
<div class="jc-rp-side">
<span class="jc-rp-tag">${input.tag}</span>
<span class="jc-rp-step">${input.step}</span>
${input.state}
</div>
</header>
<div class="jc-rp-bd">${input.body}</div>
<footer class="jc-rp-foot">${input.foot}</footer>
</article>
</div></div></div>`;
const part = (label: string, aside = ''): string =>
`<p class="jc-rp-part"><b>【${label}】</b>${aside === '' ? '' : ` —— ${aside}`}</p>`;
const criteria = (title: string, items: readonly string[]): string =>
`<div class="jc-criteria"><div class="jc-criteria-t">${title}</div><ul>${items.map((i) => `<li>${i}</li>`).join('')}</ul></div>`;
const flow = (text: string): string => `<div class="jc-flow">${text}</div>`;
/** 页脚右侧照目标件长「工单类型:执行 · <单号>」—— 目标件的 card.id 没有真数据源,摆本轮案号 */
const sign = (note: string, tag: string): string =>
`<span>${note}</span><span>${tag} · <span data-slot="round_ref" data-blank></span></span>`;
/** 编辑闸 —— `label` 是闸上那句话(目标件的措辞各工单不同,如「AI 已识别 N 个客户」) */
const editGate = (address: string, label: string): string => `<div class="jc-editgate" data-edit-for="${address}">
<span class="jc-editgate-t">${label}</span>
<button type="button" class="jc-egbtn open" data-edit-open="${address}">编辑</button>
<button type="button" class="jc-egbtn ghost" data-edit-cancel="${address}">取消</button>
<button type="button" class="jc-egbtn save" data-edit-save="${address}">保存</button>
</div>`;
/* ==================== 局面一 · 候选覆盖客户清单(S1/S2)==================== */
const CANDIDATE_TABLE = `<div class="jc-tbl-wrap"><table class="jc-tbl-in">
<thead><tr>
<th style="width:36px;min-width:36px"></th>
<th class="req" style="min-width:100px">客户编号</th>
<th class="req" style="min-width:170px">客户名称</th>
<th class="sug" style="min-width:110px">客户类型</th>
<th class="sug" style="width:150px;min-width:150px">BT 层级</th>
<th style="width:80px;min-width:80px">状态</th>
<th class="sug" style="min-width:200px;width:100%">识别依据 / 纳入原因</th>
<th class="sug" style="min-width:104px">覆盖深度</th>
</tr></thead>
<tbody data-rows="mine:${D}.candidate" data-edit="${D}.candidate"><tr>
<td class="jc-rn"><button type="button" class="jc-rowdel" data-edit-del="${D}.candidate" title="撤掉这一行">×</button></td>
<td class="req" data-h="客户编号"><span class="jc-vcell mono" data-slot="customer_code" data-col="customer_code" data-blank></span></td>
<td class="req" data-h="客户名称"><span class="jc-vcell" data-slot="customer_name" data-col="customer_name" data-blank></span></td>
<td class="sug" data-h="客户类型"><span class="jc-vcell" data-slot="cust_type" data-col="cust_type" data-blank></span></td>
<td class="sug" data-h="BT 层级">
<select class="jc-cell" data-col="bt_level">
<option value=""></option>
<option value="bt-01">BT-01 · 采购/供应链</option>
<option value="bt-02">BT-02 · 技术</option>
<option value="bt-03">BT-03 · 高层</option>
</select>
<span class="jc-tag" data-class-of="bt_level"></span>
</td>
<td data-h="状态"><span class="jc-tag" data-class-of="status"></span></td>
<td class="sug" data-h="识别依据 / 纳入原因"><span class="jc-vcell" data-slot="basis" data-col="basis" data-blank></span></td>
<td class="sug" data-h="覆盖深度">
<select class="jc-cell" data-col="depth">
<option value=""></option>
<option value="deep">深度</option>
<option value="standard">标准</option>
<option value="light">轻度</option>
</select>
<span class="jc-tag" data-class-of="depth"></span>
</td>
</tr></tbody>
</table></div>`;
export const COVERAGE_SCOPE = doc({
tone: 'human',
badge: '执行',
tag: '执行工单',
kicker: 'E04 关系覆盖与关键人经营 · 执行工单 1/2',
title: '覆盖范围确认',
titleRef: 'round_ref',
step: '步骤 ① 启动与覆盖范围确认',
state: `<span class="jc-rp-wait">共 <b data-slot="${D}_candidate_rows"></b> 家候选</span>`,
body:
part('做什么', '确认本轮覆盖范围') +
`<div class="jc-derive">
<p><span class="done">AI 已完成</span>:按台账规则(覆盖度陈旧 / 决策链空白节点 / 事件触发)从 <b>L-02 客户主台账</b>与
<b>L-08 联系人台账</b>识别本轮触发口径与覆盖范围,产出下方客户清单(候选基线)。</p>
<p class="jc-note-line">本轮触发:<b data-slot="trigger_label" data-blank></b> <b data-slot="event_type" data-blank></b></p>
<p class="jc-note-line">本轮说明:<span data-slot="note" data-blank></span></p>
<p><span class="need">需要你查漏补缺</span>:点「编辑」直接在清单上增删改 —— <b>加一行 = 额外纳入</b>(AI 未识别但你有价值判断的客户)、
<b>删一行 = 排除</b>(AI 纳入但实际应排除)、<b>改单元格 = 修正</b> AI 识别的内容。没有要补的直接在对话区卡里提交。</p>
</div>` +
part('交回什么', '核对后的覆盖范围客户清单(*为必填)') +
`<div class="jc-gapbar" data-class-of="${D}_candidate_missing">
<span><b>还差 <span data-slot="${D}_candidate_missing"></span> 行客户编号或客户名称没填</b>
—— <span data-slot="${D}_candidate_missing_keys"></span>。
按右上角「编辑」开表,红框那几格就是要填的。补齐了执行单才提交得了。</span>
</div>` +
editGate(
`${D}.candidate`,
`<b>AI 已识别 <span data-slot="${D}_candidate_rows"></span> 家候选客户</b>,点「编辑」在清单上增删改`,
) +
`<div class="jc-editbar">
<span>加一行 = 额外纳入,删一行 = 排除,改单元格 = 修正 AI 识别的内容。
<b>客户编号</b>与<b>客户名称</b>是必填,缺一条这一单走不到经营计划那一步。
改动只落任务草稿层,<b>保存不等于放行</b>。</span>
</div>` +
CANDIDATE_TABLE +
`<div class="jc-addrow">
<button type="button" class="jc-egbtn ghost" data-edit-add="${D}.candidate">+ 加一行(额外纳入客户)</button>
<em>新加行的「客户编号」「客户名称」为必填;「识别依据」栏填你纳入它的原因。</em>
</div>` +
flow('<b>审批 / 提交:</b>本工单只承载内容;「提交回执」请在对话区的【执行卡】中进行。'),
foot: sign('E04 关系覆盖与关键人经营 · 执行工单 1/2 · 覆盖范围确认', '工单类型:执行'),
});
/* ==================== 局面二 · 关键人经营计划(S3/S4,只读)==================== */
const PLAN_TABLE = `<div class="jc-tbl-wrap"><table class="jc-tbl-in">
<thead><tr>
<th style="width:40px;min-width:40px"></th>
<th style="min-width:140px">客户</th>
<th style="min-width:110px">关键人</th>
<th style="min-width:150px">建议动作</th>
<th style="width:92px;min-width:92px">时间点</th>
<th style="width:70px;min-width:70px">优先级</th>
<th style="min-width:200px;width:100%">AI 给出的理由</th>
</tr></thead>
<tbody data-rows="mine:${D}.plan">
<tr>
<td class="jc-rn"></td>
<td class="dimempty" data-h="客户"><span class="jc-vcell cust" data-slot="customer_name" data-blank></span></td>
<td class="dimempty" data-h="关键人"><span class="jc-vcell" data-slot="contact_name" data-blank></span></td>
<td class="dimempty" data-h="建议动作"><span class="jc-vcell" data-slot="action" data-blank></span></td>
<td class="dimempty" data-h="时间点"><span class="jc-vcell mono" data-slot="target_date" data-blank></span></td>
<td data-h="优先级"><span class="jc-tag" data-class-of="priority"></span></td>
<td class="dimempty" data-h="AI 给出的理由"><span class="jc-vcell" data-slot="rationale" data-blank></span></td>
</tr>
</tbody>
</table></div>`;
/*
* 比目标件多一列「时间点」(有意偏离):验收判据③让人核「动作有没有时间点」,
* 而目标件的表上没有这一列 —— 判据指向的东西必须摆在卡面上,人才核得了
* (target_date 在台账声明里是必填,数据一直都在,只是从前没摆出来)。
*/
export const COVERAGE_PLAN = doc({
tone: 'ledger',
badge: '验收',
tag: '验收工单',
kicker: 'E04 关系覆盖与关键人经营 · 验收工单 1/2',
title: '关系经营计划审批',
titleRef: 'round_ref',
step: '步骤 ②③④ 产出',
state: `<span class="jc-rp-wait">共 <b data-slot="${D}_plan_rows"></b> 位关键人 · 按影响力 × 亲密度缺口排序</span>`,
body:
part('AI 结论说明', '结论是怎么算出来的') +
`<div class="jc-derive">
<p><b>AI 已完成的计算</b>:读取 L-02 客户画像、决策链地图、关系覆盖度,读取 L-08 联系人角色 / 影响力 / 亲密度;
对照决策链识别空白节点,按「影响力 × 亲密度缺口」排序,得出本轮<b>关系经营计划</b>。</p>
</div>` +
part('验收判据', '你据此判断「通过 / 驳回」') +
criteria('判断这份计划是否合理,请逐项核对', [
'<b>关键人覆盖是否完整</b>:该经营的决策链关键人(BT-01 / BT-02 / BT-03)有没有漏掉;',
'<b>优先级排序是否合理</b>:是否符合「影响力 × 亲密度缺口」的排序逻辑,有没有高优的客户被排低了;',
'<b>动作是否可执行</b>:建议动作是否明确、有没有时间点、可不可落地。',
]) +
part('AI 产出的经营计划', '只读展示') +
PLAN_TABLE +
flow('<b>审批:</b>本工单只承载内容;「通过 / 驳回」请在对话区的【验收卡】中进行。'),
foot: sign('E04 关系覆盖与关键人经营 · 验收工单 1/2 · 关系经营计划审批', '工单类型:验收'),
});
/* ==================== 局面三 · 执行回执(S5)==================== */
const RECEIPT_TABLE = `<div class="jc-tbl-wrap"><table class="jc-tbl-in">
<thead><tr>
<th style="width:36px;min-width:36px"></th>
<th style="min-width:140px">计划动作</th>
<th style="min-width:150px">对象</th>
<th class="req" style="width:110px;min-width:110px">完成状态</th>
<th class="req" style="min-width:200px">结果摘要</th>
<th class="sug" style="min-width:180px">客户反馈</th>
<th class="req" style="width:100px;min-width:100px">关键人变动</th>
</tr></thead>
<tbody data-rows="mine:${D}.receipt" data-edit="${D}.receipt"><tr>
<td class="jc-rn"></td>
<td class="dimempty" data-h="计划动作"><span class="jc-vcell" data-slot="action" data-blank></span></td>
<td data-h="对象">
<span class="jc-vcell"><b class="cust" data-slot="customer_name" data-blank></b> · <i data-slot="contact_name" data-blank></i></span>
<span class="jc-vcell mono jc-sub" data-slot="contact_code" data-col="contact_code" data-blank></span>
</td>
<td class="req" data-h="完成状态">
<select class="jc-cell" data-col="completion_status" required>
<option value=""></option>
<option value="done">完成</option>
<option value="partial">部分完成</option>
<option value="skipped">未执行</option>
</select>
<span class="jc-tag" data-class-of="completion_status"></span>
</td>
<td class="req" data-h="结果摘要">
<span class="jc-vcell" data-slot="result_summary" data-blank></span>
<textarea class="jc-cell" data-col="result_summary" placeholder="见到的关键人、谈了什么、达成什么"></textarea>
</td>
<td class="sug" data-h="客户反馈">
<span class="jc-vcell" data-slot="customer_feedback" data-blank></span>
<textarea class="jc-cell" data-col="customer_feedback" placeholder="客户态度 / 诉求 / 下一步信号"></textarea>
</td>
<td class="req" data-h="关键人变动">
<select class="jc-cell" data-col="key_person_change" required>
<option value=""></option>
<option value="changed">是</option>
<option value="unchanged">无变动</option>
</select>
<span class="jc-tag" data-class-of="key_person_change"></span>
</td>
</tr></tbody>
</table></div>`;
export const COVERAGE_RECEIPT = doc({
tone: 'human',
badge: '执行',
tag: '执行工单',
kicker: 'E04 关系覆盖与关键人经营 · 执行工单 2/2',
title: '经营计划执行回执',
titleRef: 'round_ref',
step: '步骤 ⑤⑥ 执行经营动作',
state: `<span class="jc-rp-wait warn">待回执 <b data-slot="${D}_receipt_missing"></b> / <b data-slot="${D}_receipt_rows"></b> 项</span>`,
body:
part('做什么', '执行计划动作') +
`<div class="jc-derive">
<p><b>经营计划已验收通过</b>。以下是需要你<span class="need">到场执行</span>的动作(物理拜访、技术交流、递送实物 ——
都是 AI 做不到、需要人去干的)。每项动作执行后,在下方回执。</p>
</div>` +
part('交回什么', '逐项回执(*为必填)') +
`<div class="jc-gapbar" data-class-of="${D}_receipt_missing">
<span><b>还差 <span data-slot="${D}_receipt_missing"></span> 项回执没填齐</b>
—— <span data-slot="${D}_receipt_missing_keys"></span>。
按右上角「编辑」开表,红框那几格就是要填的。补齐了执行单才提交得了。</span>
</div>` +
editGate(
`${D}.receipt`,
`<b>共 <span data-slot="${D}_receipt_rows"></span> 项计划动作待回执</b>,点「编辑」逐项回填`,
) +
`<div class="jc-editbar">
<span>可填的是<b>完成状态</b>、<b>结果摘要</b>、<b>客户反馈</b>、<b>关键人变动</b>四列;
计划动作与对象是承继自计划的只读展示,改不了。确实没有变动的「关键人变动」请选「无变动」,不能留空。
改动只落任务草稿层,<b>保存不等于放行</b>。</span>
</div>` +
RECEIPT_TABLE +
flow('<b>审批 / 提交:</b>本工单只承载内容;「提交回执」请在对话区的【执行卡】中进行。若「关键人变动」标「是」,将触发台账写变更记录。'),
foot: sign('E04 关系覆盖与关键人经营 · 执行工单 2/2 · 经营计划执行回执', '工单类型:执行'),
});
/* ==================== 局面四 · 台账更新明细(S6/T_release,只读)==================== */
const LEDGER_UPDATE_TABLE = `<div class="jc-tbl-wrap"><table class="jc-tbl-in">
<thead><tr>
<th style="width:36px;min-width:36px"></th>
<th style="min-width:150px">台账</th>
<th style="min-width:200px">字段</th>
<th style="min-width:140px">更新前</th>
<th style="min-width:140px;width:100%">更新后</th>
</tr></thead>
<tbody data-rows="mine:${D}.ledger_update">
<tr>
<td class="jc-rn"></td>
<td class="dimempty" data-h="台账">
<span class="jc-vcell mono" data-slot="ledger_label" data-blank></span>
<span class="jc-sub mono"><i data-slot="customer_code" data-blank></i> <i data-slot="contact_code" data-blank></i></span>
</td>
<td class="dimempty" data-h="字段">
<span class="jc-vcell mono" data-slot="field_label" data-blank></span>
<span class="jc-sub">依据:<i data-slot="evidence_ref" data-blank></i></span>
</td>
<td class="dimempty" data-h="更新前"><span class="jc-vcell dim" data-slot="before_value" data-blank></span></td>
<td data-h="更新后"><span class="jc-vcell jc-diffnew" data-slot="after_value" data-blank></span></td>
</tr>
</tbody>
</table></div>`;
/*
* 「字段」格里多一行「依据」、「台账」格里多一行编码(有意偏离目标件):三条 L4 判据
* 让签字的人核「有没有依据 / 有没有张冠李戴」,而 evidence_ref / customer_code 从前
* 压根不在卡面上 —— 人被要求判他看不见的东西。次行小字摆进原格,四列结构不动。
*/
export const COVERAGE_LEDGER_UPDATE = doc({
tone: 'ledger',
badge: '验收',
tag: '验收工单',
kicker: 'E04 关系覆盖与关键人经营 · 验收工单 2/2',
title: '关系台账更新确认',
titleRef: 'round_ref',
step: '步骤 ⑥⑦ 更新台账',
state: `<span class="jc-rp-wait">共 <b data-slot="${D}_ledger_update_rows"></b> 项更新</span>`,
body:
part('AI 结论说明', 'AI 收到回执后做了哪些更新') +
`<div class="jc-derive">
<p><b>AI 已完成</b>:收到执行回执后,重算关系覆盖度(coverage_score)、更新决策链地图(decision_chain)、
更新 L-08 联系人档案(角色 / 影响力 / 亲密度 / 关键人标记)、追加互动时间线。
本轮覆盖结论:<span data-slot="summary" data-blank></span></p>
</div>` +
part('验收判据', '你据此判断「通过 / 驳回」') +
criteria('判断这次台账更新是否无误,请逐项核对', [
'<b>更新是否有依据</b>:每条更新是否都能在「执行回执」里找到对应事实(如 coverage_score 上升对应哪几次拜访);',
'<b>数值是否合理</b>:coverage_score、亲密度等数值变化幅度与回执是否吻合;',
'<b>有无漏更或误更</b>:该更新的字段都更新了、不该动的没动。',
]) +
part('拟更新的台账', '只读展示') +
LEDGER_UPDATE_TABLE +
flow('<b>审批:</b>本工单只承载内容;「通过入库 / 驳回」请在对话区的【验收卡】中进行。'),
foot: sign('E04 关系覆盖与关键人经营 · 验收工单 2/2 · 关系台账更新确认', '工单类型:验收 → 完结'),
});