Files
tps-dashboard/frontend/src/pages/Analytics/DiagnosisReport.jsx
T

184 lines
5.2 KiB
React
Raw Normal View History

import { useState, useEffect } from 'react'
import { useSearchParams, useNavigate } from 'react-router-dom'
import { Spin } from 'antd'
import Markdown from 'react-markdown'
import { generateDiagnosisReport } from '../../services/analyticsService'
import './DiagnosisReport.css'
/**
* AI 诊断报告详情页
* - 从 URL query 读取 year、start、end
* - 调用 generateDiagnosisReport(命中缓存则秒出)
* - 用 react-markdown 渲染完整报告
*/
const TIER_COLOR = {
danger: '#7aa874',
on_target: '#5b8db8',
excellent: '#c0584f',
}
function DiagnosisReport() {
const [searchParams] = useSearchParams()
const navigate = useNavigate()
const year = Number(searchParams.get('year'))
const start = Number(searchParams.get('start'))
const end = Number(searchParams.get('end'))
const [report, setReport] = useState(null)
const [loading, setLoading] = useState(true)
const [regenerating, setRegenerating] = useState(false)
const [error, setError] = useState(null)
const fetchReport = (force = false) => {
if (!year || !start || !end) {
setError('缺少必要参数(year / start / end')
setLoading(false)
return
}
if (force) {
setRegenerating(true)
} else {
setLoading(true)
}
setError(null)
generateDiagnosisReport({ year, ep_start: start, ep_end: end, force })
.then((data) => {
if (data.error) {
setError(data.error)
} else {
setReport(data)
}
})
.catch((err) => {
setError(err?.response?.data?.detail || '报告生成失败')
})
.finally(() => {
setLoading(false)
setRegenerating(false)
})
}
useEffect(() => {
fetchReport(false)
}, [year, start, end])
// 摸高完成率
const stretchPct =
report?.avg_share && report?.highest
? null // 没有 stretch_target 直接信息,从 tier 推断
: null
// 从 report 中计算摸高完成率(如果有 avg_share 和 episode_count
// 实际上后端没返回 stretch_target,所以这里用 pass_count / episode_count 做达标率展示
const passRate =
report?.episode_count > 0
? `${report.pass_count}/${report.episode_count}`
: '—'
const tierColor = report?.tier ? TIER_COLOR[report.tier] : '#999'
// 从 report_markdown 中提取第一句作为范围描述的补充
const startDate = report?.report_markdown
? '' // 已经在 header 中展示了期号范围
: ''
if (loading) {
return (
<div className="diagnosis-report-page">
<div className="diagnosis-report-loading">
<Spin size="large" />
<p>正在生成诊断报告预计 15-30 </p>
</div>
</div>
)
}
if (error) {
return (
<div className="diagnosis-report-page">
<span
className="diagnosis-report-back"
onClick={() => navigate('/analytics')}
>
返回收视分析
</span>
<div className="diagnosis-report-error">
<p>{error}</p>
</div>
</div>
)
}
return (
<div className="diagnosis-report-page">
{/* 返回链接 */}
<span
className="diagnosis-report-back"
onClick={() => navigate('/analytics')}
>
返回收视分析
</span>
{/* 头部 */}
<div className="diagnosis-report-header">
<h1>收视诊断报告</h1>
<p className="diagnosis-report-range">
{start} {end} {year} {report?.episode_count}
</p>
{report?.generated_at && (
<p className="diagnosis-report-date">
生成时间{new Date(report.generated_at).toLocaleString('zh-CN')}
{report?.model && ` 模型:${report.model}`}
</p>
)}
</div>
{/* 指标条 */}
<div className="diagnosis-report-kpi-bar">
<div className="diagnosis-report-kpi-item">
<div className="kpi-value" style={{ color: tierColor }}>
{report?.avg_share?.toFixed(4) || '—'}
</div>
<div className="kpi-label">平均份额</div>
</div>
<div className="diagnosis-report-kpi-item">
<div className="kpi-value">{passRate}</div>
<div className="kpi-label">达标期数</div>
</div>
<div className="diagnosis-report-kpi-item">
<div className="kpi-value" style={{ color: '#c0584f' }}>
{report?.highest
? `${report.highest.share.toFixed(3)}(第${report.highest.ep}期)`
: '—'}
</div>
<div className="kpi-label">最高份额</div>
</div>
</div>
{/* 报告正文 */}
<div className="diagnosis-report-body">
<Markdown>{report?.report_markdown || ''}</Markdown>
</div>
{/* 重新生成 */}
<div className="diagnosis-report-actions">
<button onClick={() => fetchReport(true)} disabled={regenerating}>
{regenerating ? '正在重新生成…' : '重新生成'}
</button>
</div>
{/* 免责声明 */}
{report?.disclaimer && (
<p className="diagnosis-report-disclaimer">
⚠️ {report.disclaimer}
</p>
)}
</div>
)
}
export default DiagnosisReport