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://zz.feitang.co/wangluobiancheng/3097.html
您可能感兴趣的文章
- 07-25如何使用 Deepseek 写的uniapp油耗计算器
- 07-25JavaScript其他类型的值转换为布尔值的规则详解
- 07-25JavaScript实现给浮点数添加千分位逗号的多种方法
- 07-25ReactNative环境搭建的教程
- 07-25JavaScript获取和操作时间戳的用法详解
- 07-25通过Vue实现Excel文件的上传和预览功能
- 07-25Node使用Puppeteer监听并打印网页的接口请求
- 07-25在Node.js中设置响应的MIME类型的代码详解
- 07-25Vue3解决Mockjs引入后并访问404(Not Found) 的页面报错问题
- 07-25如何利用SpringBoot与Vue3构建前后端分离项目


阅读排行
推荐教程
- 04-23JavaScript Array实例方法flat的实现
- 04-23Vue3使用v-if指令进行条件渲染的实例代码
- 04-23THREE.JS使用TransformControls对模型拖拽的代码实例
- 07-21JavaScript判断数据类型的四种方式总结
- 07-22JavaScript随机数生成各种技巧及实例代码
- 07-21JavaScript检查变量类型的常用方法
- 07-21基于vue3与supabase系统认证机制详解
- 04-23vue3+ts项目搭建的实现示例
- 07-21JavaScript双问号操作符(??)的惊人用法总结大全
- 07-22使用Node.js实现GitHub登录功能





