使用VueRouter实现标签页路由导航功能

2,214 阅读1分钟

背景:多个页面一起使用的时候常常会用到标签页功能,但如ElementPlus框架只提供了标签页功能可是不能结合VueRouter路由来实现链接直接打开标签,下面介绍一下如何使用链接来打开标签和根据链接切换当前标签。

先看看效果: ​

一. 准备工作

        1. 新建一个Vue3项目

        2. 安装Element Plus (npm install element-plus)

        3. 安装Vue Router (npm install vue-router@4)

二. 添加路由配置文件

        我这里将配置文件命名为/configs/route.config.js

import { defineAsyncComponent } from 'vue' //对于标签页用到的页面可以按需进行引入

export default [{
    path: '/',
    name: '标签页测试项目',
    //标签页引用的显示模版
    component: import('@/components/template/DefaultTemplate.vue'),
    meta: {
        //缓存标签页里的组件
        keepAlive: true,
        //导航中不显示此链接
        hidden: true
    }
}, {
    path: '/product',
    name: '产品管理',
    redirect: '/product/index',
    children: [{
        path: '/product/index',
        name: '产品',
        component: defineAsyncComponent(() => import('@/pages/product/index.vue')),
        //归属那个标签页模版
        meta: { tagsPath: '/' } 
    },
    {
        path: '/product/type',
        name: '产品分类',
        component: defineAsyncComponent(() => import('@/pages/product/type.vue')),
        //归属那个标签页模版
        meta: { tagsPath: '/' }
    }]
}, {
    path: '/login',
    name: '用户登陆',
    component: import('@/pages/user/login.vue'),
    meta: {
        hidden: true
    }
}]

三. App.vue文件

<script></script>
<template>
    <!-- 如果keepAlive则使用keep-alive标签 -->
    <router-view v-slot="{ Component }" v-if="$route.meta.keepAlive">
      <keep-alive>
          <component :is="Component" />
      </keep-alive>
    </router-view>
    <router-view v-else></router-view>
</template>
<style scoped></style>

四. main.js文件,这里要注意,因为整个应用可能不止只有标签页所以需要在全局router.beforeEach钩子上处理非标签页的和标签页的跳转,标签页跳转需要带上标签页唯一的链接地址作为参数。

import { createApp } from 'vue'
import { createRouter, createWebHashHistory } from 'vue-router'
import routeConfig from '@/configs/route.config.js'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'

const app = createApp(App)
app.use(ElementPlus)

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

//绑定路由钩子处理路由事件
router.beforeEach((to, from, next) => {
    //非标签页子页面,正常导航
    if(!to.meta.tagsPath) {
        next()
    } else {
        //标签页子页面,导航到标签页
        const query = Object.assign({}, to.query);
        //标签子页面的地址作为参数传入标签页
        query.path = to.path;
        next({ 
            //导航到标签页
            path: to.meta.tagsPath,
            query
        })
    }
})
app.use(router)

app.mount('#app')

五. 标签页面

        我这里将标签页面文件命名为/components/template/DefaultTemplate.vue,主要保护导航和标签(Tabs)两个大模块,关键点在于beforeRouterUpdate路由信息更新时,需要处理好标签的打开状态。

<script>
export default {
    data: () => {
        return {
            appName: "测试标签页面",
            routes: [],
            //当前打开的导航
            currentPath: '',
            //当前打开的标签
            currentTagName: '',
            tabs: []
        };
    },
    mounted() {
        //初始化导航信息
        this.initRoutes();
        //获取实践路由导航页面,打开标签
        const path = this.$route.query.path;
        this.addTag(path);
    },
    beforeRouteUpdate(to, from) {
        //当停留在标签页且路由更新时,打开标签
        this.addTag(to.query.path);
    },
    methods: {
        initRoutes() {
            const menuRoutes = [];
            this.$router.getRoutes().forEach(route => {
                if(!route.meta || !route.meta.hidden) {
                    const menuRoute = { 
                        name: route.name,
                        path: route.path, 
                        children: [] 
                    }
                    menuRoutes.push(menuRoute);
                    route.children && route.children.forEach(child => {
                        const index = menuRoutes.findIndex(x => x.path == child.path);
                        //删除子导航条目
                        if(index > -1) 
                            menuRoutes.splice(index, 1);
                        menuRoute.children.push({
                            name: child.name,
                            path: child.path,
                            component: child.component
                        })
                    });
                }
            });
            this.routes = menuRoutes;
        },
        onTagRemove(targetName) {
            const index = this.tabs.findIndex(x => x.path == targetName);
            this.tabs.splice(index, 1);
            this.currentTagName = this.tabs[0].path;
            this.currentPath = this.tabs[0].path;
            //处理路由,跳转到第一个标签的路由
            this.$router.replace(this.currentPath)
        },
        //打开标签
        addTag(path) {
            //找到符合当前导航路径的路由信息
            const route = this.findRouteByPath(this.routes, path);
            const exists = this.tabs.find(x => x.path == route.path);
            //已打开标签不重复打开
            if(!exists) {
                this.tabs.push(route);
            }
            this.currentTagName = route.path;
            this.currentPath = route.path;
        },
        findRouteByPath(routes, path) {
            for (let i = 0; i < routes.length; i++) {
                const route = routes[i];
                if(route.path == path)
                    return route;
                if(route.children) {
                    const childRoute = this.findRouteByPath(route.children, path);
                    if(childRoute)
                        return childRoute;
                }
            }
        }
     }
}
</script>
<template>
    <el-container>
    <el-header>
      <el-menu
        :default-active="currentPath"
        class="el-menu-demo"
        mode="horizontal"
        :ellipsis="false"
        :router="true">
        <h1>{{ appName }}</h1>
        <el-sub-menu 
          v-for="route in routes" 
          :key="route.path"
          :index="route.path">
          <template #title>{{route.name}}</template>
          <el-menu-item 
            v-for="child in route.children" 
            :key="child.path" 
            :index="child.path">{{child.name}}</el-menu-item>
        </el-sub-menu>
      </el-menu>
    </el-header>
    <el-main>
      <el-tabs 
        type="border-card"
        v-model="currentTagName"
        :closable="tabs.length > 1"
        @tab-remove="onTagRemove">
        <el-tab-pane 
          v-for="tab in tabs" 
          :key="tab.path" 
          :label="tab.name"
          :name="tab.path">
          <component :is="tab.component"></component>
        </el-tab-pane>
      </el-tabs>
    </el-main>
    <el-footer></el-footer>
  </el-container>
</template>

添加上面代码后,标签页就可以实现效果图一样的功能效果了。