This commit is contained in:
2026-06-24 10:04:03 +08:00
parent b103192fac
commit 0f961789dc
538 changed files with 128210 additions and 128008 deletions
+107 -107
View File
@@ -1,107 +1,107 @@
import { defineStore } from 'pinia'
import { ref, reactive } from 'vue'
// 用户信息类型
const defaultUser = {
id:'',
account: '',
name: '',
group_id: '',
type: 'backend',
avatar: ''
}
export const useAuthStore = defineStore('auth', () => {
const token = ref(localStorage.getItem('token') || '')
const isLoggedIn = ref(!!token.value)
const user = reactive({ ...defaultUser })
// 从缓存加载用户信息
function loadUserFromCache() {
const cachedUser = localStorage.getItem('userInfo')
if (cachedUser) {
try {
const userInfo = JSON.parse(cachedUser)
Object.assign(user, userInfo)
} catch (e) {
console.error('Failed to parse user info from cache:', e)
}
}
}
// 初始化时加载用户信息
loadUserFromCache()
// 保存登录信息(token 和用户信息)
function setLoginInfo(loginData) {
const userInfo = loginData.user || loginData
const normalizedUser = {
id: parseInt(userInfo.id) || null,
account: userInfo.account || '',
name: userInfo.name || '',
group_id: userInfo.group_id || '',
type: 'backend',
tid: userInfo.tid || '',
avatar: userInfo.avatar || ''
}
// 使用后端返回的真实 JWT token
const accessToken = loginData.token || ''
token.value = accessToken
isLoggedIn.value = !!accessToken
localStorage.setItem('token', accessToken)
Object.assign(user, normalizedUser)
localStorage.setItem('userInfo', JSON.stringify(normalizedUser))
}
// 设置 token(兼容旧代码)
function setToken(newToken) {
token.value = newToken
isLoggedIn.value = true
localStorage.setItem('token', newToken)
}
// 清除登录信息
function clearToken() {
token.value = ''
isLoggedIn.value = false
Object.assign(user, defaultUser)
localStorage.removeItem('token')
localStorage.removeItem('userInfo')
}
// 检查认证状态
function checkAuth() {
const storedToken = localStorage.getItem('token')
if (storedToken) {
token.value = storedToken
isLoggedIn.value = true
loadUserFromCache()
} else {
token.value = ''
isLoggedIn.value = false
Object.assign(user, defaultUser)
}
}
// 更新用户信息
function updateUserInfo(userInfo) {
Object.assign(user, userInfo)
localStorage.setItem('userInfo', JSON.stringify(userInfo))
}
return {
token,
isLoggedIn,
user,
setLoginInfo,
setToken,
clearToken,
checkAuth,
updateUserInfo
}
})
import { defineStore } from 'pinia'
import { ref, reactive } from 'vue'
// 用户信息类型
const defaultUser = {
id:'',
account: '',
name: '',
group_id: '',
type: 'backend',
avatar: ''
}
export const useAuthStore = defineStore('auth', () => {
const token = ref(localStorage.getItem('token') || '')
const isLoggedIn = ref(!!token.value)
const user = reactive({ ...defaultUser })
// 从缓存加载用户信息
function loadUserFromCache() {
const cachedUser = localStorage.getItem('userInfo')
if (cachedUser) {
try {
const userInfo = JSON.parse(cachedUser)
Object.assign(user, userInfo)
} catch (e) {
console.error('Failed to parse user info from cache:', e)
}
}
}
// 初始化时加载用户信息
loadUserFromCache()
// 保存登录信息(token 和用户信息)
function setLoginInfo(loginData) {
const userInfo = loginData.user || loginData
const normalizedUser = {
id: parseInt(userInfo.id) || null,
account: userInfo.account || '',
name: userInfo.name || '',
group_id: userInfo.group_id || '',
type: 'backend',
tid: userInfo.tid || '',
avatar: userInfo.avatar || ''
}
// 使用后端返回的真实 JWT token
const accessToken = loginData.token || ''
token.value = accessToken
isLoggedIn.value = !!accessToken
localStorage.setItem('token', accessToken)
Object.assign(user, normalizedUser)
localStorage.setItem('userInfo', JSON.stringify(normalizedUser))
}
// 设置 token(兼容旧代码)
function setToken(newToken) {
token.value = newToken
isLoggedIn.value = true
localStorage.setItem('token', newToken)
}
// 清除登录信息
function clearToken() {
token.value = ''
isLoggedIn.value = false
Object.assign(user, defaultUser)
localStorage.removeItem('token')
localStorage.removeItem('userInfo')
}
// 检查认证状态
function checkAuth() {
const storedToken = localStorage.getItem('token')
if (storedToken) {
token.value = storedToken
isLoggedIn.value = true
loadUserFromCache()
} else {
token.value = ''
isLoggedIn.value = false
Object.assign(user, defaultUser)
}
}
// 更新用户信息
function updateUserInfo(userInfo) {
Object.assign(user, userInfo)
localStorage.setItem('userInfo', JSON.stringify(userInfo))
}
return {
token,
isLoggedIn,
user,
setLoginInfo,
setToken,
clearToken,
checkAuth,
updateUserInfo
}
})
+196 -196
View File
@@ -1,197 +1,197 @@
import { defineStore } from 'pinia';
import { ref, computed, reactive } from 'vue';
// ========== 全局状态 Store ==========
function initState() {
return {
isCollapse: false,
};
}
export const useAllDataStore = defineStore('allData', () => {
const state = reactive(initState());
const count = ref(0);
const doubleCount = computed(() => count.value * 2);
function increment() {
count.value++;
}
return {
state,
count,
doubleCount,
increment,
};
});
// ========== 多标签页 Tabs Store ==========
import { defineStore as defineTabsStore } from 'pinia';
import { ref as vueRef } from 'vue';
/**
* 多标签页Tabs状态管理
* tabList每个tab结构: {
* title: 标签显示名,
* fullPath: 路由路径(唯一key,
* name: 路由name,
* icon: 图标(可选)
* }
*/
export const useTabsStore = defineTabsStore('tabs', () => {
// 固定首页tab
const defaultDashboardPath = '/home';
// 从 localStorage 恢复 tabs 状态
function loadTabsFromStorage() {
try {
const savedTabs = localStorage.getItem('tabs_list');
const savedActiveTab = localStorage.getItem('active_tab');
if (savedTabs) {
const tabs = JSON.parse(savedTabs);
// 确保至少包含首页
const hasHome = tabs.some(t => t.fullPath === defaultDashboardPath);
if (!hasHome) {
tabs.unshift({ title: '首页', fullPath: defaultDashboardPath, name: 'Home' });
}
return tabs;
}
} catch (e) {
console.warn('恢复 tabs 失败:', e);
}
return [{ title: '首页', fullPath: defaultDashboardPath, name: 'Home' }];
}
// 保存 tabs 到 localStorage
function saveTabsToStorage(tabs, active) {
try {
localStorage.setItem('tabs_list', JSON.stringify(tabs));
if (active) {
localStorage.setItem('active_tab', active);
}
} catch (e) {
console.warn('保存 tabs 失败:', e);
}
}
const tabList = vueRef(loadTabsFromStorage());
const savedActiveTab = localStorage.getItem('active_tab');
const activeTab = vueRef(savedActiveTab || defaultDashboardPath);
// 添加tab,若已存在则激活
function addTab(tab) {
const exist = tabList.value.find((t) => t.fullPath === tab.fullPath);
if (!exist) {
tabList.value.push(tab);
}
activeTab.value = tab.fullPath;
saveTabsToStorage(tabList.value, activeTab.value);
}
// 删除指定tab并切换激活tab
function removeTab(fullPath) {
const idx = tabList.value.findIndex((t) => t.fullPath === fullPath);
if (idx > -1) {
tabList.value.splice(idx, 1);
// 只在关闭当前激活tab时切换激活tab
if (activeTab.value === fullPath) {
if (tabList.value.length > 0) {
// 优先激活右侧(如无则激活左侧)
const newIdx = idx >= tabList.value.length ? tabList.value.length - 1 : idx;
activeTab.value = tabList.value[newIdx].fullPath;
} else {
// 全部关闭,兜底首页
activeTab.value = defaultDashboardPath;
}
}
saveTabsToStorage(tabList.value, activeTab.value);
}
}
// 关闭其他,只留首页和当前激活tab
function closeOthers() {
tabList.value = tabList.value.filter(
(t) => t.fullPath === defaultDashboardPath || t.fullPath === activeTab.value
);
saveTabsToStorage(tabList.value, activeTab.value);
}
// 关闭左侧(关闭指定tab左侧的所有tab,保留首页和目标tab)
function closeLeft(targetFullPath) {
const targetIndex = tabList.value.findIndex((t) => t.fullPath === targetFullPath);
if (targetIndex > -1) {
// 保留首页和目标tab及其右侧的所有tab
const beforeIndex = tabList.value.slice(0, targetIndex);
const hasCloseableLeft = beforeIndex.some(t => t.fullPath !== defaultDashboardPath);
if (hasCloseableLeft) {
tabList.value = tabList.value.filter((t, index) =>
t.fullPath === defaultDashboardPath || index >= targetIndex
);
// 如果关闭的tab中包含了当前激活的tab,则激活目标tab
if (!tabList.value.find(t => t.fullPath === activeTab.value)) {
activeTab.value = targetFullPath;
}
saveTabsToStorage(tabList.value, activeTab.value);
}
}
}
// 关闭右侧(关闭指定tab右侧的所有tab,保留首页和目标tab)
function closeRight(targetFullPath) {
const targetIndex = tabList.value.findIndex((t) => t.fullPath === targetFullPath);
if (targetIndex > -1) {
// 保留首页和目标tab及其左侧的所有tab
const afterIndex = tabList.value.slice(targetIndex + 1);
const hasCloseableRight = afterIndex.length > 0;
if (hasCloseableRight) {
tabList.value = tabList.value.filter((t, index) =>
t.fullPath === defaultDashboardPath || index <= targetIndex
);
// 如果关闭的tab中包含了当前激活的tab,则激活目标tab
if (!tabList.value.find(t => t.fullPath === activeTab.value)) {
activeTab.value = targetFullPath;
}
saveTabsToStorage(tabList.value, activeTab.value);
}
}
}
// 关闭全部,只留首页
function closeAll() {
tabList.value = tabList.value.filter((t) => t.fullPath === defaultDashboardPath);
activeTab.value = defaultDashboardPath;
saveTabsToStorage(tabList.value, activeTab.value);
}
// 设置激活tab(不触发路由跳转,仅用于更新状态)
function setActiveTab(fullPath) {
activeTab.value = fullPath;
saveTabsToStorage(tabList.value, activeTab.value);
}
// 重置 tabs store 到初始状态(登出时使用)
function resetTabs() {
tabList.value = [{ title: '首页', fullPath: defaultDashboardPath, name: 'Dashboard' }];
activeTab.value = defaultDashboardPath;
// 清除 localStorage 中的 tabs 数据
localStorage.removeItem('tabs_list');
localStorage.removeItem('active_tab');
}
return {
tabList,
activeTab,
addTab,
removeTab,
closeOthers,
closeLeft,
closeRight,
closeAll,
setActiveTab,
saveTabsToStorage,
resetTabs,
};
});
// ========== 菜单 Menu Store ==========
import { defineStore } from 'pinia';
import { ref, computed, reactive } from 'vue';
// ========== 全局状态 Store ==========
function initState() {
return {
isCollapse: false,
};
}
export const useAllDataStore = defineStore('allData', () => {
const state = reactive(initState());
const count = ref(0);
const doubleCount = computed(() => count.value * 2);
function increment() {
count.value++;
}
return {
state,
count,
doubleCount,
increment,
};
});
// ========== 多标签页 Tabs Store ==========
import { defineStore as defineTabsStore } from 'pinia';
import { ref as vueRef } from 'vue';
/**
* 多标签页Tabs状态管理
* tabList每个tab结构: {
* title: 标签显示名,
* fullPath: 路由路径(唯一key,
* name: 路由name,
* icon: 图标(可选)
* }
*/
export const useTabsStore = defineTabsStore('tabs', () => {
// 固定首页tab
const defaultDashboardPath = '/home';
// 从 localStorage 恢复 tabs 状态
function loadTabsFromStorage() {
try {
const savedTabs = localStorage.getItem('tabs_list');
const savedActiveTab = localStorage.getItem('active_tab');
if (savedTabs) {
const tabs = JSON.parse(savedTabs);
// 确保至少包含首页
const hasHome = tabs.some(t => t.fullPath === defaultDashboardPath);
if (!hasHome) {
tabs.unshift({ title: '首页', fullPath: defaultDashboardPath, name: 'Home' });
}
return tabs;
}
} catch (e) {
console.warn('恢复 tabs 失败:', e);
}
return [{ title: '首页', fullPath: defaultDashboardPath, name: 'Home' }];
}
// 保存 tabs 到 localStorage
function saveTabsToStorage(tabs, active) {
try {
localStorage.setItem('tabs_list', JSON.stringify(tabs));
if (active) {
localStorage.setItem('active_tab', active);
}
} catch (e) {
console.warn('保存 tabs 失败:', e);
}
}
const tabList = vueRef(loadTabsFromStorage());
const savedActiveTab = localStorage.getItem('active_tab');
const activeTab = vueRef(savedActiveTab || defaultDashboardPath);
// 添加tab,若已存在则激活
function addTab(tab) {
const exist = tabList.value.find((t) => t.fullPath === tab.fullPath);
if (!exist) {
tabList.value.push(tab);
}
activeTab.value = tab.fullPath;
saveTabsToStorage(tabList.value, activeTab.value);
}
// 删除指定tab并切换激活tab
function removeTab(fullPath) {
const idx = tabList.value.findIndex((t) => t.fullPath === fullPath);
if (idx > -1) {
tabList.value.splice(idx, 1);
// 只在关闭当前激活tab时切换激活tab
if (activeTab.value === fullPath) {
if (tabList.value.length > 0) {
// 优先激活右侧(如无则激活左侧)
const newIdx = idx >= tabList.value.length ? tabList.value.length - 1 : idx;
activeTab.value = tabList.value[newIdx].fullPath;
} else {
// 全部关闭,兜底首页
activeTab.value = defaultDashboardPath;
}
}
saveTabsToStorage(tabList.value, activeTab.value);
}
}
// 关闭其他,只留首页和当前激活tab
function closeOthers() {
tabList.value = tabList.value.filter(
(t) => t.fullPath === defaultDashboardPath || t.fullPath === activeTab.value
);
saveTabsToStorage(tabList.value, activeTab.value);
}
// 关闭左侧(关闭指定tab左侧的所有tab,保留首页和目标tab)
function closeLeft(targetFullPath) {
const targetIndex = tabList.value.findIndex((t) => t.fullPath === targetFullPath);
if (targetIndex > -1) {
// 保留首页和目标tab及其右侧的所有tab
const beforeIndex = tabList.value.slice(0, targetIndex);
const hasCloseableLeft = beforeIndex.some(t => t.fullPath !== defaultDashboardPath);
if (hasCloseableLeft) {
tabList.value = tabList.value.filter((t, index) =>
t.fullPath === defaultDashboardPath || index >= targetIndex
);
// 如果关闭的tab中包含了当前激活的tab,则激活目标tab
if (!tabList.value.find(t => t.fullPath === activeTab.value)) {
activeTab.value = targetFullPath;
}
saveTabsToStorage(tabList.value, activeTab.value);
}
}
}
// 关闭右侧(关闭指定tab右侧的所有tab,保留首页和目标tab)
function closeRight(targetFullPath) {
const targetIndex = tabList.value.findIndex((t) => t.fullPath === targetFullPath);
if (targetIndex > -1) {
// 保留首页和目标tab及其左侧的所有tab
const afterIndex = tabList.value.slice(targetIndex + 1);
const hasCloseableRight = afterIndex.length > 0;
if (hasCloseableRight) {
tabList.value = tabList.value.filter((t, index) =>
t.fullPath === defaultDashboardPath || index <= targetIndex
);
// 如果关闭的tab中包含了当前激活的tab,则激活目标tab
if (!tabList.value.find(t => t.fullPath === activeTab.value)) {
activeTab.value = targetFullPath;
}
saveTabsToStorage(tabList.value, activeTab.value);
}
}
}
// 关闭全部,只留首页
function closeAll() {
tabList.value = tabList.value.filter((t) => t.fullPath === defaultDashboardPath);
activeTab.value = defaultDashboardPath;
saveTabsToStorage(tabList.value, activeTab.value);
}
// 设置激活tab(不触发路由跳转,仅用于更新状态)
function setActiveTab(fullPath) {
activeTab.value = fullPath;
saveTabsToStorage(tabList.value, activeTab.value);
}
// 重置 tabs store 到初始状态(登出时使用)
function resetTabs() {
tabList.value = [{ title: '首页', fullPath: defaultDashboardPath, name: 'Dashboard' }];
activeTab.value = defaultDashboardPath;
// 清除 localStorage 中的 tabs 数据
localStorage.removeItem('tabs_list');
localStorage.removeItem('active_tab');
}
return {
tabList,
activeTab,
addTab,
removeTab,
closeOthers,
closeLeft,
closeRight,
closeAll,
setActiveTab,
saveTabsToStorage,
resetTabs,
};
});
// ========== 菜单 Menu Store ==========
export { useMenuStore } from './menu';
+237 -237
View File
@@ -1,237 +1,237 @@
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,
};
});
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,
};
});