From e30d28a0d694fcde612d5cdae4a47bdc79214a70 Mon Sep 17 00:00:00 2001 From: ThearchyHelios Date: Sat, 3 Oct 2026 11:55:45 +0800 Subject: [PATCH] =?UTF-8?q?=E6=95=B0=E6=8D=AE=E7=9B=AE=E5=BD=95=E9=98=B6?= =?UTF-8?q?=E6=AE=B5=204A=20=E6=91=98=E8=A6=81=E6=8F=90=E7=A4=BA=EF=BC=9A?= =?UTF-8?q?=E7=9B=AE=E5=BD=95=E9=A1=B5=E9=A1=B6=E9=83=A8=E5=86=99=E6=98=8E?= =?UTF-8?q?=E8=BF=90=E8=A1=8C=E4=B8=AD=E6=9F=A5=E8=AF=A2=E8=BF=87=E7=9A=84?= =?UTF-8?q?=E8=A1=A8=E9=87=8C=E8=BF=98=E6=9C=89=E5=87=A0=E5=BC=A0=E6=9C=89?= =?UTF-8?q?=E6=8E=A8=E6=96=AD=E9=A1=B9=E6=9C=AA=E7=A1=AE=E8=AE=A4=E3=80=81?= =?UTF-8?q?=E5=87=A0=E5=BC=A0=E6=B2=A1=E6=9C=89=E7=9B=AE=E5=BD=95=EF=BC=8C?= =?UTF-8?q?=E7=82=B9=E5=87=BB=E5=88=87=E5=88=B0=E5=AF=B9=E5=BA=94=E7=AD=9B?= =?UTF-8?q?=E9=80=89=E5=B9=B6=E6=8C=89=E4=BD=BF=E7=94=A8=E6=AC=A1=E6=95=B0?= =?UTF-8?q?=E6=8E=92=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/lib/terms.ts | 7 +++ frontend/src/pages/catalog/CatalogPage.tsx | 56 +++++++++++++++++++++- frontend/src/pages/catalog/model.ts | 16 +++++++ scripts/check-catalog.mjs | 37 ++++++++++++++ 4 files changed, 114 insertions(+), 2 deletions(-) diff --git a/frontend/src/lib/terms.ts b/frontend/src/lib/terms.ts index d3e14c11..56bc8e08 100644 --- a/frontend/src/lib/terms.ts +++ b/frontend/src/lib/terms.ts @@ -2009,6 +2009,13 @@ export const CATALOG_TEXT = { countTitle: (label: string, n: number) => `${label} ${formatNumber(n)} 项`, noMatch: '没有符合条件的表', noMatchBody: '请换个关键词或筛选条件。', + // ---- 用到但没确认(页面顶部的摘要) + usageLead: (used: number) => `运行中查询过的 ${formatNumber(used)} 张表里,`, + usagePending: (n: number) => `有 ${formatNumber(n)} 张还有推断项未确认`, + usageNone: (n: number) => `${formatNumber(n)} 张还没有目录`, + usageAllDone: (used: number) => `运行中查询过的 ${formatNumber(used)} 张表都已确认`, + usageHintPending: '筛选有未确认项的表,按使用次数排序', + usageHintNone: '筛选没有目录的表,按使用次数排序', // ---- 概览(没有选中表时) overviewTitle: '选择一张表开始审阅', overviewBody: '按使用次数从高到低逐表确认。推断的项只作提示,确认后才参与 SQL 检查。', diff --git a/frontend/src/pages/catalog/CatalogPage.tsx b/frontend/src/pages/catalog/CatalogPage.tsx index 7b83c11b..d1246eae 100644 --- a/frontend/src/pages/catalog/CatalogPage.tsx +++ b/frontend/src/pages/catalog/CatalogPage.tsx @@ -1,7 +1,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import type { ReactNode } from 'react' import { useNavigate, useParams } from 'react-router-dom' -import { ArrowLeft, BookMarked, Database, Sparkles } from 'lucide-react' +import { ArrowLeft, BookMarked, Database, ListFilter as ListFilterIcon, Sparkles } from 'lucide-react' import clsx from 'clsx' import { ApiError, api } from '../../api/client' import type { CatalogDetail, CatalogList, CatalogTableRow } from '../../types' @@ -12,7 +12,7 @@ import { StatusLegend, SystemNotesNotice } from './parts' import { DraftDialog } from './DraftDialog' import { TableDetail } from './TableDetail' import { TableIndex } from './TableIndex' -import { filterCounts, progressOf, rowFromDetail, visibleRows } from './model' +import { filterCounts, progressOf, rowFromDetail, usageSummary, visibleRows } from './model' import type { ListFilter, ListSort } from './model' // =========================================================================== @@ -96,6 +96,18 @@ export function CatalogPage() { return [list[i - 1]?.table_name ?? null, list[i + 1]?.table_name ?? null] }, [table, visible, rows, sort]) + const usage = useMemo(() => usageSummary(rows), [rows]) + /** + * 顶部摘要点进来:清掉搜索词,切到对应的筛选,按使用次数排——用到最多的排在最前。窄屏一次只显示一栏, + * 正在看表详情时回到清单才看得到筛选结果;宽屏两栏都在,正在看的表不动 + */ + const focusUsed = useCallback((f: ListFilter) => { + setQuery('') + setFilter(f) + setSort('usage') + if (table && typeof matchMedia === 'function' && matchMedia('(max-width: 1023px)').matches) navigate(base) + }, [table, navigate, base]) + const filtered = !!query.trim() || filter !== 'all' const empty = rows.length > 0 && rows.every((r) => progressOf(r.counts) === 'none') const hasTables = rows.length > 0 @@ -188,6 +200,7 @@ export function CatalogPage() { )} /> + {hasTables && usage.used > 0 && !empty && } {body} {drafting && data && ( void +}) { + const part = (f: 'pending' | 'none', text: string, hint: string) => ( + + ) + const todo = pending > 0 || none > 0 + return ( +
+ + {todo + ? ( +

+ {CT.usageLead(used)} + {pending > 0 && part('pending', CT.usagePending(pending), CT.usageHintPending)} + {pending > 0 && none > 0 && ','} + {none > 0 && part('none', CT.usageNone(none), CT.usageHintNone)} +

+ ) + :

{CT.usageAllDone(used)}

} +
+ ) +} + /** 没有选中表时的右栏:空目录时引导起草;否则写审阅进度,给出下一张该审的表 */ function Overview({ rows, empty, systemNotes, draft, onOpen }: { rows: CatalogTableRow[] diff --git a/frontend/src/pages/catalog/model.ts b/frontend/src/pages/catalog/model.ts index e12d6ebb..d6ffa70a 100644 --- a/frontend/src/pages/catalog/model.ts +++ b/frontend/src/pages/catalog/model.ts @@ -125,6 +125,22 @@ export function visibleRows(rows: CatalogTableRow[], q: string, filter: ListFilt }) } +/** + * 「用到但没确认」:运行中查询过的表(使用次数大于 0、还在表结构里)一共几张,其中几张还有推断项、几张还没有目录。 + * 页面顶部的摘要按它写,点进去复用清单的筛选(有未确认项 / 没有目录)和按使用次数排序,不另做一套 + */ +export function usageSummary(rows: CatalogTableRow[]): { used: number; pending: number; none: number } { + const out = { used: 0, pending: 0, none: 0 } + for (const r of rows) { + if (!r.in_schema || r.usage <= 0) continue + out.used++ + const p = progressOf(r.counts) + if (p === 'pending') out.pending++ + else if (p === 'none') out.none++ + } + return out +} + /** 各筛选项下有几张表(筛选按钮上的数字,按搜索词算) */ export function filterCounts(rows: CatalogTableRow[], q: string): Record { const out: Record = { all: 0, pending: 0, done: 0, none: 0 } diff --git a/scripts/check-catalog.mjs b/scripts/check-catalog.mjs index 697eec9b..3de98be0 100644 --- a/scripts/check-catalog.mjs +++ b/scripts/check-catalog.mjs @@ -579,6 +579,43 @@ await section('数据目录 · 表清单的排序与筛选', async () => { await close() }) +await section('数据目录 · 用到但没确认(顶部摘要)', async () => { + const state = freshState() + // 期望值照假数据现算:使用次数大于 0、还在表结构里的表,按推断项 / 没有目录分 + const used = state.tables[S1].filter((t) => !t.missing && t.usage > 0) + const progress = (t) => { const c = countsOf(t.notes ?? {}); return c.proposed ? 'pending' : c.verified + c.confirmed + c.rejected ? 'done' : 'none' } + const pending = used.filter((t) => progress(t) === 'pending').length + const none = used.filter((t) => progress(t) === 'none').length + const { page, sent, errors, close } = await open(`/data/catalog/${S1}`, { handlers: catalogHandlers(state) }) + const bar = page.locator('[data-catalog-usage-summary]') + await bar.waitFor({ timeout: 3000 }) + check('数字照清单算:查询过的表、有推断项的、没有目录的', await bar.getAttribute('data-catalog-usage-summary') === `${used.length},${pending},${none}`, + `${await bar.getAttribute('data-catalog-usage-summary')} ≠ ${used.length},${pending},${none}`) + const text = await bar.innerText() + check('写成一句话', text.includes(`运行中查询过的 ${used.length} 张表里,有 ${pending} 张还有推断项未确认,${none} 张还没有目录`), text) + await bar.locator('[data-usage-focus="pending"]').click() + check('点「还有推断项未确认」:切到「有未确认项」筛选', + (await page.locator('[data-catalog-filter]').getAttribute('data-catalog-filter')) === 'pending') + check('……排序是按使用次数', await page.locator('[data-catalog-sort]').inputValue() === 'usage') + const shown = await rowNames(page) + const expectTop = used.filter((t) => progress(t) === 'pending').sort((a, b) => b.usage - a.usage).map((t) => t.name) + check('……查询过的、有推断项的表排在最前', shown.slice(0, expectTop.length).join(',') === expectTop.join(','), + `${shown.slice(0, expectTop.length).join(',')} ≠ ${expectTop.join(',')}`) + await bar.locator('[data-usage-focus="none"]').click() + check('点「还没有目录」:切到「没有目录」筛选', (await page.locator('[data-catalog-filter]').getAttribute('data-catalog-filter')) === 'none' + && (await rowNames(page))[0] === used.filter((t) => progress(t) === 'none').sort((a, b) => b.usage - a.usage)[0].name) + check('摘要只读、不发请求', writes(sent, /./).length === 0) + // 都审完了:只写一句,不给按钮 + for (const t of state.tables[S1]) if (t.notes) t.notes = applyPut({}, { label: { value: t.notes.label?.value ?? t.name } }) + for (const t of state.tables[S1]) if (!t.notes && t.usage > 0) t.notes = { label: item(t.name, 'human') } + await goto(page, `/data/catalog/${S1}`) + await bar.waitFor({ timeout: 3000 }) + check('都确认了:写「都已确认」,没有按钮', (await bar.innerText()).includes(`运行中查询过的 ${used.length} 张表都已确认`) + && await bar.locator('[data-usage-focus]').count() === 0, await bar.innerText()) + check('没有运行时报错', errors.length === 0, errors.join(' | ')) + await close() +}) + await section('数据目录 · 起草', async () => { const state = freshState() const MODEL_ERR = '未配置模型接入,无法由助手起草数据目录。请到「设置 → 模型接入」添加'