,这里备一把梯子让 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}
`;
/** 章节头 —— 原型的 `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('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' ? '类 | ' : ''}
PN |
品类 |
年用量 |
制造商 |
目标价 |
备注 |
${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')}
`;
};
/** 编辑态那条提示带 —— 原型的 `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 受理归一 → 交下游'),
});