|
@@ -476,6 +476,215 @@ test('should create new entity', async ({ page }) => {
|
|
|
});
|
|
});
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
|
|
+## RPC Client 使用规范
|
|
|
|
|
+
|
|
|
|
|
+### 客户端创建和配置
|
|
|
|
|
+
|
|
|
|
|
+#### 客户端导入
|
|
|
|
|
+```typescript
|
|
|
|
|
+// 从统一的API模块导入客户端
|
|
|
|
|
+import { userClient, roleClient, fileClient } from '@/client/api';
|
|
|
|
|
+import type { InferRequestType, InferResponseType } from 'hono/client';
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 类型提取规范
|
|
|
|
|
+```typescript
|
|
|
|
|
+// 使用InferResponseType提取响应类型
|
|
|
|
|
+type UserResponse = InferResponseType<typeof userClient.$get, 200>['data'][0];
|
|
|
|
|
+type UserListResponse = InferResponseType<typeof userClient.$get, 200>;
|
|
|
|
|
+
|
|
|
|
|
+// 使用InferRequestType提取请求类型
|
|
|
|
|
+type CreateUserRequest = InferRequestType<typeof userClient.$post>['json'];
|
|
|
|
|
+type UpdateUserRequest = InferRequestType<typeof userClient[':id']['$put']>['json'];
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### API 调用规范
|
|
|
|
|
+
|
|
|
|
|
+#### GET 请求
|
|
|
|
|
+```typescript
|
|
|
|
|
+// 基础查询
|
|
|
|
|
+const { data, isLoading, refetch } = useQuery({
|
|
|
|
|
+ queryKey: ['users', searchParams],
|
|
|
|
|
+ queryFn: async () => {
|
|
|
|
|
+ const res = await userClient.$get({
|
|
|
|
|
+ query: {
|
|
|
|
|
+ page: searchParams.page,
|
|
|
|
|
+ pageSize: searchParams.limit,
|
|
|
|
|
+ keyword: searchParams.keyword,
|
|
|
|
|
+ filters: hasActiveFilters ? JSON.stringify(filters) : undefined
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ if (res.status !== 200) throw new Error('获取数据失败');
|
|
|
|
|
+ return await res.json();
|
|
|
|
|
+ }
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+// 带路径参数的查询
|
|
|
|
|
+const { data } = useQuery({
|
|
|
|
|
+ queryKey: ['user', userId],
|
|
|
|
|
+ queryFn: async () => {
|
|
|
|
|
+ const res = await userClient[':id'].$get({
|
|
|
|
|
+ param: { id: userId }
|
|
|
|
|
+ });
|
|
|
|
|
+ if (res.status !== 200) throw new Error('获取用户详情失败');
|
|
|
|
|
+ return await res.json();
|
|
|
|
|
+ }
|
|
|
|
|
+});
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### POST 请求
|
|
|
|
|
+```typescript
|
|
|
|
|
+// 创建资源
|
|
|
|
|
+const handleCreate = async (data: CreateUserRequest) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await userClient.$post({
|
|
|
|
|
+ json: data
|
|
|
|
|
+ });
|
|
|
|
|
+ if (res.status !== 201) throw new Error('创建失败');
|
|
|
|
|
+ toast.success('创建成功');
|
|
|
|
|
+ refetch(); // 重新获取数据
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ toast.error('创建失败,请重试');
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### PUT 请求
|
|
|
|
|
+```typescript
|
|
|
|
|
+// 更新资源
|
|
|
|
|
+const handleUpdate = async (id: number, data: UpdateUserRequest) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await userClient[':id']['$put']({
|
|
|
|
|
+ param: { id },
|
|
|
|
|
+ json: data
|
|
|
|
|
+ });
|
|
|
|
|
+ if (res.status !== 200) throw new Error('更新失败');
|
|
|
|
|
+ toast.success('更新成功');
|
|
|
|
|
+ refetch();
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ toast.error('更新失败,请重试');
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### DELETE 请求
|
|
|
|
|
+```typescript
|
|
|
|
|
+// 删除资源
|
|
|
|
|
+const handleDelete = async (id: number) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await userClient[':id']['$delete']({
|
|
|
|
|
+ param: { id }
|
|
|
|
|
+ });
|
|
|
|
|
+ if (res.status !== 204) throw new Error('删除失败');
|
|
|
|
|
+ toast.success('删除成功');
|
|
|
|
|
+ refetch();
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ toast.error('删除失败,请重试');
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 错误处理规范
|
|
|
|
|
+
|
|
|
|
|
+#### 统一错误处理模式
|
|
|
|
|
+```typescript
|
|
|
|
|
+// 统一操作处理函数
|
|
|
|
|
+const handleOperation = async (operation: () => Promise<any>) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ await operation();
|
|
|
|
|
+ toast.success('操作成功');
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('操作失败:', error);
|
|
|
|
|
+ toast.error('操作失败,请重试');
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 使用示例
|
|
|
|
|
+const handleCreateUser = async (data: CreateUserRequest) => {
|
|
|
|
|
+ await handleOperation(async () => {
|
|
|
|
|
+ const res = await userClient.$post({ json: data });
|
|
|
|
|
+ if (res.status !== 201) throw new Error('创建失败');
|
|
|
|
|
+ });
|
|
|
|
|
+};
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 状态码检查
|
|
|
|
|
+```typescript
|
|
|
|
|
+// 标准状态码检查模式
|
|
|
|
|
+const checkStatus = (res: Response, expectedStatus: number) => {
|
|
|
|
|
+ if (res.status !== expectedStatus) {
|
|
|
|
|
+ throw new Error(`操作失败,状态码: ${res.status}`);
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 使用示例
|
|
|
|
|
+const res = await userClient.$post({ json: data });
|
|
|
|
|
+checkStatus(res, 201); // 期望201 Created
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 查询参数规范
|
|
|
|
|
+
|
|
|
|
|
+#### 分页参数
|
|
|
|
|
+```typescript
|
|
|
|
|
+const searchParams = {
|
|
|
|
|
+ page: 1, // 当前页码
|
|
|
|
|
+ pageSize: 10, // 每页数量
|
|
|
|
|
+ keyword: '' // 搜索关键词
|
|
|
|
|
+};
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 筛选参数
|
|
|
|
|
+```typescript
|
|
|
|
|
+const filters = {
|
|
|
|
|
+ status: undefined as string | undefined,
|
|
|
|
|
+ category: [] as number[],
|
|
|
|
|
+ dateRange: undefined as { start?: string; end?: string } | undefined
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 将筛选条件转换为查询参数
|
|
|
|
|
+const filterParams = Object.keys(filters).reduce((acc, key) => {
|
|
|
|
|
+ const value = filters[key as keyof typeof filters];
|
|
|
|
|
+ if (value !== undefined && value !== null &&
|
|
|
|
|
+ (!Array.isArray(value) || value.length > 0)) {
|
|
|
|
|
+ acc[key] = value;
|
|
|
|
|
+ }
|
|
|
|
|
+ return acc;
|
|
|
|
|
+}, {} as Record<string, unknown>);
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 性能优化
|
|
|
|
|
+
|
|
|
|
|
+#### 查询缓存配置
|
|
|
|
|
+```typescript
|
|
|
|
|
+const { data } = useQuery({
|
|
|
|
|
+ queryKey: ['users', searchParams, filters],
|
|
|
|
|
+ queryFn: fetchUsers,
|
|
|
|
|
+ staleTime: 5 * 60 * 1000, // 5分钟
|
|
|
|
|
+ cacheTime: 10 * 60 * 1000, // 10分钟
|
|
|
|
|
+ refetchOnWindowFocus: false // 避免窗口聚焦时重复请求
|
|
|
|
|
+});
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 防抖搜索
|
|
|
|
|
+```typescript
|
|
|
|
|
+// 防抖函数
|
|
|
|
|
+const debounce = (func: Function, delay: number) => {
|
|
|
|
|
+ let timeoutId: NodeJS.Timeout;
|
|
|
|
|
+ return (...args: any[]) => {
|
|
|
|
|
+ clearTimeout(timeoutId);
|
|
|
|
|
+ timeoutId = setTimeout(() => func(...args), delay);
|
|
|
|
|
+ };
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 防抖搜索
|
|
|
|
|
+const debouncedSearch = useCallback(
|
|
|
|
|
+ debounce((keyword: string) => {
|
|
|
|
|
+ setSearchParams(prev => ({ ...prev, keyword, page: 1 }));
|
|
|
|
|
+ }, 300),
|
|
|
|
|
+ []
|
|
|
|
|
+);
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
## 代码质量规范
|
|
## 代码质量规范
|
|
|
|
|
|
|
|
### 代码风格
|
|
### 代码风格
|