资料文件真产出来了;E04/I01 补到验收线,顺手填了几个「墙拦住却不给路」的坑

**资料文件全链**(用户:审核资料这一步要真产出 word/excel/md/html,前端能渲、点资料名能开)
接手时这一步一个文件都产不出来 —— S3 剧本白纸黑字写着「file_ref 本 MVP 阶段可留空」。
文件是台账那一行的投影,不另存一份:
· 声明 `file:{titleColumn,bodyColumn,formatColumn}`,它同时是授权面(没声明就点不开)
· 现造真字节 —— docx/xlsx 是自己打的 OOXML 包(zip-write:store+CRC32,零依赖)
· 读回来渲 —— 解的就是造出来那份字节,渲得出来才证明文件是真的
· 件上 `data-file-of` → 工作区二级界面(Word 纸面/Excel 网格/渲好的 md/沙箱 html)+ 下载原件
放行落地前把每一份冻进 `sys.task_asset_file`(迁移 0029):归档件点开的必须是签字那一刻
那一份,而资料落在主数据上、试跑单不落正式表 —— 不存这一份,收工后一个都点不开。
工作成果里「产出的资料」自成一摞。Markdown 富文本渲染顺带销账(仍零依赖)。

**Harness**
· 画面按「这一拍办完之后」在办的那一步挑 —— 从前拿拍首的局面决定拍尾的事:
  收掉 S4、发出 S5 的卡,而屏上还摆着 S4 的只读件,人无处可填(墙要配路第四次现形)
· 验收「重做」真能退回重做,且打回后台账没动就不许再提交(软硬两段,不锁死单子)
· 立监听的授权面是清单声明;发卡的授权面是调人预算,不是计划项
· 读工具同参重调有兜底;空转写身后还有行动就不废串;finish 的 doneWhen 按写完后的草稿算

**界面**
· 翻件与翻页各归各位(从前二选一,多卡多页时页翻不动);没得翻写「—」不写 0/0
· 工作成果详情能翻份;表宽超出能横滑(E04/I01 抄版式时把 overflow-x 抄丢了)
· 查看面灰底滚过一屏变白 —— 两条独立的路都治了(沙箱内不同色 / 弹性子项不长个)
· 测试台「取不到」不再当成「还活着」

**活动**
E04 关系覆盖、I01 品牌内容各自补到验收线:判据全读基座数、判据指向的东西摆上卡面、
样例满载且名字 ≤14 字;提交前要裁的判据搬进说明书(模型会压拍,压过之后只有说明书还在)。

验收:e2e i01 4/4、e04 3/3 全部 released,资料在办中渲得出、放行后存了档点得开;
门禁 typecheck/lint/depcruise/1628 测全绿。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EEJX8Z7CHejvwmDQyPQuBX
This commit is contained in:
xcs
2026-08-24 10:26:45 +08:00
co-authored by Claude Opus 5
parent 70e83d0b09
commit 0aa2fc11b0
92 changed files with 13984 additions and 370 deletions
+24 -39
View File
@@ -27,7 +27,6 @@ import { Ico } from '@web-shared/Ico.tsx';
import { call } from '@web-shared/typed-fetch.ts';
import { useChatWidth } from '@web-shared/useChatWidth.ts';
import { EmptyState } from '@web-shared/desk/module-kit.tsx';
import { SURFACE_META, canSeeSurface } from '@web-shared/gov/surfaces.ts';
import { queryKeys } from './query.ts';
type ModuleKey = 'tasks' | 'approvals' | 'messages' | 'minutes' | 'report';
@@ -153,49 +152,33 @@ export function AppShell() {
);
})}
<div className="jc-rail-foot">
{/*
另两面的入口 —— 按授权亮灯:没有治理授权的人根本看不见,不给点了才说没权。
两枚并排、只留章不留名:轨底还得放身份钮,竖排三件会把左轨撑出滚动条(实测)。
*/}
{(canSeeSurface(session.data, 'design') || canSeeSurface(session.data, 'ops')) && (
<div className="jc-nav-user" style={{ flexDirection: 'row', gap: 6, paddingTop: 0 }}>
{canSeeSurface(session.data, 'design') && (
<a
className="jc-nav-ava"
href={SURFACE_META.design.path}
title="设计中心 —— 定义企业怎么运转"
style={{ width: 26, height: 26, fontSize: 11, background: 'linear-gradient(140deg,#4954e6,#232b6e)' }}
>
设
</a>
)}
{canSeeSurface(session.data, 'ops') && (
<a
className="jc-nav-ava"
href={SURFACE_META.ops.path}
title="运营中心 —— 看企业今天跑得怎样"
style={{ width: 26, height: 26, fontSize: 11, background: 'linear-gradient(140deg,#12b886,#0b7a5c)' }}
>
运
</a>
)}
</div>
)}
{/* 轨底身份即登出入口 —— 会话在服务端,点它是真作废,不只是清本地 */}
<button
type="button"
className="jc-nav-user"
title={`${me}(本工作台所有者)· 点击登出`}
disabled={logout.isPending}
onClick={() => logout.mutate()}
>
{/* 轨底只报身份,不再兼职登出入口 —— 退出登录挪到顶栏,免得点头像误触登出 */}
<div className="jc-nav-user" title={`${me}(本工作台所有者)`}>
<span className="jc-nav-ava">{me.slice(0, 1)}</span>
<span className="jc-nav-name">{me}</span>
</button>
</div>
</div>
</nav>
{module === 'tasks' ? (
<div className="app-main">
<header className="jc-topbar">
<span className="jc-topbar-title">{NAV_MODULES.find((m) => m.key === module)?.label ?? ''}</span>
<div className="jc-topbar-user">
<span className="jc-topbar-name">{me}</span>
<button
type="button"
className="jc-topbar-logout"
disabled={logout.isPending}
onClick={() => logout.mutate()}
>
<Ico name="log-out" s={14} />
退出登录
</button>
</div>
</header>
<div className="app-main-body">
{module === 'tasks' ? (
<div className={'workspace' + (collapsed ? ' jc-side-collapsed' : '')} style={split.style}>
{collapsed && (
<button
@@ -320,6 +303,8 @@ export function AppShell() {
) : (
<ReportDesk />
)}
</div>
</div>
</div>
</div>
);
+69 -3
View File
@@ -476,7 +476,7 @@
.proto-root .jc-sup:focus { outline: 0; border-color: var(--brand); box-shadow: 0 0 0 3px rgba(51,112,255,.12); }
.proto-root .jc-sup:disabled { background: #f3f4f6; color: #5c626b; }
/* ===== 左侧一体化导航轨(无顶栏):品牌 → 四大模块 → 轨底动作 + 登录者身份 ===== */
/* ===== 左侧一体化导航轨:品牌 → 四大模块 → 轨底身份(登出已挪去顶栏,见下)===== */
.proto-root .app-nav { padding: 14px 10px 12px; }
.proto-root .jc-rail-brand { text-align: center; font-size: 10px; font-weight: 800; letter-spacing: .14em; color: var(--ink-3); margin: -4px 0 6px; }
.proto-root .app-nav button { min-height: 58px; transition: background var(--t-fast), color var(--t-fast), border-color var(--t-fast); }
@@ -489,6 +489,18 @@
.proto-root .jc-nav-ava { width: 32px; height: 32px; border-radius: 999px; display: grid; place-items: center; flex: 0 0 auto; line-height: 1; color: #fff; font-size: 13px; font-weight: 800; background: linear-gradient(140deg, #2bc7d4, #0e9fd8); box-shadow: 0 2px 6px rgba(14, 159, 216, .35); }
.proto-root .jc-nav-name { font-size: 10px; font-weight: 600; color: var(--ink-3); }
/* ===== 顶栏:当前模块名 + 身份 + 退出登录 —— 与左轨互不重叠,登出只有这一处入口 ===== */
.proto-root .app-main { min-width: 0; min-height: 0; display: flex; flex-direction: column; }
.proto-root .app-main-body { flex: 1 1 auto; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
.proto-root .app-main-body > * { flex: 1 1 auto; min-width: 0; min-height: 0; }
.proto-root .jc-topbar { flex: 0 0 auto; height: 56px; display: flex; align-items: center; justify-content: space-between; padding: 0 20px; background: #fff; border-bottom: 1px solid var(--line); }
.proto-root .jc-topbar-title { font-size: 14px; font-weight: 700; color: var(--ink); }
.proto-root .jc-topbar-user { display: flex; align-items: center; gap: 12px; }
.proto-root .jc-topbar-name { font-size: 13px; color: var(--ink-3); }
.proto-root .jc-topbar-logout { display: inline-flex; align-items: center; gap: 6px; height: 32px; padding: 0 12px; border: 1px solid var(--line); border-radius: 8px; background: #fff; color: var(--ink-2); font-size: 13px; font-weight: 600; cursor: pointer; transition: background var(--t-fast), color var(--t-fast), border-color var(--t-fast); }
.proto-root .jc-topbar-logout:hover { background: var(--danger-soft); color: var(--danger); border-color: #f3c9c6; }
.proto-root .jc-topbar-logout:disabled { opacity: .6; cursor: default; }
/* 工作清单头:渐变图标点缀 + 两级筛选 */
.proto-root .jc-wl-head { display: flex; align-items: center; justify-content: flex-start; gap: 10px; }
.proto-root .jc-wl-head > div { flex: 1; min-width: 0; text-align: left; }
@@ -909,16 +921,50 @@
.proto-root .fileview-name svg { color: var(--ink-3); flex: none; }
.proto-root .fileview-name strong { font-size: 14px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.proto-root .fileview-kind { margin-left: auto; flex: none; font-size: 11px; font-weight: 700; padding: 3px 9px; border-radius: 999px; }
/* 下载原件 —— 屏上渲的是读回来的样子,这条给的是真字节(.docx 用 Word 打得开) */
.proto-root .fileview-dl { display: inline-flex; align-items: center; gap: 5px; flex: none; height: 28px; padding: 0 11px;
border: 1px solid var(--line); border-radius: 8px; background: #fff; color: var(--ink-2);
font-size: 12px; font-weight: 600; text-decoration: none; transition: all var(--t-fast); }
.proto-root .fileview-dl:hover { border-color: var(--brand-soft); background: var(--brand-wash); color: var(--brand); }
.proto-root .fileview-dl svg { flex: none; }
.proto-root .fileview-kind.k-markdown { color: var(--brand-deep); background: var(--brand-wash); }
.proto-root .fileview-kind.k-excel { color: var(--ok); background: var(--ok-soft); }
.proto-root .fileview-kind.k-word { color: var(--st-auto); background: var(--st-auto-soft); }
.proto-root .fileview-kind.k-pdf { color: var(--danger); background: var(--danger-soft); }
.proto-root .fileview-body { min-height: 0; padding: 26px 22px; background: radial-gradient(120% 80% at 50% 0%, #eef1f6 0%, #e8ecf1 100%); }
/*
* **书桌要跟着纸一样长**(2026-08-23 用户第二次报「灰底滚动后变白」,附截图)。
*
* 这一格是 `.fileview`(`height:100%`)的弹性子项,而弹性子项默认 `flex: 0 1 auto`:
* 它**不长个**,高度被容器(= 看得见的那一屏)封住。里面那张纸比一屏长时,
* 纸溢出这个盒子继续往下排,而**背景只画在盒子里** —— 于是滚到一屏之外,
* 纸两侧的灰书桌没了,底下是面板自己的白。上一轮只治了走沙箱的 markdown / html
* (那两档的底色画在 iframe 里),word / excel 这两档是父文档直接渲的,漏在外面。
*
* `flex: 1 0 auto`:不许被压(`shrink:0` → 盒子至少和内容一样高,背景铺到底),
* 内容比一屏短时又能长满(`grow:1` → 不会在下方留一条白边)。
*/
.proto-root .fileview-body { flex: 1 0 auto; min-height: 0; padding: 26px 22px; background: radial-gradient(120% 80% at 50% 0%, #eef1f6 0%, #e8ecf1 100%); }
/* 文档纸张(word:居中纸张) */
.proto-root .doc-paper { max-width: 760px; margin: 0 auto; background: #fff; border: 1px solid var(--line-soft); border-radius: 8px; box-shadow: 0 16px 44px rgba(31, 35, 41, .14), 0 2px 6px rgba(31, 35, 41, .06); padding: 44px 52px; }
/* markdown:铺满中间区全渲染,无窄栏/灰边/纸张感 */
.proto-root .fileview.k-markdown .fileview-body { background: #fff; padding: 0; }
/*
* **Markdown 与 HTML 走沙箱之后,外层这一格要跟里面同色**(2026-08-23 用户:
* 「渲染页面向下翻的时候背景的灰色在下半截会变成白色」)。
*
* 这条从前是 `background:#fff; padding:0` —— 那时 markdown 是一整块 `<pre>` 纸面铺满,
* 白是对的。现在里面是一份**自带灰底 + 居中白纸**的文档(与下载到的 .html 同一份),
* iframe 高度按内容算:内容比这一屏矮时,底下那截露出来的就是外层这块白 ——
* 人往下翻,灰底在半路变成白色。同色(与文档自己的 #f4f5f7 一致)之后接缝就没了。
*
* 也不再给内边距:那份文档自己排的版(纸面居中、两侧留白),外层再套一层边距
* 等于两套版面语言叠着。excel / word / pdf 那三种仍走上面的渐变书桌 —— 它们没有自己的底。
*/
.proto-root .fileview.k-markdown .fileview-body,
.proto-root .fileview.k-html .fileview-body { background: #f4f5f7; padding: 0; }
/* 内容比这一屏矮时,让沙箱把这一格铺满 —— 灰底一路到底,不在中途断开 */
.proto-root .fileview.k-markdown .jc-view-frame,
.proto-root .fileview.k-html .jc-view-frame { min-height: 100%; }
.proto-root .doc-paper.doc-full { max-width: none; margin: 0; min-height: 100%; border: 0; border-radius: 0; box-shadow: none; padding: 28px 40px; }
.proto-root .word-paper h1 { font-size: 22px; font-weight: 800; color: var(--ink); margin: 0 0 14px; text-align: center; }
.proto-root .word-paper h2 { font-size: 16px; font-weight: 700; color: var(--ink); margin: 20px 0 8px; }
@@ -2132,6 +2178,24 @@
.proto-root .jc-dv { padding: 12px 14px; background: #fff; border: 1px solid var(--cardline); border-left: 3px solid var(--line); border-radius: 11px; box-shadow: var(--card-shadow); }
.proto-root .jc-dv.s-ok { border-left-color: var(--ok); }
.proto-root .jc-dv.s-live { border-left-color: var(--brand); }
/* ── 产出的资料(与定稿报告分两摞,2026-08-23)────────────────────────
小标题只是分组条,不是卡片:它要看着比行「轻」,不然一摞里出现三种同重量的块 */
.proto-root .jc-dvs-sub { display: flex; align-items: baseline; gap: 8px; margin: 4px 0 -2px; padding: 0 2px;
font-size: 11.5px; font-weight: 800; letter-spacing: .04em; color: var(--ink-3); }
.proto-root .jc-dvs-sub em { font-style: normal; font-weight: 700; color: var(--ink-4); }
.proto-root .jc-dvs-sub i { margin-left: auto; font-style: normal; font-weight: 600; color: var(--ink-4); }
/* 资料行按格式上色(与件上的格式徽同一套颜色) */
.proto-root .jc-dv.m-file { border-left-color: var(--ink-4); }
.proto-root .jc-dv.m-file.f-docx { border-left-color: #2b5fd9; }
.proto-root .jc-dv.m-file.f-xlsx { border-left-color: #1f7a45; }
.proto-root .jc-dv.m-file.f-html { border-left-color: #b25e15; }
.proto-root .jc-dv-fmt { flex: none; margin-left: auto; padding: 3px 9px; border-radius: 999px;
font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase;
background: var(--soft); color: var(--ink-3); }
.proto-root .jc-dv-fmt.f-docx { background: #eaf1ff; color: #2b5fd9; }
.proto-root .jc-dv-fmt.f-xlsx { background: #e9f7ee; color: #1f7a45; }
.proto-root .jc-dv-fmt.f-html { background: #fdf0e6; color: #b25e15; }
.proto-root .jc-dv.s-bad { border-left-color: var(--danger); }
.proto-root .jc-dv-h { display: flex; align-items: flex-start; gap: 10px; }
.proto-root .jc-dv-ico { flex: none; width: 30px; height: 30px; border-radius: 8px; display: grid; place-items: center; color: var(--ink-4); background: var(--soft); }
@@ -2842,6 +2906,8 @@
.proto-root .jc-tnav-btn:active:not(:disabled) { transform: scale(.95); }
.proto-root .jc-tnav-btn:disabled { opacity: .35; cursor: default; }
.proto-root .jc-tnav-n { min-width: 34px; text-align: center; font-size: 11px; font-weight: 800; color: var(--ink-2); }
/* 两组翻页同时在场时(多张卡各自多页)各带一个字,单独在场时不摆 —— 一个数不必解释自己 */
.proto-root .jc-tnav-mk { font-style: normal; font-weight: 700; color: var(--ink-3); margin-right: 3px; }
.proto-root .jc-tnav-btn.back { width: auto; padding: 0 14px 0 10px; font-size: 12.5px; font-weight: 700; line-height: 1; white-space: nowrap; }
.proto-root .jc-tnav-btn svg { flex: none; display: block; }
/* 窄栏:页签藏图标省宽,保持一排 */