vue3构建view admin后台管理系统(1)——技术选型
vue3构建view admin后台管理系统(2)——Vue Router使用详解
vue3构建view admin后台管理系统(3)——基于Vue Router实现导航栏
上篇文章我们讲解了Vue Router路由管理工具基本的使用,但是如果同学们没有项目经验,即时看明白官方文档,也很难真正将所学应用到项目中,造成一看就懂,一用就废的尴尬局面。
如何把Vue Router真正应用到项目中,才是我们的重中之重。
其实写这篇文章之前,我是很犹豫的,因为很多东西,原理很简单,应用就略麻烦,写出来就更麻烦,最主要的是,我也不知道写完了,会不会有帮助。在这里抛砖引玉,有不当的地方,欢迎大佬指正。
本篇文章有一个躲不过的基础概念——Vue Router的嵌套路由。
详细概念请一定移步官网仔细阅读,这里只是做个概述,帮助曾经阅读过官网的同学唤起记忆。
我们在路由定义文件router.js中定义过路由数据:
const routes = [{ path: '/user/:id', component: User }]
我们在页面入口文件APP.vue中定义过路由入口:
<router-view></router-view>
当我们在main.js中引入router插件,就会自动把routes中定义的路由信息拿到,然后根据我们定义的事件,把对应的组件渲染到router-view中。
这里的意思就是把User组件渲染到router-view的位置。
如果User组件中还存在router-view标签,就是路由嵌套,则会在User中的router-view位置渲染routes中定义的对应的children内容。如:
const routes = [
{
path: '/user/:id',
component: User,
children: [
{
// 当 /user/:id/profile 匹配成功
// UserProfile 将被渲染到 User 的 内部
path: 'profile',
component: UserProfile,
},
{
// 当 /user/:id/posts 匹配成功
// UserPosts 将被渲染到 User 的 内部
path: 'posts',
component: UserPosts,
},
],
},
]
如果User组件的内容如下:
const User = {
template: `
<div class="user">
<h2>User {{ $route.params.id }}</h2>
<router-view></router-view>
</div>
`,
}
那么就会在User组件中的router-view渲染UserProfile和UserPosts。
以上就是路由嵌套的大体内容。User可以视为父组件,UserProfile和UserPosts则为子组件。
如果对router插件的使用有疑惑的同学请翻阅我上篇文章,这里假定你已经对router的基本使用都熟悉了。
我们先梳理清楚路由涉及的几个主要组件:
上节有提及App.vue中存在路由的入口
<router-view></router-view>
router.js中的数据都会渲染在这里。router.js代码精简后,只留下几条数据:
import Login from 'module-base/views/Login/Login.vue'
import Layout from 'module-base/views/Layout/Layout.vue'
export const routes = [
{
path: '/login',
name: 'login',
component: Login, //默认直接加载
meta: {
hideInMenu: true
}
},
{
path: '/home',
name: 'home',
component: Layout,
children: [
{
path: '/home_page',
name: 'home_page',
meta: {
title: '首页',
icon: 'ios-list-box'
},
component: () => import('module-base/views/Home.vue')
}
]
},
{
path: '/mailSend',
name: 'mailSend',
component: Layout,
meta: {
showAlways: true,
title: '表单',
icon: 'ios-list-box',
// hideInMenu: true
},
children: [
{
path: '/mailSend_page',
name: 'mailSend_page',
meta: {
title: '表单示例',
icon: 'ios-list-box'
},
component: () => import('@/views/MailSend/MailSend.vue')
}
]
}
]
router.js里面有两个组件Login、Layout是先加载,然后定义到component属性,而其他组件都是通过以下代码加载:
() => import('@/views/MailSend/MailSend.vue')
这是一种动态加载组件的方式,可以做到按需加载,优化速度。
我们可以发现,除了“path: ‘/login’”部分,是直接加载login,后面的都定义了一个属性:
component: Layout
这说明我们每个业务页面,其实都是嵌套在Layout组件中,Layout就是我们所有页面的父组件,其他业务页面则是子组件,如children中定义的Home.vue、MailSend.vue。
我们把Layout.vue中的主要html代码摘抄出来:
<div class="layout">
<Layout style="height: 100%">
<Sider ref="side1" style="min-width: 100px" hide-trigger collapsible :collapsed-width="78" v-model="isCollapsed">
<SideMenu ref="sideMenu" :menu-list="menuList" @on-select="turnToPage_sideMenu"></SideMenu>
<svg class="icon icon-menu" @click="collapsedSider" aria-hidden="true">
<use xlink:href="#icon-pendant-full"></use>
</svg>
</Sider>
<Layout>
<Content :style="{overflowY:'auto',overflowX:'auto', background: '#fff', minHeight: '260px'}">
<!--router-view嵌套,这里渲染Layout路由的children路由 -->
<router-view v-slot="{Component}">
<KeepAlive :max="10">
<component :is="Component"></component>
</KeepAlive>
</router-view>
</Content>
</Layout>
</Layout>
</div>
这段代码十分简单,主要分为两部分,一个是Sider,就是左侧的导航栏,一个是Layout(iview框架提供的布局标签),就是右侧渲染组件内容的区域,Layout标签里有router-view标签,这里和App.vue中的router-view就组成了路由嵌套。所以子组件Home.vue、MailSend.vue等都会渲染在这里。
Layout标签部分没什么好说的,都是router插件的基础应用。
Sider标签中的svg是应用的阿里iconfont,看不明白可忽略。这部分的核心其实是SideMenu组件。
SideMenu组件相对于Layout.vue就相对复杂一些了,因为对于router.js中一些自定义信息的判断,都是在这里,代码如下:
<Menu theme="dark" width="auto" :class="menuitemClasses">
<template v-for="item in menuList">
<template v-if="item.children && item.children.length === 1">
<!-- 只有一个children元素的情况-->
<SideMenuItem v-if="showChildren(item)" :key="`menu-${item.name}`" :parent-item="item"></SideMenuItem>
<MenuItem v-else :name="getNameOrHref(item,true)" :key="`menu-${item.children[0].name}`">
<svg v-if="item.children[0].meta.iconfont" class="icon" aria-hidden="true">
<use xlink:href="#icon-weixin"></use>
</svg>
<Icon v-else :type="item.children[0].meta.icon"></Icon>
<span v-if="!isCollapsed" :class="{active:!isCollapsed}">{{ showTitle(item.children[0]) }}</span>
</MenuItem>
</template>
<template v-else>
<!-- 没有children或者有多个children的情况-->
<SideMenuItem v-if="showChildren(item)" :key="`menu-${item.name}`" :parent-item="item"></SideMenuItem>
<MenuItem v-else :name="getNameOrHref(item)" :key="`menu-${item.name}`">
<svg v-if="item.meta.iconfont" class="icon" aria-hidden="true">
<use xlink:href="#icon-weixin"></use>
</svg>
<Icon v-else :type="item.meta.icon"></Icon>
<span v-if="!isCollapsed" :class="{active:!isCollapsed}">{{ showTitle(item) }}</span>
</MenuItem>
</template>
</template>
</Menu>
导航主要应用了iview框架的Menu组件。我们渲染导航,总得根据一个数组数据来渲染吧,这个数据从哪来的,没错,就是从router.js中获取的。
我的router数据传输过程大致为:
在渲染过程中,可以通过item的一些自定义配置,来控制渲染效果,比如我的导航栏里,有以下几个处理:
Layout和SideMenu组件都讲解完毕,那是如何做到点击导航栏实现跳转的呢?
答案是,我在Layout组件中写入SideMenu时,定义了一个方法@on-select=“turnToPage_sideMenu”,当点击选择导航栏时,调用方法如下:
function turnToPage_sideMenu(name) {
console.log(name)
turnToPage(router, name)
}
//在另一个工具函数中定义了方法turnToPage
export const turnToPage= ($router,route)=> {
let { name, params, query } = {}
if (typeof route === 'string') name = route
else {
name = route.name
params = route.params
query = route.query
}
if (name.indexOf('isTurnByHref_') > -1) {
window.open(name.split('_')[1])
return
}
$router.push({
name,
params,
query
})
}
这部分代码略多,没办法都粘贴上来,如果有兴趣详细了解的同学,可私聊或评论我获取源码。
根据路由信息渲染导航栏其实思路很简单: