品牌化设置 + Claude米色衬线体主题 + 全站中文
- 品牌设置API: GET/PUT /api/settings/brand (名称/标语/Logo) - 管理页系统设置: 品牌编辑器表单(名称/标语/Logo URL) - 恢复Anthropic风格: 米色(#FBF9F6) 底色, Source Serif 4 + Noto Serif SC 衬线体 - 暖棕(#C2693A) 主色调, 更充裕的间距与留白, 庄重大气 - 全站中文: antd zhCN locale, 所有页面文本汉化 - 数据存储在 Config singleton, 运行时更新
This commit is contained in:
1 parent
409e5d314e
commit
efde5597bd
16 files changed
+403
-672
No files matched your search
@@ -1,18 +1,16 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import {
|
||||
Table, Button, Tag, Space, Modal, Typography, message, Tooltip, Popconfirm,
|
||||
} from 'antd'
|
||||
import { Table, Button, Tag, Space, Typography, message, Tooltip, Popconfirm } from 'antd'
|
||||
import { PlusOutlined, PlayCircleOutlined, StopOutlined, DeleteOutlined, LinkOutlined } from '@ant-design/icons'
|
||||
import { fetchStreams, deleteStream, goLive, endStream, type StreamInfo } from '../../api/streams'
|
||||
|
||||
const { Title, Text } = Typography
|
||||
const { Title } = Typography
|
||||
|
||||
const statusMap: Record<string, { color: string; label: string }> = {
|
||||
pending: { color: 'blue', label: 'Scheduled' },
|
||||
live: { color: 'green', label: 'Live' },
|
||||
ended: { color: 'default', label: 'Ended' },
|
||||
cancelled: { color: 'red', label: 'Cancelled' },
|
||||
pending: { color: 'blue', label: '待开播' },
|
||||
live: { color: 'green', label: '直播中' },
|
||||
ended: { color: 'default', label: '已结束' },
|
||||
cancelled: { color: 'red', label: '已取消' },
|
||||
}
|
||||
|
||||
export default function StreamList() {
|
||||
@@ -24,80 +22,40 @@ export default function StreamList() {
|
||||
|
||||
async function loadData() {
|
||||
setLoading(true)
|
||||
try {
|
||||
const data = await fetchStreams()
|
||||
setStreams(data)
|
||||
} catch { message.error('Failed to load streams') }
|
||||
try { setStreams(await fetchStreams()) } catch { message.error('加载失败') }
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
async function handleDelete(id: number) {
|
||||
try {
|
||||
await deleteStream(id)
|
||||
message.success('Deleted')
|
||||
loadData()
|
||||
} catch { message.error('Failed to delete') }
|
||||
try { await deleteStream(id); message.success('已删除'); loadData() } catch { message.error('删除失败') }
|
||||
}
|
||||
|
||||
async function handleGoLive(id: number) {
|
||||
try {
|
||||
await goLive(id)
|
||||
message.success('Stream is now live')
|
||||
loadData()
|
||||
} catch { message.error('Operation failed') }
|
||||
try { await goLive(id); message.success('已开播'); loadData() } catch { message.error('操作失败') }
|
||||
}
|
||||
|
||||
async function handleEnd(id: number) {
|
||||
try {
|
||||
await endStream(id)
|
||||
message.success('Stream ended')
|
||||
loadData()
|
||||
} catch { message.error('Operation failed') }
|
||||
try { await endStream(id); message.success('已结束'); loadData() } catch { message.error('操作失败') }
|
||||
}
|
||||
|
||||
const columns = [
|
||||
{ title: 'ID', dataIndex: 'id', key: 'id', width: 80 },
|
||||
{ title: 'Title', dataIndex: 'title', key: 'title', ellipsis: true },
|
||||
{ title: '标题', dataIndex: 'title', key: 'title', ellipsis: true },
|
||||
{ title: '状态', dataIndex: 'status', key: 'status', width: 100,
|
||||
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 },
|
||||
{
|
||||
title: 'Status', dataIndex: 'status', key: 'status', width: 110,
|
||||
render: (s: string) => {
|
||||
const info = statusMap[s] || { color: 'default', label: s }
|
||||
return <Tag color={info.color}>{info.label}</Tag>
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Created', dataIndex: 'created_at', key: 'created_at',
|
||||
render: (v: string) => v?.slice(0, 16), width: 170,
|
||||
},
|
||||
{
|
||||
title: 'Actions', key: 'actions', width: 280,
|
||||
render: (_: any, record: StreamInfo) => (
|
||||
title: '操作', key: 'actions', width: 280,
|
||||
render: (_: any, r: StreamInfo) => (
|
||||
<Space size="small">
|
||||
<Button
|
||||
size="small"
|
||||
icon={<LinkOutlined />}
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(record.stream_key)
|
||||
message.success('Stream key copied')
|
||||
}}
|
||||
>
|
||||
Copy Key
|
||||
<Button size="small" icon={<LinkOutlined />}
|
||||
onClick={() => { navigator.clipboard.writeText(r.stream_key); message.success('推流码已复制') }}>
|
||||
复制推流码
|
||||
</Button>
|
||||
{record.status !== 'live' ? (
|
||||
<Tooltip title="Go Live">
|
||||
<Button size="small" type="primary" icon={<PlayCircleOutlined />}
|
||||
onClick={() => handleGoLive(record.id)} ghost />
|
||||
</Tooltip>
|
||||
{r.status !== 'live' ? (
|
||||
<Tooltip title="开播"><Button size="small" type="primary" icon={<PlayCircleOutlined />} onClick={() => handleGoLive(r.id)} ghost /></Tooltip>
|
||||
) : (
|
||||
<Tooltip title="End Stream">
|
||||
<Button size="small" icon={<StopOutlined />}
|
||||
onClick={() => handleEnd(record.id)} danger />
|
||||
</Tooltip>
|
||||
<Tooltip title="结束"><Button size="small" icon={<StopOutlined />} onClick={() => handleEnd(r.id)} danger /></Tooltip>
|
||||
)}
|
||||
<Button size="small" onClick={() => window.open(`/watch/${record.id}`, '_blank')}>
|
||||
Watch
|
||||
</Button>
|
||||
<Popconfirm title="Delete this stream?" onConfirm={() => handleDelete(record.id)}>
|
||||
<Button size="small" onClick={() => window.open(`/watch/${r.id}`, '_blank')}>观看</Button>
|
||||
<Popconfirm title="确定删除?" onConfirm={() => handleDelete(r.id)}>
|
||||
<Button size="small" icon={<DeleteOutlined />} danger />
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
@@ -108,19 +66,10 @@ export default function StreamList() {
|
||||
return (
|
||||
<div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}>
|
||||
<Title level={3} style={{ margin: 0, fontWeight: 600 }}>Streams</Title>
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={() => navigate('/admin/streams/new')}>
|
||||
New Stream
|
||||
</Button>
|
||||
<Title level={3} style={{ margin: 0 }}>直播列表</Title>
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={() => navigate('/admin/streams/new')}>创建直播</Button>
|
||||
</div>
|
||||
<Table
|
||||
dataSource={streams}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
pagination={{ pageSize: 15 }}
|
||||
size="middle"
|
||||
/>
|
||||
<Table dataSource={streams} columns={columns} rowKey="id" loading={loading} pagination={{ pageSize: 15 }} size="middle" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user