活动:A2 选型 / A3 寻源 / A4 定价 / A6 议价 / A7 归档五件各补成真活动包 —— 契约宽进(input.required 空,归一在活动内)、台账必填声明(含行级 requiredWhen)、 入场哨兵与 L1/L2 判据一律读基座数、两份件(在办可编辑 + 办结静态快照)、 三案例常备、包内自测。e2e 各三案例 released、行动被拒 0、发口正确、提交闸每单真拦。 基座(共享面,改一处影响所有活动,故每处都带回归): · 重发判断走在站上场之前 —— 人答完那步常已收掉,readyToWork 先抛「不能再往它上面挂活」, 于是「重发已答过的卡 = 无事发生」这条唯一活路在最需要时够不着,A1 基线当场熔断; · 决策卡补幂等兜底(与执行工单同一把尺):头两次无事发生,第三次起才算被拒; · 输出没成形先就地重问一次,原话进工作日志、不再摆上卡面; · 工具调用读数 tool_<名>_calls —— 「机检只读基座数」这条纪律第一次覆盖到工具; · 执行卡 steps/standard 补进出参 schema(从前能到卡面全靠厂商 JSON 模式宽松); · 件的就地更新改逐节点 morph,保存先把新值镜像进只读那份 —— 不闪、不等回声。 A1(基线,全程守住):措辞三档对齐(必填/建议/选填)、禁止拿历史价当客户目标价、 卡骨架从步骤剧本搬进说明书(压成一拍连做时剧本没摊开,会发出没有指导书的裸卡)。 契约两处**新增可选字段**(非破坏):FieldDeclaration.requiredWhen、工单 finalHtml; api-report 同批更新。docs/adr 与 docs/archive 的 13 份文件在工作树里不见了, 本次予以恢复而非提交删除。 门禁:typecheck / lint / depcruise / test(1366)全绿。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
721 lines
46 KiB
TypeScript
721 lines
46 KiB
TypeScript
/**
|
||
* 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 改)。每个可改的格子渲两份:只读那份与
|
||
* `<input class="jc-cell">` 孪生件,由根节点上的 `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-<N>,这里备一把梯子让 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 =>
|
||
`<svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><use href="#i-${name}"/></svg>`;
|
||
|
||
/**
|
||
* 一份件的外壳 —— **照原型的三层壳**:`.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 => `<style>${PRD_CSS}${BIND_CSS}</style>
|
||
${PRD_SPRITE}
|
||
<div class="proto-root"><div class="app-shell"><div class="jc-stage">
|
||
<article class="jc-report">
|
||
<header class="jc-rp-head">
|
||
<div class="jc-rp-badge">${ico(input.badge, 17)}</div>
|
||
<div class="jc-rp-tit">
|
||
<em>${input.kicker}</em>
|
||
<h3>${input.title} <span data-slot="${input.titleRef}"></span></h3>
|
||
</div>
|
||
${input.state}
|
||
</header>
|
||
<div class="jc-rp-bd">${input.body}</div>
|
||
<footer class="jc-rp-foot">${input.foot}</footer>
|
||
</article>
|
||
</div></div></div>`;
|
||
|
||
/** 章节头 —— 原型的 `jc-rp-part`:小标签 + 细线 + 右端小签 */
|
||
const part = (label: string, aside = '', tone = ''): string =>
|
||
`<div class="jc-rp-part"><b>${label}</b><i class="ln"></i>${aside === '' ? '' : `<em class="${tone}">${aside}</em>`}</div>`;
|
||
|
||
/** 落款 —— 署名取任务负责人(基座填);右端那枚签写「这份件在流里的位置」,不写状态 */
|
||
const sign = (note: string, tag = ''): string =>
|
||
`<div class="jc-rp-sign">
|
||
<span class="jc-rp-ava">${ico('user', 15)}</span>
|
||
<div><b data-slot="case:owner"></b><span>${note}</span></div>
|
||
</div>${tag === '' ? '' : `<span class="jc-done-tag">${ico('git-branch', 13)}${tag}</span>`}`;
|
||
|
||
/* ==================== 共用件 ==================== */
|
||
|
||
/**
|
||
* 来源信件 —— 原型的 `jc-letter`。正文 `pre-wrap` 原样照录:摘要过的原文就不是原文了。
|
||
* 收件签旁那枚归属签(通道入站 → 归了谁)与右端入站时刻都由基座填 ——
|
||
* 让活动自报「归属解析 → 冯兵」,等于让被指派的人自己说自己是谁。
|
||
*/
|
||
const LETTER = `<article class="jc-letter">
|
||
<div class="jc-letter-h">
|
||
<span class="jc-letter-dir">${ico('mail-open', 12)}收件</span>
|
||
<span class="jc-chip"><b data-slot="case:origin"></b> · 归属解析 → <b data-slot="case:owner"></b></span>
|
||
<code data-slot="case:openedAt"></code>
|
||
</div>
|
||
<div class="jc-letter-meta">
|
||
<p><i>发件人</i><span data-slot="contact_name"></span><code data-slot="customerEmail"></code></p>
|
||
</div>
|
||
<h4 class="jc-letter-sub" data-slot="subject"></h4>
|
||
<p class="jc-letter-body" data-slot="body"></p>
|
||
<div class="jc-atts" data-rows="attachments">
|
||
<span class="jc-att">${ico('sheet', 13)}<code data-slot="name"></code><span data-slot="note"></span></span>
|
||
</div>
|
||
</article>`;
|
||
|
||
/**
|
||
* 受理要素四卡 —— 原型的 `jc-four`。
|
||
* `kind` 决定第四张卡下面那条刻度条按什么上色:过程屏按缺口(红格 = 必填未补),
|
||
* 归档屏按状况标(A 绿 / B 蓝 / C 橙)。**一格一条料**,每格都对得上清单里的一行。
|
||
*/
|
||
const four = (kind: 'gap' | 'type'): string => `<div class="jc-four">
|
||
<div class="jc-fcard">
|
||
<span class="jc-fcard-k"><i>${ico('building')}</i>客户公司</span>
|
||
<div class="jc-fcard-b">
|
||
<span class="jc-logo">${ico('building', 17)}</span>
|
||
<div class="jc-fcard-t"><b data-slot="customer_name"></b><span data-slot="customer_kind"></span></div>
|
||
</div>
|
||
<span class="jc-fcard-note">已命中客户资料库 <code class="jc-mono" data-slot="customer_code"></code></span>
|
||
</div>
|
||
|
||
<div class="jc-fcard t-teal">
|
||
<span class="jc-fcard-k"><i>${ico('user')}</i>联系人</span>
|
||
<div class="jc-fcard-b">
|
||
<span class="jc-face">${ico('user', 17)}</span>
|
||
<div class="jc-fcard-t">
|
||
<b data-slot="contact_name"></b>
|
||
<div class="jc-mailrow"><code data-slot="customerEmail"></code></div>
|
||
</div>
|
||
</div>
|
||
<span class="jc-fcard-note">采购口 · 本单对外发件收件人</span>
|
||
</div>
|
||
|
||
<div class="jc-fcard t-ok">
|
||
<span class="jc-fcard-k"><i>${ico('history')}</i>客户属性</span>
|
||
<span class="jc-tagbig">${ico('shield-check', 13)}<span data-slot="customer_kind"></span></span>
|
||
<span class="jc-fcard-note">
|
||
成交台账尚未接入 —— 历史价由下游定价环节现查;
|
||
新客户走首单口径,<b>不得取锚点</b>
|
||
</span>
|
||
</div>
|
||
|
||
<div class="jc-fcard${kind === 'type' ? ' t-ok' : ''}">
|
||
<span class="jc-fcard-k"><i>${ico('boxes')}</i>有效 PN</span>
|
||
<div class="jc-count"><b data-slot="line_total"></b><span>/ <b data-slot="bom_total"></b> 行 BOM</span></div>
|
||
<div class="jc-segmeter" data-rows="mine:quote_intake.record">
|
||
<i data-class-of="${kind === 'type' ? 'type_tag' : 'missing_key_fields'}"></i>
|
||
</div>
|
||
<span class="jc-fcard-note">
|
||
${
|
||
kind === 'type'
|
||
? '一格一条料 · 按状况标着色 · <b data-slot="line_total"></b> 条已交下游'
|
||
: '一格一条料 · 红格是必填未补 <b data-slot="missing_key_total"></b> 条'
|
||
}
|
||
</span>
|
||
</div>
|
||
</div>`;
|
||
|
||
/**
|
||
* 有效 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 `<td class="${cls}" data-h="${head}"><span class="jc-vcell ${style}" data-slot="${key}" data-col="${key}" data-blank></span></td>`;
|
||
};
|
||
const req = mode === 'final' ? '' : 'req';
|
||
/*
|
||
* 序号那一格在编辑态换成「撤掉这一行」的 × —— 一列两用,不为它单开一列。
|
||
* 只读时它是行号(CSS 计数器画的),编辑时人要的是「这一行不该在」的那个动作。
|
||
*/
|
||
const del = `<td class="jc-rn"><button type="button" class="jc-rowdel"
|
||
data-edit-del="quote_intake.record" title="撤掉这一行">×</button></td>`;
|
||
return `<div class="jc-tbl-wrap"><table class="jc-tbl-in">
|
||
<thead><tr>
|
||
<th style="width:36px;min-width:36px"></th>
|
||
${mode === 'final' ? '<th style="width:44px;min-width:44px">类</th>' : ''}
|
||
<th class="${req}" style="min-width:178px">PN</th>
|
||
<th style="min-width:88px">品类</th>
|
||
<th class="${req}" style="min-width:78px">年用量</th>
|
||
<th style="min-width:140px">制造商</th>
|
||
<th style="min-width:84px">目标价</th>
|
||
<th style="min-width:200px;width:100%">备注</th>
|
||
</tr></thead>
|
||
<tbody data-rows="mine:quote_intake.record" data-edit="quote_intake.record"><tr>
|
||
${del}
|
||
${mode === 'final' ? '<td class="jc-ty" data-h="分类"><span class="jc-tbadge" data-class-of="type_tag" data-slot="type_tag" data-col="type_tag"></span></td>' : ''}
|
||
${cell('pn', 'PN', 'req', 'mono')}
|
||
${cell('category', '品类', 'sug')}
|
||
${cell('eau', '年用量', 'req', 'mono')}
|
||
${cell('brand', '制造商', 'sug')}
|
||
${cell('target_price', '目标价', 'sug', 'num')}
|
||
${cell('remark', '备注', 'opt')}
|
||
</tr></tbody>
|
||
</table></div>`;
|
||
};
|
||
|
||
/** 编辑态那条提示带 —— 原型的 `jc-editbar`,只在待处理那一屏现身 */
|
||
/**
|
||
* **缺哪几格,红字直说**(2026-08-02 用户:「执行单现在要怎么填不明显,在报告上明显一点」)。
|
||
*
|
||
* 从前这一屏只在空格子里摆一枚小小的「必填」胶囊 —— 九行料摊开,那几枚胶囊淹在表里,
|
||
* 人得逐行扫过去才知道自己要动哪一格。而他打开这一屏就是来填的。
|
||
*
|
||
* 现在表上方直接一条红带:**还差 N 格,分别是哪一行的哪一列**。
|
||
* 两个数都由基座现算(`域_对象_missing` / `_missing_keys`),模版不算也不判 ——
|
||
* 「一格不缺就别出这条带」这个判断交给样式表:`data-class-of` 把数消毒成
|
||
* `v-0` / `v-1` / …,`v-0` 那一档 `display:none`。件本身仍然只做取值。
|
||
*/
|
||
const gapBar = `<div class="jc-gapbar" data-class-of="quote_intake_record_missing">${ico('alert', 14)}
|
||
<span><b>还差 <span data-slot="quote_intake_record_missing"></span> 格必填没补</b>
|
||
—— <span data-slot="quote_intake_record_missing_keys"></span>。
|
||
按右上角「编辑」开表,红框那几格就是要填的;补齐了才提交得了。</span>
|
||
</div>`;
|
||
|
||
/**
|
||
* 一张可改的表的**闸** —— 默认锁着,按「编辑」才开(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 => `<div class="jc-editgate" data-edit-for="${address}">
|
||
<span class="jc-editgate-t">${ico('lock', 12)}${what}<b>已锁定</b> —— 看的时候不误碰,要改按右边</span>
|
||
<button type="button" class="jc-egbtn open" data-edit-open="${address}">${ico('pen', 12)}编辑</button>
|
||
<button type="button" class="jc-egbtn ghost" data-edit-cancel="${address}">取消</button>
|
||
<button type="button" class="jc-egbtn save" data-edit-save="${address}">${ico('check', 12)}保存</button>
|
||
</div>
|
||
<div class="jc-editbar">${ico('pen', 14)}
|
||
<span>改哪一格点哪一格:<b>整份清单每一格都能覆盖</b> —— AI 抽错的直接改,缺的补上;
|
||
多出来的行按右边的 × 撤掉,漏了的用底下的「加一行」补。
|
||
红框是必填未补。改动只落任务草稿层,<b>保存不等于放行</b>。</span>
|
||
</div>`;
|
||
|
||
/**
|
||
* 表尾那条「加一行」—— **只在编辑态现身**(样式表按根节点的 `editing` 决定)。
|
||
*
|
||
* 为什么人得能加行:手改本来就是**兜 AI 的错**的那一手,而错不止「这一格填错了」——
|
||
* 「BOM 里有而它没抽出来」是同一类错,只给改格子就没有出口。
|
||
*/
|
||
const addRowBar = (address: string): string => `<div class="jc-addrow">
|
||
<button type="button" class="jc-egbtn ghost" data-edit-add="${address}">${ico('plus', 12)}加一行</button>
|
||
<em>照末行的样子添一行空的;主键(型号 PN)填了才存得下,不填这一行不落库。</em>
|
||
</div>`;
|
||
|
||
/*
|
||
* ── 这里**去掉了「本轮不报」那条横幅**(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 = `<div class="jc-dist">
|
||
<div class="jc-distbar">
|
||
<i class="a" data-class-of="quote_intake_record_type_tag_a" data-slot="quote_intake_record_type_tag_a"></i>
|
||
<i class="b" data-class-of="quote_intake_record_type_tag_b" data-slot="quote_intake_record_type_tag_b"></i>
|
||
<i class="c" data-class-of="quote_intake_record_type_tag_c" data-slot="quote_intake_record_type_tag_c"></i>
|
||
</div>
|
||
<div class="jc-distlg">
|
||
<span><em class="a"></em>A 类 · 行情完备 <b data-slot="quote_intake_record_type_tag_a"></b></span>
|
||
<span><em class="b"></em>B 类 · 需寻源询价 <b data-slot="quote_intake_record_type_tag_b"></b></span>
|
||
<span><em class="c"></em>C 类 · 需替代选型 <b data-slot="quote_intake_record_type_tag_c"></b></span>
|
||
</div>
|
||
</div>`;
|
||
|
||
/* ==================== 局面一 · 受理中(阶段2 待处理页嵌的那份)==================== */
|
||
|
||
export const INTAKE_OVERVIEW = doc({
|
||
badge: 'file-text',
|
||
kicker: '统一报价 · A1 受理归一 · 过程成果',
|
||
title: '询价受理需求',
|
||
titleRef: 'rfq_ref',
|
||
state:
|
||
'<span class="jc-rp-wait warn">缺必填 <b data-slot="missing_key_total"></b> · 缺选填 <b data-slot="missing_minor_total"></b></span>',
|
||
body:
|
||
part('来源 · 客户 RFQ 邮件', '<span data-slot="case:openedAt"></span> 入站', 'jc-mono') +
|
||
LETTER +
|
||
`<div class="jc-derive">${ico('info', 14)}
|
||
<span>下方受理要素与 PN 清单,均由 AI 从这封来函与附件 BOM 抽取归一而来 —— 正文里的口径要求
|
||
(币种 / 贸易条款 / 交期单位 / 认证 / 预授权替代 / 本轮跳哪条)已逐条落到对应字段与备注;
|
||
附件那一行写的是<b>基座读取的结果</b>,读得出文本才作数。</span>
|
||
</div>` +
|
||
part('受理要素', 'F-01 完整度自检') +
|
||
four('gap') +
|
||
part('有效 PN 清单', '<b data-slot="line_total"></b> 条 · 已剔除 <b data-slot="skipped_total"></b>') +
|
||
gapBar +
|
||
editGate('quote_intake.record', '有效 PN 清单') +
|
||
listTable('draft') +
|
||
addRowBar('quote_intake.record') +
|
||
`<div class="jc-drop">${ico('info', 13)}
|
||
<span>带 * 的两列是<b>必填</b>(PN / 年用量):缺任一按 F-01 退回补全,不许放行;
|
||
品类 / 制造商 / 目标价缺了标橘「<b>建议</b>」—— 值得追问、尽量补上,备注缺了标灰「选填」。
|
||
三档都<b>不为它们拦单</b>,先受理、后追问,但那一问不能省。
|
||
<b>原文没写的一律不补</b>:受理阶段编出来的数会一路流到定价和报价单上,越往后越难发现。</span>
|
||
</div>`,
|
||
foot: sign('AI 拟稿,你审定 —— 要改哪一格,在「待处理」那张单的需求表上就地改', '统一报价 · A1 受理归一'),
|
||
});
|
||
|
||
/* ==================== 局面三 · 已分类 ==================== */
|
||
|
||
export const INTAKE_CLASSIFIED = doc({
|
||
badge: 'shield-check',
|
||
kicker: '统一报价 · A1 受理归一 · 三分类 P-03',
|
||
title: '料号分类表',
|
||
titleRef: 'rfq_ref',
|
||
state: '<span class="jc-rp-wait">已分级 · 未打标 <b data-slot="untyped_total"></b></span>',
|
||
body:
|
||
part('料号分类', '三分类规则 P-03 · 逐条判定') +
|
||
DIST +
|
||
part('逐条分级(判据 → 结论)', '按序求值 · 首个命中即出') +
|
||
editGate('quote_intake.type_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:178px">型号 PN</th>
|
||
<th style="min-width:120px">距上次报价(天)</th>
|
||
<th style="min-width:88px">行情稳定</th>
|
||
<th style="min-width:96px">生命周期</th>
|
||
<th style="width:44px;min-width:44px">类</th>
|
||
<th style="min-width:200px;width:100%">分级依据</th>
|
||
</tr></thead>
|
||
<tbody data-rows="mine:quote_intake.type_table" data-edit="quote_intake.type_table"><tr>
|
||
<td class="jc-rn"></td>
|
||
<td data-h="型号 PN"><span class="jc-vcell mono" data-slot="pn" data-col="pn"></span></td>
|
||
<td data-h="距上次报价"><span class="jc-vcell num" data-slot="history_days" data-col="history_days"></span></td>
|
||
<td data-h="行情稳定"><span class="jc-vcell" data-slot="market_stable" data-col="market_stable"></span></td>
|
||
<td data-h="生命周期"><span class="jc-vcell" data-slot="lifecycle" data-col="lifecycle"></span></td>
|
||
<td class="jc-ty" data-h="分类"><span class="jc-tbadge" data-class-of="type_tag" data-slot="type_tag" data-col="type_tag"></span></td>
|
||
<td data-h="分级依据"><span class="jc-vcell" data-slot="basis" data-col="basis"></span></td>
|
||
</tr></tbody>
|
||
</table></div>` +
|
||
`<div class="jc-drop">${ico('info', 13)}
|
||
<span>三分类规则 P-03 是<b>决策表</b>,不是临场判断:同样的输入必得同样的结论,凭哪一条命中都留了痕。
|
||
<b>A</b> = 历史价 ≤ 90 天且行情稳;<b>B</b> = 无历史 / 过期 / 波动;<b>C</b> = EOL / 缺货 / 客户要求替代。
|
||
规则从上往下第一条命中即出 —— C 那一行排在最前,EOL 的料哪怕历史价很新也不判 A。
|
||
<b>拿不准的一律降 B</b>:错标一个 A,就是让一条本该重新核价的料带着旧价走到报价单上。</span>
|
||
</div>`,
|
||
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 =>
|
||
`<div class="jc-dmn-r r${n}">
|
||
<span class="jc-dmn-i">${n}</span>
|
||
<span class="jc-dmn-c">${cond}</span>
|
||
<span class="jc-dmn-t"><code>${node}</code>${to}</span>
|
||
<span class="jc-dmn-v"><b class="y">${ico('check', 13)}命中 · ${hit}</b><b class="n">未命中 · 排他网关只落一口</b></span>
|
||
</div>`;
|
||
|
||
const G2 = `<div class="jc-dmn" data-class-of="case:routeKey">
|
||
<div class="jc-dmn-h"><span>序</span><span>条件(按序求值)</span><span>去向</span><span>判定</span></div>
|
||
${dmnRow(1, '存在 <b>C 类</b>(需替代选型)', 'A2', '替代选型', 'C 类 <b data-slot="quote_intake_record_type_tag_c"></b> 条')}
|
||
${dmnRow(2, '无 C 类、存在 <b>B 类</b>(需寻源询价)', 'A3', '渠道寻源', 'B 类 <b data-slot="quote_intake_record_type_tag_b"></b> 条')}
|
||
${dmnRow(3, '全部为 <b>A 类</b>(行情完备)', 'A4', '成本定价', 'A 类 <b data-slot="quote_intake_record_type_tag_a"></b> 条')}
|
||
</div>
|
||
<div class="jc-verdict" data-class-of="case:routeKey">
|
||
<div class="jc-vd-l">
|
||
<span class="jc-vd-k">本次判定 · 按序求值,首个命中即出</span>
|
||
<div class="jc-vd-cond"><span data-slot="case:routeTo"></span></div>
|
||
<p class="jc-vd-why">去向是<b>分类表算出来的</b>:三条规则按序求值、首个命中即出,
|
||
在你签字那一刻现算。所以它不是谁点出来的一次手感 ——
|
||
<b>要改去向,就在上面的清单里改料号的类,这一格会跟着变。</b></p>
|
||
</div>
|
||
<span class="jc-vd-arrow">${ico('arrow-right', 26)}</span>
|
||
<div class="jc-vd-r">
|
||
<span class="jc-vd-k">转入活动</span>
|
||
<div class="jc-vd-node"><span data-slot="case:routeTo"></span></div>
|
||
<span class="jc-vd-who"><i>${ico('user', 11)}</i>下游负责人按运营分派落定</span>
|
||
<p class="jc-vd-next">放行即落地:草稿层合并进正式版、intake 口的数据事件此刻才发射,
|
||
这一口的载荷随之交给下游。<b>门前的都还改得动,门后的都算数了。</b></p>
|
||
</div>
|
||
</div>`;
|
||
|
||
export const INTAKE_RELEASE = doc({
|
||
badge: 'file-check',
|
||
kicker: '统一报价 · A1 受理归一 · 交付成果',
|
||
title: '询价受理记录',
|
||
titleRef: 'rfq_ref',
|
||
/*
|
||
* 题头这枚灯**在办与归档同一句话才算数**:这份件放行前嵌在放行卡上、放行后原样进
|
||
* 「工作成果」。写「待放行」进了档案就成了谎,写「受理完成」在放行前就是替人签了字 ——
|
||
* 所以只写这份件本身是什么:归一了几条、三分类已定。
|
||
*/
|
||
state: '<span class="jc-rp-wait">受理记录 · <b data-slot="line_total"></b> 条 · 三分类已定</span>',
|
||
body:
|
||
/*
|
||
* **来件原文留在归档件里**(2026-08-02 用户指出:「成果里为啥没有邮箱原文了」)。
|
||
*
|
||
* 待处理与工作成果本来就是**同一份件**,差别只在编辑态开不开。
|
||
* 归档件少一块来件,就等于把「凭什么这么受理」的出处从档案里抽走了 ——
|
||
* 三个月后回看这一单,九条料是怎么来的、客户那句「本轮跳过」在哪儿说的,
|
||
* 全都无从对起。件要自己说得清自己。
|
||
*/
|
||
part('来源 · 客户 RFQ 邮件', '<span data-slot="case:openedAt"></span> 入站', 'jc-mono') +
|
||
LETTER +
|
||
part('受理要素', 'F-01 完整度自检 · 通过', 'ok') +
|
||
four('type') +
|
||
part('料号分类', '三分类规则 P-03 · 逐条判定') +
|
||
DIST +
|
||
part('有效 PN 清单', '<b data-slot="line_total"></b> 条 · 已剔除 <b data-slot="skipped_total"></b>') +
|
||
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('放行后即执行的落地动作', '门前 → 门后') +
|
||
`<div class="jc-acts">
|
||
<div class="jc-act"><i>${ico('check', 11)}</i>
|
||
<div><b>草稿层合并进正式版</b> <span>本任务写下的受理记录、分类表、结论与两类请求单一并落账;
|
||
人工补全与就地修订的那几处随之落定。</span></div>
|
||
</div>
|
||
<div class="jc-act"><i>${ico('check', 11)}</i>
|
||
<div><b>受理结果此刻才对外发出</b> <span>连同
|
||
<b data-slot="line_total"></b> 条有效料号与它们的分类一并交出 —— 放行之前,世界上什么都没有发生。</span></div>
|
||
</div>
|
||
<div class="jc-act"><i>${ico('check', 11)}</i>
|
||
<div><b>按分类算出的去向落下游一单</b> <span>本次转入
|
||
<b data-slot="case:routeTo"></b>,已进对方的任务清单;另两条去向本次不起单。</span></div>
|
||
</div>
|
||
</div>`,
|
||
/*
|
||
* ── 这里**去掉了「受理结论」那张表**(2026-08-02 用户当场指出:「这个也多余」)──
|
||
*
|
||
* 摆的是 `bom_total / skipped_total / skipped_note / summary` 四格,而
|
||
* 前三格与同日删掉的那条「本轮不报」横幅说的是同一件事,
|
||
* 第四格是一句 AI 复述的总结 —— 底下的清单、分布条、去向横幅把该说的都说了,
|
||
* 再加一句转述只是让人多读一行。
|
||
*
|
||
* 一份件里同一个数出现两次,人得停下来对一遍两处一不一样 —— 那是白花的注意力。
|
||
*/
|
||
foot: sign('AI 拟稿 · 待审定放行 —— 要改哪一格,在这张放行单的成果表上就地改', '统一报价 · A1 受理归一 → 交下游'),
|
||
});
|