Skip to content

Commit 9322d55

Browse files
committed
feat: complete HaloLight React implementation
1 parent b6c5bad commit 9322d55

17 files changed

Lines changed: 1257 additions & 146 deletions

.env.development

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# 开发环境配置
2-
VITE_API_URL=http://localhost:3001
3-
VITE_USE_MOCK=true
2+
VITE_API_URL=http://localhost:3000/api
3+
VITE_MOCK=true
44

55
# Demo 账号(密码与 src/lib/api/client.ts 中 authApi.login 验证一致)
66
VITE_DEMO_EMAIL=admin@halolight.h7ml.cn

.env.example

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
# HaloLight React 环境变量配置
22

33
# API 配置
4+
# Mock 模式: VITE_MOCK=true, VITE_API_URL 可以留空或设置为 /api
5+
# 真实模式: VITE_MOCK=false, VITE_API_URL=http://localhost:3000/api (或其他后端地址)
46
VITE_API_URL=https://api.halolight.h7ml.cn
5-
VITE_USE_MOCK=true
7+
VITE_MOCK=true
68

79
# Demo 账号(生产环境请留空)
810
VITE_DEMO_EMAIL=admin@halolight.h7ml.cn

API_INTEGRATION.md

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
# React 版 API 对接说明
2+
3+
## 概述
4+
5+
本项目支持 **Mock 模式****真实 API 模式** 两种运行方式,通过环境变量 `VITE_MOCK` 控制。
6+
7+
## 环境配置
8+
9+
### Mock 模式 (开发/演示)
10+
11+
```env
12+
VITE_MOCK=true
13+
VITE_API_URL=/api # 或留空
14+
```
15+
16+
### 真实 API 模式 (生产/对接后端)
17+
18+
```env
19+
VITE_MOCK=false
20+
VITE_API_URL=http://localhost:3000/api # 后端 API 地址
21+
```
22+
23+
## 后端 API 规范
24+
25+
### 认证接口
26+
27+
| 接口 | 方法 | 说明 | 请求体 | 响应体 |
28+
|------|------|------|--------|--------|
29+
| `/api/auth/login` | POST | 用户登录 | `{email, password}` | `{accessToken, refreshToken, user}` |
30+
| `/api/auth/register` | POST | 用户注册 | `{email, name, password}` | `{accessToken, refreshToken, user}` |
31+
| `/api/auth/me` | GET | 获取当前用户 | - | `{id, email, name, ..., roles}` |
32+
| `/api/auth/refresh` | POST | 刷新令牌 | `{refreshToken}` | `{accessToken, refreshToken}` |
33+
| `/api/auth/logout` | POST | 退出登录 | - | - |
34+
35+
### 用户状态枚举
36+
37+
```typescript
38+
type UserStatus = "ACTIVE" | "INACTIVE" | "SUSPENDED" // 大写
39+
```
40+
41+
### 权限格式
42+
43+
- 后端: `{id, resource, action, description}`
44+
- 前端: `"resource:action"` (如 `"users:view"`)
45+
46+
## 技术实现
47+
48+
### 目录结构
49+
50+
```
51+
src/
52+
├── lib/api/
53+
│ ├── backend-types.ts # 后端 API 原始类型定义
54+
│ ├── adapters.ts # 前后端数据格式转换
55+
│ ├── client.ts # API 客户端 (axios + 拦截器)
56+
│ ├── services.ts # 业务服务封装
57+
│ └── types.ts # 前端业务类型
58+
├── stores/
59+
│ └── auth-store.ts # Zustand 认证状态管理
60+
└── mock/ # Mock 数据
61+
```
62+
63+
### 核心功能
64+
65+
#### 1. Token 自动刷新
66+
67+
`src/lib/api/client.ts` 实现了完整的 Token 刷新机制:
68+
69+
- 响应拦截器捕获 401 错误
70+
- 自动调用 `/api/auth/refresh` 刷新令牌
71+
- 排队等待刷新完成后重放请求
72+
- 刷新失败后清理 Cookies 并跳转登录页
73+
74+
#### 2. 请求拦截器
75+
76+
自动为所有请求添加 `Authorization: Bearer {token}` 头:
77+
78+
- Mock 模式: 读取 `token` cookie
79+
- 真实模式: 读取 `accessToken` cookie
80+
81+
#### 3. Cookie 管理
82+
83+
| 模式 | Cookie 键名 | 有效期 |
84+
|------|------------|--------|
85+
| Mock | `token` | 记住我: 7天 / 普通: 1天 |
86+
| 真实 | `accessToken` | 7天 |
87+
| 真实 | `refreshToken` | 30天 |
88+
89+
#### 4. 数据适配器
90+
91+
`src/lib/api/adapters.ts` 负责前后端数据格式转换:
92+
93+
- 用户状态: 大写枚举直接透传
94+
- 角色权限: 后端对象数组 → 前端字符串数组
95+
- 多角色用户: 取第一个角色作为主角色
96+
- 分页数据: 后端 `{data, meta}` → 前端 `{list, total}`
97+
98+
### 状态管理 (Zustand)
99+
100+
`src/stores/auth-store.ts` 提供的方法:
101+
102+
| 方法 | Mock 支持 | 真实 API 支持 | 说明 |
103+
|------|-----------|---------------|------|
104+
| `login` | ✅ | ✅ | 登录 |
105+
| `register` | ✅ | ✅ | 注册 |
106+
| `logout` | ✅ | ✅ | 退出 |
107+
| `checkAuth` | ✅ | ✅ | 检查认证状态 |
108+
| `forgotPassword` | ✅ | ✅ | 忘记密码 |
109+
| `resetPassword` | ✅ | ✅ | 重置密码 |
110+
| `switchAccount` | ✅ | ❌ | 切换账号 (仅 Mock) |
111+
| `loadAccounts` | ✅ | ❌ | 加载账号列表 (仅 Mock) |
112+
113+
#### checkAuth 优化
114+
115+
为减少网络请求,`checkAuth` 优先从缓存恢复:
116+
117+
1. 检查 Cookie 中是否有 token
118+
2. 尝试从 `accounts` 缓存匹配 token
119+
3. 缓存命中 → 直接恢复状态
120+
4. 缓存未命中 → 调用 `/api/auth/me`
121+
122+
## 切换模式
123+
124+
### 开发时使用 Mock
125+
126+
1. 设置 `.env.development`:
127+
```env
128+
VITE_MOCK=true
129+
```
130+
2. 运行 `pnpm dev`
131+
3. 使用 Mock 账号登录 (见 `src/lib/api/client.ts`)
132+
133+
### 对接真实后端
134+
135+
1. 设置 `.env.production`:
136+
```env
137+
VITE_MOCK=false
138+
VITE_API_URL=http://localhost:3000/api
139+
```
140+
2. 确保后端运行在 `http://localhost:3000`
141+
3. 运行 `pnpm build && pnpm preview`
142+
143+
## 常见问题
144+
145+
### 1. 401 后无限刷新循环
146+
147+
**原因**: 刷新接口 `/api/auth/refresh` 也返回 401
148+
149+
**解决**: 确保刷新接口使用独立的 axios 实例,避免触发拦截器
150+
151+
### 2. 多账号切换失败
152+
153+
**原因**: 真实模式不支持多账号切换
154+
155+
**解决**: 在 UI 中隐藏多账号切换功能,或调用后端多租户 API
156+
157+
### 3. 权限校验失败
158+
159+
**原因**: 前端权限格式 `"resource:action"` 与后端不匹配
160+
161+
**解决**: 检查 `adapters.ts` 中的 `adaptBackendPermissions` 函数
162+
163+
## 测试场景
164+
165+
### Mock 模式
166+
167+
- ✅ 登录 / 注册
168+
- ✅ 多账号切换
169+
-Token 刷新 (模拟)
170+
- ✅ 退出登录
171+
172+
### 真实 API 模式
173+
174+
- ✅ 登录 / 注册 → 存储 accessToken + refreshToken
175+
- ✅ 请求自动附带 Authorization
176+
-401 错误 → 自动刷新 token → 重放请求
177+
- ✅ 刷新失败 → 清理 Cookies → 跳转登录
178+
- ✅ 获取当前用户信息 (`/api/auth/me`)
179+
- ✅ 退出登录 → 清理所有 Cookies
180+
181+
## 维护建议
182+
183+
1. **类型定义**: 后端 API 变更时,先更新 `backend-types.ts`
184+
2. **数据适配**: 格式差异在 `adapters.ts` 中处理,保持前端类型稳定
185+
3. **Mock 数据**: 定期同步 Mock 数据与真实 API 响应格式
186+
4. **Cookie 策略**: 生产环境设置 `secure: true, sameSite: 'strict'`
187+
5. **错误处理**: 统一在 `services.ts` 中处理业务错误
188+
6. **UI 适配**: 真实模式下隐藏/禁用多账号切换功能
189+
7. **状态字段**: `AuthState.token` 同时承载 Mock token 和真实 accessToken,注意语义区分
190+
191+
## 安全提示
192+
193+
- 清理 Cookie 会导致需要重新登录(即使 localStorage 中有缓存)
194+
- 真实模式完全依赖 Cookie 存储 token,不要在客户端代码中暴露 refreshToken
195+
- 生产环境建议使用 HTTPS + secure cookie
196+
197+
## 参考文件
198+
199+
- API 客户端: `src/lib/api/client.ts`
200+
- 类型定义: `src/lib/api/backend-types.ts`
201+
- 数据适配: `src/lib/api/adapters.ts`
202+
- 状态管理: `src/stores/auth-store.ts`
203+
- 环境配置: `.env.example`

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@
7575
"lucide-react": "^0.554.0",
7676
"mockjs": "^1.1.0",
7777
"next-themes": "^0.4.6",
78-
"react": "^19.0.0",
79-
"react-dom": "^19.0.0",
78+
"react": "^19.0.1",
79+
"react-dom": "^19.0.1",
8080
"react-grid-layout": "^1.5.2",
8181
"react-helmet-async": "^2.0.5",
8282
"react-hook-form": "^7.66.1",

pnpm-lock.yaml

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/actions/user.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export interface UserFormData {
2222
name: string
2323
email: string
2424
roleId?: string
25-
status?: "active" | "inactive" | "suspended"
25+
status?: "ACTIVE" | "INACTIVE" | "SUSPENDED"
2626
avatar?: string
2727
phone?: string
2828
department?: string
@@ -261,7 +261,7 @@ export async function batchDeleteUsersAction(
261261
*/
262262
export async function updateUserStatusAction(
263263
id: string,
264-
status: "active" | "inactive" | "suspended"
264+
status: "ACTIVE" | "INACTIVE" | "SUSPENDED"
265265
): Promise<ActionResult<User>> {
266266
try {
267267
const user = await clientFetch<User>(`/users/${id}/status`, {

0 commit comments

Comments
 (0)