Răsfoiți Sursa

Merge branch 'starter' into stock

yourname 5 luni în urmă
părinte
comite
caeeb2a37a
3 a modificat fișierele cu 175 adăugiri și 65 ștergeri
  1. 131 2
      .roo/rules/10-entity.md
  2. 11 63
      .roo/rules/11-entity-creation.md
  3. 33 0
      .roo/rules/13-ui-style.md

+ 131 - 2
.roo/rules/10-entity.md

@@ -80,6 +80,8 @@ updatedAt!: Date;
 
 ## 7. Zod Schema 规范
 
+### 7.1 基础类型规范
+
 ```typescript
 export const EntitySchema = z.object({
   id: z.number().int().positive().openapi({ description: 'ID说明' }),
@@ -87,11 +89,11 @@ export const EntitySchema = z.object({
   fieldName: z.string()
     .max(255)
     .nullable()
-    .openapi({ 
+    .openapi({
       description: '字段说明',
       example: '示例值'
     }),
-  // 数字字段  
+  // 数字字段
   numberField: z.number()
     .default(默认值)
     .openapi({...}),
@@ -100,6 +102,133 @@ export const EntitySchema = z.object({
 });
 ```
 
+### 7.2 数据类型转换规范
+
+#### 7.2.1 数字类型转换
+
+对于URL参数或表单数据中的数字类型,必须使用`z.coerce.number()`进行类型转换,以确保字符串到数字的正确转换:
+
+```typescript
+// 整数类型
+z.coerce.number().int().positive().openapi({
+  description: '正整数ID',
+  example: 1
+});
+
+// 小数类型
+z.coerce.number().multipleOf(0.01).openapi({
+  description: '金额,保留两位小数',
+  example: 19.99
+});
+
+// 状态类型(0/1)
+z.coerce.number().int().min(0).max(1).openapi({
+  description: '状态(0-禁用,1-启用)',
+  example: 1
+});
+```
+
+#### 7.2.2 日期类型转换
+
+对于日期时间类型,必须使用`z.coerce.date()`进行类型转换:
+
+```typescript
+// 日期时间类型
+z.coerce.date().openapi({
+  description: '创建时间',
+  example: '2023-10-01T12:00:00Z'
+});
+
+// 日期范围查询
+const DateRangeSchema = z.object({
+  startDate: z.coerce.date().openapi({
+    description: '开始日期',
+    example: '2023-10-01T00:00:00Z'
+  }),
+  endDate: z.coerce.date().openapi({
+    description: '结束日期',
+    example: '2023-10-31T23:59:59Z'
+  })
+});
+```
+
+#### 7.2.3 布尔类型转换
+
+对于布尔类型参数,必须使用`z.coerce.boolean()`进行类型转换:
+
+```typescript
+// 布尔类型
+z.coerce.boolean().openapi({
+  description: '是否启用',
+  example: true
+});
+```
+
+### 7.3 创建/更新Schema特殊规范
+
+创建(Create)和更新(Update)Schema必须遵循以下额外规范:
+
+1. **创建Schema**:
+   - 不包含`id`字段(由数据库自动生成)
+   - 所有必填字段必须显式定义,不得为`optional()`
+   - 必须使用适当的coerce方法处理非字符串类型
+
+```typescript
+export const CreateEntityDto = z.object({
+  name: z.string().max(255).openapi({
+    description: '名称',
+    example: '示例名称'
+  }),
+  quantity: z.coerce.number().int().min(1).openapi({
+    description: '数量',
+    example: 10
+  }),
+  price: z.coerce.number().multipleOf(0.01).openapi({
+    description: '价格',
+    example: 99.99
+  }),
+  isActive: z.coerce.boolean().default(true).openapi({
+    description: '是否激活',
+    example: true
+  }),
+  expireDate: z.coerce.date().openapi({
+    description: '过期日期',
+    example: '2024-12-31T23:59:59Z'
+  })
+});
+```
+
+2. **更新Schema**:
+   - 不包含`id`字段(通过URL参数传递)
+   - 所有字段必须为`optional()`
+   - 必须使用适当的coerce方法处理非字符串类型
+
+```typescript
+export const UpdateEntityDto = z.object({
+  name: z.string().max(255).optional().openapi({
+    description: '名称',
+    example: '更新后的名称'
+  }),
+  quantity: z.coerce.number().int().min(1).optional().openapi({
+    description: '数量',
+    example: 20
+  }),
+  price: z.coerce.number().multipleOf(0.01).optional().openapi({
+    description: '价格',
+    example: 89.99
+  }),
+  isActive: z.coerce.boolean().optional().openapi({
+    description: '是否激活',
+    example: false
+  }),
+  expireDate: z.coerce.date().optional().openapi({
+    description: '过期日期',
+    example: '2025-12-31T23:59:59Z'
+  })
+});
+```
+```
+
 ## 8. 命名规范
 
 - 实体类名:PascalCase (如 RackInfo)

+ 11 - 63
.roo/rules/11-entity-creation.md

@@ -90,7 +90,7 @@
      
      export const yourEntityClient = hc<YourEntityRoutes>('/api/v1', {
        fetch: axiosFetch,
-     }).your-entities;
+     }).api.v1['your-entities'];
      ```
 
 6. **前端调用**
@@ -450,73 +450,21 @@
      ```typescript
      import { hc } from 'hono/client';
      import { YourEntityRoutes } from '@/server/api';
-     import { axiosFetch } from '@/client/utils/fetch';
      
      export const yourEntityClient = hc<YourEntityRoutes>('/api/v1', {
        fetch: axiosFetch,
-     }).your_entities; // 注意: 路由路径中的连字符会转为下划线
-     
-     // 类型提取
-     import type { InferRequestType, InferResponseType } from 'hono/client';
-     
-     // 列表查询
-     export type YourEntityListQuery = InferRequestType<typeof yourEntityClient.$get>['query'];
-     export type YourEntityListResponse = InferResponseType<typeof yourEntityClient.$get, 200>;
-     
-     // 创建实体
-     export type CreateYourEntityRequest = InferRequestType<typeof yourEntityClient.$post>['json'];
-     export type CreateYourEntityResponse = InferResponseType<typeof yourEntityClient.$post, 200>;
-     
-     // 获取单个实体
-     export type GetYourEntityParams = InferRequestType<typeof yourEntityClient[':id']['$get']>['param'];
-     export type GetYourEntityResponse = InferResponseType<typeof yourEntityClient[':id']['$get'], 200>;
-     
-     // 更新实体
-     export type UpdateYourEntityParams = InferRequestType<typeof yourEntityClient[':id']['$put']>['param'];
-     export type UpdateYourEntityRequest = InferRequestType<typeof yourEntityClient[':id']['$put']>['json'];
-     export type UpdateYourEntityResponse = InferResponseType<typeof yourEntityClient[':id']['$put'], 200>;
-     
-     // 删除实体
-     export type DeleteYourEntityParams = InferRequestType<typeof yourEntityClient[':id']['$delete']>['param'];
-     export type DeleteYourEntityResponse = InferResponseType<typeof yourEntityClient[':id']['$delete'], 200>;
+     }).api.v1['your-entities'];
      ```
 
-### 7. **前端调用示例**
-   - 在页面组件中使用客户端API:
-     ```typescript
-     import { yourEntityClient, type YourEntityListResponse } from '@/client/api';
-     import { useQuery, useMutation, useQueryClient } from 'react-query';
-     
-     // 获取列表数据
-     const fetchEntities = async () => {
-       const res = await yourEntityClient.$get({
-         query: { page: 1, pageSize: 10, status: 1 }
-       });
-       if (!res.ok) {
-         const error = await res.json();
-         throw new Error(error.message || '获取数据失败');
-       }
-       return res.json() as Promise<YourEntityListResponse>;
-     };
-     
-     // 使用React Query获取数据
-     export function useEntitiesData() {
-       return useQuery(['entities'], fetchEntities);
-     }
-     
-     // 创建实体
-     export function useCreateEntity() {
-       const queryClient = useQueryClient();
-       return useMutation(
-         (data) => yourEntityClient.$post({ json: data }),
-         {
-           onSuccess: () => {
-             queryClient.invalidateQueries(['entities']);
-           }
-         }
-       );
-     }
-     ```
+### 7. **前端调用**
+   - 在页面组件(如`pages_users.tsx`)中:
+     - 使用`InferResponseType`提取响应类型
+     - 使用`InferRequestType`提取请求类型
+     - 示例:
+       ```typescript
+       type EntityResponse = InferResponseType<typeof entityClient.$get, 200>;
+       type CreateRequest = InferRequestType<typeof entityClient.$post>['json'];
+       ```
 
 ## 注意事项
 

+ 33 - 0
.roo/rules/13-ui-style.md

@@ -45,6 +45,39 @@
 - 表单布局使用垂直布局,标签在上,输入框在下
 - 输入框聚焦状态:`focus:border-primary focus:ring-1 focus:ring-primary`
 
+### 3.5 日期表单组件
+- 日期选择器使用 `DatePicker` 组件,时间选择使用 `TimePicker` 组件
+- 日期选择器大小与输入框保持一致:`size="middle"`
+- 日期格式统一为 `YYYY-MM-DD`,时间格式为 `HH:mm:ss`
+- 日期范围选择使用 `RangePicker` 组件,格式为 `[YYYY-MM-DD, YYYY-MM-DD]`
+- 日期选择器添加清除按钮:`allowClear`
+- 日期选择器添加占位提示:`placeholder="请选择日期"`
+- 日期选择器禁用未来日期:`disabledDate={(current) => current && current > dayjs().endOf('day')}`(根据业务需求调整)
+- 日期时间转换规范:
+  ```typescript
+  // 日期对象转字符串(提交给后端)
+  const formatDate = (date: Dayjs | null) => {
+    return date ? date.format('YYYY-MM-DD') : '';
+  };
+  
+  // 字符串转日期对象(从后端接收)
+  const parseDate = (str: string) => {
+    return str ? dayjs(str) : null;
+  };
+  
+  // 日期时间对象转字符串
+  const formatDateTime = (date: Dayjs | null) => {
+    return date ? date.format('YYYY-MM-DD HH:mm:ss') : '';
+  };
+  
+  // 日期范围转换
+  const formatDateRange = (range: [Dayjs | null, Dayjs | null]) => {
+    return range && range[0] && range[1]
+      ? [range[0].format('YYYY-MM-DD'), range[1].format('YYYY-MM-DD')]
+      : [];
+  };
+  ```
+
 ### 3.3 表格样式
 - 表格添加边框:`bordered`
 - 表头背景色使用浅灰(#f9fafb)