| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385 | <!-- 秒杀活动列表 --><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>
 |