uniapp/pages/goods_details/goods_details.vue

981 lines
25 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>
<view class="goods-details">
<navbar title="商品详情" :background="{background: `rgba(256,256,256,${percent})`}" :titleColor="`rgba(0,0,0,${percent})`" :immersive="true"></navbar>
<!-- #ifdef H5 -->
<download-nav v-if="showDownload" :top="44"></download-nav>
<!-- #endif -->
<loading-view v-if="isFirstLoading"></loading-view>
<view class="contain" v-if="!isNull">
<bubble-tips top="180rpx"></bubble-tips>
<product-swiper :imgUrls="swiperList" :video="goodsDetail.video"></product-swiper>
<!-- 秒杀 -->
<view class="seckill row-between" v-if="goodsType == 1">
<view class="price row">
<view class="row white info">
<view style="align-items: baseline;" class="row ml20">
<view class="mr10">秒杀价</view>
<price-format :first-size="46" :second-size="32" :subscript-size="32"
:price="goodsDetail.min_price" :weight="500"></price-format>
<template v-if="goodsDetail.min_price != goodsDetail.max_price">
<text style="font-size: 46rpx;">-</text>
<price-format :first-size="46" :second-size="32" :subscript-size="32"
:show-subscript="false" :price="goodsDetail.max_price" :weight="500"></price-format>
</template>
<view class="ml10">
<price-format :subscript-size="30" :line-through="true" :first-size="30"
:second-size="30" :price="goodsDetail.market_price">
</price-format>
</view>
</view>
</view>
</view>
<view class="down column-center">
<view class="xxs primary mb10">距活动结束仅剩</view>
<u-count-down :timestamp="countTime" @end="getGoodsDetailFun" color="#fff" bg-color="#FF2C3C"
separator-color="#FF2C3C" font-size="24" height="36" separator-size="26"></u-count-down>
</view>
</view>
<!-- 拼团 -->
<view class="group" v-show="goodsType == 2">
<view class="row info" style="height: 100%">
<view class="row-between ml20 white" style="flex: 1;">
<view style="align-items: baseline;" class="row">
<view class="mr10">拼团价</view>
<price-format :subscript-size="32" :first-size="46" :second-size="32"
:price="team.team_min_price" :weight="500"></price-format>
<text class="xs">起</text>
</view>
<view class="mr20 row group-num">
<view class="group-icon">
<image src="/static/images/icon_group.png" class="icon-sm"></image>
</view>
<view class="xxs ml10 mr10">{{ team.people_num }}人团</view>
</view>
</view>
<view class="down column-center">
<view class="xxs primary mb10">距活动结束仅剩</view>
<u-count-down :timestamp="countTime" color="#fff" bg-color="#FF2C3C" separator-color="#FF2C3C"
font-size="24" height="36" separator-size="26" @end="getGoodsDetailFun"></u-count-down>
</view>
</view>
</view>
<view class="goods-info bg-white">
<view class="info-header row" v-if="goodsType != 1">
<view class="price row flex1">
<view class="primary mr10">
<price-format :first-size="46" :second-size="32" :subscript-size="32"
:price="goodsDetail.min_price" :weight="500"></price-format>
<template v-if="goodsDetail.min_price != goodsDetail.max_price">
<text style="font-size: 46rpx;">-</text>
<price-format :first-size="46" :second-size="32" :subscript-size="32"
:show-subscript="false" :price="goodsDetail.max_price" :weight="500"></price-format>
</template>
</view>
<view class="line-through muted md">
<price-format :price="goodsDetail.market_price"></price-format>
</view>
</view>
<image class="icon-share" src="/static/images/icon_share.png" @tap="showShareBtn = true"></image>
</view>
<view class="row" v-if="!goodsType && (goodsDetail.member_price)">
<view class="vip-price row">
<view class="price-name xxs">会员价</view>
<view style="padding: 0 11rpx">
<price-format :price="goodsDetail.member_price " :first-size="26" :second-size="26"
:subscript-size="22" :weight="500" color="#7B3200">
</price-format>
<text class="xxs" style="color: #7B3200;">起</text>
</view>
</view>
</view>
<view class="row">
<view class="name lg bold">{{ goodsDetail.name }}</view>
<image class="icon-share" src="/static/images/icon_share.png" @tap="showShareBtn = true"
v-if="goodsType == 1"></image>
</view>
<view class="row-between xs lighter" style="padding: 0 24rpx 20rpx">
<text v-if="goodsDetail.stock !== true">库存: {{ goodsDetail.stock }}件</text>
<text>销量: {{ goodsDetail.sales_sum }}件</text>
<text>浏览量: {{ goodsDetail.click_count }}次</text>
</view>
</view>
<view class="group-play bg-white mt20" v-if="goodsType == 2">
<view class="title">拼团玩法</view>
<view class="steps row">
<view class="row step">
<view class="number xxs">1</view>
<view class="sm">开团/参团</view>
</view>
<view class="line"></view>
<view class="row step">
<view class="number xxs">2</view>
<view class="sm">团满即成新团</view>
</view>
<view class="line"></view>
<view class="row step">
<view class="number xxs">3</view>
<view class="sm">满员发货</view>
</view>
</view>
</view>
<view class="discount mt20 bg-white" v-if="couponList.length || goodsDetail.order_give_integral">
<view class="row" style="align-items: flex-start;">
<view class="text muted">优惠</view>
<view style="flex: 1">
<view :class="['row coupons', {mb30: goodsDetail.order_give_integral > 0}]"
v-if="couponList.length" @tap="showCouponFun">
<view class="flexnone">
<u-tag text="领券" size="mini" type="primary" mode="plain" />
</view>
<view class="con row ml20" style="flex: 1">
<view v-for="(item, index) in couponList" :key="index" class="coupons-item mr20">
<view v-if="index < 2" class="row xs">
<view class="line1">
{{ item.use_condition }}
</view>
</view>
</view>
</view>
<image class="icon-sm" src="/static/images/arrow_right.png"></image>
</view>
<view class="row integral" style="align-items: flex-start;"
v-if="goodsDetail.order_give_integral">
<view class="flexnone">
<u-tag text="积分" size="mini" type="primary" mode="plain" />
</view>
<view class="ml20">下单最多可获得{{goodsDetail.order_give_integral}}积分</view>
</view>
</view>
</view>
</view>
<swiper v-if="teamFound.length" class="mt20 bg-white" autoplay="true" style="height: 240rpx;"
vertical="true" circular="true" :interval="5000">
<swiper-item v-for="(sitem, index) in teamFound" :key="index">
<view class="group-list">
<view v-for="(item, index2) in sitem" :key="index2" class="group-item bg-white row-between">
<view class="row" style="max-width: 280rpx;">
<custom-image :src="item.avatar" width="80rpx" height="80rpx" radius="50%">
</custom-image>
<view class="ml20 line1 normal">{{ item.nickname }}</view>
</view>
<view class="row ml20" style="flex: none;">
<view class="column-center">
<text class="sm normal">
还差
<text class="primary">{{ item.need - item.join }}</text>
人成团
</text>
<view class="muted xs">
剩余
<u-count-down :timestamp="getTeamCountTime(item.found_end_time)"
separator-color="#999" color="#999" :separator-size="24" :font-size="24"
bg-color="transparent" @end="getGoodsDetailFun"></u-count-down>
</view>
</view>
<view class="group-btn br60 white row-center" @tap="showSpecFun(3, item.id)">去参团</view>
</view>
</view>
</view>
</swiper-item>
</swiper>
<view v-if="!goodsType" class="spec row bg-white mt20" @tap="showSpecFun(0)">
<view class="text lighter">已选</view>
<view class="line1 mr20" style="flex: 1;">{{ checkedGoods.spec_value_str || '默认' }}</view>
<image class="icon-sm" src="/static/images/arrow_right.png"></image>
</view>
<navigator class="mt20" hover-class="none" url="/bundle/pages/server_explan/server_explan?type=2">
<view class="row bg-white" style="padding: 24rpx 24rpx;">
<view class="text lighter flex1">售后保障</view>
<image class="icon-sm" src="/static/images/arrow_right.png"></image>
</view>
</navigator>
<view class="evaluation bg-white mt20">
<navigator hover-class="none" :url="'/pages/all_comments/all_comments?id=' + goodsDetail.id"
class="title row-between">
<view>
<text class="balck md mr10">用户评价</text>
<text class="primary sm">好评率{{ comment.goods_rate || '0%' }}</text>
</view>
<view class="row">
<text class="lighter">查看全部</text>
<image class="icon-sm" src="/static/images/arrow_right.png"></image>
</view>
</navigator>
<view class="con" v-if="comment.goods_rate">
<view class="user-info row">
<image class="avatar mr20" :src="comment.avatar"></image>
<view class="user-name md mr10">{{ comment.nickname }}</view>
</view>
<view class="muted xs mt10">
<text class="mr20">{{ comment.create_time }}</text>
</view>
<view v-if="comment.comment" class="dec mt20">{{ comment.comment }}</view>
</view>
<view class="con row-center muted" v-else>暂无评价</view>
</view>
<view class="goods-like mt20 bg-white" v-if="goodsLike.length">
<goods-like :list="goodsLike"></goods-like>
</view>
<view class="details mt20 bg-white">
<view class="title lg">商品详情</view>
<view class="content">
<u-parse :html="goodsDetail.content" :lazy-load="true" :show-with-animation="true"></u-parse>
</view>
</view>
<view class="footer row bg-white fixed">
<navigator class="btn column-center" hover-class="none"
url="/bundle/pages/contact_offical/contact_offical">
<image class="icon-md" src="/static/images/icon_contact.png"></image>
<text class="xxs lighter">客服</text>
</navigator>
<button class="btn column-center" hover-class="none" @tap="collectGoodsFun">
<image class="icon-md"
:src="goodsDetail.is_collect == 1 ? '/static/images/icon_collection_s.png' : '/static/images/icon_collection.png'">
</image>
<text class="xxs lighter">收藏</text>
</button>
<navigator class="btn cart column-center" hover-class="none" open-type="switchTab"
url="/pages/shop_cart/shop_cart">
<image class="icon-md" src="/static/images/icon_cart.png"></image>
<text class="xxs lighter">购物车</text>
<u-badge v-if="cartNum" bgColor="#FF2C3C" :offset="[8, 10]" :count="cartNum"></u-badge>
</navigator>
<view v-if="btnText.yellow" class="add-cart br60 white mr10 md ml20" @tap="showSpecFun(1)">
{{ btnText.yellow }}
</view>
<view class="right-buy br60 white mr20 ml10 md" @tap="showSpecFun(2)">{{ btnText.red }}</view>
</view>
</view>
<view v-else>
<view class="details-null column-center">
<image class="img-null" src="/static/images/goods_null.png"></image>
<view class="xs muted">该商品已下架或不存在去逛逛别的吧~</view>
</view>
<recommend></recommend>
</view>
<spec-popup :show="showSpec" :goods="goodsDetail" :is-seckill="goodsType == 1" @close="showSpec = false"
:show-add="popupType == 1 || popupType == 0" :show-buy="popupType == 2 || popupType == 0"
:showConfirm="popupType == 3" @buynow="onBuy" @addcart="onAddCart" @change="onChangeGoods"
:group="Boolean(isGroup)" :red-btn-text="btnText.red" :yellow-btn-text="btnText.yellow"
@confirm="onConfirm"></spec-popup>
<share-popup v-model="showShareBtn"
:share-id="id"
pagePath="pages/goods_details/goods_details"
:type="1"
:config="{
avatar: userInfo.avatar,
nickname: userInfo.nickname,
image: goodsDetail.poster || goodsDetail.image,
price: goodsDetail.min_price,
marketPrice: goodsDetail.market_price,
name: goodsDetail.name
}">
</share-popup>
<!-- 领券 -->
<u-popup v-model="showCoupon" mode="bottom" border-radius="14">
<view>
<view class="row-between" style="padding: 30rpx">
<view class="title md bold">领券</view>
<view class="close" @tap="showCoupon = false">
<image class="icon-lg" src="/static/images/icon_close.png"></image>
</view>
</view>
<view class="content bg-body">
<scroll-view scroll-y="true" style="height: 700rpx">
<coupon-list :list="couponList" @reflash="getGoodsCouponFun" :btn-type="3"></coupon-list>
</scroll-view>
</view>
</view>
</u-popup>
<view class="share-money" :class="{ show: showCommission && enableCommission}">
<view class="row-end">
<view class="share-close row-center" @tap="showCommission=false">
<u-icon name="close" size="16" color="#fff"></u-icon>
</view>
</view>
<view class="share-con mt10" @tap="showShareBtn=true">
<view class="primary" style="font-size: 45rpx;">
{{distribution.earnings}}<text class="xs"></text>
</view>
<view class="lighter xxs">
好友下单最高可赚
</view>
</view>
</view>
<u-back-top :scroll-top="scrollTop" :top="1000" :customStyle="{ backgroundColor: '#FFF', color: '#000', boxShadow: '0px 3px 6px rgba(0, 0, 0, 0.1)'}"></u-back-top>
</view>
</template>
<script>
import {
getGoodsDetail,
addCart,
getPoster,
getCartNum
} from '@/api/store';
import {
collectGoods
} from '@/api/user';
import {
getGoodsCoupon,
teamCheck
} from '@/api/activity';
import {
mapActions,
mapGetters
} from 'vuex';
import {
arraySlice,
trottle
} from '@/utils/tools';
import {
toLogin
} from '@/utils/login';
import {
getUser,
inputInviteCode
} from '@/api/user';
import Cache from '@/utils/cache';
import {
strToParams
} from '@/utils/tools'
export default {
data() {
return {
scrollTop: 0,
percent: 0,
isFirstLoading: true,
isNull: false,
showSpec: false,
showCoupon: false,
showShareBtn: false,
showCommission: true,
popupType: '',
swiperList: [],
goodsDetail: {},
goodsLike: [],
goodsType: 0,
checkedGoods: {},
couponList: [],
comment: {},
countTime: 0,
tagStyle: {
img: 'width:100%;'
},
team: {},
teamFound: [],
isGroup: 0,
id: '',
showDownload: false,
distribution: {}
};
},
onLoad(options) {
this.onPageScroll = trottle(this.onPageScroll, 500, this)
if (options && options.scene) {
let scene = strToParams(decodeURIComponent(options.scene));
console.log(scene, decodeURIComponent(options.scene))
options.id = scene.id;
}
// #ifdef H5
if (options && options.isapp == 1) {
this.showDownload = true;
}
// #endif
if (!options.id) {
return this.$toast({
title: '缺少参数,无法查看商品'
}, {
tab: 3
});
} else {
this.id = options.id;
}
this.getGoodsCouponFun();
this.getCartNum();
},
onShow() {
this.getGoodsDetailFun();
},
onPageScroll(e) {
const top = uni.upx2px(100)
const {
scrollTop
} = e
this.percent = scrollTop / top > 1 ? 1 : scrollTop / top
this.scrollTop = scrollTop
},
methods: {
...mapActions(['getCartNum']),
async getGoodsDetailFun() {
const {
data,
code
} = await getGoodsDetail({
id: this.id
});
if (code == 1) {
let {
goods_image,
content,
comment,
like,
activity,
distribution
} = data;
let {
info,
team,
team_found
} = activity; //秒杀时间
let time = info ?
info.end_time - Date.now() / 1000 //拼团时间
:
team ?
team.end_time - Date.now() / 1000 :
0;
if (team_found) {
team_found = arraySlice(team_found, [], 2);
}
this.distribution = distribution || {}
this.goodsDetail = data;
this.swiperList = goods_image;
this.comment = comment;
this.goodsLike = like;
this.countTime = time;
this.goodsType = activity.type || 0;
this.team = team ? team : {};
this.teamFound = team_found ? team_found : [];
this.$nextTick(() => {
this.isFirstLoading = false;
});
// #ifdef H5
let options = {
shareTitle: data.name,
shareLink: location.href + '&invite_code=' + this.userInfo.distribution_code,
shareImage: data.image,
shareDesc: data.remark
};
this.wxShare(options);
// #endif
} else {
this.isNull = true
this.isFirstLoading = false;
}
},
async getGoodsCouponFun() {
const {
data,
code
} = await getGoodsCoupon({
id: this.id
});
if (code == 1) {
this.couponList = data;
}
},
async collectGoodsFun() {
if (!this.isLogin) return toLogin();
const {
goodsDetail: {
is_collect
}
} = this;
const {
data,
code
} = await collectGoods({
is_collect: is_collect == 0 ? 1 : 0,
goods_id: this.id
});
if (code == 1) {
if (is_collect == 0) {
this.$toast({
title: '收藏成功'
});
} else {
this.$toast({
title: '取消收藏'
});
}
this.getGoodsDetailFun();
}
},
showCouponFun() {
if (!this.isLogin) return toLogin();
this.showCoupon = true;
},
onChangeGoods(e) {
console.log(e);
this.checkedGoods = e.detail;
},
showSpecFun(type, id) {
if (!this.isLogin) return toLogin();
if (this.goodsType == 2 && [2, 3].includes(type)) {
this.isGroup = 1;
this.foundId = id;
} else {
this.isGroup = 0;
this.foundId = '';
}
this.popupType = type;
this.showSpec = true;
},
onBuy(e) {
let {
id,
goodsNum
} = e.detail;
const {
goodsType,
team
} = this;
let goods = [{
item_id: id,
num: goodsNum
}];
const params = {
goods,
};
this.showSpec = false;
goodsType == 2 ? (params.teamId = team.team_id) : '';
this.foundId ? (params.foundId = this.foundId) : '';
uni.navigateTo({
url: '/pages/confirm_order/confirm_order?data=' + encodeURIComponent((JSON.stringify(params)))
})
console.log(1111)
},
onConfirm(e) {
const {
team: {
team_id
}
} = this;
teamCheck({
team_id,
found_id: this.foundId
}).then(res => {
if (res.code == 1) {
this.onBuy(e);
}
});
},
async onAddCart(e) {
let {
id,
goodsNum
} = e.detail;
if (this.goodsType == 2) {
// 拼团单独购买
let goods = [{
item_id: id,
num: goodsNum
}];
uni.navigateTo({
url: '/pages/confirm_order/confirm_order?data=' + encodeURIComponent((JSON.stringify({
goods
})))
})
return
}
const {
code,
data,
msg
} = await addCart({
item_id: id,
goods_num: goodsNum
});
if (code == 1) {
this.getCartNum();
this.$toast({
title: msg,
icon: 'success'
});
this.showSpec = false;
}
}
},
async onShareAppMessage() {
const {
goodsDetail,
team,
userInfo
} = this;
return {
title: team.share_title || goodsDetail.name,
imageUrl: goodsDetail.image,
path: '/pages/goods_details/goods_details?id=' + this.id + "&invite_code=" + userInfo.distribution_code
};
},
computed: {
...mapGetters(['cartNum', 'userInfo']),
btnText() {
const {
goodsType
} = this;
switch (goodsType) {
case 1:
return {
red: '立即抢购',
yellow: ''
};
case 2:
return {
red: '立即开团',
yellow: '单独购买'
};
default:
return {
red: '立即购买',
yellow: '加入购物车'
};
}
},
getTeamCountTime() {
return time => time - Date.now() / 1000;
},
enableCommission() {
const {
goodsType,
distribution: {
earnings,
is_show
}
} = this
return goodsType == 0 && earnings > 0 && is_show == 1
}
}
};
</script>
<style lang="scss" scoped>
.goods-details {
padding-bottom: calc(120rpx + env(safe-area-inset-bottom));
.seckill {
height: 100rpx;
background: #ffd4d8;
.price {
width: 504rpx;
height: 100%;
background: url(../../static/images/bg_seckill.png) no-repeat;
background-size: 100%;
}
.down {
flex: 1;
}
}
.group {
height: 100rpx;
width: 100%;
background-image: url(../../static/images/pintuan_bg.png);
background-size: 100%;
.group-num {
border: 1px solid #ffffff;
border-radius: 4rpx;
.group-icon {
background: #fff;
padding: 3rpx 7rpx;
}
}
.down {
height: 100%;
background-color: #fff5e1;
padding: 0 20rpx;
}
}
.goods-info {
position: relative;
.info-header {
padding: 20rpx 0 0rpx 24rpx;
.price {
align-items: baseline;
}
}
.vip-price {
margin: 0 24rpx;
background-color: #FFE9BA;
color: #FFD4B7;
line-height: 36rpx;
border-radius: 6rpx;
overflow: hidden;
.price-name {
background-color: #101010;
padding: 3rpx 12rpx;
position: relative;
overflow: hidden;
&::after {
content: '';
display: block;
width: 20rpx;
height: 20rpx;
position: absolute;
right: -15rpx;
background-color: #FFE9BA;
border-radius: 50%;
top: 50%;
transform: translateY(-50%);
box-sizing: border-box;
}
}
}
.name {
padding: 20rpx 24rpx;
flex: 1;
}
.icon-share {
width: 134rpx;
height: 60rpx;
}
}
.discount {
padding: 24rpx;
.text {
width: 100rpx;
flex: none;
}
.con {
width: 400rpx;
}
.coupons-item {
overflow: hidden;
&>view {
position: relative;
height: 40rpx;
line-height: 40rpx;
padding: 0 18rpx;
border-radius: 6rpx;
box-sizing: border-box;
background-color: $color-primary;
color: #fff;
white-space: nowrap;
overflow: hidden;
&::after,
&::before {
content: '';
display: block;
width: 20rpx;
height: 20rpx;
position: absolute;
left: -14rpx;
background-color: #fff;
border-radius: 50%;
border: 1px solid currentColor;
box-sizing: border-box;
}
&::after {
right: -14rpx;
left: auto;
}
}
}
}
.details-null {
padding-top: 140rpx;
margin-bottom: 100rpx;
}
.spec {
padding: 24rpx;
.text {
width: 100rpx;
}
}
.evaluation {
.title {
height: 100rpx;
border-bottom: $solid-border;
padding: 0 24rpx;
}
.con {
padding: 30rpx 24rpx;
}
.user-info .avatar {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
}
}
.details {
.title {
line-height: 88rpx;
text-align: center;
}
&>.content {
padding: 0 20rpx 20rpx;
overflow: hidden;
::v-deep image {
vertical-align: middle;
}
// #ifdef H5
::v-deep img {
vertical-align: middle;
}
// #endif
// #ifdef MP-WEIXIN || APP-PLUS
::v-deep ._img {
display: block;
}
// #endif
}
}
.footer {
height: 100rpx;
position: fixed;
bottom: 0;
left: 0;
right: 0;
box-sizing: content-box;
padding-bottom: env(safe-area-inset-bottom);
.btn {
width: 100rpx;
height: 100rpx;
position: relative;
line-height: 1.3;
}
.cart-num {
position: absolute;
left: 60rpx;
top: 6rpx;
}
.add-cart,
.right-buy {
flex: 1;
text-align: center;
padding: 16rpx 0;
}
.add-cart {
background-color: #ffa630;
}
.right-buy {
background-color: $color-primary;
}
}
.group-play {
.title {
padding: 20rpx 28rpx;
border-bottom: $solid-border;
}
.steps {
padding: 20rpx 28rpx;
.step {
flex: none;
}
.line {
flex: 1;
border: 1px dashed #999999;
margin: 0 20rpx;
}
.number {
border: 1rpx solid #707070;
width: 28rpx;
height: 28rpx;
border-radius: 50%;
line-height: 28rpx;
text-align: center;
margin-right: 6rpx;
}
}
}
.group-list {
.group-item {
padding: 20rpx 24rpx;
&:not(:last-of-type) {
border-bottom: $solid-border;
}
.group-btn {
background: linear-gradient(90deg, #f95f2f 0%, #ff2c3c 100%);
height: 58rpx;
padding-left: 28rpx;
padding-right: 28rpx;
margin-left: 30rpx;
box-shadow: 0px 6rpx 12rpx rgba(249, 47, 138, 0.4);
}
}
}
.share-money {
position: fixed;
left: 20rpx;
bottom: calc(130rpx + env(safe-area-inset-bottom));
transform: scale(0);
transition: all .3s;
&.show {
transform: scale(1);
}
.share-close {
width: 34rpx;
height: 34rpx;
background: #a7a7a7;
border-radius: 50%;
}
.share-con {
background: url('../../static/images/bg_packet_img.png');
width: 241rpx;
height: 208rpx;
background-size: 100%;
padding-top: 20rpx;
text-align: center;
}
}
}
</style>