608 lines
15 KiB
Vue
608 lines
15 KiB
Vue
<template>
|
||
<view class="mall-detail">
|
||
<!-- 顶部导航 -->
|
||
<uni-nav-bar left-icon="left" title="商品详情" color="#000000" backgroundColor="#fff" :fixed="true"
|
||
:statusBar="true" :border="false" @clickLeft="goBack"></uni-nav-bar>
|
||
|
||
<view class="detail-content-wrap">
|
||
<!-- 商品主图 -->
|
||
<view class="goods-banner">
|
||
<image :src="goodsInfo.imgurl_banner || goodsInfo.imgurl" mode="widthFix" class="banner-img"></image>
|
||
</view>
|
||
|
||
<!-- 价格区域 -->
|
||
<view class="price-section">
|
||
<text class="price-symbol">¥</text>
|
||
<text class="price-value">{{ goodsInfo.price }}</text>
|
||
</view>
|
||
|
||
<!-- 商品标题 -->
|
||
<view class="title-section">
|
||
<text class="goods-title">{{ goodsInfo.title }}</text>
|
||
</view>
|
||
|
||
<!-- 信息区域 -->
|
||
<view class="info-section">
|
||
<view class="info-row">
|
||
<text class="info-label">库存</text>
|
||
<text class="info-value">{{ goodsInfo.goodslist_surplus_stock != null ? goodsInfo.goodslist_surplus_stock : (goodsInfo.stock || 0) }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="info-section" v-if="orderData && orderData.goods_extend && orderData.goods_extend.pay_currency2 == 1">
|
||
<view class="info-row">
|
||
<text class="info-label">{{ $config.getAppSetting('currency2_name') }}{{ goodsInfo.price * 100 }}</text>
|
||
<text class="info-value info-sub">(剩余:{{ orderData.score || 0 }})</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 售前售后须知 -->
|
||
<view class="info-section" @click="showRule = true">
|
||
<view class="info-row">
|
||
<text class="info-label">售前·售后须知</text>
|
||
<text class="info-arrow">›</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 商品详情图 -->
|
||
<view class="detail-section" v-if="goodsInfo.imgurl_detail">
|
||
<text class="detail-title">商品详情</text>
|
||
<view class="detail-content">
|
||
<image :src="goodsInfo.imgurl_detail" mode="widthFix" style="width: 100%;"></image>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 商品描述 -->
|
||
<view class="detail-section" v-if="goodsInfo.goods_describe">
|
||
<text class="detail-title" v-if="!goodsInfo.imgurl_detail">商品详情</text>
|
||
<view class="detail-content">
|
||
<rich-text :nodes="goodsInfo.goods_describe"></rich-text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部占位 -->
|
||
<view style="height: 180rpx;"></view>
|
||
</view>
|
||
|
||
<!-- 底部购买按钮 -->
|
||
<view class="bottom-bar">
|
||
<view class="buy-btn" @click="onBuy">
|
||
<text v-if="orderData && orderData.goods_extend && orderData.goods_extend.pay_currency2 == 1">
|
||
{{ goodsInfo.price * 100 }}{{ $config.getAppSetting('currency2_name') }} 立即兑换
|
||
</text>
|
||
<text v-else>¥{{ goodsInfo.price }} 立即购买</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 售前售后弹窗 -->
|
||
<uni-popup ref="rulePop" type="bottom">
|
||
<view class="rule-popup">
|
||
<view class="rule-popup-hd">
|
||
<text>售前·售后须知</text>
|
||
<view @click="$refs.rulePop.close()" class="rule-close">✕</view>
|
||
</view>
|
||
<scroll-view scroll-y class="rule-popup-body">
|
||
<rich-text :nodes="sendRuleData"></rich-text>
|
||
</scroll-view>
|
||
</view>
|
||
</uni-popup>
|
||
|
||
<!-- 确认订单弹窗 -->
|
||
<uni-popup ref="orderPop" type="bottom">
|
||
<view v-if="orderData" class="order-popup">
|
||
<view class="order-popup-hd">
|
||
<view style="width: 24rpx;"></view>
|
||
<text>确认订单</text>
|
||
<view class="rule-close" @click="$refs.orderPop.close()">✕</view>
|
||
</view>
|
||
<view class="order-card">
|
||
<image :src="orderData.goods.imgurl"
|
||
style="width: 190rpx; height: 190rpx; border-radius: 10rpx; flex-shrink: 0;" mode="aspectFill"></image>
|
||
<view class="order-card-info">
|
||
<text class="hang1" style="font-size: 28rpx; color: #333;">{{ orderData.goods.title }}</text>
|
||
<view class="order-card-price">
|
||
<text style="color: #333; font-size: 28rpx;">¥{{ orderData.goods.price }}</text>
|
||
<text style="color: #999; font-size: 24rpx;">×{{ orderData.goods.prize_num }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 支付方式 -->
|
||
<view class="pay-methods">
|
||
<view class="pay-method-item" @click="zhifu = 0"
|
||
v-if="orderData.goods_extend && orderData.goods_extend.pay_wechat == 1">
|
||
<text style="color: #333;">微信支付</text>
|
||
<image :src="zhifu == 0 ? $img1('common/check_act.png') : $img1('common/check.png')"
|
||
style="width: 32rpx; height: 32rpx;"></image>
|
||
</view>
|
||
<view class="pay-method-item" @click="zhifu = 1"
|
||
v-if="orderData.goods_extend && orderData.goods_extend.pay_currency2 == 1">
|
||
<text style="color: #333;">{{ $config.getAppSetting('currency2_name') }}
|
||
{{ orderData.goods.price * 100 }} (剩余:{{ orderData.score || 0 }})</text>
|
||
<image :src="zhifu == 1 ? $img1('common/check_act.png') : $img1('common/check.png')"
|
||
style="width: 32rpx; height: 32rpx;"></image>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 规则 -->
|
||
<view class="order-rule" v-if="sendRuleData">
|
||
<scroll-view scroll-y style="max-height: 160rpx;">
|
||
<rich-text :nodes="sendRuleData" style="font-size: 20rpx; color: #999;"></rich-text>
|
||
</scroll-view>
|
||
</view>
|
||
|
||
<!-- 协议 -->
|
||
<view class="order-agree" @click="isAgree = !isAgree">
|
||
<image :src="isAgree ? $img1('common/check_act.png') : $img1('common/check.png')"
|
||
style="width: 32rpx; height: 32rpx; margin-right: 10rpx;"></image>
|
||
<text>我已满18岁,阅读并同意</text>
|
||
<text style="color: #1e88e5;" @click.stop="$c.to({ url: '/pages/guize/guize?type=4' })">《用户协议》</text>
|
||
<text style="color: #1e88e5;" @click.stop="$c.to({ url: '/pages/guize/guize?type=5' })">《隐私政策》</text>
|
||
</view>
|
||
|
||
<!-- 支付按钮 -->
|
||
<view class="order-pay-btn" @click="confirmPay">
|
||
<text v-if="zhifu == 0">¥{{ orderData.price }}</text>
|
||
<text v-if="zhifu == 1">{{ orderData.price * 100 }}{{ $config.getAppSetting('currency2_name') }}</text>
|
||
</view>
|
||
</view>
|
||
</uni-popup>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import { calcMallOrderMoney, createOrder, createMallOrder } from '@/common/server/order.js';
|
||
import { getGoodsDetail } from '@/common/server/goods.js';
|
||
import RequestManager from '@/common/request.js';
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
goodsId: '',
|
||
goodsInfo: {},
|
||
orderData: null,
|
||
sendRuleData: '',
|
||
zhifu: 0,
|
||
isAgree: true,
|
||
showRule: false
|
||
};
|
||
},
|
||
onLoad(options) {
|
||
if (options.goods_id) {
|
||
this.goodsId = options.goods_id;
|
||
// 从列表页传来的图片作为兜底
|
||
if (options.imgurl) {
|
||
this.goodsInfo = { imgurl: decodeURIComponent(options.imgurl) };
|
||
}
|
||
this.loadDetail();
|
||
}
|
||
this.$c.getRule(10).then(res => {
|
||
if (res.status == 1) this.sendRuleData = res.data;
|
||
});
|
||
},
|
||
onShow() {
|
||
// 登录回来后,如果还没有支付信息,重新加载
|
||
const token = uni.getStorageSync('token');
|
||
if (token && !this.orderData && this.goodsId) {
|
||
this.loadDetail();
|
||
}
|
||
},
|
||
watch: {
|
||
showRule(val) {
|
||
if (val) {
|
||
this.$refs.rulePop.open();
|
||
this.showRule = false;
|
||
}
|
||
}
|
||
},
|
||
methods: {
|
||
goBack() {
|
||
uni.navigateBack();
|
||
},
|
||
async loadDetail() {
|
||
// 1. 先用 goods_detail 获取商品基本信息(不需要登录)
|
||
try {
|
||
const detailRes = await getGoodsDetail(this.goodsId);
|
||
if (detailRes.status == 1 && detailRes.data) {
|
||
// goods_detail 返回 { data: { goods: {...}, goodslist: [...] } }
|
||
this.goodsInfo = detailRes.data.goods || detailRes.data;
|
||
}
|
||
} catch (e) {
|
||
console.log('获取商品详情失败', e);
|
||
}
|
||
|
||
// 2. 已登录时,再调 calcMallOrderMoney 获取支付信息
|
||
const token = uni.getStorageSync('token');
|
||
if (token) {
|
||
try {
|
||
const res = await calcMallOrderMoney({
|
||
prize_num: 1,
|
||
goods_id: this.goodsId,
|
||
num: 1,
|
||
});
|
||
if (res.status == 1) {
|
||
this.orderData = res.data;
|
||
// 用订单接口的数据补充商品信息(含 imgurl_detail、goods_describe 等)
|
||
if (res.data.goods) {
|
||
this.goodsInfo = { ...this.goodsInfo, ...res.data.goods };
|
||
}
|
||
const ext = res.data.goods_extend || {};
|
||
if (ext.pay_wechat == 1) {
|
||
this.zhifu = 0;
|
||
} else if (ext.pay_currency2 == 1) {
|
||
this.zhifu = 1;
|
||
}
|
||
}
|
||
} catch (e) {
|
||
console.log('获取支付信息失败', e);
|
||
}
|
||
}
|
||
},
|
||
|
||
// 静默刷新详情,不显示"加载中"提示
|
||
async silentLoadDetail() {
|
||
try {
|
||
const detailRes = await RequestManager.get('/goods_detail', { goods_id: this.goodsId }, false);
|
||
if (detailRes.status == 1 && detailRes.data) {
|
||
this.goodsInfo = detailRes.data.goods || detailRes.data;
|
||
}
|
||
} catch (e) {
|
||
console.log('静默刷新商品详情失败', e);
|
||
}
|
||
const token = uni.getStorageSync('token');
|
||
if (token) {
|
||
try {
|
||
const res = await RequestManager.post('/mall_ordermoney', {
|
||
prize_num: 1,
|
||
goods_id: this.goodsId,
|
||
num: 1,
|
||
}, false);
|
||
if (res.status == 1) {
|
||
this.orderData = res.data;
|
||
if (res.data.goods) {
|
||
this.goodsInfo = { ...this.goodsInfo, ...res.data.goods };
|
||
}
|
||
}
|
||
} catch (e) {
|
||
console.log('静默刷新支付信息失败', e);
|
||
}
|
||
}
|
||
},
|
||
|
||
onBuy() {
|
||
const token = uni.getStorageSync('token');
|
||
if (!token) {
|
||
uni.showToast({ title: '请先登录', icon: 'none' });
|
||
setTimeout(() => {
|
||
uni.navigateTo({ url: '/pages/user/login' });
|
||
}, 300);
|
||
return;
|
||
}
|
||
|
||
// 检查库存(商城赏用奖品维度的剩余库存)
|
||
const remainStock = this.goodsInfo.goodslist_surplus_stock != null
|
||
? this.goodsInfo.goodslist_surplus_stock
|
||
: (this.goodsInfo.stock || 0) - (this.goodsInfo.sale_stock || 0);
|
||
if (remainStock <= 0) {
|
||
this.$c.msg('库存不足,兑换失败');
|
||
return;
|
||
}
|
||
|
||
// 登录后如果还没加载支付信息,先加载
|
||
if (!this.orderData) {
|
||
this.loadDetail().then(() => {
|
||
if (this.orderData) {
|
||
this.$refs.orderPop.open();
|
||
}
|
||
});
|
||
return;
|
||
}
|
||
this.$refs.orderPop.open();
|
||
},
|
||
|
||
async confirmPay() {
|
||
if (!this.isAgree) {
|
||
this.$c.msg('请先同意用户协议');
|
||
return;
|
||
}
|
||
if (this.zhifu == 0) {
|
||
await this.payByWechat();
|
||
} else {
|
||
await this.payByCurrency();
|
||
}
|
||
},
|
||
|
||
async payByWechat() {
|
||
const data = {
|
||
goods_id: this.goodsId,
|
||
prize_num: 1,
|
||
num: 1,
|
||
use_money_is: 2,
|
||
use_integral_is: 2,
|
||
coupon_id: '',
|
||
use_money2_is: 2
|
||
};
|
||
const res = await createOrder(data);
|
||
if (res.status == 1) {
|
||
this.$refs.orderPop.close();
|
||
if (res.data.status == 1) {
|
||
const status = await this.$platform.pay({ data: res.data.res }, this);
|
||
if (status == 'success') {
|
||
this.$c.msg('购买成功');
|
||
this.loadDetail();
|
||
}
|
||
} else {
|
||
this.$c.toast({ title: res.msg, duration: 500, success: () => this.loadDetail() });
|
||
}
|
||
}
|
||
},
|
||
|
||
async payByCurrency() {
|
||
const pri = this.goodsInfo.price * 100;
|
||
if ((this.orderData.score || 0) < pri) {
|
||
this.$c.msg(this.$config.getAppSetting('currency2_name') + '不足');
|
||
return;
|
||
}
|
||
|
||
// 再次检查库存(防止并发购买)
|
||
const remainStock = this.goodsInfo.goodslist_surplus_stock != null
|
||
? this.goodsInfo.goodslist_surplus_stock
|
||
: (this.goodsInfo.stock || 0) - (this.goodsInfo.sale_stock || 0);
|
||
if (remainStock <= 0) {
|
||
this.$c.msg('库存不足,兑换失败');
|
||
return;
|
||
}
|
||
|
||
const res = await createMallOrder({ goods_id: this.goodsId, prize_num: 1 });
|
||
if (res.status == 1) {
|
||
this.$refs.orderPop.close();
|
||
if (res.data.status == 1) {
|
||
const status = await this.$platform.pay({ data: res.data.res }, this);
|
||
if (status == 'success') {
|
||
this.$c.msg('兑换成功');
|
||
// 静默刷新,避免出现"加载中"提示
|
||
setTimeout(() => {
|
||
this.silentLoadDetail();
|
||
}, 1600);
|
||
}
|
||
} else {
|
||
// 兑换成功(积分/货币直接扣除,无需微信支付)
|
||
this.$c.msg('兑换成功');
|
||
// 静默刷新,避免出现"加载中"提示
|
||
setTimeout(() => {
|
||
this.silentLoadDetail();
|
||
}, 1600);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.mall-detail {
|
||
min-height: 100vh;
|
||
background: #f5f5f5;
|
||
}
|
||
|
||
.goods-banner {
|
||
width: 100%;
|
||
background: #fff;
|
||
|
||
.banner-img {
|
||
width: 100%;
|
||
}
|
||
}
|
||
|
||
.price-section {
|
||
padding: 30rpx 30rpx 10rpx;
|
||
background: #fff;
|
||
|
||
.price-symbol {
|
||
font-size: 28rpx;
|
||
color: #ff4d4f;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.price-value {
|
||
font-size: 52rpx;
|
||
color: #ff4d4f;
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
|
||
.title-section {
|
||
padding: 10rpx 30rpx 30rpx;
|
||
background: #fff;
|
||
|
||
.goods-title {
|
||
font-size: 30rpx;
|
||
color: #333;
|
||
line-height: 1.5;
|
||
}
|
||
}
|
||
|
||
.info-section {
|
||
margin-top: 2rpx;
|
||
padding: 24rpx 30rpx;
|
||
background: #fff;
|
||
|
||
.info-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.info-label {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
}
|
||
|
||
.info-value {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
}
|
||
|
||
.info-sub {
|
||
color: #999;
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.info-arrow {
|
||
font-size: 36rpx;
|
||
color: #999;
|
||
}
|
||
}
|
||
|
||
.detail-section {
|
||
margin-top: 20rpx;
|
||
background: #fff;
|
||
padding: 30rpx;
|
||
|
||
.detail-title {
|
||
display: block;
|
||
text-align: center;
|
||
font-size: 28rpx;
|
||
color: #999;
|
||
margin-bottom: 30rpx;
|
||
}
|
||
}
|
||
|
||
.bottom-bar {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
padding: 20rpx 30rpx;
|
||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||
box-sizing: border-box;
|
||
background: #fff;
|
||
|
||
.buy-btn {
|
||
width: 100%;
|
||
height: 88rpx;
|
||
background: #5CE5D5;
|
||
border-radius: 44rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 30rpx;
|
||
font-weight: 500;
|
||
color: #333;
|
||
}
|
||
}
|
||
|
||
.rule-popup {
|
||
background: #fff;
|
||
border-radius: 24rpx 24rpx 0 0;
|
||
max-height: 70vh;
|
||
|
||
.rule-popup-hd {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 30rpx;
|
||
font-size: 32rpx;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.rule-popup-body {
|
||
padding: 0 30rpx 60rpx;
|
||
max-height: 50vh;
|
||
font-size: 24rpx;
|
||
color: #666;
|
||
}
|
||
}
|
||
|
||
.rule-close {
|
||
width: 48rpx;
|
||
height: 48rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 28rpx;
|
||
color: #999;
|
||
}
|
||
|
||
.order-popup {
|
||
background: #f7f7f7;
|
||
border-radius: 24rpx 24rpx 0 0;
|
||
padding: 0 30rpx;
|
||
padding-bottom: calc(40rpx + env(safe-area-inset-bottom));
|
||
|
||
.order-popup-hd {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 30rpx 0;
|
||
font-size: 32rpx;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
|
||
.order-card {
|
||
display: flex;
|
||
background: #fff;
|
||
border-radius: 20rpx;
|
||
padding: 24rpx;
|
||
|
||
.order-card-info {
|
||
flex: 1;
|
||
min-width: 0;
|
||
margin-left: 24rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: space-between;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.order-card-price {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
}
|
||
|
||
.pay-methods {
|
||
margin-top: 20rpx;
|
||
background: #fff;
|
||
border-radius: 20rpx;
|
||
|
||
.pay-method-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 30rpx;
|
||
font-size: 28rpx;
|
||
}
|
||
}
|
||
|
||
.order-rule {
|
||
margin-top: 20rpx;
|
||
padding: 20rpx;
|
||
background: #F9F8E1;
|
||
border-radius: 20rpx;
|
||
}
|
||
|
||
.order-agree {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-top: 20rpx;
|
||
font-size: 20rpx;
|
||
color: #999;
|
||
}
|
||
|
||
.order-pay-btn {
|
||
margin-top: 30rpx;
|
||
width: 100%;
|
||
height: 84rpx;
|
||
background: #5CE5D5;
|
||
border-radius: 42rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 30rpx;
|
||
font-weight: 500;
|
||
color: #333;
|
||
}
|
||
</style>
|