소스 검색

【功能】批量修改bug和功能

Xiao_123 9 달 전
부모
커밋
782baf9a30

+ 10 - 2
pages/activity/index.vue

@@ -7,7 +7,7 @@
         <view class="type-text ss-flex ss-row-center">满减:</view>
         <view class="ss-flex-1">
           <view class="tip-content" v-for="item in state.activityInfo.rules" :key="item">
-            {{ formatRewardActivityRule(state.activityInfo, item) }}
+            {{ item.description }}
           </view>
         </view>
         <image class="activity-left-image" src="/static/activity-left.png" />
@@ -65,8 +65,9 @@
   import sheep from '@/sheep';
   import _ from 'lodash-es';
   import RewardActivityApi from '@/sheep/api/promotion/rewardActivity';
-  import { formatRewardActivityRule } from '@/sheep/hooks/useGoods';
   import SpuApi from '@/sheep/api/product/spu';
+  import { appendSettlementProduct } from '@/sheep/hooks/useGoods';
+  import OrderApi from '@/sheep/api/trade/order';
 
   const state = reactive({
     activityId: 0, // 获得编号
@@ -123,6 +124,13 @@
     if (code !== 0) {
       return;
     }
+    // 拼接结算信息(营销)
+    await OrderApi.getSettlementProduct(data.list.map((item) => item.id).join(',')).then((res) => {
+      if (res.code !== 0) {
+        return;
+      }
+      appendSettlementProduct(data.list, res.data);
+    });
     state.pagination.list = _.concat(state.pagination.list, data.list);
     state.pagination.total = data.total;
     state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore';

+ 137 - 61
pages/activity/seckill/list.vue

@@ -3,13 +3,20 @@
   <s-layout navbar="inner" :bgStyle="{ color: 'rgb(245,28,19)' }">
     <!--顶部背景图-->
     <view
-        class="page-bg"
-        :style="[{ marginTop: '-' + Number(statusBarHeight + 88) + 'rpx' }]"
+      class="page-bg"
+      :style="[{ marginTop: '-' + Number(statusBarHeight + 88) + 'rpx' }]"
     ></view>
     <!-- 时间段轮播图 -->
     <view class="header" v-if="activeTimeConfig?.sliderPicUrls?.length > 0">
-      <swiper indicator-dots="true" autoplay="true" :circular="true" interval="3000" duration="1500"
-              indicator-color="rgba(255,255,255,0.6)" indicator-active-color="#fff">
+      <swiper
+        indicator-dots="true"
+        autoplay="true"
+        :circular="true"
+        interval="3000"
+        duration="1500"
+        indicator-color="rgba(255,255,255,0.6)"
+        indicator-active-color="#fff"
+      >
         <block v-for="(picUrl, index) in activeTimeConfig.sliderPicUrls" :key="index">
           <swiper-item class="borRadius14">
             <image :src="picUrl" class="slide-image borRadius14" lazy-load />
@@ -22,17 +29,28 @@
       <!-- 左侧图标 -->
       <view class="time-icon">
         <!-- TODO 芋艿:图片统一维护 -->
-        <image class="ss-w-100 ss-h-100" src="http://mall.yudao.iocoder.cn/static/images/priceTag.png" />
+        <image
+          class="ss-w-100 ss-h-100"
+          src="http://mall.yudao.iocoder.cn/static/images/priceTag.png"
+        />
       </view>
-      <scroll-view class="time-list" :scroll-into-view="activeTimeElId" scroll-x scroll-with-animation>
-        <view v-for="(config, index) in timeConfigList" :key="index"
-              :class="['item', { active: activeTimeIndex === index}]"
-              :id="`timeItem${index}`"
-              @tap="handleChangeTimeConfig(index)">
+      <scroll-view
+        class="time-list"
+        :scroll-into-view="activeTimeElId"
+        scroll-x
+        scroll-with-animation
+      >
+        <view
+          v-for="(config, index) in timeConfigList"
+          :key="index"
+          :class="['item', { active: activeTimeIndex === index }]"
+          :id="`timeItem${index}`"
+          @tap="handleChangeTimeConfig(index, config.id)"
+        >
           <!-- 活动起始时间 -->
           <view class="time">{{ config.startTime }}</view>
           <!-- 活动状态 -->
-          <view class="status">{{ config.status }}</view>
+          <view class="status">{{ config?.status }}</view>
         </view>
       </scroll-view>
     </view>
@@ -42,7 +60,10 @@
       <!-- 活动倒计时 -->
       <view class="content-header ss-flex-col ss-col-center ss-row-center">
         <view class="content-header-box ss-flex ss-row-center">
-          <view class="countdown-box ss-flex" v-if="activeTimeConfig?.status === TimeStatusEnum.STARTED">
+          <view
+            class="countdown-box ss-flex"
+            v-if="activeTimeConfig?.status === TimeStatusEnum.STARTED"
+          >
             <view class="countdown-title ss-m-r-12">距结束</view>
             <view class="ss-flex countdown-time">
               <view class="ss-flex countdown-h">{{ countDown.h }}</view>
@@ -70,19 +91,44 @@
             :data="{ ...activity, price: activity.seckillPrice }"
             :goodsFields="goodsFields"
             :seckillTag="true"
-            @click="sheep.$router.go('/pages/goods/seckill', { id: activity.id })"
           >
             <!-- 抢购进度 -->
             <template #activity>
-              <view class="limit">限量 <text class="ss-m-l-5">{{ activity.stock}} {{activity.unitName}}</text></view>
+              <view class="limit">
+                限量
+                <text class="ss-m-l-5">{{ activity.stock }} {{ activity.unitName }}</text>
+              </view>
               <su-progress :percentage="activity.percent" strokeWidth="10" textInside isAnimate />
             </template>
             <!-- 抢购按钮 -->
             <template #cart>
-              <button :class="['ss-reset-button cart-btn', { disabled: activeTimeConfig.status === TimeStatusEnum.END }]">
-                <span v-if="activeTimeConfig?.status === TimeStatusEnum.WAIT_START">未开始</span>
-                <span v-else-if="activeTimeConfig?.status === TimeStatusEnum.STARTED">马上抢</span>
-                <span v-else>已结束</span>
+              <button
+                :class="[
+                  'ss-reset-button cart-btn',
+                  { disabled: activeTimeConfig?.status === TimeStatusEnum.END },
+                ]"
+                v-if="activeTimeConfig?.status === TimeStatusEnum.WAIT_START"
+              >
+                <span>未开始</span>
+              </button>
+              <button
+                :class="[
+                  'ss-reset-button cart-btn',
+                  { disabled: activeTimeConfig?.status === TimeStatusEnum.END },
+                ]"
+                @click="sheep.$router.go('/pages/goods/seckill', { id: activity.id })"
+                v-else-if="activeTimeConfig?.status === TimeStatusEnum.STARTED"
+              >
+                <span>马上抢</span>
+              </button>
+              <button
+                :class="[
+                  'ss-reset-button cart-btn',
+                  { disabled: activeTimeConfig?.status === TimeStatusEnum.END },
+                ]"
+                v-else
+              >
+                <span>已结束</span>
               </button>
             </template>
           </s-goods-column>
@@ -100,40 +146,51 @@
   </s-layout>
 </template>
 <script setup>
-  import {reactive, computed, ref, nextTick} from 'vue';
+  import { reactive, computed, ref, nextTick } from 'vue';
   import { onLoad, onReachBottom } from '@dcloudio/uni-app';
   import sheep from '@/sheep';
   import { useDurationTime } from '@/sheep/hooks/useGoods';
-  import SeckillApi from "@/sheep/api/promotion/seckill";
-  import dayjs from "dayjs";
-  import {TimeStatusEnum} from "@/sheep/util/const";
+  import SeckillApi from '@/sheep/api/promotion/seckill';
+  import dayjs from 'dayjs';
+  import { TimeStatusEnum } from '@/sheep/util/const';
 
   // 计算页面高度
   const { safeAreaInsets, safeArea } = sheep.$platform.device;
   const statusBarHeight = sheep.$platform.device.statusBarHeight * 2;
-  const pageHeight = (safeArea.height + safeAreaInsets.bottom) * 2 + statusBarHeight - sheep.$platform.navbar - 350;
+  const pageHeight =
+    (safeArea.height + safeAreaInsets.bottom) * 2 + statusBarHeight - sheep.$platform.navbar - 350;
   const headerBg = sheep.$url.css('/static/img/shop/goods/seckill-header.png');
 
   // 商品控件显示的字段(不显示库存、销量。改为显示自定义的进度条)
   const goodsFields = {
-    name: { show: true },
-    introduction: { show: true },
-    price: { show: true },
-    marketPrice: { show: true },
+    name: {
+      show: true,
+    },
+    introduction: {
+      show: true,
+    },
+    price: {
+      show: true,
+    },
+    marketPrice: {
+      show: true,
+    },
   };
 
   //#region 时间段
   // 时间段列表
-  const timeConfigList = ref([])
+  const timeConfigList = ref([]);
   // 查询时间段
   const getSeckillConfigList = async () => {
-    const { data } = await SeckillApi.getSeckillConfigList()
+    const { data } = await SeckillApi.getSeckillConfigList();
     const now = dayjs();
-    const today = now.format('YYYY-MM-DD')
+    const today = now.format('YYYY-MM-DD');
+    const select = ref([]);
     // 判断时间段的状态
     data.forEach((config, index) => {
-      const startTime = dayjs(`${today} ${config.startTime}`)
-      const endTime = dayjs(`${today} ${config.endTime}`)
+      const startTime = dayjs(`${today} ${config.startTime}`);
+      const endTime = dayjs(`${today} ${config.endTime}`);
+      select.value[index] = config.id;
       if (now.isBefore(startTime)) {
         config.status = TimeStatusEnum.WAIT_START;
       } else if (now.isAfter(endTime)) {
@@ -142,35 +199,36 @@
         config.status = TimeStatusEnum.STARTED;
         activeTimeIndex.value = index;
       }
-    })
-    timeConfigList.value = data
+    });
+    timeConfigList.value = data;
     // 默认选中进行中的活动
-    handleChangeTimeConfig(activeTimeIndex.value);
+    handleChangeTimeConfig(activeTimeIndex.value, select.value[activeTimeIndex.value]);
     // 滚动到进行中的时间段
-    scrollToTimeConfig(activeTimeIndex.value)
-  }
+    scrollToTimeConfig(activeTimeIndex.value);
+  };
 
   // 滚动到指定时间段
-  const activeTimeElId = ref('') // 当前选中的时间段的元素ID
+  const activeTimeElId = ref(''); // 当前选中的时间段的元素ID
   const scrollToTimeConfig = (index) => {
-    nextTick(() => activeTimeElId.value = `timeItem${index}`)
-  }
+    nextTick(() => (activeTimeElId.value = `timeItem${index}`));
+  };
 
   // 切换时间段
-  const activeTimeIndex = ref(0) // 当前选中的时间段的索引
-  const activeTimeConfig = computed(() => timeConfigList.value[activeTimeIndex.value]) // 当前选中的时间段
-  const handleChangeTimeConfig = (index) => {
-    activeTimeIndex.value = index
+  const activeTimeIndex = ref(0); // 当前选中的时间段的索引
+  const activeTimeConfig = computed(() => timeConfigList.value[activeTimeIndex.value]); // 当前选中的时间段
+  const handleChangeTimeConfig = (index, id) => {
+    activeTimeIndex.value = index;
 
     // 查询活动列表
-    activityPageParams.pageNo = 1
-    activityList.value = []
+    activityPageParams.pageNo = 1;
+    activityPageParams.configId = id;
+    activityList.value = [];
     getActivityList();
-  }
+  };
 
   // 倒计时
   const countDown = computed(() => {
-    const endTime = activeTimeConfig.value?.endTime
+    const endTime = activeTimeConfig.value?.endTime;
     if (endTime) {
       return useDurationTime(`${dayjs().format('YYYY-MM-DD')} ${endTime}`);
     }
@@ -182,20 +240,22 @@
 
   // 查询活动列表
   const activityPageParams = reactive({
-    id: 0, // 时间段 ID
+    configId: 0, // 时间段 ID
     pageNo: 1, // 页码
     pageSize: 5, // 每页数量
-  })
-  const activityTotal = ref(0) // 活动总数
-  const activityList = ref([]) // 活动列表
-  const loadStatus = ref('') // 页面加载状态
+  });
+  const activityTotal = ref(0); // 活动总数
+  const activityList = ref([]); // 活动列表
+  const loadStatus = ref(''); // 页面加载状态
   async function getActivityList() {
     loadStatus.value = 'loading';
-    const { data } = await SeckillApi.getSeckillActivityPage(activityPageParams)
-    data.list.forEach(activity => {
+    const { data } = await SeckillApi.getSeckillActivityPage(activityPageParams);
+    data.list.forEach((activity) => {
       // 计算抢购进度
-      activity.percent = parseInt(100 * (activity.totalStock - activity.stock) / activity.totalStock);
-    })
+      activity.percent = parseInt(
+        (100 * (activity.totalStock - activity.stock)) / activity.totalStock,
+      );
+    });
     activityList.value = activityList.value.concat(...data.list);
     activityTotal.value = data.total;
 
@@ -205,7 +265,7 @@
   // 加载更多
   function loadMore() {
     if (loadStatus.value !== 'noMore') {
-      activityPageParams.pageNo += 1
+      activityPageParams.pageNo += 1;
       getActivityList();
     }
   }
@@ -216,7 +276,7 @@
 
   // 页面初始化
   onLoad(async () => {
-    await getSeckillConfigList()
+    await getSeckillConfigList();
   });
 </script>
 <style lang="scss" scoped>
@@ -235,7 +295,8 @@
     margin: -276rpx auto 0 auto;
     border-radius: 14rpx;
     overflow: hidden;
-    swiper{
+
+    swiper {
       height: 330rpx !important;
       border-radius: 14rpx;
       overflow: hidden;
@@ -246,7 +307,8 @@
       height: 100%;
       border-radius: 14rpx;
       overflow: hidden;
-      img{
+
+      img {
         border-radius: 14rpx;
       }
     }
@@ -257,10 +319,12 @@
     width: 75rpx;
     height: 70rpx;
   }
+
   // 时间段列表
   .time-list {
     width: 596rpx;
     white-space: nowrap;
+
     // 时间段
     .item {
       display: inline-block;
@@ -270,17 +334,20 @@
       box-sizing: border-box;
       margin-right: 30rpx;
       width: 130rpx;
+
       // 开始时间
       .time {
         font-size: 36rpx;
         font-weight: 600;
         color: #333;
       }
+
       // 选中的时间段
       &.active {
         .time {
           color: var(--ui-BG-Main);
         }
+
         // 状态
         .status {
           height: 30rpx;
@@ -301,6 +368,7 @@
     margin: 0 20rpx 0 20rpx;
     background: #fff;
     border-radius: 20rpx 20rpx 0 0;
+
     .content-header {
       width: 100%;
       border-radius: 20rpx 20rpx 0 0;
@@ -312,6 +380,7 @@
         height: 64rpx;
         background: rgba($color: #fff, $alpha: 0.66);
         border-radius: 32px;
+
         // 场次倒计时内容
         .countdown-title {
           font-size: 28rpx;
@@ -319,10 +388,12 @@
           color: #333333;
           line-height: 28rpx;
         }
+
         // 场次倒计时
         .countdown-time {
           font-size: 28rpx;
           color: rgba(#ed3c30, 0.23);
+
           // 场次倒计时:小时部分
           .countdown-h {
             font-size: 24rpx;
@@ -334,6 +405,7 @@
             background: rgba(#ed3c30, 0.23);
             border-radius: 6rpx;
           }
+
           // 场次倒计时:分钟、秒
           .countdown-num {
             font-size: 24rpx;
@@ -348,12 +420,15 @@
         }
       }
     }
+
     // 活动列表
     .scroll-box {
       height: 900rpx;
+
       // 活动
       .goods-box {
         position: relative;
+
         // 抢购按钮
         .cart-btn {
           position: absolute;
@@ -373,6 +448,7 @@
             color: #fff;
           }
         }
+
         // 秒杀限量商品数
         .limit {
           font-size: 22rpx;

+ 1 - 1
pages/coupon/detail.vue

@@ -33,7 +33,7 @@
               @click="getCoupon"
             >
               <text v-if="state.id > 0">{{ state.coupon.canTake ? '立即领取' : '已领取' }}</text>
-              <text v-else>{{ state.coupon.status === 1 ? '立即使用' : state.coupon.status === 2 ? '已使用' : '已过期' }}</text>
+              <text v-else>{{ state.coupon.status === 1 ? '使用' : state.coupon.status === 2 ? '已使用' : '已过期' }}</text>
             </button>
             <view class="time ss-m-y-30" v-if="state.coupon.validityType === 2">
               有效期:领取后 {{ state.coupon.fixedEndTerm }} 天内可用

+ 611 - 349
pages/goods/index.vue

@@ -1,175 +1,303 @@
 <template>
-	<view>
-		<s-layout :onShareAppMessage="shareInfo" navbar="goods">
-			<!-- 标题栏 -->
-			<detailNavbar />
-
-			<!-- 骨架屏 -->
-			<detailSkeleton v-if="state.skeletonLoading" />
-			<!-- 下架/售罄提醒 -->
-			<s-empty v-else-if="state.goodsInfo === null" text="商品不存在或已下架" icon="/static/soldout-empty.png" showAction
-               actionText="再逛逛" actionUrl="/pages/goods/list" />
-			<block v-else>
-				<view class="detail-swiper-selector">
-					<!-- 商品轮播图  -->
-					<su-swiper class="ss-m-b-14" isPreview :list="formatGoodsSwiper(state.goodsInfo.sliderPicUrls)"
-                     otStyle="tag" imageMode="widthFix" dotCur="bg-mask-40" :seizeHeight="750" />
-
-					<!-- 价格+标题 -->
-					<view class="title-card detail-card ss-p-y-40 ss-p-x-20">
-						<view class="ss-flex ss-row-between ss-col-center ss-m-b-26">
-							<view class="price-box ss-flex ss-col-bottom">
-								<view class="price-text ss-m-r-16">
-									{{ fen2yuan(state.selectedSku.price || state.goodsInfo.price) }}
-								</view>
-								<view class="origin-price-text" v-if="state.goodsInfo.marketPrice > 0">
-									{{ fen2yuan(state.selectedSku.marketPrice || state.goodsInfo.marketPrice) }}
-								</view>
-							</view>
-							<view class="sales-text">
-								{{ formatSales('exact', state.goodsInfo.salesCount) }}
-							</view>
-						</view>
-						<view class="discounts-box ss-flex ss-row-between ss-m-b-28">
-							<!-- 满减送/限时折扣活动的提示 -->
-							<div class="tag-content">
-								<view class="tag-box ss-flex">
-									<view class="tag ss-m-r-10" v-for="promos in state.activityInfo"
-                        :key="promos.id" @tap="onActivity">
-										{{ promos.name }}
-									</view>
-								</view>
-							</div>
-
-							<!-- 优惠劵 -->
-							<view class="get-coupon-box ss-flex ss-col-center ss-m-l-20" @tap="state.showModel = true"
-								v-if="state.couponInfo.length">
-								<view class="discounts-title ss-m-r-8">领券</view>
-								<text class="cicon-forward"></text>
-							</view>
-						</view>
-						<view class="title-text ss-line-2 ss-m-b-6">{{ state.goodsInfo.name }}</view>
-						<view class="subtitle-text ss-line-1">{{ state.goodsInfo.introduction }}</view>
-					</view>
-
-					<!-- 功能卡片 -->
-					<view class="detail-cell-card detail-card ss-flex-col">
-						<detail-cell-sku v-model="state.selectedSku.goods_sku_text" :sku="state.selectedSku"
-                             @tap="state.showSelectSku = true" />
-					</view>
-
-					<!-- 规格与数量弹框 -->
-					<s-select-sku :goodsInfo="state.goodsInfo" :show="state.showSelectSku" @addCart="onAddCart"
-                        @buy="onBuy" @change="onSkuChange" @close="state.showSelectSku = false" />
-				</view>
-
-				<!-- 评价 -->
-				<detail-comment-card class="detail-comment-selector" :goodsId="state.goodsId" />
-				<!-- 详情 -->
-				<detail-content-card class="detail-content-selector" :content="state.goodsInfo.description" />
-
-				<!-- 活动跳转:拼团/秒杀/砍价活动 -->
-				<detail-activity-tip v-if="state.activityList.length > 0" :activity-list="state.activityList" />
-
-				<!-- 详情 tabbar -->
-				<detail-tabbar v-model="state.goodsInfo">
-					<view class="buy-box ss-flex ss-col-center ss-p-r-20" v-if="state.goodsInfo.stock > 0">
-						<button class="ss-reset-button add-btn ui-Shadow-Main" @tap="state.showSelectSku = true">
-							加入购物车
-						</button>
-						<button class="ss-reset-button buy-btn ui-Shadow-Main" @tap="state.showSelectSku = true">
-							立即购买
-						</button>
-					</view>
-					<view class="buy-box ss-flex ss-col-center ss-p-r-20" v-else>
-						<button class="ss-reset-button disabled-btn" disabled> 已售罄 </button>
-					</view>
-				</detail-tabbar>
-
-				<!-- 优惠劵弹窗 -->
-				<s-coupon-get v-model="state.couponInfo" :show="state.showModel" @close="state.showModel = false"
-                      @get="onGet" />
-
-				<!-- 满减送/限时折扣活动弹窗 -->
-				<s-activity-pop v-model="state.activityInfo" :show="state.showActivityModel"
-                        @close="state.showActivityModel = false" />
-			</block>
-		</s-layout>
-	</view>
+  <view>
+    <s-layout :onShareAppMessage="shareInfo" navbar="goods">
+      <!-- 标题栏 -->
+      <detailNavbar />
+
+      <!-- 骨架屏 -->
+      <detailSkeleton v-if="state.skeletonLoading" />
+      <!-- 下架/售罄提醒 -->
+      <s-empty
+        v-else-if="state.goodsInfo === null"
+        text="商品不存在或已下架"
+        icon="/static/soldout-empty.png"
+        showAction
+        actionText="再逛逛"
+        actionUrl="/pages/goods/list"
+      />
+      <block v-else>
+        <view class="detail-swiper-selector">
+          <!-- 商品轮播图  -->
+          <su-swiper
+            class="ss-m-b-14"
+            isPreview
+            :list="formatGoodsSwiper(state.goodsInfo.sliderPicUrls)"
+            otStyle="tag"
+            imageMode="widthFix"
+            dotCur="bg-mask-40"
+            :seizeHeight="750"
+          />
+          <!-- 限时折扣/会员价的优惠信息 -->
+          <view
+            class="discount"
+            v-if="
+              state.settlementSku && state.settlementSku.id && state.settlementSku.promotionPrice
+            "
+          >
+            <image class="disImg" :src="sheep.$url.static('/static/img/shop/goods/dis.png')" />
+            <view class="discountCont">
+              <view class="disContT">
+                <view class="disContT1">
+                  <view class="disContT1P">
+                    ¥{{ fen2yuan(state.settlementSku.promotionPrice) }}
+                  </view>
+                  <view class="disContT1End">
+                    直降¥
+                    {{ fen2yuan(state.settlementSku.price - state.settlementSku.promotionPrice) }}
+                  </view>
+                </view>
+                <view class="disContT2" v-if="state.settlementSku.promotionType === 4">
+                  限时折扣
+                </view>
+                <view class="disContT2" v-else-if="state.settlementSku.promotionType === 6">
+                  会员折扣
+                </view>
+              </view>
+              <view class="disContB">
+                <view class="disContB1">
+                  价格:¥{{ fen2yuan(state.settlementSku.price) }} 丨 剩余:
+                  {{ state.settlementSku.stock }}
+                </view>
+                <view class="disContB2" v-if="state.settlementSku.promotionEndTime > 0">
+                  距结束仅剩
+                  <countDown
+                    :tipText="' '"
+                    :bgColor="bgColor"
+                    :dayText="':'"
+                    :hourText="':'"
+                    :minuteText="':'"
+                    :secondText="' '"
+                    :datatime="state.settlementSku.promotionEndTime / 1000"
+                    :isDay="false"
+                  />
+                </view>
+              </view>
+            </view>
+          </view>
+          <!-- 价格+标题 -->
+          <view class="title-card detail-card ss-p-y-30 ss-p-x-20">
+            <!-- 没有限时折扣/会员价的优惠信息时,展示的价格信息 -->
+            <view
+              class="ss-flex ss-row-between ss-col-center ss-m-b-26"
+              v-if="!state.settlementSku.promotionPrice"
+            >
+              <view class="price-box ss-flex ss-col-bottom">
+                <view class="price-text ss-m-r-16">
+                  {{ fen2yuan(state.selectedSku.price || state.goodsInfo.price) }}
+                </view>
+                <view class="origin-price-text" v-if="state.goodsInfo.marketPrice > state.goodsInfo.price">
+                  {{ fen2yuan(state.selectedSku.marketPrice || state.goodsInfo.marketPrice) }}
+                </view>
+              </view>
+              <view class="sales-text">
+                {{ formatSales('exact', state.goodsInfo.salesCount) }}
+              </view>
+            </view>
+            <view class="discounts-box ss-flex ss-row-between ss-m-b-28">
+              <!-- 查看优惠劵的描述 -->
+              <view
+                class="tag ss-m-r-10"
+                v-for="coupon in state.couponInfo.slice(0, 1)"
+                :key="coupon.id"
+                @tap="onOpenActivity"
+              >
+                [劵]满{{ fen2yuanSimple(coupon.usePrice) }}元{{
+                  coupon.discountType === 1
+                    ? '减' + fen2yuanSimple(coupon.discountPrice) + '元'
+                    : '打' + formatDiscountPercent(coupon.discountPercent) + '折'
+                }}
+              </view>
+              <!-- 查看满减送的描述 -->
+              <div class="tag-content">
+                <view class="tag-box ss-flex">
+                  <!-- 最多打印 3 条,所以需要扣除优惠劵已打印的 -->
+                  <view
+                    v-for="item in getRewardActivityRuleItemDescriptions(
+                      state.rewardActivity,
+                    ).slice(0, 3 - state.couponInfo.slice(0, 1).length)"
+                    :key="item"
+                    class="tag ss-m-r-10"
+                    @tap="onOpenActivity"
+                  >
+                    <text>{{ item }}</text>
+                  </view>
+                </view>
+              </div>
+              <!-- 领取优惠劵的按钮 -->
+              <view
+                class="get-coupon-box ss-flex ss-col-center ss-m-l-20"
+                @tap="onOpenActivity"
+                v-if="state.couponInfo.length"
+              >
+                <view class="discounts-title ss-m-r-8">领券</view>
+                <text class="cicon-forward"></text>
+              </view>
+            </view>
+            <view class="title-text ss-line-2 ss-m-b-6">{{ state.goodsInfo.name }}</view>
+            <view class="subtitle-text ss-line-1">{{ state.goodsInfo.introduction }}</view>
+          </view>
+
+          <!-- 功能卡片 -->
+          <view class="detail-cell-card detail-card ss-flex-col">
+            <detail-cell-sku
+              v-model="state.selectedSku.goods_sku_text"
+              :sku="state.selectedSku"
+              @tap="state.showSelectSku = true"
+            />
+          </view>
+
+          <!-- 规格与数量弹框 -->
+          <s-select-sku
+            :goodsInfo="state.goodsInfo"
+            :show="state.showSelectSku"
+            @addCart="onAddCart"
+            @buy="onBuy"
+            @change="onSkuChange"
+            @close="state.showSelectSku = false"
+          />
+        </view>
+
+        <!-- 评价 -->
+        <detail-comment-card class="detail-comment-selector" :goodsId="state.goodsId" />
+        <!-- 详情 -->
+        <detail-content-card
+          class="detail-content-selector"
+          :content="state.goodsInfo.description"
+        />
+
+        <!-- 活动跳转:拼团/秒杀/砍价活动 -->
+        <detail-activity-tip
+          v-if="state.activityList.length > 0"
+          :activity-list="state.activityList"
+        />
+
+        <!-- 详情 tabbar -->
+        <detail-tabbar v-model="state.goodsInfo">
+          <view class="buy-box ss-flex ss-col-center ss-p-r-20" v-if="state.goodsInfo.stock > 0">
+            <button
+              class="ss-reset-button add-btn ui-Shadow-Main"
+              @tap="state.showSelectSku = true"
+            >
+              加入购物车
+            </button>
+            <button
+              class="ss-reset-button buy-btn ui-Shadow-Main"
+              @tap="state.showSelectSku = true"
+            >
+              立即购买
+            </button>
+          </view>
+          <view class="buy-box ss-flex ss-col-center ss-p-r-20" v-else>
+            <button class="ss-reset-button disabled-btn" disabled> 已售罄 </button>
+          </view>
+        </detail-tabbar>
+
+        <!-- 满减送/限时折扣活动弹窗 -->
+        <s-activity-pop
+          v-model="state"
+          :show="state.showActivityModel"
+          @close="state.showActivityModel = false"
+          @get="onTakeCoupon"
+        />
+      </block>
+    </s-layout>
+  </view>
 </template>
 
 <script setup>
-	import { reactive, computed } from 'vue';
-	import { onLoad, onPageScroll } from '@dcloudio/uni-app';
-	import sheep from '@/sheep';
-	import CouponApi from '@/sheep/api/promotion/coupon';
-	import ActivityApi from '@/sheep/api/promotion/activity';
+  import { reactive, computed, ref, toRaw } from 'vue';
+  import { onLoad, onPageScroll } from '@dcloudio/uni-app';
+  import sheep from '@/sheep';
+  import CouponApi from '@/sheep/api/promotion/coupon';
+  import ActivityApi from '@/sheep/api/promotion/activity';
   import FavoriteApi from '@/sheep/api/product/favorite';
-  import { formatSales, formatGoodsSwiper, fen2yuan } from '@/sheep/hooks/useGoods';
-	import detailNavbar from './components/detail/detail-navbar.vue';
-	import detailCellSku from './components/detail/detail-cell-sku.vue';
-	import detailTabbar from './components/detail/detail-tabbar.vue';
-	import detailSkeleton from './components/detail/detail-skeleton.vue';
-	import detailCommentCard from './components/detail/detail-comment-card.vue';
-	import detailContentCard from './components/detail/detail-content-card.vue';
-	import detailActivityTip from './components/detail/detail-activity-tip.vue';
-	import { isEmpty } from 'lodash-es';
+  import RewardActivityApi from '@/sheep/api/promotion/rewardActivity';
+  import {
+    formatSales,
+    formatGoodsSwiper,
+    fen2yuan,
+    fen2yuanSimple,
+    formatDiscountPercent,
+    getRewardActivityRuleItemDescriptions,
+  } from '@/sheep/hooks/useGoods';
+  import detailNavbar from './components/detail/detail-navbar.vue';
+  import detailCellSku from './components/detail/detail-cell-sku.vue';
+  import detailTabbar from './components/detail/detail-tabbar.vue';
+  import detailSkeleton from './components/detail/detail-skeleton.vue';
+  import detailCommentCard from './components/detail/detail-comment-card.vue';
+  import detailContentCard from './components/detail/detail-content-card.vue';
+  import detailActivityTip from './components/detail/detail-activity-tip.vue';
+  import { isEmpty } from 'lodash-es';
   import SpuApi from '@/sheep/api/product/spu';
 
-	onPageScroll(() => {});
-
+  onPageScroll(() => {});
+  import countDown from '@/sheep/components/countDown/index.vue';
+  import OrderApi from '@/sheep/api/trade/order';
+  import activity from '@/sheep/api/promotion/activity';
+
+  const bgColor = {
+    bgColor: '#E93323',
+    Color: '#fff',
+    width: '44rpx',
+    timeTxtwidth: '16rpx',
+    isDay: true,
+  };
   const isLogin = computed(() => sheep.$store('user').isLogin);
   const state = reactive({
-		goodsId: 0,
-		skeletonLoading: true, // SPU 加载中
-		goodsInfo: {}, // SPU 信息
-		showSelectSku: false, // 是否展示 SKU 选择弹窗
-		selectedSku: {}, // 选中的 SKU
-		showModel: false, // 是否展示 Coupon 优惠劵的弹窗
-		couponInfo: [], // 可领取的 Coupon 优惠劵的列表
-		showActivityModel: false, // 【满减送/限时折扣】是否展示 Activity 营销活动的弹窗
-		activityInfo: [], // 【满减送/限时折扣】可参与的 Activity 营销活动的列表
-		activityList: [], // 【秒杀/拼团/砍价】可参与的 Activity 营销活动的列表
-	});
-
-	// 规格变更
-	function onSkuChange(e) {
-		state.selectedSku = e;
-	}
-
-	// 添加购物车
-	function onAddCart(e) {
+    goodsId: 0,
+    skeletonLoading: true, // SPU 加载中
+    goodsInfo: {}, // SPU 信息
+    showSelectSku: false, // 是否展示 SKU 选择弹窗
+    selectedSku: {}, // 选中的 SKU
+    settlementSku: {}, // 结算的 SKU:由于 selectedSku 不进行默认选中,所以初始使用结算价格最低的 SKU 作为基础展示
+    showModel: false, // 是否展示 Coupon 优惠劵的弹窗
+    couponInfo: [], // 可领取的 Coupon 优惠劵的列表
+    showActivityModel: false, // 【满减送/限时折扣】是否展示 Activity 营销活动的弹窗
+    rewardActivity: {}, // 【满减送】活动
+    activityList: [], // 【秒杀/拼团/砍价】可参与的 Activity 营销活动的列表
+  });
+
+  // 规格变更
+  function onSkuChange(e) {
+    state.selectedSku = e;
+    state.settlementSku = e;
+  }
+
+  // 添加购物车
+  function onAddCart(e) {
     if (!e.id) {
       sheep.$helper.toast('请选择商品规格');
       return;
     }
-		sheep.$store('cart').add(e);
-	}
+    sheep.$store('cart').add(e);
+  }
 
-	// 立即购买
-	function onBuy(e) {
-    if (!state.selectedSku.id) {
+  // 立即购买
+  function onBuy(e) {
+    if (!e.id) {
       sheep.$helper.toast('请选择商品规格');
       return;
     }
     sheep.$router.go('/pages/order/confirm', {
       data: JSON.stringify({
-        items: [{
-          skuId: e.id,
-          count: e.goods_num,
-		      categoryId: state.goodsInfo.categoryId
-        }]
+        items: [
+          {
+            skuId: e.id,
+            count: e.goods_num,
+            categoryId: state.goodsInfo.categoryId,
+          },
+        ],
       }),
     });
-	}
+  }
 
-	// 营销活动
-	function onActivity() {
-		state.showActivityModel = true;
-	}
+  // 打开营销弹窗
+  function onOpenActivity() {
+    state.showActivityModel = true;
+  }
 
-	// 立即领取
-	async function onGet(id) {
+  // 立即领取优惠劵
+  async function onTakeCoupon(id) {
     const { code } = await CouponApi.takeCoupon(id);
     if (code !== 0) {
       return;
@@ -180,27 +308,29 @@
     setTimeout(() => {
       getCoupon();
     }, 1000);
-	}
-
-	//  TODO 芋艿:待测试
-	const shareInfo = computed(() => {
-		if (isEmpty(state.goodsInfo)) return {};
-		return sheep.$platform.share.getShareInfo({
-			title: state.goodsInfo.name,
-			image: sheep.$url.cdn(state.goodsInfo.picUrl),
-			desc: state.goodsInfo.introduction,
-			params: {
-				page: '2',
-				query: state.goodsInfo.id,
-			},
-		}, {
-			type: 'goods', // 商品海报
-			title: state.goodsInfo.name, // 商品名称
-			image: sheep.$url.cdn(state.goodsInfo.picUrl), // 商品主图
-			price: fen2yuan(state.goodsInfo.price), // 商品价格
-			original_price: fen2yuan(state.goodsInfo.marketPrice), // 商品原价
-		});
-	});
+  }
+
+  const shareInfo = computed(() => {
+    if (isEmpty(state.goodsInfo)) return {};
+    return sheep.$platform.share.getShareInfo(
+      {
+        title: state.goodsInfo.name,
+        image: sheep.$url.cdn(state.goodsInfo.picUrl),
+        desc: state.goodsInfo.introduction,
+        params: {
+          page: '2',
+          query: state.goodsInfo.id,
+        },
+      },
+      {
+        type: 'goods', // 商品海报
+        title: state.goodsInfo.name, // 商品名称
+        image: sheep.$url.cdn(state.goodsInfo.picUrl), // 商品主图
+        price: fen2yuan(state.goodsInfo.price), // 商品价格
+        original_price: fen2yuan(state.goodsInfo.marketPrice), // 商品原价
+      },
+    );
+  });
 
   async function getCoupon() {
     const { code, data } = await CouponApi.getCouponTemplateList(state.goodsId, 2, 10);
@@ -209,24 +339,65 @@
     }
   }
 
-	onLoad((options) => {
-		// 非法参数
-		if (!options.id) {
-			state.goodsInfo = null;
-			return;
-		}
-		state.goodsId = options.id;
-		// 1. 加载商品信息
-		SpuApi.getSpuDetail(state.goodsId).then((res) => {
-			// 未找到商品
-			if (res.code !== 0 || !res.data) {
-				state.goodsInfo = null;
-				return;
-			}
-			// 加载到商品
-			state.skeletonLoading = false;
-			state.goodsInfo = res.data;
+  async function getSettlementByIds(ids) {
+    let { data, code } = await OrderApi.getSettlementProduct(ids);
+    if (code !== 0 || data.length !== 1) {
+      return;
+    }
+    data = data[0];
+
+    // 补充 SKU 的价格信息
+    state.goodsInfo.skus.forEach((sku) => {
+      data.skus.forEach((item) => {
+        if (sku.id === item.id) {
+          sku.promotionType = item.promotionType;
+          sku.promotionPrice = item.promotionPrice;
+          sku.promotionId = item.promotionId;
+          sku.promotionEndTime = item.promotionEndTime;
+        }
+      });
+    });
+
+    // 选择有 promotionPrice 且最小的
+    state.settlementSku = state.goodsInfo.skus
+      .filter((sku) => sku.stock > 0 && sku.promotionPrice > 0)
+      .reduce((prev, curr) => (prev.promotionPrice < curr.promotionPrice ? prev : curr), []);
+
+    // 设置满减送活动
+    if (data.rewardActivity) {
+      state.rewardActivity = data.rewardActivity;
+      //获取活动时间
+      getActivityTime(state.rewardActivity.id);
+    }
+  }
+
+  //获取活动时间
+  async function getActivityTime(id) {
+    const { code, data } = await RewardActivityApi.getRewardActivity(id);
+    if (code === 0) {
+      // console.log('获取到的活动 数据', data)
+      state.rewardActivity.startTime = data.startTime;
+      state.rewardActivity.endTime = data.endTime;
+    }
+  }
 
+  onLoad((options) => {
+    // 非法参数
+    if (!options.id) {
+      state.goodsInfo = null;
+      return;
+    }
+    state.goodsId = options.id;
+    // 1. 加载商品信息
+    SpuApi.getSpuDetail(state.goodsId).then((res) => {
+      // 未找到商品
+      if (res.code !== 0 || !res.data) {
+        state.goodsInfo = null;
+        return;
+      }
+      // 加载到商品
+      state.skeletonLoading = false;
+      state.goodsInfo = res.data;
       // 加载是否收藏
       if (isLogin.value) {
         FavoriteApi.isFavoriteExists(state.goodsId, 'goods').then((res) => {
@@ -236,172 +407,263 @@
           state.goodsInfo.favorite = res.data;
         });
       }
-		});
+    });
 
-		// 2. 加载优惠劵信息
+    // 2. 加载优惠劵信息
     getCoupon();
 
-		// 3. 加载营销活动信息
-		ActivityApi.getActivityListBySpuId(state.goodsId).then((res) => {
-			if (res.code !== 0) {
-				return;
-			}
-      res.data.forEach(activity => {
-        if ([1, 2, 3].includes(activity.type)) { // 情况一:拼团/秒杀/砍价
-          state.activityList.push(activity);
-        } else if (activity.type === 5) { // 情况二:满减送
-          state.activityInfo.push(activity);
-        } else { // 情况三:限时折扣 TODO 芋艿
-          console.log('待实现!优先级不高');
-        }
-      })
-		});
-	});
+    // 3. 加载营销活动信息
+    ActivityApi.getActivityListBySpuId(state.goodsId).then((res) => {
+      if (res.code !== 0) {
+        return;
+      }
+      state.activityList = res.data;
+    });
+    //获取结算信息
+    getSettlementByIds(state.goodsId);
+  });
 </script>
 
 <style lang="scss" scoped>
-	.detail-card {
-		background-color: #ffff;
-		margin: 14rpx 20rpx;
-		border-radius: 10rpx;
-		overflow: hidden;
-	}
-
-	// 价格标题卡片
-	.title-card {
-		.price-box {
-			.price-text {
-				font-size: 42rpx;
-				font-weight: 500;
-				color: #ff3000;
-				line-height: 30rpx;
-				font-family: OPPOSANS;
-
-				&::before {
-					content: '¥';
-					font-size: 30rpx;
-				}
-			}
-
-			.origin-price-text {
-				font-size: 26rpx;
-				font-weight: 400;
-				text-decoration: line-through;
-				color: $gray-c;
-				font-family: OPPOSANS;
-
-				&::before {
-					content: '¥';
-				}
-			}
-		}
-
-		.sales-text {
-			font-size: 26rpx;
-			font-weight: 500;
-			color: $gray-c;
-		}
-
-		.discounts-box {
-			.tag-content {
-				flex: 1;
-				min-width: 0;
-				white-space: nowrap;
-			}
-
-			.tag-box {
-				overflow: hidden;
-				text-overflow: ellipsis;
-			}
-
-			.tag {
-				flex-shrink: 0;
-				padding: 4rpx 10rpx;
-				font-size: 24rpx;
-				font-weight: 500;
-				border-radius: 4rpx;
-				color: var(--ui-BG-Main);
-				background: var(--ui-BG-Main-tag);
-			}
-
-			.discounts-title {
-				font-size: 24rpx;
-				font-weight: 500;
-				color: var(--ui-BG-Main);
-				line-height: normal;
-			}
-
-			.cicon-forward {
-				color: var(--ui-BG-Main);
-				font-size: 24rpx;
-				line-height: normal;
-				margin-top: 4rpx;
-			}
-		}
-
-		.title-text {
-			font-size: 30rpx;
-			font-weight: bold;
-			line-height: 42rpx;
-		}
-
-		.subtitle-text {
-			font-size: 26rpx;
-			font-weight: 400;
-			color: $dark-9;
-			line-height: 42rpx;
-		}
-	}
-
-	// 购买
-	.buy-box {
-		.add-btn {
-			width: 214rpx;
-			height: 72rpx;
-			font-weight: 500;
-			font-size: 28rpx;
-			border-radius: 40rpx 0 0 40rpx;
-			background-color: var(--ui-BG-Main-light);
-			color: var(--ui-BG-Main);
-		}
-
-		.buy-btn {
-			width: 214rpx;
-			height: 72rpx;
-			font-weight: 500;
-			font-size: 28rpx;
-
-			border-radius: 0 40rpx 40rpx 0;
-			background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
-			color: $white;
-		}
-
-		.disabled-btn {
-			width: 428rpx;
-			height: 72rpx;
-			border-radius: 40rpx;
-			background: #999999;
-			color: $white;
-		}
-	}
-
-	.model-box {
-		height: 60vh;
-
-		.model-content {
-			height: 56vh;
-		}
-
-		.title {
-			font-size: 36rpx;
-			font-weight: bold;
-			color: #333333;
-		}
-
-		.subtitle {
-			font-size: 26rpx;
-			font-weight: 500;
-			color: #333333;
-		}
-	}
+  .detail-card {
+    background-color: #ffff;
+    margin: 14rpx 20rpx;
+    border-radius: 10rpx;
+    overflow: hidden;
+  }
+
+  // 价格标题卡片
+  .title-card {
+    .price-box {
+      .price-text {
+        font-size: 42rpx;
+        font-weight: 500;
+        color: #ff3000;
+        line-height: 30rpx;
+        font-family: OPPOSANS;
+
+        &::before {
+          content: '¥';
+          font-size: 30rpx;
+        }
+      }
+
+      .origin-price-text {
+        font-size: 26rpx;
+        font-weight: 400;
+        text-decoration: line-through;
+        color: $gray-c;
+        font-family: OPPOSANS;
+
+        &::before {
+          content: '¥';
+        }
+      }
+    }
+
+    .sales-text {
+      font-size: 26rpx;
+      font-weight: 500;
+      color: $gray-c;
+    }
+
+    .discounts-box {
+      .tag-content {
+        flex: 1;
+        min-width: 0;
+        white-space: nowrap;
+      }
+
+      .tag-box {
+        overflow: hidden;
+        text-overflow: ellipsis;
+      }
+
+      .tag {
+        flex-shrink: 0;
+        padding: 4rpx 10rpx;
+        font-size: 24rpx;
+        font-weight: 500;
+        border-radius: 4rpx;
+        color: var(--ui-BG-Main);
+        background: var(--ui-BG-Main-tag);
+      }
+
+      .discounts-title {
+        font-size: 24rpx;
+        font-weight: 500;
+        color: var(--ui-BG-Main);
+        line-height: normal;
+      }
+
+      .cicon-forward {
+        color: var(--ui-BG-Main);
+        font-size: 24rpx;
+        line-height: normal;
+        margin-top: 4rpx;
+      }
+    }
+
+    .title-text {
+      font-size: 30rpx;
+      font-weight: bold;
+      line-height: 42rpx;
+    }
+
+    .subtitle-text {
+      font-size: 26rpx;
+      font-weight: 400;
+      color: $dark-9;
+      line-height: 42rpx;
+    }
+  }
+
+  // 购买
+  .buy-box {
+    .add-btn {
+      width: 214rpx;
+      height: 72rpx;
+      font-weight: 500;
+      font-size: 28rpx;
+      border-radius: 40rpx 0 0 40rpx;
+      background-color: var(--ui-BG-Main-light);
+      color: var(--ui-BG-Main);
+    }
+
+    .buy-btn {
+      width: 214rpx;
+      height: 72rpx;
+      font-weight: 500;
+      font-size: 28rpx;
+
+      border-radius: 0 40rpx 40rpx 0;
+      background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
+      color: $white;
+    }
+
+    .disabled-btn {
+      width: 428rpx;
+      height: 72rpx;
+      border-radius: 40rpx;
+      background: #999999;
+      color: $white;
+    }
+  }
+
+  .model-box {
+    height: 60vh;
+
+    .model-content {
+      height: 56vh;
+    }
+
+    .title {
+      font-size: 36rpx;
+      font-weight: bold;
+      color: #333333;
+    }
+
+    .subtitle {
+      font-size: 26rpx;
+      font-weight: 500;
+      color: #333333;
+    }
+  }
+
+  // 限时折扣
+  .discount {
+    width: 750rpx;
+    height: 100rpx;
+    // background-color: red;
+    overflow: hidden;
+    position: relative;
+  }
+
+  .disImg {
+    width: 750rpx;
+    height: 100rpx;
+    position: absolute;
+    top: 0;
+    z-index: -1;
+  }
+
+  .discountCont {
+    width: 680rpx;
+    height: 90rpx;
+    margin: 10rpx auto 0 auto;
+    // background-color: gold;
+  }
+
+  .disContT {
+    width: 680rpx;
+    height: 50rpx;
+    display: flex;
+    justify-content: space-between;
+  }
+
+  .disContT1 {
+    width: 400rpx;
+    height: 50rpx;
+    // background-color: green;
+    display: flex;
+    justify-content: flex-start;
+    align-items: center;
+  }
+
+  .disContT2 {
+    width: 200rpx;
+    height: 50rpx;
+    line-height: 50rpx;
+    // background-color: gold;
+    font-size: 30rpx;
+    text-align: end;
+    color: white;
+    font-weight: bolder;
+    font-style: oblique 20deg;
+    letter-spacing: 0.1rem;
+  }
+
+  .disContT1P {
+    color: white;
+    font-weight: bold;
+    font-size: 28rpx;
+  }
+
+  .disContT1End {
+    // width: 180rpx;
+    padding: 0 10rpx;
+    height: 30rpx;
+    line-height: 28rpx;
+    text-align: center;
+    font-weight: bold;
+    background-color: white;
+    color: #ff3000;
+    font-size: 23rpx;
+    border-radius: 20rpx;
+    margin-left: 10rpx;
+  }
+
+  .disContB {
+    width: 680rpx;
+    height: 40rpx;
+    display: flex;
+    justify-content: space-between;
+    font-size: 20rpx;
+    color: white;
+    align-items: center;
+  }
+
+  .disContB1 {
+    width: 300rpx;
+    height: 40rpx;
+    line-height: 40rpx;
+  }
+
+  .disContB2 {
+    width: 300rpx;
+    height: 40rpx;
+    line-height: 40rpx;
+    display: flex;
+    justify-content: flex-end;
+  }
 </style>

+ 10 - 1
pages/goods/list.vue

@@ -118,12 +118,14 @@
 </template>
 
 <script setup>
-  import { reactive } from 'vue';
+  import { reactive, ref } from 'vue';
   import { onLoad, onReachBottom } from '@dcloudio/uni-app';
   import sheep from '@/sheep';
   import _ from 'lodash-es';
   import { resetPagination } from '@/sheep/util';
   import SpuApi from '@/sheep/api/product/spu';
+  import OrderApi from '@/sheep/api/trade/order';
+  import { appendSettlementProduct } from '@/sheep/hooks/useGoods';
 
   const sys_navBar = sheep.$platform.navbar;
   const emits = defineEmits(['close', 'change']);
@@ -277,6 +279,13 @@
     if (code !== 0) {
       return;
     }
+    // 拼接结算信息(营销)
+    await OrderApi.getSettlementProduct(data.list.map((item) => item.id).join(',')).then((res) => {
+      if (res.code !== 0) {
+        return;
+      }
+      appendSettlementProduct(data.list, res.data);
+    });
     state.pagination.list = _.concat(state.pagination.list, data.list);
     state.pagination.total = data.total;
     state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore';

+ 10 - 3
pages/goods/seckill.vue

@@ -7,7 +7,9 @@
     <detailSkeleton v-if="state.skeletonLoading" />
     <!-- 下架/售罄提醒 -->
     <s-empty
-      v-else-if="state.goodsInfo === null || state.goodsInfo.activity_type !== 'seckill'"
+      v-else-if="
+        state.goodsInfo === null || state.goodsInfo.activity_type !== 'seckill' || endTime.ms <= 0
+      "
       text="活动不存在或已结束"
       icon="/static/soldout-empty.png"
       showAction
@@ -225,8 +227,8 @@
   const getActivity = async (id) => {
     const { data } = await SeckillApi.getSeckillActivity(id);
     activity.value = data;
-    timeStatusEnum.value = getTimeStatusEnum(activity.startTime, activity.endTime);
-
+    timeStatusEnum.value = getTimeStatusEnum(activity.value.startTime, activity.value.endTime);
+    state.percent = 100 - (data.stock / data.totalStock) * 100;
     // 查询商品
     await getSpu(data.spuId);
   };
@@ -284,6 +286,7 @@
   .disabled-btn-box[disabled] {
     background-color: transparent;
   }
+
   .detail-card {
     background-color: $white;
     margin: 14rpx 20rpx;
@@ -374,6 +377,7 @@
       font-size: 26rpx;
       font-weight: 500;
       color: #ffffff;
+
       .countdown-h {
         font-size: 24rpx;
         font-family: OPPOSANS;
@@ -384,6 +388,7 @@
         background: rgba(#000000, 0.1);
         border-radius: 6rpx;
       }
+
       .countdown-num {
         font-size: 24rpx;
         font-family: OPPOSANS;
@@ -467,6 +472,7 @@
       line-height: normal;
       border-radius: 0px 40rpx 40rpx 0px;
     }
+
     .btn-price {
       font-family: OPPOSANS;
 
@@ -484,6 +490,7 @@
       line-height: normal;
       font-size: 24rpx;
       font-weight: 500;
+
       .no-original {
         font-size: 28rpx;
       }

+ 12 - 0
sheep/api/product/spu.js

@@ -13,6 +13,18 @@ const SpuApi = {
       },
     });
   },
+  // 获得商品结算信息
+  getSettlementProduct: (spuIds) => {
+    return request({
+      url: '/trade/order/settlement-product',
+      method: 'GET',
+      params: { spuIds },
+      custom: {
+        showLoading: false,
+        showError: false,
+      },
+    });
+  },
   // 获得商品 SPU 分页
   getSpuPage: (params) => {
     return request({

+ 188 - 37
sheep/components/s-activity-pop/s-activity-pop.vue

@@ -2,34 +2,82 @@
 <template>
   <su-popup :show="show" type="bottom" round="20" @close="emits('close')" showClose>
     <view class="model-box">
-      <view class="title ss-m-t-16 ss-m-l-20 ss-flex">营销活动</view>
+      <view class="title ss-m-t-16 ss-m-l-20 ss-flex">优惠</view>
+      <view v-if="state.rewardActivity && state.rewardActivity.id > 0">
+        <view class="titleLi">促销</view>
+        <scroll-view
+          class="model-content"
+          scroll-y
+          :scroll-with-animation="false"
+          :enable-back-to-top="true"
+        >
+          <view
+            class="actBox"
+            v-for="(item, index) in getRewardActivityRuleGroupDescriptions(state.rewardActivity)"
+            :key="index"
+          >
+            <view
+              class="boxCont ss-flex ss-col-top ss-m-b-40"
+              @tap="onGoodsList(state.rewardActivity)"
+            >
+              <view class="model-content-tag ss-flex ss-row-center">{{ item.name }}</view>
+              <view class="model-content-title">
+                <view class="contBu">
+                  {{ item.values.join(';') }}
+                </view>
+                <view class="ss-m-b-24 cotBu-txt">
+                  {{ sheep.$helper.timeFormat(state.rewardActivity.startTime, 'yyyy.mm.dd') }}
+                  -
+                  {{ sheep.$helper.timeFormat(state.rewardActivity.endTime, 'yyyy.mm.dd') }}
+                </view>
+              </view>
+              <text class="cicon-forward" />
+            </view>
+          </view>
+        </scroll-view>
+      </view>
+      <view class="titleLi">可领优惠券</view>
       <scroll-view
-        class="model-content ss-m-t-50"
+        class="model-content"
         scroll-y
         :scroll-with-animation="false"
         :enable-back-to-top="true"
+        v-if="state.couponInfo.length"
       >
-        <view v-for="item in state.activityInfo" :key="item.id">
-          <view class="ss-flex ss-col-top ss-m-b-40" @tap="onGoodsList(item)">
-            <view class="model-content-tag ss-flex ss-row-center">满减</view>
-            <view class="ss-m-l-20 model-content-title ss-flex-1">
-              <view class="ss-m-b-24" v-for="rule in state.activityMap[item.id]?.rules" :key="rule">
-                {{ formatRewardActivityRule(state.activityMap[item.id], rule) }}
+        <view class="actBox" v-for="item in state.couponInfo" :key="item.id">
+          <view class="boxCont ss-flex ss-col-top ss-m-b-40">
+            <view class="model-content-tag2">
+              <view class="usePrice"> ¥{{ fen2yuan(item.discountPrice) }} </view>
+              <view class="impose"> 满¥{{ fen2yuan(item.usePrice) }}可用 </view>
+            </view>
+            <view class="model-content-title2">
+              <view class="contBu">
+                {{ item.name }}
+              </view>
+              <view class="ss-m-b-24 cotBu-txt">
+                {{
+                  item.validityType == 1
+                    ? sheep.$helper.timeFormat(item.validStartTime, 'yyyy-mm-dd') +
+                      '-' +
+                      sheep.$helper.timeFormat(item.validEndTime, 'yyyy-mm-dd')
+                    : '领取后' + item.fixedStartTerm + '-' + item.fixedEndTerm + '天可用'
+                }}
               </view>
             </view>
-            <text class="cicon-forward" />
+            <view class="coupon" @click.stop="getBuy(item.id)" v-if="item.canTake"> 立即领取 </view>
+            <view class="coupon2" v-else> 已领取 </view>
           </view>
         </view>
       </scroll-view>
+      <view class="nullBox" v-else> 暂无可领优惠券 </view>
     </view>
   </su-popup>
 </template>
 <script setup>
   import sheep from '@/sheep';
-  import { computed, reactive, watch } from 'vue';
-  import RewardActivityApi from '@/sheep/api/promotion/rewardActivity';
-  import { formatRewardActivityRule } from '@/sheep/hooks/useGoods';
-
+  import { getRewardActivityRuleGroupDescriptions } from '@/sheep/hooks/useGoods';
+  import { computed, reactive, watch, ref } from 'vue';
+  import { fen2yuan } from '@/sheep/hooks/useGoods';
   const props = defineProps({
     modelValue: {
       type: Object,
@@ -42,26 +90,14 @@
   });
   const emits = defineEmits(['close']);
   const state = reactive({
-    activityInfo: computed(() => props.modelValue),
-    activityMap: {}
+    rewardActivity: computed(() => props.modelValue.rewardActivity),
+    couponInfo: computed(() => props.modelValue.couponInfo),
   });
 
-  watch(
-    () => props.show,
-    () => {
-      // 展示的情况下,加载每个活动的详细信息
-      if (props.show) {
-        state.activityInfo?.forEach(activity => {
-          RewardActivityApi.getRewardActivity(activity.id).then(res => {
-            if (res.code !== 0) {
-              return;
-            }
-            state.activityMap[activity.id] = res.data;
-          })
-        });
-      }
-    },
-  );
+  // 领取优惠劵
+  const getBuy = (id) => {
+    emits('get', id);
+  };
 
   function onGoodsList(e) {
     sheep.$router.go('/pages/activity/index', {
@@ -72,34 +108,149 @@
 <style lang="scss" scoped>
   .model-box {
     height: 60vh;
+
     .title {
+      justify-content: center;
       font-size: 36rpx;
       height: 80rpx;
       font-weight: bold;
       color: #333333;
     }
   }
+
   .model-content {
+    height: fit-content;
+    max-height: 380rpx;
     padding: 0 20rpx;
     box-sizing: border-box;
+    margin-top: 20rpx;
+
     .model-content-tag {
-      background: rgba(#ff6911, 0.1);
-      font-size: 24rpx;
+      // background: rgba(#ff6911, 0.1);
+      font-size: 35rpx;
       font-weight: 500;
       color: #ff6911;
-      line-height: 42rpx;
-      width: 68rpx;
-      height: 32rpx;
-      border-radius: 5rpx;
+      line-height: 150rpx;
+      width: 200rpx;
+      height: 150rpx;
+      text-align: center;
+
+      // border-radius: 5rpx;
     }
+
     .model-content-title {
+      width: 450rpx;
+      height: 150rpx;
       font-size: 26rpx;
       font-weight: 500;
       color: #333333;
+      overflow: hidden;
     }
+
     .cicon-forward {
       font-size: 28rpx;
       color: #999999;
+      margin: 0 auto;
     }
   }
+
+  // 新增的
+  .titleLi {
+    margin: 10rpx 0 10rpx 20rpx;
+    font-size: 26rpx;
+  }
+
+  .actBox {
+    width: 700rpx;
+    height: 150rpx;
+    background-color: #fff2f2;
+    margin: 10rpx auto;
+    border-radius: 10rpx;
+  }
+
+  .boxCont {
+    width: 700rpx;
+    height: 150rpx;
+    align-items: center;
+  }
+
+  .contBu {
+    height: 80rpx;
+    line-height: 80rpx;
+    overflow: hidden;
+    font-size: 30rpx;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    -o-text-overflow: ellipsis;
+  }
+
+  .cotBu-txt {
+    height: 70rpx;
+    line-height: 70rpx;
+    font-size: 25rpx;
+    color: #999999;
+  }
+
+  .model-content-tag2 {
+    font-size: 35rpx;
+    font-weight: 500;
+    color: #ff6911;
+    width: 200rpx;
+    height: 150rpx;
+    text-align: center;
+  }
+
+  .usePrice {
+    width: 200rpx;
+    height: 90rpx;
+    line-height: 100rpx;
+    // background-color: red;
+  }
+
+  .impose {
+    width: 200rpx;
+    height: 50rpx;
+    // line-height: 75rpx;
+    font-size: 23rpx;
+    // background-color: gold;
+  }
+
+  .model-content-title2 {
+    width: 330rpx;
+    height: 150rpx;
+    font-size: 26rpx;
+    font-weight: 500;
+    color: #333333;
+    overflow: hidden;
+  }
+
+  .coupon {
+    width: 150rpx;
+    height: 50rpx;
+    line-height: 50rpx;
+    background-color: rgb(255, 68, 68);
+    color: white;
+    border-radius: 30rpx;
+    text-align: center;
+    font-size: 25rpx;
+  }
+
+  .coupon2 {
+    width: 150rpx;
+    height: 50rpx;
+    line-height: 50rpx;
+    background-color: rgb(203, 192, 191);
+    color: white;
+    border-radius: 30rpx;
+    text-align: center;
+    font-size: 25rpx;
+  }
+  .nullBox {
+    width: 100%;
+    height: 300rpx;
+    font-size: 25rpx;
+    line-height: 300rpx;
+    text-align: center;
+    color: #999999;
+  }
 </style>

+ 10 - 28
sheep/components/s-discount-list/s-discount-list.vue

@@ -15,25 +15,11 @@
         :scroll-with-animation="false"
         :enable-back-to-top="true"
       >
-        <view v-for="(item, index) in state.orderInfo.promo_infos" :key="index">
-          <view class="ss-flex ss-m-b-40 subtitle">
-            <view>共{{ item.goods_ids.length }}件,</view>
-            <view v-if="item.activity_type === 'full_discount'">
-              满{{ item.discount_rule.full }}打{{ item.discount_rule.discount }}折,已减
-            </view>
-            <view v-if="item.activity_type === 'full_gift'">满赠</view>
-            <view v-if="item.activity_type === 'full_reduce'">
-              满{{ item.discount_rule.full }}减{{ item.discount_rule.discount }},已减
-            </view>
-            <view class="price-text">¥{{ item.promo_discount_money || '0.00' }}</view>
+        <view v-for="(item, index) in state.orderInfo.promotions" :key="index">
+          <!-- 不展示积分、优惠劵、会员折扣,因为它们已经单独展示了 -->
+          <view class="ss-flex ss-m-b-40 subtitle" v-if="[1, 2, 3, 4, 5].includes(item.type)">
+            <view> {{ item.description }} </view>
           </view>
-          <scroll-view class="scroll-box" scroll-x scroll-anchoring>
-            <view class="ss-flex">
-              <view v-for="i in item.goods_ids" :key="i">
-                <image class="content-img" :src="sheep.$url.cdn(getGoodsImg(i))" />
-              </view>
-            </view>
-          </scroll-view>
         </view>
       </scroll-view>
     </view>
@@ -44,7 +30,6 @@
 </template>
 <script setup>
   import { computed, reactive } from 'vue';
-  import sheep from '@/sheep';
   const props = defineProps({
     promoInfo: {
       type: Array,
@@ -67,28 +52,22 @@
   const state = reactive({
     orderInfo: computed(() => props.modelValue),
   });
-  const getGoodsImg = (e) => {
-    let goodsImg = '';
-    state.orderInfo.goods_list.forEach((i) => {
-      if (e == i.goods_id) {
-        goodsImg = i.goods.image;
-      }
-    });
-    return goodsImg;
-  };
 </script>
 <style lang="scss" scoped>
   .model-box {
     height: 60vh;
   }
+
   .model-content {
     height: 54vh;
   }
+
   .modal-footer {
     width: 100%;
     height: 120rpx;
     background: #fff;
   }
+
   .confirm-btn {
     width: 710rpx;
     margin-left: 20rpx;
@@ -97,17 +76,20 @@
     border-radius: 40rpx;
     color: #fff;
   }
+
   .content-img {
     width: 140rpx;
     height: 140rpx;
     margin-right: 20rpx;
     margin-bottom: 20rpx;
   }
+
   .subtitle {
     font-size: 28rpx;
     font-weight: 500;
     color: #333333;
   }
+
   .price-text {
     color: #ff3000;
   }

+ 26 - 6
sheep/components/s-goods-card/s-goods-card.vue

@@ -3,7 +3,10 @@
   <!-- 商品卡片 -->
   <view>
     <!-- 布局1. 单列大图(上图,下内容)-->
-    <view v-if="layoutType === LayoutTypeEnum.ONE_COL_BIG_IMG && state.goodsList.length" class="goods-sl-box">
+    <view
+      v-if="layoutType === LayoutTypeEnum.ONE_COL_BIG_IMG && state.goodsList.length"
+      class="goods-sl-box"
+    >
       <view
         class="goods-box"
         v-for="item in state.goodsList"
@@ -100,7 +103,10 @@
     </view>
 
     <!-- 布局3. 单列小图(左图,右内容) -->
-    <view v-if="layoutType === LayoutTypeEnum.ONE_COL_SMALL_IMG && state.goodsList.length" class="goods-lg-box">
+    <view
+      v-if="layoutType === LayoutTypeEnum.ONE_COL_SMALL_IMG && state.goodsList.length"
+      class="goods-lg-box"
+    >
       <view
         class="goods-box"
         :style="[{ marginBottom: data.space + 'px' }]"
@@ -138,6 +144,8 @@
   import { computed, reactive, onMounted } from 'vue';
   import sheep from '@/sheep';
   import SpuApi from '@/sheep/api/product/spu';
+  import OrderApi from '@/sheep/api/trade/order';
+  import { appendSettlementProduct } from '@/sheep/hooks/useGoods';
 
   // 布局类型
   const LayoutTypeEnum = {
@@ -147,7 +155,7 @@
     TWO_COL: 'twoCol',
     // 单列小图
     ONE_COL_SMALL_IMG: 'oneColSmallImg',
-  }
+  };
 
   const state = reactive({
     goodsList: [],
@@ -157,11 +165,11 @@
   const props = defineProps({
     data: {
       type: Object,
-      default() {},
+      default: () => ({}),
     },
     styles: {
       type: Object,
-      default() {},
+      default: () => ({}),
     },
   });
 
@@ -215,6 +223,7 @@
     // 计数
     count++;
   }
+
   //endregion
 
   /**
@@ -231,8 +240,17 @@
   onMounted(async () => {
     // 加载商品列表
     state.goodsList = await getGoodsListByIds(spuIds.join(','));
+    // 拼接结算信息(营销)
+    await OrderApi.getSettlementProduct(state.goodsList.map((item) => item.id).join(',')).then(
+      (res) => {
+        if (res.code !== 0) {
+          return;
+        }
+        appendSettlementProduct(state.goodsList, res.data);
+      },
+    );
     // 只有双列布局时需要
-    if (layoutType === LayoutTypeEnum.TWO_COL){
+    if (layoutType === LayoutTypeEnum.TWO_COL) {
       // 分列
       calculateGoodsColumn();
     }
@@ -247,11 +265,13 @@
   .goods-list-box {
     width: 50%;
     box-sizing: border-box;
+
     .left-list {
       &:nth-last-child(1) {
         margin-bottom: 0 !important;
       }
     }
+
     .right-list {
       &:nth-last-child(1) {
         margin-bottom: 0 !important;

+ 279 - 72
sheep/components/s-goods-column/s-goods-column.vue

@@ -11,11 +11,7 @@
       <view v-if="tagStyle.show" class="tag-icon-box">
         <image class="tag-icon" :src="sheep.$url.cdn(tagStyle.src || tagStyle.imgUrl)"></image>
       </view>
-      <image
-        class="xs-img-box"
-        :src="sheep.$url.cdn(data.image || data.picUrl)"
-        mode="aspectFit"
-      ></image>
+      <image class="xs-img-box" :src="sheep.$url.cdn(data.image || data.picUrl)" mode="aspectFit" />
       <view
         v-if="goodsFields.title?.show || goodsFields.name?.show || goodsFields.price?.show"
         class="xs-goods-content ss-flex-col ss-row-around"
@@ -27,13 +23,40 @@
         >
           {{ data.title || data.name }}
         </view>
+        <!-- 活动信息 -->
+        <view class="iconBox" v-if="data.promotionType > 0 || data.rewardActivity">
+          <view class="card" v-if="discountText">{{ discountText }}</view>
+          <view
+            class="card2"
+            v-for="item in getRewardActivityRuleItemDescriptions(data.rewardActivity).slice(0, 1)"
+            :key="item"
+          >
+            {{ item }}
+          </view>
+        </view>
         <view
           v-if="goodsFields.price?.show"
           class="xs-goods-price font-OPPOSANS"
           :style="[{ color: goodsFields.price.color }]"
         >
-          <text class="price-unit ss-font-24">{{ priceUnit }}</text>
-          {{ isArray(data.price) ? fen2yuan(data.price[0]) : fen2yuan(data.price) }}
+          <!-- 活动价格 -->
+          <view class="ss-flex" v-if="data.activityType && data.activityType === PromotionActivityTypeEnum.POINT.type">
+            <image
+              :src="sheep.$url.static('/static/img/shop/goods/score1.svg')"
+              class="point-img"
+            ></image>
+            <text class="point-text ss-m-r-16">
+              {{ data.point }}
+              {{ !data.pointPrice || data.pointPrice === 0 ? '' : `+${priceUnit}${fen2yuan(data.pointPrice)}` }}
+            </text>
+          </view>
+          <template v-else>
+            <text class="price-unit ss-font-24">{{ priceUnit }}</text>
+            <text v-if="data.promotionPrice > 0">{{ fen2yuan(data.promotionPrice) }}</text>
+            <text v-else>
+              {{ isArray(data.price) ? fen2yuan(data.price[0]) : fen2yuan(data.price) }}
+            </text>
+          </template>
         </view>
       </view>
     </view>
@@ -60,13 +83,40 @@
         >
           {{ data.title || data.name }}
         </view>
+        <!-- 活动信息 -->
+        <view class="iconBox" v-if="data.promotionType > 0 || data.rewardActivity">
+          <view class="card" v-if="discountText">{{ discountText }}</view>
+          <view
+            class="card2"
+            v-for="item in getRewardActivityRuleItemDescriptions(data.rewardActivity).slice(0, 1)"
+            :key="item"
+          >
+            {{ item }}
+          </view>
+        </view>
         <view
           v-if="goodsFields.price?.show"
           class="sm-goods-price font-OPPOSANS"
           :style="[{ color: goodsFields.price.color }]"
         >
-          <text class="price-unit ss-font-24">{{ priceUnit }}</text>
-          {{ isArray(data.price) ? fen2yuan(data.price[0]) : fen2yuan(data.price) }}
+          <!-- 活动价格 -->
+          <view class="ss-flex" v-if="data.activityType && data.activityType === PromotionActivityTypeEnum.POINT.type">
+            <image
+              :src="sheep.$url.static('/static/img/shop/goods/score1.svg')"
+              class="point-img"
+            ></image>
+            <text class="point-text ss-m-r-16">
+              {{ data.point }}
+              {{ !data.pointPrice || data.pointPrice === 0 ? '' : `+${priceUnit}${fen2yuan(data.pointPrice)}` }}
+            </text>
+          </view>
+          <template v-else>
+            <text class="price-unit ss-font-24">{{ priceUnit }}</text>
+            <text v-if="data.promotionPrice > 0">{{ fen2yuan(data.promotionPrice) }}</text>
+            <text v-else>
+              {{ isArray(data.price) ? fen2yuan(data.price[0]) : fen2yuan(data.price) }}
+            </text>
+          </template>
         </view>
       </view>
     </view>
@@ -74,13 +124,9 @@
     <!-- md卡片:竖向,一行放两个,图上内容下 -->
     <view v-if="size === 'md'" class="md-goods-card ss-flex-col" :style="[elStyles]" @tap="onClick">
       <view v-if="tagStyle.show" class="tag-icon-box">
-        <image class="tag-icon" :src="sheep.$url.cdn(tagStyle.src || tagStyle.imgUrl)"></image>
+        <image class="tag-icon" :src="sheep.$url.cdn(tagStyle.src || tagStyle.imgUrl)" />
       </view>
-      <image
-        class="md-img-box"
-        :src="sheep.$url.cdn(data.image || data.picUrl)"
-        mode="widthFix"
-      ></image>
+      <image class="md-img-box" :src="sheep.$url.cdn(data.image || data.picUrl)" mode="widthFix" />
       <view
         class="md-goods-content ss-flex-col ss-row-around ss-p-b-20 ss-p-t-20 ss-p-x-16"
         :id="elId"
@@ -110,16 +156,42 @@
             </view>
           </view>
         </slot>
+        <!-- 活动信息 -->
+        <view class="iconBox" v-if="data.promotionType > 0 || data.rewardActivity">
+          <view class="card" v-if="discountText">{{ discountText }}</view>
+          <view
+            class="card2"
+            v-for="item in getRewardActivityRuleItemDescriptions(data.rewardActivity).slice(0, 1)"
+            :key="item"
+          >
+            {{ item }}
+          </view>
+        </view>
         <view class="ss-flex ss-col-bottom">
           <view
             v-if="goodsFields.price?.show"
             class="md-goods-price ss-m-t-16 font-OPPOSANS ss-m-r-10"
             :style="[{ color: goodsFields.price.color }]"
           >
-            <text class="price-unit ss-font-24">{{ priceUnit }}</text>
-            {{ isArray(data.price) ? fen2yuan(data.price[0]) : fen2yuan(data.price) }}
+            <!-- 活动价格 -->
+            <view class="ss-flex" v-if="data.activityType && data.activityType === PromotionActivityTypeEnum.POINT.type">
+              <image
+                :src="sheep.$url.static('/static/img/shop/goods/score1.svg')"
+                class="point-img"
+              ></image>
+              <text class="point-text ss-m-r-16">
+                {{ data.point }}
+                {{ !data.pointPrice || data.pointPrice === 0 ? '' : `+${priceUnit}${fen2yuan(data.pointPrice)}` }}
+              </text>
+            </view>
+            <template v-else>
+              <text class="price-unit ss-font-24">{{ priceUnit }}</text>
+              <text v-if="data.promotionPrice > 0">{{ fen2yuan(data.promotionPrice) }}</text>
+              <text v-else>
+                {{ isArray(data.price) ? fen2yuan(data.price[0]) : fen2yuan(data.price) }}
+              </text>
+            </template>
           </view>
-
           <view
             v-if="
               (goodsFields.original_price?.show || goodsFields.marketPrice?.show) &&
@@ -155,7 +227,7 @@
       <view v-if="tagStyle.show" class="tag-icon-box">
         <image class="tag-icon" :src="sheep.$url.cdn(tagStyle.src || tagStyle.imgUrl)"></image>
       </view>
-      <view v-if="seckillTag" class="seckill-tag ss-flex ss-row-center"> 秒杀 </view>
+      <view v-if="seckillTag" class="seckill-tag ss-flex ss-row-center">秒杀</view>
       <view v-if="grouponTag" class="groupon-tag ss-flex ss-row-center">
         <view class="tag-icon">拼团</view>
       </view>
@@ -163,7 +235,7 @@
         class="lg-img-box"
         :src="sheep.$url.cdn(data.image || data.picUrl)"
         mode="aspectFill"
-      ></image>
+      />
       <view class="lg-goods-content ss-flex-1 ss-flex-col ss-row-between ss-p-b-10 ss-p-t-20">
         <view>
           <view
@@ -189,21 +261,44 @@
               </view>
             </view>
           </slot>
-          <view class="ss-flex ss-col-bottom ss-m-t-10">
+          <!-- 活动信息 -->
+          <view class="iconBox" v-if="data.promotionType > 0 || data.rewardActivity">
+            <view class="card" v-if="discountText">{{ discountText }}</view>
             <view
-              v-if="goodsFields.price?.show"
-              class="lg-goods-price ss-m-r-12 ss-flex ss-col-bottom font-OPPOSANS"
-              :style="[{ color: goodsFields.price.color }]"
+              class="card2"
+              v-for="item in getRewardActivityRuleItemDescriptions(data.rewardActivity).slice(0, 1)"
+              :key="item"
             >
-              <text class="ss-font-24">{{ priceUnit }}</text>
-              {{ isArray(data.price) ? fen2yuan(data.price[0]) : fen2yuan(data.price) }}
+              {{ item }}
+            </view>
+          </view>
+          <view v-if="goodsFields.price?.show" class="ss-flex ss-col-bottom font-OPPOSANS">
+            <view class="sl-goods-price ss-m-r-12" :style="[{ color: goodsFields.price.color }]">
+              <!-- 活动价格 -->
+              <view class="ss-flex" v-if="data.activityType && data.activityType === PromotionActivityTypeEnum.POINT.type">
+                <image
+                  :src="sheep.$url.static('/static/img/shop/goods/score1.svg')"
+                  class="point-img"
+                ></image>
+                <text class="point-text ss-m-r-16">
+                  {{ data.point }}
+                  {{ !data.pointPrice || data.pointPrice === 0 ? '' : `+${priceUnit}${fen2yuan(data.pointPrice)}` }}
+                </text>
+              </view>
+              <template v-else>
+                <text class="price-unit ss-font-24">{{ priceUnit }}</text>
+                <text v-if="data.promotionPrice > 0">{{ fen2yuan(data.promotionPrice) }}</text>
+                <text v-else>
+                  {{ isArray(data.price) ? fen2yuan(data.price[0]) : fen2yuan(data.price) }}
+                </text>
+              </template>
             </view>
             <view
               v-if="
                 (goodsFields.original_price?.show || goodsFields.marketPrice?.show) &&
                 (data.original_price > 0 || data.marketPrice > 0)
               "
-              class="goods-origin-price ss-flex ss-col-bottom font-OPPOSANS"
+              class="goods-origin-price ss-m-t-16 font-OPPOSANS ss-flex"
               :style="[{ color: originPriceColor }]"
             >
               <text class="price-unit ss-font-20">{{ priceUnit }}</text>
@@ -217,22 +312,20 @@
       </view>
 
       <slot name="cart">
-        <view class="buy-box ss-flex ss-col-center ss-row-center" v-if="buttonShow"> 去购买 </view>
+        <view class="buy-box ss-flex ss-col-center ss-row-center" v-if="buttonShow"> 去购买</view>
       </slot>
     </view>
 
     <!-- sl卡片:竖向型,一行放一个,图片上内容下边 -->
     <view v-if="size === 'sl'" class="sl-goods-card ss-flex-col" :style="[elStyles]" @tap="onClick">
       <view v-if="tagStyle.show" class="tag-icon-box">
-        <image class="tag-icon" :src="sheep.$url.cdn(tagStyle.src || tagStyle.imgUrl)"></image>
+        <image class="tag-icon" :src="sheep.$url.cdn(tagStyle.src || tagStyle.imgUrl)" />
       </view>
-
       <image
         class="sl-img-box"
         :src="sheep.$url.cdn(data.image || data.picUrl)"
         mode="aspectFill"
-      ></image>
-
+      />
       <view class="sl-goods-content">
         <view>
           <view
@@ -262,10 +355,37 @@
               </view>
             </view>
           </slot>
+          <!-- 活动信息 -->
+          <view class="iconBox" v-if="data.promotionType > 0 || data.rewardActivity">
+            <view class="card" v-if="discountText">{{ discountText }}</view>
+            <view
+              class="card2"
+              v-for="item in getRewardActivityRuleItemDescriptions(data.rewardActivity).slice(0, 1)"
+              :key="item"
+            >
+              {{ item }}
+            </view>
+          </view>
           <view v-if="goodsFields.price?.show" class="ss-flex ss-col-bottom font-OPPOSANS">
             <view class="sl-goods-price ss-m-r-12" :style="[{ color: goodsFields.price.color }]">
-              <text class="price-unit ss-font-24">{{ priceUnit }}</text>
-              {{ isArray(data.price) ? fen2yuan(data.price[0]) : fen2yuan(data.price) }}
+              <!-- 活动价格 -->
+              <view class="ss-flex" v-if="data.activityType && data.activityType === PromotionActivityTypeEnum.POINT.type">
+                <image
+                  :src="sheep.$url.static('/static/img/shop/goods/score1.svg')"
+                  class="point-img"
+                ></image>
+                <text class="ss-m-r-16">
+                  {{ data.point }}
+                  {{ !data.pointPrice || data.pointPrice === 0 ? '' : `+${priceUnit}${fen2yuan(data.pointPrice)}` }}
+                </text>
+              </view>
+              <template v-else>
+                <text class="price-unit ss-font-24">{{ priceUnit }}</text>
+                <text v-if="data.promotionPrice > 0">{{ fen2yuan(data.promotionPrice) }}</text>
+                <text v-else>
+                  {{ isArray(data.price) ? fen2yuan(data.price[0]) : fen2yuan(data.price) }}
+                </text>
+              </template>
             </view>
             <view
               v-if="
@@ -285,9 +405,9 @@
         </view>
       </view>
 
-      <slot name="cart"
-        ><view class="buy-box ss-flex ss-col-center ss-row-center">去购买</view></slot
-      >
+      <slot name="cart">
+        <view class="buy-box ss-flex ss-col-center ss-row-center">去购买</view>
+      </slot>
     </view>
   </view>
 </template>
@@ -297,39 +417,41 @@
    * 商品卡片
    *
    * @property {Array} size = [xs | sm | md | lg | sl ] 			 	- 列表数据
-   * @property {String} tag 											- md及以上才有
-   * @property {String} img 											- 图片
-   * @property {String} background 									- 背景色
-   * @property {String} topRadius 									- 上圆角
-   * @property {String} bottomRadius 									- 下圆角
-   * @property {String} title 										- 标题
-   * @property {String} titleColor 									- 标题颜色
+   * @property {String} tag                      - md及以上才有
+   * @property {String} img                      - 图片
+   * @property {String} background                  - 背景色
+   * @property {String} topRadius                  - 上圆角
+   * @property {String} bottomRadius                  - 下圆角
+   * @property {String} title                    - 标题
+   * @property {String} titleColor                  - 标题颜色
    * @property {Number} titleWidth = 0								- 标题宽度,默认0,单位rpx
-   * @property {String} subTitle 										- 副标题
-   * @property {String} subTitleColor									- 副标题颜色
-   * @property {String} subTitleBackground 							- 副标题背景
-   * @property {String | Number} price 								- 价格
-   * @property {String} priceColor 									- 价格颜色
-   * @property {String | Number} originPrice 							- 原价/划线价
-   * @property {String} originPriceColor 								- 原价颜色
-   * @property {String | Number} sales 								- 销售数量
-   * @property {String} salesColor									- 销售数量颜色
+   * @property {String} subTitle                    - 副标题
+   * @property {String} subTitleColor                  - 副标题颜色
+   * @property {String} subTitleBackground              - 副标题背景
+   * @property {String | Number} price                - 价格
+   * @property {String} priceColor                  - 价格颜色
+   * @property {String | Number} originPrice              - 原价/划线价
+   * @property {String} originPriceColor                - 原价颜色
+   * @property {String | Number} sales                - 销售数量
+   * @property {String} salesColor                  - 销售数量颜色
    *
    * @slots activity												 	- 活动插槽
    * @slots cart														- 购物车插槽,默认包含文字,背景色,文字颜色 || 图片 || 行为
    *
-   * @event {Function()} click 										- 点击卡片
+   * @event {Function()} click                    - 点击卡片
    *
    */
-  import { computed, reactive, getCurrentInstance, onMounted, nextTick } from 'vue';
+  import { computed, getCurrentInstance, nextTick, onMounted } from 'vue';
   import sheep from '@/sheep';
-  import { fen2yuan, formatSales } from '@/sheep/hooks/useGoods';
-  import { formatStock } from '@/sheep/hooks/useGoods';
-  import goodsCollectVue from '@/pages/user/goods-collect.vue';
+  import {
+    fen2yuan,
+    formatExchange,
+    formatSales,
+    formatStock,
+    getRewardActivityRuleItemDescriptions,
+  } from '@/sheep/hooks/useGoods';
   import { isArray } from 'lodash-es';
-
-  // 数据
-  const state = reactive({});
+  import { PromotionActivityTypeEnum } from '@/sheep/util/const';
 
   // 接收参数
   const props = defineProps({
@@ -338,27 +460,39 @@
       default() {
         return {
           // 商品价格
-          price: { show: true },
+          price: {
+            show: true,
+          },
           // 库存
-          stock: { show: true },
+          stock: {
+            show: true,
+          },
           // 商品名称
-          name: { show: true },
+          name: {
+            show: true,
+          },
           // 商品介绍
-          introduction: { show: true },
+          introduction: {
+            show: true,
+          },
           // 市场价
-          marketPrice: { show: true },
+          marketPrice: {
+            show: true,
+          },
           // 销量
-          salesCount: { show: true },
+          salesCount: {
+            show: true,
+          },
         };
       },
     },
     tagStyle: {
       type: Object,
-      default: {},
+      default: () => ({}),
     },
     data: {
       type: Object,
-      default: {},
+      default: () => ({}),
     },
     size: {
       type: String,
@@ -418,6 +552,17 @@
     },
   });
 
+  // 优惠文案
+  const discountText = computed(() => {
+    const promotionType = props.data.promotionType;
+    if (promotionType === 4) {
+      return '限时优惠';
+    } else if (promotionType === 6) {
+      return '会员价';
+    }
+    return undefined;
+  });
+
   // 组件样式
   const elStyles = computed(() => {
     return {
@@ -433,10 +578,18 @@
   const salesAndStock = computed(() => {
     let text = [];
     if (props.goodsFields.salesCount?.show) {
-      text.push(formatSales(props.data.sales_show_type, props.data.salesCount));
+      if (props.data.activityType && props.data.activityType === PromotionActivityTypeEnum.POINT.type) {
+        text.push(formatExchange(props.data.sales_show_type, (props.data.pointTotalStock || 0) - (props.data.pointStock || 0)));
+      } else {
+        text.push(formatSales(props.data.sales_show_type, props.data.salesCount));
+      }
     }
     if (props.goodsFields.stock?.show) {
-      text.push(formatStock(props.data.stock_show_type, props.data.stock));
+      if (props.data.activityType && props.data.activityType === PromotionActivityTypeEnum.POINT.type) {
+        text.push(formatStock(props.data.stock_show_type, props.data.pointTotalStock));
+      } else {
+        text.push(formatStock(props.data.stock_show_type, props.data.stock));
+      }
     }
     return text.join(' | ');
   });
@@ -451,10 +604,14 @@
   // 获取卡片实时高度
   const { proxy } = getCurrentInstance();
   const elId = `sheep_${Math.ceil(Math.random() * 10e5).toString(36)}`;
+
   function getGoodsPriceCardWH() {
     if (props.size === 'md') {
       const view = uni.createSelectorQuery().in(proxy);
-      view.select(`#${elId}`).fields({ size: true, scrollOffset: true });
+      view.select(`#${elId}`).fields({
+        size: true,
+        scrollOffset: true,
+      });
       view.exec((data) => {
         let totalHeight = 0;
         const goodsPriceCard = data[0];
@@ -469,6 +626,7 @@
       });
     }
   }
+
   onMounted(() => {
     nextTick(() => {
       getGoodsPriceCardWH();
@@ -482,11 +640,13 @@
     left: 0;
     top: 0;
     z-index: 2;
+
     .tag-icon {
       width: 72rpx;
       height: 44rpx;
     }
   }
+
   .seckill-tag {
     position: absolute;
     left: 0;
@@ -501,6 +661,13 @@
     color: #ffffff;
     line-height: 32rpx;
   }
+
+  .point-img {
+    width: 30rpx;
+    height: 30rpx;
+    margin: 0 4rpx;
+  }
+
   .groupon-tag {
     position: absolute;
     left: 0;
@@ -515,14 +682,17 @@
     color: #ffffff;
     line-height: 32rpx;
   }
+
   .goods-img {
     width: 100%;
     height: 100%;
     background-color: #f5f5f5;
   }
+
   .price-unit {
     margin-right: -4px;
   }
+
   .sales-text {
     display: table;
     font-size: 24rpx;
@@ -586,10 +756,12 @@
       width: 100%;
       height: 208rpx;
     }
+
     .sm-goods-content {
       padding: 20rpx 16rpx;
       box-sizing: border-box;
     }
+
     .sm-goods-title {
       font-size: 26rpx;
       color: #333;
@@ -619,6 +791,7 @@
       color: #333;
       width: 100%;
     }
+
     .md-goods-subtitle {
       font-size: 24rpx;
       font-weight: 400;
@@ -669,6 +842,7 @@
       // line-height: 36rpx;
       // width: 410rpx;
     }
+
     .lg-goods-subtitle {
       font-size: 24rpx;
       font-weight: 400;
@@ -695,6 +869,7 @@
       font-size: 24rpx;
       color: #ffffff;
     }
+
     .tag-box {
       width: 100%;
     }
@@ -708,10 +883,12 @@
     z-index: 1;
     width: 100%;
     background-color: $white;
+
     .sl-goods-content {
       padding: 20rpx 20rpx;
       box-sizing: border-box;
     }
+
     .sl-img-box {
       width: 100%;
       height: 360rpx;
@@ -722,6 +899,7 @@
       color: #333;
       font-weight: 500;
     }
+
     .sl-goods-subtitle {
       font-size: 24rpx;
       font-weight: 400;
@@ -748,4 +926,33 @@
       color: #ffffff;
     }
   }
+
+  .card {
+    width: fit-content;
+    height: fit-content;
+    padding: 2rpx 10rpx;
+    background-color: red;
+    color: #ffffff;
+    font-size: 24rpx;
+    margin-top: 5rpx;
+  }
+
+  .card2 {
+    width: fit-content;
+    height: fit-content;
+    padding: 2rpx 10rpx;
+    background-color: rgb(255, 242, 241);
+    color: #ff2621;
+    font-size: 24rpx;
+    margin: 5rpx 0 5rpx 5rpx;
+  }
+
+  .iconBox {
+    width: 100%;
+    height: fit-content;
+    margin-top: 10rpx;
+    display: flex;
+    justify-content: flex-start;
+    flex-wrap: wrap;
+  }
 </style>

+ 396 - 345
sheep/components/s-select-sku/s-select-sku.vue

@@ -1,104 +1,139 @@
 <template>
-	<!-- 规格弹窗 -->
-	<su-popup :show="show" round="10" @close="emits('close')">
+  <!-- 规格弹窗 -->
+  <su-popup :show="show" round="10" @close="emits('close')">
     <!-- SKU 信息 -->
-		<view class="ss-modal-box bg-white ss-flex-col">
-			<view class="modal-header ss-flex ss-col-center">
-				<view class="header-left ss-m-r-30">
-					<image class="sku-image" :src="state.selectedSku.picUrl || goodsInfo.picUrl" mode="aspectFill" />
-				</view>
-				<view class="header-right ss-flex-col ss-row-between ss-flex-1">
-					<view class="goods-title ss-line-2">{{ goodsInfo.name }}</view>
-					<view class="header-right-bottom ss-flex ss-col-center ss-row-between">
-						<view class="ss-flex">
-							<view class="price-text">
-								{{ fen2yuan( state.selectedSku.price || goodsInfo.price) }}
-							</view>
-						</view>
-						<view class="stock-text ss-m-l-20">
-							{{ formatStock('exact', state.selectedSku.stock || goodsInfo.stock) }}
-						</view>
-					</view>
-				</view>
-			</view>
+    <view class="ss-modal-box bg-white ss-flex-col">
+      <view class="modal-header ss-flex ss-col-center">
+        <view class="header-left ss-m-r-30">
+          <image
+            class="sku-image"
+            :src="state.selectedSku.picUrl || goodsInfo.picUrl"
+            mode="aspectFill"
+          />
+        </view>
+        <view class="header-right ss-flex-col ss-row-between ss-flex-1">
+          <view class="goods-title ss-line-2">{{ goodsInfo.name }}</view>
+          <view class="header-right-bottom ss-flex ss-col-center ss-row-between">
+            <view class="ss-flex">
+              <view class="price-text">
+                {{
+                  fen2yuan(
+                    state.selectedSku.promotionPrice || state.selectedSku.price || goodsInfo.price,
+                  )
+                }}
+                <text v-if="state.selectedSku.promotionType > 0">
+                  <text class="iconBox" v-if="state.selectedSku.promotionType === 4">
+                    限时优惠
+                  </text>
+                  <text class="iconBox" v-else-if="state.selectedSku.promotionType === 6">
+                    会员价
+                  </text>
+                  <text class="origin-price-text">
+                    {{ fen2yuan(state.selectedSku.price) }}
+                  </text>
+                </text>
+              </view>
+            </view>
+            <view class="stock-text ss-m-l-20">
+              {{ formatStock('exact', state.selectedSku.stock || goodsInfo.stock) }}
+            </view>
+          </view>
+        </view>
+      </view>
 
       <!-- 属性选择 -->
-			<view class="modal-content ss-flex-1">
-				<scroll-view scroll-y="true" class="modal-content-scroll" @touchmove.stop>
-					<view class="sku-item ss-m-b-20" v-for="property in propertyList" :key="property.id">
-						<view class="label-text ss-m-b-20">{{ property.name }}</view>
-						<view class="ss-flex ss-col-center ss-flex-wrap">
-							<button class="ss-reset-button spec-btn" v-for="value in property.values" 
-								:class="[
-                  { 'ui-BG-Main-Gradient': state.currentPropertyArray[property.id] === value.id },
-                  { 'disabled-btn': value.disabled === true }
+      <view class="modal-content ss-flex-1">
+        <scroll-view scroll-y="true" class="modal-content-scroll" @touchmove.stop>
+          <view class="sku-item ss-m-b-20" v-for="property in propertyList" :key="property.id">
+            <view class="label-text ss-m-b-20">{{ property.name }}</view>
+            <view class="ss-flex ss-col-center ss-flex-wrap">
+              <button
+                class="ss-reset-button spec-btn"
+                v-for="value in property.values"
+                :class="[
+                  {
+                    'ui-BG-Main-Gradient': state.currentPropertyArray[property.id] === value.id,
+                  },
+                  {
+                    'disabled-btn': value.disabled === true,
+                  },
                 ]"
-								:key="value.id" :disabled="value.disabled === true" @tap="onSelectSku(property.id, value.id)"
-							>
-								{{ value.name }}
-							</button>
-						</view>
-					</view>
-					<view class="buy-num-box ss-flex ss-col-center ss-row-between ss-m-b-40">
-						<view class="label-text">购买数量</view>
-						<su-number-box :min="1" :max="state.selectedSku.stock" :step="1" v-model="state.selectedSku.goods_num" @change="onNumberChange($event)" />
-					</view>
-				</scroll-view>
-			</view>
+                :key="value.id"
+                :disabled="value.disabled === true"
+                @tap="onSelectSku(property.id, value.id)"
+              >
+                {{ value.name }}
+              </button>
+            </view>
+          </view>
+          <view class="buy-num-box ss-flex ss-col-center ss-row-between ss-m-b-40">
+            <view class="label-text">购买数量</view>
+            <su-number-box
+              :min="1"
+              :max="state.selectedSku.stock"
+              :step="1"
+              v-model="state.selectedSku.goods_num"
+              @change="onNumberChange($event)"
+            />
+          </view>
+        </scroll-view>
+      </view>
 
       <!-- 操作区 -->
-			<view class="modal-footer border-top">
-				<view class="buy-box ss-flex ss-col-center ss-flex ss-col-center ss-row-center">
-					<button class="ss-reset-button add-btn ui-Shadow-Main" @tap="onAddCart">加入购物车</button>
-					<button class="ss-reset-button buy-btn ui-Shadow-Main" @tap="onBuy">立即购买</button>
-				</view>
-			</view>
-		</view>
-	</su-popup>
+      <view class="modal-footer border-top">
+        <view class="buy-box ss-flex ss-col-center ss-flex ss-col-center ss-row-center">
+          <button class="ss-reset-button add-btn ui-Shadow-Main" @tap="onAddCart"
+            >加入购物车</button
+          >
+          <button class="ss-reset-button buy-btn ui-Shadow-Main" @tap="onBuy">立即购买</button>
+        </view>
+      </view>
+    </view>
+  </su-popup>
 </template>
 
 <script setup>
-	import { computed, reactive, watch } from 'vue';
-	import sheep from '@/sheep';
+  import { computed, reactive, watch } from 'vue';
+  import sheep from '@/sheep';
   import { formatStock, convertProductPropertyList, fen2yuan } from '@/sheep/hooks/useGoods';
 
-	const emits = defineEmits(['change', 'addCart', 'buy', 'close']);
-	const props = defineProps({
-		goodsInfo: {
-			type: Object,
-			default () {},
-		},
-		show: {
-			type: Boolean,
-			default: false,
-		}
-	});
-
-	const state = reactive({
-		selectedSku: {}, // 选中的 SKU
-		currentPropertyArray: [], // 当前选中的属性,实际是个 Map。key 是 property 编号,value 是 value 编号
-	});
-
-	const propertyList = convertProductPropertyList(props.goodsInfo.skus);
-
-	// SKU 列表
-	const skuList = computed(() => {
-		let skuPrices = props.goodsInfo.skus;
+  const emits = defineEmits(['change', 'addCart', 'buy', 'close']);
+  const props = defineProps({
+    goodsInfo: {
+      type: Object,
+      default() {},
+    },
+    show: {
+      type: Boolean,
+      default: false,
+    },
+  });
+
+  const state = reactive({
+    selectedSku: {}, // 选中的 SKU
+    currentPropertyArray: [], // 当前选中的属性,实际是个 Map。key 是 property 编号,value 是 value 编号
+  });
+
+  const propertyList = convertProductPropertyList(props.goodsInfo.skus);
+  // SKU 列表
+  const skuList = computed(() => {
+    let skuPrices = props.goodsInfo.skus;
     for (let price of skuPrices) {
-      price.value_id_array = price.properties.map((item) => item.valueId)
+      price.value_id_array = price.properties.map((item) => item.valueId);
     }
-		return skuPrices;
-	});
-
-	watch(
-		() => state.selectedSku,
-		(newVal) => {
-			emits('change', newVal);
-		}, {
-			immediate: true, // 立即执行
-			deep: true, // 深度监听
-		},
-	);
+    return skuPrices;
+  });
+
+  watch(
+    () => state.selectedSku,
+    (newVal) => {
+      emits('change', newVal);
+    },
+    {
+      immediate: true, // 立即执行
+      deep: true, // 深度监听
+    },
+  );
 
   // 输入框改变数量
   function onNumberChange(e) {
@@ -108,21 +143,21 @@
   }
 
   // 加入购物车
-	function onAddCart() {
-		if (state.selectedSku.id <= 0) {
+  function onAddCart() {
+    if (state.selectedSku.id <= 0) {
       sheep.$helper.toast('请选择规格');
       return;
-		}
+    }
     if (state.selectedSku.stock <= 0) {
       sheep.$helper.toast('库存不足');
       return;
     }
 
     emits('addCart', state.selectedSku);
-	}
+  }
 
   // 立即购买
-	function onBuy() {
+  function onBuy() {
     if (state.selectedSku.id <= 0) {
       sheep.$helper.toast('请选择规格');
       return;
@@ -132,282 +167,298 @@
       return;
     }
     emits('buy', state.selectedSku);
-	}
+  }
 
-	// 改变禁用状态:计算每个 property 属性值的按钮,是否禁用
-	function changeDisabled(isChecked = false, propertyId = 0, valueId = 0) {
+  // 改变禁用状态:计算每个 property 属性值的按钮,是否禁用
+  function changeDisabled(isChecked = false, propertyId = 0, valueId = 0) {
     let newSkus = []; // 所有可以选择的 sku 数组
-		if (isChecked) {
-			// 情况一:选中 property
-			// 获得当前点击选中 property 的、所有可用 SKU
-			for (let price of skuList.value) {
-				if (price.stock <= 0) {
-					continue;
-				}
-				if (price.value_id_array.indexOf(valueId) >= 0) {
-					newSkus.push(price);
-				}
-			}
-		} else {
-			// 情况二:取消选中 property
-			// 当前所选 property 下,所有可以选择的 SKU
-			newSkus = getCanUseSkuList();
-		}
-
-		// 所有存在并且有库存未选择的 SKU 的 value 属性值 id
-		let noChooseValueIds = [];
-		for (let price of newSkus) {
-			noChooseValueIds = noChooseValueIds.concat(price.value_id_array);
-		}
-		noChooseValueIds = Array.from(new Set(noChooseValueIds)); // 去重
-
-		if (isChecked) {
-			// 去除当前选中的 value 属性值 id
-			let index = noChooseValueIds.indexOf(valueId);
-			noChooseValueIds.splice(index, 1);
-		} else {
-			// 循环去除当前已选择的 value 属性值 id
-			state.currentPropertyArray.forEach((currentPropertyId) => {
-				if (currentPropertyId.toString() !== '') {
+    if (isChecked) {
+      // 情况一:选中 property
+      // 获得当前点击选中 property 的、所有可用 SKU
+      for (let price of skuList.value) {
+        if (price.stock <= 0) {
+          continue;
+        }
+        if (price.value_id_array.indexOf(valueId) >= 0) {
+          newSkus.push(price);
+        }
+      }
+    } else {
+      // 情况二:取消选中 property
+      // 当前所选 property 下,所有可以选择的 SKU
+      newSkus = getCanUseSkuList();
+    }
+
+    // 所有存在并且有库存未选择的 SKU 的 value 属性值 id
+    let noChooseValueIds = [];
+    for (let price of newSkus) {
+      noChooseValueIds = noChooseValueIds.concat(price.value_id_array);
+    }
+    noChooseValueIds = Array.from(new Set(noChooseValueIds)); // 去重
+
+    if (isChecked) {
+      // 去除当前选中的 value 属性值 id
+      let index = noChooseValueIds.indexOf(valueId);
+      noChooseValueIds.splice(index, 1);
+    } else {
+      // 循环去除当前已选择的 value 属性值 id
+      state.currentPropertyArray.forEach((currentPropertyId) => {
+        if (currentPropertyId.toString() !== '') {
           return;
-				}
+        }
         // currentPropertyId 为空是反选 填充的
         let index = noChooseValueIds.indexOf(currentPropertyId);
         if (index >= 0) {
           // currentPropertyId 存在于 noChooseValueIds
           noChooseValueIds.splice(index, 1);
         }
-			});
-		}
+      });
+    }
 
     // 当前已选择的 property 数组
-		let choosePropertyIds = [];
-		if (!isChecked) {
-			// 当前已选择的 property
-			state.currentPropertyArray.forEach((currentPropertyId, currentValueId) => {
-				if (currentPropertyId !== '') {
-					// currentPropertyId 为空是反选 填充的
-					choosePropertyIds.push(currentValueId);
-				}
-			});
-		} else {
-			// 当前点击选择的 property
-			choosePropertyIds = [propertyId];
-		}
+    let choosePropertyIds = [];
+    if (!isChecked) {
+      // 当前已选择的 property
+      state.currentPropertyArray.forEach((currentPropertyId, currentValueId) => {
+        if (currentPropertyId !== '') {
+          // currentPropertyId 为空是反选 填充的
+          choosePropertyIds.push(currentValueId);
+        }
+      });
+    } else {
+      // 当前点击选择的 property
+      choosePropertyIds = [propertyId];
+    }
 
     for (let propertyIndex in propertyList) {
-			// 当前点击的 property、或者取消选择时候,已选中的 property 不进行处理
-			if (choosePropertyIds.indexOf(propertyList[propertyIndex]['id']) >= 0) {
-				continue;
-			}
+      // 当前点击的 property、或者取消选择时候,已选中的 property 不进行处理
+      if (choosePropertyIds.indexOf(propertyList[propertyIndex]['id']) >= 0) {
+        continue;
+      }
       // 如果当前 property id 不存在于有库存的 SKU 中,则禁用
       for (let valueIndex in propertyList[propertyIndex]['values']) {
-				propertyList[propertyIndex]['values'][valueIndex]['disabled'] =
+        propertyList[propertyIndex]['values'][valueIndex]['disabled'] =
           noChooseValueIds.indexOf(propertyList[propertyIndex]['values'][valueIndex]['id']) < 0; // true 禁用 or false 不禁用
-			}
-		}
-	}
-
-	// 当前所选属性下,获取所有有库存的 SKU 们
-	function getCanUseSkuList() {
-		let newSkus = [];
-		for (let sku of skuList.value) {
-			if (sku.stock <= 0) {
-				continue;
-			}
+      }
+    }
+  }
+
+  // 当前所选属性下,获取所有有库存的 SKU 们
+  function getCanUseSkuList() {
+    let newSkus = [];
+    for (let sku of skuList.value) {
+      if (sku.stock <= 0) {
+        continue;
+      }
       let isOk = true;
       state.currentPropertyArray.forEach((propertyId) => {
-				// propertyId 不为空,并且,这个 条 sku 没有被选中,则排除
-				if (propertyId.toString() !== '' && sku.value_id_array.indexOf(propertyId) < 0) {
-					isOk = false;
-				}
-			});
-			if (isOk) {
-				newSkus.push(sku);
-			}
-		}
-		return newSkus;
-	}
-
-	// 选择规格
-	function onSelectSku(propertyId, valueId) {
-		// 清空已选择
-		let isChecked = true; // 选中 or 取消选中
-		if (state.currentPropertyArray[propertyId] !== undefined && state.currentPropertyArray[propertyId] === valueId) {
-			// 点击已被选中的,删除并填充 ''
-			isChecked = false;
-			state.currentPropertyArray.splice(propertyId, 1, '');
-		} else {
-			// 选中
-			state.currentPropertyArray[propertyId] = valueId;
-		}
+        // propertyId 不为空,并且,这个 条 sku 没有被选中,则排除
+        if (propertyId.toString() !== '' && sku.value_id_array.indexOf(propertyId) < 0) {
+          isOk = false;
+        }
+      });
+      if (isOk) {
+        newSkus.push(sku);
+      }
+    }
+    return newSkus;
+  }
+
+  // 选择规格
+  function onSelectSku(propertyId, valueId) {
+    // 清空已选择
+    let isChecked = true; // 选中 or 取消选中
+    if (
+      state.currentPropertyArray[propertyId] !== undefined &&
+      state.currentPropertyArray[propertyId] === valueId
+    ) {
+      // 点击已被选中的,删除并填充 ''
+      isChecked = false;
+      state.currentPropertyArray.splice(propertyId, 1, '');
+    } else {
+      // 选中
+      state.currentPropertyArray[propertyId] = valueId;
+    }
 
     // 选中的 property 大类
-		let choosePropertyId = [];
-		state.currentPropertyArray.forEach((currentPropertyId) => {
-			if (currentPropertyId !== '') {
-				// currentPropertyId 为空是反选 填充的
-				choosePropertyId.push(currentPropertyId);
-			}
-		});
-
-		// 当前所选 property 下,所有可以选择的 SKU 们
-		let newSkuList = getCanUseSkuList();
-
-		// 判断所有 property 大类是否选择完成
-		if (choosePropertyId.length === propertyList.length && newSkuList.length) {
-			newSkuList[0].goods_num = state.selectedSku.goods_num || 1;
-			state.selectedSku = newSkuList[0];
-		} else {
-			state.selectedSku = {};
-		}
-
-		// 改变 property 禁用状态
-		changeDisabled(isChecked, propertyId, valueId);
-	}
-
-	changeDisabled(false);
-
-	// 默认选中规格
-	if (propertyList && propertyList.length) {
-		propertyList.forEach(e => {
-			if (e.values && e.values.length) onSelectSku(e.id, e.values[0].id)
-		})
-	}
+    let choosePropertyId = [];
+    state.currentPropertyArray.forEach((currentPropertyId) => {
+      if (currentPropertyId !== '') {
+        // currentPropertyId 为空是反选 填充的
+        choosePropertyId.push(currentPropertyId);
+      }
+    });
+
+    // 当前所选 property 下,所有可以选择的 SKU 们
+    let newSkuList = getCanUseSkuList();
+
+    // 判断所有 property 大类是否选择完成
+    if (choosePropertyId.length === propertyList.length && newSkuList.length) {
+      newSkuList[0].goods_num = state.selectedSku.goods_num || 1;
+      state.selectedSku = newSkuList[0];
+    } else {
+      state.selectedSku = {};
+    }
 
+    // 改变 property 禁用状态
+    changeDisabled(isChecked, propertyId, valueId);
+  }
 
+  changeDisabled(false);
   // TODO 芋艿:待讨论的优化点:1)单规格,要不要默认选中;2)默认要不要选中第一个规格
 </script>
 
 <style lang="scss" scoped>
-	// 购买
-	.buy-box {
-		padding: 10rpx 0;
-
-		.add-btn {
-			width: 356rpx;
-			height: 80rpx;
-			border-radius: 40rpx 0 0 40rpx;
-			background-color: var(--ui-BG-Main-light);
-			color: var(--ui-BG-Main);
-		}
-
-		.buy-btn {
-			width: 356rpx;
-			height: 80rpx;
-			border-radius: 0 40rpx 40rpx 0;
-			background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
-			color: #fff;
-		}
-
-		.score-btn {
-			width: 100%;
-			margin: 0 20rpx;
-			height: 80rpx;
-			border-radius: 40rpx;
-			background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
-			color: #fff;
-		}
-	}
-
-	.ss-modal-box {
-		border-radius: 30rpx 30rpx 0 0;
-		max-height: 1000rpx;
-
-		.modal-header {
-			position: relative;
-			padding: 80rpx 20rpx 40rpx;
-
-			.sku-image {
-				width: 160rpx;
-				height: 160rpx;
-				border-radius: 10rpx;
-			}
-
-			.header-right {
-				height: 160rpx;
-			}
-
-			.close-icon {
-				position: absolute;
-				top: 10rpx;
-				right: 20rpx;
-				font-size: 46rpx;
-				opacity: 0.2;
-			}
-
-			.goods-title {
-				font-size: 28rpx;
-				font-weight: 500;
-				line-height: 42rpx;
-			}
-
-			.score-img {
-				width: 36rpx;
-				height: 36rpx;
-				margin: 0 4rpx;
-			}
-
-			.score-text {
-				font-size: 30rpx;
-				font-weight: 500;
-				color: $red;
-				font-family: OPPOSANS;
-			}
-
-			.price-text {
-				font-size: 30rpx;
-				font-weight: 500;
-				color: $red;
-				font-family: OPPOSANS;
-
-				&::before {
-					content: '¥';
-					font-size: 30rpx;
-					font-weight: 500;
-					color: $red;
-				}
-			}
-
-			.stock-text {
-				font-size: 26rpx;
-				color: #999999;
-			}
-		}
-
-		.modal-content {
-			padding: 0 20rpx;
-
-			.modal-content-scroll {
-				max-height: 600rpx;
-
-				.label-text {
-					font-size: 26rpx;
-					font-weight: 500;
-				}
-
-				.buy-num-box {
-					height: 100rpx;
-				}
-
-				.spec-btn {
-					height: 60rpx;
-					min-width: 100rpx;
-					padding: 0 30rpx;
-					background: #f4f4f4;
-					border-radius: 30rpx;
-					color: #434343;
-					font-size: 26rpx;
-					margin-right: 10rpx;
-					margin-bottom: 10rpx;
-				}
-
-				.disabled-btn {
-					font-weight: 400;
-					color: #c6c6c6;
-					background: #f8f8f8;
-				}
-			}
-		}
-	}
-</style>
+  // 购买
+  .buy-box {
+    padding: 10rpx 0;
+
+    .add-btn {
+      width: 356rpx;
+      height: 80rpx;
+      border-radius: 40rpx 0 0 40rpx;
+      background-color: var(--ui-BG-Main-light);
+      color: var(--ui-BG-Main);
+    }
+
+    .buy-btn {
+      width: 356rpx;
+      height: 80rpx;
+      border-radius: 0 40rpx 40rpx 0;
+      background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
+      color: #fff;
+    }
+
+    .score-btn {
+      width: 100%;
+      margin: 0 20rpx;
+      height: 80rpx;
+      border-radius: 40rpx;
+      background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
+      color: #fff;
+    }
+  }
+
+  .ss-modal-box {
+    border-radius: 30rpx 30rpx 0 0;
+    max-height: 1000rpx;
+
+    .modal-header {
+      position: relative;
+      padding: 80rpx 20rpx 40rpx;
+
+      .sku-image {
+        width: 160rpx;
+        height: 160rpx;
+        border-radius: 10rpx;
+      }
+
+      .header-right {
+        height: 160rpx;
+      }
+
+      .close-icon {
+        position: absolute;
+        top: 10rpx;
+        right: 20rpx;
+        font-size: 46rpx;
+        opacity: 0.2;
+      }
+
+      .goods-title {
+        font-size: 28rpx;
+        font-weight: 500;
+        line-height: 42rpx;
+      }
+
+      .score-img {
+        width: 36rpx;
+        height: 36rpx;
+        margin: 0 4rpx;
+      }
+
+      .score-text {
+        font-size: 30rpx;
+        font-weight: 500;
+        color: $red;
+        font-family: OPPOSANS;
+      }
+
+      .price-text {
+        font-size: 30rpx;
+        font-weight: 500;
+        color: $red;
+        font-family: OPPOSANS;
+
+        &::before {
+          content: '¥';
+          font-size: 30rpx;
+          font-weight: 500;
+          color: $red;
+        }
+      }
+
+      .stock-text {
+        font-size: 26rpx;
+        color: #999999;
+      }
+    }
+
+    .modal-content {
+      padding: 0 20rpx;
+
+      .modal-content-scroll {
+        max-height: 600rpx;
+
+        .label-text {
+          font-size: 26rpx;
+          font-weight: 500;
+        }
+
+        .buy-num-box {
+          height: 100rpx;
+        }
+
+        .spec-btn {
+          height: 60rpx;
+          min-width: 100rpx;
+          padding: 0 30rpx;
+          background: #f4f4f4;
+          border-radius: 30rpx;
+          color: #434343;
+          font-size: 26rpx;
+          margin-right: 10rpx;
+          margin-bottom: 10rpx;
+        }
+
+        .disabled-btn {
+          font-weight: 400;
+          color: #c6c6c6;
+          background: #f8f8f8;
+        }
+      }
+    }
+  }
+
+  .iconBox {
+    width: fit-content;
+    height: fit-content;
+    padding: 2rpx 10rpx;
+    background-color: rgb(255, 242, 241);
+    color: #ff2621;
+    font-size: 24rpx;
+    margin-left: 5rpx;
+  }
+
+  .origin-price-text {
+    font-size: 26rpx;
+    font-weight: 400;
+    text-decoration: line-through;
+    color: $gray-c;
+    font-family: OPPOSANS;
+
+    &::before {
+      content: '¥';
+    }
+  }
+</style>

+ 158 - 48
sheep/hooks/useGoods.js

@@ -11,7 +11,7 @@ import { formatDate } from '@/sheep/util';
  */
 export function formatSales(type, num) {
   let prefix = type !== 'exact' && num < 10 ? '销量' : '已售';
-  return formatNum(prefix, type, num)
+  return formatNum(prefix, type, num);
 }
 
 /**
@@ -21,10 +21,9 @@ export function formatSales(type, num) {
  * @return {string} 格式化后的销量字符串
  */
 export function formatExchange(type, num) {
-  return formatNum('已兑换', type, num)
+  return formatNum('已兑换', type, num);
 }
 
-
 /**
  * 格式化库存
  * @param {'exact' | any} type 格式类型:exact=精确值,其它=大致数量
@@ -32,7 +31,7 @@ export function formatExchange(type, num) {
  * @return {string} 格式化后的销量字符串
  */
 export function formatStock(type, num) {
-  return formatNum('库存', type, num)
+  return formatNum('库存', type, num);
 }
 
 /**
@@ -43,7 +42,7 @@ export function formatStock(type, num) {
  * @return {string} 格式化后的销量字符串
  */
 export function formatNum(prefix, type, num) {
-  num = (num || 0);
+  num = num || 0;
   // 情况一:精确数值
   if (type === 'exact') {
     return prefix + num;
@@ -67,7 +66,7 @@ export function formatPrice(e) {
 }
 
 // 视频格式后缀列表
-const VIDEO_SUFFIX_LIST = ['.avi', '.mp4']
+const VIDEO_SUFFIX_LIST = ['.avi', '.mp4'];
 
 /**
  * 转换商品轮播的链接列表:根据链接的后缀,判断是视频链接还是图片链接
@@ -76,12 +75,19 @@ const VIDEO_SUFFIX_LIST = ['.avi', '.mp4']
  * @return {{src: string, type: 'video' | 'image' }[]}  转换后的链接列表
  */
 export function formatGoodsSwiper(urlList) {
-  return urlList?.filter(url => url).map((url, key) => {
-    const isVideo = VIDEO_SUFFIX_LIST.some(suffix => url.includes(suffix));
-    const type = isVideo ? 'video' : 'image'
-    const src = $url.cdn(url);
-    return { type, src }
-  }) || [];
+  return (
+    urlList
+      ?.filter((url) => url)
+      .map((url, key) => {
+        const isVideo = VIDEO_SUFFIX_LIST.some((suffix) => url.includes(suffix));
+        const type = isVideo ? 'video' : 'image';
+        const src = $url.cdn(url);
+        return {
+          type,
+          src,
+        };
+      }) || []
+  );
 }
 
 /**
@@ -94,9 +100,7 @@ export function formatOrderColor(order) {
   if (order.status === 0) {
     return 'info-color';
   }
-  if (order.status === 10
-    || order.status === 20
-    || (order.status === 30 && !order.commentStatus)) {
+  if (order.status === 10 || order.status === 20 || (order.status === 30 && !order.commentStatus)) {
     return 'warning-color';
   }
   if (order.status === 30 && order.commentStatus) {
@@ -139,7 +143,7 @@ export function formatOrderStatus(order) {
  */
 export function formatOrderStatusDescription(order) {
   if (order.status === 0) {
-    return `请在 ${ formatDate(order.payExpireTime) } 前完成支付`;
+    return `请在 ${formatDate(order.payExpireTime)} 前完成支付`;
   }
   if (order.status === 10) {
     return '商家未发货,请耐心等待';
@@ -162,24 +166,30 @@ export function formatOrderStatusDescription(order) {
  * @param order 订单
  */
 export function handleOrderButtons(order) {
-  order.buttons = []
-  if (order.type === 3) { // 查看拼团
+  order.buttons = [];
+  if (order.type === 3) {
+    // 查看拼团
     order.buttons.push('combination');
   }
-  if (order.status === 20) { // 确认收货
+  if (order.status === 20) {
+    // 确认收货
     order.buttons.push('confirm');
   }
-  if (order.logisticsId > 0) { // 查看物流
+  if (order.logisticsId > 0) {
+    // 查看物流
     order.buttons.push('express');
   }
-  if (order.status === 0) { // 取消订单 / 发起支付
+  if (order.status === 0) {
+    // 取消订单 / 发起支付
     order.buttons.push('cancel');
     order.buttons.push('pay');
   }
-  if (order.status === 30 && !order.commentStatus) { // 发起评价
+  if (order.status === 30 && !order.commentStatus) {
+    // 发起评价
     order.buttons.push('comment');
   }
-  if (order.status === 40) { // 删除订单
+  if (order.status === 40) {
+    // 删除订单
     order.buttons.push('delete');
   }
 }
@@ -257,10 +267,12 @@ export function formatAfterSaleStatusDescription(afterSale) {
  */
 export function handleAfterSaleButtons(afterSale) {
   afterSale.buttons = [];
-  if ([10, 20, 30].includes(afterSale.status)) { // 取消订单
+  if ([10, 20, 30].includes(afterSale.status)) {
+    // 取消订单
     afterSale.buttons.push('cancel');
   }
-  if (afterSale.status === 20) { // 退货信息
+  if (afterSale.status === 20) {
+    // 退货信息
     afterSale.buttons.push('delivery');
   }
 }
@@ -324,7 +336,28 @@ function getDayjsTime(time) {
  * @returns {string} 元,例如说 1.00 元
  */
 export function fen2yuan(price) {
-  return (price / 100.0).toFixed(2)
+  return (price / 100.0).toFixed(2);
+}
+
+/**
+ * 将分转成元
+ *
+ * 如果没有小数点,则不展示小数点部分
+ *
+ * @param price 分,例如说 100 分
+ * @returns {string} 元,例如说 1 元
+ */
+export function fen2yuanSimple(price) {
+  return fen2yuan(price).replace(/\.?0+$/, '');
+}
+
+/**
+ * 将折扣百分比转化为“打x者”的 x 部分
+ *
+ * @param discountPercent
+ */
+export function formatDiscountPercent(discountPercent) {
+  return (discountPercent / 10.0).toFixed(1).replace(/\.?0+$/, '');
 }
 
 /**
@@ -345,45 +378,122 @@ export function convertProductPropertyList(skus) {
   let result = [];
   for (const sku of skus) {
     if (!sku.properties) {
-      continue
+      continue;
     }
     for (const property of sku.properties) {
       // ① 先处理属性
-      let resultProperty = result.find(item => item.id === property.propertyId)
+      let resultProperty = result.find((item) => item.id === property.propertyId);
       if (!resultProperty) {
         resultProperty = {
           id: property.propertyId,
           name: property.propertyName,
-          values: []
-        }
-        result.push(resultProperty)
+          values: [],
+        };
+        result.push(resultProperty);
       }
       // ② 再处理属性值
-      let resultValue = resultProperty.values.find(item => item.id === property.valueId)
+      let resultValue = resultProperty.values.find((item) => item.id === property.valueId);
       if (!resultValue) {
         resultProperty.values.push({
           id: property.valueId,
-          name: property.valueName
-        })
+          name: property.valueName,
+        });
       }
     }
   }
   return result;
 }
 
-/**
- * 格式化满减送活动的规则
- *
- * @param activity 活动信息
- * @param rule 优惠规格
- * @returns {string} 规格字符串
- */
-export function formatRewardActivityRule(activity, rule) {
-  if (activity.conditionType === 10) {
-    return `满 ${fen2yuan(rule.limit)} 元减 ${fen2yuan(rule.discountPrice)} 元`;
-  }
-  if (activity.conditionType === 20) {
-    return `满 ${rule.limit} 件减 ${fen2yuan(rule.discountPrice)} 元`;
+export function appendSettlementProduct(spus, settlementInfos) {
+  if (!settlementInfos || settlementInfos.length === 0) {
+    return;
+  }
+  for (const spu of spus) {
+    const settlementInfo = settlementInfos.find((info) => info.spuId === spu.id);
+    if (!settlementInfo) {
+      return;
+    }
+    // 选择价格最小的 SKU 设置到 SPU 上
+    const settlementSku = settlementInfo.skus
+      .filter((sku) => sku.promotionPrice > 0)
+      .reduce((prev, curr) => (prev.promotionPrice < curr.promotionPrice ? prev : curr), []);
+    if (settlementSku) {
+      spu.promotionType = settlementSku.promotionType;
+      spu.promotionPrice = settlementSku.promotionPrice;
+    }
+    // 设置【满减送】活动
+    if (settlementInfo.rewardActivity) {
+      spu.rewardActivity = settlementInfo.rewardActivity;
+    }
   }
-  return '';
+}
+
+// 获得满减送活动的规则描述(group)
+export function getRewardActivityRuleGroupDescriptions(activity) {
+  if (!activity || !activity.rules || activity.rules.length === 0) {
+    return [];
+  }
+  const result = [
+    { name: '满减', values: [] },
+    { name: '赠品', values: [] },
+    { name: '包邮', values: [] },
+  ];
+  activity.rules.forEach((rule) => {
+    const conditionTypeStr =
+      activity.conditionType === 10 ? `满 ${fen2yuanSimple(rule.limit)} 元` : `满 ${rule.limit} 件`;
+    // 满减
+    if (rule.limit) {
+      result[0].values.push(`${conditionTypeStr} 减 ${fen2yuanSimple(rule.discountPrice)} 元`);
+    }
+    // 赠品
+    if (rule.point || (rule.giveCouponTemplateCounts && rule.giveCouponTemplateCounts.length > 0)) {
+      let tips = [];
+      if (rule.point) {
+        tips.push(`送 ${rule.point} 积分`);
+      }
+      if (rule.giveCouponTemplateCounts && rule.giveCouponTemplateCounts.length > 0) {
+        tips.push(`送 ${rule.giveCouponTemplateCounts.length} 张优惠券`);
+      }
+      result[1].values.push(`${conditionTypeStr} ${tips.join('、')}`);
+    }
+    // 包邮
+    if (rule.freeDelivery) {
+      result[2].values.push(`${conditionTypeStr} 包邮`);
+    }
+  });
+  // 移除 values 为空的元素
+  result.forEach((item) => {
+    if (item.values.length === 0) {
+      result.splice(result.indexOf(item), 1);
+    }
+  });
+  return result;
+}
+
+// 获得满减送活动的规则描述(item)
+export function getRewardActivityRuleItemDescriptions(activity) {
+  if (!activity || !activity.rules || activity.rules.length === 0) {
+    return [];
+  }
+  const result = [];
+  activity.rules.forEach((rule) => {
+    const conditionTypeStr =
+      activity.conditionType === 10 ? `满${fen2yuanSimple(rule.limit)}元` : `满${rule.limit}件`;
+    // 满减
+    if (rule.limit) {
+      result.push(`${conditionTypeStr}减${fen2yuanSimple(rule.discountPrice)}元`);
+    }
+    // 赠品
+    if (rule.point) {
+      result.push(`${conditionTypeStr}送${rule.point}积分`);
+    }
+    if (rule.giveCouponTemplateCounts && rule.giveCouponTemplateCounts.length > 0) {
+      result.push(`${conditionTypeStr}送${rule.giveCouponTemplateCounts.length}张优惠券`);
+    }
+    // 包邮
+    if (rule.freeDelivery) {
+      result.push(`${conditionTypeStr}包邮`);
+    }
+  });
+  return result;
 }