forked from ChaiHao/BPOS-MVP
三面拆分⑨:台账动态位入运营,数据域瘦身为纯定义面
设计侧只见结构(列/主键),运营侧按域列行数与行浏览 —— 改 schema 与查行数据 是两种心智,摞一屏行数会把结构挤到看不见。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
@@ -16,7 +16,6 @@ import type { Session } from '@contracts';
|
|||||||
import { logoutRoute, sessionRoute } from '@contracts';
|
import { logoutRoute, sessionRoute } from '@contracts';
|
||||||
import { call } from '@web-shared/typed-fetch.ts';
|
import { call } from '@web-shared/typed-fetch.ts';
|
||||||
import { Ico } from '@web-shared/Ico.tsx';
|
import { Ico } from '@web-shared/Ico.tsx';
|
||||||
import { Note } from '@web-shared/gov/primitives.tsx';
|
|
||||||
import { TABS, ZM, ZONES, routeLabel } from '@web-shared/gov/gov-route.ts';
|
import { TABS, ZM, ZONES, routeLabel } from '@web-shared/gov/gov-route.ts';
|
||||||
import type { GovZone, Nav, Route } from '@web-shared/gov/gov-route.ts';
|
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 { SURFACE_META, SURFACE_ZONES, canSeeSurface } from '@web-shared/gov/surfaces.ts';
|
||||||
@@ -30,7 +29,7 @@ import { EdgesPage, PkgPage, WorkflowsHome } from '@features/gov-process';
|
|||||||
import { TriggerPage } from '@features/gov-trigger';
|
import { TriggerPage } from '@features/gov-trigger';
|
||||||
import { ActPage, LibraryHome } from '@features/gov-activity';
|
import { ActPage, LibraryHome } from '@features/gov-activity';
|
||||||
import { ToolPage, ToolsHome } from '@features/gov-tool';
|
import { ToolPage, ToolsHome } from '@features/gov-tool';
|
||||||
import { DataHome, ObjPage } from '@features/gov-data';
|
import { DataHome, LedgersHome, ObjPage } from '@features/gov-data';
|
||||||
import { CockpitHome, KnowledgeHome, MemPage } from '@features/gov-memory';
|
import { CockpitHome, KnowledgeHome, MemPage } from '@features/gov-memory';
|
||||||
import { ChanPage, LlmPage, SystemHome } from '@features/gov-system';
|
import { ChanPage, LlmPage, SystemHome } from '@features/gov-system';
|
||||||
|
|
||||||
@@ -318,11 +317,6 @@ function LedgerRail({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 台账 —— Task 10 接真;导航项已随数据域长出来了,内容随后 */
|
|
||||||
function LedgersPlaceholder() {
|
|
||||||
return <Note>这里将按域列出全部台账对象、行数据与版本。域的结构在设计中心 · 数据域维护。</Note>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ZoneHome({ zone, tab, nav }: { zone: GovZone; tab: string; nav: Nav }) {
|
function ZoneHome({ zone, tab, nav }: { zone: GovZone; tab: string; nav: Nav }) {
|
||||||
const P = {
|
const P = {
|
||||||
// 设计中心
|
// 设计中心
|
||||||
@@ -335,7 +329,7 @@ function ZoneHome({ zone, tab, nav }: { zone: GovZone; tab: string; nav: Nav })
|
|||||||
cockpit: CockpitHome,
|
cockpit: CockpitHome,
|
||||||
tasks: TasksHome,
|
tasks: TasksHome,
|
||||||
people: PeopleHome,
|
people: PeopleHome,
|
||||||
ledgers: LedgersPlaceholder,
|
ledgers: LedgersHome,
|
||||||
knowledge: KnowledgeHome,
|
knowledge: KnowledgeHome,
|
||||||
system: SystemHome,
|
system: SystemHome,
|
||||||
}[zone];
|
}[zone];
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* feature `gov-data` 唯一出口(编码规范 §9.1)。
|
* feature `gov-data` 唯一出口(编码规范 §9.1)。
|
||||||
* 数据管理:域 / 对象目录、schema 视图、记录浏览
|
* 数据的两副面孔:结构定义在设计中心(数据域),行数据在运营中心(台账 · 某域)。
|
||||||
*/
|
*/
|
||||||
export { DataHome, ObjPage } from './internal/panel.tsx';
|
export { DataHome, LedgersHome, ObjPage } from './internal/panel.tsx';
|
||||||
|
|||||||
@@ -1,61 +1,138 @@
|
|||||||
/**
|
/**
|
||||||
* feature `gov-data` —— 数据管理:域 / 对象目录、schema 视图、记录浏览
|
* feature `gov-data` —— 数据的两副面孔,各归其台:
|
||||||
|
*
|
||||||
|
* `DataHome`(设计中心 · 数据域):**定义面** —— 域 × 对象的结构:有哪些列、主键是什么、谁声明了它。
|
||||||
|
* `LedgersHome`(运营中心 · 台账 · 某域):**运营面** —— 这张表现在有多少行、里面是什么。
|
||||||
|
*
|
||||||
|
* 分开的理由:改 schema 和查行数据是两种心智。
|
||||||
|
* 设计者问「这个对象定义全不全」,运营者问「今天这张表有几条」——
|
||||||
|
* 摞在一屏,设计页就会变成一个查数据的地方,行数把结构挤到看不见。
|
||||||
*
|
*
|
||||||
* 自 `~/coding/JCAI_PRD/governance/_components/Governance.tsx` 平移:
|
* 自 `~/coding/JCAI_PRD/governance/_components/Governance.tsx` 平移:
|
||||||
* DOM 结构、类名(`g-*` + 色调 `t-*`)与文案原样保留,mock 常量换成服务端投影(README 平移规则 1/2/4)。
|
* DOM 结构、类名(`g-*` + 色调 `t-*`)与文案原样保留,mock 常量换成服务端投影(README 平移规则 1/2/4)。
|
||||||
* 区首页只负责导航,治理动作一律落在下钻详情页的**就地确认带**(影响预览三必答)。
|
|
||||||
*/
|
*/
|
||||||
import { Ico } from '@web-shared/Ico.tsx';
|
import { Ico } from '@web-shared/Ico.tsx';
|
||||||
import { Card, Chip, Clear, NList, NRow, Note, PageHead, QueryGate, Sec } from '@web-shared/gov/primitives.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 type { Nav } from '@web-shared/gov/gov-route.ts';
|
||||||
import {
|
import { useGovLedger, useGovObjects } from '@web-shared/gov/gov-api.ts';
|
||||||
useGovLedger,
|
|
||||||
useGovObjects,
|
|
||||||
} from '@web-shared/gov/gov-api.ts';
|
|
||||||
|
|
||||||
export type HomeProps = { tab: string; nav: Nav };
|
export type HomeProps = { tab: string; nav: Nav };
|
||||||
|
|
||||||
|
/* ==================== 设计侧:数据域(定义面) ==================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 只呈现**结构**:域分组 → 对象 → 列与主键。
|
||||||
|
* 行数与行浏览入口不在这里 —— 那在运营中心的台账。
|
||||||
|
*/
|
||||||
export function DataHome({ nav }: HomeProps) {
|
export function DataHome({ nav }: HomeProps) {
|
||||||
const q = useGovObjects();
|
const q = useGovObjects();
|
||||||
const objects = q.data?.objects ?? [];
|
const objects = q.data?.objects ?? [];
|
||||||
const domains = [...new Set(objects.map((o) => o.domain))];
|
const domains = [...new Set(objects.map((o) => o.domain))].sort();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<QueryGate query={q}>
|
<QueryGate query={q}>
|
||||||
<>
|
<>
|
||||||
<Note>
|
<Note>
|
||||||
数据<b>不设箱、靠域</b>:域是承载的划分,对象由活动声明物化——最大化合并、只增不破坏。
|
数据<b>不设箱、靠域</b>:域是承载的划分,对象由活动声明物化——最大化合并、只增不破坏。
|
||||||
数据可以自生长,权力不可以自生长。
|
这里改结构;<b>行数据在运营中心 · 台账看</b>。
|
||||||
</Note>
|
</Note>
|
||||||
{domains.map((d) => (
|
{domains.map((d) => (
|
||||||
<div key={d} className="g-sec">
|
<div key={d} className="g-sec">
|
||||||
<div className="g-band">
|
<div className="g-band">
|
||||||
<Ico name="database" s={13} />
|
<Ico name="database" s={13} />
|
||||||
{d}
|
{d}
|
||||||
<span className="x">{objects.filter((o) => o.domain === d).length} 个对象</span>
|
<span className="x">{objects.filter((o) => o.domain === d).length} 个对象 · 发布即在运营中心长出一位</span>
|
||||||
|
</div>
|
||||||
|
<NList>
|
||||||
|
{objects
|
||||||
|
.filter((o) => o.domain === d)
|
||||||
|
.map((o) => (
|
||||||
|
<ObjectSchemaRow key={`${o.domain}.${o.object}`} domain={o.domain} object={o.object} nav={nav} />
|
||||||
|
))}
|
||||||
|
</NList>
|
||||||
</div>
|
</div>
|
||||||
<NList>
|
))}
|
||||||
{objects
|
{objects.length === 0 && (
|
||||||
.filter((o) => o.domain === d)
|
<Clear>还没有物化的对象 —— 活动第一次写入时,系统按它的声明把表建出来,然后出现在这里。</Clear>
|
||||||
.map((o) => (
|
)}
|
||||||
<NRow
|
</>
|
||||||
key={`${o.domain}.${o.object}`}
|
|
||||||
tone="brand"
|
|
||||||
ico="table"
|
|
||||||
title={o.object}
|
|
||||||
sub={`${o.domain}.${o.object}`}
|
|
||||||
onClick={() => nav.push({ k: 'obj', id: `${o.domain}.${o.object}` })}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</NList>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
{objects.length === 0 && <Clear>尚无物化对象——活动首次写入时按声明物化。</Clear>}
|
|
||||||
</>
|
|
||||||
</QueryGate>
|
</QueryGate>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- ⑧ 记忆管理 ---------------- */
|
/** 一个对象的结构一行:列数 / 主键 —— 点开看完整模式,仍不含行数据 */
|
||||||
|
function ObjectSchemaRow({ domain, object, nav }: { domain: string; object: string; nav: Nav }) {
|
||||||
|
const page = useGovLedger(domain, object);
|
||||||
|
const columns = page.data?.columns ?? [];
|
||||||
|
const pk = page.data?.primaryKey ?? [];
|
||||||
|
return (
|
||||||
|
<NRow
|
||||||
|
tone="brand"
|
||||||
|
ico="table"
|
||||||
|
title={object}
|
||||||
|
sub={`${domain}.${object}`}
|
||||||
|
cols={[
|
||||||
|
{ k: '列', v: columns.length === 0 ? '—' : String(columns.length) },
|
||||||
|
{ k: '主键', v: pk.length === 0 ? '—' : pk.join(', ') },
|
||||||
|
]}
|
||||||
|
onClick={() => nav.push({ k: 'obj', id: `${domain}.${object}` })}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== 运营侧:台账(某一个域) ==================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 台账的一个域 —— 壳层把左轨选中的域名当页签传进来。
|
||||||
|
* 这里看的是运营事实:这张表现在多少行,点进去逐行看。
|
||||||
|
*/
|
||||||
|
export function LedgersHome({ tab, nav }: HomeProps) {
|
||||||
|
const q = useGovObjects();
|
||||||
|
const domain = tab;
|
||||||
|
const inDomain = (q.data?.objects ?? []).filter((o) => o.domain === domain);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<QueryGate query={q}>
|
||||||
|
<>
|
||||||
|
<Note>
|
||||||
|
<b>{domain}</b> 域的台账:行数据、主键与列结构。结构要改去设计中心 · 数据域;
|
||||||
|
这里做的是运营动作 —— 查行、看版本、追引用。
|
||||||
|
</Note>
|
||||||
|
{inDomain.length === 0 ? (
|
||||||
|
<Clear>这个域还没有对象 —— 对象随活动首次写入而物化,建好就出现在这里。</Clear>
|
||||||
|
) : (
|
||||||
|
<NList>
|
||||||
|
{inDomain.map((o) => (
|
||||||
|
<LedgerObjectRow key={`${o.domain}.${o.object}`} domain={o.domain} object={o.object} nav={nav} />
|
||||||
|
))}
|
||||||
|
</NList>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
</QueryGate>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 一个台账对象一行:行数是运营侧最想先看到的那个数 */
|
||||||
|
function LedgerObjectRow({ domain, object, nav }: { domain: string; object: string; nav: Nav }) {
|
||||||
|
const page = useGovLedger(domain, object);
|
||||||
|
const total = page.data?.total;
|
||||||
|
return (
|
||||||
|
<NRow
|
||||||
|
tone={total !== undefined && total > 0 ? 'brand' : 'mute'}
|
||||||
|
ico="table"
|
||||||
|
title={object}
|
||||||
|
sub={`${domain}.${object}`}
|
||||||
|
cols={[
|
||||||
|
{ k: '行数', v: total === undefined ? '…' : String(total) },
|
||||||
|
{ k: '列', v: String((page.data?.columns ?? []).length) },
|
||||||
|
]}
|
||||||
|
right={<Chip tone={total !== undefined && total > 0 ? 'ok' : 'mute'}>{total ?? 0} 行</Chip>}
|
||||||
|
onClick={() => nav.push({ k: 'obj', id: `${domain}.${object}` })}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== 对象详情(两面共用) ==================== */
|
||||||
|
|
||||||
export function ObjPage({ id }: { id: string }) {
|
export function ObjPage({ id }: { id: string }) {
|
||||||
const [domain, object] = id.split('.');
|
const [domain, object] = id.split('.');
|
||||||
@@ -90,32 +167,33 @@ export function ObjPage({ id }: { id: string }) {
|
|||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<Sec ico="table" title="记录浏览" extra={`前 ${(page.data?.rows ?? []).length} 行`}>
|
<Sec ico="table" title="记录浏览" extra={`前 ${(page.data?.rows ?? []).length} 行`}>
|
||||||
<div className="g-dbwrap">
|
{(page.data?.rows ?? []).length === 0 ? (
|
||||||
<table className="g-dbtable">
|
<Clear>这张表还没有记录 —— 任务放行后,过门的事实才落进来。</Clear>
|
||||||
<thead>
|
) : (
|
||||||
<tr>
|
<div className="g-dbwrap">
|
||||||
{columns.map((c) => (
|
<table className="g-dbtable">
|
||||||
<th key={c.name}>{c.name}</th>
|
<thead>
|
||||||
))}
|
<tr>
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{(page.data?.rows ?? []).map((row, i) => (
|
|
||||||
<tr key={i}>
|
|
||||||
{columns.map((c) => (
|
{columns.map((c) => (
|
||||||
<td key={c.name} className="g-mono">
|
<th key={c.name}>{c.name}</th>
|
||||||
{String(row[c.name] ?? '')}
|
|
||||||
</td>
|
|
||||||
))}
|
))}
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
</thead>
|
||||||
</tbody>
|
<tbody>
|
||||||
</table>
|
{(page.data?.rows ?? []).map((row, i) => (
|
||||||
</div>
|
<tr key={i}>
|
||||||
|
{columns.map((c) => (
|
||||||
|
<td key={c.name} className="g-mono">
|
||||||
|
{String(row[c.name] ?? '')}
|
||||||
|
</td>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Sec>
|
</Sec>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- 记忆条目 ---------------- */
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user