1004 lines
24 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="activitypage">
<!-- 主导航栏 -->
<view class="main-nav">
<view class="nav-item" :class="{'nav-active': tabCur == 0}" @tap="tabSelect" :data-id="0">
累计活动
<view class="nav-line" v-if="tabCur == 0"></view>
</view>
<view class="nav-item" :class="{'nav-active': tabCur == 1}" @tap="tabSelect" :data-id="1">
啤酒币换购
<view class="nav-line" v-if="tabCur == 1"></view>
</view>
</view>
<!-- 状态提示区域 -->
<view class="status-tip" v-if="!isLoggedIn || userStatus !== 'verified'">
<view class="flex-col status-text">
<block v-if="!hasNetwork">
<text class="text-lg">网络连接失败</text>
<text class="sub-text">请检查网络设置后重试</text>
<button class="login-btn" @click="checkNetwork">重新加载</button>
</block>
<block v-else-if="userStatus === 'guest'">
<text class="text-lg">暂无法查看信息</text>
<text class="sub-text">您还没有登录请登录后查看信息</text>
<button class="login-btn" @click="toLogin">
<image src="/static/send-2.svg" class="btn-icon"></image>
登录/认证
</button>
</block>
<block v-else-if="userStatus === 'unverified'">
<text class="text-lg">您的门店还未完成认证</text>
<text class="sub-text">请点击认证门店信息</text>
<button class="login-btn" @click="toLogin">
<image src="/static/send-2.svg" class="btn-icon"></image>
去认证
</button>
</block>
<block v-else-if="userStatus === 'verifying'">
<text class="text-lg">正在认证审核中</text>
<text class="sub-text">请耐心等待</text>
</block>
</view>
</view>
<!-- 内容区域 -->
<template v-if="isLoggedIn && userStatus === 'verified'">
<!-- 状态标签栏 -->
<scroll-view scroll-x class="status-nav" :scroll-left="0">
<view class="status-nav-content">
<template v-if="tabCur == 0">
<view class="status-item" :class="{'status-active': curTag == 0}" @tap="changeTag(0)">累计中</view>
<view class="status-item" :class="{'status-active': curTag == 1}" @tap="changeTag(1)">待兑付</view>
<view class="status-item" :class="{'status-active': curTag == 2}" @tap="changeTag(2)">已兑付</view>
<view class="status-item" :class="{'status-active': curTag == 3}" @tap="changeTag(3)">已完成</view>
</template>
<template v-else>
<view class="status-item" :class="{'status-active': curCoinTag == 0}" @tap="changeCoinTag(0)">全部</view>
<view class="status-item" :class="{'status-active': curCoinTag == 1}" @tap="changeCoinTag(1)">待发货</view>
<view class="status-item" :class="{'status-active': curCoinTag == 2}" @tap="changeCoinTag(2)">已收货</view>
<view class="status-item" :class="{'status-active': curCoinTag == 3}" @tap="changeCoinTag(3)">已完成</view>
</template>
</view>
</scroll-view>
<view class="bg-white flex-col" style="height: 100%;">
<!-- 累积活动列表 -->
<scroll-view v-if="tabCur == 0" scroll-y
style="padding: 44rpx 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="cu-load" :class="'over'"></view>
</scroll-view>
<!-- 啤酒币换购列表 -->
<scroll-view v-if="tabCur == 1" scroll-y
style="padding: 44rpx 28rpx 0;background-color: #F2F2F2;overflow-y: auto;flex:1"
@scrolltolower="changePageCoin">
<!-- 兑换订单列表 -->
<view class="coin-item" v-for="(it, index) in myExchangeOrder" :key="index" @click="toOrderInfo(it)">
<view class="order-header">
<view class="order-type">
<image src="/static/beerCoin.png" class="coin-icon"></image>
<text class="type-text">啤酒币兑换</text>
</view>
<view class="order-status" :class="'status-' + it.status">
<text>{{ getStatusText(it.status) }}</text>
</view>
</view>
<view class="order-content">
<image :src="it.goodsCover" class="goods-image" mode="aspectFill"></image>
<view class="goods-info">
<text class="goods-name word-all">{{ it.goodsName }}</text>
<view class="goods-specs" v-if="it.specs">
<text class="specs-text">{{ it.specs }}</text>
</view>
</view>
<view class="order-summary">
<view class="price-amount">
<text class="amount">{{ it.beerCoinNum }}</text>
<text class="cuIcon-rechargefill coin-icon"></text>
<text class="price-unit">/</text>
</view>
<view class="summary-item">
<text class="summary-label">兑换数量</text>
<text class="summary-value">{{ it.redeemNum }}</text>
</view>
</view>
</view>
</view>
<view class="cu-load" :class="orderTotal == myExchangeOrder.length ? 'over': 'more'"></view>
</scroll-view>
</view>
</template>
<!-- 登录弹窗组件 -->
<loginPopup ref="loginRef" @loginSuccess="loginSuccess"></loginPopup>
</view>
</template>
<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
},
data() {
return {
loading: true,
userInfo: null,
barInfo: null,
isLoggedIn: false,
isVerified: false,
tabCur: 0,
curTag: 0,
curCoinTag: 0,
myJoinList: [],
queryForm: {
status: 1,
pageNum: 1,
pageSize: 10
},
total: 0,
myExchangeOrder:[],
orderQuery: {
pageNum: 1,
pageSize: 10
},
orderTotal: 0,
isLoading: false,
isRefreshing: false,
hasNetwork: true,
lastRefreshTime: 0,
refreshDebounceTime: 1000,
};
},
computed: {
// 用户状态
userStatus() {
if (!this.isLoggedIn) return 'guest' // 游客
if (!this.barInfo || this.barInfo.authState === 0) return 'unverified' // 未认证
if (this.barInfo.authState === 2) return 'verifying' // 认证中
return 'verified' // 认证通过
}
},
onLoad() {
this.checkLoginStatus()
this.checkNetwork()
},
onShow() {
this.checkUserInfo()
},
methods: {
// 获取订单状态文本
getStatusText(status) {
switch(status) {
case 1:
return '待发货';
case 2:
return '已发货';
case 3:
return '已完成';
default:
return '未知状态';
}
},
// 检查登录状态
async checkLoginStatus() {
try {
const token = uni.getStorageSync('token')
const userInfo = uni.getStorageSync('userInfo')
// 检查token和userInfo是否有效
if (!token || token.startsWith('temp_') || !userInfo) {
this.isLoggedIn = false
this.isVerified = false
this.userInfo = null
this.barInfo = null
return false
}
this.isLoggedIn = true
this.userInfo = userInfo
// 获取门店信息
await this.getBarInfoFun()
return true
} catch (error) {
console.error('检查登录状态失败:', error)
return false
}
},
// 获取门店信息
async getBarInfoFun() {
console.log('【getBarInfoFun】开始获取门店信息')
console.log('【getBarInfoFun】当前登录状态:', this.isLoggedIn)
if (!this.isLoggedIn) {
console.log('【getBarInfoFun】用户未登录不获取门店信息')
return
}
try {
console.log('【getBarInfoFun】调用getBarInfo接口')
const res = await getBarInfo()
console.log('【getBarInfoFun】接口返回数据:', res)
if (res.code === 200 && res.data) {
console.log('【getBarInfoFun】获取门店信息成功')
console.log('【getBarInfoFun】门店信息:', res.data)
console.log('【getBarInfoFun】认证状态(authState):', res.data.authState)
this.barInfo = res.data
this.isVerified = res.data.authState === 1 // 1表示认证通过
console.log('【getBarInfoFun】更新后的认证状态(isVerified):', this.isVerified)
uni.setStorageSync('barInfo', res.data)
console.log('【getBarInfoFun】门店信息已存储到本地')
} else {
console.log('【getBarInfoFun】获取门店信息失败接口返回异常')
this.isVerified = false
this.barInfo = null
}
} catch (error) {
console.error('【getBarInfoFun】获取门店信息出错:', error)
this.isVerified = false
this.barInfo = null
// 只在已认证状态下显示错误提示
if (this.userStatus === 'verified') {
uni.showToast({
title: '获取门店信息失败',
icon: 'none'
})
}
}
console.log('【getBarInfoFun】方法执行完成')
console.log('【getBarInfoFun】最终状态 - barInfo:', this.barInfo)
console.log('【getBarInfoFun】最终状态 - isVerified:', this.isVerified)
console.log('【getBarInfoFun】当前userStatus:', this.userStatus)
},
// 检查用户信息变化
async checkUserInfo() {
try {
const newToken = uni.getStorageSync('token')
const newUserInfo = uni.getStorageSync('userInfo')
// 检查token和userInfo是否有变化
if (newToken !== this.userInfo?.token ||
JSON.stringify(newUserInfo) !== JSON.stringify(this.userInfo)) {
this.loading = true
const isLoggedIn = await this.checkLoginStatus()
if (isLoggedIn) {
// 只在需要时刷新数据
if (this.shouldRefreshData()) {
await this.getMyJoinList()
}
} else {
this.myJoinList = []
this.myExchangeOrder = []
}
}
} catch (error) {
console.error('检查用户信息失败:', error)
} finally {
this.loading = false
}
},
// 登录成功回调
async loginSuccess() {
try {
console.log('【loginSuccess】开始处理登录成功')
this.loading = true
const token = uni.getStorageSync('token')
const openId = uni.getStorageSync('openId')
if (token && openId) {
this.isLoggedIn = true
this.userInfo = {
token,
openId
}
await this.getBarInfoFun()
// 重置页面状态
this.queryForm.pageNum = 1
this.orderQuery.pageNum = 1
this.myJoinList = []
this.myExchangeOrder = []
// 只在需要时刷新数据
if (this.userStatus === 'verified' && this.shouldRefreshData()) {
await this.getMyJoinList()
}
} else {
this.isLoggedIn = false
this.userInfo = null
this.barInfo = null
this.isVerified = false
}
} catch (error) {
console.error('【loginSuccess】登录成功处理失败:', error)
if (this.userStatus === 'verified') {
uni.showToast({
title: '登录失败',
icon: 'none'
})
}
} finally {
this.loading = false
}
},
// 判断是否需要刷新数据
shouldRefreshData() {
const now = Date.now()
if (now - this.lastRefreshTime < this.refreshDebounceTime) {
return false
}
this.lastRefreshTime = now
return true
},
// 获取我的兑换订单
async getMyExchangeOrderFun() {
// 如果用户未认证,直接返回
if (this.userStatus !== 'verified') {
return
}
if (this.isLoading) return
try {
this.isLoading = true
const res = await getMyExchangeOrder(this.orderQuery)
if (res && res.rows) {
this.orderTotal = res.total || 0
if (this.orderQuery.pageNum === 1) {
this.myExchangeOrder = res.rows
} else {
this.myExchangeOrder = [...this.myExchangeOrder, ...res.rows]
}
}
} catch (error) {
console.error('获取兑换订单失败:', error)
// 只在已认证状态下显示错误提示
if (this.userStatus === 'verified') {
uni.showToast({
title: '获取订单失败',
icon: 'none'
})
}
} finally {
this.isLoading = false
}
},
// 切换标签页
async tabSelect(e) {
const id = e.currentTarget.dataset.id
if (this.tabCur === id) return
this.tabCur = id
if (this.tabCur == 0) {
this.myJoinList = []
this.queryForm.pageNum = 1
// 只在需要时刷新数据
if (this.shouldRefreshData()) {
await this.getMyJoinList()
}
} else {
this.myExchangeOrder = []
this.orderQuery.pageNum = 1
// 只在需要时刷新数据
if (this.shouldRefreshData()) {
await this.getMyExchangeOrderFun()
}
}
},
// 获取我参与的活动列表
async getMyJoinList() {
// 如果用户未认证,直接返回
if (this.userStatus !== 'verified') {
return
}
if (this.isLoading) return
try {
this.isLoading = true
const res = await myJoinListApi(this.queryForm)
if (res && res.data) {
this.total = res.data.total || 0
let activities = []
// 处理品牌活动
if (res.data.activities && res.data.activities.length > 0) {
activities = activities.concat(res.data.activities)
}
// 处理平台活动
if (res.data.platformActivities && res.data.platformActivities.length > 0) {
activities = activities.concat(res.data.platformActivities)
}
if (this.queryForm.pageNum === 1) {
this.myJoinList = activities
} else {
this.myJoinList = [...this.myJoinList, ...activities]
}
}
} catch (error) {
console.error('获取活动列表失败:', error)
if (this.userStatus === 'verified') {
uni.showToast({
title: '获取活动列表失败',
icon: 'none'
})
}
} finally {
this.isLoading = false
}
},
// 加载更多活动
async changePage() {
if (this.myJoinList.length < this.total && !this.isLoading) {
this.queryForm.pageNum++
await this.getMyJoinList()
}
},
// 跳转到活动详情
toInfo(it) {
if (!it.batchId) return
uni.navigateTo({
url: '/pagesActivity/myActivityDetail?batchId=' + it.batchId
})
},
// 跳转到订单详情
toOrderInfo(it) {
if (!it.id || !it.goodsId) return
uni.navigateTo({
url: '/pagesCoin/orderInfo?orderId=' + it.id + '&goodsId=' + it.goodsId
})
},
// 切换状态标签
async changeTag(index) {
if (this.curTag === index) return
this.curTag = index
this.queryForm.pageNum = 1
this.myJoinList = []
// 设置查询状态
switch(index) {
case 0:
this.queryForm.status = 1
break
case 1:
this.queryForm.status = 2
break
case 2:
this.queryForm.status = 3
break
case 3:
this.queryForm.status = 4
break
default:
delete this.queryForm.status
}
await this.getMyJoinList()
},
// 切换订单状态标签
async changeCoinTag(key) {
if (this.curCoinTag === key) return
this.curCoinTag = key
this.orderQuery.pageNum = 1
this.myExchangeOrder = []
// 设置查询状态
switch(key) {
case 0:
delete this.orderQuery.status
break
case 1:
this.orderQuery.status = 1
break
case 2:
this.orderQuery.status = 2
break
case 3:
this.orderQuery.status = 3
break
}
// 只在需要时刷新数据
if (this.shouldRefreshData()) {
await this.getMyExchangeOrderFun()
}
},
// 加载更多订单
async changePageCoin() {
if (this.myExchangeOrder.length < this.orderTotal && !this.isLoading) {
this.orderQuery.pageNum++
await this.getMyExchangeOrderFun()
}
},
// 跳转登录
toLogin() {
// 如果是未认证状态,直接跳转到认证页面
if (this.userStatus === 'unverified') {
const openId = uni.getStorageSync('openId')
uni.navigateTo({
url: '/pages/index/registration?openId=' + openId
})
} else {
// 其他状态打开登录弹窗
this.$refs.loginRef.open()
}
},
// 跳转到酒评页面
toReview(item) {
if (!item || !item.id) {
uni.showToast({
title: '酒款信息不完整',
icon: 'none'
})
return
}
uni.navigateTo({
url: "/pages/index/review?beerId=" + item.id
})
},
// 检查网络状态
checkNetwork() {
uni.getNetworkType({
success: (res) => {
this.hasNetwork = res.networkType !== 'none'
if (this.hasNetwork) {
this.checkLoginStatus()
}
}
})
// 监听网络状态变化
uni.onNetworkStatusChange((res) => {
this.hasNetwork = res.isConnected
if (this.hasNetwork) {
this.checkLoginStatus()
}
})
}
}
}
</script>
<style lang="scss" scoped>
.activitypage {
height: 100vh;
background: #FFFFFF;
display: flex;
flex-direction: column;
.main-nav {
display: flex;
justify-content: center;
align-items: center;
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
padding: 0 32rpx;
background: #FFFFFF;
height: 88rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
.nav-item {
position: relative;
width: calc((100% - 56rpx) / 2); // 两等分宽度(减去间距)
margin: 0 14rpx; // 统一的间距
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
color: #1A1A1A;
font-weight: normal;
transition: all 0.3s ease;
&.nav-active {
color: #4E63E0;
font-weight: 600;
.nav-line {
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 48rpx;
height: 4rpx;
background: #4E63E0;
border-radius: 2rpx;
}
}
}
}
.status-nav {
position: fixed;
top: 88rpx;
left: 0;
right: 0;
z-index: 99;
background: #FFFFFF;
white-space: nowrap;
padding: 20rpx 32rpx;
// border-bottom: 1rpx solid #F5F5F5;
// box-shadow: 0rpx 1rpx 3rpx 0rpx rgba(0, 0, 0, 0.1);
.status-nav-content {
display: inline-flex;
.status-item {
display: flex;
align-items: center;
justify-content: center;
width: 147rpx;
height: 64rpx;
margin-right: 16rpx;
font-size: 24rpx;
color: #606060;
background: #f9f9f9;
border-radius: 12rpx;
&.status-active {
color: #FFF;
background: #4E63E0;
}
}
}
}
.bg-white {
flex: 1;
margin-top: 172rpx; // 88rpx(导航栏) + 84rpx(标签栏)
overflow: hidden;
scroll-view {
height: 100%;
box-sizing: border-box;
padding: 44rpx 28rpx 0; // 添加顶部内边距
background-color: #F2F2F2;
overflow-y: auto;
flex: 1;
}
}
.activity-item {
border-radius: 12rpx;
background: #FFFFFF;
box-sizing: border-box;
border: 1px solid #E0E0E0;
width: 694rpx;
margin-bottom: 32rpx;
box-shadow: 0rpx 1rpx 3rpx 0rpx rgba(0, 0, 0, 0.1);
.left {
padding: 24rpx 20rpx;
border-radius: 20rpx;
background: #FFFFFF;
box-sizing: border-box;
border: 1px solid #E0E0E0;
width: 180rpx;
margin-top: -10rpx;
margin-bottom: -10rpx;
margin-left: -1rpx;
box-shadow: 0rpx 1rpx 3rpx 0rpx rgba(0, 0, 0, 0.1);
}
.right {
padding: 20rpx;
flex: 1;
.title {
font-family: Source Han Sans;
font-size: 28rpx;
font-weight: bold;
line-height: 30rpx;
color: #0B0E26;
margin-bottom: 20rpx;
}
.sub {
font-family: Source Han Sans;
font-size: 24rpx;
font-weight: 500;
line-height: 30rpx;
color: #0B0E26;
margin-bottom: 16rpx;
}
.scroll-img {
width: 470rpx;
display: flex;
flex-direction: row;
white-space: nowrap;
height: 144rpx;
margin-bottom: 20rpx;
.beer-box {
width: 100rpx;
background: #FFFFFF;
margin-right: 20rpx;
box-sizing: border-box;
display: inline-block;
.cover {
width: 100rpx;
height: 144rpx;
border-radius: 10rpx;
}
}
}
.zeng {
font-family: roboto;
font-size: 20rpx;
font-weight: bold;
line-height: normal;
text-align: center;
color: #0B0E26;
padding: 8rpx 12rpx;
border-radius: 10rpx;
background: #FEE034;
margin-right: 20rpx;
}
}
}
.coin-item {
border-radius: 16rpx;
background: #FFFFFF;
padding: 24rpx;
box-sizing: border-box;
margin-bottom: 24rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
transition: all 0.3s ease;
&:active {
transform: scale(0.98);
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
}
.order-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
.order-type {
display: flex;
align-items: center;
.coin-icon {
width: 32rpx;
height: 32rpx;
margin-right: 12rpx;
}
.type-text {
font-size: 28rpx;
color: #333333;
font-weight: 600;
}
}
.order-status {
font-size: 24rpx;
padding: 6rpx 16rpx;
border-radius: 24rpx;
background: #F5F5F5;
color: #666666;
&.status-1 {
background: #FFF8E6;
color: #F0A020;
}
&.status-2 {
background: #E6F7FF;
color: #1890FF;
}
&.status-3 {
background: #F6FFED;
color: #52C41A;
}
}
}
.order-content {
display: flex;
align-items: flex-start;
.goods-image {
width: 160rpx;
height: 160rpx;
border-radius: 12rpx;
background: #F5F5F5;
margin-right: 24rpx;
flex-shrink: 0;
}
.goods-info {
flex: 1;
display: flex;
flex-direction: column;
height: 160rpx;
.goods-name {
font-size: 32rpx;
color: #333333;
font-weight: 600;
line-height: 1.4;
margin-bottom: 8rpx;
}
.goods-specs {
margin-bottom: 8rpx;
.specs-text {
font-size: 24rpx;
color: #666666;
background: #F5F5F5;
padding: 4rpx 12rpx;
border-radius: 8rpx;
}
}
}
.order-summary {
width: 200rpx;
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: space-between;
height: 160rpx;
.price-amount {
display: flex;
align-items: center;
.amount {
font-size: 36rpx;
color: #333333;
font-weight: 600;
}
.coin-icon {
color: #FEE034;
font-size: 36rpx;
margin-left: 8rpx;
}
.price-unit {
font-size: 24rpx;
color: #999999;
margin-left: 8rpx;
}
}
.summary-item {
display: flex;
flex-direction: column;
align-items: flex-end;
.summary-label {
font-size: 24rpx;
color: #999999;
margin-bottom: 4rpx;
}
.summary-value {
font-size: 28rpx;
color: #333333;
font-weight: 500;
}
}
}
}
}
.status-tip {
position: fixed;
top: 88rpx;
left: 0;
right: 0;
bottom: 0;
background-color: #FDFDFD;
z-index: 99;
display: flex;
justify-content: center;
align-items: center;
.status-text {
text-align: center;
.text-lg {
color: #3D3D3D;
font-weight: 600;
font-size: 32rpx;
margin-bottom: 16rpx;
}
.sub-text {
color: #979797;
font-size: 28rpx;
margin-bottom: 32rpx;
}
.login-btn {
width: 306rpx;
height: 88rpx;
background-color: #4E63E0;
color: #FFFFFF;
font-size: 28rpx;
font-weight: 500;
display: flex;
align-items: center;
justify-content: center;
border-radius: 44rpx;
.btn-icon {
width: 36rpx;
height: 36rpx;
margin-right: 8rpx;
}
}
}
}
}
.word-all {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
</style>