<!-- 秒杀活动列表 --> <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, config.id)" > <!-- 活动起始时间 --> <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" > <!-- 抢购进度 --> <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 }, ]" 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> </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'); const select = ref([]); // 判断时间段的状态 data.forEach((config, index) => { 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)) { config.status = TimeStatusEnum.END; } else { config.status = TimeStatusEnum.STARTED; activeTimeIndex.value = index; } }); timeConfigList.value = data; // 默认选中进行中的活动 handleChangeTimeConfig(activeTimeIndex.value, select.value[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, id) => { activeTimeIndex.value = index; // 查询活动列表 activityPageParams.pageNo = 1; activityPageParams.configId = id; 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({ configId: 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>