Quellcode durchsuchen

✨ feat(mini): 为调试和商品图片显示添加控制台日志与回退逻辑

- 在地址编辑、地址管理、购物车、商品详情和订单详情页面启用或添加调试日志,便于问题排查
- 优化商品图片显示逻辑,为购物车和商品详情页添加轮播图作为图片回退源
- 在商品父子关系面板和子商品列表组件中添加客户端结构调试日志,辅助API路由调试

✅ test(address-edit): 添加地址编辑页面的单元测试

- 创建基础测试文件,覆盖页面渲染、表单提交、API调用和错误处理等场景
- 模拟Taro导航、API客户端、身份验证钩子和React Hook Form以进行隔离测试
- 验证添加和编辑地址模式下的UI交互与状态管理

🐛 fix(goods-module): 修复文件模块和商品父子关系API的问题

- 将文件模式中的`uploadUser`字段设为可空可选,避免关联查询失败
- 修复父子商品API路由,在查询子商品列表时关联加载图片文件的上传用户信息
- 修复批量创建子商品时的数据复制逻辑,确保正确继承父商品的图片文件ID
yourname vor 1 Monat
Ursprung
Commit
055bcb7094

+ 3 - 3
mini/src/pages/address-edit/index.tsx

@@ -139,9 +139,9 @@ export default function AddressEditPage() {
 
 
   const onSubmit = (data: AddressFormData) => {
-    //console.debug('表单提交数据:', data)
-    //console.debug('表单验证状态:', form.formState.isValid)
-    //console.debug('表单错误:', form.formState.errors)
+    console.debug('表单提交数据:', data)
+    console.debug('表单验证状态:', form.formState.isValid)
+    console.debug('表单错误:', form.formState.errors)
     saveAddressMutation.mutate(data)
   }
 

+ 2 - 1
mini/src/pages/address-manage/index.tsx

@@ -199,12 +199,13 @@ export default function AddressManagePage() {
 
   // 删除地址
   const handleDeleteAddress = (id: number) => {
-   // console.log("id:",id)
+    console.log("id:",id)
     closeAllSwipes()
     Taro.showModal({
       title: '删除地址',
       content: '确定要删除这个收货地址吗?',
       success: (res) => {
+        console.log("res",res)
         if (res.confirm) {
           deleteAddressMutation.mutate(id)
         }

+ 2 - 1
mini/src/pages/cart/index.tsx

@@ -248,6 +248,7 @@ export default function CartPage() {
                 // 获取从数据库重新获取的最新商品信息
                 const latestGoods = goodsMap.get(item.id)
                 // 优先使用数据库中的最新信息,如果没有则使用本地保存的信息
+                // console.log("latestGoods:",latestGoods);
 
                 // 判断是否为子商品(父子商品)
                 const isChildGoods = item.parentGoodsId !== 0
@@ -263,7 +264,7 @@ export default function CartPage() {
                   : null                             // 单规格商品无规格名称
 
                 const goodsPrice = latestGoods?.price || item.price
-                const goodsImage = latestGoods?.imageFile?.fullUrl || item.image
+                const goodsImage = latestGoods?.imageFile?.fullUrl || latestGoods?.slideImages?.[0]?.fullUrl || item.image
                 const goodsStock = latestGoods?.stock || item.stock
 
                 return (

+ 5 - 4
mini/src/pages/goods-detail/index.tsx

@@ -18,6 +18,7 @@ interface SelectedSpec {
   name: string
   price: number
   stock: number
+  image?: string
 }
 
 interface Review {
@@ -301,7 +302,7 @@ export default function GoodsDetailPage() {
             parentGoodsId: parentGoodsId,
             name: targetGoodsName,
             price: targetPrice,
-            image: goods.imageFile?.fullUrl || '',
+            image: spec.image || goods.imageFile?.fullUrl || '',
             stock: targetStock,
             quantity: finalQuantity
           })
@@ -337,7 +338,7 @@ export default function GoodsDetailPage() {
               id: targetGoodsId,
               name: targetGoodsName,
               price: targetPrice,
-              image: goods.imageFile?.fullUrl || '',
+              image: spec.image || goods.imageFile?.fullUrl || '',
               quantity: finalQuantity
             },
             totalAmount: targetPrice * finalQuantity
@@ -413,7 +414,7 @@ export default function GoodsDetailPage() {
       parentGoodsId: parentGoodsId,
       name: targetGoodsName,
       price: targetPrice,
-      image: goods.imageFile?.fullUrl || '',
+      image: goods.imageFile?.fullUrl || goods.slideImages?.[0]?.fullUrl || '',
       stock: targetStock,
       quantity: finalQuantity
     })
@@ -460,7 +461,7 @@ export default function GoodsDetailPage() {
         id: targetGoodsId,
         name: targetGoodsName,
         price: targetPrice,
-        image: goods.imageFile?.fullUrl || '',
+        image: goods.imageFile?.fullUrl || goods.slideImages?.[0]?.fullUrl || '',
         quantity: finalQuantity
       },
       totalAmount: targetPrice * finalQuantity

+ 1 - 0
mini/src/pages/order-detail/index.tsx

@@ -167,6 +167,7 @@ export default function OrderDetailPage() {
 
   // 使用orderGoods关联关系获取商品信息
   const orderGoods = (order as any)?.orderGoods || []
+  //console.log("ordergoods:",orderGoods);
 
   if (isLoading) {
     return (

+ 418 - 0
mini/tests/unit/pages/address-edit/basic.test.tsx

@@ -0,0 +1,418 @@
+import React from 'react'
+import { render, screen, fireEvent, waitFor } from '@testing-library/react'
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
+import AddressEditPage from '@/pages/address-edit/index'
+
+// 导入Taro mock函数
+import { mockUseRouter, mockNavigateBack, mockShowToast } from '~/__mocks__/taroMock'
+
+// Mock API client
+jest.mock('@/api', () => ({
+  deliveryAddressClient: {
+    $post: jest.fn(),
+    ':id': {
+      $get: jest.fn(),
+      $put: jest.fn(),
+    },
+  },
+}))
+
+// Mock auth hook
+jest.mock('@/utils/auth', () => ({
+  useAuth: () => ({
+    user: { id: 1 },
+  }),
+}))
+
+// Mock react-hook-form
+let mockFormData = {
+  name: '',
+  phone: '',
+  province: undefined as number | undefined,
+  city: undefined as number | undefined,
+  district: undefined as number | undefined,
+  town: undefined as number | undefined,
+  address: '',
+  isDefault: false
+}
+
+const mockHandleSubmit = jest.fn((onSubmit, onError) => (e: any) => {
+  e?.preventDefault?.()
+  // 模拟验证通过,调用onSubmit
+  onSubmit(mockFormData)
+})
+
+const mockReset = jest.fn((data: any) => {
+  if (data) {
+    mockFormData = { ...mockFormData, ...data }
+  }
+})
+
+const mockSetValue = jest.fn((name: string, value: any) => {
+  mockFormData = { ...mockFormData, [name]: value }
+})
+
+const mockWatch = jest.fn()
+
+jest.mock('react-hook-form', () => ({
+  useForm: () => ({
+    formState: {
+      isValid: true,
+      errors: {},
+    },
+    handleSubmit: mockHandleSubmit,
+    reset: mockReset,
+    setValue: mockSetValue,
+    watch: mockWatch,
+    getValues: () => mockFormData,
+  }),
+}))
+
+// Mock components
+jest.mock('@/components/ui/navbar', () => ({
+  Navbar: ({ title, onClickLeft }: { title: string; onClickLeft: () => void }) => (
+    <div data-testid="navbar">
+      <span>{title}</span>
+      <button onClick={onClickLeft}>返回</button>
+    </div>
+  ),
+}))
+
+jest.mock('@/components/ui/button', () => ({
+  Button: ({ children, onClick, disabled, className }: any) => (
+    <button
+      onClick={onClick}
+      disabled={disabled}
+      className={className}
+      data-testid="button"
+    >
+      {children}
+    </button>
+  ),
+}))
+
+jest.mock('@/components/ui/card', () => ({
+  Card: ({ children, className }: any) => (
+    <div className={className} data-testid="card">
+      {children}
+    </div>
+  ),
+}))
+
+jest.mock('@/components/ui/form', () => ({
+  Form: ({ children, ...props }: any) => (
+    <form {...props} data-testid="form">
+      {children}
+    </form>
+  ),
+  FormField: ({ name, render }: any) => (
+    <div data-testid={`form-field-${name}`}>
+      {render({ field: { value: '', onChange: jest.fn() } })}
+    </div>
+  ),
+  FormItem: ({ children }: any) => <div data-testid="form-item">{children}</div>,
+  FormLabel: ({ children }: any) => <label data-testid="form-label">{children}</label>,
+  FormControl: ({ children }: any) => <div data-testid="form-control">{children}</div>,
+  FormMessage: () => <div data-testid="form-message">错误消息</div>,
+}))
+
+jest.mock('@/components/ui/input', () => ({
+  Input: ({ placeholder, ...props }: any) => (
+    <input
+      placeholder={placeholder}
+      {...props}
+      data-testid="input"
+    />
+  ),
+}))
+
+jest.mock('@/components/ui/switch', () => ({
+  Switch: ({ checked, onChange, color }: any) => (
+    <input
+      type="checkbox"
+      checked={checked}
+      onChange={onChange}
+      data-testid="switch"
+      style={{ color }}
+    />
+  ),
+}))
+
+jest.mock('@/components/ui/city-selector', () => ({
+  CitySelector: ({
+    provinceValue,
+    cityValue,
+    districtValue,
+    townValue,
+    onProvinceChange,
+    onCityChange,
+    onDistrictChange,
+    onTownChange,
+    showLabels
+  }: any) => (
+    <div data-testid="city-selector">
+      <div>省份: {provinceValue}</div>
+      <div>城市: {cityValue}</div>
+      <div>区县: {districtValue}</div>
+      <div>乡镇: {townValue}</div>
+    </div>
+  ),
+}))
+
+describe('AddressEditPage', () => {
+  let queryClient: QueryClient
+
+  beforeEach(() => {
+    queryClient = new QueryClient({
+      defaultOptions: {
+        queries: { retry: false },
+        mutations: { retry: false },
+      },
+    })
+
+    // Reset all mocks
+    jest.clearAllMocks()
+
+    // 重置表单数据
+    mockFormData = {
+      name: '',
+      phone: '',
+      province: undefined,
+      city: undefined,
+      district: undefined,
+      town: undefined,
+      address: '',
+      isDefault: false
+    }
+
+    // 默认设置无地址ID(添加模式)
+    mockUseRouter.mockReturnValue({
+      params: {},
+    })
+  })
+
+  const renderWithProviders = (component: React.ReactElement) => {
+    return render(
+      <QueryClientProvider client={queryClient}>
+        {component}
+      </QueryClientProvider>
+    )
+  }
+
+  const mockAddress = {
+    id: 1,
+    name: '张三',
+    phone: '13812345678',
+    receiverProvince: 440000,
+    receiverCity: 440300,
+    receiverDistrict: 440305,
+    receiverTown: 440305001,
+    address: '科技大厦A座',
+    isDefault: 1,
+  }
+
+  it('渲染添加地址页面标题和布局', async () => {
+    renderWithProviders(<AddressEditPage />)
+
+    expect(screen.getByTestId('navbar')).toBeInTheDocument()
+    expect(screen.getByText('添加地址')).toBeInTheDocument()
+    expect(screen.getByTestId('form')).toBeInTheDocument()
+  })
+
+  it('渲染编辑地址页面标题和布局', async () => {
+    const { deliveryAddressClient } = await import('@/api')
+    ;(deliveryAddressClient[':id'].$get as jest.Mock).mockResolvedValue({
+      status: 200,
+      json: async () => mockAddress,
+    } as any)
+
+    mockUseRouter.mockReturnValue({
+      params: { id: '1' },
+    })
+
+    renderWithProviders(<AddressEditPage />)
+
+    await waitFor(() => {
+      expect(screen.getByText('编辑地址')).toBeInTheDocument()
+      expect(deliveryAddressClient[':id'].$get).toHaveBeenCalledWith({
+        param: { id: 1 }
+      })
+    })
+  })
+
+  it('填充编辑模式下的表单数据', async () => {
+    const { deliveryAddressClient } = await import('@/api')
+    ;(deliveryAddressClient[':id'].$get as jest.Mock).mockResolvedValue({
+      status: 200,
+      json: async () => mockAddress,
+    } as any)
+
+    mockUseRouter.mockReturnValue({
+      params: { id: '1' },
+    })
+
+    renderWithProviders(<AddressEditPage />)
+
+    await waitFor(() => {
+      expect(deliveryAddressClient[':id'].$get).toHaveBeenCalledWith({
+        param: { id: 1 }
+      })
+    })
+  })
+
+  it('提交新建地址表单', async () => {
+    const { deliveryAddressClient } = await import('@/api')
+    const mockResponse = { id: 2, ...mockAddress, isDefault: 0 }
+    ;(deliveryAddressClient.$post as jest.Mock).mockResolvedValue({
+      status: 201,
+      json: async () => mockResponse,
+    } as any)
+
+    renderWithProviders(<AddressEditPage />)
+
+    // 找到保存按钮并点击
+    await waitFor(() => {
+      const saveButton = screen.getByText('保存地址')
+      expect(saveButton).toBeInTheDocument()
+
+      fireEvent.click(saveButton)
+    })
+
+    await waitFor(() => {
+      expect(deliveryAddressClient.$post).toHaveBeenCalled()
+      expect(mockShowToast).toHaveBeenCalledWith({
+        title: '添加成功',
+        icon: 'success'
+      })
+      expect(mockNavigateBack).toHaveBeenCalled()
+    })
+  })
+
+  it('提交编辑地址表单', async () => {
+    const { deliveryAddressClient } = await import('@/api')
+    ;(deliveryAddressClient[':id'].$get as jest.Mock).mockResolvedValue({
+      status: 200,
+      json: async () => mockAddress,
+    } as any)
+
+    const updatedAddress = { ...mockAddress, name: '李四' }
+    ;(deliveryAddressClient[':id'].$put as jest.Mock).mockResolvedValue({
+      status: 200,
+      json: async () => updatedAddress,
+    } as any)
+
+    mockUseRouter.mockReturnValue({
+      params: { id: '1' },
+    })
+
+    renderWithProviders(<AddressEditPage />)
+
+    // 等待数据加载
+    await waitFor(() => {
+      expect(deliveryAddressClient[':id'].$get).toHaveBeenCalled()
+    })
+
+    // 确保表单已重置
+    await waitFor(() => {
+      expect(mockReset).toHaveBeenCalled()
+    })
+
+    // 找到保存按钮并点击
+    await waitFor(() => {
+      const saveButton = screen.getByText('保存地址')
+      expect(saveButton).toBeInTheDocument()
+
+      fireEvent.click(saveButton)
+    })
+
+    await waitFor(() => {
+      expect(deliveryAddressClient[':id'].$put).toHaveBeenCalled()
+      // 检查调用参数
+      const call = deliveryAddressClient[':id'].$put.mock.calls[0]
+      expect(call[0]).toEqual(expect.objectContaining({
+        param: { id: 1 }
+      }))
+      expect(mockShowToast).toHaveBeenCalledWith({
+        title: '更新成功',
+        icon: 'success'
+      })
+      expect(mockNavigateBack).toHaveBeenCalled()
+    })
+  })
+
+  it('显示API错误提示', async () => {
+    const { deliveryAddressClient } = await import('@/api')
+    ;(deliveryAddressClient.$post as jest.Mock).mockResolvedValue({
+      status: 400,
+      json: async () => ({ message: '保存失败' }),
+    } as any)
+
+    renderWithProviders(<AddressEditPage />)
+
+    // 找到保存按钮并点击
+    await waitFor(() => {
+      const saveButton = screen.getByText('保存地址')
+      fireEvent.click(saveButton)
+    })
+
+    await waitFor(() => {
+      expect(mockShowToast).toHaveBeenCalledWith({
+        title: '保存地址失败',
+        icon: 'none'
+      })
+    })
+  })
+
+  it('验证表单字段渲染', async () => {
+    renderWithProviders(<AddressEditPage />)
+
+    expect(screen.getByText('收货人姓名')).toBeInTheDocument()
+    expect(screen.getByText('手机号码')).toBeInTheDocument()
+    expect(screen.getByText('详细地址')).toBeInTheDocument()
+    expect(screen.getByText('设为默认地址')).toBeInTheDocument()
+  })
+
+  it('点击返回按钮', async () => {
+    renderWithProviders(<AddressEditPage />)
+
+    const navbar = screen.getByTestId('navbar')
+    const backButton = navbar.querySelector('button')
+
+    if (backButton) {
+      fireEvent.click(backButton)
+      expect(mockNavigateBack).toHaveBeenCalled()
+    }
+  })
+
+  it('显示保存中的加载状态', async () => {
+    const { deliveryAddressClient } = await import('@/api')
+    let resolvePromise: any
+    const promise = new Promise(resolve => {
+      resolvePromise = resolve
+    })
+    ;(deliveryAddressClient.$post as jest.Mock).mockReturnValue(promise)
+
+    renderWithProviders(<AddressEditPage />)
+
+    // 点击保存按钮
+    await waitFor(() => {
+      const saveButton = screen.getByText('保存地址')
+      fireEvent.click(saveButton)
+    })
+
+    // 应该显示加载状态
+    await waitFor(() => {
+      expect(screen.getByText('保存中...')).toBeInTheDocument()
+    })
+
+    // 解析promise
+    resolvePromise({
+      status: 201,
+      json: async () => ({ id: 1 }),
+    })
+
+    await waitFor(() => {
+      expect(screen.getByText('保存地址')).toBeInTheDocument()
+    })
+  })
+})

+ 1 - 1
packages/core-module-mt/file-module-mt/src/schemas/file.schema.mt.ts

@@ -119,7 +119,7 @@ export const FileSchema = z.object({
     description: '上传用户ID',
     example: 1
   }),
-  uploadUser: UserSchemaMt,
+  uploadUser: UserSchemaMt.nullable().optional(),
   uploadTime: z.coerce.date().openapi({
     description: '上传时间',
     example: '2023-01-15T10:30:00Z'

+ 27 - 4
packages/goods-management-ui-mt/src/components/ChildGoodsList.tsx

@@ -1,4 +1,4 @@
-import React, { useState } from 'react';
+import React, { useState, useEffect } from 'react';
 import { useQuery } from '@tanstack/react-query';
 import { Edit, Trash2, Package, ExternalLink, Loader2 } from 'lucide-react';
 import { toast } from 'sonner';
@@ -62,17 +62,30 @@ export const ChildGoodsList: React.FC<ChildGoodsListProps> = ({
   const [editingChildId, setEditingChildId] = useState<number | null>(null);
   const [isSaving, setIsSaving] = useState(false);
 
+  // 调试:打印客户端结构
+  useEffect(() => {
+    console.debug('ChildGoodsList客户端结构:', {
+      parentGoodsId,
+      tenantId,
+      client: goodsClientManager.get(),
+      clientIdRoute: goodsClientManager.get()[':id'],
+      clientHasChildren: 'children' in goodsClientManager.get()[':id'],
+      clientKeys: Object.keys(goodsClientManager.get()[':id']),
+      queryEnabled: !!parentGoodsId
+    });
+  }, [parentGoodsId, tenantId]);
+
   // 获取子商品列表
   const { data: childrenData, isLoading, refetch } = useQuery({
     queryKey: ['goods', 'children', 'list', parentGoodsId, tenantId],
     queryFn: async () => {
       try {
         const client = goodsClientManager.get();
-        if (!client || !client[':id'] || !client[':id'].children) {
+        if (!client || !client[':id'] || !client[':id']['children']) {
           return [];
         }
 
-        const res = await client[':id'].children.$get({
+        const res = await client[':id']['children'].$get({
           param: { id: parentGoodsId },
           query: { page: 1, pageSize: 1000, sortBy: 'sort', sortOrder: 'DESC' }
         });
@@ -83,7 +96,17 @@ export const ChildGoodsList: React.FC<ChildGoodsListProps> = ({
 
         const result = await res.json();
         return result.data || [];
-      } catch {
+      } catch (error) {
+        console.debug('ChildGoodsList获取子商品列表失败详情:', {
+          parentGoodsId,
+          tenantId,
+          error: error instanceof Error ? error.message : String(error),
+          client: goodsClientManager.get(),
+          clientIdRoute: goodsClientManager.get()[':id'],
+          clientHasChildren: 'children' in goodsClientManager.get()[':id'],
+          clientKeys: Object.keys(goodsClientManager.get()[':id']),
+          routePath: `GET /goods/:id/children`
+        });
         return [];
       }
     },

+ 3 - 2
packages/goods-management-ui-mt/src/components/GoodsManagement.tsx

@@ -241,11 +241,12 @@ export const GoodsManagement: React.FC = () => {
 
   // 提交表单
   const handleSubmit = (data: CreateRequest | UpdateRequest) => {
-    // 合并表单数据和父子商品数据
+    // 合并表单数据和父子商品数据(只合并spuId,childGoodsIds由专门API管理)
     const submitData = {
       ...data,
       spuId: parentChildData.spuId,
-      childGoodsIds: parentChildData.childGoodsIds,
+      // 注意:childGoodsIds不在CreateGoodsDto/UpdateGoodsDto中,由专门API管理
+      // 不要包含childGoodsIds,否则会导致Zod验证失败
     };
 
     if (isCreateForm) {

+ 31 - 5
packages/goods-management-ui-mt/src/components/GoodsParentChildPanel.tsx

@@ -11,6 +11,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@d8d/shared-ui-compone
 import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@d8d/shared-ui-components/components/ui/dialog';
 import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@d8d/shared-ui-components/components/ui/table';
 import { goodsClientManager } from '../api/goodsClient';
+import { GoodsMt } from '@d8d/goods-module-mt';
 import { BatchSpecCreatorInline } from './BatchSpecCreatorInline';
 import { ChildGoodsList } from './ChildGoodsList';
 
@@ -86,9 +87,24 @@ export const GoodsParentChildPanel: React.FC<GoodsParentChildPanelProps> = ({
   const [isSetAsParentDialogOpen, setIsSetAsParentDialogOpen] = useState(false);
   const [isRemoveParentDialogOpen, setIsRemoveParentDialogOpen] = useState(false);
   const [isDeleteChildDialogOpen, setIsDeleteChildDialogOpen] = useState(false);
-  const [deletingChildId, setDeletingChildId] = useState<number | null>(null);
+  const [deletingChildId, setDeletingChildId] = useState<number | undefined>(undefined);
   const queryClient = useQueryClient();
 
+  // 调试:打印客户端结构
+  useEffect(() => {
+    console.debug('GoodsParentChildPanel客户端结构:', {
+      goodsId,
+      tenantId,
+      client: goodsClientManager.get(),
+      clientIdRoute: goodsClientManager.get()[':id'],
+      clientHasChildren: 'children' in goodsClientManager.get()[':id'],
+      clientHasSetAsParent: 'set-as-parent' in goodsClientManager.get()[':id'],
+      clientHasParent: 'parent' in goodsClientManager.get()[':id'],
+      clientKeys: Object.keys(goodsClientManager.get()[':id']),
+      batchCreateChildren: goodsClientManager.get().batchCreateChildren
+    });
+  }, [goodsId, tenantId]);
+
   // 获取子商品列表(编辑模式)
   const { data: childrenData } = useQuery({
     queryKey: ['goods-children', goodsId, tenantId],
@@ -96,7 +112,7 @@ export const GoodsParentChildPanel: React.FC<GoodsParentChildPanelProps> = ({
       if (!goodsId || mode !== 'edit') return { data: [], total: 0 };
 
       try {
-        const res = await goodsClientManager.get()[':id'].children.$get({
+        const res = await goodsClientManager.get()[':id']['children'].$get({
           param: { id: goodsId },
           query: { page: 1, pageSize: 100 }
         });
@@ -107,6 +123,16 @@ export const GoodsParentChildPanel: React.FC<GoodsParentChildPanelProps> = ({
         }
         return { data: [], total: 0 };
       } catch (error) {
+        console.debug('获取子商品列表失败详情:', {
+          goodsId,
+          tenantId,
+          error: error instanceof Error ? error.message : String(error),
+          client: goodsClientManager.get(),
+          clientIdRoute: goodsClientManager.get()[':id'],
+          clientHasChildren: 'children' in goodsClientManager.get()[':id'],
+          clientKeys: Object.keys(goodsClientManager.get()[':id']),
+          routePath: `GET /goods/:id/children`
+        });
         console.error('获取子商品列表失败:', error);
         return { data: [], total: 0 };
       }
@@ -151,7 +177,7 @@ export const GoodsParentChildPanel: React.FC<GoodsParentChildPanelProps> = ({
   const removeParentMutation = useMutation({
     mutationFn: async () => {
       if (!goodsId) throw new Error('商品ID不能为空');
-      const res = await goodsClientManager.get()[':id'].parent.$delete({
+      const res = await goodsClientManager.get()[':id']['parent'].$delete({
         param: { id: goodsId }
       });
       if (res.status !== 200) throw new Error('解除父子关系失败');
@@ -194,7 +220,7 @@ export const GoodsParentChildPanel: React.FC<GoodsParentChildPanelProps> = ({
         if (detailRes.status !== 200) {
           throw new Error('获取商品详情失败');
         }
-        const goodsDetail = await detailRes.json();
+        const goodsDetail = await detailRes.json() as unknown as GoodsMt;
 
         // 验证必须是子商品
         if (!goodsDetail.spuId || goodsDetail.spuId <= 0) {
@@ -223,7 +249,7 @@ export const GoodsParentChildPanel: React.FC<GoodsParentChildPanelProps> = ({
     onSuccess: () => {
       toast.success('子商品删除成功');
       setIsDeleteChildDialogOpen(false);
-      setDeletingChildId(null);
+      setDeletingChildId(undefined);
       onUpdate?.();
       // 使子商品列表查询失效,强制刷新
       if (goodsId) {

+ 4 - 2
packages/goods-module-mt/src/routes/admin-goods-parent-child.mt.ts

@@ -293,7 +293,8 @@ const app = new OpenAPIHono<AuthContext>()
       .leftJoinAndSelect('goods.category3', 'category3')
       .leftJoinAndSelect('goods.supplier', 'supplier')
       .leftJoinAndSelect('goods.merchant', 'merchant')
-      .leftJoinAndSelect('goods.imageFile', 'imageFile');
+      .leftJoinAndSelect('goods.imageFile', 'imageFile')
+      .leftJoinAndSelect('imageFile.uploadUser', 'imageFile_uploadUser');
 
     // 分页
     const skip = (page - 1) * pageSize;
@@ -478,7 +479,7 @@ const app = new OpenAPIHono<AuthContext>()
     // 获取父商品
     const parentGoods = await queryRunner.manager.findOne(GoodsMt, {
       where: { id: parentGoodsId, tenantId } as any,
-      select: ['id', 'name', 'categoryId1', 'categoryId2', 'categoryId3', 'goodsType', 'supplierId', 'merchantId', 'spuId']
+      select: ['id', 'name', 'categoryId1', 'categoryId2', 'categoryId3', 'goodsType', 'supplierId', 'merchantId', 'spuId', 'imageFileId']
     });
 
     if (!parentGoods) {
@@ -525,6 +526,7 @@ const app = new OpenAPIHono<AuthContext>()
       childGoods.goodsType = parentGoods.goodsType;
       childGoods.supplierId = parentGoods.supplierId;
       childGoods.merchantId = parentGoods.merchantId;
+      childGoods.imageFileId = parentGoods.imageFileId;
 
       // 用户跟踪
       childGoods.createdBy = userId;

+ 1 - 1
packages/orders-module/src/routes/admin/orders.ts

@@ -12,7 +12,7 @@ const adminOrderRoutes = createCrudRoutes({
   getSchema: OrderSchema,
   listSchema: OrderSchema,
   searchFields: ['orderNo', 'userPhone', 'recevierName'],
-  relations: ['user', 'merchant', 'supplier', 'deliveryAddress'],
+  relations: ['user', 'merchant', 'supplier', 'deliveryAddress', 'orderGoods.imageFile'],
   middleware: [authMiddleware],
   userTracking: {
     createdByField: 'createdBy',

+ 1 - 1
packages/orders-module/src/routes/user/orders.ts

@@ -12,7 +12,7 @@ const userOrderRoutes = createCrudRoutes({
   getSchema: OrderSchema,
   listSchema: OrderSchema,
   searchFields: ['orderNo', 'userPhone', 'recevierName'],
-  relations: ['user', 'merchant', 'supplier', 'deliveryAddress'],
+  relations: ['user', 'merchant', 'supplier', 'deliveryAddress', 'orderGoods.imageFile'],
   middleware: [authMiddleware],
   userTracking: {
     createdByField: 'createdBy',