Vue3动态路由(Vite+Vue3+TS+Mock)

news2025/7/19 2:40:05

一、动态路由简介

Vue通过路由进行页面管理,不同的路由绑定到不同的页面。一般来说,前端直接写好的路由为静态路由,在不修改代码的情况下,路由表是不会改变的。对于不需要动态改变路由表的网站,静态路由就已经足够了,但是当页面需要与权限进行绑定时,不同用户允许浏览的页面是不一样的,在这种情况下,静态路由就难以满足需求了。动态路由就是由后端根据场景生成的合适路由数据,前端获取此数据并进行解析,最后与固定不变的静态路由组合而成的路由。

本文将基于Vite+Vue3+TypeScript构建的Vue项目与mock模拟的后端接口,简单介绍Vue的动态路由配置。

此项目代码已上传至GitHub,链接如下:

https://github.com/XMNHCAS/VueDynamicRoute


二、创建Vite+Vue3+TS基础项目

首先使用以下命令创建项目

npm create vite 项目名 -- --template vue-ts

在刚刚创建的项目的目录下执行以下命令,安装vue初始的node依赖包 

npm i

参考示例如下:

项目创建完成后,在项目目录下执行以下命令,使用VSCode打开此项目。也可以直接在此目录下右键点击使用VSCode打开。

Code .

注意:使用VSCode编辑Vue+TS的项目,建议禁用Vetur,使用Volar插件。


三、项目初始化及静态路由配置 

3.1、安装需要的Node Module 

接下来我们需要安装几个node模块:vue-router、axios和mock。其中vue-router是Vue的路由配置模块,axios是请求包,mock则是前端模拟api接口的包。

可依次执行以下命令,或修改package.json文件之后直接npm i。

安装vue-router和axios:

npm i vue-router axios -S

安装mockjs和vite-plugin-mock。由于本项目的mock主要用途仅为模拟后端数据接口,所以安装为开发依赖,若打包为生产环境则会失效。

npm i mockjs vite-plugin-mock -D

3.2、创建需要的文件夹以及文件

首先我们打开src文件夹下的components文件,删除HelloWorld.vue。打开App.vue,将文件内容修改为以下代码:

<template>
  <router-view></router-view>
</template>

然后在src文件夹下分别创建router、utils、apis、views和mock文件夹,并在文件夹中创建如图所示的文件:

 

3.3、配置vue-router

/src/router/index.ts:此文件为路由配置文件,我们先在此创建初始的静态路由。

import { RouteRecordRaw, createRouter, createWebHistory } from 'vue-router'

// 静态路由表
const routes: Array<RouteRecordRaw> = [
    {
        // 路由重定向配置
        path: '/',
        redirect: '/Home'
    }, {
        path: '/Home',
        component: () => import('../views/HomePage.vue')
    }
]

// 路由对象
const router = createRouter({
    history: createWebHistory(),
    routes
})

export default router

3.4、配置mock

/src/mock/index.ts:此文件为mock模拟接口配置,我们先创建一个测试接口,返回一个Hello World

import { MockMethod } from "vite-plugin-mock"

const mock: Array<MockMethod> = [
    {
        // 接口路径
        url: '/api/test',
        
        // 接口方法
        method: 'get',

        // 返回数据
        response: () => {
            return {
                status: 200,
                message: 'success',
                data: 'Hello World'
            }
        }
    }
]

export default mock

3.5、配置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/",
    }),
  ]
})

3.6、配置axios

/src/utils/request.ts:此文件为axios配置文件,它将创建一个axios全局单例,由于本项目仅做最简单的演示,所以仅配置baseUrl,实际使用时可根据实际情况添加拦截器等功能。

import axios from 'axios'

// axios对象
const service = axios.create({
    // axios请求基础URL
    // 由于本项目使用mock + vite-plugin-mock启动的mock服务,默认使用的端口号与页面一致
    baseURL: "http://localhost:5173",
    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' })

3.7、首页代码

/src/views/HomePage.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>

3.8、配置main.ts

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router'

const app = createApp(App)

// 启用路由
app.use(router)

app.mount('#app')

3.9、静态路由运行 

使用此命令运行,vite3默认的运行端口为5173。 

npm run dev

运行结果如下所示:

从右侧开发者工具中可以看出,我们模拟的test接口已经成功返回了数据,页面也正常根据路由跳转至Home页面。


四、配置动态路由

4.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/routes',
        method: 'get',
        response: () => {
            // 路由
            const routes = [
                {
                    path: '/PageOne',
                    name: 'PageOne',
                    component: 'PageOne.vue'
                }, {
                    path: '/PageTwo',
                    name: 'PageTwo',
                    component: 'PageTwo.vue'
                }, {
                    path: '/PageThree',
                    name: 'PageThree',
                    component: 'PageThree.vue',
                }
            ]

            return {
                status: 200,
                message: 'success',
                data: routes
            }
        }
    }
]

export default mock

此接口返回三个页面的路由,根据这个接口的数据,我们在views文件夹中创建这三个页面。 

 

页面仅由一个h1标签组成,h1的内容为文件名。此外再加上一个按钮,用以跳转回Home页面。PageOne.vue代码如下:

<template>
    <h1>Page One</h1>
    <button @click="handleClick">Home</button>
</template>

<script lang="ts" setup>
import { useRouter } from 'vue-router';

const router = useRouter()
const handleClick = () => router.push({ path: '/Home' })
</script>

其余两个页面仅需修改h1标签的内容。

4.2、安装并配置pinia

我们的动态路由数据应由一个公共的地方进行管理,本文选用vue的状态管理器来实现这个功能。pinia是vue新一代的状态管理器,与vuex作用基本相同,但是功能比vuex更加强大。

pinia官方文档:https://pinia.web3doc.top

首先在项目目录下执行以下命令,安装pinia:

npm i pinia -S

安装完成后,创建store文件夹,并在此文件夹下创建index.ts文件。

 /src/store/index.ts

import { defineStore } from 'pinia'

// pinia状态管理器
export const useStore = defineStore('myStore', {
    state: () => {
        return {}
    },
    getters: {},
    actions: {}
})

 完成基础配置后,需要在mian.ts中引入pinia,需要注意的是,pinia必须在vue-router之后引入。

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')

4.3、添加路由数据接口

/src/apis/index.ts

import req from '../utils/request'

/**
 * 测试接口
 */

// 测试用Hello World
export const TestApi = () => req({ url: '/api/test', method: 'get' })

/**
 * 动态路由接口
 */
export const GetDynamicRoutes = () => req({ url: '/api/routes', method: 'get' })

4.4、在pinia中添加配置项

由于我们使用的pinia存储我们的路由数据,所以我们需要在pinia的state中添加一个路由项(RouteRecordRaw类型的数组routes)。

我们在pinia的action中还需要添加一个根据路由数据加载动态路由的方法(addRoutes),路由数据和router对象由外部传入。外部传入router是为了避免循环调用router,毕竟需要进行加载动态路由的地方基本都有个router的示例对象,不过也可以直接在此方法里面直接调用router,并不影响结果,也不会报错。

加载路由的思路很简单,首先解析外部传入的路由数据,根据路由的数据类型生成对应的路由表,并存储到pinia中,然后直接遍历这个pinia中的路由表,使用router.addRoute()方法将路由加载进去。router.addRoute()方法还支持传如两个参数,这是为了在指定位置的路由中插入children,这种情况下第一个参数是父级路由的name,第二个参数就是要添加的children路由对象。本文的项目并没有做多层级的路由,所以使用一个参数即可。

vite使用动态路由,在动态导入组件的时候,需要注意不能将页面路径直接作为component导入,虽然开发环境一般是能正常加载,但是打包到生产环境的时候就会出错,所以我们需要添加以下代码:

let modules = import.meta.glob("../views/Pages/*.vue")

然后使用这个modules来配置组件:

// 错误示例:components:()=>import(`../views/Pages/${m.component}`)
// 正确示例如下:
component: modules[`../views/Pages/${m.component}`],

最终的pinia代码如下: 

 /src/store/index.ts

import { defineStore } from 'pinia'
import { RouteRecordRaw } from 'vue-router'

let modules = import.meta.glob("../views/Pages/*.vue")

// pinia状态管理器
export const useStore = defineStore('myStore', {
    state: () => {
        return {
            // 路由表
            routes: [] as Array<RouteRecordRaw>
        }
    },
    getters: {},
    actions: {
        // 添加动态路由,并同步到状态管理器中
        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/Pages/${m.component}`],
                })
            })

            this.routes.forEach(m => router.addRoute(m))
        },
    }
})

4.5、加载动态路由

配置了路由接口和加载路由的方法,接下来我们就需要加载我们的动态路由了。

思路也很简单,在我们的Home页面中调用路由的数据接口,在获取到数据之后调用加载的方法即可。为了验证我们的路由是否被加载成功,我们还需要添加三个路由对应的按钮,以便我们进行路由跳转。

/src/views/HomePage.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, GetDynamicRoutes } 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) {
        // 获取动态路由
        GetDynamicRoutes().then(res => {
            store.addRoutes(res.data.data, router)
        })      
    }

     // 测试接口
     TestApi().then(res => console.log(res.data)).catch(err => console.log(err))
})
</script>

效果如下: 

随便点击一个按钮,我们就能成功跳转到我们的动态路由了。

4.6、配置路由守卫

到这里,我们的动态路由已经加载成功了。不过其实我们还没有做完,这个动态路由还存在一个bug,假如我们刷新跳转后的页面,或者直接使用动态路由的路径进行跳转,就会出现以下情况:

可以看到,这种情况下我们的动态路由失效了,页面没了。这是因为我们的路由和pinia在刷新之后都会被重置,而我们加载路由的方法是在Home页面被调用的,当我们直接跳转到动态加载出来的路径或者直接在这个路径刷新的时候,Home页面并没有被加载,也就是说我们的动态路由并没有被加载上去,自然这个动态的页面也就丢失了。

解决方法有很多,举个例子,既然我们刷新丢了路由是因为Home页面没被加载,那我们把加载的方法直接写到必然会被调用的App.vue就可以了,这是个简单直接的方法。不过考虑到实际使用的时候,我们可能需要在路由跳转时进行鉴权操作,下面介绍另一种方法,使用路由守卫进行加载。

其实路由守卫加载的思路也很简单,假如我们的页面请求路径不是某个指定的路径的时候,我们就在跳转之前先去查询状态管理器中是否存在我们的动态路由,或者该动态路由是否满足我们的跳转要求,如果不满足就请求接口并加载我们的动态路由,并在加载完成后再继续跳转操作。在本项目中,我们的固定页面是Home页面,所以只要我们跳转的不是Home页面,就查询pinia是否存在路由表,如果没有则请求接口获取路由并加载。代码示例如下:

/src/router/index.ts

import { RouteRecordRaw, createRouter, createWebHistory } from 'vue-router'
import { useStore } from "../store";
import { GetDynamicRoutes } from '../apis'

// 静态路由表
const routes: Array<RouteRecordRaw> = [
    {
        // 路由重定向配置
        path: '/',
        redirect: '/Home'
    }, {
        path: '/Home',
        component: () => import('../views/HomePage.vue')
    }
]

// 路由对象
const router = createRouter({
    history: createWebHistory(),
    routes
})

// 路由守卫
router.beforeEach((to, from, next) => {
    if (to.path !== '/Home' && to.path !== '/') {
        const store = useStore()
        if (store.routes.length < 1) {

            GetDynamicRoutes().then(res => {
                store.addRoutes(res.data.data, router)
                next({ path: to.path, replace: true })

            }).catch(_ => {
                next()
            })

        } else {
            next()
        }
    } else {
        next()
    }
})

export default router

现在我们直接访问我们的动态页面也能成功加载了。

虽然能成功加载了,但是开发者工具那里还有一条报错:

[Vue Router warn]: No match found for location with path "/PageOne"

这是因为我们在路由跳转之前请求了一次接口进行加载,但是在完成路由加载之前,vue-router就直接先去找了这个路径,我们的路由都还没加载完成,当然也就找不到了,所以就会出这个警告。其实这个警告完全可以无视,不过我们最好还是处理一下,避免出现意外情况。

解决方法也很简单,添加一个404页面就可以了。在views文件夹中添加一个Error文件夹,并在此文件夹中添加404.vue文件。

<template>
    <h1>404</h1>
</template>

然后在静态路由中添加一个错误的路由:

 /src/router/index.ts

import { RouteRecordRaw, createRouter, createWebHistory } from 'vue-router'
import { useStore } from "../store";
import { GetDynamicRoutes } from '../apis'

// 静态路由表
const routes: Array<RouteRecordRaw> = [
    {
        // 路由重定向配置
        path: '/',
        redirect: '/Home'
    }, {
        path: '/Home',
        component: () => import('../views/HomePage.vue')
    }, {
        // 404页面配置
        path: '/:catchAll(.*)',
        component: () => import('../views/Errors/404.vue')
    }
]

// 路由对象
const router = createRouter({
    history: createWebHistory(),
    routes
})

// 路由守卫
router.beforeEach((to, from, next) => {
    if (to.path !== '/Home' && to.path !== '/') {
        const store = useStore()
        if (store.routes.length < 1) {

            GetDynamicRoutes().then(res => {
                store.addRoutes(res.data.data, router)
                next({ path: to.path, replace: true })

            }).catch(_ => {
                next()
            })

        } else {
            next()
        }
    } else {
        next()
    }
})

export default router

 效果如下:


五、结尾

至此我们的动态路由就完成了,动态路由的实现方法有很多,本文介绍的也只是其中一种,也有很大的优化空间,这个就看需要参照实际需求了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/36186.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

关于数据治理工具的选型,你了解多少?

数据治理的本质是盘点数据资产、治理数据质量&#xff0c;实施数据全生命周期的管理&#xff0c;这里面包括了建组织、立制度或者使用一款数据治理的软件帮助企业开展数据治理的相关工作等等。根据不同的数据治理项目特点&#xff0c;会用到不同的技术或工具。拥有一套趁手好用…

功率放大器的三种类型是什么意思

很多人都知道功率放大器&#xff0c;但是却不知道同样都是功率放大器&#xff0c;但是名字相同&#xff0c;作用却是完全不同的&#xff0c;总是会有工程师发出这样的疑问“功率放大器的三种类型是什么以及功率放大器怎么选择型号”等等&#xff0c;今天就请安泰电子来为我们解…

SpringBoot整合Alibaba-Dubbo和Apache-Dubbo

文章目录1 Alibaba整合Dubbo1.1 服务提供者1.1.1 服务提供者接口1.1.2 服务提供者实现类1.1.2.1 项目结构图1.1.2.2 pom.xml1.1.2.3 服务实现类1.1.2.4 配置文件1.1.2.5 启动类1.2 服务消费者1.2.1 项目结构图示1.2.2 请求入口1.2.3 配置文件1.2.4 启动类2 Apache整合Dubbo2.1 …

Pytorch学习笔记(四)官方60min入门教程之图像分类器

你已经了解了如何定义神经网络&#xff0c;计算损失值和网络里权重的更新。 现在你也许会想应该怎么处理数据&#xff1f; 通常来说&#xff0c;当你处理图像&#xff0c;文本&#xff0c;语音或者视频数据时&#xff0c;你可以使用标准 python 包将数据加载成 numpy 数组格式…

Web3中文|10月份超48%的以太坊NFT交易额是假的

来源 | cryptoslate 编译 | BoweniNFTnews.com 10月份全球NFT销售额超过8.5亿美元&#xff0c;总交易量约为300万笔。 NFT月销售额 数据来源&#xff1a;Footprint Analytics 在市场状况不佳的情况下&#xff0c;仍有大量唯一买家与卖家。10月份有超过100 万的唯一买家和卖家…

第九章 哈希表 AcWing 1532. 找硬币

第九章 哈希表 AcWing 1532. 找硬币 原题链接 AcWing 1532. 找硬币 算法标签 哈希表 双指针 思路 使用哈希表集合 用一个哈希表存储硬币。 对于每一枚硬币 x&#xff0c;判断在集合中是否存在 y&#xff0c;使得 x y m。 如果存在&#xff0c;则是一组解&#xff0c;判…

Linux Command htpasswd 创建密码文件

文章目录Linux Command htpasswd 创建密码文件1. 简介2. 安装3. 语法4. 选项5. 示例6. 其他Linux Command htpasswd 创建密码文件 1. 简介 htpasswd是Apache的Web服务器内置的工具,用于创建和更新储存用户名和用户基本认证的密码文件。 2. 安装 centos 7、 redhat&#xff…

MCE | 靶向 cGAS-STING 通路或可治疗渐冻症

自从 12 年前被发现以来&#xff0c;STING 途径就吸引了众多TOP生物学家的关注&#xff0c;去年 3 月&#xff0c;陈志坚教授带领的研究团队和其合作者在 Nature 上同日发表三篇论文&#xff0c;让 cGAS-STING 通路大火了一把&#xff0c;并被认为是未来十年内肿瘤免疫靶点的“…

指静脉当前遇到的问题

一、《基于改进残差网络的指静脉识别算法》_易芮 2020.5.20 ①采集到的指静脉图像质量不高"边缘曝光"及手指的自由度导致图像存在的偏移问题 &#xff08;传统的指静脉识别技术是基于图像的纹理、特征点等细节进行特征提取&#xff0c;若图像质量较差的话&#xff…

thymeleaf抽取公共页面

thymeleaf抽取公共页面Thymeleaf中th:include、th:replace、th:insert、th:fragment用法及区别th:include、th:replace、th:insert区别在开发Web网站的时候&#xff0c;HTML页面有很多是相同的&#xff0c;如果每一个页面都写一遍&#xff0c;不仅非常麻烦&#xff0c;而且非常…

基于粒子群算法的城轨列车牵引多目标能耗优化问题附matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

python基础之元组

文章目录一、元组注意&#xff1a;二、创建元组怎么验证这个变量真的是一个元组类型&#xff1a;三、使用迭代遍历元组四、应用场景五、格式化字符串后面的&#xff08;&#xff09;本质上就是元组六、元组和列表之间的转换一、元组 元组&#xff08;Tuple&#xff09;与列表类…

Python(PyQt5)制作帮助文档查看器(可显示后缀名为md的文件)同时显示文本和图片

先看完整效果图: 帮助文档查看器是很多程序中必备要素,而利用Qt中的QTreeView组件可以很方便的查看文件,而QTextBrowser可以直接显示格式化的MarkDown文本。因此可以利用这两个组件制作一个帮助文件查看器。 未优化 效果图: 问题优化: 你会发现QT treeView列宽设置不成功问题…

Mybatis-Plus 06 条件构造器和常用接口

一、wapper介绍 Wrapper &#xff1a; 条件构造抽象类&#xff0c;最顶端父类 AbstractWrapper &#xff1a; 用于查询条件封装&#xff0c;生成 sql 的 where 条件 ​ QueryWrapper &#xff1a; 查询条件封装 ​ UpdateWrapper &#xff1a; Update 条件封装 AbstractL…

PG::Inclusiveness

nmap -Pn -p- -T4 --min-rate1000 192.168.134.14 nmap -Pn -p 21,22,80 -sCV 192.168.134.14 80端口是默认页面&#xff0c;先查看21端口FTP服务的匿名登录&#xff0c;并未发现可利用信息。 继续查看80端口&#xff0c;在robots.txt中得到提示。 https://www.howtogeek.co…

【Kubernetes | Pod 系列】Pod的 YAML 清单文件详解

目录3. Pod的 YAML 清单文件3.1 获取资源对象 YAML3.2 解析 YAML 清单文件&#xff08;1&#xff09;apiVersion查看 Kubernetes API 中全部的 API 组&#xff08;2&#xff09;kind查看 Kubernetes 中全部的对象资源类型&#xff08;3&#xff09;metadata&#xff08;4&#…

SpringBoot整合Mybatis方式2:使用注解方式整合Mybatis

SpringBoot整合Mybatis简介SpringBoot整合Mybatis方式2&#xff1a;使用注解方式整合Mybatis1.先用idea创建一个添加mybatis需要的相关依赖的工程。2.准备数据库和表3.创建表映射类4.创建mapper代理接口5.创建Service层和Service的实现层6.创建控制层&#xff08;也就是web层&a…

JQuery使用

简介 一个JavaScript的框架&#xff0c;简化了JS的语法和操作&#xff0c;定义了HTML属性操作、样式操作、DOM 操作等相关函数&#xff0c;实现了对ajax异步请求封装。提供了很多预定义函数的JS文件。 作用 简化部分JavaScript开发 使用步骤 1,引入Jquery 下载 地址1:http:/…

D-0015 红外接收硬件电路设计

红外接收硬件电路设计1 简介2 硬件电路实战3 红外遥控的原理4 主要参数5 电路设计要点1 简介 肉眼可见的光&#xff0c;按照波长进行排列依次为&#xff1a;红、成、黄、绿、青、蓝、紫。其中红光的波长范围为0.620.76μm&#xff1b;紫光的波长范围为0.380.46μm。比紫光波长…

[iOS]代码混淆

混淆有几点注意&#xff1a; 不能混淆系统方法 不能混淆init开头的等初始化方法 混淆属性时需要额外注意set方法 如果xib、 storyboard中用到了混淆的内容&#xff0c;需要手动修正。 可以考虑把需要混淆的符号都加上前缀&#xff0c;跟系统自带的符号进行区分。 混淆有风险&am…