save code
This commit is contained in:
@@ -0,0 +1,879 @@
|
||||
# 路由设计文档
|
||||
|
||||
## 1. 路由架构
|
||||
|
||||
### 1.1 路由结构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ App Root │
|
||||
├─────────────────────────────────────────┤
|
||||
│ Router (React Router v6) │
|
||||
│ ├─ /login (登录页) │
|
||||
│ ├─ /403 (无权限页) │
|
||||
│ ├─ /404 (404页) │
|
||||
│ ├─ / (主布局) │
|
||||
│ │ ├─ /dashboard (仪表盘) │
|
||||
│ │ ├─ /projects (项目列表) │
|
||||
│ │ ├─ /projects/create (创建项目) │
|
||||
│ │ ├─ /projects/:id (项目详情) │
|
||||
│ │ ├─ /projects/:id/edit (编辑项目) │
|
||||
│ │ ├─ /users (用户列表) [admin] │
|
||||
│ │ ├─ /users/create (创建用户) [admin] │
|
||||
│ │ ├─ /users/:id (用户详情) [admin] │
|
||||
│ │ └─ /statistics (统计分析) │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 1.2 路由配置
|
||||
|
||||
```javascript
|
||||
// router/routes.js
|
||||
const routes = [
|
||||
{
|
||||
path: '/login',
|
||||
element: <Login />,
|
||||
meta: {
|
||||
title: '登录',
|
||||
requiresAuth: false,
|
||||
hideInMenu: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/403',
|
||||
element: <Forbidden />,
|
||||
meta: {
|
||||
title: '无权限',
|
||||
requiresAuth: false,
|
||||
hideInMenu: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/404',
|
||||
element: <NotFound />,
|
||||
meta: {
|
||||
title: '页面不存在',
|
||||
requiresAuth: false,
|
||||
hideInMenu: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
element: <Layout />,
|
||||
meta: {
|
||||
title: '主页',
|
||||
requiresAuth: true,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/dashboard',
|
||||
element: <Dashboard />,
|
||||
meta: {
|
||||
title: '仪表盘',
|
||||
icon: 'DashboardOutlined',
|
||||
order: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/projects',
|
||||
element: <ProjectList />,
|
||||
meta: {
|
||||
title: '项目管理',
|
||||
icon: 'FileTextOutlined',
|
||||
order: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/projects/create',
|
||||
element: <ProjectForm />,
|
||||
meta: {
|
||||
title: '创建项目',
|
||||
parent: '/projects',
|
||||
hideInMenu: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/projects/:id',
|
||||
element: <ProjectDetail />,
|
||||
meta: {
|
||||
title: '项目详情',
|
||||
parent: '/projects',
|
||||
hideInMenu: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/projects/:id/edit',
|
||||
element: <ProjectForm />,
|
||||
meta: {
|
||||
title: '编辑项目',
|
||||
parent: '/projects',
|
||||
hideInMenu: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/users',
|
||||
element: <UserList />,
|
||||
meta: {
|
||||
title: '用户管理',
|
||||
icon: 'UserOutlined',
|
||||
order: 3,
|
||||
requiresRole: ['admin'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/users/create',
|
||||
element: <UserForm />,
|
||||
meta: {
|
||||
title: '创建用户',
|
||||
parent: '/users',
|
||||
requiresRole: ['admin'],
|
||||
hideInMenu: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/users/:id',
|
||||
element: <UserDetail />,
|
||||
meta: {
|
||||
title: '用户详情',
|
||||
parent: '/users',
|
||||
requiresRole: ['admin'],
|
||||
hideInMenu: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/users/:id/edit',
|
||||
element: <UserForm />,
|
||||
meta: {
|
||||
title: '编辑用户',
|
||||
parent: '/users',
|
||||
requiresRole: ['admin'],
|
||||
hideInMenu: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/statistics',
|
||||
element: <Statistics />,
|
||||
meta: {
|
||||
title: '统计分析',
|
||||
icon: 'BarChartOutlined',
|
||||
order: 4,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
||||
```
|
||||
|
||||
## 2. 路由守卫
|
||||
|
||||
### 2.1 PrivateRoute组件
|
||||
|
||||
```javascript
|
||||
// router/PrivateRoute.jsx
|
||||
import { Navigate, useLocation } from 'react-router-dom';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
|
||||
/**
|
||||
* 路由守卫组件
|
||||
*/
|
||||
const PrivateRoute = ({ children, meta = {} }) => {
|
||||
const { isAuthenticated, user, loading } = useAuth();
|
||||
const location = useLocation();
|
||||
|
||||
const { requiresAuth = true, requiresRole = [] } = meta;
|
||||
|
||||
// 检查加载状态
|
||||
if (loading) {
|
||||
return <Spin />;
|
||||
}
|
||||
|
||||
// 检查认证状态
|
||||
if (requiresAuth && !isAuthenticated) {
|
||||
return <Navigate to="/login" state={{ from: location }} replace />;
|
||||
}
|
||||
|
||||
// 检查角色权限
|
||||
if (requiresRole.length > 0 && !requiresRole.includes(user?.role)) {
|
||||
return <Navigate to="/403" replace />;
|
||||
}
|
||||
|
||||
return children;
|
||||
};
|
||||
|
||||
export default PrivateRoute;
|
||||
```
|
||||
|
||||
### 2.2 使用路由守卫
|
||||
|
||||
```javascript
|
||||
// router/index.jsx
|
||||
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
|
||||
import PrivateRoute from './PrivateRoute';
|
||||
import routes from './routes';
|
||||
|
||||
const Router = () => {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
{routes.map((route) => {
|
||||
const { path, element, meta, children } = route;
|
||||
|
||||
if (children) {
|
||||
return (
|
||||
<Route
|
||||
key={path}
|
||||
path={path}
|
||||
element={
|
||||
<PrivateRoute meta={meta}>
|
||||
{element}
|
||||
</PrivateRoute>
|
||||
}
|
||||
>
|
||||
{children.map((child) => (
|
||||
<Route
|
||||
key={child.path}
|
||||
path={child.path}
|
||||
element={
|
||||
<PrivateRoute meta={child.meta}>
|
||||
{child.element}
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</Route>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Route
|
||||
key={path}
|
||||
path={path}
|
||||
element={
|
||||
<PrivateRoute meta={meta}>
|
||||
{element}
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* 默认重定向 */}
|
||||
<Route path="/" element={<Navigate to="/dashboard" replace />} />
|
||||
|
||||
{/* 404页面 */}
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
);
|
||||
};
|
||||
|
||||
export default Router;
|
||||
```
|
||||
|
||||
## 3. 路由参数处理
|
||||
|
||||
### 3.1 useParams Hook
|
||||
|
||||
```jsx
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
const ProjectDetail = () => {
|
||||
const { id } = useParams();
|
||||
const { data, loading } = useApi(() => projectAPI.getDetail(id));
|
||||
|
||||
if (loading) return <Spin />;
|
||||
return <div>{/* 项目详情 */}</div>;
|
||||
};
|
||||
```
|
||||
|
||||
### 3.2 useSearchParams Hook
|
||||
|
||||
```jsx
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { useDebounce } from '../hooks/useDebounce';
|
||||
|
||||
const ProjectList = () => {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const keyword = searchParams.get('keyword') || '';
|
||||
const debouncedKeyword = useDebounce(keyword, 300);
|
||||
|
||||
// 搜索逻辑
|
||||
useEffect(() => {
|
||||
fetchList({ keyword: debouncedKeyword });
|
||||
}, [debouncedKeyword]);
|
||||
|
||||
const handleSearch = (value) => {
|
||||
setSearchParams({ keyword: value });
|
||||
};
|
||||
|
||||
return <Input.Search value={keyword} onChange={(e) => handleSearch(e.target.value)} />;
|
||||
};
|
||||
```
|
||||
|
||||
### 3.3 useLocation Hook
|
||||
|
||||
```jsx
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
const ProjectForm = () => {
|
||||
const location = useLocation();
|
||||
const isEditMode = location.pathname.includes('/edit');
|
||||
|
||||
return <div>{isEditMode ? '编辑模式' : '创建模式'}</div>;
|
||||
};
|
||||
```
|
||||
|
||||
### 3.4 useNavigate Hook
|
||||
|
||||
```jsx
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
const ProjectList = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleCreate = () => {
|
||||
navigate('/projects/create');
|
||||
};
|
||||
|
||||
const handleEdit = (id) => {
|
||||
navigate(`/projects/${id}/edit`);
|
||||
};
|
||||
|
||||
const handleBack = () => {
|
||||
navigate(-1); // 返回上一页
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button onClick={handleCreate}>创建项目</Button>
|
||||
<Button onClick={() => handleEdit(1)}>编辑项目</Button>
|
||||
<Button onClick={handleBack}>返回</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## 4. 路由元信息
|
||||
|
||||
### 4.1 路由元信息定义
|
||||
|
||||
```javascript
|
||||
// router/routes.js
|
||||
|
||||
const routeMeta = {
|
||||
// 页面标题
|
||||
title: '项目管理',
|
||||
|
||||
// 菜单图标
|
||||
icon: 'FileTextOutlined',
|
||||
|
||||
// 菜单排序
|
||||
order: 1,
|
||||
|
||||
// 是否需要认证
|
||||
requiresAuth: true,
|
||||
|
||||
// 允许的角色
|
||||
requiresRole: ['admin', 'market'],
|
||||
|
||||
// 父路由
|
||||
parent: '/projects',
|
||||
|
||||
// 是否在菜单中隐藏
|
||||
hideInMenu: false,
|
||||
|
||||
// 是否在面包屑中隐藏
|
||||
hideInBreadcrumb: false,
|
||||
|
||||
// 缓存配置
|
||||
keepAlive: false,
|
||||
};
|
||||
```
|
||||
|
||||
### 4.2 使用路由元信息
|
||||
|
||||
```javascript
|
||||
// hooks/useRouteMeta.js
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
import routes from '../router/routes';
|
||||
|
||||
/**
|
||||
* 路由元信息Hook
|
||||
*/
|
||||
export const useRouteMeta = () => {
|
||||
const location = useLocation();
|
||||
const params = useParams();
|
||||
|
||||
// 查找当前路由配置
|
||||
const findRouteMeta = (path, routeList) => {
|
||||
for (const route of routeList) {
|
||||
// 精确匹配
|
||||
if (route.path === path) {
|
||||
return route.meta;
|
||||
}
|
||||
|
||||
// 参数匹配
|
||||
const routePattern = route.path.replace(/:[^/]+/g, '[^/]+');
|
||||
const regex = new RegExp(`^${routePattern}$`);
|
||||
if (regex.test(path)) {
|
||||
return route.meta;
|
||||
}
|
||||
|
||||
// 递归查找子路由
|
||||
if (route.children) {
|
||||
const childMeta = findRouteMeta(path, route.children);
|
||||
if (childMeta) return childMeta;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
const meta = findRouteMeta(location.pathname, routes);
|
||||
|
||||
return {
|
||||
meta,
|
||||
pathname: location.pathname,
|
||||
search: location.search,
|
||||
params,
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
### 4.3 动态设置页面标题
|
||||
|
||||
```javascript
|
||||
// App.jsx
|
||||
import { useEffect } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useRouteMeta } from './hooks/useRouteMeta';
|
||||
|
||||
const App = () => {
|
||||
const { meta } = useRouteMeta();
|
||||
|
||||
useEffect(() => {
|
||||
if (meta.title) {
|
||||
document.title = `${meta.title} - 海洋项目管理系统`;
|
||||
}
|
||||
}, [meta.title]);
|
||||
|
||||
return <Router />;
|
||||
};
|
||||
```
|
||||
|
||||
## 5. 菜单生成
|
||||
|
||||
### 5.1 根据路由生成菜单
|
||||
|
||||
```javascript
|
||||
// hooks/useMenu.js
|
||||
import { useMemo } from 'react';
|
||||
import { useAuth } from './useAuth';
|
||||
import { useRouteMeta } from './useRouteMeta';
|
||||
import routes from '../router/routes';
|
||||
|
||||
/**
|
||||
* 生成菜单
|
||||
*/
|
||||
export const useMenu = () => {
|
||||
const { hasRole } = useAuth();
|
||||
const { pathname } = useRouteMeta();
|
||||
|
||||
const menuItems = useMemo(() => {
|
||||
const generateMenu = (routeList) => {
|
||||
return routeList
|
||||
.filter((route) => {
|
||||
// 过滤隐藏的菜单项
|
||||
if (route.meta?.hideInMenu) return false;
|
||||
|
||||
// 过滤需要权限的菜单项
|
||||
if (route.meta?.requiresRole?.length > 0) {
|
||||
return hasRole(route.meta.requiresRole);
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.map((route) => {
|
||||
if (route.children) {
|
||||
return {
|
||||
key: route.path,
|
||||
icon: route.meta?.icon,
|
||||
label: route.meta?.title,
|
||||
children: generateMenu(route.children),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
key: route.path,
|
||||
icon: route.meta?.icon,
|
||||
label: route.meta?.title,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
return generateMenu(routes);
|
||||
}, [hasRole]);
|
||||
|
||||
const selectedKeys = useMemo(() => {
|
||||
return [pathname];
|
||||
}, [pathname]);
|
||||
|
||||
const openKeys = useMemo(() => {
|
||||
// 根据当前路径展开子菜单
|
||||
const paths = pathname.split('/').filter(Boolean);
|
||||
const keys = paths.map((_, index) => `/${paths.slice(0, index + 1).join('/')}`);
|
||||
return keys;
|
||||
}, [pathname]);
|
||||
|
||||
return {
|
||||
menuItems,
|
||||
selectedKeys,
|
||||
openKeys,
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
### 5.2 侧边栏菜单
|
||||
|
||||
```jsx
|
||||
// components/Layout/Sidebar.jsx
|
||||
import { Menu } from 'antd';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import { useMenu } from '../../hooks/useMenu';
|
||||
|
||||
const Sidebar = () => {
|
||||
const navigate = useNavigate();
|
||||
const { menuItems, selectedKeys, openKeys } = useMenu();
|
||||
|
||||
const handleMenuClick = ({ key }) => {
|
||||
navigate(key);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="sidebar">
|
||||
<div className="sidebar-logo">
|
||||
<img src="/logo.png" alt="Logo" />
|
||||
<span>海洋项目管理系统</span>
|
||||
</div>
|
||||
<Menu
|
||||
mode="inline"
|
||||
theme="dark"
|
||||
items={menuItems}
|
||||
selectedKeys={selectedKeys}
|
||||
defaultOpenKeys={openKeys}
|
||||
onClick={handleMenuClick}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## 6. 面包屑导航
|
||||
|
||||
### 6.1 面包屑生成
|
||||
|
||||
```javascript
|
||||
// hooks/useBreadcrumb.js
|
||||
import { useMemo } from 'react';
|
||||
import { useLocation, useMatches } from 'react-router-dom';
|
||||
import routes from '../router/routes';
|
||||
|
||||
/**
|
||||
* 生成面包屑
|
||||
*/
|
||||
export const useBreadcrumb = () => {
|
||||
const location = useLocation();
|
||||
|
||||
const breadcrumbItems = useMemo(() => {
|
||||
const items = [];
|
||||
|
||||
// 查找当前路径的所有路由
|
||||
const pathSegments = location.pathname.split('/').filter(Boolean);
|
||||
let currentPath = '';
|
||||
|
||||
for (const segment of pathSegments) {
|
||||
currentPath += `/${segment}`;
|
||||
|
||||
// 查找路由配置
|
||||
const findRoute = (routeList, path) => {
|
||||
for (const route of routeList) {
|
||||
if (route.path === path) {
|
||||
return route;
|
||||
}
|
||||
|
||||
if (route.children) {
|
||||
const found = findRoute(route.children, path);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const route = findRoute(routes, currentPath);
|
||||
|
||||
if (route && !route.meta?.hideInBreadcrumb) {
|
||||
items.push({
|
||||
path: currentPath,
|
||||
title: route.meta?.title || segment,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}, [location.pathname]);
|
||||
|
||||
return { breadcrumbItems };
|
||||
};
|
||||
```
|
||||
|
||||
### 6.2 面包屑组件
|
||||
|
||||
```jsx
|
||||
// components/Common/Breadcrumb.jsx
|
||||
import { Breadcrumb } from 'antd';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useBreadcrumb } from '../../hooks/useBreadcrumb';
|
||||
|
||||
const BreadcrumbNav = () => {
|
||||
const navigate = useNavigate();
|
||||
const { breadcrumbItems } = useBreadcrumb();
|
||||
|
||||
const handleBreadcrumbClick = (path) => {
|
||||
navigate(path);
|
||||
};
|
||||
|
||||
return (
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item onClick={() => navigate('/home')}>首页</Breadcrumb.Item>
|
||||
{breadcrumbItems.map((item, index) => (
|
||||
<Breadcrumb.Item
|
||||
key={item.path}
|
||||
onClick={() => handleBreadcrumbClick(item.path)}
|
||||
>
|
||||
{item.title}
|
||||
</Breadcrumb.Item>
|
||||
))}
|
||||
</Breadcrumb>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## 7. 路由过渡动画
|
||||
|
||||
### 7.1 路由过渡组件
|
||||
|
||||
```jsx
|
||||
// components/RouterTransition.jsx
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { CSSTransition, SwitchTransition } from 'react-transition-group';
|
||||
import './RouterTransition.css';
|
||||
|
||||
const RouterTransition = ({ children }) => {
|
||||
const location = useLocation();
|
||||
|
||||
return (
|
||||
<SwitchTransition>
|
||||
<CSSTransition
|
||||
key={location.pathname}
|
||||
timeout={300}
|
||||
classNames="fade"
|
||||
unmountOnExit
|
||||
>
|
||||
{children}
|
||||
</CSSTransition>
|
||||
</SwitchTransition>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### 7.2 过渡动画样式
|
||||
|
||||
```css
|
||||
/* RouterTransition.css */
|
||||
.fade-enter {
|
||||
opacity: 0;
|
||||
transform: translateX(20px);
|
||||
}
|
||||
|
||||
.fade-enter-active {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
transition: opacity 300ms, transform 300ms;
|
||||
}
|
||||
|
||||
.fade-exit {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.fade-exit-active {
|
||||
opacity: 0;
|
||||
transform: translateX(-20px);
|
||||
transition: opacity 300ms, transform 300ms;
|
||||
}
|
||||
```
|
||||
|
||||
## 8. 路由缓存
|
||||
|
||||
### 8.1 KeepAlive组件
|
||||
|
||||
```jsx
|
||||
// hooks/useKeepAlive.js
|
||||
import { useState, useRef } from 'react';
|
||||
|
||||
const keepAliveCache = new Map();
|
||||
|
||||
export const useKeepAlive = (cacheKey, children) => {
|
||||
const [isAlive, setIsAlive] = useState(false);
|
||||
|
||||
const activate = () => setIsAlive(true);
|
||||
const deactivate = () => setIsAlive(false);
|
||||
|
||||
if (!keepAliveCache.has(cacheKey)) {
|
||||
keepAliveCache.set(cacheKey, { children, isActive: false });
|
||||
}
|
||||
|
||||
const cache = keepAliveCache.get(cacheKey);
|
||||
|
||||
return {
|
||||
isAlive,
|
||||
activate,
|
||||
deactivate,
|
||||
cachedChildren: cache.children,
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
### 8.2 使用KeepAlive
|
||||
|
||||
```jsx
|
||||
const CachedComponent = () => {
|
||||
const { cachedChildren } = useKeepAlive('dashboard', <Dashboard />);
|
||||
return cachedChildren;
|
||||
};
|
||||
```
|
||||
|
||||
## 9. 路由权限控制
|
||||
|
||||
### 9.1 权限指令
|
||||
|
||||
```jsx
|
||||
// components/Permission.jsx
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
|
||||
/**
|
||||
* 权限控制组件
|
||||
*/
|
||||
const Permission = ({ role, permission, children, fallback = null }) => {
|
||||
const { user, hasRole, hasPermission } = useAuth();
|
||||
|
||||
// 检查角色权限
|
||||
if (role && !hasRole(role)) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
// 检查功能权限
|
||||
if (permission && !hasPermission(permission)) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return children;
|
||||
};
|
||||
|
||||
export default Permission;
|
||||
```
|
||||
|
||||
### 9.2 使用权限组件
|
||||
|
||||
```jsx
|
||||
import Permission from './Permission';
|
||||
|
||||
const ProjectList = () => {
|
||||
return (
|
||||
<div>
|
||||
{/* 只有管理员可见 */}
|
||||
<Permission role="admin">
|
||||
<Button>删除项目</Button>
|
||||
</Permission>
|
||||
|
||||
{/* 只有市场部用户可见 */}
|
||||
<Permission role="market">
|
||||
<Button>创建项目</Button>
|
||||
</Permission>
|
||||
|
||||
{/* 没有权限时显示备用内容 */}
|
||||
<Permission permission="edit_project" fallback={<span>无权限</span>}>
|
||||
<Button>编辑项目</Button>
|
||||
</Permission>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## 10. 路由最佳实践
|
||||
|
||||
### 10.1 路由命名规范
|
||||
|
||||
```javascript
|
||||
// 好的路由命名
|
||||
'/projects' // 项目列表
|
||||
'/projects/create' // 创建项目
|
||||
'/projects/:id' // 项目详情
|
||||
'/projects/:id/edit' // 编辑项目
|
||||
|
||||
// 不好的路由命名
|
||||
'/p' // 太短,不清晰
|
||||
'/project-list-page' // 太长,冗余
|
||||
'/projects/1/edit' // 使用硬编码ID
|
||||
```
|
||||
|
||||
### 10.2 路由嵌套
|
||||
|
||||
```javascript
|
||||
// 合理的路由嵌套
|
||||
<Route path="/projects" element={<Layout />}>
|
||||
<Route index element={<ProjectList />} />
|
||||
<Route path="create" element={<ProjectForm />} />
|
||||
<Route path=":id" element={<ProjectDetail />} />
|
||||
<Route path=":id/edit" element={<ProjectForm />} />
|
||||
</Route>
|
||||
|
||||
// 不合理的路由嵌套(过深)
|
||||
<Route path="/projects" element={<A />}>
|
||||
<Route path="list" element={<B />}>
|
||||
<Route path=":id" element={<C />}>
|
||||
<Route path="detail" element={<D />} />
|
||||
</Route>
|
||||
</Route>
|
||||
</Route>
|
||||
```
|
||||
|
||||
### 10.3 路由懒加载
|
||||
|
||||
```javascript
|
||||
// 懒加载路由组件
|
||||
import { lazy, Suspense } from 'react';
|
||||
|
||||
const ProjectList = lazy(() => import('./pages/Projects/ProjectList'));
|
||||
const ProjectDetail = lazy(() => import('./pages/Projects/ProjectDetail'));
|
||||
|
||||
const Router = () => {
|
||||
return (
|
||||
<Suspense fallback={<Spin />}>
|
||||
<Routes>
|
||||
<Route path="/projects" element={<ProjectList />} />
|
||||
<Route path="/projects/:id" element={<ProjectDetail />} />
|
||||
</Routes>
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**文档维护**: 前端程序员
|
||||
**文档类型**: 路由设计文档
|
||||
**最后更新**: 2026-01-25
|
||||
Reference in New Issue
Block a user