vue3+element-plus+vite实现动态路由菜单方式
目录
- 1. 环境搭建
- 1.1 新建一个vite搭建的vue3项目
- 1.2 选择项目框架 vue
- 1.3 选择语言类型 ts
- 1.4 执行命令进入到新建的项目文件中
- 1.5 下载依赖
- 1.6 完善项目目录结构以及环境配置
- 1.7 因为考虑是纯前端模拟后端给的路由数据
- 2. 在views文件夹下新建文件夹login
- 3. layout中制作动态路由菜单
- 总结
1. 环境搭建
1.1 新建一个vite搭建的vue3项目
先执行以下命令
npm create vite@latest my-project(你的项目名)
1.2 选择项目框架 vue
1.3 选择语言类型 ts
1.4 执行命令进入到新建的项目文件中
cd my-project
1.5 下载依赖
npm i
下载项目中需要使用到的环境
npm install vue-router@4 pinia element-plus @element-plus/icons-vue
1.6 完善项目目录结构以及环境配置
1.6.1 先清空App.vue文件中内容,增加router-view作为路由出口
1.6.2 在src目录下新建文件夹layout,在该文件中新建文件AppLayout.vue (文件名看自己)
1.6.3 在src目录下分别新建文件夹store和router分别用来pinia状态管理和路由管理
1.6.3.1 router文件夹中新建两个文件一个index.ts用来初始化路由和存放静态路由一个dynamicRoutes.ts存放处理动态路由
// router/dynamicRoutes.ts // 更新 initDynamicRoutes,确保 dynamicRoutes 被更新 import router from './index'; import { useRouteStore } from '@/store/index'; // 导入 store import type { RouteRecordRaw, RouteRecordRedirectOption } from 'vue-router'; // 定义菜单项类型,确保 `name` 是 `string` type MenuItem = Omit& { name: string; // 必须有 name 属性 path: string; // 必须有 path 属性 component?: () => Promise ; // 用于动态加载组件的路径 children?: MenuItem[]; // 子路由类型 redirect?: string; // 调整 redirect 为更简单的 string 类型 meta?: { title: string; }; }; // Vite 支持使用特殊的 import.meta.glob 函数从文件系统导入多个模块 const modules: Record Promise > = import.meta.glob('../views/**/**.vue'); // 初始化动态路由 export const initDynamicRoutes = (menuData: MenuItem[]) => { const routeStore = useRouteStore(); // 获取 store const routerList: MenuItem[] = []; const addedRoutes = new Set(); // 用于跟踪已添加的路由,防止重复添加 // 递归处理路由 const processRoutes = (routes: MenuItem[]): MenuItem[] => { return routes.map((item) => { if (addedRoutes.has(item.name)) return null; // 防止重复处理 addedRoutes.add(item.name); // 标记路由为已处理 const componentLoader = modules[`../views${item.component}.vue`]; const route: MenuItem = { path: item.path, name: item.name as string, component: componentLoader , // 提供默认组件以防找不到 meta: item.meta, }; // 如果有子路由,递归处理 if (item.children && item.children.length > 0) { route.children = processRoutes(item.children); route.redirect = route.children[0]?.path; // 默认重定向到第一个子路由 } else { route.children = undefined; // 明确设置为 undefined } return route; }).filter((route) => route !== null) as MenuItem[]; // 过滤掉 null 项 }; // 顶级路由处理 const parentRouter = processRoutes(menuData); // 根路由配置 routerList.push({ path: '/', name: 'home', component: () => import('../layout/AppLayout.vue'), children: parentRouter, // 顶级路由作为子路由 redirect: parentRouter[0]?.path || '/', // 确保有默认重定向路径 }); // 将路由存储到 store 中 routeStore.dynamicRoutes = routerList; // 添加路由到 Vue Router routerList.forEach((route) => { router.addRoute(route as RouteRecordRaw); }); };
// router/index.ts import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router"; import { useRouteStore } from "@/store"; // 静态路由 const routes: RouteRecordRaw[] = [ { path: "/login", name: "login", component: () => import("@/views/login/index.vue"), }, { path: "/404", component: () => import("@/views/error-page/404.vue"), }, { path: "/401", component: () => import("@/views/error-page/401.vue"), }, // 匹配所有路径 { path: "/:pathMatch(.*)", redirect: "/login" }, ]; // 创建路由 const router = createRouter({ history: createWebHashHistory(), // 路由模式 routes, // 静态路由 }); // 路由守卫:初始化时跳转到上次访问的页面 window.addEventListener('DOMContentLoaded', () => { const routeStore = useRouteStore() const beforeReloadRoute = sessionStorage.getItem('beforeReloadRoute') if (beforeReloadRoute) { const to = JSON.parse(beforeReloadRoute) routeStore.beforeRouter = to.path // 清除保存的路由信息 sessionStorage.removeItem('beforeReloadRoute') // 导航回刷新前的路由 router.replace(to) const keys = Object.keys(to) if (keys.includes('name')) { sessionStorage.setItem('roterPath', JSON.stringify(to.name)) } } }) // 在页面即将刷新时保存当前路由信息 window.addEventListener('beforeunload', () => { const currentRoute = JSON.stringify(router.currentRoute.value) sessionStorage.setItem('beforeReloadRoute', currentRoute) }) export default router;
1.6.3.2 实现路由持久化和白名单,需要在src目录下新建一个permission.ts文件
import { createVNode, render } from 'vue'; import { initDynamicRoutes } from '@/router/dynamicRoutes'; import router from './router/index'; import loadingBar from '@/component/loadingBar.vue'; import Cookies from 'js-cookie'; // 引入 js-cookie import { useRouteStore } from '@/store/index'; import menuData from '/public/dynamicRouter.json'; // 导入动态菜单数据 const whileList = ['/login']; // 白名单 const Vnode = createVNode(loadingBar); render(Vnode, document.body); router.beforeEach(async (to, from, next) => { const routeStore = useRouteStore(); // 获取 Pinia 中的路由状态 const token = Cookies.get('token'); // 从 cookie 获取 token // 判断是否有 token,存在则说明用户已登录 if (token) { // 检查是否已经加载过动态路由 if (routeStore.dynamicRoutes.length === 0) { // 检查是否有持久化的动态路由 const persistedRoutes = sessionStorage.getItem('dynamicRoutes'); // 使用 sessionStorage if (persistedRoutes) { // 如果有持久化的动态路由,直接从 sessionStorage 加载 const routerList = JSON.parse(persistedRoutes); initDynamicRoutes(routerList); // 动态初始化路由 routeStore.setDynamicRoutes(routerList); // 将动态路由存入 Pinia next({ ...to, replace: true }); // 确保动态路由加载后再跳转 Vnode.component?.exposed?.startLoading(); // 启动加载条 } else { // 如果没有持久化的动态路由,则使用静态的 dynamicRouter.json const dynamicRoutes = initDynamicRoutes(menuData); // 动态初始化路由 if (dynamicRoutes !== undefined) { routeStore.setDynamicRoutes(dynamicRoutes); // 将动态路由存入 Pinia sessionStorage.setItem('dynamicRoutes', JSON.stringify(dynamicRoutes)); // 存储动态路由到 sessionStorage next({ ...to, replace: true }); // 确保动态路由加载后再跳转 Vnode.component?.exposed?.startLoading(); // 启动加载条 } else { next('/login'); // 如果没有动态路由信息,跳转到登录页面 } } } else { next(); // 如果已经加载过动态路由,直接跳转 } } else { // 如果没有 token,判断是否在白名单中 if (whileList.includes(to.path)) { next(); // 白名单路由放行 } else { next('/login'); // 否则跳转到登录页 } } }); router.afterEach(() => { Vnode.component?.exposed?.endLoading(); // 结束加载条 });
1.6.3.2 store文件夹下新建文件index.ts初始化pinia仓
// store/index.ts import { createPinia } from 'pinia'; import { useRouteStore } from './useRouteStore'; import { useUserStore } from './tokenStore'; // 创建 pinia 实例 const pinia = createPinia(); // 将所有 store 模块暴露 export { pinia, useRouteStore, useUserStore };
1.6.3.2 store文件夹下新建文件useRouteStore.ts处理存储动态路由文件
import { defineStore } from 'pinia'; import { ref } from 'vue'; import { initDynamicRoutes } from "@/router/dynamicRoutes"; // 导入初始化动态路由的方法 import type { RouteRecordRaw, RouteRecordRedirectOption } from 'vue-router'; // 定义菜单项类型,确保 `name` 是 `string` type MenuItem = Omit& { name: string; // 必须有 name 属性 path: string; // 必须有 path 属性 component?: () => Promise ; // 用于动态加载组件的路径 children?: MenuItem[]; // 子路由类型 redirect?: string; // 调整 redirect 为更简单的 string 类型 meta?: { title: string; }; }; // 定义路由数据 Store export const useRouteStore = defineStore('route', () => { // 存储菜单数据 const menuData = ref
1.6.4 在src目录下新建文件夹plugins,在该文件夹中新建文件element-plus.ts
/* Element-plus组件库 */ import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' import zhCn from 'element-plus/es/locale/lang/zh-cn' import { App } from 'vue' export default { install (app: App) { app.use(ElementPlus, { locale: zhCn }) } }
1.6.5 需要来配置main.ts,vite.config.ts以及tsconfig.json
1.6.5.1 main.ts配置
import { createApp } from "vue"; import App from "./App.vue"; import router from "./router/index"; import ElementPlus from "./plugins/element-plus"; import * as ElementPlusIconsVue from "@element-plus/icons-vue"; import { pinia } from '@/store/index'; // 导入 store // 创建 Pinia 实例 // 路由拦截 路由发生变化修改页面title router.beforeEach((to, from, next) => { if (to.meta.title) { document.title = to.meta.title; } next(); }); const app = createApp(App); // // 自动注册全局组件 app.use(router).use(ElementPlus).use(pinia).mount("#app"); for (const [key, component] of Object.entries(ElementPlusIconsVue)) { app.component(key, component); }
1.6.5.2 vite.config.ts配置
import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import path from 'path' export default defineConfig({ plugins: [vue()], resolve: { alias: { // 设置别名 方便路径引入 '@': path.resolve(__dirname, 'src'), } } })
1.6.5.3 tsconfig.json配置
{ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, "module": "ESNext", "moduleResolution": "Node", "strict": true, "jsx": "preserve", "sourceMap": true, "resolveJsonModule": true, "isolatedModules": true, "esModuleInterop": true, "lib": ["ESNext", "DOM"], "skipLibCheck": true, "noEmit": true, "paths": { "@/*": ["./src/*"] // 配置路径别名,不做配置会报错 } //就是这个没有设置导致的 }, // "extends": "./tsconfig.extends.json", "include": ["src/**/*.tsx","src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue"], "references": [{ "path": "./tsconfig.node.json" }] }
1.6.5.4 此外vue3在插件引入时有些时候会报错无法找到模块"xxx"的声明文件,此时需要在src目录下新建一个env.d.ts文件
///// 类型补充、环境变量 declare module "*.vue" { import type { DefineComponent } from "vue"; // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types const component: DefineComponent<{}, {}, any>; export default component; } // eslint-disable-next-line no-unused-vars interface ImportMetaEnv { readonly VITE_APP_TITLE: string; readonly VITE_API_BASEURL: string; // 更多环境变量... } // 如果遇到路径缺失找不到的情况 // 无法找到模块“xxx”的声明文件,就将该模块加入到下列代码中进行补充声明 declare module "xxxx";
1.7 因为考虑是纯前端模拟后端给的路由数据
所以我自己模拟一个json文件,需在public文件夹中新建dynamicRouter.json来存放模拟后端返回的路由数据,后期从接口获取可进行更改
[ { "path": "/principle", "name": "principle", "component": "/principle/index", "meta": { "title": "Vue3响应式原理" } }, { "path": "/ref", "name": "ref", "meta": { "title": "ref类" }, "children": [ { "path": "/ref/index", "name": "ref", "component": "/ref/common/ref", "meta": { "title": "ref" } }, { "path": "/ref/toRaw", "name": "toRaw", "component": "/ref/common/toRaw", "meta": { "title": "toRaw" } }, { "path": "/ref/toRef", "name": "toRef", "component": "/ref/common/toRef", "meta": { "title": "toRef" } }, { "path": "/ref/toRefs", "name": "toRefs", "component": "/ref/common/toRefs", "meta": { "title": "toRefs" } }, { "path": "/ref/isRef", "name": "isRef", "component": "/ref/no-common/isRef", "meta": { "title": "isRef" } }, { "path": "/ref/Ref", "name": "Ref", "component": "/ref/no-common/Ref", "meta": { "title": "Ref" } }, { "path": "/ref/shallowRef", "name": "shallowRef", "component": "/ref/no-common/shallowRef", "meta": { "title": "shallowRef" } }, { "path": "/ref/triggerRef", "name": "triggerRef", "component": "/ref/no-common/triggerRef", "meta": { "title": "triggerRef" } } ] } ]
如下是文件对应的位置
到目前为止整体的环境已经搭建完善,大概结构如下
2. 在views文件夹下新建文件夹login
在其中新建文件index.vue
//登录框用户登录
登录
3. layout中制作动态路由菜单
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持科站长。
栏 目:JavaScript
下一篇:Windows中彻底删除Node.js环境(以及npm)的方法
本文标题:vue3+element-plus+vite实现动态路由菜单方式
本文地址:https://fushidao.cc/wangluobiancheng/3097.html
您可能感兴趣的文章
- 02-11js中基本事件的总结(onclick、onblur、onchange等)
- 02-11详解如何在Node.js中使用中间件处理请求
- 02-11Vue3中Provide和Inject的用法及工作原理详解
- 02-11Vue+vant实现图片上传添加水印
- 02-11快速解决 keep-alive 缓存组件中定时器干扰问题
- 02-11uniapp 使用 tree.js 解决模型加载不出来的问题及解决方法
- 02-11基于uniapp vue3 的滑动抢单组件实例代码
- 02-10JavaScript 中的 Map使用指南
- 02-10vue3中使用print-js组件实现打印操作步骤
- 02-10Vue 中v-model的完整用法及v-model的实现原理解析


阅读排行
推荐教程
- 04-23JavaScript Array实例方法flat的实现
- 04-23THREE.JS使用TransformControls对模型拖拽的代码实例
- 04-23Vue3使用v-if指令进行条件渲染的实例代码
- 04-23vue3+ts项目搭建的实现示例
- 04-23JavaScript实现下载超大文件的方法详解
- 04-23vue如何使用pdf.js实现在线查看pdf文件功能
- 04-23vue.js调用python脚本并给脚本传数据
- 12-18使用JavaScript遍历输出页面中的所有元素的方法详解
- 04-23JS加密解密之保存到桌面书签
- 12-18Vue实现滚动加载更多效果的示例代码