A2-A7 全部补齐到验收线;顺手把几处「门拦住却不给路」的坑填了

活动: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>
This commit is contained in:
2026-08-03 12:28:31 +08:00
co-authored by Claude Opus 5
parent 85466e815f
commit ade0fa5a94
90 changed files with 17969 additions and 2316 deletions
@@ -0,0 +1,409 @@
/**
* A1 两份执行页的样式表 —— **从 PRD 目标件原样搬过来**,不是我照着复述的。
*
* 出处:`~/coding/JCAI_PRD/docs/活动执行页/A1-询价受理需求.html` 的整段 `<style>`,
* 外加 `A1-询价受理记录.html` 里归档件独有的那几件(分类分布条 / 类徽 / 判定表 /
* 结论横幅 / 落地动作 / 完成标)。**一个数值都没改**。
*
* 为什么是搬而不是抄:前三轮我用自己那套外壳「照着做」,块搬齐了、版面是两套 ——
* 章节头成了彩色方块编号、多出摘要与元信息带、字号用 clamp。人一眼就看出不是那份件。
* 目标件是**像素级的答案**,搬过来才不会在复述里走样。
*
* 选择器保留 `.proto-root .app-shell` 前缀,所以模版正文也要照原型那样包两层壳 ——
* 少一层壳,整张样式表一条都不生效(踩过)。
*/
export const PRD_CSS = `
/* ============================================================================
JCAI 员工面工作台 · A1 受理归一的过程成果「询价受理需求」——报告本体
立场:**AI 已从 RFQ 邮件抽取归一**,人在这里审核并就地修订。
两态:读态(AI 稿 + 红/灰占位提示缺项) ⇄ 编辑态(全字段可改,保存 / 取消)
核心是「有效 PN 清单」;清单之上四件受理要素:客户公司 / 联系人 / 客户属性 / 有效 PN。
令牌与既有组件取自 workbench/src/app/(proto)/app.css,数值未改。
⚠️ 修正一处原型 bug:app.css:2062-2064 的 '.jc-rp-sign span'(权重 0,1,1)
盖过了 '.jc-rp-ava'(0,1,0),把头像的 display:grid 覆盖成 block、字色改灰,
导致落款头像里的字掉到圆外。本文件把该规则收窄为 '.jc-rp-sign > div > span'。
新增件:四要素卡 .jc-four/.jc-fcard · 占位胶囊 .jc-ph · 分段控件 .jc-seg ·
来源条 .jc-src · 录入表 .jc-tbl-in
========================================================================== */
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body { background: #e6eaf1; -webkit-font-smoothing: antialiased; }
.proto-root .app-shell {
--brand: #3370ff; --brand-deep: #245bdb;
--brand-soft: #e1eaff; --brand-wash: #f0f4ff;
--ai-grad: linear-gradient(135deg, #3370ff, #7f3bf5);
--ink: #1f2329; --ink-2: #41464e; --ink-3: #646a73; --ink-4: #8f959e;
--line: #dee0e3; --line-soft: #e8eaed; --soft: #f8f9fb;
--jcai-bg: #e6eaf1; --cardline: #e1e6ec; --card-shadow: 0 1px 2px rgba(31, 35, 41, .05);
--ok: #278717; --ok-soft: #e8f8e4;
--warn: #de7802; --warn-accent: #ff8800; --warn-soft: #fff4e5;
--danger: #d83931; --danger-soft: #feeae9;
--mono: ui-monospace, "SF Mono", SFMono-Regular, "JetBrains Mono", Menlo, Consolas, monospace;
--shadow-brand: 0 6px 16px rgba(51, 112, 255, .22);
--t-fast: .16s cubic-bezier(.3, 0, .2, 1);
--t-med: .26s cubic-bezier(.3, 0, .2, 1);
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
background: var(--jcai-bg); min-height: 100vh; color: var(--ink);
}
.proto-root :where(button, input, select, a, [tabindex]):focus-visible {
outline: 2px solid var(--brand); outline-offset: 2px; border-radius: 8px;
}
.proto-root ::selection { background: var(--brand-soft); }
@keyframes rise { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@keyframes jcBlink { 0%,100% { opacity: 1 } 50% { opacity: .35 } }
@keyframes jcStamp { from { opacity: 0; transform: rotate(-6deg) scale(1.7); } to { opacity: .9; transform: rotate(-6deg) scale(1); } }
/* 放开宽度:1160px 是「9 列全展开、一个字都不裁」的最小容器宽度(见下方列 min-width 之和) */
.jc-stage { max-width: 1160px; margin: 0 auto; padding: 22px 16px 52px; }
.proto-root .jc-mono { font-family: var(--mono); font-size: 11px; color: var(--ink-3); }
.proto-root .jc-pulse-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--brand); display: inline-block; animation: jcBlink 1.1s ease-in-out infinite; }
/* ── 报告外壳 ───────────────────────────────────────────────────────── */
.proto-root .jc-report { background: #fff; border: 1px solid var(--cardline); border-radius: 14px; box-shadow: var(--card-shadow); overflow: hidden; animation: rise var(--t-med) both; }
.proto-root .jc-rp-head { position: relative; overflow: hidden; display: flex; align-items: flex-start; gap: 12px; padding: 16px 20px 13px; border-bottom: 2px solid var(--ink); }
/* 题头下缘粗细双线 = 正式件版式 */
.proto-root .jc-rp-head::after { content: ""; position: absolute; left: 0; right: 0; bottom: -4px; height: 1px; background: var(--line); }
/* 题头右上的品牌光晕:与工作台标题区同一签名件 */
.proto-root .jc-rp-head::before { content: ""; position: absolute; right: -70px; top: -96px; width: 240px; height: 240px; border-radius: 999px; background: radial-gradient(circle, rgba(51,112,255,.10), transparent 70%); pointer-events: none; }
.proto-root .jc-rp-head > * { position: relative; z-index: 1; }
.proto-root .jc-rp-badge { flex: none; width: 36px; height: 36px; border-radius: 10px; display: grid; place-items: center; color: #fff; background: var(--ai-grad); box-shadow: var(--shadow-brand); }
.proto-root .jc-rp-tit { flex: 1; min-width: 0; }
.proto-root .jc-rp-tit em { display: block; font-style: normal; font-size: 10px; font-weight: 800; letter-spacing: .08em; color: var(--ink-4); font-family: var(--mono); }
.proto-root .jc-rp-tit h3 { margin: 4px 0 0; font-size: 18px; font-weight: 800; color: var(--ink); letter-spacing: -.01em; line-height: 1.3; }
.proto-root .jc-rp-wait { display: inline-flex; align-items: center; gap: 6px; flex: none; margin-top: 4px; font-size: 11px; font-weight: 700; border-radius: 999px; padding: 4px 12px; color: var(--brand-deep); background: var(--brand-wash); border: 1px solid var(--brand-soft); }
.proto-root .jc-rp-wait.warn { color: var(--warn); background: var(--warn-soft); border-color: #ffd9a8; }
.proto-root .jc-rp-wait.edit { color: #fff; background: var(--brand); border-color: var(--brand); }
.proto-root .jc-rp-stamp { flex: none; margin-top: 2px; transform: rotate(-6deg); border: 2px solid currentColor; border-radius: 9px; padding: 4px 8px 3px 12px; font-size: 13px; font-weight: 900; letter-spacing: .28em; color: var(--ok); opacity: .9; animation: jcStamp .32s cubic-bezier(.2, 1.4, .4, 1) both; }
.proto-root .jc-rp-bd { padding: 18px 20px 12px; }
.proto-root .jc-rp-part { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 9px; margin: 20px 0 11px; }
.proto-root .jc-rp-part:first-child { margin-top: 0; }
.proto-root .jc-rp-part > b { flex: none; font-size: 11px; font-weight: 800; letter-spacing: .12em; color: var(--ink-3); }
.proto-root .jc-rp-part > .ln { flex: 1; height: 1px; background: var(--line-soft); }
.proto-root .jc-rp-part > em { flex: none; font-style: normal; font-size: 10px; font-weight: 700; color: var(--ink-4); font-family: var(--mono); background: var(--soft); border-radius: 999px; padding: 2px 9px; white-space: nowrap; }
.proto-root .jc-rp-part > em.warn { color: var(--warn); background: var(--warn-soft); }
/* 落款 —— ⚠️ 选择器收窄到 > div > *,别再命中头像那个 span */
.proto-root .jc-rp-foot { display: flex; align-items: center; flex-wrap: wrap; gap: 12px; padding: 13px 20px 15px; border-top: 2px solid var(--line); background: #fbfcfd; }
.proto-root .jc-rp-sign { display: flex; align-items: center; gap: 10px; flex: 1 1 200px; min-width: 0; }
.proto-root .jc-rp-ava { flex: 0 0 auto; width: 30px; height: 30px; border-radius: 999px; display: inline-flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 700; line-height: 1; color: #fff; background: var(--brand); }
.proto-root .jc-rp-sign > div { min-width: 0; }
.proto-root .jc-rp-sign > div > b { display: block; font-size: 12px; font-weight: 700; color: var(--ink-2); }
.proto-root .jc-rp-sign > div > span { display: block; margin-top: 1px; font-size: 10.5px; color: var(--ink-4); }
.proto-root .jc-rp-acts { display: flex; align-items: center; gap: 8px; margin-left: auto; flex: none; }
.proto-root .jc-hintline { font-size: 11px; font-weight: 700; color: var(--warn); line-height: 1.5; }
.proto-root .jc-btn { display: inline-flex; align-items: center; gap: 5px; border: 0; border-radius: 7px; padding: 8px 14px; font-size: 12px; font-weight: 600; font-family: inherit; background: var(--brand); color: #fff; cursor: pointer; transition: all var(--t-fast); }
.proto-root .jc-btn:hover:not(:disabled) { background: var(--brand-deep); }
.proto-root .jc-btn:disabled { opacity: .45; cursor: not-allowed; }
.proto-root .jc-btn-ghost { background: #fff; color: var(--ink-2); border: 1px solid var(--line); }
.proto-root .jc-btn-ghost:hover:not(:disabled) { background: var(--brand-wash); color: var(--brand); border-color: var(--brand-soft); }
.proto-root .jc-btn-primary { background: var(--ai-grad); }
.proto-root .jc-btn-primary:hover:not(:disabled) { filter: brightness(1.07); box-shadow: var(--shadow-brand); transform: translateY(-1px); }
/* ── 来源信件:客户 RFQ 邮件原文(复用原型 jc-letter 件,青色 = 收件) ── */
.proto-root .jc-letter { position: relative; border: 1px solid var(--cardline); border-left: 3px solid #0aa294; border-radius: 12px; padding: 14px 16px 15px; background: #fff; box-shadow: var(--card-shadow); }
.proto-root .jc-letter-h { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 11px; }
.proto-root .jc-letter-dir { display: inline-flex; align-items: center; gap: 5px; font-size: 10.5px; font-weight: 800; padding: 3px 9px; border-radius: 999px; background: #e4f8f5; color: #0a7f74; }
.proto-root .jc-letter-h .jc-chip { font-size: 10.5px; padding: 3px 9px; border-radius: 999px; background: var(--brand-wash); color: var(--brand-deep); border: 1px solid var(--brand-soft); }
.proto-root .jc-letter-h > code { margin-left: auto; font-family: var(--mono); font-size: 10.5px; color: var(--ink-4); }
.proto-root .jc-letter-meta { display: flex; flex-direction: column; gap: 4px; margin-bottom: 11px; padding-bottom: 11px; border-bottom: 1px solid var(--line-soft); font-size: 11.5px; color: var(--ink-2); }
.proto-root .jc-letter-meta p { margin: 0; display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.proto-root .jc-letter-meta i { flex: none; font-style: normal; width: 44px; font-size: 10px; font-weight: 800; letter-spacing: .06em; color: var(--ink-4); }
.proto-root .jc-letter-meta code { font-family: var(--mono); font-size: 10.5px; color: var(--ink-3); overflow-wrap: anywhere; }
.proto-root .jc-letter-sub { margin: 0 0 9px; font-size: 14px; font-weight: 800; color: var(--ink); line-height: 1.45; overflow-wrap: anywhere; }
/* 正文:原文照录,pre-wrap 保原始换行;不截断、不折叠 */
.proto-root .jc-letter-body { margin: 0; font-size: 12.5px; line-height: 1.72; color: var(--ink-2); white-space: pre-wrap; overflow-wrap: anywhere; }
.proto-root .jc-letter-body b { color: var(--ink); font-weight: 700; }
.proto-root .jc-atts { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; padding-top: 11px; border-top: 1px dashed var(--line-soft); }
.proto-root .jc-att { display: inline-flex; align-items: center; gap: 6px; padding: 5px 11px 5px 8px; border: 1px solid var(--brand-soft); border-radius: 8px; background: var(--brand-wash); font-size: 11.5px; color: var(--brand-deep); }
.proto-root .jc-att svg { flex: none; color: var(--brand); }
.proto-root .jc-att code { font-family: var(--mono); font-size: 10.5px; }
.proto-root .jc-att span { color: var(--ink-4); }
/* 抽取指向:从原文到下方要素的一句衔接 */
.proto-root .jc-derive { display: flex; align-items: flex-start; gap: 8px; margin-top: 11px; padding: 9px 12px; border-radius: 9px; background: var(--brand-wash); font-size: 11.5px; line-height: 1.7; color: var(--brand-deep); }
.proto-root .jc-derive svg { flex: none; margin-top: 2px; }
/* ── 四要素卡 ───────────────────────────────────────────────────────────
色调字典:每张卡带一套 --tone / --tone-soft / --tone-line,组件只读变量。
卡 3、卡 4 的色调随数据变(老客户绿 / 新客户橙;齐了蓝 / 有缺橙),
所以这条要素带本身就是一行可读的状态,不只是四个格子。 */
.proto-root .jc-four { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 11px; }
.proto-root .jc-fcard {
position: relative; overflow: hidden; display: flex; flex-direction: column; justify-content: space-between; gap: 10px; min-width: 0;
padding: 14px 14px 14px; border: 1px solid var(--cardline); border-radius: 13px; background: #fff;
box-shadow: 0 1px 2px rgba(31,35,41,.05), 0 8px 20px -14px rgba(31,35,41,.35);
transition: transform var(--t-fast), box-shadow var(--t-fast), border-color var(--t-fast);
--tone: var(--brand); --tone-soft: rgba(51,112,255,.13); --tone-line: var(--brand-soft);
}
/* 顶缘色棱 + 右上角光晕:卡的身份色,一眼分得清四件是四件 */
.proto-root .jc-fcard::before { content: ""; position: absolute; left: 0; right: 0; top: 0; height: 3px; background: var(--tone); }
.proto-root .jc-fcard::after { content: ""; position: absolute; right: -46px; top: -52px; width: 132px; height: 132px; border-radius: 999px; background: radial-gradient(circle, var(--tone-soft), transparent 70%); pointer-events: none; }
.proto-root .jc-fcard > * { position: relative; z-index: 1; }
.proto-root .jc-fcard:hover { transform: translateY(-2px); border-color: var(--tone-line); box-shadow: 0 2px 4px rgba(31,35,41,.05), 0 14px 26px -16px rgba(31,35,41,.45); }
.proto-root .jc-fcard.t-teal { --tone: #0aa294; --tone-soft: rgba(10,162,148,.15); --tone-line: #9fe0d9; }
.proto-root .jc-fcard.t-ok { --tone: var(--ok); --tone-soft: rgba(39,135,23,.14); --tone-line: #bfedb4; }
.proto-root .jc-fcard.t-warn { --tone: var(--warn-accent); --tone-soft: rgba(255,136,0,.12); --tone-line: #ffd9a8; }
.proto-root .jc-fcard-k { display: flex; align-items: center; gap: 6px; font-size: 9.5px; font-weight: 800; letter-spacing: .12em; color: var(--tone); }
.proto-root .jc-fcard-k > i { flex: none; width: 19px; height: 19px; border-radius: 6px; display: inline-grid; place-items: center; background: var(--tone-soft); color: var(--tone); }
.proto-root .jc-fcard-b { display: flex; align-items: center; gap: 10px; min-width: 0; }
.proto-root .jc-fcard-t { min-width: 0; flex: 1; }
/* 一律换行,不省略号——宁可多占一行,也不丢字 */
.proto-root .jc-fcard-t > b { display: block; font-size: 14px; font-weight: 800; color: var(--ink); line-height: 1.35; overflow-wrap: anywhere; }
.proto-root .jc-fcard-t > span { display: block; margin-top: 2px; font-size: 10.5px; color: var(--ink-4); line-height: 1.55; overflow-wrap: anywhere; }
/* 公司标:圆角方 + AI 渐变 + 外圈光环(方=组织) */
.proto-root .jc-logo { flex: none; width: 38px; height: 38px; border-radius: 12px; display: inline-flex; align-items: center; justify-content: center; font-size: 13px; font-weight: 800; line-height: 1; letter-spacing: .04em; color: #fff; background: var(--ai-grad); box-shadow: var(--shadow-brand), 0 0 0 3px rgba(51,112,255,.10); }
/* 人像:圆 + 青色系(圆=人,与方形的组织标一眼分开) */
.proto-root .jc-face { flex: none; width: 38px; height: 38px; border-radius: 999px; display: inline-flex; align-items: center; justify-content: center; font-size: 13px; font-weight: 800; line-height: 1; color: #0a7f74; background: #e4f8f5; border: 1px solid #9fe0d9; box-shadow: 0 0 0 3px rgba(10,162,148,.10); }
/* 邮箱做成可点的胶囊,复制按钮嵌在里面——比一行裸文字有抓手 */
/* 邮箱:软底胶囊、无描边——有描边会读成输入框,而读态这里不是可填的 */
.proto-root .jc-mailrow { display: flex; align-items: center; gap: 4px; margin-top: 5px; min-width: 0; max-width: 100%; padding: 3px 4px 3px 9px; border: 0; border-radius: 8px; background: var(--tone-soft); transition: background var(--t-fast); }
.proto-root .jc-mailrow:hover { background: rgba(10,162,148,.2); }
/* 编辑态里它包着输入框,软底会变成「框中框」,去掉 */
.proto-root .jc-report.editing .jc-mailrow { background: transparent; padding: 0; margin-top: 6px; }
.proto-root .jc-mailrow > code { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; line-height: 1.5; font-family: var(--mono); font-size: 10.5px; color: var(--ink-2); }
.proto-root .jc-copy { flex: none; width: 22px; height: 22px; display: inline-grid; place-items: center; border: 0; border-radius: 6px; background: #fff; color: var(--ink-4); cursor: pointer; box-shadow: 0 1px 2px rgba(31,35,41,.08); transition: all var(--t-fast); }
.proto-root .jc-copy:hover { color: #0aa294; }
.proto-root .jc-copy.ok { color: var(--ok); background: var(--ok-soft); box-shadow: none; }
/* 客户属性徽章 */
.proto-root .jc-tagbig { display: inline-flex; align-items: center; gap: 6px; align-self: flex-start; padding: 7px 14px; border-radius: 999px; font-size: 13.5px; font-weight: 800; line-height: 1; color: var(--tone); background: var(--tone-soft); border: 1px solid var(--tone-line); }
.proto-root .jc-fcard-note { font-size: 10.5px; color: var(--ink-4); line-height: 1.55; }
.proto-root .jc-fcard-note b { color: var(--ink-2); font-weight: 700; }
.proto-root .jc-fcard-note b.bad { color: var(--tone); font-weight: 800; }
/* 成交迷你柱:把「台账 6 笔成交」从一句话变成看得见的节奏 */
.proto-root .jc-spark { display: flex; align-items: flex-end; gap: 4px; height: 26px; }
.proto-root .jc-spark > i { flex: 1 1 0; min-width: 4px; max-width: 15px; border-radius: 3px 3px 1px 1px; background: var(--tone); opacity: .3; transition: opacity var(--t-fast), height var(--t-med); }
.proto-root .jc-spark > i:last-child { opacity: 1; }
.proto-root .jc-spark:hover > i { opacity: .55; }
.proto-root .jc-spark:hover > i:last-child { opacity: 1; }
.proto-root .jc-spark.empty { align-items: stretch; }
.proto-root .jc-spark.empty > i { height: 100%; opacity: 1; background: repeating-linear-gradient(45deg, var(--tone-soft) 0 5px, transparent 5px 10px); border: 1px dashed var(--tone-line); border-radius: 5px; }
/* 有效 PN 计数 */
.proto-root .jc-count { display: flex; align-items: baseline; gap: 6px; }
.proto-root .jc-count > b { font-family: var(--mono); font-size: 32px; font-weight: 800; line-height: .95; color: var(--ink); letter-spacing: -.04em; }
.proto-root .jc-count > span { font-size: 11px; color: var(--ink-4); }
/* 分段刻度条:一格 = 一条 PN,缺必填的那格直接标红——比一根实心进度条信息量大 */
.proto-root .jc-segmeter { display: flex; gap: 3px; }
.proto-root .jc-segmeter > i { flex: 1; height: 7px; border-radius: 2px; background: var(--ok); transition: background var(--t-fast); }
.proto-root .jc-segmeter > i.bad { background: var(--danger); }
.proto-root .jc-lockhint { display: inline-flex; align-items: center; gap: 4px; font-size: 10px; color: var(--ink-4); }
/* ── 占位胶囊:必填=红虚线,选填=灰虚线;**都带文字**,不靠颜色单打 ── */
.proto-root .jc-ph { 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-ph.req { color: var(--danger); background: var(--danger-soft); border-color: #f3b6b3; }
.proto-root .jc-ph.opt { color: var(--ink-4); background: var(--soft); border-color: var(--line); }
/* ── 分段控件(新客户 / 老客户)── */
.proto-root .jc-seg { display: inline-flex; padding: 2px; gap: 2px; border: 1px solid var(--cardline); border-radius: 9px; background: var(--soft); align-self: flex-start; }
.proto-root .jc-seg button { border: 0; border-radius: 7px; padding: 6px 13px; font: inherit; font-size: 11.5px; font-weight: 700; color: var(--ink-3); background: transparent; cursor: pointer; transition: all var(--t-fast); }
.proto-root .jc-seg button:hover:not(.on) { color: var(--brand); }
.proto-root .jc-seg button.on { background: #fff; color: var(--ink); box-shadow: 0 1px 3px rgba(31,35,41,.12); }
/* ── 表:读态与编辑态共用骨架 ─────────────────────────────────────── */
.proto-root .jc-tbl-wrap { overflow-x: auto; border: 1px solid var(--line-soft); border-radius: 10px; }
.proto-root .jc-tbl-in { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 12px; }
.proto-root .jc-tbl-in th { background: var(--soft); text-align: left; padding: 8px 9px; font-size: 10.5px; font-weight: 800; letter-spacing: .03em; color: var(--ink-3); white-space: nowrap; border-bottom: 1px solid var(--line-soft); }
.proto-root .jc-tbl-in th.req::after { content: "*"; margin-left: 1px; color: var(--danger); font-size: 10px; }
.proto-root .jc-tbl-in td { padding: 4px 5px; border-bottom: 1px solid var(--line-soft); vertical-align: middle; }
.proto-root .jc-tbl-in tr:last-child td { border-bottom: 0; }
.proto-root .jc-tbl-in tbody tr:hover td { background: #f7f9fd; }
/* 缺必填的整行浅红打底 + 行首红标——不用找红胶囊,一眼扫到是哪几行 */
.proto-root .jc-tbl-in tr.miss td { background: #fffbfb; }
.proto-root .jc-tbl-in tbody tr.miss:hover td { background: #fff5f5; }
.proto-root .jc-tbl-in td.jc-rn { position: relative; width: 38px; padding: 4px 6px 4px 13px; white-space: nowrap; font-family: var(--mono); font-size: 10.5px; color: var(--ink-4); }
.proto-root .jc-tbl-in tr.miss td.jc-rn { color: var(--danger); font-weight: 800; }
/* 行首红标只在表态用 ::after —— 卡片态的 ::after 被「第 N 条」占着,不能抢 */
@media (min-width: 961px) {
.proto-root .jc-tbl-in tr.miss td.jc-rn::after { content: ""; position: absolute; left: 0; top: 4px; bottom: 4px; width: 3px; border-radius: 0 3px 3px 0; background: var(--danger); }
}
.proto-root .jc-rndot { display: inline-block; width: 5px; height: 5px; margin-left: 4px; border-radius: 999px; background: var(--danger); vertical-align: 2px; }
/* 读态单元格 */
.proto-root .jc-vcell { display: block; padding: 6px 6px; font-size: 12px; color: var(--ink); line-height: 1.5; overflow-wrap: anywhere; }
.proto-root .jc-vcell.mono { font-family: var(--mono); font-size: 11px; color: var(--ink-2); }
.proto-root .jc-vcell.num { font-family: var(--mono); font-size: 11.5px; text-align: right; }
/* 编辑态单元格:安静的电子表格 */
.proto-root .jc-cell { width: 100%; min-width: 64px; border: 1px solid var(--line); border-radius: 6px; padding: 6px 7px; font: inherit; font-size: 12px; color: var(--ink); background: #fff; transition: border-color var(--t-fast), box-shadow var(--t-fast); }
.proto-root .jc-cell.mono { font-family: var(--mono); font-size: 11px; }
.proto-root .jc-cell.num { text-align: right; font-family: var(--mono); font-size: 11.5px; }
.proto-root .jc-cell::placeholder { color: var(--ink-4); }
.proto-root .jc-cell:focus { border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-wash); outline: none; }
.proto-root .jc-cell.req-empty { border-color: #f3b6b3; background: #fffafa; }
.proto-root .jc-cell.req-empty::placeholder { color: var(--danger); }
/* 备注这类长文本用文本域:能换行、随内容长高,不裁字 */
.proto-root textarea.jc-cell { display: block; resize: vertical; overflow: hidden; min-height: 31px; line-height: 1.55; }
/* 编辑态的整表提示带 */
.proto-root .jc-editbar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; padding: 9px 12px; border-radius: 9px; background: var(--brand-wash); border: 1px solid var(--brand-soft); font-size: 11.5px; color: var(--brand-deep); }
.proto-root .jc-editbar svg { flex: none; }
.proto-root .jc-editbar .sp { flex: 1 1 auto; }
/* 剔除行说明 */
.proto-root .jc-drop { display: flex; align-items: flex-start; gap: 8px; margin-top: 10px; padding: 9px 12px; border-radius: 9px; border: 1px dashed var(--line); background: var(--soft); font-size: 11.5px; color: var(--ink-3); line-height: 1.6; }
.proto-root .jc-drop svg { flex: none; margin-top: 2px; color: var(--ink-4); }
.proto-root .jc-drop code { font-family: var(--mono); font-size: 10.5px; color: var(--ink-2); }
.proto-root .jc-callout { display: flex; gap: 8px; align-items: flex-start; border-radius: 9px; padding: 11px 13px; font-size: 12.5px; line-height: 1.75; }
.proto-root .jc-callout p { margin: 0; }
.proto-root .jc-callout svg { flex: none; margin-top: 2px; }
.proto-root .jc-callout.t-mute { background: var(--soft); color: var(--ink-2) }
@media (prefers-reduced-motion: reduce) {
.proto-root .jc-pulse-dot { animation: none; }
.proto-root .jc-report, .proto-root .jc-rp-stamp { animation: none; }
}
/* ══════════════════════════════════════════════════════════════════════════
响应式:宁可另起一行,也不损失完整度
≥961px 六列全展开的表(列 min-width 之和 878px,容器有余量,绝不裁字、绝不横滚)
≤960px 表 → 卡片:一条 PN 一张卡、一行一字段(标签 / 值),无横滚、无省略
≤560px 字段行再拆成上下两行(标签在上、值在下)
══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 1040px) { .proto-root .jc-four { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 560px) { .proto-root .jc-four { grid-template-columns: 1fr; } }
@media (max-width: 960px) {
.proto-root .jc-tbl-wrap { overflow: visible; border: 0; border-radius: 0; background: transparent; }
.proto-root .jc-tbl-in,
.proto-root .jc-tbl-in tbody,
.proto-root .jc-tbl-in tr,
.proto-root .jc-tbl-in td { display: block; width: 100%; }
.proto-root .jc-tbl-in thead { display: none; }
/* 一条 PN = 一张卡 */
.proto-root .jc-tbl-in tr { margin-bottom: 10px; padding: 4px 13px 9px; border: 1px solid var(--line-soft); border-radius: 11px; background: #fff; box-shadow: var(--card-shadow); }
.proto-root .jc-tbl-in tr:last-child { margin-bottom: 0; }
.proto-root .jc-tbl-in tbody tr:hover td { background: transparent; }
.proto-root .jc-tbl-in tr.miss { border-color: #f3b6b3; background: #fffbfb; }
/* 卡头:第 N 条 */
.proto-root .jc-tbl-in td.jc-rn { padding: 8px 0 8px; margin-bottom: 4px; border-bottom: 1px solid var(--line-soft); font-family: inherit; font-size: 12px; font-weight: 800; color: var(--ink-2); }
.proto-root .jc-tbl-in td.jc-rn::before { content: "第 "; }
.proto-root .jc-tbl-in td.jc-rn::after { content: " 条"; }
.proto-root .jc-tbl-in tr.miss td.jc-rn { color: var(--danger); }
/* 字段行:左标签 + 右值,值超长自动折行 */
.proto-root .jc-tbl-in td:not(.jc-rn) { display: grid; grid-template-columns: 96px minmax(0, 1fr); gap: 10px; align-items: center; padding: 6px 0; border-bottom: 1px dashed var(--line-soft); }
.proto-root .jc-tbl-in tr td:last-child { border-bottom: 0; padding-bottom: 2px; }
.proto-root .jc-tbl-in td:not(.jc-rn)::before {
content: attr(data-h); font-size: 10.5px; font-weight: 700; letter-spacing: .02em; color: var(--ink-4); line-height: 1.5;
}
.proto-root .jc-tbl-in td[data-req="1"]:not(.jc-rn)::before { content: attr(data-h) " *"; }
.proto-root .jc-tbl-in .jc-vcell { padding: 0; overflow-wrap: anywhere; }
.proto-root .jc-tbl-in .jc-vcell.num { text-align: left; }
.proto-root .jc-tbl-in .jc-cell.num { text-align: left; }
.proto-root .jc-tbl-in .jc-cell { min-width: 0; }
}
/* 窄屏:落款三件各占一行,动作按钮撑满一行 */
@media (max-width: 560px) {
.proto-root .jc-rp-acts { margin-left: 0; width: 100%; flex-wrap: wrap; }
.proto-root .jc-rp-acts .jc-btn { flex: 1 1 auto; justify-content: center; }
.proto-root .jc-hintline { flex: 1 1 100%; }
.proto-root .jc-tbl-in td:not(.jc-rn) { grid-template-columns: 1fr; gap: 3px; align-items: start; }
}
/* ══ 以下取自 A1-询价受理记录.html(归档件独有的件)══ */
/* ── 料号分类:分布条 + 图例 ───────────────────────────────────────── */
.proto-root .jc-dist { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; padding: 13px 15px; border: 1px solid var(--line-soft); border-radius: 12px; background: linear-gradient(180deg, #fbfcfe, #fff); }
.proto-root .jc-distbar { flex: 1 1 320px; min-width: 260px; display: flex; gap: 3px; }
.proto-root .jc-distbar > i { height: 30px; border-radius: 4px; display: grid; place-items: center; font-size: 10.5px; font-weight: 800; color: #fff; font-family: var(--mono); }
.proto-root .jc-distbar > i.a { background: var(--ok); }
.proto-root .jc-distbar > i.b { background: var(--brand); }
.proto-root .jc-distbar > i.c { background: var(--warn-accent); }
.proto-root .jc-distlg { display: flex; flex-wrap: wrap; gap: 8px 18px; }
.proto-root .jc-distlg > span { display: inline-flex; align-items: center; gap: 7px; font-size: 11.5px; color: var(--ink-3); }
.proto-root .jc-distlg em { width: 10px; height: 10px; border-radius: 3px; }
.proto-root .jc-distlg em.a { background: var(--ok); } .proto-root .jc-distlg em.b { background: var(--brand); } .proto-root .jc-distlg em.c { background: var(--warn-accent); }
.proto-root .jc-distlg b { font-family: var(--mono); font-weight: 800; color: var(--ink); }
/* 类别徽 A / B / C */
.proto-root .jc-tbadge { display: inline-grid; place-items: center; width: 20px; height: 20px; border-radius: 6px; font-family: var(--mono); font-size: 11px; font-weight: 800; line-height: 1; color: #fff; }
.proto-root .jc-tbadge.a { background: var(--ok); } .proto-root .jc-tbadge.b { background: var(--brand); } .proto-root .jc-tbadge.c { background: var(--warn-accent); }
/* ── 结论与去向 ─────────────────────────────────────────────────────────
G2 是**分支网关**:三条互斥条件按序求值,只命中一条、只落一个活动。
所以这里不是三张并列的去向卡,而是一张**判定表 + 一条结论**。 */
/* 判定表:一行一条规则,命中行上色留痕(DMN 口径) */
.proto-root .jc-dmn { border: 1px solid var(--line-soft); border-radius: 12px; overflow: hidden; }
.proto-root .jc-dmn-h, .proto-root .jc-dmn-r { display: grid; grid-template-columns: 40px minmax(0, 1fr) 170px 200px; align-items: center; gap: 12px; padding: 9px 14px; }
.proto-root .jc-dmn-h { background: var(--soft); border-bottom: 1px solid var(--line-soft); font-size: 10px; font-weight: 800; letter-spacing: .08em; color: var(--ink-4); }
.proto-root .jc-dmn-r { font-size: 12px; color: var(--ink-4); background: #fff; }
.proto-root .jc-dmn-r + .jc-dmn-r { border-top: 1px solid var(--line-soft); }
.proto-root .jc-dmn-i { font-family: var(--mono); font-size: 11px; font-weight: 800; }
.proto-root .jc-dmn-c { line-height: 1.6; overflow-wrap: anywhere; }
.proto-root .jc-dmn-t { display: inline-flex; align-items: center; gap: 6px; font-weight: 700; }
.proto-root .jc-dmn-t code { font-family: var(--mono); font-size: 10.5px; font-weight: 800; color: var(--ink-4); background: var(--soft); border: 1px solid var(--line); border-radius: 5px; padding: 2px 6px; }
.proto-root .jc-dmn-v { font-size: 11px; line-height: 1.55; }
/* 命中行:上色 + 左缘棱 + 加粗 */
.proto-root .jc-dmn-r.hit { position: relative; background: var(--tone-soft); color: var(--ink-2); }
.proto-root .jc-dmn-r.hit::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 4px; background: var(--tone); }
.proto-root .jc-dmn-r.hit .jc-dmn-i { color: var(--tone); }
.proto-root .jc-dmn-r.hit .jc-dmn-c { color: var(--ink); }
.proto-root .jc-dmn-r.hit .jc-dmn-c b { color: var(--tone); font-weight: 800; }
.proto-root .jc-dmn-r.hit .jc-dmn-t { color: var(--ink); }
.proto-root .jc-dmn-r.hit .jc-dmn-t code { color: #fff; background: var(--tone); border-color: var(--tone); }
.proto-root .jc-dmn-v.y { display: inline-flex; align-items: center; gap: 5px; font-weight: 800; color: var(--tone); }
.proto-root .jc-dmn-v.n { color: var(--ink-4); }
/* 色调随命中类别 */
.proto-root .jc-dmn.k-a { --tone: var(--ok); --tone-soft: rgba(39,135,23,.10); }
.proto-root .jc-dmn.k-b { --tone: var(--brand); --tone-soft: rgba(51,112,255,.09); }
.proto-root .jc-dmn.k-c { --tone: var(--warn-accent); --tone-soft: rgba(255,136,0,.11); }
/* 结论横幅:命中条件 → 唯一去向 */
.proto-root .jc-verdict {
position: relative; overflow: hidden; display: flex; align-items: stretch; flex-wrap: wrap; gap: 16px;
margin-top: 11px; padding: 16px 18px; border: 1px solid var(--tone-line); border-radius: 13px;
background: linear-gradient(105deg, var(--tone-soft), #fff 62%);
box-shadow: 0 1px 2px rgba(31,35,41,.05), 0 10px 24px -16px rgba(31,35,41,.4);
--tone: var(--warn-accent); --tone-soft: rgba(255,136,0,.14); --tone-line: #ffd9a8;
}
.proto-root .jc-verdict.k-a { --tone: var(--ok); --tone-soft: rgba(39,135,23,.13); --tone-line: #bfedb4; }
.proto-root .jc-verdict.k-b { --tone: var(--brand); --tone-soft: rgba(51,112,255,.13); --tone-line: var(--brand-soft); }
.proto-root .jc-verdict::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 5px; background: var(--tone); }
.proto-root .jc-verdict > * { position: relative; z-index: 1; }
.proto-root .jc-vd-l { flex: 1 1 300px; min-width: 0; display: flex; flex-direction: column; gap: 7px; }
.proto-root .jc-vd-k { font-size: 9.5px; font-weight: 800; letter-spacing: .12em; color: var(--tone); }
.proto-root .jc-vd-cond { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 15px; font-weight: 800; color: var(--ink); }
.proto-root .jc-vd-why { margin: 0; font-size: 11.5px; line-height: 1.7; color: var(--ink-3); overflow-wrap: anywhere; }
.proto-root .jc-vd-why code { font-family: var(--mono); font-size: 10.5px; color: var(--ink); }
.proto-root .jc-vd-arrow { flex: none; align-self: center; color: var(--tone); }
.proto-root .jc-vd-r { flex: 1 1 260px; min-width: 0; display: flex; flex-direction: column; gap: 8px; padding: 12px 14px; border-radius: 11px; background: #fff; border: 1px solid var(--tone-line); }
.proto-root .jc-vd-node { display: flex; align-items: center; gap: 8px; font-size: 16px; font-weight: 800; color: var(--ink); }
.proto-root .jc-vd-node code { font-family: var(--mono); font-size: 12px; font-weight: 800; color: #fff; background: var(--tone); border-radius: 6px; padding: 3px 8px; }
.proto-root .jc-vd-who { display: inline-flex; align-items: center; gap: 6px; font-size: 11.5px; color: var(--ink-2); }
.proto-root .jc-vd-who > i { width: 20px; height: 20px; border-radius: 999px; display: inline-grid; place-items: center; font-style: normal; font-size: 10px; font-weight: 700; color: #fff; background: var(--tone); }
.proto-root .jc-vd-next { margin: 0; padding-top: 8px; border-top: 1px dashed var(--line-soft); font-size: 10.5px; line-height: 1.65; color: var(--ink-4); }
.proto-root .jc-vd-next b { color: var(--ink-2); font-weight: 700; }
/* 命中分支携带的条目 */
.proto-root .jc-pnlist { display: flex; flex-wrap: wrap; gap: 5px; margin: 0; padding: 0; list-style: none; }
.proto-root .jc-pnlist > li { display: inline-flex; align-items: center; gap: 5px; padding: 3px 9px; border-radius: 999px; background: #fff; border: 1px solid var(--tone-line); font-size: 11px; color: var(--ink-2); overflow-wrap: anywhere; }
.proto-root .jc-pnlist > li > i { flex: none; width: 5px; height: 5px; border-radius: 999px; background: var(--tone); }
.proto-root .jc-pnlist code { font-family: var(--mono); font-size: 10.5px; color: var(--ink); }
/* ── 落地动作 ───────────────────────────────────────────────────────── */
.proto-root .jc-acts { display: flex; flex-direction: column; gap: 0; border: 1px solid var(--line-soft); border-radius: 11px; overflow: hidden; }
.proto-root .jc-act { display: flex; align-items: flex-start; gap: 9px; padding: 10px 13px; font-size: 12px; color: var(--ink-2); line-height: 1.6; background: #fff; }
.proto-root .jc-act + .jc-act { border-top: 1px solid var(--line-soft); }
.proto-root .jc-act > i { flex: none; width: 18px; height: 18px; margin-top: 1px; border-radius: 999px; display: inline-grid; place-items: center; color: #fff; background: var(--ok); }
.proto-root .jc-act b { font-weight: 700; color: var(--ink); }
.proto-root .jc-act span { color: var(--ink-4); }
.proto-root .jc-act code { font-family: var(--mono); font-size: 10.5px; color: var(--brand-deep); background: var(--brand-wash); border-radius: 4px; padding: 1px 5px; }
.proto-root .jc-done-tag { margin-left: auto; display: inline-flex; align-items: center; gap: 6px; flex: none; padding: 6px 13px; border-radius: 999px; font-size: 11.5px; font-weight: 800; color: var(--ok); background: #fff; border: 1px solid #bfedb4; }
.proto-root .jc-vcell.dim { color: var(--ink-4); }
.proto-root .jc-tbl-in td.jc-ty { width: 44px; text-align: center; }
.proto-root .jc-tbl-in td.jc-ty { width: auto; text-align: left; }
`;
/**
* 图标精灵 —— 与样式表同一出处,两份目标件的 `<symbol>` 取并集。
* 沙箱里取不到图标字体,所以图标一律内联;`<use href="#i-xxx">` 引的就是这一坨。
*/
export const PRD_SPRITE = `<svg width="0" height="0" style="position:absolute" aria-hidden="true"><defs><symbol id="i-alert" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><line x1="12" x2="12" y1="8" y2="12"/><line x1="12" x2="12.01" y1="16" y2="16"/></symbol><symbol id="i-arrow-right" viewBox="0 0 24 24"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></symbol><symbol id="i-boxes" viewBox="0 0 24 24"><path d="M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z"/><path d="m22 17.65-9.17 4.16a2 2 0 0 1-1.66 0L2 17.65"/><path d="m22 12.65-9.17 4.16a2 2 0 0 1-1.66 0L2 12.65"/></symbol><symbol id="i-building" viewBox="0 0 24 24"><path d="M6 22V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v18Z"/><path d="M6 12H4a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h2"/><path d="M18 9h2a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-2"/><path d="M10 6h4"/><path d="M10 10h4"/><path d="M10 14h4"/><path d="M10 18h4"/></symbol><symbol id="i-check" viewBox="0 0 24 24"><path d="M20 6 9 17l-5-5"/></symbol><symbol id="i-copy" viewBox="0 0 24 24"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></symbol><symbol id="i-file-check" viewBox="0 0 24 24"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="m9 15 2 2 4-4"/></symbol><symbol id="i-file-text" viewBox="0 0 24 24"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M10 9H8"/><path d="M16 13H8"/><path d="M16 17H8"/></symbol><symbol id="i-git-branch" viewBox="0 0 24 24"><line x1="6" x2="6" y1="3" y2="15"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M18 9a9 9 0 0 1-9 9"/></symbol><symbol id="i-history" viewBox="0 0 24 24"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/><path d="M12 7v5l4 2"/></symbol><symbol id="i-info" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></symbol><symbol id="i-lock" viewBox="0 0 24 24"><rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></symbol><symbol id="i-mail-open" viewBox="0 0 24 24"><path d="M21.2 8.4c.5.38.8.97.8 1.6v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V10a2 2 0 0 1 .8-1.6l8-6a2 2 0 0 1 2.4 0z"/><path d="m22 10-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 10"/></symbol><symbol id="i-pen" viewBox="0 0 24 24"><path d="M12 20h9"/><path d="M16.376 3.622a1 1 0 0 1 3.002 3.002L7.368 18.635a2 2 0 0 1-.855.506l-2.872.838a.5.5 0 0 1-.62-.62l.838-2.872a2 2 0 0 1 .506-.854z"/></symbol><symbol id="i-plus" viewBox="0 0 24 24"><path d="M5 12h14"/><path d="M12 5v14"/></symbol><symbol id="i-sheet" viewBox="0 0 24 24"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M8 13h2"/><path d="M14 13h2"/><path d="M8 17h2"/><path d="M14 17h2"/></symbol><symbol id="i-shield-check" viewBox="0 0 24 24"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/><path d="m9 12 2 2 4-4"/></symbol><symbol id="i-user" viewBox="0 0 24 24"><circle cx="12" cy="8" r="5"/><path d="M20 21a8 8 0 0 0-16 0"/></symbol><symbol id="i-x" viewBox="0 0 24 24"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></symbol></defs></svg>`;
+704 -173
View File
@@ -1,189 +1,720 @@
/**
* A6 报价议价的 HTML 报告模版 —— **活动自己持有的资产**(§10.3),随包版本冻结。
* A6 报价议价的 HTML 报告模版 —— **活动自己持有的资产**,随包版本冻结。
*
* 议价是**多轮往来**:成单 → 人工按下发出 → D+3 催 → D+7 催 → 客户还价 → 改一版再发。
* 所以这两屏都要让人看得出**先后**:这一版是第几轮、上一轮报的是多少、下一记提醒在哪天。
* 只摆一个「当前单价」的屏,人签字时没有参照系,也就无从判断这一让让得值不值 ——
* 于是「历次报价」那一节摆的是共享域整表(含既往单),它不是本单成果,是本单的尺子。
* 两个局面各一张(与 A2/A3/A4 同款口径):
* · `negotiate-worksheet` **议价工作单** —— 排版、发出与落客户回音期间摆它:
* 逐颗料一张报价档案卡(条款在左、议价在右)、议价结果 N-01 与让步梯度 N-02 两把尺、
* 填回工作表就长在同一屏;
* · `negotiate-roster` **成交定案清单** —— 异议拍板、放行与归档摆它:整份报价函,
* 定案分布、去向落边图与落地动作在上面。
*
* 两个局面:
* ① 正式报价单(草稿)—— 六样条款齐不齐;这一步还没碰外界,一封信都没发;
* ② 放行即发出 —— 按下的那一刻信就到客户手上,签字前是最后一次能改的机会。
* 「1:1」的落点是**每份件与它的办结快照**(在办可编辑 ↔ 回执收下那一刻的 finalHtml),
* 不是全程只许一张:作业的件与收口的件是两件工作两份件(规范姊妹篇 §1)。
*
* 容器是 `sandbox=""` iframe:无脚本、无外链、样式自带;数据由基座推送时填
* (`data-slot` 取值、`data-rows` 逐行展开)。
* 版面语言沿用 A1 从 PRD 目标件机械抽取的那套(`prd-style.ts` 原样拷入)——
* A6 没有自己的 PRD 目标件,与 A2/A3/A4 同一处置:沿用家族版面自造。
*
* 两条贯穿全包的呈现纪律:
* · **状态章一律用「在办」灯,不用「已办」印** —— 模版是随包冻结的静态字符串,
* 它盖不了一个还没发生的章;这两张都是放行前的局面;
* · **`verdict` 的橙档专指「还没发生,等你按」** —— 发出的后果是预告不是既成事实。
* 整屏的结构色一律品牌蓝/案卷紫,红橙绿只落在「达标没达标」上,不拿来渲染气氛。
* 取值三纪律:顶层只取汇总列与单值列;逐条走 `mine:quote_negotiate.line`;
* 列名逐字照 `declarations.ledger` 抄。**模版不算不判** —— 缺表态红带、定案徽、分布条、
* 落边图全部由基座算好的数经 `data-class-of` 投影,判断留在样式表。
*/
import { noteOnly, section, stagesOnly, statsBand, tableOnly, verdict, viewShell } from '@sdk';
import { PRD_CSS, PRD_SPRITE } from './prd-style.ts';
/**
* 静态阶梯 —— **规则与节拍不是数据**:六样条款的要求、D+3/D+7 的节拍、三个出口的去向
* 都随包冻结,不该走 `data-rows` 等基座来填(那会让「规矩」看起来像「这一单的结果」)。
* 沿用外壳已有的 `.steps` 版式,与引擎推的阶段列表同一副面孔。
*/
function ladder(rows: ReadonlyArray<readonly [string, string, string]>): string {
return `<ul class="steps">${rows
.map(
([no, term, gloss]) =>
`<li><span class="n">${no}</span><span><span class="t">${term}</span> <span class="d">${gloss}</span></span></li>`,
)
.join('')}</ul>`;
/* ==================== 绑定期补的样式(照 A1/A3/A4 的 BIND_CSS,地址换成本活动)==================== */
const BIND_CSS = `
/* 空占位三级(2026-08-02 全局口径):必填红 / 建议填橘 / 选填灰 —— 值真的空才轮到 */
.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; }
/* 缺客户表态的整行浅红 + 行首红条 —— 原型的 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);
}
}
/**
* 键值格 —— 报价单的条款是**一份单据的正面**,六样并排比一张六列横向滚动的表好读。
* `@sdk` 的 `kvCard` 自带一个「·」号章节,与本页「一/二/三」的编号排不到一起,
* 所以这里只借外壳已有的 `.kv` 版式,章节仍交给 `section` 统一编号。
*/
function kvGrid(items: ReadonlyArray<readonly [string, string, ('mono' | 'tag')?]>): string {
return `<div class="kv">${items
.map(([label, path, style]) => {
const cls = style === undefined ? '' : ` class="${style === 'mono' ? 'mono' : 'tag'}"`;
return `<div><em>${label}</em><b${cls} data-slot="${path}"></b></div>`;
})
.join('')}</div>`;
/* 序号由 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); }
@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; }
}
/** 局面一:S1 成单 —— 人此刻要核的是「六样齐没齐、这一版能不能发」 */
export const QUOTE_COMPOSED = viewShell({
title: '正式报价单(草稿)',
kicker: 'A6 · 报价议价 · S1 成单',
tone: 'brand',
icon: 'handshake',
stamp: { kind: 'live', text: '成单中' },
sign: 'A6 报价议价 · 正式报价单(本单草稿)',
/* 就地改:可改格子渲了只读与输入两份,默认锁着,表上加 on 才换孪生件 */
.proto-root .jc-cell { display: none; }
.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; }
/* 类徽 / 表态徽 / 定案徽不进编辑态(承继与引擎产物),on 时也照旧摆着 */
.proto-root .jc-tbl-in.on td > .jc-obadge,
.proto-root .jc-tbl-in.on td > .jc-dbadge { display: inline-flex; }
/* 数值列与表头同侧左对齐(一行一个料,彼此不相加) */
.proto-root .jc-vcell.num, .proto-root .jc-cell.num { text-align: left; }
/* 编辑态下必填空格子:红框 + 整行浅红(由 :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-report { border: 0; border-radius: 0; box-shadow: none; }
/* 那道闸 —— 默认「已锁定 + 编辑」,开了变成「取消 / 保存」;只在可改屏现身 */
.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; }
/* 缺表态红带 —— 只在可改屏出,一格不缺自动收(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 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; }
/* 增删行 */
.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); }
/* 类徽按值上色(承继上游的三分类) */
.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); }
.proto-root .jc-segmeter > i.v-won { background: var(--ok); }
.proto-root .jc-segmeter > i.v-haggle { background: var(--warn-accent); }
.proto-root .jc-segmeter > i.v-revise { background: var(--brand); }
.proto-root .jc-segmeter > i.v-lost { background: var(--ink-4); }
/* 客户表态徽:接受绿 / 还价橙 / 拒绝红 / 无回复灰 —— 值是机器码,人读名交给样式表 */
.proto-root .jc-obadge {
display: inline-flex; align-items: center; justify-content: center;
min-width: 52px; height: 20px; padding: 0 7px; border-radius: 6px;
font-size: 10px; font-weight: 800; letter-spacing: .02em;
color: var(--ink-4); border: 1.5px dashed var(--ink-4); background: #fff;
}
.proto-root .jc-obadge::after { content: "待回音"; }
.proto-root .jc-obadge.v-accept { color: var(--ok); border-color: var(--ok); border-style: solid; }
.proto-root .jc-obadge.v-accept::after { content: "接受"; }
.proto-root .jc-obadge.v-counter { color: var(--warn-accent); border-color: var(--warn-accent); border-style: solid; }
.proto-root .jc-obadge.v-counter::after { content: "还价"; }
.proto-root .jc-obadge.v-reject { color: var(--danger); border-color: var(--danger); border-style: solid; }
.proto-root .jc-obadge.v-reject::after { content: "拒绝"; }
.proto-root .jc-obadge.v-noreply { color: var(--ink-3); border-color: var(--ink-4); border-style: solid; }
.proto-root .jc-obadge.v-noreply::after { content: "无回复"; }
/* 定案徽:成交绿 / 还在还价橙 / 不成交灰 */
.proto-root .jc-dbadge {
display: inline-flex; align-items: center; justify-content: center;
min-width: 62px; height: 20px; padding: 0 7px; border-radius: 6px;
font-size: 10px; font-weight: 800; letter-spacing: .02em;
color: var(--ink-4); border: 1.5px solid var(--ink-4); background: #fff;
}
.proto-root .jc-dbadge::after { content: "待定案"; }
.proto-root .jc-dbadge.v-won { color: var(--ok); border-color: var(--ok); }
.proto-root .jc-dbadge.v-won::after { content: "成交"; }
.proto-root .jc-dbadge.v-haggle { color: var(--warn-accent); border-color: var(--warn-accent); border-style: dashed; }
.proto-root .jc-dbadge.v-haggle::after { content: "待你拍板"; }
.proto-root .jc-dbadge.v-revise { color: var(--brand); border-color: var(--brand); }
.proto-root .jc-dbadge.v-revise::after { content: "退回重批"; }
.proto-root .jc-dbadge.v-lost { color: var(--ink-3); border-color: var(--ink-4); }
.proto-root .jc-dbadge.v-lost::after { content: "不成交"; }
/* 让步处置(N-02)—— 只在还价的行上有值 */
.proto-root .jc-cbadge { font-size: 10.5px; font-weight: 700; color: var(--ink-4); }
.proto-root .jc-cbadge.v-accept_counter { color: var(--ok); }
.proto-root .jc-cbadge.v-accept_counter::after { content: "接了拿单"; }
.proto-root .jc-cbadge.v-negotiate { color: var(--brand); }
.proto-root .jc-cbadge.v-negotiate::after { content: "授权内回旋"; }
.proto-root .jc-cbadge.v-escalate { color: var(--danger); }
.proto-root .jc-cbadge.v-escalate::after { content: "超授权 · 退回重批"; }
/* 分布条:三段按条数分宽 —— 条数投影成 v-<N>,梯子取宽;零条那段自己消失 */
.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 }
.proto-root .jc-distbar > i.p { background: var(--ok); }
.proto-root .jc-distbar > i.n { background: var(--warn-accent); }
.proto-root .jc-distbar > i.r { background: var(--brand); }
.proto-root .jc-distbar > i.k { background: var(--ink-4); }
.proto-root .jc-distbar > i.p::before { content: "成交 · "; }
.proto-root .jc-distbar > i.n::before { content: "待拍板 · "; }
.proto-root .jc-distbar > i.r::before { content: "退回重批 · "; }
.proto-root .jc-distbar > i.k::before { content: "不成交 · "; }
.proto-root .jc-logo svg, .proto-root .jc-face svg, .proto-root .jc-rp-ava svg { width: 17px; height: 17px; }
/* ── 报价档案卡(工作单独有,照 A4 定价档案卡的骨架)──────────────────────
一颗料一张卡,data-rows 逐行复制:左条款、右议价。
形态按定案分档(data-class-of):还在还价的整卡描橙,不成交的压灰 —— 判断在样式表。 */
.proto-root .jc-selcards { display: flex; flex-direction: column; gap: 10px; margin-bottom: 14px; }
.proto-root .jc-selcard { border: 1px solid var(--line); border-radius: 11px; background: #fff; overflow: hidden; }
.proto-root .jc-selcard.v-haggle { border-color: #f0cf9a; box-shadow: 0 1px 0 rgba(191,132,32,.06); }
.proto-root .jc-selcard.v-lost { border-color: var(--line); opacity: .82; }
.proto-root .jc-selcard-h {
display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
padding: 9px 14px; background: var(--soft); border-bottom: 1px solid var(--line-soft);
}
.proto-root .jc-selcard.v-haggle .jc-selcard-h { background: var(--warn-soft); border-bottom-color: #f0cf9a; }
.proto-root .jc-selcard-h code { font-size: 13px; font-weight: 800; color: var(--ink); }
.proto-root .jc-selcard-h .why { flex: 1 1 auto; min-width: 0; font-size: 11px; color: var(--ink-3); }
.proto-root .jc-selcard-b { display: grid; grid-template-columns: 1fr 1fr; gap: 0; }
@media (max-width: 1000px) { .proto-root .jc-selcard-b { grid-template-columns: 1fr; } }
.proto-root .jc-selcol { padding: 11px 14px; }
.proto-root .jc-selcol + .jc-selcol { border-left: 1px solid var(--line-soft); }
@media (max-width: 1000px) { .proto-root .jc-selcol + .jc-selcol { border-left: 0; border-top: 1px solid var(--line-soft); } }
.proto-root .jc-selcol h5 { margin: 0 0 7px; font-size: 10.5px; font-weight: 800; letter-spacing: .08em; color: var(--ink-3); }
.proto-root .jc-kvrow { display: flex; gap: 8px; padding: 3px 0; font-size: 12px; line-height: 1.55; }
.proto-root .jc-kvrow i { flex: 0 0 84px; font-style: normal; color: var(--ink-4); font-size: 11px; padding-top: 1px; }
.proto-root .jc-kvrow span { flex: 1 1 auto; min-width: 0; color: var(--ink); overflow-wrap: anywhere; }
.proto-root .jc-kvrow span:empty::after { content: "—"; color: var(--ink-4); }
.proto-root .jc-selcard .sc-req: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;
content: "必填"; color: var(--danger); background: var(--danger-soft); border-color: #f3b6b3;
}
.proto-root .jc-selcard .sc-sug: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;
content: "建议填"; color: var(--warn-accent); background: var(--warn-soft); border-color: #f0cf9a;
}
.proto-root .jc-selcard .sc-opt: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;
content: "选填"; color: var(--ink-4); background: var(--soft); border-color: var(--line);
}
/* 两把尺(静态规则表 —— 尺挂在墙上,不是这一单的数据) */
.proto-root .jc-graderule { border: 1px solid var(--line); border-radius: 10px; overflow: hidden; margin-bottom: 12px; }
.proto-root .jc-graderule-r { display: flex; gap: 12px; align-items: flex-start; padding: 9px 13px; font-size: 12px; line-height: 1.6; }
.proto-root .jc-graderule-r + .jc-graderule-r { border-top: 1px solid var(--line-soft); }
.proto-root .jc-graderule-r p { margin: 0; color: var(--ink-2); }
.proto-root .jc-graderule-r p b { color: var(--ink); }
.proto-root .jc-band {
flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; margin-top: 1px;
min-width: 84px; height: 20px; padding: 0 8px; border-radius: 6px;
font-size: 10.5px; font-weight: 800; letter-spacing: .04em; border: 1.5px solid; background: #fff;
}
.proto-root .jc-band.ga { color: var(--ok); border-color: var(--ok); }
.proto-root .jc-band.gb { color: var(--brand); border-color: var(--brand); }
.proto-root .jc-band.gc { color: var(--warn-accent); border-color: var(--warn-accent); }
.proto-root .jc-band.gk { color: var(--danger); border-color: var(--danger); }
.proto-root .jc-band.gz { color: var(--ink-3); border-color: var(--ink-4); }
/* 一单议价怎么走完:报价 → 发出 → 客户表态 → 定案 → 去向,内联线稿走位图 */
.proto-root .jc-priceline { display: flex; align-items: center; gap: 0; margin: 2px 0 14px; padding: 12px 14px; border: 1px solid var(--line); border-radius: 11px; background: var(--soft); }
.proto-root .jc-plnode { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: 3px; align-items: flex-start; }
.proto-root .jc-plnode b { font-size: 10.5px; font-weight: 800; letter-spacing: .06em; color: var(--ink-3); }
.proto-root .jc-plnode span { font-size: 12px; color: var(--ink-2); line-height: 1.5; }
.proto-root .jc-plarrow { flex: 0 0 auto; padding: 0 10px; color: var(--brand-soft); }
@media (max-width: 1000px) { .proto-root .jc-priceline { flex-direction: column; align-items: stretch; gap: 8px; } .proto-root .jc-plarrow { display: none; } }
/* ── 落边图:定案清单 → 三个去向(命中那条实线亮,别的留灰虚线;件不算不判)── */
.proto-root .route { margin: 2px 0 14px; padding: 10px 6px 6px; border: 1px solid var(--line); border-radius: 11px; background: #fff; }
.proto-root .route svg { width: 100%; height: auto; display: block; }
.proto-root .route .rspine { stroke: var(--line); stroke-width: 2; }
.proto-root .route .redge { stroke: var(--line); stroke-width: 2; stroke-dasharray: 5 5; fill: none; }
.proto-root .route .rnode { fill: #fff; stroke: var(--line); stroke-width: 1.5; }
.proto-root .route .rtxt { font-size: 12px; font-weight: 800; fill: var(--ink-3); }
.proto-root .route .rsub { font-size: 10px; fill: var(--ink-4); }
.proto-root .route .rcond { font-size: 10.5px; font-weight: 700; fill: var(--ink-4); }
.proto-root .route .rtip { fill: var(--line); }
.proto-root .route.v-r1 .e1, .proto-root .route.v-r2 .e2, .proto-root .route.v-r3 .e3 { stroke: var(--brand); stroke-dasharray: none; }
.proto-root .route.v-r1 .n1, .proto-root .route.v-r2 .n2, .proto-root .route.v-r3 .n3 { stroke: var(--brand); stroke-width: 2; }
.proto-root .route.v-r1 .t1, .proto-root .route.v-r2 .t2, .proto-root .route.v-r3 .t3 { fill: var(--brand-deep); }
.proto-root .route.v-r1 .k1, .proto-root .route.v-r2 .k2, .proto-root .route.v-r3 .k3 { fill: var(--brand); }
.proto-root .route.v-r1 .c1, .proto-root .route.v-r2 .c2, .proto-root .route.v-r3 .c3 { fill: var(--brand-deep); }
.proto-root .route .legend { display: flex; gap: 14px; flex-wrap: wrap; padding: 6px 10px 2px; font-size: 10.5px; color: var(--ink-4); }
/* 结论带 */
.proto-root .concl { display: grid; grid-template-columns: auto 1fr; gap: 4px 10px; align-items: start; margin: 2px 0 14px; padding: 11px 14px; border: 1px solid var(--line); border-radius: 11px; background: var(--soft); }
.proto-root .concl svg { grid-row: span 2; margin-top: 2px; color: var(--brand); }
.proto-root .concl .cl-main { font-size: 12.5px; color: var(--ink); line-height: 1.6; }
.proto-root .concl .cl-main b { font-weight: 800; }
.proto-root .concl .cl-route { display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap; font-size: 11.5px; color: var(--ink-3); }
.proto-root .concl .cl-route em { font-style: normal; color: var(--ink-4); }
.proto-root .concl .cl-route b { color: var(--brand-deep); font-weight: 800; }
.proto-root .concl .cl-route code { font-family: var(--mono, ui-monospace, monospace); font-size: 10.5px; color: var(--ink-4); }
`;
/** 一枚图标 —— 与原型同一写法 */
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>`;
/** 三层壳照原型:少一层样式表全失效 */
const doc = (input: { badge: string; kicker: string; title: string; titleRef: string; 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>`;
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>`}`;
/* ==================== 组成件 ==================== */
/** 要素四卡:客户 / 待回音 / 一格一料的定案刻度 / 议价的尺 */
const FOUR = `<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" data-blank></b><span data-slot="customer_code" data-blank></span></div>
</div>
<span class="jc-fcard-note">承自上游;单飞起单时此栏可空</span>
</div>
<div class="jc-fcard t-teal">
<span class="jc-fcard-k"><i>${ico('boxes')}</i>待回音</span>
<div class="jc-count"><b data-slot="quote_negotiate_line_missing"></b><span>/ <b data-slot="quote_negotiate_line_rows"></b> 条报价</span></div>
<span class="jc-fcard-note">客户表态还空着的行 —— 这一列只有收信的人填得了,系统不替客户说话;补一格少一格,基座现数</span>
</div>
<div class="jc-fcard">
<span class="jc-fcard-k"><i>${ico('shield-check')}</i>逐条定案</span>
<div class="jc-segmeter" data-rows="mine:quote_negotiate.line">
<i data-class-of="deal_status"></i>
</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('check', 13)}<span><b data-slot="valid_days"></b> 天 · 至 <b data-slot="valid_until" data-blank></b></span></span>
<span class="jc-fcard-note">按报价有效期 P-04 注明;D+3 催一轮、D+7 最后一催,催过仍不表态才按超时定案</span>
</div>
</div>`;
/** 一单议价是怎么走完的 —— 走位图(形状写死,不算不判) */
const DEAL_LINE = `<div class="jc-priceline">
<div class="jc-plnode"><b>批准价</b><span>A5 批过的那个数 —— 报出去的价一个数字都不改</span></div>
<div class="jc-plarrow">${ico('arrow-right', 18)}</div>
<div class="jc-plnode"><b>报价函发出</b><span>条款齐备(SPQ/MOQ/贸易条款/有效期),BD 发给客户</span></div>
<div class="jc-plarrow">${ico('arrow-right', 18)}</div>
<div class="jc-plnode"><b>客户表态</b><span>接受 / 还价 / 拒绝 / 无回复 —— 这一格由收信的人落</span></div>
<div class="jc-plarrow">${ico('arrow-right', 18)}</div>
<div class="jc-plnode"><b>定案 N-01</b><span>成交 / 待拍板 / 退回重批 / 不成交,逐条落定</span></div>
<div class="jc-plarrow">${ico('arrow-right', 18)}</div>
<div class="jc-plnode"><b>去向</b><span>退回重批→回定价 · 有成交→转履约 · 都没成→转归档</span></div>
</div>`;
/** 缺表态红带 —— 与提交闸、放行门 L1 同一个数同一套算法(基座现数);一格不缺自动收 */
const GAP_BAR = `<div class="jc-gapbar" data-class-of="quote_negotiate_line_missing">${ico('alert', 14)}
<span><b>还差 <span data-slot="quote_negotiate_line_missing"></span> 行客户表态没落</b>
—— <span data-slot="quote_negotiate_line_missing_keys"></span>。
按右上角「编辑」开表,红框那几格就是要填的:接受填 ACCEPT,还价填 COUNTER 并把客户报的那个价
填进「客户还价」,拒绝填 REJECT,催到有效期过仍不表态填 NOREPLY。补齐了执行单才提交得了。</span>
</div>`;
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>、<b>客户还价</b>与<b>客户原话</b>三列:
<b>这三列系统一格都不写</b> —— 客户回了什么只有你看得见,别让系统替他表态。
还价的行务必把他报的那个数填上,后面拍板全看这一格。本轮不报的料按行首 × 撤掉。
改动只落任务草稿层,<b>保存不等于放行</b>。</span>
</div>`;
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>`;
/**
* 报价函主表 —— 一行一料:条款列 + 客户那一侧三列 + 定案列。
*
* `data-col` 只挂人可改的三列(reply_type / counter_price / reply_note)+ 主键 pn ——
* 编辑面精确等于 BD 的职权面;**主键格必挂 `data-col`**(它不是编辑面,是行的钥匙:
* 不挂,服务端按「全表一致列」补键,两行会对到同一行上,后者盖前者 —— A2 走查逮过)。
* 客户表态挂 `req`:空着出「必填」胶囊、整行标底,与提交闸、放行门同源(都数 `_missing`)。
*/
const LINE_TABLE = `<div class="jc-tbl-wrap"><table class="jc-tbl-in">
<thead><tr>
<th style="width:36px;min-width:36px"></th>
<th style="width:44px;min-width:44px">类</th>
<th style="min-width:170px">PN</th>
<th style="min-width:84px">品类</th>
<th style="min-width:70px">年用量</th>
<th style="min-width:70px">交期</th>
<th style="min-width:64px">SPQ</th>
<th style="min-width:64px">MOQ</th>
<th style="min-width:86px">贸易条款</th>
<th style="min-width:88px">报价 CNY</th>
<th class="req" style="width:88px;min-width:88px">客户表态(你落)</th>
<th style="min-width:80px">客户还价</th>
<th style="width:80px;min-width:80px">定案</th>
<th style="min-width:150px;width:100%">客户原话 / 让价理由</th>
</tr></thead>
<tbody data-rows="mine:quote_negotiate.line" data-edit="quote_negotiate.line"><tr>
<td class="jc-rn"><button type="button" class="jc-rowdel" data-edit-del="quote_negotiate.line" title="撤掉这一行">×</button></td>
<td class="jc-ty" data-h="类"><span class="jc-tbadge" data-class-of="type_tag" data-slot="type_tag"></span></td>
<td data-h="PN"><span class="jc-vcell mono" data-slot="pn" data-col="pn" data-blank></span></td>
<td class="dimempty" data-h="品类"><span class="jc-vcell" data-slot="category" data-blank></span></td>
<td class="dimempty" data-h="年用量"><span class="jc-vcell mono" data-slot="eau" data-blank></span></td>
<td class="dimempty" data-h="交期"><span class="jc-vcell" data-slot="lead_time" data-blank></span></td>
<td class="dimempty" data-h="SPQ"><span class="jc-vcell num" data-slot="spq" data-blank></span></td>
<td class="dimempty" data-h="MOQ"><span class="jc-vcell num" data-slot="moq" data-blank></span></td>
<td class="dimempty" data-h="贸易条款"><span class="jc-vcell" data-slot="trade_terms" data-blank></span></td>
<td class="dimempty" data-h="报价 CNY"><span class="jc-vcell num" data-slot="quoted_price" data-blank></span></td>
<td class="req" data-h="客户表态(你落)">
<span class="jc-obadge" data-class-of="reply_type"></span>
<span class="jc-vcell" data-slot="reply_type" data-col="reply_type" data-blank></span>
</td>
<td class="sug" data-h="客户还价"><span class="jc-vcell num" data-slot="counter_price" data-col="counter_price" data-blank></span></td>
<td data-h="定案"><span class="jc-dbadge" data-class-of="deal_status"></span></td>
<td class="opt" data-h="客户原话 / 让价理由"><span class="jc-vcell" data-slot="reply_note" data-col="reply_note" data-blank></span></td>
</tr></tbody>
</table></div>`;
/**
* 成交定案表(收口件用)—— 编辑面换成**放行时可改的两列**(成交价与客户原话),
* 与 `requiredDeliverables[].editableAtRelease` 逐字对齐。
*/
const SETTLED_TABLE = `<div class="jc-tbl-wrap"><table class="jc-tbl-in">
<thead><tr>
<th style="width:36px;min-width:36px"></th>
<th style="width:44px;min-width:44px">类</th>
<th style="min-width:170px">PN</th>
<th style="min-width:88px">报价 CNY</th>
<th style="width:88px;min-width:88px">客户表态</th>
<th style="min-width:80px">客户还价</th>
<th style="min-width:110px">让步处置</th>
<th style="width:80px;min-width:80px">定案</th>
<th style="min-width:88px">成交价</th>
<th style="min-width:150px;width:100%">客户原话 / 让价理由</th>
</tr></thead>
<tbody data-rows="mine:quote_negotiate.line" data-edit="quote_negotiate.line"><tr>
<td class="jc-rn"><button type="button" class="jc-rowdel" data-edit-del="quote_negotiate.line" title="撤掉这一行">×</button></td>
<td class="jc-ty" data-h="类"><span class="jc-tbadge" data-class-of="type_tag" data-slot="type_tag"></span></td>
<td data-h="PN"><span class="jc-vcell mono" data-slot="pn" data-col="pn" data-blank></span></td>
<td class="dimempty" data-h="报价 CNY"><span class="jc-vcell num" data-slot="quoted_price" data-blank></span></td>
<td data-h="客户表态"><span class="jc-obadge" data-class-of="reply_type"></span></td>
<td class="dimempty" data-h="客户还价"><span class="jc-vcell num" data-slot="counter_price" data-blank></span></td>
<td class="dimempty" data-h="让步处置"><span class="jc-cbadge" data-class-of="concession"></span></td>
<td data-h="定案"><span class="jc-dbadge" data-class-of="deal_status"></span></td>
<td class="opt" data-h="成交价"><span class="jc-vcell num" data-slot="final_price" data-col="final_price" data-blank></span></td>
<td class="opt" data-h="客户原话 / 让价理由"><span class="jc-vcell" data-slot="reply_note" data-col="reply_note" data-blank></span></td>
</tr></tbody>
</table></div>`;
/** 定案分布:四段按条数分宽 —— 守恒等式(四档相加 = 行数)的可视形 */
const DIST = `<div class="jc-dist">
<div class="jc-distbar">
<i class="p" data-class-of="quote_negotiate_line_deal_status_won" data-slot="quote_negotiate_line_deal_status_won"></i>
<i class="n" data-class-of="quote_negotiate_line_deal_status_haggle" data-slot="quote_negotiate_line_deal_status_haggle"></i>
<i class="r" data-class-of="quote_negotiate_line_deal_status_revise" data-slot="quote_negotiate_line_deal_status_revise"></i>
<i class="k" data-class-of="quote_negotiate_line_deal_status_lost" data-slot="quote_negotiate_line_deal_status_lost"></i>
</div>
<div class="jc-distlg">
<span><em class="a"></em>成交 · 客户接受 <b data-slot="quote_negotiate_line_deal_status_won"></b></span>
<span><em class="b"></em>待拍板 · 客户还了价 <b data-slot="quote_negotiate_line_deal_status_haggle"></b></span>
<span><em class="b"></em>退回重批 · 让幅超授权 <b data-slot="quote_negotiate_line_deal_status_revise"></b></span>
<span><em class="c"></em>不成交 · 拒绝或超时 <b data-slot="quote_negotiate_line_deal_status_lost"></b></span>
</div>
</div>`;
/* ==================== 局面一 · 议价工作单(排版 / 发出 / 落回音)==================== */
/**
* 逐颗报价档案 —— 一颗料一张卡(data-rows 逐行复制):左条款、右议价。
*
* **容器用 `<section>` 不用 `<div>`**:渲染器的行展开按第一个同名闭合标签截行内容,
* 行模版里嵌着 `<div>` 时 `<div data-rows>` 会把行截成套娃(A2 逮过,记在陷阱表)。
*/
const QUOTE_CARDS = `<section class="jc-selcards" data-rows="mine:quote_negotiate.line">
<article class="jc-selcard" data-class-of="deal_status">
<div class="jc-selcard-h">
<span class="jc-tbadge" data-class-of="type_tag" data-slot="type_tag"></span>
<code data-slot="pn"></code>
<span class="why" data-slot="remark" data-blank></span>
<span class="jc-obadge" data-class-of="reply_type"></span>
<span class="jc-dbadge" data-class-of="deal_status"></span>
</div>
<div class="jc-selcard-b">
<div class="jc-selcol">
<h5>报价函条款(发出去就是承诺)</h5>
<div class="jc-kvrow"><i>客户料号</i><span data-slot="customer_pn" data-blank></span></div>
<div class="jc-kvrow"><i>品类 / 制造商</i><span data-slot="category" data-blank></span></div>
<div class="jc-kvrow"><i>年用量</i><span data-slot="eau" data-blank></span></div>
<div class="jc-kvrow"><i>报价 CNY</i><span class="num" data-slot="quoted_price" data-blank></span></div>
<div class="jc-kvrow"><i>交期</i><span data-slot="lead_time" data-blank></span></div>
<div class="jc-kvrow"><i>SPQ / MOQ</i><span class="num" data-slot="spq" data-blank></span></div>
<div class="jc-kvrow"><i>贸易条款</i><span data-slot="trade_terms" data-blank></span></div>
<div class="jc-kvrow"><i>有效期至</i><span data-slot="valid_until" data-blank></span></div>
</div>
<div class="jc-selcol">
<h5>议价(填回下表,这里回显)</h5>
<div class="jc-kvrow"><i>客户表态</i><span class="sc-req" data-slot="reply_type" data-blank></span></div>
<div class="jc-kvrow"><i>客户还价</i><span class="sc-sug num" data-slot="counter_price" data-blank></span></div>
<div class="jc-kvrow"><i>客户原话</i><span class="sc-opt" data-slot="reply_note" data-blank></span></div>
<div class="jc-kvrow"><i>让步处置</i><span class="jc-cbadge" data-class-of="concession"></span></div>
<div class="jc-kvrow"><i>定案</i><span class="jc-dbadge" data-class-of="deal_status"></span></div>
<div class="jc-kvrow"><i>成交价</i><span class="num" data-slot="final_price" data-blank></span></div>
<div class="jc-kvrow"><i>客户约束</i><span data-slot="remark" data-blank></span></div>
</div>
</div>
</article>
</section>`;
/** 议价结果 N-01 的四档 —— 静态规则,尺挂在墙上(素材:MVP 示例工作流 §1.4) */
const OUTCOME_RULES = `<div class="jc-graderule">
<div class="jc-graderule-r">
<span class="jc-band ga">ACCEPT</span>
<p><b>客户明确接受这个价。</b>「按这个价走」「下单吧」「PO 稍后发」——
定案成交,成交价就是报价,转订单履约。</p>
</div>
<div class="jc-graderule-r">
<span class="jc-band gc">COUNTER</span>
<p><b>客户还了一个价。</b>把他报的那个数落进「客户还价」那一格 ——
没有数字的「太贵了」先追问,追不到才按无回复落。还价的条目<b>先落成「待拍板」</b>,
等人按让步梯度拍一下才有结论:接了算成交、退回重批、或守价不让 —— 系统不替你让价。</p>
</div>
<div class="jc-graderule-r">
<span class="jc-band gk">REJECT</span>
<p><b>客户明确不做了。</b>选了别家、项目取消、规格不匹配 ——
定案不成交,把缘由记进备注,转情报归档:这一单丢了,情报留下。</p>
</div>
<div class="jc-graderule-r">
<span class="jc-band gz">NOREPLY</span>
<p><b>催到有效期过仍不表态。</b><b>客户不回不是结论</b> ——
D+3 第一轮催问确认收到探态度,D+7 最后一催;催过了才轮到按超时定案。</p>
</div>
</div>`;
/** 让步梯度 N-02 的三档 —— 静态规则(素材:MVP 示例工作流 §1.4 让价核线) */
const CONCESSION_RULES = `<div class="jc-graderule">
<div class="jc-graderule-r">
<span class="jc-band ga">≤ 3%</span>
<p><b>接了拿单。</b>意思意思的压价 —— 来回一轮的时间成本比这三个点贵,
当场接住,把单子锁下来。</p>
</div>
<div class="jc-graderule-r">
<span class="jc-band gb">3% ~ 10%</span>
<p><b>授权内回旋。</b>让一半再报回去,把让出去的换成量或账期 ——
先弄清争的是价格、交期还是 MOQ:争交期的用交期换,一上来就降价,后面只剩降价这一招。</p>
</div>
<div class="jc-graderule-r">
<span class="jc-band gk">&gt; 10%</span>
<p><b>超出授权,退回成本定价重批。</b>这一档系统不替任何人放行,也不许私自破线 ——
底价线以下的单,每补一次货都在亏。<b>破线不是勇气是事故</b>。</p>
</div>
</div>`;
export const NEGOTIATE_WORKSHEET = doc({
badge: 'sheet',
kicker: '统一报价 · A6 报价议价 · 议价作业',
title: '议价工作单',
titleRef: 'rfq_ref',
state:
'<span class="jc-rp-wait warn">待回音 <b data-slot="quote_negotiate_line_missing"></b> / <b data-slot="quote_negotiate_line_rows"></b> 条</span>',
body:
/* `items` 是起单入参的数组,基座渲染数组时给的是条数 —— 正好是「这一单要报几条」 */
statsBand([
['报价条目', 'items'],
['单价 CNY', 'unit_price'],
['交期', 'lead_time'],
['有效期', 'valid_until'],
]) +
section('报价条款六样 —— 缺一不可',
kvGrid([
['单价 CNY', 'unit_price'],
['交期', 'lead_time'],
['最小包装量 SPQ', 'spq'],
['最小起订量 MOQ', 'moq'],
['贸易条款', 'trade_terms'],
['有效期', 'valid_until'],
]) +
noteOnly(
'单价与交期<b>照审批通过的草案抄</b>,不做任何「顺手优化」—— 改价要重新过审批点。' +
'有效期按行情波动定,不要一律写「长期有效」:那等于把汇率与料价风险自己扛了。' +
'拿不准的项<b>宁可空着走补全</b>,不要填一个看起来合理的值。',
),
{ text: '缺一样多一轮' },
) +
section('本单已成形的报价行',
tableOnly('mine:quote_negotiate.formal_quote', [
['报价单号', 'quote_no', 'mono'],
['型号 PN', 'pn', 'mono'],
['单价', 'unit_price'],
['交期', 'lead_time'],
['有效期', 'valid_until'],
]),
{ text: '只列本单写下的行' },
) +
section('议价的节拍 —— 一轮一版',
ladder([
['1', '写成正式报价单', '中英双语,六样齐备'],
['2', '人工按下发出', '触达客户的一下,系统不替人做这个决定'],
['3', 'D+3 提醒到点', '没有跟进的报价等于没报'],
['4', 'D+7 提醒到点', '第二记催办,仍无回音即等超时'],
['5', '客户回音 → 议价调整', '改价、改交期、换料都是新一版,重新走发出'],
]),
{ text: '往来看得出先后' },
) +
section('案例当下进程', stagesOnly('case:stages'), { text: 'Enabled ≠ Active' }) +
section('这一步还没碰外界',
verdict('报价单此刻只是草稿 —— 一封信都还没发出去', 'wait', '发出这一下必须人工按') +
noteOnly(
'发出是<b>写类动作</b>:过程中只登记为「动作意向」—— 入参冻结、等放行,放行前不触达世界。' +
'预授权是逐项列举的:催办模板可即时发,<b>报价单不行</b>。',
),
),
part('议价任务', '客户 <b data-slot="customer_name" data-blank></b> · 报价函 <b data-slot="quote_no" data-blank></b> · 共 <b data-slot="line_total"></b> 条') +
`<div class="jc-derive">${ico('info', 14)}
<span>这一屏是一份报价函的作业台:系统已按 A5 批准的价排好版,条款与有效期都在下面 ——
<b>报出去的价一个数字都不改</b>(要改价得回审批)。你要做的是把它发给客户,
按 D+3 / D+7 的节奏催问,<b>客户回话之后在「客户表态」那一列逐行落</b>。
这一列系统一格都不写:客户回了什么,只有收信的人看得见。</span>
</div>` +
FOUR +
part('一单议价是怎么走完的', '批准价 → 发出 → 客户表态 → 定案 → 去向') +
DEAL_LINE +
part('逐颗报价档案', '条款在左 · 议价在右') +
QUOTE_CARDS +
part('议价结果 N-01', '客户的话只有四种表态 —— 尺挂在墙上') +
OUTCOME_RULES +
part('让步梯度 N-02', '还价幅度决定让不让、谁来让') +
CONCESSION_RULES +
part('填回工作表', '客户表态落在这 —— 补齐了执行单才提交得了') +
GAP_BAR +
editGate('quote_negotiate.line', '报价函填回表') +
LINE_TABLE +
addRowBar('quote_negotiate.line') +
`<div class="jc-drop">${ico('info', 13)}
<span>带红标的<b>客户表态</b>是必填,而且<b>只由你填</b> —— 缺一行执行单按不下提交,
与放行门是同一个数同一套算法。四个取值一个字母不许差:
<b>ACCEPT</b> 接受 · <b>COUNTER</b> 还价 · <b>REJECT</b> 拒绝 · <b>NOREPLY</b> 无回复。
还价的行务必把客户报的那个价填进<b>客户还价</b> —— 那一格空着这张单同样提交不了,
后面拍板全看它。客户只回了一部分料的,没回的那几行按 NOREPLY 落,
<b>别替客户猜</b>;本轮不报的料按行首 × 撤掉。</span>
</div>`,
foot: sign('BD 议价 · AI 排版报价函并按两把尺定案,客户那一侧的话由你落', '统一报价 · A6 报价议价 · 在办'),
});
/** 局面二:待放行 —— 按下的那一刻信就发出去,这是最后一次能改的机会 */
export const QUOTE_RELEASE = viewShell({
title: '正式报价单 · 放行即发出',
kicker: 'A6 · 报价议价 · 成果待签',
tone: 'case',
icon: 'send',
stamp: { kind: 'live', text: '待签字放行' },
sign: 'A6 报价议价 · 正式报价单(待放行即发出)',
/* ==================== 局面二 · 成交定案清单(拍板 / 放行 / 归档)==================== */
/** 落边图:定案清单 → 三个去向(命中边由壳上 v-r1/v-r2/v-r3 上色,件不算不判) */
const ROUTE_FIG = `<div class="route" data-class-of="case:routeKey">
<svg viewBox="0 0 660 250" fill="none">
<rect class="rnode" x="8" y="95" width="150" height="60" rx="10"/>
<text class="rtxt" x="83" y="120" text-anchor="middle">成交定案清单</text>
<text class="rsub" x="83" y="138" text-anchor="middle">共 <tspan data-slot="quote_negotiate_line_rows"></tspan> 条</text>
<path class="rspine" d="M158 125 H210"/>
<path class="redge e1" d="M210 125 C280 125 300 40 460 40"/>
<path class="redge e2" d="M210 125 C300 125 340 125 460 125"/>
<path class="redge e3" d="M210 125 C280 125 300 210 460 210"/>
<path class="rtip k1" d="M448 34 l14 6 -14 6 z"/>
<path class="rtip k2" d="M448 119 l14 6 -14 6 z"/>
<path class="rtip k3" d="M448 204 l14 6 -14 6 z"/>
<text class="rcond c1" x="300" y="60">退回重批 <tspan data-slot="quote_negotiate_line_deal_status_revise"></tspan> 条</text>
<text class="rcond c2" x="300" y="115">成交 <tspan data-slot="quote_negotiate_line_deal_status_won"></tspan> 条</text>
<text class="rcond c3" x="300" y="230">不成交 <tspan data-slot="quote_negotiate_line_deal_status_lost"></tspan> 条</text>
<rect class="rnode n1" x="460" y="8" width="180" height="64" rx="10"/>
<text class="rtxt t1" x="550" y="35" text-anchor="middle">A4 · 成本定价</text>
<text class="rsub" x="550" y="53" text-anchor="middle">让幅超授权 · 退回重批</text>
<rect class="rnode n2" x="460" y="93" width="180" height="64" rx="10"/>
<text class="rtxt t2" x="550" y="120" text-anchor="middle">转订单履约 ORM</text>
<text class="rsub" x="550" y="138" text-anchor="middle">成交 · 这条流走到终点</text>
<rect class="rnode n3" x="460" y="178" width="180" height="64" rx="10"/>
<text class="rtxt t3" x="550" y="205" text-anchor="middle">A7 · 情报归档</text>
<text class="rsub" x="550" y="223" text-anchor="middle">拒绝 / 超时 · 情报留下</text>
</svg>
<div class="legend">
<span>命中边实线亮 · 未命中留灰虚线</span>
<span>按序求值首个命中即出:判了退回重批 → 回定价;其次有成交 → 转履约;都没成 → 转归档</span>
<span>判定在你签字那一刻由系统现算 —— 要改去向,在这张表上改定案那几格</span>
</div>
</div>`;
export const NEGOTIATE_ROSTER = doc({
badge: 'file-check',
kicker: '统一报价 · A6 报价议价 · 交付成果',
title: '成交定案清单',
titleRef: 'rfq_ref',
state:
'<span class="jc-rp-wait">报价 <b data-slot="quote_negotiate_line_rows"></b> 条 · 待拍板 <b data-slot="quote_negotiate_line_deal_status_haggle"></b> 条(要归零)</span>',
body:
statsBand([
['单价 CNY', 'unit_price'],
['交期', 'lead_time'],
['最小起订量 MOQ', 'moq'],
['有效期', 'valid_until'],
]) +
section('本单交付:正式报价单',
tableOnly('mine:quote_negotiate.formal_quote', [
['报价单号', 'quote_no', 'mono'],
['型号 PN', 'pn', 'mono'],
['单价', 'unit_price'],
['交期', 'lead_time'],
['SPQ', 'spq'],
['MOQ', 'moq'],
['贸易条款', 'trade_terms'],
['有效期', 'valid_until'],
]),
{ text: '发出去的就是这几行' },
) +
section('历次报价对照 —— 这一轮让了多少',
/*
* 这一张**故意不用 `mine:`**:它取共享域整表,含既往单的行。
* 本单成果只看 `mine:`(见上一节),而「让了多少」这件事天然要跟别的单比 ——
* 没有上一轮的价摆在旁边,人签字时判断不了这一让的分寸。
*/
tableOnly('shared.history_price', [
['型号 PN', 'pn', 'mono'],
['报价日期', 'quoted_at', 'mono'],
['单价', 'unit_price'],
['交期', 'lead_time'],
]) +
noteOnly(
'这一节<b>不是本单成果</b>,是共享域里同一料号的历次报价(含既往单)—— 摆在这里只做一件事:' +
'给这一轮的价一个参照系。报价日期在列上,先后按日期对;它由 A7 情报归档一单一单沉下来。',
),
{ text: '含既往单' },
) +
section('放行前要过的四道判据',
ladder([
['1', '单价 / 交期 / SPQ / MOQ / 贸易条款 / 有效期齐备', 'L1 机检 · missingQuoteFields = 0'],
['2', '按报价有效期 P-04 注明有效期', 'L2 机检 · validityDays 大于 0'],
['3', '已建 D+3 / D+7 跟进', 'L2 机检 · 没有跟进的报价等于没报'],
['4', 'BD 放行', 'L5 人签 · 签了才算数'],
]),
{ text: '三机检 · 一人签' },
) +
section('出口三选一 —— 恰发一口',
ladder([
['1', '成交', '转订单履约(ORM),这条流走到终点'],
['2', '有异议', '回 A4 重开新实例重算 —— 旧的已终,不再入算'],
['3', '拒绝 / 超时', '转 A7 情报归档,把拒绝原因与竞对价位登记下来'],
]) +
noteOnly(
'三个出口对应三种后续,随放行<b>恰选一口</b>,下游网关读的就是它;<b>不选口不许放行</b> ——' +
'含糊的结果比没结果更坏。「拒绝」不是失败终态:原因回写历史情报后,下一单的三分类与利润模型会用上它。',
),
{ text: '下游只读这一口' },
) +
section('按下放行会发生什么',
verdict('放行即落地:台账回写 + 报价单出站发送', 'wait', '已成动作留存,不回滚') +
noteOnly(
'① <b>落地预检</b>先跑:交付物齐备、动作意向可执行、授权在契约边界内 —— 预检不过不发卡;' +
'② 放行通过<b>即刻落地</b>,出站动作附幂等键;' +
'③ <b>落地收敛</b>之后输出口才发射 —— 下游只见落地完成的任务。',
),
),
part('议价要素', '报价议价 · 能查表的归判定表,要让价的归人') +
FOUR +
part('定案分布', '成交 + 待拍板 + 退回重批 + 不成交 = 总条数') +
DIST +
`<div class="jc-derive">${ico('info', 14)}
<span>四段条数由基座逐行现数,不是谁报上来的。橙段(<b>待拍板</b>)是过渡态 ——
客户还了价而还没人拍板,它<b>归零这一单才走得到放行门</b>:接了算成交、
退回重批算一条正经的路、守价不让算不成交。要让价就得在授权内让,
超过一成的让幅一律回去重批,<b>不私自破线</b>。</span>
</div>` +
part('正式报价函', '<b data-slot="line_total"></b> 条 · 每条都带客户的话') +
GAP_BAR +
editGate('quote_negotiate.line', '成交定案表') +
SETTLED_TABLE +
`<div class="jc-drop">${ico('info', 13)}
<span>每一行摆的是这一条的<b>来路</b>:报了多少、客户怎么表态、还了什么价、
让步梯度判在哪一档、最后定成什么。签字前请重点看两处:<b>定案列</b>里非绿的条目,
与<b>客户原话列</b> —— 没有客户原话的还价,下一轮谈判就是重新开始。
这一屏可改的是<b>成交价</b>与<b>客户原话</b>两列(你的职权面)。</span>
</div>` +
part('落边 · 这一签流向哪里', '出口按序求值 · 首个命中即出') +
ROUTE_FIG +
`<div class="concl">
${ico('git-branch', 15)}
<div class="cl-main"><b>议价结论</b> —— <span data-slot="summary" data-blank></span></div>
<div class="cl-route"><em>转入活动</em><b data-slot="case:routeTo"></b><code data-slot="case:routeWhy"></code></div>
</div>` +
part('放行后即执行的落地动作', '门前 → 门后') +
`<div class="jc-acts">
<div class="jc-act"><i>${ico('check', 11)}</i>
<div><b>草稿层合并进正式版</b>&#12288;<span>本任务写下的报价函与议价结论一并落账;
你就地改过的成交价与客户原话随之落定。</span></div>
</div>
<div class="jc-act"><i>${ico('check', 11)}</i>
<div><b>命中的那一口此刻才发射</b>&#12288;<span>报价函
<b data-slot="quote_negotiate_line_rows"></b> 条随载荷交给上面亮线指向的下游;
另两口本次不起单 —— 放行之前,世界上什么都没有发生。</span></div>
</div>
<div class="jc-act"><i>${ico('check', 11)}</i>
<div><b>不成交也留资产</b>&#12288;<span>拒绝原因与竞对价位随载荷交给情报归档 ——
这一单丢了,下一单的三分类与利润模型会用上它。</span></div>
</div>
</div>`,
foot: sign('BD 议价放行 · AI 排版与定案留痕 —— 要改哪一格,在这张定案表上就地改', '统一报价 · A6 报价议价 → 定去向'),
});