/** * Admin 后台 - 数据管线管理页(报刊风) * * 功能: * - 采集失败记录列表(可重试) * - 数据质量检查结果 * - 手动触发质量检查 */ import { useEffect, useState, useCallback } from 'react' import { fetchDataQuality, runDataQualityCheck, fetchIngestFailures, retryIngestFailure, } from '../dal' import type { IngestFailureItem, DataQualityCheckItem } from '../dal' import { Card, CardBody, CardHeader, Badge, SectionHeader, Alert, Spinner } from '../components' export default function DataPipelinePage() { const [quality, setQuality] = useState<{ failures: IngestFailureItem[]; checks: DataQualityCheckItem[] } | null>(null) const [failures, setFailures] = useState([]) const [loading, setLoading] = useState(true) const [running, setRunning] = useState(false) const [error, setError] = useState(null) const [notice, setNotice] = useState<{ ok: boolean; text: string } | null>(null) const load = useCallback(async () => { setLoading(true) setError(null) try { const [q, f] = await Promise.all([fetchDataQuality(), fetchIngestFailures()]) setQuality(q) setFailures(f) } catch (err) { setError(err instanceof Error ? err.message : '加载失败') } finally { setLoading(false) } }, []) useEffect(() => { load() }, [load]) const handleRunCheck = async () => { setRunning(true) setNotice(null) try { const res = await runDataQualityCheck() const failed = res.checks.filter(c => !c.passed) setNotice({ ok: failed.length === 0, text: failed.length === 0 ? '数据质量检查通过' : `检查完成: ${failed.length} 项未通过`, }) await load() } catch { setNotice({ ok: false, text: '质量检查执行失败' }) } finally { setRunning(false) } } const handleRetry = async (id: number) => { setNotice(null) try { const res = await retryIngestFailure(id) setNotice({ ok: true, text: res.message }) await load() } catch { setNotice({ ok: false, text: '重试操作失败' }) } } const pendingFailures = failures.filter(f => f.status === 'pending' || f.status === 'retrying') return (
{running ? <> 检查中 : '运行质量检查'} } /> {notice && ( setNotice(null)} /> )} {error && setError(null)} />} {loading && (
)} {!loading && ( <> {/* 采集失败记录 */} 0 ? `${pendingFailures.length} 条待处理` : '暂无待处理失败记录'} /> {failures.length === 0 ? (

暂无采集失败记录

) : (
{failures.map(f => ( ))}
来源 实体类型 错误类型 重试次数 状态 操作
{f.source} {f.entity_type} {f.error_type} {f.retry_count} {f.status} {(f.status === 'pending' || f.status === 'retrying') && ( )}
)}
{/* 数据质量检查 */} {quality?.checks.length === 0 ? (

暂无质量检查记录,点击右上角「运行质量检查」触发

) : (
{quality?.checks.map(c => ( ))}
检查项 实体 结果 严重度 时间
{c.check_name} {c.entity_type} {c.passed ? '通过' : '未通过'} {c.severity} {c.checked_at ? new Date(c.checked_at).toLocaleString('zh-CN', { hour12: false }) : '—'}
)}
)}
) }