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,669 @@
<!doctype html>
<!--
JCAI one-pager · 目标件骨架(html-one-pager 技能产出起点)
· 浏览器直接打开;设计定稿后按 docs/HTML报告生成规范.md §7 平移进活动包
· 照 PRD 方法论这是「带脚本演示页」:演示脚本集中写在文尾一个 script 标签里
(不写内联事件),平移时整段剥掉;初始画面不靠脚本也要完整
-->
<html lang="zh-CN" class="editable">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>替代选型工作单 · JCAI one-pager 目标件</title>
<style>
/* ⇩ standalone 包装层:真件跑在工作区 iframe 里,这几行由宿主环境承担。平移时丢弃 */
html, body { margin: 0; padding: 0; }
/* ⇧ 丢弃到此 */
/* ============================================================================
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; }
/* ① 空格子自己说话 —— 值真的空才轮到这三条(基座 data-blank)。
* 占位三级(2026-08-03 补齐,用户:「目标价是建议」):必填红 / 建议橘 / 选填灰,
* 档位跟台账声明走(required → req,expect → sug,其余 opt),颜色与 A2 同一套。 */
/* 胶囊与同一行的数字坐同一条线(2026-08-03 用户:「没有和数字对齐」)——
* 裸 inline-flex 的胶囊按 baseline 挂:盒子(10px 字 + 上下 3px 内距 + 边框 = 18px)比
* 字的基线深好几像素,视觉上整枚往下坠。治法:空格子自己撑起文字那份盒高(12px×1.5),
* 胶囊在里面垂直居中 —— 中线对中线,行高也不再忽高忽低。
* :not(.on) 不能省(2026-08-03 用户:「编辑态太拥挤」):这条比「编辑态藏起只读份」
* 的那条更特异,不圈住的话编辑态里胶囊会盖过 display:none 冒出来,54 个输入框旁边
* 各站一枚 —— 编辑态里格子该不该填由闸与红带说,胶囊只属于看的那份。 */
.proto-root .jc-tbl-in:not(.on) td.req > .jc-vcell:empty,
.proto-root .jc-tbl-in:not(.on) td.sug > .jc-vcell:empty,
.proto-root .jc-tbl-in:not(.on) td.opt > .jc-vcell:empty { display: flex; align-items: center; min-height: 18px; }
.proto-root .jc-tbl-in td.req > .jc-vcell:empty::after,
.proto-root .jc-tbl-in td.sug > .jc-vcell:empty::after,
.proto-root .jc-tbl-in td.opt > .jc-vcell:empty::after {
display: inline-flex; align-items: center; font-style: normal; font-size: 10px; font-weight: 700;
line-height: 1; padding: 3px 7px; border-radius: 5px; border: 1px dashed;
}
.proto-root .jc-tbl-in td.req > .jc-vcell:empty::after { content: "必填"; color: var(--danger); background: var(--danger-soft); border-color: #f3b6b3; }
.proto-root .jc-tbl-in td.sug > .jc-vcell:empty::after { content: "建议"; color: #b45309; background: #fff7ed; border-color: #fcd9a8; }
.proto-root .jc-tbl-in td.opt > .jc-vcell:empty::after { content: "选填"; color: var(--ink-4); background: var(--soft); border-color: var(--line); }
/* 归档件里空格子就是「客户没给」,不再摆「你还能填」的胶囊 */
.proto-root .jc-tbl-in td.dimempty > .jc-vcell:empty::after { content: "—"; color: var(--ink-4); }
/* ② 缺必填的整行 —— 原型是 tr.miss,这里由 :has() 认 */
.proto-root .jc-tbl-in tbody tr:has(td.req > .jc-vcell:empty) td { background: #fffbfb; }
.proto-root .jc-tbl-in tbody tr:has(td.req > .jc-vcell:empty):hover td { background: #fff5f5; }
.proto-root .jc-tbl-in tbody tr:has(td.req > .jc-vcell:empty) td.jc-rn { color: var(--danger); font-weight: 800; position: relative; }
@media (min-width: 1001px) {
.proto-root .jc-tbl-in tbody tr:has(td.req > .jc-vcell:empty) td.jc-rn::after {
content: ""; position: absolute; left: 0; top: 4px; bottom: 4px; width: 3px; border-radius: 0 3px 3px 0; background: var(--danger);
}
}
/* 序号由 CSS 计数器排 —— 模版里没有循环变量,行是基座复制出来的 */
.proto-root .jc-tbl-in tbody { counter-reset: rn; }
.proto-root .jc-tbl-in td.jc-rn::before { counter-increment: rn; content: counter(rn); }
/* 窄屏原型给的是 ::before「第 」+ ::after「 条」;这里 ::before 已被计数器占用,
把两句合进 ::before,并把原型那句 ::after 收掉 —— 否则窄屏上写着「第 9 条 条」 */
@media (max-width: 1000px) {
.proto-root .jc-tbl-in td.jc-rn::before { content: "第 " counter(rn) " 条"; }
.proto-root .jc-tbl-in td.jc-rn::after { content: none; }
}
/* ④ 就地改:每个可改的格子渲了两份 —— 只读那份与输入那份,这里二选一。
**默认锁着**:开关是那张表上的 on(外层按下「编辑」才加),
一屏两张表各管各的。判断在样式表里,不在填数里,件本身仍是同一份。 */
/* 默认锁着:只读那份在,输入孪生件收起来 —— **件首先是给人看的** */
.proto-root .jc-cell { display: none; }
/* 开闸是给那张表的容器加 「on」(外层按下「编辑」才加)—— 一屏两张表各管各的 */
.proto-root .jc-tbl-in.on td > .jc-vcell,
.proto-root .jc-tbl-in.on td > .jc-tbadge { display: none; }
.proto-root .jc-tbl-in.on td > .jc-cell { display: block; }
/* 类那一列平时只有一枚 24px 的徽,编辑态要放得下一个字 */
.proto-root .jc-tbl-in.on td.jc-ty, .proto-root .jc-tbl-in.on th:nth-child(2) { width: 56px; min-width: 56px; }
/*
* **数值列也左对齐**(2026-08-02 用户:「目标价的价格也要跟标题左对齐,右对齐太奇怪了」)。
*
* 右对齐是给**要竖着相加的那种数**用的(金额合计、数量小计)——这张表里的目标价
* 一行一个料、彼此不相加,右对齐只是让它和自己的表头分家:标题贴左、数贴右,
* 中间空一条,眼睛得来回找。等宽字体留着(位数对齐仍有用),对齐方式跟表头走。
*/
.proto-root .jc-vcell.num, .proto-root .jc-cell.num { text-align: left; }
.proto-root .jc-tbl-in.on td.num > .jc-cell, .proto-root .jc-tbl-in.on .jc-cell.num { text-align: left; }
/* 编辑态下必填空格子:红框 + 红 placeholder(原型的 req-empty 同款,只是由 :placeholder-shown 认) */
.proto-root .jc-tbl-in.on td.req > .jc-cell:placeholder-shown { border-color: #f3b6b3; background: #fffafa; }
.proto-root .jc-tbl-in.on tbody tr:has(td.req > .jc-cell:placeholder-shown) td { background: #fffbfb; }
/* ⑥ **铺满工作区**(2026-08-02 用户定:「报告不要做成单独的嵌入层」)。
原型那份是独立网页,jc-stage 给了 1160px 上限与 22/16/52px 的四周留白 ——
摆进工作区就成了「画布上浮着的一张小卡」:左右各空一条,底下还坠着 52px。
件在这里**就是这一屏的正文**,不是嵌在里面的另一个页面:去掉上限与横向留白,
外框那圈圆角边也一并卸掉(工作区自己已经是一张卡,两层框叠着像贴纸)。 */
.jc-stage { max-width: none; padding: 0 0 4px; }
.proto-root .jc-report { border: 0; border-radius: 0; box-shadow: none; }
/* ⑤ 那道闸 —— 默认「已锁定 + 编辑」,开了变成「取消 / 保存」。
整块只在这一屏可改时才出现(外层给根节点加 「editable」);
归档件里它一个字都不该有 —— 那时件是件,不是表单。 */
.proto-root .jc-editgate { display: none; }
html.editable .proto-root .jc-editgate {
display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
margin-bottom: 10px; padding: 8px 12px; border-radius: 9px;
background: var(--soft); border: 1px solid var(--line-soft);
font-size: 11.5px; color: var(--ink-3);
}
.proto-root .jc-editgate-t { display: inline-flex; align-items: center; gap: 6px; flex: 1 1 auto; min-width: 0; }
.proto-root .jc-editgate-t b { color: var(--ink-2); font-weight: 700; }
.proto-root .jc-egbtn {
flex: none; display: inline-flex; align-items: center; gap: 4px; cursor: pointer;
border: 1px solid var(--line); border-radius: 7px; padding: 5px 11px;
font: inherit; font-size: 11.5px; font-weight: 700; color: var(--ink-2); background: #fff;
transition: border-color var(--t-fast), color var(--t-fast), background var(--t-fast);
}
.proto-root .jc-egbtn:hover { border-color: var(--brand); color: var(--brand); }
.proto-root .jc-egbtn.save { color: #fff; background: var(--brand); border-color: var(--brand); }
.proto-root .jc-egbtn.save:hover { background: var(--brand-deep); border-color: var(--brand-deep); }
/* 锁着的时候只有「编辑」;开了换成「取消 / 保存」—— 三个钮同时摆着,人不知道现在算哪一态 */
.proto-root .jc-editgate .jc-egbtn.ghost, .proto-root .jc-editgate .jc-egbtn.save { display: none; }
.proto-root .jc-editgate.on { background: var(--brand-wash); border-color: var(--brand-soft); color: var(--brand-deep); }
.proto-root .jc-editgate.on .jc-egbtn.open { display: none; }
.proto-root .jc-editgate.on .jc-egbtn.ghost, .proto-root .jc-editgate.on .jc-egbtn.save { display: inline-flex; }
.proto-root .jc-editgate.on .jc-editgate-t { visibility: hidden; }
/* 缺项红带 —— **只在这一屏可改时出现**,且一格不缺就不出(判断在样式表,不在填数里) */
.proto-root .jc-gapbar { display: none; }
html.editable .proto-root .jc-gapbar {
display: flex; align-items: flex-start; gap: 8px;
margin: 0 0 10px; padding: 9px 12px; border-radius: 9px;
background: #fff5f5; border: 1px solid #f3b6b3; color: var(--danger);
font-size: 12px; line-height: 1.65;
}
.proto-root .jc-gapbar svg { flex: 0 0 auto; margin-top: 2px; }
.proto-root .jc-gapbar b { font-weight: 800; }
.proto-root .jc-gapbar span { color: #9a3b36; }
/* 一格不缺 —— 那条带没有话可说 */
html.editable .proto-root .jc-gapbar.v-0 { display: none; }
/* 那条「怎么改」的提示只在开了闸之后说 —— 锁着的时候它是句废话 */
.proto-root .jc-editbar { display: none; }
.proto-root .jc-editgate.on + .jc-editbar { display: flex; }
/* ⑦ 增删行 —— 手改是兜 AI 的错那一手,而错不止「这一格填错了」:
「这一行根本不该在」与「漏了一行」是同一类错,只给改格子就没有出口。 */
/* 序号那一格一列两用:只读时是行号(计数器画的),编辑时是「撤掉这一行」的 × */
.proto-root .jc-rowdel { display: none; }
.proto-root .jc-tbl-in.on td.jc-rn::before { content: none; }
.proto-root .jc-tbl-in.on .jc-rowdel {
display: block; width: 20px; height: 20px; margin: 0 auto; cursor: pointer;
border: 1px solid var(--line); border-radius: 6px; padding: 0;
font: inherit; font-size: 13px; line-height: 1; color: var(--ink-3); background: #fff;
transition: border-color var(--t-fast), color var(--t-fast), background var(--t-fast);
}
.proto-root .jc-tbl-in.on .jc-rowdel:hover { border-color: var(--danger); color: var(--danger); background: #fff6f6; }
/* 撤掉的行**不当场抹掉** —— 划着摆在那儿,人看得见自己删了哪一行,也才有得后悔 */
.proto-root .jc-tbl-in.on tr.jc-row-gone td { background: #fff6f6; opacity: .55; }
.proto-root .jc-tbl-in.on tr.jc-row-gone .jc-cell { text-decoration: line-through; color: var(--ink-3); }
.proto-root .jc-tbl-in.on tr.jc-row-gone .jc-rowdel { border-color: var(--danger); color: var(--danger); background: #fff; }
.proto-root .jc-tbl-in.on tr.jc-row-new td { background: #f2fbf7; }
/* 表尾那条「加一行」—— 与闸同款,只在这一屏可改且开了闸时现身 */
.proto-root .jc-addrow { display: none; }
html.editable .proto-root .jc-tbl-wrap:has(table.on) + .jc-addrow {
display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
margin: -2px 0 12px; padding: 7px 12px; border-radius: 9px;
background: var(--soft); border: 1px dashed var(--line);
}
.proto-root .jc-addrow em { font-style: normal; font-size: 11px; color: var(--ink-3); }
/* ③ 按值上色:基座把值消毒成 v-* 追加进 class */
/* 刻度条按缺口:missing_key_fields 是条数,0 = 完整(绿,原型的默认色),非 0 = 缺必填(红) */
.proto-root .jc-segmeter > i.v-1, .proto-root .jc-segmeter > i.v-2, .proto-root .jc-segmeter > i.v-3 { background: var(--danger); }
/* 刻度条按状况标:归档屏那张卡上,一格即一条料的类别 */
.proto-root .jc-segmeter > i.v-a { background: var(--ok); }
.proto-root .jc-segmeter > i.v-b { background: var(--brand); }
.proto-root .jc-segmeter > i.v-c { background: var(--warn-accent); }
.proto-root .jc-tbadge { background: var(--ink-4); }
.proto-root .jc-tbadge.v-a { background: var(--ok); }
.proto-root .jc-tbadge.v-b { background: var(--brand); }
.proto-root .jc-tbadge.v-c { background: var(--warn-accent); }
/* 分布条:三段按条数分宽。原型是 JS 写 inline style="flex:N",而模版不许算 ——
改由 data-class-of 把**条数本身**投影成 v-<N>,这里备一把梯子让 CSS 认。
条数 0 的那段整段不出:摆一条零宽的彩带不如不摆。 */
/* 原型那段是 grid + place-items:center;这里段里多了个 ::before 前缀,
grid 会把前缀和数字排成上下两行 —— 改成一行居中的 flex */
.proto-root .jc-distbar > i { flex: 24; min-width: 34px; display: inline-flex; align-items: center; justify-content: center; }
.proto-root .jc-distbar > i.v-0, .proto-root .jc-distbar > i:not([class*=" v-"]) { display: none; }
.proto-root .jc-distbar > i.v-1 { flex: 1 } .proto-root .jc-distbar > i.v-2 { flex: 2 }
.proto-root .jc-distbar > i.v-3 { flex: 3 } .proto-root .jc-distbar > i.v-4 { flex: 4 }
.proto-root .jc-distbar > i.v-5 { flex: 5 } .proto-root .jc-distbar > i.v-6 { flex: 6 }
.proto-root .jc-distbar > i.v-7 { flex: 7 } .proto-root .jc-distbar > i.v-8 { flex: 8 }
.proto-root .jc-distbar > i.v-9 { flex: 9 } .proto-root .jc-distbar > i.v-10 { flex: 10 }
.proto-root .jc-distbar > i.v-11 { flex: 11 } .proto-root .jc-distbar > i.v-12 { flex: 12 }
.proto-root .jc-distbar > i.v-13 { flex: 13 } .proto-root .jc-distbar > i.v-14 { flex: 14 }
.proto-root .jc-distbar > i.v-15 { flex: 15 } .proto-root .jc-distbar > i.v-16 { flex: 16 }
.proto-root .jc-distbar > i.v-17 { flex: 17 } .proto-root .jc-distbar > i.v-18 { flex: 18 }
.proto-root .jc-distbar > i.v-19 { flex: 19 } .proto-root .jc-distbar > i.v-20 { flex: 20 }
/* 段里那句「A · 5」的前缀 —— 类别是这一段的固有属性,不必从数据里取 */
.proto-root .jc-distbar > i.a::before { content: "A · "; }
.proto-root .jc-distbar > i.b::before { content: "B · "; }
.proto-root .jc-distbar > i.c::before { content: "C · "; }
/* 判定表命中行:基座把命中的那一条注入成 v-r1/r2/r3,壳上带 class,行自己认 */
.proto-root .jc-dmn.v-r1 { --tone: var(--warn-accent); --tone-soft: rgba(255,136,0,.11); }
.proto-root .jc-dmn.v-r2 { --tone: var(--brand); --tone-soft: rgba(51,112,255,.09); }
.proto-root .jc-dmn.v-r3 { --tone: var(--ok); --tone-soft: rgba(39,135,23,.10); }
.proto-root .jc-dmn.v-r1 .jc-dmn-r.r1, .proto-root .jc-dmn.v-r2 .jc-dmn-r.r2, .proto-root .jc-dmn.v-r3 .jc-dmn-r.r3 {
position: relative; background: var(--tone-soft); color: var(--ink-2);
}
.proto-root .jc-dmn.v-r1 .jc-dmn-r.r1::before, .proto-root .jc-dmn.v-r2 .jc-dmn-r.r2::before, .proto-root .jc-dmn.v-r3 .jc-dmn-r.r3::before {
content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 4px; background: var(--tone);
}
.proto-root .jc-dmn.v-r1 .jc-dmn-r.r1 .jc-dmn-c, .proto-root .jc-dmn.v-r2 .jc-dmn-r.r2 .jc-dmn-c, .proto-root .jc-dmn.v-r3 .jc-dmn-r.r3 .jc-dmn-c { color: var(--ink); }
.proto-root .jc-dmn.v-r1 .jc-dmn-r.r1 .jc-dmn-i, .proto-root .jc-dmn.v-r2 .jc-dmn-r.r2 .jc-dmn-i, .proto-root .jc-dmn.v-r3 .jc-dmn-r.r3 .jc-dmn-i { color: var(--tone); }
.proto-root .jc-dmn.v-r1 .jc-dmn-r.r1 .jc-dmn-c b, .proto-root .jc-dmn.v-r2 .jc-dmn-r.r2 .jc-dmn-c b, .proto-root .jc-dmn.v-r3 .jc-dmn-r.r3 .jc-dmn-c b { color: var(--tone); font-weight: 800; }
.proto-root .jc-dmn.v-r1 .jc-dmn-r.r1 .jc-dmn-t, .proto-root .jc-dmn.v-r2 .jc-dmn-r.r2 .jc-dmn-t, .proto-root .jc-dmn.v-r3 .jc-dmn-r.r3 .jc-dmn-t { color: var(--ink); }
.proto-root .jc-dmn.v-r1 .jc-dmn-r.r1 .jc-dmn-t code, .proto-root .jc-dmn.v-r2 .jc-dmn-r.r2 .jc-dmn-t code, .proto-root .jc-dmn.v-r3 .jc-dmn-r.r3 .jc-dmn-t code {
color: #fff; background: var(--tone); border-color: var(--tone);
}
/* 判定那一列:两句话都写在模版里,哪一句现身**由壳上那个 v-rN 决定** ——
模版仍然不许算,而「命中 / 未求值」这层判断留在样式表。
一条都没命中(算不出去向)时壳上没有 v-rN,两句都不出:不冒充判过。 */
.proto-root .jc-dmn .jc-dmn-v > .y, .proto-root .jc-dmn .jc-dmn-v > .n { display: none; }
.proto-root .jc-dmn.v-r1 .r1 .jc-dmn-v > .y, .proto-root .jc-dmn.v-r2 .r2 .jc-dmn-v > .y, .proto-root .jc-dmn.v-r3 .r3 .jc-dmn-v > .y {
display: inline-flex; align-items: center; gap: 5px; font-weight: 800; color: var(--tone);
}
.proto-root .jc-dmn.v-r1 .r2 .jc-dmn-v > .n, .proto-root .jc-dmn.v-r1 .r3 .jc-dmn-v > .n,
.proto-root .jc-dmn.v-r2 .r1 .jc-dmn-v > .n, .proto-root .jc-dmn.v-r2 .r3 .jc-dmn-v > .n,
.proto-root .jc-dmn.v-r3 .r1 .jc-dmn-v > .n, .proto-root .jc-dmn.v-r3 .r2 .jc-dmn-v > .n { display: inline; color: var(--ink-4); }
.proto-root .jc-verdict.v-r1 { --tone: var(--warn-accent); --tone-soft: rgba(255,136,0,.14); --tone-line: #ffd9a8; }
.proto-root .jc-verdict.v-r2 { --tone: var(--brand); --tone-soft: rgba(51,112,255,.13); --tone-line: var(--brand-soft); }
.proto-root .jc-verdict.v-r3 { --tone: var(--ok); --tone-soft: rgba(39,135,23,.13); --tone-line: #bfedb4; }
/* 圆标里放图标不放首字母 —— 首字母得算,而画一个算错的缩写不如画一个说得清类别的图标 */
.proto-root .jc-logo svg, .proto-root .jc-face svg, .proto-root .jc-rp-ava svg { width: 17px; height: 17px; }
/* ⇩ ONE_CSS · 本件自有补丁:平移时随模版走(补在 BIND_CSS 之后,只补不改) */
/* ⇧ ONE_CSS 到此 */
</style>
</head>
<body>
<!-- ⇩⇩ 模版片段从此开始:平移进 views/report.ts 的就是这一段(样式与精灵由 doc() 工厂另拼) -->
<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>
<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"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><use href="#i-git-branch"/></svg></div>
<div class="jc-rp-tit">
<em>统一报价 · A2 替代选型 · 执行工单</em>
<h3>替代选型工作单 <span>RFQ MD14.1</span></h3>
</div>
<span class="jc-rp-wait">在办 · 待选型 2 颗 · 已定案 1 颗</span>
</header>
<div class="jc-rp-bd">
<!-- 正文区块从这里画。基调与组件抄仓内范本(A1/A2 report.ts、JCAI_PRD 原型),
名录见技能 references/block-catalog.md;设计上行 —— 业务要什么摆什么,
现有台账没有的列记进文尾平移指引的「建模欠账」。 -->
</div>
<footer class="jc-rp-foot">
<div class="jc-rp-sign">
<span class="jc-rp-ava"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><use href="#i-user"/></svg></span>
<div><b>冯兵</b><span>AI 拟稿,你审定 —— 落款说明按件改写</span></div>
</div>
<span class="jc-done-tag"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><use href="#i-git-branch"/></svg>统一报价 · A2 替代选型 · 执行工单</span>
</footer>
</article>
</div></div></div>
<!-- ⇧⇧ 模版片段到此为止 -->
<!-- 演示脚本写在这里(可选):开编辑、改数重算、切状态。平移时整段剥掉。 -->
<!-- ═══ 平移指引(交付必带;照 docs/HTML报告生成规范.md §7 平移法)═══
① 取「模版片段」两枚边界注释之间的标记进 views/report.ts;样式与精灵改由 doc() 工厂拼
(PRD_CSS/BIND_CSS 用活动自己的 views/ 那份,ONE_CSS 段随本件带走,补在最后)
② 剥掉所有 script 标签与写死的示例数据;逐行数据换 data-rows 行模版,取值换 data-slot,
可改格换 data-col + data-edit(语义见规范 §4/§6;本件已预埋的钩子逐个核对)
③ 建模欠账(按件填写):本件摆了、现有台账还没有的列/呈现口径,逐条点名 ——
该加哪张表哪一列、哪些列该标 required、存储口径要不要改成人读形态
④ 绑定键名逐字核对目标活动 manifest.ts 的 declarations.ledger(列名照抄,不照记忆写)
═══ -->
</body>
</html>
@@ -0,0 +1,669 @@
<!doctype html>
<!--
JCAI one-pager · 目标件骨架(html-one-pager 技能产出起点)
· 浏览器直接打开;设计定稿后按 docs/HTML报告生成规范.md §7 平移进活动包
· 照 PRD 方法论这是「带脚本演示页」:演示脚本集中写在文尾一个 script 标签里
(不写内联事件),平移时整段剥掉;初始画面不靠脚本也要完整
-->
<html lang="zh-CN" class="editable">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>替代选型验收报告 · JCAI one-pager 目标件</title>
<style>
/* ⇩ standalone 包装层:真件跑在工作区 iframe 里,这几行由宿主环境承担。平移时丢弃 */
html, body { margin: 0; padding: 0; }
/* ⇧ 丢弃到此 */
/* ============================================================================
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; }
/* ① 空格子自己说话 —— 值真的空才轮到这三条(基座 data-blank)。
* 占位三级(2026-08-03 补齐,用户:「目标价是建议」):必填红 / 建议橘 / 选填灰,
* 档位跟台账声明走(required → req,expect → sug,其余 opt),颜色与 A2 同一套。 */
/* 胶囊与同一行的数字坐同一条线(2026-08-03 用户:「没有和数字对齐」)——
* 裸 inline-flex 的胶囊按 baseline 挂:盒子(10px 字 + 上下 3px 内距 + 边框 = 18px)比
* 字的基线深好几像素,视觉上整枚往下坠。治法:空格子自己撑起文字那份盒高(12px×1.5),
* 胶囊在里面垂直居中 —— 中线对中线,行高也不再忽高忽低。
* :not(.on) 不能省(2026-08-03 用户:「编辑态太拥挤」):这条比「编辑态藏起只读份」
* 的那条更特异,不圈住的话编辑态里胶囊会盖过 display:none 冒出来,54 个输入框旁边
* 各站一枚 —— 编辑态里格子该不该填由闸与红带说,胶囊只属于看的那份。 */
.proto-root .jc-tbl-in:not(.on) td.req > .jc-vcell:empty,
.proto-root .jc-tbl-in:not(.on) td.sug > .jc-vcell:empty,
.proto-root .jc-tbl-in:not(.on) td.opt > .jc-vcell:empty { display: flex; align-items: center; min-height: 18px; }
.proto-root .jc-tbl-in td.req > .jc-vcell:empty::after,
.proto-root .jc-tbl-in td.sug > .jc-vcell:empty::after,
.proto-root .jc-tbl-in td.opt > .jc-vcell:empty::after {
display: inline-flex; align-items: center; font-style: normal; font-size: 10px; font-weight: 700;
line-height: 1; padding: 3px 7px; border-radius: 5px; border: 1px dashed;
}
.proto-root .jc-tbl-in td.req > .jc-vcell:empty::after { content: "必填"; color: var(--danger); background: var(--danger-soft); border-color: #f3b6b3; }
.proto-root .jc-tbl-in td.sug > .jc-vcell:empty::after { content: "建议"; color: #b45309; background: #fff7ed; border-color: #fcd9a8; }
.proto-root .jc-tbl-in td.opt > .jc-vcell:empty::after { content: "选填"; color: var(--ink-4); background: var(--soft); border-color: var(--line); }
/* 归档件里空格子就是「客户没给」,不再摆「你还能填」的胶囊 */
.proto-root .jc-tbl-in td.dimempty > .jc-vcell:empty::after { content: "—"; color: var(--ink-4); }
/* ② 缺必填的整行 —— 原型是 tr.miss,这里由 :has() 认 */
.proto-root .jc-tbl-in tbody tr:has(td.req > .jc-vcell:empty) td { background: #fffbfb; }
.proto-root .jc-tbl-in tbody tr:has(td.req > .jc-vcell:empty):hover td { background: #fff5f5; }
.proto-root .jc-tbl-in tbody tr:has(td.req > .jc-vcell:empty) td.jc-rn { color: var(--danger); font-weight: 800; position: relative; }
@media (min-width: 1001px) {
.proto-root .jc-tbl-in tbody tr:has(td.req > .jc-vcell:empty) td.jc-rn::after {
content: ""; position: absolute; left: 0; top: 4px; bottom: 4px; width: 3px; border-radius: 0 3px 3px 0; background: var(--danger);
}
}
/* 序号由 CSS 计数器排 —— 模版里没有循环变量,行是基座复制出来的 */
.proto-root .jc-tbl-in tbody { counter-reset: rn; }
.proto-root .jc-tbl-in td.jc-rn::before { counter-increment: rn; content: counter(rn); }
/* 窄屏原型给的是 ::before「第 」+ ::after「 条」;这里 ::before 已被计数器占用,
把两句合进 ::before,并把原型那句 ::after 收掉 —— 否则窄屏上写着「第 9 条 条」 */
@media (max-width: 1000px) {
.proto-root .jc-tbl-in td.jc-rn::before { content: "第 " counter(rn) " 条"; }
.proto-root .jc-tbl-in td.jc-rn::after { content: none; }
}
/* ④ 就地改:每个可改的格子渲了两份 —— 只读那份与输入那份,这里二选一。
**默认锁着**:开关是那张表上的 on(外层按下「编辑」才加),
一屏两张表各管各的。判断在样式表里,不在填数里,件本身仍是同一份。 */
/* 默认锁着:只读那份在,输入孪生件收起来 —— **件首先是给人看的** */
.proto-root .jc-cell { display: none; }
/* 开闸是给那张表的容器加 「on」(外层按下「编辑」才加)—— 一屏两张表各管各的 */
.proto-root .jc-tbl-in.on td > .jc-vcell,
.proto-root .jc-tbl-in.on td > .jc-tbadge { display: none; }
.proto-root .jc-tbl-in.on td > .jc-cell { display: block; }
/* 类那一列平时只有一枚 24px 的徽,编辑态要放得下一个字 */
.proto-root .jc-tbl-in.on td.jc-ty, .proto-root .jc-tbl-in.on th:nth-child(2) { width: 56px; min-width: 56px; }
/*
* **数值列也左对齐**(2026-08-02 用户:「目标价的价格也要跟标题左对齐,右对齐太奇怪了」)。
*
* 右对齐是给**要竖着相加的那种数**用的(金额合计、数量小计)——这张表里的目标价
* 一行一个料、彼此不相加,右对齐只是让它和自己的表头分家:标题贴左、数贴右,
* 中间空一条,眼睛得来回找。等宽字体留着(位数对齐仍有用),对齐方式跟表头走。
*/
.proto-root .jc-vcell.num, .proto-root .jc-cell.num { text-align: left; }
.proto-root .jc-tbl-in.on td.num > .jc-cell, .proto-root .jc-tbl-in.on .jc-cell.num { text-align: left; }
/* 编辑态下必填空格子:红框 + 红 placeholder(原型的 req-empty 同款,只是由 :placeholder-shown 认) */
.proto-root .jc-tbl-in.on td.req > .jc-cell:placeholder-shown { border-color: #f3b6b3; background: #fffafa; }
.proto-root .jc-tbl-in.on tbody tr:has(td.req > .jc-cell:placeholder-shown) td { background: #fffbfb; }
/* ⑥ **铺满工作区**(2026-08-02 用户定:「报告不要做成单独的嵌入层」)。
原型那份是独立网页,jc-stage 给了 1160px 上限与 22/16/52px 的四周留白 ——
摆进工作区就成了「画布上浮着的一张小卡」:左右各空一条,底下还坠着 52px。
件在这里**就是这一屏的正文**,不是嵌在里面的另一个页面:去掉上限与横向留白,
外框那圈圆角边也一并卸掉(工作区自己已经是一张卡,两层框叠着像贴纸)。 */
.jc-stage { max-width: none; padding: 0 0 4px; }
.proto-root .jc-report { border: 0; border-radius: 0; box-shadow: none; }
/* ⑤ 那道闸 —— 默认「已锁定 + 编辑」,开了变成「取消 / 保存」。
整块只在这一屏可改时才出现(外层给根节点加 「editable」);
归档件里它一个字都不该有 —— 那时件是件,不是表单。 */
.proto-root .jc-editgate { display: none; }
html.editable .proto-root .jc-editgate {
display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
margin-bottom: 10px; padding: 8px 12px; border-radius: 9px;
background: var(--soft); border: 1px solid var(--line-soft);
font-size: 11.5px; color: var(--ink-3);
}
.proto-root .jc-editgate-t { display: inline-flex; align-items: center; gap: 6px; flex: 1 1 auto; min-width: 0; }
.proto-root .jc-editgate-t b { color: var(--ink-2); font-weight: 700; }
.proto-root .jc-egbtn {
flex: none; display: inline-flex; align-items: center; gap: 4px; cursor: pointer;
border: 1px solid var(--line); border-radius: 7px; padding: 5px 11px;
font: inherit; font-size: 11.5px; font-weight: 700; color: var(--ink-2); background: #fff;
transition: border-color var(--t-fast), color var(--t-fast), background var(--t-fast);
}
.proto-root .jc-egbtn:hover { border-color: var(--brand); color: var(--brand); }
.proto-root .jc-egbtn.save { color: #fff; background: var(--brand); border-color: var(--brand); }
.proto-root .jc-egbtn.save:hover { background: var(--brand-deep); border-color: var(--brand-deep); }
/* 锁着的时候只有「编辑」;开了换成「取消 / 保存」—— 三个钮同时摆着,人不知道现在算哪一态 */
.proto-root .jc-editgate .jc-egbtn.ghost, .proto-root .jc-editgate .jc-egbtn.save { display: none; }
.proto-root .jc-editgate.on { background: var(--brand-wash); border-color: var(--brand-soft); color: var(--brand-deep); }
.proto-root .jc-editgate.on .jc-egbtn.open { display: none; }
.proto-root .jc-editgate.on .jc-egbtn.ghost, .proto-root .jc-editgate.on .jc-egbtn.save { display: inline-flex; }
.proto-root .jc-editgate.on .jc-editgate-t { visibility: hidden; }
/* 缺项红带 —— **只在这一屏可改时出现**,且一格不缺就不出(判断在样式表,不在填数里) */
.proto-root .jc-gapbar { display: none; }
html.editable .proto-root .jc-gapbar {
display: flex; align-items: flex-start; gap: 8px;
margin: 0 0 10px; padding: 9px 12px; border-radius: 9px;
background: #fff5f5; border: 1px solid #f3b6b3; color: var(--danger);
font-size: 12px; line-height: 1.65;
}
.proto-root .jc-gapbar svg { flex: 0 0 auto; margin-top: 2px; }
.proto-root .jc-gapbar b { font-weight: 800; }
.proto-root .jc-gapbar span { color: #9a3b36; }
/* 一格不缺 —— 那条带没有话可说 */
html.editable .proto-root .jc-gapbar.v-0 { display: none; }
/* 那条「怎么改」的提示只在开了闸之后说 —— 锁着的时候它是句废话 */
.proto-root .jc-editbar { display: none; }
.proto-root .jc-editgate.on + .jc-editbar { display: flex; }
/* ⑦ 增删行 —— 手改是兜 AI 的错那一手,而错不止「这一格填错了」:
「这一行根本不该在」与「漏了一行」是同一类错,只给改格子就没有出口。 */
/* 序号那一格一列两用:只读时是行号(计数器画的),编辑时是「撤掉这一行」的 × */
.proto-root .jc-rowdel { display: none; }
.proto-root .jc-tbl-in.on td.jc-rn::before { content: none; }
.proto-root .jc-tbl-in.on .jc-rowdel {
display: block; width: 20px; height: 20px; margin: 0 auto; cursor: pointer;
border: 1px solid var(--line); border-radius: 6px; padding: 0;
font: inherit; font-size: 13px; line-height: 1; color: var(--ink-3); background: #fff;
transition: border-color var(--t-fast), color var(--t-fast), background var(--t-fast);
}
.proto-root .jc-tbl-in.on .jc-rowdel:hover { border-color: var(--danger); color: var(--danger); background: #fff6f6; }
/* 撤掉的行**不当场抹掉** —— 划着摆在那儿,人看得见自己删了哪一行,也才有得后悔 */
.proto-root .jc-tbl-in.on tr.jc-row-gone td { background: #fff6f6; opacity: .55; }
.proto-root .jc-tbl-in.on tr.jc-row-gone .jc-cell { text-decoration: line-through; color: var(--ink-3); }
.proto-root .jc-tbl-in.on tr.jc-row-gone .jc-rowdel { border-color: var(--danger); color: var(--danger); background: #fff; }
.proto-root .jc-tbl-in.on tr.jc-row-new td { background: #f2fbf7; }
/* 表尾那条「加一行」—— 与闸同款,只在这一屏可改且开了闸时现身 */
.proto-root .jc-addrow { display: none; }
html.editable .proto-root .jc-tbl-wrap:has(table.on) + .jc-addrow {
display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
margin: -2px 0 12px; padding: 7px 12px; border-radius: 9px;
background: var(--soft); border: 1px dashed var(--line);
}
.proto-root .jc-addrow em { font-style: normal; font-size: 11px; color: var(--ink-3); }
/* ③ 按值上色:基座把值消毒成 v-* 追加进 class */
/* 刻度条按缺口:missing_key_fields 是条数,0 = 完整(绿,原型的默认色),非 0 = 缺必填(红) */
.proto-root .jc-segmeter > i.v-1, .proto-root .jc-segmeter > i.v-2, .proto-root .jc-segmeter > i.v-3 { background: var(--danger); }
/* 刻度条按状况标:归档屏那张卡上,一格即一条料的类别 */
.proto-root .jc-segmeter > i.v-a { background: var(--ok); }
.proto-root .jc-segmeter > i.v-b { background: var(--brand); }
.proto-root .jc-segmeter > i.v-c { background: var(--warn-accent); }
.proto-root .jc-tbadge { background: var(--ink-4); }
.proto-root .jc-tbadge.v-a { background: var(--ok); }
.proto-root .jc-tbadge.v-b { background: var(--brand); }
.proto-root .jc-tbadge.v-c { background: var(--warn-accent); }
/* 分布条:三段按条数分宽。原型是 JS 写 inline style="flex:N",而模版不许算 ——
改由 data-class-of 把**条数本身**投影成 v-<N>,这里备一把梯子让 CSS 认。
条数 0 的那段整段不出:摆一条零宽的彩带不如不摆。 */
/* 原型那段是 grid + place-items:center;这里段里多了个 ::before 前缀,
grid 会把前缀和数字排成上下两行 —— 改成一行居中的 flex */
.proto-root .jc-distbar > i { flex: 24; min-width: 34px; display: inline-flex; align-items: center; justify-content: center; }
.proto-root .jc-distbar > i.v-0, .proto-root .jc-distbar > i:not([class*=" v-"]) { display: none; }
.proto-root .jc-distbar > i.v-1 { flex: 1 } .proto-root .jc-distbar > i.v-2 { flex: 2 }
.proto-root .jc-distbar > i.v-3 { flex: 3 } .proto-root .jc-distbar > i.v-4 { flex: 4 }
.proto-root .jc-distbar > i.v-5 { flex: 5 } .proto-root .jc-distbar > i.v-6 { flex: 6 }
.proto-root .jc-distbar > i.v-7 { flex: 7 } .proto-root .jc-distbar > i.v-8 { flex: 8 }
.proto-root .jc-distbar > i.v-9 { flex: 9 } .proto-root .jc-distbar > i.v-10 { flex: 10 }
.proto-root .jc-distbar > i.v-11 { flex: 11 } .proto-root .jc-distbar > i.v-12 { flex: 12 }
.proto-root .jc-distbar > i.v-13 { flex: 13 } .proto-root .jc-distbar > i.v-14 { flex: 14 }
.proto-root .jc-distbar > i.v-15 { flex: 15 } .proto-root .jc-distbar > i.v-16 { flex: 16 }
.proto-root .jc-distbar > i.v-17 { flex: 17 } .proto-root .jc-distbar > i.v-18 { flex: 18 }
.proto-root .jc-distbar > i.v-19 { flex: 19 } .proto-root .jc-distbar > i.v-20 { flex: 20 }
/* 段里那句「A · 5」的前缀 —— 类别是这一段的固有属性,不必从数据里取 */
.proto-root .jc-distbar > i.a::before { content: "A · "; }
.proto-root .jc-distbar > i.b::before { content: "B · "; }
.proto-root .jc-distbar > i.c::before { content: "C · "; }
/* 判定表命中行:基座把命中的那一条注入成 v-r1/r2/r3,壳上带 class,行自己认 */
.proto-root .jc-dmn.v-r1 { --tone: var(--warn-accent); --tone-soft: rgba(255,136,0,.11); }
.proto-root .jc-dmn.v-r2 { --tone: var(--brand); --tone-soft: rgba(51,112,255,.09); }
.proto-root .jc-dmn.v-r3 { --tone: var(--ok); --tone-soft: rgba(39,135,23,.10); }
.proto-root .jc-dmn.v-r1 .jc-dmn-r.r1, .proto-root .jc-dmn.v-r2 .jc-dmn-r.r2, .proto-root .jc-dmn.v-r3 .jc-dmn-r.r3 {
position: relative; background: var(--tone-soft); color: var(--ink-2);
}
.proto-root .jc-dmn.v-r1 .jc-dmn-r.r1::before, .proto-root .jc-dmn.v-r2 .jc-dmn-r.r2::before, .proto-root .jc-dmn.v-r3 .jc-dmn-r.r3::before {
content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 4px; background: var(--tone);
}
.proto-root .jc-dmn.v-r1 .jc-dmn-r.r1 .jc-dmn-c, .proto-root .jc-dmn.v-r2 .jc-dmn-r.r2 .jc-dmn-c, .proto-root .jc-dmn.v-r3 .jc-dmn-r.r3 .jc-dmn-c { color: var(--ink); }
.proto-root .jc-dmn.v-r1 .jc-dmn-r.r1 .jc-dmn-i, .proto-root .jc-dmn.v-r2 .jc-dmn-r.r2 .jc-dmn-i, .proto-root .jc-dmn.v-r3 .jc-dmn-r.r3 .jc-dmn-i { color: var(--tone); }
.proto-root .jc-dmn.v-r1 .jc-dmn-r.r1 .jc-dmn-c b, .proto-root .jc-dmn.v-r2 .jc-dmn-r.r2 .jc-dmn-c b, .proto-root .jc-dmn.v-r3 .jc-dmn-r.r3 .jc-dmn-c b { color: var(--tone); font-weight: 800; }
.proto-root .jc-dmn.v-r1 .jc-dmn-r.r1 .jc-dmn-t, .proto-root .jc-dmn.v-r2 .jc-dmn-r.r2 .jc-dmn-t, .proto-root .jc-dmn.v-r3 .jc-dmn-r.r3 .jc-dmn-t { color: var(--ink); }
.proto-root .jc-dmn.v-r1 .jc-dmn-r.r1 .jc-dmn-t code, .proto-root .jc-dmn.v-r2 .jc-dmn-r.r2 .jc-dmn-t code, .proto-root .jc-dmn.v-r3 .jc-dmn-r.r3 .jc-dmn-t code {
color: #fff; background: var(--tone); border-color: var(--tone);
}
/* 判定那一列:两句话都写在模版里,哪一句现身**由壳上那个 v-rN 决定** ——
模版仍然不许算,而「命中 / 未求值」这层判断留在样式表。
一条都没命中(算不出去向)时壳上没有 v-rN,两句都不出:不冒充判过。 */
.proto-root .jc-dmn .jc-dmn-v > .y, .proto-root .jc-dmn .jc-dmn-v > .n { display: none; }
.proto-root .jc-dmn.v-r1 .r1 .jc-dmn-v > .y, .proto-root .jc-dmn.v-r2 .r2 .jc-dmn-v > .y, .proto-root .jc-dmn.v-r3 .r3 .jc-dmn-v > .y {
display: inline-flex; align-items: center; gap: 5px; font-weight: 800; color: var(--tone);
}
.proto-root .jc-dmn.v-r1 .r2 .jc-dmn-v > .n, .proto-root .jc-dmn.v-r1 .r3 .jc-dmn-v > .n,
.proto-root .jc-dmn.v-r2 .r1 .jc-dmn-v > .n, .proto-root .jc-dmn.v-r2 .r3 .jc-dmn-v > .n,
.proto-root .jc-dmn.v-r3 .r1 .jc-dmn-v > .n, .proto-root .jc-dmn.v-r3 .r2 .jc-dmn-v > .n { display: inline; color: var(--ink-4); }
.proto-root .jc-verdict.v-r1 { --tone: var(--warn-accent); --tone-soft: rgba(255,136,0,.14); --tone-line: #ffd9a8; }
.proto-root .jc-verdict.v-r2 { --tone: var(--brand); --tone-soft: rgba(51,112,255,.13); --tone-line: var(--brand-soft); }
.proto-root .jc-verdict.v-r3 { --tone: var(--ok); --tone-soft: rgba(39,135,23,.13); --tone-line: #bfedb4; }
/* 圆标里放图标不放首字母 —— 首字母得算,而画一个算错的缩写不如画一个说得清类别的图标 */
.proto-root .jc-logo svg, .proto-root .jc-face svg, .proto-root .jc-rp-ava svg { width: 17px; height: 17px; }
/* ⇩ ONE_CSS · 本件自有补丁:平移时随模版走(补在 BIND_CSS 之后,只补不改) */
/* ⇧ ONE_CSS 到此 */
</style>
</head>
<body>
<!-- ⇩⇩ 模版片段从此开始:平移进 views/report.ts 的就是这一段(样式与精灵由 doc() 工厂另拼) -->
<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>
<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"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><use href="#i-shield-check"/></svg></div>
<div class="jc-rp-tit">
<em>统一报价 · A2 替代选型 · 验收报告</em>
<h3>替代选型验收报告 <span>RFQ MD14.1</span></h3>
</div>
<span class="jc-rp-wait">待放行 · 清单 5 条已转正</span>
</header>
<div class="jc-rp-bd">
<!-- 正文区块从这里画。基调与组件抄仓内范本(A1/A2 report.ts、JCAI_PRD 原型),
名录见技能 references/block-catalog.md;设计上行 —— 业务要什么摆什么,
现有台账没有的列记进文尾平移指引的「建模欠账」。 -->
</div>
<footer class="jc-rp-foot">
<div class="jc-rp-sign">
<span class="jc-rp-ava"><svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><use href="#i-user"/></svg></span>
<div><b>冯兵</b><span>AI 拟稿,你审定 —— 落款说明按件改写</span></div>
</div>
<span class="jc-done-tag"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><use href="#i-git-branch"/></svg>统一报价 · A2 替代选型 · 验收报告</span>
</footer>
</article>
</div></div></div>
<!-- ⇧⇧ 模版片段到此为止 -->
<!-- 演示脚本写在这里(可选):开编辑、改数重算、切状态。平移时整段剥掉。 -->
<!-- ═══ 平移指引(交付必带;照 docs/HTML报告生成规范.md §7 平移法)═══
① 取「模版片段」两枚边界注释之间的标记进 views/report.ts;样式与精灵改由 doc() 工厂拼
(PRD_CSS/BIND_CSS 用活动自己的 views/ 那份,ONE_CSS 段随本件带走,补在最后)
② 剥掉所有 script 标签与写死的示例数据;逐行数据换 data-rows 行模版,取值换 data-slot,
可改格换 data-col + data-edit(语义见规范 §4/§6;本件已预埋的钩子逐个核对)
③ 建模欠账(按件填写):本件摆了、现有台账还没有的列/呈现口径,逐条点名 ——
该加哪张表哪一列、哪些列该标 required、存储口径要不要改成人读形态
④ 绑定键名逐字核对目标活动 manifest.ts 的 declarations.ledger(列名照抄,不照记忆写)
═══ -->
</body>
</html>
+65 -2
View File
@@ -22,8 +22,12 @@
三条硬规矩:
1. **一单一模版贯穿始终**:同一个局面,在办与归档用同一张;局面变了换张
(A1 三张:受理需求 → 料号分类 → 受理记录),不在一张里堆条件分支。
1. **每份件两态 1:1,不同工作各有各的件**(2026-08-02/03 用户两次校准):
「1:1」的落点是**每份件与它的办结快照**(在办可编辑 ↔ 回执收下那一刻存
`finalHtml`,成果页静态重放,数据/内容/布局一个字不差)—— 不是全程只许一张。
作业的件(如 A2 选型 One Pager)与收口的件(整合清单)是两件工作两份件:
「清单是所有选型完成后收口才整合的东西」。别再把两态统一误执行成一张到底
(踩过:验收工单打开还是选型页,用户当场指出)。
2. **剧本每步的 `view` 定推哪张**,写清单时定死,不归模型临场挑;放行那一步的
`view` 必须指向验收目标件 —— 最后摆上去的那张就是归档件。
3. **数据变了基座重渲**(repush):人在件上保存、在卡上回填,落草稿层后服务端拿
@@ -180,6 +184,14 @@ A1 两份报告是 1:1 平移 PRD 目标件:样式表整段搬(`prd-style.ts`
## 8. 版式纪律
- **空占位三级统一**(2026-08-02 用户定,全局口径):**必填(红)· 建议填(橘)·
选填(灰)** —— 与声明的 `required` / `expect` / 其余一一对应,胶囊文案就写这三个词。
归档静态态它们如实呈现「没有」。
- **选择列直接选**(如 A2 替代等级):三枚自绘 radio(`input[type=radio].jc-cell[data-col]`,
radio 盖满选项格可点、`:has(:checked)` 高亮),**不写 checked** —— 当前值由
`data-class-of` 投影 v-* 描虚线;基座对未选中的 radio 整列不交(「没点」=「不改」)。
收值按「input 的祖父」归行:一张卡里所有输入格的祖父必须是同一个元素。
- **铺满工作区**:去掉外层宽度上限与横向留白、卸掉外框圆角边 —— 件就是这一屏的正文,
不是嵌在里面的另一个页面(`jc-stage { max-width:none }`)。
- 必填/选填胶囊只在**在办态**摆;归档件一律 `dimempty`(那时空格的意思不再是
@@ -187,6 +199,51 @@ A1 两份报告是 1:1 平移 PRD 目标件:样式表整段搬(`prd-style.ts`
- 别摆恒常说「没有」的横幅(「本轮不报 0 行:—」),别把同一个数摆两处
(人得停下来对一遍两处一不一样);已在别处说过的结论不再复述一遍。
- 标记里不写 `**` 星号这类记号 —— 卡面与件都是纯文本/纯 HTML,记号不会变粗体。
- **概念图形是报告的第一语言**(A2 迭代周用户反复要「图形占比大一点」):
芯片小像、流光走线、引脚对照、漏斗 —— 内联线稿 SVG(stroke currentColor,
配色走样式表)把「这是件什么事」画出来;数据格是第二语言。一份让人愿意看的
报告先立视觉隐喻,再摆数。
- **收值三铁律**(件上就地改的结构约束,违者行互相覆盖或值丢失):
① 同一行所有输入格的**祖父必须是同一个元素**(collect 按祖父归行);
② 主键格必挂 `data-col`(行的钥匙,可以是隐藏格);
③ radio 不写 `checked`,「没点」=「不改」(当前值靠 `data-class-of` 投影描样式)。
## 8.5 一页汇报(slide 语言)—— 强交互全走纯 CSS
用户对 A2 选型件的要求(2026-08-02):「不够华丽,要强交互的汇报形式,
直接拿去当一页 PPT 的程度」。沙箱零脚本不变 —— **「强交互」全部由样式表承担**,
数据仍只经 `data-slot` / `data-class-of` 投影,动效是呈现,不是判断。
参考实现:`activities/quote-alternative/src/views/report.ts` 的 `ALT_WORKSHEET`。
**构图**:主视觉(hero)+ 编号小节(slide part)推进。
- **hero 深空底**:`radial-gradient` 两团光 + `linear-gradient` 深空 +
`::before` 电路点阵(radial-gradient 1px 点)+ `::after` 顶部一道光。
hero 内配色自成一组(亮字/暗丸/信标),别把 PRD 亮色变量直接用在深底上。
- **slide 小节头**(`jc-sp`):`01/02/…` 编号丸 + 题 + 一句注 + 延伸线 ——
编号是版面语言,不是数据。
- **逐段入场**(`jc-rise` + `.d1…d6` 延迟):`opacity/transform` 动画,
**全部包在 `@media (prefers-reduced-motion: no-preference)` 里**(连 `@keyframes`
一起包 —— 引用未定义的动画名等于没有动画,减弱设置下自然全静)。
- **流光走线**:两条同 path 的 SVG(`wire` 底线 + `pulse` 虚线),
`stroke-dasharray` + `stroke-dashoffset` 动画 + `drop-shadow` 发光。
- **悬停揭示**:卡片 hover 浮起(`transform + box-shadow` 过渡);
等级卡的口径段 `max-height:0 → hover/focus-within 展开`,加 `tabindex="0"`
让键盘也进得去;**触屏常开**(`@media (hover: none)` 里展开、藏掉「悬停展开」hint)。
- **空节自折**:整节只在有数据时出现 —— 包一层 wrapper,
`:not(:has(.行.v-xxx)) { display:none }`(如 nowwrap/donewrap)。
没有 NOW 行时「当前这一颗」整节(含小节头)不出,不留空壳。
**三条工程账**(都踩过):
1. **媒体查询要排在被它覆盖的基础规则之后** —— 同权重下后定义胜。
竖排断点里写了 `.jc-trace{display:none}`,而 `.jc-trace{display:flex}` 基础规则
定义在媒体块之后,于是竖排下走线照显:横着一根流光指向右边的空气。
2. **动效只动 `transform`/`opacity`/`box-shadow`**,别动布局属性;
`jc-rise` 会在每次 srcdoc 重渲后重播(保存一次播一次)—— 半秒内是 slide 感,
长了就是折磨,入场动画别超过 0.6s。
3. 队列丸/信标这类**状态灯**照旧走 `data-class-of`(`v-now` 信标呼吸、`v-done` 绿、
`v-wait` 暗)—— 「哪颗在办」是数据说的,动画只是把它说响一点。
## 9. 自测与浏览器走查
@@ -208,6 +265,9 @@ A1 两份报告是 1:1 平移 PRD 目标件:样式表整段搬(`prd-style.ts`
|---|---|---|
| 点「编辑」毫无反应,一切选择器都对 | `instanceof Element` 跨 realm 恒 false;或监听挂在 about:blank 上 | 认能力不认血统;幂等重挂(§3) |
| 保存时件「闪黑」一下 | srcDoc 换文档整页重载 | 就地换 body.innerHTML(§3) |
| 就地换了还是「闪一下」(2026-08-03 再报) | innerHTML 仍是**整树重建**:47KB 样式在 body 再解析一遍(head 那份还在,一文档两份)、SVG 全重建、CSS 动画回第 0 帧重起 | 第二层:逐节点 morph(`ViewContainer.replaceInPlace`)—— 一样的节点不碰,样式归位 head 去重,涟漪只落在真改了的格子上;任何一步栽了退回整树重建(闪但不错) |
| **保存后旧值还摆着一会儿**(真正让人以为没生效的那个) | 保存要跑个来回(POST 落草稿 → 失效 → 重取详情 → 新件推回),而闸在按下那一刻就关了 —— 这段时间屏上是只读那份的**旧值** | 按下保存**先把新值写进只读孪生件**再关闸(`mirrorTwins`),回声到达时 morph 一比已经一样;只镜像格子自己的值,红带/分布条/去向等基座算的数一律等回声 |
| 镜像/取值找不到孪生件而无声失效 | 渲染那一遍把 `data-slot` **连同槽位一起消费掉**,活 DOM 里两份都没有这个属性 | 认**位置**不认属性:`twinInputs` 把 input 紧接着只读那份写出,`previousElementSibling` 就是孪生件;**验证要在真单的 DOM 上做** —— 合成 fixture 里两份都写了 `data-slot`,照样绿 |
| 人保存了,一格都没落库 | 保存借道当下那张卡的 blocks | 按 `data-edit` 地址收,授权=台账声明(§2) |
| 改了类标,分布条/去向不动 | 汇总数是自报的没人重算;或没 repush | 分类列标 `tally` 让基座数;保存后必 repush(§1) |
| 纯色块整条灰着像没算出来 | `data-class-of` 曾以 `data-slot` 为前提 | 已修;自测钉住 |
@@ -216,3 +276,6 @@ A1 两份报告是 1:1 平移 PRD 目标件:样式表整段搬(`prd-style.ts`
| 报告与卡说的判据不一致 | 两处各算各的 | 全部读基座读数,同源(§2) |
| 归档件上还有编辑闸/胶囊 | 闸没锁在 `editable` 根类下;胶囊没分态 | §6-1、§8 |
| 版面「像又不像」目标件 | 用拼装件照着做而不是搬 | §7;截图比对 |
| 人保存后一行的值被另一行盖掉 | **主键格没挂 `data-col`** —— 收上去的行没有钥匙,服务端按「全表一致列」补键,多行对到同一行 | 主键列必挂 `data-col`(它不是编辑面,是行的钥匙);e2e 走 API 自带主键测不出,只有件上真按保存才现形(A2 走查逮过) |
| 逐行卡片克隆成套娃(第二行长进第一行里) | 渲染器按**第一个同名闭合标签**截行内容 —— `<div data-rows>` 的行模版里嵌了别的 `<div>`,行在半截被断开 | **行容器的标签名不得在行模版内部再出现**:容器换 `<section>`/`<tbody>` 这类行内不会嵌的标签(A2 选型档案卡逮过) |
| 空格胶囊比同行的数字**往下坠几像素** | 裸 inline-flex 胶囊按 baseline 挂:盒(字 + 内距 + 边框)比字的基线深,行高也随之忽高忽低 | 空格子(`:empty`)转 flex + `align-items:center` + `min-height` 撑住文字那份盒高,胶囊中线对数字中线(A1 逮过,2026-08-03) |
+165
View File
@@ -787,3 +787,168 @@ JS 拼的 class 与 `style="flex:N"`→`data-class-of`)。规范 §10.3.1 已立
`customer.contact`(主数据)里,可老客户不重写主数据、本单草稿里根本没有那一行;
顶层裸键又与 `customer.profile.name` 撞名(后写的赢)。
**一张写着公司名的联系人卡比空着更坏**,所以跟 `customer_name` 同一笔写下。
## 算出来的口只在前端算数(2026-08-02,A2 e2e 逼出来的,已销)
A2 第一轮端到端:七拍零被拒、去向已算出 `to-sourcing`、验收 pass ——
死在最后一步:「放行未确认分支结论:选择组 alt-route 未选出口」,台账已合并、单落 `blocked`。
查下来是 2026-08-01 那次「routedPort 那一路 selectedPorts 恒空」的修法**只修了一半**:
补的是**前端**(chat-bubbles 回传时把算出的口塞进 `selectedPorts`)与
**服务端返回值**(human-tool-service 的 outcome)。而放行落地读的是第三样东西 ——
**人事件载荷**(`publishHumanEvent({ payload: { ...reply } })`,原始回传),
那一份谁都没动。于是浏览器活着(它自带口)、返回值好看(没人读它),
换个客户端(脚本、API 调用)就死在落地,而服务端注释还写着
「人回传里带不带都不作数」—— **注释描述的行为在引擎读的那条路上从来不成立**。
修法:`replyWorkOrder` 在发事件**之前**算好有效口(routedPort 优先、算不出才认人选),
事件载荷与返回值用同一份。**服务端说了算的事,不靠每个客户端自觉** ——
前端那段照旧发,发什么都会被有效口盖掉。
顺带两条实测账(代答脚本要知道的):
- 执行卡 `fields` 缺席时基座兜底加一枚**必填**「结果」回执字段(`fillCard`),
交卷载荷要带 `values.result`,否则「必填字段未回填:结果」;
- A3 的提交闸第一次真拦人:单飞归一出两颗料、脚本只填一颗,交卷被点名
「还有 1 条必填没填 —— SRC-…/XC6206P332MR 缺底价」—— 与件上红带、放行门 L1
同一个数同一套算法,拦的位置和话术都对。
## 2026-08-02 · A2 判稳该 decisionTask 化 —— 说明书拦不住「不查工具直接编」
单飞案例连续两轮走岔:S3 判稳一拍,模型**一次都没调** `crm.history-price-read`,
直接给替代料写「历史 43 天·行情稳 → 转 A」(数是编的),去向从 to-sourcing
岔成 to-pricing。说明书已写到头(「判稳的行情只有一条来路 —— 这一拍亲自查
alt_pn」「不许拿上游的数顶替」都在),流内两案例被这些话治住了,单飞治不住:
没有上游数可抄时它就自己编,措辞层面无解。
**该走的路**:判稳是死规则(≤90 天且稳 → A,否则 → B)+ 机器可得的入参
(工具出参)—— 「能算的不问 LLM」,这一步本该像 A1 的 S5 那样交给引擎:
① 判稳规则进 DMN 表(`ALT_DMN` 现在是空的);② S3 改 decisionTask 或
第一方工具出参直接带 `type_tag`/`basis`(模型只照抄,抄错有对号检查);
③ 收步判据加「凭据齐」(原 C 行 `history_days` 非空 —— 需要 `requiredWhen`
或「非空行数」读数承载)。
**现状兜底没断**:验收页 `editableAtRelease` 含 `type_tag`,人改标去向即变
(routedPort 读 tally 现算)—— AI 第一稿错,人签字前看得见改得动。
## 2026-08-02 · 入场哨兵读自报列 —— A2/A3 也有同一条缝
A1 踩实的病(用户实测单 584655b6,humanCalls 4/4 耗尽 exhausted):`s_parsed` 读
模型自报的 `line_total`,模型把 record 三行在 S1 拍写完、没记这个数,S2 空手收步 ——
门永不开,引擎报「依赖断点」,提示词「断点请调人」把模型推去发**补全卡**绕路,
两张多余的卡烧光人环预算。已修:s_parsed 改读基座数的 `quote_intake_record_rows`
(A1@46.0.0),该形态复跑 released、被拒 0、断点 0、补全卡 0。
**同一条缝还在 A2/A3 的入场哨兵里**(尚未咬人,记账待修):
- A2 `s_normalized` 读自报 `alt_line_total`、`s_filled` 读自报 `filled_total`;
- A3 对应哨兵同款。
S1 的 guide 都教了「第一件事写这个数」,所以节奏对时不踩;但 A1 证明了
节奏是模型抖出来的,不是承诺。照 A1 的方子换成 `_rows` 类基座读数即可
(s_filled 这种「人环收回」型要另想承载 —— filled_total 是「答复处理过」的记账,
基座数不出来,或可读 DONE 计数 `_sel_stage_done`)。
## 2026-08-02 · 补全卡退役的扫尾
行为已全局退役(归一层 ask→零输入执行工单;引擎直发拒;提示词/规范/三活动清理)。
留着的旧物,后续轮清:
- 契约类型 `ChatCard` 的 elicitation 分支与 `checkChatCard`/`checkChatCardReply`
的对应校验(历史卡回放还要认,删除走三步弃用);
- 前端补全卡渲染(历史对话里旧卡要能显示);
- `process.start` 起单缺 required 顶层键时发补全卡的老路 —— 三活动 required
全空后实际不触发,彻底移除时一并清。
## 2026-08-02 · A2 已落的两条全局口径,A1/A3 待跟进
- **一张模版贯穿到底**(用户:「待处理的 HTML 报告完成后直接转变为静态的工作成果」):
A2 已并成一张(alt-roster 退役);A1(四张按局面换)与 A3(worksheet/roster 两张)
仍是旧制,按 A2 的样子归一。
- **空占位三级统一**(必填红/建议填橘/选填灰):A2 已上;~~A1~~(2026-08-03 已上:
品类/制造商/目标价按声明的 expect 标橘「建议」,quote-intake@48.0.0,用户点的是目标价)/
A3 的占位词还是旧两级(待填红/选填灰),跟着声明的 required/expect 换新三级。
## 2026-08-03 · A4 补齐时发现、没动的两处共享面
- **放行门拒回仍会指向两扇打不开的门**:`_rows > 0` 但某个 `tally` 列一格没填时,
拒回只给「改数 / 改行」两条路(而两者都对),模型原样重提到熔断。
基座那条「`_rows = 0` → 直说那一步还没办」只覆盖了「一行都没写」,
没覆盖「行写了但分类/处置列没填」。**拒回的话是下一拍的行动指南**(已立纪律),
该按「哪一列还空着」点名。A4 靠独立成项 `S3_gate` 绕过去了,病根还在。
- **单飞造号会借用正式表里别单的单号**(第三次现形:A2 借走 MD14.1、A4 借走 RFQ-2026-0777)。
A2/A3/A4 三处都只靠说明书措辞防守(「只从本单来件取材」「必须 PRC- 开头」)。
**措辞治不了的结构治**(设计律第六条):造号该由基座给(同组主键 + 序号,
与 `FieldDeclaration.auto` 同一条路),不该让模型自己编。
~~**决策卡没有幂等兜底**~~(2026-08-03 已修:`resendVerdict` 抽成纯函数,
执行工单与决策卡同一把尺,头两次无事发生、第三次起算被拒,6 测钉死)。
## 2026-08-03 · A6 补齐时发现、没动的三处基座欠账
- **`listen` 动作在引擎里恒挂起**:`turn-runner` 对 `listen` 返回 `awaiting`,要外部
data/time 事件才唤得醒,而 desk 侧**没有任何路由能把 data 事件投给指定任务** ——
「等客户回音」这类等外部的活,活动只能落在执行工单上(人收到信、把回音填进件里),
时限那一侧靠 `declarations.listeners` 静态监听。A6 已按这条口径建模并有测钉死
(剧本不许教 `listen`)。要让 `listen` 真能用,得补一条「把外部事件投给某任务」的入口。
- **依赖断点那几拍,全局提示词只说「请调人或立监听」**,而当下可为为空时模型手上
只剩计划项的**中文名** —— 它会拿中文名当 `itemId`(实测连拒两拍)。活动侧只能靠
说明书兜(A6 把 itemId / ask / 选项值写死进说明书)。**治法在基座**:断点提示里
把可用项的英文 id 一并摆出来。
- **`mail.send` 至今没有活动敢用**:写类工具放行落地要过「入参 schema + `isInvocable`
+ 目标预校验」三道,而治理台白名单授权口仍挂在 §15⑤。A6 `tools: []`,
报价函靠执行单上那句话交给人发 —— 现状如实,但「谁真发了出去」系统并不知道。
## ~~2026-08-03 · A2 判稳偶发不查工具~~(当日已结构治)
**症状**:A2 单飞那条案例约十次一次 —— 替代料没查行情就判「稳」转 A,
去向从 `to-sourcing` 走成 `to-pricing`(实测 2026-08-03 全量回归一次)。
说明书已经写到头(「判稳的行情只有这一条来路 —— 这一拍亲自查 alt_pn」
「不许传原 PN」「不许拿上游带进来的数顶替」),**措辞治不了的结构治**(设计律第六条)。
**可行治法**(基座已具备一半):工具出参本来就入痕 ——
`turn-runner` 每次调完写一条 `kind: 'result'` / 标题 `${tool} 返回` / detail 带 output。
所以「这一单到底调没调 `crm.history-price-read`」是**基座数得出来的事实**,
不必让活动自报。补一类判据谓词(如 `tool_called_crm_history_price_read > 0`,
与 `域_对象_rows` 同族的读数),A2 就能立一条 L2:
**「转 A 的依据来自本单亲查的行情」** —— 机检只读基座数,这条纪律第一次真正覆盖到工具。
**当下兜底**(已在):验收页 `type_tag` 可改、去向跟着变;人在放行那一屏能纠。
**~~已落地~~**(2026-08-03 当日):基座新增读数 `tool_<工具名去符号>_calls`
(`toolCallReadings`,按留痕数成功返回的次数,声明过的工具一律先铺 0;
调失败不算查过 —— 调不通等于没拿到行情)。静态审与包内自测的「谓词绑得上」
名单同批扩到这一类。A2@4.14.0 立 L2:
`quote_alternative_list_type_tag_a = 0 or tool_crm_history_price_read_calls > 0`
——**写成析取**:一颗都没转 A 就无需查行情(全 B 那条路合法),
有 A 才必须有过这一次亲查。**「机检只读基座数」这条纪律第一次覆盖到工具**。
## 2026-08-03 · A2 常路案例会**跳过派给 FAE 的那一步**(未修,已定位)
**症状**:`a2` 案例①(两颗 C · 替代无历史)连续三轮 6 拍收口,**一张执行工单都没发**——
轨迹里模型自称「2 颗替代料都查过行情、都转成 B 类」:替代料是它自己填的,
FAE 从头到尾没被叫过,单子照样 released(去向还对)。案例②③ 正常派单(7 拍)。
**病根**:S2「派选型单」的 `doneWhen` 认的是**件上那几格填没填**
(`alt_pn/alt_brand/alt_grade` 由 requiredWhen 钉在 orig_type=C 行)——
而这几格模型自己也写得进去。它一写,收步条件当场成立,引擎就把这一步收了,
**人环整个消失**。这正是「凡触达世界的项一律不给 `doneWhen`」那条纪律的反面教材:
「把清单交给 FAE 选型」触达的是人,它的完成与否不该由数据形状推出来。
同时它也踩了 `no-fake-alt` 禁令(没把握的替代不该编一个顶上)。
**治法**(与 T_release 同款,未施工):S2 改 **人工激活 + 不给 doneWhen** ——
派单那一下由模型显式做,收步由人交回触发;并在剧本里写死「替代料三列一格都不许自己填」。
**验证口径**:`a2` 三案例都必须出现「件上保存 / 交卷执行卡」两行,6 拍收口即判失败。
**排除项**:不是 2026-08-03 那条工具留痕判据引起的 —— 撤掉判据(@4.15.0)后原样复现。
## 2026-08-03 · 件上保存偶发不落那一格(间歇,两处独立撞见)
**症状**:`a1` 案例把 `DAG2400L321504XZB01-R0` 的年用量存进件上(`POST /tasks/:id/table`),
返回 200,而紧接着的提交闸仍报「缺年用量 EAU」,连拦三次判失败;
同一条案例前后几轮都正常。A6 那一轮独立撞见**同一行同一列**的同款症状。
**已排除**:不是提交闸失效(拿单直接空手交卷实测吃 400,且点名缺哪一行哪一格);
不是那一轮的说明书改动(A1@49.x 只改措辞,保存走的是基座那条链)。
**下一步怎么查**:保存那一下服务端按「收件自己说的话」(`data-edit` 地址 + `data-col` 列名)
落草稿层,主键靠行内 `data-col` 的主键格给出 —— 怀疑点在**主键补齐**那一段
(A2 走查逮过一次「主键格没挂 data-col → 服务端按全表一致列补键 → 两行对到同一行」)。
复现要碰运气,建议下次带着「保存请求体 + 落库后草稿行」两份留痕对着看。
@@ -0,0 +1,780 @@
<!doctype html>
<!-- JCAI-WOS 产品汇报 · 讲解用 slide 件(浏览器全屏即放映;滚轮/PageDown 翻页;#s0-#s14 锚点直达) -->
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>JCAI-WOS · 产品设计与系统机制</title>
<style>
:root {
--brand: #3370ff; --deep: #245bdb; --violet: #7f3bf5;
--grad: linear-gradient(135deg, #3370ff, #7f3bf5);
--ink: #1f2329; --ink2: #41464e; --ink3: #646a73; --ink4: #8f959e;
--line: #dee0e3; --soft: #f8f9fb; --wash: #f0f4ff; --brandsoft: #e1eaff;
--ok: #278717; --oksoft: #e8f8e4; --warn: #ff8800; --warnsoft: #fff4e5;
--danger: #d83931; --dangersoft: #feeae9;
--mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
--night: #0b1020; --night2: #121a33;
}
* { box-sizing: border-box; }
html { scroll-snap-type: y proximity; }
html, body { margin: 0; padding: 0; }
body {
font-family: Inter, ui-sans-serif, system-ui, -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
color: var(--ink); background: #e9edf3; -webkit-font-smoothing: antialiased;
line-height: 1.6;
}
.slide {
min-height: 100vh; scroll-snap-align: start; position: relative;
display: flex; flex-direction: column; justify-content: center;
padding: 64px clamp(28px, 6vw, 96px) 72px; background: #fff; overflow: hidden;
}
.slide + .slide { border-top: 1px solid var(--line); }
.pg {
position: absolute; right: 26px; bottom: 20px; font-size: 12px; color: var(--ink4);
font-family: var(--mono); letter-spacing: .08em;
}
.kicker {
display: inline-flex; align-items: center; gap: 8px;
font-size: 13px; font-weight: 700; color: var(--brand); letter-spacing: .18em; margin-bottom: 10px;
}
.kicker i { width: 22px; height: 2px; background: var(--grad); border-radius: 2px; }
h1 { font-size: clamp(28px, 3.2vw, 42px); line-height: 1.25; margin: 0 0 10px; letter-spacing: .01em; }
h2.sub { font-size: clamp(14.5px, 1.4vw, 18px); font-weight: 500; color: var(--ink3); margin: 0 0 24px; max-width: 66em; }
h2.sub b { color: var(--ink); }
.dia { width: 100%; max-width: 1120px; margin: 4px auto 0; display: block; }
.note { max-width: 1120px; margin: 16px auto 0; font-size: 13.5px; color: var(--ink3); }
.note b { color: var(--ink2); }
.center { text-align: center; }
/* 深空封面 / 收尾 */
.hero { background: radial-gradient(1200px 700px at 70% -10%, #1d2a55 0%, var(--night2) 45%, var(--night) 100%); color: #eef2ff; }
.hero .kicker { color: #9db4ff; }
.hero h1 { font-size: clamp(38px, 5vw, 64px); }
.hero .sub { color: #b9c4e6; }
.hero .pg { color: #5a6690; }
.threeword { display: flex; gap: 14px; flex-wrap: wrap; margin-top: 34px; }
.threeword span {
padding: 12px 22px; border-radius: 999px; font-size: clamp(15px, 1.6vw, 19px); font-weight: 700;
border: 1px solid rgba(157, 180, 255, .35); background: rgba(51, 112, 255, .12);
}
.threeword span b { color: #9db4ff; font-weight: 800; }
.heroline { position: absolute; inset: 0; pointer-events: none; opacity: .5; }
.beacon { animation: pulse 2.8s ease-in-out infinite; transform-origin: center; }
@keyframes pulse { 0%,100% { opacity: .9 } 50% { opacity: .35 } }
@media (prefers-reduced-motion: reduce) { .beacon { animation: none } }
/* 解释卡 */
.cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; max-width: 1120px; margin: 18px auto 0; }
.cards.c2 { grid-template-columns: repeat(2, 1fr); }
.cards.c4 { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 980px) { .cards, .cards.c4 { grid-template-columns: 1fr 1fr; } }
.card { border: 1px solid var(--line); border-radius: 12px; padding: 14px 18px; background: #fff; }
.card.tint { background: var(--soft); }
.card h4 { margin: 0 0 7px; font-size: 14.5px; display: flex; align-items: center; gap: 8px; }
.card h4 em { font-style: normal; font-size: 11px; font-weight: 800; padding: 2px 8px; border-radius: 6px; background: var(--wash); color: var(--deep); letter-spacing: .04em; flex: none; }
.card h4 em.g { background: var(--oksoft); color: var(--ok); }
.card h4 em.o { background: var(--warnsoft); color: #b25f00; }
.card h4 em.r { background: var(--dangersoft); color: var(--danger); }
.card p { margin: 0; font-size: 13px; color: var(--ink3); line-height: 1.75; }
.card p b { color: var(--ink2); }
.card p code { font-family: var(--mono); font-size: 11.5px; background: var(--soft); border: 1px solid var(--line); border-radius: 5px; padding: 1px 5px; color: var(--ink2); }
/* 芯片 */
.chips { display: flex; gap: 10px; flex-wrap: wrap; margin: 14px 0 6px; }
.chip {
display: inline-flex; align-items: center; gap: 7px; padding: 7px 14px; border-radius: 9px;
font-size: 13.5px; font-weight: 700; border: 1px solid var(--line); background: var(--soft); color: var(--ink2);
}
.chip.b { border-color: var(--brandsoft); background: var(--wash); color: var(--deep); }
.chip.g { border-color: #bfedb4; background: var(--oksoft); color: var(--ok); }
.chip.o { border-color: #ffd9a8; background: var(--warnsoft); color: #b25f00; }
.duo { display: grid; grid-template-columns: 1fr 1fr; gap: 26px; max-width: 1120px; margin: 0 auto; }
@media (max-width: 900px) { .duo { grid-template-columns: 1fr; } }
/* 判据梯(S8) */
.gates { max-width: 1120px; margin: 10px auto 0; display: flex; flex-direction: column; gap: 8px; }
.gate { display: grid; grid-template-columns: 92px 1fr 300px; gap: 14px; align-items: center;
border: 1px solid var(--line); border-radius: 11px; padding: 10px 16px; background: #fff; }
.gate .lv { font-family: var(--mono); font-weight: 800; font-size: 13px; }
.gate .lv small { display: block; font-family: inherit; font-weight: 700; font-size: 11px; color: var(--ink4); }
.gate .what { font-size: 13.5px; color: var(--ink2); }
.gate .what b { color: var(--ink); }
.gate .how { font-size: 12px; color: var(--ink4); text-align: right; }
.gate.m { border-left: 4px solid var(--brand); }
.gate.d { border-left: 4px solid var(--warn); }
.gate.h { border-left: 4px solid var(--ok); }
/* 三态样卡 */
.tri { display: flex; gap: 10px; flex-wrap: wrap; max-width: 1120px; margin: 14px auto 0; }
.tri span { display: inline-flex; align-items: center; gap: 8px; padding: 8px 14px; border-radius: 9px; font-size: 12.5px; font-weight: 700; border: 1px solid; }
.tri .ok { color: var(--ok); background: var(--oksoft); border-color: #bfedb4; }
.tri .amb { color: #8a5a00; background: var(--warnsoft); border-color: #ffd9a8; border-left: 4px solid var(--warn); }
.tri .no { color: var(--danger); background: var(--dangersoft); border-color: #f3b6b3; }
/* 前后对比(S13) */
.delta { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; max-width: 1120px; margin: 14px auto 0; }
@media (max-width: 980px) { .delta { grid-template-columns: 1fr; } }
.dcard { border: 1px solid var(--line); border-radius: 14px; padding: 18px 20px; background: var(--soft); }
.dcard .row { display: flex; align-items: baseline; gap: 10px; row-gap: 4px; flex-wrap: wrap; font-family: var(--mono); }
.dcard .row s { color: var(--ink4); font-size: 14.5px; text-decoration-thickness: 2px; white-space: nowrap; }
.dcard .row b { font-size: 22px; color: var(--deep); white-space: nowrap; }
.dcard .row i { font-style: normal; color: var(--ink4); font-size: 14px; }
.dcard h4 { margin: 0 0 8px; font-size: 14.5px; }
.dcard p { margin: 8px 0 0; font-size: 12.5px; color: var(--ink3); line-height: 1.7; }
/* 证据面板 */
.stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; max-width: 1120px; margin: 8px auto 0; }
@media (max-width: 900px) { .stats { grid-template-columns: 1fr 1fr; } }
.stat { border: 1px solid rgba(157,180,255,.25); background: rgba(51,112,255,.08); border-radius: 14px; padding: 18px 20px; }
.stat b { display: block; font-size: clamp(26px, 2.8vw, 38px); line-height: 1.2; color: #fff; font-family: var(--mono); }
.stat span { font-size: 13px; color: #b9c4e6; }
.stat em { font-style: normal; color: #9db4ff; }
/* SVG 通用 */
svg text { font-family: inherit; }
.t { font-size: 14px; fill: var(--ink); font-weight: 700; }
.t2 { font-size: 12.5px; fill: var(--ink3); }
.t3 { font-size: 11.5px; fill: var(--ink4); }
.tb { font-size: 13px; fill: var(--deep); font-weight: 700; }
.tm { font-family: var(--mono); font-size: 11.5px; fill: var(--ink3); }
.tw { font-size: 13px; fill: #fff; font-weight: 700; }
.tr { font-size: 12px; fill: var(--danger); font-weight: 700; }
</style>
</head>
<body>
<!-- ═══ S0 封面 ═══ -->
<section id="s0" class="slide hero">
<svg class="heroline" viewBox="0 0 1440 800" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
<g fill="none" stroke="#33427a" stroke-width="1">
<path d="M-20 620 H 320 L 380 560 H 700"/><path d="M-20 690 H 240 L 300 750 H 620"/>
<path d="M760 80 H 1050 L 1110 140 H 1460"/><path d="M900 30 H 1180 L 1240 -30"/>
<path d="M1460 640 H 1160 L 1100 700 H 840"/>
</g>
<g fill="#3370ff">
<circle class="beacon" cx="320" cy="620" r="4"/><circle class="beacon" cx="700" cy="560" r="3" style="animation-delay:.9s"/>
<circle class="beacon" cx="1050" cy="80" r="4" style="animation-delay:.5s"/><circle class="beacon" cx="840" cy="700" r="3" style="animation-delay:1.4s"/>
</g>
</svg>
<div style="position:relative">
<span class="kicker"><i></i>JCAI-WOS · 产品汇报 · 2026-08</span>
<h1>企业级 AI Native<br>任务操作系统</h1>
<h2 class="sub" style="max-width:50em">把组织的工作定义为可编排的工作流;工作流由「活动」编排而成 ——
活动是完成一项任务的 Agent 机制,<b style="color:#fff">系统外开发、安装即用</b>。<br>
本篇讲三件事:产品怎么设计的 · 系统靠什么机制立住 · 已经跑出了什么。</h2>
<div class="threeword">
<span><b>确定</b>&#8201;交系统</span>
<span><b>临场</b>&#8201;交 AI</span>
<span><b>落地</b>&#8201;交人</span>
</div>
</div>
<span class="pg">JCAI-WOS</span>
</section>
<!-- ═══ S1 为什么做 ═══ -->
<section id="s1" class="slide">
<span class="kicker"><i></i>01 · 为什么做</span>
<h1>企业落 AI,现有三条路各断在一处</h1>
<h2 class="sub">不是没人试过让「系统 + 人 + AI」干活 —— 而是每条路都把三种成分<b>混在一个执行者身上</b>,断点因此结构性地无解。</h2>
<div class="cards">
<div class="card tint">
<h4><em>路线一</em>传统流程系统 / BPM</h4>
<p>把每一步写死在代码里。<b>断点:变更成本</b> —— 业务改一处,流程改一版,排期以月计;
长尾业务永远排不上,例外全靠线下走,系统里的流程和真实的干法渐行渐远。
它把「临场」也当成了「确定」来编。</p>
</div>
<div class="card tint">
<h4><em>路线二</em>人肉 + IM + 表格</h4>
<p>靠资深员工的手感把活串起来。<b>断点:不可见、不可度量</b> —— 单子在谁手里、卡在哪一步,
管理者看不见;判断标准在人脑子里,人一走经验清零。
它把「确定」也交给了「临场」。</p>
</div>
<div class="card tint">
<h4><em>路线三</em>裸大模型 / 通用 Agent</h4>
<p>能说会写,演示惊艳。<b>断点:不可控、无责任</b> —— 没有授权边界(什么都能碰),
没有责任交割(错了没人签过字),关键数字可能是编的。
企业敢让它聊天,不敢让它办事。它把「落地」也托付给了「临场」。</p>
</div>
</div>
<svg class="dia" viewBox="0 0 1120 170" style="margin-top:22px">
<defs><marker id="m0" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="#3370ff"/></marker></defs>
<g fill="none" stroke="#c9d6ff" stroke-width="1.8">
<path d="M186 30 C 186 80, 400 80, 520 96" marker-end="url(#m0)" stroke="#3370ff"/>
<path d="M560 30 V 88" marker-end="url(#m0)" stroke="#3370ff"/>
<path d="M934 30 C 934 80, 720 80, 600 96" marker-end="url(#m0)" stroke="#3370ff"/>
</g>
<g class="t2" text-anchor="middle">
<text x="186" y="20">确定性 · 从路线一继承</text>
<text x="560" y="20">临场力 · 从路线三继承</text>
<text x="934" y="20">责任制 · 从路线二继承</text>
</g>
<rect x="380" y="96" width="360" height="54" rx="27" fill="#f0f4ff" stroke="#3370ff" stroke-width="1.6"/>
<text x="560" y="122" text-anchor="middle" class="t" style="font-size:16px">JCAI-WOS:按成分拆开,各归其位</text>
<text x="560" y="141" text-anchor="middle" class="t3">确定交系统 · 临场交 AI · 落地交人 —— 边界由机制立死,不靠自觉</text>
</svg>
<span class="pg">01 / 14</span>
</section>
<!-- ═══ S2 核心命题:三分法 ═══ -->
<section id="s2" class="slide" style="background:var(--soft)">
<span class="kicker"><i></i>02 · 核心命题</span>
<h1>把一件工作拆开,答案就清楚了</h1>
<h2 class="sub">以一封客户询价邮件为例:同一单工作在三条泳道间流转,每一段都落在最擅长它的执行者手里 —— <b>谁该做什么,机制说了算</b>。</h2>
<svg class="dia" viewBox="0 0 1120 400">
<defs><marker id="m1" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="#3370ff"/></marker></defs>
<rect x="120" y="16" width="990" height="112" rx="12" fill="#f0f4ff"/>
<rect x="120" y="140" width="990" height="112" rx="12" fill="#f5f1ff"/>
<rect x="120" y="264" width="990" height="112" rx="12" fill="#eef8ea"/>
<g>
<text x="16" y="58" class="t">确定的部分</text><text x="16" y="80" class="t2">交系统</text>
<text x="16" y="182" class="t">临场的部分</text><text x="16" y="204" class="t2">交 AI</text>
<text x="16" y="306" class="t">落地的部分</text><text x="16" y="328" class="t2">交人</text>
</g>
<g class="t3">
<text x="1096" y="46" text-anchor="end">规则写成判定表 · 门与闸把关口</text>
<text x="1096" y="66" text-anchor="end">同样输入,必得同样结论</text>
<text x="1096" y="170" text-anchor="end">读懂来件 · 归一数据 · 编排步骤</text>
<text x="1096" y="190" text-anchor="end">每一步留痕,可回核</text>
<text x="1096" y="294" text-anchor="end">责任点与放行点</text>
<text x="1096" y="314" text-anchor="end">字签下去,世界才改变</text>
</g>
<g fill="none" stroke="#3370ff" stroke-width="2.2">
<path d="M198 196 H 300" marker-end="url(#m1)"/>
<path d="M400 176 C 430 120, 450 96, 486 76" marker-end="url(#m1)"/>
<path d="M600 76 C 640 120, 650 260, 690 300" marker-end="url(#m1)"/>
<path d="M806 320 H 890" marker-end="url(#m1)"/>
<path d="M994 300 C 1020 260, 1024 236, 1028 210" marker-end="url(#m1)"/>
</g>
<g>
<g><rect x="140" y="172" width="58" height="48" rx="9" fill="#fff" stroke="#c9d6ff"/><path d="M152 186 h34 v22 h-34 z M152 186 l17 12 17-12" fill="none" stroke="#3370ff" stroke-width="1.8"/><text x="169" y="238" text-anchor="middle" class="t3">来件</text></g>
<g><rect x="300" y="172" width="104" height="48" rx="9" fill="#fff" stroke="#c9d6ff"/><text x="352" y="192" text-anchor="middle" class="tb">AI 归一</text><text x="352" y="210" text-anchor="middle" class="t3">拆成受理清单</text></g>
<g><rect x="486" y="52" width="116" height="48" rx="9" fill="#fff" stroke="#c9d6ff"/><text x="544" y="72" text-anchor="middle" class="tb">判定表分级</text><text x="544" y="90" text-anchor="middle" class="t3">A / B / C 三分类</text></g>
<g><rect x="690" y="288" width="116" height="48" rx="9" fill="#fff" stroke="#bfe3b2"/><text x="748" y="308" text-anchor="middle" style="font-size:13px;font-weight:700" fill="#278717">人审定放行</text><text x="748" y="326" text-anchor="middle" class="t3">就地改 · 签字</text></g>
<g><rect x="890" y="288" width="104" height="48" rx="9" fill="#fff" stroke="#bfe3b2"/><text x="942" y="308" text-anchor="middle" style="font-size:13px;font-weight:700" fill="#278717">落账发出</text><text x="942" y="326" text-anchor="middle" class="t3">此刻才算数</text></g>
<g><rect x="1006" y="164" width="88" height="46" rx="9" fill="#fff" stroke="#c9d6ff"/><text x="1050" y="184" text-anchor="middle" class="tb">下游活动</text><text x="1050" y="201" text-anchor="middle" class="t3">按分类分流</text></g>
</g>
</svg>
<div class="cards">
<div class="card"><h4><em>确定</em>沉淀为资产</h4><p>三分类规则、完整度门槛、利润档位 —— 全部写成<b>判定表</b>与<b>判据</b>装进活动包。
从此这类判断不再依赖任何人的手感:换人、换模型,结论不变;要改口径,改表,一处生效。</p></div>
<div class="card"><h4><em>临场</em>收敛成编排</h4><p>俄文附件、中文正文、表格贴在邮件里 —— 来件形状千奇百怪,这正是大模型擅长的。
但 AI 在这里<b>只做归一与编排,不做裁断</b>:它算出的每个数都要过系统的门,过不了就得回去改。</p></div>
<div class="card"><h4><em>落地</em>绑定到责任</h4><p>建客户档案、审定受理需求、放行发出 —— 触达世界的动作全部落在<b>具名的人</b>身上。
谁签的字、签之前看到了什么、签下去发生了什么,台账留痕,事后可完整回放。</p></div>
</div>
<span class="pg">02 / 14</span>
</section>
<!-- ═══ S3 三面一体 ═══ -->
<section id="s3" class="slide">
<span class="kicker"><i></i>03 · 产品形态</span>
<h1>一套系统,三个面</h1>
<h2 class="sub">三种角色各有各的门面,底下是同一套台账 —— <b>定义工作的、看住工作的、干工作的,各看各的屏</b>。</h2>
<svg class="dia" viewBox="0 0 1120 380">
<defs><marker id="m2" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="#8f959e"/></marker></defs>
<g>
<rect x="30" y="50" width="320" height="240" rx="16" fill="#fff" stroke="#dee0e3"/>
<rect x="30" y="50" width="320" height="54" rx="16" fill="#f0f4ff"/>
<text x="54" y="84" class="t" style="font-size:16px">设计中心</text><text x="290" y="84" class="tm">/design</text>
<g class="t2"><text x="54" y="136">蓝图 · 触发器 · 流程包</text><text x="54" y="164">活动库 · 工具箱 · 数据域</text><text x="54" y="192">知识库 · 大模型 · 测试台</text><text x="54" y="220">(九区 —— 定义工作怎么做)</text></g>
<rect x="54" y="242" width="120" height="30" rx="15" fill="#e1eaff"/><text x="114" y="262" text-anchor="middle" class="tb">设计人员</text>
</g>
<g>
<rect x="400" y="50" width="320" height="240" rx="16" fill="#fff" stroke="#dee0e3"/>
<rect x="400" y="50" width="320" height="54" rx="16" fill="#eef8ea"/>
<text x="424" y="84" class="t" style="font-size:16px">运营中心</text><text x="668" y="84" class="tm">/ops</text>
<g class="t2"><text x="424" y="136">驾驶舱 · 工作流 · 任务</text><text x="424" y="164">员工 · 台账 · 系统</text><text x="424" y="192">(六区 —— 看住正在发生的)</text><text x="424" y="220">在途全景,卡点一眼可见</text></g>
<rect x="424" y="242" width="120" height="30" rx="15" fill="#e8f8e4"/><text x="484" y="262" text-anchor="middle" style="font-size:13px;font-weight:700" fill="#278717">管理者</text>
</g>
<g>
<rect x="770" y="50" width="320" height="240" rx="16" fill="#fff" stroke="#dee0e3"/>
<rect x="770" y="50" width="320" height="54" rx="16" fill="#f5f1ff"/>
<text x="794" y="84" class="t" style="font-size:16px">工作台</text><text x="1042" y="84" class="tm">/</text>
<g class="t2"><text x="794" y="136">对话区:AI 交代 + 工作卡</text><text x="794" y="164">工作区:件(报告即表单)</text><text x="794" y="192">待办 · 成果 · 沟通记录 · 消息</text><text x="794" y="220">(员工每天呆的地方)</text></g>
<rect x="794" y="242" width="120" height="30" rx="15" fill="#f0e9ff"/><text x="854" y="262" text-anchor="middle" style="font-size:13px;font-weight:700" fill="#7f3bf5">员工</text>
</g>
<g fill="none" stroke="#8f959e" stroke-width="1.8">
<path d="M350 170 H 396" marker-end="url(#m2)"/>
<path d="M720 170 H 766" marker-end="url(#m2)"/>
<path d="M930 296 C 930 352, 560 352, 560 296" marker-end="url(#m2)" stroke-dasharray="5 4"/>
</g>
<g class="t3">
<text x="373" y="156" text-anchor="middle">上线</text>
<text x="743" y="156" text-anchor="middle">派单</text>
<text x="745" y="344" text-anchor="middle">台账与指标实时回流</text>
</g>
</svg>
<div class="cards">
<div class="card"><h4><em>设计</em>装配式,不是开发式</h4><p>设计人员在治理台<b>装配</b>流程与活动:挑活动、连节点、配触发器、绑模型与工具 ——
不写代码。测试台里投一封信就能整流试跑,历次试跑存档可回放,改完当场验。</p></div>
<div class="card"><h4><em>运营</em>全程可观测</h4><p>每一单在哪个节点、AI 跑到第几步、等谁签字,驾驶舱一屏尽收;
配置变更、任务流转、台账写入全部实时推送 —— <b>不用刷新,不看快照</b>。</p></div>
<div class="card"><h4><em>员工</em>像带助理,不像用系统</h4><p>AI 把活干到只剩「该人拍板的那一下」再来找你:一轮一句交代进展,
该办的事变成一张卡,该看的件铺满工作区。<b>你面对的是一个汇报有条理的同事</b>,不是一堆待填表单。</p></div>
</div>
<span class="pg">03 / 14</span>
</section>
<!-- ═══ S4 三层抽象 + 版本治理 ═══ -->
<section id="s4" class="slide" style="background:var(--soft)">
<span class="kicker"><i></i>04 · 组织方式</span>
<h1>工作流 → 活动 → 单,三层各管各的</h1>
<h2 class="sub">工作流定<b>顺序</b>,活动定<b>做法</b>,单是<b>一次真实发生</b> —— 三层解耦,才谈得上各自演化、互不牵连。</h2>
<svg class="dia" viewBox="0 0 1120 420">
<defs><marker id="m3" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6.5" markerHeight="6.5" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="#3370ff"/></marker></defs>
<rect x="40" y="24" width="1040" height="86" rx="14" fill="#f0f4ff"/>
<text x="64" y="56" class="t">工作流(流程包)</text>
<text x="64" y="78" class="t3">节点 + 边;边只表达顺序与条件,数据按字段名流动 —— 编排层不掺业务逻辑</text>
<g>
<g fill="#fff" stroke="#3370ff" stroke-width="1.6">
<circle cx="600" cy="66" r="15"/><circle cx="672" cy="66" r="15"/><circle cx="744" cy="66" r="15"/>
<rect x="792" y="52" width="28" height="28" rx="5" transform="rotate(45 806 66)"/>
<circle cx="884" cy="66" r="15"/><circle cx="956" cy="66" r="15"/><circle cx="1028" cy="66" r="15"/>
</g>
<g fill="none" stroke="#3370ff" stroke-width="1.6">
<path d="M615 66 H 655"/><path d="M687 66 H 727"/><path d="M759 66 H 783"/><path d="M829 66 H 867"/><path d="M899 66 H 939"/><path d="M971 66 H 1011"/>
</g>
<g class="t3" text-anchor="middle"><text x="600" y="41">A1</text><text x="672" y="41">A2</text><text x="744" y="41">A3</text><text x="806" y="36">分流</text><text x="884" y="41">A4</text><text x="956" y="41">A6</text><text x="1028" y="41">A7</text></g>
</g>
<g stroke="#c9d6ff" stroke-dasharray="4 4" fill="none"><path d="M657 112 L 400 158"/><path d="M687 112 L 760 158"/></g>
<rect x="330" y="158" width="500" height="120" rx="14" fill="#fff" stroke="#3370ff" stroke-width="1.6"/>
<text x="354" y="190" class="t">活动 —— 完成一项任务的 Agent 机制</text>
<g class="t2"><text x="354" y="216">案例模型(每步剧本) + 说明书 + 判定表 + 台账声明</text><text x="354" y="240">系统外开发,像装 App 一样装进系统,升版本迭代</text></g>
<rect x="354" y="252" width="132" height="20" rx="10" fill="#e1eaff"/><text x="420" y="266" text-anchor="middle" class="t3" fill="#245bdb">安装即用 · 可替换</text>
<g stroke="#c9d6ff" stroke-dasharray="4 4" fill="none"><path d="M520 278 L 430 322"/><path d="M640 278 L 700 322"/></g>
<g>
<rect x="392" y="338" width="230" height="58" rx="10" fill="#fff" stroke="#dee0e3"/>
<rect x="380" y="330" width="230" height="58" rx="10" fill="#fff" stroke="#dee0e3"/>
<rect x="368" y="322" width="230" height="58" rx="10" fill="#fff" stroke="#3370ff" stroke-width="1.4"/>
<text x="388" y="346" class="tm">TY-2026-018 · 询价受理</text>
<text x="388" y="366" class="t3">一次触发起一单 · 起单宽进:门口不拒缺项</text>
<rect x="700" y="326" width="96" height="26" rx="13" fill="#e8f8e4"/><text x="748" y="344" text-anchor="middle" style="font-size:12px;font-weight:700" fill="#278717">推进中</text>
<rect x="700" y="360" width="96" height="26" rx="13" fill="#fff4e5"/><text x="748" y="378" text-anchor="middle" style="font-size:12px;font-weight:700" fill="#b25f00">等你签字</text>
</g>
<text x="64" y="360" class="t">单(任务)</text>
</svg>
<div class="cards">
<div class="card"><h4><em>治理</em>版本有纪律</h4><p>一条流<b>恰有一个生效版本</b>;新版本上线,在途的单按启动时的版本跑完,不被中途换规则。
活动改了内容必须升版本号 —— 同版重装无效,升级永远是显式动作,可追溯、可回退。</p></div>
<div class="card"><h4><em>宽进</em>门口不拒,过程补齐</h4><p>触发一到就起单 —— 缺字段不挡在门外(挡了就是把例外赶回线下)。
缺什么由活动在过程里向人开口补,<b>严出</b>由放行门把住:该有的没有,签不了字。</p></div>
<div class="card"><h4><em>价值</em>长尾业务也养得起</h4><p>新业务 = 装一个新活动 + 在流程包里连上节点,不动基座一行代码。
流程系统最养不起的长尾场景,在这里变成「多装一个包」的边际成本。</p></div>
</div>
<span class="pg">04 / 14</span>
</section>
<!-- ═══ S5 活动解剖 + 解耦 ═══ -->
<section id="s5" class="slide">
<span class="kicker"><i></i>05 · 解耦机制</span>
<h1>活动是安装进来的,声明就是授权</h1>
<h2 class="sub">系统(基座)与活动之间只有一条通道:<b>安装</b>。装进来的是一份「声明 + 剧本」,基座照声明给它一个能跑的世界 —— <b>没声明的,一律办不成</b>。</h2>
<svg class="dia" viewBox="0 0 1120 400">
<defs><marker id="m4" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="#3370ff"/></marker></defs>
<rect x="40" y="50" width="330" height="290" rx="16" fill="#fff" stroke="#dee0e3"/>
<text x="64" y="86" class="t" style="font-size:16px">系统基座(不动的部分)</text>
<g class="t2">
<text x="64" y="122">编排引擎 —— 逐拍推进 · 纪律把关</text>
<text x="64" y="150">台账基座 —— 草稿层 / 正式版两层账</text>
<text x="64" y="178">人环 —— 四类工作卡 · 工作区渲件</text>
<text x="64" y="206">门与闸 —— 判据 · 提交闸 · 放行门</text>
<text x="64" y="234">治理台 —— 装载 · 版本 · 授权</text>
</g>
<text x="64" y="286" class="t3">对每个活动,基座只做一件事:</text>
<text x="64" y="308" class="tb">照它的声明,给它一个能跑的世界</text>
<path d="M370 168 h18 v54 h-18" fill="none" stroke="#3370ff" stroke-width="2"/>
<g><path d="M640 195 H 396" fill="none" stroke="#3370ff" stroke-width="3" marker-end="url(#m4)"/>
<rect x="428" y="146" width="186" height="30" rx="15" fill="#e1eaff"/><text x="521" y="166" text-anchor="middle" class="tb">安装通道(唯一入口)</text>
<text x="521" y="232" text-anchor="middle" class="t3">读产物文件装入 · 改内容必升版本</text></g>
<g>
<rect x="640" y="50" width="440" height="290" rx="16" fill="#fff" stroke="#3370ff" stroke-width="1.6"/>
<text x="664" y="86" class="t" style="font-size:16px">活动包(系统外开发)</text>
<text x="1056" y="86" text-anchor="end" class="tm">quote-intake@49</text>
<g>
<rect x="664" y="104" width="196" height="62" rx="10" fill="#f0f4ff"/><text x="678" y="130" class="tb">案例模型</text><text x="678" y="150" class="t3">每个计划项一条剧本</text>
<rect x="872" y="104" width="188" height="62" rx="10" fill="#f0f4ff"/><text x="886" y="130" class="tb">活动说明书</text><text x="886" y="150" class="t3">AI 的作业规程</text>
<rect x="664" y="176" width="196" height="62" rx="10" fill="#f0f4ff"/><text x="678" y="202" class="tb">判定表 DMN</text><text x="678" y="222" class="t3">确定的判断查表出</text>
<rect x="872" y="176" width="188" height="62" rx="10" fill="#f0f4ff"/><text x="886" y="202" class="tb">台账声明</text><text x="886" y="222" class="t3">写哪几张表 = 授权面</text>
<rect x="664" y="248" width="196" height="62" rx="10" fill="#f0f4ff"/><text x="678" y="274" class="tb">HTML 报告模版</text><text x="678" y="294" class="t3">件 · 表单 · 归档件一体</text>
<rect x="872" y="248" width="188" height="62" rx="10" fill="#f0f4ff"/><text x="886" y="274" class="tb">出口与谓词</text><text x="886" y="294" class="t3">去向按数据算,不靠手感</text>
</g>
</g>
</svg>
<div class="cards">
<div class="card"><h4><em>写权</em>台账声明</h4><p>活动声明自己要写哪几张表、哪些列、哪些必填 —— 声明之外的表<b>一个字都写不进</b>。
列声明还顺带驱动界面:必填红、建议橘、自增列不问人,全从同一份声明长出来。</p></div>
<div class="card"><h4><em>调权 / 发权</em>工具与出口声明</h4><p>能调哪些工具、能向哪几个出口发数据、载荷什么形状 —— 全部声明制。
AI 在运行期<b>结构上不可能越权</b>:它想调一个没声明的工具,引擎连这个选项都不给它。</p></div>
<div class="card"><h4><em>产业含义</em>活动是可交易的能力单元</h4><p>活动在系统外开发、测试、打包 —— 意味着能力可以由不同团队并行生产、按版本分发。
基座越用越稳,业务能力靠「装活动」长出来,这是「操作系统」三个字的分量。</p></div>
</div>
<span class="pg">05 / 14</span>
</section>
<!-- ═══ S6 一单的一生 ═══ -->
<section id="s6" class="slide" style="background:var(--soft)">
<span class="kicker"><i></i>06 · 运行机制</span>
<h1>一单的一生:门前的都改得动,门后的都算数</h1>
<h2 class="sub">所有改动先落<b>草稿层</b>;人签字放行那一刻才合并进<b>正式版</b>、才对外发出 —— <b>放行之前,世界上什么都没有发生</b>。</h2>
<svg class="dia" viewBox="0 0 1120 430">
<defs><marker id="m5" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="#3370ff"/></marker>
<marker id="m5g" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="#278717"/></marker></defs>
<g fill="none" stroke="#3370ff" stroke-width="2">
<path d="M120 90 H 218"/><path d="M300 90 H 358"/><path d="M470 90 H 528"/><path d="M640 90 H 690"/><path d="M780 90 H 826" marker-end="url(#m5)"/>
</g>
<g>
<g><rect x="36" y="66" width="84" height="48" rx="9" fill="#fff" stroke="#dee0e3"/><text x="78" y="86" text-anchor="middle" class="t2">来件入站</text><text x="78" y="104" text-anchor="middle" class="t3">沟通记录归档</text></g>
<g><rect x="218" y="66" width="82" height="48" rx="9" fill="#fff" stroke="#c9d6ff"/><text x="259" y="86" text-anchor="middle" class="tb">起单</text><text x="259" y="104" text-anchor="middle" class="t3">宽进不拒缺</text></g>
<g><rect x="358" y="66" width="112" height="48" rx="9" fill="#fff" stroke="#c9d6ff"/><text x="414" y="86" text-anchor="middle" class="tb">AI 逐拍办事</text><text x="414" y="104" text-anchor="middle" class="t3">写草稿 · 全留痕</text></g>
<g><rect x="528" y="66" width="112" height="48" rx="9" fill="#fff" stroke="#e2d6ff"/><text x="584" y="86" text-anchor="middle" style="font-size:13px;font-weight:700" fill="#7f3bf5">人环 · 工作卡</text><text x="584" y="104" text-anchor="middle" class="t3">该问人的问人</text></g>
<g><rect x="690" y="48" width="90" height="84" rx="10" fill="#fff4e5" stroke="#ffd9a8"/>
<g stroke="#b25f00" stroke-width="2.4"><path d="M712 66 v48"/><path d="M735 66 v48"/><path d="M758 66 v48"/></g>
<text x="735" y="152" text-anchor="middle" class="t2" style="font-weight:700" fill="#b25f00">提交门</text>
<text x="735" y="170" text-anchor="middle" class="t3">判据逐条过</text></g>
<g><rect x="826" y="54" width="104" height="72" rx="10" fill="#eef8ea" stroke="#bfe3b2"/>
<circle cx="856" cy="82" r="13" fill="none" stroke="#278717" stroke-width="2"/><path d="M850 82 l5 5 9-10" fill="none" stroke="#278717" stroke-width="2"/>
<text x="892" y="80" class="t2" style="font-weight:700" fill="#278717">放行</text><text x="892" y="98" class="t3">人签字</text>
<text x="878" y="146" text-anchor="middle" class="t3">责任在此,清清楚楚</text></g>
<g><path d="M930 90 H 976" fill="none" stroke="#278717" stroke-width="2" marker-end="url(#m5g)"/>
<rect x="976" y="66" width="112" height="48" rx="9" fill="#fff" stroke="#bfe3b2"/><text x="1032" y="86" text-anchor="middle" style="font-size:13px;font-weight:700" fill="#278717">发射出口</text><text x="1032" y="104" text-anchor="middle" class="t3">载荷 = 验收过的成果</text></g>
</g>
<path d="M806 36 V 404" stroke="#8f959e" stroke-dasharray="6 5" fill="none"/>
<text x="796" y="30" text-anchor="end" class="t3">门前 · 都改得动</text><text x="818" y="30" class="t3">门后 · 都算数</text>
<g>
<rect x="120" y="230" width="640" height="66" rx="12" fill="#f5f1ff"/>
<text x="140" y="258" class="t">草稿层</text><text x="140" y="278" class="t3">AI 写的、人在件上改的、撤掉的行 —— 全在这层,随时可改可撤</text>
<g><rect x="470" y="242" width="86" height="24" rx="12" fill="#fff"/><text x="513" y="259" text-anchor="middle" class="t3">AI 写入</text>
<rect x="566" y="242" width="86" height="24" rx="12" fill="#fff"/><text x="609" y="259" text-anchor="middle" class="t3">人改两格</text>
<rect x="662" y="242" width="86" height="24" rx="12" fill="#fff"/><text x="705" y="259" text-anchor="middle" class="t3">撤掉一行</text></g>
<rect x="120" y="320" width="968" height="66" rx="12" fill="#eef8ea"/>
<text x="140" y="348" class="t">正式版</text><text x="140" y="368" class="t3">组织的事实。只有放行那一刻的合并能写进来;下游读到的永远是这层</text>
<path d="M780 296 C 812 306, 826 310, 848 318" fill="none" stroke="#278717" stroke-width="2.4" marker-end="url(#m5g)"/>
<text x="836" y="300" class="t3" fill="#278717">合并(放行那一刻)</text>
</g>
</svg>
<div class="cards">
<div class="card"><h4><em>类比</em>Git 的工作方式,搬进业务</h4><p>草稿层之于正式版,如同分支之于主干:AI 和人都在「分支」上放开干、随时改、随时撤;
<b>合入主干只有一条路 —— 人签字</b>。写错一行有退路(撤的只是草稿,正式版一个字不动)。</p></div>
<div class="card"><h4><em>放行那一刻</em>四件事原子发生</h4><p>草稿合并进正式版 → 出口按谓词求值、命中哪口发哪口 → 载荷带走验收过的成果 →
结构化留痕落档。签完那一屏直接回答「这一签下去发生了什么」:进了哪几张表各几行、转去了哪儿。</p></div>
<div class="card"><h4><em>止损面</em>错误出不了门</h4><p>AI 抽错一格、多写一行、编了一个数 —— 最坏情况也只是草稿层的一笔脏数据,
被红带点名、被闸拦住、被人改掉。<b>对外发出、写进正式账的,必然是人过目签字之后的版本</b>。</p></div>
</div>
<span class="pg">06 / 14</span>
</section>
<!-- ═══ S7 AI 的缰绳 ═══ -->
<section id="s7" class="slide">
<span class="kicker"><i></i>07 · AI 的缰绳</span>
<h1>AI 怎么被管住:一拍一串行动,纪律引擎当场把关</h1>
<h2 class="sub">AI 不是放出去自由发挥 —— 每一拍只看该看的局面,发出的每个行动都过纪律引擎;<b>判断归判定表,AI 只做编排</b>。</h2>
<div class="duo">
<svg viewBox="0 0 520 380" style="width:100%">
<defs><marker id="m6" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="#3370ff"/></marker></defs>
<circle cx="260" cy="190" r="128" fill="none" stroke="#dbe4f8" stroke-width="2"/>
<text x="260" y="184" text-anchor="middle" class="t" style="font-size:17px">一拍</text>
<text x="260" y="206" text-anchor="middle" class="t3">1.5 - 4.9 秒</text>
<g fill="none" stroke="#3370ff" stroke-width="2">
<path d="M330 84 A128 128 0 0 1 388 178" marker-end="url(#m6)"/>
<path d="M382 244 A128 128 0 0 1 300 308" marker-end="url(#m6)"/>
<path d="M190 296 A128 128 0 0 1 134 212" marker-end="url(#m6)"/>
<path d="M140 124 A128 128 0 0 1 216 68" marker-end="url(#m6)"/>
</g>
<g>
<rect x="186" y="26" width="148" height="44" rx="10" fill="#fff" stroke="#c9d6ff"/><text x="260" y="45" text-anchor="middle" class="tb">局面</text><text x="260" y="62" text-anchor="middle" class="t3">只摊当前这一步该看的</text>
<rect x="384" y="168" width="122" height="44" rx="10" fill="#fff" stroke="#c9d6ff"/><text x="445" y="187" text-anchor="middle" class="tb">模型决策</text><text x="445" y="204" text-anchor="middle" class="t3">编排,不裁断</text>
<rect x="186" y="310" width="148" height="44" rx="10" fill="#fff" stroke="#c9d6ff"/><text x="260" y="329" text-anchor="middle" class="tb">行动串(至多 6 个)</text><text x="260" y="346" text-anchor="middle" class="t3">记账 · 查表 · 调工具 · 发卡</text>
<rect x="10" y="168" width="128" height="44" rx="10" fill="#fff" stroke="#ffd9a8"/><text x="74" y="187" text-anchor="middle" style="font-size:13px;font-weight:700" fill="#b25f00">纪律引擎</text><text x="74" y="204" text-anchor="middle" class="t3">非法行动当场拒回</text>
</g>
</svg>
<div>
<div class="card" style="margin-bottom:12px"><h4><em>局面</em>上下文按状态组织</h4>
<p>每拍只摊开<b>当前这一步</b>的做法与该用的东西,其余步骤各留一行;台账读数、剧本、轨迹、可用工具全在局面里。
AI 不靠记忆干活 —— 它看到的就是此刻的真实世界。</p></div>
<div class="card" style="margin-bottom:12px"><h4><em>行动</em>十四种,各有边界</h4>
<p>记账、查判定表、调工具、发四类卡、宣告完成、提交放行、撤草稿…… 每种行动引擎都有一套合法性检查;
一拍最多六个、<b>被拒即停在被拒处</b>(前缀生效),不会带病往下跑。</p></div>
<div class="card"><h4><em>自动收步</em>与熔断</h4>
<p>每步剧本带「办成了没有」的判据 —— 数据到位引擎<b>自己收步翻页</b>,不再问模型,省拍省钱;
触达世界的步骤例外(不给引擎替人签字的钥匙)。连续被拒五拍熔断交人,调人有预算 —— <b>不空转、不越权、不无限烧</b>。</p></div>
</div>
</div>
<div class="cards" style="margin-top:14px">
<div class="card tint"><h4><em>纪律一</em>机检只读系统数出来的数</h4><p>判据要么由基座从台账<b>现算</b>(缺几格、写了几行、打了几个标),要么交人裁量。
AI 自报的数不当判据 —— 否则它总有一天会学会「把数改对」。</p></div>
<div class="card tint"><h4><em>纪律二</em>拒回的话是行动指南</h4><p>拦下一个错误动作时,连着告诉它:差的是数还是行、差在哪一行哪一格、哪条路能走通。
<b>指错方向比不给指引更糟</b> —— 这是我们在实测里反复验证过的工程铁律。</p></div>
<div class="card tint"><h4><em>纪律三</em>全程留痕,三色日志</h4><p>每一拍谁干了什么 —— 负责人绿、智能体蓝、系统灰,一单一本工作日志;
对话区只留一轮一句的交代,过程细节沉在日志里,<b>随时回放,永不失忆</b>。</p></div>
</div>
<span class="pg">07 / 14</span>
</section>
<!-- ═══ S8 判据体系 ═══ -->
<section id="s8" class="slide" style="background:var(--soft)">
<span class="kicker"><i></i>08 · 门的构造</span>
<h1>放行门里有什么:五层判据,各归其位</h1>
<h2 class="sub">「能不能放行」不是一个感觉,是一摞<b>可逐条核对的判据</b> —— 机器判得了的机器判,判不了的交人,<b>责任那一层永远留给签字的人</b>。</h2>
<div class="gates">
<div class="gate m"><span class="lv">L1<small>机检</small></span><span class="what"><b>必填齐</b> —— 型号 / 年用量一格不缺</span><span class="how">基座现算:缺几格、缺在哪一行哪一列</span></div>
<div class="gate m"><span class="lv">L2<small>机检</small></span><span class="what"><b>账面守恒</b> —— 每条都打了标、行数对得上、该查的工具真查过</span><span class="how">基座读数两边对账,自报无效</span></div>
<div class="gate d"><span class="lv">L3<small>裁量</small></span><span class="what"><b>质的判断</b> —— 拿不准的分类是否已按规则降级处理</span><span class="how">AI 裁,裁断与理由随卡交给人</span></div>
<div class="gate d"><span class="lv">L4<small>裁量</small></span><span class="what"><b>缺口处置</b> —— 选填缺口已补齐,或由你确认可以先受理</span><span class="how">带谓词:数据已齐则自动达标,否则才轮到裁量</span></div>
<div class="gate h"><span class="lv">L5<small>人</small></span><span class="what"><b>放行</b> —— 这一签本身就是这道关</span><span class="how">不列进判据表:那张卡就是 L5</span></div>
</div>
<div class="tri">
<span class="ok">达标 —— 机器核过,绿</span>
<span class="amb">AI 判为不满足 · 交你定 —— 琥珀,理由附在旁边</span>
<span class="no">未达标 —— 红,差什么直说</span>
</div>
<div class="cards" style="margin-top:16px">
<div class="card"><h4><em>三态</em>「裁了但不满足」不等于「没裁」</h4><p>判据在卡面上分三态呈现。AI 判为不满足的那条不假装绿、也不冒充红 ——
<b>连同理由一起摆到签字人面前</b>,由人定夺。裁量项裁过即可提交,不逼 AI 把裁断翻供成「满足」。</p></div>
<div class="card"><h4><em>可信</em>判据必须指向一个可做的动作</h4><p>每条判据背后都有一条能走的路:缺数去补、多行去撤、没办的回去办。
我们删掉过一批「恒不可满足」和「给自己盖章」式的判据 —— <b>门是用来把关的,不是用来表演严格的</b>。</p></div>
<div class="card"><h4><em>对老板的含义</em>审计级的放行记录</h4><p>每一单放行都留着:当时哪几条判据什么状态、AI 的裁断与理由是什么、人最终怎么定的。
合规回查时,这就是一份<b>逐条可回放的决策档案</b>。</p></div>
</div>
<span class="pg">08 / 14</span>
</section>
<!-- ═══ S9 人的位置 ═══ -->
<section id="s9" class="slide">
<span class="kicker"><i></i>09 · 人的位置</span>
<h1>人只在该出现的地方出现,出现时体验是对的</h1>
<h2 class="sub">人的介入收敛成<b>四类工作卡</b>;要看要改的内容是<b>一份件</b> —— 看是报告,按「编辑」就地改,办结即归档,三个身份同一份 HTML。</h2>
<div class="chips" style="max-width:1120px;margin:0 auto 14px">
<span class="chip b">执行工单 —— 把活干了:指导书说清缺什么,填报全在件上,零输入框卡片</span>
<span class="chip g">验收工单 —— 把关签字:判据三态 + 就地可改 + 落地结果回执</span>
<span class="chip o">决策卡 —— 岔路口拍板:至少两个可点的选项,不让人对着空表单猜</span>
<span class="chip">录入卡 —— 建档确认:一卡两段(公司 + 联系人),自增编号不问人</span>
</div>
<svg class="dia" viewBox="0 0 1120 380">
<defs><marker id="m7" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="#3370ff"/></marker></defs>
<rect x="60" y="20" width="1000" height="330" rx="14" fill="#fff" stroke="#dee0e3"/>
<rect x="60" y="20" width="1000" height="34" rx="14" fill="#f8f9fb"/>
<circle cx="86" cy="37" r="5" fill="#dee0e3"/><circle cx="104" cy="37" r="5" fill="#dee0e3"/><circle cx="122" cy="37" r="5" fill="#dee0e3"/>
<text x="560" y="42" text-anchor="middle" class="t3">员工工作台</text>
<rect x="80" y="70" width="330" height="262" rx="10" fill="#f8f9fb"/>
<text x="98" y="96" class="t2" style="font-weight:700">对话区</text>
<rect x="98" y="110" width="250" height="40" rx="9" fill="#fff" stroke="#e8eaed"/>
<text x="112" y="135" class="t3">AI:三颗料归一完毕,两格必填缺着</text>
<rect x="98" y="160" width="294" height="74" rx="9" fill="#fff" stroke="#c9d6ff"/>
<text x="112" y="186" class="tb">执行工单</text><text x="112" y="206" class="t3">补齐必填 · 建议追问目标价</text>
<rect x="286" y="196" width="90" height="26" rx="7" fill="#3370ff"/><text x="331" y="214" text-anchor="middle" class="tw">查看工单</text>
<rect x="98" y="244" width="250" height="40" rx="9" fill="#fff" stroke="#e8eaed"/>
<text x="112" y="269" class="t3">卡上只有整单级:判据 · 指导书 · 提交钮</text>
<rect x="440" y="70" width="600" height="262" rx="10" fill="#fff" stroke="#e8eaed"/>
<text x="458" y="96" class="t2" style="font-weight:700">工作区 · 件(报告即表单)</text>
<rect x="458" y="108" width="564" height="30" rx="7" fill="#f0f4ff"/><text x="472" y="128" class="tb">询价受理需求 · TY-2026-018</text>
<rect x="458" y="146" width="564" height="26" rx="7" fill="#feeae9"/><text x="472" y="164" style="font-size:12px;font-weight:700" fill="#d83931">还差 2 格必填没补 —— 第 2 行·年用量 / 第 3 行·型号</text>
<g>
<rect x="458" y="180" width="564" height="26" fill="#f8f9fb"/><text x="472" y="198" class="t3">SS8050&#12288;&#12288;三极管&#12288;&#12288;20,000&#12288;&#12288;长电</text>
<rect x="458" y="206" width="564" height="26" fill="#fffbfb"/><text x="472" y="224" class="t3" fill="#d83931">TL072CDR&#12288;运放&#12288;&#12288;(必填)&#12288;&#12288;TI</text>
<rect x="458" y="232" width="564" height="26" fill="#f8f9fb"/><text x="472" y="250" class="t3">NE555P&#12288;&#12288;定时器&#12288;12,000&#12288;&#12288;TI</text>
</g>
<rect x="458" y="272" width="200" height="28" rx="8" fill="#f8f9fb" stroke="#e8eaed"/><text x="472" y="291" class="t3">已锁定 —— 要改按「编辑」</text>
<rect x="672" y="272" width="66" height="28" rx="8" fill="#fff" stroke="#c9d6ff"/><text x="705" y="291" text-anchor="middle" class="tb">编辑</text>
<g fill="none" stroke="#3370ff" stroke-width="1.8"><path d="M392 197 C 420 197, 420 122, 452 122" marker-end="url(#m7)"/></g>
<text x="404" y="150" class="t3" fill="#245bdb">点卡,跳到件</text>
</svg>
<div class="cards">
<div class="card"><h4><em>分工</em>逐行数据归件,整单级归卡</h4><p>编辑在件(表格就地改、能增删行、红带点名缺口),提交在卡(判据 + 指导书 + 那一枚提交钮)。
两个区各说各的话,<b>不再有第二套表单</b> —— 人填的每一格都落在它将来归档的位置上。</p></div>
<div class="card"><h4><em>兜错</em>手改是兜 AI 的错的那一手</h4><p>改格子、撤掉不该在的行、补漏掉的行 —— 三类错都有出口;删是挂记号不当场抹掉,看得见、可后悔。
保存只落草稿层,<b>保存不等于放行</b>;必填还空着,提交钮按不下去,闸上直说差在哪。</p></div>
<div class="card"><h4><em>克制</em>该问人的问人,答不上来的不摆</h4><p>卡面摆表的人读名不摆库表英文名;系统能生成的编号不让人填;
AI 已办完的不再请示。人被打扰的每一次,都是<b>真正需要人</b>的那一次。</p></div>
</div>
<span class="pg">09 / 14</span>
</section>
<!-- ═══ S10 界面不撒谎 ═══ -->
<section id="s10" class="slide" style="background:var(--soft)">
<span class="kicker"><i></i>10 · 信任工程</span>
<h1>界面不撒谎:一个数,三处呈现,同一个源</h1>
<h2 class="sub">「还缺几格必填」由基座从台账现算 —— 件上的红带、卡上的提交闸、放行门的机检,读的是<b>同一个数、同一套算法</b>。没有两套口径,就没有扯皮。</h2>
<svg class="dia" viewBox="0 0 1120 330">
<defs><marker id="m8" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="#3370ff"/></marker></defs>
<rect x="430" y="240" width="260" height="56" rx="12" fill="#1f2329"/>
<text x="560" y="264" text-anchor="middle" class="tw" style="font-family:var(--mono)">基座读数 · 缺必填 = 2</text>
<text x="560" y="284" text-anchor="middle" style="font-size:11.5px" fill="#9aa3ad">从台账现算,不由 AI 自报</text>
<g fill="none" stroke="#3370ff" stroke-width="2">
<path d="M470 240 C 380 200, 300 180, 240 156" marker-end="url(#m8)"/>
<path d="M560 240 V 166" marker-end="url(#m8)"/>
<path d="M650 240 C 740 200, 820 180, 880 156" marker-end="url(#m8)"/>
</g>
<g>
<rect x="60" y="60" width="300" height="96" rx="12" fill="#fff" stroke="#dee0e3"/>
<text x="82" y="90" class="t">件上 · 缺项红带</text>
<rect x="82" y="104" width="256" height="30" rx="7" fill="#feeae9"/>
<text x="94" y="124" style="font-size:12px;font-weight:700" fill="#d83931">还差 2 格必填 —— 差在哪行哪列</text>
<rect x="410" y="60" width="300" height="96" rx="12" fill="#fff" stroke="#dee0e3"/>
<text x="432" y="90" class="t">卡上 · 提交闸</text>
<rect x="432" y="104" width="120" height="32" rx="8" fill="#f0f1f3"/><text x="492" y="125" text-anchor="middle" class="t3">提交(按不下)</text>
<text x="568" y="125" class="t3">缺着就拦,补齐自动开</text>
<rect x="760" y="60" width="300" height="96" rx="12" fill="#fff" stroke="#dee0e3"/>
<text x="782" y="90" class="t">放行门 · 机检判据</text>
<text x="782" y="120" class="t2">必填齐(型号 / 年用量)</text>
<rect x="990" y="102" width="52" height="24" rx="12" fill="#feeae9"/><text x="1016" y="119" text-anchor="middle" style="font-size:12px;font-weight:700" fill="#d83931">未达标</text>
</g>
</svg>
<div class="cards">
<div class="card"><h4><em>如实</em>状态、错误、能力都不装</h4><p>模型没绑密钥就写「不可用」,不摆假数字;外部系统报错,把厂商原话带给人看;
附件读不出文本,就写「读不出」—— <b>宁可难看,不可失真</b>。</p></div>
<div class="card"><h4><em>实时</em>屏上的永远是现在</h4><p>任务推进、台账写入、配置变更,全部事件推送实时刷新;别处销毁的单不会在你屏上留鬼影;
连「签完字到落定之间」的中间态都有自己的话:「字已签下,正在落地」。</p></div>
<div class="card"><h4><em>不冒充</em>算不出就空着</h4><p>去向没算出来,判定表一行都不高亮 —— 不冒充判过;没有数据来源的图表不画 ——
画六根编出来的柱子是撒谎。<b>报告只呈现,不计算、不判断</b>,判断永远发生在有留痕的地方。</p></div>
</div>
<span class="pg">10 / 14</span>
</section>
<!-- ═══ S11 实例:统一报价 ═══ -->
<section id="s11" class="slide">
<span class="kicker"><i></i>11 · 跑起来的样子</span>
<h1>实例:统一报价工作流,七节点全链</h1>
<h2 class="sub">客户一封询价邮件进来,到报价单发出、情报归档 —— 六个活动包 + 一道人工审批;每个分岔的去向都由<b>出口谓词按数据现算</b>。</h2>
<svg class="dia" viewBox="0 0 1120 430">
<defs><marker id="m9" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6.5" markerHeight="6.5" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="#3370ff"/></marker>
<marker id="m9d" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6.5" markerHeight="6.5" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="#8f959e"/></marker></defs>
<g>
<g><rect x="20" y="176" width="64" height="52" rx="9" fill="#fff" stroke="#dee0e3"/><path d="M34 192 h36 v22 h-36 z M34 192 l18 13 18-13" fill="none" stroke="#3370ff" stroke-width="1.8"/><text x="52" y="246" text-anchor="middle" class="t3">客户 RFQ</text></g>
<g><rect x="128" y="170" width="130" height="64" rx="11" fill="#f0f4ff" stroke="#c9d6ff"/><text x="193" y="196" text-anchor="middle" class="tb">A1 询价受理</text><text x="193" y="216" text-anchor="middle" class="t3">归一 · 三分类</text></g>
<path d="M300 174 l30 28 -30 28 -30 -28 z" fill="#fff" stroke="#3370ff" stroke-width="1.6"/><text x="300" y="160" text-anchor="middle" class="t3">分流</text>
<g><rect x="400" y="46" width="130" height="64" rx="11" fill="#f0f4ff" stroke="#c9d6ff"/><text x="465" y="72" text-anchor="middle" class="tb">A2 替代选型</text><text x="465" y="92" text-anchor="middle" class="t3">FAE 选替代 · 判稳</text></g>
<g><rect x="400" y="170" width="130" height="64" rx="11" fill="#f0f4ff" stroke="#c9d6ff"/><text x="465" y="196" text-anchor="middle" class="tb">A3 渠道寻源</text><text x="465" y="216" text-anchor="middle" class="t3">圈渠道 · 拿货源价</text></g>
<g><rect x="400" y="300" width="130" height="64" rx="11" fill="#f0f4ff" stroke="#c9d6ff"/><text x="465" y="326" text-anchor="middle" class="tb">A4 成本定价</text><text x="465" y="346" text-anchor="middle" class="t3">利润模型 · 底价门禁</text></g>
<g><path d="M640 300 l32 32 -32 32 -32 -32 z" fill="#fff4e5" stroke="#ffd9a8" stroke-width="1.6"/>
<circle cx="640" cy="326" r="7" fill="none" stroke="#b25f00" stroke-width="1.8"/><path d="M630 342 a10 7 0 0 1 20 0" fill="none" stroke="#b25f00" stroke-width="1.8"/>
<text x="640" y="384" text-anchor="middle" class="t3">A5 定价审批 · 人</text></g>
<g><rect x="740" y="170" width="130" height="64" rx="11" fill="#f0f4ff" stroke="#c9d6ff"/><text x="805" y="196" text-anchor="middle" class="tb">A6 报价议价</text><text x="805" y="216" text-anchor="middle" class="t3">发报价函 · 让步梯度</text></g>
<g><rect x="960" y="170" width="130" height="64" rx="11" fill="#eef8ea" stroke="#bfe3b2"/><text x="1025" y="196" text-anchor="middle" style="font-size:13px;font-weight:700" fill="#278717">A7 情报归档</text><text x="1025" y="216" text-anchor="middle" class="t3">输赢归因 · 竞对价</text></g>
</g>
<g fill="none" stroke="#3370ff" stroke-width="1.8">
<path d="M84 202 H 122" marker-end="url(#m9)"/>
<path d="M258 202 H 264" marker-end="url(#m9)"/>
<path d="M316 186 C 350 140, 360 110, 394 88" marker-end="url(#m9)"/>
<path d="M330 202 H 394" marker-end="url(#m9)"/>
<path d="M316 218 C 350 264, 360 300, 394 322" marker-end="url(#m9)"/>
<path d="M530 96 C 570 120, 580 150, 560 168 C 550 178, 540 186, 536 190" marker-end="url(#m9)"/>
<path d="M530 78 C 640 78, 700 180, 560 300 C 545 312, 540 316, 536 318" marker-end="url(#m9)" stroke-dasharray="5 4"/>
<path d="M465 234 V 294" marker-end="url(#m9)"/>
<path d="M530 332 H 602" marker-end="url(#m9)"/>
<path d="M672 332 C 700 332, 710 260, 736 224" marker-end="url(#m9)"/>
<path d="M870 202 H 954" marker-end="url(#m9)"/>
</g>
<path d="M805 236 C 772 330, 662 400, 538 356" fill="none" stroke="#8f959e" stroke-width="1.6" stroke-dasharray="5 4" marker-end="url(#m9d)"/>
<g class="t3">
<text x="330" y="120">有 C 类 · 需替代</text>
<text x="336" y="194">无 C 有 B</text>
<text x="330" y="286">全 A · 直通</text>
<text x="560" y="146">判稳仍有 B</text>
<text x="600" y="66" fill="#8f959e">判稳全转 A · 免寻源</text>
<text x="700" y="266">批下来</text>
<text x="668" y="404" fill="#8f959e">再议 · 回核价重批</text>
<text x="884" y="192">成交 / 未成</text>
</g>
</svg>
<div class="cards">
<div class="card"><h4><em>真实形状</em>来件千奇百怪,照单全收</h4><p>常备案例真不相同:俄文 BOM 附件、中文正文列料无附件、客户点名「这颗本轮跳过」——
全部端到端跑通。<b>宽进的意义就在这</b>:业务的真实形状不需要先被系统「格式化」。</p></div>
<div class="card"><h4><em>交接</em>随令牌流向下游的只有验收过的成果</h4><p>上游发什么、下游要什么,契约写死;每一步交接的载荷都是<b>人签过字的正式数据</b>。
A1 不替 A2 摆输入,A2 进场自己归一 —— 同一份事实只存一份,不分叉。</p></div>
<div class="card"><h4><em>去向</em>算出来的,不是点出来的</h4><p>「转替代还是转寻源」由分类计数按谓词现算,在放行那一刻与发口同一次判定;
要改去向,改的是清单里料号的分类,去向跟着变 —— <b>决策有据,回头可核</b>。</p></div>
</div>
<span class="pg">11 / 14</span>
</section>
<!-- ═══ S12 质量工程 ═══ -->
<section id="s12" class="slide" style="background:var(--soft)">
<span class="kicker"><i></i>12 · 质量工程</span>
<h1>可靠性不是许愿,是一条流水线</h1>
<h2 class="sub">AI 系统最怕「演示能跑,生产翻车」。我们把可靠性拆成<b>三道防线</b>:门禁拦代码,测试台拦设计,验收口径拦侥幸。</h2>
<svg class="dia" viewBox="0 0 1120 150">
<defs><marker id="mg" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0 L10 5 L0 10 z" fill="#3370ff"/></marker></defs>
<g fill="none" stroke="#3370ff" stroke-width="2">
<path d="M196 76 H 222" marker-end="url(#mg)"/><path d="M400 76 H 426" marker-end="url(#mg)"/>
<path d="M604 76 H 630" marker-end="url(#mg)"/><path d="M808 76 H 834" marker-end="url(#mg)"/>
</g>
<g>
<rect x="30" y="46" width="166" height="60" rx="11" fill="#fff" stroke="#c9d6ff"/><text x="113" y="72" text-anchor="middle" class="tb">类型 + 规范</text><text x="113" y="92" text-anchor="middle" class="t3">静态检查零豁免</text>
<rect x="234" y="46" width="166" height="60" rx="11" fill="#fff" stroke="#c9d6ff"/><text x="317" y="72" text-anchor="middle" class="tb">依赖边界</text><text x="317" y="92" text-anchor="middle" class="t3">模块分层不许穿</text>
<rect x="438" y="46" width="166" height="60" rx="11" fill="#fff" stroke="#c9d6ff"/><text x="521" y="72" text-anchor="middle" class="tb">契约入库</text><text x="521" y="92" text-anchor="middle" class="t3">接口变更必显式登记</text>
<rect x="642" y="46" width="166" height="60" rx="11" fill="#fff" stroke="#c9d6ff"/><text x="725" y="72" text-anchor="middle" class="tb">千余条测试</text><text x="725" y="92" text-anchor="middle" class="t3">含活动包内自测</text>
<rect x="846" y="46" width="166" height="60" rx="11" fill="#eef8ea" stroke="#bfe3b2"/><text x="929" y="72" text-anchor="middle" style="font-size:13px;font-weight:700" fill="#278717">五道全绿才合入</text><text x="929" y="92" text-anchor="middle" class="t3">以退出码验,不许口头绿</text>
</g>
</svg>
<div class="cards">
<div class="card"><h4><em>测试台</em>设计中心里的风洞</h4><p>投一封信就能试跑整条流或单个活动;逐拍轨迹、台账草稿、发出的人卡全存档,
可回放可对比。<b>设计人员改完配置,当场吹一遍风洞再上线</b>。</p></div>
<div class="card"><h4><em>验收口径</em>released + 被拒 0</h4><p>每个活动配三条形状不同的常备案例,标准是双硬指标:全部跑到放行收尾,且 <b>AI 行动被拒 0 次</b> ——
每一次被拒都被当成设计欠账修掉,而不是靠重试蒙混。这个口径逼着系统把「话与门」对齐。</p></div>
<div class="card"><h4><em>基线</em>动共享面,全量回归</h4><p>A1 是不可破的基线(连跑 10/10 全放行):任何人改了引擎、提示词、渲染层这些共享面,
全部案例重跑一遍 —— <b>「稳定的活动突然出错」这类事故,在流程上被堵死</b>。</p></div>
</div>
<span class="pg">12 / 14</span>
</section>
<!-- ═══ S13 效率账 ═══ -->
<section id="s13" class="slide">
<span class="kicker"><i></i>13 · 成本工程</span>
<h1>效率不是口号,是一笔一笔抠出来的账</h1>
<h2 class="sub">三组真实优化数据 —— 每一刀都从逐拍 trace 里挖出来(输入 / 输出 / 推理字数逐拍实证),<b>这个体系的成本曲线是可持续下压的</b>。</h2>
<div class="delta">
<div class="dcard">
<h4>每拍时延</h4>
<div class="row"><s>20 - 56s</s><i>&#8594;</i><b>1.5 - 4.9s</b></div>
<p>病根:模型的隐藏推理在烧时间。处方:<b>判断归判定表,模型只做编排</b> —— 关掉思考额度,
规则写死在说明书与判定表里。快了一个数量级,判断反而更稳(查表不抖)。</p>
</div>
<div class="dcard">
<h4>一单开销</h4>
<div class="row"><s>21 拍 · 17.3万 token</s><i>&#8594;</i><b>7 拍 · 4.9万</b></div>
<p>病根:一拍只许一个行动,每个动作都重烧一遍完整上下文。处方:<b>一拍一串行动</b>(封顶六个,
纪律引擎保证串内被拒即停)+ 成果已在就自动收步。同一单活,开销降到三分之一以下。</p>
</div>
<div class="dcard">
<h4>对话密度</h4>
<div class="row"><s>逐拍刷屏 9 条</s><i>&#8594;</i><b>一轮一句 5 条</b></div>
<p>病根:AI 每拍都播报,员工被日志淹没。处方:<b>只有交出控制权那一拍才进对话区</b>,
过程细节沉进工作日志。员工看到的是同事式汇报,细节要查随时能查 —— 体验和成本一起赢。</p>
</div>
</div>
<div class="cards" style="margin-top:16px">
<div class="card tint"><h4><em>方法</em>先度量,后动刀</h4><p>每拍的输入 token、输出 token、推理字数、耗时全部落 trace;优化是对着数据找最慢的那一环,
不是拍脑袋换模型。<b>同样的方法可以持续用在新活动、新模型上</b>。</p></div>
<div class="card tint"><h4><em>结构</em>贵的能力只花在刀刃上</h4><p>归一、编排这类临场活交给模型;分级、算价、判稳这类确定活交给判定表(零 token、零延迟、零抖动)。
成分拆得越清,<b>单位业务的 AI 成本越低</b> —— 这是三分法在成本上的红利。</p></div>
<div class="card tint"><h4><em>底线</em>省钱不省责任</h4><p>所有优化都在「验收口径不降」的前提下做:全部案例放行、被拒 0、数据逐轮对得上。
快和便宜,从来不以「少一道门」为代价。</p></div>
</div>
<span class="pg">13 / 14</span>
</section>
<!-- ═══ S14 证据与下一步 ═══ -->
<section id="s14" class="slide hero">
<span class="kicker"><i></i>14 · 现状与下一步</span>
<h1>不是概念稿 —— 是跑出来的</h1>
<h2 class="sub" style="margin-bottom:26px">以下全部来自 2026-08 端到端验收记录(测试台真跑 + 浏览器真人走查):</h2>
<div class="stats">
<div class="stat"><b>6 <em>个</em></b><span>真活动包全部到验收线(受理 / 替代 / 寻源 / 定价 / 议价 / 归档),经安装通道装入</span></div>
<div class="stat"><b>16 <em>条</em></b><span>端到端案例全部放行收尾,来件形状各不相同(俄文附件 / 中文正文 / 点名跳料…)</span></div>
<div class="stat"><b>0 <em>次</em></b><span>末轮 AI 行动被拒 —— 每次被拒都当设计欠账修掉,不靠重试蒙混</span></div>
<div class="stat"><b>1.5-4.9<em>s</em></b><span>每拍时延(自 20-56s 优化而来);一单 6-11 拍、五万至十万 token</span></div>
<div class="stat"><b>10 / 10</b><span>A1 连跑十轮全部放行、零被拒 —— 稳定性有基线,动共享面必全量回归</span></div>
<div class="stat"><b>5 <em>道</em></b><span>工程门禁(类型 / 规范 / 依赖 / 契约 / 千余条测试)全绿才许合入</span></div>
</div>
<div class="threeword" style="margin-top:34px">
<span>下一步:七节点全流程连跑常态化</span>
<span>运营度量与评价体系</span>
<span>活动包生态扩展</span>
</div>
<p style="margin-top:30px;font-size:15px;color:#b9c4e6">一句话收尾:<b style="color:#fff">确定交系统,临场交 AI,落地交人</b> —— 效率给机器,责任给人。</p>
<span class="pg">14 / 14</span>
</section>
</body>
</html>
+61 -19
View File
@@ -152,8 +152,8 @@ activities/<id>/src/ ──(pnpm activities:build)──> dist/{manifest.json,in
| 门 | 在哪 | 规则 |
|---|---|---|
| **起单宽进** | `process.start` | 不按 Schema 拒缺项;缺 required 顶层键 → 发补全卡(不占预算)。「没法填」给理由即带缺口开跑 |
| **执行卡提交闸** | 卡面 `gate` + 服务端 | 卡上绑了台账表的执行单,必填缺着按不下提交(`{ready,why,blanks}`,与放行门 L1 **同一个数同一套算法**);**服务端在收下回传之前也把一遍**(前端灰按钮只是给人看的)。算不出来(卡没绑表)不给闸 —— 没有依据的时候不拦人 |
| **起单宽进** | `process.start` | 不按 Schema 拒缺项。三活动 `input.required` 一律空(宽进严出),起单补全卡那条老路实际不再触发(彻底移除记 debt) |
| **执行卡提交闸** | 卡面 `gate` + 服务端 | 必填缺着按不下提交(`{ready,why,blanks}`,与放行门 L1 **同一个数同一套算法**);**服务端在收下回传之前也把一遍**(前端灰按钮只是给人看的)。看哪几张表:卡上绑了 blocks 看绑的;**没绑(执行卡零输入后是常态)回落到必交付物声明的 `ledgers`/`ledger`**(`gateAddressesOf`,有测钉着)。两边都空才不给闸 —— 没有依据的时候不拦人 |
| **提交门**(`submit` 预检) | engines | 机检(L1/L2)**必须为真**;裁量(L3/L4)**裁过即可**(裁成「不满足」照样能提交,裁断连理由随卡交给人 —— 一条只接受「是」的判据是一句教模型说谎的提示);**L4 带谓词时谓词说了算**(谓词真 = 已达标,模型那句相反的理由一并丢掉;谓词假才轮到裁量);必交付物按 ledger 地址数本单草稿行 |
| **放行门入场哨兵** | CMMN sentry | **必须有 ifPart,且读基座数、认「办完了没有」** —— 只有 onPart 的哨兵等于「有人写过一笔就开门」,模型写个 `false` 就推开了(实测:跳过三步直达放行门) |
| **熔断** | Harness | 连续 5 拍行动被拒 → 停问模型,发熔断卡把拒绝理由摆给人(比对削掉串位前缀、另开 60 条窗) |
@@ -165,13 +165,13 @@ activities/<id>/src/ ──(pnpm activities:build)──> dist/{manifest.json,in
- 已收掉的项按状态分开说(「挂到当下这一步」+ 列出可为的项),别说「先 start」——
start 一个 Completed 项同样被拒,两堵墙对着撞。
### 3.4 人类工具:五类卡
### 3.4 人类工具:四类卡(补全卡已退役)
| 类 | 行动 | 形态与硬规矩 |
|---|---|---|
| **执行** | `human` + `workOrder:'execution'` | 卡 = 查看工单钮(四字粗体,与验收卡同形)+ ask + **作业指导书**(`steps`+`standard`,与判据同一套版面、不画对勾)+ 依据 + 表单 + 提交闸。指导书写「哪几格必须有、哪几格值得追」,不写「怎么核表」。**`steps`/`standard` 是纯文本** —— 别写 `**` 这类记号,它们不会变粗体,只会原样摆在人眼前 |
| **执行** | `human` + `workOrder:'execution'` | 卡 = 查看工单钮(四字粗体,与验收卡同形)+ ask + **作业指导书**(`steps`+`standard`,与判据同一套版面、不画对勾)+ 依据 + 提交闸。**零输入**(2026-08-02 用户定):卡上没有任何填写 —— 不摆 `fields`、不绑 `blocks`,填报全在工作区的件上(人按「保存」基座直写草稿层,**别再 write 一遍**);空表单是常态,基座照发(从前的「表单不得为空」拒发已删)。指导书写「哪几格必须有、哪几格值得追」,不写「怎么核表」。**`steps`/`standard` 是纯文本** —— 别写 `**` 这类记号,它们不会变粗体,只会原样摆在人眼前 |
| **验收** | `submit` | 判据表(读时现算 —— 人在件上补一格判据跟着变;**L5 不上卡**:那张卡本身就是那道关,列进去等于摆一条恒红的项)+ 落地动作 + 可修订值 + 出口(算得出摆 `routedPort` 告知,算不出退 `portChoice` 让人选)。**办结之后换成落地结果**(landing):按交付物点名落了几行、转出哪一口、带过去哪些字段;一个口都没发直说「到此为止」 |
| **补全** | `ask` | 对话卡就地填;`fields` 空即拒;能预填的带 `value` 预填 |
| ~~补全~~ | ~~`ask`~~ | **已全局退役**(2026-08-02 用户定:「执行卡完全就是包含补全语义的人类工具」)—— 缺信息发**零输入执行工单**:指导书写清缺什么,人在工作区的件上补(保存即落草稿层)。旧输出里的 `ask` 由基座原地转成执行工单(归一不拒);引擎侧直发 `ask` 拒并指路 |
| **决策** | `decide` | 选择题(≥2 个选项,少了拒发);人点哪个进轨迹 |
| **录入** | `entry` | **人按下确认那一刻基座直接写库,AI 不经手** —— 建档这类主数据用它。可多段(`also`):建客户 = 公司档 + 联系人档,一张卡两段、确认一次落两行(值按 `段号.列名` 隔开)。卡面摆表的**人读名**(不摆 `customer.profile`);`auto` 列不进表单、另摆一句说明 |
@@ -179,8 +179,9 @@ activities/<id>/src/ ──(pnpm activities:build)──> dist/{manifest.json,in
- **一单同一张卡只发一次**。人交回来之后不论还缺什么都不许再发 ——
必填有提交闸兜着(卡回来时必填一定齐);**人留空交回来 = 对方给不了,那也是一个答案**:
如实记账、缺口照实留着、把这一步收掉往下走。
- 发卡即挂起,回传即续跑;在途验收先于一切;人答的值出现在下一拍轨迹里,
**看到了要自己 write 落台账**(绑台账的表除外 —— 基座直写)。
- 发卡即挂起,回传即续跑;在途验收先于一切。**值怎么落**:件上保存的、录入卡确认的,
基座直写;执行卡零输入(空答复 = 人照原样确认,痕上写明);决策卡选项进轨迹 ——
该记账的记账,**别当没答过再发一张**。
- 调人预算(`humanCallBudget`)环外扣账。
### 3.5 视图渲染(概要,详见《HTML报告生成规范》)
@@ -229,17 +230,17 @@ activities/<id>/src/ ──(pnpm activities:build)──> dist/{manifest.json,in
| 行动 | 形状 | 语义 |
|---|---|---|
| `start` | `{itemId}` | 启动标了人工激活的项;**已 Active / 已收掉再按 = 无事发生** |
| `start` | `{itemId}` | 显式启动一项(**可省**:2026-08-02 起动它就算启动);已 Active / 已收掉再按 = 无事发生 |
| `plan` | `{itemId}` | 裁量项入场 |
| `disable` | `{itemId, reason}` | 此单不走这条 |
| `judge` | `{criterion, satisfied, rationale}` | 裁语义性前提 / L3-L4 判据;criterion 照判据名**整句原样抄** |
| `write` | `{itemId, address, rows:[{key,patch}]}` | 写草稿。授权面是台账声明;`itemId` 只是记账,挂不上照写不拦;patch 只带有值的列;空转写(一行没变)即拒 |
| `drop` | `{itemId, address, keys:[{主键}], reason}` | 撤本单草稿层的行;主键与写进去时一字不差 |
| `tool` | `{itemId, tool, input}` | 读类即调;写类转意向;未命中转人挂起 |
| `ask` | `{itemId, ask, fields:[{key,label,hint?,value?}], basis}` | 补全卡,挂起 |
| `ask` | ~~已退役~~ | 基座原地转成零输入执行工单(字段名并进指导书);别再用 |
| `entry` | `{itemId, ask, ledger, entryKey, fields, also?}` | 数据录入卡,挂起;确认那一下基座直写 |
| `decide` | `{itemId, ask, options(≥2), basis}` | 决策卡,挂起 |
| `human` | `{itemId, workOrder, card{title,ask,steps?,standard?,basis,fields?,blocks?}}` | 执行/验收工单,挂起。`blocks` 里绑 `ledger`+`fields` 的表**由基座铺行、由基座写回**,别自己抄行(回复有长度上限,铺长了截断整拍作废) |
| `human` | `{itemId, workOrder, card{title,ask,steps?,standard?,basis}}` | 执行/验收工单,挂起。**执行卡零输入**(2026-08-02 用户定):不摆 `fields` 不绑 `blocks`,填写全在工作区的件上(editGate + 孪生件),人按「保存」基座直写草稿层;必填由提交闸看住(闸回落到必交付物的台账声明) |
| `listen` | `{itemId, eventType, keyFilter, deadline}` | 立监听,挂起;时限必配 |
| `finish` | `{itemId, rationale}` | 宣告一项完成;非人工激活的项就地过 Active 再收 |
| `submit` | `{rationale, fields?}` | 提交放行 → 预检 → 发验收卡挂起 |
@@ -248,16 +249,20 @@ activities/<id>/src/ ──(pnpm activities:build)──> dist/{manifest.json,in
`tool` 之后截断(出参没看见,后面是闭着眼计划的);挂起型收拍;超封顶丢弃留痕。
拍级附带 `say{text, tag, view?}`,不占行动位。
### 4.3 人工激活与 start(话与门同宽)
### 4.3 人工激活:动它就算启动(start 墙已拆)
标了 `manualActivationRule` 的项,**动它之前必须先 `start`** —— 调工具、发卡
(ask/human/decide/entry)、宣告完成(finish)都算,一拍里 start 排在那个动作前面即可。
只有 `write` 例外(itemId 只是记账)。
标了 `manualActivationRule` 的项,**模型显式动它(调工具/发卡/finish)就算启动**,
不必单发 `start`(2026-08-02 拆墙)。它的分量在引擎那一侧,原样保留:
**引擎不因哨兵满足替它启动**(哨兵只推到 Available/Enabled),
**也不给 doneWhen 替人收**(自动收步跳过人工激活项)—— 收它只能是模型的 finish。
实测账(2026-08-02):这条规矩从前在提示词里写成「先 start **再调工具**」——
只说了调工具,而门拦的是一整片;模型那一拍发的是 `human`,照字面读规矩管不着它,
于是漏了 start 白烧一拍。**话说的范围必须盖住门拦的范围,话窄一格,模型就正好漏在那一格上。**
写全之后三案例复跑全部被拒 0。
实测账(两轮,同一堵墙):先是「话与门同宽」—— 提示词写「先 start 再调工具」只说了
调工具,而门拦 tool/ask/human/decide/entry/finish 一整片,模型发 `human` 照字面漏了
start 白烧一拍;措辞写全之后**仍然熔断**(A1 一单连拒五拍,五次全是「要先 start」,
提示词与拒回都写着,它就是学不稳)。这才看清:那道墙保护不了任何东西 ——
start 是**模型自己的**动作,同一串连发即过,拦下只是逼它多演一步礼仪,
而学不稳的礼仪就是死法。**状态机的礼仪不该长成墙**(与 finish 就地过 Active、
start 已收项无事发生同族)。剧本里也别再教 start —— 教它白烧 token。
### 4.4 自动收步(doneWhen)
@@ -431,6 +436,33 @@ A1 是 `customerEmail / subject / body / attachments`)。
---
## 6.5 设计律(2026-08-02/03 一整轮实测沉淀 —— 每条都付过学费)
排障表治的是「这一处坏了」;这几条治的是「为什么总在坏」。改 Harness、写活动、
做报告之前先过一遍:
1. **墙要配路**:任何一道门拦住一件事时,必须存在一个模型(或人)做得出的动作
把事办对 —— 没有出路的门只是死胡同(drop 之于「多写一行」、撤行之于「替代未果」)。
2. **学不稳的礼仪就是死法**:纯礼仪的墙一律拆(start 墙、空表单拒发)。
模型十次里学会九次不算学会 —— 第十次熔断的是用户的单。幂等优于拒回:
按钮按两下不该报错(start / finish / 重发已答卡,同族三例)。
3. **拒回喂对抗,观察喂事实**:拒回的话是模型下一拍的行动指南,指错方向比不给更糟;
头几次犯浑先转成 observation 指路(卡已答过→接着办交回后的事),连犯才算撞墙进熔断。
4. **「机检只读基座数」管的是一整片**:验收判据、收步判据、放行哨兵、**入场哨兵**、
提交闸、红带 —— 任何一处读模型自报的数,那道门就等一个可能永远不来的记账
(第四次违反藏在 s_parsed 哨兵里:行落了门却不开,模型被「依赖断点」推去发卡绕路)。
5. **提示词分层**:全局提示词只说通用真理,活动级细节归说明书 —— 一句全局禁令
(「别再 write 一遍」)能把所有活动的既有约定顶翻,且症状出在别的活动身上,
查起来像闹鬼。改全局面 = 改所有活动,回归也要按「所有活动 × 全部常备案例」跑。
6. **措辞治不了的要结构治**:模型「不查工具直接编结论」这类顽疾,说明书写死到头
仍概率性犯 —— 根治靠机制(规则进判定表由引擎求值 / 工具出参直接带结论只让照抄),
措辞只能降频。一条纪律连续两轮被同一形状踩穿,就该停止改话、开始改结构。
7. **改共享面必须全量回归**:提示词、turn-runner、解析器、容器 —— 动一处,
三活动的全部常备案例都要重跑(A1 新客户路的熔断就是「只回归了老客户路」漏出来的)。
「稳定的活动出错」比新功能不 work 伤得多。
8. **用户重复提的要求,说明上一轮没打中病根**:往下再挖一层,别在同一层再修一遍
(「工作成果 1:1」挖了三层才到「办结那一刻存快照」这个真解)。
## 7. 排障速查表(症状 → 根因 → 处置;全部实测账)
| 症状 | 根因 | 处置 |
@@ -445,7 +477,15 @@ A1 是 `customerEmail / subject / body / attachments`)。
| 同一条理由连拒到熔断而数与行都对 | 拒回把「活没干完」说成「数/行不对」 | 已修:`_rows`=0 直说那一步没办 |
| 行数对不上,模型只会改数字 | 拒回没摆主键名单 | 已修:`_keys` 名单;照名单 drop |
| 「要先 start」而 start 也被拒 | 拒回没按状态分开说 | 已修;start 已收的项无事发生 |
| 模型漏 start 白烧一拍 | 提示词的话比门窄(只说了调工具) | 已修:话与门同宽(§4.3) |
| 「要先 start」连拒五拍熔断(措辞已写全) | start 墙本身:学不稳的礼仪就是死法 | 已拆墙(2026-08-02):动它就算启动(§4.3) |
| 零输入执行卡被「表单不得为空」拒发到熔断 | 基座旧机检把表单当交付通道 | 已删该拒发:空表单是常态(卡族表「执行」行) |
| 整拍六个动作全对却退化成「需要你判断下一步」 | 模型多吐一个闭括号,单数花括号的配平切出半截 | 已修:括号栈 + 丢不配对闭括号(`parseJsonLoose`,有测) |
| 「需要你判断下一步」卡面摆着一坨 JSON 原话 | 整拍输出没成形,旧兜底一次就发卡还把原话上卡 | 已修(2026-08-03):就地重问一次、原话归工作日志、卡面只说人话(agent-runner,有测) |
| 模型逐字复读一句被明令禁止的话 | **反例把原话引进了提示词**(「别说『…』」)—— 关思考的模型不读否定框,只认框里现成的句子 | 反例只描述、不引原话;要引原话就引**正例**(想听哪句就示范哪句)。A1「照历史价估」逮过(2026-08-03) |
| 执行卡上作业指导书整个是空的(basis 里是「当下可为(空)/目标缺口」这类家具) | **卡骨架写在那一步的剧本里**,而模型在「干活+收步+派卡」压成的一拍里发了它 —— 那一拍该步剧本没摊开,模型手上没有骨架,凭记忆拼出裸卡,基座兜底 basis 又摆上局面家具 | **凡要模型照抄的卡骨架,放说明书不放步骤剧本**(说明书每拍都在;A6 决策卡先立此法,A1 审定卡 2026-08-03 同坑)。另:`steps`/`standard` 已补进出参 schema —— 从前能到卡面纯靠厂商 JSON 模式宽松 |
| 人答完后同一条拒回重复五遍熔断(「这一步已办完,不能再往它上面挂活」) | **重发判断走在站上场之后** —— 人答完那一步常已收掉,`readyToWork` 先抛,于是「重发已答过的卡 = 无事发生」这条活路在最需要时够不着 | 已修(2026-08-03):**先判重发再站上场**;重发一张已答过的卡本就不需要那一项在场(墙要配路,有测) |
| 同一个岔路口连发四张决策卡,调人预算烧光落 `exhausted` | 决策卡没有幂等兜底(执行工单一直有) | 已修:`resendVerdict` 两族共用 —— 头两次无事发生、第三次起算被拒 |
| 收上一步时连拒「写了等于没写」 | 值已在台账(件上保存/上一拍写过),模型照抄重写;或提前干了下一步的活(如在 S2 查行情) | 拒回已带指南「该办下一件事」;剧本明写这一拍只有哪几件事 |
| 人答了卡,模型又发一张一样的 | 剧本残留「还缺就再发一张卡」死路 | §5.5-5:一单一卡;必填有闸兜着 |
| 人在卡上填了值,台账还是空的 | doneWhen 写成「问过就算完」 | 等「答案落了表」的计数(`answered_total`) |
| 该经过人的一步整个没发生 | 「经过人」写成了收步条件里的半句 | §5.3-4:独立一项+人工激活 |
@@ -466,6 +506,8 @@ A1 是 `customerEmail / subject / body / attachments`)。
| 下游网关取不到值 | 出口标量没有台账列承载 | §5.2-② |
| 改了包重装没生效 | 同版重装 no-op | 升 packageVersion |
| 收口前一两拍预算耗尽 | budget 给太紧 | §5.2-⑦ 留一倍余量 |
| 代答脚本交执行卡被拒「必填字段未回填:结果」 | 卡没声明 `fields` 时基座兜底加必填「结果」回执字段 | 交卷 `values.result` 带一句 |
| 验收 pass 后落地判「选择组未选出口」 | routedPort 只在前端回传里顶上(已修:服务端发事件前算有效口) | 升级基座;临时:回传自带 `selectedPorts` |
## 8. 待实现与已知不一致