zdtap-uniapp-main/pagesActivity/activityDetail.vue

803 lines
19 KiB
Vue
Raw Normal View History

2025-03-29 16:01:43 +08:00
<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>
2025-03-29 16:01:43 +08:00
</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>
2025-03-29 16:01:43 +08:00
</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>
2025-03-29 16:01:43 +08:00
</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>
2025-03-29 16:01:43 +08:00
</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>
2025-03-29 16:01:43 +08:00
</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>
2025-03-29 16:01:43 +08:00
</view>
</view>
</view>
</view>
</template>
<script>
import {
getActivityInfo
} from '@/api/bar.js'
import process from './components/progress.vue';
import loginPopup from '@/components/loginPopup.vue';
2025-03-29 16:01:43 +08:00
export default {
components: {
process,
loginPopup
2025-03-29 16:01:43 +08:00
},
data() {
return {
id: '',
activityInfo: {},
currentTab: 1,
countdownTime: {
hours: '00',
minutes: '00',
seconds: '00'
},
timer: null,
isLoggedIn: false,
isVerified: false
2025-03-29 16:01:43 +08:00
};
},
onLoad({
id
}) {
this.id = id;
this.init();
this.checkLoginStatus();
2025-03-29 16:01:43 +08:00
},
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;
},
2025-03-29 16:01:43 +08:00
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);
2025-03-29 16:01:43 +08:00
}
return 0;
},
targetText() {
const { beer_scope, activityTarget, beers } = this.activityInfo;
if (!activityTarget) return '-';
return beer_scope === 0 ? '全系列酒款' : '以下酒款';
2025-03-29 16:01:43 +08:00
}
},
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'
});
});
2025-03-29 16:01:43 +08:00
},
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);
},
2025-03-29 16:01:43 +08:00
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);
2025-03-29 16:01:43 +08:00
const currentDate = new Date();
const timeDiff = targetDate.getTime() - currentDate.getTime();
const remainingDays = Math.ceil(timeDiff / (1000 * 3600 * 24));
return Math.max(remainingDays, 0); // 确保不会显示负数天数
2025-03-29 16:01:43 +08:00
},
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;
2025-03-29 16:01:43 +08:00
}
},
onShow() {
// 每次页面显示时检查登录状态
this.checkLoginStatus();
2025-03-29 16:01:43 +08:00
}
}
</script>
<style lang="scss" scoped>
.page {
min-height: 100vh;
background: #F7F7F7;
2025-03-29 16:01:43 +08:00
.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;
}
2025-03-29 16:01:43 +08:00
}
.cuIcon-right {
color: #CCCCCC;
font-size: 24rpx;
margin-left: 8rpx;
2025-03-29 16:01:43 +08:00
}
}
}
.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;
2025-03-29 16:01:43 +08:00
}
.unit {
font-size: 24rpx;
color: #67677A;
margin-left: 8rpx;
}
}
}
.beer-scroll {
padding: 24rpx 0;
.beer-list {
display: flex;
padding: 0 32rpx;
gap: 24rpx;
2025-03-29 16:01:43 +08:00
.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;
2025-03-29 16:01:43 +08:00
}
.beer-image {
width: 200rpx;
height: 280rpx;
border-radius: 8rpx;
margin-bottom: 12rpx;
2025-03-29 16:01:43 +08:00
}
.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;
}
}
2025-03-29 16:01:43 +08:00
}
}
}
}
2025-03-29 16:01:43 +08:00
.reward-content {
display: flex;
align-items: center;
gap: 24rpx;
padding: 0 32rpx 32rpx;
.goods-image {
width: 160rpx;
height: 160rpx;
border-radius: 12rpx;
2025-03-29 16:01:43 +08:00
background: #FFFFFF;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
2025-03-29 16:01:43 +08:00
}
.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;
}
}
2025-03-29 16:01:43 +08:00
}
}
2025-03-29 16:01:43 +08:00
</style>