品牌真正接入全站渲染 + document.title 同步 + 设置页实时刷新

- BrandContext 全局上下文: 挂载时拉取 GET /api/settings/brand
- HomePage: 品牌名称/标语/Logo 动态渲染, 封面卡片精调
- AdminLayout: 侧栏品牌名称/顶栏标语动态显示
- LoginPage: 登录页品牌 Logo + 名称
- WatchPage: 播放页接入品牌上下文
- Settings: 保存后调用 refresh() 全站即时刷新
- App.tsx: document.title 跟随 brand_name 变化
- 组件分层: AppRoutes + BrandProvider 避免 import 循环
This commit is contained in:
admin committed 2026-08-10 16:28:00 +08:00
1 parent efde5597bd
commit d4a3742136
7 files changed
+178 -61

No files matched your search

+45 -18
View File
@@ -1,8 +1,9 @@
import { useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import { Row, Col, Card, Typography, Tag, Skeleton, Empty, Space } from 'antd'
import { Row, Col, Card, Typography, Tag, Skeleton, Empty } from 'antd'
import { FireOutlined, ClockCircleOutlined, VideoCameraOutlined } from '@ant-design/icons'
import { fetchStreams, type StreamInfo } from '../../api/streams'
import { useBrand } from '../../brand/BrandContext'
const { Title, Text } = Typography
@@ -14,6 +15,7 @@ const statusMeta: Record<string, { color: string; label: string; icon: React.Rea
}
export default function HomePage() {
const { brand } = useBrand()
const [streams, setStreams] = useState<StreamInfo[]>([])
const [loading, setLoading] = useState(true)
const navigate = useNavigate()
@@ -30,23 +32,35 @@ export default function HomePage() {
return (
<div style={{ minHeight: '100vh', background: '#F6F3ED' }}>
<div style={{
background: '#FBF9F6', borderBottom: '1px solid #E8E3D8',
padding: '48px 48px 40px',
background: '#FBF9F6',
borderBottom: '1px solid #E8E3D8',
padding: '56px 48px 48px',
}}>
<div style={{ maxWidth: 1280, margin: '0 auto' }}>
{brand.brand_logo_url && (
<img src={brand.brand_logo_url} alt={brand.brand_name}
style={{ height: 40, marginBottom: 16, display: 'block' }} />
)}
<Title level={1} style={{
margin: 0, fontWeight: 600, letterSpacing: -1,
margin: 0, fontWeight: 600, letterSpacing: -1, fontSize: 36,
fontFamily: '"Source Serif 4", "Noto Serif SC", Georgia, serif',
}}>
StreamCore
{brand.brand_name}
</Title>
<Text type="secondary" style={{ fontSize: 17, marginTop: 8, display: 'block', fontWeight: 400 }}>
企业级直播平台
</Text>
{brand.brand_tagline && (
<Text type="secondary" style={{ fontSize: 17, marginTop: 8, display: 'block', fontWeight: 400 }}>
{brand.brand_tagline}
</Text>
)}
</div>
</div>
<div style={{ maxWidth: 1280, margin: '0 auto', padding: '40px 48px' }}>
<Title level={3} style={{ marginBottom: 24, fontWeight: 600 }}>直播频道</Title>
<div style={{ display: 'flex', alignItems: 'baseline', gap: 12, marginBottom: 24 }}>
<Title level={3} style={{ margin: 0, fontWeight: 600 }}>直播频道</Title>
<Text type="secondary">{sorted.length} 个直播</Text>
</div>
{loading ? (
<Row gutter={[24, 24]}>
{[1,2,3,4,5,6].map(i => (
@@ -63,7 +77,7 @@ export default function HomePage() {
const meta = statusMeta[s.status] ?? statusMeta.pending
return (
<Col key={s.id} xs={24} sm={12} md={8} lg={8}>
<Card hoverable style={{ overflow: 'hidden', borderRadius: 10 }}
<Card hoverable style={{ overflow: 'hidden', borderRadius: 10, transition: 'box-shadow 0.2s, transform 0.2s' }}
cover={
<div style={{
height: 180, position: 'relative',
@@ -72,17 +86,24 @@ export default function HomePage() {
: 'linear-gradient(135deg, #3D3226 0%, #5C4A3A 50%, #2B241C 100%)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
}}>
{!s.cover_url && (
<VideoCameraOutlined style={{ color: 'rgba(232,227,216,0.2)', fontSize: 48 }} />
{!s.cover_url && <VideoCameraOutlined style={{ color: 'rgba(232,227,216,0.15)', fontSize: 52 }} />}
{s.status === 'live' && (
<div style={{ position: 'absolute', top: 12, left: 12 }}>
<Tag color="red" style={{ margin: 0, fontWeight: 600, border: 0, fontSize: 12 }}>
{meta.icon} {meta.label}
</Tag>
</div>
)}
{s.status !== 'live' && (
<div style={{ position: 'absolute', bottom: 12, left: 12 }}>
<Tag color={meta.color} style={{ margin: 0, fontWeight: 600, border: 0, fontSize: 12 }}>
{meta.icon} {meta.label}
</Tag>
</div>
)}
<div style={{ position: 'absolute', top: 12, right: 12 }}>
<Tag color={meta.color} style={{ margin: 0, fontWeight: 600, border: 0 }}>
{meta.icon} {meta.label}
</Tag>
</div>
</div>
}
bodyStyle={{ padding: '16px 20px' }}
bodyStyle={{ padding: '18px 22px' }}
onClick={() => navigate(`/watch/${s.id}`)}
>
<Card.Meta
@@ -96,6 +117,12 @@ export default function HomePage() {
</Row>
)}
</div>
<div style={{ maxWidth: 1280, margin: '0 auto', padding: '0 48px 48px', textAlign: 'center' }}>
<Text type="secondary" style={{ fontSize: 13 }}>
{brand.brand_name} &copy; {new Date().getFullYear()}
</Text>
</div>
</div>
)
}
+2
View File
@@ -4,10 +4,12 @@ import { Select, Button, Typography, Space, Tag, Spin, Skeleton, Empty, message
import { ArrowLeftOutlined, WifiOutlined, SignalFilled } from '@ant-design/icons'
import Hls from 'hls.js'
import { getWatchUrls, type WatchInfo, type LineInfo } from '../../api/streams'
import { useBrand } from '../../brand/BrandContext'
const { Title, Text } = Typography
export default function WatchPage() {
const { brand } = useBrand()
const navigate = useNavigate()
const { id } = useParams<{ id: string }>()
const videoRef = useRef<HTMLVideoElement>(null)