vending-machine/mobile/pages/mine/mine.vue
18631081161 fa0cf7e41c
All checks were successful
continuous-integration/drone/push Build is passing
bug修复
2026-04-21 04:08:37 +08:00

506 lines
12 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="fixed-nav" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="nav-inner">
<text class="nav-title">贩卖机</text>
</view>
</view>
<!-- 可滚动内容 -->
<view class="scroll-body" :style="{ paddingTop: (statusBarHeight + 44) + 'px' }">
<!-- 灰色头部背景区域 -->
<view class="header-bg">
<!-- 用户信息 -->
<view class="user-section">
<!-- 未登录 -->
<view v-if="!userStore.isLoggedIn" class="user-row" @click="goLogin">
<view class="avatar-wrap">
<image class="avatar-icon" src="/static/ic_me.png" mode="aspectFit" />
</view>
<text class="login-btn-text">注册/登录 ></text>
</view>
<!-- 已登录 -->
<view v-else class="user-row">
<view class="avatar-wrap">
<image class="avatar-icon" src="/static/ic_me.png" mode="aspectFit" />
</view>
<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>
<!-- 内容区域 -->
<view class="content-area">
<!-- 积分卡片(上浮压在灰色头部上) -->
<view 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 class="func-row">
<view class="func-card" @click="onFuncClick('gift')">
<view class="func-icon-wrap">
<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="onFuncClick('coupons')">
<view class="func-icon-wrap">
<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>
<!-- 退出登录确认弹窗 -->
<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>
<!-- 自定义底部导航 -->
<CustomTabBar current="/pages/mine/mine" />
</view>
</template>
<script>
export default {
onShow() {
uni.hideTabBar({ animation: false })
uni.$emit('mine:refresh')
}
}
</script>
<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'
import CustomTabBar from '../../components/CustomTabBar.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) {}
}
// 功能入口点击(未登录跳登录)
function onFuncClick(type) {
if (!userStore.isLoggedIn) {
uni.navigateTo({ url: '/pages/login/login' })
return
}
if (type === 'gift') showGiftPopup.value = true
else if (type === 'coupons') goCoupons()
}
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() {
if (!userStore.isLoggedIn) { uni.navigateTo({ url: '/pages/login/login' }); return }
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()
}
})
// tab切换回来时刷新
uni.$on('mine:refresh', () => {
if (userStore.isLoggedIn) {
userStore.fetchUserInfo()
loadBalance()
}
})
</script>
<style scoped>
.mine-page {
min-height: 100vh;
background-color: #f0f0f0;
padding-bottom: 120rpx;
}
/* 固定标题栏 */
.fixed-nav {
position: fixed;
top: 0; left: 0; right: 0;
z-index: 200;
background-color: #DBDBDB;
}
.nav-inner {
height: 44px;
display: flex;
align-items: center;
justify-content: center;
}
.nav-title {
font-size: 34rpx;
font-weight: 600;
color: #333;
}
/* 灰色头部背景(跟随滚动) */
.header-bg {
background-color: #DBDBDB;
border-bottom-left-radius: 60rpx;
border-bottom-right-radius: 60rpx;
padding: 16rpx 0 100rpx;
}
/* 用户信息 */
.user-section {
padding: 16rpx 32rpx 0;
}
.user-row {
display: flex;
align-items: center;
}
.avatar-wrap {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
border: 2rpx solid #c9a96e;
background-color: rgba(201, 169, 110, 0.1);
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
}
.avatar-icon {
width: 52rpx;
height: 52rpx;
opacity: 0.6;
}
.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: #666;
}
/* 内容区域 */
.content-area {
padding: 0 24rpx;
margin-top: -50rpx;
position: relative;
z-index: 101;
}
/* 积分卡片 */
.points-card {
display: flex;
align-items: center;
justify-content: space-between;
background-color: #ffffff;
padding: 28rpx 32rpx;
border-radius: 20rpx;
margin-bottom: 24rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
position: relative;
z-index: 101;
}
.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;
gap: 24rpx;
margin-bottom: 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;
background-color: #f5f0e8;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 12rpx;
}
.func-icon {
width: 44rpx;
height: 44rpx;
}
.func-label {
font-size: 26rpx;
color: #333;
}
/* 菜单列表 */
.menu-card {
background-color: #ffffff;
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>