zdtap-uniapp-main/pagesActivity/activityDetail.vue

783 lines
20 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="page-content">
<!-- 登录组件 -->
<login-popup ref="loginPopup" @success="onLoginSuccess"></login-popup>
<!-- 2. 进度展示区域 -->
<view class="bg-white progress-box" style="border-radius: 0 0 12rpx 12rpx;">
<!-- 标题文字 -->
<view class="title-text section-title">活动招募即将结束</view>
<!-- 倒计时区域 -->
<view class="countdown-container">
<view class="countdown-item">
<text class="number">{{activityInfo.remainingDays || '0'}}</text>
<text class="unit"></text>
</view>
<view class="countdown-item">
<text class="number">{{countdownTime.hours || '00'}}</text>
<text class="unit"></text>
</view>
<view class="countdown-item">
<text class="number">{{countdownTime.minutes || '00'}}</text>
<text class="unit"></text>
</view>
<view class="countdown-item">
<text class="number">{{countdownTime.seconds || '00'}}</text>
<text class="unit"></text>
</view>
</view>
<!-- 进度条区域 -->
<view class="progress-bar">
<view class="progress-track">
<view class="progress-fill" :style="{ width: processNum + '%' }"></view>
</view>
<view class="progress-labels">
<text class="start">0</text>
<text class="end">{{totalDays}}</text>
</view>
</view>
</view>
<!-- 3. 活动信息区域 -->
<view class="bg-white padding margin-bottom-sm activity-info-box" style="border-radius:12rpx;margin-top: 24rpx;">
<view v-if="activityInfo.beers" class="brand-info" @click="toBrand">
<image :src="activityInfo.beers[0].breweryLogo" class="brand-logo"></image>
<view class="brand-text">
<view class="initiator">活动发起方</view>
<view class="section-title">{{ activityInfo.beers[0].brandName}}</view>
</view>
<view class="cuIcon-right"></view>
</view>
<template v-if="currentTab == 1">
<view class="activity-duration">首次扫码开始累计 <text class="duration-value">{{activityInfo.duration || '-'}} 天内</text></view>
<view class="activity-target">
{{ activityInfo.beer_scope === 0 ? '全系列酒款' : '以下酒款' }}累计扫码 {{activityInfo.activityTarget || '-'}}
</view>
</template>
<scroll-view v-if="currentTab == 1" scroll-x="true" class="scroll-container">
<view v-for="(it, index) in activityInfo.beers" :key="index" style="display: inline-block;" class="row-box" @click="toReview(it)">
<view class="beer-box">
<image :src="it.cover" class="cover"></image>
<view class="title word-all">{{ it.beerName || ''}}</view>
<view class="desc word-all">{{ it.beerStyles || '' }}</view>
<view class="desc word-all">{{ it.brandName ||'' }}</view>
<view class="flex align-center num">
<image src="@/static/vector.png" style="width: 20rpx;height: 20rpx;margin-right: 10rpx;">
</image>
{{ it.beerOverallRating || 0 }}{{ it.beerReviewsCount || 0}}
</view>
</view>
</view>
</scroll-view>
</view>
<!-- 1 商品 2 啤酒币 -->
<view v-if="activityInfo.activityRewardType == 1 || currentTab == 1" class="bg-white padding margin-bottom-sm"
style="border-radius:12rpx;">
<view class="section-title margin-bottom-sm">挑战奖励</view>
<view v-if="activityInfo.activityRewardGoods" class="flex align-center">
<image :src="activityInfo.activityRewardGoods.goodsCover" style="width: 144rpx;height: 204rpx;margin-right:16rpx;border-radius: 12rpx;"></image>
<view class="flex-1">
<view class="flex flex-1 align-center">
<view class="flex-1">
<view class="word-all margin-bottom-sm" style="color:#1E2019">{{activityInfo.activityRewardGoods.goodsName}}</view>
<!-- <view class="word-all margin-bottom-sm" style="color:rgba(30, 32, 25, 0.8)">风味西打酒</view>
<view class="word-all margin-bottom-sm" style="color:#1E2019">TasteRoom 风味屋</view> -->
</view>
<view class="text-red text-right">x<text class="text-xxl text-bold">{{activityInfo.activityRewardCount}}</text></view>
</view>
</view>
</view>
</view>
<view v-if="activityInfo.activityRewardType == 2" class="bg-white padding margin-bottom-sm"
style="border-radius:12rpx;">
<view class="margin-bottom-sm">奖励</view>
<view class="flex align-center">
<!-- <image src="@/static/img/icon-logo.png" class="margin-right"
style="width: 72rpx;height: 72rpx;border-radius: 30rpx;"></image> -->
<view class="flex flex-1 align-center">
<view class="flex-1 word-all" style="color:#1E2019">啤酒币</view>
<view class="text-red text-right">x<text
class="text-xxl text-bold">{{ activityInfo.activityRewardCount }}</text></view>
</view>
</view>
</view>
<!-- <view style="height: 200rpx;background-color: transparent;"></view> -->
<!-- 5. 底部操作栏 -->
<view class="bg-white flex justify-center align-start padding-top bottom-bar">
<view class="scan-btn" :class="{'btn-disabled': !isLoggedIn || !isVerified}" @click="handleButtonClick">
<text class="cuIcon-qr_code margin-right-xs"></text>
扫桶标自动开始累计
</view>
</view>
</view>
</template>
<script>
import {
getActivityInfo
} from '@/api/bar.js'
import process from './components/progress.vue';
import loginPopup from '@/components/loginPopup.vue';
export default {
components: {
process,
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;
},
handleButtonClick() {
const token = uni.getStorageSync('token');
if (!token) {
this.$refs.loginPopup.open();
return;
}
// 检查认证状态
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;
}
// 处理不同的认证状态
if (barInfo.authState === 2) {
uni.showModal({
title: '提示',
content: '您的门店正在认证中,请耐心等待',
showCancel: true,
success: (res) => {
if (res.confirm) {
uni.navigateTo({
url: '/pages/index/registration'
});
}
}
});
return;
} else if (barInfo.authState === 1) {
// 已认证,允许操作
this.handleScan();
return;
}
// 未认证状态
uni.showModal({
title: '提示',
content: '请先认证门店',
showCancel: true,
success: (res) => {
if (res.confirm) {
uni.navigateTo({
url: '/pages/index/registration'
});
}
}
});
},
onLoginSuccess(data) {
// 登录成功后重新检查状态
this.checkLoginStatus();
// 如果需要刷新页面
if (data && data.needRefresh) {
// 重新初始化页面数据
this.init();
}
// 检查认证状态
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;
}
// 处理不同的认证状态
if (barInfo.authState === 2) {
uni.showModal({
title: '提示',
content: '您的门店正在认证中,请耐心等待',
showCancel: true,
success: (res) => {
if (res.confirm) {
uni.navigateTo({
url: '/pages/index/registration'
});
}
}
});
return;
} else if (barInfo.authState === 1) {
// 已认证,允许操作
this.handleScan();
return;
}
// 未认证状态
uni.showModal({
title: '提示',
content: '请先认证门店',
showCancel: true,
success: (res) => {
if (res.confirm) {
uni.navigateTo({
url: '/pages/index/registration'
});
}
}
});
}
},
onUnload() {
// 页面卸载时清除定时器
if(this.timer) {
clearInterval(this.timer);
this.timer = null;
}
},
onShow() {
// 每次页面显示时检查登录状态
this.checkLoginStatus();
}
}
</script>
<style lang="scss" scoped>
.page-content {
min-height: 100vh;
color: #F9F9F9;
padding-bottom: 200rpx;
.progress-box {
position: relative;
background: #FFFFFF;
padding: 32rpx;
.title-text {
margin-bottom: 24rpx;
}
.countdown-container {
display: flex;
gap: 24rpx;
margin-bottom: 32rpx;
.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;
}
.unit {
font-size: 24rpx;
color: #67677A;
margin-left: 8rpx;
}
}
}
.progress-bar {
width: 100%;
padding-left: 8rpx;
.progress-track {
height: 16rpx;
background: rgba(227, 227, 229, 0.6);
border-radius: 12rpx;
overflow: hidden;
position: relative;
box-shadow: inset 0 2rpx 4rpx rgba(0, 0, 0, 0.05);
margin-bottom: 12rpx;
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #D42E78 0%, #19367A 100%);
border-radius: 12rpx;
transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 2rpx 4rpx rgba(212, 46, 120, 0.2);
}
}
.progress-labels {
display: flex;
justify-content: space-between;
text {
font-size: 28rpx;
font-family: Roboto;
font-weight: 500;
&.start {
color: #D42E78;
}
&.end {
color: #19367A;
}
}
}
}
}
/* 横向滚动容器样式 */
.scroll-container {
display: flex; /* 使用弹性布局 */
padding-top: 16rpx; /* 顶部内边距 */
flex-direction: row; /* 水平方向排列 */
white-space: nowrap; /* 防止内容换行 */
min-height: 505rpx; /* 最小高度确保内容显示完整 */
/* 行容器样式 */
.row-box {
&:nth-child(1) { /* 第一个子元素特殊处理 */
margin-left: 32rpx; /* 左侧外边距,与容器对齐 */
}
}
/* 啤酒卡片样式 */
.beer-box {
width: 208rpx; /* 卡片固定宽度 */
background: #FFFFFF; /* 白色背景 */
margin-right: 12rpx; /* 卡片间距 */
margin-bottom: 36rpx; /* 底部外边距 */
box-sizing: border-box; /* 盒模型计算方式 */
display: flex; /* 弹性布局 */
flex-direction: column; /* 垂直方向排列 */
justify-content: flex-start; /* 内容从顶部开始排列 */
/* 啤酒图片封面样式 */
.cover {
width: 208rpx; /* 图片宽度 */
height: 300rpx; /* 图片高度 */
border-radius: 12rpx; /* 圆角效果 */
margin-bottom: 16rpx; /* 与下方文字的间距 */
}
/* 啤酒名称样式 */
.title {
font-size: 32rpx; /* 字体大小 */
color: #030303; /* 文字颜色 */
font-weight: 600; /* 字体粗细 */
line-height: 48rpx; /* 行高 */
margin-bottom: 8rpx; /* 底部间距 */
white-space: nowrap; /* 不换行 */
overflow: hidden; /* 溢出隐藏 */
text-overflow: ellipsis; /* 文本溢出显示省略号 */
}
/* 描述文本样式 */
.desc {
font-family: Roboto; /* 字体 */
font-size: 24rpx; /* 字体大小 */
font-weight: normal; /* 字体粗细 */
line-height: 32rpx; /* 行高 */
color: #606060; /* 文字颜色 */
margin-bottom: 8rpx; /* 底部间距 */
white-space: nowrap; /* 不换行 */
overflow: hidden; /* 溢出隐藏 */
text-overflow: ellipsis; /* 文本溢出显示省略号 */
}
/* 数量/评分样式 */
.num {
display: flex; /* 弹性布局 */
align-items: center; /* 垂直居中 */
font-size: 20rpx; /* 字体大小 */
color: #606060; /* 文字颜色 */
line-height: 32rpx; /* 行高 */
image {
margin-right: 10rpx; /* 图标右侧间距 */
}
}
}
}
// 奖励盒子
.reward-box {
background: #FFFFFF;
border-radius: 30rpx;
padding: 30rpx;
margin-bottom: 36rpx;
}
.progress-bar {
width: 100%;
padding: 0 32rpx;
.progress-track {
height: 12rpx;
background: #E3E3E5;
border-radius: 12rpx;
overflow: hidden;
position: relative;
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #D42E78 0%, #19367A 100%);
border-radius: 6rpx;
transition: width 0.3s ease;
}
}
.progress-labels {
display: flex;
justify-content: space-between;
margin-top: 12rpx;
text {
font-size: 24rpx;
color: #67677A;
&.start {
color: #D42E78;
}
&.end {
color: #19367A;
}
}
}
}
.activity-info-box {
padding: 32rpx;
min-height: 360rpx; /* 设置最小高度 */
.brand-info {
display: flex;
align-items: center;
margin-bottom: 24rpx;
// padding-bottom: 24rpx;
// border-bottom: 1rpx solid #F5F5F5;
border-radius: 12rpx;
.brand-logo {
width: 88rpx;
height: 88rpx;
border-radius: 12rpx;
margin-right: 16rpx;
}
.brand-text {
flex: 1;
.initiator {
color: #979797;
font-size: 24rpx;
font-weight: normal;
margin-bottom: 16rpx;
}
}
}
.activity-duration {
color: #0B0E26;
font-size: 28rpx;
font-weight: normal;
line-height: 40rpx;
margin-bottom: 16rpx;
.duration-value {
color: #D42E78;
font-size: 32rpx;
}
}
.activity-target {
color: #0B0E26;
font-size: 28rpx;
margin-bottom: 24rpx;
}
.scroll-container {
margin-top: 16rpx;
height: 505rpx; /* 固定高度确保滚动区域统一 */
}
}
.bottom-bar {
border-radius: 12rpx;
height: 182rpx;
width: 100%;
position: fixed;
bottom: 0;
box-shadow: 0 -2rpx 8rpx rgba(0, 0, 0, 0.05);
.scan-btn {
width: 686rpx;
height: 96rpx;
background: #19367A;
border-radius: 12rpx;
color: #FFFFFF;
font-size: 32rpx;
display: flex;
align-items: center;
justify-content: center;
&.btn-disabled {
background: #E5E6EB;
color: #86909C;
}
.cuIcon-qr_code {
font-size: 36rpx;
margin-right: 16rpx;
}
}
}
.section-title {
font-size: 32rpx;
font-weight: 600;
color: #0B0E26;
line-height: 48rpx;
}
}
</style>