97 lines
4.4 KiB
TypeScript
97 lines
4.4 KiB
TypeScript
import { useState, useEffect } from 'react'
|
|
import { Row, Col, Statistic, Table, Tag, Typography } from 'antd'
|
|
import { SignalFilled, ClockCircleOutlined, PlayCircleOutlined } from '@ant-design/icons'
|
|
import { fetchStreams, type StreamInfo } from '../../api/streams'
|
|
import { useBrand } from '../../brand/BrandContext'
|
|
|
|
const { Title, Text } = Typography
|
|
|
|
export default function Dashboard() {
|
|
const { brand } = useBrand()
|
|
const [streams, setStreams] = useState<StreamInfo[]>([])
|
|
const [loading, setLoading] = useState(true)
|
|
|
|
useEffect(() => { loadData(); const t = setInterval(loadData, 10000); return () => clearInterval(t) }, [])
|
|
|
|
async function loadData() {
|
|
try { setStreams(await fetchStreams(true)) } catch { /* */ }
|
|
setLoading(false)
|
|
}
|
|
|
|
const liveCount = streams.filter(s => s.status === 'live').length
|
|
const pendingCount = streams.filter(s => s.status === 'pending').length
|
|
|
|
const statusMap: Record<string, { color: string; label: string }> = {
|
|
pending: { color: 'blue', label: '待开播' },
|
|
live: { color: 'green', label: '直播中' },
|
|
ended: { color: 'default', label: '已结束' },
|
|
cancelled: { color: 'red', label: '已取消' },
|
|
}
|
|
|
|
const columns = [
|
|
{ title: '标题', dataIndex: 'title', key: 'title', ellipsis: true },
|
|
{ title: '状态', dataIndex: 'status', key: 'status', width: 90,
|
|
render: (s: string) => <Tag color={(statusMap[s] || {}).color}>{(statusMap[s] || { label: s }).label}</Tag> },
|
|
{ title: '创建时间', dataIndex: 'created_at', key: 'created_at', render: (v: string) => v?.slice(0, 16), width: 170 },
|
|
]
|
|
|
|
return (
|
|
<div className="admin-page admin-dashboard">
|
|
<div className="admin-page-heading">
|
|
<Title level={1} style={{ margin: '0 0 8px', fontWeight: 600, fontSize: 32, letterSpacing: '-0.03em' }}>
|
|
{brand.brand_name}
|
|
</Title>
|
|
<Text className="admin-page-kicker">管理控制台 · 实时刷新</Text>
|
|
</div>
|
|
|
|
<Row gutter={[24, 24]} style={{ marginBottom: 36 }}>
|
|
<Col xs={24} sm={8}>
|
|
<div style={{
|
|
background: 'linear-gradient(145deg, rgba(26, 33, 47, 0.78), rgba(12, 17, 27, 0.72))', borderRadius: 18, padding: '28px 32px',
|
|
border: '1px solid rgba(255, 255, 255, 0.095)',
|
|
boxShadow: '0 18px 48px rgba(0, 0, 0, 0.26), inset 0 1px 0 rgba(255, 255, 255, 0.045)',
|
|
backdropFilter: 'blur(24px) saturate(135%)',
|
|
borderTop: '3px solid #5B8C5A',
|
|
}}>
|
|
<Statistic title="播出中" value={liveCount} loading={loading}
|
|
prefix={<SignalFilled style={{ color: '#5B8C5A', fontSize: 20 }} />} />
|
|
</div>
|
|
</Col>
|
|
<Col xs={24} sm={8}>
|
|
<div style={{
|
|
background: 'linear-gradient(145deg, rgba(26, 33, 47, 0.78), rgba(12, 17, 27, 0.72))', borderRadius: 18, padding: '28px 32px',
|
|
border: '1px solid rgba(255, 255, 255, 0.095)',
|
|
boxShadow: '0 18px 48px rgba(0, 0, 0, 0.26), inset 0 1px 0 rgba(255, 255, 255, 0.045)',
|
|
backdropFilter: 'blur(24px) saturate(135%)',
|
|
borderTop: '3px solid #6B8CA8',
|
|
}}>
|
|
<Statistic title="待开播" value={pendingCount} loading={loading}
|
|
prefix={<ClockCircleOutlined style={{ color: '#6B8CA8', fontSize: 20 }} />} />
|
|
</div>
|
|
</Col>
|
|
<Col xs={24} sm={8}>
|
|
<div style={{
|
|
background: 'linear-gradient(145deg, rgba(26, 33, 47, 0.78), rgba(12, 17, 27, 0.72))', borderRadius: 18, padding: '28px 32px',
|
|
border: '1px solid rgba(255, 255, 255, 0.095)',
|
|
boxShadow: '0 18px 48px rgba(0, 0, 0, 0.26), inset 0 1px 0 rgba(255, 255, 255, 0.045)',
|
|
backdropFilter: 'blur(24px) saturate(135%)',
|
|
borderTop: '3px solid #C2693A',
|
|
}}>
|
|
<Statistic title="节目总数" value={streams.length} loading={loading}
|
|
prefix={<PlayCircleOutlined style={{ color: '#C2693A', fontSize: 20 }} />} />
|
|
</div>
|
|
</Col>
|
|
</Row>
|
|
|
|
<div className="admin-glass-panel admin-table-panel" style={{
|
|
padding: '28px 32px',
|
|
}}>
|
|
<Title level={4} style={{ margin: '0 0 20px', fontWeight: 600 }}>最近节目</Title>
|
|
<Table dataSource={streams.slice(0, 10)} columns={columns}
|
|
rowKey="id" loading={loading} pagination={false} size="middle"
|
|
locale={{ emptyText: '暂无节目数据' }} />
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|