608 lines
17 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<s-layout :onShareAppMessage="shareInfo" navbar="goods">
<!-- 标题栏 -->
<detailNavbar />
<!-- 骨架屏 -->
<detailSkeleton v-if="state.skeletonLoading" />
<!-- 空置页 -->
<s-empty
v-else-if="
state.goodsInfo === null ||
!['groupon', 'groupon_ladder'].includes(state.goodsInfo.activity_type)
"
text="活动不存在或已结束"
icon="/static/soldout-empty.png"
showAction
actionText="返回上一页"
@clickAction="sheep.$router.back()"
/>
<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 detail-card ss-m-y-14 ss-m-x-20 ss-p-x-20 ss-p-y-34">
<view class="ss-flex ss-row-between ss-m-b-60">
<view>
<view class="price-box ss-flex ss-col-bottom ss-m-b-18">
<view class="price-text ss-m-r-16">
{{ goodsPrice }}
</view>
<view class="tig ss-flex ss-col-center">
<view class="tig-icon ss-flex ss-col-center ss-row-center">
<view class="groupon-tag">
<image
:src="
sheep.$url.static('/assets/addons/shopro/uniapp/goods/groupon-tag.png')
"
></image>
</view>
</view>
<view class="tig-title">拼团价</view>
</view>
</view>
<view class="ss-flex ss-row-between">
<view
class="origin-price ss-flex ss-col-center"
v-if="state.goodsInfo.original_price"
>
单买价
<view class="origin-price-text">
{{ state.goodsInfo.original_goods_price[0] || state.goodsInfo.original_price }}
</view>
</view>
</view>
</view>
<view class="countdown-box" v-if="endTime.ms > 0">
<view class="countdown-title ss-m-b-20">{{ endTime.text }}</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="title-text ss-line-2 ss-m-b-6">{{ state.goodsInfo.title }}</view>
<view class="subtitle-text ss-line-1">{{ state.goodsInfo.subtitle }}</view>
</view>
<!-- 功能卡片 -->
<view class="detail-cell-card detail-card ss-flex-col">
<!-- 规格 -->
<detail-cell-sku
v-model="state.selectedSkuPrice.goods_sku_text"
:skus="state.goodsInfo.skus"
@tap="state.showSelectSku = true"
/>
<!-- 服务 -->
<detail-cell-service v-model="state.goodsInfo.service" />
<!-- 参数 -->
<detail-cell-params v-model="state.goodsInfo.params" />
<!-- 玩法 -->
<detail-cell
v-if="state.goodsInfo.activity.richtext_id > 0"
label="玩法"
:value="state.goodsInfo.activity.richtext_title"
@click="
sheep.$router.go('/pages/public/richtext', {
id: state.goodsInfo.activity.richtext_id,
title: state.goodsInfo.activity.richtext_title,
})
"
/>
</view>
<!-- 参团列表 -->
<groupon-card-list
v-if="state.goodsInfo.activity.rules.is_team_card === '1'"
v-model="state.goodsInfo"
@join="onJoinGroupon"
/>
<!-- 规格与数量弹框 -->
<s-select-groupon-sku
:show="state.showSelectSku"
:goodsInfo="state.goodsInfo"
:grouponAction="state.grouponAction"
:grouponNum="state.grouponNum"
@buy="onBuy"
@ladder="onLadder"
@change="onSkuChange"
@close="onSkuClose"
/>
</view>
<!-- 评价 -->
<detail-comment-card class="detail-comment-selector" :goodsId="state.goodsId" />
<!-- 详情 -->
<detail-content-card class="detail-content-selector" :content="state.goodsInfo.content" />
<!-- 商品tabbar -->
<!-- TODO: 已售罄预热 判断 设计-->
<detail-tabbar v-model="state.goodsInfo">
<view class="buy-box ss-flex ss-col-center ss-p-r-20">
<button
v-if="state.goodsInfo.activity.rules.is_alone == 1"
class="ss-reset-button origin-price-btn ss-flex-col"
@tap="sheep.$router.go('/pages/goods/index', { id: state.goodsInfo.id })"
>
<view class="btn-price">{{
state.goodsInfo.original_goods_price[0] || state.goodsInfo.original_price
}}</view>
<view>原价购买</view>
</button>
<button v-else class="ss-reset-button origin-price-btn ss-flex-col">
<view class="btn-title">{{
state.grouponNum == 0 ? '阶梯团' : state.grouponNum + '人团'
}}</view>
</button>
<button
class="ss-reset-button btn-tox ss-flex-col"
@tap="onCreateGroupon"
:class="
state.goodsInfo.activity.status === 'ing' && state.goodsInfo.stock != 0
? 'check-btn-box'
: 'disabled-btn-box'
"
:disabled="state.goodsInfo.stock === 0 || state.goodsInfo.activity.status != 'ing'"
>
<view class="btn-price">{{ goodsPrice }}</view>
<view v-if="state.goodsInfo.activity.status === 'ing'">
<view v-if="state.goodsInfo.stock === 0">已售罄</view>
<view v-else>立即开团</view>
</view>
<view v-else>{{ state.goodsInfo.activity.status_text }}</view>
</button>
</view>
</detail-tabbar>
</block>
<!-- 轮播 -->
</s-layout>
</template>
<script setup>
import { reactive, getCurrentInstance, computed, ref } from 'vue';
import { onLoad, onPageScroll } from '@dcloudio/uni-app';
import sheep from '@/sheep';
import { isEmpty } from 'lodash';
import detailNavbar from './components/detail/detail-navbar.vue';
import detailCell from './components/detail/detail-cell.vue';
import detailCellSku from './components/detail/detail-cell-sku.vue';
import detailCellService from './components/detail/detail-cell-service.vue';
import detailCellParams from './components/detail/detail-cell-params.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 grouponCardList from './components/groupon/groupon-card-list.vue';
import { useDurationTime, formatPrice, formatGoodsSwiper } from '@/sheep/hooks/useGoods';
const headerBg = sheep.$url.css('/assets/addons/shopro/uniapp/goods/groupon-bg.png');
const btnBg = sheep.$url.css('/assets/addons/shopro/uniapp/goods/groupon-btn.png');
const disabledBtnBg = sheep.$url.css(
'/assets/addons/shopro/uniapp/goods/activity-btn-disabled.png',
);
const seckillBg = sheep.$url.css('/assets/addons/shopro/uniapp/goods/seckill-tip-bg.png');
const grouponBg = sheep.$url.css('/assets/addons/shopro/uniapp/goods/groupon-tip-bg.png');
onPageScroll(() => {});
const state = reactive({
skeletonLoading: true, // 骨架屏
goodsId: 0, // 商品ID
goodsInfo: {}, // 商品信息
goodsSwiper: [], // 商品轮播图
showSelectSku: false, // 显示规格弹框
selectedSkuPrice: {}, // 选中的规格价格
grouponId: 0, // 团购ID
grouponType: '', // 团购类型
grouponNum: 0, // 团购人数
grouponAction: 'create', // 团购操作
});
// 商品主价格
const goodsPrice = computed(() => {
if (isEmpty(state.selectedSkuPrice)) {
return formatPrice(state.goodsInfo.price);
}
if (state.grouponNum === 0 && state.grouponType === 'groupon_ladder') {
return formatPrice(state.goodsInfo.price);
}
if (state.grouponType === 'groupon') {
return state.selectedSkuPrice.groupon_price;
}
if (state.grouponType === 'groupon_ladder') {
return state.selectedSkuPrice.ladder_price;
}
return '';
});
// 倒计时
const endTime = computed(() => {
if (useDurationTime(state.goodsInfo.activity.start_time).ms > 0) {
return {
...useDurationTime(state.goodsInfo.activity.start_time),
text: '距开始',
};
} else {
state.goodsInfo.activity.status = 'ing';
return {
...useDurationTime(state.goodsInfo.activity.end_time),
text: '距结束',
};
}
});
// 规格变更
function onSkuChange(e) {
state.selectedSkuPrice = e;
}
// 阶梯变更
function onLadder(e) {
state.showSelectSku = false;
state.grouponNum = e;
setTimeout(() => {
state.showSelectSku = true;
}, 80);
}
function onSkuClose() {
state.showSelectSku = false;
}
// 发起拼团
function onCreateGroupon() {
state.grouponAction = 'create';
state.grouponId = 0;
state.showSelectSku = true;
}
// 点击参团
function onJoinGroupon(groupon) {
state.grouponAction = 'join';
state.grouponId = groupon.id;
state.grouponNum = groupon.num;
state.showSelectSku = true;
}
// 立即购买
function onBuy(e) {
sheep.$router.go('/pages/order/confirm', {
data: JSON.stringify({
order_type: 'goods',
buy_type: 'groupon',
activity_id: state.goodsInfo.activity.id,
groupon_id: state.grouponId,
groupon_num: state.grouponNum,
goods_list: [
{
goods_id: e.goods_id,
goods_num: e.goods_num,
goods_sku_price_id: e.id,
},
],
}),
});
}
const shareInfo = computed(() => {
if (isEmpty(state.goodsInfo?.activity)) return {};
return sheep.$platform.share.getShareInfo(
{
title: state.goodsInfo.title,
image: sheep.$url.cdn(state.goodsInfo.image),
params: {
page: '3',
query: state.goodsInfo.id + ',' + state.goodsInfo.activity.id,
},
},
{
type: 'goods', // 商品海报
title: state.goodsInfo.title, // 商品标题
image: sheep.$url.cdn(state.goodsInfo.image), // 商品主图
price: state.goodsInfo.price[0], // 商品价格
original_price: state.goodsInfo.original_price, // 商品原价
},
);
});
onLoad(async (options) => {
// 非法参数
if (!options.id) {
state.goodsInfo = null;
return;
}
state.goodsId = options.id;
// 加载商品信息
const { code, data } = await sheep.$api.goods.detail(options.id, {
activity_id: options.activity_id,
});
// 关闭骨架屏
state.skeletonLoading = false;
if (code === 1) {
state.goodsInfo = data;
state.grouponType = state.goodsInfo.activity_type;
if (state.grouponType === 'groupon') {
state.grouponNum = state.goodsInfo.activity.rules.team_num;
}
state.goodsSwiper = formatGoodsSwiper(state.goodsInfo.images);
} else {
// 未找到商品
state.goodsInfo = null;
}
});
</script>
<style lang="scss" scoped>
.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 {
margin-left: -2rpx;
width: 40rpx;
height: 40rpx;
background: #ffffff;
border-radius: 4rpx 0 0 4rpx;
.groupon-tag {
width: 32rpx;
height: 32rpx;
}
}
.tig-title {
font-size: 24rpx;
font-weight: 500;
line-height: normal;
color: #ffffff;
width: 86rpx;
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;
}
}
.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 {
.disabled-btn-box[disabled] {
background-color: transparent;
}
.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;
}
.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;
.btn-title {
font-size: 28rpx;
}
}
.btn-price {
font-family: OPPOSANS;
&::before {
content: '¥';
}
}
.more-item-box {
.more-item {
width: 156rpx;
height: 58rpx;
font-size: 26rpx;
font-weight: 500;
color: #999999;
border-radius: 10rpx;
}
.more-item-hover {
background: rgba(#ffefe5, 0.32);
color: #ff6000;
}
}
}
//秒杀卡片
.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>