import { defineStore } from 'pinia' import { ref, computed } from 'vue' // import { getUserInfo } from '@/utils/auth' import { getMenus } from '@/api/menu'; // 是否属于「登录态失效」类错误。 // request.js 已给鉴权错误打上 status=401,同时兼容历史文案与 axios 原始错误。 const isAuthError = (err) => err?.status === 401 || err?.response?.status === 401 || err?.isAuthError === true || /token已过期|token无效|无效的token/.test(err?.message || ''); export const useMenuStore = defineStore('menu', () => { // 菜单数据 const menus = ref([]); // 加载状态 const loading = ref(false); // 加载错误 const error = ref(null); // 正在加载的 Promise(用于避免重复请求) let loadingPromise = null; // 菜单缓存 key(基于用户类型、当前企业与角色ID) // 必须带 tid:菜单是按企业下发的,key 里没有企业会让「切换企业」后 // 直接复用上一家的菜单,导致新企业未开通的模块仍能进入。 const getCacheKey = () => { try { const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}'); const loginType = userInfo.type || 'user'; const roleId = userInfo.group_id || 0; const tid = userInfo.tid || 0; return `menu_cache_${loginType}_${tid}_${roleId}`; } catch (e) { return 'menu_cache_default'; } }; // 从缓存加载菜单 const loadFromCache = () => { try { const cacheKey = getCacheKey(); const cached = localStorage.getItem(cacheKey); if (cached) { const menuData = JSON.parse(cached); // 检查缓存是否过期(5分钟过期) if (menuData.timestamp && Date.now() - menuData.timestamp < 5 * 60 * 1000) { return menuData.menus; } } } catch (e) { console.warn('加载菜单缓存失败:', e); } return null; }; // 保存菜单到缓存 const saveToCache = (menuData) => { try { const cacheKey = getCacheKey(); localStorage.setItem(cacheKey, JSON.stringify({ menus: menuData, timestamp: Date.now() })); } catch (e) { console.warn('保存菜单缓存失败:', e); } }; // 清除菜单缓存 const clearCache = () => { try { const cacheKey = getCacheKey(); localStorage.removeItem(cacheKey); // 也清除其他可能的缓存key(兼容旧代码) localStorage.removeItem('menu_cache'); } catch (e) { console.warn('清除菜单缓存失败:', e); } }; // 获取用户信息 const getUserInfo = () => { try { return JSON.parse(localStorage.getItem('userInfo') || '{}'); } catch (e) { return {}; } }; // 从 API 加载菜单(核心方法,确保只请求一次) const fetchMenus = async (forceRefresh = false) => { // 如果已经有正在加载的请求,直接返回该 Promise if (loadingPromise && !forceRefresh) { return loadingPromise; } // 如果不强制刷新,先尝试从缓存加载 if (!forceRefresh) { const cachedMenus = loadFromCache(); if (cachedMenus && cachedMenus.length > 0) { menus.value = cachedMenus; return Promise.resolve(cachedMenus); } } // 如果正在加载且不是强制刷新,返回现有的 Promise if (loading.value && !forceRefresh) { return loadingPromise; } // 创建新的加载 Promise loadingPromise = (async () => { loading.value = true; error.value = null; try { const userInfo = getUserInfo(); const loginType = userInfo.type || 'user'; const roleId = userInfo.group_id || 0; let res; // 检查用户ID是否存在 if (!userInfo.id) { throw new Error('用户ID不存在,请重新登录'); } // 用户登录,使用 getMenus 接口 res = await getMenus(userInfo.id); // 检查响应格式 if (!res) { throw new Error('获取菜单失败:服务器无响应'); } // 检查后端返回的 code 字段 if (res.code !== 200) { throw new Error(res.msg || '获取菜单失败'); } // 如果 code 为 200,检查 data if (res.code === 200) { // data 可能是空数组,这也是有效的 if (res.data !== undefined && res.data !== null) { // 确保 data 是数组 const menuData = Array.isArray(res.data) ? res.data : []; // 直接使用后端返回的树形结构数据,不需要额外过滤 menus.value = menuData; // 保存到缓存 saveToCache(menuData); return menuData; } else { // data 为 null 或 undefined,使用空数组 console.warn('菜单数据为空,使用空数组'); menus.value = []; saveToCache([]); return []; } } // 如果响应格式不符合预期,尝试直接使用 res.data if (res.data !== undefined) { const menuData = Array.isArray(res.data) ? res.data : []; const filtered = menuData.filter(m => (m.isShow ?? 1) !== 0); menus.value = filtered; saveToCache(filtered); return filtered; } // 如果都不符合,抛出错误 throw new Error(res.message || '获取菜单失败:响应格式错误'); } catch (err) { error.value = err.message || '获取菜单失败'; // 鉴权失败(令牌过期/无效、会话被吊销):清掉缓存并继续抛出, // 由 request.js 静默续期或跳认证中心。 // 不能回退到缓存:否则令牌过期时会用旧菜单把登录态问题掩盖掉, // 用户看到的是一份已经失效的权限菜单。 if (isAuthError(err)) { clearCache(); menus.value = []; console.warn('获取菜单失败(登录状态失效):', err.message); throw err; } console.error('获取菜单失败:', err); console.error('错误详情:', { message: err.message, response: err.response, stack: err.stack }); // 如果出错,尝试使用缓存数据 const cachedMenus = loadFromCache(); if (cachedMenus && cachedMenus.length > 0) { console.warn('使用缓存的菜单数据'); menus.value = cachedMenus; return cachedMenus; } // 如果连缓存都没有,设置空数组,避免页面崩溃 menus.value = []; throw err; } finally { loading.value = false; loadingPromise = null; } })(); return loadingPromise; }; // 刷新菜单(强制从 API 获取) const refreshMenus = async () => { clearCache(); return await fetchMenus(true); }; // 重置菜单 store(登出时使用) const resetMenus = () => { menus.value = []; loading.value = false; error.value = null; loadingPromise = null; clearCache(); }; // 计算属性:获取菜单列表 const menuList = computed(() => menus.value); // 计算属性:菜单是否已加载 const isLoaded = computed(() => menus.value.length > 0); return { // 状态 menus: menuList, loading, error, isLoaded, // 方法 fetchMenus, refreshMenus, resetMenus, clearCache, loadFromCache, }; });