hookehuyr

fix(user): 修复冷启动时接口抢在登录态前请求的竞态问题

- 新增 waitForLoginStatusReady 方法供外部等待登录态就绪
- checkLoginStatus 使用 Promise 缓存避免并发重复调用
- 首页 fetchHotArticles 调用前等待登录态初始化完成
- 清除首页残留的导航栏标题配置

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 /* 1 /*
2 * @Date: 2025-06-28 10:33:00 2 * @Date: 2025-06-28 10:33:00
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2026-01-30 10:07:28 4 + * @LastEditTime: 2026-05-28 17:28:54
5 * @FilePath: /manulife-weapp/src/app.config.js 5 * @FilePath: /manulife-weapp/src/app.config.js
6 * @Description: 小程序配置文件 6 * @Description: 小程序配置文件
7 */ 7 */
...@@ -57,7 +57,7 @@ export default { ...@@ -57,7 +57,7 @@ export default {
57 window: { 57 window: {
58 backgroundTextStyle: 'light', 58 backgroundTextStyle: 'light',
59 navigationBarBackgroundColor: '#fff', 59 navigationBarBackgroundColor: '#fff',
60 - navigationBarTitleText: '西园寺预约', 60 + navigationBarTitleText: '',
61 navigationBarTextStyle: 'black', 61 navigationBarTextStyle: 'black',
62 }, 62 },
63 // 开发环境开启调试模式 63 // 开发环境开启调试模式
......
...@@ -355,6 +355,10 @@ const hotArticlesLoading = ref(false); ...@@ -355,6 +355,10 @@ const hotArticlesLoading = ref(false);
355 const fetchHotArticles = async () => { 355 const fetchHotArticles = async () => {
356 try { 356 try {
357 hotArticlesLoading.value = true; 357 hotArticlesLoading.value = true;
358 +
359 + // 冷启动时先等登录态初始化完成,避免 week_hot 抢在 session 建立前发出请求
360 + await userStore.waitForLoginStatusReady();
361 +
358 const res = await weekHotAPI({ 362 const res = await weekHotAPI({
359 page: 0, 363 page: 0,
360 limit: 10 364 limit: 10
......
...@@ -29,9 +29,15 @@ export const useUserStore = defineStore('user', () => { ...@@ -29,9 +29,15 @@ export const useUserStore = defineStore('user', () => {
29 /** 加载状态 */ 29 /** 加载状态 */
30 const loading = ref(false) 30 const loading = ref(false)
31 31
32 + /** 登录态是否已经完成过首次检查 */
33 + const loginStatusChecked = ref(false)
34 +
32 /** 上次获取用户信息的时间戳(用于防抖) */ 35 /** 上次获取用户信息的时间戳(用于防抖) */
33 let lastFetchTime = 0 36 let lastFetchTime = 0
34 37
38 + /** 进行中的登录态检查 Promise,用于并发复用 */
39 + let loginStatusPromise = null
40 +
35 /** 防抖时间间隔(毫秒) */ 41 /** 防抖时间间隔(毫秒) */
36 const FETCH_DEBOUNCE_TIME = 5000 42 const FETCH_DEBOUNCE_TIME = 5000
37 43
...@@ -48,41 +54,67 @@ export const useUserStore = defineStore('user', () => { ...@@ -48,41 +54,67 @@ export const useUserStore = defineStore('user', () => {
48 * await userStore.checkLoginStatus() 54 * await userStore.checkLoginStatus()
49 */ 55 */
50 async function checkLoginStatus() { 56 async function checkLoginStatus() {
51 - loading.value = true 57 + if (loginStatusPromise) {
52 - 58 + return loginStatusPromise
53 - try { 59 + }
54 - // 1. 确保 openid 已授权并尝试自动登录
55 - const user = await ensureOpenidAuthorized()
56 60
57 - if (user) { 61 + loginStatusPromise = (async () => {
58 - // miniProgramAuthAPI 返回了用户信息,说明已自动登录 62 + loading.value = true
59 - userInfo.value = user
60 - isOpenid.value = true
61 - isLoggedIn.value = true
62 - return
63 - }
64 63
65 - // 2. 查询登录状态 64 + try {
66 - const res = await loginStatusAPI() 65 + // 1. 确保 openid 已授权并尝试自动登录
66 + const user = await ensureOpenidAuthorized()
67 67
68 - if (res.code === 1) { 68 + if (user) {
69 - isOpenid.value = res.data.is_openid 69 + // miniProgramAuthAPI 返回了用户信息,说明已自动登录
70 - isLoggedIn.value = res.data.is_login 70 + userInfo.value = user
71 + isOpenid.value = true
72 + isLoggedIn.value = true
73 + return
74 + }
71 75
72 - // 3. 如果已登录,获取用户信息 76 + // 2. 查询登录状态
73 - if (isLoggedIn.value) { 77 + const res = await loginStatusAPI()
74 - await fetchUserInfo() 78 +
79 + if (res.code === 1) {
80 + isOpenid.value = res.data.is_openid
81 + isLoggedIn.value = res.data.is_login
82 +
83 + // 3. 如果已登录,获取用户信息
84 + if (isLoggedIn.value) {
85 + await fetchUserInfo()
86 + }
87 + // 注意:这里不跳转登录页,让用户可以浏览小程序
88 + // 当用户操作触发接口返回 401 时,会自动跳转登录页
89 + } else {
90 + throw new Error(res.msg || '查询登录状态失败')
75 } 91 }
76 - // 注意:这里不跳转登录页,让用户可以浏览小程序 92 + } catch (err) {
77 - // 当用户操作触发接口返回 401 时,会自动跳转登录页 93 + console.error('检查登录状态失败:', err)
78 - } else { 94 + throw err
79 - throw new Error(res.msg || '查询登录状态失败') 95 + } finally {
96 + loading.value = false
97 + loginStatusChecked.value = true
98 + loginStatusPromise = null
80 } 99 }
81 - } catch (err) { 100 + })()
82 - console.error('检查登录状态失败:', err) 101 +
83 - throw err 102 + return loginStatusPromise
84 - } finally { 103 + }
85 - loading.value = false 104 +
105 + /**
106 + * 等待登录态初始化完成
107 + * @description 冷启动时复用 App.onLaunch 的登录态检查,避免页面抢跑请求
108 + * @returns {Promise<void>}
109 + */
110 + async function waitForLoginStatusReady() {
111 + if (loginStatusPromise) {
112 + await loginStatusPromise
113 + return
114 + }
115 +
116 + if (!loginStatusChecked.value) {
117 + await checkLoginStatus()
86 } 118 }
87 } 119 }
88 120
...@@ -235,12 +267,14 @@ export const useUserStore = defineStore('user', () => { ...@@ -235,12 +267,14 @@ export const useUserStore = defineStore('user', () => {
235 isOpenid, 267 isOpenid,
236 isLoggedIn, 268 isLoggedIn,
237 loading, 269 loading,
270 + loginStatusChecked,
238 271
239 // 计算属性 272 // 计算属性
240 tabBarBadges, 273 tabBarBadges,
241 274
242 // 方法 275 // 方法
243 checkLoginStatus, 276 checkLoginStatus,
277 + waitForLoginStatusReady,
244 fetchUserInfo, 278 fetchUserInfo,
245 login, 279 login,
246 logout 280 logout
......