Files
integral-shop/single_uniapp22miao/pages/integral/detail.vue
panchengyong 786bf78282 更新项目配置和添加小程序模块
- 修改 ArticleController.java
- 更新 application.yml 配置
- 更新 frontend/.env.production 环境配置
- 添加 single_uniapp22miao 小程序模块
- 添加 logs 目录
2026-03-13 13:27:13 +08:00

850 lines
21 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-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>