import { useState, useEffect } from 'react'
import { Row, Col, Card, Avatar, Tooltip, Button, Spin } from 'antd'
import {
EyeOutlined,
FireOutlined,
CalendarOutlined,
BarChartOutlined,
PictureOutlined,
UploadOutlined,
LineChartOutlined,
AimOutlined,
} from '@ant-design/icons'
import useAuthStore from '../../stores/authStore'
import { listEpisodes } from '../../services/episodeService'
import { listTargets } from '../../services/yearlyTargetService'
import { getCurrentCover, getUpcomingSchedules } from '../../services/dashboardService'
import ChangeCoverModal from './ChangeCoverModal'
import './Dashboard.css'
/**
* 收视份额颜色判定(极易写反,禁止凭直觉):
* audience_share > stretch_target → 🔴 红(优秀)
* base_target ≤ audience_share ≤ stretch_target → 🔵 蓝(达标)
* audience_share < base_target → 🟢 绿(待提升)
* 判色取该期 air_date 所属年份的 yearly_targets 行(不是当前年)。
*/
function getShareColor(share, targets, airYear) {
const n = Number(share)
const target = (targets || []).find(t => Number(t.year) === Number(airYear))
if (!target) return '#999'
if (isNaN(n)) return '#999'
const stretch = Number(target.stretch_target)
const base = Number(target.base_target)
if (n > stretch) return '#c0584f' // 红=超过摸高目标(优秀)
if (n >= base) return '#5b8db8' // 蓝=未达摸高目标(达标)
return '#7aa874' // 绿=未达基础目标(待提升)
}
function getColorLabel(share, targets, airYear) {
const n = Number(share)
const target = (targets || []).find(t => Number(t.year) === Number(airYear))
if (!target) return '未知'
const stretch = Number(target.stretch_target)
const base = Number(target.base_target)
if (n > stretch) return '优秀'
if (n >= base) return '达标'
return '待提升'
}
function getBarHeight(share) {
return Math.round((Number(share) / 1.0) * 160)
}
function getShortTitle(title) {
if (!title) return '?'
return title.length > 9 ? title.slice(0, 9) + '...' : title
}
/**
* 计算当年完成率(纯前端,量纲已确认为 0~1 小数直接相除)
* - "当年"= 当前自然年(new Date().getFullYear())
* - avg(audience_share) 当年已录期 ÷ base/stretch_target
* - 无数据时始终返回 { base: '—', stretch: '—' }
* - 完成率可 >100% 正常显示
*/
function calcCompletionRate(episodes, targets) {
const yearInt = new Date().getFullYear()
const yearEpisodes = episodes.filter(e => {
const airYear = new Date(e.air_date).getFullYear()
return airYear === yearInt && e.audience_share != null
})
const target = (targets || []).find(t => Number(t.year) === yearInt)
if (!yearEpisodes.length || !target) return { base: '—', stretch: '—' }
const avgShare = yearEpisodes.reduce((sum, e) => sum + Number(e.audience_share), 0) / yearEpisodes.length
const baseRate = avgShare / Number(target.base_target)
const stretchRate = avgShare / Number(target.stretch_target)
return {
base: (baseRate * 100).toFixed(1) + '%',
stretch: (stretchRate * 100).toFixed(1) + '%',
}
}
function Dashboard() {
const { user } = useAuthStore()
const [episodes, setEpisodes] = useState([])
const [targets, setTargets] = useState([])
const [cover, setCover] = useState({ cover_path: null, episode_number: null, episode_title: null })
const [schedules, setSchedules] = useState([])
const [loading, setLoading] = useState(true)
const [coverModalOpen, setCoverModalOpen] = useState(false)
const showChangeCoverBtn = user?.role === 'zhipianren' || user?.role === 'zebian'
useEffect(() => {
Promise.all([
listEpisodes(50),
listTargets(),
getCurrentCover(),
getUpcomingSchedules(6),
]).then(([epData, tgtData, coverData, schedData]) => {
const sorted = (epData || []).sort((a, b) => new Date(b.air_date) - new Date(a.air_date))
setEpisodes(sorted)
setTargets(tgtData || [])
setCover(coverData || { cover_path: null, episode_number: null, episode_title: null })
setSchedules(Array.isArray(schedData) ? schedData : [])
setLoading(false)
}).catch(() => {
setLoading(false)
})
}, [])
// 显示最近有份额数据的期次(最多12个)
const displayEpisodes = episodes
.filter(e => e.audience_share != null)
.slice(0, 12)
const bestEpisode = [...displayEpisodes].sort((a, b) => Number(b.audience_share) - Number(a.audience_share))[0]
const handleCoverUploaded = (newCover) => {
setCover(newCover)
setCoverModalOpen(false)
}
// KPI 数据(扩为 5 项:原有 3 项 + 年度完成率 2 项)
const completion = calcCompletionRate(episodes, targets)
const kpiData = [
{
icon:
{ e.target.style.display = 'none' }}
/>
{/* 左侧文字 */}
本月收视最佳·题图
{bestEpisode ? `收视份额 ${Number(bestEpisode.audience_share).toFixed(4)} · 当月最高` : '暂无收视数据'}
建议:可选用近期收视表现好的节目海报
{item.label}
{item.value}
真实抓取 Phase 4c 实现
编导个人首页核心功能暂无排播数据