Archived
三面拆分⑫: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:
@@ -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,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}`}
|
||||
|
||||
Reference in New Issue
Block a user