vending-machine/mobile/pages/mine/mine.vue
18631081161 d43406380c
Some checks reported errors
continuous-integration/drone/push Build encountered an error
逻辑优化
2026-04-13 13:43:06 +08:00

444 lines
11 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="mine-page">
<!-- 自定义头部 -->
<view class="header" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="header-inner">
<text class="header-title">贩卖机</text>
</view>
</view>
<!-- 用户信息区域 -->
<view class="user-section" :style="{ marginTop: (statusBarHeight + 44) + 'px' }">
<!-- 未登录状态 -->
<view v-if="!userStore.isLoggedIn" class="user-row" @click="goLogin">
<image class="avatar" src="/static/logo.png" mode="aspectFill" />
<text class="login-btn-text">注册/登录 ></text>
</view>
<!-- 已登录状态 -->
<view v-else class="user-row">
<image class="avatar" src="/static/logo.png" mode="aspectFill" />
<view class="user-info">
<view class="name-row">
<text class="nickname">{{ userStore.userInfo?.nickname || '' }}</text>
<view v-if="userStore.userInfo?.isMember" class="vip-tag">
<image class="vip-icon" src="/static/ic_vip.png" mode="aspectFit" />
<text class="vip-text">会员</text>
</view>
</view>
<text class="uid">UID{{ userStore.userInfo?.uid || '' }}</text>
</view>
</view>
</view>
<!-- 积分区域 -->
<view v-if="userStore.isLoggedIn" class="points-card" @click="goPoints">
<view class="points-left">
<view class="points-icon-wrap">
<image class="points-icon" src="/static/ic_integral.png" mode="aspectFit" />
</view>
<text class="points-label">{{ t('mine.points') }}</text>
</view>
<text class="points-value">{{ balance }}</text>
</view>
<!-- 功能入口(赠送积分 + 我的优惠券) -->
<view v-if="userStore.isLoggedIn" class="func-row">
<view class="func-card" @click="showGiftPopup = true">
<view class="func-icon-wrap gift-bg">
<image class="func-icon" src="/static/ic_reward_points.png" mode="aspectFit" />
</view>
<text class="func-label">{{ t('mine.giftPoints') }}</text>
</view>
<view class="func-card" @click="goCoupons">
<view class="func-icon-wrap coupon-bg">
<image class="func-icon" src="/static/ic_my_coupon.png" mode="aspectFit" />
</view>
<text class="func-label">{{ t('mine.myCoupons') }}</text>
</view>
</view>
<!-- 菜单列表 -->
<view class="menu-card">
<view class="menu-item" @click="showLangPicker = true">
<view class="menu-icon-wrap">
<image class="menu-icon" src="/static/ic_language.png" mode="aspectFit" />
</view>
<text class="menu-text">{{ t('mine.switchLang') }}</text>
<text class="menu-extra">{{ currentLangLabel }}</text>
<text class="menu-arrow"></text>
</view>
<view class="menu-item" @click="goAgreement">
<view class="menu-icon-wrap">
<image class="menu-icon" src="/static/ic_agreement.png" mode="aspectFit" />
</view>
<text class="menu-text">{{ t('mine.userAgreement') }}</text>
<text class="menu-arrow"></text>
</view>
<view class="menu-item" @click="goPrivacy">
<view class="menu-icon-wrap">
<image class="menu-icon" src="/static/ic_agreement2.png" mode="aspectFit" />
</view>
<text class="menu-text">{{ t('mine.privacyPolicy') }}</text>
<text class="menu-arrow"></text>
</view>
<view class="menu-item" @click="goAbout">
<view class="menu-icon-wrap">
<image class="menu-icon" src="/static/ic_about.png" mode="aspectFit" />
</view>
<text class="menu-text">{{ t('mine.about') }}</text>
<text class="menu-arrow"></text>
</view>
<view v-if="userStore.isLoggedIn" class="menu-item" @click="showLogoutConfirm = true">
<view class="menu-icon-wrap">
<image class="menu-icon" src="/static/ic_exit.png" mode="aspectFit" />
</view>
<text class="menu-text">{{ t('mine.logout') }}</text>
<text class="menu-arrow"></text>
</view>
</view>
<!-- 退出登录确认弹窗 -->
<view class="popup-mask" v-if="showLogoutConfirm" @click="showLogoutConfirm = false">
<view class="popup-content" @click.stop>
<text class="popup-title">{{ t('mine.logoutConfirm') }}</text>
<view class="popup-actions">
<view class="btn cancel-btn" @click="showLogoutConfirm = false">
<text class="btn-text cancel-text">{{ t('common.cancel') }}</text>
</view>
<view class="btn confirm-btn" @click="handleLogout">
<text class="btn-text confirm-text">{{ t('common.confirm') }}</text>
</view>
</view>
</view>
</view>
<!-- 赠送积分弹窗 -->
<GiftPointsPopup :visible="showGiftPopup" @close="showGiftPopup = false" @success="loadBalance" />
<!-- 语言选择器弹窗 -->
<LanguagePicker :visible="showLangPicker" @close="showLangPicker = false" />
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { useUserStore } from '../../stores/user.js'
import { getBalance } from '../../api/points.js'
import { getStorage, LOCALE_KEY } from '../../utils/storage.js'
import GiftPointsPopup from '../../components/GiftPointsPopup.vue'
import LanguagePicker from '../../components/LanguagePicker.vue'
const { t, locale } = useI18n()
const userStore = useUserStore()
// 状态栏高度
const statusBarHeight = ref(0)
try {
const sysInfo = uni.getSystemInfoSync()
statusBarHeight.value = sysInfo.statusBarHeight || 0
} catch (e) {}
// 积分余额
const balance = ref(0)
// 弹窗状态
const showGiftPopup = ref(false)
const showLangPicker = ref(false)
const showLogoutConfirm = ref(false)
// 当前语言标签
const currentLangLabel = computed(() => {
const map = { 'zh-CN': '简体中文', 'zh-TW': '繁體中文', 'en': 'English' }
return map[locale.value] || '简体中文'
})
// 加载积分余额
async function loadBalance() {
if (!userStore.isLoggedIn) return
try {
const res = await getBalance()
balance.value = res.data?.balance ?? res.data ?? 0
} catch (e) {}
}
// 退出登录
async function handleLogout() {
showLogoutConfirm.value = false
await userStore.logout()
balance.value = 0
// 清除所有页面栈,返回首页
uni.reLaunch({ url: '/pages/index/index' })
}
// 页面导航
function goLogin() { uni.navigateTo({ url: '/pages/login/login' }) }
function goPoints() { uni.navigateTo({ url: '/pages/points/points' }) }
function goCoupons() { uni.navigateTo({ url: '/pages/coupons/coupons' }) }
function goAgreement() { uni.navigateTo({ url: '/pages/agreement/agreement' }) }
function goPrivacy() { uni.navigateTo({ url: '/pages/privacy/privacy' }) }
function goAbout() { uni.navigateTo({ url: '/pages/about/about' }) }
onMounted(() => {
if (userStore.isLoggedIn) {
userStore.fetchUserInfo()
loadBalance()
}
})
</script>
<style scoped>
.mine-page {
min-height: 100vh;
background-color: #f5f0e8;
}
/* 自定义头部 */
.header {
position: fixed;
top: 0; left: 0; right: 0;
z-index: 100;
background-color: #f5f0e8;
}
.header-inner {
height: 44px;
display: flex;
align-items: center;
justify-content: center;
}
.header-title {
font-size: 34rpx;
font-weight: 600;
color: #333;
}
/* 用户信息 */
.user-section {
padding: 30rpx 30rpx 0;
}
.user-row {
display: flex;
align-items: center;
}
.avatar {
width: 100rpx;
height: 100rpx;
border-radius: 50rpx;
margin-right: 20rpx;
background-color: #e0e0e0;
}
.login-btn-text {
font-size: 32rpx;
color: #333;
font-weight: 500;
}
.user-info {
flex: 1;
}
.name-row {
display: flex;
align-items: center;
margin-bottom: 8rpx;
}
.nickname {
font-size: 34rpx;
color: #333;
font-weight: 600;
margin-right: 12rpx;
}
.vip-tag {
display: flex;
align-items: center;
background-color: #4a5d4a;
border-radius: 20rpx;
padding: 4rpx 16rpx 4rpx 8rpx;
}
.vip-icon {
width: 28rpx;
height: 28rpx;
margin-right: 4rpx;
}
.vip-text {
font-size: 22rpx;
color: #ffffff;
}
.uid {
font-size: 24rpx;
color: #999;
}
/* 积分卡片 */
.points-card {
display: flex;
align-items: center;
justify-content: space-between;
background-color: #ffffff;
margin: 24rpx 24rpx 0;
padding: 28rpx 32rpx;
border-radius: 20rpx;
}
.points-left {
display: flex;
align-items: center;
}
.points-icon-wrap {
width: 72rpx;
height: 72rpx;
border-radius: 50%;
background-color: #f5f0e8;
display: flex;
align-items: center;
justify-content: center;
margin-right: 16rpx;
}
.points-icon {
width: 40rpx;
height: 40rpx;
}
.points-label {
font-size: 30rpx;
color: #333;
}
.points-value {
font-size: 40rpx;
color: #333;
font-weight: 700;
}
/* 功能入口行 */
.func-row {
display: flex;
padding: 24rpx 24rpx 0;
gap: 24rpx;
}
.func-card {
flex: 1;
background-color: #ffffff;
border-radius: 20rpx;
padding: 32rpx 0;
display: flex;
flex-direction: column;
align-items: center;
}
.func-icon-wrap {
width: 72rpx;
height: 72rpx;
border-radius: 18rpx;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 12rpx;
}
.gift-bg {
background-color: #f5f0e8;
}
.coupon-bg {
background-color: #f5f0e8;
}
.func-icon {
width: 44rpx;
height: 44rpx;
}
.func-label {
font-size: 26rpx;
color: #333;
}
/* 菜单列表 */
.menu-card {
background-color: #ffffff;
margin: 24rpx 24rpx 0;
border-radius: 20rpx;
overflow: hidden;
}
.menu-item {
display: flex;
align-items: center;
padding: 30rpx 32rpx;
border-bottom: 1rpx solid #f5f5f5;
}
.menu-item:last-child {
border-bottom: none;
}
.menu-icon-wrap {
width: 72rpx;
height: 72rpx;
border-radius: 16rpx;
background-color: #eeeeee;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
}
.menu-icon {
width: 40rpx;
height: 40rpx;
}
.menu-text {
flex: 1;
font-size: 30rpx;
color: #333;
}
.menu-extra {
font-size: 26rpx;
color: #999;
margin-right: 8rpx;
}
.menu-arrow {
font-size: 32rpx;
color: #ccc;
}
/* 弹窗样式 */
.popup-mask {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
}
.popup-content {
width: 600rpx;
background-color: #ffffff;
border-radius: 24rpx;
padding: 40rpx;
}
.popup-title {
font-size: 32rpx;
color: #333;
text-align: center;
display: block;
margin-bottom: 40rpx;
}
.popup-actions {
display: flex;
gap: 20rpx;
}
.btn {
flex: 1;
height: 84rpx;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
}
.cancel-btn {
background-color: #ffffff;
border: 2rpx solid #6b7c5e;
}
.confirm-btn {
background-color: #6b7c5e;
}
.btn-text {
font-size: 30rpx;
}
.cancel-text {
color: #6b7c5e;
}
.confirm-text {
color: #ffffff;
}
</style>