hookehuyr

fix: 优化用户信息获取,避免重复请求

性能优化:
- User Store 添加防抖机制(5秒内不重复请求)
- 修复 useShow 为 useDidShow(使用正确的 Taro 生命周期)
- "我的"页面统一使用 userStore.fetchUserInfo()
- 移除本地 userInfo ref 和 fetchUserProfile 函数
- 移除对 mainStore 和 getProfileAPI 的直接依赖

配置优化:
- 添加 @/config 路径别名到 config/index.js

效果:
- 从首页跳转到"我的"页面时,请求次数从 2 次减少到 1 次
- 页面间快速切换不会触发重复请求
- 统一状态管理,代码更简洁

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
...@@ -36,6 +36,7 @@ export default defineConfig(async (merge) => { ...@@ -36,6 +36,7 @@ export default defineConfig(async (merge) => {
36 "@/api": path.resolve(__dirname, "../src/api"), 36 "@/api": path.resolve(__dirname, "../src/api"),
37 "@/stores": path.resolve(__dirname, "../src/stores"), 37 "@/stores": path.resolve(__dirname, "../src/stores"),
38 "@/hooks": path.resolve(__dirname, "../src/hooks"), 38 "@/hooks": path.resolve(__dirname, "../src/hooks"),
39 + "@/config": path.resolve(__dirname, "../src/config"),
39 }, 40 },
40 sourceRoot: 'src', 41 sourceRoot: 'src',
41 outputRoot: 'dist', 42 outputRoot: 'dist',
......
...@@ -164,7 +164,7 @@ ...@@ -164,7 +164,7 @@
164 164
165 <script setup> 165 <script setup>
166 import { ref, shallowRef } from 'vue'; 166 import { ref, shallowRef } from 'vue';
167 -import Taro, { useShareAppMessage, useLoad, useShow } from '@tarojs/taro'; 167 +import Taro, { useShareAppMessage, useLoad, useDidShow } from '@tarojs/taro';
168 import { useGo } from '@/hooks/useGo'; 168 import { useGo } from '@/hooks/useGo';
169 import { useListItemClick, ListType } from '@/composables/useListItemClick'; 169 import { useListItemClick, ListType } from '@/composables/useListItemClick';
170 import { getDocumentIcon, getDocumentLabel } from '@/utils/documentIcons'; 170 import { getDocumentIcon, getDocumentLabel } from '@/utils/documentIcons';
...@@ -342,7 +342,7 @@ useLoad(() => { ...@@ -342,7 +342,7 @@ useLoad(() => {
342 }); 342 });
343 343
344 // 页面显示时刷新用户信息(更新 TabBar 红点状态) 344 // 页面显示时刷新用户信息(更新 TabBar 红点状态)
345 -useShow(() => { 345 +useDidShow(() => {
346 // 只在已登录状态下刷新 346 // 只在已登录状态下刷新
347 if (userStore.isLoggedIn) { 347 if (userStore.isLoggedIn) {
348 userStore.fetchUserInfo().catch(err => { 348 userStore.fetchUserInfo().catch(err => {
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
12 > 12 >
13 <!-- Avatar --> 13 <!-- Avatar -->
14 <view class="w-[160rpx] h-[160rpx] rounded-full overflow-hidden border-2 border-white shadow-sm shrink-0"> 14 <view class="w-[160rpx] h-[160rpx] rounded-full overflow-hidden border-2 border-white shadow-sm shrink-0">
15 - <img class="w-full h-full object-cover" :src="userInfo?.avatar?.src || defaultAvatar" /> 15 + <img class="w-full h-full object-cover" :src="userInfo?.avatar_url || defaultAvatar" />
16 </view> 16 </view>
17 17
18 <!-- Info --> 18 <!-- Info -->
...@@ -68,62 +68,41 @@ ...@@ -68,62 +68,41 @@
68 </template> 68 </template>
69 69
70 <script setup> 70 <script setup>
71 -import { ref } from 'vue' 71 +import { computed } from 'vue'
72 import { useGo } from '@/hooks/useGo' 72 import { useGo } from '@/hooks/useGo'
73 -import { mainStore } from '@/stores/main'
74 import { useUserStore } from '@/stores/user' 73 import { useUserStore } from '@/stores/user'
75 import IconFont from '@/components/IconFont.vue' 74 import IconFont from '@/components/IconFont.vue'
76 import TabBar from '@/components/TabBar.vue' 75 import TabBar from '@/components/TabBar.vue'
77 import NavHeader from '@/components/NavHeader.vue' 76 import NavHeader from '@/components/NavHeader.vue'
78 import Taro, { useLoad, useDidShow } from '@tarojs/taro' 77 import Taro, { useLoad, useDidShow } from '@tarojs/taro'
79 -import { getProfileAPI } from '@/api/user'
80 import defaultAvatar from '@/assets/images/icon/avatar.svg' 78 import defaultAvatar from '@/assets/images/icon/avatar.svg'
81 79
82 const go = useGo() 80 const go = useGo()
83 -const store = mainStore()
84 const userStore = useUserStore() 81 const userStore = useUserStore()
85 82
86 /** 83 /**
87 - * @description 用户信息(响应式) 84 + * @description 用户信息(从 userStore 读取,自动响应变化)
88 - * @type {import('vue').Ref<{id?: number, name?: string, avatar_url?: string}|null>}
89 */ 85 */
90 -const userInfo = ref(null) 86 +const userInfo = computed(() => userStore.userInfo)
91 -
92 -/**
93 - * @description 获取用户个人信息
94 - * @description 进入页面时调用,401 自动跳转登录页(由 request.js 拦截器处理)
95 - * @returns {Promise<void>}
96 - */
97 -const fetchUserProfile = async () => {
98 - try {
99 - const res = await getProfileAPI()
100 - if (res.code === 1 && res.data?.user) {
101 - // 更新响应式数据
102 - userInfo.value = res.data.user
103 - // 更新全局状态
104 - store.changeUserInfo(res.data.user)
105 - } else {
106 - // 接口返回失败(非 401,因为 401 已被 request.js 拦截器处理)
107 - console.warn('获取用户信息失败:', res.msg)
108 - }
109 - } catch (err) {
110 - console.error('获取用户信息异常:', err)
111 - }
112 -}
113 87
114 /** 88 /**
115 * @description 页面加载时获取用户信息(首次进入) 89 * @description 页面加载时获取用户信息(首次进入)
116 */ 90 */
117 useLoad(() => { 91 useLoad(() => {
118 - fetchUserProfile() 92 + // 只在未登录时请求,避免与首页的 useDidShow 重复请求
93 + if (!userStore.isLoggedIn) {
94 + userStore.fetchUserInfo()
95 + }
119 }) 96 })
120 97
121 /** 98 /**
122 * @description 页面显示时刷新用户信息(从其他页面返回时) 99 * @description 页面显示时刷新用户信息(从其他页面返回时)
123 * @description 例如:从头像设置页面保存后返回,需要刷新显示新头像 100 * @description 例如:从头像设置页面保存后返回,需要刷新显示新头像
101 + * @description 带防抖机制(5秒内不重复请求)
124 */ 102 */
125 useDidShow(() => { 103 useDidShow(() => {
126 - fetchUserProfile() 104 + // userStore.fetchUserInfo() 内部有防抖逻辑,不会频繁请求
105 + userStore.fetchUserInfo()
127 }) 106 })
128 107
129 const menuItems = [ 108 const menuItems = [
......
...@@ -26,6 +26,12 @@ export const useUserStore = defineStore('user', () => { ...@@ -26,6 +26,12 @@ export const useUserStore = defineStore('user', () => {
26 /** 加载状态 */ 26 /** 加载状态 */
27 const loading = ref(false) 27 const loading = ref(false)
28 28
29 + /** 上次获取用户信息的时间戳(用于防抖) */
30 + let lastFetchTime = 0
31 +
32 + /** 防抖时间间隔(毫秒) */
33 + const FETCH_DEBOUNCE_TIME = 5000
34 +
29 // ========== 方法 ========== 35 // ========== 方法 ==========
30 36
31 /** 37 /**
...@@ -79,24 +85,40 @@ export const useUserStore = defineStore('user', () => { ...@@ -79,24 +85,40 @@ export const useUserStore = defineStore('user', () => {
79 85
80 /** 86 /**
81 * 获取用户信息 87 * 获取用户信息
82 - * @description 调用 getProfileAPI 获取用户信息 88 + * @description 调用 getProfileAPI 获取用户信息,带防抖机制(5秒内不重复请求)
89 + * @param {boolean} force - 是否强制刷新(忽略防抖)
83 * @throws {Error} 获取失败时抛出错误 90 * @throws {Error} 获取失败时抛出错误
84 * 91 *
85 * @example 92 * @example
86 * await userStore.fetchUserInfo() 93 * await userStore.fetchUserInfo()
94 + * await userStore.fetchUserInfo(true) // 强制刷新
87 */ 95 */
88 - async function fetchUserInfo() { 96 + async function fetchUserInfo(force = false) {
97 + // 防抖检查:如果不是强制刷新,且距离上次请求不足 5 秒,则跳过
98 + if (!force) {
99 + const now = Date.now()
100 + if (now - lastFetchTime < FETCH_DEBOUNCE_TIME) {
101 + console.log('[UserStore] 跳过频繁的用户信息请求')
102 + return
103 + }
104 + }
105 +
89 try { 106 try {
107 + loading.value = true
90 const res = await getProfileAPI() 108 const res = await getProfileAPI()
91 109
92 if (res.code === 1) { 110 if (res.code === 1) {
93 userInfo.value = res.data.user 111 userInfo.value = res.data.user
112 + // 更新最后请求时间
113 + lastFetchTime = Date.now()
94 } else { 114 } else {
95 throw new Error(res.msg || '获取用户信息失败') 115 throw new Error(res.msg || '获取用户信息失败')
96 } 116 }
97 } catch (err) { 117 } catch (err) {
98 console.error('获取用户信息失败:', err) 118 console.error('获取用户信息失败:', err)
99 throw err 119 throw err
120 + } finally {
121 + loading.value = false
100 } 122 }
101 } 123 }
102 124
......