import { computed, ref, watch } from 'vue' import { defineStore } from 'pinia' import { loadDataApi } from '@/services/api' import { useAuthStore } from './auth' import { buildTree, sortTree } from '@/utils/tree' /** 递归裁剪菜单树:目录仅当存在可见子菜单时渲染(空目录不显示) */ function pruneByAccess(nodes) { const result = [] for (const node of nodes) { const children = node.children?.length ? pruneByAccess(node.children) : [] // 目录:没有可访问子菜单时不渲染 if (node.b_menu_type === 'directory') { if (children.length) result.push({ ...node, children }) continue } result.push(children.length ? { ...node, children } : { ...node }) } return result } /** * 动态导航菜单 store(侧栏菜单数据源)。 * * 菜单树由「菜单管理」维护在 s_menu 表,含目录 / 页面 / 外链三类节点。 * 可见性规则由前端执行(不改后端): * - 全部启用菜单按登录主体加载,目录仅当存在可见子菜单时渲染(空目录不显示); * - 模块中心等系统管理员专用页面不进 s_menu:入口固定写在 AppSidebar(仅 g3soft 渲染), * 路由层由 meta.adminOnly 守卫拦截非 g3soft,菜单管理也配不出这些路由。 * 与权限 store 一致:同一登录主体加载去重、并发请求复用、登录主体切换清空缓存。 */ export const useMenuStore = defineStore('menus', () => { const authStore = useAuthStore() /** s_menu 原始记录(b_canuse = 1,接口按 b_xh / b_id 排序返回) */ const rawMenus = ref([]) const loadedPrincipalKey = ref('') let loadingPromise = null let loadingPrincipalKey = '' let loadVersion = 0 /** * 加载菜单数据;同一主体重复调用去重,并发调用共享同一请求。 */ async function load() { const userId = String(authStore.loginInfo.user?.id || '') const principalKey = `${String(authStore.userInfo.orgId || '')}:${userId}` if (!authStore.isAuthenticated) return if (loadedPrincipalKey.value === principalKey) return if (loadingPromise && loadingPrincipalKey === principalKey) return loadingPromise const requestVersion = ++loadVersion loadingPrincipalKey = principalKey const request = loadDataApi('s_menu', 'b_canuse = 1', 'b_xh ASC, b_id ASC') .then((res) => { if (requestVersion !== loadVersion) return rawMenus.value = res.data || [] loadedPrincipalKey.value = principalKey }) .finally(() => { if (requestVersion !== loadVersion) return loadingPromise = null loadingPrincipalKey = '' }) loadingPromise = request return request } /** * 当前主体可见的菜单树(s_menu 原始行结构,含 children 子树)。 */ const visibleMenus = computed(() => { if (!rawMenus.value.length) return [] const tree = sortTree(buildTree(JSON.parse(JSON.stringify(rawMenus.value)))) return pruneByAccess(tree) }) function clear() { // 仅当“正在进行中的请求属于别的主体”时才作废它(如切换账号), // 否则保留进行中请求、只清空已加载数据,交由后续 load 结果填充。 const userId = String(authStore.loginInfo.user?.id || '') const principalKey = `${String(authStore.userInfo.orgId || '')}:${userId}` if (loadingPrincipalKey && loadingPrincipalKey !== principalKey) { loadVersion++ loadingPromise = null loadingPrincipalKey = '' } loadedPrincipalKey.value = '' rawMenus.value = [] } // 登录主体变化(退出/切换账号)时清空菜单缓存 watch( () => [ authStore.userInfo.orgId, authStore.loginInfo.user?.id, authStore.loginInfo.user?.account, authStore.loginInfo.token, ], clear, ) return { load, clear, rawMenus, visibleMenus, } })