Ver código fonte

修复新标签页机构列表丢失:机构缓存增加 localStorage 镜像,登出时清理

lihaiting 6 dias atrás
pai
commit
9756d9289d

+ 17 - 0
src/store/index.js

@@ -9,6 +9,23 @@ import dictEnum from '@/enum/dict';
 
 Vue.use(Vuex);
 
+/** vuex 持久化缓存键:乾坤与独立运行各一份 */
+export function persistedStateKeys() {
+  return [`vuex-${SYSTEM_NAME}`, `private-vuex-${SYSTEM_NAME}`];
+}
+
+/**
+ * 清除 vuex 持久化缓存(登出用)
+ * 两种 key、两种 storage 都清:运行时环境切换(乾坤 / 独立)可能留下另一份,
+ * 且 utils/current-org.js 读取时会 sessionStorage、localStorage 双读。
+ */
+export function clearPersistedState() {
+  for (const key of persistedStateKeys()) {
+    window.sessionStorage.removeItem(key);
+    window.localStorage.removeItem(key);
+  }
+}
+
 export default new Vuex.Store({
   modules: {
     user,

+ 3 - 0
src/store/modules/user.js

@@ -6,6 +6,7 @@ import { USER_MENUS, SYSTEM_NAME, HOME_PATH } from '@/config/setting';
 import { DEFAULT_MENUS } from '@/config/menus';
 import { getResourcesTree } from '@/api/layout';
 import { getCurrentUser } from '@/utils/token-util';
+import { saveLoginOrgs } from '@/utils/login-org-cache';
 import { rewriteLegacyHrMenus } from '@/utils/menu-rewrite';
 import Vue from 'vue';
 
@@ -47,6 +48,8 @@ export default {
   mutations: {
     setUserInfo(state, info) {
       state.info = info;
+      /* 机构列表额外落一份 localStorage 镜像,避免新标签页丢机构(详见 login-org-cache.js) */
+      saveLoginOrgs(info);
     },
     setMenus(state, menus) {
       state.menus = menus == null ? menus : rewriteLegacyHrMenus(menus);

+ 8 - 1
src/utils/current-org.js

@@ -6,6 +6,9 @@
  *   { user: { info: { loginChangeGroupVOList: [{ groupId, groupName, loginChangeRoleVOList }] } } }
  * 独立运行:private-vuex-hr;乾坤子应用:vuex-hr
  *
+ * 兜底:sessionStorage / localStorage 都没有时,读 utils/login-org-cache.js 的
+ * localStorage 镜像(同一次登录时写入),避免新标签页出现「机构只剩 1 个」的半登录态。
+ *
  * 约定:
  * 1. 所有「所属机构」下拉选项一律取 loginChangeGroupVOList,不再调 /main/group/getGroupList;
  * 2. 接口需要的所属机构 id 默认取缓存机构列表第一条(用户所属机构有多个时默认第一个),
@@ -13,6 +16,7 @@
  */
 import { SYSTEM_NAME } from '@/config/setting';
 import { getCurrentUser } from '@/utils/token-util';
+import { readLoginOrgs } from '@/utils/login-org-cache';
 
 /** vuex 持久化缓存键(顺序即读取优先级) */
 function vuexCacheKeys() {
@@ -42,7 +46,10 @@ export function getLoginUserInfo() {
 
 /** 登录缓存中的可选机构列表(loginChangeGroupVOList 原始结构) */
 export function getLoginGroups() {
-  const list = getLoginUserInfo().loginChangeGroupVOList;
+  const cached = getLoginUserInfo().loginChangeGroupVOList;
+  /* 缓存列表缺失(如新标签页 sessionStorage 为空)时,用 localStorage 镜像兜底 */
+  const list =
+    Array.isArray(cached) && cached.length ? cached : readLoginOrgs();
   if (!Array.isArray(list)) return [];
   return list.filter((item) => item && isValidId(item.groupId));
 }

+ 66 - 0
src/utils/login-org-cache.js

@@ -0,0 +1,66 @@
+/**
+ * 「所属机构」列表的持久化镜像(localStorage)
+ *
+ * 背景:
+ * 「所属机构」下拉的数据源是 user.info.loginChangeGroupVOList,由 vuex-persistedstate
+ * 写入 **sessionStorage**(key: 乾坤 `vuex-hr` / 独立 `private-vuex-hr`,标签独占)。
+ * 而 token / currentUser 在「记住我」(默认勾选)时落在 **localStorage**(跨标签持久)。
+ * 两者生命周期不一致 → 新开标签页、或把 URL 直接粘到新标签时,会出现
+ * 「已登录(token 还在)但机构列表为空」的半登录态:页面机构下拉只能回落到
+ * currentGroupId 单项,表现为「机构只剩 1 个」。
+ *
+ * 且路由守卫里的 fetchUserInfo() 只拉资源树菜单,**不会补拉 loginChangeGroupVOList**,
+ * 列表一旦为空就一直空到重新登录。
+ *
+ * 策略(针对性补齐,不改变现有 sessionStorage 行为):
+ * 1. 每次 user/setUserInfo 时,把机构列表额外落一份 localStorage 镜像;
+ * 2. 读取端(utils/current-org.js)在 sessionStorage 缺失时用镜像兜底;
+ * 3. 登出时清除镜像(utils/page-tab-util.js 的 logout)。
+ *
+ * 注意:这不是「默认机构」逻辑,也不改变机构取值口径(仍取列表第 1 条);
+ * 只是保证「只要登录态还在,机构列表就在」。
+ */
+
+/** 镜像存储键(专用,避免与门户/其他系统的 localStorage 键冲突) */
+const STORE_KEY = 'hr-login-orgs';
+
+/**
+ * 写入机构列表镜像
+ * @param {object} info 登录用户信息(含 loginChangeGroupVOList)
+ */
+export function saveLoginOrgs(info) {
+  const list = info && info.loginChangeGroupVOList;
+  if (!Array.isArray(list) || !list.length) return;
+  try {
+    localStorage.setItem(
+      STORE_KEY,
+      JSON.stringify({ loginChangeGroupVOList: list }),
+    );
+  } catch (error) {
+    // 隐私模式 / 存储配额满:镜像写不进去不影响主流程
+  }
+}
+
+/**
+ * 读取机构列表镜像
+ * @returns {Array} loginChangeGroupVOList 原始结构;没有则空数组
+ */
+export function readLoginOrgs() {
+  try {
+    const raw = localStorage.getItem(STORE_KEY);
+    if (!raw) return [];
+    const list = JSON.parse(raw)?.loginChangeGroupVOList;
+    return Array.isArray(list) ? list : [];
+  } catch (error) {
+    return [];
+  }
+}
+
+/** 清除机构列表镜像(登出时调用) */
+export function clearLoginOrgs() {
+  try {
+    localStorage.removeItem(STORE_KEY);
+  } catch (error) {
+    // 忽略:清不掉也不影响登出
+  }
+}

+ 7 - 1
src/utils/page-tab-util.js

@@ -1,10 +1,11 @@
 /**
  * 页签操作封装
  */
-import store from '@/store';
+import store, { clearPersistedState } from '@/store';
 import router from '@/router';
 import { Message } from 'element-ui';
 import { removeToken, removeCurrentUser } from '@/utils/token-util';
+import { clearLoginOrgs } from '@/utils/login-org-cache';
 import { setDocumentTitle } from '@/utils/document-title-util';
 import {
   HOME_PATH,
@@ -260,6 +261,11 @@ export function goHomeRoute(from) {
 export function logout(route) {
   removeToken();
   removeCurrentUser();
+  clearLoginOrgs();
+  clearPersistedState();
+  /* 内存态一并复位,避免登出后残留上一个账号的信息(setUserInfo 不会回写镜像) */
+  store.commit('user/setUserInfo', null);
+  store.commit('user/setMenus', null);
   if (route) {
     router.push({
       path: '/login',