Browse Source

✨ feat(address): 优化地址管理和订单流程

- 移除城市选择器组件,简化地址编辑界面
- 为地址字段设置默认值,避免空值错误
- 优化地址管理页面交互,改进侧滑操作体验
- 添加设置默认地址功能,支持乐观更新和错误回滚
- 改进订单提交页面的地址选择逻辑,使用本地存储传递数据

📦 build(order): 扩展订单发货功能

- 新增订单发货相关路由和API客户端
- 添加发货信息字段到订单实体
- 实现管理员发货界面,支持多种发货方式
- 完善发货表单验证和状态管理

♻️ refactor(storage): 优化数据存储管理

- 清理购物车和立即购买的临时存储数据
- 使用事件监听机制处理页面间数据传递
- 改进地址选择后的导航流程
yourname 1 month ago
parent
commit
38040a35fa

+ 26 - 21
mini/src/pages/address-edit/index.tsx

@@ -14,16 +14,16 @@ import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from '
 import { Input } from '@/components/ui/input'
 import { Switch } from '@/components/ui/switch'
 import { useAuth } from '@/utils/auth'
-import { CitySelector } from '@/components/ui/city-selector'
+// import { CitySelector } from '@/components/ui/city-selector'
 
 
 const addressSchema = z.object({
   name: z.string().min(1, '请输入收货人姓名'),
   phone: z.string().regex(/^1[3-9]\d{9}$/, '请输入正确的手机号'),
-  province: z.number().positive('请选择省份'),
-  city: z.number().positive('请选择城市'),
-  district: z.number().positive('请选择区县'),
-  town: z.number().optional(),
+  province: z.number().default(1),
+  city: z.number().default(2),
+  district: z.number().default(3),
+  town: z.number().default(4),
   address: z.string().min(1, '请输入详细地址'),
   isDefault: z.boolean().optional()
 })
@@ -61,10 +61,10 @@ export default function AddressEditPage() {
     defaultValues: {
       name: '',
       phone: '',
-      province: 0,
-      city: 0,
-      district: 0,
-      town: 0,
+      province: 1,
+      city: 2,
+      district: 3,
+      town: 4,
       address: '',
       isDefault: false
     }
@@ -76,10 +76,10 @@ export default function AddressEditPage() {
       form.reset({
         name: address.name,
         phone: address.phone,
-        province: address.receiverProvince,
-        city: address.receiverCity,
-        district: address.receiverDistrict,
-        town: address.receiverTown || 0,
+        province: address.receiverProvince || 1,
+        city: address.receiverCity || 2,
+        district: address.receiverDistrict || 3,
+        town: address.receiverTown || 4,
         address: address.address,
         isDefault: address.isDefault === 1
       })
@@ -92,21 +92,26 @@ export default function AddressEditPage() {
       const addressData: any = {
         name: data.name,
         phone: data.phone,
-        receiverProvince: data.province,
-        receiverCity: data.city,
-        receiverDistrict: data.district,
-        receiverTown: data.town || 0,
+        receiverProvince: data.province||1,
+        receiverCity: data.city||2,
+        receiverDistrict: data.district||3,
+        receiverTown: data.town || 4,
         address: data.address,
-        userId: user?.id,
         isDefault: data.isDefault ? 1 : 0
       }
 
       let response
+
+      console.log("addressId:",addressId)
+      console.log("addressData:",addressData)
+
       if (addressId) {
-        response = await deliveryAddressClient[':id']['$put']({
+        response = await deliveryAddressClient[':id'].$put({
           param: { id: addressId },
           json: addressData
         })
+        console.log("response:",response)
+        return response.json()
       } else {
         response = await deliveryAddressClient.$post({ json: addressData })
       }
@@ -177,7 +182,7 @@ export default function AddressEditPage() {
                     )}
                   />
 
-                  <View className="mb-4">
+                  {/* <View className="mb-4">
                     <Text className="text-sm font-medium text-gray-700 mb-2">所在地区</Text>
                     <CitySelector
                       provinceValue={form.watch('province')}
@@ -190,7 +195,7 @@ export default function AddressEditPage() {
                       onTownChange={(value) => form.setValue('town', value)}
                       showLabels={false}
                     />
-                  </View>
+                  </View> */}
 
                   <FormField
                     name="address"

+ 74 - 38
mini/src/pages/address-manage/index.tsx

@@ -1,9 +1,9 @@
 import { View, ScrollView, Text } from '@tarojs/components'
 import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
-import { useState, useRef } from 'react'
+import { useState } from 'react'
 import Taro from '@tarojs/taro'
 import { deliveryAddressClient } from '@/api'
-import { InferResponseType, InferRequestType } from 'hono'
+import { InferResponseType } from 'hono'
 import { Navbar } from '@/components/ui/navbar'
 import { Button } from '@/components/ui/button'
 import { useAuth } from '@/utils/auth'
@@ -11,13 +11,10 @@ import './index.css'
 
 type AddressResponse = InferResponseType<typeof deliveryAddressClient.$get, 200>
 type Address = AddressResponse['data'][0]
-type CreateAddressRequest = InferRequestType<typeof deliveryAddressClient.$post>['json']
-type UpdateAddressRequest = InferRequestType<typeof deliveryAddressClient[':id']['$put']>['json']
 
 export default function AddressManagePage() {
   const { user } = useAuth()
   const queryClient = useQueryClient()
-  const [selectedAddressId, setSelectedAddressId] = useState<number | null>(null)
   const [swipeStates, setSwipeStates] = useState<Record<number, boolean>>({})
 
   // 获取地址列表
@@ -49,7 +46,8 @@ export default function AddressManagePage() {
       if (response.status !== 204) {
         throw new Error('删除地址失败')
       }
-      return response.json()
+      // 204 响应没有内容,不需要调用 response.json()
+      return null
     },
     onSuccess: () => {
       queryClient.invalidateQueries({ queryKey: ['delivery-addresses'] })
@@ -78,12 +76,50 @@ export default function AddressManagePage() {
       }
       return response.json()
     },
+    onMutate: async (id: number) => {
+      // 取消之前的查询,避免冲突
+      await queryClient.cancelQueries({ queryKey: ['delivery-addresses', user?.id] })
+
+      // 保存之前的快照
+      const previousAddresses = queryClient.getQueryData(['delivery-addresses', user?.id])
+
+      // 乐观更新:将所有地址设为非默认,将指定地址设为默认
+      queryClient.setQueryData(['delivery-addresses', user?.id], (old: any) => {
+        if (!old?.data) return old
+
+        return {
+          ...old,
+          data: old.data.map((address: Address) => ({
+            ...address,
+            isDefault: address.id === id ? 1 : 0
+          }))
+        }
+      })
+
+      return { previousAddresses }
+    },
+    onError: (error, id, context) => {
+      // 出错时回滚到之前的状态
+      if (context?.previousAddresses) {
+        queryClient.setQueryData(['delivery-addresses', user?.id], context.previousAddresses)
+      }
+      Taro.showToast({
+        title: error.message || '设置失败',
+        icon: 'none'
+      })
+    },
     onSuccess: () => {
+      // 成功后重新获取数据确保一致性
       queryClient.invalidateQueries({ queryKey: ['delivery-addresses'] })
       Taro.showToast({
         title: '设置成功',
         icon: 'success'
       })
+      
+      Taro.navigateTo({
+        url: '/pages/order-submit/index'
+      })
+  
     }
   })
 
@@ -104,17 +140,10 @@ export default function AddressManagePage() {
   // 选择地址并返回
   const handleSelectAddress = (address: Address) => {
     closeAllSwipes()
-    // 检查是否是选择模式
-    const pages = Taro.getCurrentPages()
-    const prevPage = pages[pages.length - 2]
-
-    if (prevPage && prevPage.route === 'pages/order-submit/index') {
-      // 返回订单页面
-      prevPage.setData({
-        selectedAddress: address
-      })
-      Taro.navigateBack()
-    }
+    Taro.setStorageSync('selectedAddressData', address)
+    console.log("selectedAddressData:", address)
+    Taro.navigateBack()
+     
   }
 
   // 检查是否是选择模式
@@ -124,12 +153,10 @@ export default function AddressManagePage() {
     return prevPage && prevPage.route === 'pages/order-submit/index'
   }
 
-  // 侧滑切换
-  const handleSwipeToggle = (id: number) => {
-    setSwipeStates(prev => ({
-      ...prev,
-      [id]: !prev[id]
-    }))
+
+  // 打开侧滑
+  const openSwipe = (id: number) => {
+    setSwipeStates({ [id]: true })
   }
 
   // 关闭所有侧滑
@@ -139,6 +166,7 @@ export default function AddressManagePage() {
 
   // 删除地址
   const handleDeleteAddress = (id: number) => {
+    console.log("id:",id)
     closeAllSwipes()
     Taro.showModal({
       title: '删除地址',
@@ -173,6 +201,7 @@ export default function AddressManagePage() {
                   <View
                     className="swipe-cell"
                     onClick={() => closeAllSwipes()}
+                    onTouchStart={() => openSwipe(address.id)}
                   >
                     <View
                       className="swipe-content"
@@ -189,24 +218,29 @@ export default function AddressManagePage() {
                           )}
                         </View>
 
-                        <Text className="address-detail">
-                          {address.province?.name || ''}
+                          {/* {address.province?.name || ''}
                           {address.city?.name || ''}
                           {address.district?.name || ''}
-                          {address.town?.name || ''}
+                          {address.town?.name || ''} */}
+
+                        <Text className="address-detail">
+                          
                           {address.address}
                         </Text>
 
                         <View className="address-actions">
-                          {isSelectMode() ? (
-                            <Button
-                              size="sm"
-                              className="select-btn"
-                              onClick={() => handleSelectAddress(address)}
-                            >
-                              选择
-                            </Button>
-                          ) : (
+                          {
+                          // isSelectMode() ? (
+                          //   <Button
+                          //     size="sm"
+                          //     className="select-btn"
+                          //     onClick={() => handleSelectAddress(address)}
+                          //   >
+                          //     选择
+                          //   </Button>
+                          // ) : 
+                          
+                         // (
                             <>
                               <View className="action-buttons">
                                 {address.isDefault !== 1 && (
@@ -230,15 +264,17 @@ export default function AddressManagePage() {
                                 </Button>
                               </View>
 
-                              <Button
+                              {/* <Button
                                 size="sm"
                                 className="select-btn"
                                 onClick={() => handleSelectAddress(address)}
                               >
                                 选择
-                              </Button>
+                              </Button> */}
+
                             </>
-                          )}
+                         // )
+                          }
                         </View>
                       </View>
                     </View>

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

@@ -57,6 +57,7 @@ export default function CartPage() {
 
     const checkoutItems = cart.items.filter(item => selectedItems.includes(item.id))
 
+    Taro.removeStorageSync('checkoutItems')
     // 存储选中的商品信息
     Taro.setStorageSync('checkoutItems', {
       items: checkoutItems,

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

@@ -185,6 +185,7 @@ export default function GoodsDetailPage() {
       return
     }
 
+    Taro.removeStorageSync('buyNow')
     // 将商品信息存入临时存储,跳转到订单确认页
     Taro.setStorageSync('buyNow', {
       goods: {

+ 46 - 10
mini/src/pages/order-submit/index.tsx

@@ -1,6 +1,6 @@
 import { View, ScrollView, Text, Textarea } from '@tarojs/components'
 import { useQuery, useMutation } from '@tanstack/react-query'
-import { useState, useEffect } from 'react'
+import React, { useState, useEffect } from 'react'
 import Taro from '@tarojs/taro'
 import { deliveryAddressClient, orderClient } from '@/api'
 import { InferResponseType, InferRequestType } from 'hono'
@@ -29,6 +29,7 @@ export default function OrderSubmitPage() {
   const [orderItems, setOrderItems] = useState<CheckoutItem[]>([])
   const [totalAmount, setTotalAmount] = useState(0)
   const [remark, setRemark] = useState("")
+  const [isUserSelected, setIsUserSelected] = useState(false) 
 
   // 获取地址列表
   const { data: addresses } = useQuery({
@@ -95,6 +96,36 @@ export default function OrderSubmitPage() {
     }
   })
 
+  // 页面显示时检查是否有新选择的地址
+  useEffect(() => {
+    const handlePageShow = () => {
+      console.log('pageShow 事件触发')
+      const storedAddress = Taro.getStorageSync('selectedAddressData')
+      console.log("get selectedAddressData:", storedAddress);
+      if (storedAddress) {
+        setSelectedAddress(storedAddress)
+        setIsUserSelected(true) // 标记为用户手动选择
+        Taro.removeStorageSync('selectedAddressData')
+        console.log("selectedAddress00:", storedAddress)
+      }
+    }
+
+    // 监听页面显示事件
+    Taro.eventCenter.on('pageShow', handlePageShow)
+
+    // 组件挂载时也检查一次存储
+    const initialAddress = Taro.getStorageSync('selectedAddressData')
+    if (initialAddress) {
+      setSelectedAddress(initialAddress)
+      setIsUserSelected(true) // 标记为用户手动选择
+      Taro.removeStorageSync('selectedAddressData')
+    }
+
+    return () => {
+      Taro.eventCenter.off('pageShow', handlePageShow)
+    }
+  }, [])
+
   // 页面加载时获取订单数据 - 只执行一次
   useEffect(() => {
     // 从立即购买获取数据
@@ -118,15 +149,14 @@ export default function OrderSubmitPage() {
       }])
       setTotalAmount(buyNowData.totalAmount)
       // 清除立即购买数据,避免下次进入时重复使用
-      Taro.removeStorageSync('buyNow')
+      
     }
     else if(checkoutData&&checkoutData.items)
     {
       // 从购物车获取数据
         setOrderItems(checkoutData.items)
         setTotalAmount(checkoutData.totalAmount)
-
-        Taro.removeStorageSync('checkoutItems')
+       
       }
        else if (cartData && cartData.items) {
         // 使用购物车数据
@@ -136,7 +166,7 @@ export default function OrderSubmitPage() {
         setOrderItems(items)
         setTotalAmount(total)
 
-        Taro.removeStorageSync('mini_cart')
+        // Taro.removeStorageSync('mini_cart')
   }
     else
     {
@@ -146,13 +176,15 @@ export default function OrderSubmitPage() {
      }
   }, []) // 空依赖数组,确保只执行一次
 
-  // 设置默认地址 - 单独处理地址数据变化
+  // 设置默认地址 - 只在页面初始加载且没有手动选择地址时设置
   useEffect(() => {
+    // 只在页面首次加载且没有手动选择地址且不是用户手动选择时设置默认地址
     if (addresses?.data) {
       const defaultAddress = addresses.data.find(addr => addr.isDefault === 1)
       setSelectedAddress(defaultAddress || addresses.data[0] || null)
     }
-  }, [addresses])
+  }, [addresses]) // 只在地址数据变化时设置默认地址,且只在没有选择地址且不是用户手动选择时
+
 
   // 选择地址
   const handleSelectAddress = () => {
@@ -179,6 +211,8 @@ export default function OrderSubmitPage() {
     createOrderMutation.mutate()
   }
 
+  console.log("渲染时 selectedAddress:", selectedAddress)
+
   return (
     <View className="order-sure">
       <Navbar
@@ -193,11 +227,13 @@ export default function OrderSubmitPage() {
           {selectedAddress ? (
             <View className="order-address" onClick={handleSelectAddress}>
               <View className="address-content">
-                <Text className="detail">
-                  {selectedAddress.province?.name || ''}
+
+              {/* {selectedAddress.province?.name || ''}
                   {selectedAddress.city?.name || ''}
                   {selectedAddress.district?.name || ''}
-                  {selectedAddress.town?.name || ''}
+                  {selectedAddress.town?.name || ''} */}
+
+                <Text className="detail">
                   {selectedAddress.address}
                 </Text>
                 <View className="info">

+ 3 - 0
mini/tests/unit/pages/address-manage/basic.test.tsx

@@ -14,6 +14,9 @@ jest.mock('@/api', () => ({
     ':id': {
       $put: jest.fn(),
       $delete: jest.fn(),
+      'set-default': {
+        $post: jest.fn(),
+      },
     },
   },
 }))

+ 98 - 0
packages/delivery-address-module-mt/src/routes/user-custom.routes.mt.ts

@@ -122,6 +122,50 @@ const deleteDeliveryAddressRoute = createRoute({
   }
 });
 
+// 设置默认地址路由
+const setDefaultAddressRoute = createRoute({
+  method: 'post',
+  path: '/{id}/set-default',
+  middleware: [authMiddleware],
+  request: {
+    params: z.object({
+      id: z.coerce.number().openapi({
+        param: { name: 'id', in: 'path' },
+        example: 1,
+        description: '配送地址ID'
+      })
+    })
+  },
+  responses: {
+    200: {
+      description: '设置默认地址成功',
+      content: {
+        'application/json': { schema: UserDeliveryAddressSchema }
+      }
+    },
+    400: {
+      description: '参数错误',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    401: {
+      description: '认证失败',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    403: {
+      description: '权限不足',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    404: {
+      description: '配送地址不存在',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    500: {
+      description: '设置默认地址失败',
+      content: { 'application/json': { schema: ErrorSchema } }
+    }
+  }
+});
+
 const app = new OpenAPIHono<AuthContext>()
   .openapi(createDeliveryAddressRoute, async (c) => {
     try {
@@ -259,6 +303,60 @@ const app = new OpenAPIHono<AuthContext>()
         message: error instanceof Error ? error.message : '删除配送地址失败'
       }, 500);
     }
+  })
+  .openapi(setDefaultAddressRoute, async (c) => {
+    try {
+      const { id } = c.req.valid('param');
+      const user = c.get('user');
+      const areaService = new AreaServiceMt(AppDataSource);
+      const deliveryAddressService = new DeliveryAddressServiceMt(AppDataSource, areaService);
+
+      console.debug('用户设置默认地址 - 用户ID:', user.id, '地址ID:', id, '租户ID:', user.tenantId);
+
+      // 先检查地址是否存在且属于当前租户和用户
+      const existingAddress = await deliveryAddressService.repository.findOne({
+        where: {
+          id,
+          tenantId: user.tenantId
+        }
+      });
+
+      if (!existingAddress) {
+        return c.json({ code: 404, message: '资源不存在' }, 404);
+      }
+
+      // 检查数据权限 - 用户只能设置自己的地址为默认
+      if (existingAddress.userId !== user.id) {
+        console.debug('权限检查失败: 地址用户ID', existingAddress.userId, '当前用户ID', user.id);
+        return c.json({ code: 403, message: '权限不足,无法设置其他用户的地址为默认' }, 403);
+      }
+
+      console.debug('权限检查通过,开始设置默认地址');
+
+      // 使用服务层的设置默认地址方法
+      const success = await deliveryAddressService.setDefault(id, user.id, user.tenantId);
+
+      if (!success) {
+        return c.json({ code: 404, message: '配送地址不存在' }, 404);
+      }
+
+      // 获取更新后的地址信息
+      const updatedAddress = await deliveryAddressService.repository.findOne({
+        where: { id, tenantId: user.tenantId },
+        relations: ['user', 'province', 'city', 'district', 'town']
+      });
+      if (!updatedAddress) {
+        return c.json({ code: 404, message: '配送地址不存在' }, 404);
+      }
+
+      return c.json(await parseWithAwait(UserDeliveryAddressSchema, updatedAddress), 200);
+    } catch (error) {
+      console.error('设置默认地址失败:', error);
+      return c.json({
+        code: 500,
+        message: error instanceof Error ? error.message : '设置默认地址失败'
+      }, 500);
+    }
   });
 
 export default app;

+ 1 - 1
packages/delivery-address-module-mt/src/routes/user-routes.mt.ts

@@ -27,7 +27,7 @@ const userCrudRoutes = createCrudRoutes({
     enabled: true,
     tenantIdField: 'tenantId'
   },
-  readOnly: true, // 创建/更新/删除使用自定义路由
+  readOnly: true, // 禁用通用CRUD路由的更新操作,强制使用自定义路由
   // 配置数据权限控制,确保用户只能操作自己的数据
   dataPermission: {
     enabled: true,

+ 24 - 1
packages/delivery-address-module-mt/src/services/delivery-address.mt.service.ts

@@ -203,7 +203,30 @@ export class DeliveryAddressServiceMt extends GenericCrudService<DeliveryAddress
       return null;
     }
 
-    // 更新地址
+    // 如果设置默认地址,需要处理其他地址
+    if (data.isDefault === 1 && existingAddress.userId) {
+      await this.repository.manager.transaction(async (transactionalEntityManager) => {
+        // 先将该用户的所有地址设为非默认
+        await transactionalEntityManager.update(DeliveryAddressMt,
+          { userId: existingAddress.userId, tenantId },
+          { isDefault: 0 }
+        );
+
+        // 将指定地址设为默认
+        await transactionalEntityManager.update(DeliveryAddressMt,
+          { id, userId: existingAddress.userId, tenantId },
+          { isDefault: 1 }
+        );
+      });
+
+      // 重新获取更新后的地址
+      return await this.repository.findOne({
+        where: { id, tenantId },
+        relations: ['user', 'province', 'city', 'district', 'town']
+      });
+    }
+
+    // 普通更新
     Object.assign(existingAddress, data);
     return await this.repository.save(existingAddress);
   }

+ 4 - 0
packages/delivery-address-module/src/routes/index.ts

@@ -1,5 +1,6 @@
 import userDeliveryAddressRoutes from './user-routes';
 import adminDeliveryAddressRoutes from './admin-routes';
+import userCustomRoutes from './user-custom.routes';
 
 // 导出用户路由集合 - 仅限当前用户使用
 export { default as userDeliveryAddressRoutes } from './user-routes';
@@ -7,5 +8,8 @@ export { default as userDeliveryAddressRoutes } from './user-routes';
 // 导出管理员路由集合 - 完整权限
 export { default as adminDeliveryAddressRoutes } from './admin-routes';
 
+// 导出用户自定义路由
+export { default as userCustomRoutes } from './user-custom.routes';
+
 // 默认导出用户路由,保持向后兼容性
 export default userDeliveryAddressRoutes;

+ 96 - 0
packages/delivery-address-module/src/routes/user-custom.routes.ts

@@ -0,0 +1,96 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { z } from '@hono/zod-openapi';
+import { DeliveryAddressService } from '../services/delivery-address.service';
+import { AreaService } from '@d8d/geo-areas';
+import { AppDataSource, ErrorSchema } from '@d8d/shared-utils';
+import { authMiddleware } from '@d8d/auth-module';
+import { AuthContext } from '@d8d/shared-types';
+import { UserDeliveryAddressSchema } from '../schemas/user-delivery-address.schema';
+import { parseWithAwait } from '@d8d/shared-utils';
+
+// 设置默认地址路由
+const setDefaultAddressRoute = createRoute({
+  method: 'post',
+  path: '/{id}/set-default',
+  middleware: [authMiddleware],
+  request: {
+    params: z.object({
+      id: z.coerce.number().openapi({
+        param: { name: 'id', in: 'path' },
+        example: 1,
+        description: '配送地址ID'
+      })
+    })
+  },
+  responses: {
+    200: {
+      description: '设置默认地址成功',
+      content: {
+        'application/json': { schema: UserDeliveryAddressSchema }
+      }
+    },
+    400: {
+      description: '参数错误',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    401: {
+      description: '认证失败',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    404: {
+      description: '配送地址不存在',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    500: {
+      description: '设置默认地址失败',
+      content: { 'application/json': { schema: ErrorSchema } }
+    }
+  }
+});
+
+const app = new OpenAPIHono<AuthContext>()
+  .openapi(setDefaultAddressRoute, async (c) => {
+    try {
+      const { id } = c.req.valid('param');
+      const userId = c.get('user')?.id;
+
+      if (!userId) {
+        return c.json({
+          code: 401,
+          message: '用户未认证'
+        }, 401);
+      }
+
+      const areaService = new AreaService(AppDataSource);
+      const deliveryAddressService = new DeliveryAddressService(AppDataSource, areaService);
+
+      // 使用服务层的设置默认地址方法
+      const success = await deliveryAddressService.setDefault(id, userId);
+
+      if (!success) {
+        return c.json({
+          code: 404,
+          message: '配送地址不存在'
+        }, 404);
+      }
+
+      // 获取更新后的地址信息
+      const updatedAddress = await deliveryAddressService.findOne(id);
+      if (!updatedAddress) {
+        return c.json({
+          code: 404,
+          message: '配送地址不存在'
+        }, 404);
+      }
+
+      return c.json(await parseWithAwait(UserDeliveryAddressSchema, updatedAddress), 200);
+    } catch (error) {
+      console.error('设置默认地址失败:', error);
+      return c.json({
+        code: 500,
+        message: error instanceof Error ? error.message : '设置默认地址失败'
+      }, 500);
+    }
+  });
+
+export default app;

+ 18 - 1
packages/order-management-ui-mt/src/api/orderClient.ts

@@ -1,4 +1,4 @@
-import { adminOrderRoutes, adminOrderItemsRoutes, adminRefundsRoutes } from '@d8d/orders-module-mt';
+import { adminOrderRoutes, adminOrderItemsRoutes, adminRefundsRoutes, adminDeliveryRoutes } from '@d8d/orders-module-mt';
 import { rpcClient } from '@d8d/shared-ui-components/utils/hc'
 
 class OrderClientManager {
@@ -6,6 +6,7 @@ class OrderClientManager {
   private adminOrderClient: ReturnType<typeof rpcClient<typeof adminOrderRoutes>> | null = null;
   private adminOrderItemsClient: ReturnType<typeof rpcClient<typeof adminOrderItemsRoutes>> | null = null;
   private adminRefundsClient: ReturnType<typeof rpcClient<typeof adminRefundsRoutes>> | null = null;
+  private adminDeliveryClient: ReturnType<typeof rpcClient<typeof adminDeliveryRoutes>> | null = null;
 
   private constructor() {}
 
@@ -31,6 +32,11 @@ class OrderClientManager {
     return this.adminRefundsClient = rpcClient<typeof adminRefundsRoutes>(baseUrl);
   }
 
+  // 初始化管理员发货客户端
+  public initAdminDeliveryClient(baseUrl: string = '/'): ReturnType<typeof rpcClient<typeof adminDeliveryRoutes>> {
+    return this.adminDeliveryClient = rpcClient<typeof adminDeliveryRoutes>(baseUrl);
+  }
+
   // 获取管理员订单客户端实例
   public getAdminOrderClient(): ReturnType<typeof rpcClient<typeof adminOrderRoutes>> {
     if (!this.adminOrderClient) {
@@ -55,11 +61,20 @@ class OrderClientManager {
     return this.adminRefundsClient;
   }
 
+  // 获取管理员发货客户端实例
+  public getAdminDeliveryClient(): ReturnType<typeof rpcClient<typeof adminDeliveryRoutes>> {
+    if (!this.adminDeliveryClient) {
+      return this.initAdminDeliveryClient()
+    }
+    return this.adminDeliveryClient;
+  }
+
   // 通用初始化方法 - 初始化所有管理员客户端
   public init(baseUrl: string = '/'): void {
     this.initAdminOrderClient(baseUrl);
     this.initAdminOrderItemsClient(baseUrl);
     this.initAdminRefundsClient(baseUrl);
+    this.initAdminDeliveryClient(baseUrl);
   }
 
   // 重置所有客户端(用于测试或重新初始化)
@@ -67,6 +82,7 @@ class OrderClientManager {
     this.adminOrderClient = null;
     this.adminOrderItemsClient = null;
     this.adminRefundsClient = null;
+    this.adminDeliveryClient = null;
   }
 }
 
@@ -77,6 +93,7 @@ const orderClientManager = OrderClientManager.getInstance();
 export const adminOrderClient = orderClientManager.getAdminOrderClient()
 export const adminOrderItemsClient = orderClientManager.getAdminOrderItemsClient()
 export const adminRefundsClient = orderClientManager.getAdminRefundsClient()
+export const adminDeliveryClient = orderClientManager.getAdminDeliveryClient()
 
 export {
   orderClientManager

+ 205 - 9
packages/order-management-ui-mt/src/components/OrderManagement.tsx

@@ -4,7 +4,7 @@ import { useForm } from 'react-hook-form';
 import { zodResolver } from '@hookform/resolvers/zod';
 import { format } from 'date-fns';
 import { toast } from 'sonner';
-import { Search, Edit, Eye, Package } from 'lucide-react';
+import { Search, Edit, Eye, Package, Truck } from 'lucide-react';
 
 // 使用共享UI组件包的具体路径导入
 import { Button } from '@d8d/shared-ui-components/components/ui/button';
@@ -62,12 +62,14 @@ const DataTablePagination = ({
   );
 };
 import { adminOrderClient, orderClientManager } from '../api';
-import type { InferRequestType, InferResponseType } from 'hono/client';
+import type { InferResponseType } from 'hono/client';
 import { UpdateOrderDto } from '@d8d/orders-module-mt/schemas';
 
 // 类型定义
 type OrderResponse = InferResponseType<typeof adminOrderClient.index.$get, 200>['data'][0];
-type UpdateRequest = InferRequestType<typeof adminOrderClient[':id']['$put']>['json'];
+type UpdateRequest = any;
+type DeliveryRequest = any;
+type DeliveryResponse = any;
 
 // 状态映射
 const orderStatusMap = {
@@ -91,6 +93,14 @@ const orderTypeMap = {
   2: { label: '虚拟订单', color: 'secondary' },
 } as const;
 
+const deliveryTypeMap = {
+  0: { label: '未发货', color: 'warning' },
+  1: { label: '物流快递', color: 'info' },
+  2: { label: '同城配送', color: 'info' },
+  3: { label: '用户自提', color: 'info' },
+  4: { label: '虚拟发货', color: 'info' },
+} as const;
+
 export const OrderManagement = () => {
   const [searchParams, setSearchParams] = useState({
     page: 1,
@@ -103,6 +113,8 @@ export const OrderManagement = () => {
   const [editingOrder, setEditingOrder] = useState<OrderResponse | null>(null);
   const [detailModalOpen, setDetailModalOpen] = useState(false);
   const [selectedOrder, setSelectedOrder] = useState<OrderResponse | null>(null);
+  const [deliveryModalOpen, setDeliveryModalOpen] = useState(false);
+  const [deliveringOrder, setDeliveringOrder] = useState<OrderResponse | null>(null);
 
   // 表单实例
   const updateForm = useForm<UpdateRequest>({
@@ -110,6 +122,16 @@ export const OrderManagement = () => {
     defaultValues: {},
   });
 
+  // 发货表单实例
+  const deliveryForm = useForm<DeliveryRequest>({
+    defaultValues: {
+      deliveryType: 1,
+      deliveryCompany: '',
+      deliveryNo: '',
+      deliveryRemark: '',
+    },
+  });
+
   // 数据查询 - 60秒自动刷新
   const { data, isLoading, refetch } = useQuery({
     queryKey: ['orders', searchParams],
@@ -162,12 +184,49 @@ export const OrderManagement = () => {
     setDetailModalOpen(true);
   };
 
+  // 处理发货
+  const handleDeliveryOrder = (order: OrderResponse) => {
+    setDeliveringOrder(order);
+    deliveryForm.reset({
+      deliveryType: 1,
+      deliveryCompany: '',
+      deliveryNo: '',
+      deliveryRemark: '',
+    });
+    setDeliveryModalOpen(true);
+  };
+
+  // 处理发货提交
+  const handleDeliverySubmit = async (data: DeliveryRequest) => {
+    if (!deliveringOrder || !deliveringOrder.id) return;
+
+    try {
+      const res = await (orderClientManager.getAdminDeliveryClient() as any)[':id']['delivery']['$post']({
+        param: { id: deliveringOrder.id },
+        json: data,
+      });
+
+      if (res.status === 200) {
+        const result = await res.json() as DeliveryResponse;
+        toast.success(result.message || '发货成功');
+        setDeliveryModalOpen(false);
+        refetch();
+      } else {
+        const error = await res.json();
+        toast.error(error.message || '发货失败');
+      }
+    } catch (error) {
+      console.error('发货失败:', error);
+      toast.error('发货失败,请重试');
+    }
+  };
+
   // 处理更新订单
   const handleUpdateSubmit = async (data: UpdateRequest) => {
     if (!editingOrder || !editingOrder.id) return;
 
     try {
-      const res = await orderClientManager.getAdminOrderClient()[':id']['$put']({
+      const res = await (orderClientManager.getAdminOrderClient() as any)[':id']['$put']({
         param: { id: editingOrder.id },
         json: data,
       });
@@ -192,8 +251,8 @@ export const OrderManagement = () => {
   };
 
   // 获取状态颜色
-  const getStatusBadge = (status: number, type: 'order' | 'pay') => {
-    const map = type === 'order' ? orderStatusMap : payStatusMap;
+  const getStatusBadge = (status: number, type: 'order' | 'pay' | 'delivery') => {
+    const map = type === 'order' ? orderStatusMap : type === 'pay' ? payStatusMap : deliveryTypeMap;
     const config = map[status as keyof typeof map] || { label: '未知', color: 'default' };
 
     return <Badge variant={config.color as any}>{config.label}</Badge>;
@@ -466,6 +525,16 @@ export const OrderManagement = () => {
                         >
                           <Edit className="h-4 w-4" />
                         </Button>
+                        {order.state === 0 && order.payState === 2 && (
+                          <Button
+                            variant="ghost"
+                            size="icon"
+                            onClick={() => handleDeliveryOrder(order)}
+                            data-testid="order-delivery-button"
+                          >
+                            <Truck className="h-4 w-4" />
+                          </Button>
+                        )}
                       </div>
                     </TableCell>
                   </TableRow>
@@ -702,9 +771,6 @@ export const OrderManagement = () => {
                             )}
                             <div>
                               <p className="font-medium text-sm">{item.goodsName}</p>
-                              {item.specification && (
-                                <p className="text-xs text-muted-foreground">{item.specification}</p>
-                              )}
                             </div>
                           </div>
                           <div className="col-span-2 text-center text-sm">
@@ -738,6 +804,136 @@ export const OrderManagement = () => {
           )}
         </DialogContent>
       </Dialog>
+
+      {/* 发货模态框 */}
+      <Dialog open={deliveryModalOpen} onOpenChange={setDeliveryModalOpen}>
+        <DialogContent className="sm:max-w-[500px] max-h-[90vh] overflow-y-auto">
+          <DialogHeader>
+            <DialogTitle>订单发货</DialogTitle>
+            <DialogDescription>选择发货方式并填写发货信息</DialogDescription>
+          </DialogHeader>
+
+          {deliveringOrder && (
+            <div className="space-y-4">
+              <div className="bg-muted p-3 rounded-md">
+                <h4 className="font-medium mb-2">订单信息</h4>
+                <div className="text-sm space-y-1">
+                  <div className="flex justify-between">
+                    <span className="text-muted-foreground">订单号:</span>
+                    <span>{deliveringOrder.orderNo}</span>
+                  </div>
+                  <div className="flex justify-between">
+                    <span className="text-muted-foreground">收货人:</span>
+                    <span>{deliveringOrder.recevierName || '-'}</span>
+                  </div>
+                  <div className="flex justify-between">
+                    <span className="text-muted-foreground">手机号:</span>
+                    <span>{deliveringOrder.receiverMobile || '-'}</span>
+                  </div>
+                  <div className="flex justify-between">
+                    <span className="text-muted-foreground">地址:</span>
+                    <span>{deliveringOrder.address || '-'}</span>
+                  </div>
+                </div>
+              </div>
+
+              <Form {...deliveryForm}>
+                <form onSubmit={deliveryForm.handleSubmit(handleDeliverySubmit)} className="space-y-4">
+                  <FormField
+                    control={deliveryForm.control}
+                    name="deliveryType"
+                    render={({ field }) => (
+                      <FormItem>
+                        <FormLabel>发货方式</FormLabel>
+                        <Select onValueChange={(value) => field.onChange(parseInt(value))} value={field.value?.toString()}>
+                          <FormControl>
+                            <SelectTrigger data-testid="delivery-type-select">
+                              <SelectValue placeholder="选择发货方式" />
+                            </SelectTrigger>
+                          </FormControl>
+                          <SelectContent>
+                            <SelectItem value="1">物流快递</SelectItem>
+                            <SelectItem value="2">同城配送</SelectItem>
+                            <SelectItem value="3">用户自提</SelectItem>
+                            <SelectItem value="4">虚拟发货</SelectItem>
+                          </SelectContent>
+                        </Select>
+                        <FormMessage />
+                      </FormItem>
+                    )}
+                  />
+
+                  {deliveryForm.watch('deliveryType') === 1 && (
+                    <>
+                      <FormField
+                        control={deliveryForm.control}
+                        name="deliveryCompany"
+                        render={({ field }) => (
+                          <FormItem>
+                            <FormLabel>快递公司</FormLabel>
+                            <FormControl>
+                              <Input
+                                placeholder="输入快递公司名称"
+                                data-testid="delivery-company-input"
+                                {...field}
+                              />
+                            </FormControl>
+                            <FormMessage />
+                          </FormItem>
+                        )}
+                      />
+
+                      <FormField
+                        control={deliveryForm.control}
+                        name="deliveryNo"
+                        render={({ field }) => (
+                          <FormItem>
+                            <FormLabel>快递单号</FormLabel>
+                            <FormControl>
+                              <Input
+                                placeholder="输入快递单号"
+                                data-testid="delivery-no-input"
+                                {...field}
+                              />
+                            </FormControl>
+                            <FormMessage />
+                          </FormItem>
+                        )}
+                      />
+                    </>
+                  )}
+
+                  <FormField
+                    control={deliveryForm.control}
+                    name="deliveryRemark"
+                    render={({ field }) => (
+                      <FormItem>
+                        <FormLabel>发货备注</FormLabel>
+                        <FormControl>
+                          <Textarea
+                            placeholder="输入发货备注信息..."
+                            className="resize-none"
+                            data-testid="delivery-remark-textarea"
+                            {...field}
+                          />
+                        </FormControl>
+                        <FormMessage />
+                      </FormItem>
+                    )}
+                  />
+
+                  <DialogFooter>
+                    <Button type="button" variant="outline" onClick={() => setDeliveryModalOpen(false)}>
+                      取消
+                    </Button>
+                    <Button type="submit" data-testid="delivery-submit-button">确认发货</Button>
+                  </DialogFooter>
+                </form>
+              </Form>
+            </div>
+          )}
+        </DialogContent>
+      </Dialog>
     </div>
   );
 };

+ 15 - 0
packages/orders-module-mt/src/entities/order.mt.entity.ts

@@ -124,6 +124,21 @@ export class OrderMt {
   @Column({ name: 'remark', type: 'varchar', length: 255, nullable: true, comment: '管理员备注信息' })
   remark!: string | null;
 
+  @Column({ name: 'delivery_type', type: 'int', default: 0, comment: '发货方式 0未发货 1物流快递 2同城配送 3用户自提 4虚拟发货' })
+  deliveryType!: number;
+
+  @Column({ name: 'delivery_company', type: 'varchar', length: 100, nullable: true, comment: '快递公司' })
+  deliveryCompany!: string | null;
+
+  @Column({ name: 'delivery_no', type: 'varchar', length: 100, nullable: true, comment: '快递单号' })
+  deliveryNo!: string | null;
+
+  @Column({ name: 'delivery_time', type: 'timestamp', nullable: true, comment: '发货时间' })
+  deliveryTime!: Date | null;
+
+  @Column({ name: 'delivery_remark', type: 'varchar', length: 500, nullable: true, comment: '发货备注' })
+  deliveryRemark!: string | null;
+
   @Column({ name: 'created_by', type: 'int', unsigned: true, nullable: true, comment: '创建人ID' })
   createdBy!: number | null;
 

+ 124 - 0
packages/orders-module-mt/src/routes/admin/delivery.mt.ts

@@ -0,0 +1,124 @@
+import { Hono } from 'hono';
+import { authMiddleware } from '@d8d/auth-module-mt';
+import { AppDataSource } from '@d8d/shared-utils';
+import { OrderMt } from '../../entities/order.mt.entity';
+import { DeliveryOrderDto } from '../../schemas/delivery.mt.schema';
+import { OrderStatus } from '../../schemas/order.mt.schema';
+
+const deliveryRoutes = new Hono();
+
+// 发货订单
+deliveryRoutes.post('/:id/delivery', authMiddleware, async (c) => {
+  const orderId = parseInt(c.req.param('id'));
+  const body = await c.req.json();
+
+  // 验证请求数据
+  const validatedData = DeliveryOrderDto.parse(body);
+
+  const orderRepository = AppDataSource.getRepository(OrderMt);
+
+  try {
+    // 查找订单
+    const order = await orderRepository.findOne({
+      where: { id: orderId }
+    });
+
+    if (!order) {
+      return c.json({
+        success: false,
+        message: '订单不存在'
+      }, 404);
+    }
+
+    // 检查订单状态是否允许发货
+    if (order.state !== OrderStatus.PENDING) {
+      return c.json({
+        success: false,
+        message: '订单状态不允许发货'
+      }, 400);
+    }
+
+    // 检查支付状态
+    if (order.payState !== 2) { // 支付成功
+      return c.json({
+        success: false,
+        message: '订单未支付成功,不能发货'
+      }, 400);
+    }
+
+    // 更新订单发货信息
+    order.state = OrderStatus.SHIPPED;
+    order.deliveryType = validatedData.deliveryType;
+    order.deliveryCompany = validatedData.deliveryCompany || null;
+    order.deliveryNo = validatedData.deliveryNo || null;
+    order.deliveryTime = new Date();
+    order.deliveryRemark = validatedData.deliveryRemark || null;
+
+    await orderRepository.save(order);
+
+    return c.json({
+      success: true,
+      message: '发货成功',
+      order: {
+        id: order.id,
+        orderNo: order.orderNo,
+        state: order.state,
+        deliveryType: order.deliveryType,
+        deliveryCompany: order.deliveryCompany,
+        deliveryNo: order.deliveryNo,
+        deliveryTime: order.deliveryTime
+      }
+    });
+
+  } catch (error) {
+    console.error('发货失败:', error);
+    return c.json({
+      success: false,
+      message: '发货失败,请重试'
+    }, 500);
+  }
+});
+
+// 获取发货信息
+deliveryRoutes.get('/:id/delivery', authMiddleware, async (c) => {
+  const orderId = parseInt(c.req.param('id'));
+
+  const orderRepository = AppDataSource.getRepository(OrderMt);
+
+  try {
+    const order = await orderRepository.findOne({
+      where: { id: orderId },
+      select: ['id', 'orderNo', 'state', 'deliveryType', 'deliveryCompany', 'deliveryNo', 'deliveryTime', 'deliveryRemark']
+    });
+
+    if (!order) {
+      return c.json({
+        success: false,
+        message: '订单不存在'
+      }, 404);
+    }
+
+    return c.json({
+      success: true,
+      data: {
+        id: order.id,
+        orderNo: order.orderNo,
+        state: order.state,
+        deliveryType: order.deliveryType,
+        deliveryCompany: order.deliveryCompany,
+        deliveryNo: order.deliveryNo,
+        deliveryTime: order.deliveryTime,
+        deliveryRemark: order.deliveryRemark
+      }
+    });
+
+  } catch (error) {
+    console.error('获取发货信息失败:', error);
+    return c.json({
+      success: false,
+      message: '获取发货信息失败'
+    }, 500);
+  }
+});
+
+export default deliveryRoutes;

+ 1 - 0
packages/orders-module-mt/src/routes/index.ts

@@ -2,6 +2,7 @@
 export { default as adminOrderRoutes } from './admin/orders.mt';
 export { default as adminOrderItemsRoutes } from './admin/order-items.mt';
 export { default as adminRefundsRoutes } from './admin/refunds.mt';
+export { default as adminDeliveryRoutes } from './admin/delivery.mt';
 
 export { default as userOrderRoutes } from './user/orders.mt';
 export { default as userOrderItemsRoutes } from './user/order-items.mt';

+ 63 - 0
packages/orders-module-mt/src/schemas/delivery.mt.schema.ts

@@ -0,0 +1,63 @@
+import { z } from '@hono/zod-openapi';
+
+// 发货请求DTO
+export const DeliveryOrderDto = z.object({
+  deliveryType: z.coerce.number().int().min(1, '发货方式最小为1').max(4, '发货方式最大为4').openapi({
+    description: '发货方式 1物流快递 2同城配送 3用户自提 4虚拟发货',
+    example: 1
+  }),
+  deliveryCompany: z.string().max(100, '快递公司最多100个字符').nullable().optional().openapi({
+    description: '快递公司',
+    example: '顺丰速运'
+  }),
+  deliveryNo: z.string().max(100, '快递单号最多100个字符').nullable().optional().openapi({
+    description: '快递单号',
+    example: 'SF1234567890'
+  }),
+  deliveryRemark: z.string().max(500, '发货备注最多500个字符').nullable().optional().openapi({
+    description: '发货备注',
+    example: '已包装好,请注意查收'
+  })
+});
+
+// 发货响应DTO
+export const DeliveryResponseDto = z.object({
+  success: z.boolean().openapi({
+    description: '发货是否成功',
+    example: true
+  }),
+  message: z.string().openapi({
+    description: '发货结果消息',
+    example: '发货成功'
+  }),
+  order: z.object({
+    id: z.number().int().positive().openapi({
+      description: '订单ID',
+      example: 1
+    }),
+    orderNo: z.string().openapi({
+      description: '订单号',
+      example: 'ORD20240101123456'
+    }),
+    state: z.number().int().openapi({
+      description: '订单状态',
+      example: 1
+    }),
+    deliveryType: z.number().int().openapi({
+      description: '发货方式',
+      example: 1
+    }),
+    deliveryCompany: z.string().nullable().openapi({
+      description: '快递公司',
+      example: '顺丰速运'
+    }),
+    deliveryNo: z.string().nullable().openapi({
+      description: '快递单号',
+      example: 'SF1234567890'
+    }),
+    deliveryTime: z.coerce.date().nullable().openapi({
+      description: '发货时间',
+      example: '2024-01-01T12:00:00Z'
+    })
+  }).optional()
+});

+ 69 - 0
packages/orders-module-mt/src/schemas/order.mt.schema.ts

@@ -8,6 +8,15 @@ export const OrderStatus = {
   RETURNED: 3, // 已退货
 } as const;
 
+// 发货方式枚举
+export const DeliveryType = {
+  NOT_SHIPPED: 0, // 未发货
+  EXPRESS: 1, // 物流快递
+  LOCAL_DELIVERY: 2, // 同城配送
+  SELF_PICKUP: 3, // 用户自提
+  VIRTUAL: 4, // 虚拟发货
+} as const;
+
 // 支付状态枚举
 export const PayStatus = {
   UNPAID: 0, // 未支付
@@ -172,6 +181,26 @@ export const OrderSchema = z.object({
     description: '管理员备注信息',
     example: '请尽快发货'
   }),
+  deliveryType: z.coerce.number().int().min(0, '发货方式最小为0').max(4, '发货方式最大为4').default(0).openapi({
+    description: '发货方式 0未发货 1物流快递 2同城配送 3用户自提 4虚拟发货',
+    example: 1
+  }),
+  deliveryCompany: z.string().max(100, '快递公司最多100个字符').nullable().optional().openapi({
+    description: '快递公司',
+    example: '顺丰速运'
+  }),
+  deliveryNo: z.string().max(100, '快递单号最多100个字符').nullable().optional().openapi({
+    description: '快递单号',
+    example: 'SF1234567890'
+  }),
+  deliveryTime: z.coerce.date().nullable().optional().openapi({
+    description: '发货时间',
+    example: '2024-01-01T12:00:00Z'
+  }),
+  deliveryRemark: z.string().max(500, '发货备注最多500个字符').nullable().optional().openapi({
+    description: '发货备注',
+    example: '已包装好,请注意查收'
+  }),
   createdBy: z.number().int().positive().nullable().optional().openapi({
     description: '创建人ID',
     example: 1
@@ -368,6 +397,26 @@ export const CreateOrderDto = z.object({
   remark: z.string().max(255, '管理员备注信息最多255个字符').nullable().optional().openapi({
     description: '管理员备注信息',
     example: '请尽快发货'
+  }),
+  deliveryType: z.coerce.number().int().min(0, '发货方式最小为0').max(4, '发货方式最大为4').default(0).optional().openapi({
+    description: '发货方式 0未发货 1物流快递 2同城配送 3用户自提 4虚拟发货',
+    example: 1
+  }),
+  deliveryCompany: z.string().max(100, '快递公司最多100个字符').nullable().optional().openapi({
+    description: '快递公司',
+    example: '顺丰速运'
+  }),
+  deliveryNo: z.string().max(100, '快递单号最多100个字符').nullable().optional().openapi({
+    description: '快递单号',
+    example: 'SF1234567890'
+  }),
+  deliveryTime: z.coerce.date().nullable().optional().openapi({
+    description: '发货时间',
+    example: '2024-01-01T12:00:00Z'
+  }),
+  deliveryRemark: z.string().max(500, '发货备注最多500个字符').nullable().optional().openapi({
+    description: '发货备注',
+    example: '已包装好,请注意查收'
   })
 });
 
@@ -504,6 +553,26 @@ export const UpdateOrderDto = z.object({
   remark: z.string().max(255, '管理员备注信息最多255个字符').nullable().optional().openapi({
     description: '管理员备注信息',
     example: '请尽快发货'
+  }),
+  deliveryType: z.coerce.number().int().min(0, '发货方式最小为0').max(4, '发货方式最大为4').default(0).optional().openapi({
+    description: '发货方式 0未发货 1物流快递 2同城配送 3用户自提 4虚拟发货',
+    example: 1
+  }),
+  deliveryCompany: z.string().max(100, '快递公司最多100个字符').nullable().optional().openapi({
+    description: '快递公司',
+    example: '顺丰速运'
+  }),
+  deliveryNo: z.string().max(100, '快递单号最多100个字符').nullable().optional().openapi({
+    description: '快递单号',
+    example: 'SF1234567890'
+  }),
+  deliveryTime: z.coerce.date().nullable().optional().openapi({
+    description: '发货时间',
+    example: '2024-01-01T12:00:00Z'
+  }),
+  deliveryRemark: z.string().max(500, '发货备注最多500个字符').nullable().optional().openapi({
+    description: '发货备注',
+    example: '已包装好,请注意查收'
   })
 });