Compare commits

..

No commits in common. "b0a688df3658a4eb29b15d3848bdbde5b72bb753" and "740eea26839095b3e017b95533e2b7da77015c6c" have entirely different histories.

10 changed files with 1624 additions and 2314 deletions

View File

@ -1,12 +1,11 @@
<template>
<view class="activity-item flex" v-if="isActivityVisible" @click="handleClick">
<view class="store-tag" v-if="item.activityType === 1">指定门店</view>
<view class="left">
<image :src="item.brandLogo" class="brand-logo"></image>
<image :src="item.brandLogo"></image>
<view class="status-content">
<text v-if="activityState === 'recruiting' && item.remainingDays > 0" class="status-label">招募剩余天数</text>
<text v-else-if="activityState === 'recruiting' && item.remainingDays <= 0" class="status-label">活动招募已结束</text>
<text v-else-if="activityState === 'inProgress'" class="status-label">距离达成还剩</text>
<text v-if="activityState === 'recruiting' && item.remainingDays > 0">招募剩余天数</text>
<text v-else-if="activityState === 'recruiting' && item.remainingDays <= 0">活动招募已结束</text>
<text v-else-if="activityState === 'inProgress'">距离达成还剩</text>
<view class="status-box">
<template v-if="activityState === 'recruiting' && item.remainingDays > 0">
<view class="days-left">
@ -22,8 +21,8 @@
</template>
<template v-else-if="activityState === 'inProgress'">
<view v-if="item.remainingBeerCount > 0" class="beer-count">
<text class="count-num">{{item.remainingBeerCount}}</text>
<text class="count-unit"></text>
<text>{{item.remainingBeerCount}}</text>
<text></text>
</view>
</template>
<template v-else-if="activityState === 'completed'">
@ -36,17 +35,18 @@
</view>
<view class="right">
<view class="title">{{ item.breweryName }}</view>
<view class="sub">时间首次扫码开始累计 <text class="highlight">{{item.duration}}天内</text></view>
<view class="sub">目标{{item.beerScope === 0 ? '品牌全系' : '以下'}}酒款累积扫码 <text class="highlight">{{ item.activityTarget}}</text></view>
<scroll-view v-if="item.beers" scroll-x="true" class="scroll-img" enhanced show-scrollbar="false">
<view class="sub">时间首次扫码开始累计 <text style="color:#DE3C4B">{{item.duration}}天内</text></view>
<view class="sub">目标全系列酒款累积扫码 {{ item.activityTarget}}</view>
<scroll-view v-if="item.beers" scroll-x="true" class="scroll-img">
<view class="beer-box" v-for="(beer, beerIndex) in item.beers" :key="beerIndex" @click.stop="handleReview(beer)">
<image v-if="beer.cover" :src="beer.cover" class="cover" mode="aspectFit"></image>
<image v-if="beer.cover" :src="beer.cover" class="cover"></image>
</view>
</scroll-view>
<view class="reward-info">
<view class="flex align-center">
<text v-if="item.activityRewardType == 2 || (item.activityRewardType == 1 && item.activityRewardGoods)" class="zeng"></text>
<text v-if="item.activityRewardType == 1 && item.activityRewardGoods" class="reward-text">{{item.activityRewardGoods.goodsName}} {{item.activityRewardGoods.specs}} 奖励数量{{item.activityRewardCount}}</text>
<text v-if="item.activityRewardType == 2" class="reward-text">品牌啤酒币 奖励数量 {{item.activityRewardCount}}</text>
<text v-if="item.activityRewardType == 1 && item.activityRewardGoods" style="color: #0B0E26;font-size: 24rpx;">{{item.activityRewardGoods.goodsName}} * {{item.activityRewardCount}}</text>
<text v-if="item.activityRewardType == 2" style="color: #0B0E26;font-size: 24rpx;">啤酒币 * {{item.activityRewardCount}}</text>
<text v-if="item.barAwardStatus" style="color: #0B0E26;font-size: 24rpx;">已发放</text>
</view>
</view>
</view>
@ -117,56 +117,34 @@ export default {
position: relative;
width: 702rpx;
margin-bottom: 48rpx;
transition: transform 0.2s ease;
&:active {
transform: scale(0.98);
}
.store-tag {
position: absolute;
top: 24rpx;
right: 24rpx;
font-size: 20rpx;
color: #FFFFFF;
background-color: #D42E78;
padding: 6rpx 16rpx;
border-radius: 8rpx;
z-index: 2;
box-shadow: 0 2rpx 6rpx rgba(212, 46, 120, 0.3);
}
// &:last-child {
// margin-bottom: 0;
// }
.right {
width: 702rpx;
min-height: 428rpx;
background: #FFFFFF;
border-radius: 20rpx;
padding: 32rpx 32rpx 32rpx 200rpx;
padding: 24rpx 24rpx 24rpx 200rpx;
box-sizing: border-box;
box-shadow: 0 8rpx 16rpx rgba(0, 0, 0, 0.08);
position: relative;
z-index: 1;
border: 1px solid #EFEDE9;
box-shadow: 0rpx 1rpx 3rpx 0rpx rgba(0, 0, 0, 0.1);
.title {
font-size: 36rpx;
font-size: 32rpx;
font-weight: bold;
line-height: 48rpx;
color: #0B0E26;
margin-bottom: 20rpx;
margin-bottom: 16rpx;
}
.sub {
font-size: 28rpx;
font-weight: 500;
line-height: 44rpx;
line-height: 40rpx;
color: #0B0E26;
margin-bottom: 20rpx;
.highlight {
color: #D42E78;
font-weight: bold;
}
margin-bottom: 16rpx;
}
.scroll-img {
@ -174,62 +152,35 @@ export default {
display: flex;
flex-direction: row;
white-space: nowrap;
height: 200rpx;
margin-bottom: 24rpx;
height: 144rpx;
margin-bottom: 18rpx;
overflow-x: auto;
padding: 4rpx 0;
.beer-box {
width: 120rpx;
height: 170rpx;
width: 100rpx;
background: #FFFFFF;
margin-right: 8rpx;
margin-right: 20rpx;
box-sizing: border-box;
display: inline-flex;
align-items: center;
justify-content: center;
transition: transform 0.2s;
border-radius: 12rpx;
overflow: hidden;
position: relative;
&:active {
transform: scale(0.95);
}
display: inline-block;
.cover {
width: 100%;
height: 100%;
border-radius: 12rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
object-fit: contain;
width: 100rpx;
height: 144rpx;
border-radius: 10rpx;
}
}
}
.reward-info {
display: flex;
align-items: center;
margin-top: 8rpx;
}
.zeng {
font-size: 22rpx;
font-size: 20rpx;
font-weight: bold;
line-height: normal;
text-align: center;
color: #0B0E26;
padding: 8rpx 16rpx;
padding: 8rpx 12rpx;
border-radius: 10rpx;
background: #FEE034;
margin-right: 20rpx;
box-shadow: 0 2rpx 4rpx rgba(254, 224, 52, 0.3);
}
.reward-text {
color: #0B0E26;
font-size: 26rpx;
line-height: 36rpx;
}
}
@ -244,18 +195,17 @@ export default {
border-radius: 20rpx;
box-sizing: border-box;
border: 1px solid #EFEDE9;
z-index: 3;
z-index: 1;
box-shadow: 0rpx 1rpx 3rpx 0rpx rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
align-items: center;
.brand-logo {
image {
width: 132rpx;
height: 132rpx;
border-radius: 6rpx;
margin-bottom: 40rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
.status-content {
@ -263,7 +213,7 @@ export default {
flex-direction: column;
align-items: center;
.status-label {
text {
color: #606060;
font-size: 24rpx;
font-weight: bold;
@ -294,26 +244,18 @@ export default {
}
.beer-count {
display: flex;
align-items: baseline;
justify-content: center;
.count-num {
font-size: 48rpx;
color: #19367A;
font-weight: bold;
line-height: 1;
}
.count-unit {
text {
font-size: 24rpx;
color: #606060;
margin-left: 4rpx;
color: #0B0E26;
&:last-child {
margin-left: 4rpx;
}
}
}
.status-text {
font-size: 28rpx;
font-size: 24rpx;
color: #19367A;
font-weight: bold;

View File

@ -198,17 +198,15 @@
"style" :
{
"navigationBarTitleText" : "活动详情",
"navigationBarBackgroundColor": "#19367A",
"navigationBarTextStyle": "white"
"navigationBarBackgroundColor": "#FFFFFF",
"navigationBarTextStyle": "black"
}
},
{
"path" : "myActivityDetail",
"style" :
{
"navigationBarTitleText" : "我的活动",
"navigationBarBackgroundColor": "#19367A",
"navigationBarTextStyle": "white"
"navigationBarTitleText" : "我的活动"
}
},
{

View File

@ -69,13 +69,67 @@
style="padding: 16rpx 28rpx 0;background-color: #F2F2F2;overflow-y: auto;flex:1"
@scrolltolower="changePage">
<!-- 活动列表项 -->
<activity-item
v-for="(it, index) in myJoinList"
:key="index"
:item="it"
@click="toInfo(it)"
@review="toReview"
></activity-item>
<view class="activity-item flex" v-for="(it, index) in myJoinList" :key="index" @click="toInfo(it)">
<!-- 左侧区域品牌logo和达成进度 -->
<view class="left flex flex-col justify-between align-center">
<!-- 品牌logo固定尺寸140x140 -->
<image :src="it.brandLogo" style="width: 140rpx;height: 140rpx;border-radius: 6rpx;">
</image>
<!-- 达成进度提示文字 -->
<view style="color: #606060;font-size: 24rpx;font-weight: bold;white-space: nowrap;">距离达成还剩</view>
<!-- 剩余数量/达标状态根据不同状态显示不同内容 -->
<view>
<!-- 累计中remainingBeerCount > 0 -->
<template v-if="it.remainingBeerCount > 0">
<text style="font-family: Roboto;font-size: 24rpx;font-weight: normal;line-height: 16rpx; text-align: center;letter-spacing: normal;color: #0B0E26;">{{ it.remainingBeerCount }}</text>
<text style="font-size: 24rpx;color: #0B0E26;"></text>
</template>
<!-- 待兑付remainingBeerCount <= 0 且未兑付 -->
<template v-else-if="it.remainingBeerCount <= 0 && !it.barAwardStatus">
<text style="font-family: Roboto;font-size: 24rpx; color: #19367A;font-weight: bold;line-height: 130%;text-align: center;letter-spacing: normal;">待兑付</text>
</template>
<!-- 已兑付厂家已兑付状态 -->
<template v-else-if="it.barAwardStatus">
<text style="font-family: Roboto;font-size: 24rpx; color: #19367A;font-weight: bold;line-height: 130%;text-align: center;letter-spacing: normal;">已兑付</text>
</template>
<!-- 已完成活动已完成状态 -->
<template v-else-if="it.activityStatus === 'COMPLETED'">
<text style="font-family: Roboto;font-size: 24rpx; color: #19367A;font-weight: bold;line-height: 130%;text-align: center;letter-spacing: normal;">已完成</text>
</template>
</view>
</view>
<!-- 右侧区域活动详细信息 -->
<view class="right">
<!-- 活动名称 -->
<view class="title">{{ it.activityName }}</view>
<!-- 活动时间首次扫码开始计算 -->
<view class="sub">时间首次扫码开始累计 <text style="color:#19367A">{{it.duration}}天内</text></view>
<!-- 活动目标累积扫码数量 -->
<view class="sub">目标全系列酒款累积扫码 {{ it.activityTarget}}</view>
<!-- 酒款图片横向滚动展示 -->
<scroll-view v-if="it.beers" scroll-x="true" class="scroll-img">
<view class="beer-box" v-for="(it, index) in it.beers" :key="index"
@click="toReview(it)">
<image v-if="it.cover" :src="it.cover" class="cover"></image>
</view>
</scroll-view>
<!-- 活动奖励信息实物或啤酒币 -->
<view class="flex align-center">
<text class="zeng"></text>
<!-- 实物奖励type=1 -->
<text v-if="it.activityRewardType == 1 && it.activityRewardGoods"
style="color: #0B0E26;font-size: 24rpx;">{{it.activityRewardGoods.goodsName}} * {{it.activityRewardCount}}</text>
<!-- 啤酒币奖励type=2 -->
<text v-if="it.activityRewardType == 2"
style="color: #0B0E26;font-size: 24rpx;">啤酒币 * {{it.activityRewardCount}}</text>
<!-- 奖励发放状态 -->
<text v-if="it.barAwardStatus"
style="color: #0B0E26;font-size: 24rpx;">已发放</text>
</view>
</view>
</view>
<!-- 列表加载状态 -->
<view class="cu-load" :class="'over'"></view>
</scroll-view>
@ -118,42 +172,38 @@
<script>
import loginPopup from '@/components/loginPopup.vue';
import ActivityItem from '@/components/ActivityItem.vue'
import { myJoinListApi, getMyExchangeOrder } from "@/api/user.js"
import { getBarInfo } from "@/api/bar.js"
export default {
components: {
loginPopup,
ActivityItem
loginPopup
},
data() {
return {
loading: true,
userInfo: null,
barInfo: null,
isLoggedIn: false,
isVerified: false,
tabCur: 0,
curTag: 0,
curCoinTag: 0,
myJoinList: [],
userInfo: null, //
barInfo: null, //
isLoggedIn: false, //
isVerified: false, //
tabCur: 0, //
curTag: 0, //
curCoinTag: 0, //
myJoinList: [], //
queryForm: {
status: 1,
status: 1, //
pageNum: 1,
pageSize: 10
},
total: 0,
myExchangeOrder:[],
total: 0, //
myExchangeOrder:[], //
orderQuery: {
pageNum: 1,
pageSize: 10
},
orderTotal: 0,
isLoading: false,
isRefreshing: false,
hasNetwork: true,
lastRefreshTime: 0,
refreshDebounceTime: 1000,
orderTotal: 0, //
isLoading: false, //
isRefreshing: false, //
hasNetwork: true, //
};
},
computed: {
@ -251,6 +301,29 @@
console.log('【getBarInfoFun】当前userStatus:', this.userStatus)
},
//
async initData() {
try {
this.loading = true
const isLoggedIn = await this.checkLoginStatus()
if (isLoggedIn) {
await this.getMyJoinList()
}
} catch (error) {
console.error('初始化数据失败:', error)
//
if (this.userStatus === 'verified') {
uni.showToast({
title: '加载失败',
icon: 'none'
})
}
} finally {
this.loading = false
}
},
//
async checkUserInfo() {
try {
@ -265,10 +338,7 @@
const isLoggedIn = await this.checkLoginStatus()
if (isLoggedIn) {
//
if (this.shouldRefreshData()) {
await this.getMyJoinList()
}
await this.getMyJoinList()
} else {
this.myJoinList = []
this.myExchangeOrder = []
@ -287,16 +357,22 @@
console.log('【loginSuccess】开始处理登录成功')
this.loading = true
// tokenopenId
const token = uni.getStorageSync('token')
const openId = uni.getStorageSync('openId')
console.log('【loginSuccess】获取到的token:', token)
console.log('【loginSuccess】获取到的openId:', openId)
if (token && openId) {
//
this.isLoggedIn = true
this.userInfo = {
token,
openId
}
console.log('【loginSuccess】更新登录状态:', this.isLoggedIn)
//
await this.getBarInfoFun()
//
@ -305,11 +381,12 @@
this.myJoinList = []
this.myExchangeOrder = []
//
if (this.userStatus === 'verified' && this.shouldRefreshData()) {
//
if (this.userStatus === 'verified') {
await this.getMyJoinList()
}
} else {
console.log('【loginSuccess】token或openId不存在')
this.isLoggedIn = false
this.userInfo = null
this.barInfo = null
@ -317,6 +394,7 @@
}
} catch (error) {
console.error('【loginSuccess】登录成功处理失败:', error)
//
if (this.userStatus === 'verified') {
uni.showToast({
title: '登录失败',
@ -328,16 +406,6 @@
}
},
//
shouldRefreshData() {
const now = Date.now()
if (now - this.lastRefreshTime < this.refreshDebounceTime) {
return false
}
this.lastRefreshTime = now
return true
},
//
async getMyExchangeOrderFun() {
//
@ -382,17 +450,11 @@
if (this.tabCur == 0) {
this.myJoinList = []
this.queryForm.pageNum = 1
//
if (this.shouldRefreshData()) {
await this.getMyJoinList()
}
await this.getMyJoinList()
} else {
this.myExchangeOrder = []
this.orderQuery.pageNum = 1
//
if (this.shouldRefreshData()) {
await this.getMyExchangeOrderFun()
}
await this.getMyExchangeOrderFun()
}
},
@ -493,26 +555,23 @@
//
switch(key) {
case 0:
this.queryForm.status = 1
case 0: //
this.queryForm.status = 1 // 使
break
case 1:
this.queryForm.status = 2
case 1: //
this.queryForm.status = 2 // 使
break
case 2:
this.queryForm.status = 3
case 2: //
this.queryForm.status = 3 // 使
break
case 3:
this.queryForm.status = 4
case 3: //
this.queryForm.status = 4 // 使
break
default:
delete this.queryForm.status
}
//
if (this.shouldRefreshData()) {
await this.getMyJoinList()
}
await this.getMyJoinList()
},
//
@ -526,23 +585,20 @@
//
switch(key) {
case 0:
delete this.orderQuery.status
delete this.orderQuery.status //
break
case 1:
this.orderQuery.status = 1
this.orderQuery.status = 1 //
break
case 2:
this.orderQuery.status = 2
this.orderQuery.status = 2 //
break
case 3:
this.orderQuery.status = 3
this.orderQuery.status = 3 //
break
}
//
if (this.shouldRefreshData()) {
await this.getMyExchangeOrderFun()
}
await this.getMyExchangeOrderFun()
},
//
@ -863,5 +919,3 @@
-webkit-box-orient: vertical;
}
</style>
}
</style>

View File

@ -200,7 +200,7 @@
</view>
<!-- 组件 -->
<loginPopup ref="loginRef" @loginSuccess="loginSuccess"></loginPopup>
<loginPopup ref="loginRef"></loginPopup>
<createPoster v-if="showShare" ref="createPosterRef" :url="tempUrl" @close="showShare=false"></createPoster>
<canvas type="2d" id="myCanvas" style="width: 360px;height: 570px;position: fixed;left:8888px"></canvas>
</view>
@ -249,8 +249,7 @@
isAnimating: false,
lastRefreshTime: 0,
refreshDebounceTime: 1000,
loading: false,
needRefresh: false //
loading: false
};
},
onLoad({beerId}) {
@ -266,32 +265,23 @@
//
checkLoginStatus() {
const token = uni.getStorageSync('token')
const wasLoggedIn = this.isLoggedIn
this.isLoggedIn = !!token
if (this.isLoggedIn) {
const barInfo = uni.getStorageSync('barInfo')
this.isBarAuthenticated = barInfo && barInfo.authState === 1 // 1
//
if (!wasLoggedIn) {
this.needRefresh = true
}
} else {
this.isBarAuthenticated = false
}
},
//
async initPageData() {
const now = Date.now()
if (now - this.lastRefreshTime < this.refreshDebounceTime && !this.needRefresh) {
if (now - this.lastRefreshTime < this.refreshDebounceTime) {
return
}
this.lastRefreshTime = now
this.loading = true
this.needRefresh = false //
try {
//
@ -770,42 +760,6 @@
}
this.currentTab = tab;
},
//
async loginSuccess() {
console.log('登录成功,开始刷新页面数据')
//
this.needRefresh = true
this.loading = true
try {
//
await this.checkLoginStatus()
//
if (this.isLoggedIn) {
//
this.reviewList = []
this.queryForm.pageNum = 1
//
await this.initPageData()
//
uni.showToast({
title: '登录成功',
icon: 'success'
})
}
} catch (error) {
console.error('登录后刷新数据失败:', error)
uni.showToast({
title: '刷新数据失败',
icon: 'none'
})
} finally {
this.loading = false
}
}
}
}

View File

@ -1,137 +1,132 @@
<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">
累计天数还剩
<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">
<template v-if="activityInfo.hasJoined">
累计天数还剩
</template>
<template v-else>
<template v-if="activityInfo.remainingDays <= 0">
活动招募已结束
</template>
<template v-else>
<template v-if="activityInfo.remainingDays <= 0">
活动招募已结束
</template>
<template v-else>
活动招募即将结束
</template>
活动招募即将结束
</template>
</view>
</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 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 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 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>
</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>
</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>
</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 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 || (!activityInfo.hasJoined && activityInfo.remainingDays <= 0)
}"
@click="activityInfo.remainingDays <= 0 && !activityInfo.hasJoined ? null : handleButtonClick">
<text class="cuIcon-qr_code margin-right-xs"></text>
扫桶标自动开始累计
</view>
</view>
</view>
</template>
@ -365,8 +360,14 @@
this.isLoggedIn = !!token;
this.isVerified = barInfo && barInfo.authState === 1;
},
//
checkAuthAndHandle() {
handleButtonClick() {
const token = uni.getStorageSync('token');
if (!token) {
this.$refs.loginPopup.open();
return;
}
//
const barInfo = uni.getStorageSync('barInfo');
if (!barInfo) {
uni.showModal({
@ -381,9 +382,10 @@
}
}
});
return false;
return;
}
//
if (barInfo.authState === 2) {
uni.showModal({
title: '提示',
@ -397,11 +399,14 @@
}
}
});
return false;
return;
} else if (barInfo.authState === 1) {
return true;
//
this.handleScan();
return;
}
//
uni.showModal({
title: '提示',
content: '请先认证门店',
@ -414,18 +419,6 @@
}
}
});
return false;
},
handleButtonClick() {
const token = uni.getStorageSync('token');
if (!token) {
this.$refs.loginPopup.open();
return;
}
if (this.checkAuthAndHandle()) {
this.handleScan();
}
},
onLoginSuccess(data) {
//
@ -437,32 +430,57 @@
this.init();
}
if (this.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;
}
//
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();
}
},
//
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
//
uni.showModal({
title: '提示',
content: '请先认证门店',
showCancel: true,
success: (res) => {
if (res.confirm) {
uni.navigateTo({
url: '/pages/index/registration'
});
}
}
});
}
},
@ -481,323 +499,301 @@
</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));
}
min-height: 100vh;
color: #F9F9F9;
padding-bottom: 200rpx;
.section {
background: #FFFFFF;
border-radius: 16rpx;
margin-bottom: 24rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
overflow: hidden;
.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;
padding: 32rpx;
color: #333333;
position: relative;
&::after {
content: '';
position: absolute;
left: 32rpx;
bottom: 0;
width: 48rpx;
height: 4rpx;
background: #19367A;
border-radius: 2rpx;
}
color: #0B0E26;
line-height: 48rpx;
}
}
.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>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 14 KiB

BIN
static/tick-circle@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB