This repository has been archived on 2026-09-13. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
BPOS-MVP/activities/quote-intake/src/views/report.ts
T
ChaiHaoandClaude Opus 5 ac2fecd005 汇报流:活动按步序位置推视图,容器沙箱渲染
「flow:按**步序位置**推送声明的模版」(AM 07)。原先这条链只有写面没有读面,
活动的 viewTemplates 是一行占位字符串,工作台视图区永远只显示一份硬编码概览。

补齐整条链:
  · 步声明新增 `view`:这一步呈现哪张模版 —— 推什么由声明给定,不由临场决定。
  · 新增模版填数的纯函数(interaction/domain):`data-slot` 取值、`data-rows` 逐条展开。
    **填数只做取值与转义**,没有表达式、没有分支、没有循环 —— 一旦模版能算,
    它就成了代码,容器的「无脚本执行权」也就形同虚设。填充**消费槽位**,
    交出去的是干净 HTML,不是带模板属性的半成品。
  · interaction 补读面 `viewOf`;desk 路由把推送的 HTML 交给前端;
    ViewRegion 经 `@ui` 的 ViewContainer(sandbox="" 的 iframe)渲染 —— feature 不碰 innerHTML。
  · A1 写了三张真模版(事态全景 / 打标判据 / 成果预览),内联自带样式
    (沙箱页面不继承工作台 CSS),按步序位置挂在 S3 / S5 / S6 上。

沿路两处修:
① 取值路径遇到**本身带点号的键**(台账地址 `域.对象`)会被拆成两级、永远取不到。
   改为整键优先、找不到再下行;`view-render` 与 `step-sequence` 两处同修。
② 「本单交付」不能画成台账全表:任务视图是正式版 + 我的草稿的合并结果,
   直接拿来列会把**别单**放行过的记录一并列出。本单自己写下的行单独放在
   `mine:域.对象` 下(整键,取值不歧义)。

另:视图推送失败原先静默吞掉 —— 会让「视图区一直空着」变成查不出原因的怪事;
改为留痕(仍不影响步序推进,呈现是投影)。

实测:一封 RFQ 入站 → 放行前那一屏列出本单交付 LM358DR / TI / 12000 / 8.2 / 10周,
以及 Type A「直接快答」的分类结论与四项打标判据。

gates: tsc / eslint / depcruise(321 模块)/ api-report / 423 测试 全绿

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
2026-07-27 10:47:20 +08:00

134 lines
6.0 KiB
TypeScript

/**
* A1 视图模版 —— 视图区呈现的 HTML(AM 07)。
*
* 「模版是**内容**不是代码」:这里没有脚本、没有事件处理器、没有外链;
* 槽位 `data-slot` 由基座按任务视图填数,行模版 `data-rows` 逐条展开。
* 容器是沙箱 iframe(无脚本执行权),所以样式必须内联自带 —— 页面不继承工作台的 CSS。
*
* 三张模版对应三种局面(事态全景 / 进展 / 成果预览),按步序位置推送。
*/
/** 共用外壳:一套克制的排版,深浅色都能看 */
const SHELL = (title: string, kicker: string, accent: string, body: string): string => `
<style>
:root { color-scheme: light; }
* { box-sizing: border-box; }
body {
margin: 0; padding: 18px;
font: 13px/1.6 -apple-system, "PingFang SC", "Noto Sans SC", "Segoe UI", sans-serif;
color: #1f2933; background: #f7f8fa;
}
.wrap { max-width: 720px; margin: 0 auto; }
.hd { display: flex; align-items: flex-start; gap: 12px; margin-bottom: 14px; }
.hd .bar { width: 4px; align-self: stretch; border-radius: 2px; background: ${accent}; }
.hd .kick { font-size: 11px; letter-spacing: .08em; color: #6a7686; text-transform: uppercase; }
.hd h1 { margin: 2px 0 0; font-size: 17px; font-weight: 650; letter-spacing: .01em; }
.card { background: #fff; border: 1px solid #e6e9ee; border-radius: 10px; padding: 14px 16px; margin-bottom: 12px; }
.card h2 { margin: 0 0 10px; font-size: 12px; font-weight: 600; color: #6a7686; letter-spacing: .04em; }
.kv { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px 18px; }
.kv div { min-width: 0; }
.kv em { display: block; font-style: normal; font-size: 11px; color: #8b95a3; margin-bottom: 2px; }
.kv b { font-size: 14px; font-weight: 600; word-break: break-all; }
table { width: 100%; border-collapse: collapse; font-size: 12px; }
th { text-align: left; font-weight: 600; color: #6a7686; padding: 6px 8px; border-bottom: 1px solid #e6e9ee; white-space: nowrap; }
td { padding: 7px 8px; border-bottom: 1px solid #f0f2f5; }
tr:last-child td { border-bottom: 0; }
.mono { font-family: "SF Mono", Menlo, Consolas, monospace; font-size: 12px; }
.tag { display: inline-block; padding: 1px 8px; border-radius: 999px; font-size: 11px; font-weight: 600;
background: ${accent}1a; color: ${accent}; }
.note { font-size: 11px; color: #8b95a3; margin-top: 10px; line-height: 1.7; }
.empty { color: #a8b0bb; font-size: 12px; padding: 6px 0; }
</style>
<div class="wrap">
<div class="hd"><span class="bar"></span><div><div class="kick">${kicker}</div><h1>${title}</h1></div></div>
${body}
</div>`;
/** ① 事态全景:受理刚开始,先把「这一单是什么」摆清楚 */
export const INTAKE_OVERVIEW = SHELL(
'RFQ 受理全景',
'A1 · 统一受理',
'#3370ff',
`<div class="card">
<h2>询价来源</h2>
<div class="kv">
<div><em>客户编码</em><b class="mono" data-slot="customer_code"></b></div>
<div><em>询价单号</em><b class="mono" data-slot="rfqRef"></b></div>
<div><em>主题</em><b data-slot="subject"></b></div>
</div>
</div>
<div class="card">
<h2>归一结果</h2>
<div class="kv">
<div><em>型号 PN</em><b class="mono" data-slot="pn"></b></div>
<div><em>厂牌</em><b data-slot="brand"></b></div>
<div><em>年用量 EAU</em><b data-slot="eau"></b></div>
<div><em>目标价</em><b data-slot="target_price"></b></div>
<div><em>交期</em><b data-slot="lead_time"></b></div>
<div><em>缺关键字段</em><b data-slot="missing_key_fields"></b></div>
</div>
<p class="note">归一由模型从询价原文抽取,逐条落任务级草稿层;放行前不入正式版。</p>
</div>`,
);
/** ② 进展:打标之后,把判据摊开 —— 人要在数据上作判断,数据就得摆出来 */
export const INTAKE_CLASSIFIED = SHELL(
'料号分类与判据',
'A1 · 三分类 P-03',
'#0aa294',
`<div class="card">
<h2>分类结论</h2>
<div class="kv">
<div><em>型号 PN</em><b class="mono" data-slot="pn"></b></div>
<div><em>Type 标</em><b class="tag" data-slot="type_tag"></b></div>
<div><em>分级依据</em><b data-slot="basis"></b></div>
</div>
</div>
<div class="card">
<h2>打标判据(DMN 入参)</h2>
<div class="kv">
<div><em>距上次报价</em><b data-slot="history_days"></b></div>
<div><em>行情稳定</em><b data-slot="market_stable"></b></div>
<div><em>生命周期</em><b data-slot="lifecycle"></b></div>
<div><em>历史价命中</em><b data-slot="history_hit"></b></div>
</div>
<p class="note">
三分类规则 P-03 是确定性决策表:同样的判据必得同样的结论,零方差零 token。
A = 直接快答;B = 重估后报(转寻源);C = 转案处理(转替代选型)。
</p>
</div>`,
);
/** ③ 成果预览:验收放行前的最后一屏 —— 要放行的到底是什么,一眼看全 */
export const INTAKE_RELEASE = SHELL(
'统一受理记录 · 待放行',
'A1 · 成果预览',
'#7f3bf5',
`<div class="card">
<h2>本单交付</h2>
<table>
<thead><tr><th>型号</th><th>厂牌</th><th>年用量</th><th>目标价</th><th>交期</th></tr></thead>
<tbody data-rows="mine:quote_intake.record">
<tr>
<td class="mono" data-slot="pn"></td>
<td data-slot="brand"></td>
<td data-slot="eau"></td>
<td data-slot="target_price"></td>
<td data-slot="lead_time"></td>
</tr>
</tbody>
</table>
</div>
<div class="card">
<h2>分流去向</h2>
<div class="kv">
<div><em>Type 标</em><b class="tag" data-slot="type_tag"></b></div>
<div><em>依据</em><b data-slot="basis"></b></div>
</div>
<p class="note">
放行通过即刻落地:草稿层合并入正式版、数据事件此刻才发射,G2 网关按 Type 分流。
放行是草稿态与事实态之间的那道门 —— 门前的一切都还改得动,门后的都算数了。
</p>
</div>`,
);