三面拆分⑫:UI 打磨 —— 补 10 枚缺失图标,蓝图改因果链,台账收成一个模块

图标注册表缺 arrow-left-right/columns/table/tag 等 10 名,用到即渲染成空 SVG —— 补齐;
蓝图四库改真箭头相连的因果链节点卡(卡即入口,删重复的入库段);
驾驶舱终态分布改五色堆叠条;运营数据收成左轨一枚「台账」,域下沉为 2-3 字短名页签
(受理→寻源→定价→议价→替代→共享,按流程序),进模块自动落首域。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
2026-07-27 22:02:49 +08:00
co-authored by Claude Fable 5
parent 4a19690389
commit 34e207c3e8
11 changed files with 266 additions and 170 deletions
@@ -9,7 +9,8 @@
* 这里**不出现任何运行态数字**(在跑几单、30 天起单数、死信数)。
* 设计者看的是定义完不完整,不是今天忙不忙 —— 混进运行数字,这页就退化成第二个驾驶舱。
*/
import { Clear, Entry, Kpi, Sec, Todo } from '@web-shared/gov/primitives.tsx';
import { Ico } from '@web-shared/Ico.tsx';
import { Clear, Sec, Todo } from '@web-shared/gov/primitives.tsx';
import type { Nav } from '@web-shared/gov/gov-route.ts';
import {
useGovActivities,
@@ -58,38 +59,67 @@ export function BlueprintHome({ nav }: HomeProps) {
const gaps = draftBad.length + missingActivities.length + uncalled.length;
// 因果链那句话写在区说明里(ZM.blueprint.desc),此处不重复 —— 直接上四库读数
// 因果链是这一屏的主角:四库以真箭头相连 —— 箭头编码的是真实依赖
// (流程包引用活动、活动调工具、活动物化数据),节点卡即入口,点卡即入库。
return (
<>
<div className="g-kpis">
<Kpi
ico="workflow"
tone="brand"
label="流程包 · 战略"
value={packages.length}
sub={`草稿 ${drafts.length} · 未闭合 ${draftBad.length}`}
/>
<Kpi
ico="boxes"
tone="auto"
label="活动 · 方法"
value={activities.length}
sub="一个活动 = 一个任务的定义"
/>
<Kpi
ico="wrench"
tone="collab"
label="工具 · 能力"
value={toolRows.length}
sub={`读类 ${toolRows.filter((t) => t.effect === 'read').length} · 写类 ${toolRows.filter((t) => t.effect === 'write').length}`}
/>
<Kpi
ico="database"
tone="ok"
label="数据域 · 对象"
value={domains.length}
sub={`${objectRows.length} 个对象 · 发布即在运营中心长出台账位`}
/>
<div className="g-chain">
<button className="g-chain-node t-brand" onClick={() => nav.jump('workflows', 'pkgs')}>
<span className="g-chain-k">
<span className="ic">
<Ico name="workflow" s={13} />
</span>
战略 · 流程包
</span>
<span className="g-chain-v">{packages.length}</span>
<span className="g-chain-h">
企业以哪些流转运转 —— 草稿 {drafts.length} · 未闭合 {draftBad.length}
</span>
</button>
<span className="g-chain-arr">
<Ico name="chevron-right" s={15} />
</span>
<button className="g-chain-node t-auto" onClick={() => nav.jump('library', 'shelf')}>
<span className="g-chain-k">
<span className="ic">
<Ico name="boxes" s={13} />
</span>
方法 · 活动库
</span>
<span className="g-chain-v">{activities.length}</span>
<span className="g-chain-h">每件事怎么干 —— 一个活动 = 一个任务的定义</span>
</button>
<span className="g-chain-arr">
<Ico name="chevron-right" s={15} />
</span>
<button className="g-chain-node t-collab" onClick={() => nav.jump('tools', 'catalog')}>
<span className="g-chain-k">
<span className="ic">
<Ico name="wrench" s={13} />
</span>
能力 · 工具箱
</span>
<span className="g-chain-v">{toolRows.length}</span>
<span className="g-chain-h">
系统能对世界做什么 —— 读类 {toolRows.filter((t) => t.effect === 'read').length} · 写类{' '}
{toolRows.filter((t) => t.effect === 'write').length}
</span>
</button>
<span className="g-chain-arr">
<Ico name="chevron-right" s={15} />
</span>
<button className="g-chain-node t-ok" onClick={() => nav.jump('data', 'domains')}>
<span className="g-chain-k">
<span className="ic">
<Ico name="database" s={13} />
</span>
对象 · 数据域
</span>
<span className="g-chain-v">{domains.length}</span>
<span className="g-chain-h">
企业运营什么 —— {objectRows.length} 个对象,发布即入运营台账
</span>
</button>
</div>
<Sec ico="flag" title="待补清单" extra="定义上的缺口聚在这里,点进即改 —— 不散落在四库">
@@ -144,34 +174,6 @@ export function BlueprintHome({ nav }: HomeProps) {
)}
</Sec>
<Sec ico="route" title="从这里进四库" extra="每库一页:定义在库里改,缺口在上面看">
<div className="g-entries">
<Entry
ico="workflow"
title="流程包 · 企业以哪些流转运转"
desc="五要素编排、版本谱系、边映射校验;调起规则也在本区"
onClick={() => nav.jump('workflows', 'pkgs')}
/>
<Entry
ico="boxes"
title="活动库 · 每件事怎么干"
desc="已安装活动的资产货架:契约面、范式、两条版本序列"
onClick={() => nav.jump('library', 'shelf')}
/>
<Entry
ico="wrench"
title="工具箱 · 系统能对世界做什么"
desc="读类过程中自由调用,写类只登记动作意向、放行后落地"
onClick={() => nav.jump('tools', 'catalog')}
/>
<Entry
ico="database"
title="数据域 · 企业运营什么"
desc="域与对象的结构定义;行数据在运营中心的台账看"
onClick={() => nav.jump('data', 'domains')}
/>
</div>
</Sec>
</>
);
}
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* feature `gov-data` 唯一出口(编码规范 §9.1)。
* 数据的两副面孔:结构定义在设计中心(数据域),行数据在运营中心(台账 · 某域)。
* 数据的两副面孔:结构定义在设计中心(数据域),行数据在运营中心(台账 · 某域页签)。
*/
export { DataHome, LedgersHome, ObjPage } from './internal/panel.tsx';
@@ -2,7 +2,7 @@
* feature `gov-data` —— 数据的两副面孔,各归其台:
*
* `DataHome`(设计中心 · 数据域):**定义面** —— 域 × 对象的结构:有哪些列、主键是什么、谁声明了它。
* `LedgersHome`(运营中心 · 台账 · 某域):**运营面** —— 这张表现在有多少行、里面是什么。
* `LedgersHome`(运营中心 · 台账 · 某域页签):**运营面** —— 这张表现在有多少行、里面是什么。
*
* 分开的理由:改 schema 和查行数据是两种心智。
* 设计者问「这个对象定义全不全」,运营者问「今天这张表有几条」——
@@ -15,6 +15,7 @@ import { Ico } from '@web-shared/Ico.tsx';
import { Card, Chip, Clear, NList, NRow, Note, PageHead, QueryGate, Sec } from '@web-shared/gov/primitives.tsx';
import type { Nav } from '@web-shared/gov/gov-route.ts';
import { useGovLedger, useGovObjects } from '@web-shared/gov/gov-api.ts';
import { domainShort, sortDomains } from '@web-shared/gov/domains.ts';
export type HomeProps = { tab: string; nav: Nav };
@@ -27,7 +28,7 @@ export type HomeProps = { tab: string; nav: Nav };
export function DataHome({ nav }: HomeProps) {
const q = useGovObjects();
const objects = q.data?.objects ?? [];
const domains = [...new Set(objects.map((o) => o.domain))].sort();
const domains = sortDomains([...new Set(objects.map((o) => o.domain))]);
return (
<QueryGate query={q}>
@@ -40,8 +41,11 @@ export function DataHome({ nav }: HomeProps) {
<div key={d} className="g-sec">
<div className="g-band">
<Ico name="database" s={13} />
{d}
<span className="x">{objects.filter((o) => o.domain === d).length} 个对象 · 发布即在运营中心长出一位</span>
{domainShort(d)}
<span className="g-mono" style={{ fontWeight: 500 }}>
{d}
</span>
<span className="x">{objects.filter((o) => o.domain === d).length} 个对象 · 发布即入运营台账</span>
</div>
<NList>
{objects
@@ -80,10 +84,10 @@ function ObjectSchemaRow({ domain, object, nav }: { domain: string; object: stri
);
}
/* ==================== 运营侧:台账(某一个域) ==================== */
/* ==================== 运营侧:台账(某一个域页签) ==================== */
/**
* 台账的一个域 —— 壳层把左轨选中的域名当页签传进来。
* 台账的一个域 —— 壳层把选中的域页签(键)传进来;没带域时壳层已落到首域。
* 这里看的是运营事实:这张表现在多少行,点进去逐行看。
*/
export function LedgersHome({ tab, nav }: HomeProps) {
@@ -95,8 +99,8 @@ export function LedgersHome({ tab, nav }: HomeProps) {
<QueryGate query={q}>
<>
<Note>
<b>{domain}</b> 域的台账:行数据、主键与列结构。结构要改去设计中心 · 数据域;
这里做的是运营动作 —— 查行、看版本、追引用。
<b>{domainShort(domain)}</b>域(<span className="g-mono">{domain}</span>)的行数据、主键与列结构。
结构要改去设计中心 · 数据域;这里做的是运营动作 —— 查行、看版本、追引用。
</Note>
{inDomain.length === 0 ? (
<Clear>这个域还没有对象 —— 对象随活动首次写入而物化,建好就出现在这里。</Clear>
@@ -112,7 +116,7 @@ export function LedgersHome({ tab, nav }: HomeProps) {
);
}
/** 一个台账对象一行:行数是运营侧最想先看到的那个数 */
/** 一个数据对象一行:行数是运营侧最想先看到的那个数 */
function LedgerObjectRow({ domain, object, nav }: { domain: string; object: string; nav: Nav }) {
const page = useGovLedger(domain, object);
const total = page.data?.total;
@@ -10,6 +10,7 @@
import { useState } from 'react';
import { TERMINAL_STATE_LABEL } from '@contracts';
import type { TerminalState } from '@contracts';
import { Ico } from '@web-shared/Ico.tsx';
import { Chip, Clear, Kpi, NList, NRow, Note, PageHead, QueryGate, Todo } from '@web-shared/gov/primitives.tsx';
import type { Nav } from '@web-shared/gov/gov-route.ts';
import {
@@ -25,6 +26,15 @@ export type HomeProps = { tab: string; nav: Nav };
const pct = (v: number | undefined) => Math.round((v ?? 0) * 100);
/** 终态五名的呈现色 —— 放行绿、驳回琥珀、停滞灰、耗尽红、终止墨,与状态章同族 */
const TERMINAL_TONE: Readonly<Record<string, string>> = {
released: 'var(--ok-accent)',
blocked: 'var(--warn-accent)',
stalled: '#8f959e',
exhausted: 'var(--danger-accent)',
terminated: '#41464e',
};
/* ---------------- 经营驾驶舱(运营首页) ---------------- */
/**
@@ -119,18 +129,35 @@ export function CockpitHome({ nav }: HomeProps) {
unit="tok"
sub={`每放行一单:${(m?.unitCost.minutes ?? 0).toFixed(1)} 分钟 · 调人 ${(m?.unitCost.humanCalls ?? 0).toFixed(1)} 次`}
/>
<Kpi
ico="flag"
label="终态分布"
value={mixTop.length === 0 ? '—' : `${pct(mixTop[0]![1])}%`}
sub={
mixTop.length === 0
? '还没有任务落终态'
: mixTop
.map(([k, v]) => `${TERMINAL_STATE_LABEL[k as TerminalState] ?? k} ${pct(v)}%`)
.join(' · ')
}
/>
{/* 终态分布:比例即信息 —— 五终态五色一条,不把五个百分比塞进一行小字 */}
<div className="g-kpi">
<span className="g-kpi-l">
<Ico name="flag" s={12} />
终态分布
</span>
{mixTop.length === 0 ? (
<>
<span className="g-kpi-v">—</span>
<span className="g-kpi-s">还没有任务落终态</span>
</>
) : (
<>
<span className="g-mixbar">
{mixTop.map(([k, v]) => (
<i key={k} style={{ width: `${pct(v)}%`, background: TERMINAL_TONE[k] ?? '#c9d2dd' }} />
))}
</span>
<span className="g-mixbar-legend">
{mixTop.map(([k, v]) => (
<span key={k}>
<i style={{ background: TERMINAL_TONE[k] ?? '#c9d2dd' }} />
{TERMINAL_STATE_LABEL[k as TerminalState] ?? k} <b>{pct(v)}%</b>
</span>
))}
</span>
</>
)}
</div>
</div>
<Note>
自主率与纠正率<b>成对读</b>,复合为有效自主率——单看自主率会奖励「猛冲后返工」。度量只是仪表,不载权力。
@@ -282,7 +282,7 @@ function TriggerForm({
)}
{draft.spec.kind === 'periodic-data' && (
<>
<Field k="台账域 · 对象" hint="按这张表的日期字段逐行起单">
<Field k="数据域 · 对象" hint="按这张表的日期字段逐行起单">
<select
className="g-input"
value={`${draft.spec.domain}.${draft.spec.object}`}