Files
yunzerwebsiteallinone/backend/src/stores/menu.js
T
2026-06-24 10:04:03 +08:00

238 lines
6.9 KiB
JavaScript

import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
// import { getUserInfo } from '@/utils/auth'
import { getMenus } from '@/api/menu';
export const useMenuStore = defineStore('menu', () => {
// 菜单数据
const menus = ref([]);
// 加载状态
const loading = ref(false);
// 加载错误
const error = ref(null);
// 正在加载的 Promise(用于避免重复请求)
let loadingPromise = null;
// 菜单缓存 key(基于用户类型和角色ID)
const getCacheKey = () => {
try {
const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}');
const loginType = userInfo.type || 'user';
const roleId = userInfo.group_id || 0;
return `menu_cache_${loginType}_${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 || '获取菜单失败';
console.error('获取菜单失败:', err);
console.error('错误详情:', {
message: err.message,
response: err.response,
stack: err.stack
});
// 如果是 token 无效错误,不使用缓存,直接抛出
if (err.message === 'token无效' || err.response?.status === 401) {
clearCache();
menus.value = [];
throw err;
}
// 如果出错,尝试使用缓存数据
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,
};
});