zdtap-uniapp-main/pagesActivity/myActivityDetail.vue

938 lines
21 KiB
Vue
Raw Normal View History

<template>
<view class="page">
<view class="page-content">
<!-- 进度展示区域 -->
<view class="section">
<view class="section-title">累计进度</view>
<view class="progress-content">
<!-- 状态提示信息 -->
<view class="status-tip" v-if="activity.activityStatus >= 2">
<text class="tip-text" v-if="activity.activityStatus == 2">累计目标已完成请耐心等待品牌方兑付奖励</text>
<text class="tip-text" v-if="activity.activityStatus == 3">品牌方已发起兑付奖励快递单号{{activity.expressNo || '暂无'}}请注意查收</text>
<text class="tip-text" v-if="activity.activityStatus == 4">累计活动已结束兑付时间{{activity.claimTime || '-'}}</text>
</view>
<!-- 剩余目标显示 -->
<view class="target-display" v-if="activity.activityStatus < 2">
<text class="target-label">剩余累计目标</text>
<view class="target-number">
<text class="number">{{activity.remainingBeerCount || 0}}</text>
<text class="unit"></text>
</view>
</view>
<!-- 倒计时区域 -->
<view class="countdown-container" v-if="activity.activityStatus < 2">
<view class="countdown-item">
<text class="number">{{remainingDays}}</text>
<text class="unit"></text>
</view>
<view class="countdown-item">
<text class="number">{{remainingHours}}</text>
<text class="unit"></text>
</view>
<view class="countdown-item">
<text class="number">{{remainingMinutes}}</text>
<text class="unit"></text>
</view>
<view class="countdown-item">
<text class="number">{{remainingSeconds}}</text>
<text class="unit"></text>
</view>
</view>
<!-- 进度条区域 -->
<view class="progress-bar-section">
<view class="progress-track">
<view class="progress-fill" :style="{ width: processNum + '%' }">
<view class="progress-marker">
<text>{{processNum}}%</text>
</view>
</view>
</view>
<view class="progress-labels">
<text class="start">0</text>
<text class="end">{{activity.activityTarget}}</text>
</view>
</view>
</view>
</view>
<!-- 切换标签 -->
<view class="tab-box">
<view class="tab-item" :class="{'active':currentTab == 1}" @click="changeTab(1)">
<text>活动信息</text>
</view>
<view class="tab-item" :class="{'active':currentTab == 2}" @click="changeTab(2)">
<text>累积记录</text>
</view>
</view>
<!-- 活动信息区域 -->
<view class="section" v-if="currentTab == 1">
<view class="info-list">
<view class="info-item" @click="toBrand" v-if="activity.beers">
<text>活动发起方</text>
<view class="right-content">
<image :src="activity.beers[0].breweryLogo" class="brand-logo"></image>
<text class="info-text">{{ activity.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">{{activity.duration || '-'}} 天内</text></text>
</view>
</view>
<view class="info-item">
<text>活动目标</text>
<view class="right-content">
<text class="info-text">{{ activity.beerScope === 0 ? '品牌全系列酒款' : '以下酒款' }}累计扫码 <text class="highlight">{{activity.activityTarget || '-'}} </text></text>
</view>
</view>
</view>
<view class="beer-section">
<view class="section-subtitle">参与酒款</view>
<scroll-view scroll-x="true" class="beer-scroll">
<view class="beer-list">
<view v-for="(it, index) in activity.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>
<!-- 累积记录区域 -->
<view class="section" v-if="currentTab == 2">
<view class="info-list">
<view v-if="logs && logs.length > 0">
<view class="info-item" v-for="(it, index) in logs" :key="index">
<view class="log-info">
<text class="time">{{ it.date}}</text>
<text class="operation">{{ it.operation}}</text>
</view>
<text class="percent" v-if="it.percent != null">{{it.percent || 0}}%</text>
</view>
</view>
<view v-else class="empty-state">
<text>暂无记录</text>
</view>
</view>
</view>
<!-- 奖励展示区域 -->
<view class="section" v-if="activity.activityRewardType">
<view class="section-title">挑战奖励</view>
<view class="info-item reward-content" v-if="activity.activityRewardType == 1">
<image :src="activity.activityRewardGoods.goodsCover" class="goods-image" mode="aspectFit"></image>
<view class="goods-info">
<text class="name">{{activity.activityRewardGoods.goodsName}}</text>
<view class="specs-info">
<text class="specs-label">规格</text>
<text class="specs-value">{{activity.activityRewardGoods.specs}}</text>
</view>
<text class="count">奖励数量{{activity.activityRewardCount}}</text>
</view>
</view>
<view class="info-item reward-content" v-if="activity.activityRewardType == 2">
<view class="coin-container">
<image :src="activity.brandLogo" class="coin-logo" mode="aspectFit"></image>
</view>
<view class="coin-info">
<text class="name">{{activity.breweryName}}品牌啤酒币</text>
<text class="count">奖励数量 {{ activity.activityRewardCount }}</text>
</view>
</view>
</view>
<!-- 底部操作栏 -->
<view class="bottom-bar">
<view class="status-info">
<text class="label">活动状态</text>
<text class="status" :class="{
'status-ongoing': activity.activityStatus == 1,
'status-pending': activity.activityStatus == 2 || activity.activityStatus == 3,
'status-completed': activity.activityStatus == 4
}">
{{activity.activityStatus == 0 ? '未参与' :
activity.activityStatus == 1 ? '累计中' :
activity.activityStatus == 2 ? '待兑付' :
activity.activityStatus == 3 ? '待确认' : '已完成'}}
</text>
</view>
<view class="action-button"
:class="{
'scan-btn': activity.activityStatus == 1,
'confirm-btn': activity.activityStatus == 3,
'completed-btn': activity.activityStatus == 2 || activity.activityStatus == 4
}"
@click="activity.activityStatus == 1 ? toScan() :
activity.activityStatus == 3 ? confirmPay() : null">
<text class="cuIcon-qr_code" v-if="activity.activityStatus == 1"></text>
<text>{{activity.activityStatus == 0 ? '未参与' :
activity.activityStatus == 1 ? '扫码累计' :
activity.activityStatus == 2 ? '累计目标已完成' :
activity.activityStatus == 3 ? '确认兑付完成' : '已完成'}}</text>
</view>
</view>
</view>
</view>
</template>
<script>
import {
myJoinDetailApi,
confirmPayApi
} from '@/api/user.js'
import process from './components/progress.vue';
import rowBeer from '@/components/rowBeer.vue'
export default {
components: {
process,
rowBeer
},
data() {
return {
statusBaeHeight: 40,
batchId: '',
activity: {},
logs:[], // 累积记录
currentTab: 1,
reward: null,
remainingDays: 0,
remainingHours: 0,
remainingMinutes: 0,
remainingSeconds: 0,
timer: null
};
},
onLoad({
batchId
}) {
const sysInfo = uni.getSystemInfoSync()
this.statusBaeHeight = sysInfo.statusBarHeight
this.batchId = batchId
this.init()
},
onUnload() {
if(this.timer) {
clearInterval(this.timer)
}
},
computed: {
processNum() {
if(this.activity.remainingBeerCount != null && this.activity.activityTarget) {
return ((this.activity.activityTarget - this.activity.remainingBeerCount) / this.activity.activityTarget * 100).toFixed(0)
}else {
return 0
}
}
},
methods: {
init() {
myJoinDetailApi(this.batchId).then(res => {
this.activity = res.data.activity
this.reward = res.data.reward
this.logs = res.data.logs
// 计算结束时间
if(this.activity.activityType === 2 && this.logs && this.logs.length > 0) {
const startDate = new Date(this.logs[0].date)
const endDate = new Date(startDate.getTime() + this.activity.duration * 24 * 60 * 60 * 1000)
this.startCountdown(endDate)
}
})
},
startCountdown(endDate) {
const updateCountdown = () => {
const now = new Date()
const diff = endDate.getTime() - now.getTime()
if(diff <= 0) {
this.remainingDays = 0
this.remainingHours = 0
this.remainingMinutes = 0
this.remainingSeconds = 0
clearInterval(this.timer)
return
}
this.remainingDays = Math.floor(diff / (1000 * 60 * 60 * 24))
this.remainingHours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))
this.remainingMinutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60))
this.remainingSeconds = Math.floor((diff % (1000 * 60)) / 1000)
}
updateCountdown()
this.timer = setInterval(updateCountdown, 1000) // 每秒更新一次
},
changeTab(index) {
this.currentTab = index
},
// 跳转 品牌方
toBrand() {
uni.navigateTo({
url: '/pages/index/brandHome?breweryId=' + this.activity.breweryId
})
},
// 计算剩余天数
getRemainingDays(date) {
const targetDate = new Date(date);
const currentDate = new Date();
const timeDiff = targetDate.getTime() - currentDate.getTime();
const remainingDays = Math.ceil(timeDiff / (1000 * 3600 * 24));
return remainingDays;
},
handleScan() {
uni.scanCode({
success: (res) => {
console.log(res)
uni.navigateTo({
url: '/pagesActivity/scanResult?result=' + res.result
})
}
})
},
// 确认兑付
confirmPay() {
uni.showModal({
title: '提示',
content: '确认兑付完成?',
success: (res) => {
if (res.confirm) {
confirmPayApi(this.reward.id).then(res => {
uni.showToast({
title: '确认成功',
icon: 'success',
})
// 更新活动状态为4已完成
this.activity.activityStatus = 4
})
}
},
})
},
toScan() {
uni.switchTab({
url: '/pages/index/scan'
})
},
// 跳转到酒款详情
toBeerDetail(beer) {
if (!beer || !beer.id) {
uni.showToast({
title: '酒款信息不完整',
icon: 'none'
});
return;
}
const beerId = parseInt(beer.id);
if (isNaN(beerId)) {
uni.showToast({
title: '酒款ID无效',
icon: 'none'
});
return;
}
uni.navigateTo({
url: '/pages/index/review?beerId=' + beerId
});
}
}
}
</script>
<style lang="scss" scoped>
.page {
min-height: 100vh;
background: #F7F7F7;
.page-content {
padding: 24rpx 32rpx;
padding-bottom: calc(180rpx + 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;
}
}
.section-subtitle {
font-size: 28rpx;
font-weight: 600;
padding: 24rpx 32rpx 16rpx;
color: #333333;
}
}
.progress-content {
padding: 32rpx;
}
.status-tip {
text-align: center;
margin-bottom: 32rpx;
padding: 24rpx;
background: rgba(25, 54, 122, 0.05);
border-radius: 12rpx;
.tip-text {
font-size: 28rpx;
color: #19367A;
line-height: 1.5;
display: block;
}
}
.target-display {
text-align: center;
margin-bottom: 32rpx;
.target-label {
font-size: 28rpx;
color: #333333;
margin-bottom: 16rpx;
display: block;
}
.target-number {
display: flex;
align-items: baseline;
justify-content: center;
.number {
font-size: 64rpx;
color: #D42E78;
font-weight: bold;
font-family: 'DIN';
line-height: 1;
}
.unit {
font-size: 32rpx;
color: #333333;
margin-left: 8rpx;
font-weight: 500;
}
}
}
.countdown-container {
display: flex;
gap: 24rpx;
justify-content: center;
margin-bottom: 64rpx;
.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;
}
}
}
.progress-bar-section {
.progress-track {
height: 16rpx;
background: rgba(227, 227, 229, 0.6);
border-radius: 8rpx;
position: relative;
margin-bottom: 16rpx;
box-shadow: inset 0 2rpx 4rpx rgba(0, 0, 0, 0.05);
overflow: hidden;
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #D42E78 0%, #19367A 100%);
border-radius: 8rpx;
position: relative;
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 2rpx 4rpx rgba(212, 46, 120, 0.2);
min-width: 40rpx;
.progress-marker {
position: absolute;
right: -2rpx;
top: -28rpx;
transform: translateX(50%);
background: #D42E78;
padding: 4rpx 16rpx;
border-radius: 12rpx;
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 2rpx 8rpx rgba(212, 46, 120, 0.2);
text {
font-size: 24rpx;
color: #FFFFFF;
font-weight: 600;
}
&::after {
content: '';
position: absolute;
bottom: -6rpx;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
border-left: 6rpx solid transparent;
border-right: 6rpx solid transparent;
border-top: 6rpx solid #D42E78;
}
}
}
}
.progress-labels {
display: flex;
justify-content: space-between;
padding: 0 8rpx;
text {
font-size: 28rpx;
font-family: Roboto;
font-weight: 500;
&.start {
color: #D42E78;
}
&.end {
color: #19367A;
}
}
}
}
.tab-box {
display: flex;
background: #FFFFFF;
margin-bottom: 24rpx;
padding: 8rpx 20rpx;
border-radius: 16rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
.tab-item {
flex: 1;
text-align: center;
font-size: 28rpx;
color: #666666;
padding: 24rpx 0;
position: relative;
transition: all 0.3s ease;
&.active {
color: #19367A;
font-weight: 600;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 48rpx;
height: 4rpx;
background: #19367A;
border-radius: 2rpx;
transition: all 0.3s ease;
}
}
}
}
.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;
&:active {
background: rgba(25, 54, 122, 0.05);
}
&: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;
}
}
.log-info {
.time {
font-size: 28rpx;
color: #606060;
margin-bottom: 8rpx;
display: block;
}
.operation {
font-size: 32rpx;
color: #19367A;
font-weight: 500;
}
}
.percent {
font-size: 32rpx;
color: #19367A;
font-weight: 500;
}
}
}
.beer-section {
padding: 0 0 32rpx;
}
.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: 100%;
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;
}
}
}
}
}
}
.empty-state {
padding: 100rpx 0;
text-align: center;
color: #A5A7B9;
font-size: 28rpx;
}
.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: 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;
.status-info {
.label {
font-size: 28rpx;
color: #606060;
margin-left: 24rpx;
margin-bottom: 8rpx;
display: block;
}
.status {
font-size: 32rpx;
font-weight: 500;
padding: 8rpx 24rpx;
border-radius: 8rpx;
background: rgba(0, 0, 0, 0.03);
&.status-ongoing {
color: #FDCA40;
background: rgba(253, 202, 64, 0.1);
}
&.status-pending {
color: #19367A;
background: rgba(25, 54, 122, 0.1);
}
&.status-completed {
color: #D42E78;
background: rgba(212, 46, 120, 0.1);
}
}
}
.action-button {
width: 434rpx;
height: 96rpx;
border-radius: 48rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 12rpx;
font-size: 32rpx;
color: #FFFFFF;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
margin-left: 24rpx;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(255,255,255,0.1), rgba(255,255,255,0));
opacity: 0;
transition: opacity 0.3s ease;
}
&:active::before {
opacity: 1;
}
.cuIcon-qr_code {
font-size: 40rpx;
}
&.scan-btn,
&.confirm-btn {
background: linear-gradient(135deg, #19367A, #2C4C99);
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);
}
}
&.completed-btn {
background: #EFEFEF;
color: #333333;
box-shadow: none;
&:active {
background: #E5E5E5;
}
}
}
}
}
</style>