Files
venue-miniapp/miniprogram/src/pages/comprehensive/profile/index.vue
T
gqt 6a18d3de61 feat: 页面重写与流程优化
- 门票Tab展示已购门票记录(可核销/已核销/已退款)
- 购买次数去掉合计金额行
- 登录后直接跳profile/detail,合并setup页(不区分模式)
- profile/detail增加返回首页按钮
- 约课页移除UnauthLoginPanel
- 401跳转改为reLaunch到login页,不显示toast
- membership/cards仅展示可用卡,恢复点击进详情
- available-sessions改为只读展示,移除预约逻辑
- profile/orders重写样式,单行状态筛选
- profile/balance重写样式,onShow自动刷新
- profile/index移除冗余菜单项(篮球门票/充值/我的预约/个人信息/切换场馆)
- profile/index移除AgreementLinks,我的次卡改为我的会员卡
- pages.json移除setup页注册
2026-07-13 21:43:59 +08:00

517 lines
11 KiB
Vue

<template>
<view class="profile-page container">
<view v-if="!session.isLoggedIn" class="user-profile-hero" @tap="login">
<view class="hero-left">
<view class="hero-avatar placeholder">
<text class="hero-placeholder-text">点击登录</text>
</view>
<view class="hero-meta">
<text class="hero-name">点击登录</text>
</view>
</view>
<view class="hero-right">
<view class="hero-arrow" />
</view>
</view>
<view v-else class="user-profile-hero" @tap="go('/pages/comprehensive/profile/detail')">
<view class="hero-left">
<image v-if="session.member?.avatar_url" :src="session.member.avatar_url" mode="aspectFill" class="hero-avatar" />
<view v-else class="hero-avatar placeholder">
<text>{{ avatarInitial }}</text>
</view>
<view class="hero-meta">
<text class="hero-name">{{ session.member?.nickname }}</text>
<view v-if="session.member?.phone" class="hero-sub-row">
<text class="hero-phone">{{ session.member.phone }}</text>
</view>
<text class="hero-balance">余额 ¥{{ balanceText }}</text>
</view>
</view>
<view class="hero-right">
<view class="hero-arrow" />
</view>
</view>
<view class="menu-group-card">
<view v-for="item in menuItems" :key="item.title" class="menu-item" @tap="go(item.url)">
<view class="item-left">
<view class="item-icon-container" :class="item.theme">
<view class="icon-order" v-if="item.icon === 'order'">
<view class="icon-order-line" />
<view class="icon-order-line short" />
</view>
<view class="icon-private" v-else-if="item.icon === 'private'">
<view class="icon-private-net" />
</view>
<view class="icon-ticket" v-else-if="item.icon === 'ticket'">
<view class="icon-ticket-dot" />
</view>
<view class="icon-profile" v-else>
<view class="icon-profile-head" />
<view class="icon-profile-body" />
</view>
</view>
<text class="menu-title">{{ item.title }}</text>
</view>
<view class="item-right">
<view class="menu-arrow" />
</view>
</view>
<view v-if="session.isLoggedIn" class="menu-item logout" @tap="logout">
<view class="item-left">
<view class="item-icon-container logout-theme">
<view class="icon-logout">
<view class="icon-logout-door" />
<view class="icon-logout-arrow" />
</view>
</view>
<text class="menu-title">退出登录</text>
</view>
<view class="item-right">
<view class="menu-arrow" />
</view>
</view>
</view>
</view>
</template>
<script setup>
import { computed, onMounted, ref } from 'vue'
import { useComprehensiveSessionStore } from '@/modules/comprehensive/stores/session'
import { memberApi } from '@/modules/comprehensive/api/member'
import { formatAmount } from '@/modules/comprehensive/utils/money'
const session = useComprehensiveSessionStore()
const avatarInitial = computed(() => (session.member?.nickname || '用户').slice(0, 1))
const balance = ref('0.00')
const balanceText = computed(() => formatAmount(balance.value))
const menuItems = [
{ title: '我的会员卡', url: '/pages/comprehensive/membership/cards', theme: 'profile-theme', icon: 'profile' },
{ title: '我的订单', url: '/pages/comprehensive/profile/orders', theme: 'order-theme', icon: 'order' },
{ title: '余额流水', url: '/pages/comprehensive/profile/balance', theme: 'order-theme', icon: 'order' }
]
onMounted(async () => {
await session.init()
try {
const data = await memberApi.getBalance()
balance.value = data.balance
} catch {
balance.value = '0.00'
}
})
const login = () => {
uni.navigateTo({ url: '/pages/comprehensive/profile/login' })
}
const logout = () => {
session.logout()
uni.showToast({ title: '已退出', icon: 'none' })
}
const go = (url) => {
uni.navigateTo({ url })
}
defineExpose({ onPageShow: () => {} })
</script>
<style lang="scss" scoped>
.login-entry {
display: flex;
align-items: center;
justify-content: space-between;
min-height: 160rpx;
padding: $spacing-lg;
border: 1rpx solid rgba(241, 245, 249, 0.9);
border-radius: $border-radius-lg;
background-color: $bg-color-main;
box-shadow: 0 10rpx 40rpx rgba(15, 23, 42, 0.02);
}
.login-entry:active {
opacity: 0.96;
transform: scale(0.99);
}
.login-entry-left {
display: flex;
align-items: center;
gap: $spacing-md;
}
.login-entry-avatar {
width: 104rpx;
height: 104rpx;
flex-shrink: 0;
border-radius: 50%;
background: $bg-color-hover;
display: flex;
align-items: center;
justify-content: center;
}
.login-entry-avatar.placeholder {
background: $color-primary-light;
}
.login-entry-icon {
font-size: 48rpx;
}
.login-entry-meta {
display: flex;
flex-direction: column;
gap: 6rpx;
}
.login-entry-title {
color: $text-color-main;
font-size: 34rpx;
font-weight: 700;
}
.login-entry-sub {
color: $text-color-muted;
font-size: 24rpx;
}
.user-profile-hero {
display: flex;
align-items: center;
justify-content: space-between;
min-height: 160rpx;
padding: $spacing-lg;
border: 1rpx solid rgba(241, 245, 249, 0.9);
border-radius: $border-radius-lg;
background-color: $bg-color-main;
box-shadow: 0 10rpx 40rpx rgba(15, 23, 42, 0.02);
transition: all 0.2s ease;
}
.user-profile-hero:active {
opacity: 0.96;
transform: scale(0.99);
}
.hero-left {
display: flex;
align-items: center;
gap: $spacing-md;
min-width: 0;
}
.hero-avatar {
width: 104rpx;
height: 104rpx;
flex-shrink: 0;
border-radius: 50%;
background: $bg-color-hover;
}
.hero-avatar.placeholder {
display: flex;
align-items: center;
justify-content: center;
color: $text-color-main;
font-size: 36rpx;
font-weight: 900;
background: $color-primary-light;
}
.hero-placeholder-text {
font-size: 20rpx;
color: $text-color-muted;
font-weight: 700;
}
.hero-meta {
display: flex;
flex-direction: column;
gap: 6rpx;
min-width: 0;
}
.hero-name,
.hero-phone,
.hero-balance,
.menu-title {
display: block;
}
.hero-name {
color: $text-color-main;
font-size: 34rpx;
font-weight: 700;
line-height: 1.3;
}
.hero-phone {
color: $text-color-muted;
font-size: 24rpx;
font-weight: 500;
}
.hero-balance {
margin-top: 4rpx;
color: $color-primary;
font-size: 24rpx;
font-weight: 700;
}
.hero-arrow,
.menu-arrow {
width: 18rpx;
height: 18rpx;
border-top: 3rpx solid $text-color-light;
border-right: 3rpx solid $text-color-light;
transform: rotate(45deg);
}
.menu-group-card {
margin-top: $spacing-lg;
overflow: hidden;
border: 1rpx solid rgba(241, 245, 249, 0.9);
border-radius: $border-radius-lg;
background-color: $bg-color-main;
box-shadow: 0 10rpx 40rpx rgba(15, 23, 42, 0.02);
}
.menu-item {
min-height: 112rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 $spacing-lg;
border-bottom: 1rpx solid rgba(241, 245, 249, 0.9);
}
.menu-item:last-child {
border-bottom: none;
}
.item-left,
.item-right {
display: flex;
align-items: center;
}
.item-left {
gap: $spacing-md;
}
.item-icon-container {
width: 58rpx;
height: 58rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: $border-radius-base;
background: $bg-color-soft;
color: $text-color-muted;
}
.item-icon-container.order-theme {
background: $color-primary-light;
color: $color-primary;
}
.item-icon-container.profile-theme {
background: $color-success-light;
color: $color-success;
}
.item-icon-container.private-theme {
background: rgba(14, 165, 233, 0.12);
color: #0284C7;
}
.item-icon-container.ticket-theme {
background: rgba(245, 158, 11, 0.14);
color: #D97706;
}
.item-icon-container.logout-theme {
background: $color-danger-light;
color: $color-danger;
}
.icon-order,
.icon-private,
.icon-ticket,
.icon-profile,
.icon-logout {
position: relative;
width: 32rpx;
height: 32rpx;
}
.icon-order {
border: 3rpx solid currentColor;
border-radius: 4rpx;
}
.icon-order::before {
content: '';
position: absolute;
top: -8rpx;
left: 7rpx;
width: 14rpx;
height: 10rpx;
border: 3rpx solid currentColor;
border-bottom: none;
border-radius: 4rpx 4rpx 0 0;
background: inherit;
}
.icon-order-line {
position: absolute;
left: 7rpx;
right: 7rpx;
top: 10rpx;
height: 3rpx;
border-radius: 2rpx;
background: currentColor;
}
.icon-order-line.short {
top: 19rpx;
right: 12rpx;
}
.icon-private {
border: 3rpx solid currentColor;
border-radius: 50%;
}
.icon-private::before,
.icon-private::after {
content: '';
position: absolute;
left: 5rpx;
right: 5rpx;
height: 3rpx;
border-radius: 2rpx;
background: currentColor;
}
.icon-private::before {
top: 9rpx;
}
.icon-private::after {
bottom: 9rpx;
}
.icon-private-net {
position: absolute;
top: 3rpx;
bottom: 3rpx;
left: 12rpx;
width: 4rpx;
border-radius: 2rpx;
background: currentColor;
}
.icon-ticket {
border: 3rpx solid currentColor;
border-radius: 6rpx;
transform: rotate(-8deg);
}
.icon-ticket::before,
.icon-ticket::after {
content: '';
position: absolute;
top: 9rpx;
bottom: 9rpx;
width: 3rpx;
border-radius: 2rpx;
background: currentColor;
}
.icon-ticket::before {
left: 8rpx;
}
.icon-ticket::after {
right: 8rpx;
}
.icon-ticket-dot {
position: absolute;
top: 12rpx;
left: 13rpx;
width: 6rpx;
height: 6rpx;
border-radius: 50%;
background: currentColor;
}
.icon-profile-head {
position: absolute;
top: 3rpx;
left: 10rpx;
width: 12rpx;
height: 12rpx;
border: 3rpx solid currentColor;
border-radius: 50%;
}
.icon-profile-body {
position: absolute;
left: 5rpx;
right: 5rpx;
bottom: 3rpx;
height: 12rpx;
border: 3rpx solid currentColor;
border-radius: 16rpx 16rpx 4rpx 4rpx;
}
.icon-logout-door {
position: absolute;
left: 4rpx;
top: 4rpx;
width: 15rpx;
height: 24rpx;
border: 3rpx solid currentColor;
border-right: none;
border-radius: 4rpx 0 0 4rpx;
}
.icon-logout-arrow {
position: absolute;
right: 2rpx;
top: 14rpx;
width: 20rpx;
height: 3rpx;
border-radius: 2rpx;
background: currentColor;
}
.icon-logout-arrow::after {
content: '';
position: absolute;
right: 0;
top: -6rpx;
width: 10rpx;
height: 10rpx;
border-top: 3rpx solid currentColor;
border-right: 3rpx solid currentColor;
transform: rotate(45deg);
}
.menu-title {
color: $text-color-main;
font-size: 28rpx;
font-weight: 700;
}
.logout .menu-title {
color: $color-danger;
}
</style>