品牌真正接入全站渲染 + 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:
1 parent
efde5597bd
commit
d4a3742136
7 files changed
+178
-61
No files matched your search
@@ -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} © {new Date().getFullYear()}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user