ZGC4846 4 дней назад
Родитель
Сommit
2d69b6024c
5 измененных файлов с 131 добавлено и 221 удалено
  1. 20 21
      qiankun_config/store.js
  2. 5 13
      src/main.js
  3. 65 95
      src/router/index.js
  4. 13 23
      src/store/modules/user.js
  5. 28 69
      src/utils/request.js

+ 20 - 21
qiankun_config/store.js

@@ -1,36 +1,35 @@
 import store from '../src/store';
 
 /**
- * 同步门户下发的主题与用户信息(对齐 EOM qiankun_config/store.js)
- * @param {object} state 可为 vuex store.state,或 onGlobalStateChange 下发的 state
+ * 同步门户下发的主题与用户信息(与 EOM qiankun_config/store.js 一致)
  */
 export default function initParentStore(state) {
-  if (!state) return;
-
-  // 兼容传入整个 vuex store
-  const data = state.state && (state.theme || state.user) ? state.state : state;
+  if (!state) {
+    return;
+  }
 
-  if (data.theme) {
-    for (const key in data.theme) {
-      store.commit('theme/SET', { key, value: data.theme[key] });
+  // 主题(门户可能传 store 或 store.state)
+  const theme = state.theme || state.state?.theme;
+  if (theme) {
+    for (const key in theme) {
+      store.commit('theme/SET', { key, value: theme[key] });
     }
-    if (data.theme.color != null) {
-      store.dispatch('theme/setColor', data.theme.color);
+    if (theme.color != null) {
+      store.dispatch('theme/setColor', theme.color);
     }
-    if (data.theme.weakMode != null) {
-      store.dispatch('theme/setWeakMode', data.theme.weakMode);
+    if (theme.weakMode != null) {
+      store.dispatch('theme/setWeakMode', theme.weakMode);
     }
-    if (data.theme.styleResponsive != null) {
-      store.dispatch('theme/setStyleResponsive', data.theme.styleResponsive);
+    if (theme.styleResponsive != null) {
+      store.dispatch('theme/setStyleResponsive', theme.styleResponsive);
     }
   }
 
-  if (data.user?.info) {
-    store.commit('user/setUserInfo', data.user.info);
+  const user = state.user || state.state?.user;
+  if (user?.info) {
+    store.commit('user/setUserInfo', user.info);
   }
-
-  // 与 EOM 一致:同步按钮权限
-  if (data.user?.authorities) {
-    store.commit('user/setAuthorities', data.user.authorities);
+  if (user?.authorities) {
+    store.commit('user/setAuthorities', user.authorities);
   }
 }

+ 5 - 13
src/main.js

@@ -3,12 +3,12 @@ import Vue from 'vue';
 import EleAdmin from 'ele-admin';
 import VueClipboard from 'vue-clipboard2';
 import App from './App.vue';
-import router, { resetRouter } from './router';
+import router from './router';
 import store from './store';
 import permission from './utils/permission';
 import initParentStore from '../qiankun_config/store';
 import i18n from './i18n';
-import { MAP_KEY, LICENSE_CODE, SYSTEM_NAME } from '@/config/setting';
+import { MAP_KEY, LICENSE_CODE } from '@/config/setting';
 import { installMessageGuard } from '@/utils/message';
 import HeaderTitle from '@/components/header-title';
 import DictSelection from '@/components/Dict/DictSelection';
@@ -99,29 +99,21 @@ if (!window.__POWERED_BY_QIANKUN__) {
 }
 
 export async function bootstrap() {
-  console.info('[page-hr] bootstrap');
+  console.log('[page-hr] vue app bootstraped');
 }
 
+/** 与 EOM main.js mount 对齐 */
 export async function mount(props) {
   console.log('[page-hr] props from main framework', props);
   Vue.prototype.$portalStore = props.store;
 
-  // 与 EOM 一致:只同步主题/用户,绝不改门户 menus
-  initParentStore(props.store?.state || props.store);
+  initParentStore(props.store);
   if (typeof props.onGlobalStateChange === 'function') {
     props.onGlobalStateChange((state) => {
       initParentStore(state);
     });
   }
 
-  // 乾坤下强制校正 router base 为 /page-hr/
-  if (
-    window.__POWERED_BY_QIANKUN__ &&
-    !String(router.options?.base || '').includes(`page-${SYSTEM_NAME}`)
-  ) {
-    resetRouter();
-  }
-
   render(props);
 }
 

+ 65 - 95
src/router/index.js

@@ -10,135 +10,101 @@ import {
   LAYOUT_PATH,
   SYSTEM_NAME,
   HOME_PATH,
-  isHomeAliasPath,
 } from '@/config/setting';
 import store from '@/store';
 import { getToken, setToken, getCurrentUser } from '@/utils/token-util';
 import { routes, getMenuRoutes } from './routes';
 import { changeRole } from '@/api/layout';
-import { rewriteLegacyHrMenus } from '@/utils/menu-rewrite';
 
 Vue.use(VueRouter);
 
-/**
- * 乾坤:/page-hr/(同 EOM 的 /page-eos/)
- * 独立:/hr/(同 EOM 的 /eos/)
- */
-export function getAppRouterBase() {
-  return window.__POWERED_BY_QIANKUN__
+const router = new VueRouter({
+  // 与 EOM 一致:乾坤 /page-hr/ ,独立 /hr/
+  base: window.__POWERED_BY_QIANKUN__
     ? `/page-${SYSTEM_NAME}/`
-    : `/${SYSTEM_NAME}/`;
+    : `/${SYSTEM_NAME}/`,
+  routes,
+  mode: 'history',
+  scrollBehavior() {
+    return { y: 0 };
+  },
+});
+
+export function getAppRouterBase() {
+  return router.options.base;
 }
 
-function createRouter() {
-  return new VueRouter({
-    base: getAppRouterBase(),
+export function resetRouter() {
+  const newRouter = new VueRouter({
+    base: window.__POWERED_BY_QIANKUN__
+      ? `/page-${SYSTEM_NAME}/`
+      : `/${SYSTEM_NAME}/`,
     routes,
     mode: 'history',
     scrollBehavior() {
       return { y: 0 };
     },
   });
-}
-
-const router = createRouter();
-
-let dynamicRoutesReady = false;
-
-export function resetRouter() {
-  const newRouter = createRouter();
   router.matcher = newRouter.matcher;
   router.options.base = newRouter.options.base;
-  dynamicRoutesReady = false;
-}
-
-function registerMenuRoutes(menus, homePath, authoritiesRouter = []) {
-  const nextMenus = rewriteLegacyHrMenus(menus || []);
-  const nextAuth = rewriteLegacyHrMenus(authoritiesRouter || []);
-  store.commit('user/setMenus', nextMenus);
-  store.commit('user/setAuthoritiesRouter', nextAuth);
-  if (nextMenus.length || nextAuth.length) {
-    router.addRoute(
-      getMenuRoutes(
-        [...nextMenus, ...nextAuth],
-        homePath || HOME_PATH || '/home/index',
-      ),
-    );
-  }
-  dynamicRoutesReady = true;
-}
-
-export function setupDynamicRoutes(menus, homePath, authoritiesRouter = []) {
-  resetRouter();
-  registerMenuRoutes(menus, homePath, authoritiesRouter);
 }
 
+/**
+ * 路由守卫(对齐 EOM)
+ */
 router.beforeEach((to, from, next) => {
   if (!from.path.includes(REDIRECT_PATH)) {
     NProgress.start();
   }
 
   if (getToken()) {
-    const homePath = HOME_PATH || '/home/index';
-
-    if (isHomeAliasPath(to.path) && to.path !== homePath) {
-      next({
-        path: homePath,
-        query: to.query,
-        hash: to.hash,
-        replace: true,
-      });
-      return;
-    }
-
-    // 与 EOM:menus 未加载时拉取;有菜单才 addRoute
-    if (store.state.user.menus == null) {
+    // 还未注册动态路由则先获取
+    if (!store.state.user.menus) {
       store
         .dispatch('user/fetchUserInfo')
-        .then(({ menus, homePath: menuHome, authoritiesRouter }) => {
-          if (menus?.length || authoritiesRouter?.length) {
-            registerMenuRoutes(
-              menus || [],
-              menuHome || homePath,
-              authoritiesRouter || [],
+        .then(({ menus, homePath, authoritiesRouter }) => {
+          if (menus) {
+            router.addRoute(
+              getMenuRoutes(
+                [...menus, ...(authoritiesRouter || [])],
+                homePath || HOME_PATH,
+              ),
             );
+            next({ ...to, replace: true });
           } else {
-            // 标记已尝试,避免死循环;乾坤下侧栏仍由门户管
-            store.commit('user/setMenus', menus || []);
-            dynamicRoutesReady = true;
+            // 与 EOM:无菜单时仍放行,避免卡死;侧栏由门户按 /page-hr 维护
+            next();
           }
-          next({ ...to, replace: true });
         })
         .catch((e) => {
           console.error(e);
           next();
         });
-      return;
-    }
-
-    if (!dynamicRoutesReady && store.state.user.menus?.length) {
-      registerMenuRoutes(
-        store.state.user.menus,
-        homePath,
-        store.state.user.authoritiesRouter || [],
-      );
-      next({ ...to, replace: true });
-      return;
+    } else {
+      // 热更新等导致动态路由丢失时补注册
+      if (routes.length >= router.getRoutes()?.length) {
+        router.addRoute(
+          getMenuRoutes(
+            [
+              ...store.state.user.menus,
+              ...(store.state.user.authoritiesRouter || []),
+            ],
+            HOME_PATH,
+          ),
+        );
+        next({ ...to });
+      } else {
+        next();
+      }
     }
-
+  } else if (WHITE_LIST.includes(to.path)) {
     next();
-    return;
-  }
-
-  if (WHITE_LIST.includes(to.path)) {
-    next();
-    return;
+  } else {
+    next({
+      path: '/login',
+      query: to.path === LAYOUT_PATH ? {} : { from: to.path },
+    });
   }
-
-  next({
-    path: '/login',
-    query: to.path === LAYOUT_PATH ? {} : { from: to.path },
-  });
 });
 
 router.afterEach((to) => {
@@ -151,12 +117,16 @@ router.afterEach((to) => {
 
 router.roleChange = async ({ menus, homePath, authoritiesRouter }) => {
   const currentUser = getCurrentUser();
-  if (menus?.length) {
-    resetRouter();
-    registerMenuRoutes(menus, homePath, authoritiesRouter);
-    const target = HOME_PATH || menus[0].redirect || menus[0].path;
-    if (router.currentRoute.path !== target) {
-      await router.replace({ path: target });
+  if (menus && menus.length > 0) {
+    router.addRoute(
+      getMenuRoutes([...menus, ...(authoritiesRouter || [])], homePath),
+    );
+    if (
+      router.currentRoute.path != (menus[0].redirect || menus[0].path)
+    ) {
+      await router.replace({
+        path: menus[0].redirect || menus[0].path,
+      });
     }
     const newToken = await changeRole({
       groupId: currentUser.currentGroupId,

+ 13 - 23
src/store/modules/user.js

@@ -1,5 +1,5 @@
 /**
- * 登录用户 / 菜单 / 权限(对齐 EOM,不写门户 store
+ * 登录用户 / 菜单 / 权限(对齐 EOM)
  */
 import { formatMenus, toTreeData, formatTreeData } from 'ele-admin';
 import { USER_MENUS, SYSTEM_NAME, HOME_PATH } from '@/config/setting';
@@ -9,8 +9,6 @@ import { getCurrentUser } from '@/utils/token-util';
 import { rewriteLegacyHrMenus } from '@/utils/menu-rewrite';
 import Vue from 'vue';
 
-const APP_MENU_PATH = `/page-${SYSTEM_NAME}`;
-
 const formatRouter = (list = []) => {
   const authorities = [];
 
@@ -70,31 +68,20 @@ export default {
   },
   actions: {
     /**
-     * 与 EOM 一致:只按 `/page-hr` 过滤,只写入本子应用 store。
-     * 切勿 commit 到 $portalStore —— 会污染门户顶栏/侧栏并丢掉 /page-hr 前缀。
+     * 与 EOM 一致:按 `/page-hr` 过滤资源树,只写入本子应用 store
      */
     async fetchUserInfo({ commit }) {
       const currentUser = getCurrentUser() || {};
-      let result = [];
-      try {
-        if (currentUser.currentGroupId && currentUser.currentRoleId) {
-          result =
-            (await getResourcesTree({
-              groupId: currentUser.currentGroupId,
-              roleId: currentUser.currentRoleId,
-            })) || [];
-        }
-      } catch (e) {
-        console.warn('[hr] 菜单接口不可用', e);
-        result = [];
-      }
+      const result = await getResourcesTree({
+        groupId: currentUser.currentGroupId,
+        roleId: currentUser.currentRoleId,
+      }).catch(() => {});
 
       const list = (Array.isArray(result) ? result : []).filter(
-        (item) => item.path === APP_MENU_PATH,
+        (item) => item.path === `/page-${SYSTEM_NAME}`,
       );
 
       if (!list?.length) {
-        // 乾坤下侧栏由门户按 /page-hr 自行切换;无节点时与 EOM 一样返回空
         if (window.__POWERED_BY_QIANKUN__) {
           return {};
         }
@@ -105,13 +92,14 @@ export default {
         commit('setAuthoritiesRouter', []);
         return {
           menus: nextMenus,
-          homePath: HOME_PATH || local.homePath || '/home/index',
+          homePath: HOME_PATH || local.homePath,
           authoritiesRouter: [],
         };
       }
 
       const { menuList, authorities } = formatRouter(list[0].children || []);
       commit('setAuthorities', authorities);
+
       const { menus, homePath } = formatMenus(
         USER_MENUS ??
           toTreeData({
@@ -128,13 +116,15 @@ export default {
             parentIdField: 'parentId',
           }),
       );
+
       const nextMenus = rewriteLegacyHrMenus(menus || []);
       const nextAuth = rewriteLegacyHrMenus(authoritiesRouter || []);
       commit('setMenus', nextMenus);
       commit('setAuthoritiesRouter', nextAuth);
+
       return {
         menus: nextMenus,
-        homePath: HOME_PATH || homePath || '/home/index',
+        homePath: HOME_PATH || homePath,
         authoritiesRouter: nextAuth,
       };
     },
@@ -147,7 +137,7 @@ export default {
     setMenuBadge({ commit, state }, { path, value, color }) {
       if (window.__POWERED_BY_QIANKUN__) {
         Vue.prototype.$portalStore?.dispatch('user/setMenuBadge', {
-          path: `${APP_MENU_PATH}${path}`,
+          path: `/page-${SYSTEM_NAME}${path}`,
           value,
           color,
         });

+ 28 - 69
src/utils/request.js

@@ -1,47 +1,19 @@
+/**
+ * axios 实例(乾坤行为对齐 EOM:成功响应不回调门户 fetchNew)
+ *
+ * 说明:此前每次接口成功都调 parentWindow.fetchNew,
+ * 进入 HR 后拉资源树会触发门户重新初始化菜单,侧栏闪一下又回到第一个系统。
+ * EOM 无此逻辑,故去掉成功路径的 notifyParent。
+ */
 import axios from 'axios';
 import JSONBIG from 'json-bigint';
 import { MessageBox } from 'element-ui';
 import { API_BASE_URL, TOKEN_HEADER_NAME } from '@/config/setting';
 import { getRouter, resetRouter } from '@/router';
 import { getToken, refreshToken, removeToken } from '@/utils/token-util';
-import { suppressNextErrorToasts } from '@/utils/message';
 
 let loginExpiredDialogVisible = false;
 
-function isBusinessFailed(data) {
-  if (!data || typeof data !== 'object') return false;
-  const code = Number(data.code);
-  if (Number.isNaN(code)) return false;
-  return code !== 0 && code !== 200;
-}
-
-/** 将接口状态同步给门户,便于主应用统一处理业务状态。 */
-function notifyParent(response) {
-  if (!window.__POWERED_BY_QIANKUN__) {
-    return false;
-  }
-
-  const fetchNew = window.parentWindow?.fetchNew;
-  if (typeof fetchNew !== 'function') {
-    return false;
-  }
-
-  const data = response?.data || {};
-  fetchNew({
-    code: data.code ?? response?.status,
-    message: data.message || response?.statusText,
-    apiUrl: response?.config?.url
-  });
-  return true;
-}
-
-/** 门户已接手失败提示时,抑制子应用下一次 error toast,避免双弹。 */
-function markParentHandledToast(notified) {
-  if (notified && window.__POWERED_BY_QIANKUN__) {
-    suppressNextErrorToasts(1);
-  }
-}
-
 /** 跳转登录页,独立运行和 qiankun 嵌入运行分别处理。 */
 function redirectToLogin(from) {
   const isHome =
@@ -70,14 +42,13 @@ function redirectToLogin(from) {
     router
       .replace({
         path: '/login',
-        query: isHome ? {} : { from }
+        query: isHome ? {} : { from },
       })
       .catch(() => {});
   }
 }
 
-function handleLoginExpired(error) {
-  notifyParent(error.response);
+function handleLoginExpired() {
   removeToken();
 
   if (loginExpiredDialogVisible) {
@@ -86,13 +57,14 @@ function handleLoginExpired(error) {
 
   loginExpiredDialogVisible = true;
   const router = getRouter();
-  const currentPath = router?.currentRoute?.fullPath || window.location.pathname;
+  const currentPath =
+    router?.currentRoute?.fullPath || window.location.pathname;
 
   MessageBox.alert('登录状态已过期,请退出后重新登录', '系统提示', {
     confirmButtonText: '重新登录',
     closeOnClickModal: false,
     closeOnPressEscape: false,
-    showClose: false
+    showClose: false,
   })
     .then(() => redirectToLogin(currentPath))
     .catch(() => redirectToLogin(currentPath))
@@ -114,50 +86,37 @@ const request = axios.create({
       } catch (error) {
         return data;
       }
-    }
-  ]
+    },
+  ],
 });
 
-request.interceptors.request.use((config) => {
-  const token = getToken();
-  if (token) {
-    config.headers = config.headers || {};
-    config.headers[TOKEN_HEADER_NAME] = token;
-  }
-  return config;
-}, (error) => Promise.reject(error));
+request.interceptors.request.use(
+  (config) => {
+    const token = getToken();
+    if (token) {
+      config.headers = config.headers || {};
+      config.headers[TOKEN_HEADER_NAME] = token;
+    }
+    return config;
+  },
+  (error) => Promise.reject(error),
+);
 
 request.interceptors.response.use(
   (response) => {
-    const notified = notifyParent(response);
-    // 业务失败由门户统一提示时,标记并抑制页面 catch 再弹一次
-    if (notified && isBusinessFailed(response?.data)) {
-      response.__toastShown = true;
-      markParentHandledToast(true);
-    }
-
+    // 与 EOM 一致:不在成功响应里回调门户 fetchNew
     const token = response.headers?.[TOKEN_HEADER_NAME.toLowerCase()];
     if (token) {
       refreshToken(token);
     }
-
     return response;
   },
   (error) => {
     if (error.response?.status === 401) {
-      handleLoginExpired(error);
+      handleLoginExpired();
       return Promise.reject(error);
     }
 
-    if (error.response) {
-      const notified = notifyParent(error.response);
-      if (notified) {
-        error.__toastShown = true;
-        markParentHandledToast(true);
-      }
-    }
-
-    // 仅规范化错误文案;独立运行时由页面 catch 提示,嵌入门户时由主应用提示
     const message =
       error.response?.data?.message ||
       (error.response?.status
@@ -165,7 +124,7 @@ request.interceptors.response.use(
         : '服务调用失败,请联系管理员!');
     error.message = message;
     return Promise.reject(error);
-  }
+  },
 );
 
 export default request;