Bläddra i källkod

feat(coupon): 优化优惠券功能及支付流程

- 新增接口支持获取订单优惠券及新优惠券弹窗数据
- 在订单确认页支持优惠券选择,计算显示折扣金额与实付金额
- 优化下单接口支持传递选择的优惠券ID
- 新增优惠券到账弹窗组件并集成至首页,提升用户优惠券使用体验
- 优化购物车及商品详情页优惠券名称显示,支持活动名或优惠券名同时展示
- 调整优惠券组件支持隐藏操作按钮,适应选券模式
- 优化确认订单页展示逻辑及支付流程,修复部分逻辑异常
- 新增系统状态管理优惠券到账弹窗显隐状态
- 修复库存不足提示及下单过程异步状态处理
zhangtao 1 vecka sedan
förälder
incheckning
8b4c5d485a

+ 1 - 1
src/api/api.type.d.ts

@@ -1175,7 +1175,7 @@ namespace Api {
      */
     coupon: number
     couponName: string
-
+    orderCouponItemDTOS?: AppMemberCouponVO[]
     [property: string]: any
   }
   interface AppletOrderSkuVo {

+ 2 - 0
src/api/apiDefinitions.ts

@@ -66,6 +66,8 @@ export default {
   'xsb.refundDetails':['GET', '/smqjh-oms/app-api/v1/refund/findByDetails'],
   'xsb.popupConfig':['GET', '/smqjh-system/app-api/v1/appAdvertInfo/popupConfig'],
   'xsb.memberCouponPage':['GET', '/smqjh-system/app-api/memberCoupon/getPageList'],
+  'xsb.orderCoupons':['GET', '/smqjh-oms/api/v1/order/orderCoupons'],
+  'xsb.newCouponPopup':['GET', '/smqjh-system/sys-api/coupon/newCouponPopup'],
 
   'common.myShoppingCart':['GET', '/smqjh-oms/app-api/v1/shoppingCart/myShoppingCart'],
   'common.addShoppingCart':['POST', '/smqjh-oms/app-api/v1/shoppingCart/addShoppingCart'],

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

@@ -205,6 +205,23 @@ declare global {
       ): Alova2Method<listData<Api.sysDict>, 'sys.dictPage', Config>;
     }
     xsb: {
+      orderCoupons<
+        Config extends Alova2MethodConfig<apiResData<Api.AppMemberCouponVO[]>> & {
+          data: {
+            channelId: number;
+            num: number;
+            shopId: number;
+            skuId: number;
+          };
+        }
+      >(
+        config: Config
+      ): Alova2Method<apiResData<Api.AppMemberCouponVO[]>, 'xsb.orderCoupons', Config>;
+      newCouponPopup<
+        Config extends Alova2MethodConfig<apiResData<Api.AppMemberCouponVO[]>> & {}
+      >(
+        config: Config
+      ): Alova2Method<apiResData<Api.AppMemberCouponVO[]>, 'xsb.newCouponPopup', Config>;
       popupConfig<
         Config extends Alova2MethodConfig<apiResData<any>> & {}
       >(

+ 5 - 2
src/store/user.ts

@@ -156,17 +156,19 @@ export const useUserStore = defineStore('user', {
       })
     }, /**
         *
+        * @param freightFee
         * @param businessType
         * @param dvyType  配送类型 1:快递 2:自提 3:及时配送
-        * @param remarks
         * @param shopId
         * @param orderItemList
+        * @param remarks
+        * @param allowanceId
         * @returns 下单获取待支付订单号
         */
     getOrderPayMent(freightFee: number, businessType: string, dvyType: number, shopId: number, orderItemList: {
       prodCount?: number
       skuId?: number
-    }[], remarks?: string): Promise<string> {
+    }[], remarks?: string, allowanceId?: string): Promise<string> {
       uni.showLoading({ mask: true })
       return new Promise((resolve, reject) => {
         if (!this.selectedAddress) {
@@ -183,6 +185,7 @@ export const useUserStore = defineStore('user', {
             shopId,
             orderItemList,
             remarks,
+            allowanceIds: [allowanceId],
           },
         }).then((res) => {
           resolve(res.data)

+ 2 - 2
src/subPack-xsb/commonTab/components/cart.vue

@@ -193,9 +193,9 @@ onMounted(async () => {
               -¥{{ totalProduct?.coupon }}
             </view>
           </view>
-          <view v-if="totalProduct?.couponName" class="flex items-center justify-between pb-20rpx">
+          <view v-if="totalProduct?.activityName || totalProduct?.couponName" class="flex items-center justify-between pb-20rpx">
             <view class="text-24rpx text-#AAAAAA">
-              {{ totalProduct?.couponName }}
+              {{ totalProduct?.activityName || totalProduct?.couponName }}
             </view>
             <view class="text-24rpx text-#AAAAAA">
               -¥{{ totalProduct?.coupon }}

+ 2 - 2
src/subPack-xsb/commonTab/components/classfiy.vue

@@ -725,9 +725,9 @@ export default {
             </view>
           </view>
           <!-- 券名称子行 -->
-          <view v-if="totalProduct?.couponName" class="flex items-center justify-between pb-20rpx">
+          <view v-if="totalProduct?.activityName || totalProduct?.couponName" class="flex items-center justify-between pb-20rpx">
             <view class="text-24rpx text-#AAAAAA">
-              {{ totalProduct?.couponName }}
+              {{ totalProduct?.activityName || totalProduct?.couponName }}
             </view>
             <view class="text-24rpx text-#AAAAAA">
               -¥{{ totalProduct?.coupon }}

+ 96 - 0
src/subPack-xsb/commonTab/components/couponArrivalPopup.vue

@@ -0,0 +1,96 @@
+<script setup lang="ts">
+import { dayjs } from 'wot-design-uni'
+import { StaticUrl } from '@/config'
+
+const emit = defineEmits<{
+  use: [coupon: Api.AppMemberCouponVO]
+}>()
+const show = ref(false)
+
+const couponList = ref<Api.AppMemberCouponVO[]>([])
+
+function getCouponTitle(coupon: Api.AppMemberCouponVO) {
+  return coupon.activityName || coupon.couponName || '星闪豹优惠券'
+}
+
+function getCouponThreshold(coupon: Api.AppMemberCouponVO) {
+  return Number(coupon.amountMoney || 0) > 0 ? `满${coupon.amountMoney}可用` : '无门槛'
+}
+
+function getCouponExpireTime(coupon: Api.AppMemberCouponVO) {
+  if (!coupon.expirationTime) {
+    return '长期有效'
+  }
+  return dayjs(coupon.expirationTime).format('YYYY-MM-DD HH:mm:ss')
+}
+
+function handleUse(coupon: Api.AppMemberCouponVO) {
+  show.value = false
+  emit('use', coupon)
+}
+async function tryShowCouponArrivalPopup() {
+  try {
+    const res = await Apis.xsb.newCouponPopup({})
+    couponList.value = res.data ?? []
+    show.value = couponList.value.length > 0
+  }
+  catch (error) {
+    console.error('get coupon arrival popup failed', error)
+  }
+}
+tryShowCouponArrivalPopup()
+</script>
+
+<template>
+  <wd-overlay :show="show" :z-index="100001">
+    <view class="h-full w-full flex items-center justify-center">
+      <view :style="{ backgroundImage: `url(${StaticUrl}/xsb-coupon-idx-popup-bg.png)` }" class="relative h1078rpx w702rpx bg-contain bg-no-repeat" @click.stop>
+        <view class="absolute left-0 top-180rpx box-border h-full w-full flex justify-center px-66rpx pb-60rpx pt-184rpx">
+          <scroll-view scroll-y class="mt-110rpx box-border h-362rpx w500rpx flex justify-center px-20rpx pb-20rpx">
+            <view v-for="coupon in couponList" :key="coupon.id || coupon.allowanceId" :style="{ backgroundImage: `url(${StaticUrl}/xsb-coupon-idx-bg-popup.png)` }" class="relative mb-20rpx h-164rpx w450rpx bg-contain bg-no-repeat">
+              <view class="absolute left-0 top-0 box-border h-full w-full flex items-center px12rpx py24rpx">
+                <view class="h-full w-full flex items-center">
+                  <view class="box-border flex-shrink-0 px20rpx text-center">
+                    <view class="absolute left-0 top-0 inline-flex rounded-br-10rpx rounded-tl-10rpx bg-[#FF5A36] px-16rpx py-6rpx text-20rpx text-white">
+                      {{ Number(coupon.amountMoney) > 0 ? '满减券' : '无门槛' }}
+                    </view>
+                    <view class="mt-18rpx text-[#FF5331] font-bold">
+                      <text class="text-20rpx">
+                        ¥
+                      </text>
+                      <text class="text-40rpx leading-none">
+                        {{ coupon.discountMoney ?? 0 }}
+                      </text>
+                    </view>
+                    <view class="mt-10rpx text-24rpx text-[#666666]">
+                      {{ getCouponThreshold(coupon) }}
+                    </view>
+                  </view>
+                  <view class="box-border min-w-0 flex-1 pl30rpx">
+                    <view class="text-28rpx text-[#333333] font-semibold">
+                      {{ getCouponTitle(coupon) }}
+                    </view>
+                    <view class="mt-20rpx text-24rpx text-[#9A9A9A]">
+                      有效期:
+                    </view>
+                    <view class="mt-6rpx text-24rpx text-[#A8A8A8]">
+                      {{ getCouponExpireTime(coupon) }}
+                    </view>
+                    <view class="coupon-arrival-card__action absolute right-22rpx top-50% rounded-full bg-[#FF5736] px-24rpx py-12rpx text-24rpx text-white -translate-y-50%" @click.stop="handleUse(coupon)">
+                      去使用
+                    </view>
+                  </view>
+                </view>
+              </view>
+            </view>
+          </scroll-view>
+        </view>
+        <image :src="`${StaticUrl}/xsb-coupon-idx-kaixing-btn.png`" class="absolute bottom-90rpx left-50% h112rpx w384rpx transform -translate-x-50%" @click.stop="show = false" />
+      </view>
+    </view>
+  </wd-overlay>
+</template>
+
+<style scoped lang="scss">
+
+</style>

+ 19 - 2
src/subPack-xsb/commonTab/index.vue

@@ -1,4 +1,5 @@
 <script setup lang="ts">
+import CouponArrivalPopup from './components/couponArrivalPopup.vue'
 import indexHome from './components/index.vue'
 import cart from './components/cart.vue'
 import classfiy from './components/classfiy.vue'
@@ -22,7 +23,14 @@ definePage({
     disableScroll: true,
   },
 })
-const { opcity, backTop, SelectShopInfo, allShopHasPermission, xsbShopList } = storeToRefs(useSysXsbStore())
+const sysXsbStore = useSysXsbStore()
+const {
+  opcity,
+  backTop,
+  SelectShopInfo,
+  allShopHasPermission,
+  xsbShopList,
+} = storeToRefs(sysXsbStore)
 const { userInfo } = storeToRefs(useUserStore())
 const commonCategoryData = ref<Api.xsbCategories[]>([])
 const { data: goodsList, isLastPage, page, error, reload, refresh } = usePagination((pageNum, pageSize) =>
@@ -82,6 +90,10 @@ async function getSearchData(type: number) {
   return data
 }
 
+function handleCouponArrivalUse() {
+  handleChange('xsb-classfiy')
+}
+
 onMounted(async () => {
   allShopHasPermission.value = false
   await useSysXsbStore().getAllShopList()
@@ -93,7 +105,9 @@ onMounted(async () => {
   loading.value = false
   queryPopupConfig()
 })
-onShow(() => refresh())
+onShow(() => {
+  refresh()
+})
 
 watch(() => SelectShopInfo.value.shopId, () => {
   getCategories()
@@ -213,6 +227,9 @@ async function queryPopupConfig() {
         <image class="h-784rpx w-750rpx" :src="curtainInfo.imageUrl" />
       </view>
     </wd-overlay>
+    <CouponArrivalPopup
+      @use="handleCouponArrivalUse"
+    />
   </view>
 </template>
 

+ 63 - 57
src/subPack-xsb/components/coupon/index.vue

@@ -5,8 +5,10 @@ import router from '@/router'
 
 defineProps<{
   itemcoupon: Api.AppMemberCouponVO
-  /** 是否显示"去使用"按,默认 true */
+  /** 是否显示"去使用"按,默认 true */
   showUseBtn?: boolean
+  /** 隐藏所有操作按鈕(选券模式用) */
+  hideAllBtn?: boolean
 }>()
 function handleUseCoupon() {
   router.replace({ name: 'xsb-homeTabbar', params: { name: 'xsb-classfiy' } })
@@ -14,67 +16,71 @@ function handleUseCoupon() {
 </script>
 
 <template>
-  <view class="relative box-border h164rpx w-full flex overflow-hidden rounded-16rpx">
-    <image
-      v-if="itemcoupon.useStatus !== 2"
-      :src="`${StaticUrl}/xsb-coupon-bg-un.png`"
-      class="h-full w-full"
-    />
-    <image
-      v-else
-      :src="`${StaticUrl}/xsb-coupon-bg-item.png`"
-      class="h-full w-full"
-    />
-    <view class="absolute left-0 top-0 h-full w-full flex items-center">
-      <view class="w-184rpx flex flex-shrink-0 flex-col items-center justify-center py-36rpx">
-        <view class="flex items-end leading-none" :class="[itemcoupon.useStatus == 2 ? 'text-[#FF4A39]' : 'text-[#AAAAAA]']">
-          <text class="mb-6rpx text-24rpx font-semibold">
-            ¥
-          </text>
-          <text class="text-48rpx font-bold">
-            {{ itemcoupon.discountMoney ?? 0 }}
-          </text>
-        </view>
-        <view class="mt-12rpx text-24rpx text-[#AAAAAA]">
-          {{ Number(itemcoupon.amountMoney) > 0 ? `满${itemcoupon.amountMoney}可用` : '无门槛' }}
-        </view>
-      </view>
-      <view class="flex flex-1 items-center px-24rpx py-28rpx">
-        <view class="flex-1 pr-20rpx">
-          <view class="text-28rpx text-[#333] font-semibold">
-            {{ itemcoupon.activityName || '优惠券' }}
+  <view class="relative">
+    <view class="relative box-border h164rpx w-full flex overflow-hidden rounded-16rpx">
+      <image
+        v-if="itemcoupon.useStatus !== 2"
+        :src="`${StaticUrl}/xsb-coupon-bg-un.png`"
+        class="h-full w-full"
+      />
+      <image
+        v-else
+        :src="`${StaticUrl}/xsb-coupon-bg-item.png`"
+        class="h-full w-full"
+      />
+      <view class="absolute left-0 top-0 h-full w-full flex items-center">
+        <view class="w-184rpx flex flex-shrink-0 flex-col items-center justify-center py-36rpx">
+          <view class="flex items-end leading-none" :class="[itemcoupon.useStatus == 2 ? 'text-[#FF4A39]' : 'text-[#AAAAAA]']">
+            <text class="mb-6rpx text-24rpx font-semibold">
+              ¥
+            </text>
+            <text class="text-48rpx font-bold">
+              {{ itemcoupon.discountMoney ?? 0 }}
+            </text>
+          </view>
+          <view class="mt-12rpx text-24rpx text-[#AAAAAA]">
+            {{ Number(itemcoupon.amountMoney) > 0 ? `满${itemcoupon.amountMoney}可用` : '无门槛' }}
           </view>
-          <template v-if="[2, 5].includes(itemcoupon.useStatus || 2)">
-            <view v-if="itemcoupon.expirationTime" class="mt-12rpx text-22rpx text-[#AAAAAA]">
-              有效期:{{ dayjs(itemcoupon.expirationTime).format('YYYY-MM-DD') }}
+        </view>
+        <view class="flex flex-1 items-center px-24rpx py-28rpx">
+          <view class="flex-1 pr-20rpx">
+            <view class="text-28rpx text-[#333] font-semibold">
+              {{ itemcoupon.activityName || itemcoupon.couponName || '优惠券' }}
             </view>
-            <view v-if="itemcoupon.scopeDesc" class="mt-8rpx text-22rpx text-[#AAAAAA]">
-              限:{{ itemcoupon.scopeDesc }}
+            <template v-if="[2, 5].includes(itemcoupon.useStatus || 2)">
+              <view v-if="itemcoupon.expirationTime" class="mt-12rpx text-22rpx text-[#AAAAAA]">
+                有效期:{{ dayjs(itemcoupon.expirationTime).format('YYYY-MM-DD') }}
+              </view>
+              <view v-if="itemcoupon.scopeDesc" class="mt-8rpx text-22rpx text-[#AAAAAA]">
+                限:{{ itemcoupon.scopeDesc }}
+              </view>
+            </template>
+            <view v-if="itemcoupon.orderCreateTime" class="mt-8rpx text-22rpx text-[#AAAAAA]">
+              使用时间: {{ itemcoupon.orderCreateTime }}
+            </view>
+            <view v-if="itemcoupon.useStatus === 1" class="mt-8rpx text-22rpx text-[#AAAAAA]">
+              说明:部分退款,优惠卷不退还
+            </view>
+            <view v-if="itemcoupon.useStatus === 7" class="mt-8rpx text-22rpx text-[#AAAAAA]">
+              冻结原因:该券已被占用,取消可恢复,有效期内可用。
             </view>
-          </template>
-          <view v-if="itemcoupon.orderCreateTime" class="mt-8rpx text-22rpx text-[#AAAAAA]">
-            使用时间: {{ itemcoupon.orderCreateTime }}
-          </view>
-          <view v-if="itemcoupon.useStatus === 1" class="mt-8rpx text-22rpx text-[#AAAAAA]">
-            说明:部分退款,优惠卷不退还
-          </view>
-          <view v-if="itemcoupon.useStatus === 7" class="mt-8rpx text-22rpx text-[#AAAAAA]">
-            冻结原因:该券已被占用,取消可恢复,有效期内可用。
           </view>
+          <template v-if="!hideAllBtn">
+            <view
+              v-if="showUseBtn !== false"
+              class="flex-shrink-0 rounded-full bg-[#FF4A39] px-28rpx py-16rpx text-26rpx text-white"
+              @click="handleUseCoupon"
+            >
+              去使用
+            </view>
+            <wd-button v-if="itemcoupon.useStatus === 1" hairline plain size="small" type="info" @click="router.replace({ name: 'xsb-orderDetaile', params: { id: String(itemcoupon.lockOrderId) } })">
+              查看订单
+            </wd-button>
+            <wd-button v-if="itemcoupon.useStatus === 7" plain hairline size="small" type="info" @click="router.replace({ name: 'xsb-orderDetaile', params: { id: String(itemcoupon.lockOrderId) } })">
+              去取消订单
+            </wd-button>
+          </template>
         </view>
-        <view
-          v-if="showUseBtn !== false"
-          class="flex-shrink-0 rounded-full bg-[#FF4A39] px-28rpx py-16rpx text-26rpx text-white"
-          @click="handleUseCoupon"
-        >
-          去使用
-        </view>
-        <wd-button v-if="itemcoupon.useStatus === 1" plain hairline size="small" type="info" @click="router.replace({ name: 'xsb-orderDetaile', params: { id: String(itemcoupon.lockOrderId) } })">
-          查看订单
-        </wd-button>
-        <wd-button v-if="itemcoupon.useStatus === 7" hairline plain size="small" type="info" @click="router.replace({ name: 'xsb-orderDetaile', params: { id: String(itemcoupon.lockOrderId) } })">
-          去取消订单
-        </wd-button>
       </view>
     </view>
   </view>

+ 188 - 23
src/subPack-xsb/confirmOrder/index.vue

@@ -1,4 +1,5 @@
 <script setup lang="ts">
+import CouponItem from '../components/coupon/index.vue'
 import router from '@/router'
 
 definePage({
@@ -14,18 +15,63 @@ const orderInfo = ref<Api.shoppingCartOrderConfirm>()
 const { totalProduct } = storeToRefs(useSmqjhCartStore())
 const isPay = ref(false)
 const remarks = ref('')
+const initialCouponId = ref<string | undefined>(undefined)
+const confirmedCouponId = ref<string | undefined>(undefined)
+const draftCouponId = ref<string | undefined>(undefined)
 
+const deliveryType = ref(1)
+
+// 优惠券选择
+const couponPopup = ref(false)
+const couponList = ref<Api.AppMemberCouponVO[]>([])
+const couponTabActive = ref(0)
+const availableCoupons = computed(() => couponList.value.filter(it => it.useStatus === 2))
+const unavailableCoupons = computed(() => couponList.value.filter(it => it.useStatus !== 2))
+const displayCoupons = computed(() => couponTabActive.value === 0 ? availableCoupons.value : unavailableCoupons.value)
+const confirmedCoupon = computed(() => couponList.value.find(it => it.allowanceId === confirmedCouponId.value))
+const draftCoupon = computed(() => couponList.value.find(it => it.allowanceId === draftCouponId.value))
+const hasManualCouponSelection = computed(() => confirmedCouponId.value !== initialCouponId.value)
+const currentCouponDiscount = computed(() => {
+  if (hasManualCouponSelection.value) {
+    return Number(confirmedCoupon.value?.discountMoney || 0)
+  }
+  return Number(orderInfo.value?.coupon || 0)
+})
 const offsetPoints = computed(() => {
-  const money = Math.round(Number(unref(orderInfo)?.transfee) * 100) + Math.round(Number(unref(orderInfo)?.price) * 100)
-  if (Number(unref(orderInfo)?.offsetPoints) > money) {
-    return money
+  const couponCents = Math.round(currentCouponDiscount.value * 100)
+  const money = Math.round(Number(unref(orderInfo)?.transfee) * 100) + Math.round(Number(unref(orderInfo)?.price) * 100) - couponCents
+  const cap = Math.max(0, money)
+  if (Number(unref(orderInfo)?.offsetPoints) > cap) {
+    return cap
   }
   return Number(unref(orderInfo)?.offsetPoints)
 })
-const deliveryType = ref(1)
+const currentCouponName = computed(() => {
+  if (hasManualCouponSelection.value) {
+    return getCouponDisplayName(confirmedCoupon.value)
+  }
+  return getCouponDisplayName(orderInfo.value)
+})
+const displayTotalPrice = computed(() => {
+  if (!orderInfo.value) {
+    return 0
+  }
+  if (!hasManualCouponSelection.value) {
+    return Number(orderInfo.value.totalPrice || 0)
+  }
+  const goodsPrice = Number(orderInfo.value.price || 0)
+  const freight = Number(orderInfo.value.transfee || 0)
+  const pointsDiscount = offsetPoints.value / 100
+  return Math.max(0, Number((goodsPrice + freight - currentCouponDiscount.value - pointsDiscount).toFixed(2)))
+})
+const displayDiscountTotal = computed(() => Number((currentCouponDiscount.value + offsetPoints.value / 100).toFixed(2)))
+
 onLoad((options: any) => {
-  console.log(options.data)
   orderInfo.value = JSON.parse(options.data)
+  couponList.value = orderInfo.value?.orderCouponItemDTOS || []
+  initialCouponId.value = orderInfo.value?.allowanceId
+  confirmedCouponId.value = initialCouponId.value
+  draftCouponId.value = initialCouponId.value
 })
 onShow(() => {
   useUserStore().getuserAddresslist()
@@ -34,11 +80,15 @@ onShow(() => {
   }
 })
 
+function getCouponDisplayName(coupon?: Partial<Api.AppMemberCouponVO> & Record<string, any>) {
+  return coupon?.activityName || coupon?.couponName || ''
+}
+
 async function getDevryList() {
   const res = await Apis.xsb.delivery({
     data: {
       memberId: userInfo.value.id,
-      shopId: SelectShopInfo.value.shopId,
+      shopId: Number(orderInfo.value?.skuList[0].shopId || SelectShopInfo.value?.shopId || 2),
       addressId: selectedAddress.value?.id,
     },
   })
@@ -46,6 +96,23 @@ async function getDevryList() {
   console.log(res)
 }
 
+async function openCouponPopup() {
+  draftCouponId.value = confirmedCouponId.value
+  couponPopup.value = true
+}
+
+function handleSelectCoupon(item: Api.AppMemberCouponVO) {
+  if (item.useStatus !== 2 || !item.allowanceId) {
+    return
+  }
+  draftCouponId.value = item.allowanceId
+}
+
+function confirmCoupon() {
+  confirmedCouponId.value = draftCouponId.value
+  couponPopup.value = false
+}
+
 async function handlePay() {
   if (!selectedAddress.value) {
     useGlobalToast().show({ msg: '请选择收货地址' })
@@ -64,11 +131,18 @@ async function handlePay() {
       }
     })
 
-    const orderNumber = await useUserStore().getOrderPayMent(orderInfo.value.transfee, 'XSB', deliveryType.value, Number(orderInfo.value?.skuList[0].shopId || SelectShopInfo.value.shopId), orderItemList, unref(remarks))
+    const orderNumber = await useUserStore().getOrderPayMent(
+      orderInfo.value.transfee,
+      'XSB',
+      deliveryType.value,
+      Number(orderInfo.value?.skuList[0].shopId || SelectShopInfo.value?.shopId),
+      orderItemList,
+      unref(remarks),
+      confirmedCouponId.value,
+    )
     const res = await useUserStore().handleCommonPayMent(orderNumber)
     await useUserStore().clearCart(orderInfo.value.skuList)
     totalProduct.value = null
-    console.log('进入微信支付', res)
     if (res.payType !== 1) {
       try {
         await useUserStore().getWxCommonPayment(res)
@@ -90,7 +164,10 @@ async function handlePay() {
 
 <template>
   <view class="page px20rpx py20rpx">
-    <view class="mb20rpx rounded-16rpx bg-white p24rpx" @click="router.push({ name: 'common-addressList', params: { type: 'select' } })">
+    <view
+      class="mb20rpx rounded-16rpx bg-white p24rpx"
+      @click="router.push({ name: 'common-addressList', params: { type: 'select' } })"
+    >
       <view class="flex items-center justify-between">
         <view v-if="!selectedAddress" class="flex items-center">
           <wd-icon name="location" size="18px" />
@@ -128,10 +205,7 @@ async function handlePay() {
       <CollapsePanel :line-height="150">
         <view v-for="item in orderInfo?.skuList" :key="item.id" class="mb20rpx w-full flex items-center">
           <view class="mr20rpx w120rpx flex-shrink-0">
-            <image
-              :src="item.pic"
-              class="h120rpx w120rpx"
-            />
+            <image :src="item.pic" class="h120rpx w120rpx" />
           </view>
           <view class="flex-1">
             <view class="w-full flex items-center justify-between font-semibold">
@@ -170,6 +244,18 @@ async function handlePay() {
           ¥{{ orderInfo?.transfee }}
         </view>
       </view>
+      <view class="mb28rpx flex items-center justify-between text-28rpx" @click="openCouponPopup">
+        <view>优惠券</view>
+        <view class="flex items-center">
+          <view v-if="currentCouponDiscount > 0" class="text-[#FF4D3A] font-semibold">
+            -¥{{ currentCouponDiscount }}
+          </view>
+          <view v-else class="text-[#AAAAAA]">
+            {{ availableCoupons.length > 0 ? `${availableCoupons.length}张可用` : '暂无可用' }}
+          </view>
+          <wd-icon name="arrow-right" size="18px" color="#aaa" class="ml10rpx" />
+        </view>
+      </view>
       <view class="flex items-center justify-between text-28rpx">
         <view>积分({{ offsetPoints }})</view>
         <view class="text-#FF4D3A font-semibold">
@@ -182,7 +268,7 @@ async function handlePay() {
           总计:
         </view>
         <view class="text-#FF4D3A font-semibold">
-          ¥ {{ orderInfo?.totalPrice }}
+          ¥ {{ displayTotalPrice }}
         </view>
       </view>
     </view>
@@ -195,19 +281,94 @@ async function handlePay() {
       </view>
     </view>
     <view class="h250rpx" />
+
+    <!-- 优惠券选择弹窗 -->
+    <Zpopup v-model="couponPopup" :zindex="100" bg="#fff">
+      <view class="ios box-border w-full">
+        <view class="px-32rpx pt-32rpx">
+          <view class="mb-24rpx text-center text-32rpx font-semibold">
+            优惠券
+          </view>
+          <view class="mb-24rpx flex items-center">
+            <view
+              class="mr-16rpx flex-1 rounded-full py-18rpx text-center text-28rpx"
+              :class="couponTabActive === 0 ? 'bg-[#9ED605] text-white font-semibold' : 'bg-[#F0F0F0] text-[#666]'"
+              @click="couponTabActive = 0"
+            >
+              可用券({{ availableCoupons.length }})
+            </view>
+            <view
+              class="flex-1 rounded-full py-18rpx text-center text-28rpx"
+              :class="couponTabActive === 1 ? 'bg-[#9ED605] text-white font-semibold' : 'bg-[#F0F0F0] text-[#666]'"
+              @click="couponTabActive = 1"
+            >
+              不可用券({{ unavailableCoupons.length }})
+            </view>
+          </view>
+          <view class="mb-20rpx flex items-center justify-between text-24rpx">
+            <view class="text-[#333]">
+              优惠券共减
+            </view>
+            <view v-if="draftCoupon" class="text-right text-[#FF4D3A]">
+              {{ getCouponDisplayName(draftCoupon) }},可减-¥{{ draftCoupon.discountMoney }}
+            </view>
+            <view v-else-if="currentCouponDiscount > 0 && currentCouponName" class="text-right text-[#FF4D3A]">
+              {{ currentCouponName }},可减-¥{{ currentCouponDiscount }}
+            </view>
+            <view v-else class="text-[#AAAAAA]">
+              暂未选择
+            </view>
+          </view>
+        </view>
+        <scroll-view scroll-y class="box-border h-560rpx px-32rpx">
+          <view v-for="item in displayCoupons" :key="item.id" class="relative mb-20rpx">
+            <view class="relative" @click="handleSelectCoupon(item)">
+              <CouponItem :itemcoupon="item" :hide-all-btn="true" />
+              <view
+                class="absolute right-32rpx top-50% h-44rpx w-44rpx flex items-center justify-center -translate-y-50%"
+              >
+                <wd-icon v-if="draftCouponId === item.allowanceId" name="check-circle" size="44rpx" color="#FF4D3A" />
+                <view v-else class="h-40rpx w-40rpx border-2rpx border-[#ccc] rounded-full border-solid" />
+              </view>
+            </view>
+            <view
+              v-if="item.unavailableReason"
+              class="relative z-1 w-full rounded-b-16rpx bg-[#FFF4F3] px20rpx py18rpx text-24rpx -mt16rpx"
+            >
+              <view>
+                不可用原因
+              </view>
+              <view>
+                {{ item.unavailableReason }}
+              </view>
+            </view>
+          </view>
+          <view v-if="displayCoupons.length === 0" class="py-80rpx text-center text-28rpx text-[#AAAAAA]">
+            暂无优惠券
+          </view>
+        </scroll-view>
+      </view>
+      <template #footer>
+        <view class="box-border w-full px24rpx">
+          <wd-button size="large" block type="primary" @click="confirmCoupon">
+            确定
+          </wd-button>
+        </view>
+      </template>
+    </Zpopup>
     <view class="ios footer fixed bottom-0 left-0 box-border w-full rounded-t-16rpx bg-white px24rpx">
       <view class="box-border w-full flex items-center justify-between py20rpx">
         <view class="flex items-center text-#FF4D3A">
           <view class="font-semibold10 flex items-baseline text-36rpx">
             <text class="text-24rpx">
-            </text> {{ orderInfo?.totalPrice }}
+            </text> {{ displayTotalPrice }}
           </view>
           <view class="ml20rpx text-22rpx">
-            共减¥{{ offsetPoints / 100 }}
+            共减¥{{ displayDiscountTotal }}
           </view>
         </view>
-        <view class="w180rpx">
+        <view class="w180-btn w180rpx">
           <wd-button size="large" :loading="isPay" loading-color="#9ED605" @click="handlePay">
             立即支付
           </wd-button>
@@ -218,14 +379,18 @@ async function handlePay() {
 </template>
 
 <style scoped lang="scss">
-  .footer{
-    box-shadow: 0rpx -6rpx 12rpx 2rpx rgba(0,0,0,0.05);
-  }
-  .page{
-    :deep(){
-      .wd-button{
+.footer {
+  box-shadow: 0rpx -6rpx 12rpx 2rpx rgba(0, 0, 0, 0.05);
+}
+
+.page {
+  .w180-btn {
+    :deep() {
+      .wd-button {
         width: 180rpx !important;
       }
     }
   }
+
+}
 </style>

+ 20 - 16
src/subPack-xsb/goods/index.vue

@@ -113,24 +113,28 @@ async function handleConfimOrder() {
   }
   await useUserStore().checkLogin()
   isLoging.value = true
-  const res = await Apis.xsb.skuOrderConfirm({
-    data: {
-      skuId: specId.value,
-      num: SelectGoodsNum.value,
-      channelId: unref(userInfo).channelId,
-      shopId: unref(goodsInfo)?.shopId || 2,
-    },
-  })
-  if (Number(res.data.sku?.shopSkuStocks) < unref(SelectGoodsNum)) {
-    useGlobalToast().show('库存不足,请调整购买数量')
+  try {
+    const res = await Apis.xsb.skuOrderConfirm({
+      data: {
+        skuId: specId.value,
+        num: SelectGoodsNum.value,
+        channelId: unref(userInfo).channelId,
+        shopId: unref(goodsInfo)?.shopId || 2,
+      },
+    })
+    if (Number(res.data.sku?.shopSkuStocks) < unref(SelectGoodsNum)) {
+      useGlobalToast().show('库存不足,请调整购买数量')
+      isLoging.value = false
+      return
+    }
+    router.push({
+      name: 'xsb-confirmOrder',
+      params: { data: JSON.stringify({ ...res.data, skuList: [{ ...res.data.sku, num: SelectGoodsNum.value, shopId: unref(goodsInfo)?.shopId || 2 }] }) },
+    })
+  }
+  finally {
     isLoging.value = false
-    return
   }
-  router.push({
-    name: 'xsb-confirmOrder',
-    params: { data: JSON.stringify({ ...res.data, skuList: [{ ...res.data.sku, num: SelectGoodsNum.value, shopId: unref(goodsInfo)?.shopId || 2 }] }) },
-  })
-  isLoging.value = false
 }
 async function handleAddCart() {
   if (Number(unref(goodsInfo)?.spuStock) < unref(SelectGoodsNum)) {

+ 5 - 0
src/subPack-xsb/store-xsb/sys.ts

@@ -33,6 +33,10 @@ interface SysState {
    * 是否所有门店都有权限
    */
   allShopHasPermission: boolean
+  /**
+   * 优惠券到账弹窗是否显示
+   */
+  couponArrivalPopupVisible: boolean
 
 }
 export const useSysXsbStore = defineStore('system-xsb', {
@@ -46,6 +50,7 @@ export const useSysXsbStore = defineStore('system-xsb', {
     opcity: 0,
     selectAddressId: undefined,
     allShopHasPermission: false,
+    couponArrivalPopupVisible: false,
   }),
   actions: {
     getTabbarItemValue(name: string) {