feat: 收视分析页面(指标卡 + 走势图 + 路由导航)
This commit is contained in:
@@ -10,6 +10,7 @@ import KnowledgeBase from './pages/KnowledgeBase/KnowledgeBase'
|
|||||||
import Doco from './pages/Doco/Doco'
|
import Doco from './pages/Doco/Doco'
|
||||||
import UserManage from './pages/UserManage/UserManage'
|
import UserManage from './pages/UserManage/UserManage'
|
||||||
import EditorDesk from './pages/EditorDesk/EditorDesk'
|
import EditorDesk from './pages/EditorDesk/EditorDesk'
|
||||||
|
import Analytics from './pages/Analytics/Analytics'
|
||||||
import AuthGuard from './components/AuthGuard/AuthGuard'
|
import AuthGuard from './components/AuthGuard/AuthGuard'
|
||||||
import RoleGuard from './components/AuthGuard/RoleGuard'
|
import RoleGuard from './components/AuthGuard/RoleGuard'
|
||||||
|
|
||||||
@@ -31,6 +32,7 @@ function App() {
|
|||||||
<Route element={<AppLayout />}>
|
<Route element={<AppLayout />}>
|
||||||
<Route index element={<Navigate to="/dashboard" replace />} />
|
<Route index element={<Navigate to="/dashboard" replace />} />
|
||||||
<Route path="dashboard" element={<Dashboard />} />
|
<Route path="dashboard" element={<Dashboard />} />
|
||||||
|
<Route path="analytics" element={<Analytics />} />
|
||||||
<Route path="tps" element={<TPS />} />
|
<Route path="tps" element={<TPS />} />
|
||||||
<Route path="knowledge" element={<KnowledgeBase />} />
|
<Route path="knowledge" element={<KnowledgeBase />} />
|
||||||
<Route path="doco" element={<Doco />} />
|
<Route path="doco" element={<Doco />} />
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
UserOutlined,
|
UserOutlined,
|
||||||
TeamOutlined,
|
TeamOutlined,
|
||||||
SoundOutlined,
|
SoundOutlined,
|
||||||
|
LineChartOutlined,
|
||||||
} from '@ant-design/icons'
|
} from '@ant-design/icons'
|
||||||
import useAuthStore from '../../stores/authStore'
|
import useAuthStore from '../../stores/authStore'
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ function SideNav() {
|
|||||||
|
|
||||||
const allMenuItems = [
|
const allMenuItems = [
|
||||||
{ key: '/dashboard', icon: <DashboardOutlined />, label: '仪表盘' },
|
{ key: '/dashboard', icon: <DashboardOutlined />, label: '仪表盘' },
|
||||||
|
{ key: '/analytics', icon: <LineChartOutlined />, label: '收视分析' },
|
||||||
{ key: '/editor-desk', icon: <FileTextOutlined />, label: '责编录入' },
|
{ key: '/editor-desk', icon: <FileTextOutlined />, label: '责编录入' },
|
||||||
{ key: '/tps', icon: <FileTextOutlined />, label: 'TPS 选题策划' },
|
{ key: '/tps', icon: <FileTextOutlined />, label: 'TPS 选题策划' },
|
||||||
{ key: '/knowledge', icon: <BookOutlined />, label: '知识库' },
|
{ key: '/knowledge', icon: <BookOutlined />, label: '知识库' },
|
||||||
@@ -53,6 +55,8 @@ function SideNav() {
|
|||||||
const visibleItems = allMenuItems.filter(item => {
|
const visibleItems = allMenuItems.filter(item => {
|
||||||
// /dashboard 三角色都可见
|
// /dashboard 三角色都可见
|
||||||
if (item.key === '/dashboard') return true
|
if (item.key === '/dashboard') return true
|
||||||
|
// /analytics 三角色都可见
|
||||||
|
if (item.key === '/analytics') return true
|
||||||
// /users 仅 zhipianren
|
// /users 仅 zhipianren
|
||||||
if (item.key === '/users') return String(user?.role) === 'zhipianren'
|
if (item.key === '/users') return String(user?.role) === 'zhipianren'
|
||||||
// /editor-desk 仅 zhipianren + zebian
|
// /editor-desk 仅 zhipianren + zebian
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
/* ===== 收视分析页面 ===== */
|
||||||
|
|
||||||
|
.analytics-page {
|
||||||
|
max-width: 1100px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 24px;
|
||||||
|
min-height: 100vh;
|
||||||
|
background: linear-gradient(135deg, #fdf6ee 0%, #f0f4f8 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 头部 */
|
||||||
|
.analytics-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analytics-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 加载与空状态 */
|
||||||
|
.analytics-loading {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 80px 0;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analytics-loading p {
|
||||||
|
margin-top: 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analytics-empty {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 80px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 指标卡行 ===== */
|
||||||
|
.analytics-kpi-row {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analytics-kpi-card {
|
||||||
|
background: rgba(255, 255, 255, 0.72);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
-webkit-backdrop-filter: blur(12px);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 20px 24px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||||
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
|
||||||
|
transition: transform 0.2s, box-shadow 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analytics-kpi-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.analytics-kpi-label {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #888;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analytics-kpi-value {
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #333;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 走势图卡片 ===== */
|
||||||
|
.analytics-chart-card {
|
||||||
|
background: rgba(255, 255, 255, 0.72);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
-webkit-backdrop-filter: blur(12px);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 24px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||||
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.analytics-chart-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analytics-chart-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analytics-chart-hint {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 图例 */
|
||||||
|
.analytics-chart-legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analytics-legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analytics-legend-dot {
|
||||||
|
display: inline-block;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 响应式 ===== */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.analytics-page {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analytics-header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analytics-kpi-card {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analytics-kpi-value {
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.analytics-chart-card {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,318 @@
|
|||||||
|
import { useState, useEffect, useMemo } from 'react'
|
||||||
|
import { Select, Spin, Empty, Row, Col } from 'antd'
|
||||||
|
import ReactECharts from 'echarts-for-react'
|
||||||
|
import { getShareColor } from '../../utils/ratingColors'
|
||||||
|
import { getAnalyticsEpisodes, getAvailableYears } from '../../services/analyticsService'
|
||||||
|
import './Analytics.css'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收视分析页面
|
||||||
|
* - 年份选择器
|
||||||
|
* - 指标卡行(4 张)
|
||||||
|
* - 收视走势折线图(ECharts)
|
||||||
|
* - 空状态兜底
|
||||||
|
*/
|
||||||
|
function Analytics() {
|
||||||
|
const [years, setYears] = useState([])
|
||||||
|
const [selectedYear, setSelectedYear] = useState(null)
|
||||||
|
const [yearlyTarget, setYearlyTarget] = useState(null)
|
||||||
|
const [episodes, setEpisodes] = useState([])
|
||||||
|
const [loading, setLoading] = useState(true)
|
||||||
|
|
||||||
|
// 获取可用年份列表
|
||||||
|
useEffect(() => {
|
||||||
|
getAvailableYears()
|
||||||
|
.then((data) => {
|
||||||
|
setYears(data || [])
|
||||||
|
if (data && data.length > 0) {
|
||||||
|
setSelectedYear(data[0]) // 默认选最近年份
|
||||||
|
} else {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setLoading(false)
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// 年份切换时获取数据
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedYear == null) return
|
||||||
|
setLoading(true)
|
||||||
|
getAnalyticsEpisodes(selectedYear)
|
||||||
|
.then((data) => {
|
||||||
|
setEpisodes(data.episodes || [])
|
||||||
|
setYearlyTarget(data.yearly_target)
|
||||||
|
setLoading(false)
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setEpisodes([])
|
||||||
|
setYearlyTarget(null)
|
||||||
|
setLoading(false)
|
||||||
|
})
|
||||||
|
}, [selectedYear])
|
||||||
|
|
||||||
|
// ===== 指标卡计算 =====
|
||||||
|
const kpiMetrics = useMemo(() => {
|
||||||
|
const withShare = episodes.filter((ep) => ep.audience_share != null)
|
||||||
|
const count = withShare.length
|
||||||
|
if (count === 0) {
|
||||||
|
return {
|
||||||
|
count: 0,
|
||||||
|
avgShare: null,
|
||||||
|
avgShareColor: '#999',
|
||||||
|
baseRate: '—',
|
||||||
|
stretchRate: '—',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const avgShare =
|
||||||
|
withShare.reduce((sum, ep) => sum + Number(ep.audience_share), 0) / count
|
||||||
|
|
||||||
|
// 平均份额的颜色判定(用选定年份的 target)
|
||||||
|
let avgShareColor = '#999'
|
||||||
|
if (yearlyTarget) {
|
||||||
|
const base = Number(yearlyTarget.base_target)
|
||||||
|
const stretch = Number(yearlyTarget.stretch_target)
|
||||||
|
if (avgShare > stretch) avgShareColor = '#c0584f' // 红=优秀
|
||||||
|
else if (avgShare >= base) avgShareColor = '#5b8db8' // 蓝=达标
|
||||||
|
else avgShareColor = '#7aa874' // 绿=待提升
|
||||||
|
}
|
||||||
|
|
||||||
|
let baseRate = '—'
|
||||||
|
let stretchRate = '—'
|
||||||
|
if (yearlyTarget) {
|
||||||
|
const base = Number(yearlyTarget.base_target)
|
||||||
|
const stretch = Number(yearlyTarget.stretch_target)
|
||||||
|
baseRate = ((avgShare / base) * 100).toFixed(1) + '%'
|
||||||
|
stretchRate = ((avgShare / stretch) * 100).toFixed(1) + '%'
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
count,
|
||||||
|
avgShare: (avgShare * 100).toFixed(2) + '%',
|
||||||
|
avgShareColor,
|
||||||
|
baseRate,
|
||||||
|
stretchRate,
|
||||||
|
}
|
||||||
|
}, [episodes, yearlyTarget])
|
||||||
|
|
||||||
|
// ===== ECharts 配置 =====
|
||||||
|
const chartOption = useMemo(() => {
|
||||||
|
if (!episodes.length) return {}
|
||||||
|
|
||||||
|
const withShare = episodes.filter((ep) => ep.audience_share != null)
|
||||||
|
const xData = withShare.map((ep) => `第${ep.episode_number}期`)
|
||||||
|
|
||||||
|
// 构建用于 getShareColor 的 targets 数组格式
|
||||||
|
const targetsForChart = yearlyTarget
|
||||||
|
? [{ year: selectedYear, ...yearlyTarget }]
|
||||||
|
: []
|
||||||
|
|
||||||
|
const sharePoints = withShare.map((ep) => ({
|
||||||
|
value: Number(ep.audience_share),
|
||||||
|
itemStyle: {
|
||||||
|
color: getShareColor(ep.audience_share, targetsForChart, selectedYear),
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
const markLineData = []
|
||||||
|
if (yearlyTarget) {
|
||||||
|
markLineData.push(
|
||||||
|
{
|
||||||
|
name: '基础目标',
|
||||||
|
yAxis: Number(yearlyTarget.base_target),
|
||||||
|
lineStyle: { color: '#5b8db8', type: 'dashed', width: 2 },
|
||||||
|
label: {
|
||||||
|
formatter: '基础目标 {c}',
|
||||||
|
position: 'insideEndTop',
|
||||||
|
color: '#5b8db8',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '摸高目标',
|
||||||
|
yAxis: Number(yearlyTarget.stretch_target),
|
||||||
|
lineStyle: { color: '#c0584f', type: 'dashed', width: 2 },
|
||||||
|
label: {
|
||||||
|
formatter: '摸高目标 {c}',
|
||||||
|
position: 'insideEndTop',
|
||||||
|
color: '#c0584f',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
formatter: (params) => {
|
||||||
|
const idx = params[0].dataIndex
|
||||||
|
const ep = withShare[idx]
|
||||||
|
if (!ep) return ''
|
||||||
|
const share = ep.audience_share != null ? Number(ep.audience_share).toFixed(4) : '无数据'
|
||||||
|
const rating = ep.audience_rating != null ? Number(ep.audience_rating).toFixed(4) : '无数据'
|
||||||
|
const editor = ep.editor_name_snapshot || '未知'
|
||||||
|
return `<strong>第 ${ep.episode_number} 期 · ${ep.program_name}</strong><br/>` +
|
||||||
|
`编导:${editor}<br/>` +
|
||||||
|
`收视份额:${share}<br/>` +
|
||||||
|
`收视率:${rating}`
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: 60,
|
||||||
|
right: 30,
|
||||||
|
top: 40,
|
||||||
|
bottom: 70,
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: xData,
|
||||||
|
axisLabel: {
|
||||||
|
rotate: 30,
|
||||||
|
fontSize: 11,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
name: '收视份额',
|
||||||
|
axisLabel: {
|
||||||
|
formatter: (val) => val.toFixed(2),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataZoom: [
|
||||||
|
{ type: 'inside', start: 0, end: 100 },
|
||||||
|
{
|
||||||
|
type: 'slider',
|
||||||
|
bottom: 10,
|
||||||
|
height: 20,
|
||||||
|
start: 0,
|
||||||
|
end: 100,
|
||||||
|
borderColor: '#ddd',
|
||||||
|
fillerColor: 'rgba(107,142,107,0.15)',
|
||||||
|
handleStyle: { color: '#6b8e6b' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '收视份额',
|
||||||
|
type: 'line',
|
||||||
|
data: sharePoints,
|
||||||
|
smooth: false,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 8,
|
||||||
|
lineStyle: { width: 2, color: '#6b8e6b' },
|
||||||
|
markLine: {
|
||||||
|
silent: true,
|
||||||
|
data: markLineData,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}, [episodes, yearlyTarget, selectedYear])
|
||||||
|
|
||||||
|
const hasData = episodes.length > 0 && episodes.some((ep) => ep.audience_share != null)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="analytics-page">
|
||||||
|
{/* 页面头部 + 年份选择器 */}
|
||||||
|
<div className="analytics-header">
|
||||||
|
<h2 className="analytics-title">收视分析</h2>
|
||||||
|
<Select
|
||||||
|
className="analytics-year-select"
|
||||||
|
value={selectedYear}
|
||||||
|
onChange={setSelectedYear}
|
||||||
|
placeholder="选择年份"
|
||||||
|
loading={years.length === 0}
|
||||||
|
options={years.map((y) => ({ label: `${y} 年`, value: y }))}
|
||||||
|
style={{ width: 120 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{loading ? (
|
||||||
|
<div className="analytics-loading">
|
||||||
|
<Spin size="large" />
|
||||||
|
<p>加载中...</p>
|
||||||
|
</div>
|
||||||
|
) : !hasData ? (
|
||||||
|
<div className="analytics-empty">
|
||||||
|
<Empty description="暂无收视数据" />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{/* 指标卡行 */}
|
||||||
|
<Row gutter={16} className="analytics-kpi-row">
|
||||||
|
<Col xs={24} sm={12} md={6}>
|
||||||
|
<div className="analytics-kpi-card">
|
||||||
|
<div className="analytics-kpi-label">本年已播期数</div>
|
||||||
|
<div className="analytics-kpi-value">{kpiMetrics.count}</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} sm={12} md={6}>
|
||||||
|
<div className="analytics-kpi-card">
|
||||||
|
<div className="analytics-kpi-label">平均收视份额</div>
|
||||||
|
<div
|
||||||
|
className="analytics-kpi-value"
|
||||||
|
style={{ color: kpiMetrics.avgShareColor }}
|
||||||
|
>
|
||||||
|
{kpiMetrics.avgShare || '—'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} sm={12} md={6}>
|
||||||
|
<div className="analytics-kpi-card">
|
||||||
|
<div className="analytics-kpi-label">基础目标完成率</div>
|
||||||
|
<div className="analytics-kpi-value">{kpiMetrics.baseRate}</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col xs={24} sm={12} md={6}>
|
||||||
|
<div className="analytics-kpi-card">
|
||||||
|
<div className="analytics-kpi-label">摸高目标完成率</div>
|
||||||
|
<div className="analytics-kpi-value">{kpiMetrics.stretchRate}</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
{/* 收视走势折线图 */}
|
||||||
|
<div className="analytics-chart-card">
|
||||||
|
<div className="analytics-chart-header">
|
||||||
|
<h3 className="analytics-chart-title">{selectedYear} 年收视走势</h3>
|
||||||
|
<span className="analytics-chart-hint">
|
||||||
|
拖拽底部滑块缩放 · 颜色只对收视份额
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<ReactECharts
|
||||||
|
option={chartOption}
|
||||||
|
style={{ height: 400, width: '100%' }}
|
||||||
|
notMerge
|
||||||
|
/>
|
||||||
|
<div className="analytics-chart-legend">
|
||||||
|
<span className="analytics-legend-item">
|
||||||
|
<span
|
||||||
|
className="analytics-legend-dot"
|
||||||
|
style={{ background: '#7aa874' }}
|
||||||
|
/>
|
||||||
|
绿=未达基础目标(待提升)
|
||||||
|
</span>
|
||||||
|
<span className="analytics-legend-item">
|
||||||
|
<span
|
||||||
|
className="analytics-legend-dot"
|
||||||
|
style={{ background: '#5b8db8' }}
|
||||||
|
/>
|
||||||
|
蓝=未达摸高目标(达标)
|
||||||
|
</span>
|
||||||
|
<span className="analytics-legend-item">
|
||||||
|
<span
|
||||||
|
className="analytics-legend-dot"
|
||||||
|
style={{ background: '#c0584f' }}
|
||||||
|
/>
|
||||||
|
红=超过摸高目标(优秀)
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Analytics
|
||||||
Reference in New Issue
Block a user