|
|
@@ -45,16 +45,32 @@ const glob = useGlobSetting();
|
|
|
/**
|
|
|
* 本次导航是否要求免密(mock)登录。
|
|
|
*
|
|
|
- * 除了看路由 query,还要看地址栏原始地址:vue-router 会在所有导航守卫之前解析路由记录上的 redirect
|
|
|
- * (详见 utils/homeStyle.ts 的 resolveRootRedirect),某些情况下入口地址的 query 到不了 `to.query`,
|
|
|
- * 只看 query 会让 `/?mock-login=1` 这类根路径入口被漏掉。
|
|
|
+ * 判断只认导航对象(to.query 与 to.redirectedFrom.query),**不能**读 window.location.search:
|
|
|
+ * 地址栏要等本次导航真正完成才会更新,而"mock 登录 → next(目标) → 守卫再次执行"属于同一串导航
|
|
|
+ * (next() 出去的目标带 redirectedFrom 指回同一个 to 对象),这段期间地址栏还是旧地址,
|
|
|
+ * 读它会让每次守卫执行都重新命中这里 —— 表现为 /sys/login 与 /sys/user/getUserInfo 反复请求,
|
|
|
+ * 最终被 vue-router 以 "Detected a possibly infinite redirection in a navigation guard" 中止导航。
|
|
|
+ *
|
|
|
+ * redirectedFrom 这一跳用于覆盖"入口地址的 query 被中间重定向覆盖"(见 utils/homeStyle.ts 的说明),
|
|
|
+ * 这种入口参数只能消费一次(mockLoginEntryConsumed),否则同一串导航里会被反复命中。
|
|
|
*/
|
|
|
+let mockLoginEntryConsumed = false;
|
|
|
+
|
|
|
function isMockLoginRequest(to: RouteLocationNormalized): boolean {
|
|
|
- if (to.query[MOCK_LOGIN_URL_QUERY.key] === MOCK_LOGIN_URL_QUERY.val) return true;
|
|
|
+ if (to.query && to.query[MOCK_LOGIN_URL_QUERY.key] === MOCK_LOGIN_URL_QUERY.val) return true;
|
|
|
+ if (mockLoginEntryConsumed) return false;
|
|
|
+ return to.redirectedFrom?.query?.[MOCK_LOGIN_URL_QUERY.key] === MOCK_LOGIN_URL_QUERY.val;
|
|
|
+}
|
|
|
+
|
|
|
+/** 把地址栏里的 mock-login 摘掉:避免刷新页面或后续导航(地址栏更新滞后)再次命中免密登录 */
|
|
|
+function clearMockLoginFromUrl(): void {
|
|
|
try {
|
|
|
- return new URLSearchParams(window.location.search).get(MOCK_LOGIN_URL_QUERY.key) === MOCK_LOGIN_URL_QUERY.val;
|
|
|
- } catch {
|
|
|
- return false;
|
|
|
+ const url = new URL(window.location.href);
|
|
|
+ if (!url.searchParams.has(MOCK_LOGIN_URL_QUERY.key)) return;
|
|
|
+ url.searchParams.delete(MOCK_LOGIN_URL_QUERY.key);
|
|
|
+ window.history.replaceState(window.history.state, '', `${url.pathname}${url.search}${url.hash}`);
|
|
|
+ } catch (error) {
|
|
|
+ console.warn('[mock-login] 地址栏参数清理失败,不影响登录流程', error);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -108,8 +124,13 @@ export function createPermissionGuard(router: Router) {
|
|
|
return next({ path: to.fullPath, replace: true, query: to.query });
|
|
|
}
|
|
|
// 如果指定了需要模拟登录则执行模拟登录,覆盖原有的登录信息
|
|
|
- // 判断同时看 to.query 与地址栏原始地址(见 isMockLoginRequest 说明)
|
|
|
+ // 判断看 to.query 与 to.redirectedFrom 链(见 isMockLoginRequest 说明)
|
|
|
if (isMockLoginRequest(to)) {
|
|
|
+ // 参数只消费一次:先摘掉,确保随后 next() 产生的导航不会再命中这里;
|
|
|
+ // 同时清理地址栏,避免刷新页面又触发一遍
|
|
|
+ mockLoginEntryConsumed = true;
|
|
|
+ delete to.query[MOCK_LOGIN_URL_QUERY.key];
|
|
|
+ clearMockLoginFromUrl();
|
|
|
try {
|
|
|
// 凭据缺失时直接判失败:避免发一次注定被拒的空用户名/密码请求
|
|
|
if (!MOCK_LOGIN_UESRNAME) throw new Error('mock 登录凭据未注入(VITE_MOCK_LOGIN_USERNAME_CHAR_CODE)');
|
|
|
@@ -119,9 +140,10 @@ export function createPermissionGuard(router: Router) {
|
|
|
} catch (error) {
|
|
|
// 失败不能让守卫抛错:守卫 await 抛错会中断导航,main.ts 的 router.isReady() 一直不 resolve → 白屏
|
|
|
console.error('[mock-login] 免密登录失败,转正常登录流程', error);
|
|
|
- return next({ path: LOGIN_PATH, replace: true, query: { redirect: to.fullPath } });
|
|
|
+ // redirect 用清洗过的 query 重新解析:to.fullPath 里还留着 mock-login,登录成功回跳时会再次触发免密登录
|
|
|
+ const cleanFullPath = router.resolve({ path: to.path, hash: to.hash, query: to.query }).fullPath;
|
|
|
+ return next({ path: LOGIN_PATH, replace: true, query: { redirect: cleanFullPath } });
|
|
|
}
|
|
|
- delete to.query[MOCK_LOGIN_URL_QUERY.key];
|
|
|
// 用 path + 清洗后的 query 跳转:to.fullPath 里还留着 mock-login,交给 query 覆盖容易看漏
|
|
|
return next({ path: to.path, hash: to.hash, replace: true, query: to.query });
|
|
|
}
|