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>
Showing
4 changed files
with
38 additions
and
36 deletions
| ... | @@ -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 | ... | ... |
-
Please register or login to post a comment