zdtap-uniapp-main/pagesActivity/activityDetail.vue

801 lines
18 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">
<!-- 登录组件 -->
<login-popup ref="loginPopup" @success="onLoginSuccess"></login-popup>
<!-- 进度展示区域 -->
<view class="section">
<view class="section-title">
<template v-if="activityInfo.hasJoined">
累计天数还剩
</template>
<template v-else>
<template v-if="activityInfo.remainingDays <= 0">
活动招募已结束
</template>
<template v-else>
活动招募即将结束
</template>
</template>
</view>
<view class="info-list">
<!-- 倒计时区域 -->
<view class="info-item countdown-box">
<view class="countdown-container">
<view class="countdown-item">
<text class="number">{{activityInfo.hasJoined ? activityInfo.remainingDuration : (activityInfo.remainingDays || '0')}}</text>
<text class="unit"></text>
</view>
<view class="countdown-item" v-if="!activityInfo.hasJoined">
<text class="number">{{countdownTime.hours || '00'}}</text>
<text class="unit"></text>
</view>
<view class="countdown-item" v-if="!activityInfo.hasJoined">
<text class="number">{{countdownTime.minutes || '00'}}</text>
<text class="unit"></text>
</view>
<view class="countdown-item" v-if="!activityInfo.hasJoined">
<text class="number">{{countdownTime.seconds || '00'}}</text>
<text class="unit"></text>
</view>
</view>
</view>
</view>
</view>
<!-- 活动信息区域 -->
<view class="section">
<view class="section-title">活动信息</view>
<view class="info-list">
<view class="info-item" v-if="activityInfo.beers" @click="toBrand">
<text>活动发起方</text>
<view class="right-content">
<image :src="activityInfo.beers[0].breweryLogo" class="brand-logo"></image>
<text class="info-text">{{ activityInfo.beers[0].brandName}}</text>
<text class="cuIcon-right"></text>
</view>
</view>
<view class="info-item">
<text>活动时间</text>
<view class="right-content">
<text class="info-text">首次扫码开始累计 <text class="highlight">{{activityInfo.duration || '-'}} 天内</text></text>
</view>
</view>
<view class="info-item">
<text>活动目标</text>
<view class="right-content">
<text class="info-text">{{ activityInfo.beerScope === 0 ? '品牌全系列酒款' : '以下酒款' }}累计扫码 <text class="highlight">{{activityInfo.activityTarget || '-'}} </text></text>
</view>
</view>
</view>
</view>
<!-- 参与酒款区域 -->
<view class="section">
<view class="section-title">参与酒款</view>
<scroll-view scroll-x="true" class="beer-scroll">
<view class="beer-list">
<view v-for="(it, index) in activityInfo.beers" :key="index" class="beer-card" @click="toBeerDetail(it)">
<image :src="it.cover" class="beer-image"></image>
<view class="beer-info">
<text class="name">{{ it.beerName || ''}}</text>
<text class="style">{{ it.beerStyles || '' }}</text>
<view class="rating">
<image src="@/static/vector.png" class="rating-icon"></image>
<text>{{ it.beerOverallRating || 0 }}{{ it.beerReviewsCount || 0}}</text>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
<!-- 奖励区域 -->
<view class="section" v-if="activityInfo.activityRewardType">
<view class="section-title">挑战奖励</view>
<view class="info-list">
<view class="info-item reward-content" v-if="activityInfo.activityRewardType == 1">
<image :src="activityInfo.activityRewardGoods.goodsCover" class="goods-image" mode="aspectFit"></image>
<view class="goods-info">
<text class="name">{{activityInfo.activityRewardGoods.goodsName}}</text>
<view class="specs-info">
<text class="specs-label">规格</text>
<text class="specs-value">{{activityInfo.activityRewardGoods.specs}}</text>
</view>
<text class="count">奖励数量{{activityInfo.activityRewardCount}}</text>
</view>
</view>
<view class="info-item reward-content" v-if="activityInfo.activityRewardType == 2">
<view class="coin-container">
<image :src="activityInfo.brandLogo" class="coin-logo" mode="aspectFit"></image>
</view>
<view class="coin-info">
<text class="name">{{activityInfo.breweryName}}品牌啤酒币</text>
<text class="count">奖励数量 {{ activityInfo.activityRewardCount }}</text>
</view>
</view>
</view>
</view>
<!-- 底部操作栏 -->
<view class="bottom-bar">
<view class="action-button"
:class="{
'disabled': !isLoggedIn || !isVerified || (!activityInfo.hasJoined && activityInfo.remainingDays <= 0)
}"
@click="activityInfo.remainingDays <= 0 && !activityInfo.hasJoined ? null : handleButtonClick">
<text class="cuIcon-qr_code"></text>
<text>扫桶标自动开始累计</text>
</view>
</view>
</view>
</view>
</template>
<script>
import {
getActivityInfo
} from '@/api/bar.js'
import loginPopup from '@/components/loginPopup.vue';
export default {
components: {
loginPopup
},
data() {
return {
id: '',
activityInfo: {},
currentTab: 1,
countdownTime: {
hours: '00',
minutes: '00',
seconds: '00'
},
timer: null,
isLoggedIn: false,
isVerified: false
};
},
onLoad({
id
}) {
this.id = id;
this.init();
this.checkLoginStatus();
},
computed: {
totalDays() {
if(this.activityInfo.endDate && this.activityInfo.startDate) {
const endDate = new Date(this.activityInfo.endDate);
const startDate = new Date(this.activityInfo.startDate);
return Math.ceil((endDate - startDate) / (1000 * 3600 * 24));
}
return 0;
},
processNum() {
if(this.activityInfo.endDate) {
const endDate = new Date(this.activityInfo.endDate);
const startDate = new Date(this.activityInfo.startDate || new Date());
const currentDate = new Date();
// 计算总天数
const totalDays = Math.ceil((endDate - startDate) / (1000 * 3600 * 24));
// 计算已经过去的天数
const passedDays = Math.ceil((currentDate - startDate) / (1000 * 3600 * 24));
// 确保进度在0-100之间
return Math.min(Math.max(Math.floor((passedDays / totalDays) * 100), 0), 100);
}
return 0;
},
targetText() {
const { beer_scope, activityTarget, beers } = this.activityInfo;
if (!activityTarget) return '-';
return beer_scope === 0 ? '全系列酒款' : '以下酒款';
}
},
methods: {
init() {
getActivityInfo(this.id).then(res => {
console.log('活动详情数据:', res.data);
this.activityInfo = res.data;
// 检查关键数据是否存在
if (!this.activityInfo.duration) {
console.warn('缺少活动累计天数duration');
}
if (!this.activityInfo.activityTarget) {
console.warn('缺少活动目标数量activityTarget');
}
if (typeof this.activityInfo.beer_scope === 'undefined') {
console.warn('缺少活动产品范围beer_scope');
}
// 计算剩余天数
this.activityInfo.remainingDays = this.getRemainingDays(res.data.endDate);
// 启动倒计时
this.startCountdown();
}).catch(err => {
console.error('获取活动详情失败:', err);
uni.showToast({
title: '获取活动详情失败',
icon: 'none'
});
});
},
startCountdown() {
if(this.timer) {
clearInterval(this.timer);
}
const updateCountdown = () => {
if(!this.activityInfo.endDate) return;
const now = new Date().getTime();
const end = new Date(this.activityInfo.endDate).getTime();
const diff = end - now;
if(diff <= 0) {
clearInterval(this.timer);
this.countdownTime = {
hours: '00',
minutes: '00',
seconds: '00'
};
return;
}
// 更新剩余天数
this.activityInfo.remainingDays = this.getRemainingDays(this.activityInfo.endDate);
// 计算时分秒
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((diff % (1000 * 60)) / 1000);
this.countdownTime = {
hours: hours.toString().padStart(2, '0'),
minutes: minutes.toString().padStart(2, '0'),
seconds: seconds.toString().padStart(2, '0')
};
};
// 立即执行一次
updateCountdown();
// 每秒更新一次
this.timer = setInterval(updateCountdown, 1000);
},
changeTab(index) {
this.currentTab = index
},
// 跳转 品牌方
toBrand() {
uni.navigateTo({
url: '/pages/index/brandHome?breweryId=' + this.activityInfo.breweryId
})
},
// 计算剩余天数
getRemainingDays(endDate) {
if(!endDate) return 0;
const targetDate = new Date(endDate);
const currentDate = new Date();
const timeDiff = targetDate.getTime() - currentDate.getTime();
const remainingDays = Math.ceil(timeDiff / (1000 * 3600 * 24));
return Math.max(remainingDays, 0); // 确保不会显示负数天数
},
handleScan() {
uni.getSetting({
success: (res) => {
if (!res.authSetting['scope.userLocation']) {
uni.authorize({
scope: 'scope.userLocation',
success: (res) => {
console.log(res)
this.startScan()
},
fail: (err) => {
uni.showModal({
title: '提示',
content: '请先授权获取您的位置信息',
success: (res) => {
if (res.confirm) {
uni.openSetting({
success: (res) => {
if (res.authSetting['scope.userLocation']) {
this.startScan()
}
}
})
} else if (res.cancel) {
console.log('用户点击取消')
}
}
})
}
})
}else {
this.startScan()
}
},
})
},
// 开始扫码
startScan() {
this.handleGetLocation()
},
handleGetLocation() {
uni.getLocation({
type: "gcj02",
success: (resLoc) => {
// console.log(resLoc)
uni.scanCode({
success: (res) => {
if(!resLoc.latitude && !resLoc.longitude) {
uni.showToast({
title: '位置信息获取失败',
icon: 'none',
})
return
}
uni.navigateTo({
url: '/pagesActivity/scanResult?result=' + res.result + '&latitude=' + resLoc.latitude + '&longitude=' + resLoc.longitude
})
}
})
},
fail: (err) => {
console.log(err)
}
})
},
goBack() {
uni.navigateBack()
},
checkLoginStatus() {
const token = uni.getStorageSync('token');
const barInfo = uni.getStorageSync('barInfo');
this.isLoggedIn = !!token;
this.isVerified = barInfo && barInfo.authState === 1;
},
// 检查认证状态并处理
checkAuthAndHandle() {
const barInfo = uni.getStorageSync('barInfo');
if (!barInfo) {
uni.showModal({
title: '提示',
content: '请先认证门店',
showCancel: true,
success: (res) => {
if (res.confirm) {
uni.navigateTo({
url: '/pages/index/registration'
});
}
}
});
return false;
}
if (barInfo.authState === 2) {
uni.showModal({
title: '提示',
content: '您的门店正在认证中,请耐心等待',
showCancel: true,
success: (res) => {
if (res.confirm) {
uni.navigateTo({
url: '/pages/index/registration'
});
}
}
});
return false;
} else if (barInfo.authState === 1) {
return true;
}
uni.showModal({
title: '提示',
content: '请先认证门店',
showCancel: true,
success: (res) => {
if (res.confirm) {
uni.navigateTo({
url: '/pages/index/registration'
});
}
}
});
return false;
},
handleButtonClick() {
const token = uni.getStorageSync('token');
if (!token) {
this.$refs.loginPopup.open();
return;
}
if (this.checkAuthAndHandle()) {
this.handleScan();
}
},
onLoginSuccess(data) {
// 登录成功后重新检查状态
this.checkLoginStatus();
// 如果需要刷新页面
if (data && data.needRefresh) {
// 重新初始化页面数据
this.init();
}
if (this.checkAuthAndHandle()) {
this.handleScan();
}
},
// 跳转到酒款详情页
toBeerDetail(beer) {
if (!beer || !beer.id) {
uni.showToast({
title: '酒款信息不完整',
icon: 'none'
});
return;
}
// 确保id是数字类型
const beerId = parseInt(beer.id);
if (isNaN(beerId)) {
uni.showToast({
title: '酒款ID无效',
icon: 'none'
});
return;
}
uni.navigateTo({
url: '/pages/index/review?beerId=' + beerId
});
}
},
onUnload() {
// 页面卸载时清除定时器
if(this.timer) {
clearInterval(this.timer);
this.timer = null;
}
},
onShow() {
// 每次页面显示时检查登录状态
this.checkLoginStatus();
}
}
</script>
<style lang="scss" scoped>
.page {
min-height: 100vh;
background: #F7F7F7;
.page-content {
padding: 24rpx 32rpx;
padding-bottom: calc(152rpx + env(safe-area-inset-bottom));
}
.section {
background: #FFFFFF;
border-radius: 16rpx;
margin-bottom: 24rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
overflow: hidden;
.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;
}
}
}
.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;
}
}
}
.countdown-box {
padding: 24rpx 32rpx;
justify-content: center;
}
}
.countdown-container {
display: flex;
gap: 24rpx;
justify-content: center;
.countdown-item {
display: flex;
align-items: baseline;
.number {
font-size: 48rpx;
color: #D42E78;
font-weight: bold;
font-family: 'DIN';
background: #F5F5F5;
padding: 8rpx 12rpx;
border-radius: 12rpx;
min-width: 80rpx;
text-align: center;
}
.unit {
font-size: 24rpx;
color: #67677A;
margin-left: 8rpx;
}
}
}
.beer-scroll {
padding: 24rpx 0;
.beer-list {
display: flex;
padding: 0 32rpx;
gap: 24rpx;
.beer-card {
width: 200rpx;
flex-shrink: 0;
background: #F9F9F9;
border-radius: 12rpx;
padding: 16rpx;
transition: all 0.3s ease;
&:active {
transform: scale(0.95);
background: #F0F0F0;
}
.beer-image {
width: 200rpx;
height: 280rpx;
border-radius: 8rpx;
margin-bottom: 12rpx;
}
.beer-info {
.name {
font-size: 28rpx;
color: #333333;
font-weight: bold;
margin-bottom: 8rpx;
display: block;
}
.style {
font-size: 24rpx;
color: rgba(51, 51, 51, 0.8);
margin-bottom: 8rpx;
display: block;
}
.rating {
display: flex;
align-items: center;
gap: 8rpx;
.rating-icon {
width: 20rpx;
height: 20rpx;
}
text {
font-size: 24rpx;
color: #666666;
}
}
}
}
}
}
.reward-content {
display: flex;
align-items: center;
gap: 24rpx;
padding: 0 32rpx 32rpx;
.goods-image {
width: 160rpx;
height: 160rpx;
border-radius: 12rpx;
background: #FFFFFF;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
}
.goods-info {
flex: 1;
.name {
font-size: 32rpx;
color: #333333;
margin-bottom: 12rpx;
display: block;
font-weight: 600;
}
.specs-info {
display: flex;
align-items: center;
margin-bottom: 12rpx;
.specs-label {
font-size: 32rpx;
color: #666666;
}
.specs-value {
font-size: 32rpx;
color: #333333;
font-weight: 500;
}
}
.count {
font-size: 32rpx;
color: #333333;
font-weight: 500;
}
}
.coin-container {
position: relative;
width: 120rpx;
height: 120rpx;
flex-shrink: 0;
.coin-logo {
width: 100%;
height: 100%;
border-radius: 50%;
position: relative;
background: #FFFFFF;
padding: 16rpx;
box-sizing: border-box;
object-fit: contain;
}
}
.coin-info {
flex: 1;
.name {
font-size: 32rpx;
color: #333333;
margin-bottom: 12rpx;
display: block;
font-weight: 600;
}
.count {
font-size: 32rpx;
color: #333333;
font-weight: 500;
}
}
}
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #FFFFFF;
padding: 24rpx 32rpx;
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
display: flex;
justify-content: center;
box-shadow: 0 -4rpx 12rpx rgba(0, 0, 0, 0.05);
.action-button {
width: 686rpx;
height: 88rpx;
background: linear-gradient(135deg, #19367A, #2C4C99);
border-radius: 44rpx;
color: #FFFFFF;
font-size: 32rpx;
font-weight: 500;
letter-spacing: 2rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 8rpx;
box-shadow: 0 4rpx 16rpx rgba(25, 54, 122, 0.2);
transition: all 0.3s ease;
&:active {
transform: scale(0.98);
box-shadow: 0 2rpx 8rpx rgba(25, 54, 122, 0.1);
}
&.disabled {
background: #EFEFEF;
color: #999999;
box-shadow: none;
&:active {
transform: none;
}
}
.cuIcon-qr_code {
font-size: 36rpx;
}
}
}
}
</style>