first commit
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, reactive } from 'vue'
|
||||
|
||||
// 用户信息类型
|
||||
const defaultUser = {
|
||||
id:'',
|
||||
account: '',
|
||||
name: '',
|
||||
group_id: '',
|
||||
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 || userInfo.id) || null,
|
||||
account: userInfo.account || userInfo.account,
|
||||
name: userInfo.name || userInfo.name,
|
||||
group_id: userInfo.group_id || userInfo.group_id,
|
||||
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
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import { getDictItemsByCode } from '@/api/dict'
|
||||
|
||||
/**
|
||||
* 字典 Store
|
||||
*
|
||||
* 用于全局管理系统字典数据
|
||||
* 缓存字典数据避免重复请求,提高性能
|
||||
*
|
||||
* 使用示例:
|
||||
* const dictStore = useDictStore()
|
||||
* const statusDict = await dictStore.getDictItems('user_status')
|
||||
* const roleDict = dictStore.getDictItemsSync('user_role') // 已加载则同步返回
|
||||
*/
|
||||
export const useDictStore = defineStore('dict', () => {
|
||||
// 字典缓存:{ dictCode: [...items] }
|
||||
const dictCache = ref({})
|
||||
|
||||
// 正在加载的字典代码集合
|
||||
const loadingCodes = ref(new Set())
|
||||
|
||||
/**
|
||||
* 获取字典项(异步)
|
||||
* @param {string} code - 字典编码,如 'user_status'
|
||||
* @returns {Promise<Array>} 字典项数组
|
||||
*/
|
||||
async function getDictItems(code) {
|
||||
// 如果缓存中已有,直接返回
|
||||
if (dictCache.value[code]) {
|
||||
return dictCache.value[code]
|
||||
}
|
||||
|
||||
// 避免重复请求:如果已在加载中,等待
|
||||
if (loadingCodes.value.has(code)) {
|
||||
// 等待加载完成(最多 5 秒)
|
||||
return await new Promise((resolve) => {
|
||||
let count = 0
|
||||
const timer = setInterval(() => {
|
||||
if (dictCache.value[code]) {
|
||||
clearInterval(timer)
|
||||
resolve(dictCache.value[code])
|
||||
}
|
||||
count++
|
||||
if (count > 50) {
|
||||
clearInterval(timer)
|
||||
resolve([])
|
||||
}
|
||||
}, 100)
|
||||
})
|
||||
}
|
||||
|
||||
// 标记为正在加载
|
||||
loadingCodes.value.add(code)
|
||||
|
||||
try {
|
||||
const res = await getDictItemsByCode(code)
|
||||
let items = []
|
||||
|
||||
// 兼容不同的 API 响应格式
|
||||
if (res?.data && Array.isArray(res.data)) {
|
||||
items = res.data
|
||||
} else if (Array.isArray(res)) {
|
||||
items = res
|
||||
} else if (res?.data?.data && Array.isArray(res.data.data)) {
|
||||
items = res.data.data
|
||||
}
|
||||
|
||||
// 缓存字典项
|
||||
dictCache.value[code] = items
|
||||
// console.log(`✅ 字典 [${code}] 已加载,共 ${items.length} 项`)
|
||||
|
||||
return items
|
||||
} catch (error) {
|
||||
console.error(`❌ 加载字典 [${code}] 失败:`, error)
|
||||
dictCache.value[code] = []
|
||||
return []
|
||||
} finally {
|
||||
// 移除加载标记
|
||||
loadingCodes.value.delete(code)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步获取字典项(如果已缓存)
|
||||
* @param {string} code - 字典编码
|
||||
* @returns {Array} 字典项数组,未缓存则返回空数组
|
||||
*/
|
||||
function getDictItemsSync(code) {
|
||||
return dictCache.value[code] || []
|
||||
}
|
||||
|
||||
/**
|
||||
* 预加载字典(在应用启动时调用)
|
||||
* @param {Array<string>} codes - 字典编码数组
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function preloadDicts(codes) {
|
||||
const promises = codes.map(code => getDictItems(code))
|
||||
await Promise.all(promises)
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据字典编码和值获取标签
|
||||
* @param {string} code - 字典编码
|
||||
* @param {any} value - 字典值
|
||||
* @returns {string} 字典标签
|
||||
*/
|
||||
function getDictLabel(code, value) {
|
||||
const items = getDictItemsSync(code)
|
||||
if (!items.length) {
|
||||
console.warn(`⚠️ 字典 [${code}] 未加载,无法获取标签`)
|
||||
return String(value)
|
||||
}
|
||||
|
||||
const item = items.find(i => String(i.dict_value) === String(value) || i.dict_value === value)
|
||||
return item ? item.dict_label : String(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据字典编码和标签获取值
|
||||
* @param {string} code - 字典编码
|
||||
* @param {string} label - 字典标签
|
||||
* @returns {any} 字典值
|
||||
*/
|
||||
function getDictValue(code, label) {
|
||||
const items = getDictItemsSync(code)
|
||||
if (!items.length) {
|
||||
console.warn(`⚠️ 字典 [${code}] 未加载,无法获取值`)
|
||||
return null
|
||||
}
|
||||
|
||||
const item = items.find(i => i.dict_label === label)
|
||||
return item ? item.dict_value : null
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空缓存
|
||||
* @param {string} code - 字典编码,不指定则清空所有
|
||||
*/
|
||||
function clearCache(code) {
|
||||
if (code) {
|
||||
delete dictCache.value[code]
|
||||
// console.log(`✅ 已清除字典 [${code}] 缓存`)
|
||||
} else {
|
||||
dictCache.value = {}
|
||||
// console.log(`✅ 已清除所有字典缓存`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新字典
|
||||
* @param {string} code - 字典编码
|
||||
* @returns {Promise<Array>}
|
||||
*/
|
||||
async function refreshDict(code) {
|
||||
clearCache(code)
|
||||
return getDictItems(code)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有已缓存的字典
|
||||
* @returns {Object}
|
||||
*/
|
||||
const allDicts = computed(() => dictCache.value)
|
||||
|
||||
return {
|
||||
dictCache,
|
||||
loadingCodes,
|
||||
getDictItems,
|
||||
getDictItemsSync,
|
||||
preloadDicts,
|
||||
getDictLabel,
|
||||
getDictValue,
|
||||
clearCache,
|
||||
refreshDict,
|
||||
allDicts,
|
||||
}
|
||||
})
|
||||
@@ -0,0 +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 = '/dashboard';
|
||||
|
||||
// 从 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 hasDashboard = tabs.some(t => t.fullPath === defaultDashboardPath);
|
||||
if (!hasDashboard) {
|
||||
tabs.unshift({ title: '首页', fullPath: defaultDashboardPath, name: 'Dashboard' });
|
||||
}
|
||||
return tabs;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('恢复 tabs 失败:', e);
|
||||
}
|
||||
return [{ title: '首页', fullPath: defaultDashboardPath, name: 'Dashboard' }];
|
||||
}
|
||||
|
||||
// 保存 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';
|
||||
@@ -0,0 +1,234 @@
|
||||
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.role || 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;
|
||||
// 注释掉后台更新缓存的逻辑,避免重复请求
|
||||
// fetchMenus(true).catch(err => {
|
||||
// console.warn('后台更新菜单失败:', err);
|
||||
// });
|
||||
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.role || 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
|
||||
});
|
||||
|
||||
// 如果出错,尝试使用缓存数据
|
||||
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,
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user