<!-- 秒杀商品详情 --> <template> <s-layout :onShareAppMessage="shareInfo" navbar="goods"> <!-- 标题栏 --> <detailNavbar /> <!-- 骨架屏 --> <detailSkeleton v-if="state.skeletonLoading" /> <!-- 下架/售罄提醒 --> <s-empty v-else-if="state.goodsInfo === null || state.goodsInfo.activity_type !== 'seckill'" 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="state.goodsSwiper" dotStyle="tag" imageMode="widthFix" dotCur="bg-mask-40" :seizeHeight="750" /> <!-- 价格+标题 --> <view class="title-card ss-m-y-14 ss-m-x-20 ss-p-x-20 ss-p-y-34"> <view class="price-box ss-flex ss-row-between ss-m-b-18"> <view class="ss-flex"> <view class="price-text ss-m-r-16"> {{ fen2yuan(state.selectedSku.price || state.goodsInfo.price) }} </view> <view class="tig ss-flex ss-col-center"> <view class="tig-icon ss-flex ss-col-center ss-row-center"> <text class="cicon-alarm"></text> </view> <view class="tig-title">秒杀价</view> </view> </view> <view class="countdown-box" v-if="endTime.ms > 0"> <view class="countdown-title ss-m-b-20">距结束仅剩</view> <view class="ss-flex countdown-time"> <view class="ss-flex countdown-h">{{ endTime.h }}</view> <view class="ss-m-x-4">:</view> <view class="countdown-num ss-flex ss-row-center">{{ endTime.m }}</view> <view class="ss-m-x-4">:</view> <view class="countdown-num ss-flex ss-row-center">{{ endTime.s }}</view> </view> </view> <view class="countdown-title" v-else> 活动已结束 </view> </view> <view class="ss-flex ss-row-between ss-m-b-60"> <view class="origin-price ss-flex ss-col-center" v-if="state.goodsInfo.marketPrice"> 原价 <view class="origin-price-text"> {{ fen2yuan(state.selectedSku.marketPrice || state.goodsInfo.marketPrice) }} </view> </view> <detail-progress :percent="state.percent" /> </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 :sku="state.selectedSku" @tap="state.showSelectSku = true" /> </view> <!-- 规格与数量弹框 --> <s-select-seckill-sku v-model="state.goodsInfo" :show="state.showSelectSku" :single-limit-count="activity.singleLimitCount" @buy="onBuy" @change="onSkuChange" @close="state.showSelectSku = false" /> </view> <!-- 评价 --> <detail-comment-card class="detail-comment-selector" :goodsId="state.goodsInfo.id" /> <!-- 详情 --> <detail-content-card class="detail-content-selector" :content="state.goodsInfo.description" /> <!-- 详情tabbar --> <detail-tabbar v-model="state.goodsInfo"> <!-- TODO: 缺货中 已售罄 判断 设计--> <view class="buy-box ss-flex ss-col-center ss-p-r-20"> <button class="ss-reset-button origin-price-btn ss-flex-col" v-if="state.goodsInfo.marketPrice" @tap="sheep.$router.go('/pages/goods/index', { id: state.goodsInfo.id })" > <view> <view class="btn-price">{{ fen2yuan(state.goodsInfo.marketPrice) }}</view> <view>原价购买</view> </view> </button> <button v-else class="ss-reset-button origin-price-btn ss-flex-col"> <view class="no-original" :class="state.goodsInfo.stock === 0 || timeStatusEnum !== TimeStatusEnum.STARTED ? '' : ''" > 秒杀价 </view> </button> <button class="ss-reset-button btn-box ss-flex-col" @tap="state.showSelectSku = true" :class=" timeStatusEnum === TimeStatusEnum.STARTED && state.goodsInfo.stock != 0 ? 'check-btn-box' : 'disabled-btn-box' " :disabled="state.goodsInfo.stock === 0 || timeStatusEnum !== TimeStatusEnum.STARTED" > <view class="btn-price">{{ fen2yuan(state.goodsInfo.price) }}</view> <view v-if="timeStatusEnum === TimeStatusEnum.STARTED"> <view v-if="state.goodsInfo.stock === 0">已售罄</view> <view v-else>立即秒杀</view> </view> <view v-else>{{ timeStatusEnum }}</view> </button> </view> </detail-tabbar> </block> </s-layout> </template> <script setup> import {reactive, computed, ref} from 'vue'; import { onLoad, onPageScroll } from '@dcloudio/uni-app'; import sheep from '@/sheep'; import {isEmpty, min} from 'lodash'; import {useDurationTime, 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 detailProgress from './components/detail/detail-progress.vue'; import SeckillApi from "@/sheep/api/promotion/seckill"; import SpuApi from "@/sheep/api/product/spu"; import {getTimeStatusEnum, TimeStatusEnum} from "@/sheep/util/const"; const headerBg = sheep.$url.css('/static/img/shop/goods/seckill-bg.png'); const btnBg = sheep.$url.css('/static/img/shop/goods/seckill-btn.png'); const disabledBtnBg = sheep.$url.css( '/static/img/shop/goods/activity-btn-disabled.png', ); const seckillBg = sheep.$url.css('/static/img/shop/goods/seckill-tip-bg.png'); const grouponBg = sheep.$url.css('/static/img/shop/goods/groupon-tip-bg.png'); onPageScroll(() => {}); const state = reactive({ skeletonLoading: true, goodsInfo: {}, showSelectSku: false, goodsSwiper: [], selectedSku: {}, showModel: false, total: 0, percent: 0, price: '', }); const endTime = computed(() => { return useDurationTime(activity.value.endTime); }); // 规格变更 function onSkuChange(e) { state.selectedSku = e; } // 立即购买 function onBuy(sku) { sheep.$router.go('/pages/order/confirm', { data: JSON.stringify({ order_type: 'goods', buy_type: 'seckill', seckillActivityId: activity.value.id, items: [ { skuId: sku.id, count: sku.count, }, ], }), }); } // 分享信息 TODO 芋艿:待接入 const shareInfo = computed(() => { if (isEmpty(activity)) return {}; return sheep.$platform.share.getShareInfo( { title: activity.value.name, image: sheep.$url.cdn(state.goodsInfo.picUrl), params: { page: '4', query: activity.value.id, }, }, { type: 'goods', // 商品海报 title: activity.value.name, // 商品标题 image: sheep.$url.cdn(state.goodsInfo.picUrl), // 商品主图 price: state.goodsInfo.price, // 商品价格 marketPrice: state.goodsInfo.marketPrice, // 商品原价 }, ); }); const activity = ref() const timeStatusEnum = ref('') // 查询活动 const getActivity = async (id) => { const { data } = await SeckillApi.getSeckillActivity(id) activity.value = data timeStatusEnum.value = getTimeStatusEnum(activity.startTime, activity.endTime) // 查询商品 await getSpu(data.spuId) } const getSpu = async (id) => { const { data } = await SpuApi.getSpuDetail(id) // 模拟 data.activity_type = 'seckill' state.goodsInfo = data // 处理轮播图 state.goodsSwiper = formatGoodsSwiper(state.goodsInfo.sliderPicUrls); // 默认显示最低价 state.goodsInfo.price = min([state.goodsInfo.price, ...activity.value.products.map(spu => spu.seckillPrice)]) // 价格、库存使用活动的 data.skus.forEach(sku => { const product = activity.value.products.find(product => product.skuId === sku.id); if (product) { sku.price = product.seckillPrice; sku.stock = Math.min(sku.stock, product.stock); } else { // 找不到可能是没配置,则不能发起秒杀 sku.stock = 0; } // 设置限购数量 if (activity.value.totalLimitCount > 0 && activity.value.singleLimitCount > 0) { sku.limitCount = Math.min(activity.value.totalLimitCount, activity.value.singleLimitCount); } else if (activity.value.totalLimitCount > 0) { sku.limitCount = activity.value.totalLimitCount; } else if (activity.value.singleLimitCount > 0) { sku.limitCount = activity.value.singleLimitCount; } }); state.skeletonLoading = false; } onLoad((options) => { // 非法参数 if (!options.id) { state.goodsInfo = null; return; } // 查询活动 getActivity(options.id) }); </script> <style lang="scss" scoped> .disabled-btn-box[disabled] { background-color: transparent; } .detail-card { background-color: $white; margin: 14rpx 20rpx; border-radius: 10rpx; overflow: hidden; } // 价格标题卡片 .title-card { width: 710rpx; box-sizing: border-box; // height: 320rpx; background-size: 100% 100%; border-radius: 10rpx; background-image: v-bind(headerBg); background-repeat: no-repeat; .price-box { .price-text { font-size: 30rpx; font-weight: 500; color: #fff; line-height: normal; font-family: OPPOSANS; &::before { content: '¥'; font-size: 30rpx; } } } .origin-price { font-size: 24rpx; font-weight: 400; color: #fff; opacity: 0.7; .origin-price-text { text-decoration: line-through; font-family: OPPOSANS; &::before { content: '¥'; } } } .tig { border: 2rpx solid #ffffff; border-radius: 4rpx; width: 126rpx; height: 38rpx; .tig-icon { width: 40rpx; height: 40rpx; margin-left: -2rpx; background: #ffffff; border-radius: 4rpx 0 0 4rpx; .cicon-alarm { font-size: 32rpx; color: #fc6e6f; } } .tig-title { width: 86rpx; font-size: 24rpx; font-weight: 500; line-height: normal; color: #ffffff; display: flex; justify-content: center; align-items: center; } } .countdown-title { font-size: 26rpx; font-weight: 500; color: #ffffff; } .countdown-time { font-size: 26rpx; font-weight: 500; color: #ffffff; .countdown-h { font-size: 24rpx; font-family: OPPOSANS; font-weight: 500; color: #ffffff; padding: 0 4rpx; height: 40rpx; background: rgba(#000000, 0.1); border-radius: 6rpx; } .countdown-num { font-size: 24rpx; font-family: OPPOSANS; font-weight: 500; color: #ffffff; width: 40rpx; height: 40rpx; background: rgba(#000000, 0.1); border-radius: 6rpx; } } .discounts-box { .discounts-tag { padding: 4rpx 10rpx; font-size: 24rpx; font-weight: 500; border-radius: 4rpx; color: var(--ui-BG-Main); // background: rgba(#2aae67, 0.05); 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; color: #fff; } .subtitle-text { font-size: 26rpx; font-weight: 400; color: #ffffff; line-height: 42rpx; opacity: 0.9; } } // 购买 .buy-box { .check-btn-box { width: 248rpx; height: 80rpx; font-size: 24rpx; font-weight: 600; margin-left: -36rpx; background-image: v-bind(btnBg); background-repeat: no-repeat; background-size: 100% 100%; color: #ffffff; line-height: normal; border-radius: 0px 40rpx 40rpx 0px; } .disabled-btn-box { width: 248rpx; height: 80rpx; font-size: 24rpx; font-weight: 600; margin-left: -36rpx; background-image: v-bind(disabledBtnBg); background-repeat: no-repeat; background-size: 100% 100%; color: #999999; line-height: normal; border-radius: 0px 40rpx 40rpx 0px; } .btn-price { font-family: OPPOSANS; &::before { content: '¥'; } } .origin-price-btn { width: 236rpx; height: 80rpx; background: rgba(#ff5651, 0.1); color: #ff6000; border-radius: 40rpx 0px 0px 40rpx; line-height: normal; font-size: 24rpx; font-weight: 500; .no-original { font-size: 28rpx; } .btn-title { font-size: 28rpx; } } } //秒杀卡片 .seckill-box { background: v-bind(seckillBg) no-repeat; background-size: 100% 100%; } .groupon-box { background: v-bind(grouponBg) no-repeat; background-size: 100% 100%; } //活动卡片 .activity-box { width: 100%; height: 80rpx; box-sizing: border-box; margin-bottom: 10rpx; .activity-title { font-size: 26rpx; font-weight: 500; color: #ffffff; line-height: 42rpx; .activity-icon { width: 38rpx; height: 38rpx; } } .activity-go { width: 70rpx; height: 32rpx; background: #ffffff; border-radius: 16rpx; font-weight: 500; color: #ff6000; font-size: 24rpx; line-height: normal; } } .model-box { .title { font-size: 36rpx; font-weight: bold; color: #333333; } .subtitle { font-size: 26rpx; font-weight: 500; color: #333333; } } image { width: 100%; height: 100%; } </style>