1、为什么需要动态路由?
一般开发都是写静态路由,我们为什么要使用动态路由呢?因为动态路由对权限的划分是一个最有效的解决方法,下面我们就开始搭建一个动态路由的项目,使用技术是vite+ts+vue3+pinia+mock
,mock
主要用于模拟请求接口之后的处理,更接近实际项目
2、创建一个vite项目
yarn create vite
创建一个项目之后启动,具体启动过程初始化命令里面都会有提示的这里就不详细讲解了,删除里面的HelloWord.vue
文件,这样一个空白项目就有了,下面我们先进行安装需要的插件
3、插件安装
需要安装vue-router
、pinia
、axios
和mock
,我这边是使用的yarn
安装的,如果使用npm
安装也是可以的
//安装vue-router和axios、pinia
yarn add vue-router axios pinia
//安装mockjs和vite-plugin-mock。mock主要用途仅为模拟后端数据接口,所以安装为开发依赖,若打包为生产环境则会失效。
yarn add mockjs vite-plugin-mock
安装好插件之后,开始创建文件夹以及需要的文件
4、创建文件夹以及文件
4.1 在src
下面新建router/index.ts
:
import { RouteRecordRaw, createRouter, createWebHistory,createWebHashHistory } from 'vue-router'
// 静态路由表
const routes: Array<RouteRecordRaw> = [
{
// 路由重定向配置
path: '/',
redirect: '/Home'
}, {
path: '/Home',
component: () => import('../views/Home.vue')
}
]
// 路由对象
const router = createRouter({
history: createWebHashHistory(),//hash路由
routes
})
export default router
4.2 打开app.vue
文件,改成如下代码:
<template>
<router-view></router-view>
</template>
<script setup lang="ts"></script>
<style scoped>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
}
</style>
4.4 在main.ts里面引入
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router' //引入router
const app=createApp(App)
app.use(router) //使用router
app.mount('#app')
这样就可以看到home页面了
4.5 模拟mock数据使用
4.5.1 创建/src/mock/index.ts
// /src/mock/index.ts
import { MockMethod } from "vite-plugin-mock"
const mock: Array<MockMethod> = [
{
url: '/api/test',
method: 'get',
response: () => {
return {
status: 200,
message: 'success',
data: '返回的数据'
}
}
}
]
export default mock
4.5.2 配置vite.config.ts
要想使用mock
,我们还需要在vite.config.ts
文件下对mock
进行配置,让vite
启动的同时启动mock
服务。
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { viteMockServe } from 'vite-plugin-mock'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
// mock服务
viteMockServe({
supportTs: false,
logger: false,
mockPath: "./src/mock/",
}),
]
})
4.5.3 配置axios
/src/utils/request.ts
:此文件为axios
配置文件,它将创建一个axios
全局单例,由于本项目仅做最简单的演示,所以仅配置baseUrl
,实际使用时可根据实际情况添加拦截器等功能。
import axios from 'axios'
// axios对象
const service = axios.create({
// axios请求基础URL
baseURL: "http://127.0.0.1:5175", //此处根据自己启动的端口来
timeout: 5000
})
export default service
/src/apis/index.ts
:此文件为接口文件,接口统一放到此文件中。
import req from '../utils/request'
/**
* 测试接口
*/
// 测试用Hello World
export const TestApi = () => req({ url: '/api/test', method: 'get' })
home.vue
代码修改如下
<template>
<h1>Home</h1>
</template>
<script lang="ts" setup>
import { TestApi } from '../apis'
TestApi().then(res => console.log(res)).catch(err => console.log(err))
</script>
在这里就可以看到网络里面有接口请求成功了。
5、配置动态路由
5.1 配置动态路由接口
首先我们先在刚刚创建的mock接口文件/src/mock/index.ts
中添加一个返回路由信息的路由接口,如下所示
import { MockMethod } from "vite-plugin-mock"
const mock: Array<MockMethod> = [
{
url: '/api/test',
method: 'get',
response: () => {
return {
status: 200,
message: 'success',
data: 'Hello World'
}
}
},
{
url: '/api/getRoutes',
method: 'get',
response: () => {
const routes = [
{
path: '/Page1',
name: 'Page1',
component: 'Page1/index'
},
{
path: '/Page2',
name: 'Page2',
component: 'Page2/index'
}
]
return {
status: 200,
message: 'success',
data: routes
}
}
}
]
export default mock
/src/api/index.ts
import req from '../utils/request'
export const TestApi = () => req({ url: '/api/test', method: 'get' })
export const GetRoutes = () => req({ url: '/api/getRoutes', method: 'get' })
5.2 安装并配置pinia
pinia
和vuex
都是vue
的全局状态管理工具,在前面的步骤已经安装了这个插件,我们就直接使用。
/src/store/index.ts
代码如下
// /src/store/index.ts
import { defineStore } from 'pinia'
import { RouteRecordRaw } from 'vue-router'
//引入所有views下.vue文件
let modules = import.meta.glob("../views/**")
// pinia状态管理器
export const useStore = defineStore('myStore', {
state: () => {
return {
// 路由表
routes: [] as Array<RouteRecordRaw>
}
},
getters: {},
actions: {
// 添加动态路由,并同步到状态管理器中,这个地方逻辑是写的最简单的方式,大家可以根据自己的业务需求来改写,本质就是使用addRoute来实现
addRoutes(data: Array<any>, router: any) {
data.forEach(m => {
this.routes.push({
path: m.path,
name: m.name,
// 错误示例:components:()=>import(`../views/Pages/${m.component}`)
// 正确示例如下:
component: modules[`../views/${m.component}.vue`],
})
})
console.log('this.routes',this.routes)
this.routes.forEach(m => router.addRoute(m))
},
}
})
main.ts
代码如下
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router'
import { createPinia } from 'pinia'
const pinia = createPinia()
const app = createApp(App)
// 启用路由
app.use(router)
// 启用pinia
app.use(pinia)
app.mount('#app')
/src/views/home.vue
代码如下
<template>
<h1>Home</h1>
<div style="display: flex; gap: 20px">
<button v-for="item in routes" @click="handleClick(item.path)">
{{ item.name }}
</button>
</div>
</template>
<script lang="ts" setup>
import { useStore } from "../store";
import { TestApi, GetRoutes } from "../apis";
import { useRouter } from "vue-router";
import { computed } from "@vue/reactivity";
import { onMounted } from "vue";
const router = useRouter();
const store = useStore();
// 动态路由表
const routes = computed(() => store.routes);
// 路由按钮点击事件
const handleClick = (path: string) => {
router.push({ path });
};
onMounted(() => {
if (store.routes.length < 1) {
// 获取动态路由
GetRoutes().then((res) => {
store.addRoutes(res.data.data, router);
});
}
// 测试接口
TestApi()
.then((res) => console.log(res.data))
.catch((err) => console.log(err));
});
</script>
这样就实现了动态路由的跳转,但是这样直接刷新page1
页面就会导致页面空白,所以我们是在路由拦截里面进行了处理
import { RouteRecordRaw, createRouter, createWebHashHistory } from 'vue-router'
import { useStore } from "../store";
import { GetRoutes } from '../apis'
// 静态路由表
const routes: Array<RouteRecordRaw> = [
{
// 路由重定向配置
path: '/',
redirect: '/Home'
}, {
path: '/Home',
component: () => import('../views/Home.vue')
}, {
// 404页面配置
path: '/:catchAll(.*)',
component: () => import('../views/404.vue')
}
]
// 路由对象
const router = createRouter({
history: createWebHashHistory(),
routes
})
// 路由守卫
router.beforeEach((to, from, next) => {
if (to.path !== '/Home' && to.path !== '/') {
const store = useStore()
if (store.routes.length < 1) {
GetRoutes().then(res => {
store.addRoutes(res.data.data, router)
next({ path: to.path, replace: true })
}).catch(_ => {
next()
})
} else {
next()
}
} else {
next()
}
})
export default router
这样动态路由就搭建成功了。