数据目录 F2b / B9:在目录页改过目录(编辑保存、确认、驳回、填码值)之后,表头也说「已保存。N 个已发布模板引用这张表,下次正式运行时会提示数据目录有变化」,和助手卡片保存后同一句;带「查看模板」跳到影响面一栏,可收起,没有模板引用时不说;补检查脚本的断言

This commit is contained in:
ThearchyHelios
2026-10-04 02:15:23 +08:00
parent 1483af474c
commit b328341a86
4 changed files with 68 additions and 5 deletions
+6 -2
View File
@@ -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<CatalogImpact | null>(null)
const [error, setError] = useState<unknown>(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])
+4
View File
@@ -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: '收起这条提示',
}
// ===========================================================================
+37 -3
View File
@@ -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<number | null>(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
</button>
</div>
</div>
{!edit && savedAt === detail.version && impact?.version === detail.version && impact.n > 0 && (
<div className="flex items-start gap-1.5 rounded-md border px-2 py-1.5 text-2xs leading-relaxed text-dim" role="status"
style={{ borderColor: 'color-mix(in srgb, var(--accent) 35%, var(--border))', background: 'color-mix(in srgb, var(--accent) 6%, transparent)' }}
data-catalog-saved-impact={impact.n}>
<GitBranch size={11} className="mt-0.5 shrink-0" style={{ color: 'var(--accent)' }} aria-hidden />
<span className="min-w-0 flex-1">
{UT.savedImpact(impact.n)}
<button type="button" className="ml-1.5 rounded px-0.5 text-[var(--accent)] underline-offset-2 outline-none hover:underline focus-visible:ring-2 focus-visible:ring-[var(--accent)]"
onClick={showImpact} data-catalog-saved-impact-show="">{UT.savedImpactShow}</button>
</span>
<button type="button" className="shrink-0 rounded p-0.5 text-faint outline-none hover:bg-hover hover:text-fg focus-visible:ring-2 focus-visible:ring-[var(--accent)]"
onClick={() => setSavedAt(null)} aria-label={UT.savedImpactDismiss} title={UT.savedImpactDismiss} data-catalog-saved-impact-dismiss="">
<X size={10} aria-hidden />
</button>
</div>
)}
{!edit && (
<div className="flex flex-wrap items-center gap-1.5">
{counts.proposed > 0 && (
@@ -482,10 +515,11 @@ export function TableDetail({ sourceId, table, rows, drafted, fillCodes, onFillD
<section aria-labelledby="catalog-sec-impact" data-catalog-section="impact">
<div className="mb-2 flex flex-wrap items-baseline gap-2">
<h3 id="catalog-sec-impact" className="text-xs font-semibold">{CATALOG_IMPACT_TEXT.title}</h3>
<h3 id="catalog-sec-impact" className="text-xs font-semibold outline-none" tabIndex={-1}>{CATALOG_IMPACT_TEXT.title}</h3>
<span className="text-2xs text-faint">{CATALOG_IMPACT_TEXT.hint}</span>
</div>
<CatalogImpactList sourceId={sourceId} table={detail.table_name} refreshKey={detail.version} />
<CatalogImpactList sourceId={sourceId} table={detail.table_name} refreshKey={detail.version}
onLoad={(d) => setImpact({ version: detail.version, n: d.templates.length })} />
</section>
</div>
</div>
+21
View File
@@ -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()
})