Vue3动态路由设计

1,188 阅读2分钟

经过我两年半的思考,总算写出来这个vue3的动态路由方案

中分头背带裤 git地址看此处

小白云的养鸡场

拉代码的话需要自己把http改成https,不然拉不下来 之前写过vue2.x的动态路由,vue3所使用的vue-router有些许的api更新。所以暂作记录.

模块版本

  • "vue": "^3.2.37"
  • "vue-router": "^4.1.3"
  • "vite": "^3.0.0"

基础工作:typings编写

一般自己需要定义一下meta中的自定义字段,当然也可以使用原生的Record<string,unknow>


interface RouteMeta {
  title?: string;
  inWhite?: boolean;
  inMenu?: boolean;
  fixed?: boolean;
  icon?: string;
  isLink?: boolean;
  link?: string;
}
interface RouteRecord extends Omit<Omit<RouteRecordRaw, 'meta'>, 'children'> {
  children?: RouteRecord[];
  meta?: RouteMeta;
} //app需要的路由信息类型
interface AsyncRoute extends Omit<Omit<RouteRecord, 'children'>, 'component'> {
  component: string;
  children?: AsyncRoute[];
} // 异步路由的component是string类型

export type AppRouteRecord = RouteRecord; //导出类型
export type AppAsyncRecord = AsyncRoute; //导出类型


流程梳理

  1. 页面加载初始路由
       const constantRoute: Array<AppRouteRecord> = [
       {
         name: 'login',
         path: '/login',
         component: () => import('@/views/login/login'),
         meta: {
           name: 'login',
           inMenu: false,
         },
       },
       {
         name: '404',
         path: '/:pathMatch(.*)',
         component: ErrorPage404,
       }
     ];
    
  2. 检测用户登陆情况,如果用户未登录,就去登录,登录了就请求一下权限路由表
      router.beforeEach(async (to, form, next) => {
      const sys = sysStore();
      if (to.name !== 'login') {
        if (!sys.token) {
          next({
            name: 'login',
            query: {
              redirect: to.fullPath,
            },
          });
        } else {
          if (!sys.asyncRoutes.length) {
            await handleRoutes();
            const { path, query } = to;
            next({ path, query, replace: true });此处解构是为了修改页面重新加载路由的时候页面报错,warning
          } else {
            next();
          }
        }
      } else {
        next();
      }
    });
    
  3. handleRoutes处理对应路由
     import * as asyncRoutes from '@/mock/routes.json';//mock的路由结构
     import { ResponseData } from '@/utils/request';
     import { AppAsyncRecord, RouteRecord, AsyncRoute } from '@/typings/evt';
     import { cloneDeep, forEach, map } from 'lodash';
     import { router } from '@/router';
     import baseLayout from '@/layout/baseLayout';
     import Home from '@/views/home/index';
     import emptyLayout from '@/layout/emptyLayout';
     import { RouteRecordRaw } from 'vue-router';
     import { sysStore } from '@/store/modules/sys';
     const baseComponents = {
       baseLayout,
       Home,
       emptyLayout,
     };//定义一下基础的布局和
     type baseComponents = 'baseLayout' | 'Home' | 'emptyLayout';
     /**
      * 模拟请求获取后端接口的路由表
      * @return {Record<string,unknown>} asyncRoutes
      */
     export const getRoutes = () => {
       return new Promise((resolve) => {
         setTimeout(() => {
           resolve(asyncRoutes);
         }, 500);
       });
     };
     /**
      * 处理异步路由
      */
     export const handleRoutes = () => {
       return new Promise((resolve) => {
         getRoutes().then((result) => {
           const { data } = result as ResponseData;
           const routes = formatRoutes(data as AppAsyncRecord[]);
           addRoutes(routes);
           const sys = sysStore();
           sys.asyncRoutes = data as RouteRecord[];//将获取到的路由存到store,方便其他地方使用
           resolve(routes);
         });
       });
     };
     /**
      * @param  {AppAsyncRecord[]} data 路由数据
      * @return 返回符合component规范的数据
      */
     export const formatRoutes = (data: AppAsyncRecord[]) => {
       return map(data, (current) => {
         const route = { ...current };
         const { component: currentComp } = current;
         Object.assign(route, {
           component: resolvePage(currentComp),
         });
         if (current.children?.length) {
           const currentChild: AsyncRoute[] = cloneDeep(
             current.children
           ) as AsyncRoute[];
           const children = formatRoutes(currentChild);
           Object.assign(route, { children });
         }
         return route as unknown as RouteRecordRaw;
       });
     };
     /**
      * @param  {AppRouteRecord[]} data 路由表
      * @param  {string|undefined} parentName 父级name
      */
     export const addRoutes = (
       data: RouteRecordRaw[],
       parentName?: string | undefined
     ) => {
       forEach(data, (v) => {
         router.addRoute(parentName ?? (v.name || ''), v as RouteRecordRaw);
       });
     };
     const pages = import.meta.glob('../views/**/*.tsx');//先获取到所有的组件列表
     /**
      * @param  {string} currentComp 路由名称
      * 返回路由的component
      */
     const resolvePage = (currentComp: string) => {
       if (Object.keys(baseComponents).includes(currentComp)) {
         return baseComponents[currentComp as baseComponents];
       }
       if (!currentComp) return '';
       return pages[currentComp];//返回对应的组件
     };
    

总结

总体来说跟vue2版本的动态路由没啥区别,只是api有些更改。只要加载到对应的组件就可以了。还有一种实现方案就是动态import,在vite中我没有实现这个功能,有对应的例子可以共同探讨一下。