mirror of
				https://github.com/soybeanjs/soybean-admin.git
				synced 2025-11-04 15:53:43 +08:00 
			
		
		
		
	feat(projects): new router system [新的路由系统]
This commit is contained in:
		
							
								
								
									
										21
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								README.md
									
									
									
									
									
								
							@@ -65,27 +65,6 @@
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
## 开发计划
 | 
			
		||||
 | 
			
		||||
- [x] 引入 ECharts 替换 AntV G2Plot
 | 
			
		||||
- [x] 图表示例:ECharts、AntV G2
 | 
			
		||||
- [x] 多页签:支持 query、hash 等参数,同一页面支持多个 Tab
 | 
			
		||||
- [x] 缓存主题配置
 | 
			
		||||
- [x] 精简版(新分支 thin)
 | 
			
		||||
- [ ] v0.9.7 表单、表格示例(ing...)
 | 
			
		||||
- [ ] v0.9.8 可修改的 KeepAlive 的页面缓存和全局 Tab 组件 store 重构
 | 
			
		||||
- [ ] v0.9.9 全局 Iframe 组件
 | 
			
		||||
- [ ] v1.0 示例页面完善
 | 
			
		||||
- [ ] v1.0 版本文档
 | 
			
		||||
- [ ] element-plus 版本
 | 
			
		||||
- [ ] i18n 国际化
 | 
			
		||||
- [ ] 其他 UI 版本
 | 
			
		||||
- [ ] soybean-admin cli 工具(选择不同 UI)
 | 
			
		||||
- [ ] soybean-admin 后台服务 java 版: [soybean-admin-java](https://github.com/honghuangdc/soybean-admin-java)
 | 
			
		||||
- [ ] soybean-admin 后台服务 go 版: [soybean-admin-go](https://github.com/honghuangdc/soybean-admin-go)
 | 
			
		||||
- [ ] soybean-admin 后台服务 nodejs 版: [soybean-admin-nestjs](https://github.com/honghuangdc/soybean-admin-nestjs)
 | 
			
		||||
- [ ] 前端可视化创建路由页面
 | 
			
		||||
 | 
			
		||||
## 安装使用
 | 
			
		||||
 | 
			
		||||
- 环境配置
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ const apis: MockMethod[] = [
 | 
			
		||||
    response: (options: Service.MockOption): Service.MockServiceResult => {
 | 
			
		||||
      const { userId = undefined } = options.body;
 | 
			
		||||
 | 
			
		||||
      const routeHomeName: AuthRoute.RouteKey = 'dashboard_analysis';
 | 
			
		||||
      const routeHomeName: AuthRoute.LastDegreeRouteKey = 'dashboard_analysis';
 | 
			
		||||
 | 
			
		||||
      const role = userModel.find(item => item.userId === userId)?.userRole || 'user';
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -49,7 +49,8 @@
 | 
			
		||||
    "esno": "esno",
 | 
			
		||||
    "cleanup": "esno ./scripts/cleanup.ts",
 | 
			
		||||
    "update-pkg": "ncu --deep -u",
 | 
			
		||||
    "update-version": "bumpp package.json",
 | 
			
		||||
    "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
 | 
			
		||||
    "release": "standard-version",
 | 
			
		||||
    "prepare": "husky install"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
@@ -86,7 +87,7 @@
 | 
			
		||||
    "@iconify/json": "^2.1.133",
 | 
			
		||||
    "@iconify/vue": "^4.0.0",
 | 
			
		||||
    "@soybeanjs/cli": "^0.1.2",
 | 
			
		||||
    "@soybeanjs/router-page": "0.2.0",
 | 
			
		||||
    "@soybeanjs/router-page": "1.0.3",
 | 
			
		||||
    "@tauri-apps/cli": "^1.1.1",
 | 
			
		||||
    "@types/bmapgl": "^0.0.5",
 | 
			
		||||
    "@types/crypto-js": "^4.1.1",
 | 
			
		||||
@@ -97,7 +98,7 @@
 | 
			
		||||
    "@unocss/vite": "^0.46.3",
 | 
			
		||||
    "@vitejs/plugin-vue": "^3.2.0",
 | 
			
		||||
    "@vitejs/plugin-vue-jsx": "^2.1.0",
 | 
			
		||||
    "bumpp": "^8.2.1",
 | 
			
		||||
    "conventional-changelog": "^3.1.25",
 | 
			
		||||
    "cross-env": "^7.0.3",
 | 
			
		||||
    "eslint": "^8.27.0",
 | 
			
		||||
    "eslint-config-soybeanjs-vue": "^0.1.2",
 | 
			
		||||
@@ -108,6 +109,7 @@
 | 
			
		||||
    "rimraf": "^3.0.2",
 | 
			
		||||
    "rollup-plugin-visualizer": "^5.8.3",
 | 
			
		||||
    "sass": "^1.56.0",
 | 
			
		||||
    "standard-version": "^9.5.0",
 | 
			
		||||
    "typescript": "4.8.4",
 | 
			
		||||
    "unplugin-icons": "^0.14.13",
 | 
			
		||||
    "unplugin-vue-components": "0.22.8",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										850
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										850
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -3,7 +3,7 @@ import { breakpointsTailwind, useBreakpoints } from '@vueuse/core';
 | 
			
		||||
import { useAppStore, useThemeStore } from '@/store';
 | 
			
		||||
 | 
			
		||||
type LayoutMode = 'vertical' | 'horizontal';
 | 
			
		||||
type LayoutHeaderProps = Record<EnumType.ThemeLayoutMode, GlobalHeaderProps>;
 | 
			
		||||
type LayoutHeaderProps = Record<EnumType.ThemeLayoutMode, App.GlobalHeaderProps>;
 | 
			
		||||
 | 
			
		||||
export function useBasicLayout() {
 | 
			
		||||
  const app = useAppStore();
 | 
			
		||||
 
 | 
			
		||||
@@ -30,8 +30,10 @@ export enum EnumDataType {
 | 
			
		||||
  undefined = '[object Undefined]',
 | 
			
		||||
  object = '[object Object]',
 | 
			
		||||
  array = '[object Array]',
 | 
			
		||||
  function = '[object Function]',
 | 
			
		||||
  date = '[object Date]',
 | 
			
		||||
  regexp = '[object RegExp]',
 | 
			
		||||
  promise = '[object Promise]',
 | 
			
		||||
  set = '[object Set]',
 | 
			
		||||
  map = '[object Map]',
 | 
			
		||||
  file = '[object File]'
 | 
			
		||||
 
 | 
			
		||||
@@ -42,7 +42,7 @@ const routeStore = useRouteStore();
 | 
			
		||||
const { routerPush } = useRouterPush();
 | 
			
		||||
 | 
			
		||||
const breadcrumbs = computed(() =>
 | 
			
		||||
  getBreadcrumbByRouteKey(route.name as string, routeStore.menus as GlobalMenuOption[], routePath('root'))
 | 
			
		||||
  getBreadcrumbByRouteKey(route.name as string, routeStore.menus as App.GlobalMenuOption[], routePath('root'))
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
function dropdownSelect(key: string) {
 | 
			
		||||
 
 | 
			
		||||
@@ -28,11 +28,11 @@ const routeStore = useRouteStore();
 | 
			
		||||
const theme = useThemeStore();
 | 
			
		||||
const { routerPush } = useRouterPush();
 | 
			
		||||
 | 
			
		||||
const menus = computed(() => routeStore.menus as GlobalMenuOption[]);
 | 
			
		||||
const menus = computed(() => routeStore.menus as App.GlobalMenuOption[]);
 | 
			
		||||
const activeKey = computed(() => (route.meta?.activeMenu ? route.meta.activeMenu : route.name) as string);
 | 
			
		||||
 | 
			
		||||
function handleUpdateMenu(_key: string, item: MenuOption) {
 | 
			
		||||
  const menuItem = item as GlobalMenuOption;
 | 
			
		||||
  const menuItem = item as App.GlobalMenuOption;
 | 
			
		||||
  routerPush(menuItem.routePath);
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -38,7 +38,7 @@
 | 
			
		||||
defineOptions({ name: 'MessageList' });
 | 
			
		||||
 | 
			
		||||
interface Props {
 | 
			
		||||
  list?: Message.List[];
 | 
			
		||||
  list?: App.MessageList[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
withDefaults(defineProps<Props>(), {
 | 
			
		||||
 
 | 
			
		||||
@@ -62,7 +62,7 @@ const { bool: loading, setBool: setLoading } = useBoolean();
 | 
			
		||||
 | 
			
		||||
const currentTab = ref(0);
 | 
			
		||||
 | 
			
		||||
const tabData = ref<Message.Tab[]>([
 | 
			
		||||
const tabData = ref<App.MessageTab[]>([
 | 
			
		||||
  {
 | 
			
		||||
    key: 1,
 | 
			
		||||
    name: '通知',
 | 
			
		||||
 
 | 
			
		||||
@@ -39,11 +39,11 @@ defineOptions({ name: 'GlobalHeader' });
 | 
			
		||||
 | 
			
		||||
interface Props {
 | 
			
		||||
  /** 显示logo */
 | 
			
		||||
  showLogo: GlobalHeaderProps['showLogo'];
 | 
			
		||||
  showLogo: App.GlobalHeaderProps['showLogo'];
 | 
			
		||||
  /** 显示头部菜单 */
 | 
			
		||||
  showHeaderMenu: GlobalHeaderProps['showHeaderMenu'];
 | 
			
		||||
  showHeaderMenu: App.GlobalHeaderProps['showHeaderMenu'];
 | 
			
		||||
  /** 显示菜单折叠按钮 */
 | 
			
		||||
  showMenuCollapse: GlobalHeaderProps['showMenuCollapse'];
 | 
			
		||||
  showMenuCollapse: App.GlobalHeaderProps['showMenuCollapse'];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
defineProps<Props>();
 | 
			
		||||
 
 | 
			
		||||
@@ -42,7 +42,7 @@ interface Props {
 | 
			
		||||
  /** 菜单抽屉可见性 */
 | 
			
		||||
  visible: boolean;
 | 
			
		||||
  /** 子菜单数据 */
 | 
			
		||||
  menus: GlobalMenuOption[];
 | 
			
		||||
  menus: App.GlobalMenuOption[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const props = defineProps<Props>();
 | 
			
		||||
@@ -59,7 +59,7 @@ const activeKey = computed(() => (route.meta?.activeMenu ? route.meta.activeMenu
 | 
			
		||||
const expandedKeys = ref<string[]>([]);
 | 
			
		||||
 | 
			
		||||
function handleUpdateMenu(_key: string, item: MenuOption) {
 | 
			
		||||
  const menuItem = item as GlobalMenuOption;
 | 
			
		||||
  const menuItem = item as App.GlobalMenuOption;
 | 
			
		||||
  routerPush(menuItem.routePath);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -84,7 +84,7 @@ function resetFirstDegreeMenus() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const activeChildMenus = computed(() => {
 | 
			
		||||
  const menus: GlobalMenuOption[] = [];
 | 
			
		||||
  const menus: App.GlobalMenuOption[] = [];
 | 
			
		||||
  routeStore.menus.some(item => {
 | 
			
		||||
    const flag = item.routeName === activeParentRouteName.value && Boolean(item.children?.length);
 | 
			
		||||
    if (flag) {
 | 
			
		||||
 
 | 
			
		||||
@@ -31,13 +31,13 @@ const theme = useThemeStore();
 | 
			
		||||
const routeStore = useRouteStore();
 | 
			
		||||
const { routerPush } = useRouterPush();
 | 
			
		||||
 | 
			
		||||
const menus = computed(() => routeStore.menus as GlobalMenuOption[]);
 | 
			
		||||
const menus = computed(() => routeStore.menus as App.GlobalMenuOption[]);
 | 
			
		||||
 | 
			
		||||
const activeKey = computed(() => (route.meta?.activeMenu ? route.meta.activeMenu : route.name) as string);
 | 
			
		||||
const expandedKeys = ref<string[]>([]);
 | 
			
		||||
 | 
			
		||||
function handleUpdateMenu(_key: string, item: MenuOption) {
 | 
			
		||||
  const menuItem = item as GlobalMenuOption;
 | 
			
		||||
  const menuItem = item as App.GlobalMenuOption;
 | 
			
		||||
  routerPush(menuItem.routePath);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@ export async function createDynamicRouteGuard(
 | 
			
		||||
  if (!route.isInitAuthRoute) {
 | 
			
		||||
    // 未登录情况下直接回到登录页,登录成功后再加载权限路由
 | 
			
		||||
    if (!isLogin) {
 | 
			
		||||
      const toName = to.name as AuthRoute.RouteKey;
 | 
			
		||||
      const toName = to.name as AuthRoute.AllRouteKey;
 | 
			
		||||
      if (route.isValidConstantRoute(toName) && !to.meta.requiresAuth) {
 | 
			
		||||
        next();
 | 
			
		||||
      } else {
 | 
			
		||||
@@ -30,19 +30,19 @@ export async function createDynamicRouteGuard(
 | 
			
		||||
 | 
			
		||||
    await route.initAuthRoute();
 | 
			
		||||
 | 
			
		||||
    if (to.name === routeName('not-found-page')) {
 | 
			
		||||
      // 动态路由没有加载导致被not-found-page路由捕获,等待权限路由加载好了,回到之前的路由
 | 
			
		||||
    if (to.name === routeName('not-found')) {
 | 
			
		||||
      // 动态路由没有加载导致被not-found路由捕获,等待权限路由加载好了,回到之前的路由
 | 
			
		||||
      // 若路由是从根路由重定向过来的,重新回到根路由
 | 
			
		||||
      const ROOT_ROUTE_NAME: AuthRoute.RouteKey = 'root';
 | 
			
		||||
      const ROOT_ROUTE_NAME: AuthRoute.AllRouteKey = 'root';
 | 
			
		||||
      const path = to.redirectedFrom?.name === ROOT_ROUTE_NAME ? '/' : to.fullPath;
 | 
			
		||||
      next({ path, replace: true, query: to.query, hash: to.hash });
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 权限路由已经加载,仍然未找到,重定向到not-found
 | 
			
		||||
  if (to.name === routeName('not-found-page')) {
 | 
			
		||||
    next({ name: routeName('not-found'), replace: true });
 | 
			
		||||
  // 权限路由已经加载,仍然未找到,重定向到404
 | 
			
		||||
  if (to.name === routeName('not-found')) {
 | 
			
		||||
    next({ name: routeName('404'), replace: true });
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -61,7 +61,7 @@ export async function createPermissionGuard(
 | 
			
		||||
      // 登录状态进入需要登录权限的页面,无权限,重定向到无权限页面
 | 
			
		||||
      isLogin && needLogin && !hasPermission,
 | 
			
		||||
      () => {
 | 
			
		||||
        next({ name: routeName('no-permission') });
 | 
			
		||||
        next({ name: routeName('403') });
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  ];
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import type { App } from 'vue';
 | 
			
		||||
import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router';
 | 
			
		||||
import { transformAuthRoutesToVueRoutes, transformRouteNameToRoutePath } from '@/utils';
 | 
			
		||||
import { transformRouteNameToRoutePath } from '@/utils';
 | 
			
		||||
import { transformAuthRouteToVueRoutes } from '@/utils/router/transform';
 | 
			
		||||
import { constantRoutes } from './routes';
 | 
			
		||||
import { scrollBehavior } from './helpers';
 | 
			
		||||
import { createRouterGuard } from './guard';
 | 
			
		||||
@@ -9,7 +10,7 @@ const { VITE_HASH_ROUTE = 'N', VITE_BASE_URL } = import.meta.env;
 | 
			
		||||
 | 
			
		||||
export const router = createRouter({
 | 
			
		||||
  history: VITE_HASH_ROUTE === 'Y' ? createWebHashHistory(VITE_BASE_URL) : createWebHistory(VITE_BASE_URL),
 | 
			
		||||
  routes: transformAuthRoutesToVueRoutes(constantRoutes),
 | 
			
		||||
  routes: transformAuthRouteToVueRoutes(constantRoutes),
 | 
			
		||||
  scrollBehavior
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@@ -21,9 +22,9 @@ export async function setupRouter(app: App) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 路由名称 */
 | 
			
		||||
export const routeName = (key: AuthRoute.RouteKey) => key;
 | 
			
		||||
export const routeName = (key: AuthRoute.AllRouteKey) => key;
 | 
			
		||||
/** 路由路径 */
 | 
			
		||||
export const routePath = (key: Exclude<AuthRoute.RouteKey, 'not-found-page'>) => transformRouteNameToRoutePath(key);
 | 
			
		||||
export const routePath = (key: Exclude<AuthRoute.AllRouteKey, 'not-found'>) => transformRouteNameToRoutePath(key);
 | 
			
		||||
 | 
			
		||||
export * from './routes';
 | 
			
		||||
export * from './modules';
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
const about: AuthRoutes.Route = {
 | 
			
		||||
const about: AuthRoute.Route = {
 | 
			
		||||
  name: 'about',
 | 
			
		||||
  path: '/about',
 | 
			
		||||
  component: 'self',
 | 
			
		||||
 
 | 
			
		||||
@@ -39,8 +39,8 @@ export const constantRoutes: AuthRoute.Route[] = [
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: 'no-permission',
 | 
			
		||||
    path: '/no-permission',
 | 
			
		||||
    name: '403',
 | 
			
		||||
    path: '/403',
 | 
			
		||||
    component: 'self',
 | 
			
		||||
    meta: {
 | 
			
		||||
      title: '无权限',
 | 
			
		||||
@@ -48,8 +48,8 @@ export const constantRoutes: AuthRoute.Route[] = [
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: 'not-found',
 | 
			
		||||
    path: '/not-found',
 | 
			
		||||
    name: '404',
 | 
			
		||||
    path: '/404',
 | 
			
		||||
    component: 'self',
 | 
			
		||||
    meta: {
 | 
			
		||||
      title: '未找到',
 | 
			
		||||
@@ -57,8 +57,8 @@ export const constantRoutes: AuthRoute.Route[] = [
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: 'service-error',
 | 
			
		||||
    path: '/service-error',
 | 
			
		||||
    name: '500',
 | 
			
		||||
    path: '/500',
 | 
			
		||||
    component: 'self',
 | 
			
		||||
    meta: {
 | 
			
		||||
      title: '服务器错误',
 | 
			
		||||
@@ -67,7 +67,7 @@ export const constantRoutes: AuthRoute.Route[] = [
 | 
			
		||||
  },
 | 
			
		||||
  // 匹配无效路径的路由
 | 
			
		||||
  {
 | 
			
		||||
    name: 'not-found-page',
 | 
			
		||||
    name: 'not-found',
 | 
			
		||||
    path: '/:pathMatch(.*)*',
 | 
			
		||||
    component: 'blank',
 | 
			
		||||
    meta: {
 | 
			
		||||
 
 | 
			
		||||
@@ -7,12 +7,11 @@ import {
 | 
			
		||||
  getConstantRouteNames,
 | 
			
		||||
  getUserInfo,
 | 
			
		||||
  transformAuthRouteToMenu,
 | 
			
		||||
  transformAuthRouteToVueRoute,
 | 
			
		||||
  transformAuthRoutesToSearchMenus,
 | 
			
		||||
  transformAuthRoutesToVueRoutes,
 | 
			
		||||
  transformAuthRouteToSearchMenus,
 | 
			
		||||
  transformRouteNameToRoutePath,
 | 
			
		||||
  transformRoutePathToRouteName
 | 
			
		||||
} from '@/utils';
 | 
			
		||||
import { transformAuthRouteToVueRoutes, transformAuthRouteToVueRoute } from '@/utils/router/transform';
 | 
			
		||||
import { useAuthStore } from '../auth';
 | 
			
		||||
import { useTabStore } from '../tab';
 | 
			
		||||
 | 
			
		||||
@@ -26,9 +25,9 @@ interface RouteState {
 | 
			
		||||
  /** 是否初始化了权限路由 */
 | 
			
		||||
  isInitAuthRoute: boolean;
 | 
			
		||||
  /** 路由首页name(前端静态路由时生效,后端动态路由该值会被后端返回的值覆盖) */
 | 
			
		||||
  routeHomeName: AuthRoute.RouteKey;
 | 
			
		||||
  routeHomeName: AuthRoute.AllRouteKey;
 | 
			
		||||
  /** 菜单 */
 | 
			
		||||
  menus: GlobalMenuOption[];
 | 
			
		||||
  menus: App.GlobalMenuOption[];
 | 
			
		||||
  /** 搜索的菜单 */
 | 
			
		||||
  searchMenus: AuthRoute.Route[];
 | 
			
		||||
  /** 缓存的路由名称 */
 | 
			
		||||
@@ -54,7 +53,7 @@ export const useRouteStore = defineStore('route-store', {
 | 
			
		||||
    resetRoutes() {
 | 
			
		||||
      const routes = router.getRoutes();
 | 
			
		||||
      routes.forEach(route => {
 | 
			
		||||
        const name: AuthRoute.RouteKey = (route.name || 'root') as AuthRoute.RouteKey;
 | 
			
		||||
        const name = (route.name || 'root') as AuthRoute.AllRouteKey;
 | 
			
		||||
        if (!this.isConstantRoute(name)) {
 | 
			
		||||
          router.removeRoute(name);
 | 
			
		||||
        }
 | 
			
		||||
@@ -64,7 +63,7 @@ export const useRouteStore = defineStore('route-store', {
 | 
			
		||||
     * 是否是固定路由
 | 
			
		||||
     * @param name 路由名称
 | 
			
		||||
     */
 | 
			
		||||
    isConstantRoute(name: AuthRoute.RouteKey) {
 | 
			
		||||
    isConstantRoute(name: AuthRoute.AllRouteKey) {
 | 
			
		||||
      const constantRouteNames = getConstantRouteNames(constantRoutes);
 | 
			
		||||
      return constantRouteNames.includes(name);
 | 
			
		||||
    },
 | 
			
		||||
@@ -72,8 +71,8 @@ export const useRouteStore = defineStore('route-store', {
 | 
			
		||||
     * 是否是有效的固定路由
 | 
			
		||||
     * @param name 路由名称
 | 
			
		||||
     */
 | 
			
		||||
    isValidConstantRoute(name: AuthRoute.RouteKey) {
 | 
			
		||||
      const NOT_FOUND_PAGE_NAME: AuthRoute.RouteKey = 'not-found-page';
 | 
			
		||||
    isValidConstantRoute(name: AuthRoute.AllRouteKey) {
 | 
			
		||||
      const NOT_FOUND_PAGE_NAME: AuthRoute.NotFoundRouteKey = 'not-found';
 | 
			
		||||
      const constantRouteNames = getConstantRouteNames(constantRoutes);
 | 
			
		||||
      return constantRouteNames.includes(name) && name !== NOT_FOUND_PAGE_NAME;
 | 
			
		||||
    },
 | 
			
		||||
@@ -81,11 +80,11 @@ export const useRouteStore = defineStore('route-store', {
 | 
			
		||||
     * 处理权限路由
 | 
			
		||||
     * @param routes - 权限路由
 | 
			
		||||
     */
 | 
			
		||||
    handleAuthRoutes(routes: AuthRoute.Route[]) {
 | 
			
		||||
      (this.menus as GlobalMenuOption[]) = transformAuthRouteToMenu(routes);
 | 
			
		||||
      this.searchMenus = transformAuthRoutesToSearchMenus(routes);
 | 
			
		||||
    handleAuthRoute(routes: AuthRoute.Route[]) {
 | 
			
		||||
      (this.menus as App.GlobalMenuOption[]) = transformAuthRouteToMenu(routes);
 | 
			
		||||
      this.searchMenus = transformAuthRouteToSearchMenus(routes);
 | 
			
		||||
 | 
			
		||||
      const vueRoutes = transformAuthRoutesToVueRoutes(routes);
 | 
			
		||||
      const vueRoutes = transformAuthRouteToVueRoutes(routes);
 | 
			
		||||
 | 
			
		||||
      vueRoutes.forEach(route => {
 | 
			
		||||
        router.addRoute(route);
 | 
			
		||||
@@ -94,12 +93,12 @@ export const useRouteStore = defineStore('route-store', {
 | 
			
		||||
      this.cacheRoutes = getCacheRoutes(vueRoutes);
 | 
			
		||||
    },
 | 
			
		||||
    /** 动态路由模式下:更新根路由的重定向 */
 | 
			
		||||
    handleUpdateRootRedirect(routeKey: AuthRoute.RouteKey) {
 | 
			
		||||
      if (routeKey === 'root' || routeKey === 'not-found-page') {
 | 
			
		||||
        throw new Error('routeKey的值不能为root或者not-found-page');
 | 
			
		||||
    handleUpdateRootRedirect(routeKey: AuthRoute.AllRouteKey) {
 | 
			
		||||
      if (routeKey === 'root' || routeKey === 'not-found') {
 | 
			
		||||
        throw new Error('routeKey的值不能为root或者not-found');
 | 
			
		||||
      }
 | 
			
		||||
      const rootRoute: AuthRoute.Route = { ...ROOT_ROUTE, redirect: transformRouteNameToRoutePath(routeKey) };
 | 
			
		||||
      const rootRouteName: AuthRoute.RouteKey = 'root';
 | 
			
		||||
      const rootRouteName: AuthRoute.AllRouteKey = 'root';
 | 
			
		||||
      router.removeRoute(rootRouteName);
 | 
			
		||||
      const rootVueRoute = transformAuthRouteToVueRoute(rootRoute)[0];
 | 
			
		||||
      router.addRoute(rootVueRoute);
 | 
			
		||||
@@ -111,14 +110,14 @@ export const useRouteStore = defineStore('route-store', {
 | 
			
		||||
      if (data) {
 | 
			
		||||
        this.routeHomeName = data.home;
 | 
			
		||||
        this.handleUpdateRootRedirect(data.home);
 | 
			
		||||
        this.handleAuthRoutes(data.routes);
 | 
			
		||||
        this.handleAuthRoute(data.routes);
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    /** 初始化静态路由 */
 | 
			
		||||
    async initStaticRoute() {
 | 
			
		||||
      const auth = useAuthStore();
 | 
			
		||||
      const routes = filterAuthRoutesByUserPermission(staticRoutes, auth.userInfo.userRole);
 | 
			
		||||
      this.handleAuthRoutes(routes);
 | 
			
		||||
      this.handleAuthRoute(routes);
 | 
			
		||||
    },
 | 
			
		||||
    /** 初始化权限路由 */
 | 
			
		||||
    async initAuthRoute() {
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ import { getLocal, setLocal } from '@/utils';
 | 
			
		||||
export function getTabRouteByVueRoute(route: RouteRecordNormalized | RouteLocationNormalizedLoaded) {
 | 
			
		||||
  const fullPath = hasFullPath(route) ? route.fullPath : route.path;
 | 
			
		||||
 | 
			
		||||
  const tabRoute: GlobalTabRoute = {
 | 
			
		||||
  const tabRoute: App.GlobalTabRoute = {
 | 
			
		||||
    name: route.name,
 | 
			
		||||
    fullPath,
 | 
			
		||||
    meta: route.meta,
 | 
			
		||||
@@ -26,7 +26,7 @@ export function getTabRouteByVueRoute(route: RouteRecordNormalized | RouteLocati
 | 
			
		||||
 * @param tabs - 多页签数据
 | 
			
		||||
 * @param fullPath - 该页签的路径
 | 
			
		||||
 */
 | 
			
		||||
export function getIndexInTabRoutes(tabs: GlobalTabRoute[], fullPath: string) {
 | 
			
		||||
export function getIndexInTabRoutes(tabs: App.GlobalTabRoute[], fullPath: string) {
 | 
			
		||||
  return tabs.findIndex(tab => tab.fullPath === fullPath);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -35,7 +35,7 @@ export function getIndexInTabRoutes(tabs: GlobalTabRoute[], fullPath: string) {
 | 
			
		||||
 * @param tabs - 多页签数据
 | 
			
		||||
 * @param fullPath - 该页签的路径
 | 
			
		||||
 */
 | 
			
		||||
export function isInTabRoutes(tabs: GlobalTabRoute[], fullPath: string) {
 | 
			
		||||
export function isInTabRoutes(tabs: App.GlobalTabRoute[], fullPath: string) {
 | 
			
		||||
  return getIndexInTabRoutes(tabs, fullPath) > -1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -44,7 +44,7 @@ export function isInTabRoutes(tabs: GlobalTabRoute[], fullPath: string) {
 | 
			
		||||
 * @param tabs - 多页签数据
 | 
			
		||||
 * @param routeName - 路由名称
 | 
			
		||||
 */
 | 
			
		||||
export function getIndexInTabRoutesByRouteName(tabs: GlobalTabRoute[], routeName: string) {
 | 
			
		||||
export function getIndexInTabRoutesByRouteName(tabs: App.GlobalTabRoute[], routeName: string) {
 | 
			
		||||
  return tabs.findIndex(tab => tab.name === routeName);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -59,14 +59,14 @@ function hasFullPath(
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 缓存多页签数据 */
 | 
			
		||||
export function setTabRoutes(data: GlobalTabRoute[]) {
 | 
			
		||||
export function setTabRoutes(data: App.GlobalTabRoute[]) {
 | 
			
		||||
  setLocal(EnumStorageKey['multi-tab-routes'], data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 获取缓存的多页签数据 */
 | 
			
		||||
export function getTabRoutes() {
 | 
			
		||||
  const routes: GlobalTabRoute[] = [];
 | 
			
		||||
  const data = getLocal<GlobalTabRoute[]>(EnumStorageKey['multi-tab-routes']);
 | 
			
		||||
  const routes: App.GlobalTabRoute[] = [];
 | 
			
		||||
  const data = getLocal<App.GlobalTabRoute[]>(EnumStorageKey['multi-tab-routes']);
 | 
			
		||||
  if (data) {
 | 
			
		||||
    const defaultTabRoutes = data.map(item => ({
 | 
			
		||||
      ...item,
 | 
			
		||||
 
 | 
			
		||||
@@ -14,9 +14,9 @@ import {
 | 
			
		||||
 | 
			
		||||
interface TabState {
 | 
			
		||||
  /** 多页签数据 */
 | 
			
		||||
  tabs: GlobalTabRoute[];
 | 
			
		||||
  tabs: App.GlobalTabRoute[];
 | 
			
		||||
  /** 多页签首页 */
 | 
			
		||||
  homeTab: GlobalTabRoute;
 | 
			
		||||
  homeTab: App.GlobalTabRoute;
 | 
			
		||||
  /** 当前激活状态的页签(路由fullPath) */
 | 
			
		||||
  activeTab: string;
 | 
			
		||||
}
 | 
			
		||||
@@ -213,7 +213,7 @@ export const useTabStore = defineStore('tab-store', {
 | 
			
		||||
    iniTabStore(currentRoute: RouteLocationNormalizedLoaded) {
 | 
			
		||||
      const theme = useThemeStore();
 | 
			
		||||
 | 
			
		||||
      const tabs: GlobalTabRoute[] = theme.tab.isCache ? getTabRoutes() : [];
 | 
			
		||||
      const tabs: App.GlobalTabRoute[] = theme.tab.isCache ? getTabRoutes() : [];
 | 
			
		||||
 | 
			
		||||
      const hasHome = getIndexInTabRoutesByRouteName(tabs, this.homeTab.name as string) > -1;
 | 
			
		||||
      if (!hasHome && this.homeTab.name !== 'root') {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								src/typings/api.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								src/typings/api.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -18,7 +18,7 @@ declare namespace ApiRoute {
 | 
			
		||||
    /** 动态路由 */
 | 
			
		||||
    routes: AuthRoute.Route[];
 | 
			
		||||
    /** 路由首页对应的key */
 | 
			
		||||
    home: AuthRoute.RouteKey;
 | 
			
		||||
    home: AuthRoute.AllRouteKey;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								src/typings/env.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								src/typings/env.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -34,7 +34,7 @@ interface ImportMetaEnv {
 | 
			
		||||
   */
 | 
			
		||||
  readonly VITE_AUTH_ROUTE_MODE: 'static' | 'dynamic';
 | 
			
		||||
  /** 路由首页的路径 */
 | 
			
		||||
  readonly VITE_ROUTE_HOME_PATH: Exclude<AuthRoute.RoutePath, '/' | '/not-found-page' | '/:pathMatch(.*)*'>;
 | 
			
		||||
  readonly VITE_ROUTE_HOME_PATH: AuthRoute.RoutePath;
 | 
			
		||||
  /** iconify图标作为组件的前缀 */
 | 
			
		||||
  readonly VITE_ICON_PREFFIX: string;
 | 
			
		||||
  /**
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										210
									
								
								src/typings/route.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										210
									
								
								src/typings/route.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -1,74 +1,22 @@
 | 
			
		||||
/** 权限路由相关类型 */
 | 
			
		||||
declare namespace AuthRoute {
 | 
			
		||||
  /** 多级路由分割符号 */
 | 
			
		||||
  type RouteSplitMark = '_';
 | 
			
		||||
  /** 根路由路径 */
 | 
			
		||||
  type RootRoutePath = '/';
 | 
			
		||||
 | 
			
		||||
  /** 路由的key */
 | 
			
		||||
  type RouteKey =
 | 
			
		||||
    // 固定的路由
 | 
			
		||||
    | 'root' // 根路由
 | 
			
		||||
    | 'login'
 | 
			
		||||
    | 'not-found'
 | 
			
		||||
    | 'no-permission'
 | 
			
		||||
    | 'service-error'
 | 
			
		||||
    | 'constant-page'
 | 
			
		||||
    | 'not-found-page' // 捕获无效path的路由
 | 
			
		||||
    // 自定义路由
 | 
			
		||||
    | 'dashboard'
 | 
			
		||||
    | 'dashboard_analysis'
 | 
			
		||||
    | 'dashboard_workbench'
 | 
			
		||||
    | 'document'
 | 
			
		||||
    | 'document_vue'
 | 
			
		||||
    | 'document_vite'
 | 
			
		||||
    | 'document_naive'
 | 
			
		||||
    | 'document_project'
 | 
			
		||||
    | 'document_project-link'
 | 
			
		||||
    | 'component'
 | 
			
		||||
    | 'component_button'
 | 
			
		||||
    | 'component_card'
 | 
			
		||||
    | 'component_table'
 | 
			
		||||
    | 'plugin'
 | 
			
		||||
    | 'plugin_map'
 | 
			
		||||
    | 'plugin_video'
 | 
			
		||||
    | 'plugin_editor'
 | 
			
		||||
    | 'plugin_editor_quill'
 | 
			
		||||
    | 'plugin_editor_markdown'
 | 
			
		||||
    | 'plugin_copy'
 | 
			
		||||
    | 'plugin_icon'
 | 
			
		||||
    | 'plugin_print'
 | 
			
		||||
    | 'plugin_swiper'
 | 
			
		||||
    | 'plugin_charts'
 | 
			
		||||
    | 'plugin_charts_echarts'
 | 
			
		||||
    | 'plugin_charts_antv'
 | 
			
		||||
    | 'auth-demo'
 | 
			
		||||
    | 'auth-demo_permission'
 | 
			
		||||
    | 'auth-demo_super'
 | 
			
		||||
    | 'function'
 | 
			
		||||
    | 'function_tab'
 | 
			
		||||
    | 'function_tab-detail'
 | 
			
		||||
    | 'function_tab-multi-detail'
 | 
			
		||||
    | 'exception'
 | 
			
		||||
    | 'exception_403'
 | 
			
		||||
    | 'exception_404'
 | 
			
		||||
    | 'exception_500'
 | 
			
		||||
    | 'multi-menu'
 | 
			
		||||
    | 'multi-menu_first'
 | 
			
		||||
    | 'multi-menu_first_second'
 | 
			
		||||
    | 'multi-menu_first_second-new'
 | 
			
		||||
    | 'multi-menu_first_second-new_third'
 | 
			
		||||
    | 'management'
 | 
			
		||||
    | 'management_user'
 | 
			
		||||
    | 'management_role'
 | 
			
		||||
    | 'management_auth'
 | 
			
		||||
    | 'management_route'
 | 
			
		||||
    | 'about';
 | 
			
		||||
  /** 捕获无效路由的路由路径 */
 | 
			
		||||
  type NotFoundRoutePath = '/:pathMatch(.*)*';
 | 
			
		||||
 | 
			
		||||
  /** 路由的path */
 | 
			
		||||
  type RoutePath =
 | 
			
		||||
    | '/'
 | 
			
		||||
    | Exclude<KeyToPath<RouteKey>, '/root' | '/not-found-page'>
 | 
			
		||||
    | SingleRouteParentPath
 | 
			
		||||
    | '/:pathMatch(.*)*';
 | 
			
		||||
  type RootRouteKey = RouterPage.RootRouteKey;
 | 
			
		||||
 | 
			
		||||
  type NotFoundRouteKey = RouterPage.NotFoundRouteKey;
 | 
			
		||||
 | 
			
		||||
  type RouteKey = RouterPage.RouteKey;
 | 
			
		||||
 | 
			
		||||
  type LastDegreeRouteKey = RouterPage.LastDegreeRouteKey;
 | 
			
		||||
 | 
			
		||||
  type AllRouteKey = RouteKey | RootRouteKey | NotFoundRouteKey;
 | 
			
		||||
 | 
			
		||||
  /** 路由路径 */
 | 
			
		||||
  type RoutePath<K extends AllRouteKey = AllRouteKey> = AuthRouteUtils.GetRoutePath<K>;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 路由的组件
 | 
			
		||||
@@ -77,16 +25,16 @@ declare namespace AuthRoute {
 | 
			
		||||
   * - multi - 多级路由布局(三级路由或三级以上时,除第一级路由和最后一级路由,其余的采用该布局)
 | 
			
		||||
   * - self - 作为子路由,使用自身的布局(作为最后一级路由,没有子路由)
 | 
			
		||||
   */
 | 
			
		||||
  type RouteComponent = 'basic' | 'blank' | 'multi' | 'self';
 | 
			
		||||
  type RouteComponentType = 'basic' | 'blank' | 'multi' | 'self';
 | 
			
		||||
 | 
			
		||||
  /** 路由描述 */
 | 
			
		||||
  interface RouteMeta {
 | 
			
		||||
    /** 路由标题(可用来作document.title或者菜单的名称) */
 | 
			
		||||
    title: string;
 | 
			
		||||
    /** 路由的动态路径(需要动态路径的页面需要将path添加进范型参数) */
 | 
			
		||||
    dynamicPath?: PathToDynamicPath<'/login'>;
 | 
			
		||||
    dynamicPath?: AuthRouteUtils.GetDynamicPath<'/login'>;
 | 
			
		||||
    /** 作为单级路由的父级路由布局组件 */
 | 
			
		||||
    singleLayout?: Extract<RouteComponent, 'basic' | 'blank'>;
 | 
			
		||||
    singleLayout?: Extract<RouteComponentType, 'basic' | 'blank'>;
 | 
			
		||||
    /** 需要登录权限 */
 | 
			
		||||
    requiresAuth?: boolean;
 | 
			
		||||
    /**
 | 
			
		||||
@@ -96,7 +44,7 @@ declare namespace AuthRoute {
 | 
			
		||||
    permissions?: Auth.RoleType[];
 | 
			
		||||
    /** 缓存页面 */
 | 
			
		||||
    keepAlive?: boolean;
 | 
			
		||||
    /** 菜单和面包屑对应的图标(iconify图标名称) */
 | 
			
		||||
    /** 菜单和面包屑对应的图标 */
 | 
			
		||||
    icon?: string;
 | 
			
		||||
    /** 使用本地svg作为的菜单和面包屑对应的图标(assets/svg-icon文件夹的的svg文件名) */
 | 
			
		||||
    localIcon?: string;
 | 
			
		||||
@@ -114,62 +62,86 @@ declare namespace AuthRoute {
 | 
			
		||||
    multi?: boolean;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** 单个路由的类型结构(动态路由模式:后端返回此类型结构的路由) */
 | 
			
		||||
  interface Route {
 | 
			
		||||
    /** 路由名称(路由唯一标识) */
 | 
			
		||||
    name: RouteKey;
 | 
			
		||||
    /** 路由路径 */
 | 
			
		||||
    path: RoutePath;
 | 
			
		||||
    /** 路由重定向 */
 | 
			
		||||
    redirect?: RoutePath;
 | 
			
		||||
    /**
 | 
			
		||||
     * 路由组件
 | 
			
		||||
     * - basic: 基础布局,具有公共部分的布局
 | 
			
		||||
     * - blank: 空白布局
 | 
			
		||||
     * - multi: 多级路由布局(三级路由或三级以上时,除第一级路由和最后一级路由,其余的采用该布局)
 | 
			
		||||
     * - self: 作为子路由,使用自身的布局(作为最后一级路由,没有子路由)
 | 
			
		||||
     */
 | 
			
		||||
    component?: RouteComponent;
 | 
			
		||||
    /** 子路由 */
 | 
			
		||||
    children?: Route[];
 | 
			
		||||
    /** 路由描述 */
 | 
			
		||||
    meta: RouteMeta;
 | 
			
		||||
    /** 路由属性 */
 | 
			
		||||
    props?: boolean | Record<string, any> | ((to: any) => Record<string, any>);
 | 
			
		||||
  }
 | 
			
		||||
  type Route<K extends AllRouteKey = AllRouteKey> = K extends AllRouteKey
 | 
			
		||||
    ? {
 | 
			
		||||
        /** 路由名称(路由唯一标识) */
 | 
			
		||||
        name: K;
 | 
			
		||||
        /** 路由路径 */
 | 
			
		||||
        path: AuthRouteUtils.GetRoutePath<K>;
 | 
			
		||||
        /** 路由重定向 */
 | 
			
		||||
        redirect?: AuthRouteUtils.GetRoutePath;
 | 
			
		||||
        /**
 | 
			
		||||
         * 路由组件
 | 
			
		||||
         * - basic: 基础布局,具有公共部分的布局
 | 
			
		||||
         * - blank: 空白布局
 | 
			
		||||
         * - multi: 多级路由布局(三级路由或三级以上时,除第一级路由和最后一级路由,其余的采用该布局)
 | 
			
		||||
         * - self: 作为子路由,使用自身的布局(作为最后一级路由,没有子路由)
 | 
			
		||||
         */
 | 
			
		||||
        component?: RouteComponentType;
 | 
			
		||||
        /** 子路由 */
 | 
			
		||||
        children?: Route[];
 | 
			
		||||
        /** 路由描述 */
 | 
			
		||||
        meta: RouteMeta;
 | 
			
		||||
      } & Omit<import('vue-router').RouteRecordRaw, 'name' | 'path' | 'redirect' | 'component' | 'children' | 'meta'>
 | 
			
		||||
    : never;
 | 
			
		||||
 | 
			
		||||
  /** 前端导入的路由模块 */
 | 
			
		||||
  type RouteModule = Record<string, { default: AuthRoute.Route }>;
 | 
			
		||||
  type RouteModule = Record<string, { default: Route }>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
  /** 单独一级路由的key (单独路由需要添加一个父级路由用于应用布局组件) */
 | 
			
		||||
declare namespace AuthRouteUtils {
 | 
			
		||||
  /** 路由key层级分割符 */
 | 
			
		||||
  type RouteKeySplitMark = '_';
 | 
			
		||||
 | 
			
		||||
  /** 路由path层级分割符 */
 | 
			
		||||
  type RoutePathSplitMark = '/';
 | 
			
		||||
 | 
			
		||||
  /** 空白字符串 */
 | 
			
		||||
  type BlankString = '';
 | 
			
		||||
 | 
			
		||||
  /** key转换成path */
 | 
			
		||||
  type KeyToPath<K extends string> = K extends `${infer _Left}${RouteKeySplitMark}${RouteKeySplitMark}${infer _Right}`
 | 
			
		||||
    ? never
 | 
			
		||||
    : K extends `${infer Left}${RouteKeySplitMark}${infer Right}`
 | 
			
		||||
    ? Left extends BlankString
 | 
			
		||||
      ? never
 | 
			
		||||
      : Right extends BlankString
 | 
			
		||||
      ? never
 | 
			
		||||
      : KeyToPath<`${Left}${RoutePathSplitMark}${Right}`>
 | 
			
		||||
    : `${RoutePathSplitMark}${K}`;
 | 
			
		||||
 | 
			
		||||
  /** 根据路由key获取路由路径 */
 | 
			
		||||
  type GetRoutePath<K extends AuthRoute.AllRouteKey = AuthRoute.AllRouteKey> = K extends AuthRoute.AllRouteKey
 | 
			
		||||
    ? K extends AuthRoute.RootRouteKey
 | 
			
		||||
      ? AuthRoute.RootRoutePath
 | 
			
		||||
      : K extends AuthRoute.NotFoundRouteKey
 | 
			
		||||
      ? AuthRoute.NotFoundRoutePath
 | 
			
		||||
      : KeyToPath<K>
 | 
			
		||||
    : never;
 | 
			
		||||
 | 
			
		||||
  /** 获取一级路由(有子路由的一级路由和没有子路由的路由) */
 | 
			
		||||
  type GetFirstDegreeRouteKey<K extends AuthRoute.RouteKey = AuthRoute.RouteKey> =
 | 
			
		||||
    K extends `${infer _Left}${RouteKeySplitMark}${infer _Right}` ? never : K;
 | 
			
		||||
 | 
			
		||||
  /** 获取有子路由的一级路由 */
 | 
			
		||||
  type GetFirstDegreeRouteKeyWithChildren<K extends AuthRoute.RouteKey = AuthRoute.RouteKey> =
 | 
			
		||||
    K extends `${infer Left}${RouteKeySplitMark}${infer _Right}` ? Left : never;
 | 
			
		||||
 | 
			
		||||
  /** 单级路由的key (单级路由需要添加一个父级路由用于应用布局组件) */
 | 
			
		||||
  type SingleRouteKey = Exclude<
 | 
			
		||||
    GetSingleRouteKey<RouteKey>,
 | 
			
		||||
    GetRouteFirstParentKey<RouteKey> | 'root' | 'not-found-page'
 | 
			
		||||
    GetFirstDegreeRouteKey,
 | 
			
		||||
    GetFirstDegreeRouteKeyWithChildren | AuthRoute.RootRouteKey | AuthRoute.NotFoundRouteKey
 | 
			
		||||
  >;
 | 
			
		||||
 | 
			
		||||
  /** 单独路由父级路由key */
 | 
			
		||||
  type SingleRouteParentKey = `${SingleRouteKey}-parent`;
 | 
			
		||||
 | 
			
		||||
  /** 单独路由父级路由path */
 | 
			
		||||
  type SingleRouteParentPath = KeyToPath<SingleRouteParentKey>;
 | 
			
		||||
 | 
			
		||||
  /** 路由key转换路由path */
 | 
			
		||||
  type KeyToPath<Key extends string> = Key extends `${infer Left}_${infer Right}`
 | 
			
		||||
    ? KeyToPath<`${Left}/${Right}`>
 | 
			
		||||
    : `/${Key}`;
 | 
			
		||||
 | 
			
		||||
  /** 路由path转换动态路径 */
 | 
			
		||||
  type PathToDynamicPath<Path extends RoutePath> =
 | 
			
		||||
    | `${Path}/:${string}`
 | 
			
		||||
    | `${Path}/:${string}(${string})`
 | 
			
		||||
    | `${Path}/:${string}(${string})?`;
 | 
			
		||||
 | 
			
		||||
  /** 获取一级路由(包括有子路由的一级路由) */
 | 
			
		||||
  type GetSingleRouteKey<Key extends RouteKey> = Key extends `${infer _Left}${RouteSplitMark}${infer _Right}`
 | 
			
		||||
    ? never
 | 
			
		||||
    : Key;
 | 
			
		||||
 | 
			
		||||
  /** 获取子路由的一级父路由 */
 | 
			
		||||
  type GetRouteFirstParentKey<Key extends RouteKey> = Key extends `${infer Left}${RouteSplitMark}${infer _Right}`
 | 
			
		||||
    ? Left
 | 
			
		||||
    : never;
 | 
			
		||||
  /** 获取路由动态路径 */
 | 
			
		||||
  type GetDynamicPath<P extends AuthRoute.RoutePath> =
 | 
			
		||||
    | `${P}/:${string}`
 | 
			
		||||
    | `${P}/:${string}(${string})`
 | 
			
		||||
    | `${P}/:${string}(${string})?`;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										200
									
								
								src/typings/routes.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										200
									
								
								src/typings/routes.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -1,200 +0,0 @@
 | 
			
		||||
declare namespace AuthRoutes {
 | 
			
		||||
  /** 路由key */
 | 
			
		||||
  type RouteKey = ConstantRouteKey | AuthRouteKey;
 | 
			
		||||
 | 
			
		||||
  /** 根路由key */
 | 
			
		||||
  type RootRouteKey = 'root';
 | 
			
		||||
 | 
			
		||||
  /** 捕获无效路由的路由key */
 | 
			
		||||
  type RouteCaptureKey = 'not-found-page';
 | 
			
		||||
 | 
			
		||||
  /** 固定的路由key */
 | 
			
		||||
  type ConstantRouteKey = RootRouteKey | 'login' | 'not-found' | 'no-permission' | 'service-error' | RouteCaptureKey;
 | 
			
		||||
 | 
			
		||||
  /** 权限路由key */
 | 
			
		||||
  type AuthRouteKey =
 | 
			
		||||
    | 'dashboard'
 | 
			
		||||
    | 'dashboard_analysis'
 | 
			
		||||
    | 'dashboard_workbench'
 | 
			
		||||
    | 'document'
 | 
			
		||||
    | 'document_vue'
 | 
			
		||||
    | 'document_vue-new'
 | 
			
		||||
    | 'document_vite'
 | 
			
		||||
    | 'document_naive'
 | 
			
		||||
    | 'document_project'
 | 
			
		||||
    | 'component'
 | 
			
		||||
    | 'component_button'
 | 
			
		||||
    | 'component_card'
 | 
			
		||||
    | 'component_table'
 | 
			
		||||
    | 'plugin'
 | 
			
		||||
    | 'plugin_map'
 | 
			
		||||
    | 'plugin_video'
 | 
			
		||||
    | 'plugin_editor'
 | 
			
		||||
    | 'plugin_editor_quill'
 | 
			
		||||
    | 'plugin_editor_markdown'
 | 
			
		||||
    | 'plugin_copy'
 | 
			
		||||
    | 'plugin_icon'
 | 
			
		||||
    | 'plugin_print'
 | 
			
		||||
    | 'plugin_swiper'
 | 
			
		||||
    | 'plugin_charts'
 | 
			
		||||
    | 'plugin_charts_echarts'
 | 
			
		||||
    | 'plugin_charts_antv'
 | 
			
		||||
    | 'auth-demo'
 | 
			
		||||
    | 'auth-demo_permission'
 | 
			
		||||
    | 'auth-demo_super'
 | 
			
		||||
    | 'function'
 | 
			
		||||
    | 'function_tab'
 | 
			
		||||
    | 'function_tab-detail'
 | 
			
		||||
    | 'function_tab-multi-detail'
 | 
			
		||||
    | 'exception'
 | 
			
		||||
    | 'exception_403'
 | 
			
		||||
    | 'exception_404'
 | 
			
		||||
    | 'exception_500'
 | 
			
		||||
    | 'multi-menu'
 | 
			
		||||
    | 'multi-menu_first'
 | 
			
		||||
    | 'multi-menu_first_second'
 | 
			
		||||
    | 'multi-menu_first_second-new'
 | 
			
		||||
    | 'multi-menu_first_second-new_third'
 | 
			
		||||
    | 'management'
 | 
			
		||||
    | 'management_user'
 | 
			
		||||
    | 'management_role'
 | 
			
		||||
    | 'management_auth'
 | 
			
		||||
    | 'management_route'
 | 
			
		||||
    | 'about';
 | 
			
		||||
 | 
			
		||||
  /** 根路由路径 */
 | 
			
		||||
  type RootRoutePath = '/';
 | 
			
		||||
 | 
			
		||||
  /** 捕获无效路由的路由路径 */
 | 
			
		||||
  type RouteCapturePath = '/:pathMatch(.*)*';
 | 
			
		||||
 | 
			
		||||
  /** 路由路径 */
 | 
			
		||||
  type RoutePath<K extends RouteKey = RouteKey> = AuthRouteUtils.GetRoutePath<K>;
 | 
			
		||||
 | 
			
		||||
  /** 常用的路由路径 */
 | 
			
		||||
  type CommonRoutePath = Exclude<RoutePath, RootRoutePath | RouteCapturePath>;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 路由的组件
 | 
			
		||||
   * - basic - 基础布局,具有公共部分的布局
 | 
			
		||||
   * - blank - 空白布局
 | 
			
		||||
   * - multi - 多级路由布局(三级路由或三级以上时,除第一级路由和最后一级路由,其余的采用该布局)
 | 
			
		||||
   * - self - 作为子路由,使用自身的布局(作为最后一级路由,没有子路由)
 | 
			
		||||
   */
 | 
			
		||||
  type RouteComponent = 'basic' | 'blank' | 'multi' | 'self';
 | 
			
		||||
 | 
			
		||||
  /** 路由描述 */
 | 
			
		||||
  interface RouteMeta {
 | 
			
		||||
    /** 路由标题(可用来作document.title或者菜单的名称) */
 | 
			
		||||
    title: string;
 | 
			
		||||
    /** 路由的动态路径(需要动态路径的页面需要将path添加进范型参数) */
 | 
			
		||||
    dynamicPath?: AuthRouteUtils.GetDynamicPath<'/login'>;
 | 
			
		||||
    /** 作为单级路由的父级路由布局组件 */
 | 
			
		||||
    singleLayout?: Extract<RouteComponent, 'basic' | 'blank'>;
 | 
			
		||||
    /** 需要登录权限 */
 | 
			
		||||
    requiresAuth?: boolean;
 | 
			
		||||
    /**
 | 
			
		||||
     * 哪些类型的用户有权限才能访问的路由(空的话则表示不需要权限)
 | 
			
		||||
     * @description 后端动态路由数据不需要该属性,直接由后端根据用户角色返回对应权限的路由数据
 | 
			
		||||
     */
 | 
			
		||||
    permissions?: Auth.RoleType[];
 | 
			
		||||
    /** 缓存页面 */
 | 
			
		||||
    keepAlive?: boolean;
 | 
			
		||||
    /** 菜单和面包屑对应的图标 */
 | 
			
		||||
    icon?: string;
 | 
			
		||||
    /** 自定义的菜单和面包屑对应的图标 */
 | 
			
		||||
    customIcon?: string;
 | 
			
		||||
    /** 是否在菜单中隐藏(一些列表、表格的详情页面需要通过参数跳转,所以不能显示在菜单中) */
 | 
			
		||||
    hide?: boolean;
 | 
			
		||||
    /** 外链链接 */
 | 
			
		||||
    href?: string;
 | 
			
		||||
    /** 是否支持多个tab页签(默认一个,即相同name的路由会被替换) */
 | 
			
		||||
    multiTab?: boolean;
 | 
			
		||||
    /** 路由顺序,可用于菜单的排序 */
 | 
			
		||||
    order?: number;
 | 
			
		||||
    /** 当前路由需要选中的菜单项(用于跳转至不在左侧菜单显示的路由且需要高亮某个菜单的情况) */
 | 
			
		||||
    activeMenu?: RouteKey;
 | 
			
		||||
    /** 表示是否是多级路由的中间级路由(用于转换路由数据时筛选多级路由的标识,定义路由时不用填写) */
 | 
			
		||||
    multi?: boolean;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  type Route<K extends RouteKey = RouteKey> = K extends RouteKey
 | 
			
		||||
    ? {
 | 
			
		||||
        /** 路由名称(路由唯一标识) */
 | 
			
		||||
        name: K;
 | 
			
		||||
        /** 路由路径 */
 | 
			
		||||
        path: AuthRouteUtils.GetRoutePath<K>;
 | 
			
		||||
        /** 路由重定向 */
 | 
			
		||||
        redirect?: CommonRoutePath;
 | 
			
		||||
        /**
 | 
			
		||||
         * 路由组件
 | 
			
		||||
         * - basic: 基础布局,具有公共部分的布局
 | 
			
		||||
         * - blank: 空白布局
 | 
			
		||||
         * - multi: 多级路由布局(三级路由或三级以上时,除第一级路由和最后一级路由,其余的采用该布局)
 | 
			
		||||
         * - self: 作为子路由,使用自身的布局(作为最后一级路由,没有子路由)
 | 
			
		||||
         */
 | 
			
		||||
        component?: RouteComponent;
 | 
			
		||||
        /** 子路由 */
 | 
			
		||||
        children?: Route[];
 | 
			
		||||
        /** 路由描述 */
 | 
			
		||||
        meta: RouteMeta;
 | 
			
		||||
      } & Omit<import('vue-router').RouteRecordRaw, 'name' | 'path' | 'redirect' | 'component' | 'children' | 'meta'>
 | 
			
		||||
    : never;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
declare namespace AuthRouteUtils {
 | 
			
		||||
  /** 路由key层级分割符 */
 | 
			
		||||
  type RouteKeySplitMark = '_';
 | 
			
		||||
 | 
			
		||||
  /** 路由path层级分割符 */
 | 
			
		||||
  type RoutePathSplitMark = '/';
 | 
			
		||||
 | 
			
		||||
  /** 空白字符串 */
 | 
			
		||||
  type BlankString = '';
 | 
			
		||||
 | 
			
		||||
  /** key转换成path */
 | 
			
		||||
  type KeyToPath<K extends string> = K extends `${infer _Left}${RouteKeySplitMark}${RouteKeySplitMark}${infer _Right}`
 | 
			
		||||
    ? never
 | 
			
		||||
    : K extends `${infer Left}${RouteKeySplitMark}${infer Right}`
 | 
			
		||||
    ? Left extends BlankString
 | 
			
		||||
      ? never
 | 
			
		||||
      : Right extends BlankString
 | 
			
		||||
      ? never
 | 
			
		||||
      : KeyToPath<`${Left}${RoutePathSplitMark}${Right}`>
 | 
			
		||||
    : `${RoutePathSplitMark}${K}`;
 | 
			
		||||
 | 
			
		||||
  /** 根据路由key获取路由路径 */
 | 
			
		||||
  type GetRoutePath<K extends AuthRoutes.RouteKey = AuthRoutes.RouteKey> = K extends AuthRoutes.RouteKey
 | 
			
		||||
    ? K extends AuthRoutes.RootRouteKey
 | 
			
		||||
      ? AuthRoutes.RootRoutePath
 | 
			
		||||
      : K extends AuthRoutes.RouteCaptureKey
 | 
			
		||||
      ? AuthRoutes.RouteCapturePath
 | 
			
		||||
      : KeyToPath<K>
 | 
			
		||||
    : never;
 | 
			
		||||
 | 
			
		||||
  /** 获取一级路由(有子路由的一级路由和没有子路由的路由) */
 | 
			
		||||
  type GetFirstDegreeRouteKey<K extends AuthRoutes.RouteKey = AuthRoutes.RouteKey> =
 | 
			
		||||
    K extends `${infer _Left}${RouteKeySplitMark}${infer _Right}` ? never : K;
 | 
			
		||||
 | 
			
		||||
  /** 获取有子路由的一级路由 */
 | 
			
		||||
  type GetFirstDegreeRouteKeyWithChildren<K extends AuthRoutes.RouteKey = AuthRoutes.RouteKey> =
 | 
			
		||||
    K extends `${infer Left}${RouteKeySplitMark}${infer _Right}` ? Left : never;
 | 
			
		||||
 | 
			
		||||
  /** 单级路由的key (单级路由需要添加一个父级路由用于应用布局组件) */
 | 
			
		||||
  type SingleRouteKey = Exclude<
 | 
			
		||||
    GetFirstDegreeRouteKey,
 | 
			
		||||
    GetFirstDegreeRouteKeyWithChildren | AuthRoutes.RootRouteKey | AuthRoutes.RouteCaptureKey
 | 
			
		||||
  >;
 | 
			
		||||
 | 
			
		||||
  /** 单独路由父级路由key */
 | 
			
		||||
  type SingleRouteParentKey = `${SingleRouteKey}-parent`;
 | 
			
		||||
 | 
			
		||||
  /** 单独路由父级路由path */
 | 
			
		||||
  type SingleRouteParentPath = KeyToPath<SingleRouteParentKey>;
 | 
			
		||||
 | 
			
		||||
  /** 获取路由动态路径 */
 | 
			
		||||
  type GetDynamicPath<P extends AuthRoutes.CommonRoutePath> =
 | 
			
		||||
    | `${P}/:${string}`
 | 
			
		||||
    | `${P}/:${string}(${string})`
 | 
			
		||||
    | `${P}/:${string}(${string})?`;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										83
									
								
								src/typings/system.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										83
									
								
								src/typings/system.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -254,49 +254,48 @@ declare namespace Theme {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 全局头部属性 */
 | 
			
		||||
interface GlobalHeaderProps {
 | 
			
		||||
  /** 显示logo */
 | 
			
		||||
  showLogo: boolean;
 | 
			
		||||
  /** 显示头部菜单 */
 | 
			
		||||
  showHeaderMenu: boolean;
 | 
			
		||||
  /** 显示菜单折叠按钮 */
 | 
			
		||||
  showMenuCollapse: boolean;
 | 
			
		||||
}
 | 
			
		||||
declare namespace App {
 | 
			
		||||
  /** 全局头部属性 */
 | 
			
		||||
  interface GlobalHeaderProps {
 | 
			
		||||
    /** 显示logo */
 | 
			
		||||
    showLogo: boolean;
 | 
			
		||||
    /** 显示头部菜单 */
 | 
			
		||||
    showHeaderMenu: boolean;
 | 
			
		||||
    /** 显示菜单折叠按钮 */
 | 
			
		||||
    showMenuCollapse: boolean;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
/** 菜单项配置 */
 | 
			
		||||
type GlobalMenuOption = import('naive-ui').MenuOption & {
 | 
			
		||||
  key: string;
 | 
			
		||||
  label: string;
 | 
			
		||||
  routeName: string;
 | 
			
		||||
  routePath: string;
 | 
			
		||||
  icon?: () => import('vue').VNodeChild;
 | 
			
		||||
  children?: GlobalMenuOption[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** 面包屑 */
 | 
			
		||||
type GlobalBreadcrumb = import('naive-ui').DropdownOption & {
 | 
			
		||||
  key: string;
 | 
			
		||||
  label: string;
 | 
			
		||||
  disabled: boolean;
 | 
			
		||||
  routeName: string;
 | 
			
		||||
  hasChildren: boolean;
 | 
			
		||||
  children?: GlobalBreadcrumb[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** 多页签Tab的路由 */
 | 
			
		||||
interface GlobalTabRoute
 | 
			
		||||
  extends Pick<import('vue-router').RouteLocationNormalizedLoaded, 'name' | 'fullPath' | 'meta'> {
 | 
			
		||||
  /** 滚动的位置 */
 | 
			
		||||
  scrollPosition: {
 | 
			
		||||
    left: number;
 | 
			
		||||
    top: number;
 | 
			
		||||
  /** 菜单项配置 */
 | 
			
		||||
  type GlobalMenuOption = import('naive-ui').MenuOption & {
 | 
			
		||||
    key: string;
 | 
			
		||||
    label: string;
 | 
			
		||||
    routeName: string;
 | 
			
		||||
    routePath: string;
 | 
			
		||||
    icon?: () => import('vue').VNodeChild;
 | 
			
		||||
    children?: GlobalMenuOption[];
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 系统消息 */
 | 
			
		||||
declare namespace Message {
 | 
			
		||||
  interface Tab {
 | 
			
		||||
  /** 面包屑 */
 | 
			
		||||
  type GlobalBreadcrumb = import('naive-ui').DropdownOption & {
 | 
			
		||||
    key: string;
 | 
			
		||||
    label: string;
 | 
			
		||||
    disabled: boolean;
 | 
			
		||||
    routeName: string;
 | 
			
		||||
    hasChildren: boolean;
 | 
			
		||||
    children?: GlobalBreadcrumb[];
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  /** 多页签Tab的路由 */
 | 
			
		||||
  interface GlobalTabRoute
 | 
			
		||||
    extends Pick<import('vue-router').RouteLocationNormalizedLoaded, 'name' | 'fullPath' | 'meta'> {
 | 
			
		||||
    /** 滚动的位置 */
 | 
			
		||||
    scrollPosition: {
 | 
			
		||||
      left: number;
 | 
			
		||||
      top: number;
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  interface MessageTab {
 | 
			
		||||
    /** tab的key */
 | 
			
		||||
    key: number;
 | 
			
		||||
    /** tab名称 */
 | 
			
		||||
@@ -304,10 +303,10 @@ declare namespace Message {
 | 
			
		||||
    /** badge类型 */
 | 
			
		||||
    badgeProps?: import('naive-ui').BadgeProps;
 | 
			
		||||
    /** 消息数据 */
 | 
			
		||||
    list: List[];
 | 
			
		||||
    list: MessageList[];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  interface List {
 | 
			
		||||
  interface MessageList {
 | 
			
		||||
    /** 数据唯一值 */
 | 
			
		||||
    id: number;
 | 
			
		||||
    /** 头像 */
 | 
			
		||||
 
 | 
			
		||||
@@ -1,38 +1,57 @@
 | 
			
		||||
import { EnumDataType } from '@/enum';
 | 
			
		||||
 | 
			
		||||
export function isNumber(data: unknown) {
 | 
			
		||||
export function isNumber<T extends number>(data: T | unknown): data is T {
 | 
			
		||||
  return Object.prototype.toString.call(data) === EnumDataType.number;
 | 
			
		||||
}
 | 
			
		||||
export function isString(data: unknown) {
 | 
			
		||||
 | 
			
		||||
export function isString<T extends string>(data: T | unknown): data is T {
 | 
			
		||||
  return Object.prototype.toString.call(data) === EnumDataType.string;
 | 
			
		||||
}
 | 
			
		||||
export function isBoolean(data: unknown) {
 | 
			
		||||
 | 
			
		||||
export function isBoolean<T extends boolean>(data: T | unknown): data is T {
 | 
			
		||||
  return Object.prototype.toString.call(data) === EnumDataType.boolean;
 | 
			
		||||
}
 | 
			
		||||
export function isNull(data: unknown) {
 | 
			
		||||
 | 
			
		||||
export function isNull<T extends null>(data: T | unknown): data is T {
 | 
			
		||||
  return Object.prototype.toString.call(data) === EnumDataType.null;
 | 
			
		||||
}
 | 
			
		||||
export function isUndefined(data: unknown) {
 | 
			
		||||
 | 
			
		||||
export function isUndefined<T extends undefined>(data: T | unknown): data is T {
 | 
			
		||||
  return Object.prototype.toString.call(data) === EnumDataType.undefined;
 | 
			
		||||
}
 | 
			
		||||
export function isObject(data: unknown) {
 | 
			
		||||
 | 
			
		||||
export function isObject<T extends Record<string, any>>(data: T | unknown): data is T {
 | 
			
		||||
  return Object.prototype.toString.call(data) === EnumDataType.object;
 | 
			
		||||
}
 | 
			
		||||
export function isArray(data: unknown) {
 | 
			
		||||
 | 
			
		||||
export function isArray<T extends any[]>(data: T | unknown): data is T {
 | 
			
		||||
  return Object.prototype.toString.call(data) === EnumDataType.array;
 | 
			
		||||
}
 | 
			
		||||
export function isDate(data: unknown) {
 | 
			
		||||
 | 
			
		||||
export function isFunction<T extends (...args: any[]) => any | void | never>(data: T | unknown): data is T {
 | 
			
		||||
  return Object.prototype.toString.call(data) === EnumDataType.function;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function isDate<T extends Date>(data: T | unknown): data is T {
 | 
			
		||||
  return Object.prototype.toString.call(data) === EnumDataType.date;
 | 
			
		||||
}
 | 
			
		||||
export function isRegExp(data: unknown) {
 | 
			
		||||
 | 
			
		||||
export function isRegExp<T extends RegExp>(data: T | unknown): data is T {
 | 
			
		||||
  return Object.prototype.toString.call(data) === EnumDataType.regexp;
 | 
			
		||||
}
 | 
			
		||||
export function isSet(data: unknown) {
 | 
			
		||||
 | 
			
		||||
export function isPromise<T extends Promise<any>>(data: T | unknown): data is T {
 | 
			
		||||
  return Object.prototype.toString.call(data) === EnumDataType.promise;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function isSet<T extends Set<any>>(data: T | unknown): data is T {
 | 
			
		||||
  return Object.prototype.toString.call(data) === EnumDataType.set;
 | 
			
		||||
}
 | 
			
		||||
export function isMap(data: unknown) {
 | 
			
		||||
 | 
			
		||||
export function isMap<T extends Map<any, any>>(data: T | unknown): data is T {
 | 
			
		||||
  return Object.prototype.toString.call(data) === EnumDataType.map;
 | 
			
		||||
}
 | 
			
		||||
export function isFile(data: unknown) {
 | 
			
		||||
 | 
			
		||||
export function isFile<T extends File>(data: T | unknown): data is T {
 | 
			
		||||
  return Object.prototype.toString.call(data) === EnumDataType.file;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
 * @param menus - 菜单数据
 | 
			
		||||
 * @param rootPath - 根路由路径
 | 
			
		||||
 */
 | 
			
		||||
export function getBreadcrumbByRouteKey(activeKey: string, menus: GlobalMenuOption[], rootPath: string) {
 | 
			
		||||
export function getBreadcrumbByRouteKey(activeKey: string, menus: App.GlobalMenuOption[], rootPath: string) {
 | 
			
		||||
  const breadcrumbMenu = getBreadcrumbMenu(activeKey, menus);
 | 
			
		||||
  const breadcrumb = breadcrumbMenu.map(item => transformBreadcrumbMenuToBreadcrumb(item, rootPath));
 | 
			
		||||
  return breadcrumb;
 | 
			
		||||
@@ -15,8 +15,8 @@ export function getBreadcrumbByRouteKey(activeKey: string, menus: GlobalMenuOpti
 | 
			
		||||
 * @param activeKey - 当前页面路由的key
 | 
			
		||||
 * @param menus - 菜单数据
 | 
			
		||||
 */
 | 
			
		||||
function getBreadcrumbMenu(activeKey: string, menus: GlobalMenuOption[]) {
 | 
			
		||||
  const breadcrumbMenu: GlobalMenuOption[] = [];
 | 
			
		||||
function getBreadcrumbMenu(activeKey: string, menus: App.GlobalMenuOption[]) {
 | 
			
		||||
  const breadcrumbMenu: App.GlobalMenuOption[] = [];
 | 
			
		||||
  menus.some(menu => {
 | 
			
		||||
    const flag = activeKey.includes(menu.routeName);
 | 
			
		||||
    if (flag) {
 | 
			
		||||
@@ -32,15 +32,15 @@ function getBreadcrumbMenu(activeKey: string, menus: GlobalMenuOption[]) {
 | 
			
		||||
 * @param activeKey - 当前页面路由的key
 | 
			
		||||
 * @param menu - 单个菜单数据
 | 
			
		||||
 */
 | 
			
		||||
function getBreadcrumbMenuItem(activeKey: string, menu: GlobalMenuOption) {
 | 
			
		||||
  const breadcrumbMenu: GlobalMenuOption[] = [];
 | 
			
		||||
function getBreadcrumbMenuItem(activeKey: string, menu: App.GlobalMenuOption) {
 | 
			
		||||
  const breadcrumbMenu: App.GlobalMenuOption[] = [];
 | 
			
		||||
  if (activeKey === menu.routeName) {
 | 
			
		||||
    breadcrumbMenu.push(menu);
 | 
			
		||||
  }
 | 
			
		||||
  if (activeKey.includes(menu.routeName) && menu.children && menu.children.length) {
 | 
			
		||||
    breadcrumbMenu.push(menu);
 | 
			
		||||
    breadcrumbMenu.push(
 | 
			
		||||
      ...menu.children.map(item => getBreadcrumbMenuItem(activeKey, item as GlobalMenuOption)).flat(1)
 | 
			
		||||
      ...menu.children.map(item => getBreadcrumbMenuItem(activeKey, item as App.GlobalMenuOption)).flat(1)
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -52,9 +52,9 @@ function getBreadcrumbMenuItem(activeKey: string, menu: GlobalMenuOption) {
 | 
			
		||||
 * @param menu - 单个菜单数据
 | 
			
		||||
 * @param rootPath - 根路由路径
 | 
			
		||||
 */
 | 
			
		||||
function transformBreadcrumbMenuToBreadcrumb(menu: GlobalMenuOption, rootPath: string) {
 | 
			
		||||
function transformBreadcrumbMenuToBreadcrumb(menu: App.GlobalMenuOption, rootPath: string) {
 | 
			
		||||
  const hasChildren = Boolean(menu.children && menu.children.length);
 | 
			
		||||
  const breadcrumb: GlobalBreadcrumb = {
 | 
			
		||||
  const breadcrumb: App.GlobalBreadcrumb = {
 | 
			
		||||
    key: menu.routeName,
 | 
			
		||||
    label: menu.label as string,
 | 
			
		||||
    routeName: menu.routeName,
 | 
			
		||||
@@ -66,7 +66,7 @@ function transformBreadcrumbMenuToBreadcrumb(menu: GlobalMenuOption, rootPath: s
 | 
			
		||||
  }
 | 
			
		||||
  if (hasChildren) {
 | 
			
		||||
    breadcrumb.children = menu.children?.map(item =>
 | 
			
		||||
      transformBreadcrumbMenuToBreadcrumb(item as GlobalMenuOption, rootPath)
 | 
			
		||||
      transformBreadcrumbMenuToBreadcrumb(item as App.GlobalMenuOption, rootPath)
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
  return breadcrumb;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,35 +0,0 @@
 | 
			
		||||
import type { Component } from 'vue';
 | 
			
		||||
import { BasicLayout, BlankLayout } from '@/layouts';
 | 
			
		||||
import { views } from '@/views';
 | 
			
		||||
 | 
			
		||||
type LayoutComponent = Record<EnumType.LayoutComponentName, () => Promise<Component>>;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 获取页面导入的vue文件(懒加载的方式)
 | 
			
		||||
 * @param layoutType - 布局类型
 | 
			
		||||
 */
 | 
			
		||||
export function getLayoutComponent(layoutType: EnumType.LayoutComponentName) {
 | 
			
		||||
  const layoutComponent: LayoutComponent = {
 | 
			
		||||
    basic: BasicLayout,
 | 
			
		||||
    blank: BlankLayout
 | 
			
		||||
  };
 | 
			
		||||
  return layoutComponent[layoutType];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 获取页面导入的vue文件(懒加载的方式)
 | 
			
		||||
 * @param routeKey - 路由key
 | 
			
		||||
 */
 | 
			
		||||
export function getViewComponent(routeKey: AuthRoute.RouteKey) {
 | 
			
		||||
  if (!views[routeKey]) {
 | 
			
		||||
    window.console.error(`路由“${routeKey}”没有对应的组件文件!`);
 | 
			
		||||
  }
 | 
			
		||||
  return () => setViewComponentName(views[routeKey], routeKey) as Promise<Component>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 给页面组件设置名称 */
 | 
			
		||||
async function setViewComponentName(asyncComponent: () => Promise<Component>, name: string) {
 | 
			
		||||
  const component = (await asyncComponent()) as { default: Component };
 | 
			
		||||
  Object.assign(component.default, { name });
 | 
			
		||||
  return component;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +1,3 @@
 | 
			
		||||
import type { RouteRecordRaw } from 'vue-router';
 | 
			
		||||
import { getLayoutComponent, getViewComponent } from './component';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 获取所有固定路由的名称集合
 | 
			
		||||
 * @param routes - 固定路由
 | 
			
		||||
@@ -9,41 +6,30 @@ export function getConstantRouteNames(routes: AuthRoute.Route[]) {
 | 
			
		||||
  return routes.map(route => getConstantRouteName(route)).flat(1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 将权限路由转换成vue路由
 | 
			
		||||
 * @param routes - 权限路由
 | 
			
		||||
 * @description 所有多级路由都会被转换成二级路由
 | 
			
		||||
 */
 | 
			
		||||
export function transformAuthRoutesToVueRoutes(routes: AuthRoute.Route[]) {
 | 
			
		||||
  return routes.map(route => transformAuthRouteToVueRoute(route)).flat(1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 将权限路由转换成搜索的菜单数据
 | 
			
		||||
 * @param routes - 权限路由
 | 
			
		||||
 * @param treeMap
 | 
			
		||||
 */
 | 
			
		||||
export function transformAuthRoutesToSearchMenus(routes: AuthRoute.Route[], treeMap: AuthRoute.Route[] = []) {
 | 
			
		||||
export function transformAuthRouteToSearchMenus(routes: AuthRoute.Route[], treeMap: AuthRoute.Route[] = []) {
 | 
			
		||||
  if (routes && routes.length === 0) return [];
 | 
			
		||||
  return routes.reduce((acc, cur) => {
 | 
			
		||||
    if (!cur.meta?.hide) {
 | 
			
		||||
      acc.push(cur);
 | 
			
		||||
    }
 | 
			
		||||
    if (cur.children && cur.children.length > 0) {
 | 
			
		||||
      transformAuthRoutesToSearchMenus(cur.children, treeMap);
 | 
			
		||||
      transformAuthRouteToSearchMenus(cur.children, treeMap);
 | 
			
		||||
    }
 | 
			
		||||
    return acc;
 | 
			
		||||
  }, treeMap);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 将路由名字转换成路由路径 */
 | 
			
		||||
export function transformRouteNameToRoutePath(
 | 
			
		||||
  name: Exclude<AuthRoute.RouteKey, 'not-found-page'>
 | 
			
		||||
): AuthRoute.RoutePath {
 | 
			
		||||
export function transformRouteNameToRoutePath(name: Exclude<AuthRoute.AllRouteKey, 'not-found'>): AuthRoute.RoutePath {
 | 
			
		||||
  const rootPath: AuthRoute.RoutePath = '/';
 | 
			
		||||
  if (name === 'root') return rootPath;
 | 
			
		||||
 | 
			
		||||
  const splitMark: AuthRoute.RouteSplitMark = '_';
 | 
			
		||||
  const splitMark = '_';
 | 
			
		||||
  const pathSplitMark = '/';
 | 
			
		||||
  const path = name.split(splitMark).join(pathSplitMark);
 | 
			
		||||
 | 
			
		||||
@@ -51,15 +37,13 @@ export function transformRouteNameToRoutePath(
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 将路由路径转换成路由名字 */
 | 
			
		||||
export function transformRoutePathToRouteName(
 | 
			
		||||
  path: Exclude<AuthRoute.RoutePath, '/not-found-page' | '/:pathMatch(.*)*'>
 | 
			
		||||
): AuthRoute.RouteKey {
 | 
			
		||||
export function transformRoutePathToRouteName<K extends AuthRoute.RoutePath>(path: K) {
 | 
			
		||||
  if (path === '/') return 'root';
 | 
			
		||||
 | 
			
		||||
  const pathSplitMark = '/';
 | 
			
		||||
  const routeSplitMark: AuthRoute.RouteSplitMark = '_';
 | 
			
		||||
  const routeSplitMark = '_';
 | 
			
		||||
 | 
			
		||||
  const name = path.split(pathSplitMark).slice(1).join(routeSplitMark) as AuthRoute.RouteKey;
 | 
			
		||||
  const name = path.split(pathSplitMark).slice(1).join(routeSplitMark) as AuthRoute.AllRouteKey;
 | 
			
		||||
 | 
			
		||||
  return name;
 | 
			
		||||
}
 | 
			
		||||
@@ -76,140 +60,6 @@ function getConstantRouteName(route: AuthRoute.Route) {
 | 
			
		||||
  return names;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ComponentAction = Record<AuthRoute.RouteComponent, () => void>;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 将单个权限路由转换成vue路由
 | 
			
		||||
 * @param item - 单个权限路由
 | 
			
		||||
 */
 | 
			
		||||
export function transformAuthRouteToVueRoute(item: AuthRoute.Route) {
 | 
			
		||||
  const resultRoute: RouteRecordRaw[] = [];
 | 
			
		||||
 | 
			
		||||
  const itemRoute = { ...item } as RouteRecordRaw;
 | 
			
		||||
 | 
			
		||||
  // 动态path
 | 
			
		||||
  if (hasDynamicPath(item)) {
 | 
			
		||||
    Object.assign(itemRoute, { path: item.meta.dynamicPath });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 外链路由
 | 
			
		||||
  if (hasHref(item)) {
 | 
			
		||||
    Object.assign(itemRoute, { component: getViewComponent('not-found-page') });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 路由组件
 | 
			
		||||
  if (hasComponent(item)) {
 | 
			
		||||
    const action: ComponentAction = {
 | 
			
		||||
      basic() {
 | 
			
		||||
        itemRoute.component = getLayoutComponent('basic');
 | 
			
		||||
      },
 | 
			
		||||
      blank() {
 | 
			
		||||
        itemRoute.component = getLayoutComponent('blank');
 | 
			
		||||
      },
 | 
			
		||||
      multi() {
 | 
			
		||||
        // 多级路由一定有子路由
 | 
			
		||||
        if (hasChildren(item)) {
 | 
			
		||||
          Object.assign(itemRoute, { meta: { ...itemRoute.meta, multi: true } });
 | 
			
		||||
          delete itemRoute.component;
 | 
			
		||||
        } else {
 | 
			
		||||
          window.console.error('多级路由缺少子路由: ', item);
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      self() {
 | 
			
		||||
        itemRoute.component = getViewComponent(item.name);
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
    try {
 | 
			
		||||
      if (item.component) {
 | 
			
		||||
        action[item.component]();
 | 
			
		||||
      } else {
 | 
			
		||||
        window.console.error('路由组件解析失败: ', item);
 | 
			
		||||
      }
 | 
			
		||||
    } catch {
 | 
			
		||||
      window.console.error('路由组件解析失败: ', item);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 注意:单独路由没有children
 | 
			
		||||
  if (isSingleRoute(item)) {
 | 
			
		||||
    if (hasChildren(item)) {
 | 
			
		||||
      window.console.error('单独路由不应该有子路由: ', item);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 捕获无效路由的需特殊处理
 | 
			
		||||
    if (item.name === 'not-found-page') {
 | 
			
		||||
      itemRoute.children = [
 | 
			
		||||
        {
 | 
			
		||||
          path: '',
 | 
			
		||||
          name: item.name,
 | 
			
		||||
          component: getViewComponent('not-found-page')
 | 
			
		||||
        }
 | 
			
		||||
      ];
 | 
			
		||||
    } else {
 | 
			
		||||
      const parentPath = `${itemRoute.path}-parent` as AuthRoute.SingleRouteParentPath;
 | 
			
		||||
 | 
			
		||||
      const layout = item.meta.singleLayout === 'basic' ? getLayoutComponent('basic') : getLayoutComponent('blank');
 | 
			
		||||
 | 
			
		||||
      const parentRoute: RouteRecordRaw = {
 | 
			
		||||
        path: parentPath,
 | 
			
		||||
        component: layout,
 | 
			
		||||
        redirect: item.path,
 | 
			
		||||
        children: [itemRoute]
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      return [parentRoute];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 子路由
 | 
			
		||||
  if (hasChildren(item)) {
 | 
			
		||||
    const children = (item.children as AuthRoute.Route[]).map(child => transformAuthRouteToVueRoute(child)).flat();
 | 
			
		||||
 | 
			
		||||
    // 找出第一个不为多级路由中间级的子路由路径作为重定向路径
 | 
			
		||||
    const redirectPath: AuthRoute.RoutePath = (children.find(v => !v.meta?.multi)?.path || '/') as AuthRoute.RoutePath;
 | 
			
		||||
    if (redirectPath === '/') {
 | 
			
		||||
      window.console.error('该多级路由没有有效的子路径', item);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (item.component === 'multi') {
 | 
			
		||||
      // 多级路由,将子路由提取出来变成同级
 | 
			
		||||
      resultRoute.push(...children);
 | 
			
		||||
      delete itemRoute.children;
 | 
			
		||||
    } else {
 | 
			
		||||
      itemRoute.children = children;
 | 
			
		||||
    }
 | 
			
		||||
    itemRoute.redirect = redirectPath;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  resultRoute.push(itemRoute);
 | 
			
		||||
 | 
			
		||||
  return resultRoute;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 是否有外链
 | 
			
		||||
 * @param item - 权限路由
 | 
			
		||||
 */
 | 
			
		||||
function hasHref(item: AuthRoute.Route) {
 | 
			
		||||
  return Boolean(item.meta.href);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 是否有动态路由path
 | 
			
		||||
 * @param item - 权限路由
 | 
			
		||||
 */
 | 
			
		||||
function hasDynamicPath(item: AuthRoute.Route) {
 | 
			
		||||
  return Boolean(item.meta.dynamicPath);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 是否有路由组件
 | 
			
		||||
 * @param item - 权限路由
 | 
			
		||||
 */
 | 
			
		||||
function hasComponent(item: AuthRoute.Route) {
 | 
			
		||||
  return Boolean(item.component);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 是否有子路由
 | 
			
		||||
 * @param item - 权限路由
 | 
			
		||||
@@ -217,11 +67,3 @@ function hasComponent(item: AuthRoute.Route) {
 | 
			
		||||
function hasChildren(item: AuthRoute.Route) {
 | 
			
		||||
  return Boolean(item.children && item.children.length);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 是否是单层级路由
 | 
			
		||||
 * @param item - 权限路由
 | 
			
		||||
 */
 | 
			
		||||
function isSingleRoute(item: AuthRoute.Route) {
 | 
			
		||||
  return Boolean(item.meta.singleLayout);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,10 +7,10 @@ function hideInMenu(route: AuthRoute.Route) {
 | 
			
		||||
 | 
			
		||||
/** 给菜单添加可选属性 */
 | 
			
		||||
function addPartialProps(config: {
 | 
			
		||||
  menu: GlobalMenuOption;
 | 
			
		||||
  menu: App.GlobalMenuOption;
 | 
			
		||||
  icon?: string;
 | 
			
		||||
  localIcon?: string;
 | 
			
		||||
  children?: GlobalMenuOption[];
 | 
			
		||||
  children?: App.GlobalMenuOption[];
 | 
			
		||||
}) {
 | 
			
		||||
  const { iconRender } = useIconRender();
 | 
			
		||||
 | 
			
		||||
@@ -36,16 +36,16 @@ function addPartialProps(config: {
 | 
			
		||||
 * 将权限路由转换成菜单
 | 
			
		||||
 * @param routes - 路由
 | 
			
		||||
 */
 | 
			
		||||
export function transformAuthRouteToMenu(routes: AuthRoute.Route[]): GlobalMenuOption[] {
 | 
			
		||||
  const globalMenu: GlobalMenuOption[] = [];
 | 
			
		||||
export function transformAuthRouteToMenu(routes: AuthRoute.Route[]): App.GlobalMenuOption[] {
 | 
			
		||||
  const globalMenu: App.GlobalMenuOption[] = [];
 | 
			
		||||
  routes.forEach(route => {
 | 
			
		||||
    const { name, path, meta } = route;
 | 
			
		||||
    const routeName = name as string;
 | 
			
		||||
    let menuChildren: GlobalMenuOption[] | undefined;
 | 
			
		||||
    let menuChildren: App.GlobalMenuOption[] | undefined;
 | 
			
		||||
    if (route.children) {
 | 
			
		||||
      menuChildren = transformAuthRouteToMenu(route.children);
 | 
			
		||||
    }
 | 
			
		||||
    const menuItem: GlobalMenuOption = addPartialProps({
 | 
			
		||||
    const menuItem: App.GlobalMenuOption = addPartialProps({
 | 
			
		||||
      menu: {
 | 
			
		||||
        key: routeName,
 | 
			
		||||
        label: meta.title,
 | 
			
		||||
@@ -70,18 +70,18 @@ export function transformAuthRouteToMenu(routes: AuthRoute.Route[]): GlobalMenuO
 | 
			
		||||
 * @param activeKey - 当前路由的key
 | 
			
		||||
 * @param menus - 菜单数据
 | 
			
		||||
 */
 | 
			
		||||
export function getActiveKeyPathsOfMenus(activeKey: string, menus: GlobalMenuOption[]) {
 | 
			
		||||
export function getActiveKeyPathsOfMenus(activeKey: string, menus: App.GlobalMenuOption[]) {
 | 
			
		||||
  const keys = menus.map(menu => getActiveKeyPathsOfMenu(activeKey, menu)).flat(1);
 | 
			
		||||
  return keys;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getActiveKeyPathsOfMenu(activeKey: string, menu: GlobalMenuOption) {
 | 
			
		||||
function getActiveKeyPathsOfMenu(activeKey: string, menu: App.GlobalMenuOption) {
 | 
			
		||||
  const keys: string[] = [];
 | 
			
		||||
  if (activeKey.includes(menu.routeName)) {
 | 
			
		||||
    keys.push(menu.routeName);
 | 
			
		||||
  }
 | 
			
		||||
  if (menu.children) {
 | 
			
		||||
    keys.push(...menu.children.map(item => getActiveKeyPathsOfMenu(activeKey, item as GlobalMenuOption)).flat(1));
 | 
			
		||||
    keys.push(...menu.children.map(item => getActiveKeyPathsOfMenu(activeKey, item as App.GlobalMenuOption)).flat(1));
 | 
			
		||||
  }
 | 
			
		||||
  return keys;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										214
									
								
								src/utils/router/transform.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										214
									
								
								src/utils/router/transform.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,214 @@
 | 
			
		||||
import type { RouteComponent, RouteRecordRaw } from 'vue-router';
 | 
			
		||||
import { BasicLayout, BlankLayout } from '@/layouts';
 | 
			
		||||
import { views } from '@/views';
 | 
			
		||||
import { isFunction } from '@/utils';
 | 
			
		||||
 | 
			
		||||
type Lazy<T> = () => Promise<T>;
 | 
			
		||||
 | 
			
		||||
type LayoutComponent = Record<EnumType.LayoutComponentName, Lazy<RouteComponent>>;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 获取布局的vue文件(懒加载的方式)
 | 
			
		||||
 * @param layoutType - 布局类型
 | 
			
		||||
 */
 | 
			
		||||
export function getLayoutComponent(layoutType: EnumType.LayoutComponentName) {
 | 
			
		||||
  const layoutComponent: LayoutComponent = {
 | 
			
		||||
    basic: BasicLayout,
 | 
			
		||||
    blank: BlankLayout
 | 
			
		||||
  };
 | 
			
		||||
  return layoutComponent[layoutType];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 获取页面导入的vue文件
 | 
			
		||||
 * @param routeKey - 路由key
 | 
			
		||||
 */
 | 
			
		||||
export function getViewComponent(routeKey: AuthRoute.LastDegreeRouteKey) {
 | 
			
		||||
  if (!views[routeKey]) {
 | 
			
		||||
    throw new Error(`路由“${routeKey}”没有对应的组件文件!`);
 | 
			
		||||
  }
 | 
			
		||||
  return setViewComponentName(views[routeKey], routeKey);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface ModuleComponent {
 | 
			
		||||
  default: RouteComponent;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 给页面组件设置名称 */
 | 
			
		||||
function setViewComponentName(component: RouteComponent | Lazy<ModuleComponent>, name: string) {
 | 
			
		||||
  if (isAsyncComponent(component)) {
 | 
			
		||||
    return async () => {
 | 
			
		||||
      const result = await component();
 | 
			
		||||
      Object.assign(result.default, { name });
 | 
			
		||||
      return result;
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Object.assign(component, { name });
 | 
			
		||||
 | 
			
		||||
  return component;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function isAsyncComponent(component: RouteComponent | Lazy<ModuleComponent>): component is Lazy<ModuleComponent> {
 | 
			
		||||
  return isFunction(component);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 是否有外链
 | 
			
		||||
 * @param item - 权限路由
 | 
			
		||||
 */
 | 
			
		||||
function hasHref(item: AuthRoute.Route) {
 | 
			
		||||
  return Boolean(item.meta.href);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 是否有动态路由path
 | 
			
		||||
 * @param item - 权限路由
 | 
			
		||||
 */
 | 
			
		||||
function hasDynamicPath(item: AuthRoute.Route) {
 | 
			
		||||
  return Boolean(item.meta.dynamicPath);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 是否有路由组件
 | 
			
		||||
 * @param item - 权限路由
 | 
			
		||||
 */
 | 
			
		||||
function hasComponent(item: AuthRoute.Route) {
 | 
			
		||||
  return Boolean(item.component);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 是否有子路由
 | 
			
		||||
 * @param item - 权限路由
 | 
			
		||||
 */
 | 
			
		||||
function hasChildren(item: AuthRoute.Route) {
 | 
			
		||||
  return Boolean(item.children && item.children.length);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 是否是单层级路由
 | 
			
		||||
 * @param item - 权限路由
 | 
			
		||||
 */
 | 
			
		||||
function isSingleRoute(item: AuthRoute.Route) {
 | 
			
		||||
  return Boolean(item.meta.singleLayout);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 将权限路由转换成vue路由
 | 
			
		||||
 * @param routes - 权限路由
 | 
			
		||||
 * @description 所有多级路由都会被转换成二级路由
 | 
			
		||||
 */
 | 
			
		||||
export function transformAuthRouteToVueRoutes(routes: AuthRoute.Route[]) {
 | 
			
		||||
  return routes.map(route => transformAuthRouteToVueRoute(route)).flat(1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ComponentAction = Record<AuthRoute.RouteComponentType, () => void>;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 将单个权限路由转换成vue路由
 | 
			
		||||
 * @param item - 单个权限路由
 | 
			
		||||
 */
 | 
			
		||||
export function transformAuthRouteToVueRoute(item: AuthRoute.Route) {
 | 
			
		||||
  const resultRoute: RouteRecordRaw[] = [];
 | 
			
		||||
 | 
			
		||||
  const itemRoute = { ...item } as RouteRecordRaw;
 | 
			
		||||
 | 
			
		||||
  // 动态path
 | 
			
		||||
  if (hasDynamicPath(item)) {
 | 
			
		||||
    Object.assign(itemRoute, { path: item.meta.dynamicPath });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 外链路由
 | 
			
		||||
  if (hasHref(item)) {
 | 
			
		||||
    Object.assign(itemRoute, { component: getViewComponent('404') });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 路由组件
 | 
			
		||||
  if (hasComponent(item)) {
 | 
			
		||||
    const action: ComponentAction = {
 | 
			
		||||
      basic() {
 | 
			
		||||
        itemRoute.component = getLayoutComponent('basic');
 | 
			
		||||
      },
 | 
			
		||||
      blank() {
 | 
			
		||||
        itemRoute.component = getLayoutComponent('blank');
 | 
			
		||||
      },
 | 
			
		||||
      multi() {
 | 
			
		||||
        // 多级路由一定有子路由
 | 
			
		||||
        if (hasChildren(item)) {
 | 
			
		||||
          Object.assign(itemRoute, { meta: { ...itemRoute.meta, multi: true } });
 | 
			
		||||
          delete itemRoute.component;
 | 
			
		||||
        } else {
 | 
			
		||||
          window.console.error('多级路由缺少子路由: ', item);
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      self() {
 | 
			
		||||
        itemRoute.component = getViewComponent(item.name as AuthRoute.LastDegreeRouteKey);
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
    try {
 | 
			
		||||
      if (item.component) {
 | 
			
		||||
        action[item.component]();
 | 
			
		||||
      } else {
 | 
			
		||||
        window.console.error('路由组件解析失败: ', item);
 | 
			
		||||
      }
 | 
			
		||||
    } catch {
 | 
			
		||||
      window.console.error('路由组件解析失败: ', item);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 注意:单独路由没有children
 | 
			
		||||
  if (isSingleRoute(item)) {
 | 
			
		||||
    if (hasChildren(item)) {
 | 
			
		||||
      window.console.error('单独路由不应该有子路由: ', item);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 捕获无效路由的需特殊处理
 | 
			
		||||
    if (item.name === 'not-found') {
 | 
			
		||||
      itemRoute.children = [
 | 
			
		||||
        {
 | 
			
		||||
          path: '',
 | 
			
		||||
          name: item.name,
 | 
			
		||||
          component: getViewComponent('not-found')
 | 
			
		||||
        }
 | 
			
		||||
      ];
 | 
			
		||||
    } else {
 | 
			
		||||
      const parentPath = `${itemRoute.path}-parent` as AuthRouteUtils.SingleRouteKey;
 | 
			
		||||
 | 
			
		||||
      const layout = item.meta.singleLayout === 'basic' ? getLayoutComponent('basic') : getLayoutComponent('blank');
 | 
			
		||||
 | 
			
		||||
      const parentRoute: RouteRecordRaw = {
 | 
			
		||||
        path: parentPath,
 | 
			
		||||
        component: layout,
 | 
			
		||||
        redirect: item.path,
 | 
			
		||||
        children: [itemRoute]
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      return [parentRoute];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 子路由
 | 
			
		||||
  if (hasChildren(item)) {
 | 
			
		||||
    const children = (item.children as AuthRoute.Route[]).map(child => transformAuthRouteToVueRoute(child)).flat();
 | 
			
		||||
 | 
			
		||||
    // 找出第一个不为多级路由中间级的子路由路径作为重定向路径
 | 
			
		||||
    const redirectPath = (children.find(v => !v.meta?.multi)?.path || '/') as AuthRoute.RoutePath;
 | 
			
		||||
 | 
			
		||||
    if (redirectPath === '/') {
 | 
			
		||||
      window.console.error('该多级路由没有有效的子路径', item);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (item.component === 'multi') {
 | 
			
		||||
      // 多级路由,将子路由提取出来变成同级
 | 
			
		||||
      resultRoute.push(...children);
 | 
			
		||||
      delete itemRoute.children;
 | 
			
		||||
    } else {
 | 
			
		||||
      itemRoute.children = children;
 | 
			
		||||
    }
 | 
			
		||||
    itemRoute.redirect = redirectPath;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  resultRoute.push(itemRoute);
 | 
			
		||||
 | 
			
		||||
  return resultRoute;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								src/views/document/project-link/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/views/document/project-link/index.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div></div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts"></script>
 | 
			
		||||
 | 
			
		||||
<style scoped></style>
 | 
			
		||||
@@ -1,31 +1,45 @@
 | 
			
		||||
import type { Component } from 'vue';
 | 
			
		||||
import type { RouteComponent } from 'vue-router';
 | 
			
		||||
 | 
			
		||||
type ViewComponent = Record<string, () => Promise<Component>>;
 | 
			
		||||
 | 
			
		||||
const importViews = import.meta.glob('./**/index.vue') as ViewComponent;
 | 
			
		||||
 | 
			
		||||
const COMPONENTS_KEY = 'components';
 | 
			
		||||
const PREFIX = './';
 | 
			
		||||
const SUFFIX = '/index.vue';
 | 
			
		||||
const PATH_SPLIT_MARK = '/';
 | 
			
		||||
const ROUTE_KEY_SPLIT_MARK = '_';
 | 
			
		||||
/** 系统的内置路由,该文件夹名称不作为RouteKey */
 | 
			
		||||
const SYSTEM_VIEW = 'system-view_';
 | 
			
		||||
 | 
			
		||||
/** 过滤掉组件文件 */
 | 
			
		||||
const viewKeys = Object.keys(importViews).filter(key => !key.includes(COMPONENTS_KEY));
 | 
			
		||||
 | 
			
		||||
function getViewComponent() {
 | 
			
		||||
  const components: ViewComponent = {};
 | 
			
		||||
  viewKeys.forEach(key => {
 | 
			
		||||
    const routeKey = key
 | 
			
		||||
      .replace(PREFIX, '')
 | 
			
		||||
      .replace(SUFFIX, '')
 | 
			
		||||
      .replace(new RegExp(PATH_SPLIT_MARK, 'g'), ROUTE_KEY_SPLIT_MARK)
 | 
			
		||||
      .replace(SYSTEM_VIEW, '');
 | 
			
		||||
    components[routeKey] = importViews[key];
 | 
			
		||||
  });
 | 
			
		||||
  return components;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const views = getViewComponent();
 | 
			
		||||
export const views: Record<RouterPage.LastDegreeRouteKey, RouteComponent | (() => Promise<RouteComponent>)> = {
 | 
			
		||||
  403: () => import('./_builtin/403/index.vue'),
 | 
			
		||||
  404: () => import('./_builtin/404/index.vue'),
 | 
			
		||||
  500: () => import('./_builtin/500/index.vue'),
 | 
			
		||||
  'constant-page': () => import('./_builtin/constant-page/index.vue'),
 | 
			
		||||
  login: () => import('./_builtin/login/index.vue'),
 | 
			
		||||
  'not-found': () => import('./_builtin/not-found/index.vue'),
 | 
			
		||||
  about: () => import('./about/index.vue'),
 | 
			
		||||
  'auth-demo_permission': () => import('./auth-demo/permission/index.vue'),
 | 
			
		||||
  'auth-demo_super': () => import('./auth-demo/super/index.vue'),
 | 
			
		||||
  component_button: () => import('./component/button/index.vue'),
 | 
			
		||||
  component_card: () => import('./component/card/index.vue'),
 | 
			
		||||
  component_table: () => import('./component/table/index.vue'),
 | 
			
		||||
  dashboard_analysis: () => import('./dashboard/analysis/index.vue'),
 | 
			
		||||
  dashboard_workbench: () => import('./dashboard/workbench/index.vue'),
 | 
			
		||||
  document_naive: () => import('./document/naive/index.vue'),
 | 
			
		||||
  'document_project-link': () => import('./document/project-link/index.vue'),
 | 
			
		||||
  document_project: () => import('./document/project/index.vue'),
 | 
			
		||||
  document_vite: () => import('./document/vite/index.vue'),
 | 
			
		||||
  document_vue: () => import('./document/vue/index.vue'),
 | 
			
		||||
  exception_403: () => import('./exception/403/index.vue'),
 | 
			
		||||
  exception_404: () => import('./exception/404/index.vue'),
 | 
			
		||||
  exception_500: () => import('./exception/500/index.vue'),
 | 
			
		||||
  'function_tab-detail': () => import('./function/tab-detail/index.vue'),
 | 
			
		||||
  'function_tab-multi-detail': () => import('./function/tab-multi-detail/index.vue'),
 | 
			
		||||
  function_tab: () => import('./function/tab/index.vue'),
 | 
			
		||||
  management_auth: () => import('./management/auth/index.vue'),
 | 
			
		||||
  management_role: () => import('./management/role/index.vue'),
 | 
			
		||||
  management_route: () => import('./management/route/index.vue'),
 | 
			
		||||
  management_user: () => import('./management/user/index.vue'),
 | 
			
		||||
  'multi-menu_first_second-new_third': () => import('./multi-menu/first/second-new/third/index.vue'),
 | 
			
		||||
  'multi-menu_first_second': () => import('./multi-menu/first/second/index.vue'),
 | 
			
		||||
  plugin_charts_antv: () => import('./plugin/charts/antv/index.vue'),
 | 
			
		||||
  plugin_charts_echarts: () => import('./plugin/charts/echarts/index.vue'),
 | 
			
		||||
  plugin_copy: () => import('./plugin/copy/index.vue'),
 | 
			
		||||
  plugin_editor_markdown: () => import('./plugin/editor/markdown/index.vue'),
 | 
			
		||||
  plugin_editor_quill: () => import('./plugin/editor/quill/index.vue'),
 | 
			
		||||
  plugin_icon: () => import('./plugin/icon/index.vue'),
 | 
			
		||||
  plugin_map: () => import('./plugin/map/index.vue'),
 | 
			
		||||
  plugin_print: () => import('./plugin/print/index.vue'),
 | 
			
		||||
  plugin_swiper: () => import('./plugin/swiper/index.vue'),
 | 
			
		||||
  plugin_video: () => import('./plugin/video/index.vue')
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user