全局整改: 布局重构、鉴权加固、API修复

- 首页/播放页: 去掉 antd Layout 改用 div 避免 flex 布局问题
- 播放页: 返回按钮改为 navigate('/') 替代 window.close()
- 后端登录/验证: 改用 POST JSON body 替代 query param
- 后端鉴权: require_auth 依赖注入保护所有管理接口
- nodes路由: 修复 /strategy 被 /{node_id} 拦截的顺序问题
- 前端 Settings: 策略更新改为 JSON body
This commit is contained in:
admin committed 2026-08-07 21:22:52 +08:00
1 parent f3b2a9cb46
commit b81a7bf293
7 files changed
+110 -64

No files matched your search

+2 -2
View File
@@ -34,7 +34,7 @@ export default function SettingsPage() {
async function saveStrategy(val: string) {
setSaving(true)
try {
await api.put(`/nodes/strategy?strategy=${val}&multicast_enabled=${multicastEnabled}`)
await api.put('/nodes/strategy', { strategy: val, multicast_enabled: multicastEnabled })
setStrategy(val)
message.success('策略已更新')
} catch { message.error('更新失败') }
@@ -44,7 +44,7 @@ export default function SettingsPage() {
async function toggleMulticast(val: boolean) {
setMulticastEnabled(val)
try {
await api.put(`/nodes/strategy?strategy=${strategy}&multicast_enabled=${val}`)
await api.put('/nodes/strategy', { strategy, multicast_enabled: val })
message.success('组播设置已更新')
} catch { message.error('更新失败') }
}
+2 -2
View File
@@ -1,7 +1,7 @@
import api, { setToken, getToken } from './client'
export async function login(password: string): Promise<string> {
const { data } = await api.post(`/auth/login?password=${encodeURIComponent(password)}`)
const { data } = await api.post('/auth/login', { password })
setToken(data.token)
return data.token
}
@@ -10,7 +10,7 @@ export async function verifyToken(): Promise<boolean> {
const token = getToken()
if (!token) return false
try {
await api.post(`/auth/verify?token=${encodeURIComponent(token)}`)
await api.post('/auth/verify', { token })
return true
} catch {
return false
+8 -9
View File
@@ -1,12 +1,11 @@
import { useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import {
Layout, Row, Col, Card, Typography, Tag, Button, Skeleton, Empty, Space,
Row, Col, Card, Typography, Tag, Button, Skeleton, Empty, Space,
} from 'antd'
import { LoginOutlined, FireOutlined, ClockCircleOutlined } from '@ant-design/icons'
import { fetchStreams, type StreamInfo } from '../../api/streams'
const { Header, Content } = Layout
const { Title, Text } = Typography
const statusMeta: Record<string, { color: string; label: string; icon: React.ReactNode }> = {
@@ -34,8 +33,8 @@ export default function HomePage() {
})
return (
<Layout style={{ minHeight: '100vh', background: '#FAF8F5' }}>
<Header style={{
<div style={{ minHeight: '100vh', background: '#FAF8F5' }}>
<div style={{
background: '#FFFFFF',
borderBottom: '1px solid #E8E4DD',
display: 'flex',
@@ -57,13 +56,13 @@ export default function HomePage() {
>
管理后台
</Button>
</Header>
<Content style={{ padding: '32px 48px', maxWidth: 1280, margin: '0 auto' }}>
</div>
<div style={{ maxWidth: 1280, margin: '0 auto', padding: '32px 48px' }}>
<Title level={3} style={{ marginBottom: 24 }}>全部直播</Title>
{loading ? (
<Row gutter={[24, 24]}>
{[1,2,3,4,5,6].map((i) => (
{[1, 2, 3, 4, 5, 6].map((i) => (
<Col key={i} xs={24} sm={12} md={8} lg={8}>
<Card><Skeleton active /></Card>
</Col>
@@ -120,7 +119,7 @@ export default function HomePage() {
})}
</Row>
)}
</Content>
</Layout>
</div>
</div>
)
}
+35 -16
View File
@@ -1,16 +1,16 @@
import { useState, useEffect, useRef } from 'react'
import { useParams } from 'react-router-dom'
import { useParams, useNavigate } from 'react-router-dom'
import {
Layout, Card, Select, Button, Typography, Space, Tag, Spin, Skeleton, Empty, message,
Select, Button, Typography, Space, Tag, Spin, Skeleton, Empty, message,
} from 'antd'
import { ArrowLeftOutlined, WifiOutlined, SignalFilled } from '@ant-design/icons'
import Hls from 'hls.js'
import { getWatchUrls, type WatchInfo, type LineInfo } from '../../api/streams'
const { Header, Content } = Layout
const { Title, Text } = Typography
export default function WatchPage() {
const navigate = useNavigate()
const { id } = useParams<{ id: string }>()
const videoRef = useRef<HTMLVideoElement>(null)
const [info, setInfo] = useState<WatchInfo | null>(null)
@@ -41,7 +41,7 @@ export default function WatchPage() {
setActiveQuality(data.qualities[0].label)
}
}
} catch { /* 直播尚未就绪 */ }
} catch { }
setLoading(false)
}
@@ -98,7 +98,13 @@ export default function WatchPage() {
if (!info) {
return (
<div style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center', background: '#FAF8F5' }}>
<div style={{
minHeight: '100vh',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: '#FAF8F5',
}}>
<Empty description="未找到该直播" />
</div>
)
@@ -107,19 +113,25 @@ export default function WatchPage() {
const statusLabel = info.status === 'live' ? '直播中' : info.status === 'pending' ? '待开播' : info.status === 'ended' ? '已结束' : info.status
return (
<Layout style={{ minHeight: '100vh', background: '#1A1A16' }}>
<Header style={{
<div style={{
minHeight: '100vh',
background: '#1A1A16',
display: 'flex',
flexDirection: 'column',
}}>
<div style={{
height: 56,
minHeight: 56,
background: 'rgba(26,26,22,0.95)',
borderBottom: '1px solid rgba(255,255,255,0.08)',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '0 24px',
height: 56,
}}>
<Space>
<Button type="text" icon={<ArrowLeftOutlined />} style={{ color: '#fff' }}
onClick={() => window.close()}>
onClick={() => navigate('/')}>
返回
</Button>
<div>
@@ -157,21 +169,28 @@ export default function WatchPage() {
placeholder="选择线路"
/>
</Space>
</Header>
<Content style={{
</div>
<div style={{
flex: 1,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: 24,
maxHeight: 'calc(100vh - 56px)',
}}>
<div style={{ width: '100%', maxWidth: 1280, aspectRatio: '16/9', background: '#000', position: 'relative' }}>
<div style={{
width: '100%',
maxWidth: 1280,
aspectRatio: '16 / 9',
maxHeight: 'calc(100vh - 56px - 48px)',
background: '#000',
position: 'relative',
}}>
<video
ref={videoRef}
controls
autoPlay
playsInline
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
style={{ width: '100%', height: '100%', objectFit: 'contain', display: 'block' }}
/>
{info.status !== 'live' && (
<div style={{
@@ -189,7 +208,7 @@ export default function WatchPage() {
</div>
)}
</div>
</Content>
</Layout>
</div>
</div>
)
}