健康摘要与联赛卡片联动

- 问题项新增「定位」按钮,点击滚动到对应联赛卡片
- 被定位的联赛卡片高亮显示(ring + scale),3秒后自动消失
- 健康摘要 → 联赛详情形成闭环操作

Co-Authored-By: new-provider/LongCat-2.0 <<EMAIL>>
)
This commit is contained in:
shangfangjian
2026-09-21 00:44:19 +08:00
parent 7025338226
commit 83cc24e54b
+34 -9
View File
@@ -7,7 +7,7 @@
* 3. 覆盖是否新鲜(最近一场/最近一次采集)
*/
import { useEffect, useState, useCallback } from 'react'
import { useEffect, useState, useCallback, useRef } from 'react'
import { fetchDataCompleteness } from '../dal'
import type { DataCompletenessResponse } from '../dal'
import {
@@ -32,20 +32,20 @@ function pctColor(pct: number): string {
}
/** 根据问题描述生成可操作的修复链接 */
function getIssueAction(issue: string): { href: string; label: string } | null {
function getIssueAction(issue: string): { href: string; label: string; code: string } | null {
// 提取联赛代码(大写字母+数字,如 E0, SP1)
const codeMatch = issue.match(/\b([A-Z]{1,2}\d?)\b/)
const code = codeMatch ? codeMatch[1] : ''
if (!code) return null
if (issue.includes('无已完赛比赛')) {
return { href: `/admin/collection?task=events&league=${code}`, label: '去采集' }
return { href: `/admin/collection?task=events&league=${code}`, label: '去采集', code }
}
if (issue.includes('无统计回填') || issue.includes('统计覆盖率')) {
return { href: `/admin/collection?task=stats&league=${code}`, label: '去回填' }
return { href: `/admin/collection?task=stats&league=${code}`, label: '去回填', code }
}
if (issue.includes('无积分榜')) {
return { href: `/admin/collection?task=standings&league=${code}`, label: '去采集' }
return { href: `/admin/collection?task=standings&league=${code}`, label: '去采集', code }
}
return null
}
@@ -54,6 +54,8 @@ export default function DataCompletenessPage() {
const [data, setData] = useState<DataCompletenessResponse | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [highlightedLeague, setHighlightedLeague] = useState<string | null>(null)
const leagueRefs = useRef<Record<string, HTMLDivElement | null>>({})
const load = useCallback(async () => {
setLoading(true)
@@ -70,6 +72,14 @@ export default function DataCompletenessPage() {
useEffect(() => { load() }, [load])
// 点击问题项 → 滚动到对应联赛卡片并高亮
const scrollToLeague = useCallback((code: string) => {
setHighlightedLeague(code)
leagueRefs.current[code]?.scrollIntoView({ behavior: 'smooth', block: 'center' })
// 3秒后移除高亮
setTimeout(() => setHighlightedLeague(null), 3000)
}, [])
return (
<div className="space-y-6">
<SectionHeader
@@ -128,9 +138,17 @@ export default function DataCompletenessPage() {
title={issue.includes('良好') ? '数据良好' : '需要关注'}
message={issue}
action={action ? (
<a href={action.href} className="btn btn-sm whitespace-nowrap">
{action.label}
</a>
<div className="flex items-center gap-2">
<button
onClick={() => scrollToLeague(action.code)}
className="btn btn-sm whitespace-nowrap"
>
</button>
<a href={action.href} className="btn btn-sm whitespace-nowrap">
{action.label}
</a>
</div>
) : undefined}
/>
)
@@ -144,8 +162,14 @@ export default function DataCompletenessPage() {
{data.leagues.map(league => {
const finished = league.matches.finished
const statsPct = finished > 0 ? Math.round((league.stats.rows / finished) * 100) : 0
const isHighlighted = highlightedLeague === league.code
return (
<Card key={league.code}>
<div
key={league.code}
ref={el => { leagueRefs.current[league.code] = el }}
className={`transition-all duration-500 ${isHighlighted ? 'ring-2 ring-press scale-[1.01]' : ''}`}
>
<Card>
<CardHeader
title={league.name}
description={[
@@ -196,6 +220,7 @@ export default function DataCompletenessPage() {
)}
</CardBody>
</Card>
</div>
)
})}
</div>