1528 lines
39 KiB
Markdown
1528 lines
39 KiB
Markdown
# GoalfyMax Admin Web 前端项目探索指南
|
||
|
||
## 项目概览
|
||
|
||
**项目路径**: `/Users/youziba/goalfyagent/goalfymax-admin-web`
|
||
|
||
**技术栈**:
|
||
- **框架**: React 18.3.1
|
||
- **路由**: React Router DOM 7.9.4
|
||
- **UI库**: Ant Design 5.27.4
|
||
- **状态管理**: Jotai 2.15.0
|
||
- **构建工具**: Vite 5.4.20
|
||
- **HTTP客户端**: Axios 1.12.2
|
||
- **语言**: TypeScript
|
||
|
||
**项目结构**:
|
||
```
|
||
src/
|
||
├── pages/ # 页面组件
|
||
├── components/ # 通用组件
|
||
├── services/ # API 服务层
|
||
├── types/ # TypeScript 类型定义
|
||
├── hooks/ # 自定义 hooks
|
||
├── atoms/ # Jotai 状态管理
|
||
├── routes/ # 路由配置
|
||
├── assets/ # 静态资源
|
||
├── utils/ # 工具函数
|
||
├── App.tsx # 主应用入口
|
||
└── main.tsx # 应用启动文件
|
||
```
|
||
|
||
---
|
||
|
||
## 1. 前端项目框架
|
||
|
||
### 框架:React 18.3.1 + TypeScript
|
||
|
||
**主入口文件**: `/Users/youziba/goalfyagent/goalfymax-admin-web/src/main.tsx`
|
||
|
||
```typescript
|
||
import { StrictMode } from 'react'
|
||
import { createRoot } from 'react-dom/client'
|
||
import { Provider } from 'jotai'
|
||
import { BrowserRouter } from 'react-router-dom'
|
||
import './App.css'
|
||
import 'antd/dist/reset.css'
|
||
import App from './App.tsx'
|
||
import AuthGuard from './components/AuthGuard'
|
||
|
||
const root = createRoot(document.getElementById('root')!)
|
||
root.render(
|
||
<StrictMode>
|
||
<Provider>
|
||
<BrowserRouter>
|
||
<AuthGuard>
|
||
<App />
|
||
</AuthGuard>
|
||
</BrowserRouter>
|
||
</Provider>
|
||
</StrictMode>
|
||
)
|
||
```
|
||
|
||
**关键特点**:
|
||
- 使用 Jotai 进行全局状态管理
|
||
- React Router 管理路由
|
||
- AuthGuard 包裹整个应用,确保认证
|
||
- Ant Design 提供 UI 组件
|
||
|
||
---
|
||
|
||
## 2. 现有配置管理页面实现
|
||
|
||
### 用户等级配置页面 (参考实现)
|
||
|
||
**文件路径**: `/Users/youziba/goalfyagent/goalfymax-admin-web/src/pages/UserLevelConfigs.tsx`
|
||
|
||
**文件大小**: 283 行
|
||
|
||
**页面功能**:
|
||
- 列表展示(分页)
|
||
- 创建新配置
|
||
- 编辑配置
|
||
- 删除配置
|
||
- 切换状态(启用/禁用)
|
||
|
||
**关键代码片段**:
|
||
|
||
```typescript
|
||
import React, { useEffect, useState } from 'react';
|
||
import {
|
||
Table,
|
||
Button,
|
||
Modal,
|
||
Form,
|
||
Input,
|
||
InputNumber,
|
||
Tag,
|
||
Space,
|
||
Popconfirm,
|
||
message,
|
||
Row,
|
||
Col,
|
||
Card,
|
||
} from 'antd';
|
||
import {
|
||
PlusOutlined,
|
||
EditOutlined,
|
||
DeleteOutlined,
|
||
CheckCircleOutlined,
|
||
StopOutlined,
|
||
} from '@ant-design/icons';
|
||
|
||
export default function UserLevelConfigs() {
|
||
const [loading, setLoading] = useState(false);
|
||
const [list, setList] = useState<UserLevelConfig[]>([]);
|
||
const [total, setTotal] = useState(0);
|
||
const [page, setPage] = useState(1);
|
||
const [size, setSize] = useState(10);
|
||
const [editOpen, setEditOpen] = useState(false);
|
||
const [editing, setEditing] = useState<UserLevelConfig | null>(null);
|
||
const [createOpen, setCreateOpen] = useState(false);
|
||
const [form] = Form.useForm();
|
||
const [createForm] = Form.useForm();
|
||
|
||
// 获取列表
|
||
const fetchList = async () => {
|
||
setLoading(true);
|
||
try {
|
||
const res = await getUserLevelConfigList({ page, size });
|
||
setList(res?.data ?? []);
|
||
setTotal(res?.total ?? 0);
|
||
} catch (e) {
|
||
message.error('获取列表失败');
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
useEffect(() => {
|
||
fetchList();
|
||
}, [page, size]);
|
||
|
||
// 打开编辑弹窗
|
||
const openEdit = (config: UserLevelConfig) => {
|
||
setEditing(config);
|
||
form.setFieldsValue({
|
||
level_name: config.level_name,
|
||
project_limit: config.project_limit,
|
||
description: config.description,
|
||
sort_order: config.sort_order,
|
||
});
|
||
setEditOpen(true);
|
||
};
|
||
|
||
// 提交编辑
|
||
const submitEdit = async () => {
|
||
try {
|
||
const values = await form.validateFields();
|
||
if (!editing) return;
|
||
await updateUserLevelConfig(editing.id, values);
|
||
message.success('更新成功');
|
||
setEditOpen(false);
|
||
fetchList();
|
||
} catch (error) {
|
||
message.error('更新失败');
|
||
}
|
||
};
|
||
|
||
// 创建配置
|
||
const submitCreate = async () => {
|
||
try {
|
||
const values = await createForm.validateFields();
|
||
await createUserLevelConfig(values);
|
||
message.success('创建成功');
|
||
setCreateOpen(false);
|
||
fetchList();
|
||
} catch (error: any) {
|
||
message.error(error?.response?.data?.message || '创建失败');
|
||
}
|
||
};
|
||
|
||
// 删除配置
|
||
const handleDelete = async (config: UserLevelConfig) => {
|
||
try {
|
||
await deleteUserLevelConfig(config.id);
|
||
message.success('删除成功');
|
||
fetchList();
|
||
} catch (error) {
|
||
message.error('删除失败');
|
||
}
|
||
};
|
||
|
||
// 切换状态
|
||
const handleToggleStatus = async (config: UserLevelConfig) => {
|
||
try {
|
||
const newStatus = config.status === 1 ? 0 : 1;
|
||
await updateUserLevelConfigStatus(config.id, { status: newStatus });
|
||
message.success(newStatus === 1 ? '已启用' : '已禁用');
|
||
fetchList();
|
||
} catch (error) {
|
||
message.error('状态更新失败');
|
||
}
|
||
};
|
||
|
||
// 定义表格列
|
||
const columns = [
|
||
{
|
||
title: '等级名称',
|
||
dataIndex: 'level_name',
|
||
key: 'level_name',
|
||
},
|
||
{
|
||
title: '等级代码',
|
||
dataIndex: 'level_code',
|
||
key: 'level_code',
|
||
},
|
||
{
|
||
title: '项目数限制',
|
||
dataIndex: 'project_limit',
|
||
key: 'project_limit',
|
||
render: (value: number) => (value === 0 ? '不限' : value),
|
||
},
|
||
{
|
||
title: '描述',
|
||
dataIndex: 'description',
|
||
key: 'description',
|
||
},
|
||
{
|
||
title: '排序',
|
||
dataIndex: 'sort_order',
|
||
key: 'sort_order',
|
||
},
|
||
{
|
||
title: '状态',
|
||
dataIndex: 'status',
|
||
key: 'status',
|
||
render: (value: number) =>
|
||
value === 1 ? <Tag color="green">启用</Tag> : <Tag color="red">禁用</Tag>,
|
||
},
|
||
{
|
||
title: '操作',
|
||
key: 'action',
|
||
render: (_: any, config: UserLevelConfig) => (
|
||
<Space>
|
||
<Button type="link" icon={<EditOutlined />} onClick={() => openEdit(config)}>
|
||
编辑
|
||
</Button>
|
||
<Button
|
||
type="link"
|
||
icon={config.status === 1 ? <StopOutlined /> : <CheckCircleOutlined />}
|
||
onClick={() => handleToggleStatus(config)}
|
||
>
|
||
{config.status === 1 ? '禁用' : '启用'}
|
||
</Button>
|
||
<Popconfirm title="确定删除该等级配置?" onConfirm={() => handleDelete(config)}>
|
||
<Button type="link" danger icon={<DeleteOutlined />}>
|
||
删除
|
||
</Button>
|
||
</Popconfirm>
|
||
</Space>
|
||
),
|
||
},
|
||
];
|
||
|
||
return (
|
||
<div>
|
||
<Row gutter={16} style={{ marginBottom: 16 }}>
|
||
<Col span={24}>
|
||
<Card>
|
||
<div style={{ marginBottom: 16 }}>
|
||
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate}>
|
||
新建等级配置
|
||
</Button>
|
||
</div>
|
||
<Table
|
||
dataSource={list}
|
||
columns={columns}
|
||
rowKey="id"
|
||
loading={loading}
|
||
pagination={{
|
||
current: page,
|
||
pageSize: size,
|
||
total: total,
|
||
onChange: (p, s) => {
|
||
setPage(p);
|
||
setSize(s);
|
||
},
|
||
}}
|
||
/>
|
||
</Card>
|
||
</Col>
|
||
</Row>
|
||
|
||
{/* 编辑弹窗 */}
|
||
<Modal
|
||
title="编辑等级配置"
|
||
open={editOpen}
|
||
onOk={submitEdit}
|
||
onCancel={() => setEditOpen(false)}
|
||
>
|
||
<Form form={form} layout="vertical">
|
||
<Form.Item
|
||
name="level_name"
|
||
label="等级名称"
|
||
rules={[{ required: true, message: '请输入等级名称' }]}
|
||
>
|
||
<Input placeholder="请输入等级名称" />
|
||
</Form.Item>
|
||
<Form.Item
|
||
name="project_limit"
|
||
label="项目数限制(0表示不限)"
|
||
rules={[{ required: true, message: '请输入项目数限制' }]}
|
||
>
|
||
<InputNumber min={0} style={{ width: '100%' }} />
|
||
</Form.Item>
|
||
<Form.Item name="description" label="描述">
|
||
<Input.TextArea rows={3} />
|
||
</Form.Item>
|
||
<Form.Item name="sort_order" label="排序顺序">
|
||
<InputNumber min={0} style={{ width: '100%' }} />
|
||
</Form.Item>
|
||
</Form>
|
||
</Modal>
|
||
|
||
{/* 创建弹窗 */}
|
||
<Modal
|
||
title="新建等级配置"
|
||
open={createOpen}
|
||
onOk={submitCreate}
|
||
onCancel={() => setCreateOpen(false)}
|
||
>
|
||
<Form form={createForm} layout="vertical">
|
||
<Form.Item
|
||
name="level_name"
|
||
label="等级名称"
|
||
rules={[{ required: true, message: '请输入等级名称' }]}
|
||
>
|
||
<Input placeholder="请输入等级名称" />
|
||
</Form.Item>
|
||
<Form.Item
|
||
name="level_code"
|
||
label="等级代码"
|
||
rules={[{ required: true, message: '请输入等级代码' }]}
|
||
>
|
||
<Input placeholder="请输入等级代码(如:vip_plus)" />
|
||
</Form.Item>
|
||
<Form.Item
|
||
name="project_limit"
|
||
label="项目数限制(0表示不限)"
|
||
rules={[{ required: true, message: '请输入项目数限制' }]}
|
||
initialValue={0}
|
||
>
|
||
<InputNumber min={0} style={{ width: '100%' }} />
|
||
</Form.Item>
|
||
<Form.Item name="description" label="描述">
|
||
<Input.TextArea rows={3} />
|
||
</Form.Item>
|
||
<Form.Item name="sort_order" label="排序顺序" initialValue={0}>
|
||
<InputNumber min={0} style={{ width: '100%' }} />
|
||
</Form.Item>
|
||
</Form>
|
||
</Modal>
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 路由配置
|
||
|
||
### 路由配置方式
|
||
|
||
**主路由文件**: `/Users/youziba/goalfyagent/goalfymax-admin-web/src/App.tsx`
|
||
|
||
```typescript
|
||
import React from 'react';
|
||
import { Routes, Route, Navigate } from 'react-router-dom';
|
||
import Layout from './components/Layout';
|
||
import Dashboard from './pages/Dashboard';
|
||
import Overview from './pages/Overview';
|
||
import Operations from './pages/Operations';
|
||
import Monitoring from './pages/Monitoring';
|
||
import Finance from './pages/Finance';
|
||
import TokenHistoryPage from './pages/TokenHistory';
|
||
import TokenAnalytics from './pages/TokenAnalytics';
|
||
import SystemHealth from './pages/SystemHealth';
|
||
import QuotaRulesPage from './pages/QuotaRules';
|
||
import UserProjectQuotaWrapper from './pages/UserProjectQuota';
|
||
import UserManagement from './pages/UserManagement';
|
||
import RoleManagement from './pages/RoleManagement';
|
||
import GoalfyMaxUsers from './pages/GoalfyMaxUsers';
|
||
import UserFeedback from './pages/UserFeedback';
|
||
import MessagePush from './pages/MessagePush';
|
||
import VendorModelPricing from './pages/VendorModelPricing';
|
||
|
||
function App() {
|
||
return (
|
||
<Layout>
|
||
<Routes>
|
||
{/* 默认重定向到仪表盘 */}
|
||
<Route path="/" element={<Navigate to="/dashboard" replace />} />
|
||
|
||
{/* 仪表盘 */}
|
||
<Route path="/dashboard" element={<Dashboard />} />
|
||
|
||
{/* 总览页面 */}
|
||
<Route path="/overview" element={<Overview />} />
|
||
|
||
{/* 运营页面 - 嵌套路由 */}
|
||
<Route path="/operations" element={<Navigate to="/operations/user-feedback" replace />} />
|
||
<Route path="/operations/user-feedback" element={<UserFeedback />} />
|
||
<Route path="/operations/message-push" element={<MessagePush />} />
|
||
<Route path="/operations/vendor-model-pricing" element={<VendorModelPricing />} />
|
||
|
||
{/* 监控页面 - 嵌套路由 */}
|
||
<Route path="/monitoring" element={<Navigate to="/monitoring/token-history" replace />} />
|
||
<Route path="/monitoring/token-history" element={<TokenHistoryPage />} />
|
||
<Route path="/monitoring/token-analytics" element={<TokenAnalytics />} />
|
||
<Route path="/monitoring/system-health" element={<SystemHealth />} />
|
||
|
||
{/* 财务页面 */}
|
||
<Route path="/finance" element={<Finance />} />
|
||
|
||
{/* 系统管理页面 - 嵌套路由 */}
|
||
<Route path="/system" element={<Navigate to="/system/quota-rules" replace />} />
|
||
<Route path="/system/quota-rules" element={<QuotaRulesPage />} />
|
||
<Route path="/system/user-project-quota" element={<UserProjectQuotaWrapper />} />
|
||
<Route path="/system/user-management" element={<UserManagement />} />
|
||
<Route path="/system/role-management" element={<RoleManagement />} />
|
||
<Route path="/system/goalfymax-users" element={<GoalfyMaxUsers />} />
|
||
|
||
{/* 404页面 */}
|
||
<Route path="*" element={<Navigate to="/dashboard" replace />} />
|
||
</Routes>
|
||
</Layout>
|
||
);
|
||
}
|
||
|
||
export default App;
|
||
```
|
||
|
||
**新页面的添加步骤**:
|
||
1. 在 `src/pages/` 目录下创建新页面组件
|
||
2. 在 `App.tsx` 中导入页面组件
|
||
3. 在 `Routes` 中添加 `<Route>` 配置
|
||
4. 如果需要在菜单中显示,更新 `Layout.tsx` 中的导航配置
|
||
|
||
---
|
||
|
||
## 4. 菜单配置
|
||
|
||
### 菜单配置方式
|
||
|
||
**主菜单文件**: `/Users/youziba/goalfyagent/goalfymax-admin-web/src/components/Layout.tsx`
|
||
|
||
菜单配置使用静态定义的方式,在 Layout 组件中硬编码:
|
||
|
||
```typescript
|
||
// Layout.tsx 中的导航菜单代码(简化版)
|
||
|
||
{/* 总览 */}
|
||
{accessiblePages.includes('/overview') && (
|
||
<a
|
||
href="#"
|
||
className={activeTab === 'overview' ? 'active' : ''}
|
||
onClick={(e) => {
|
||
e.preventDefault();
|
||
navigate('/overview');
|
||
}}
|
||
>
|
||
<span className="icon">⬤</span> 总览 Overview
|
||
</a>
|
||
)}
|
||
|
||
{/* 运营 */}
|
||
{accessiblePages.includes('/operations') && (
|
||
<a
|
||
href="#"
|
||
className={activeTab === 'operations' ? 'active' : ''}
|
||
onClick={(e) => {
|
||
e.preventDefault();
|
||
navigate('/operations');
|
||
}}
|
||
>
|
||
<span className="icon">⬤</span> 运营 Operations
|
||
</a>
|
||
)}
|
||
|
||
{/* 系统管理 */}
|
||
{accessiblePages.includes('/system') && (
|
||
<a
|
||
href="#"
|
||
className={activeTab === 'admin' ? 'active' : ''}
|
||
onClick={(e) => {
|
||
e.preventDefault();
|
||
navigate('/system');
|
||
}}
|
||
>
|
||
<span className="icon">⬤</span> 系统管理 Admin
|
||
</a>
|
||
)}
|
||
```
|
||
|
||
### 子菜单配置(以系统管理为例)
|
||
|
||
```typescript
|
||
// Layout.tsx 中的系统管理子菜单代码
|
||
|
||
{activeTab === 'admin' && (
|
||
<nav className="subnav">
|
||
<div className="segment" data-tabs="admin">
|
||
<button
|
||
className={activeSubTab === 'quota-rules' ? 'active' : ''}
|
||
onClick={() => navigate('/system/quota-rules')}
|
||
>
|
||
配额/套餐
|
||
</button>
|
||
<button
|
||
className={activeSubTab === 'user-project-quota' ? 'active' : ''}
|
||
onClick={() => navigate('/system/user-project-quota')}
|
||
>
|
||
用户项目配额
|
||
</button>
|
||
<button
|
||
className={activeSubTab === 'user-management' ? 'active' : ''}
|
||
onClick={() => navigate('/system/user-management')}
|
||
>
|
||
系统用户管理
|
||
</button>
|
||
<button
|
||
className={activeSubTab === 'role-management' ? 'active' : ''}
|
||
onClick={() => navigate('/system/role-management')}
|
||
>
|
||
角色管理
|
||
</button>
|
||
<button
|
||
className={activeSubTab === 'goalfymax-users' ? 'active' : ''}
|
||
onClick={() => navigate('/system/goalfymax-users')}
|
||
>
|
||
GoalfyMax用户
|
||
</button>
|
||
</div>
|
||
</nav>
|
||
)}
|
||
```
|
||
|
||
**菜单的权限控制**:
|
||
|
||
菜单项通过 `usePagePermissions` hook 获取用户权限,使用 `accessiblePages.includes()` 来判断是否显示:
|
||
|
||
```typescript
|
||
import { usePagePermissions } from '../hooks/usePagePermissions';
|
||
|
||
const Layout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||
const { getAccessiblePages } = usePagePermissions();
|
||
const accessiblePages = getAccessiblePages();
|
||
|
||
// 根据权限显示菜单项
|
||
if (!accessiblePages.includes('/overview')) {
|
||
// 不显示总览菜单
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 5. API 调用的封装方式
|
||
|
||
### API 客户端配置
|
||
|
||
**文件路径**: `/Users/youziba/goalfyagent/goalfymax-admin-web/src/services/api.ts`
|
||
|
||
**关键特点**:
|
||
- 使用 Axios 作为 HTTP 客户端
|
||
- 自动处理 Token 认证和刷新
|
||
- 支持请求/响应拦截
|
||
- 处理 401 错误自动刷新 Token
|
||
|
||
```typescript
|
||
import axios from 'axios';
|
||
|
||
class ApiClient {
|
||
private instance: any;
|
||
private isRefreshing = false;
|
||
private failedQueue: Array<{
|
||
resolve: (value: any) => void;
|
||
reject: (reason: any) => void;
|
||
}> = [];
|
||
|
||
constructor(baseURL: string = '/api') {
|
||
this.instance = axios.create({
|
||
baseURL,
|
||
timeout: 60000,
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
});
|
||
|
||
this.setupInterceptors();
|
||
}
|
||
|
||
private setupInterceptors() {
|
||
// 请求拦截器 - 添加 Authorization header
|
||
this.instance.interceptors.request.use(
|
||
config => {
|
||
const token = localStorage.getItem('token');
|
||
if (token) {
|
||
config.headers.Authorization = `Bearer ${token}`;
|
||
}
|
||
return config;
|
||
},
|
||
error => {
|
||
return Promise.reject(error);
|
||
}
|
||
);
|
||
|
||
// 响应拦截器 - 处理 401 错误和 Token 刷新
|
||
this.instance.interceptors.response.use(
|
||
(response: any) => {
|
||
return response;
|
||
},
|
||
error => {
|
||
if (error.response?.status === 401) {
|
||
const originalRequest = error.config;
|
||
|
||
if (originalRequest._retry) {
|
||
// 重试失败,触发重新登录
|
||
const event = new CustomEvent('auth:relogin');
|
||
window.dispatchEvent(event);
|
||
return Promise.reject(error);
|
||
}
|
||
|
||
if (this.isRefreshing) {
|
||
// 如果正在刷新token,将请求加入队列
|
||
return new Promise((resolve, reject) => {
|
||
this.failedQueue.push({ resolve, reject });
|
||
})
|
||
.then(token => {
|
||
originalRequest.headers.Authorization = token;
|
||
return this.instance(originalRequest);
|
||
})
|
||
.catch(err => {
|
||
return Promise.reject(err);
|
||
});
|
||
}
|
||
|
||
originalRequest._retry = true;
|
||
this.isRefreshing = true;
|
||
|
||
return this.handleTokenRefresh()
|
||
.then(refreshResult => {
|
||
const { access_token, refresh_token, expires_in } = refreshResult;
|
||
this.processQueue(null, `Bearer ${access_token}`);
|
||
originalRequest.headers.Authorization = `Bearer ${access_token}`;
|
||
|
||
// 触发 token 更新事件
|
||
const event = new CustomEvent('auth:tokenRefreshed', {
|
||
detail: {
|
||
access_token,
|
||
refresh_token,
|
||
expires_in,
|
||
},
|
||
});
|
||
window.dispatchEvent(event);
|
||
|
||
return this.instance(originalRequest);
|
||
})
|
||
.catch(refreshError => {
|
||
this.processQueue(refreshError, null);
|
||
|
||
// 刷新失败,重新登录
|
||
const event = new CustomEvent('auth:relogin');
|
||
window.dispatchEvent(event);
|
||
|
||
return Promise.reject(refreshError);
|
||
})
|
||
.finally(() => {
|
||
this.isRefreshing = false;
|
||
});
|
||
}
|
||
|
||
return Promise.reject(error);
|
||
}
|
||
);
|
||
}
|
||
|
||
// GET 请求
|
||
async get<T = any>(url: string, config?: any): Promise<T> {
|
||
const response = await this.instance.get<T>(url, config);
|
||
return response.data;
|
||
}
|
||
|
||
// POST 请求
|
||
async post<T = any>(
|
||
url: string,
|
||
data?: any,
|
||
config?: any
|
||
): Promise<T> {
|
||
const response = await this.instance.post<T>(url, data, config);
|
||
return response.data;
|
||
}
|
||
|
||
// PUT 请求
|
||
async put<T = any>(
|
||
url: string,
|
||
data?: any,
|
||
config?: any
|
||
): Promise<T> {
|
||
const response = await this.instance.put<T>(url, data, config);
|
||
return response.data;
|
||
}
|
||
|
||
// DELETE 请求
|
||
async delete<T = any>(
|
||
url: string,
|
||
data?: any,
|
||
config?: any
|
||
): Promise<T> {
|
||
const response = await this.instance.delete<T>(url, {
|
||
...config,
|
||
data,
|
||
});
|
||
return response.data;
|
||
}
|
||
}
|
||
|
||
export const apiClient = new ApiClient();
|
||
```
|
||
|
||
### API 服务层示例
|
||
|
||
**文件路径**: `/Users/youziba/goalfyagent/goalfymax-admin-web/src/services/userLevelConfigApi.ts`
|
||
|
||
```typescript
|
||
import { apiClient } from './api';
|
||
import type {
|
||
UserLevelConfig,
|
||
UserLevelConfigListRequest,
|
||
UserLevelConfigListResponse,
|
||
UserLevelConfigCreateRequest,
|
||
UserLevelConfigUpdateRequest,
|
||
UserLevelConfigStatusRequest,
|
||
} from '../types/userLevelConfig';
|
||
|
||
// 获取用户等级配置列表
|
||
export const getUserLevelConfigList = async (
|
||
params: UserLevelConfigListRequest
|
||
): Promise<UserLevelConfigListResponse> => {
|
||
const response = await apiClient.get('/admin/user-level-configs', { params });
|
||
return response.data;
|
||
};
|
||
|
||
// 获取用户等级配置详情
|
||
export const getUserLevelConfigById = async (id: number): Promise<UserLevelConfig> => {
|
||
const response = await apiClient.get(`/admin/user-level-configs/${id}`);
|
||
return response.data;
|
||
};
|
||
|
||
// 创建用户等级配置
|
||
export const createUserLevelConfig = async (
|
||
data: UserLevelConfigCreateRequest
|
||
): Promise<UserLevelConfig> => {
|
||
const response = await apiClient.post('/admin/user-level-configs', data);
|
||
return response.data;
|
||
};
|
||
|
||
// 更新用户等级配置
|
||
export const updateUserLevelConfig = async (
|
||
id: number,
|
||
data: UserLevelConfigUpdateRequest
|
||
): Promise<UserLevelConfig> => {
|
||
const response = await apiClient.put(`/admin/user-level-configs/${id}`, data);
|
||
return response.data;
|
||
};
|
||
|
||
// 删除用户等级配置
|
||
export const deleteUserLevelConfig = async (id: number): Promise<void> => {
|
||
await apiClient.delete(`/admin/user-level-configs/${id}`);
|
||
};
|
||
|
||
// 更新用户等级配置状态
|
||
export const updateUserLevelConfigStatus = async (
|
||
id: number,
|
||
data: UserLevelConfigStatusRequest
|
||
): Promise<void> => {
|
||
await apiClient.put(`/admin/user-level-configs/${id}/status`, data);
|
||
};
|
||
```
|
||
|
||
**API 命名规范**:
|
||
- 列表接口: `/admin/xxx`
|
||
- 详情接口: `/admin/xxx/:id`
|
||
- 创建接口: `POST /admin/xxx`
|
||
- 更新接口: `PUT /admin/xxx/:id`
|
||
- 删除接口: `DELETE /admin/xxx/:id`
|
||
- 状态变更: `PUT /admin/xxx/:id/status`
|
||
|
||
---
|
||
|
||
## 6. 表单组件和表格组件的使用方式
|
||
|
||
### Ant Design Form 组件使用
|
||
|
||
```typescript
|
||
import { Form, Input, InputNumber, Button, Modal } from 'antd';
|
||
|
||
// 基本表单使用
|
||
const [form] = Form.useForm();
|
||
|
||
const submitForm = async () => {
|
||
try {
|
||
const values = await form.validateFields();
|
||
// 处理表单数据
|
||
console.log(values);
|
||
} catch (error) {
|
||
console.error('表单验证失败:', error);
|
||
}
|
||
};
|
||
|
||
// 在 JSX 中使用
|
||
<Form form={form} layout="vertical">
|
||
<Form.Item
|
||
name="level_name"
|
||
label="等级名称"
|
||
rules={[{ required: true, message: '请输入等级名称' }]}
|
||
>
|
||
<Input placeholder="请输入等级名称" />
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
name="project_limit"
|
||
label="项目数限制"
|
||
rules={[{ required: true, message: '请输入项目数限制' }]}
|
||
>
|
||
<InputNumber min={0} style={{ width: '100%' }} />
|
||
</Form.Item>
|
||
|
||
<Form.Item name="description" label="描述">
|
||
<Input.TextArea rows={3} />
|
||
</Form.Item>
|
||
</Form>
|
||
```
|
||
|
||
### Ant Design Table 组件使用
|
||
|
||
```typescript
|
||
import { Table, Button, Space, Tag } from 'antd';
|
||
import { EditOutlined, DeleteOutlined } from '@ant-design/icons';
|
||
|
||
// 定义表格列
|
||
const columns = [
|
||
{
|
||
title: '等级名称',
|
||
dataIndex: 'level_name',
|
||
key: 'level_name',
|
||
},
|
||
{
|
||
title: '项目数限制',
|
||
dataIndex: 'project_limit',
|
||
key: 'project_limit',
|
||
render: (value: number) => (value === 0 ? '不限' : value),
|
||
},
|
||
{
|
||
title: '状态',
|
||
dataIndex: 'status',
|
||
key: 'status',
|
||
render: (value: number) =>
|
||
value === 1 ? <Tag color="green">启用</Tag> : <Tag color="red">禁用</Tag>,
|
||
},
|
||
{
|
||
title: '操作',
|
||
key: 'action',
|
||
render: (_: any, record: UserLevelConfig) => (
|
||
<Space>
|
||
<Button type="link" icon={<EditOutlined />} onClick={() => openEdit(record)}>
|
||
编辑
|
||
</Button>
|
||
<Button type="link" danger icon={<DeleteOutlined />} onClick={() => handleDelete(record)}>
|
||
删除
|
||
</Button>
|
||
</Space>
|
||
),
|
||
},
|
||
];
|
||
|
||
// 在 JSX 中使用
|
||
<Table
|
||
dataSource={list}
|
||
columns={columns}
|
||
rowKey="id"
|
||
loading={loading}
|
||
pagination={{
|
||
current: page,
|
||
pageSize: size,
|
||
total: total,
|
||
onChange: (p, s) => {
|
||
setPage(p);
|
||
setSize(s);
|
||
},
|
||
}}
|
||
/>
|
||
```
|
||
|
||
### Modal 弹窗组件使用
|
||
|
||
```typescript
|
||
import { Modal, Button, Form } from 'antd';
|
||
import { PlusOutlined } from '@ant-design/icons';
|
||
|
||
const [createOpen, setCreateOpen] = useState(false);
|
||
const [createForm] = Form.useForm();
|
||
|
||
const openCreate = () => {
|
||
createForm.resetFields();
|
||
setCreateOpen(true);
|
||
};
|
||
|
||
const submitCreate = async () => {
|
||
try {
|
||
const values = await createForm.validateFields();
|
||
// 提交创建请求
|
||
setCreateOpen(false);
|
||
} catch (error) {
|
||
message.error('创建失败');
|
||
}
|
||
};
|
||
|
||
// 在 JSX 中使用
|
||
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate}>
|
||
新建配置
|
||
</Button>
|
||
|
||
<Modal
|
||
title="新建配置"
|
||
open={createOpen}
|
||
onOk={submitCreate}
|
||
onCancel={() => setCreateOpen(false)}
|
||
>
|
||
<Form form={createForm} layout="vertical">
|
||
{/* 表单项 */}
|
||
</Form>
|
||
</Modal>
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 类型定义
|
||
|
||
### TypeScript 类型定义位置
|
||
|
||
**文件路径**: `/Users/youziba/goalfyagent/goalfymax-admin-web/src/types/userLevelConfig.ts`
|
||
|
||
```typescript
|
||
export interface UserLevelConfig {
|
||
id: number;
|
||
level_name: string;
|
||
level_code: string;
|
||
project_limit: number;
|
||
description: string;
|
||
sort_order: number;
|
||
status: number; // 1-启用 0-禁用
|
||
created_at: string;
|
||
updated_at: string;
|
||
}
|
||
|
||
export interface UserLevelConfigListRequest {
|
||
level_name?: string;
|
||
status?: number;
|
||
page?: number;
|
||
size?: number;
|
||
}
|
||
|
||
export interface UserLevelConfigListResponse {
|
||
data: UserLevelConfig[];
|
||
total: number;
|
||
page: number;
|
||
size: number;
|
||
}
|
||
|
||
export interface UserLevelConfigCreateRequest {
|
||
level_name: string;
|
||
level_code: string;
|
||
project_limit: number;
|
||
description?: string;
|
||
sort_order?: number;
|
||
}
|
||
|
||
export interface UserLevelConfigUpdateRequest {
|
||
level_name: string;
|
||
project_limit: number;
|
||
description?: string;
|
||
sort_order?: number;
|
||
}
|
||
|
||
export interface UserLevelConfigStatusRequest {
|
||
status: number;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 8. 权限管理
|
||
|
||
### 权限检查 Hook
|
||
|
||
**文件路径**: `/Users/youziba/goalfyagent/goalfymax-admin-web/src/hooks/usePagePermissions.ts`
|
||
|
||
```typescript
|
||
import { useAtom } from 'jotai';
|
||
import { authStateAtom } from '../atoms/auth';
|
||
|
||
export const usePagePermissions = () => {
|
||
const [authState] = useAtom(authStateAtom);
|
||
|
||
// 检查页面权限
|
||
const hasPagePermission = (pagePath: string, action: string = 'read'): boolean => {
|
||
if (!authState.user?.pages) {
|
||
return false;
|
||
}
|
||
|
||
const hasAccess = authState.user.pages.some(page =>
|
||
page.path === pagePath && page.is_active !== false
|
||
);
|
||
|
||
return hasAccess;
|
||
};
|
||
|
||
// 获取页面操作权限
|
||
const getPageActions = (pagePath: string): string[] => {
|
||
if (!authState.user?.pages) {
|
||
return [];
|
||
}
|
||
|
||
const hasAccess = authState.user.pages.some(page => page.path === pagePath);
|
||
if (!hasAccess) {
|
||
return [];
|
||
}
|
||
|
||
return ['read', 'create', 'update', 'delete'];
|
||
};
|
||
|
||
// 获取用户可访问的所有页面
|
||
const getAccessiblePages = (): string[] => {
|
||
if (!authState.user?.pages) {
|
||
return [];
|
||
}
|
||
|
||
const pages = authState.user.pages
|
||
.filter(page => page.is_active !== false)
|
||
.map(page => page.path);
|
||
|
||
return pages;
|
||
};
|
||
|
||
return {
|
||
hasPagePermission,
|
||
getPageActions,
|
||
getAccessiblePages
|
||
};
|
||
};
|
||
```
|
||
|
||
**使用方式**:
|
||
|
||
```typescript
|
||
import { usePagePermissions } from '../hooks/usePagePermissions';
|
||
|
||
function MyComponent() {
|
||
const { getAccessiblePages, hasPagePermission } = usePagePermissions();
|
||
const accessiblePages = getAccessiblePages();
|
||
|
||
// 检查权限
|
||
if (!hasPagePermission('/system')) {
|
||
return <div>无权访问</div>;
|
||
}
|
||
|
||
return <div>有权访问</div>;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 9. 其他关键概念
|
||
|
||
### 状态管理 (Jotai)
|
||
|
||
**文件路径**: `/Users/youziba/goalfyagent/goalfymax-admin-web/src/atoms/auth.ts`
|
||
|
||
Jotai 用于管理全局认证状态和用户权限信息。
|
||
|
||
### 国际化和本地化
|
||
|
||
整个项目使用中文进行开发,暂无国际化配置。
|
||
|
||
### 样式处理
|
||
|
||
- 使用 Ant Design 组件样式
|
||
- 主样式文件: `src/App.css`
|
||
- 各组件可使用 Ant Design 的 `style` prop 进行内联样式
|
||
|
||
---
|
||
|
||
## 创建新的配置管理页面的完整步骤
|
||
|
||
### 第1步:创建 TypeScript 类型定义
|
||
|
||
创建文件 `/Users/youziba/goalfyagent/goalfymax-admin-web/src/types/systemConfig.ts`:
|
||
|
||
```typescript
|
||
export interface SystemConfig {
|
||
id: number;
|
||
config_name: string;
|
||
config_value: string;
|
||
description: string;
|
||
status: number; // 1-启用 0-禁用
|
||
created_at: string;
|
||
updated_at: string;
|
||
}
|
||
|
||
export interface SystemConfigListRequest {
|
||
config_name?: string;
|
||
status?: number;
|
||
page?: number;
|
||
size?: number;
|
||
}
|
||
|
||
export interface SystemConfigListResponse {
|
||
data: SystemConfig[];
|
||
total: number;
|
||
page: number;
|
||
size: number;
|
||
}
|
||
|
||
export interface SystemConfigCreateRequest {
|
||
config_name: string;
|
||
config_value: string;
|
||
description?: string;
|
||
}
|
||
|
||
export interface SystemConfigUpdateRequest {
|
||
config_name: string;
|
||
config_value: string;
|
||
description?: string;
|
||
}
|
||
```
|
||
|
||
### 第2步:创建 API 服务
|
||
|
||
创建文件 `/Users/youziba/goalfyagent/goalfymax-admin-web/src/services/systemConfigApi.ts`:
|
||
|
||
```typescript
|
||
import { apiClient } from './api';
|
||
import type {
|
||
SystemConfig,
|
||
SystemConfigListRequest,
|
||
SystemConfigListResponse,
|
||
SystemConfigCreateRequest,
|
||
SystemConfigUpdateRequest,
|
||
} from '../types/systemConfig';
|
||
|
||
// 获取系统配置列表
|
||
export const getSystemConfigList = async (
|
||
params: SystemConfigListRequest
|
||
): Promise<SystemConfigListResponse> => {
|
||
const response = await apiClient.get('/admin/system-configs', { params });
|
||
return response.data;
|
||
};
|
||
|
||
// 获取系统配置详情
|
||
export const getSystemConfigById = async (id: number): Promise<SystemConfig> => {
|
||
const response = await apiClient.get(`/admin/system-configs/${id}`);
|
||
return response.data;
|
||
};
|
||
|
||
// 创建系统配置
|
||
export const createSystemConfig = async (
|
||
data: SystemConfigCreateRequest
|
||
): Promise<SystemConfig> => {
|
||
const response = await apiClient.post('/admin/system-configs', data);
|
||
return response.data;
|
||
};
|
||
|
||
// 更新系统配置
|
||
export const updateSystemConfig = async (
|
||
id: number,
|
||
data: SystemConfigUpdateRequest
|
||
): Promise<SystemConfig> => {
|
||
const response = await apiClient.put(`/admin/system-configs/${id}`, data);
|
||
return response.data;
|
||
};
|
||
|
||
// 删除系统配置
|
||
export const deleteSystemConfig = async (id: number): Promise<void> => {
|
||
await apiClient.delete(`/admin/system-configs/${id}`);
|
||
};
|
||
|
||
// 更新系统配置状态
|
||
export const updateSystemConfigStatus = async (
|
||
id: number,
|
||
status: number
|
||
): Promise<void> => {
|
||
await apiClient.put(`/admin/system-configs/${id}/status`, { status });
|
||
};
|
||
```
|
||
|
||
### 第3步:创建页面组件
|
||
|
||
创建文件 `/Users/youziba/goalfyagent/goalfymax-admin-web/src/pages/SystemConfigs.tsx`:
|
||
|
||
```typescript
|
||
import React, { useEffect, useState } from 'react';
|
||
import {
|
||
Table,
|
||
Button,
|
||
Modal,
|
||
Form,
|
||
Input,
|
||
Tag,
|
||
Space,
|
||
Popconfirm,
|
||
message,
|
||
Row,
|
||
Col,
|
||
Card,
|
||
} from 'antd';
|
||
import {
|
||
PlusOutlined,
|
||
EditOutlined,
|
||
DeleteOutlined,
|
||
CheckCircleOutlined,
|
||
StopOutlined,
|
||
} from '@ant-design/icons';
|
||
import type { SystemConfig } from '../types/systemConfig';
|
||
import {
|
||
getSystemConfigList,
|
||
createSystemConfig,
|
||
updateSystemConfig,
|
||
deleteSystemConfig,
|
||
updateSystemConfigStatus,
|
||
} from '../services/systemConfigApi';
|
||
|
||
export default function SystemConfigs() {
|
||
const [loading, setLoading] = useState(false);
|
||
const [list, setList] = useState<SystemConfig[]>([]);
|
||
const [total, setTotal] = useState(0);
|
||
const [page, setPage] = useState(1);
|
||
const [size, setSize] = useState(10);
|
||
const [editOpen, setEditOpen] = useState(false);
|
||
const [editing, setEditing] = useState<SystemConfig | null>(null);
|
||
const [createOpen, setCreateOpen] = useState(false);
|
||
const [form] = Form.useForm();
|
||
const [createForm] = Form.useForm();
|
||
|
||
// 获取列表
|
||
const fetchList = async () => {
|
||
setLoading(true);
|
||
try {
|
||
const res = await getSystemConfigList({ page, size });
|
||
setList(res?.data ?? []);
|
||
setTotal(res?.total ?? 0);
|
||
} catch (e) {
|
||
message.error('获取列表失败');
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
useEffect(() => {
|
||
fetchList();
|
||
}, [page, size]);
|
||
|
||
// 打开编辑弹窗
|
||
const openEdit = (config: SystemConfig) => {
|
||
setEditing(config);
|
||
form.setFieldsValue({
|
||
config_name: config.config_name,
|
||
config_value: config.config_value,
|
||
description: config.description,
|
||
});
|
||
setEditOpen(true);
|
||
};
|
||
|
||
// 提交编辑
|
||
const submitEdit = async () => {
|
||
try {
|
||
const values = await form.validateFields();
|
||
if (!editing) return;
|
||
await updateSystemConfig(editing.id, values);
|
||
message.success('更新成功');
|
||
setEditOpen(false);
|
||
fetchList();
|
||
} catch (error) {
|
||
message.error('更新失败');
|
||
}
|
||
};
|
||
|
||
// 打开创建弹窗
|
||
const openCreate = () => {
|
||
createForm.resetFields();
|
||
setCreateOpen(true);
|
||
};
|
||
|
||
// 提交创建
|
||
const submitCreate = async () => {
|
||
try {
|
||
const values = await createForm.validateFields();
|
||
await createSystemConfig(values);
|
||
message.success('创建成功');
|
||
setCreateOpen(false);
|
||
fetchList();
|
||
} catch (error: any) {
|
||
message.error(error?.response?.data?.message || '创建失败');
|
||
}
|
||
};
|
||
|
||
// 删除配置
|
||
const handleDelete = async (config: SystemConfig) => {
|
||
try {
|
||
await deleteSystemConfig(config.id);
|
||
message.success('删除成功');
|
||
fetchList();
|
||
} catch (error) {
|
||
message.error('删除失败');
|
||
}
|
||
};
|
||
|
||
// 切换状态
|
||
const handleToggleStatus = async (config: SystemConfig) => {
|
||
try {
|
||
const newStatus = config.status === 1 ? 0 : 1;
|
||
await updateSystemConfigStatus(config.id, newStatus);
|
||
message.success(newStatus === 1 ? '已启用' : '已禁用');
|
||
fetchList();
|
||
} catch (error) {
|
||
message.error('状态更新失败');
|
||
}
|
||
};
|
||
|
||
// 定义表格列
|
||
const columns = [
|
||
{
|
||
title: '配置名称',
|
||
dataIndex: 'config_name',
|
||
key: 'config_name',
|
||
},
|
||
{
|
||
title: '配置值',
|
||
dataIndex: 'config_value',
|
||
key: 'config_value',
|
||
},
|
||
{
|
||
title: '描述',
|
||
dataIndex: 'description',
|
||
key: 'description',
|
||
},
|
||
{
|
||
title: '状态',
|
||
dataIndex: 'status',
|
||
key: 'status',
|
||
render: (value: number) =>
|
||
value === 1 ? <Tag color="green">启用</Tag> : <Tag color="red">禁用</Tag>,
|
||
},
|
||
{
|
||
title: '操作',
|
||
key: 'action',
|
||
render: (_: any, config: SystemConfig) => (
|
||
<Space>
|
||
<Button type="link" icon={<EditOutlined />} onClick={() => openEdit(config)}>
|
||
编辑
|
||
</Button>
|
||
<Button
|
||
type="link"
|
||
icon={config.status === 1 ? <StopOutlined /> : <CheckCircleOutlined />}
|
||
onClick={() => handleToggleStatus(config)}
|
||
>
|
||
{config.status === 1 ? '禁用' : '启用'}
|
||
</Button>
|
||
<Popconfirm title="确定删除该配置?" onConfirm={() => handleDelete(config)}>
|
||
<Button type="link" danger icon={<DeleteOutlined />}>
|
||
删除
|
||
</Button>
|
||
</Popconfirm>
|
||
</Space>
|
||
),
|
||
},
|
||
];
|
||
|
||
return (
|
||
<div>
|
||
<Row gutter={16} style={{ marginBottom: 16 }}>
|
||
<Col span={24}>
|
||
<Card>
|
||
<div style={{ marginBottom: 16 }}>
|
||
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate}>
|
||
新建配置
|
||
</Button>
|
||
</div>
|
||
<Table
|
||
dataSource={list}
|
||
columns={columns}
|
||
rowKey="id"
|
||
loading={loading}
|
||
pagination={{
|
||
current: page,
|
||
pageSize: size,
|
||
total: total,
|
||
onChange: (p, s) => {
|
||
setPage(p);
|
||
setSize(s);
|
||
},
|
||
}}
|
||
/>
|
||
</Card>
|
||
</Col>
|
||
</Row>
|
||
|
||
{/* 编辑弹窗 */}
|
||
<Modal
|
||
title="编辑配置"
|
||
open={editOpen}
|
||
onOk={submitEdit}
|
||
onCancel={() => setEditOpen(false)}
|
||
>
|
||
<Form form={form} layout="vertical">
|
||
<Form.Item
|
||
name="config_name"
|
||
label="配置名称"
|
||
rules={[{ required: true, message: '请输入配置名称' }]}
|
||
>
|
||
<Input placeholder="请输入配置名称" />
|
||
</Form.Item>
|
||
<Form.Item
|
||
name="config_value"
|
||
label="配置值"
|
||
rules={[{ required: true, message: '请输入配置值' }]}
|
||
>
|
||
<Input placeholder="请输入配置值" />
|
||
</Form.Item>
|
||
<Form.Item name="description" label="描述">
|
||
<Input.TextArea rows={3} placeholder="请输入描述" />
|
||
</Form.Item>
|
||
</Form>
|
||
</Modal>
|
||
|
||
{/* 创建弹窗 */}
|
||
<Modal
|
||
title="新建配置"
|
||
open={createOpen}
|
||
onOk={submitCreate}
|
||
onCancel={() => setCreateOpen(false)}
|
||
>
|
||
<Form form={createForm} layout="vertical">
|
||
<Form.Item
|
||
name="config_name"
|
||
label="配置名称"
|
||
rules={[{ required: true, message: '请输入配置名称' }]}
|
||
>
|
||
<Input placeholder="请输入配置名称" />
|
||
</Form.Item>
|
||
<Form.Item
|
||
name="config_value"
|
||
label="配置值"
|
||
rules={[{ required: true, message: '请输入配置值' }]}
|
||
>
|
||
<Input placeholder="请输入配置值" />
|
||
</Form.Item>
|
||
<Form.Item name="description" label="描述">
|
||
<Input.TextArea rows={3} placeholder="请输入描述" />
|
||
</Form.Item>
|
||
</Form>
|
||
</Modal>
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
### 第4步:添加路由
|
||
|
||
在 `/Users/youziba/goalfyagent/goalfymax-admin-web/src/App.tsx` 中添加:
|
||
|
||
```typescript
|
||
import SystemConfigs from './pages/SystemConfigs';
|
||
|
||
// 在 Routes 中添加新路由
|
||
<Route path="/system/system-configs" element={<SystemConfigs />} />
|
||
```
|
||
|
||
### 第5步:添加菜单项
|
||
|
||
在 `/Users/youziba/goalfyagent/goalfymax-admin-web/src/components/Layout.tsx` 中的系统管理子菜单中添加:
|
||
|
||
```typescript
|
||
<button
|
||
className={activeSubTab === 'system-configs' ? 'active' : ''}
|
||
onClick={() => navigate('/system/system-configs')}
|
||
>
|
||
通用配置
|
||
</button>
|
||
```
|
||
|
||
---
|
||
|
||
## 总结
|
||
|
||
**核心文件清单**:
|
||
|
||
| 文件类型 | 路径 | 说明 |
|
||
|---------|------|------|
|
||
| 类型定义 | `/src/types/*.ts` | TypeScript 接口定义 |
|
||
| API 服务 | `/src/services/*Api.ts` | API 调用封装 |
|
||
| 页面组件 | `/src/pages/*.tsx` | 页面级别组件 |
|
||
| 通用组件 | `/src/components/*.tsx` | 可复用的组件 |
|
||
| Hooks | `/src/hooks/*.ts` | 自定义 hooks |
|
||
| 状态管理 | `/src/atoms/*.ts` | Jotai 原子状态 |
|
||
| 路由配置 | `/src/App.tsx` | 路由定义 |
|
||
| 菜单配置 | `/src/components/Layout.tsx` | 菜单和导航 |
|
||
|
||
**最佳实践**:
|
||
1. 按照现有的目录结构和命名规范创建新文件
|
||
2. 使用 TypeScript 定义强类型
|
||
3. 在 API 层使用 `apiClient` 进行请求
|
||
4. 在页面中使用 Ant Design 组件
|
||
5. 通过 `usePagePermissions` hook 检查权限
|
||
6. 使用 Jotai 的 `useAtom` 进行状态管理
|
||
|