91 lines
4.4 KiB
TypeScript
91 lines
4.4 KiB
TypeScript
import { useState, useEffect } from 'react'
|
|
import { useNavigate } from 'react-router-dom'
|
|
import { Table, Button, Tag, Space, Typography, message, Tooltip, Popconfirm } from 'antd'
|
|
import { PlusOutlined, PlayCircleOutlined, StopOutlined, DeleteOutlined, LinkOutlined, EyeOutlined, EditOutlined } from '@ant-design/icons'
|
|
import { fetchStreams, deleteStream, goLive, endStream, type StreamInfo } from '../../api/streams'
|
|
|
|
const { Title, Text } = Typography
|
|
|
|
const statusMap: Record<string, { color: string; label: string }> = {
|
|
pending: { color: 'blue', label: '待开播' },
|
|
live: { color: 'green', label: '直播中' },
|
|
ended: { color: 'default', label: '已结束' },
|
|
cancelled: { color: 'red', label: '已取消' },
|
|
}
|
|
|
|
export default function StreamList() {
|
|
const [streams, setStreams] = useState<StreamInfo[]>([])
|
|
const [loading, setLoading] = useState(true)
|
|
const navigate = useNavigate()
|
|
|
|
useEffect(() => { loadData() }, [])
|
|
|
|
async function loadData() {
|
|
setLoading(true)
|
|
try { setStreams(await fetchStreams(true)) } catch { message.error('加载失败') }
|
|
setLoading(false)
|
|
}
|
|
async function handleDelete(id: number) {
|
|
try { await deleteStream(id); message.success('已删除'); loadData() } catch { message.error('删除失败') }
|
|
}
|
|
async function handleGoLive(id: number) {
|
|
try { await goLive(id); message.success('已开播'); loadData() } catch { message.error('操作失败') }
|
|
}
|
|
async function handleEnd(id: number) {
|
|
try { await endStream(id); message.success('已结束'); loadData() } catch { message.error('操作失败') }
|
|
}
|
|
|
|
const columns = [
|
|
{ title: 'ID', dataIndex: 'id', key: 'id', width: 60 },
|
|
{ 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: 'stream_key', key: 'stream_key', width: 120,
|
|
render: (v: string) => <Text code style={{ fontSize: 12 }}>{v.slice(0, 12)}...</Text> },
|
|
{ title: '创建时间', dataIndex: 'created_at', key: 'created_at', render: (v: string) => v?.slice(0, 16), width: 160 },
|
|
{
|
|
title: '操作', key: 'actions', width: 240,
|
|
render: (_: any, r: StreamInfo) => (
|
|
<Space size={4}>
|
|
<Tooltip title="复制推流码">
|
|
<Button size="small" icon={<LinkOutlined />}
|
|
onClick={() => { if (r.stream_key) navigator.clipboard.writeText(r.stream_key); message.success('已复制') }} />
|
|
</Tooltip>
|
|
{r.status !== 'live' ? (
|
|
<Tooltip title="开播"><Button size="small" type="primary" ghost icon={<PlayCircleOutlined />} onClick={() => handleGoLive(r.id)} /></Tooltip>
|
|
) : (
|
|
<Tooltip title="结束"><Button size="small" danger icon={<StopOutlined />} onClick={() => handleEnd(r.id)} /></Tooltip>
|
|
)}
|
|
<Tooltip title="观看"><Button size="small" icon={<EyeOutlined />} onClick={() => window.open(`/watch/${r.id}`, '_blank')} /></Tooltip>
|
|
<Tooltip title="编辑"><Button size="small" icon={<EditOutlined />} onClick={() => navigate(`/admin/streams/${r.id}/edit`)} /></Tooltip>
|
|
<Popconfirm title="确定删除?" onConfirm={() => handleDelete(r.id)}>
|
|
<Tooltip title="删除"><Button size="small" danger icon={<DeleteOutlined />} /></Tooltip>
|
|
</Popconfirm>
|
|
</Space>
|
|
),
|
|
},
|
|
]
|
|
|
|
return (
|
|
<div>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 28 }}>
|
|
<div>
|
|
<Title level={2} style={{ margin: '0 0 4px', fontWeight: 600, fontSize: 28, letterSpacing: '-0.02em' }}>节目列表</Title>
|
|
<Text style={{ color: '#949088', fontSize: 14 }}>管理直播、排期与回放节目</Text>
|
|
</div>
|
|
<Button type="primary" icon={<PlusOutlined />} size="large" onClick={() => navigate('/admin/streams/new')}>
|
|
创建节目
|
|
</Button>
|
|
</div>
|
|
<div style={{
|
|
background: '#fff', borderRadius: 12,
|
|
border: '1px solid #EBE5DB', padding: '8px 8px 0',
|
|
}}>
|
|
<Table dataSource={streams} columns={columns} rowKey="id" loading={loading}
|
|
pagination={{ pageSize: 15, showSizeChanger: false }} size="middle"
|
|
locale={{ emptyText: '暂无节目,创建第一个节目吧' }} />
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|