import { computed, nextTick, ref, shallowRef } from 'vue'; import type { RouteRecordRaw } from 'vue-router'; import { defineStore } from 'pinia'; import { useBoolean } from '@sa/hooks'; import type { CustomRoute, ElegantConstRoute, LastLevelRouteKey, RouteKey, RouteMap } from '@elegant-router/types'; import { fetchGetUserRoutes, fetchIsRouteExist } from '@/service/api'; import { SetupStoreId } from '@/enum'; import { getRouteName, getRoutePath } from '@/router/elegant/transform'; import { getGlobalRouter } from '@/router/instance'; import { useAuthStore } from '../auth'; import { useDictStore } from '../dict'; import { useTabStore } from '../tab'; import { filterAuthRoutesByRoles, getBreadcrumbsByRoute, getCacheRouteNames, getGlobalMenusByAuthRoutes, getSelectedMenuKeyPathByKey, isRouteExistByRouteName, sortRoutesByOrder, transformMenuToSearchMenus, updateLocaleOfGlobalMenus } from './shared'; type RouteModule = typeof import('@/router/routes'); async function loadRouteModule(): Promise { return import('@/router/routes'); } function createRootRoute(redirect: string): CustomRoute { return { name: 'root', path: '/', redirect, meta: { title: 'root', constant: true } }; } export const useRouteStore = defineStore(SetupStoreId.Route, () => { const authStore = useAuthStore(); const dictStore = useDictStore(); const tabStore = useTabStore(); const { bool: isInitConstantRoute, setBool: setIsInitConstantRoute } = useBoolean(); const { bool: isInitAuthRoute, setBool: setIsInitAuthRoute } = useBoolean(); /** * 权限路由模式 * * 建议开发环境先使用 static,生产环境再切换到 dynamic。 * 在 static 模式下,权限路由由前端结合 "@elegant-router/vue" 自动生成。 */ const authRouteMode = ref(import.meta.env.VITE_AUTH_ROUTE_MODE); /** 首页路由 key */ const routeHome = ref(import.meta.env.VITE_ROUTE_HOME); /** * 设置首页路由 * * @param routeKey 路由 key */ function setRouteHome(routeKey: LastLevelRouteKey) { routeHome.value = routeKey; } /** 常量路由 */ const constantRoutes = shallowRef([]); function addConstantRoutes(routes: ElegantConstRoute[]) { const constantRoutesMap = new Map([]); routes.forEach(route => { constantRoutesMap.set(route.name, route); }); constantRoutes.value = Array.from(constantRoutesMap.values()); } /** 权限路由 */ const authRoutes = shallowRef([]); function addAuthRoutes(routes: ElegantConstRoute[]) { const authRoutesMap = new Map([]); routes.forEach(route => { authRoutesMap.set(route.name, route); }); authRoutes.value = Array.from(authRoutesMap.values()); } const removeRouteFns: (() => void)[] = []; /** 全局菜单 */ const menus = ref([]); const searchMenus = computed(() => transformMenuToSearchMenus(menus.value)); /** 生成全局菜单 */ function getGlobalMenus(routes: ElegantConstRoute[]) { menus.value = getGlobalMenusByAuthRoutes(routes); } /** 根据当前语言更新全局菜单文案 */ function updateGlobalMenusByLocale() { menus.value = updateLocaleOfGlobalMenus(menus.value); } /** 需要缓存的路由 */ const cacheRoutes = ref([]); /** * 排除缓存的路由 * * 用于重置指定路由缓存 */ const excludeCacheRoutes = ref([]); /** * 提取需要缓存的路由 * * @param routes Vue 路由 */ function getCacheRoutes(routes: RouteRecordRaw[]) { cacheRoutes.value = getCacheRouteNames(routes); } /** * 重置路由缓存 * * @default router.currentRoute.value.name 当前路由名 * @param routeKey */ async function resetRouteCache(routeKey?: RouteKey) { const routeName = routeKey || (getGlobalRouter().currentRoute.value.name as RouteKey); excludeCacheRoutes.value.push(routeName); await nextTick(); excludeCacheRoutes.value = []; } /** 全局面包屑 */ const breadcrumbs = computed(() => getBreadcrumbsByRoute(getGlobalRouter().currentRoute.value, menus.value)); /** 重置 store */ async function resetStore() { const routeStore = useRouteStore(); routeStore.$reset(); resetVueRoutes(); // 重置后需要重新初始化常量路由 await initConstantRoute(); } /** 重置已注册到 Vue Router 的路由 */ function resetVueRoutes() { removeRouteFns.forEach(fn => fn()); removeRouteFns.length = 0; } /** 初始化常量路由 */ async function initConstantRoute() { if (isInitConstantRoute.value) return; const { createStaticRoutes } = await loadRouteModule(); const staticRoute = createStaticRoutes(); addConstantRoutes(staticRoute.constantRoutes); await handleConstantAndAuthRoutes(); setIsInitConstantRoute(true); tabStore.initHomeTab(); } /** 初始化权限路由 */ async function initAuthRoute() { // 先确保用户信息已经初始化 if (!authStore.userInfo.userId) { await authStore.initUserInfo(); } await dictStore.initDictCache(); if (authRouteMode.value === 'static') { await initStaticAuthRoute(); } else { await initDynamicAuthRoute(); } tabStore.initHomeTab(); } /** 初始化静态权限路由 */ async function initStaticAuthRoute() { const { createStaticRoutes } = await loadRouteModule(); const { authRoutes: staticAuthRoutes } = createStaticRoutes(); if (authStore.isStaticSuper) { addAuthRoutes(staticAuthRoutes); } else { const filteredAuthRoutes = filterAuthRoutesByRoles(staticAuthRoutes, authStore.userInfo.roles); addAuthRoutes(filteredAuthRoutes); } await handleConstantAndAuthRoutes(); setIsInitAuthRoute(true); } /** 初始化动态权限路由 */ async function initDynamicAuthRoute() { const { data, error } = await fetchGetUserRoutes(); if (!error) { const { routes, home } = data; addAuthRoutes(routes); await handleConstantAndAuthRoutes(); setRouteHome(home); await handleUpdateRootRouteRedirect(home); setIsInitAuthRoute(true); } else { // 获取用户路由失败时,重置认证状态 authStore.resetStore(); } } /** 统一处理常量路由和权限路由 */ async function handleConstantAndAuthRoutes() { const { getAuthVueRoutes } = await loadRouteModule(); const allRoutes = [...constantRoutes.value, ...authRoutes.value]; const sortRoutes = sortRoutesByOrder(allRoutes); const vueRoutes = getAuthVueRoutes(sortRoutes); resetVueRoutes(); addRoutesToVueRouter(vueRoutes); getGlobalMenus(sortRoutes); getCacheRoutes(vueRoutes); } /** * 将路由注册到 Vue Router * * @param routes Vue 路由 */ function addRoutesToVueRouter(routes: RouteRecordRaw[]) { routes.forEach(route => { const removeFn = getGlobalRouter().addRoute(route); addRemoveRouteFn(removeFn); }); } /** * 记录移除路由的方法 * * @param fn */ function addRemoveRouteFn(fn: () => void) { removeRouteFns.push(fn); } /** * 在动态权限路由模式下更新根路由的重定向 * * @param redirectKey 重定向目标路由 key */ async function handleUpdateRootRouteRedirect(redirectKey: LastLevelRouteKey) { const redirect = getRoutePath(redirectKey); if (redirect) { const { getAuthVueRoutes } = await loadRouteModule(); const rootRoute = createRootRoute(redirect); const router = getGlobalRouter(); router.removeRoute(rootRoute.name); const [rootVueRoute] = getAuthVueRoutes([rootRoute]); router.addRoute(rootVueRoute); } } /** * 判断权限路由是否存在 * * @param routePath 路由路径 */ async function getIsAuthRouteExist(routePath: RouteMap[RouteKey]) { const routeName = getRouteName(routePath); if (!routeName) { return false; } if (authRouteMode.value === 'static') { const { createStaticRoutes } = await loadRouteModule(); const { authRoutes: staticAuthRoutes } = createStaticRoutes(); return isRouteExistByRouteName(routeName, staticAuthRoutes); } const { data } = await fetchIsRouteExist(routeName); return data; } /** * 获取当前选中菜单的 key 路径 * * @param selectedKey 当前选中的菜单 key */ function getSelectedMenuKeyPath(selectedKey: string) { return getSelectedMenuKeyPathByKey(selectedKey, menus.value); } async function onRouteSwitchWhenLoggedIn() { await authStore.initUserInfo(); } async function onRouteSwitchWhenNotLoggedIn() { // 这里可以放未登录情况下也需要执行的全局初始化逻辑 } return { resetStore, routeHome, authRoutes, menus, searchMenus, updateGlobalMenusByLocale, cacheRoutes, excludeCacheRoutes, resetRouteCache, breadcrumbs, initConstantRoute, isInitConstantRoute, initAuthRoute, isInitAuthRoute, setIsInitAuthRoute, getIsAuthRouteExist, getSelectedMenuKeyPath, onRouteSwitchWhenLoggedIn, onRouteSwitchWhenNotLoggedIn }; });