From b328341a86f42799b1fd2e9436f9d3456d59bcc5 Mon Sep 17 00:00:00 2001 From: ThearchyHelios Date: Sun, 4 Oct 2026 02:15:23 +0800 Subject: [PATCH] =?UTF-8?q?=E6=95=B0=E6=8D=AE=E7=9B=AE=E5=BD=95=20F2b=20/?= =?UTF-8?q?=20B9=EF=BC=9A=E5=9C=A8=E7=9B=AE=E5=BD=95=E9=A1=B5=E6=94=B9?= =?UTF-8?q?=E8=BF=87=E7=9B=AE=E5=BD=95=EF=BC=88=E7=BC=96=E8=BE=91=E4=BF=9D?= =?UTF-8?q?=E5=AD=98=E3=80=81=E7=A1=AE=E8=AE=A4=E3=80=81=E9=A9=B3=E5=9B=9E?= =?UTF-8?q?=E3=80=81=E5=A1=AB=E7=A0=81=E5=80=BC=EF=BC=89=E4=B9=8B=E5=90=8E?= =?UTF-8?q?=EF=BC=8C=E8=A1=A8=E5=A4=B4=E4=B9=9F=E8=AF=B4=E3=80=8C=E5=B7=B2?= =?UTF-8?q?=E4=BF=9D=E5=AD=98=E3=80=82N=20=E4=B8=AA=E5=B7=B2=E5=8F=91?= =?UTF-8?q?=E5=B8=83=E6=A8=A1=E6=9D=BF=E5=BC=95=E7=94=A8=E8=BF=99=E5=BC=A0?= =?UTF-8?q?=E8=A1=A8=EF=BC=8C=E4=B8=8B=E6=AC=A1=E6=AD=A3=E5=BC=8F=E8=BF=90?= =?UTF-8?q?=E8=A1=8C=E6=97=B6=E4=BC=9A=E6=8F=90=E7=A4=BA=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E7=9B=AE=E5=BD=95=E6=9C=89=E5=8F=98=E5=8C=96=E3=80=8D=EF=BC=8C?= =?UTF-8?q?=E5=92=8C=E5=8A=A9=E6=89=8B=E5=8D=A1=E7=89=87=E4=BF=9D=E5=AD=98?= =?UTF-8?q?=E5=90=8E=E5=90=8C=E4=B8=80=E5=8F=A5=EF=BC=9B=E5=B8=A6=E3=80=8C?= =?UTF-8?q?=E6=9F=A5=E7=9C=8B=E6=A8=A1=E6=9D=BF=E3=80=8D=E8=B7=B3=E5=88=B0?= =?UTF-8?q?=E5=BD=B1=E5=93=8D=E9=9D=A2=E4=B8=80=E6=A0=8F=EF=BC=8C=E5=8F=AF?= =?UTF-8?q?=E6=94=B6=E8=B5=B7=EF=BC=8C=E6=B2=A1=E6=9C=89=E6=A8=A1=E6=9D=BF?= =?UTF-8?q?=E5=BC=95=E7=94=A8=E6=97=B6=E4=B8=8D=E8=AF=B4=EF=BC=9B=E8=A1=A5?= =?UTF-8?q?=E6=A3=80=E6=9F=A5=E8=84=9A=E6=9C=AC=E7=9A=84=E6=96=AD=E8=A8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/CatalogImpact.tsx | 8 +++-- frontend/src/lib/terms.ts | 4 +++ frontend/src/pages/catalog/TableDetail.tsx | 40 ++++++++++++++++++++-- scripts/check-catalog.mjs | 21 ++++++++++++ 4 files changed, 68 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/CatalogImpact.tsx b/frontend/src/components/CatalogImpact.tsx index 97542951..a7f64c5e 100644 --- a/frontend/src/components/CatalogImpact.tsx +++ b/frontend/src/components/CatalogImpact.tsx @@ -18,24 +18,28 @@ import { Skeleton } from './ui' const SHOWN = 5 -export function CatalogImpactList({ sourceId, table, refreshKey, compact = false }: { +export function CatalogImpactList({ sourceId, table, refreshKey, compact = false, onLoad }: { sourceId: string table: string /** 变了就重新统计(表详情传目录版本:保存之后刷新) */ refreshKey?: number | string /** 卡片里的紧凑版:只列前几个模板,节点收成一行 */ compact?: boolean + /** 统计好了:数据目录页据此在保存之后说「N 个已发布模板引用这张表」 */ + onLoad?: (d: CatalogImpact) => void }) { const [data, setData] = useState(null) const [error, setError] = useState(null) const [all, setAll] = useState(false) const seq = useRef(0) + const onLoadRef = useRef(onLoad) + onLoadRef.current = onLoad const load = useCallback(() => { const mine = ++seq.current setError(null) api.dataCatalog.impact(sourceId, table) - .then((d) => { if (mine === seq.current) setData(d) }) + .then((d) => { if (mine === seq.current) { setData(d); onLoadRef.current?.(d) } }) .catch((e) => { if (mine === seq.current) setError(e) }) }, [sourceId, table]) diff --git a/frontend/src/lib/terms.ts b/frontend/src/lib/terms.ts index efd28dc6..aee96903 100644 --- a/frontend/src/lib/terms.ts +++ b/frontend/src/lib/terms.ts @@ -2525,6 +2525,10 @@ export const CATALOG_UI_TEXT = { codesPendingAfter: '确认后这些码值仍显示「含义待填写」,之后可以再填写', confirmColumnTitle: (col: string, n: number) => `确认列 ${col} 的 ${formatNumber(n)} 项推断?`, confirmCodesTitle: (where: string) => `确认${where}?`, + // ---- 在目录页改过之后:和助手卡片保存后同一句,说有几个已发布模板引用这张表 + savedImpact: (n: number) => `已保存。${formatNumber(n)} 个已发布模板引用这张表,下次正式运行时会提示数据目录有变化`, + savedImpactShow: '查看模板', + savedImpactDismiss: '收起这条提示', } // =========================================================================== diff --git a/frontend/src/pages/catalog/TableDetail.tsx b/frontend/src/pages/catalog/TableDetail.tsx index 2da1df27..e4ac8c95 100644 --- a/frontend/src/pages/catalog/TableDetail.tsx +++ b/frontend/src/pages/catalog/TableDetail.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import type { ReactNode } from 'react' -import { AlertTriangle, ArrowLeft, ChevronLeft, ChevronRight, ListChecks, Lock, Pencil, Plus, RotateCw, Search } from 'lucide-react' +import { AlertTriangle, ArrowLeft, ChevronLeft, ChevronRight, GitBranch, ListChecks, Lock, Pencil, Plus, RotateCw, Search, X } from 'lucide-react' import clsx from 'clsx' import { ApiError, api } from '../../api/client' import type { @@ -95,6 +95,12 @@ export function TableDetail({ sourceId, table, rows, drafted, fillCodes, onFillD setCodesFor(null) if (seq != null && back) onFillDoneRef.current?.(seq, pending) }, []) + /** + * 在这一页改过目录(保存、确认、驳回、填码值):改了就升版本,从已发布版本发起的正式运行会提醒「数据目录有变化」。 + * 改完在表头说一句有几个已发布模板引用这张表(和助手的「建议更新数据目录」卡片保存后同一句),版本号对上才说 + */ + const [savedAt, setSavedAt] = useState(null) + const [impact, setImpact] = useState<{ version: number; n: number } | null>(null) const loadSeq = useRef(0) const onDetailRef = useRef(onDetail) onDetailRef.current = onDetail @@ -125,6 +131,8 @@ export function TableDetail({ sourceId, table, rows, drafted, fillCodes, onFillD setLive('') setCodesFor(null) fillOpen.current = null + setSavedAt(null) + setImpact(null) void load() }, [load]) @@ -172,6 +180,7 @@ export function TableDetail({ sourceId, table, rows, drafted, fillCodes, onFillD setDetail(d) setConflict(null) onDetailRef.current(d) + setSavedAt(d.version) return d } catch (e) { if (e instanceof ApiError && e.status === 409) setConflict(editingRef.current ? 'edit' : 'review') @@ -236,6 +245,7 @@ export function TableDetail({ sourceId, table, rows, drafted, fillCodes, onFillD setDetail(next) setConflict(null) onDetailRef.current(next) + setSavedAt(next.version) closeCodes(pendingCodes(value)) const filled = Math.max(0, pendingCodes(item.value) - pendingCodes(value)) toast.ok(KT.saved(filled)) @@ -342,6 +352,13 @@ export function TableDetail({ sourceId, table, rows, drafted, fillCodes, onFillD await load() } + /** 表头那句影响面提示的「查看」:滚到「引用这张表的模板」一栏,焦点给它的标题 */ + const showImpact = () => { + const el = document.getElementById('catalog-sec-impact') + el?.scrollIntoView({ block: 'start', behavior: 'smooth' }) + el?.focus({ preventScroll: true }) + } + const targetOf = (name: string) => { const lower = name.toLowerCase() return rows.find((r) => r.table_name === name || r.qualified === name) @@ -385,6 +402,22 @@ export function TableDetail({ sourceId, table, rows, drafted, fillCodes, onFillD + {!edit && savedAt === detail.version && impact?.version === detail.version && impact.n > 0 && ( +
+ + + {UT.savedImpact(impact.n)} + + + +
+ )} {!edit && (
{counts.proposed > 0 && ( @@ -482,10 +515,11 @@ export function TableDetail({ sourceId, table, rows, drafted, fillCodes, onFillD
-

{CATALOG_IMPACT_TEXT.title}

+

{CATALOG_IMPACT_TEXT.title}

{CATALOG_IMPACT_TEXT.hint}
- + setImpact({ version: detail.version, n: d.templates.length })} />
diff --git a/scripts/check-catalog.mjs b/scripts/check-catalog.mjs index 7d5e0f75..b34df21a 100644 --- a/scripts/check-catalog.mjs +++ b/scripts/check-catalog.mjs @@ -925,8 +925,27 @@ await section('数据目录 · 引用这张表的模板(影响面)', async ( // 开发模式下 StrictMode 会把挂载时的取数做两遍,只查有没有取、没有写 check('统计影响面只读', writes(sent, /impact/).length === 0 && count(sent, /\/visits\/impact$/) >= 1, `${count(sent, /\/visits\/impact$/)} 次`) + // 在目录页改过(这里是编辑保存):表头说有几个已发布模板引用这张表,和助手卡片保存后同一句 + const savedHint = page.locator('[data-catalog-saved-impact]') + check('没改过:表头不说影响面', await savedHint.count() === 0) + await page.locator('[data-catalog-edit]').click() + await page.locator('[data-input="grain"]').fill('一张门票的一次入园(检查)') + await page.locator('[data-catalog-save]').click() + check('在目录页编辑保存后:表头说「2 个已发布模板引用这张表,下次正式运行时会提示数据目录有变化」', + await until(async () => (await savedHint.count()) === 1) + && (await savedHint.innerText()).includes('2 个已发布模板引用这张表,下次正式运行时会提示数据目录有变化'), + await savedHint.innerText().catch(() => '')) + await savedHint.locator('[data-catalog-saved-impact-show]').click() + check('……点「查看模板」:焦点落到「引用这张表的模板」一栏', await until(async () => page.evaluate(() => document.activeElement?.id === 'catalog-sec-impact'), 2000)) + await savedHint.locator('[data-catalog-saved-impact-dismiss]').click() + check('……可以收起', await savedHint.count() === 0) + // 单项确认也升版本:同样说 + await openMark(page, 'description') + await page.locator('[data-item-panel] [data-review-action="confirm"]').click() + check('单项确认之后同样说', await until(async () => (await savedHint.count()) === 1)) // 没有模板引用的表:直说没有 await openDetail(page, S1, 'orders') + check('换一张表:上一张的提示不带过来', await savedHint.count() === 0) check('没有模板引用:直说没有', (await page.locator('[data-catalog-section="impact"] [data-catalog-impact="0"]').innerText()) .includes('没有已发布或受管的模板引用这张表')) // 表详情保存之后(版本变了)重新统计 @@ -936,6 +955,8 @@ await section('数据目录 · 引用这张表的模板(影响面)', async ( await page.locator('[data-catalog-save]').click() await until(async () => count(sent, /\/orders\/impact$/) > before, 3000) check('保存之后重新统计影响面', count(sent, /\/orders\/impact$/) > before, `${before} → ${count(sent, /\/orders\/impact$/)}`) + await sleep(300) + check('……没有模板引用的表保存后不说影响面', await savedHint.count() === 0) check('没有运行时报错', errors.length === 0, errors.join(' | ')) await close() })