mirror of
https://github.com/soybeanjs/soybean-admin.git
synced 2025-09-17 17:26:38 +08:00
feat(projects): new router system [新的路由系统]
This commit is contained in:
parent
40c1e13b50
commit
c7b6a3fbec
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 => {
|
response: (options: Service.MockOption): Service.MockServiceResult => {
|
||||||
const { userId = undefined } = options.body;
|
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';
|
const role = userModel.find(item => item.userId === userId)?.userRole || 'user';
|
||||||
|
|
||||||
|
@ -49,7 +49,8 @@
|
|||||||
"esno": "esno",
|
"esno": "esno",
|
||||||
"cleanup": "esno ./scripts/cleanup.ts",
|
"cleanup": "esno ./scripts/cleanup.ts",
|
||||||
"update-pkg": "ncu --deep -u",
|
"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"
|
"prepare": "husky install"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -86,7 +87,7 @@
|
|||||||
"@iconify/json": "^2.1.133",
|
"@iconify/json": "^2.1.133",
|
||||||
"@iconify/vue": "^4.0.0",
|
"@iconify/vue": "^4.0.0",
|
||||||
"@soybeanjs/cli": "^0.1.2",
|
"@soybeanjs/cli": "^0.1.2",
|
||||||
"@soybeanjs/router-page": "0.2.0",
|
"@soybeanjs/router-page": "1.0.3",
|
||||||
"@tauri-apps/cli": "^1.1.1",
|
"@tauri-apps/cli": "^1.1.1",
|
||||||
"@types/bmapgl": "^0.0.5",
|
"@types/bmapgl": "^0.0.5",
|
||||||
"@types/crypto-js": "^4.1.1",
|
"@types/crypto-js": "^4.1.1",
|
||||||
@ -97,7 +98,7 @@
|
|||||||
"@unocss/vite": "^0.46.3",
|
"@unocss/vite": "^0.46.3",
|
||||||
"@vitejs/plugin-vue": "^3.2.0",
|
"@vitejs/plugin-vue": "^3.2.0",
|
||||||
"@vitejs/plugin-vue-jsx": "^2.1.0",
|
"@vitejs/plugin-vue-jsx": "^2.1.0",
|
||||||
"bumpp": "^8.2.1",
|
"conventional-changelog": "^3.1.25",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"eslint": "^8.27.0",
|
"eslint": "^8.27.0",
|
||||||
"eslint-config-soybeanjs-vue": "^0.1.2",
|
"eslint-config-soybeanjs-vue": "^0.1.2",
|
||||||
@ -108,6 +109,7 @@
|
|||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"rollup-plugin-visualizer": "^5.8.3",
|
"rollup-plugin-visualizer": "^5.8.3",
|
||||||
"sass": "^1.56.0",
|
"sass": "^1.56.0",
|
||||||
|
"standard-version": "^9.5.0",
|
||||||
"typescript": "4.8.4",
|
"typescript": "4.8.4",
|
||||||
"unplugin-icons": "^0.14.13",
|
"unplugin-icons": "^0.14.13",
|
||||||
"unplugin-vue-components": "0.22.8",
|
"unplugin-vue-components": "0.22.8",
|
||||||
|
850
pnpm-lock.yaml
850
pnpm-lock.yaml
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';
|
import { useAppStore, useThemeStore } from '@/store';
|
||||||
|
|
||||||
type LayoutMode = 'vertical' | 'horizontal';
|
type LayoutMode = 'vertical' | 'horizontal';
|
||||||
type LayoutHeaderProps = Record<EnumType.ThemeLayoutMode, GlobalHeaderProps>;
|
type LayoutHeaderProps = Record<EnumType.ThemeLayoutMode, App.GlobalHeaderProps>;
|
||||||
|
|
||||||
export function useBasicLayout() {
|
export function useBasicLayout() {
|
||||||
const app = useAppStore();
|
const app = useAppStore();
|
||||||
|
@ -30,8 +30,10 @@ export enum EnumDataType {
|
|||||||
undefined = '[object Undefined]',
|
undefined = '[object Undefined]',
|
||||||
object = '[object Object]',
|
object = '[object Object]',
|
||||||
array = '[object Array]',
|
array = '[object Array]',
|
||||||
|
function = '[object Function]',
|
||||||
date = '[object Date]',
|
date = '[object Date]',
|
||||||
regexp = '[object RegExp]',
|
regexp = '[object RegExp]',
|
||||||
|
promise = '[object Promise]',
|
||||||
set = '[object Set]',
|
set = '[object Set]',
|
||||||
map = '[object Map]',
|
map = '[object Map]',
|
||||||
file = '[object File]'
|
file = '[object File]'
|
||||||
|
@ -42,7 +42,7 @@ const routeStore = useRouteStore();
|
|||||||
const { routerPush } = useRouterPush();
|
const { routerPush } = useRouterPush();
|
||||||
|
|
||||||
const breadcrumbs = computed(() =>
|
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) {
|
function dropdownSelect(key: string) {
|
||||||
|
@ -28,11 +28,11 @@ const routeStore = useRouteStore();
|
|||||||
const theme = useThemeStore();
|
const theme = useThemeStore();
|
||||||
const { routerPush } = useRouterPush();
|
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 activeKey = computed(() => (route.meta?.activeMenu ? route.meta.activeMenu : route.name) as string);
|
||||||
|
|
||||||
function handleUpdateMenu(_key: string, item: MenuOption) {
|
function handleUpdateMenu(_key: string, item: MenuOption) {
|
||||||
const menuItem = item as GlobalMenuOption;
|
const menuItem = item as App.GlobalMenuOption;
|
||||||
routerPush(menuItem.routePath);
|
routerPush(menuItem.routePath);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
defineOptions({ name: 'MessageList' });
|
defineOptions({ name: 'MessageList' });
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
list?: Message.List[];
|
list?: App.MessageList[];
|
||||||
}
|
}
|
||||||
|
|
||||||
withDefaults(defineProps<Props>(), {
|
withDefaults(defineProps<Props>(), {
|
||||||
|
@ -62,7 +62,7 @@ const { bool: loading, setBool: setLoading } = useBoolean();
|
|||||||
|
|
||||||
const currentTab = ref(0);
|
const currentTab = ref(0);
|
||||||
|
|
||||||
const tabData = ref<Message.Tab[]>([
|
const tabData = ref<App.MessageTab[]>([
|
||||||
{
|
{
|
||||||
key: 1,
|
key: 1,
|
||||||
name: '通知',
|
name: '通知',
|
||||||
|
@ -39,11 +39,11 @@ defineOptions({ name: 'GlobalHeader' });
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
/** 显示logo */
|
/** 显示logo */
|
||||||
showLogo: GlobalHeaderProps['showLogo'];
|
showLogo: App.GlobalHeaderProps['showLogo'];
|
||||||
/** 显示头部菜单 */
|
/** 显示头部菜单 */
|
||||||
showHeaderMenu: GlobalHeaderProps['showHeaderMenu'];
|
showHeaderMenu: App.GlobalHeaderProps['showHeaderMenu'];
|
||||||
/** 显示菜单折叠按钮 */
|
/** 显示菜单折叠按钮 */
|
||||||
showMenuCollapse: GlobalHeaderProps['showMenuCollapse'];
|
showMenuCollapse: App.GlobalHeaderProps['showMenuCollapse'];
|
||||||
}
|
}
|
||||||
|
|
||||||
defineProps<Props>();
|
defineProps<Props>();
|
||||||
|
@ -42,7 +42,7 @@ interface Props {
|
|||||||
/** 菜单抽屉可见性 */
|
/** 菜单抽屉可见性 */
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
/** 子菜单数据 */
|
/** 子菜单数据 */
|
||||||
menus: GlobalMenuOption[];
|
menus: App.GlobalMenuOption[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<Props>();
|
const props = defineProps<Props>();
|
||||||
@ -59,7 +59,7 @@ const activeKey = computed(() => (route.meta?.activeMenu ? route.meta.activeMenu
|
|||||||
const expandedKeys = ref<string[]>([]);
|
const expandedKeys = ref<string[]>([]);
|
||||||
|
|
||||||
function handleUpdateMenu(_key: string, item: MenuOption) {
|
function handleUpdateMenu(_key: string, item: MenuOption) {
|
||||||
const menuItem = item as GlobalMenuOption;
|
const menuItem = item as App.GlobalMenuOption;
|
||||||
routerPush(menuItem.routePath);
|
routerPush(menuItem.routePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ function resetFirstDegreeMenus() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const activeChildMenus = computed(() => {
|
const activeChildMenus = computed(() => {
|
||||||
const menus: GlobalMenuOption[] = [];
|
const menus: App.GlobalMenuOption[] = [];
|
||||||
routeStore.menus.some(item => {
|
routeStore.menus.some(item => {
|
||||||
const flag = item.routeName === activeParentRouteName.value && Boolean(item.children?.length);
|
const flag = item.routeName === activeParentRouteName.value && Boolean(item.children?.length);
|
||||||
if (flag) {
|
if (flag) {
|
||||||
|
@ -31,13 +31,13 @@ const theme = useThemeStore();
|
|||||||
const routeStore = useRouteStore();
|
const routeStore = useRouteStore();
|
||||||
const { routerPush } = useRouterPush();
|
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 activeKey = computed(() => (route.meta?.activeMenu ? route.meta.activeMenu : route.name) as string);
|
||||||
const expandedKeys = ref<string[]>([]);
|
const expandedKeys = ref<string[]>([]);
|
||||||
|
|
||||||
function handleUpdateMenu(_key: string, item: MenuOption) {
|
function handleUpdateMenu(_key: string, item: MenuOption) {
|
||||||
const menuItem = item as GlobalMenuOption;
|
const menuItem = item as App.GlobalMenuOption;
|
||||||
routerPush(menuItem.routePath);
|
routerPush(menuItem.routePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ export async function createDynamicRouteGuard(
|
|||||||
if (!route.isInitAuthRoute) {
|
if (!route.isInitAuthRoute) {
|
||||||
// 未登录情况下直接回到登录页,登录成功后再加载权限路由
|
// 未登录情况下直接回到登录页,登录成功后再加载权限路由
|
||||||
if (!isLogin) {
|
if (!isLogin) {
|
||||||
const toName = to.name as AuthRoute.RouteKey;
|
const toName = to.name as AuthRoute.AllRouteKey;
|
||||||
if (route.isValidConstantRoute(toName) && !to.meta.requiresAuth) {
|
if (route.isValidConstantRoute(toName) && !to.meta.requiresAuth) {
|
||||||
next();
|
next();
|
||||||
} else {
|
} else {
|
||||||
@ -30,19 +30,19 @@ export async function createDynamicRouteGuard(
|
|||||||
|
|
||||||
await route.initAuthRoute();
|
await route.initAuthRoute();
|
||||||
|
|
||||||
if (to.name === routeName('not-found-page')) {
|
if (to.name === routeName('not-found')) {
|
||||||
// 动态路由没有加载导致被not-found-page路由捕获,等待权限路由加载好了,回到之前的路由
|
// 动态路由没有加载导致被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;
|
const path = to.redirectedFrom?.name === ROOT_ROUTE_NAME ? '/' : to.fullPath;
|
||||||
next({ path, replace: true, query: to.query, hash: to.hash });
|
next({ path, replace: true, query: to.query, hash: to.hash });
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 权限路由已经加载,仍然未找到,重定向到not-found
|
// 权限路由已经加载,仍然未找到,重定向到404
|
||||||
if (to.name === routeName('not-found-page')) {
|
if (to.name === routeName('not-found')) {
|
||||||
next({ name: routeName('not-found'), replace: true });
|
next({ name: routeName('404'), replace: true });
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ export async function createPermissionGuard(
|
|||||||
// 登录状态进入需要登录权限的页面,无权限,重定向到无权限页面
|
// 登录状态进入需要登录权限的页面,无权限,重定向到无权限页面
|
||||||
isLogin && needLogin && !hasPermission,
|
isLogin && needLogin && !hasPermission,
|
||||||
() => {
|
() => {
|
||||||
next({ name: routeName('no-permission') });
|
next({ name: routeName('403') });
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import type { App } from 'vue';
|
import type { App } from 'vue';
|
||||||
import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router';
|
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 { constantRoutes } from './routes';
|
||||||
import { scrollBehavior } from './helpers';
|
import { scrollBehavior } from './helpers';
|
||||||
import { createRouterGuard } from './guard';
|
import { createRouterGuard } from './guard';
|
||||||
@ -9,7 +10,7 @@ const { VITE_HASH_ROUTE = 'N', VITE_BASE_URL } = import.meta.env;
|
|||||||
|
|
||||||
export const router = createRouter({
|
export const router = createRouter({
|
||||||
history: VITE_HASH_ROUTE === 'Y' ? createWebHashHistory(VITE_BASE_URL) : createWebHistory(VITE_BASE_URL),
|
history: VITE_HASH_ROUTE === 'Y' ? createWebHashHistory(VITE_BASE_URL) : createWebHistory(VITE_BASE_URL),
|
||||||
routes: transformAuthRoutesToVueRoutes(constantRoutes),
|
routes: transformAuthRouteToVueRoutes(constantRoutes),
|
||||||
scrollBehavior
|
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 './routes';
|
||||||
export * from './modules';
|
export * from './modules';
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
const about: AuthRoutes.Route = {
|
const about: AuthRoute.Route = {
|
||||||
name: 'about',
|
name: 'about',
|
||||||
path: '/about',
|
path: '/about',
|
||||||
component: 'self',
|
component: 'self',
|
||||||
|
@ -39,8 +39,8 @@ export const constantRoutes: AuthRoute.Route[] = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'no-permission',
|
name: '403',
|
||||||
path: '/no-permission',
|
path: '/403',
|
||||||
component: 'self',
|
component: 'self',
|
||||||
meta: {
|
meta: {
|
||||||
title: '无权限',
|
title: '无权限',
|
||||||
@ -48,8 +48,8 @@ export const constantRoutes: AuthRoute.Route[] = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'not-found',
|
name: '404',
|
||||||
path: '/not-found',
|
path: '/404',
|
||||||
component: 'self',
|
component: 'self',
|
||||||
meta: {
|
meta: {
|
||||||
title: '未找到',
|
title: '未找到',
|
||||||
@ -57,8 +57,8 @@ export const constantRoutes: AuthRoute.Route[] = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'service-error',
|
name: '500',
|
||||||
path: '/service-error',
|
path: '/500',
|
||||||
component: 'self',
|
component: 'self',
|
||||||
meta: {
|
meta: {
|
||||||
title: '服务器错误',
|
title: '服务器错误',
|
||||||
@ -67,7 +67,7 @@ export const constantRoutes: AuthRoute.Route[] = [
|
|||||||
},
|
},
|
||||||
// 匹配无效路径的路由
|
// 匹配无效路径的路由
|
||||||
{
|
{
|
||||||
name: 'not-found-page',
|
name: 'not-found',
|
||||||
path: '/:pathMatch(.*)*',
|
path: '/:pathMatch(.*)*',
|
||||||
component: 'blank',
|
component: 'blank',
|
||||||
meta: {
|
meta: {
|
||||||
|
@ -7,12 +7,11 @@ import {
|
|||||||
getConstantRouteNames,
|
getConstantRouteNames,
|
||||||
getUserInfo,
|
getUserInfo,
|
||||||
transformAuthRouteToMenu,
|
transformAuthRouteToMenu,
|
||||||
transformAuthRouteToVueRoute,
|
transformAuthRouteToSearchMenus,
|
||||||
transformAuthRoutesToSearchMenus,
|
|
||||||
transformAuthRoutesToVueRoutes,
|
|
||||||
transformRouteNameToRoutePath,
|
transformRouteNameToRoutePath,
|
||||||
transformRoutePathToRouteName
|
transformRoutePathToRouteName
|
||||||
} from '@/utils';
|
} from '@/utils';
|
||||||
|
import { transformAuthRouteToVueRoutes, transformAuthRouteToVueRoute } from '@/utils/router/transform';
|
||||||
import { useAuthStore } from '../auth';
|
import { useAuthStore } from '../auth';
|
||||||
import { useTabStore } from '../tab';
|
import { useTabStore } from '../tab';
|
||||||
|
|
||||||
@ -26,9 +25,9 @@ interface RouteState {
|
|||||||
/** 是否初始化了权限路由 */
|
/** 是否初始化了权限路由 */
|
||||||
isInitAuthRoute: boolean;
|
isInitAuthRoute: boolean;
|
||||||
/** 路由首页name(前端静态路由时生效,后端动态路由该值会被后端返回的值覆盖) */
|
/** 路由首页name(前端静态路由时生效,后端动态路由该值会被后端返回的值覆盖) */
|
||||||
routeHomeName: AuthRoute.RouteKey;
|
routeHomeName: AuthRoute.AllRouteKey;
|
||||||
/** 菜单 */
|
/** 菜单 */
|
||||||
menus: GlobalMenuOption[];
|
menus: App.GlobalMenuOption[];
|
||||||
/** 搜索的菜单 */
|
/** 搜索的菜单 */
|
||||||
searchMenus: AuthRoute.Route[];
|
searchMenus: AuthRoute.Route[];
|
||||||
/** 缓存的路由名称 */
|
/** 缓存的路由名称 */
|
||||||
@ -54,7 +53,7 @@ export const useRouteStore = defineStore('route-store', {
|
|||||||
resetRoutes() {
|
resetRoutes() {
|
||||||
const routes = router.getRoutes();
|
const routes = router.getRoutes();
|
||||||
routes.forEach(route => {
|
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)) {
|
if (!this.isConstantRoute(name)) {
|
||||||
router.removeRoute(name);
|
router.removeRoute(name);
|
||||||
}
|
}
|
||||||
@ -64,7 +63,7 @@ export const useRouteStore = defineStore('route-store', {
|
|||||||
* 是否是固定路由
|
* 是否是固定路由
|
||||||
* @param name 路由名称
|
* @param name 路由名称
|
||||||
*/
|
*/
|
||||||
isConstantRoute(name: AuthRoute.RouteKey) {
|
isConstantRoute(name: AuthRoute.AllRouteKey) {
|
||||||
const constantRouteNames = getConstantRouteNames(constantRoutes);
|
const constantRouteNames = getConstantRouteNames(constantRoutes);
|
||||||
return constantRouteNames.includes(name);
|
return constantRouteNames.includes(name);
|
||||||
},
|
},
|
||||||
@ -72,8 +71,8 @@ export const useRouteStore = defineStore('route-store', {
|
|||||||
* 是否是有效的固定路由
|
* 是否是有效的固定路由
|
||||||
* @param name 路由名称
|
* @param name 路由名称
|
||||||
*/
|
*/
|
||||||
isValidConstantRoute(name: AuthRoute.RouteKey) {
|
isValidConstantRoute(name: AuthRoute.AllRouteKey) {
|
||||||
const NOT_FOUND_PAGE_NAME: AuthRoute.RouteKey = 'not-found-page';
|
const NOT_FOUND_PAGE_NAME: AuthRoute.NotFoundRouteKey = 'not-found';
|
||||||
const constantRouteNames = getConstantRouteNames(constantRoutes);
|
const constantRouteNames = getConstantRouteNames(constantRoutes);
|
||||||
return constantRouteNames.includes(name) && name !== NOT_FOUND_PAGE_NAME;
|
return constantRouteNames.includes(name) && name !== NOT_FOUND_PAGE_NAME;
|
||||||
},
|
},
|
||||||
@ -81,11 +80,11 @@ export const useRouteStore = defineStore('route-store', {
|
|||||||
* 处理权限路由
|
* 处理权限路由
|
||||||
* @param routes - 权限路由
|
* @param routes - 权限路由
|
||||||
*/
|
*/
|
||||||
handleAuthRoutes(routes: AuthRoute.Route[]) {
|
handleAuthRoute(routes: AuthRoute.Route[]) {
|
||||||
(this.menus as GlobalMenuOption[]) = transformAuthRouteToMenu(routes);
|
(this.menus as App.GlobalMenuOption[]) = transformAuthRouteToMenu(routes);
|
||||||
this.searchMenus = transformAuthRoutesToSearchMenus(routes);
|
this.searchMenus = transformAuthRouteToSearchMenus(routes);
|
||||||
|
|
||||||
const vueRoutes = transformAuthRoutesToVueRoutes(routes);
|
const vueRoutes = transformAuthRouteToVueRoutes(routes);
|
||||||
|
|
||||||
vueRoutes.forEach(route => {
|
vueRoutes.forEach(route => {
|
||||||
router.addRoute(route);
|
router.addRoute(route);
|
||||||
@ -94,12 +93,12 @@ export const useRouteStore = defineStore('route-store', {
|
|||||||
this.cacheRoutes = getCacheRoutes(vueRoutes);
|
this.cacheRoutes = getCacheRoutes(vueRoutes);
|
||||||
},
|
},
|
||||||
/** 动态路由模式下:更新根路由的重定向 */
|
/** 动态路由模式下:更新根路由的重定向 */
|
||||||
handleUpdateRootRedirect(routeKey: AuthRoute.RouteKey) {
|
handleUpdateRootRedirect(routeKey: AuthRoute.AllRouteKey) {
|
||||||
if (routeKey === 'root' || routeKey === 'not-found-page') {
|
if (routeKey === 'root' || routeKey === 'not-found') {
|
||||||
throw new Error('routeKey的值不能为root或者not-found-page');
|
throw new Error('routeKey的值不能为root或者not-found');
|
||||||
}
|
}
|
||||||
const rootRoute: AuthRoute.Route = { ...ROOT_ROUTE, redirect: transformRouteNameToRoutePath(routeKey) };
|
const rootRoute: AuthRoute.Route = { ...ROOT_ROUTE, redirect: transformRouteNameToRoutePath(routeKey) };
|
||||||
const rootRouteName: AuthRoute.RouteKey = 'root';
|
const rootRouteName: AuthRoute.AllRouteKey = 'root';
|
||||||
router.removeRoute(rootRouteName);
|
router.removeRoute(rootRouteName);
|
||||||
const rootVueRoute = transformAuthRouteToVueRoute(rootRoute)[0];
|
const rootVueRoute = transformAuthRouteToVueRoute(rootRoute)[0];
|
||||||
router.addRoute(rootVueRoute);
|
router.addRoute(rootVueRoute);
|
||||||
@ -111,14 +110,14 @@ export const useRouteStore = defineStore('route-store', {
|
|||||||
if (data) {
|
if (data) {
|
||||||
this.routeHomeName = data.home;
|
this.routeHomeName = data.home;
|
||||||
this.handleUpdateRootRedirect(data.home);
|
this.handleUpdateRootRedirect(data.home);
|
||||||
this.handleAuthRoutes(data.routes);
|
this.handleAuthRoute(data.routes);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/** 初始化静态路由 */
|
/** 初始化静态路由 */
|
||||||
async initStaticRoute() {
|
async initStaticRoute() {
|
||||||
const auth = useAuthStore();
|
const auth = useAuthStore();
|
||||||
const routes = filterAuthRoutesByUserPermission(staticRoutes, auth.userInfo.userRole);
|
const routes = filterAuthRoutesByUserPermission(staticRoutes, auth.userInfo.userRole);
|
||||||
this.handleAuthRoutes(routes);
|
this.handleAuthRoute(routes);
|
||||||
},
|
},
|
||||||
/** 初始化权限路由 */
|
/** 初始化权限路由 */
|
||||||
async initAuthRoute() {
|
async initAuthRoute() {
|
||||||
|
@ -9,7 +9,7 @@ import { getLocal, setLocal } from '@/utils';
|
|||||||
export function getTabRouteByVueRoute(route: RouteRecordNormalized | RouteLocationNormalizedLoaded) {
|
export function getTabRouteByVueRoute(route: RouteRecordNormalized | RouteLocationNormalizedLoaded) {
|
||||||
const fullPath = hasFullPath(route) ? route.fullPath : route.path;
|
const fullPath = hasFullPath(route) ? route.fullPath : route.path;
|
||||||
|
|
||||||
const tabRoute: GlobalTabRoute = {
|
const tabRoute: App.GlobalTabRoute = {
|
||||||
name: route.name,
|
name: route.name,
|
||||||
fullPath,
|
fullPath,
|
||||||
meta: route.meta,
|
meta: route.meta,
|
||||||
@ -26,7 +26,7 @@ export function getTabRouteByVueRoute(route: RouteRecordNormalized | RouteLocati
|
|||||||
* @param tabs - 多页签数据
|
* @param tabs - 多页签数据
|
||||||
* @param fullPath - 该页签的路径
|
* @param fullPath - 该页签的路径
|
||||||
*/
|
*/
|
||||||
export function getIndexInTabRoutes(tabs: GlobalTabRoute[], fullPath: string) {
|
export function getIndexInTabRoutes(tabs: App.GlobalTabRoute[], fullPath: string) {
|
||||||
return tabs.findIndex(tab => tab.fullPath === fullPath);
|
return tabs.findIndex(tab => tab.fullPath === fullPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ export function getIndexInTabRoutes(tabs: GlobalTabRoute[], fullPath: string) {
|
|||||||
* @param tabs - 多页签数据
|
* @param tabs - 多页签数据
|
||||||
* @param fullPath - 该页签的路径
|
* @param fullPath - 该页签的路径
|
||||||
*/
|
*/
|
||||||
export function isInTabRoutes(tabs: GlobalTabRoute[], fullPath: string) {
|
export function isInTabRoutes(tabs: App.GlobalTabRoute[], fullPath: string) {
|
||||||
return getIndexInTabRoutes(tabs, fullPath) > -1;
|
return getIndexInTabRoutes(tabs, fullPath) > -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ export function isInTabRoutes(tabs: GlobalTabRoute[], fullPath: string) {
|
|||||||
* @param tabs - 多页签数据
|
* @param tabs - 多页签数据
|
||||||
* @param routeName - 路由名称
|
* @param routeName - 路由名称
|
||||||
*/
|
*/
|
||||||
export function getIndexInTabRoutesByRouteName(tabs: GlobalTabRoute[], routeName: string) {
|
export function getIndexInTabRoutesByRouteName(tabs: App.GlobalTabRoute[], routeName: string) {
|
||||||
return tabs.findIndex(tab => tab.name === routeName);
|
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);
|
setLocal(EnumStorageKey['multi-tab-routes'], data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取缓存的多页签数据 */
|
/** 获取缓存的多页签数据 */
|
||||||
export function getTabRoutes() {
|
export function getTabRoutes() {
|
||||||
const routes: GlobalTabRoute[] = [];
|
const routes: App.GlobalTabRoute[] = [];
|
||||||
const data = getLocal<GlobalTabRoute[]>(EnumStorageKey['multi-tab-routes']);
|
const data = getLocal<App.GlobalTabRoute[]>(EnumStorageKey['multi-tab-routes']);
|
||||||
if (data) {
|
if (data) {
|
||||||
const defaultTabRoutes = data.map(item => ({
|
const defaultTabRoutes = data.map(item => ({
|
||||||
...item,
|
...item,
|
||||||
|
@ -14,9 +14,9 @@ import {
|
|||||||
|
|
||||||
interface TabState {
|
interface TabState {
|
||||||
/** 多页签数据 */
|
/** 多页签数据 */
|
||||||
tabs: GlobalTabRoute[];
|
tabs: App.GlobalTabRoute[];
|
||||||
/** 多页签首页 */
|
/** 多页签首页 */
|
||||||
homeTab: GlobalTabRoute;
|
homeTab: App.GlobalTabRoute;
|
||||||
/** 当前激活状态的页签(路由fullPath) */
|
/** 当前激活状态的页签(路由fullPath) */
|
||||||
activeTab: string;
|
activeTab: string;
|
||||||
}
|
}
|
||||||
@ -213,7 +213,7 @@ export const useTabStore = defineStore('tab-store', {
|
|||||||
iniTabStore(currentRoute: RouteLocationNormalizedLoaded) {
|
iniTabStore(currentRoute: RouteLocationNormalizedLoaded) {
|
||||||
const theme = useThemeStore();
|
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;
|
const hasHome = getIndexInTabRoutesByRouteName(tabs, this.homeTab.name as string) > -1;
|
||||||
if (!hasHome && this.homeTab.name !== 'root') {
|
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[];
|
routes: AuthRoute.Route[];
|
||||||
/** 路由首页对应的key */
|
/** 路由首页对应的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_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图标作为组件的前缀 */
|
/** iconify图标作为组件的前缀 */
|
||||||
readonly VITE_ICON_PREFFIX: string;
|
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 {
|
declare namespace AuthRoute {
|
||||||
/** 多级路由分割符号 */
|
/** 根路由路径 */
|
||||||
type RouteSplitMark = '_';
|
type RootRoutePath = '/';
|
||||||
|
|
||||||
/** 路由的key */
|
/** 捕获无效路由的路由路径 */
|
||||||
type RouteKey =
|
type NotFoundRoutePath = '/:pathMatch(.*)*';
|
||||||
// 固定的路由
|
|
||||||
| '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';
|
|
||||||
|
|
||||||
/** 路由的path */
|
type RootRouteKey = RouterPage.RootRouteKey;
|
||||||
type RoutePath =
|
|
||||||
| '/'
|
type NotFoundRouteKey = RouterPage.NotFoundRouteKey;
|
||||||
| Exclude<KeyToPath<RouteKey>, '/root' | '/not-found-page'>
|
|
||||||
| SingleRouteParentPath
|
type RouteKey = RouterPage.RouteKey;
|
||||||
| '/:pathMatch(.*)*';
|
|
||||||
|
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 - 多级路由布局(三级路由或三级以上时,除第一级路由和最后一级路由,其余的采用该布局)
|
* - multi - 多级路由布局(三级路由或三级以上时,除第一级路由和最后一级路由,其余的采用该布局)
|
||||||
* - self - 作为子路由,使用自身的布局(作为最后一级路由,没有子路由)
|
* - self - 作为子路由,使用自身的布局(作为最后一级路由,没有子路由)
|
||||||
*/
|
*/
|
||||||
type RouteComponent = 'basic' | 'blank' | 'multi' | 'self';
|
type RouteComponentType = 'basic' | 'blank' | 'multi' | 'self';
|
||||||
|
|
||||||
/** 路由描述 */
|
/** 路由描述 */
|
||||||
interface RouteMeta {
|
interface RouteMeta {
|
||||||
/** 路由标题(可用来作document.title或者菜单的名称) */
|
/** 路由标题(可用来作document.title或者菜单的名称) */
|
||||||
title: string;
|
title: string;
|
||||||
/** 路由的动态路径(需要动态路径的页面需要将path添加进范型参数) */
|
/** 路由的动态路径(需要动态路径的页面需要将path添加进范型参数) */
|
||||||
dynamicPath?: PathToDynamicPath<'/login'>;
|
dynamicPath?: AuthRouteUtils.GetDynamicPath<'/login'>;
|
||||||
/** 作为单级路由的父级路由布局组件 */
|
/** 作为单级路由的父级路由布局组件 */
|
||||||
singleLayout?: Extract<RouteComponent, 'basic' | 'blank'>;
|
singleLayout?: Extract<RouteComponentType, 'basic' | 'blank'>;
|
||||||
/** 需要登录权限 */
|
/** 需要登录权限 */
|
||||||
requiresAuth?: boolean;
|
requiresAuth?: boolean;
|
||||||
/**
|
/**
|
||||||
@ -96,7 +44,7 @@ declare namespace AuthRoute {
|
|||||||
permissions?: Auth.RoleType[];
|
permissions?: Auth.RoleType[];
|
||||||
/** 缓存页面 */
|
/** 缓存页面 */
|
||||||
keepAlive?: boolean;
|
keepAlive?: boolean;
|
||||||
/** 菜单和面包屑对应的图标(iconify图标名称) */
|
/** 菜单和面包屑对应的图标 */
|
||||||
icon?: string;
|
icon?: string;
|
||||||
/** 使用本地svg作为的菜单和面包屑对应的图标(assets/svg-icon文件夹的的svg文件名) */
|
/** 使用本地svg作为的菜单和面包屑对应的图标(assets/svg-icon文件夹的的svg文件名) */
|
||||||
localIcon?: string;
|
localIcon?: string;
|
||||||
@ -114,62 +62,86 @@ declare namespace AuthRoute {
|
|||||||
multi?: boolean;
|
multi?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 单个路由的类型结构(动态路由模式:后端返回此类型结构的路由) */
|
type Route<K extends AllRouteKey = AllRouteKey> = K extends AllRouteKey
|
||||||
interface Route {
|
? {
|
||||||
/** 路由名称(路由唯一标识) */
|
/** 路由名称(路由唯一标识) */
|
||||||
name: RouteKey;
|
name: K;
|
||||||
/** 路由路径 */
|
/** 路由路径 */
|
||||||
path: RoutePath;
|
path: AuthRouteUtils.GetRoutePath<K>;
|
||||||
/** 路由重定向 */
|
/** 路由重定向 */
|
||||||
redirect?: RoutePath;
|
redirect?: AuthRouteUtils.GetRoutePath;
|
||||||
/**
|
/**
|
||||||
* 路由组件
|
* 路由组件
|
||||||
* - basic: 基础布局,具有公共部分的布局
|
* - basic: 基础布局,具有公共部分的布局
|
||||||
* - blank: 空白布局
|
* - blank: 空白布局
|
||||||
* - multi: 多级路由布局(三级路由或三级以上时,除第一级路由和最后一级路由,其余的采用该布局)
|
* - multi: 多级路由布局(三级路由或三级以上时,除第一级路由和最后一级路由,其余的采用该布局)
|
||||||
* - self: 作为子路由,使用自身的布局(作为最后一级路由,没有子路由)
|
* - self: 作为子路由,使用自身的布局(作为最后一级路由,没有子路由)
|
||||||
*/
|
*/
|
||||||
component?: RouteComponent;
|
component?: RouteComponentType;
|
||||||
/** 子路由 */
|
/** 子路由 */
|
||||||
children?: Route[];
|
children?: Route[];
|
||||||
/** 路由描述 */
|
/** 路由描述 */
|
||||||
meta: RouteMeta;
|
meta: RouteMeta;
|
||||||
/** 路由属性 */
|
} & Omit<import('vue-router').RouteRecordRaw, 'name' | 'path' | 'redirect' | 'component' | 'children' | 'meta'>
|
||||||
props?: boolean | Record<string, any> | ((to: any) => Record<string, any>);
|
: 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<
|
type SingleRouteKey = Exclude<
|
||||||
GetSingleRouteKey<RouteKey>,
|
GetFirstDegreeRouteKey,
|
||||||
GetRouteFirstParentKey<RouteKey> | 'root' | 'not-found-page'
|
GetFirstDegreeRouteKeyWithChildren | AuthRoute.RootRouteKey | AuthRoute.NotFoundRouteKey
|
||||||
>;
|
>;
|
||||||
|
|
||||||
/** 单独路由父级路由key */
|
/** 单独路由父级路由key */
|
||||||
type SingleRouteParentKey = `${SingleRouteKey}-parent`;
|
type SingleRouteParentKey = `${SingleRouteKey}-parent`;
|
||||||
|
|
||||||
/** 单独路由父级路由path */
|
/** 单独路由父级路由path */
|
||||||
type SingleRouteParentPath = KeyToPath<SingleRouteParentKey>;
|
type SingleRouteParentPath = KeyToPath<SingleRouteParentKey>;
|
||||||
|
|
||||||
/** 路由key转换路由path */
|
/** 获取路由动态路径 */
|
||||||
type KeyToPath<Key extends string> = Key extends `${infer Left}_${infer Right}`
|
type GetDynamicPath<P extends AuthRoute.RoutePath> =
|
||||||
? KeyToPath<`${Left}/${Right}`>
|
| `${P}/:${string}`
|
||||||
: `/${Key}`;
|
| `${P}/:${string}(${string})`
|
||||||
|
| `${P}/:${string}(${string})?`;
|
||||||
/** 路由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;
|
|
||||||
}
|
}
|
||||||
|
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 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 全局头部属性 */
|
declare namespace App {
|
||||||
interface GlobalHeaderProps {
|
/** 全局头部属性 */
|
||||||
/** 显示logo */
|
interface GlobalHeaderProps {
|
||||||
showLogo: boolean;
|
/** 显示logo */
|
||||||
/** 显示头部菜单 */
|
showLogo: boolean;
|
||||||
showHeaderMenu: boolean;
|
/** 显示头部菜单 */
|
||||||
/** 显示菜单折叠按钮 */
|
showHeaderMenu: boolean;
|
||||||
showMenuCollapse: boolean;
|
/** 显示菜单折叠按钮 */
|
||||||
}
|
showMenuCollapse: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
/** 菜单项配置 */
|
/** 菜单项配置 */
|
||||||
type GlobalMenuOption = import('naive-ui').MenuOption & {
|
type GlobalMenuOption = import('naive-ui').MenuOption & {
|
||||||
key: string;
|
key: string;
|
||||||
label: string;
|
label: string;
|
||||||
routeName: string;
|
routeName: string;
|
||||||
routePath: string;
|
routePath: string;
|
||||||
icon?: () => import('vue').VNodeChild;
|
icon?: () => import('vue').VNodeChild;
|
||||||
children?: GlobalMenuOption[];
|
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;
|
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
/** 系统消息 */
|
/** 面包屑 */
|
||||||
declare namespace Message {
|
type GlobalBreadcrumb = import('naive-ui').DropdownOption & {
|
||||||
interface Tab {
|
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 */
|
/** tab的key */
|
||||||
key: number;
|
key: number;
|
||||||
/** tab名称 */
|
/** tab名称 */
|
||||||
@ -304,10 +303,10 @@ declare namespace Message {
|
|||||||
/** badge类型 */
|
/** badge类型 */
|
||||||
badgeProps?: import('naive-ui').BadgeProps;
|
badgeProps?: import('naive-ui').BadgeProps;
|
||||||
/** 消息数据 */
|
/** 消息数据 */
|
||||||
list: List[];
|
list: MessageList[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface List {
|
interface MessageList {
|
||||||
/** 数据唯一值 */
|
/** 数据唯一值 */
|
||||||
id: number;
|
id: number;
|
||||||
/** 头像 */
|
/** 头像 */
|
||||||
|
@ -1,38 +1,57 @@
|
|||||||
import { EnumDataType } from '@/enum';
|
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;
|
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;
|
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;
|
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;
|
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;
|
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;
|
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;
|
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;
|
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;
|
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;
|
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;
|
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;
|
return Object.prototype.toString.call(data) === EnumDataType.file;
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
* @param menus - 菜单数据
|
* @param menus - 菜单数据
|
||||||
* @param rootPath - 根路由路径
|
* @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 breadcrumbMenu = getBreadcrumbMenu(activeKey, menus);
|
||||||
const breadcrumb = breadcrumbMenu.map(item => transformBreadcrumbMenuToBreadcrumb(item, rootPath));
|
const breadcrumb = breadcrumbMenu.map(item => transformBreadcrumbMenuToBreadcrumb(item, rootPath));
|
||||||
return breadcrumb;
|
return breadcrumb;
|
||||||
@ -15,8 +15,8 @@ export function getBreadcrumbByRouteKey(activeKey: string, menus: GlobalMenuOpti
|
|||||||
* @param activeKey - 当前页面路由的key
|
* @param activeKey - 当前页面路由的key
|
||||||
* @param menus - 菜单数据
|
* @param menus - 菜单数据
|
||||||
*/
|
*/
|
||||||
function getBreadcrumbMenu(activeKey: string, menus: GlobalMenuOption[]) {
|
function getBreadcrumbMenu(activeKey: string, menus: App.GlobalMenuOption[]) {
|
||||||
const breadcrumbMenu: GlobalMenuOption[] = [];
|
const breadcrumbMenu: App.GlobalMenuOption[] = [];
|
||||||
menus.some(menu => {
|
menus.some(menu => {
|
||||||
const flag = activeKey.includes(menu.routeName);
|
const flag = activeKey.includes(menu.routeName);
|
||||||
if (flag) {
|
if (flag) {
|
||||||
@ -32,15 +32,15 @@ function getBreadcrumbMenu(activeKey: string, menus: GlobalMenuOption[]) {
|
|||||||
* @param activeKey - 当前页面路由的key
|
* @param activeKey - 当前页面路由的key
|
||||||
* @param menu - 单个菜单数据
|
* @param menu - 单个菜单数据
|
||||||
*/
|
*/
|
||||||
function getBreadcrumbMenuItem(activeKey: string, menu: GlobalMenuOption) {
|
function getBreadcrumbMenuItem(activeKey: string, menu: App.GlobalMenuOption) {
|
||||||
const breadcrumbMenu: GlobalMenuOption[] = [];
|
const breadcrumbMenu: App.GlobalMenuOption[] = [];
|
||||||
if (activeKey === menu.routeName) {
|
if (activeKey === menu.routeName) {
|
||||||
breadcrumbMenu.push(menu);
|
breadcrumbMenu.push(menu);
|
||||||
}
|
}
|
||||||
if (activeKey.includes(menu.routeName) && menu.children && menu.children.length) {
|
if (activeKey.includes(menu.routeName) && menu.children && menu.children.length) {
|
||||||
breadcrumbMenu.push(menu);
|
breadcrumbMenu.push(menu);
|
||||||
breadcrumbMenu.push(
|
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 menu - 单个菜单数据
|
||||||
* @param rootPath - 根路由路径
|
* @param rootPath - 根路由路径
|
||||||
*/
|
*/
|
||||||
function transformBreadcrumbMenuToBreadcrumb(menu: GlobalMenuOption, rootPath: string) {
|
function transformBreadcrumbMenuToBreadcrumb(menu: App.GlobalMenuOption, rootPath: string) {
|
||||||
const hasChildren = Boolean(menu.children && menu.children.length);
|
const hasChildren = Boolean(menu.children && menu.children.length);
|
||||||
const breadcrumb: GlobalBreadcrumb = {
|
const breadcrumb: App.GlobalBreadcrumb = {
|
||||||
key: menu.routeName,
|
key: menu.routeName,
|
||||||
label: menu.label as string,
|
label: menu.label as string,
|
||||||
routeName: menu.routeName,
|
routeName: menu.routeName,
|
||||||
@ -66,7 +66,7 @@ function transformBreadcrumbMenuToBreadcrumb(menu: GlobalMenuOption, rootPath: s
|
|||||||
}
|
}
|
||||||
if (hasChildren) {
|
if (hasChildren) {
|
||||||
breadcrumb.children = menu.children?.map(item =>
|
breadcrumb.children = menu.children?.map(item =>
|
||||||
transformBreadcrumbMenuToBreadcrumb(item as GlobalMenuOption, rootPath)
|
transformBreadcrumbMenuToBreadcrumb(item as App.GlobalMenuOption, rootPath)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return breadcrumb;
|
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 - 固定路由
|
* @param routes - 固定路由
|
||||||
@ -9,41 +6,30 @@ export function getConstantRouteNames(routes: AuthRoute.Route[]) {
|
|||||||
return routes.map(route => getConstantRouteName(route)).flat(1);
|
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 routes - 权限路由
|
||||||
* @param treeMap
|
* @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 [];
|
if (routes && routes.length === 0) return [];
|
||||||
return routes.reduce((acc, cur) => {
|
return routes.reduce((acc, cur) => {
|
||||||
if (!cur.meta?.hide) {
|
if (!cur.meta?.hide) {
|
||||||
acc.push(cur);
|
acc.push(cur);
|
||||||
}
|
}
|
||||||
if (cur.children && cur.children.length > 0) {
|
if (cur.children && cur.children.length > 0) {
|
||||||
transformAuthRoutesToSearchMenus(cur.children, treeMap);
|
transformAuthRouteToSearchMenus(cur.children, treeMap);
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
}, treeMap);
|
}, treeMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 将路由名字转换成路由路径 */
|
/** 将路由名字转换成路由路径 */
|
||||||
export function transformRouteNameToRoutePath(
|
export function transformRouteNameToRoutePath(name: Exclude<AuthRoute.AllRouteKey, 'not-found'>): AuthRoute.RoutePath {
|
||||||
name: Exclude<AuthRoute.RouteKey, 'not-found-page'>
|
|
||||||
): AuthRoute.RoutePath {
|
|
||||||
const rootPath: AuthRoute.RoutePath = '/';
|
const rootPath: AuthRoute.RoutePath = '/';
|
||||||
if (name === 'root') return rootPath;
|
if (name === 'root') return rootPath;
|
||||||
|
|
||||||
const splitMark: AuthRoute.RouteSplitMark = '_';
|
const splitMark = '_';
|
||||||
const pathSplitMark = '/';
|
const pathSplitMark = '/';
|
||||||
const path = name.split(splitMark).join(pathSplitMark);
|
const path = name.split(splitMark).join(pathSplitMark);
|
||||||
|
|
||||||
@ -51,15 +37,13 @@ export function transformRouteNameToRoutePath(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 将路由路径转换成路由名字 */
|
/** 将路由路径转换成路由名字 */
|
||||||
export function transformRoutePathToRouteName(
|
export function transformRoutePathToRouteName<K extends AuthRoute.RoutePath>(path: K) {
|
||||||
path: Exclude<AuthRoute.RoutePath, '/not-found-page' | '/:pathMatch(.*)*'>
|
|
||||||
): AuthRoute.RouteKey {
|
|
||||||
if (path === '/') return 'root';
|
if (path === '/') return 'root';
|
||||||
|
|
||||||
const pathSplitMark = '/';
|
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;
|
return name;
|
||||||
}
|
}
|
||||||
@ -76,140 +60,6 @@ function getConstantRouteName(route: AuthRoute.Route) {
|
|||||||
return names;
|
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 - 权限路由
|
* @param item - 权限路由
|
||||||
@ -217,11 +67,3 @@ function hasComponent(item: AuthRoute.Route) {
|
|||||||
function hasChildren(item: AuthRoute.Route) {
|
function hasChildren(item: AuthRoute.Route) {
|
||||||
return Boolean(item.children && item.children.length);
|
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: {
|
function addPartialProps(config: {
|
||||||
menu: GlobalMenuOption;
|
menu: App.GlobalMenuOption;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
localIcon?: string;
|
localIcon?: string;
|
||||||
children?: GlobalMenuOption[];
|
children?: App.GlobalMenuOption[];
|
||||||
}) {
|
}) {
|
||||||
const { iconRender } = useIconRender();
|
const { iconRender } = useIconRender();
|
||||||
|
|
||||||
@ -36,16 +36,16 @@ function addPartialProps(config: {
|
|||||||
* 将权限路由转换成菜单
|
* 将权限路由转换成菜单
|
||||||
* @param routes - 路由
|
* @param routes - 路由
|
||||||
*/
|
*/
|
||||||
export function transformAuthRouteToMenu(routes: AuthRoute.Route[]): GlobalMenuOption[] {
|
export function transformAuthRouteToMenu(routes: AuthRoute.Route[]): App.GlobalMenuOption[] {
|
||||||
const globalMenu: GlobalMenuOption[] = [];
|
const globalMenu: App.GlobalMenuOption[] = [];
|
||||||
routes.forEach(route => {
|
routes.forEach(route => {
|
||||||
const { name, path, meta } = route;
|
const { name, path, meta } = route;
|
||||||
const routeName = name as string;
|
const routeName = name as string;
|
||||||
let menuChildren: GlobalMenuOption[] | undefined;
|
let menuChildren: App.GlobalMenuOption[] | undefined;
|
||||||
if (route.children) {
|
if (route.children) {
|
||||||
menuChildren = transformAuthRouteToMenu(route.children);
|
menuChildren = transformAuthRouteToMenu(route.children);
|
||||||
}
|
}
|
||||||
const menuItem: GlobalMenuOption = addPartialProps({
|
const menuItem: App.GlobalMenuOption = addPartialProps({
|
||||||
menu: {
|
menu: {
|
||||||
key: routeName,
|
key: routeName,
|
||||||
label: meta.title,
|
label: meta.title,
|
||||||
@ -70,18 +70,18 @@ export function transformAuthRouteToMenu(routes: AuthRoute.Route[]): GlobalMenuO
|
|||||||
* @param activeKey - 当前路由的key
|
* @param activeKey - 当前路由的key
|
||||||
* @param menus - 菜单数据
|
* @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);
|
const keys = menus.map(menu => getActiveKeyPathsOfMenu(activeKey, menu)).flat(1);
|
||||||
return keys;
|
return keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getActiveKeyPathsOfMenu(activeKey: string, menu: GlobalMenuOption) {
|
function getActiveKeyPathsOfMenu(activeKey: string, menu: App.GlobalMenuOption) {
|
||||||
const keys: string[] = [];
|
const keys: string[] = [];
|
||||||
if (activeKey.includes(menu.routeName)) {
|
if (activeKey.includes(menu.routeName)) {
|
||||||
keys.push(menu.routeName);
|
keys.push(menu.routeName);
|
||||||
}
|
}
|
||||||
if (menu.children) {
|
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;
|
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>>;
|
export const views: Record<RouterPage.LastDegreeRouteKey, RouteComponent | (() => Promise<RouteComponent>)> = {
|
||||||
|
403: () => import('./_builtin/403/index.vue'),
|
||||||
const importViews = import.meta.glob('./**/index.vue') as ViewComponent;
|
404: () => import('./_builtin/404/index.vue'),
|
||||||
|
500: () => import('./_builtin/500/index.vue'),
|
||||||
const COMPONENTS_KEY = 'components';
|
'constant-page': () => import('./_builtin/constant-page/index.vue'),
|
||||||
const PREFIX = './';
|
login: () => import('./_builtin/login/index.vue'),
|
||||||
const SUFFIX = '/index.vue';
|
'not-found': () => import('./_builtin/not-found/index.vue'),
|
||||||
const PATH_SPLIT_MARK = '/';
|
about: () => import('./about/index.vue'),
|
||||||
const ROUTE_KEY_SPLIT_MARK = '_';
|
'auth-demo_permission': () => import('./auth-demo/permission/index.vue'),
|
||||||
/** 系统的内置路由,该文件夹名称不作为RouteKey */
|
'auth-demo_super': () => import('./auth-demo/super/index.vue'),
|
||||||
const SYSTEM_VIEW = 'system-view_';
|
component_button: () => import('./component/button/index.vue'),
|
||||||
|
component_card: () => import('./component/card/index.vue'),
|
||||||
/** 过滤掉组件文件 */
|
component_table: () => import('./component/table/index.vue'),
|
||||||
const viewKeys = Object.keys(importViews).filter(key => !key.includes(COMPONENTS_KEY));
|
dashboard_analysis: () => import('./dashboard/analysis/index.vue'),
|
||||||
|
dashboard_workbench: () => import('./dashboard/workbench/index.vue'),
|
||||||
function getViewComponent() {
|
document_naive: () => import('./document/naive/index.vue'),
|
||||||
const components: ViewComponent = {};
|
'document_project-link': () => import('./document/project-link/index.vue'),
|
||||||
viewKeys.forEach(key => {
|
document_project: () => import('./document/project/index.vue'),
|
||||||
const routeKey = key
|
document_vite: () => import('./document/vite/index.vue'),
|
||||||
.replace(PREFIX, '')
|
document_vue: () => import('./document/vue/index.vue'),
|
||||||
.replace(SUFFIX, '')
|
exception_403: () => import('./exception/403/index.vue'),
|
||||||
.replace(new RegExp(PATH_SPLIT_MARK, 'g'), ROUTE_KEY_SPLIT_MARK)
|
exception_404: () => import('./exception/404/index.vue'),
|
||||||
.replace(SYSTEM_VIEW, '');
|
exception_500: () => import('./exception/500/index.vue'),
|
||||||
components[routeKey] = importViews[key];
|
'function_tab-detail': () => import('./function/tab-detail/index.vue'),
|
||||||
});
|
'function_tab-multi-detail': () => import('./function/tab-multi-detail/index.vue'),
|
||||||
return components;
|
function_tab: () => import('./function/tab/index.vue'),
|
||||||
}
|
management_auth: () => import('./management/auth/index.vue'),
|
||||||
|
management_role: () => import('./management/role/index.vue'),
|
||||||
export const views = getViewComponent();
|
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')
|
||||||
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user