refactor(projects): 重构路由类型和路由元数据类型,重构多级菜单路由写法

This commit is contained in:
Soybean 2021-11-28 12:17:48 +08:00
parent 9fb641f71e
commit d683894beb
20 changed files with 144 additions and 115 deletions

View File

@ -1,26 +1,7 @@
import type { RouteRecordRaw } from 'vue-router';
/** 路由描述 */
export interface CustomRouteMeta {
/** 路由名称 */
title?: string;
/** 缓存页面 */
keepAlive?: boolean;
/** 页面100%视高 */
fullPage?: boolean;
/** 不作为菜单 */
isNotMenu?: boolean;
/** 菜单和面包屑对应的图标 */
icon?: string;
/** 导入的路由模块排序,可用于菜单的排序 */
order?: number;
}
/** 路由配置 */
export type CustomRoute = RouteRecordRaw & { meta: CustomRouteMeta };
/** 导入的路由模块 */
export type ImportedRouteModules = Record<string, { default: CustomRoute; [key: string]: any }>;
export type ImportedRouteModules = Record<string, { default: RouteRecordRaw; [key: string]: any }>;
/** 路由声明的key */
export type RouteKey =

View File

@ -1,6 +0,0 @@
<template>
<router-view />
</template>
<script setup lang="ts"></script>
<style scoped></style>

View File

@ -61,7 +61,7 @@ function generateBreadcrumb() {
function recursionBreadcrumb(routeMatched: RouteLocationMatched[]) {
const list: Breadcrumb[] = [];
routeMatched.forEach(item => {
if (!item.meta?.isNotMenu) {
if (!item.meta?.notAsMenu) {
const routeName = item.name as RouteKey;
const breadcrumItem: Breadcrumb = {
key: routeName,

View File

@ -69,7 +69,7 @@ const activeParentRouteName = ref(getActiveRouteName());
function getActiveRouteName() {
let name = '';
const menuMatched = route.matched.filter(item => !item.meta.isNotMenu);
const menuMatched = route.matched.filter(item => !item.meta?.notAsMenu);
if (menuMatched.length) {
name = menuMatched[0].name as string;
}

View File

@ -69,7 +69,7 @@ const activeParentRouteName = ref(getActiveRouteName());
function getActiveRouteName() {
let name = '';
const menuMatched = route.matched.filter(item => !item.meta.isNotMenu);
const menuMatched = route.matched.filter(item => !item.meta?.notAsMenu);
if (menuMatched.length) {
name = menuMatched[0].name as string;
}

View File

@ -1,5 +1,4 @@
import BasicLayout from './BasicLayout/index.vue';
import BlankLayout from './BlankLayout/index.vue';
import RouterViewLayout from './RouterViewLayout/index.vue';
export { BasicLayout, BlankLayout, RouterViewLayout };
export { BasicLayout, BlankLayout };

View File

@ -1,4 +1,4 @@
import type { CustomRoute } from '@/interface';
import type { RouteRecordRaw } from 'vue-router';
import { setSingleRoute } from '@/utils';
import { BasicLayout } from '@/layouts';
import About from '@/views/about/index.vue';
@ -6,19 +6,21 @@ import { getRouteConst, routeName } from '../constant';
const { name, path, title } = getRouteConst('about');
const ABOUT: CustomRoute = setSingleRoute({
const ABOUT: RouteRecordRaw = setSingleRoute({
route: {
name,
path,
component: About,
meta: {
requiresAuth: true,
title,
requiresAuth: true,
keepAlive: true,
icon: 'fluent:book-information-24-regular'
}
},
container: BasicLayout,
meta: {
containerMeta: {
title,
order: 7
},
notFoundName: routeName('not-found')

View File

@ -1,5 +1,5 @@
import type { CustomRoute } from '@/interface';
import { BasicLayout, RouterViewLayout } from '@/layouts';
import type { RouteRecordRaw } from 'vue-router';
import { BasicLayout } from '@/layouts';
import ComponentMap from '@/views/component/map/index.vue';
import ComponentVideo from '@/views/component/video/index.vue';
import EditorQuill from '@/views/component/editor/quill/index.vue';
@ -7,7 +7,7 @@ import EditorMarkdown from '@/views/component/editor/markdown/index.vue';
import ComponentSwiper from '@/views/component/swiper/index.vue';
import { routeName, routePath, routeTitle } from '../constant';
const COMPONENT: CustomRoute = {
const COMPONENT: RouteRecordRaw = {
name: routeName('component'),
path: routePath('component'),
component: BasicLayout,
@ -42,12 +42,9 @@ const COMPONENT: CustomRoute = {
{
name: routeName('component_editor'),
path: routePath('component_editor'),
component: RouterViewLayout,
redirect: { name: routeName('component_editor_quill') },
meta: {
requiresAuth: true,
title: routeTitle('component_editor'),
fullPage: true
title: routeTitle('component_editor')
},
children: [
{

View File

@ -1,10 +1,10 @@
import type { CustomRoute } from '@/interface';
import type { RouteRecordRaw } from 'vue-router';
import { BasicLayout } from '@/layouts';
import DashboardAnalysis from '@/views/dashboard/analysis/index.vue';
import DashboardWorkbench from '@/views/dashboard/workbench/index.vue';
import { routeName, routePath, routeTitle } from '../constant';
const DASHBOARD: CustomRoute = {
const DASHBOARD: RouteRecordRaw = {
name: routeName('dashboard'),
path: routePath('dashboard'),
component: BasicLayout,

View File

@ -1,11 +1,11 @@
import type { CustomRoute } from '@/interface';
import type { RouteRecordRaw } from 'vue-router';
import { BasicLayout } from '@/layouts';
import DocumentVue from '@/views/document/vue/index.vue';
import DocumentVite from '@/views/document/vite/index.vue';
import DocumentNaive from '@/views/document/naive/index.vue';
import { routeName, routePath, routeTitle } from '../constant';
const DOCUMENT: CustomRoute = {
const DOCUMENT: RouteRecordRaw = {
name: routeName('document'),
path: routePath('document'),
component: BasicLayout,

View File

@ -1,11 +1,11 @@
import type { CustomRoute } from '@/interface';
import type { RouteRecordRaw } from 'vue-router';
import { BasicLayout } from '@/layouts';
import Exception403 from '@/views/system/exception/403.vue';
import Exception404 from '@/views/system/exception/404.vue';
import Exception500 from '@/views/system/exception/500.vue';
import { routeName, routePath, routeTitle } from '../constant';
const EXCEPTION: CustomRoute = {
const EXCEPTION: RouteRecordRaw = {
name: routeName('exception'),
path: routePath('exception'),
component: BasicLayout,

View File

@ -1,11 +1,11 @@
import type { CustomRoute } from '@/interface';
import type { RouteRecordRaw } from 'vue-router';
import { BasicLayout } from '@/layouts';
import FeatCopy from '@/views/feat/copy/index.vue';
import FeatIcon from '@/views/feat/icon/index.vue';
import FeatPrint from '@/views/feat/print/index.vue';
import { routeName, routePath, routeTitle } from '../constant';
const FEAT: CustomRoute = {
const FEAT: RouteRecordRaw = {
name: routeName('feat'),
path: routePath('feat'),
component: BasicLayout,

View File

@ -1,9 +1,9 @@
import type { CustomRoute } from '@/interface';
import { BasicLayout, RouterViewLayout } from '@/layouts';
import type { RouteRecordRaw } from 'vue-router';
import { BasicLayout } from '@/layouts';
import MultiMenuFirstSecond from '@/views/multi-menu/first/second/index.vue';
import { routeName, routePath, routeTitle } from '../constant';
const MULTI_MENU: CustomRoute = {
const MULTI_MENU: RouteRecordRaw = {
name: routeName('multi-menu'),
path: routePath('multi-menu'),
component: BasicLayout,
@ -17,12 +17,9 @@ const MULTI_MENU: CustomRoute = {
{
name: routeName('multi-menu_first'),
path: routePath('multi-menu_first'),
component: RouterViewLayout,
redirect: { name: routeName('multi-menu_first_second') },
meta: {
keepAlive: true,
requiresAuth: true,
title: routeTitle('multi-menu_first_second')
title: routeTitle('multi-menu_first')
},
children: [
{
@ -30,9 +27,9 @@ const MULTI_MENU: CustomRoute = {
path: routePath('multi-menu_first_second'),
component: MultiMenuFirstSecond,
meta: {
keepAlive: true,
requiresAuth: true,
title: routeTitle('multi-menu_first_second'),
requiresAuth: true,
keepAlive: true,
fullPage: true
}
}

View File

@ -1,4 +1,4 @@
import type { CustomRoute } from '@/interface';
import type { RouteRecordRaw } from 'vue-router';
import { setSingleRoute } from '@/utils';
import { BlankLayout } from '@/layouts';
import Website from '@/views/website/index.vue';
@ -6,7 +6,7 @@ import { getRouteConst, routeName } from '../constant';
const { name, path, title } = getRouteConst('website');
const WEBSITE: CustomRoute = setSingleRoute({
const WEBSITE: RouteRecordRaw = setSingleRoute({
route: {
name,
path,
@ -14,11 +14,12 @@ const WEBSITE: CustomRoute = setSingleRoute({
meta: {
title,
icon: 'codicon:remote-explorer',
isNotMenu: true
notAsMenu: true
}
},
container: BlankLayout,
meta: {
containerMeta: {
title,
order: 8
},
notFoundName: routeName('not-found')

View File

@ -19,12 +19,13 @@ const constantRoutes: RouteRecordRaw[] = [
redirect: { name: ROUTE_HOME_NAME }
},
{
// 名称、路由随意不在路由声明里面只是为各个页面充当传递BlankLayout的桥梁因此访问该路由时重定向到404
// 名称、路径随意不在路由声明里面只是为各个子路由充当应用BlankLayout布局的桥梁因此访问该路由时重定向到404
name: 'constant-single_',
path: '/constant-single_',
component: BlankLayout,
redirect: { name: routeName('not-found') },
meta: {
title: 'constant-single_',
keepAlive: true
},
children: [

View File

@ -1,8 +1,11 @@
import type { RouteRecordRaw } from 'vue-router';
import { transformMultiDegreeRoutes } from '@/utils';
import customRoutes from '../modules';
import constantRoutes from './constant-routes';
const transformRoutes = transformMultiDegreeRoutes(customRoutes);
/** 所有路由 */
export const routes: RouteRecordRaw[] = [...customRoutes, ...constantRoutes];
export const routes: RouteRecordRaw[] = [...transformRoutes, ...constantRoutes];
export { ROUTE_HOME } from './route-home';

22
src/typings/router.d.ts vendored Normal file
View File

@ -0,0 +1,22 @@
import 'vue-router';
declare module 'vue-router' {
interface RouteMeta {
/** 路由名称(作为菜单时为菜单的名称) */
title: string;
/** 需要登录权限 */
requiresAuth?: boolean;
/** 缓存页面 */
keepAlive?: boolean;
/** 页面占满剩余高度(去除头部、tab和底部后的高度) */
fullPage?: boolean;
/** 不作为菜单 */
notAsMenu?: boolean;
/** 菜单和面包屑对应的图标 */
icon?: string;
/** 导入的路由模块排序,可用于菜单的排序 */
order?: number;
/** y方向滚动的距离(被缓存的页面保留滚动行为) */
scrollY?: number;
}
}

View File

@ -1,6 +1,13 @@
import type { Component } from 'vue';
import type { RouteRecordRaw } from 'vue-router';
/** 给需要缓存的页面组件设置名称 */
function setComponentName(component?: Component, name?: string) {
if (component && name) {
Object.assign(component, { name });
}
}
function getCacheName(route: RouteRecordRaw, isCache: boolean) {
const cacheNames: string[] = [];
const hasChild = hasChildren(route);
@ -26,13 +33,6 @@ function hasChildren(route: RouteRecordRaw) {
return Boolean(route.children && route.children.length);
}
/** 给需要缓存的页面组件设置名称 */
export function setComponentName(component?: Component, name?: string) {
if (component && name) {
Object.assign(component, { name });
}
}
/** 获取被缓存的路由 */
export function getCacheRoutes(routes: RouteRecordRaw[]) {
const cacheNames: string[] = [];

View File

@ -1,13 +1,45 @@
import type { Component } from 'vue';
import type { Router } from 'vue-router';
import type { CustomRoute, ImportedRouteModules, CustomRouteMeta } from '@/interface';
import type { Router, RouteRecordRaw, RouteMeta } from 'vue-router';
import type { ImportedRouteModules } from '@/interface';
interface SingleRouteConfig {
/** 路由 */
route: RouteRecordRaw;
/** 路由容器 */
container: Component;
/** 路由容器的描述 */
containerMeta: RouteMeta;
/** 404路由的名称 */
notFoundName: string;
}
/** 设置单个路由 */
export function setSingleRoute(config: SingleRouteConfig) {
const { route, container, containerMeta, notFoundName } = config;
const routeItem: RouteRecordRaw = {
name: `${route.name as string}_`,
path: `${route.path}_`,
component: container,
redirect: { name: notFoundName },
meta: {
notAsMenu: true,
...containerMeta,
title: `${containerMeta.title}-container`
},
children: [route]
};
return routeItem;
}
/** 处理导入的路由模块 */
export function transformRouteModules(routeModules: ImportedRouteModules) {
const modules = Object.keys(routeModules).map(key => {
return routeModules[key].default;
});
const constantRoutes: CustomRoute[] = modules.sort((next, pre) => Number(next.meta.order) - Number(pre.meta.order));
const constantRoutes: RouteRecordRaw[] = modules.sort(
(next, pre) => Number(next.meta?.order) - Number(pre.meta?.order)
);
return constantRoutes;
}
@ -16,12 +48,12 @@ export function transformRouteModules(routeModules: ImportedRouteModules) {
* @param routes -
* @param routeHomeName -
*/
export function getRouteHome(routes: CustomRoute[], routeHomeName: string) {
let routeHome: CustomRoute;
function hasChildren(route: CustomRoute) {
export function getRouteHome(routes: RouteRecordRaw[], routeHomeName: string) {
let routeHome: RouteRecordRaw;
function hasChildren(route: RouteRecordRaw) {
return Boolean(route.children && route.children.length);
}
function getRouteHomeByRoute(route: CustomRoute) {
function getRouteHomeByRoute(route: RouteRecordRaw) {
if (routeHome) return;
const hasChild = hasChildren(route);
if (!hasChild) {
@ -29,12 +61,12 @@ export function getRouteHome(routes: CustomRoute[], routeHomeName: string) {
routeHome = route;
}
} else {
getRouteHomeByRoutes(route.children as CustomRoute[]);
getRouteHomeByRoutes(route.children as RouteRecordRaw[]);
}
}
function getRouteHomeByRoutes(_routes: CustomRoute[]) {
function getRouteHomeByRoutes(_routes: RouteRecordRaw[]) {
_routes.some(item => {
getRouteHomeByRoute(item as CustomRoute);
getRouteHomeByRoute(item as RouteRecordRaw);
return routeHome !== undefined;
});
}
@ -42,37 +74,36 @@ export function getRouteHome(routes: CustomRoute[], routeHomeName: string) {
return routeHome!;
}
/**
*
* @param routes -
*/
export function transformMultiDegreeRoutes(routes: RouteRecordRaw[]) {
function hasComponent(route: RouteRecordRaw) {
return Boolean(route.component);
}
function hasChildren(route: RouteRecordRaw) {
return Boolean(route.children && route.children.length);
}
function upDimension(route: RouteRecordRaw): RouteRecordRaw[] {
if (hasChildren(route)) {
const updateRoute = { ...route };
if (!hasComponent(route)) {
return updateRoute.children!;
}
updateRoute.children = updateRoute.children?.map(item => upDimension(item)).flat();
return [updateRoute];
}
return [route];
}
return routes.map(item => upDimension(item)).flat();
}
/** 获取登录后的重定向地址 */
export function getLoginRedirectUrl(router: Router) {
const path = router.currentRoute.value.fullPath as string;
const redirectUrl = path === '/' ? undefined : path;
return redirectUrl;
}
interface SingleRouteConfig {
/** 路由 */
route: CustomRoute;
/** 路由容器 */
container: Component;
/** 路由容器的描述 */
meta: CustomRouteMeta;
/** 404路由的名称 */
notFoundName: string;
}
/** * 设置单个路由 */
export function setSingleRoute(config: SingleRouteConfig) {
const { route, container, meta, notFoundName } = config;
const routeItem: CustomRoute = {
name: `${route.name as string}_`,
path: `${route.path}_`,
component: container,
redirect: { name: notFoundName },
meta: {
...meta,
isNotMenu: true
},
children: [route]
};
return routeItem;
}

View File

@ -1,9 +1,10 @@
import type { CustomRoute, GlobalMenuOption } from '@/interface';
import type { RouteRecordRaw } from 'vue-router';
import type { GlobalMenuOption } from '@/interface';
import { iconifyRender } from '../common';
/** 判断路由是否作为菜单 */
function asMenu(route: CustomRoute) {
return !route.meta?.isNotMenu;
function asMenu(route: RouteRecordRaw) {
return !route.meta?.notAsMenu;
}
/** 给菜单添加可选属性 */
@ -19,14 +20,14 @@ function addPartialProps(menuItem: GlobalMenuOption, icon?: string, children?: G
}
/** 将路由转换成菜单 */
export function transformRouteToMenu(routes: CustomRoute[]) {
export function transformRouteToMenu(routes: RouteRecordRaw[]) {
const globalMenu: GlobalMenuOption[] = [];
routes.forEach(route => {
const { name, path, meta } = route;
const routeName = name as string;
let menuChildren: GlobalMenuOption[] | undefined;
if (route.children) {
menuChildren = transformRouteToMenu(route.children as CustomRoute[]);
menuChildren = transformRouteToMenu(route.children as RouteRecordRaw[]);
}
const menuItem: GlobalMenuOption = addPartialProps(
{