Author SHA1 Message Date
shangfangjian d7d903b35b Merge branch 'main' into fix-completeness-refresh 2026-09-22 19:54:39 +08:00
WorkBuddy a6740f6140 fix(admin): 数据完整性页补齐自动刷新(文案承诺但未实现)
页面描述写着「每 5 秒自动刷新」,实际只有挂载时加载一次 + 手动按钮
—— 采集完成后停在页面上的数字永远不动。

- 补 5s 自动轮询(可开关,复用 Logs 页的 checkbox 样式);已有数据时
  刷新不闪骨架(loading && !data 条件已具备)
- 采集页任务终态广播 profeto:ingest-done 事件,完整性页监听后
  立即刷新,不必等下一个 5s 周期
2026-09-22 19:52:39 +08:00
shangfangjian 38e1c6c31f Merge pull request 'fix(quality): 质量检查引用已删除的 MatchStats.id 必然 500 + 检查无日志' (#20) from fix-data-quality-check into main
Reviewed-on: #20
2026-09-22 18:20:49 +08:00
2 changed files with 35 additions and 3 deletions
+4
View File
@@ -94,6 +94,10 @@ export default function CollectionPage() {
setTaskStatus(job.status === 'success' ? 'done' : 'error') setTaskStatus(job.status === 'success' ? 'done' : 'error')
stopPolling() stopPolling()
loadRecentJobs() // 终态后刷新历史列表 loadRecentJobs() // 终态后刷新历史列表
// 广播采集终态:数据完整性等依赖页立即刷新,不必等轮询周期
window.dispatchEvent(new CustomEvent('profeto:ingest-done', {
detail: { jobId: job.id, status: job.status },
}))
} }
} catch { /* 单次轮询失败不影响后续 */ } } catch { /* 单次轮询失败不影响后续 */ }
} }
+31 -3
View File
@@ -55,6 +55,9 @@ export default function DataCompletenessPage() {
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
const [highlightedLeague, setHighlightedLeague] = useState<string | null>(null) const [highlightedLeague, setHighlightedLeague] = useState<string | null>(null)
// 页面文案承诺「每 5 秒自动刷新」,此前并未实现(仅挂载加载一次),
// 采集完成后数字不动 —— 现补齐:5s 轮询 + 采集完成事件即时刷新。
const [autoRefresh, setAutoRefresh] = useState(true)
const leagueRefs = useRef<Record<string, HTMLDivElement | null>>({}) const leagueRefs = useRef<Record<string, HTMLDivElement | null>>({})
const load = useCallback(async () => { const load = useCallback(async () => {
@@ -72,6 +75,20 @@ export default function DataCompletenessPage() {
useEffect(() => { load() }, [load]) useEffect(() => { load() }, [load])
// 5s 自动轮询(与页面文案一致);已有数据时刷新不闪骨架
useEffect(() => {
if (!autoRefresh) return
const t = setInterval(load, 5_000)
return () => clearInterval(t)
}, [autoRefresh, load])
// 采集页任务终态广播 → 立即刷新(不等下一个 5s 周期)
useEffect(() => {
const onIngestDone = () => load()
window.addEventListener('profeto:ingest-done', onIngestDone)
return () => window.removeEventListener('profeto:ingest-done', onIngestDone)
}, [load])
// 点击问题项 → 滚动到对应联赛卡片并高亮 // 点击问题项 → 滚动到对应联赛卡片并高亮
const scrollToLeague = useCallback((code: string) => { const scrollToLeague = useCallback((code: string) => {
setHighlightedLeague(code) setHighlightedLeague(code)
@@ -92,9 +109,20 @@ export default function DataCompletenessPage() {
title="数据完整性" title="数据完整性"
description="按联赛统计 bzzoiro 数据采集覆盖度。每 5 秒自动刷新,或点击右上角按钮手动刷新。" description="按联赛统计 bzzoiro 数据采集覆盖度。每 5 秒自动刷新,或点击右上角按钮手动刷新。"
action={ action={
<button onClick={load} disabled={loading} className="btn-sm btn-outline"> <div className="flex items-center gap-2">
{loading ? <><Spinner /> </> : '刷新'} <label className="flex cursor-pointer items-center gap-1.5 text-2xs text-ink-500">
</button> <input
type="checkbox"
checked={autoRefresh}
onChange={e => setAutoRefresh(e.target.checked)}
className="accent-current"
/>
5s
</label>
<button onClick={load} disabled={loading} className="btn-sm btn-outline">
{loading ? <><Spinner /> </> : '刷新'}
</button>
</div>
} }
/> />