zdtap-uniapp-main/pagesCoin/goodsDetail.vue

563 lines
12 KiB
Vue
Raw Permalink 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="page">
<view class="page-content">
<!-- 自定义导航栏 -->
<view class="custom-nav" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="nav-content">
<view class="back-btn" @click="goBack">
<text class="cuIcon-back"></text>
</view>
</view>
</view>
<!-- 广告轮播区域 -->
<view class="ad-section">
<swiper class="ad-swiper" circular autoplay interval="3000" duration="500" @change="handleSwiperChange">
<swiper-item v-for="(item, index) in adList" :key="index">
<view class="ad-placeholder" v-if="!item.imageUrl">
<text class="placeholder-text">广告位</text>
</view>
<image v-else :src="item.imageUrl" mode="aspectFill" class="ad-image" @click="handleAdClick(item)"></image>
</swiper-item>
</swiper>
<view class="ad-dots" v-if="adList.length > 1">
<view class="dot" v-for="(item, index) in adList" :key="index" :class="{ active: currentAdIndex === index }"></view>
</view>
</view>
<!-- 商品信息区域 -->
<view class="section">
<view class="section-title">商品信息</view>
<view class="info-list">
<view class="info-item" @click="goBreweryDetail">
<text>服务商家</text>
<view class="right-content">
<image :src="breweryInfo.brandLogo" class="brand-logo"></image>
<text class="info-text">{{ breweryInfo.brandName}}</text>
<text class="cuIcon-right"></text>
</view>
</view>
<view class="info-item">
<text>商品名称</text>
<view class="right-content">
<text class="info-text">{{ detail.goodsName}}</text>
</view>
</view>
<view class="info-item">
<text>库存数量</text>
<view class="right-content">
<text class="info-text">限量<text class="highlight">{{ detail.stockNum}}</text>兑完即止</text>
</view>
</view>
<view class="info-item">
<text>已兑换</text>
<view class="right-content">
<text class="info-text highlight">{{ detail.salesCount }}</text>
</view>
</view>
</view>
</view>
<!-- 兑换须知区域 -->
<view class="section">
<view class="section-title">兑换须知</view>
<view class="notice-list">
<view class="notice-item">
<image src="@/static/info-circle.png" class="notice-icon"></image>
<text class="notice-text">限购说明此商品每人限兑1件</text>
</view>
</view>
</view>
<!-- 商品图片区域 -->
<view class="section">
<view class="section-title">商品展示</view>
<view class="goods-image-container">
<image class="goods-image" :src="detail.goodsCover" mode="aspectFit"></image>
</view>
</view>
</view>
<!-- 底部操作栏 -->
<view class="bottom-bar">
<view class="coin-info">
<view class="coin-amount">
<text class="amount">{{ detail.redeemedNum}}</text>
<image src="@/static/beerCoin.png" class="coin-icon"></image>
</view>
<view class="balance-info">
<text>当前可用品牌币{{ isLogin ? (coinBalance.balance || 0) : '登录后可见' }}</text>
<image src="@/static/beerCoin.png" class="coin-icon"></image>
</view>
</view>
<view class="action-button" @click="handleRedeem" :class="{'disabled': !canRedeem}">
<text>立即兑换</text>
</view>
</view>
<!-- 登录弹窗 -->
<loginPopup ref="loginRef"></loginPopup>
</view>
</template>
<script>
import loginPopup from '@/components/loginPopup.vue';
import { getGoodsInfo, getBreweryInfo, getBreweryCoinBalance } from '@/api/bar.js'
export default {
components: {
loginPopup
},
data() {
return {
id:'',
breweryId:'', // 品牌方id
detail: {},
breweryInfo:{},
coinBalance:{},
isLogin: false, // 登录状态
statusBarHeight: 0, // 状态栏高度
adList: [{}, {}, {}], // 广告列表测试阶段使用3个占位
currentAdIndex: 0, // 当前广告索引
};
},
onLoad({id, breweryId}) {
// 获取状态栏高度
this.statusBarHeight = uni.getSystemInfoSync().statusBarHeight
this.id = id
this.breweryId = breweryId
this.checkLoginStatus()
this.getBreweryInfoFun()
this.getDetailFun()
},
onShow() {
this.checkLoginStatus()
if(this.isLogin) {
this.getBreweryCoinBalanceFun()
}
},
methods: {
// 返回上一页
goBack() {
uni.navigateBack()
},
// 检查登录状态
checkLoginStatus() {
try {
const token = uni.getStorageSync('token')
const userInfo = uni.getStorageSync('userInfo')
this.isLogin = !!token && !!userInfo
// 如果登录了但没有用户信息,说明登录状态异常
if(token && !userInfo) {
uni.removeStorageSync('token')
this.isLogin = false
}
} catch(e) {
console.error('检查登录状态失败:', e)
this.isLogin = false
}
},
// 品牌方详情
getBreweryInfoFun() {
getBreweryInfo(this.breweryId).then(res => {
this.breweryInfo = res.data
}).catch(err => {
console.error('获取品牌信息失败:', err)
uni.showToast({
title: '获取品牌信息失败',
icon: 'none'
})
})
},
// 获取详情
getDetailFun() {
uni.showLoading({
title: '加载中',
mask: true
})
getGoodsInfo(this.id).then(res => {
uni.hideLoading()
if (res.code == 200) {
this.detail = res.data
}
}).catch(err => {
console.error('获取商品详情失败:', err)
uni.hideLoading()
uni.showToast({
title: '获取商品详情失败',
icon: 'none'
})
})
},
// 跳转品牌方详情
goBreweryDetail() {
uni.navigateTo({
url: '/pages/index/brandHome?breweryId=' + this.breweryId
})
},
// 特定品牌啤酒币余额
getBreweryCoinBalanceFun() {
getBreweryCoinBalance(this.breweryId).then(res => {
this.coinBalance = res.data
}).catch(err => {
console.error('获取币种余额失败:', err)
uni.showToast({
title: '获取币种余额失败',
icon: 'none'
})
})
},
// 兑换
async handleRedeem() {
// 重新检查登录状态
this.checkLoginStatus()
if(!this.isLogin) {
// 使用 nextTick 确保组件已加载
await this.$nextTick()
if(this.$refs.loginRef) {
this.$refs.loginRef.open()
} else {
uni.showToast({
title: '请稍后再试',
icon: 'none'
})
}
return
}
if(!this.coinBalance.balance || this.coinBalance.balance < this.detail.redeemedNum) {
uni.showToast({
title: '品牌币余额不足',
icon: 'none',
duration: 2000
})
return
}
uni.navigateTo({
url: '/pagesCoin/redeemOrder?goodsId=' + this.id
})
},
// 处理轮播图切换
handleSwiperChange(e) {
this.currentAdIndex = e.detail.current
},
// 处理广告点击
handleAdClick(item) {
if (item.linkUrl) {
uni.navigateTo({
url: item.linkUrl
})
}
},
},
computed: {
canRedeem() {
return this.isLogin && this.coinBalance.balance && this.coinBalance.balance >= this.detail.redeemedNum
}
}
}
</script>
<style lang="scss" scoped>
.page {
min-height: 100vh;
background: #F7F7F7;
.page-content {
padding: 0;
padding-bottom: calc(180rpx + env(safe-area-inset-bottom));
}
.ad-section {
margin: 0;
position: relative;
width: 100%;
overflow: hidden;
.ad-swiper {
width: 100%;
height: 500rpx;
.ad-image {
width: 100%;
height: 100%;
}
.ad-placeholder {
width: 100%;
height: 100%;
background: #F5F5F5;
display: flex;
align-items: center;
justify-content: center;
.placeholder-text {
font-size: 28rpx;
color: #999999;
}
}
}
.ad-dots {
position: absolute;
bottom: 20rpx;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 12rpx;
padding: 8rpx 16rpx;
background: rgba(0, 0, 0, 0.2);
border-radius: 20rpx;
.dot {
width: 12rpx;
height: 12rpx;
border-radius: 50%;
background: rgba(255, 255, 255, 0.6);
transition: all 0.3s ease;
&.active {
width: 24rpx;
border-radius: 6rpx;
background: #FFFFFF;
}
}
}
}
.custom-nav {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 999;
background: transparent;
.nav-content {
height: 44px;
display: flex;
align-items: center;
padding: 0 32rpx;
.back-btn {
width: 64rpx;
height: 64rpx;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.8);
border-radius: 50%;
.cuIcon-back {
font-size: 36rpx;
color: #000;
}
}
}
}
.section {
background: #FFFFFF;
border-radius: 16rpx;
margin: 24rpx 32rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
overflow: hidden;
position: relative;
z-index: 10;
.section-title {
font-size: 32rpx;
font-weight: 600;
padding: 32rpx;
color: #333333;
position: relative;
&::after {
content: '';
position: absolute;
left: 32rpx;
bottom: 0;
width: 48rpx;
height: 4rpx;
background: #19367A;
border-radius: 2rpx;
margin-bottom: 12rpx;
}
}
}
.info-list {
.info-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 32rpx;
border-bottom: 1rpx solid rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
&:last-child {
border-bottom: none;
}
text {
font-size: 28rpx;
color: #333333;
}
.right-content {
display: flex;
align-items: center;
gap: 16rpx;
.brand-logo {
width: 48rpx;
height: 48rpx;
border-radius: 50%;
border: 1rpx solid #19367A;
}
.info-text {
font-size: 28rpx;
color: #666666;
text-align: right;
.highlight {
color: #19367A;
font-weight: 600;
}
}
.cuIcon-right {
color: #CCCCCC;
font-size: 24rpx;
margin-left: 8rpx;
}
}
}
}
.goods-image-container {
padding: 32rpx;
display: flex;
justify-content: center;
.goods-image {
width: 400rpx;
height: 400rpx;
border-radius: 12rpx;
background: #FFFFFF;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
}
}
.notice-list {
padding: 0 32rpx 32rpx;
.notice-item {
display: flex;
align-items: center;
gap: 16rpx;
.notice-icon {
width: 32rpx;
height: 32rpx;
}
.notice-text {
font-size: 28rpx;
color: #666666;
}
}
}
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #FFFFFF;
padding: 32rpx;
padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 -4rpx 12rpx rgba(0, 0, 0, 0.05);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
z-index: 100;
.coin-info {
flex: 1;
.coin-amount {
display: flex;
align-items: center;
margin-bottom: 12rpx;
.amount {
font-size: 48rpx;
color: #333333;
font-weight: 700;
}
.coin-icon {
width: 40rpx;
height: 40rpx;
margin-left: 12rpx;
}
}
.balance-info {
display: flex;
align-items: center;
text {
font-size: 24rpx;
color: #666666;
}
.coin-icon {
width: 32rpx;
height: 32rpx;
margin-left: 8rpx;
}
}
}
.action-button {
width: 344rpx;
height: 88rpx;
background: linear-gradient(135deg, #19367A, #2C4C99);
border-radius: 44rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
color: #FFFFFF;
transition: all 0.3s ease;
box-shadow: 0 4rpx 16rpx rgba(25, 54, 122, 0.2);
&:active {
transform: scale(0.98);
box-shadow: 0 2rpx 8rpx rgba(25, 54, 122, 0.1);
}
&.disabled {
background: #CCCCCC;
box-shadow: none;
pointer-events: none;
opacity: 0.6;
}
}
}
}
// 确保登录弹窗在最顶层
:deep(.uni-popup) {
z-index: 9999;
}
</style>