feat: 新增登录页面,更新 AGENTS.md 项目结构
This commit is contained in:
66
AGENTS.md
66
AGENTS.md
@@ -22,31 +22,61 @@
|
||||
|
||||
```
|
||||
src/
|
||||
index.tsx # 应用入口,将 React 根节点挂载到 #root
|
||||
App.tsx # 根组件,渲染 <RouterProvider>,包含 ConfigProvider / AntdApp 全局配置
|
||||
App.css # 全局样式(reset)
|
||||
router.tsx # createBrowserRouter,由路由树自动生成
|
||||
env.d.ts # Rsbuild 环境变量类型声明(ImportMetaEnv)
|
||||
index.tsx # 应用入口,挂载 React 根节点 + 启动 MSW mock
|
||||
App.tsx # 根组件,ConfigProvider / AntdApp / RouterProvider
|
||||
App.css # 全局样式(reset)
|
||||
router.tsx # createBrowserRouter,登录页独立路由 + 布局子路由
|
||||
env.d.ts # Rsbuild 环境变量类型声明(ImportMetaEnv)
|
||||
routes/
|
||||
types.ts # RouteItem 类型定义
|
||||
index.tsx # 路由树数据(唯一数据源),导出 routes / RouteItem
|
||||
utils.tsx # toRouteObjects():将路由树转为 React Router RouteObject[]
|
||||
types.ts # RouteItem 类型定义
|
||||
index.tsx # 路由树数据(唯一数据源),导出 routes / RouteItem
|
||||
utils.tsx # toRouteObjects():将路由树转为 React Router RouteObject[]
|
||||
layouts/
|
||||
RootLayout.tsx # 根布局(Header + Sider + Content)
|
||||
RootLayout.tsx # 根布局(Header + Sider + Content)
|
||||
SystemLayout.tsx # 系统配置布局(<Outlet />,作为 /system 父路由容器)
|
||||
api/
|
||||
auth.ts # 登录接口
|
||||
system/
|
||||
user.ts # 部门 / 用户接口
|
||||
role.ts # 角色接口
|
||||
store/
|
||||
index.ts # 统一导出入口
|
||||
app.ts # 全局应用状态(侧边栏折叠等)
|
||||
user.ts # 用户状态(userInfo / token)
|
||||
mock/
|
||||
index.ts # MSW worker 初始化,汇总所有 handlers
|
||||
auth.ts # 登录 mock
|
||||
system.ts # 部门 / 用户 / 角色 mock
|
||||
pages/
|
||||
Home.tsx # "/" 首页
|
||||
About.tsx # "/about" 关于页
|
||||
NotFound.tsx # "*" 兜底 404 页
|
||||
login/
|
||||
index.tsx # "/login" 登录页(不加载布局)
|
||||
home/
|
||||
index.tsx # "/" 首页
|
||||
about/
|
||||
index.tsx # "/about" 关于页
|
||||
not-found/
|
||||
index.tsx # "*" 兜底 404 页
|
||||
system/
|
||||
user/
|
||||
index.tsx # "/system/user" 用户管理入口
|
||||
DeptTree.tsx # 部门树组件
|
||||
DeptModal.tsx # 部门弹窗组件
|
||||
UserTable.tsx # 用户表格组件
|
||||
UserModal.tsx # 用户弹窗组件
|
||||
role/
|
||||
index.tsx # "/system/role" 角色管理入口
|
||||
RoleTable.tsx # 角色表格组件
|
||||
RoleModal.tsx # 角色弹窗组件
|
||||
types/
|
||||
http.d.ts # 全局 API 命名空间(无需 import 直接使用 API.Response<T>)
|
||||
http.d.ts # 全局 API 命名空间(无需 import 直接使用 API.Response<T>)
|
||||
utils/
|
||||
request.ts # axios 实例封装,导出 get / post / put / del
|
||||
.env # 本地环境变量(已 gitignore,勿提交)
|
||||
.env.example # 环境变量模板(提交到仓库供参考)
|
||||
request.ts # axios 实例封装,导出 get / post(自动附加 token)
|
||||
.env # 本地环境变量(已 gitignore,勿提交)
|
||||
.env.example # 环境变量模板(提交到仓库供参考)
|
||||
public/
|
||||
favicon.png
|
||||
rsbuild.config.ts # 构建配置
|
||||
eslint.config.mjs # ESLint 扁平配置(仅作用于 TS/TSX,忽略 dist/)
|
||||
rsbuild.config.ts # 构建配置
|
||||
eslint.config.mjs # ESLint 扁平配置(仅作用于 TS/TSX,忽略 dist/)
|
||||
tsconfig.json
|
||||
```
|
||||
|
||||
|
||||
23
src/api/auth.ts
Normal file
23
src/api/auth.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { post } from '@/utils/request';
|
||||
|
||||
/** 登录请求参数(不导出) */
|
||||
interface LoginParams {
|
||||
tenantId: string;
|
||||
userName: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
/** 登录响应数据(不导出) */
|
||||
interface LoginResult {
|
||||
id: string;
|
||||
userName: string;
|
||||
nickName: string;
|
||||
roles: string[];
|
||||
token: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录接口
|
||||
* @param data 登录参数
|
||||
*/
|
||||
export const login = (data: LoginParams) => post<LoginResult>('/api/auth/login', data);
|
||||
19
src/mock/auth.ts
Normal file
19
src/mock/auth.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { http, HttpResponse } from 'msw';
|
||||
|
||||
/** 包装为统一响应格式 */
|
||||
const ok = (data: unknown) => ({ code: '0', msg: 'ok', data, time: Date.now(), ok: true });
|
||||
|
||||
export const authHandlers = [
|
||||
http.post('/api/auth/login', async ({ request }) => {
|
||||
const body = await request.json() as { tenantId: string; userName: string; password: string };
|
||||
// 模拟登录:任意租户号 + 用户名密码均返回成功
|
||||
const result = {
|
||||
id: '1',
|
||||
userName: body.userName,
|
||||
nickName: body.userName === 'admin' ? '管理员' : body.userName,
|
||||
roles: ['admin'],
|
||||
token: `mock_token_${Date.now()}`,
|
||||
};
|
||||
return HttpResponse.json(ok(result));
|
||||
}),
|
||||
];
|
||||
@@ -1,7 +1,8 @@
|
||||
import { setupWorker } from 'msw/browser';
|
||||
import { authHandlers } from './auth';
|
||||
import { systemHandlers } from './system';
|
||||
|
||||
/** 汇总所有模块的 mock handlers */
|
||||
const worker = setupWorker(...systemHandlers);
|
||||
const worker = setupWorker(...authHandlers, ...systemHandlers);
|
||||
|
||||
export default worker;
|
||||
|
||||
80
src/pages/login/index.tsx
Normal file
80
src/pages/login/index.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import { LockOutlined, UserOutlined } from '@ant-design/icons';
|
||||
import { App, Button, Card, Form, Input } from 'antd';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { login } from '@/api/auth';
|
||||
import { useUserStore } from '@/store';
|
||||
|
||||
/** 登录表单字段 */
|
||||
interface LoginFormValues {
|
||||
tenantId: string;
|
||||
userName: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
/** 登录页面(不加载布局) */
|
||||
const Login = () => {
|
||||
const navigate = useNavigate();
|
||||
const { message } = App.useApp();
|
||||
const setUserInfo = useUserStore((s) => s.setUserInfo);
|
||||
const [form] = Form.useForm<LoginFormValues>();
|
||||
|
||||
/** 登录提交 */
|
||||
const handleLogin = async (values: LoginFormValues) => {
|
||||
const res = await login(values);
|
||||
const data = res.data!;
|
||||
setUserInfo({
|
||||
id: data.id,
|
||||
userName: data.userName,
|
||||
nickName: data.nickName,
|
||||
roles: data.roles,
|
||||
token: data.token,
|
||||
});
|
||||
message.success('登录成功');
|
||||
navigate('/');
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '100vh',
|
||||
background: '#f0f2f5',
|
||||
}}
|
||||
>
|
||||
<Card title="TaoTie 管理系统" style={{ width: 400 }}>
|
||||
<Form form={form} onFinish={handleLogin} layout="vertical">
|
||||
<Form.Item
|
||||
label="租户号"
|
||||
name="tenantId"
|
||||
rules={[{ required: true, message: '请输入租户号' }]}
|
||||
>
|
||||
<Input prefix={<UserOutlined />} placeholder="请输入租户号" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="用户名"
|
||||
name="userName"
|
||||
rules={[{ required: true, message: '请输入用户名' }]}
|
||||
>
|
||||
<Input prefix={<UserOutlined />} placeholder="请输入用户名" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="密码"
|
||||
name="password"
|
||||
rules={[{ required: true, message: '请输入密码' }]}
|
||||
>
|
||||
<Input.Password prefix={<LockOutlined />} placeholder="请输入密码" />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit" block>
|
||||
登录
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
||||
@@ -1,9 +1,13 @@
|
||||
import { createBrowserRouter } from 'react-router';
|
||||
import RootLayout from '@/layouts/RootLayout';
|
||||
import Login from '@/pages/login/index';
|
||||
import { routes } from '@/routes';
|
||||
import { toRouteObjects } from '@/routes/utils';
|
||||
|
||||
const router = createBrowserRouter([
|
||||
// 登录页:独立路由,不加载布局
|
||||
{ path: '/login', element: <Login /> },
|
||||
// 带布局的主应用
|
||||
{
|
||||
path: '/',
|
||||
element: <RootLayout />,
|
||||
|
||||
Reference in New Issue
Block a user