vue3+vite 配置动态路由菜单

14,284 阅读2分钟

接口返回数据格式:

image.png

1、路由文件配置router.ts

import { RouteRecordRaw, createRouter, createWebHistory } from 'vue-router';
import { App } from 'vue'
import { createRouterGuards } from "./router-guards"

export const indexChildRoutes = [
  {
    path: '/home',
    name: 'Home',
    hidden: false,
    meta: { title: '首页', hidden: false, icon: 'icon-ic_menu_home' },
    component: () => import(/* @vite-ignore */`../views/home/index`)
  }
]
export const routes: RouteRecordRaw[] = [
  {
    path: '/login',
    name: 'Login',
    component: () => import("../views/LoginIndex.vue")
  },
  {
    path: '/',
    name: 'sysjindex',
    component: () => import('../layouts/BasicLayoutIndex.vue'),
    redirect: '/home',
    children: []
  },
];

const router = createRouter({
  history: createWebHistory(),
  routes,
});

export function setupRouter(app: App) {
  createRouterGuards(router, routes)
  app.use(router)
}


export default router;

// main中使用
// import router, { setupRouter } from './router';
// // 挂载路由
// setupRouter(app)
// 路由准备就绪后挂载APP实例
// router.isReady().then(() => app.mount('#app'))

2、./router-guards 设置动态路由

import { Router } from "vue-router"
import store from "../store"

const whiteList = ['/login']
// const store = useStore()

export function createRouterGuards(router: Router, routes: any) {
    router.beforeEach((to, form, next) => {
        let token = localStorage.getItem("token")
        if (token) {
            // console.log(store.state.user.routes.length)
            if (!store.state.user.routes.length) {
                // 拉取useinfo信息
                store.dispatch('GetInfo')
                store.dispatch("GenerateRoutes").then((res) => {
                    const layout = routes.find((item: any) => item.name == 'sysjindex')!
                    layout.children = [...res]
                    router.addRoute(layout)
                    // res.map((item: any) => router.addRoute("sysjindex", item))
                    next({ ...to, replace: true })
                })
            } else {
                next()
            }
        } else {
            // 没有token
            if (whiteList.indexOf(to.path) !== -1) {
                // 在免登录白名单,直接进入
                next()
            } else {
                next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
            }
        }
    })
}

2.1、store获取处理getRoutes

// actions
const actions = {
    // 生成路由
    GenerateRoutes({ commit }: ActionContext<any, any>) {
        return new Promise(resolve => {

            // 向后端请求路由数据
            getRouters().then(res => {
                let accessedRoutes = filterAsnycRouter(res.data);
                accessedRoutes = indexChildRoutes.concat(accessedRoutes)
                // accessedRoutes.push({ path: "*", redirect: "/404", hidden: true });

                commit("SET_ROUTES", accessedRoutes);
                // console.log(accessedRoutes)
                resolve(accessedRoutes);
            });
        });
    },
   ...
}
// mutations
const mutations = {
    SET_ROUTES: (state: State, routes: Array<any>) => {
        state.routes = routes;
    },

中filterAsnycRouter,在main.ts文件平级目录下新建importRoutercom.ts文件,为了处理。Glob 模式会被当成导入标识符:必须是相对路径(以 ./ 开头)或绝对路径(以 / 开头,相对于项目根目录解析)。

2.2、importRoutercom.ts

import 'vite/dynamic-import-polyfill';

export function filterAsnycRouter(asyncRouterMap: any) {
    return asyncRouterMap.filter((route: any) => {
        if (route.component == 'Layout') {
            route.component = () => import('./layouts/BlankIndex.vue')
        } else {
            route.path = `${route.path}`
            route.component = resolveComponent(route.component);
        }
        if (route.children != null && route.children && route.children.length) {
            route.children = filterAsnycRouter(route.children);
        }
        return true;
    })
}

const pages = import.meta.globEager('./views/**/*.vue');
// 以 `./` 开头)或绝对路径(以 `/` 开头

const resolveComponent = (name: any) => {
    const importPage = pages[`./views/${name}.vue`];

    if (!importPage) {
        throw new Error(`Unknown page ${name}. Is it located under Pages with a .vue extension?`);
    }

    // return importPage().then(module => module.default);
    return importPage.default
}

处理在build下,unknown variable dynamic import:报错

参照代码:owenconti.com/posts/repla…

image.png

3.上述解决添加keep-alive缓存不生效问题

用在其【一个】【直属】的子组件被切换。KeepAlive包裹仅有一个组件且是单层的路由

在 basicLayoutIndex 中<RouterView ... />修改为添加

<BlankIndex :isRouterAlive="isRouterAlive"/>组件

import {provide, ref} from "vue";

const isRouterAlive = ref(true);

// 页面刷新按钮
provide("reload", () => {
  isRouterAlive.value = false;
  nextTick(() => {
    isRouterAlive.value = true;
  });
});

BlankIndex.vue

<script setup lang="ts">
import {computed} from "vue"
import {useUserStore} from "@/store/user.ts";

const props = defineProps({
  isRouterAlive: {
    type: Boolean,
    default: true
  },
});
const store = useUserStore();

// 顶部缓存的tab 标签页面
const tabsCacheList = computed(() => {
  let aaa:any = []
  store.tabsList.forEach((item:any) => {
    if(item.meta.isCache == '0') {
      aaa.push(item.name)
    }
  })
  return aaa
});

</script>
<template>
  <RouterView v-slot="{ Component,route }" v-if="props.isRouterAlive">
    <KeepAlive :include="tabsCacheList">
      <component :is="Component" :key="route.fullPath"/>
    </KeepAlive>
  </RouterView>
</template>

router.js修改

export const indexChildRoutes = [
  {
    path: '',
    component: "Layout",
    menuType: 'C', // C菜单,M 目录
    redirect: '/home',
    meta: { title: '首页', hidden: false, icon: 'icon-ic_menu_home' },
    children: [
      {
        path: '/home',
        component: () => import(/* @vite-ignore */`../views/home.vue`),
        name: 'home',
        // isCache 0缓存
        meta: { title: '首页', hidden: false, icon: 'icon-ic_menu_home', isCache: '1' }
      }
    ]
  }
]

importRoutercom修改,调整Layout基类,设置路由 name 从 path 中取,动态设置组件 name 为路由的 name

export function filterAsnycRouter(asyncRouterMap: any) {
    return asyncRouterMap.filter((route: any) => {
        if (route.component == 'Layout') {
            route.component = () => import('./layouts/BasicLayoutIndex.vue')
        } else if (route.component === 'ParentView') {
            route.component = () => import('./components/ParentView/index.vue')
        } else {
            // route.path = `/${route.component}`
            if(route.name !== 'home') {
                let name = route.path.split('/').pop()
                route.name = name.charAt(0).toUpperCase() + name.slice(1)
                route.path = `${route.path}`
                route.component = resolveComponent(route.component,route.name);
            }
        }
        if (route.children != null && route.children && route.children.length) {
            route.children = filterAsnycRouter(route.children);
        }
        return true;
    })
}

const pages = import.meta.glob('./views/**/*.vue');

const resolveComponent = (name: any,routername:any) => {
    let importPage = null
    for (let key of Object.keys(pages)) {
        if (key.indexOf(name) !== -1) {
            importPage = pages[key]
            break;
        }
    }

    if (!importPage) throw new Error(`${name} 组件文件不存在!`)
    //重新构造组件,调整组件name
    return importPage().then(comp => ({
        ...comp.default,
        name: routername,
    }))
    // return importPage
}

store 中修改GenerateRoutesaction,追加 home 到动态处理路由组件中res.data.unshift(indexChildRoutes[0])

// 生成路由
GenerateRoutes() {
  return new Promise((resolve) => {
    // 向后端请求路由数据
    getRouters().then((res) => {
      if (!res) return;
      res.data.unshift(indexChildRoutes[0])
      let accessedRoutes = filterAsnycRouter(res.data);
      this.routes = accessedRoutes;
      resolve(accessedRoutes);
    });
  });
},