Bläddra i källkod

```
feat(api): 添加店铺地址信息和会员优惠相关字段

- 在AppletOrderSkuVo接口中添加shopAddressDTO字段,包含距离、店铺地址、经纬度等信息
- 添加ShopAddressDTO接口定义,支持自提功能的位置服务
- 添加会员优惠相关字段:memberDiscountAmount、memberDiscountDesc、memberPerKwhDiscount
- 支持显示会员权益折扣信息

feat(order): 增加自提配送方式和订单流程优化

- 修改skuOrderConfirm API方法从GET改为POST并更新路径
- 添加自提配送类型支持(dvyType: 2),支持用户选择自提点
- 实现自提订单联系人和联系电话验证功能
- 更新订单确认页面,支持即时配送和自提两种模式切换
- 添加自提码展示和核销功能

chore(config): 更新开发环境配置注释说明

- 为开发环境配置添加测试代理和测试直连的注释说明
- 清理冗余的环境配置注释

fix(address): 修复自提订单地址验证逻辑

- 修正支付时的地址验证逻辑,自提订单不需要选择收货地址
- 在用户Store中添加自提信息参数传递
- 增加自提订单的联系人和联系方式验证

docs(manifest): 修复JSON文件格式问题

- 移除多余的逗号,修复manifest.json文件格式

feat(ui): 增强订单详情页和商品页功能

- 在充电订单详情页添加会员优惠金额显示
- 在商品页面添加配送类型参数
- 在订单列表页增加自提选项卡
- 优化自提订单详情页UI,显示自提码和取货信息
```

Co-authored-by: Copilot <copilot@github.com>

zouzexu 1 dag sedan
förälder
incheckning
931e930041

+ 46 - 0
src/api/api.type.d.ts

@@ -1397,6 +1397,10 @@ namespace Api {
     coupon: number
     couponName: string
     orderCouponItemDTOS?: AppMemberCouponVO[]
+    /**
+     * 店铺地址信息
+     */
+    shopAddressDTO?: ShopAddressDTO
     [property: string]: any
   }
   interface AppletOrderSkuVo {
@@ -1421,6 +1425,36 @@ namespace Api {
      * sku
      */
     sku?: SkuConfirmVo
+    /**
+     * 店铺地址信息
+     */
+    shopAddressDTO?: ShopAddressDTO
+    [property: string]: any
+  }
+  /**
+   * 店铺地址信息
+   */
+  export interface ShopAddressDTO {
+    /**
+     * 距离(单位:千米)
+     */
+    distance?: number
+    /**
+     * 完整店铺地址(省+市+区+详细地址 拼接结果)
+     */
+    shopAddress?: string
+    /**
+     * 纬度
+     */
+    shopLat?: string
+    /**
+     * 经度
+     */
+    shopLng?: string
+    /**
+     * 店铺名称
+     */
+    shopName?: string
     [property: string]: any
   }
   interface SkuConfirmVo {
@@ -2697,6 +2731,18 @@ namespace Api {
      * 用户实付
      */
     actualTotal?: number
+    /**
+     * 会员优惠金额,单位元。非会员、非 XSB 电商商品或无可用权益时为 0。
+     */
+    memberDiscountAmount?: number
+    /**
+     * 会员优惠说明,例如“会员权益(9.5折)”或“会员权益(每度立减0.3元)”。
+     */
+    memberDiscountDesc?: string
+    /**
+     * 会员每度立减金额,单位元/度。
+     */
+    memberPerKwhDiscount?: number
     platformVolume?: number
 
   }

+ 1 - 1
src/api/apiDefinitions.ts

@@ -54,7 +54,7 @@ export default {
   'xsb.orderInfo':['GET', '/smqjh-oms/api/v1/order/orderInfo'],
   'xsb.cancelOrder':['GET', '/smqjh-oms/api/v1/order/cancelOrder'],
   'xsb.deleteOrder':['DELETE', '/smqjh-oms/api/v1/order/deleteOrder/{ids}'],
-  'xsb.skuOrderConfirm':['GET', '/smqjh-oms/api/v1/order/skuOrderConfirm'],
+  'xsb.skuOrderConfirm':['POST', '/smqjh-oms/api/v1/order/skuOrderConfirmNew'],
   'xsb.myShoppingCartCategory':['GET', '/smqjh-oms/app-api/v1/shoppingCart/myShoppingCartCategory'],
   'xsb.delivery':['GET', '/smqjh-system/app-api/v1/delivery'],
   'xsb.confirmReceipt':['GET', '/smqjh-oms/api/v1/order/confirmReceipt'],

+ 20 - 0
src/api/globals.d.ts

@@ -570,6 +570,18 @@ declare global {
         Config extends Alova2MethodConfig<apiResData<Api.AppletOrderSkuVo>> & {
           data: {
             channelId?: number;
+            /**
+             * 配送类型 1:快递 2:自提 3:及时配送
+             */
+            dvyType?: number;
+            /**
+             * 当前位置纬度
+             */
+            latitude?: number;
+            /**
+             * 当前位置经度
+             */
+            longitude?: number;
             num?: number;
             shopId?: number;
             skuId?: number;
@@ -691,6 +703,14 @@ declare global {
              * 企业ID
              */
             channelId: number;
+            /**
+             * 预留电话 自提时需要填写
+             */
+            consigneeMobile?: string;
+            /**
+             * 联系人姓名 自提时需要填写
+             */
+            consigneeName?: string;
             /**
              * 配送类型 1:快递 2:自提 3:及时配送
              */

+ 2 - 2
src/config/index.ts

@@ -11,8 +11,8 @@ const mapEnvVersion = {
   // develop: 'http://192.168.0.11:8080', // 王
   // develop: 'http://192.168.1.21:8080', // 田
   // develop: 'http://74949mkfh190.vicp.fun', // 付
-  // develop: 'http://47.109.84.152:8081',
-  develop: 'http://192.168.1.242:8080',
+  // develop: 'http://47.109.84.152:8081', // 测试代理
+  develop: 'http://192.168.1.242:8080', // 测试直连
   // develop: 'https://5ed0f7cc.r9.vip.cpolar.cn',
   // develop: 'https://smqjh.api.zswlgz.com',
   /**

+ 19 - 13
src/store/user.ts

@@ -187,25 +187,31 @@ export const useUserStore = defineStore('user', {
     getOrderPayMent(freightFee: number, businessType: string, dvyType: number, shopId: number, orderItemList: {
       prodCount?: number
       skuId?: number
-    }[], remarks?: string, allowanceId?: string): Promise<string> {
+    }[], remarks?: string, allowanceId?: string, pickupInfo?: {
+      consigneeName?: string
+      consigneeMobile?: string
+    }): Promise<string> {
       uni.showLoading({ mask: true })
       return new Promise((resolve, reject) => {
-        if (!this.selectedAddress) {
+        if (dvyType !== 2 && !this.selectedAddress) {
+          uni.hideLoading()
           reject(new Error('请选择收货地址'))
           return
         }
+        const orderData = {
+          channelId: Number(this.userInfo.channelId),
+          businessType,
+          addressId: this.selectedAddress?.id,
+          dvyType,
+          freightAmount: freightFee,
+          shopId,
+          orderItemList,
+          remarks,
+          allowanceIds: allowanceId ? [allowanceId] : [],
+          ...pickupInfo,
+        }
         Apis.common.addOrder({
-          data: {
-            channelId: Number(this.userInfo.channelId),
-            businessType,
-            addressId: this.selectedAddress.id,
-            dvyType,
-            freightAmount: freightFee,
-            shopId,
-            orderItemList,
-            remarks,
-            allowanceIds: allowanceId ? [allowanceId] : [],
-          },
+          data: orderData,
         }).then((res) => {
           resolve(res.data)
           uni.hideLoading()

+ 8 - 0
src/subPack-charge/chargeOrderDetail/chargeOrderDetail.vue

@@ -164,6 +164,14 @@ async function getOrderDetail() {
             {{ chargeOrderDetail?.actualTotal || '--' }}元
           </view>
         </view>
+        <view v-if="(chargeOrderDetail?.memberDiscountAmount ?? 0) > 0" class="mt-28rpx flex items-center justify-between">
+          <view class="text-24rpx">
+            {{ chargeOrderDetail?.memberDiscountDesc }}
+          </view>
+          <view class="text-24rpx text-#F44033">
+            {{ chargeOrderDetail?.memberDiscountAmount || '--' }}元
+          </view>
+        </view>
         <view class="mt-28rpx flex items-center justify-between">
           <view class="text-24rpx">
             积分扣减

+ 134 - 41
src/subPack-xsb/confirmOrder/index.vue

@@ -11,7 +11,10 @@ definePage({
 })
 const { selectedAddress, userInfo } = storeToRefs(useUserStore())
 const { SelectShopInfo } = storeToRefs(useSysXsbStore())
-const orderInfo = ref<Api.shoppingCartOrderConfirm>()
+const { Location } = storeToRefs(useAddressStore())
+type ConfirmOrderInfo = Api.shoppingCartOrderConfirm & Partial<Api.AppletOrderSkuVo>
+
+const orderInfo = ref<ConfirmOrderInfo>()
 const { totalProduct } = storeToRefs(useSmqjhCartStore())
 const isPay = ref(false)
 const remarks = ref('')
@@ -27,8 +30,19 @@ const model = reactive<{
   value1: '',
   value2: '',
 })
+const mobilePattern = /^1[3-9]\d{9}$/
+const mobileRules = [{ required: true, pattern: mobilePattern, message: '请输入正确的手机号' }]
 const deliveryType = ref(1)
 const isMemberGiftOrder = computed(() => !!orderInfo.value?.memberGiftItems?.length)
+const isSelfPickup = computed(() => current.value === '自提')
+const shopAddressInfo = computed(() => orderInfo.value?.shopAddressDTO)
+const shopDistanceText = computed(() => {
+  const distance = shopAddressInfo.value?.distance
+  if (distance === undefined || distance === null) {
+    return ''
+  }
+  return `距你${Number(distance).toFixed(2)}km`
+})
 
 // 优惠券选择
 const couponPopup = ref(false)
@@ -77,15 +91,22 @@ const displayDiscountTotal = computed(() => Number((currentCouponDiscount.value
 
 onLoad((options: any) => {
   orderInfo.value = JSON.parse(options.data)
-  couponList.value = orderInfo.value?.orderCouponItemDTOS || []
-  initialCouponId.value = orderInfo.value?.allowanceId
-  confirmedCouponId.value = initialCouponId.value
-  draftCouponId.value = initialCouponId.value
+  syncCouponState()
+  deliveryType.value = current.value === '自提' ? 2 : 3
+  // model.value1 = userInfo.value?.nickName || ''
+  model.value2 = userInfo.value?.mobile || ''
 })
 onShow(() => {
   useUserStore().getuserAddresslist()
-  if (selectedAddress.value) {
-    getDevryList()
+  if (orderInfo.value && current.value === '即时配送') {
+    getConfirmOrder()
+  }
+})
+
+watch(current, async (value) => {
+  deliveryType.value = value === '自提' ? 2 : 3
+  if (orderInfo.value) {
+    await getConfirmOrder()
   }
 })
 
@@ -93,16 +114,59 @@ function getCouponDisplayName(coupon?: Partial<Api.AppMemberCouponVO> & Record<s
   return coupon?.activityName || coupon?.couponName || ''
 }
 
-async function getDevryList() {
-  const res = await Apis.xsb.delivery({
-    data: {
-      memberId: userInfo.value.id,
-      shopId: Number(orderInfo.value?.skuList[0].shopId || SelectShopInfo.value?.shopId || 2),
-      addressId: selectedAddress.value?.id,
-    },
+function syncCouponState() {
+  couponList.value = orderInfo.value?.orderCouponItemDTOS || []
+  initialCouponId.value = orderInfo.value?.allowanceId
+  confirmedCouponId.value = initialCouponId.value
+  draftCouponId.value = initialCouponId.value
+}
+
+function getOrderConfirmParams() {
+  const firstSku = orderInfo.value?.skuList?.[0]
+  return {
+    skuId: firstSku?.skuId,
+    num: Number(firstSku?.num || 1),
+    channelId: userInfo.value?.channelId,
+    shopId: Number(firstSku?.shopId || SelectShopInfo.value?.shopId || 2),
+    dvyType: deliveryType.value,
+  }
+}
+
+async function getConfirmOrder() {
+  const params: Record<string, any> = getOrderConfirmParams()
+  if (!params.skuId) {
+    return
+  }
+  if (isSelfPickup.value) {
+    if (!Location.value.latitude || !Location.value.longitude) {
+      useGlobalToast().show('暂无定位缓存,无法计算自提点距离')
+    }
+    else {
+      params.latitude = Location.value.latitude
+      params.longitude = Location.value.longitude
+    }
+  }
+  const res = await Apis.xsb.skuOrderConfirm({
+    data: params,
+  })
+  const skuList = res.data.sku
+    ? [{ ...res.data.sku, num: params.num, shopId: params.shopId }]
+    : (orderInfo.value?.skuList || [])
+  orderInfo.value = {
+    ...orderInfo.value,
+    ...res.data,
+    skuList,
+  } as ConfirmOrderInfo
+  syncCouponState()
+}
+
+function openShopLocation() {
+  uni.openLocation({
+    latitude: Number(shopAddressInfo.value?.shopLat) || 0,
+    longitude: Number(shopAddressInfo.value?.shopLng) || 0,
+    name: shopAddressInfo.value?.shopName,
+    address: shopAddressInfo.value?.shopAddress,
   })
-  deliveryType.value = res.data.deliveryType
-  console.log(res)
 }
 
 async function openCouponPopup() {
@@ -123,7 +187,7 @@ function confirmCoupon() {
 }
 
 async function handlePay() {
-  if (!selectedAddress.value) {
+  if (!isSelfPickup.value && !selectedAddress.value) {
     useGlobalToast().show({ msg: '请选择收货地址' })
     return
   }
@@ -131,9 +195,23 @@ async function handlePay() {
     useGlobalToast().show({ msg: '网络异常!请联系客服' })
     return
   }
+  if (isSelfPickup.value) {
+    if (!model.value1) {
+      useGlobalToast().show({ msg: '请填写联系人' })
+      return
+    }
+    if (!mobilePattern.test(model.value2)) {
+      useGlobalToast().show({ msg: '请输入正确的手机号' })
+      return
+    }
+  }
   isPay.value = true
   try {
     if (isMemberGiftOrder.value) {
+      if (!selectedAddress.value) {
+        useGlobalToast().show({ msg: '请选择收货地址' })
+        return
+      }
       await Apis.sys.giftsReceive({
         data: {
           addressId: selectedAddress.value.id,
@@ -158,6 +236,12 @@ async function handlePay() {
       orderItemList,
       unref(remarks),
       confirmedCouponId.value,
+      isSelfPickup.value
+        ? {
+            consigneeName: model.value1,
+            consigneeMobile: model.value2,
+          }
+        : undefined,
     )
     const res = await useUserStore().handleCommonPayMent(orderNumber)
     await useUserStore().clearCart(orderInfo.value.skuList)
@@ -220,40 +304,46 @@ async function handlePay() {
 
     <view v-else class="my20rpx rounded-16rpx bg-white p24rpx">
       <view class="flex items-center gap-50rpx">
-        <view>
+        <view class="flex-1">
           <view class="text-28rpx font-semibold">
-            自提点:星闪豹(贵阳店)
+            自提点:{{ shopAddressInfo?.shopName || orderInfo?.shopName || '暂无门店信息' }}
           </view>
           <view class="mt-20rpx text-24rpx text-#AAAAAA">
-            贵州省贵阳市贵阳市观山湖区金麦安置小区12组团26号-28号门面
+            {{ shopAddressInfo?.shopAddress || '暂无门店地址' }}
           </view>
         </view>
-        <view>
-          <view class="mb-20rpx text-24rpx text-#AAAAAA">
-            距你XXkm
+        <view class="flex-shrink-0" @click="openShopLocation">
+          <view v-if="shopDistanceText" class="mb-20rpx text-24rpx text-#AAAAAA">
+            {{ shopDistanceText }}
           </view>
           <wd-icon name="location" size="22px" color="#9ED605" />
         </view>
       </view>
       <view>
-        <wd-input
-          v-model="model.value1"
-          label="联系人"
-          label-width="80px"
-          prop="value1"
-          clearable
-          placeholder="请输入联系人"
-          :rules="[{ required: true, message: '请填写联系人' }]"
-        />
-        <wd-input
-          v-model="model.value2"
-          label="预留电话"
-          label-width="80px"
-          prop="value1"
-          clearable
-          placeholder="请输入预留电话"
-          :rules="[{ required: true, pattern: /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/, message: '请输入正确的手机号' }]"
-        />
+        <view class="mt-20rpx flex items-center">
+          <view class="w100rpx text-28rpx">
+            联系人:
+          </view>
+          <wd-input
+            v-model="model.value1"
+            prop="value1"
+            clearable
+            placeholder="请输入联系人"
+            :rules="[{ required: true, message: '请填写联系人' }]"
+          />
+        </view>
+        <view class="mt-20rpx flex items-center">
+          <view class="w140rpx text-28rpx">
+            预留电话:
+          </view>
+          <wd-input
+            v-model="model.value2"
+            prop="value2"
+            clearable
+            placeholder="请输入预留电话"
+            :rules="mobileRules"
+          />
+        </view>
       </view>
     </view>
 
@@ -300,6 +390,9 @@ async function handlePay() {
         <view v-if="deliveryType == 1">
           快递
         </view>
+        <view v-if="deliveryType == 2">
+          自提
+        </view>
         <view class="text-#FF4D3A font-semibold">
           ¥{{ orderInfo?.transfee }}
         </view>

+ 1 - 0
src/subPack-xsb/goods/index.vue

@@ -119,6 +119,7 @@ async function handleConfimOrder() {
         skuId: specId.value,
         num: SelectGoodsNum.value,
         channelId: unref(userInfo).channelId,
+        dvyType: 3,
         shopId: unref(goodsInfo)?.shopId || 2,
       },
     })

+ 1 - 0
src/subPack-xsb/order/index.vue

@@ -13,6 +13,7 @@ const navTabTypeList = [
   { name: '全部', value: 0 },
   { name: '配送(外卖)', value: 3 },
   { name: '快递', value: 1 },
+  { name: '自提', value: 2 },
 ]
 
 const orderStatusList = [

+ 44 - 2
src/subPack-xsb/orderDetaile/index.vue

@@ -246,6 +246,15 @@ async function handleMarkerTap(e: UniHelper.MapOnMarkertapEvent) {
     }
   }
 }
+function openShopLocation() {
+  uni.openLocation({
+    latitude: orderInfo.value?.latitude || 0,
+    longitude: orderInfo.value?.longitude || 0,
+    name: orderInfo.value?.shopName,
+    address: orderInfo.value?.consigneeAddress,
+  })
+}
+
 async function handleReceive() {
   await useUserStore().handleCommonOrderReceive(orderInfo.value as Api.xsbOrderList)
   getDetail(String(unref(orderInfo)?.orderNumber))
@@ -405,7 +414,19 @@ function handleRefundDetail(item: any) {
         </view> -->
         </view>
       </view>
-      <view class="mt-20rpx rounded-16rpx bg-white p-24rpx">
+      <view v-if="orderInfo?.dvyType === 2" class="mt-20rpx flex flex-col items-center rounded-16rpx bg-white p-24rpx text-center">
+        <QCode class="mb-20rpx rounded-16rpx" :text="orderInfo?.selfPickCode" :qwidth="80" qr-key="0" />
+        <view class="text-28rpx font-semibold">
+          {{ orderInfo?.selfPickCode }}
+        </view>
+        <view class="text-28rpx" :class="orderInfo?.isWriteOff === 1 ? '' : 'line-through'">
+          {{ orderInfo?.isWriteOff === 1 ? '(未核销)' : '(已核销)' }}
+        </view>
+        <view class="mt-20rpx text-24rpx text-#AAA">
+          {{ orderInfo?.completeTime ? `核销时间:${orderInfo?.completeTime}` : '出示该二维码进行核销确认' }}
+        </view>
+      </view>
+      <view v-if="orderInfo?.dvyType === 3" class="mt-20rpx rounded-16rpx bg-white p-24rpx">
         <view class="flex items-center">
           <image :src="`${StaticUrl}/orderDetaile-user.png`" class="mr-20rpx h-40rpx w-40rpx" />
           <view class="text-32rpx text-[#222] font-semibold">
@@ -416,6 +437,27 @@ function handleRefundDetail(item: any) {
           {{ orderInfo?.consigneeAddress }}
         </view>
       </view>
+      <view v-if="orderInfo?.dvyType === 2" class="mt-20rpx rounded-16rpx bg-white p-24rpx">
+        <view class="flex items-center gap-50rpx">
+          <view class="flex-1">
+            <view class="text-28rpx font-semibold">
+              自提点:{{ orderInfo?.shopName }}
+            </view>
+            <view class="mt-20rpx text-24rpx text-#AAAAAA">
+              {{ orderInfo?.consigneeAddress }}
+            </view>
+          </view>
+          <view class="flex-shrink-0" @click="openShopLocation">
+            <wd-icon name="location" size="24px" color="#9ED605" />
+          </view>
+        </view>
+        <view class="mt-20rpx text-26rpx text-#AAA">
+          <view>联系人:{{ orderInfo?.consigneeName }}</view>
+          <view class="mt-20rpx">
+            预留电话:{{ orderInfo?.consigneeMobile }}
+          </view>
+        </view>
+      </view>
       <view class="mt-20rpx rounded-16rpx bg-white p-24rpx">
         <view class="flex items-center">
           <image :src="`${StaticUrl}/order-icon.png`" class="h-36rpx w-36rpx" />
@@ -457,7 +499,7 @@ function handleRefundDetail(item: any) {
             ¥{{ orderInfo?.total }}
           </view>
         </view>
-        <view class="mt-24rpx flex items-center justify-between">
+        <view v-if="orderInfo.dvyType != 2" class="mt-24rpx flex items-center justify-between">
           <view v-if="orderInfo.dvyType == 3" class="text-28rpx">
             配送费(即时配送)
           </view>