Files
integral-shop/single_uniapp22miao/pages/integral/detail.vue

850 lines
21 KiB
Vue
Raw Normal View History

<template>
<view class="goods-detail">
<!-- 顶部导航栏 -->
<view class="nav-bar">
<view class="nav-back" @click="goBack">
<text class="iconfont icon-fanhui"></text>
</view>
<text class="nav-title">商品详情</text>
<view class="nav-right"></view>
</view>
<!-- 商品图片轮播 -->
<swiper
class="goods-swiper"
:indicator-dots="true"
indicator-color="rgba(0,0,0,0.3)"
indicator-active-color="#FF4D4F"
>
<swiper-item v-for="(image, index) in sliderImage" :key="index">
<image
:src="image"
class="swiper-image"
mode="aspectFill"
@click="previewImage(index)"
/>
</swiper-item>
</swiper>
<!-- 商品信息 -->
<view class="wrapper" v-if="attr.productSelect">
<view class="share-section">
<view class="money-box">
<text class="points-symbol">积分</text>
<text class="points-num">{{ attr.productSelect.price || 0 }}</text>
</view>
</view>
<view class="introduce">{{ productInfo.storeName }}</view>
<!-- <view class="label">
<view>原价:{{ attr.productSelect.otPrice || 0 }}</view>
<view>库存:{{ attr.productSelect.stock || 0 }}{{ productInfo.unitName }}</view>
<view>销量:{{ Number(productInfo.sales) + Number(productInfo.ficti) }}{{ productInfo.unitName }}</view>
</view> -->
</view>
<!-- 规格选择 -->
<view class="attribute" @click="selecAttr">
<view class="attr-row">
<view class="attr-text">{{attrTxt}}<text class="selected-attr">{{attrValue}}</text></view>
<view class="iconfont icon-jiantou arrow-right"></view>
</view>
</view>
<!-- 属性选择组件 -->
<productWindow
:attr="attr"
:isShow='1'
:iSplus='1'
@myevent="onMyEvent"
@ChangeAttr="ChangeAttr"
@ChangeCartNum="ChangeCartNum"
@attrVal="attrVal"
@iptCartNum="iptCartNum">
</productWindow>
<!-- 商品详情 -->
<view class="detail-section">
<view class="detail-info-list" v-if="attr.productSelect">
<view class="detail-item">库存{{ attr.productSelect.stock || 0 }}</view>
<!-- <view class="detail-item">已兑换{{ Number(productInfo.sales) + Number(productInfo.ficti) }}</view> -->
</view>
<view class="detail-content">
<rich-text :nodes="description" />
</view>
</view>
<!-- 浮动购物车按钮 -->
<view class="floating-cart animated" :class="animated ? 'bounceIn' : ''" @click="goToCart">
<view class='iconfont icon-gouwuche1'>
<text v-if="Math.floor(CartCount) > 0" class='cart-badge'>{{CartCount}}</text>
</view>
<!-- <view class="cart-text">购物车</view> -->
</view>
<!-- 底部操作栏 -->
<view class="bottom-bar safe-area-inset-bottom">
<view class="btn-group">
<view class="btn-cart" @click="onCartAdd">
<text>加入购物车</text>
</view>
<view
class="btn-buy"
:class="{ disabled: attr.productSelect && attr.productSelect.stock <= 0 }"
@click="onExchange"
>
<text>{{ exchangeButtonText }}</text>
</view>
</view>
</view>
</view>
</template>
<script>
import { collectIntegralGoods } from '@/api/miao.js'
import { getProductDetail, postCartAdd } from '@/api/store.js';
import { getCartCounts } from '@/api/order.js';
import { toLogin } from '@/libs/login.js';
import { mapGetters } from "vuex";
import productWindow from '@/components/productWindow';
export default {
components: {
productWindow
},
data() {
return {
goodsId: 0,
productInfo: {
sales: 0,
ficti: 0,
unitName: '件'
},
productValue: {},
skuArr: [],
sliderImage: [],
attr: {
cartAttr: false,
productAttr: [],
productSelect: {
price: 0,
stock: 0,
otPrice: 0,
unique: '',
cart_num: 1
}
},
attrValue: '',
attrTxt: '请选择',
reply: [],
replyCount: 0,
replyChance: 0,
description: '',
isOpen: false, // 是否打开过属性选择器
CartCount: 0, // 购物车数量
animated: false // 购物车动画
}
},
computed: {
...mapGetters(['isLogin', 'uid']),
exchangeButtonText() {
if (this.attr.productSelect && this.attr.productSelect.stock <= 0) {
return '库存不足'
}
return '立即兑换'
}
},
onLoad(options) {
if (options.id) {
this.goodsId = parseInt(options.id)
this.getDetail()
}
},
onShow() {
// 从兑换页面返回时刷新数据
if (this.goodsId) {
this.getDetail()
}
// 获取购物车数量
this.getCartCount();
},
methods: {
// 获取商品详情
getDetail() {
let that = this;
uni.showLoading({
title: '加载中...'
})
getProductDetail(that.goodsId, 1).then(res => {
let productInfo = res.data.productInfo;
// 字符串数组转数组;
let arrayImg = productInfo.sliderImage;
let sliderImage = JSON.parse(arrayImg);
that.$set(that, 'sliderImage', sliderImage);
that.$set(that, 'productInfo', productInfo);
that.$set(that, 'description', productInfo.content);
that.$set(that.attr, 'productAttr', res.data.productAttr);
that.$set(that, 'productValue', res.data.productValue);
that.skuArr = [];
for (let key in res.data.productValue) {
let obj = res.data.productValue[key];
that.skuArr.push(obj)
}
let productAttr = that.attr.productAttr.map(item => {
return {
attrName: item.attrName,
attrValues: item.attrValues.split(','),
id: item.id,
isDel: item.isDel,
productId: item.productId,
type: item.type
}
});
that.$set(that.attr, 'productAttr', productAttr);
that.DefaultSelect();
uni.hideLoading();
}).catch(err => {
uni.hideLoading();
uni.showToast({
title: err.msg || '加载失败',
icon: 'none'
})
})
},
/**
* 默认选中属性
*
*/
DefaultSelect: function() {
let productAttr = this.attr.productAttr;
let value = [];
//默认选中每种规格的第一个
for (let key in this.productValue) {
if (this.productValue[key].stock > 0) {
value = this.attr.productAttr.length ? key.split(",") : [];
break;
}
}
for (let i = 0; i < value.length; i++) {
this.$set(productAttr[i], "index", value[i]);
}
//sort();排序函数:数字-英文-汉字;
let productSelect = this.productValue[value.join(",")];
if (productSelect && productAttr.length) {
this.$set(this.attr.productSelect, "storeName", this.productInfo.storeName);
this.$set(this.attr.productSelect, "image", productSelect.image);
this.$set(this.attr.productSelect, "price", productSelect.price);
this.$set(this.attr.productSelect, "stock", productSelect.stock);
this.$set(this.attr.productSelect, "unique", productSelect.id);
this.$set(this.attr.productSelect, "cart_num", 1);
this.$set(this.attr.productSelect, "vipPrice", productSelect.vipPrice);
this.$set(this.attr.productSelect, 'otPrice', productSelect.otPrice);
this.$set(this, "attrValue", value.join(","));
this.$set(this, "attrTxt", "已选择");
} else if (!productSelect && productAttr.length) {
this.$set(this.attr.productSelect, "storeName", this.productInfo.storeName);
this.$set(this.attr.productSelect, "image", this.productInfo.image);
this.$set(this.attr.productSelect, "price", this.productInfo.price);
this.$set(this.attr.productSelect, "stock", 0);
this.$set(this.attr.productSelect, "unique", this.productInfo.id);
this.$set(this.attr.productSelect, "cart_num", 1);
this.$set(this.attr.productSelect, "vipPrice", this.productInfo.vipPrice);
this.$set(this.attr.productSelect, 'otPrice', this.productInfo.otPrice);
this.$set(this, "attrValue", "");
this.$set(this, "attrTxt", "请选择");
} else if (!productSelect && !productAttr.length) {
this.$set(this.attr.productSelect, "storeName", this.productInfo.storeName);
this.$set(this.attr.productSelect, "image", this.productInfo.image);
this.$set(this.attr.productSelect, "price", this.productInfo.price);
this.$set(this.attr.productSelect, "stock", this.productInfo.stock);
this.$set(this.attr.productSelect, "unique", this.productInfo.id || "");
this.$set(this.attr.productSelect, "cart_num", 1);
this.$set(this.attr.productSelect, "vipPrice", this.productInfo.vipPrice);
this.$set(this.attr.productSelect, 'otPrice', this.productInfo.otPrice);
this.$set(this, "attrValue", "");
this.$set(this, "attrTxt", "已选择");
}
},
// 预览图片
previewImage(index) {
uni.previewImage({
urls: this.sliderImage,
current: index
})
},
// 打开属性选择器
selecAttr() {
this.$set(this.attr, 'cartAttr', true);
this.$set(this, 'isOpen', true);
},
// 关闭属性选择器
onMyEvent() {
this.$set(this.attr, 'cartAttr', false);
this.$set(this, 'isOpen', false);
},
// 属性变动赋值
ChangeAttr(res) {
let productSelect = this.productValue[res];
if (productSelect) {
this.$set(this.attr.productSelect, "image", productSelect.image);
this.$set(this.attr.productSelect, "price", productSelect.price);
this.$set(this.attr.productSelect, "stock", productSelect.stock);
this.$set(this.attr.productSelect, "unique", productSelect.id);
this.$set(this.attr.productSelect, "cart_num", 1);
this.$set(this.attr.productSelect, "vipPrice", productSelect.vipPrice);
this.$set(this.attr.productSelect, 'otPrice', productSelect.otPrice);
this.$set(this, "attrValue", res);
this.$set(this, "attrTxt", "已选择");
} else {
this.$set(this.attr.productSelect, "image", this.productInfo.image);
this.$set(this.attr.productSelect, "price", this.productInfo.price);
this.$set(this.attr.productSelect, "stock", 0);
this.$set(this.attr.productSelect, "unique", this.productInfo.id);
this.$set(this.attr.productSelect, "cart_num", 1);
this.$set(this.attr.productSelect, "vipPrice", this.productInfo.vipPrice);
this.$set(this.attr.productSelect, 'otPrice', this.productInfo.otPrice);
this.$set(this, "attrValue", "");
this.$set(this, "attrTxt", "请选择");
}
},
// 购物车数量加减
ChangeCartNum(changeValue) {
let productSelect = this.productValue[this.attrValue];
if (productSelect === undefined && !this.attr.productAttr.length)
productSelect = this.attr.productSelect;
if (productSelect === undefined) return;
let stock = productSelect.stock || 0;
let num = this.attr.productSelect;
if (changeValue) {
num.cart_num++;
if (num.cart_num > stock) {
this.$set(this.attr.productSelect, "cart_num", stock);
}
} else {
num.cart_num--;
if (num.cart_num < 1) {
this.$set(this.attr.productSelect, "cart_num", 1);
}
}
},
// 属性值选择
attrVal(val) {
this.$set(this.attr.productAttr[val.indexw], 'index', this.attr.productAttr[val.indexw].attrValues[val.indexn]);
},
// 购物车手动填写
iptCartNum(e) {
this.$set(this.attr.productSelect, 'cart_num', e ? e : 1);
},
// 加入购物车
onCartAdd() {
// 检查是否登录
if (this.isLogin === false) {
toLogin();
return;
}
// 调用统一的处理方法1 表示加入购物车
this.goCat(1);
},
// 立即兑换
onExchange() {
// 检查库存
if (this.attr.productSelect && this.attr.productSelect.stock <= 0) {
uni.showToast({
title: '库存不足',
icon: 'none'
});
return;
}
// 检查是否登录
if (this.isLogin === false) {
toLogin();
return;
}
// 调用统一的处理方法0 表示立即购买
this.goCat(0);
},
/**
* 统一处理加入购物车和立即兑换
* @param {Number} num 1-加入购物车 0-立即购买/兑换
*/
goCat(num) {
let that = this;
let productSelect = that.productValue[this.attrValue];
// 打开属性选择器
if (that.attrValue) {
// 默认选中了属性,但是没有打开过属性弹窗还是自动打开让用户查看默认选中的属性
that.attr.cartAttr = !that.isOpen ? true : false;
} else {
if (that.isOpen) that.attr.cartAttr = true;
else that.attr.cartAttr = !that.attr.cartAttr;
}
// 只有关闭属性弹窗时进行操作
if (that.attr.cartAttr === true && that.isOpen === false) {
return (that.isOpen = true);
}
// 如果有属性,没有选择或库存为0,提示用户选择
if (
that.attr.productAttr.length &&
productSelect &&
productSelect.stock === 0 &&
that.isOpen === true
) {
uni.showToast({
title: "产品库存不足,请选择其它",
icon: 'none'
});
return;
}
// 执行对应操作
if (num === 1) {
// 加入购物车
let q = {
productId: parseFloat(that.goodsId),
cartNum: parseFloat(that.attr.productSelect.cart_num),
isNew: false,
productAttrUnique: that.attr.productSelect !== undefined ?
that.attr.productSelect.unique : that.productInfo.id
};
postCartAdd(q).then(function(res) {
that.isOpen = false;
that.attr.cartAttr = false;
uni.showToast({
title: "添加购物车成功",
icon: 'success'
});
// 更新购物车数量
that.getCartCount(true);
}).catch(res => {
that.isOpen = false;
that.attr.cartAttr = false;
uni.showToast({
title: res.msg || '添加失败',
icon: 'none'
});
});
} else {
// 立即兑换 - 直接跳转到积分商城下单页面
this.goToConfirm();
}
},
// 直接跳转到积分商城下单页面
goToConfirm() {
// 保存商品信息到缓存
const goodsInfo = {
id: this.goodsId,
name: this.productInfo.storeName,
storeName: this.productInfo.storeName,
image: this.attr.productSelect.image || this.productInfo.image,
pic: this.attr.productSelect.image || this.productInfo.image,
points: this.attr.productSelect.price || this.productInfo.price,
integral: this.attr.productSelect.price || this.productInfo.price,
quantity: this.attr.productSelect.cart_num || 1,
attrValueId: this.attr.productSelect.unique || '',
unique: this.attr.productSelect.unique || '',
productAttrUnique: this.attr.productSelect.unique || '',
sku: this.attrValue || ''
};
uni.setStorageSync('buy_now_goods', goodsInfo);
this.isOpen = false;
this.attr.cartAttr = false;
// 跳转到积分商城下单页面
uni.navigateTo({
url: `/pages/integral/confirm?id=${this.goodsId}&quantity=${this.attr.productSelect.cart_num || 1}`
});
},
/**
* 获取购物车数量
* @param {Boolean} isAnima 是否展示购物车动画和重置属性
*/
getCartCount(isAnima) {
let that = this;
const isLogin = that.isLogin;
if (isLogin) {
getCartCounts(true, 'total').then(res => {
that.CartCount = res.data.count;
// 加入购物车后重置属性
if (isAnima) {
that.animated = true;
setTimeout(function() {
that.animated = false;
}, 500);
}
});
}
},
// 跳转购物车
goToCart() {
uni.navigateTo({
url: '/pages/integral/cart',
fail: (err) => {
console.error('跳转购物车失败:', err);
uni.showToast({
title: '跳转失败',
icon: 'none'
});
}
});
},
// 返回上一页
goBack() {
uni.navigateBack()
}
}
}
</script>
<style lang="scss" scoped>
.goods-detail {
min-height: 100vh;
background-color: #F5F5F5;
padding-bottom: 120rpx;
}
// 顶部导航栏
.nav-bar {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 113.344rpx;
background: linear-gradient(180deg, rgba(0, 0, 0, 0.4) 0%, rgba(0, 0, 0, 0) 100%);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 31.996rpx;
z-index: 200;
padding-top: constant(safe-area-inset-top);
padding-top: env(safe-area-inset-top);
}
.nav-back {
width: 72rpx;
height: 64rpx;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.3);
border-radius: 50%;
.iconfont {
font-size: 36rpx;
color: #FFFFFF;
}
}
.nav-title {
flex: 1;
font-size: 32rpx;
color: #FFFFFF;
font-weight: normal;
text-align: center;
line-height: 48rpx;
}
.nav-right {
width: 72rpx;
height: 64rpx;
}
// 图片轮播
.goods-swiper {
width: 100%;
height: 750rpx;
background-color: #FFFFFF;
}
.swiper-image {
width: 100%;
height: 100%;
}
// 仿照 goods_details 的 wrapper 样式
.wrapper {
background-color: #fff;
margin: 20rpx;
padding: 30rpx;
border-radius: 14rpx;
}
.share-section {
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-bottom: 20rpx;
}
.money-box {
display: flex;
align-items: baseline;
color: #FF4D4F;
}
.points-num {
font-size: 48rpx;
font-weight: bold;
}
.points-symbol {
font-size: 24rpx;
margin-right: 4rpx;
font-weight: bold;
}
.introduce {
font-size: 32rpx;
color: #282828;
font-weight: bold;
line-height: 1.5;
margin-bottom: 20rpx;
}
.label {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 24rpx;
color: #999;
}
// 仿照 goods_details 的 attribute 样式
.attribute {
background-color: #fff;
margin: 20rpx;
padding: 30rpx;
border-radius: 14rpx;
}
.attr-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.attr-text {
font-size: 28rpx;
color: #282828;
width: 600rpx;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.selected-attr {
color: #666;
margin-left: 10rpx;
}
.arrow-right {
font-size: 24rpx;
color: #999;
}
// 兑换规则
.rules-section,
.detail-section {
margin: 20rpx;
padding: 30rpx;
background-color: #FFFFFF;
border-radius: 20rpx;
}
.section-title {
font-size: 30rpx;
font-weight: bold;
color: #333333;
margin-bottom: 24rpx;
}
.detail-info-list {
margin-bottom: 30rpx;
}
.detail-item {
font-size: 26rpx;
color: #666666;
margin-bottom: 16rpx;
line-height: 1.5;
}
// 底部操作栏
.bottom-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
background-color: #FFFFFF;
border-top: 1px solid #EEEEEE;
padding: 20rpx 30rpx;
z-index: 100;
padding-bottom: calc(20rpx + constant(safe-area-inset-bottom));
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
display: flex;
align-items: center;
gap: 20rpx;
}
// 浮动购物车按钮
.floating-cart {
position: fixed;
right: 30rpx;
bottom: calc(240rpx + constant(safe-area-inset-bottom));
bottom: calc(240rpx + env(safe-area-inset-bottom));
width: 100rpx;
height: 100rpx;
background: #FFFFFF;
border-radius: 50%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.15);
z-index: 101;
.iconfont {
font-size: 44rpx;
color: #FF4D4F;
position: relative;
}
.cart-badge {
position: absolute;
top: -8rpx;
right: -16rpx;
background: linear-gradient(90deg, #FF6900 0%, #FF4D4F 100%);
color: #FFFFFF;
font-size: 18rpx;
padding: 2rpx 8rpx 3rpx;
border-radius: 200rpx;
min-width: 28rpx;
text-align: center;
}
.cart-text {
font-size: 18rpx;
color: #666666;
margin-top: 2rpx;
}
}
.animated {
animation-duration: 0.5s;
animation-fill-mode: both;
}
.bounceIn {
animation-name: bounceIn;
}
@keyframes bounceIn {
from,
20%,
40%,
60%,
80%,
to {
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
0% {
opacity: 0;
transform: scale3d(0.3, 0.3, 0.3);
}
20% {
transform: scale3d(1.1, 1.1, 1.1);
}
40% {
transform: scale3d(0.9, 0.9, 0.9);
}
60% {
opacity: 1;
transform: scale3d(1.03, 1.03, 1.03);
}
80% {
transform: scale3d(0.97, 0.97, 0.97);
}
to {
opacity: 1;
transform: scale3d(1, 1, 1);
}
}
.btn-group {
flex: 1;
display: flex;
gap: 20rpx;
}
.btn-cart,
.btn-buy {
flex: 1;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8rpx;
font-size: 28rpx;
font-weight: 500;
}
.btn-cart {
background-color: #FFFFFF;
border: 1px solid #EEEEEE;
color: #333333;
}
.btn-buy {
background: linear-gradient(90deg, #FF6900 0%, #FF4D4F 100%);
color: #FFFFFF;
&.disabled {
background: #CCCCCC;
color: #FFFFFF;
}
}
.detail-content img, .detail-content image{
width: 100%;
}
</style>