From e0ccfb20e7fd430fb2bd664e3ee90eb66f2776ec Mon Sep 17 00:00:00 2001 From: simonkoson <28867558@qq.com> Date: Thu, 21 May 2026 19:47:36 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20Dashboard=20=E6=8E=A5=E7=9C=9F=20API?= =?UTF-8?q?=EF=BC=8C=E9=A2=98=E5=9B=BE=E6=B8=90=E5=8F=98=E5=8D=A0=E4=BD=8D?= =?UTF-8?q?=EF=BC=8C=E6=8E=92=E6=92=AD=E5=8D=A0=E4=BD=8D=EF=BC=8C=E8=BF=91?= =?UTF-8?q?9=E6=9C=9F=E6=94=B6=E8=A7=86=E6=9F=B1=E5=9B=BE=EF=BC=8C?= =?UTF-8?q?=E9=A2=9C=E8=89=B2=E6=8C=89=E5=B9=B4=E5=BA=A6=E7=9B=AE=E6=A0=87?= =?UTF-8?q?=E5=88=A4=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/Dashboard/Dashboard.css | 27 ++++ frontend/src/pages/Dashboard/Dashboard.jsx | 159 ++++++++++++++------- 2 files changed, 134 insertions(+), 52 deletions(-) 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 (
第 1245 期 隐形战场 · 收视份额 0.94
+ {bestEpisode ? ( ++ 第 {bestEpisode.episode_number} 期 {bestEpisode.program_name} · + 收视份额 {bestEpisode.audience_share} +
+ ) : ( +暂无收视数据
+ )}模块 H · Phase 4c 实施
- 热点雷达将在 Phase 4c 开发,作为编导个人首页的核心功能 +热点雷达 · Phase 4c
+ 编导个人首页核心功能,Phase 4c 实施模块 F · Phase 4b 实施
- 甘特图排期将在 Phase 4b 开发,使用 frappe-gantt 实现拖拽排期 +排播计划 · Phase 4b
+ 甘特图排期使用 frappe-gantt 实现,Phase 4b 开发