ソースを参照

[Fix 0000] 模拟、自动登录和客制化首页的query冲突问题修复

houzekong 1 日 前
コミット
a7da001e22

+ 4 - 2
src/router/guard/index.ts

@@ -17,7 +17,7 @@ import { createParamMenuGuard } from './paramMenuGuard';
 import { RootRoute } from '/@/router/routes';
 import { useGlobSetting } from '/@/hooks/setting';
 import { PageEnum } from '/@/enums/pageEnum';
-import { getSavedHomeRoute } from '/@/utils/homeStyle';
+import { getSavedHomeRoute, resolveRootRedirect } from '/@/utils/homeStyle';
 // import { unmountMicroApps } from '/@/qiankun';
 
 // Don't change the order of creation
@@ -34,7 +34,9 @@ export function setupRouterGuard(router: Router) {
 }
 const glob = useGlobSetting();
 
-RootRoute.redirect = getSavedHomeRoute() || glob.homePath || PageEnum.BASE_HOME;
+// 根路由 redirect 用"函数 + 合并 query"的形式,否则首页路由自带 query 时会覆盖掉入口地址的 query
+// (如 /?mock-login=1、/?auto-login=1&realname=..&workNo=.. 的参数会丢失),详见 resolveRootRedirect 说明
+RootRoute.redirect = (to) => resolveRootRedirect(getSavedHomeRoute() || glob.homePath || PageEnum.BASE_HOME, to.query);
 /**
  * Hooks for handling page state
  */

+ 35 - 6
src/router/guard/permissionGuard.ts

@@ -1,4 +1,4 @@
-import type { Router, RouteRecordRaw } from 'vue-router';
+import type { Router, RouteRecordRaw, RouteLocationNormalized } from 'vue-router';
 
 import { usePermissionStoreWithOut } from '/@/store/modules/permission';
 
@@ -14,10 +14,11 @@ import { OAUTH2_THIRD_LOGIN_TENANT_ID } from '/@/enums/cacheEnum';
 
 import { useGlobSetting } from '/@/hooks/setting';
 
-import { getSavedHomeRoute } from '/@/utils/homeStyle';
+import { getSavedHomeRoute, resolveRootRedirect } from '/@/utils/homeStyle';
 
 import { isEmpty, assign } from 'lodash-es';
 import { MOCK_LOGIN_URL_QUERY, SKIP_SSO_URL_QUERY } from '../constant';
+import { MOCK_LOGIN_UESRNAME } from '/@/store/constant';
 import { useSso } from '/@/hooks/web/useSso';
 import { useAutoLogin } from '/@/hooks/vent/useAutoLogin';
 import { addBrowseLog } from '@/router/helper/menuHelper';
@@ -41,12 +42,28 @@ const whitePathList: PageEnum[] = [LOGIN_PATH, OAUTH2_LOGIN_PAGE_PATH, SYS_FILES
 //update-end---author:wangshuai ---date:20220629  for:[issues/I5BG1I]vue3不支持auth2登录------------
 const glob = useGlobSetting();
 
+/**
+ * 本次导航是否要求免密(mock)登录。
+ *
+ * 除了看路由 query,还要看地址栏原始地址:vue-router 会在所有导航守卫之前解析路由记录上的 redirect
+ * (详见 utils/homeStyle.ts 的 resolveRootRedirect),某些情况下入口地址的 query 到不了 `to.query`,
+ * 只看 query 会让 `/?mock-login=1` 这类根路径入口被漏掉。
+ */
+function isMockLoginRequest(to: RouteLocationNormalized): boolean {
+  if (to.query[MOCK_LOGIN_URL_QUERY.key] === MOCK_LOGIN_URL_QUERY.val) return true;
+  try {
+    return new URLSearchParams(window.location.search).get(MOCK_LOGIN_URL_QUERY.key) === MOCK_LOGIN_URL_QUERY.val;
+  } catch {
+    return false;
+  }
+}
+
 export function createPermissionGuard(router: Router) {
   const userStore = useUserStoreWithOut();
   const permissionStore = usePermissionStoreWithOut();
   const { doAutoLogin, doTokenLogin, validateRoute, tokenValidateRoute } = useAutoLogin();
   router.beforeEach(async (to, from, next) => {
-    RootRoute.redirect = getSavedHomeRoute() || glob.homePath || PageEnum.BASE_HOME;
+    RootRoute.redirect = (rootTo) => resolveRootRedirect(getSavedHomeRoute() || glob.homePath || PageEnum.BASE_HOME, rootTo.query);
     if (to.query['isNoReverse'] != '1') {
       if (VENT_PARAM['is2DModel']) {
         for (const key in to) {
@@ -91,10 +108,22 @@ export function createPermissionGuard(router: Router) {
       return next({ path: to.fullPath, replace: true, query: to.query });
     }
     // 如果指定了需要模拟登录则执行模拟登录,覆盖原有的登录信息
-    if (to.query[MOCK_LOGIN_URL_QUERY.key] === MOCK_LOGIN_URL_QUERY.val) {
-      await userStore.mockLogin({ goHome: false });
+    // 判断同时看 to.query 与地址栏原始地址(见 isMockLoginRequest 说明)
+    if (isMockLoginRequest(to)) {
+      try {
+        // 凭据缺失时直接判失败:避免发一次注定被拒的空用户名/密码请求
+        if (!MOCK_LOGIN_UESRNAME) throw new Error('mock 登录凭据未注入(VITE_MOCK_LOGIN_USERNAME_CHAR_CODE)');
+        await userStore.mockLogin({ goHome: false });
+        // 登录接口 200 但没带回 token 时 afterLoginAction 会静默返回,这里显式判失败
+        if (!userStore.getToken) throw new Error('mock 登录未取得 token');
+      } catch (error) {
+        // 失败不能让守卫抛错:守卫 await 抛错会中断导航,main.ts 的 router.isReady() 一直不 resolve → 白屏
+        console.error('[mock-login] 免密登录失败,转正常登录流程', error);
+        return next({ path: LOGIN_PATH, replace: true, query: { redirect: to.fullPath } });
+      }
       delete to.query[MOCK_LOGIN_URL_QUERY.key];
-      return next({ path: to.fullPath, replace: true, query: to.query });
+      // 用 path + 清洗后的 query 跳转:to.fullPath 里还留着 mock-login,交给 query 覆盖容易看漏
+      return next({ path: to.path, hash: to.hash, replace: true, query: to.query });
     }
 
     const token = userStore.getToken;

+ 3 - 1
src/router/routes/index.ts

@@ -7,6 +7,7 @@ import { LAYOUT } from '/@/router/constant';
 import { PageEnum } from '/@/enums/pageEnum';
 import { t } from '/@/hooks/web/useI18n';
 import { useGlobSetting, getRemoteSetting } from '/@/hooks/setting';
+import { resolveRootRedirect } from '/@/utils/homeStyle';
 
 // const modules = import.meta.glob('./modules/**/*.ts', { eager: true });
 
@@ -26,7 +27,8 @@ export const RootRoute: AppRouteRecordRaw = await (async () => {
   return {
     path: '/',
     name: 'Root',
-    redirect: glob.homePath || PageEnum.BASE_HOME,
+    // 用"函数 + 合并 query"的形式,避免首页路由自带 query 时覆盖入口地址的 query(详见 resolveRootRedirect 说明)
+    redirect: (to) => resolveRootRedirect(glob.homePath || PageEnum.BASE_HOME, to.query),
     meta: {
       title: 'Root',
     },

+ 24 - 0
src/utils/homeStyle.ts

@@ -1,5 +1,7 @@
+import type { LocationQueryRaw } from 'vue-router';
 import { ThemeEnum } from '/@/enums/appEnum';
 import { HOME_STYLE_KEY } from '/@/enums/cacheEnum';
+import { PageEnum } from '/@/enums/pageEnum';
 
 /**
  * 首页风格选择:localStorage 的存放与读取工具。
@@ -43,3 +45,25 @@ export function saveHomeStyle(option: HomeStyleOption | null): void {
 export function getSavedHomeRoute(): string {
   return getSavedHomeStyle()?.route || '';
 }
+
+/**
+ * 构造根路由('/')的 redirect 目标:把"目标路径自带的 query"和"入口地址的 query"合并后返回。
+ *
+ * 为什么不能直接用字符串(如 '/xxx?a=1'):
+ * vue-router 会在所有导航守卫之前解析路由记录上的 redirect(内部 handleRedirectRecord),
+ * 当 redirect 是自身带 '?' 的字符串时,它解析出的 query 会**覆盖**原始地址的 query。
+ * 因此当首页路由(后端 systemHome 或本地保存的首页风格)形如
+ * '/monitorChannel/monitor-firedoor?type=tunMonitor&deviceType=door' 时,从根路径进来的
+ * `/?mock-login=1`、`/?auto-login=1&realname=..&workNo=..`、`/?token=..` 等参数会在守卫里完全看不到。
+ * 返回对象并显式合并 query 即可保住入口参数;同名参数以入口为准。
+ *
+ * @param target 首页路由(可自带 query)
+ * @param query 入口地址的 query(通常是守卫里的 to.query)
+ */
+export function resolveRootRedirect(target: string, query?: LocationQueryRaw): { path: string; query: LocationQueryRaw } {
+  const [path, search = ''] = String(target || '').split('?');
+  return {
+    path: path || PageEnum.BASE_HOME,
+    query: { ...Object.fromEntries(new URLSearchParams(search)), ...(query || {}) },
+  };
+}