zdtap-uniapp-main/pagesActivity/myActivityDetail.vue

926 lines
21 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">
<view class="page-content">
<!-- 进度展示区域 -->
<view class="section">
<view class="section-title">
<text>累计进度</text>
<text class="remaining-days-tag" v-if="activity.activityStatus < 2">剩余天数:{{remainingDays}}天</text>
</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">
<view class="target-info">
<text class="target-label">剩余累计目标</text>
<view class="target-number">
<text class="number">{{activity.remainingBeerCount || 0}}</text>
<text class="unit">桶</text>
</view>
</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,
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
clearInterval(this.timer)
return
}
this.remainingDays = Math.ceil(diff / (1000 * 60 * 60 * 24))
}
updateCountdown()
this.timer = setInterval(updateCountdown, 1000 * 60 * 60) // 每小时更新一次
},
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;
display: flex;
align-items: center;
justify-content: space-between;
&::after {
content: '';
position: absolute;
left: 32rpx;
bottom: 0;
width: 48rpx;
height: 4rpx;
background: #19367A;
border-radius: 2rpx;
}
.remaining-days-tag {
font-size: 28rpx;
color: #19367A;
background: rgba(25, 54, 122, 0.05);
padding: 8rpx 16rpx;
border-radius: 8rpx;
}
}
.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: 48rpx;
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
.target-info {
flex: 1;
text-align: center;
.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;
}
}
}
}
.progress-bar-section {
margin-top: 72rpx;
.progress-track {
height: 16rpx;
background: rgba(227, 227, 229, 0.6);
border-radius: 8rpx;
position: relative;
margin-bottom: 24rpx;
box-shadow: inset 0 2rpx 4rpx rgba(0, 0, 0, 0.05);
overflow: visible;
.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: -72rpx;
transform: translateX(50%);
background: #FFFFFF;
padding: 8rpx 16rpx;
border-radius: 16rpx;
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
border: 2rpx solid #D42E78;
z-index: 1;
text {
font-size: 26rpx;
color: #D42E78;
font-weight: 600;
line-height: 1;
}
&::after {
content: '';
position: absolute;
bottom: -10rpx;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
border-left: 10rpx solid transparent;
border-right: 10rpx solid transparent;
border-top: 10rpx solid #FFFFFF;
filter: drop-shadow(0 2rpx 2rpx rgba(0, 0, 0, 0.1));
}
&::before {
content: '';
position: absolute;
bottom: -12rpx;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
border-left: 10rpx solid transparent;
border-right: 10rpx solid transparent;
border-top: 10rpx solid #D42E78;
z-index: -1;
}
}
}
}
.progress-labels {
display: flex;
justify-content: space-between;
padding: 0 8rpx;
text {
font-size: 26rpx;
font-family: 'DIN';
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: 40rpx;
margin-bottom: 12rpx;
display: block;
}
.status {
font-size: 32rpx;
margin-top: 8rpx;
margin-left: 24rpx;
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>