import type { CustomRoute, ElegantConstRoute, LastLevelRouteKey, RouteKey, RouteMap } from '@elegant-router/types' import type { RouteRecordRaw } from 'vue-router' import { useBoolean } from '@sa/hooks' import { defineStore } from 'pinia' import { computed, nextTick, ref, shallowRef } from 'vue' import { SetupStoreId } from '@/enum' import { router } from '@/router' import { getRouteName, getRoutePath } from '@/router/elegant/transform' import { createStaticRoutes, getAuthVueRoutes } from '@/router/routes' import { ROOT_ROUTE } from '@/router/routes/builtin' import { fetchGetConstantRoutes, fetchGetUserRoutes, fetchIsRouteExist } from '@/service/api' import { useAuthStore } from '../auth' import { useTabStore } from '../tab' import { filterAuthRoutesByRoles, getBreadcrumbsByRoute, getCacheRouteNames, getGlobalMenusByAuthRoutes, getSelectedMenuKeyPathByKey, isRouteExistByRouteName, sortRoutesByOrder, transformMenuToSearchMenus, updateLocaleOfGlobalMenus, } from './shared' export const useRouteStore = defineStore(SetupStoreId.Route, () => { const authStore = useAuthStore() const tabStore = useTabStore() const { bool: isInitConstantRoute, setBool: setIsInitConstantRoute } = useBoolean() const { bool: isInitAuthRoute, setBool: setIsInitAuthRoute } = useBoolean() /** * Auth route mode * * It recommends to use static mode in the development environment, and use dynamic mode in the production * environment, if use static mode in development environment, the auth routes will be auto generated by plugin * "@elegant-router/vue" */ const authRouteMode = ref(import.meta.env.VITE_AUTH_ROUTE_MODE) /** Home route key */ const routeHome = ref(import.meta.env.VITE_ROUTE_HOME) /** * Set route home * * @param routeKey Route key */ function setRouteHome(routeKey: LastLevelRouteKey) { routeHome.value = routeKey } /** constant routes */ 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()) } /** auth routes */ 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)[] = [] /** Global menus */ const menus = ref([]) const searchMenus = computed(() => transformMenuToSearchMenus(menus.value)) /** Get global menus */ function getGlobalMenus(routes: ElegantConstRoute[]) { menus.value = getGlobalMenusByAuthRoutes(routes) } /** Update global menus by locale */ function updateGlobalMenusByLocale() { menus.value = updateLocaleOfGlobalMenus(menus.value) } /** Cache routes */ const cacheRoutes = ref([]) /** * Exclude cache routes * * for reset route cache */ const excludeCacheRoutes = ref([]) /** * Get cache routes * * @param routes Vue routes */ function getCacheRoutes(routes: RouteRecordRaw[]) { cacheRoutes.value = getCacheRouteNames(routes) } /** * Reset route cache * * @default * @param routeKey */ async function resetRouteCache(routeKey?: RouteKey) { const routeName = routeKey || (router.currentRoute.value.name as RouteKey) excludeCacheRoutes.value.push(routeName) await nextTick() excludeCacheRoutes.value = [] } /** Global breadcrumbs */ const breadcrumbs = computed(() => getBreadcrumbsByRoute(router.currentRoute.value, menus.value)) /** Reset store */ async function resetStore() { const routeStore = useRouteStore() routeStore.$reset() resetVueRoutes() // after reset store, need to re-init constant route await initConstantRoute() } /** Reset vue routes */ function resetVueRoutes() { removeRouteFns.forEach(fn => fn()) removeRouteFns.length = 0 } /** init constant route */ async function initConstantRoute() { if (isInitConstantRoute.value) return const staticRoute = createStaticRoutes() if (authRouteMode.value === 'static') { addConstantRoutes(staticRoute.constantRoutes) } else { const { data, error } = await fetchGetConstantRoutes() if (!error) { addConstantRoutes(data) } else { // if fetch constant routes failed, use static constant routes addConstantRoutes(staticRoute.constantRoutes) } } handleConstantAndAuthRoutes() setIsInitConstantRoute(true) tabStore.initHomeTab() } /** Init auth route */ async function initAuthRoute() { // check if user info is initialized if (!authStore.userInfo.userId) { await authStore.initUserInfo() } if (authRouteMode.value === 'static') { initStaticAuthRoute() } else { await initDynamicAuthRoute() } tabStore.initHomeTab() } /** Init static auth route */ function initStaticAuthRoute() { const { authRoutes: staticAuthRoutes } = createStaticRoutes() if (authStore.isStaticSuper) { addAuthRoutes(staticAuthRoutes) } else { const filteredAuthRoutes = filterAuthRoutesByRoles(staticAuthRoutes, authStore.userInfo.roles) addAuthRoutes(filteredAuthRoutes) } handleConstantAndAuthRoutes() setIsInitAuthRoute(true) } /** Init dynamic auth route */ async function initDynamicAuthRoute() { const { data, error } = await fetchGetUserRoutes() if (!error) { const { routes, home } = data addAuthRoutes(routes) handleConstantAndAuthRoutes() setRouteHome(home) handleUpdateRootRouteRedirect(home) setIsInitAuthRoute(true) } else { // if fetch user routes failed, reset store authStore.resetStore() } } /** handle constant and auth routes */ function handleConstantAndAuthRoutes() { const allRoutes = [...constantRoutes.value, ...authRoutes.value] const sortRoutes = sortRoutesByOrder(allRoutes) const vueRoutes = getAuthVueRoutes(sortRoutes) resetVueRoutes() addRoutesToVueRouter(vueRoutes) getGlobalMenus(sortRoutes) getCacheRoutes(vueRoutes) } /** * Add routes to vue router * * @param routes Vue routes */ function addRoutesToVueRouter(routes: RouteRecordRaw[]) { routes.forEach((route) => { const removeFn = router.addRoute(route) addRemoveRouteFn(removeFn) }) } /** * Add remove route fn * * @param fn */ function addRemoveRouteFn(fn: () => void) { removeRouteFns.push(fn) } /** * Update root route redirect when auth route mode is dynamic * * @param redirectKey Redirect route key */ function handleUpdateRootRouteRedirect(redirectKey: LastLevelRouteKey) { const redirect = getRoutePath(redirectKey) if (redirect) { const rootRoute: CustomRoute = { ...ROOT_ROUTE, redirect } router.removeRoute(rootRoute.name) const [rootVueRoute] = getAuthVueRoutes([rootRoute]) router.addRoute(rootVueRoute) } } /** * Get is auth route exist * * @param routePath Route path */ async function getIsAuthRouteExist(routePath: RouteMap[RouteKey]) { const routeName = getRouteName(routePath) if (!routeName) { return false } if (authRouteMode.value === 'static') { const { authRoutes: staticAuthRoutes } = createStaticRoutes() return isRouteExistByRouteName(routeName, staticAuthRoutes) } const { data } = await fetchIsRouteExist(routeName) return data } /** * Get selected menu key path * * @param selectedKey Selected menu key */ function getSelectedMenuKeyPath(selectedKey: string) { return getSelectedMenuKeyPathByKey(selectedKey, menus.value) } async function onRouteSwitchWhenLoggedIn() { // some global init logic when logged in and switch route } async function onRouteSwitchWhenNotLoggedIn() { // some global init logic if it does not need to be logged in } return { resetStore, routeHome, menus, searchMenus, updateGlobalMenusByLocale, cacheRoutes, excludeCacheRoutes, resetRouteCache, breadcrumbs, initConstantRoute, isInitConstantRoute, initAuthRoute, isInitAuthRoute, setIsInitAuthRoute, getIsAuthRouteExist, getSelectedMenuKeyPath, onRouteSwitchWhenLoggedIn, onRouteSwitchWhenNotLoggedIn, } })