238 lines
6.9 KiB
JavaScript
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,
|
|
};
|
|
});
|
|
|