index.vue
5.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
<template>
<view class="min-h-screen bg-[#F9FAFB] pb-[200rpx] flex flex-col items-center">
<!-- Header -->
<NavHeader title="我的" />
<!-- User Info Card -->
<!-- Width: 353px -> 706rpx, Height: 124px -> 248rpx -->
<!-- Background image from design -->
<view
class="w-[706rpx] h-[248rpx] mt-[40rpx] bg-white rounded-[24rpx] flex items-center px-[40rpx]"
@tap="go('/pages/avatar/index')"
>
<!-- Avatar -->
<view class="w-[160rpx] h-[160rpx] rounded-full overflow-hidden border-2 border-white shadow-sm shrink-0">
<img class="w-full h-full object-cover" :src="userInfo?.avatar_url || defaultAvatar" />
</view>
<!-- Info -->
<view class="ml-[32rpx] flex-1 flex flex-col justify-center">
<text class="text-[36rpx] font-bold text-gray-800 mb-[8rpx]">{{ userInfo?.name || '加载中...' }}</text>
<text class="text-[28rpx] text-gray-500 mb-[4rpx]">工号: {{ userInfo?.employee_no || '--' }}</text>
<text class="text-[24rpx] text-gray-400">点击修改头像</text>
</view>
<!-- Arrow -->
<IconFont name="rect-right" size="20" color="#9CA3AF" />
</view>
<!-- Menu List -->
<!-- Width: 353px -> 706rpx, Radius: 12px -> 24rpx, Padding: 16px -> 32rpx -->
<view class="w-[706rpx] bg-white rounded-[24rpx] p-[32rpx] mt-[32rpx]">
<view
v-for="(item, index) in menuItems"
:key="index"
class="flex flex-col"
@tap="handleMenuClick(item)"
>
<view class="flex items-center justify-between py-[24rpx]">
<view class="flex items-center">
<!-- Icon Size: 40px -> 80rpx. Using IconFont to match request, centered in a box if needed, or just large icon -->
<!-- Design had 40px images. I'll use 32px (64rpx) IconFont for balance or 40px if needed. -->
<view class="w-[80rpx] h-[80rpx] bg-blue-50 rounded-[16rpx] flex items-center justify-center mr-[24rpx]">
<IconFont :name="item.icon" size="24" color="#2563EB" />
</view>
<text class="text-[32rpx] text-gray-800">{{ item.title }}</text>
</view>
<IconFont name="rectRight" size="16" color="#9CA3AF" />
</view>
<!-- Separator -->
<view v-if="index < menuItems.length - 1" class="h-[2rpx] bg-gray-100 w-full"></view>
</view>
</view>
<!-- Logout Button -->
<view class="w-[730rpx] rounded-[24rpx] p-[32rpx] mt-[32rpx]">
<view
class="flex items-center justify-center py-[20rpx] px-[32rpx] rounded-[16rpx] border-[2rpx] border-[#FEE2E2] bg-[#FEF2F2] active:opacity-70"
@tap="handleLogout"
>
<IconFont name="issue" size="18" color="#EF4444" class="mr-[12rpx]" />
<text class="text-[28rpx] text-[#EF4444] font-medium">退出登录</text>
</view>
</view>
<!-- TabBar -->
<TabBar current="me" />
</view>
</template>
<script setup>
import { computed } from 'vue'
import { useGo } from '@/hooks/useGo'
import { useUserStore } from '@/stores/user'
import IconFont from '@/components/IconFont.vue'
import TabBar from '@/components/TabBar.vue'
import NavHeader from '@/components/NavHeader.vue'
import Taro, { useLoad, useDidShow } from '@tarojs/taro'
import defaultAvatar from '@/assets/images/icon/avatar.svg'
const go = useGo()
const userStore = useUserStore()
/**
* @description 用户信息(从 userStore 读取,自动响应变化)
*/
const userInfo = computed(() => userStore.userInfo)
/**
* @description 页面加载时获取用户信息(首次进入)
*/
useLoad(() => {
// 只在未登录时请求,避免与首页的 useDidShow 重复请求
if (!userStore.isLoggedIn) {
userStore.fetchUserInfo()
}
})
/**
* @description 页面显示时刷新用户信息(从其他页面返回时)
* @description 例如:从头像设置页面保存后返回,需要刷新显示新头像
* @description 带防抖机制(5秒内不重复请求)
*/
useDidShow(() => {
// userStore.fetchUserInfo() 内部有防抖逻辑,不会频繁请求
userStore.fetchUserInfo()
})
const menuItems = [
{ title: '我的计划书', icon: 'order', path: '/pages/plan/index' },
{ title: '我的消息', icon: 'message', path: '/pages/message/index' },
{ title: '我的收藏', icon: 'star', path: '/pages/favorites/index' },
{ title: '帮助中心', icon: 'service', path: '/pages/help-center/index' },
{ title: '意见反馈', icon: 'edit', path: '/pages/feedback-list/index' }
]
const handleMenuClick = (item) => {
if (item.path) {
go(item.path)
} else if (item.action === 'toast') {
Taro.showToast({
title: '功能开发中',
icon: 'none'
})
}
}
/**
* 退出登录
* @description 调用 logoutAPI 解绑 openid,清除本地状态
*/
const handleLogout = async () => {
Taro.showModal({
title: '提示',
content: '确定要退出登录吗?',
confirmText: '确定',
cancelText: '取消',
confirmColor: '#EF4444',
success: async (res) => {
if (res.confirm) {
// 显示加载提示
Taro.showLoading({
title: '退出中...',
mask: true
})
try {
// 调用 userStore 的 logout 方法(会调用 logoutAPI)
await userStore.logout()
// 清除 mainStore 中的用户信息
store.changeUserInfo(null)
Taro.hideLoading()
// 跳转到首页
Taro.reLaunch({
url: '/pages/index/index'
})
Taro.showToast({
title: '已退出登录',
icon: 'success'
})
} catch (error) {
Taro.hideLoading()
console.error('退出登录失败:', error)
Taro.showToast({
title: error.message || '退出失败,请重试',
icon: 'none'
})
}
}
}
})
}
</script>
<style lang="less">
/* No custom CSS needed, all Tailwind */
</style>