|
@@ -183,48 +183,91 @@ export { Component, componentVariants }
|
|
|
|
|
|
|
|
## 5. 状态管理
|
|
## 5. 状态管理
|
|
|
|
|
|
|
|
-### Store结构
|
|
|
|
|
|
|
+### 实际状态管理架构
|
|
|
|
|
+
|
|
|
|
|
+基于对现有代码的分析,项目采用混合状态管理模式:
|
|
|
|
|
+
|
|
|
|
|
+**React Context + React Query组合**:
|
|
|
|
|
+- **认证状态**: 使用React Context管理用户认证状态
|
|
|
|
|
+- **服务端数据**: 使用React Query管理API数据
|
|
|
|
|
+- **本地UI状态**: 使用useState管理组件内部状态
|
|
|
|
|
+
|
|
|
|
|
+### 认证状态管理 (React Context)
|
|
|
|
|
|
|
|
```text
|
|
```text
|
|
|
src/
|
|
src/
|
|
|
├── client/
|
|
├── client/
|
|
|
-│ ├── hooks/
|
|
|
|
|
-│ │ ├── use-api.ts # API调用封装
|
|
|
|
|
-│ │ ├── use-auth.ts # 认证状态
|
|
|
|
|
-│ │ ├── use-users.ts # 用户数据
|
|
|
|
|
-│ │ ├── use-roles.ts # 角色数据
|
|
|
|
|
-│ │ └── index.ts # hooks导出
|
|
|
|
|
-│ ├── lib/
|
|
|
|
|
-│ │ ├── query-client.ts # React Query配置
|
|
|
|
|
-│ │ └── query-keys.ts # 查询键常量
|
|
|
|
|
-│ └── providers/
|
|
|
|
|
-│ └── query-provider.tsx # QueryClientProvider
|
|
|
|
|
|
|
+│ ├── admin/
|
|
|
|
|
+│ │ └── hooks/
|
|
|
|
|
+│ │ └── AuthProvider.tsx # 管理后台认证上下文
|
|
|
|
|
+│ ├── home/
|
|
|
|
|
+│ │ └── hooks/
|
|
|
|
|
+│ │ └── AuthProvider.tsx # 用户前台认证上下文
|
|
|
|
|
+│ └── api.ts # API客户端配置
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-### React Query配置
|
|
|
|
|
|
|
+### React Query使用模式
|
|
|
|
|
|
|
|
|
|
+项目采用简单的React Query使用模式:
|
|
|
|
|
+
|
|
|
|
|
+**配置方式**: 在每个入口文件独立创建QueryClient
|
|
|
```typescript
|
|
```typescript
|
|
|
-export const queryClient = new QueryClient({
|
|
|
|
|
|
|
+// 在入口文件中创建QueryClient
|
|
|
|
|
+const queryClient = new QueryClient({
|
|
|
defaultOptions: {
|
|
defaultOptions: {
|
|
|
queries: {
|
|
queries: {
|
|
|
- staleTime: 5 * 60 * 1000, // 5分钟
|
|
|
|
|
- gcTime: 10 * 60 * 1000, // 10分钟
|
|
|
|
|
retry: 1,
|
|
retry: 1,
|
|
|
refetchOnWindowFocus: false,
|
|
refetchOnWindowFocus: false,
|
|
|
},
|
|
},
|
|
|
- mutations: {
|
|
|
|
|
- retry: 1,
|
|
|
|
|
- },
|
|
|
|
|
},
|
|
},
|
|
|
})
|
|
})
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
|
|
+**查询模式**: 直接在组件中使用useQuery
|
|
|
|
|
+```typescript
|
|
|
|
|
+// 数据查询示例
|
|
|
|
|
+const { data: usersData, isLoading, refetch } = useQuery({
|
|
|
|
|
+ queryKey: ['users', searchParams],
|
|
|
|
|
+ queryFn: async () => {
|
|
|
|
|
+ const res = await userClient.$get({ query: searchParams })
|
|
|
|
|
+ if (res.status !== 200) throw new Error('获取失败')
|
|
|
|
|
+ return await res.json()
|
|
|
|
|
+ }
|
|
|
|
|
+})
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**数据变更模式**: 直接调用API客户端 + 手动refetch
|
|
|
|
|
+```typescript
|
|
|
|
|
+// 数据变更示例(未使用useMutation)
|
|
|
|
|
+const handleCreateSubmit = async (data: CreateUserFormData) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await userClient.$post({ json: data })
|
|
|
|
|
+ if (res.status !== 201) throw new Error('创建失败')
|
|
|
|
|
+ toast.success('创建成功')
|
|
|
|
|
+ refetch() // 手动重新获取数据
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('操作失败:', error)
|
|
|
|
|
+ toast.error('操作失败,请重试')
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 当前状态管理特点
|
|
|
|
|
+
|
|
|
|
|
+1. **模块化认证**: 管理后台和用户前台有独立的认证Provider
|
|
|
|
|
+2. **简单配置**: QueryClient在每个入口文件独立配置,无复杂全局配置
|
|
|
|
|
+3. **混合模式**: Context管理认证状态 + React Query管理数据查询
|
|
|
|
|
+4. **手动数据同步**: 数据变更后手动调用refetch更新缓存
|
|
|
|
|
+5. **渐进式采用**: 逐步引入React Query,尚未全面使用useMutation
|
|
|
|
|
+
|
|
|
## 6. API集成
|
|
## 6. API集成
|
|
|
|
|
|
|
|
### Hono RPC集成模式
|
|
### Hono RPC集成模式
|
|
|
|
|
|
|
|
|
|
+项目使用自定义的axios适配器实现Hono RPC客户端:
|
|
|
|
|
+
|
|
|
```typescript
|
|
```typescript
|
|
|
-// 创建axios适配器用于Hono RPC
|
|
|
|
|
|
|
+// 自定义axios适配器 (src/client/api.ts)
|
|
|
const axiosFetch = async (url: RequestInfo | URL, init?: RequestInit) => {
|
|
const axiosFetch = async (url: RequestInfo | URL, init?: RequestInit) => {
|
|
|
const requestHeaders: Record<string, string> = {};
|
|
const requestHeaders: Record<string, string> = {};
|
|
|
|
|
|
|
@@ -251,20 +294,71 @@ const axiosFetch = async (url: RequestInfo | URL, init?: RequestInit) => {
|
|
|
throw error;
|
|
throw error;
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
|
|
+ const responseHeaders = new Headers();
|
|
|
|
|
+ if (response.headers) {
|
|
|
|
|
+ for (const [key, value] of Object.entries(response.headers)) {
|
|
|
|
|
+ responseHeaders.set(key, value);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 处理204 No Content响应
|
|
|
|
|
+ const body = response.status === 204
|
|
|
|
|
+ ? null
|
|
|
|
|
+ : responseHeaders.get('content-type')?.includes('application/json')
|
|
|
|
|
+ ? JSON.stringify(response.data)
|
|
|
|
|
+ : response.data;
|
|
|
|
|
+
|
|
|
return new Response(
|
|
return new Response(
|
|
|
- JSON.stringify(response.data),
|
|
|
|
|
|
|
+ body,
|
|
|
{
|
|
{
|
|
|
status: response.status,
|
|
status: response.status,
|
|
|
statusText: response.statusText,
|
|
statusText: response.statusText,
|
|
|
- headers: response.headers
|
|
|
|
|
|
|
+ headers: responseHeaders
|
|
|
}
|
|
}
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// Hono客户端实例
|
|
|
|
|
|
|
+// Hono客户端实例导出
|
|
|
|
|
+export const authClient = hc<AuthRoutes>('/', {
|
|
|
|
|
+ fetch: axiosFetch,
|
|
|
|
|
+}).api.v1.auth;
|
|
|
|
|
+
|
|
|
export const userClient = hc<UserRoutes>('/', {
|
|
export const userClient = hc<UserRoutes>('/', {
|
|
|
fetch: axiosFetch,
|
|
fetch: axiosFetch,
|
|
|
}).api.v1.users;
|
|
}).api.v1.users;
|
|
|
|
|
+
|
|
|
|
|
+export const roleClient = hc<RoleRoutes>('/', {
|
|
|
|
|
+ fetch: axiosFetch,
|
|
|
|
|
+}).api.v1.roles;
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 类型安全集成
|
|
|
|
|
+
|
|
|
|
|
+后端使用OpenAPIHono定义路由类型:
|
|
|
|
|
+```typescript
|
|
|
|
|
+// 服务器端路由定义 (src/server/api.ts)
|
|
|
|
|
+const userRoutes = api.route('/api/v1/users', usersRouter)
|
|
|
|
|
+const authRoutes = api.route('/api/v1/auth', authRoute)
|
|
|
|
|
+const roleRoutes = api.route('/api/v1/roles', rolesRoute)
|
|
|
|
|
+
|
|
|
|
|
+export type AuthRoutes = typeof authRoutes
|
|
|
|
|
+export type UserRoutes = typeof userRoutes
|
|
|
|
|
+export type RoleRoutes = typeof roleRoutes
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 使用模式
|
|
|
|
|
+
|
|
|
|
|
+在组件中直接使用类型安全的客户端:
|
|
|
|
|
+```typescript
|
|
|
|
|
+// 使用Hono RPC客户端
|
|
|
|
|
+const response = await userClient.$get({
|
|
|
|
|
+ query: { page: 1, pageSize: 10, keyword: 'search' }
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+if (response.status === 200) {
|
|
|
|
|
+ const data = await response.json()
|
|
|
|
|
+ // 类型安全的data
|
|
|
|
|
+}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
## 7. 路由配置
|
|
## 7. 路由配置
|