feat: 完成TikTok短剧管理后台基础功能开发
本次提交搭建了完整的后台管理系统,包含: 1. 登录认证与权限校验体系 2. 工作台、短剧、用户、角色、人员、日志等全业务模块 3. 响应式布局与通用样式组件 4. 菜单动态过滤与路由权限控制 5. 移除了README中冗余的权限管理菜单项
This commit is contained in:
114
src/layout/index.tsx
Normal file
114
src/layout/index.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
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 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="/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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user