zdtap-uniapp-main/pagesActivity/myActivityDetail.vue

910 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">
<view class="page-content">
<!-- 进度展示区域 -->
<view class="section">
<view class="section-title">累计进度</view>
<view class="progress-content">
<!-- 剩余目标显示 -->
<view class="target-display">
<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">
<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': !reward,
'status-pending': reward && reward.rewardStatus == 0,
'status-completed': reward && reward.rewardStatus == 1
}">
{{!reward ? '累计中' : (reward.rewardStatus == 0 ? '待确认' : '已确认')}}
</text>
</view>
<view class="action-button"
:class="{
'scan-btn': !reward,
'confirm-btn': reward && reward.rewardStatus == 0,
'completed-btn': reward && reward.rewardStatus == 1
}"
@click="!reward ? toScan() : (reward.rewardStatus == 0 ? confirmPay() : null)">
<text class="cuIcon-qr_code" v-if="!reward"></text>
<text>{{!reward ? '扫码累计' : (reward.rewardStatus == 0 ? '确认兑付完成' : '兑付已确认')}}</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',
})
this.init()
})
}
},
})
},
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;
}
.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>