- BrandContext 全局上下文: 挂载时拉取 GET /api/settings/brand - HomePage: 品牌名称/标语/Logo 动态渲染, 封面卡片精调 - AdminLayout: 侧栏品牌名称/顶栏标语动态显示 - LoginPage: 登录页品牌 Logo + 名称 - WatchPage: 播放页接入品牌上下文 - Settings: 保存后调用 refresh() 全站即时刷新 - App.tsx: document.title 跟随 brand_name 变化 - 组件分层: AppRoutes + BrandProvider 避免 import 循环
80 lines
2.8 KiB
TypeScript
80 lines
2.8 KiB
TypeScript
import { useState } from 'react'
|
|
import { Outlet, useNavigate, useLocation } from 'react-router-dom'
|
|
import { Layout, Menu, Button, Typography } from 'antd'
|
|
import {
|
|
DashboardOutlined, VideoCameraOutlined, PlusOutlined,
|
|
CloudServerOutlined, SettingOutlined, LogoutOutlined,
|
|
} from '@ant-design/icons'
|
|
import { clearToken } from '../api/client'
|
|
import { useBrand } from '../brand/BrandContext'
|
|
|
|
const { Sider, Content, Header } = Layout
|
|
const { Text } = Typography
|
|
|
|
const menuItems = [
|
|
{ key: '/admin', icon: <DashboardOutlined />, label: '概览' },
|
|
{ key: '/admin/streams', icon: <VideoCameraOutlined />, label: '直播列表' },
|
|
{ key: '/admin/streams/new', icon: <PlusOutlined />, label: '创建直播' },
|
|
{ key: '/admin/nodes', icon: <CloudServerOutlined />, label: '边缘节点' },
|
|
{ key: '/admin/settings', icon: <SettingOutlined />, label: '系统设置' },
|
|
]
|
|
|
|
export default function AdminLayout() {
|
|
const { brand } = useBrand()
|
|
const [collapsed, setCollapsed] = useState(false)
|
|
const navigate = useNavigate()
|
|
const location = useLocation()
|
|
|
|
const selectedKey = menuItems.find(
|
|
(item) => location.pathname === item.key || (location.pathname === '/' && item.key === '/admin')
|
|
)?.key || '/admin'
|
|
|
|
const handleLogout = () => {
|
|
clearToken()
|
|
navigate('/admin/login')
|
|
}
|
|
|
|
return (
|
|
<Layout style={{ minHeight: '100vh' }}>
|
|
<Sider
|
|
collapsible collapsed={collapsed} onCollapse={setCollapsed}
|
|
width={220}
|
|
style={{ borderRight: '1px solid #E8E3D8', background: '#FBF9F6' }}
|
|
>
|
|
<div style={{
|
|
height: 60, display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
borderBottom: '1px solid #E8E3D8',
|
|
}}>
|
|
<Text strong style={{
|
|
fontSize: collapsed ? 14 : 18, letterSpacing: -0.5,
|
|
fontFamily: '"Source Serif 4", "Noto Serif SC", Georgia, serif',
|
|
color: '#C2693A',
|
|
}}>
|
|
{collapsed ? brand.brand_name.slice(0, 2) : brand.brand_name}
|
|
</Text>
|
|
</div>
|
|
<Menu
|
|
mode="inline" selectedKeys={[selectedKey]} items={menuItems}
|
|
onClick={({ key }) => navigate(key)}
|
|
style={{ border: 'none', marginTop: 8 }}
|
|
/>
|
|
</Sider>
|
|
<Layout>
|
|
<Header style={{
|
|
background: '#FBF9F6', borderBottom: '1px solid #E8E3D8',
|
|
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
|
|
padding: '0 40px',
|
|
}}>
|
|
<Text type="secondary" style={{ fontSize: 13 }}>{brand.brand_tagline}</Text>
|
|
<Button type="text" icon={<LogoutOutlined />} onClick={handleLogout}>
|
|
退出登录
|
|
</Button>
|
|
</Header>
|
|
<Content style={{ padding: 32, overflow: 'auto' }}>
|
|
<Outlet />
|
|
</Content>
|
|
</Layout>
|
|
</Layout>
|
|
)
|
|
}
|