瀏覽代碼

♻️ refactor(routes): 重构父子商品路由模块

- 统一从 `index.mt` 文件导入路由,替换直接导入 `admin-goods-routes.mt`
- 将路由方法调用从 `app.openapi` 改为链式调用 `.openapi`
- 将请求体验证从 `c.req.valid('body')` 改为 `c.req.valid('json')`
- 将批量创建子商品的路由路径从 `/batch-create-children` 改为 `/batchCreateChildren`
- 在错误响应中统一使用 `parseWithAwait` 包装错误模式
- 更新集成测试中的客户端类型引用

🐛 fix(schema): 修复 Zod 数字类型定义

- 将 `z.number<number>()` 统一改为 `z.coerce.number<Number>()` 以支持类型转换
- 修复批量创建子商品响应中 `count` 字段的类型定义,移除泛型参数
yourname 1 月之前
父節點
當前提交
5f2446a13f

+ 1 - 1
packages/goods-module-mt/src/index.mt.ts

@@ -5,7 +5,7 @@ export * from './routes/index.mt';
 export * from './types/index.mt';
 
 // 导出路由
-import { adminGoodsRoutesMt } from './routes/admin-goods-routes.mt';
+import { adminGoodsRoutesMt } from './routes/index.mt';
 import { adminGoodsCategoriesRoutesMt } from './routes/admin-goods-categories.mt';
 import { userGoodsCategoriesRoutesMt } from './routes/user-goods-categories.mt';
 import { userGoodsRoutesMt } from './routes/user-goods-routes.mt';

+ 47 - 39
packages/goods-module-mt/src/routes/admin-goods-parent-child.mt.ts

@@ -10,13 +10,13 @@ import { parseWithAwait } from '@d8d/shared-utils';
 
 // 定义批量创建子商品Schema
 const BatchCreateChildrenSchema = z.object({
-  parentGoodsId: z.number<number>().int().positive('父商品ID必须是正整数'),
+  parentGoodsId: z.coerce.number<Number>().int().positive('父商品ID必须是正整数'),
   specs: z.array(z.object({
     name: z.string().min(1, '规格名称不能为空').max(255, '规格名称不能超过255个字符'),
-    price: z.number<number>().nonnegative('价格不能为负数'),
-    costPrice: z.number<number>().nonnegative('成本价不能为负数'),
-    stock: z.number<number>().int().nonnegative('库存不能为负数'),
-    sort: z.number<number>().int().default(0)
+    price: z.coerce.number<Number>().nonnegative('价格不能为负数'),
+    costPrice: z.coerce.number<Number>().nonnegative('成本价不能为负数'),
+    stock: z.coerce.number<Number>().int().nonnegative('库存不能为负数'),
+    sort: z.coerce.number<Number>().int().default(0)
   })).min(1, '至少需要一个规格')
 });
 
@@ -36,8 +36,8 @@ const getChildrenRoute = createRoute({
       }).refine((val) => val > 0, '商品ID必须是正整数')
     }),
     query: z.object({
-      page: z.coerce.number<number>().int().positive('页码必须是正整数').default(1),
-      pageSize: z.coerce.number<number>().int().positive('每页数量必须是正整数').default(10),
+      page: z.coerce.number<Number>().int().positive('页码必须是正整数').default(1),
+      pageSize: z.coerce.number<Number>().int().positive('每页数量必须是正整数').default(10),
       keyword: z.string().optional(),
       sortBy: z.string().optional().default('sort'),
       sortOrder: z.enum(['ASC', 'DESC']).optional().default('ASC')
@@ -50,10 +50,10 @@ const getChildrenRoute = createRoute({
         'application/json': {
           schema: z.object({
             data: z.array(GoodsSchema),
-            total: z.number<number>().int().nonnegative(),
-            page: z.number<number>().int().positive(),
-            pageSize: z.number<number>().int().positive(),
-            totalPages: z.number<number>().int().nonnegative()
+            total: z.coerce.number<Number>().int().nonnegative(),
+            page: z.coerce.number<Number>().int().positive(),
+            pageSize: z.coerce.number<Number>().int().positive(),
+            totalPages: z.coerce.number<Number>().int().nonnegative()
           })
         }
       }
@@ -160,7 +160,7 @@ const removeParentRoute = createRoute({
 // 4. 批量创建子商品路由
 const batchCreateChildrenRoute = createRoute({
   method: 'post',
-  path: '/batch-create-children',
+  path: '/batchCreateChildren',
   middleware: [authMiddleware],
   request: {
     body: {
@@ -178,7 +178,7 @@ const batchCreateChildrenRoute = createRoute({
         'application/json': {
           schema: z.object({
             success: z.boolean(),
-            count: z.number<number>().int().nonnegative(),
+            count: z.number().int().nonnegative(),
             children: z.array(GoodsSchema)
           })
         }
@@ -204,10 +204,10 @@ const batchCreateChildrenRoute = createRoute({
 });
 
 // 创建路由实例
-const app = new OpenAPIHono<AuthContext>();
+const app = new OpenAPIHono<AuthContext>()
 
 // 1. 实现获取子商品列表
-app.openapi(getChildrenRoute, async (c) => {
+.openapi(getChildrenRoute, async (c) => {
   try {
     const { id: parentId } = c.req.valid('param');
     const query = c.req.valid('query');
@@ -226,10 +226,11 @@ app.openapi(getChildrenRoute, async (c) => {
 
     if (!tenantId) {
       console.debug('无法获取租户信息,用户对象:', user);
-      return c.json({
+      const error = await parseWithAwait(ErrorSchema, {
         code: 400,
         message: '无法获取租户信息'
-      }, 400);
+      });
+      return c.json(error, 400);
     }
 
     // 验证父商品是否存在且属于当前租户
@@ -239,10 +240,11 @@ app.openapi(getChildrenRoute, async (c) => {
     });
 
     if (!parentGoods) {
-      return c.json({
+      const error = await parseWithAwait(ErrorSchema, {
         code: 404,
         message: '父商品不存在'
-      }, 404);
+      });
+      return c.json(error, 404);
     }
 
     // 创建查询构建器
@@ -291,15 +293,16 @@ app.openapi(getChildrenRoute, async (c) => {
     }, 200);
   } catch (error) {
     console.error('获取子商品列表失败:', error);
-    return c.json({
+    const errorResponse = await parseWithAwait(ErrorSchema, {
       code: 500,
       message: error instanceof Error ? error.message : '获取子商品列表失败'
-    }, 500);
+    });
+    return c.json(errorResponse, 500);
   }
-});
+})
 
 // 2. 实现设为父商品
-app.openapi(setAsParentRoute, async (c) => {
+.openapi(setAsParentRoute, async (c) => {
   try {
     const { id } = c.req.valid('param');
 
@@ -308,10 +311,11 @@ app.openapi(setAsParentRoute, async (c) => {
     const tenantId = c.get('tenantId');
 
     if (!tenantId) {
-      return c.json({
+      const error = await parseWithAwait(ErrorSchema, {
         code: 400,
         message: '无法获取租户信息'
-      }, 400);
+      });
+      return c.json(error, 400);
     }
 
     // 获取商品
@@ -320,18 +324,20 @@ app.openapi(setAsParentRoute, async (c) => {
     });
 
     if (!goods) {
-      return c.json({
+      const error = await parseWithAwait(ErrorSchema, {
         code: 404,
         message: '商品不存在'
-      }, 404);
+      });
+      return c.json(error, 404);
     }
 
     // 验证:不能是子商品
     if (goods.spuId > 0) {
-      return c.json({
+      const error = await parseWithAwait(ErrorSchema, {
         code: 400,
         message: '子商品不能设为父商品'
-      }, 400);
+      });
+      return c.json(error, 400);
     }
 
     // 更新商品为父商品(spuId已经是0,不需要修改)
@@ -352,10 +358,10 @@ app.openapi(setAsParentRoute, async (c) => {
       message: error instanceof Error ? error.message : '设为父商品失败'
     }, 500);
   }
-});
+})
 
 // 3. 实现解除父子关系
-app.openapi(removeParentRoute, async (c) => {
+.openapi(removeParentRoute, async (c) => {
   try {
     const { id } = c.req.valid('param');
 
@@ -364,10 +370,11 @@ app.openapi(removeParentRoute, async (c) => {
     const tenantId = c.get('tenantId');
 
     if (!tenantId) {
-      return c.json({
+      const error = await parseWithAwait(ErrorSchema, {
         code: 400,
         message: '无法获取租户信息'
-      }, 400);
+      });
+      return c.json(error, 400);
     }
 
     // 获取商品
@@ -376,10 +383,11 @@ app.openapi(removeParentRoute, async (c) => {
     });
 
     if (!goods) {
-      return c.json({
+      const error = await parseWithAwait(ErrorSchema, {
         code: 404,
         message: '商品不存在'
-      }, 404);
+      });
+      return c.json(error, 404);
     }
 
     // 验证:必须是子商品
@@ -419,16 +427,16 @@ app.openapi(removeParentRoute, async (c) => {
       message: error instanceof Error ? error.message : '解除父子关系失败'
     }, 500);
   }
-});
+})
 
 // 4. 实现批量创建子商品
-app.openapi(batchCreateChildrenRoute, async (c) => {
+.openapi(batchCreateChildrenRoute, async (c) => {
   const queryRunner = AppDataSource.createQueryRunner();
   await queryRunner.connect();
   await queryRunner.startTransaction();
 
   try {
-    const { parentGoodsId, specs } = c.req.valid('body');
+    const { parentGoodsId, specs } = c.req.valid('json');
 
     // 获取当前用户和租户
     const user = c.get('user');
@@ -529,6 +537,6 @@ app.openapi(batchCreateChildrenRoute, async (c) => {
   } finally {
     await queryRunner.release();
   }
-});
+})
 
 export const adminGoodsParentChildRoutesMt = app;

+ 1 - 1
packages/goods-module-mt/tests/integration/admin-goods-parent-child.integration.test.ts

@@ -16,7 +16,7 @@ setupIntegrationDatabaseHooksWithEntities([
 ])
 
 describe('管理员父子商品管理API集成测试', () => {
-  let client: ReturnType<typeof testClient<typeof adminGoodsParentChildRoutesMt>>;
+  let client: ReturnType<typeof testClient<typeof adminGoodsRoutesMt>>;
   let testUser: UserEntityMt;
   let testCategory: GoodsCategoryMt;
   let testSupplier: SupplierMt;