Procházet zdrojové kódy

✨ feat(city-selector): 实现地址选择器自动选择功能

- 新增四个useEffect钩子,实现省份、城市、区县、街道数据加载完成后自动选择第一个选项
- 优化清除下级选择器逻辑,添加注释说明使用默认值而非0

✨ feat(address-edit): 优化地址编辑表单验证和默认值

- 更新表单验证规则,要求省份、城市、区县为正数
- 修改默认值为0,配合CitySelector组件实现自动选择功能
- 调整地址数据保存逻辑,移除默认值填充

✨ feat(address-manage): 实现地址项滑动删除功能

- 添加触摸事件处理,支持左右滑动打开/关闭删除按钮
- 优化滑动状态管理,防止多个地址项同时打开滑动菜单
- 移除hover效果,完全依赖JS控制滑动行为

🐛 fix(delivery-address): 修复地址更新和默认地址设置问题

- 优化默认地址设置逻辑,使用事务确保数据一致性
- 添加订单引用检查,防止已被订单引用的地址修改关键字段
- 改进地址更新逻辑,根据是否有订单引用限制可更新字段

🔧 chore(profile): 更新版本号至v0.0.5

- 修改版本显示文本,标识小程序版更新
yourname před 1 měsícem
rodič
revize
4bf9ebe66f

+ 35 - 6
mini/src/components/ui/city-selector.tsx

@@ -108,18 +108,47 @@ export const CitySelector: React.FC<CitySelectorProps> = ({
     enabled: !!districtValue,
   })
 
+  // 自动选中第一个选项
+  useEffect(() => {
+    // 当省份数据加载完成且没有选中省份时,自动选中第一个省份
+    if (provinces && provinces.length > 0 && (!provinceValue || provinceValue === 0)) {
+      onProvinceChange?.(provinces[0].id)
+    }
+  }, [provinces, provinceValue, onProvinceChange])
+
+  useEffect(() => {
+    // 当城市数据加载完成且没有选中城市时,自动选中第一个城市
+    if (cities && cities.length > 0 && (!cityValue || cityValue === 0)) {
+      onCityChange?.(cities[0].id)
+    }
+  }, [cities, cityValue, onCityChange])
+
+  useEffect(() => {
+    // 当区县数据加载完成且没有选中区县时,自动选中第一个区县
+    if (districts && districts.length > 0 && (!districtValue || districtValue === 0)) {
+      onDistrictChange?.(districts[0].id)
+    }
+  }, [districts, districtValue, onDistrictChange])
+
+  useEffect(() => {
+    // 当街道数据加载完成且没有选中街道时,自动选中第一个街道
+    if (towns && towns.length > 0 && (!townValue || townValue === 0)) {
+      onTownChange?.(towns[0].id)
+    }
+  }, [towns, townValue, onTownChange])
+
   // 清除下级选择器
   useEffect(() => {
     // 当省份变化时,清除城市、区县、街道
     if (provinceValue !== undefined) {
       if (cityValue !== undefined) {
-        onCityChange?.(0)
+        onCityChange?.(0) // 使用默认值而不是0
       }
       if (districtValue !== undefined) {
-        onDistrictChange?.(0)
+        onDistrictChange?.(0) // 使用默认值而不是0
       }
       if (townValue !== undefined) {
-        onTownChange?.(0)
+        onTownChange?.(0) // 使用默认值而不是0
       }
     }
   }, [provinceValue])
@@ -128,10 +157,10 @@ export const CitySelector: React.FC<CitySelectorProps> = ({
     // 当城市变化时,清除区县、街道
     if (cityValue !== undefined) {
       if (districtValue !== undefined) {
-        onDistrictChange?.(0)
+        onDistrictChange?.(0) // 使用默认值而不是0
       }
       if (townValue !== undefined) {
-        onTownChange?.(0)
+        onTownChange?.(0) // 使用默认值而不是0
       }
     }
   }, [cityValue])
@@ -140,7 +169,7 @@ export const CitySelector: React.FC<CitySelectorProps> = ({
     // 当区县变化时,清除街道
     if (districtValue !== undefined) {
       if (townValue !== undefined) {
-        onTownChange?.(0)
+        onTownChange?.(0) // 使用默认值而不是0
       }
     }
   }, [districtValue])

+ 22 - 19
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().default(1),
-  city: z.number().default(2),
-  district: z.number().default(3),
-  town: z.number().default(4),
+  province: z.number().positive('请选择省份'),
+  city: z.number().positive('请选择城市'),
+  district: z.number().positive('请选择区县'),
+  town: z.number().optional(),
   address: z.string().min(1, '请输入详细地址'),
   isDefault: z.boolean().optional()
 })
@@ -61,10 +61,10 @@ export default function AddressEditPage() {
     defaultValues: {
       name: '',
       phone: '',
-      province: 1,
-      city: 2,
-      district: 3,
-      town: 4,
+      province: 0, // 设为0,让CitySelector自动选择第一个省份
+      city: 0,     // 设为0,让CitySelector自动选择第一个城市
+      district: 0, // 设为0,让CitySelector自动选择第一个区县
+      town: 0,     // 设为0,让CitySelector自动选择第一个乡镇
       address: '',
       isDefault: false
     }
@@ -76,10 +76,10 @@ export default function AddressEditPage() {
       form.reset({
         name: address.name,
         phone: address.phone,
-        province: address.receiverProvince || 1,
-        city: address.receiverCity || 2,
-        district: address.receiverDistrict || 3,
-        town: address.receiverTown || 4,
+        province: address.receiverProvince,
+        city: address.receiverCity,
+        district: address.receiverDistrict,
+        town: address.receiverTown || 0,
         address: address.address,
         isDefault: address.isDefault === 1
       })
@@ -92,10 +92,10 @@ export default function AddressEditPage() {
       const addressData: any = {
         name: data.name,
         phone: data.phone,
-        receiverProvince: data.province||1,
-        receiverCity: data.city||2,
-        receiverDistrict: data.district||3,
-        receiverTown: data.town || 4,
+        receiverProvince: data.province,
+        receiverCity: data.city,
+        receiverDistrict: data.district,
+        receiverTown: data.town,
         address: data.address,
         isDefault: data.isDefault ? 1 : 0
       }
@@ -139,6 +139,9 @@ export default function AddressEditPage() {
 
 
   const onSubmit = (data: AddressFormData) => {
+    console.debug('表单提交数据:', data)
+    console.debug('表单验证状态:', form.formState.isValid)
+    console.debug('表单错误:', form.formState.errors)
     saveAddressMutation.mutate(data)
   }
 
@@ -182,7 +185,7 @@ export default function AddressEditPage() {
                     )}
                   />
 
-                  {/* <View className="mb-4">
+                  <View className="mb-4" style={{ display: 'none' }}>
                     <Text className="text-sm font-medium text-gray-700 mb-2">所在地区</Text>
                     <CitySelector
                       provinceValue={form.watch('province')}
@@ -195,7 +198,7 @@ export default function AddressEditPage() {
                       onTownChange={(value) => form.setValue('town', value)}
                       showLabels={false}
                     />
-                  </View> */}
+                  </View>
 
                   <FormField
                     name="address"

+ 5 - 2
mini/src/pages/address-manage/index.css

@@ -79,16 +79,19 @@
   align-items: center;
   justify-content: center;
   transition: right 0.3s ease;
+  z-index: 10;
 }
 
 .swipe-actions.swipe-open {
   right: 0;
 }
 
-.swipe-cell:hover .swipe-actions {
-  right: 0;
+.swipe-cell {
+  touch-action: pan-x;
 }
 
+/* 移除hover效果,完全依赖JS控制 */
+
 .delete-btn {
   width: 100%;
   height: 100%;

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

@@ -1,6 +1,6 @@
 import { View, ScrollView, Text } from '@tarojs/components'
 import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
-import { useState } from 'react'
+import { useState, useRef } from 'react'
 import Taro from '@tarojs/taro'
 import { deliveryAddressClient } from '@/api'
 import { InferResponseType } from 'hono'
@@ -16,6 +16,8 @@ export default function AddressManagePage() {
   const { user } = useAuth()
   const queryClient = useQueryClient()
   const [swipeStates, setSwipeStates] = useState<Record<number, boolean>>({})
+  const [touchStartX, setTouchStartX] = useState<number | null>(null)
+  const [currentSwipeId, setCurrentSwipeId] = useState<number | null>(null)
 
   // 获取地址列表
   const { data: addresses, isLoading } = useQuery({
@@ -157,11 +159,42 @@ export default function AddressManagePage() {
   // 打开侧滑
   const openSwipe = (id: number) => {
     setSwipeStates({ [id]: true })
+    setCurrentSwipeId(id)
   }
 
   // 关闭所有侧滑
   const closeAllSwipes = () => {
     setSwipeStates({})
+    setCurrentSwipeId(null)
+    setTouchStartX(null)
+  }
+
+  // 触摸开始
+  const handleTouchStart = (e: any, id: number) => {
+    setTouchStartX(e.touches[0].clientX)
+    setCurrentSwipeId(id)
+  }
+
+  // 触摸移动
+  const handleTouchMove = (e: any, id: number) => {
+    if (touchStartX === null) return
+
+    const touchCurrentX = e.touches[0].clientX
+    const diffX = touchStartX - touchCurrentX
+
+    // 向左滑动超过50px时打开侧滑
+    if (diffX > 50) {
+      openSwipe(id)
+    }
+    // 向右滑动超过30px时关闭侧滑
+    else if (diffX < -30 && currentSwipeId === id) {
+      closeAllSwipes()
+    }
+  }
+
+  // 触摸结束
+  const handleTouchEnd = () => {
+    setTouchStartX(null)
   }
 
   // 删除地址
@@ -201,7 +234,9 @@ export default function AddressManagePage() {
                   <View
                     className="swipe-cell"
                     onClick={() => closeAllSwipes()}
-                    onTouchStart={() => openSwipe(address.id)}
+                    onTouchStart={(e) => handleTouchStart(e, address.id)}
+                    onTouchMove={(e) => handleTouchMove(e, address.id)}
+                    onTouchEnd={handleTouchEnd}
                   >
                     <View
                       className="swipe-content"

+ 1 - 1
mini/src/pages/profile/index.tsx

@@ -278,7 +278,7 @@ const ProfilePage: React.FC = () => {
         {/* 版本信息 */}
         <View className="pb-8">
           <Text className="text-center text-xs text-gray-400">
-            v0.0.4 - 小程序版
+            v0.0.5 - 小程序版
           </Text>
         </View>
       </ScrollView>

+ 45 - 4
packages/delivery-address-module-mt/src/services/delivery-address.mt.service.ts

@@ -212,10 +212,13 @@ export class DeliveryAddressServiceMt extends GenericCrudService<DeliveryAddress
           { isDefault: 0 }
         );
 
-        // 将指定地址设为默认
+        // 准备更新数据,包含其他字段
+        const updateData = { ...data, isDefault: 1 };
+
+        // 将指定地址设为默认并更新其他字段
         await transactionalEntityManager.update(DeliveryAddressMt,
           { id, userId: existingAddress.userId, tenantId },
-          { isDefault: 1 }
+          updateData
         );
       });
 
@@ -226,11 +229,49 @@ export class DeliveryAddressServiceMt extends GenericCrudService<DeliveryAddress
       });
     }
 
-    // 普通更新
-    Object.assign(existingAddress, data);
+    // 检查是否有订单引用该地址
+    const hasOrderReference = await this.checkOrderReference(id, tenantId);
+    if (hasOrderReference) {
+      // 如果有订单引用,只允许更新非关键字段
+      const allowedFields = ['name', 'phone', 'isDefault'];
+      const updateData: any = {};
+
+      for (const field of allowedFields) {
+        if (field in data) {
+          updateData[field] = (data as any)[field];
+        }
+      }
+
+      Object.assign(existingAddress, updateData);
+    } else {
+      // 没有订单引用,可以更新所有字段
+      Object.assign(existingAddress, data);
+    }
+
     return await this.repository.save(existingAddress);
   }
 
+  /**
+   * 检查是否有订单引用该地址
+   * @param addressId 地址ID
+   * @param tenantId 租户ID
+   * @returns 是否有订单引用
+   */
+  private async checkOrderReference(addressId: number, tenantId: number): Promise<boolean> {
+    try {
+      // 使用原生SQL查询检查是否有订单引用该地址
+      const result = await this.repository.manager.query(
+        `SELECT COUNT(*) as count FROM orders_mt WHERE address_id = $1 AND tenant_id = $2`,
+        [addressId, tenantId]
+      );
+
+      return parseInt(result[0].count) > 0;
+    } catch (error) {
+      console.error('检查订单引用失败:', error);
+      return false;
+    }
+  }
+
   /**
    * 删除配送地址(包含租户检查)
    * @param id 地址ID