1. 新增数据管理页面,支持查看数据概览、短剧/剧集指标和同步记录 2. 新增短剧详情页,支持查看短剧信息、管理剧集和上传视频 3. 新增菜单配置项,添加数据管理入口 4. 扩展API类型定义,新增剧集、上传任务、数据统计等类型 5. 修复timestamp类型为数字类型,新增短剧额外字段 6. 为页面布局添加统一的page-stack样式类 7. 新增短剧详情路由,优化短剧列表页跳转逻辑
118 lines
5.1 KiB
TypeScript
118 lines
5.1 KiB
TypeScript
import { useEffect, useMemo, useState } from 'react';
|
||
import {
|
||
Avatar,
|
||
Button,
|
||
Dropdown,
|
||
Layout,
|
||
Menu,
|
||
Space,
|
||
Typography,
|
||
} from '@arco-design/web-react';
|
||
import { IconMenuFold, IconMenuUnfold } from '@arco-design/web-react/icon';
|
||
import { Navigate, Route, Routes, useLocation, useNavigate } from 'react-router-dom';
|
||
import type { ApiResponse, AdminProfile } from '../api/types';
|
||
import { API_BASE_URL, TOKEN_KEY, api } from '../api/client';
|
||
import { filterMenuItems, findCurrentMenu, menuItems, renderMenuItem } from './menu';
|
||
import { PermissionRoute } from './PermissionRoute';
|
||
import DashboardPage from '../Dashboard';
|
||
import DramasPage from '../Drama';
|
||
import DataPage from '../Data';
|
||
import UsersPage from '../User';
|
||
import RolesPage from '../Role';
|
||
import PersonnelPage from '../Personnel';
|
||
import ProfilePage from '../Profile';
|
||
import LogsPage from '../Log';
|
||
import './layout.css';
|
||
|
||
const { Header, Sider, Content } = Layout;
|
||
const { Text } = Typography;
|
||
|
||
export function AdminLayout() {
|
||
const navigate = useNavigate();
|
||
const location = useLocation();
|
||
const [collapsed, setCollapsed] = useState(false);
|
||
const [profile, setProfile] = useState<AdminProfile | null>(null);
|
||
const current = useMemo(() => findCurrentMenu(location.pathname), [location.pathname]);
|
||
const visibleMenuItems = useMemo(() => filterMenuItems(menuItems, profile), [profile]);
|
||
const [openMenuKeys, setOpenMenuKeys] = useState<string[]>(current?.parent ? [current.parent.key] : []);
|
||
|
||
useEffect(() => {
|
||
api.get<ApiResponse<AdminProfile>>('/api/admin/v1/auth/profile')
|
||
.then((response) => setProfile(response.data.data))
|
||
.catch(() => {
|
||
localStorage.removeItem(TOKEN_KEY);
|
||
navigate('/login', { replace: true });
|
||
});
|
||
}, [navigate]);
|
||
|
||
useEffect(() => {
|
||
if (current?.parent && !openMenuKeys.includes(current.parent.key)) {
|
||
setOpenMenuKeys((keys) => [...keys, current.parent!.key]);
|
||
}
|
||
}, [current, openMenuKeys]);
|
||
|
||
const logout = () => {
|
||
localStorage.removeItem(TOKEN_KEY);
|
||
navigate('/login', { replace: true });
|
||
};
|
||
|
||
return (
|
||
<Layout className="layout">
|
||
<Sider className="sidebar" width={220} collapsed={collapsed} collapsible trigger={null}>
|
||
<div className="logo-row">
|
||
<span className="logo-mark">TK</span>
|
||
{!collapsed && <span>TK短剧管理后台</span>}
|
||
</div>
|
||
<Menu
|
||
selectedKeys={[current?.item.key || '/dashboard']}
|
||
openKeys={openMenuKeys}
|
||
onClickMenuItem={(key) => navigate(key)}
|
||
onClickSubMenu={(_, openKeys) => setOpenMenuKeys(openKeys)}
|
||
>
|
||
{visibleMenuItems.map((item) => renderMenuItem(item))}
|
||
</Menu>
|
||
</Sider>
|
||
<Layout>
|
||
<Header className="header">
|
||
<Space>
|
||
<Button icon={collapsed ? <IconMenuUnfold /> : <IconMenuFold />} onClick={() => setCollapsed(!collapsed)} />
|
||
<div className="header-title">
|
||
<strong>{current?.item.label || '工作台'}</strong>
|
||
<span>后端 API: {API_BASE_URL}</span>
|
||
</div>
|
||
</Space>
|
||
<Dropdown
|
||
position="br"
|
||
trigger="click"
|
||
droplist={(
|
||
<Menu>
|
||
<Menu.Item key="profile" onClick={() => navigate('/profile')}>个人中心</Menu.Item>
|
||
<Menu.Item key="logout" onClick={logout}>退出登录</Menu.Item>
|
||
</Menu>
|
||
)}
|
||
>
|
||
<button className="user-menu-trigger" type="button">
|
||
<Avatar size={28}>{profile?.username?.slice(0, 1).toUpperCase() || 'A'}</Avatar>
|
||
<Text>{profile?.nickname || profile?.username || '管理员'}</Text>
|
||
</button>
|
||
</Dropdown>
|
||
</Header>
|
||
<Content className="content">
|
||
<Routes>
|
||
<Route path="/dashboard" element={<DashboardPage profile={profile} />} />
|
||
<Route path="/dramas" element={<PermissionRoute profile={profile} requiredPermissions={['drama:read']}><DramasPage /></PermissionRoute>} />
|
||
<Route path="/dramas/:id" element={<PermissionRoute profile={profile} requiredPermissions={['drama:read']}><DramasPage /></PermissionRoute>} />
|
||
<Route path="/data" element={<PermissionRoute profile={profile} requiredPermissions={['data:read']}><DataPage /></PermissionRoute>} />
|
||
<Route path="/users" element={<PermissionRoute profile={profile} requiredPermissions={['user:read']}><UsersPage /></PermissionRoute>} />
|
||
<Route path="/roles" element={<PermissionRoute profile={profile} requiredPermissions={['role:read']}><RolesPage /></PermissionRoute>} />
|
||
<Route path="/personnel" element={<PermissionRoute profile={profile} requiredPermissions={['admin-user:read']}><PersonnelPage /></PermissionRoute>} />
|
||
<Route path="/profile" element={<PermissionRoute profile={profile}><ProfilePage /></PermissionRoute>} />
|
||
<Route path="/logs" element={<PermissionRoute profile={profile} requiredPermissions={['log:read']}><LogsPage /></PermissionRoute>} />
|
||
<Route path="*" element={<Navigate to="/dashboard" replace />} />
|
||
</Routes>
|
||
</Content>
|
||
</Layout>
|
||
</Layout>
|
||
);
|
||
}
|