Files
cyh-tk-admin/src/layout/index.tsx
zhxiao1124 a691db573c feat: 新增数据管理页面与短剧详情页,完善接口类型与菜单配置
1.  新增数据管理页面,支持查看数据概览、短剧/剧集指标和同步记录
2.  新增短剧详情页,支持查看短剧信息、管理剧集和上传视频
3.  新增菜单配置项,添加数据管理入口
4.  扩展API类型定义,新增剧集、上传任务、数据统计等类型
5.  修复timestamp类型为数字类型,新增短剧额外字段
6.  为页面布局添加统一的page-stack样式类
7.  新增短剧详情路由,优化短剧列表页跳转逻辑
2026-07-01 16:35:57 +08:00

118 lines
5.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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>
);
}