From 34e207c3e8c7183c392269ae66e67118511807a4 Mon Sep 17 00:00:00 2001 From: 18628386721 <18628386721@163.com> Date: Mon, 27 Jul 2026 22:02:49 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=89=E9=9D=A2=E6=8B=86=E5=88=86=E2=91=AB:U?= =?UTF-8?q?I=20=E6=89=93=E7=A3=A8=20=E2=80=94=E2=80=94=20=E8=A1=A5=2010=20?= =?UTF-8?q?=E6=9E=9A=E7=BC=BA=E5=A4=B1=E5=9B=BE=E6=A0=87,=E8=93=9D?= =?UTF-8?q?=E5=9B=BE=E6=94=B9=E5=9B=A0=E6=9E=9C=E9=93=BE,=E5=8F=B0?= =?UTF-8?q?=E8=B4=A6=E6=94=B6=E6=88=90=E4=B8=80=E4=B8=AA=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 图标注册表缺 arrow-left-right/columns/table/tag 等 10 名,用到即渲染成空 SVG —— 补齐; 蓝图四库改真箭头相连的因果链节点卡(卡即入口,删重复的入库段); 驾驶舱终态分布改五色堆叠条;运营数据收成左轨一枚「台账」,域下沉为 2-3 字短名页签 (受理→寻源→定价→议价→替代→共享,按流程序),进模块自动落首域。 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m --- apps/web/src/app/gov-shell.tsx | 123 ++++++------------ apps/web/src/app/styles/console.css | 32 +++++ .../features/gov-blueprint/internal/panel.tsx | 120 ++++++++--------- apps/web/src/features/gov-data/index.ts | 2 +- .../src/features/gov-data/internal/panel.tsx | 22 ++-- .../features/gov-memory/internal/panel.tsx | 51 ++++++-- .../features/gov-trigger/internal/panel.tsx | 2 +- .../src/shared/gov/__tests__/domains.test.ts | 30 +++++ apps/web/src/shared/gov/domains.ts | 36 +++++ apps/web/src/shared/gov/gov-route.ts | 8 +- packages/ui-kit/src/Ico.tsx | 10 ++ 11 files changed, 266 insertions(+), 170 deletions(-) create mode 100644 apps/web/src/shared/gov/__tests__/domains.test.ts create mode 100644 apps/web/src/shared/gov/domains.ts diff --git a/apps/web/src/app/gov-shell.tsx b/apps/web/src/app/gov-shell.tsx index 917a55b..23913e8 100644 --- a/apps/web/src/app/gov-shell.tsx +++ b/apps/web/src/app/gov-shell.tsx @@ -21,6 +21,7 @@ import type { GovZone, Nav, Route } from '@web-shared/gov/gov-route.ts'; import { SURFACE_META, SURFACE_ZONES, canSeeSurface } from '@web-shared/gov/surfaces.ts'; import type { Surface } from '@web-shared/gov/surfaces.ts'; import { useGovObjects, useGovTasks } from '@web-shared/gov/gov-api.ts'; +import { domainShort, sortDomains } from '@web-shared/gov/domains.ts'; import { queryKeys } from './query.ts'; import { BlueprintHome } from '@features/gov-blueprint'; import { TasksHome, TaskLogPage, TaskPage, WfPage } from '@features/gov-task'; @@ -126,17 +127,19 @@ export function GovShell({ surface }: { surface: Surface }) { (tasks.data?.workflows ?? []).filter((w) => w.state === 'abnormal').length + (tasks.data?.tasks ?? []).filter((t) => t.terminal !== undefined && t.terminal !== 'released').length; - /** 台账的导航项按**已有数据域**长出来 —— 设计侧一发布新域,运营侧自动多一位 */ - const domains = [...new Set((objects.data?.objects ?? []).map((o) => o.domain))].sort(); + /** 台账的域页签按**已有域**长出来 —— 设计侧一发布新域,台账里自动多一个页签 */ + const domains = sortDomains([...new Set((objects.data?.objects ?? []).map((o) => o.domain))]); + // 进台账没带域(左轨点进来)时落到首域 —— 页签与内容同一判定 + const effectiveTab = zone === 'ledgers' && tab === '' ? (domains[0] ?? '') : tab; /* 面包屑:台面 › 区 › 页签 › 下钻各级 */ const crumbs: Array<{ label: string; go?: () => void }> = [ { label: SURFACE_META[surface].brand, go: () => goZone(zone) }, ]; crumbs.push({ label: meta.title, go: () => go({ zone, tab, stack: [] }) }); - // 台账区的页签名就是域名 —— 静态表里查不到,直接用它本身 + // 台账区的页签名就是域键 —— 静态表里查不到,呈现走人读短名 crumbs.push({ - label: TABS[zone].find((t) => t.key === tab)?.label ?? tab, + label: TABS[zone].find((t) => t.key === tab)?.label ?? (zone === 'ledgers' ? domainShort(effectiveTab) : tab), go: () => go({ zone, tab, stack: [] }), }); stack.forEach((r, i) => @@ -154,24 +157,14 @@ export function GovShell({ surface }: { surface: Surface }) {
{SURFACE_META[surface].brand}
- {ZONES.filter((z) => SURFACE_ZONES[surface].includes(z.key)).map((z) => - // 台账不是一个固定位,是一段**随数据域生长**的动态位 —— 每域一枚 - z.key === 'ledgers' ? ( - go({ zone: 'ledgers', tab: domain, stack: [] })} - /> - ) : ( - - ), - )} + {ZONES.filter((z) => SURFACE_ZONES[surface].includes(z.key)).map((z) => ( + + ))}
{(me?.name ?? '—').slice(0, 1)} {me?.name ?? '未登录'} @@ -242,18 +235,31 @@ export function GovShell({ surface }: { surface: Surface }) {
- {TABS[zone].map((t) => ( - - ))} + {zone === 'ledgers' + ? // 台账的页签是**数据域**(短名 2-3 字),随发布生长 —— 设计产生运营的形状,落在页签上 + domains.map((d) => ( + + )) + : TABS[zone].map((t) => ( + + ))}
- + ) : (
@@ -266,57 +272,6 @@ export function GovShell({ surface }: { surface: Surface }) { ); } -/** - * 台账段 —— 左轨里唯一的**动态位**:一域一枚,随数据域发布而生长。 - * - * 这是「设计产生运营的形状」最直观的一处:导航本身就是被设计出来的。 - */ -function LedgerRail({ - domains, - active, - onPick, -}: { - domains: readonly string[]; - active: string | undefined; - onPick: (domain: string) => void; -}) { - return ( - <> -
- 台账 -
- {domains.length === 0 ? ( -
- 随数据域 -
- 发布生长 -
- ) : ( - domains.map((d) => ( - - )) - )} - - ); -} - function ZoneHome({ zone, tab, nav }: { zone: GovZone; tab: string; nav: Nav }) { const P = { // 设计中心 diff --git a/apps/web/src/app/styles/console.css b/apps/web/src/app/styles/console.css index 8a3764d..f0cc6fa 100644 --- a/apps/web/src/app/styles/console.css +++ b/apps/web/src/app/styles/console.css @@ -458,3 +458,35 @@ .g-dbpage button:hover:not(:disabled) { border-color: var(--brand); color: var(--brand); } .g-dbpage button:disabled { opacity: .35; cursor: default; } .g-dbpage b { min-width: 36px; text-align: center; font-size: 11px; color: var(--ink-2); } + +/* ═══════════ 三面补充(非原型)—— 蓝图因果链 / 终态堆叠条 / 数据域紧凑轨钮 ═══════════ */ + +/* 蓝图因果链:四库节点卡以真箭头相连 —— 箭头编码真实依赖(流程包→活动→工具→数据),节点即入口 */ +.g-chain { display: flex; align-items: stretch; gap: 0; margin-bottom: 14px; } +.g-chain-node { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 8px; padding: 14px 16px; background: var(--card); border: 1px solid var(--cline); border-radius: 13px; box-shadow: var(--cshadow); text-align: left; font: inherit; color: var(--ink); cursor: pointer; transition: border-color var(--t-fast), transform var(--t-fast), box-shadow var(--t-fast); } +.g-chain-node:hover { border-color: var(--brand); transform: translateY(-1px); box-shadow: 0 4px 14px -6px rgba(51, 112, 255, .35); } +.g-chain-node:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; } +.g-chain-k { display: flex; align-items: center; gap: 7px; font-size: 11px; font-weight: 600; color: var(--ink-3); } +.g-chain-k .ic { width: 22px; height: 22px; border-radius: 7px; display: grid; place-items: center; flex: none; background: var(--fill-2, #eef1f5); color: var(--ink-3); } +.g-chain-node.t-brand .ic { background: var(--brand-soft); color: var(--brand-deep); } +.g-chain-node.t-auto .ic { background: #e6f7f4; color: #0a6a64; } +.g-chain-node.t-collab .ic { background: #f0e9ff; color: #5b21cf; } +.g-chain-node.t-ok .ic { background: var(--ok-soft); color: var(--ok); } +.g-chain-v { font-size: 24px; font-weight: 800; font-family: var(--mono); letter-spacing: -.03em; line-height: 1; } +.g-chain-node.t-brand .g-chain-v { color: var(--brand-deep); } +.g-chain-node.t-auto .g-chain-v { color: #0a6a64; } +.g-chain-node.t-collab .g-chain-v { color: #5b21cf; } +.g-chain-node.t-ok .g-chain-v { color: var(--ok); } +.g-chain-h { font-size: 11px; line-height: 1.5; color: var(--ink-4); } +.g-chain-arr { flex: none; width: 26px; display: grid; place-items: center; color: var(--ink-4); } +@media (max-width: 1100px) { + .g-chain { flex-direction: column; } + .g-chain-arr { transform: rotate(90deg); width: auto; padding: 2px 0; } +} + +/* 终态堆叠条:五终态五色一条,比例即信息 —— 装在 .g-kpi 卡里 */ +.g-mixbar { display: flex; height: 8px; border-radius: 4px; overflow: hidden; background: var(--fill-2, #eef1f5); } +.g-mixbar i { display: block; min-width: 2px; } +.g-mixbar-legend { display: flex; flex-wrap: wrap; gap: 3px 10px; font-size: 10px; color: var(--ink-3); } +.g-mixbar-legend b { font-family: var(--mono); font-weight: 700; color: var(--ink-2); } +.g-mixbar-legend i { display: inline-block; width: 7px; height: 7px; border-radius: 2px; margin-right: 4px; } diff --git a/apps/web/src/features/gov-blueprint/internal/panel.tsx b/apps/web/src/features/gov-blueprint/internal/panel.tsx index 6430afa..8725660 100644 --- a/apps/web/src/features/gov-blueprint/internal/panel.tsx +++ b/apps/web/src/features/gov-blueprint/internal/panel.tsx @@ -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 ( <> -
- - - t.effect === 'read').length} · 写类 ${toolRows.filter((t) => t.effect === 'write').length}`} - /> - +
+ + + + + + + + + + + + +
@@ -144,34 +174,6 @@ export function BlueprintHome({ nav }: HomeProps) { )} - -
- nav.jump('workflows', 'pkgs')} - /> - nav.jump('library', 'shelf')} - /> - nav.jump('tools', 'catalog')} - /> - nav.jump('data', 'domains')} - /> -
-
); } diff --git a/apps/web/src/features/gov-data/index.ts b/apps/web/src/features/gov-data/index.ts index e0aa4dc..a274856 100644 --- a/apps/web/src/features/gov-data/index.ts +++ b/apps/web/src/features/gov-data/index.ts @@ -1,5 +1,5 @@ /** * feature `gov-data` 唯一出口(编码规范 §9.1)。 - * 数据的两副面孔:结构定义在设计中心(数据域),行数据在运营中心(台账 · 某域)。 + * 数据的两副面孔:结构定义在设计中心(数据域),行数据在运营中心(台账 · 某域页签)。 */ export { DataHome, LedgersHome, ObjPage } from './internal/panel.tsx'; diff --git a/apps/web/src/features/gov-data/internal/panel.tsx b/apps/web/src/features/gov-data/internal/panel.tsx index 087f684..09019f4 100644 --- a/apps/web/src/features/gov-data/internal/panel.tsx +++ b/apps/web/src/features/gov-data/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 ( @@ -40,8 +41,11 @@ export function DataHome({ nav }: HomeProps) {
- {d} - {objects.filter((o) => o.domain === d).length} 个对象 · 发布即在运营中心长出一位 + {domainShort(d)} + + {d} + + {objects.filter((o) => o.domain === d).length} 个对象 · 发布即入运营台账
{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) { <> - {domain} 域的台账:行数据、主键与列结构。结构要改去设计中心 · 数据域; - 这里做的是运营动作 —— 查行、看版本、追引用。 + {domainShort(domain)}域({domain})的行数据、主键与列结构。 + 结构要改去设计中心 · 数据域;这里做的是运营动作 —— 查行、看版本、追引用。 {inDomain.length === 0 ? ( 这个域还没有对象 —— 对象随活动首次写入而物化,建好就出现在这里。 @@ -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; diff --git a/apps/web/src/features/gov-memory/internal/panel.tsx b/apps/web/src/features/gov-memory/internal/panel.tsx index b43e03b..3800172 100644 --- a/apps/web/src/features/gov-memory/internal/panel.tsx +++ b/apps/web/src/features/gov-memory/internal/panel.tsx @@ -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> = { + 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)} 次`} /> - `${TERMINAL_STATE_LABEL[k as TerminalState] ?? k} ${pct(v)}%`) - .join(' · ') - } - /> + {/* 终态分布:比例即信息 —— 五终态五色一条,不把五个百分比塞进一行小字 */} +
+ + + 终态分布 + + {mixTop.length === 0 ? ( + <> + — + 还没有任务落终态 + + ) : ( + <> + + {mixTop.map(([k, v]) => ( + + ))} + + + {mixTop.map(([k, v]) => ( + + + {TERMINAL_STATE_LABEL[k as TerminalState] ?? k} {pct(v)}% + + ))} + + + )} +
自主率与纠正率成对读,复合为有效自主率——单看自主率会奖励「猛冲后返工」。度量只是仪表,不载权力。 diff --git a/apps/web/src/features/gov-trigger/internal/panel.tsx b/apps/web/src/features/gov-trigger/internal/panel.tsx index 2f68f6b..1921960 100644 --- a/apps/web/src/features/gov-trigger/internal/panel.tsx +++ b/apps/web/src/features/gov-trigger/internal/panel.tsx @@ -282,7 +282,7 @@ function TriggerForm({ )} {draft.spec.kind === 'periodic-data' && ( <> - +