UI全面精雕: 米兰色衬线体系 + 全页质感升级
主题层: - 更精细的色彩层级(primaryBg/hover/active等语义色) - 字号体系: heading1-5, fontSize 15 基准 - 控件高度 40/48/32 三档, 圆角 6/10 - 三层阴影分层, motionDuration 首页: - 大幅面渐变英雄区(64px padding, 40px 标题) - 直播卡片: 暖棕渐变封面, LIVE 脉冲红点标签 - 悬停 transform+shadow 过渡, 描边高亮 仪表盘: - 彩色顶部边框卡片(绿/蓝/棕), 品牌名+标语头部 登录页: - 48px 大内边距, 返回首页链接 直播列表: - 描述行内展示, 推流码截断, 操作图标组 节点页: - 发光圆点在线指示器, 三色 CPU/内存进度阈值 - 在线/离线统计, Origin 标签 播放页: - 暗色 #1A1815 背景, 毛玻璃顶栏 - 圆角视频容器, box-shadow 景深 - document.title 动态标题
This commit is contained in:
1 parent
d4a3742136
commit
f0b8c2d64f
8 files changed
+461
-218
No files matched your search
@@ -1,10 +1,10 @@
|
||||
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 } from '@ant-design/icons'
|
||||
import { PlusOutlined, PlayCircleOutlined, StopOutlined, DeleteOutlined, LinkOutlined, EyeOutlined } from '@ant-design/icons'
|
||||
import { fetchStreams, deleteStream, goLive, endStream, type StreamInfo } from '../../api/streams'
|
||||
|
||||
const { Title } = Typography
|
||||
const { Title, Text } = Typography
|
||||
|
||||
const statusMap: Record<string, { color: string; label: string }> = {
|
||||
pending: { color: 'blue', label: '待开播' },
|
||||
@@ -36,27 +36,51 @@ export default function StreamList() {
|
||||
}
|
||||
|
||||
const columns = [
|
||||
{ title: 'ID', dataIndex: 'id', key: 'id', width: 80 },
|
||||
{ title: '标题', dataIndex: 'title', key: 'title', ellipsis: true },
|
||||
{ title: '状态', dataIndex: 'status', key: 'status', width: 100,
|
||||
{ title: 'ID', dataIndex: 'id', key: 'id', width: 70 },
|
||||
{
|
||||
title: '标题', dataIndex: 'title', key: 'title', ellipsis: true,
|
||||
render: (v: string, r: StreamInfo) => (
|
||||
<Space direction="vertical" size={0}>
|
||||
<Text strong>{v}</Text>
|
||||
{r.description && <Text type="secondary" style={{ fontSize: 12 }} ellipsis>{r.description}</Text>}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{ 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 },
|
||||
{ title: '推流码', dataIndex: 'stream_key', key: 'stream_key', width: 130,
|
||||
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: 280,
|
||||
render: (_: any, r: StreamInfo) => (
|
||||
<Space size="small">
|
||||
<Button size="small" icon={<LinkOutlined />}
|
||||
onClick={() => { navigator.clipboard.writeText(r.stream_key); message.success('推流码已复制') }}>
|
||||
复制推流码
|
||||
</Button>
|
||||
<Space size={4}>
|
||||
<Tooltip title="复制推流码">
|
||||
<Button size="small" icon={<LinkOutlined />}
|
||||
onClick={e => { e.stopPropagation(); navigator.clipboard.writeText(r.stream_key); message.success('推流码已复制') }} />
|
||||
</Tooltip>
|
||||
{r.status !== 'live' ? (
|
||||
<Tooltip title="开播"><Button size="small" type="primary" icon={<PlayCircleOutlined />} onClick={() => handleGoLive(r.id)} ghost /></Tooltip>
|
||||
<Tooltip title="开播">
|
||||
<Button size="small" type="primary" icon={<PlayCircleOutlined />} ghost
|
||||
onClick={e => { e.stopPropagation(); handleGoLive(r.id) }} />
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Tooltip title="结束"><Button size="small" icon={<StopOutlined />} onClick={() => handleEnd(r.id)} danger /></Tooltip>
|
||||
<Tooltip title="结束直播">
|
||||
<Button size="small" icon={<StopOutlined />} danger
|
||||
onClick={e => { e.stopPropagation(); handleEnd(r.id) }} />
|
||||
</Tooltip>
|
||||
)}
|
||||
<Button size="small" onClick={() => window.open(`/watch/${r.id}`, '_blank')}>观看</Button>
|
||||
<Popconfirm title="确定删除?" onConfirm={() => handleDelete(r.id)}>
|
||||
<Button size="small" icon={<DeleteOutlined />} danger />
|
||||
<Tooltip title="观看">
|
||||
<Button size="small" icon={<EyeOutlined />}
|
||||
onClick={e => { e.stopPropagation(); window.open(`/watch/${r.id}`, '_blank') }} />
|
||||
</Tooltip>
|
||||
<Popconfirm title="确定删除这条直播?" okText="删除" cancelText="取消" placement="left"
|
||||
onConfirm={e => { e?.stopPropagation(); handleDelete(r.id) }}
|
||||
onCancel={e => e?.stopPropagation()}>
|
||||
<Tooltip title="删除">
|
||||
<Button size="small" icon={<DeleteOutlined />} danger
|
||||
onClick={e => e.stopPropagation()} />
|
||||
</Tooltip>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
),
|
||||
@@ -66,10 +90,23 @@ export default function StreamList() {
|
||||
return (
|
||||
<div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}>
|
||||
<Title level={3} style={{ margin: 0 }}>直播列表</Title>
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={() => navigate('/admin/streams/new')}>创建直播</Button>
|
||||
<div>
|
||||
<Title level={3} style={{ margin: 0, fontWeight: 600 }}>直播列表</Title>
|
||||
<Text type="secondary" style={{ fontSize: 14 }}>管理所有直播流</Text>
|
||||
</div>
|
||||
<Button type="primary" icon={<PlusOutlined />} size="large" 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, showSizeChanger: false }}
|
||||
size="middle"
|
||||
locale={{ emptyText: '暂无直播,点击上方按钮创建' }}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user