diff --git a/frontend/src/pages/Dashboard/Dashboard.css b/frontend/src/pages/Dashboard/Dashboard.css index 1a4fce1..96b8d59 100644 --- a/frontend/src/pages/Dashboard/Dashboard.css +++ b/frontend/src/pages/Dashboard/Dashboard.css @@ -34,6 +34,33 @@ justify-content: center; color: var(--color-primary-green); font-size: 12px; + position: relative; + overflow: hidden; +} + +.banner-placeholder-gradient { + position: absolute; + inset: 0; + background: linear-gradient(135deg, #6b8e6b 0%, #a8c89a 50%, #d4e6b5 100%); +} + +.change-cover-btn { + position: absolute; + bottom: 6px; + right: 6px; + z-index: 2; +} + +.placeholder-label { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 2; + color: rgba(255,255,255,0.85); + font-size: 13px; + font-weight: 600; + letter-spacing: 2px; } /* KPI Cards */ diff --git a/frontend/src/pages/Dashboard/Dashboard.jsx b/frontend/src/pages/Dashboard/Dashboard.jsx index 7ce3d6f..d0751c5 100644 --- a/frontend/src/pages/Dashboard/Dashboard.jsx +++ b/frontend/src/pages/Dashboard/Dashboard.jsx @@ -1,32 +1,30 @@ -import { Row, Col, Card, Avatar, Tooltip } from 'antd' +import { useState, useEffect } from 'react' +import { Row, Col, Card, Avatar, Tooltip, Button } from 'antd' import { BarChartOutlined, EyeOutlined, CalendarOutlined, FireOutlined, + PictureOutlined, } from '@ant-design/icons' +import useAuthStore from '../../stores/authStore' +import { listEpisodes } from '../../services/episodeService' +import { listTargets } from '../../services/yearlyTargetService' import './Dashboard.css' -// 假数据:近 9 期收视(基础目标 0.6,摸高目标 0.9) -const fakeEpisodes = [ - { id: 1, title: '第 1247 期 现代防空反导大对决', editor: 'simonkoson', share: 0.72 }, - { id: 2, title: '第 1246 期 深海暗战', editor: 'simonkoson', share: 0.85 }, - { id: 3, title: '第 1245 期 隐形战场', editor: 'simonkoson', share: 0.94 }, - { id: 4, title: '第 1244 期 无人机革命', editor: 'simonkoson', share: 0.65 }, - { id: 5, title: '第 1243 期 核潜艇传奇', editor: 'simonkoson', share: 0.58 }, - { id: 6, title: '第 1242 期 电子战风暴', editor: 'simonkoson', share: 0.88 }, - { id: 7, title: '第 1241 期 太空博弈', editor: 'simonkoson', share: 0.91 }, - { id: 8, title: '第 1240 期 装甲对决', editor: 'simonkoson', share: 0.67 }, - { id: 9, title: '第 1239 期 隐身战机', editor: 'simonkoson', share: 0.55 }, -] - -const BASE_TARGET = 0.6 -const STRETCH_TARGET = 0.9 - -function getBarColor(share) { - if (share > STRETCH_TARGET) return '#cf1322' // 红=优秀 - if (share >= BASE_TARGET) return '#6b8e6b' // 蓝=达标 - return '#d4a017' // 绿=待提升 +/** + * 收视份额颜色判定(极易写反,禁止凭直觉): + * audience_share > stretch_target → 🔴 红(优秀) + * base_target ≤ audience_share ≤ stretch_target → 🔵 蓝(达标) + * audience_share < base_target → 🟢 绿(待提升) + * 判色取该期 air_date 所属年份的 yearly_targets 行(不是当前年)。 + */ +function getShareColor(share, targets, airYear) { + const target = targets.find(t => t.year === airYear) + if (!target) return '#999' + if (share > target.stretch_target) return '#cf1322' // 红=优秀 + if (share >= target.base_target) return '#6b8e6b' // 蓝=达标 + return '#d4a017' // 绿=待提升 } function getBarHeight(share) { @@ -38,16 +36,63 @@ function getShortTitle(title) { } function Dashboard() { + const { user } = useAuthStore() + const [episodes, setEpisodes] = useState([]) + const [targets, setTargets] = useState([]) + const [loading, setLoading] = useState(true) + + useEffect(() => { + Promise.all([ + listEpisodes(limit = 9), + listTargets(), + ]).then(([epData, tgtData]) => { + // 按 air_date 倒序取前 9 + const sorted = (epData || []).sort((a, b) => new Date(b.air_date) - new Date(a.air_date)).slice(0, 9) + setEpisodes(sorted) + setTargets(tgtData || []) + setLoading(false) + }).catch(() => { + setLoading(false) + }) + }, []) + + // 显示最近有份额数据的期次(最多9个,不够用真实数据补位) + const hasShare = episodes.filter(e => e.audience_share != null) + const displayEpisodes = hasShare.length >= 5 ? hasShare.slice(0, 9) : episodes.slice(0, Math.max(9, hasShare.length || 5)) + + const bestEpisode = [...displayEpisodes].filter(e => e.audience_share != null).sort((a, b) => b.audience_share - a.audience_share)[0] + + const showChangeCoverBtn = user?.role === 'zhipianren' || user?.role === 'zebian' + return (
{/* 顶部 Banner */}

本月收视最佳

-

第 1245 期 隐形战场 · 收视份额 0.94

+ {bestEpisode ? ( +

+ 第 {bestEpisode.episode_number} 期 {bestEpisode.program_name} · + 收视份额 {bestEpisode.audience_share} +

+ ) : ( +

暂无收视数据

+ )}
+ {/* 题图渐变占位 */}
- 题图位 · 待 Phase 2 实装 +
+ {showChangeCoverBtn && ( + + )} + 敬请期待
@@ -59,8 +104,8 @@ function Dashboard() {
- 0.82 - 本期平均收视 + {displayEpisodes.filter(e => e.audience_share).length || '--'} + 近 9 期已录
@@ -70,8 +115,10 @@ function Dashboard() {
- 98.6% - 完成率 + + {bestEpisode ? (bestEpisode.audience_share * 100).toFixed(1) + '%' : '--'} + + 最佳收视份额
@@ -81,8 +128,8 @@ function Dashboard() {
- 6 - 未来待排期 + {targets.length || '--'} + 年度目标条数
@@ -95,35 +142,43 @@ function Dashboard() {
-

模块 H · Phase 4c 实施

- 热点雷达将在 Phase 4c 开发,作为编导个人首页的核心功能 +

热点雷达 · Phase 4c

+ 编导个人首页核心功能,Phase 4c 实施
{/* 近 9 期收视柱图 */} - +
- {fakeEpisodes.map((ep) => ( -
- -
- -
{getShortTitle(ep.title)}
-
- {ep.editor[0]} - {ep.editor} + {displayEpisodes.map((ep) => { + const airYear = new Date(ep.air_date).getFullYear() + const color = ep.audience_share != null + ? getShareColor(ep.audience_share, targets, airYear) + : '#ccc' + return ( +
+ +
+ +
{getShortTitle(ep.program_name)}
+
+ + {(ep.editor_name_snapshot || '?')[0]} + + {ep.editor_name_snapshot || '未知'} +
-
- ))} + ) + })}
红=优秀 @@ -136,11 +191,11 @@ function Dashboard() { {/* 排播计划 */} - +
-

模块 F · Phase 4b 实施

- 甘特图排期将在 Phase 4b 开发,使用 frappe-gantt 实现拖拽排期 +

排播计划 · Phase 4b

+ 甘特图排期使用 frappe-gantt 实现,Phase 4b 开发