资料文件真产出来了;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,163 @@
/**
* I01 品牌形象与销售工具包管理 · 视觉底(**I01 自己的 PRD 目标件**)。
*
* `docs/SS01-客户与市场/i01-html/` 下的三份 HTML(执行工单 1、验收工单 1/2)逐字量出。
* 颜色变量、卡头渐变、判据框、表格排版与 E04(`crm-relationship-coverage`)的目标件
* **逐字节相同**——两份目标件本就是同一套设计语言产出的(SS01 客户与市场同一批交付),
* 不是巧合也不是抄近路;`activities/*` 只准 import `@sdk`/`@contracts`,两个活动包之间
* 没有代码通道,只能各自持有一份(与 E04 那份原是 A1 家族版面拷贝、后改为自有目标件同理)。
*
* 版面照目标件分两层:这份文件是**底**(配色变量、卡壳、头/尾、判据框、说明框、表格基础排版),
* `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; }
/*
* 资料名 = 可点开的那一格(2026-08-23)。**看得出能点**才会有人点:
* 品牌蓝 + 下划虚线 + 手型;悬停实线。点了去哪由外层接(件里一行脚本也没有)。
*/
.proto-root .jc-fileln { color: var(--brand); border-bottom: 1px dashed var(--brand-soft); cursor: pointer; transition: all var(--t-fast); }
.proto-root .jc-fileln:hover { border-bottom-style: solid; background: var(--brand-wash); }
/* 空标题不该长得像能点开 —— 那一格根本没有资料 */
.proto-root .jc-fileln:empty { border-bottom: 0; cursor: default; background: none; }
/* 格式徽:摆的是这份资料交出去时的样子(md / html / docx / xlsx) */
.proto-root .jc-fmt { display: inline-flex; align-items: center; justify-content: center; min-width: 44px; min-height: 19px;
padding: 1px 7px; border-radius: 5px; font-size: 10px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase;
background: var(--soft); color: var(--ink-3); border: 1px solid var(--line); }
.proto-root .jc-fmt.v-docx { background: #eaf1ff; color: #2b5fd9; border-color: #c3d6ff; }
.proto-root .jc-fmt.v-xlsx { background: #e9f7ee; color: #1f7a45; border-color: #bfe6cd; }
.proto-root .jc-fmt.v-html { background: #fdf0e6; color: #b25e15; border-color: #f3d3b4; }
.proto-root .jc-fmt.v-md { background: #f2f3f5; color: #5a6068; border-color: #dfe1e5; }
.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 .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-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,387 @@
/**
* I01 品牌形象与销售工具包管理的 HTML 报告模版 —— **活动自己持有的资产**,随包版本冻结。
*
* 三个局面各一张:
* · `requirement-list` —— S1/S2 摆,内容需求清单,可编辑(增删改);
* · `draft-review` —— S3/S4 摆,内容初稿,**只读**(业务说明明文"人只审不改" ——
* 通过/驳回的按钮在对话区的决策卡上,不在这份件里);
* · `publish-confirm` —— S5/T_release 摆,拟发布内容,**只读**,放行后即归档件。
*
* 版面语言逐字量自 I01 自己的目标件(`docs/SS01-客户与市场/i01-html/` 三份 HTML,
* `prd-style.ts` 头注写了它与 E04 目标件"逐字节相同"的原因)。
*
* 取值三纪律:顶层只取汇总列与单值列;逐条走 `mine:content_brand.<object>` 或
* `mine:content.asset`;列名逐字照 `declarations.ledger` 抄。**模版不算不判** ——
* 缺必填红带、状态徽、排序位次全部由基座算好的数经 `data-class-of` 投影,判断留在样式表。
*/
import { PRD_CSS } from './prd-style.ts';
const D = 'content_brand';
/* ==================== 绑定期补的样式(数据驱动的开关,判断都在这一层)==================== */
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 才换孪生件 */
.proto-root .jc-cell { display: none; }
.proto-root .jc-tbl-in.on td > .jc-vcell { display: none; }
.proto-root .jc-tbl-in.on td > .jc-cell { display: block; }
/* 编辑态下必填空格子:红框 + 整行浅红(由 :placeholder-shown 认) */
.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) td { 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 决定,不写死在取值那一侧。
*/
.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-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-draft::after { content: "草稿"; }
.proto-root .jc-tag.v-draft { color: var(--ink-3); }
.proto-root .jc-tag.v-reviewed::after { content: "已审核"; }
.proto-root .jc-tag.v-reviewed { color: var(--warn-accent); }
.proto-root .jc-tag.v-published::after { content: "已发布"; }
.proto-root .jc-tag.v-published { color: var(--ok); }
.proto-root .jc-tag.v-deprecated::after { content: "已废弃"; }
.proto-root .jc-tag.v-deprecated { color: var(--ink-4); text-decoration: line-through; }
`;
/** 三层壳照目标件:少一层样式表全失效 */
/**
* 三层壳照目标件。卡头/页脚的共用动态位照说明书 §6.2 补齐(2026-08-23):
* 收件人(case:owner)、**关联任务**(case:taskId,基座注入)、**步骤**(step,
* 每份工单盖哪几步是写清单时定死的静态文案)、页脚**单号**(round_ref)。
* 目标件还有一格「最迟交回」—— 本仓没有工单期限这个事实,**不摆假数据**,那一格不做
* (与 E04 同一口径)。
*/
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 REQUIREMENT_TABLE = `<div class="jc-tbl-wrap"><table class="jc-tbl-in">
<thead><tr>
<th style="width:36px;min-width:36px"></th>
<th style="width:56px;min-width:56px">序</th>
<th class="req" style="min-width:150px">内容类型</th>
<th class="sug" style="min-width:170px">需求来源</th>
<th class="sug" style="min-width:120px">目标客户</th>
<th class="sug" style="min-width:100px">适用场景</th>
<th style="width:80px;min-width:80px">优先级</th>
</tr></thead>
<tbody data-rows="mine:${D}.requirement" data-edit="${D}.requirement"><tr>
<td class="jc-rn"><button type="button" class="jc-rowdel" data-edit-del="${D}.requirement" title="撤掉这一行">×</button></td>
<td data-h="序"><span class="jc-vcell mono" data-slot="req_seq" data-col="req_seq" data-blank></span></td>
<td class="req" data-h="内容类型"><span class="jc-vcell" data-slot="content_type" data-col="content_type" data-blank></span></td>
<td class="sug" data-h="需求来源"><span class="jc-vcell" data-slot="source" data-col="source" data-blank></span></td>
<td class="sug" data-h="目标客户"><span class="jc-vcell" data-slot="target_customer" data-col="target_customer" data-blank></span></td>
<td class="sug" data-h="适用场景"><span class="jc-vcell" data-slot="scene" data-col="scene" data-blank></span></td>
<td data-h="优先级"><span class="jc-tag" data-class-of="priority"></span></td>
</tr></tbody>
</table></div>`;
export const REQUIREMENT_LIST = doc({
tone: 'human',
badge: '执行',
tag: '执行工单',
kicker: 'I01 品牌形象与销售工具包管理 · 执行工单 1/3',
title: '需求受理与信息补充',
titleRef: 'round_ref',
step: '步骤 ① 需求识别与受理',
state: `<span class="jc-rp-wait">共 <b data-slot="${D}_requirement_rows"></b> 条需求</span>`,
body:
part('做什么', '确认本轮要生产的内容') +
`<div class="jc-derive">
<p><span class="done">AI 已完成</span>:按规则识别本轮内容需求来源(战略调整 / 新市场 / 工具过期 / 使用方请求)与
需求类型(公司介绍 / 能力声明 / 销售工具包 / 案例集 / 白皮书等),产出下方需求清单(候选基线)。</p>
<p class="jc-note-line">本轮触发:<b data-slot="trigger" data-blank></b> <b data-slot="trigger_detail" data-blank></b></p>
<p class="jc-note-line">本轮说明:<span data-slot="note" data-blank></span></p>
<p><span class="need">需要你查漏补缺</span>:点「编辑」在清单上增删改 —— <b>加一行 = 补充新需求</b>、
<b>删一行 = 排除</b>、<b>改单元格 = 修正</b>;尤其要补「目标客户、适用场景、优先级」这些 AI 规则之外的信息。</p>
</div>` +
part('交回什么', '核对后的内容需求清单(*为必填)') +
`<div class="jc-gapbar" data-class-of="${D}_requirement_missing">
<span><b>还差 <span data-slot="${D}_requirement_missing"></span> 行内容类型没填</b>
—— <span data-slot="${D}_requirement_missing_keys"></span>。
按右上角「编辑」开表,红框那几格就是要填的。补齐了执行单才提交得了。</span>
</div>` +
editGate(
`${D}.requirement`,
`<b>AI 已识别 <span data-slot="${D}_requirement_rows"></span> 个内容需求</b>,点「编辑」增删改`,
) +
`<div class="jc-editbar">
<span>加一行 = 补充新需求,删一行 = 排除,改单元格 = 修正 AI 识别的内容。
<b>内容类型</b>是必填,缺一条这一单走不到内容创作那一步。
改动只落任务草稿层,<b>保存不等于放行</b>。</span>
</div>` +
REQUIREMENT_TABLE +
`<div class="jc-addrow">
<button type="button" class="jc-egbtn ghost" data-edit-add="${D}.requirement">+ 加一行(补充新需求)</button>
<em>新加行的「内容类型」为必填;「目标客户」填定向客户,通用内容可留空;<b>序号留空即可</b> —— 系统按本轮现有最大值往下续。</em>
</div>` +
flow('<b>审批 / 提交:</b>本工单只承载内容;「提交回执」请在对话区的【执行卡】中进行。'),
foot: sign('I01 品牌形象与销售工具包管理 · 执行工单 1/3 · 需求受理', '工单类型:执行'),
});
/* ==================== 局面二 · 内容初稿(S3/S4,只读)==================== */
/*
* **资料名点得开**(2026-08-23 用户定):标题那一格挂 `data-file-of="content_id"`,
* 渲染时换成 `data-file="content.asset/<内容ID>"` —— 点它,工作区就跳到那一份资料的
* 查看面(基座按台账那一行现造成 docx / xlsx / md / html 再读回来渲)。
* 这份件仍然只做取值:「点了去哪」不在 HTML 里,它只说清「这一格是哪一份资料」。
*
* 格式一列摆在标题右边 —— 人点之前就知道点开的是张表还是一份文档。
*/
const DRAFT_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:200px">内容标题</th>
<th style="width:70px;min-width:70px">资料</th>
<th style="min-width:130px">内容类型</th>
<th style="width:120px;min-width:120px">语言版本</th>
<th style="width:80px;min-width:80px">BT 标签</th>
<th style="width:110px;min-width:110px">品牌调性</th>
<th style="width:90px;min-width:90px">状态</th>
</tr></thead>
<tbody data-rows="mine:content.asset">
<tr>
<td class="jc-rn"></td>
<td class="dimempty" data-h="内容标题"><span class="jc-vcell cust jc-fileln" data-file-of="content_id" data-slot="title" data-blank></span></td>
<td data-h="资料"><span class="jc-fmt" data-class-of="file_format" data-slot="file_format" data-blank></span></td>
<td class="dimempty" data-h="内容类型"><span class="jc-vcell" data-slot="content_type" data-blank></span></td>
<td class="dimempty" data-h="语言版本"><span class="jc-vcell mono" data-slot="language" data-blank></span></td>
<td class="dimempty" data-h="BT 标签"><span class="jc-vcell mono" data-slot="bt_tag" data-blank></span></td>
<td class="dimempty" data-h="品牌调性"><span class="jc-vcell mono" data-slot="brand_tone" data-blank></span></td>
<td data-h="状态"><span class="jc-tag" data-class-of="content_status"></span></td>
</tr>
</tbody>
</table></div>`;
export const DRAFT_REVIEW = doc({
tone: 'ledger',
badge: '验收',
tag: '验收工单',
kicker: 'I01 品牌形象与销售工具包管理 · 验收工单 2/3',
title: '内容初稿审核',
titleRef: 'round_ref',
step: '步骤 ②③④ 产出',
state: `<span class="jc-rp-wait">共 <b data-slot="content_asset_rows"></b> 份初稿</span>`,
body:
part('AI 结论说明', '结论是怎么算出来的') +
`<div class="jc-derive">
<p><b>AI 已完成的计算</b>:读取 L-01 宏观市场信号(产业趋势、市场依据),按 BT 层级草拟内容初稿——
BT-01 侧重供货能力 / 资质文件、BT-02 侧重技术规格书 / 替代对照表 / 认证报告、
BT-03 侧重系统方案白皮书 / 参考设计 / 成功案例;维护多语言版本(中 / 英 / 俄)。</p>
</div>` +
part('验收判据', '你据此判断「通过 / 驳回」') +
criteria('判断这份内容初稿是否合格,请逐项核对', [
'<b>品牌调性是否一致</b>:是否符合品牌"气质"标准(专业 / 可信赖 / 高效 / 本土化);',
'<b>技术内容是否准确</b>:规格、数据、认证信息有无错误;',
'<b>合规与多语言校对</b>:合规检查、多语言校对是否通过。',
]) +
part('AI 产出的内容初稿', '只读展示') +
DRAFT_TABLE +
flow('<b>审批:</b>本工单只承载内容;「通过 / 驳回」请在对话区的【验收卡】中进行。通过后进入发布,驳回由 AI 重做。'),
foot: sign('I01 品牌形象与销售工具包管理 · 验收工单 2/3 · 内容初稿审核', '工单类型:验收'),
});
/* ==================== 局面三 · 拟发布内容(S5/T_release,只读)==================== */
const PUBLISH_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:110px">内容 ID</th>
<th style="min-width:200px">标题</th>
<th style="width:120px;min-width:120px">语言版本</th>
<th style="width:80px;min-width:80px">版本号</th>
<th style="min-width:100px">适用场景</th>
<th style="width:80px;min-width:80px">BT 标签</th>
<th style="min-width:150px">状态流转</th>
</tr></thead>
<tbody data-rows="mine:content.asset">
<tr>
<td class="jc-rn"></td>
<td class="dimempty" data-h="内容 ID"><span class="jc-vcell mono" data-slot="content_id" data-blank></span></td>
<td class="dimempty" data-h="标题"><span class="jc-vcell cust jc-fileln" data-file-of="content_id" data-slot="title" data-blank></span></td>
<td class="dimempty" data-h="语言版本"><span class="jc-vcell mono" data-slot="language" data-blank></span></td>
<td class="dimempty" data-h="版本号"><span class="jc-vcell mono" data-slot="version" data-blank></span></td>
<td class="dimempty" data-h="适用场景"><span class="jc-vcell" data-slot="scene" data-blank></span></td>
<td class="dimempty" data-h="BT 标签"><span class="jc-vcell mono" data-slot="bt_tag" data-blank></span></td>
<td data-h="状态流转"><span class="jc-vcell dim">已审核</span> → <span class="jc-tag" data-class-of="content_status"></span></td>
</tr>
</tbody>
</table></div>`;
export const PUBLISH_CONFIRM = doc({
tone: 'ledger',
badge: '验收',
tag: '验收工单',
kicker: 'I01 品牌形象与销售工具包管理 · 验收工单 3/3',
title: '发布确认',
titleRef: 'round_ref',
step: '步骤 ⑤ 发布入库',
state: `<span class="jc-rp-wait">共 <b data-slot="content_asset_content_status_published"></b> 份拟发布</span>`,
body:
part('AI 结论说明', 'AI 做了什么') +
`<div class="jc-derive">
<p><b>AI 已完成</b>:内容初稿通过审核后,拟正式发布 —— 更新版本号、状态流转(待审核 → 已审核 → 已发布)、
登记适用场景与品牌调性标签,写入 L-05 品牌内容库。本轮产出:<span data-slot="summary" data-blank></span></p>
</div>` +
part('验收判据', '你据此判断「通过 / 驳回」') +
criteria('判断这次发布是否无误,请逐项核对', [
'<b>版本号与状态是否正确</b>:版本号已更新、状态流转(草稿 → 已审核 → 已发布)无误;',
'<b>场景与 BT 标签是否登记完整</b>;',
'<b>多语言版本是否齐全</b>。',
]) +
part('拟发布的内容', '只读展示') +
PUBLISH_TABLE +
flow('<b>审批:</b>本工单只承载内容;「通过入库 / 驳回」请在对话区的【验收卡】中进行。通过即落账写入 L-05,本次内容生产结束。'),
foot: sign('I01 品牌形象与销售工具包管理 · 验收工单 3/3 · 发布确认', '工单类型:验收 → 完结'),
});