/** * A1 询价受理的 HTML 报告模版 —— **从 PRD 目标件搬过来的那两份件**。 * * 出处(逐块照搬,样式表见 `prd-style.ts`,一个数值都没改): * · `~/coding/JCAI_PRD/docs/活动执行页/A1-询价受理需求.html` → `INTAKE_OVERVIEW` * (阶段2 · 待处理页里嵌的那份); * · `~/coding/JCAI_PRD/docs/活动执行页/A1-询价受理记录.html` → `INTAKE_RELEASE` * (阶段3 · 工作成果里定稿归档的那份)。 * * ── 为什么是搬,不是照着做 ───────────────────────────────────────────── * 前三轮我拿自己那套 SDK 外壳「照着做」:块搬齐了,版面却是两套 —— 章节头成了 * 彩色方块编号、多出一段摘要与一条元信息带、字号用 clamp 响应式。三次报「25 块到位」, * 而那是**结构断言**(某个 class 在不在),对版面一点约束都没有。人一眼就看出不是那份件。 * 目标件是像素级的答案:搬过来,才不会在复述里走样。 * * ── 搬的时候,哪些东西必须换掉 ───────────────────────────────────────── * 原型是**带脚本的演示页**:数据写死在 script 里,由 JS 造表、算计数、切编辑态。 * 而这里的容器是 sandbox iframe,**一行脚本也跑不了**;填数只有三件事 —— * `data-slot` 取值、`data-rows` 逐行展开、`data-class-of` 按值上色。所以: * · 写死的行 → `data-rows` 模版,一行一条从台账草稿展开; * · JS 算的计数 → 台账里的汇总列,`data-slot` 取; * · JS 造的「必填/选填」占位胶囊 → CSS `:empty::after`(基座对空值留空,见 `data-blank`); * · JS 算的**命中行**与结论横幅 → 基座按契约的出口谓词求值后注入 `case:route*` * (与放行时发口的是同一次判定,不是这屏自己又算了一遍); * · 编辑态 → **就长在件里**(2026-08-01 改)。每个可改的格子渲两份:只读那份与 * `` 孪生件,由根节点上的 `editing` 二选一显示。 * 待处理那一屏摆的**只有这份件**,所以改数据也只能在件上改; * 工作成果不加那个 class,同一份 HTML 就是归档件。 * 收值在外层(`allow-same-origin` 读得到 DOM),iframe 里仍然一行脚本都没有。 * · 「修改 / 确认」按钮 → 不做:提交那一下在**对话区的卡**上, * 判据与作业指导书也在那儿 —— 工作区只放件本身。 * * ── 搬不过来的两处,如实空着 ─────────────────────────────────────────── * · 圆标里的首字母(IT / EV / 冯)—— 得算,而模版不许算;换成同形状里的图标。 * · 「近 6 笔成交」迷你柱 —— `market.price_history` 有列没数据(种子不写、CRM 是桩), * 没有写入方。画六根编出来的柱子是撒谎,画一条空轨像组件坏了,所以那一格不画, * 把「为什么没有」写在卡脚。 * * ── 取值路径三纪律(编错一个字,屏上就是一片「—」)──────────────────── * ① 顶层只取汇总列与单值列;逐行列摊到顶层是「后写的赢」,摆到题头上是抽样不是概览。 * ② 逐条数据一律走 `mine:域.对象` —— `域.对象` 是正式版全表,别单的行也在里面。 * ③ 列名逐字照 `manifest.ts` 的 `declarations.ledger` 抄,不照记忆写。 */ import { PRD_CSS, PRD_SPRITE } from './prd-style.ts'; /* ==================== 搬运期补的几条样式 ==================== */ /** * 原型用 JS 干、这里只能用 CSS 干的那几件 —— **补在原样式表之后,不改原值**。 * * ① 空格子的「必填 / 选填」胶囊:原型是 JS 判空后插一个 `jc-ph`; * 这里改成 `:empty::after`,靠基座对空值留空(`data-blank`)触发,胶囊样式仍用原型的数值。 * ② 缺必填的整行浅红 + 行首红条:原型给 `tr.miss`,这里用 `:has()` 认。 * ③ 类徽 / 分布条 / 判定表命中行按值上色:原型由 JS 拼 class, * 这里由基座把值消毒成 `v-*` 追加进 class(`data-class-of`)—— 判断在样式表里,不在填数里。 */ const BIND_CSS = ` /* ① 空格子自己说话 —— 值真的空才轮到这三条(基座 data-blank)。 * 占位三级(2026-08-03 补齐,用户:「目标价是建议」):必填红 / 建议橘 / 选填灰, * 档位跟台账声明走(required → req,expect → sug,其余 opt),颜色与 A2 同一套。 */ /* 胶囊与同一行的数字坐同一条线(2026-08-03 用户:「没有和数字对齐」)—— * 裸 inline-flex 的胶囊按 baseline 挂:盒子(10px 字 + 上下 3px 内距 + 边框 = 18px)比 * 字的基线深好几像素,视觉上整枚往下坠。治法:空格子自己撑起文字那份盒高(12px×1.5), * 胶囊在里面垂直居中 —— 中线对中线,行高也不再忽高忽低。 * :not(.on) 不能省(2026-08-03 用户:「编辑态太拥挤」):这条比「编辑态藏起只读份」 * 的那条更特异,不圈住的话编辑态里胶囊会盖过 display:none 冒出来,54 个输入框旁边 * 各站一枚 —— 编辑态里格子该不该填由闸与红带说,胶囊只属于看的那份。 */ .proto-root .jc-tbl-in:not(.on) td.req > .jc-vcell:empty, .proto-root .jc-tbl-in:not(.on) td.sug > .jc-vcell:empty, .proto-root .jc-tbl-in:not(.on) td.opt > .jc-vcell:empty { display: flex; align-items: center; min-height: 18px; } .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: #b45309; background: #fff7ed; border-color: #fcd9a8; } .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); } /* ② 缺必填的整行 —— 原型是 tr.miss,这里由 :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; position: relative; } @media (min-width: 1001px) { .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); } /* 窄屏原型给的是 ::before「第 」+ ::after「 条」;这里 ::before 已被计数器占用, 把两句合进 ::before,并把原型那句 ::after 收掉 —— 否则窄屏上写着「第 9 条 条」 */ @media (max-width: 1000px) { .proto-root .jc-tbl-in td.jc-rn::before { content: "第 " counter(rn) " 条"; } .proto-root .jc-tbl-in td.jc-rn::after { content: none; } } /* ④ 就地改:每个可改的格子渲了两份 —— 只读那份与输入那份,这里二选一。 **默认锁着**:开关是那张表上的 on(外层按下「编辑」才加), 一屏两张表各管各的。判断在样式表里,不在填数里,件本身仍是同一份。 */ /* 默认锁着:只读那份在,输入孪生件收起来 —— **件首先是给人看的** */ .proto-root .jc-cell { display: none; } /* 开闸是给那张表的容器加 「on」(外层按下「编辑」才加)—— 一屏两张表各管各的 */ .proto-root .jc-tbl-in.on td > .jc-vcell, .proto-root .jc-tbl-in.on td > .jc-tbadge { display: none; } .proto-root .jc-tbl-in.on td > .jc-cell { display: block; } /* 类那一列平时只有一枚 24px 的徽,编辑态要放得下一个字 */ .proto-root .jc-tbl-in.on td.jc-ty, .proto-root .jc-tbl-in.on th:nth-child(2) { width: 56px; min-width: 56px; } /* * **数值列也左对齐**(2026-08-02 用户:「目标价的价格也要跟标题左对齐,右对齐太奇怪了」)。 * * 右对齐是给**要竖着相加的那种数**用的(金额合计、数量小计)——这张表里的目标价 * 一行一个料、彼此不相加,右对齐只是让它和自己的表头分家:标题贴左、数贴右, * 中间空一条,眼睛得来回找。等宽字体留着(位数对齐仍有用),对齐方式跟表头走。 */ .proto-root .jc-vcell.num, .proto-root .jc-cell.num { text-align: left; } .proto-root .jc-tbl-in.on td.num > .jc-cell, .proto-root .jc-tbl-in.on .jc-cell.num { text-align: left; } /* 编辑态下必填空格子:红框 + 红 placeholder(原型的 req-empty 同款,只是由 :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; } /* ⑥ **铺满工作区**(2026-08-02 用户定:「报告不要做成单独的嵌入层」)。 原型那份是独立网页,jc-stage 给了 1160px 上限与 22/16/52px 的四周留白 —— 摆进工作区就成了「画布上浮着的一张小卡」:左右各空一条,底下还坠着 52px。 件在这里**就是这一屏的正文**,不是嵌在里面的另一个页面:去掉上限与横向留白, 外框那圈圆角边也一并卸掉(工作区自己已经是一张卡,两层框叠着像贴纸)。 */ .jc-stage { max-width: none; padding: 0 0 4px; } .proto-root .jc-report { border: 0; border-radius: 0; box-shadow: none; } /* ⑤ 那道闸 —— 默认「已锁定 + 编辑」,开了变成「取消 / 保存」。 整块只在这一屏可改时才出现(外层给根节点加 「editable」); 归档件里它一个字都不该有 —— 那时件是件,不是表单。 */ .proto-root .jc-editgate { display: none; } html.editable .proto-root .jc-editgate { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; padding: 8px 12px; border-radius: 9px; background: var(--soft); border: 1px solid var(--line-soft); font-size: 11.5px; color: var(--ink-3); } .proto-root .jc-editgate-t { display: inline-flex; align-items: center; gap: 6px; 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: 4px; cursor: pointer; border: 1px solid var(--line); border-radius: 7px; padding: 5px 11px; font: inherit; font-size: 11.5px; 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(--brand); border-color: var(--brand); } .proto-root .jc-egbtn.save:hover { background: var(--brand-deep); border-color: var(--brand-deep); } /* 锁着的时候只有「编辑」;开了换成「取消 / 保存」—— 三个钮同时摆着,人不知道现在算哪一态 */ .proto-root .jc-editgate .jc-egbtn.ghost, .proto-root .jc-editgate .jc-egbtn.save { display: none; } .proto-root .jc-editgate.on { background: var(--brand-wash); border-color: var(--brand-soft); color: var(--brand-deep); } .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; } /* 缺项红带 —— **只在这一屏可改时出现**,且一格不缺就不出(判断在样式表,不在填数里) */ .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 svg { flex: 0 0 auto; margin-top: 2px; } .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; } /* ⑦ 增删行 —— 手改是兜 AI 的错那一手,而错不止「这一格填错了」: 「这一行根本不该在」与「漏了一行」是同一类错,只给改格子就没有出口。 */ /* 序号那一格一列两用:只读时是行号(计数器画的),编辑时是「撤掉这一行」的 × */ .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; width: 20px; height: 20px; margin: 0 auto; cursor: pointer; border: 1px solid var(--line); border-radius: 6px; padding: 0; font: inherit; font-size: 13px; 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-tbl-in.on .jc-rowdel:hover { border-color: var(--danger); color: var(--danger); background: #fff6f6; } /* 撤掉的行**不当场抹掉** —— 划着摆在那儿,人看得见自己删了哪一行,也才有得后悔 */ .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); } .proto-root .jc-addrow em { font-style: normal; font-size: 11px; color: var(--ink-3); } /* ③ 按值上色:基座把值消毒成 v-* 追加进 class */ /* 刻度条按缺口:missing_key_fields 是条数,0 = 完整(绿,原型的默认色),非 0 = 缺必填(红) */ .proto-root .jc-segmeter > i.v-1, .proto-root .jc-segmeter > i.v-2, .proto-root .jc-segmeter > i.v-3 { background: var(--danger); } /* 刻度条按状况标:归档屏那张卡上,一格即一条料的类别 */ .proto-root .jc-segmeter > i.v-a { background: var(--ok); } .proto-root .jc-segmeter > i.v-b { background: var(--brand); } .proto-root .jc-segmeter > i.v-c { background: var(--warn-accent); } .proto-root .jc-tbadge { background: var(--ink-4); } .proto-root .jc-tbadge.v-a { background: var(--ok); } .proto-root .jc-tbadge.v-b { background: var(--brand); } .proto-root .jc-tbadge.v-c { background: var(--warn-accent); } /* 分布条:三段按条数分宽。原型是 JS 写 inline style="flex:N",而模版不许算 —— 改由 data-class-of 把**条数本身**投影成 v-,这里备一把梯子让 CSS 认。 条数 0 的那段整段不出:摆一条零宽的彩带不如不摆。 */ /* 原型那段是 grid + place-items:center;这里段里多了个 ::before 前缀, grid 会把前缀和数字排成上下两行 —— 改成一行居中的 flex */ .proto-root .jc-distbar > i { flex: 24; min-width: 34px; display: inline-flex; align-items: center; justify-content: center; } .proto-root .jc-distbar > i.v-0, .proto-root .jc-distbar > i:not([class*=" v-"]) { display: none; } .proto-root .jc-distbar > i.v-1 { flex: 1 } .proto-root .jc-distbar > i.v-2 { flex: 2 } .proto-root .jc-distbar > i.v-3 { flex: 3 } .proto-root .jc-distbar > i.v-4 { flex: 4 } .proto-root .jc-distbar > i.v-5 { flex: 5 } .proto-root .jc-distbar > i.v-6 { flex: 6 } .proto-root .jc-distbar > i.v-7 { flex: 7 } .proto-root .jc-distbar > i.v-8 { flex: 8 } .proto-root .jc-distbar > i.v-9 { flex: 9 } .proto-root .jc-distbar > i.v-10 { flex: 10 } .proto-root .jc-distbar > i.v-11 { flex: 11 } .proto-root .jc-distbar > i.v-12 { flex: 12 } .proto-root .jc-distbar > i.v-13 { flex: 13 } .proto-root .jc-distbar > i.v-14 { flex: 14 } .proto-root .jc-distbar > i.v-15 { flex: 15 } .proto-root .jc-distbar > i.v-16 { flex: 16 } .proto-root .jc-distbar > i.v-17 { flex: 17 } .proto-root .jc-distbar > i.v-18 { flex: 18 } .proto-root .jc-distbar > i.v-19 { flex: 19 } .proto-root .jc-distbar > i.v-20 { flex: 20 } /* 段里那句「A · 5」的前缀 —— 类别是这一段的固有属性,不必从数据里取 */ .proto-root .jc-distbar > i.a::before { content: "A · "; } .proto-root .jc-distbar > i.b::before { content: "B · "; } .proto-root .jc-distbar > i.c::before { content: "C · "; } /* 判定表命中行:基座把命中的那一条注入成 v-r1/r2/r3,壳上带 class,行自己认 */ .proto-root .jc-dmn.v-r1 { --tone: var(--warn-accent); --tone-soft: rgba(255,136,0,.11); } .proto-root .jc-dmn.v-r2 { --tone: var(--brand); --tone-soft: rgba(51,112,255,.09); } .proto-root .jc-dmn.v-r3 { --tone: var(--ok); --tone-soft: rgba(39,135,23,.10); } .proto-root .jc-dmn.v-r1 .jc-dmn-r.r1, .proto-root .jc-dmn.v-r2 .jc-dmn-r.r2, .proto-root .jc-dmn.v-r3 .jc-dmn-r.r3 { position: relative; background: var(--tone-soft); color: var(--ink-2); } .proto-root .jc-dmn.v-r1 .jc-dmn-r.r1::before, .proto-root .jc-dmn.v-r2 .jc-dmn-r.r2::before, .proto-root .jc-dmn.v-r3 .jc-dmn-r.r3::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 4px; background: var(--tone); } .proto-root .jc-dmn.v-r1 .jc-dmn-r.r1 .jc-dmn-c, .proto-root .jc-dmn.v-r2 .jc-dmn-r.r2 .jc-dmn-c, .proto-root .jc-dmn.v-r3 .jc-dmn-r.r3 .jc-dmn-c { color: var(--ink); } .proto-root .jc-dmn.v-r1 .jc-dmn-r.r1 .jc-dmn-i, .proto-root .jc-dmn.v-r2 .jc-dmn-r.r2 .jc-dmn-i, .proto-root .jc-dmn.v-r3 .jc-dmn-r.r3 .jc-dmn-i { color: var(--tone); } .proto-root .jc-dmn.v-r1 .jc-dmn-r.r1 .jc-dmn-c b, .proto-root .jc-dmn.v-r2 .jc-dmn-r.r2 .jc-dmn-c b, .proto-root .jc-dmn.v-r3 .jc-dmn-r.r3 .jc-dmn-c b { color: var(--tone); font-weight: 800; } .proto-root .jc-dmn.v-r1 .jc-dmn-r.r1 .jc-dmn-t, .proto-root .jc-dmn.v-r2 .jc-dmn-r.r2 .jc-dmn-t, .proto-root .jc-dmn.v-r3 .jc-dmn-r.r3 .jc-dmn-t { color: var(--ink); } .proto-root .jc-dmn.v-r1 .jc-dmn-r.r1 .jc-dmn-t code, .proto-root .jc-dmn.v-r2 .jc-dmn-r.r2 .jc-dmn-t code, .proto-root .jc-dmn.v-r3 .jc-dmn-r.r3 .jc-dmn-t code { color: #fff; background: var(--tone); border-color: var(--tone); } /* 判定那一列:两句话都写在模版里,哪一句现身**由壳上那个 v-rN 决定** —— 模版仍然不许算,而「命中 / 未求值」这层判断留在样式表。 一条都没命中(算不出去向)时壳上没有 v-rN,两句都不出:不冒充判过。 */ .proto-root .jc-dmn .jc-dmn-v > .y, .proto-root .jc-dmn .jc-dmn-v > .n { display: none; } .proto-root .jc-dmn.v-r1 .r1 .jc-dmn-v > .y, .proto-root .jc-dmn.v-r2 .r2 .jc-dmn-v > .y, .proto-root .jc-dmn.v-r3 .r3 .jc-dmn-v > .y { display: inline-flex; align-items: center; gap: 5px; font-weight: 800; color: var(--tone); } .proto-root .jc-dmn.v-r1 .r2 .jc-dmn-v > .n, .proto-root .jc-dmn.v-r1 .r3 .jc-dmn-v > .n, .proto-root .jc-dmn.v-r2 .r1 .jc-dmn-v > .n, .proto-root .jc-dmn.v-r2 .r3 .jc-dmn-v > .n, .proto-root .jc-dmn.v-r3 .r1 .jc-dmn-v > .n, .proto-root .jc-dmn.v-r3 .r2 .jc-dmn-v > .n { display: inline; color: var(--ink-4); } .proto-root .jc-verdict.v-r1 { --tone: var(--warn-accent); --tone-soft: rgba(255,136,0,.14); --tone-line: #ffd9a8; } .proto-root .jc-verdict.v-r2 { --tone: var(--brand); --tone-soft: rgba(51,112,255,.13); --tone-line: var(--brand-soft); } .proto-root .jc-verdict.v-r3 { --tone: var(--ok); --tone-soft: rgba(39,135,23,.13); --tone-line: #bfedb4; } /* 圆标里放图标不放首字母 —— 首字母得算,而画一个算错的缩写不如画一个说得清类别的图标 */ .proto-root .jc-logo svg, .proto-root .jc-face svg, .proto-root .jc-rp-ava svg { width: 17px; height: 17px; } `; /** 一枚图标 —— 与原型同一写法:`use` 引精灵里的 symbol */ const ico = (name: string, size = 11): string => ``; /** * 一份件的外壳 —— **照原型的三层壳**:`.proto-root > .app-shell > .jc-stage > article.jc-report`。 * 少一层,整张样式表(选择器带 `.proto-root .app-shell` 前缀)一条都不生效。 */ const doc = (input: { badge: string; kicker: string; title: string; titleRef: string; /** 题头右上:在办亮灯(jc-rp-wait);办结那一枚印章由归档态自己带 */ state: string; body: string; foot: string; }): string => ` ${PRD_SPRITE}
${ico(input.badge, 17)}
${input.kicker}

${input.title}

${input.state}
${input.body}
${input.foot}
`; /** 章节头 —— 原型的 `jc-rp-part`:小标签 + 细线 + 右端小签 */ const part = (label: string, aside = '', tone = ''): string => `
${label}${aside === '' ? '' : `${aside}`}
`; /** 落款 —— 署名取任务负责人(基座填);右端那枚签写「这份件在流里的位置」,不写状态 */ const sign = (note: string, tag = ''): string => `
${ico('user', 15)}
${note}
${tag === '' ? '' : `${ico('git-branch', 13)}${tag}`}`; /* ==================== 共用件 ==================== */ /** * 来源信件 —— 原型的 `jc-letter`。正文 `pre-wrap` 原样照录:摘要过的原文就不是原文了。 * 收件签旁那枚归属签(通道入站 → 归了谁)与右端入站时刻都由基座填 —— * 让活动自报「归属解析 → 冯兵」,等于让被指派的人自己说自己是谁。 */ const LETTER = `
${ico('mail-open', 12)}收件 · 归属解析 →

发件人

${ico('sheet', 13)}
`; /** * 受理要素四卡 —— 原型的 `jc-four`。 * `kind` 决定第四张卡下面那条刻度条按什么上色:过程屏按缺口(红格 = 必填未补), * 归档屏按状况标(A 绿 / B 蓝 / C 橙)。**一格一条料**,每格都对得上清单里的一行。 */ const four = (kind: 'gap' | 'type'): string => `
${ico('building')}客户公司
已命中客户资料库
${ico('user')}联系人
${ico('user', 17)}
采购口 · 本单对外发件收件人
${ico('history')}客户属性 ${ico('shield-check', 13)} 成交台账尚未接入 —— 历史价由下游定价环节现查; 新客户走首单口径,不得取锚点
${ico('boxes')}有效 PN
/ 行 BOM
${ kind === 'type' ? '一格一条料 · 按状况标着色 · 条已交下游' : '一格一条料 · 红格是必填未补 条' }
`; /** * 有效 PN 清单 —— 原型那六列,一列不多一列不少。 * * 「客户料号」原型明写不收(『Itelma Code 不是本记录的字段』),这里也不摆: * 报告是给报价这条线看的,客户自己的编码在 A1 出口的载荷里,不占版面。 * `req`/`opt` 只决定空格子出哪种胶囊;归档屏一律 `dimempty`(那时空就是「客户没给」, * 再摆「你还能填」的红胶囊就是在催一件已经办完的事)。 */ const listTable = (mode: 'draft' | 'final'): string => { /* * `data-col` 一挂,这一格就多渲一个输入孪生件(基座 `twinInputs`), * 待处理那一屏可就地改;写的是**台账列名**,不是表头文字 —— * 表头改个说法不该让写回错位。归档件不挂:那时它是件,不是表单。 */ const cell = (key: string, head: string, kind: 'req' | 'sug' | 'opt', style = ''): string => { const cls = mode === 'final' ? 'dimempty' : kind; return ``; }; const req = mode === 'final' ? '' : 'req'; /* * 序号那一格在编辑态换成「撤掉这一行」的 × —— 一列两用,不为它单开一列。 * 只读时它是行号(CSS 计数器画的),编辑时人要的是「这一行不该在」的那个动作。 */ const del = ``; return `
${mode === 'final' ? '' : ''} ${del} ${mode === 'final' ? '' : ''} ${cell('pn', 'PN', 'req', 'mono')} ${cell('category', '品类', 'sug')} ${cell('eau', '年用量', 'req', 'mono')} ${cell('brand', '制造商', 'sug')} ${cell('target_price', '目标价', 'sug', 'num')} ${cell('remark', '备注', 'opt')}
类PN 品类 年用量 制造商 目标价 备注
`; }; /** 编辑态那条提示带 —— 原型的 `jc-editbar`,只在待处理那一屏现身 */ /** * **缺哪几格,红字直说**(2026-08-02 用户:「执行单现在要怎么填不明显,在报告上明显一点」)。 * * 从前这一屏只在空格子里摆一枚小小的「必填」胶囊 —— 九行料摊开,那几枚胶囊淹在表里, * 人得逐行扫过去才知道自己要动哪一格。而他打开这一屏就是来填的。 * * 现在表上方直接一条红带:**还差 N 格,分别是哪一行的哪一列**。 * 两个数都由基座现算(`域_对象_missing` / `_missing_keys`),模版不算也不判 —— * 「一格不缺就别出这条带」这个判断交给样式表:`data-class-of` 把数消毒成 * `v-0` / `v-1` / …,`v-0` 那一档 `display:none`。件本身仍然只做取值。 */ const gapBar = `
${ico('alert', 14)} 还差 格必填没补 —— 。 按右上角「编辑」开表,红框那几格就是要填的;补齐了才提交得了。
`; /** * 一张可改的表的**闸** —— 默认锁着,按「编辑」才开(2026-08-01 用户定)。 * * 从前进这一屏整份件就是编辑态:54 个输入框一起亮着,人还没决定要改什么, * 屏上已经是一张待填的表 —— **看**与**改**是两件事,件首先是给人看的。 * * 三个按钮由外层挂事件(容器 `allow-same-origin`,读得到也改得动 iframe 的 DOM); * **iframe 里仍然一行脚本都没有**。`data-edit-*` 是外层认它们的钩子: * · `open` → 给根节点加 `editing`,输入孪生件现身; * · `cancel` → 把每个输入还原成进编辑态那一刻的值,再关掉; * · `save` → 收值交给外层缓冲(提交那一下才真落库),再关掉。 * 一屏可能有两张表(需求表 / 分类表),所以闸带 `data-edit-for="域.对象"` —— 各管各的。 */ const editGate = (address: string, what: string): string => `
${ico('lock', 12)}${what}已锁定 —— 看的时候不误碰,要改按右边
${ico('pen', 14)} 改哪一格点哪一格:整份清单每一格都能覆盖 —— AI 抽错的直接改,缺的补上; 多出来的行按右边的 × 撤掉,漏了的用底下的「加一行」补。 红框是必填未补。改动只落任务草稿层,保存不等于放行。
`; /** * 表尾那条「加一行」—— **只在编辑态现身**(样式表按根节点的 `editing` 决定)。 * * 为什么人得能加行:手改本来就是**兜 AI 的错**的那一手,而错不止「这一格填错了」—— * 「BOM 里有而它没抽出来」是同一类错,只给改格子就没有出口。 */ const addRowBar = (address: string): string => `
照末行的样子添一行空的;主键(型号 PN)填了才存得下,不填这一行不落库。
`; /* * ── 这里**去掉了「本轮不报」那条横幅**(2026-08-02 用户当场指出:「也删了」)──── * * 原文:「本轮不报 N 行:… —— 客户补齐规格后可另起一单。 * 有效条目数 = 来件条目 − 本轮不报,两个数对不上就是拆漏了」。 * * 绝大多数单子它是「0 行:—」——**一条永远摆在那儿、九成时候说「没有」的横幅**, * 读的人每次都得扫过它才知道没事。而后半句是讲给拆料那一步听的对账口诀, * 不是给验收的人看的:拆漏没拆漏由收步判据 `record_rows = line_total` 当场拦着, * 拦不住的时候摆一句口诀也没用。 * * 题头那一行仍如实写着「N 条 · 已剔除 M」——「剔除了几条」这个事实没有丢。 */ /** * 料号分类:分布条 + 图例 —— 原型的 `jc-dist`。 * * 三段按条数分宽:段宽在原型里是 JS 写的 `style="flex:N"`,模版不许算, * 于是把**条数本身**投影成 class(`data-class-of` → `v-5`),让样式表按梯子取宽。 * 段里印的数字与图例读的是同三个汇总列 —— 一处数据两处呈现,对不上就是台账自己错了。 */ const DIST = `
A 类 · 行情完备 B 类 · 需寻源询价 C 类 · 需替代选型
`; /* ==================== 局面一 · 受理中(阶段2 待处理页嵌的那份)==================== */ export const INTAKE_OVERVIEW = doc({ badge: 'file-text', kicker: '统一报价 · A1 受理归一 · 过程成果', title: '询价受理需求', titleRef: 'rfq_ref', state: '缺必填 · 缺选填 ', body: part('来源 · 客户 RFQ 邮件', ' 入站', 'jc-mono') + LETTER + `
${ico('info', 14)} 下方受理要素与 PN 清单,均由 AI 从这封来函与附件 BOM 抽取归一而来 —— 正文里的口径要求 (币种 / 贸易条款 / 交期单位 / 认证 / 预授权替代 / 本轮跳哪条)已逐条落到对应字段与备注; 附件那一行写的是基座读取的结果,读得出文本才作数。
` + part('受理要素', 'F-01 完整度自检') + four('gap') + part('有效 PN 清单', ' 条 · 已剔除 ') + gapBar + editGate('quote_intake.record', '有效 PN 清单') + listTable('draft') + addRowBar('quote_intake.record') + `
${ico('info', 13)} 带 * 的两列是必填(PN / 年用量):缺任一按 F-01 退回补全,不许放行; 品类 / 制造商 / 目标价缺了标橘「建议」—— 值得追问、尽量补上,备注缺了标灰「选填」。 三档都不为它们拦单,先受理、后追问,但那一问不能省。 原文没写的一律不补:受理阶段编出来的数会一路流到定价和报价单上,越往后越难发现。
`, foot: sign('AI 拟稿,你审定 —— 要改哪一格,在「待处理」那张单的需求表上就地改', '统一报价 · A1 受理归一'), }); /* ==================== 局面三 · 已分类 ==================== */ export const INTAKE_CLASSIFIED = doc({ badge: 'shield-check', kicker: '统一报价 · A1 受理归一 · 三分类 P-03', title: '料号分类表', titleRef: 'rfq_ref', state: '已分级 · 未打标 ', body: part('料号分类', '三分类规则 P-03 · 逐条判定') + DIST + part('逐条分级(判据 → 结论)', '按序求值 · 首个命中即出') + editGate('quote_intake.type_table', '料号分类表') + `
型号 PN 距上次报价(天) 行情稳定 生命周期 类 分级依据
` + `
${ico('info', 13)} 三分类规则 P-03 是决策表,不是临场判断:同样的输入必得同样的结论,凭哪一条命中都留了痕。 A = 历史价 ≤ 90 天且行情稳;B = 无历史 / 过期 / 波动;C = EOL / 缺货 / 客户要求替代。 规则从上往下第一条命中即出 —— C 那一行排在最前,EOL 的料哪怕历史价很新也不判 A。 拿不准的一律降 B:错标一个 A,就是让一条本该重新核价的料带着旧价走到报价单上。
`, foot: sign('状况标由判定表 P-03 求值 —— 判据与结论逐条可回核', '统一报价 · A1 受理归一'), }); /* ==================== 局面四 · 待放行(阶段3 工作成果里的那份)==================== */ /** * G2 分支网关 —— 三条互斥条件按序求值,只命中一条、只落一个活动。 * * **命中哪一条不是这屏自己算的**:契约给每个出口写了判据,基座在放行那一刻按序求值, * 把结果注入 `case:route*`(与发口用的是同一次判定)。这里只把它摆出来: * 壳上带 `v-r1/r2/r3`,对应那一行自己上色。 * 摆成三张并列的去向卡就丢了「按序求值、只落一个」那层意思,所以照原型做成判定表。 */ const dmnRow = (n: 1 | 2 | 3, cond: string, node: string, to: string, hit: string): string => `
${n} ${cond} ${node}${to} ${ico('check', 13)}命中 · ${hit}未命中 · 排他网关只落一口
`; const G2 = `
序条件(按序求值)去向判定
${dmnRow(1, '存在 C 类(需替代选型)', 'A2', '替代选型', 'C 类 条')} ${dmnRow(2, '无 C 类、存在 B 类(需寻源询价)', 'A3', '渠道寻源', 'B 类 条')} ${dmnRow(3, '全部为 A 类(行情完备)', 'A4', '成本定价', 'A 类 条')}
本次判定 · 按序求值,首个命中即出

去向是分类表算出来的:三条规则按序求值、首个命中即出, 在你签字那一刻现算。所以它不是谁点出来的一次手感 —— 要改去向,就在上面的清单里改料号的类,这一格会跟着变。

${ico('arrow-right', 26)}
转入活动
${ico('user', 11)}下游负责人按运营分派落定

放行即落地:草稿层合并进正式版、intake 口的数据事件此刻才发射, 这一口的载荷随之交给下游。门前的都还改得动,门后的都算数了。

`; export const INTAKE_RELEASE = doc({ badge: 'file-check', kicker: '统一报价 · A1 受理归一 · 交付成果', title: '询价受理记录', titleRef: 'rfq_ref', /* * 题头这枚灯**在办与归档同一句话才算数**:这份件放行前嵌在放行卡上、放行后原样进 * 「工作成果」。写「待放行」进了档案就成了谎,写「受理完成」在放行前就是替人签了字 —— * 所以只写这份件本身是什么:归一了几条、三分类已定。 */ state: '受理记录 · 条 · 三分类已定', body: /* * **来件原文留在归档件里**(2026-08-02 用户指出:「成果里为啥没有邮箱原文了」)。 * * 待处理与工作成果本来就是**同一份件**,差别只在编辑态开不开。 * 归档件少一块来件,就等于把「凭什么这么受理」的出处从档案里抽走了 —— * 三个月后回看这一单,九条料是怎么来的、客户那句「本轮跳过」在哪儿说的, * 全都无从对起。件要自己说得清自己。 */ part('来源 · 客户 RFQ 邮件', ' 入站', 'jc-mono') + LETTER + part('受理要素', 'F-01 完整度自检 · 通过', 'ok') + four('type') + part('料号分类', '三分类规则 P-03 · 逐条判定') + DIST + part('有效 PN 清单', ' 条 · 已剔除 ') + editGate('quote_intake.record', '有效 PN 清单') + listTable('final') + addRowBar('quote_intake.record') + part('结论与去向', '分支判定 · 起单 ×1', 'ok') + G2 + /* * ── 这里**去掉了两张请求单表与那条对账说明**(2026-08-02 用户当场指出)────── * * 原来摆的是「开出的替代评估请求(交 A2)」「开出的询价请求(交 A3)」两张明细表, * 外加一句「这两条等式在你签字前由系统当场对账,对不上直接拒收」。 * * 两个理由: * · 那句话**已经不成立** —— `route-alt` / `route-rfq` 两条对账判据同日删掉了 * (它们是内部一致性,该由引擎自己看住,不占放行卡)。件上还写着「系统当场对账」, * 就是在说一件不再发生的事; * · 这份件是**受理记录**:它答的是「这一单受理成了什么样」。派出去的两类单是 * **下游的输入**,不是这一单的成果 —— 要看它们,去 A2 / A3 那两条流水线上看。 * 摆在这儿只是把同一份数据在两处各画一遍。 * * 「这一签下去开出了几张单」仍看得见:放行之后验收卡那一屏(落地结果)按表点名, * 询价请求 / 替代评估请求各几行都在上面 —— 那才是它该出现的时刻与位置。 */ part('放行后即执行的落地动作', '门前 → 门后') + `
${ico('check', 11)}
草稿层合并进正式版 本任务写下的受理记录、分类表、结论与两类请求单一并落账; 人工补全与就地修订的那几处随之落定。
${ico('check', 11)}
受理结果此刻才对外发出 连同 条有效料号与它们的分类一并交出 —— 放行之前,世界上什么都没有发生。
${ico('check', 11)}
按分类算出的去向落下游一单 本次转入 ,已进对方的任务清单;另两条去向本次不起单。
`, /* * ── 这里**去掉了「受理结论」那张表**(2026-08-02 用户当场指出:「这个也多余」)── * * 摆的是 `bom_total / skipped_total / skipped_note / summary` 四格,而 * 前三格与同日删掉的那条「本轮不报」横幅说的是同一件事, * 第四格是一句 AI 复述的总结 —— 底下的清单、分布条、去向横幅把该说的都说了, * 再加一句转述只是让人多读一行。 * * 一份件里同一个数出现两次,人得停下来对一遍两处一不一样 —— 那是白花的注意力。 */ foot: sign('AI 拟稿 · 待审定放行 —— 要改哪一格,在这张放行单的成果表上就地改', '统一报价 · A1 受理归一 → 交下游'), });