386 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Vue
		
	
	
			
		
		
	
	
			386 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Vue
		
	
	
| <!-- 秒杀活动列表 -->
 | ||
| <template>
 | ||
|   <s-layout navbar="inner" :bgStyle="{ color: 'rgb(245,28,19)' }">
 | ||
|     <!--顶部背景图-->
 | ||
|     <view
 | ||
|         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">
 | ||
|         <block v-for="(picUrl, index) in activeTimeConfig.sliderPicUrls" :key="index">
 | ||
|           <swiper-item class="borRadius14">
 | ||
|             <image :src="picUrl" class="slide-image borRadius14" lazy-load />
 | ||
|           </swiper-item>
 | ||
|         </block>
 | ||
|       </swiper>
 | ||
|     </view>
 | ||
|     <!-- 时间段列表 -->
 | ||
|     <view class="flex align-center justify-between ss-p-25">
 | ||
|       <!-- 左侧图标 -->
 | ||
|       <view class="time-icon">
 | ||
|         <!-- TODO 芋艿:图片统一维护 -->
 | ||
|         <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)">
 | ||
|           <!-- 活动起始时间 -->
 | ||
|           <view class="time">{{ config.startTime }}</view>
 | ||
|           <!-- 活动状态 -->
 | ||
|           <view class="status">{{ config.status }}</view>
 | ||
|         </view>
 | ||
|       </scroll-view>
 | ||
|     </view>
 | ||
| 
 | ||
|     <!-- 内容区 -->
 | ||
|     <view class="list-content">
 | ||
|       <!-- 活动倒计时 -->
 | ||
|       <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-title ss-m-r-12">距结束</view>
 | ||
|             <view class="ss-flex countdown-time">
 | ||
|               <view class="ss-flex countdown-h">{{ countDown.h }}</view>
 | ||
|               <view class="ss-m-x-4">:</view>
 | ||
|               <view class="countdown-num ss-flex ss-row-center">{{ countDown.m }}</view>
 | ||
|               <view class="ss-m-x-4">:</view>
 | ||
|               <view class="countdown-num ss-flex ss-row-center">{{ countDown.s }}</view>
 | ||
|             </view>
 | ||
|           </view>
 | ||
|           <view v-else> {{ activeTimeConfig?.status }} </view>
 | ||
|         </view>
 | ||
|       </view>
 | ||
| 
 | ||
|       <!-- 活动列表 -->
 | ||
|       <scroll-view
 | ||
|         class="scroll-box"
 | ||
|         :style="{ height: pageHeight + 'rpx' }"
 | ||
|         scroll-y="true"
 | ||
|         :scroll-with-animation="false"
 | ||
|         :enable-back-to-top="true"
 | ||
|       >
 | ||
|         <view class="goods-box ss-m-b-20" v-for="activity in activityList" :key="activity.id">
 | ||
|           <s-goods-column
 | ||
|             size="lg"
 | ||
|             :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>
 | ||
|               <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>
 | ||
|             </template>
 | ||
|           </s-goods-column>
 | ||
|         </view>
 | ||
|         <uni-load-more
 | ||
|           v-if="activityTotal > 0"
 | ||
|           :status="loadStatus"
 | ||
|           :content-text="{
 | ||
|             contentdown: '上拉加载更多',
 | ||
|           }"
 | ||
|           @tap="loadMore"
 | ||
|         />
 | ||
|       </scroll-view>
 | ||
|     </view>
 | ||
|   </s-layout>
 | ||
| </template>
 | ||
| <script setup>
 | ||
|   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";
 | ||
| 
 | ||
|   // 计算页面高度
 | ||
|   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 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 },
 | ||
|   };
 | ||
| 
 | ||
|   //#region 时间段
 | ||
|   // 时间段列表
 | ||
|   const timeConfigList = ref([])
 | ||
|   // 查询时间段
 | ||
|   const getSeckillConfigList = async () => {
 | ||
|     const { data } = await SeckillApi.getSeckillConfigList()
 | ||
|     const now = dayjs();
 | ||
|     const today = now.format('YYYY-MM-DD')
 | ||
|     // 判断时间段的状态
 | ||
|     data.forEach((config, index) => {
 | ||
|       const startTime = dayjs(`${today} ${config.startTime}`)
 | ||
|       const endTime = dayjs(`${today} ${config.endTime}`)
 | ||
|       if (now.isBefore(startTime)) {
 | ||
|         config.status = TimeStatusEnum.WAIT_START;
 | ||
|       } else if (now.isAfter(endTime)) {
 | ||
|         config.status = TimeStatusEnum.END;
 | ||
|       } else {
 | ||
|         config.status = TimeStatusEnum.STARTED;
 | ||
|         activeTimeIndex.value = index;
 | ||
|       }
 | ||
|     })
 | ||
|     timeConfigList.value = data
 | ||
|     // 默认选中进行中的活动
 | ||
|     handleChangeTimeConfig(activeTimeIndex.value);
 | ||
|     // 滚动到进行中的时间段
 | ||
|     scrollToTimeConfig(activeTimeIndex.value)
 | ||
|   }
 | ||
| 
 | ||
|   // 滚动到指定时间段
 | ||
|   const activeTimeElId = ref('') // 当前选中的时间段的元素ID
 | ||
|   const scrollToTimeConfig = (index) => {
 | ||
|     nextTick(() => activeTimeElId.value = `timeItem${index}`)
 | ||
|   }
 | ||
| 
 | ||
|   // 切换时间段
 | ||
|   const activeTimeIndex = ref(0) // 当前选中的时间段的索引
 | ||
|   const activeTimeConfig = computed(() => timeConfigList.value[activeTimeIndex.value]) // 当前选中的时间段
 | ||
|   const handleChangeTimeConfig = (index) => {
 | ||
|     activeTimeIndex.value = index
 | ||
| 
 | ||
|     // 查询活动列表
 | ||
|     activityPageParams.pageNo = 1
 | ||
|     activityList.value = []
 | ||
|     getActivityList();
 | ||
|   }
 | ||
| 
 | ||
|   // 倒计时
 | ||
|   const countDown = computed(() => {
 | ||
|     const endTime = activeTimeConfig.value?.endTime
 | ||
|     if (endTime) {
 | ||
|       return useDurationTime(`${dayjs().format('YYYY-MM-DD')} ${endTime}`);
 | ||
|     }
 | ||
|   });
 | ||
| 
 | ||
|   //#endregion
 | ||
| 
 | ||
|   //#region 分页查询活动列表
 | ||
| 
 | ||
|   // 查询活动列表
 | ||
|   const activityPageParams = reactive({
 | ||
|     id: 0, // 时间段 ID
 | ||
|     pageNo: 1, // 页码
 | ||
|     pageSize: 5, // 每页数量
 | ||
|   })
 | ||
|   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 => {
 | ||
|       // 计算抢购进度
 | ||
|       activity.percent = parseInt(100 * (activity.totalStock - activity.stock) / activity.totalStock);
 | ||
|     })
 | ||
|     activityList.value = activityList.value.concat(...data.list);
 | ||
|     activityTotal.value = data.total;
 | ||
| 
 | ||
|     loadStatus.value = activityList.value.length < activityTotal.value ? 'more' : 'noMore';
 | ||
|   }
 | ||
| 
 | ||
|   // 加载更多
 | ||
|   function loadMore() {
 | ||
|     if (loadStatus.value !== 'noMore') {
 | ||
|       activityPageParams.pageNo += 1
 | ||
|       getActivityList();
 | ||
|     }
 | ||
|   }
 | ||
|   // 上拉加载更多
 | ||
|   onReachBottom(() => loadMore());
 | ||
| 
 | ||
|   //#endregion
 | ||
| 
 | ||
|   // 页面初始化
 | ||
|   onLoad(async () => {
 | ||
|     await getSeckillConfigList()
 | ||
|   });
 | ||
| </script>
 | ||
| <style lang="scss" scoped>
 | ||
|   // 顶部背景图
 | ||
|   .page-bg {
 | ||
|     width: 100%;
 | ||
|     height: 458rpx;
 | ||
|     background: v-bind(headerBg) no-repeat;
 | ||
|     background-size: 100% 100%;
 | ||
|   }
 | ||
| 
 | ||
|   // 时间段轮播图
 | ||
|   .header {
 | ||
|     width: 710rpx;
 | ||
|     height: 330rpx;
 | ||
|     margin: -276rpx auto 0 auto;
 | ||
|     border-radius: 14rpx;
 | ||
|     overflow: hidden;
 | ||
|     swiper{
 | ||
|       height: 330rpx !important;
 | ||
|       border-radius: 14rpx;
 | ||
|       overflow: hidden;
 | ||
|     }
 | ||
| 
 | ||
|     image {
 | ||
|       width: 100%;
 | ||
|       height: 100%;
 | ||
|       border-radius: 14rpx;
 | ||
|       overflow: hidden;
 | ||
|       img{
 | ||
|         border-radius: 14rpx;
 | ||
|       }
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   // 时间段列表:左侧图标
 | ||
|   .time-icon {
 | ||
|     width: 75rpx;
 | ||
|     height: 70rpx;
 | ||
|   }
 | ||
|   // 时间段列表
 | ||
|   .time-list {
 | ||
|     width: 596rpx;
 | ||
|     white-space: nowrap;
 | ||
|     // 时间段
 | ||
|     .item {
 | ||
|       display: inline-block;
 | ||
|       font-size: 20rpx;
 | ||
|       color: #666;
 | ||
|       text-align: center;
 | ||
|       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;
 | ||
|           line-height: 30rpx;
 | ||
|           border-radius: 15rpx;
 | ||
|           width: 128rpx;
 | ||
|           background: linear-gradient(90deg, var(--ui-BG-Main) 0%, var(--ui-BG-Main-gradient) 100%);
 | ||
|           color: #fff;
 | ||
|         }
 | ||
|       }
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   // 内容区
 | ||
|   .list-content {
 | ||
|     position: relative;
 | ||
|     z-index: 3;
 | ||
|     margin: 0 20rpx 0 20rpx;
 | ||
|     background: #fff;
 | ||
|     border-radius: 20rpx 20rpx 0 0;
 | ||
|     .content-header {
 | ||
|       width: 100%;
 | ||
|       border-radius: 20rpx 20rpx 0 0;
 | ||
|       height: 150rpx;
 | ||
|       background: linear-gradient(180deg, #fff4f7, #ffe6ec);
 | ||
| 
 | ||
|       .content-header-box {
 | ||
|         width: 678rpx;
 | ||
|         height: 64rpx;
 | ||
|         background: rgba($color: #fff, $alpha: 0.66);
 | ||
|         border-radius: 32px;
 | ||
|         // 场次倒计时内容
 | ||
|         .countdown-title {
 | ||
|           font-size: 28rpx;
 | ||
|           font-weight: 500;
 | ||
|           color: #333333;
 | ||
|           line-height: 28rpx;
 | ||
|         }
 | ||
|         // 场次倒计时
 | ||
|         .countdown-time {
 | ||
|           font-size: 28rpx;
 | ||
|           color: rgba(#ed3c30, 0.23);
 | ||
|           // 场次倒计时:小时部分
 | ||
|           .countdown-h {
 | ||
|             font-size: 24rpx;
 | ||
|             font-family: OPPOSANS;
 | ||
|             font-weight: 500;
 | ||
|             color: #ffffff;
 | ||
|             padding: 0 4rpx;
 | ||
|             height: 40rpx;
 | ||
|             background: rgba(#ed3c30, 0.23);
 | ||
|             border-radius: 6rpx;
 | ||
|           }
 | ||
|           // 场次倒计时:分钟、秒
 | ||
|           .countdown-num {
 | ||
|             font-size: 24rpx;
 | ||
|             font-family: OPPOSANS;
 | ||
|             font-weight: 500;
 | ||
|             color: #ffffff;
 | ||
|             width: 40rpx;
 | ||
|             height: 40rpx;
 | ||
|             background: rgba(#ed3c30, 0.23);
 | ||
|             border-radius: 6rpx;
 | ||
|           }
 | ||
|         }
 | ||
|       }
 | ||
|     }
 | ||
|     // 活动列表
 | ||
|     .scroll-box {
 | ||
|       height: 900rpx;
 | ||
|       // 活动
 | ||
|       .goods-box {
 | ||
|         position: relative;
 | ||
|         // 抢购按钮
 | ||
|         .cart-btn {
 | ||
|           position: absolute;
 | ||
|           bottom: 10rpx;
 | ||
|           right: 20rpx;
 | ||
|           z-index: 11;
 | ||
|           height: 44rpx;
 | ||
|           line-height: 50rpx;
 | ||
|           padding: 0 20rpx;
 | ||
|           border-radius: 25rpx;
 | ||
|           font-size: 24rpx;
 | ||
|           color: #fff;
 | ||
|           background: linear-gradient(90deg, #ff6600 0%, #fe832a 100%);
 | ||
| 
 | ||
|           &.disabled {
 | ||
|             background: $gray-b;
 | ||
|             color: #fff;
 | ||
|           }
 | ||
|         }
 | ||
|         // 秒杀限量商品数
 | ||
|         .limit {
 | ||
|           font-size: 22rpx;
 | ||
|           color: $dark-9;
 | ||
|           margin-bottom: 5rpx;
 | ||
|         }
 | ||
|       }
 | ||
|     }
 | ||
|   }
 | ||
| </style>
 |