vue3.0+ts+element-plus多页签应用模板:头部工具栏(上)
Posted W先生-SirW
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue3.0+ts+element-plus多页签应用模板:头部工具栏(上)相关的知识,希望对你有一定的参考价值。
目录
系列文章
- vue3.0+ts+element-plus多页签应用模板:前言
- vue3.0+ts+element-plus多页签应用模板:项目搭建
- vue3.0+ts+element-plus多页签应用模板:vue-router与vuex
- vue3.0+ts+element-plus多页签应用模板:element-plus按需引入与动态换肤
- vue3.0+ts+element-plus多页签应用模板:如何优雅地使用Svg图标
- vue3.0+ts+element-plus多页签应用模板:侧边导航菜单(上)
- vue3.0+ts+element-plus多页签应用模板:侧边导航菜单(下)
- vue3.0+ts+element-plus多页签应用模板:多级路由缓存
- vue3.0+ts+element-plus多页签应用模板:头部工具栏(上)
- vue3.0+ts+element-plus多页签应用模板:头部工具栏(中)
- vue3.0+ts+element-plus多页签应用模板:头部工具栏(下)
一、组件分析
还是老套路,在进行实际开发之前,我们需要先进行组件的分析。这次我们要编写的,是位于页面顶部的头部工具条。我们先来看一下这个头部工具条的作用是什么吧。
1. 这玩意能干啥
- 提供一些快捷操作:对侧边栏的展开和折叠,页面的刷新,全屏,系统设置等等
- 显示当前路由位置的面包屑
- 显示用户的基本信息:avatar和username (nickname)
- 提供一个下拉快捷菜单,承载一些有关用户的操作
- 提供一个标签栏,承载所有已经打开的标签页
2. 我要讲的内容
上面已经列出了我们的头部工具条所具有的功能,但是我不会全都讲述一遍,因为有些内容也不需要多费口舌,很简单就能实现。我只讲有一些难度的内容:
- 面包屑的实现
- 自定义全屏指令
- 标签栏的实现
- 页面的刷新
二、路由定位——面包屑导航
当用户在一个多级路由的系统中频繁地切换路由时,我们需要一种东西,能够很清晰的显示当前所访问的路由,像这样:
这就是面包屑导航,简单的说一下他的作用:
- 让用户了解当前所处位置,以及当前页面在整个网站中的位置
- 体现了网站的架构层级,能够帮助用户快速学习和了解网站内容和组织方式,从而形成很好的位置感
- 提供返回各个层级的快速入口,方便用户操作
是不是很符合当前我们所需要的东西呢?那么我们就开始实现吧。
1. 获取路由位置
因为面包屑导航最本质的作用就是让用户了解当前所处位置,所以每次切换路由的时候,我们必须要获取切换到当前路由会经过的所有路由,并生成一个路由数组,作为面包屑导航的基础。其实这步操作我们已经在前面的章节实现过了,大家可以回去看一下同系列文章:多级路由缓存的路由扁平化部分,或者接着往下看:
由于我的所有子级菜单项,都依赖于一个名叫AppMain的组件(不明白我在说什么的可以参照项目源码wyb199877/multi-tabs),也就是说,所有路由模块都没有直接引用Layout组件(你的主布局组件),而是引用了一个只有router-view的AppMain组件。所以在路由扁平化的时候,会删掉所有引用AppMain组件的中间路由。这就导致了一个问题,我们的route.mathed
在经过处理之后,只会剩下两级路由记录,无法明确的表示当前路由的所在位置及其层级关系。所以,我们需要在删除路由之前,将路由记录保存下来,作为面包屑导航的基础。
// 路由扁平化,将二级以上的路由转换成二级路由
const handleKeepAlive = (to: RouteLocationNormalized) => {
// 判断目标路由记录是否大于2
if (to.matched && to.matched.length > 2) {
// 遍历路由记录,制作面包屑导航列表,并删除依赖于AppMain组件的中间路由,实现路由扁平化
for (let i = 0; i < to.matched.length; i++) {
const element = to.matched[i]
/* 从这里开始处理面包屑 */
if (to.meta.breadcrumb && element.name !== 'index') {
;(to.meta.breadcrumb as BreadcrumbItemProps[]).push({
name: element.name!,
label: element.meta.label as string,
type: element.meta.type as MenuItemType
})
}
if (!to.meta.breadcrumb) {
to.meta.breadcrumb = []
}
/* 从这里结束处理面包屑 */
if (element.components.default.name === 'AppMain') {
to.matched.splice(i, 1)
handleKeepAlive(to)
}
}
} else {
// 当路由已经扁平化完毕的时候,将目标路由自身加入面包屑中
/* 从这里开始处理面包屑 */
const toBreadcrumb = to.meta.breadcrumb as BreadcrumbItemProps[]
if (!toBreadcrumb) return
const isToInToBreadcrumb = toBreadcrumb.some(
(item: BreadcrumbItemProps) => item.name === to.name
)
if (!isToInToBreadcrumb) {
;(to.meta.breadcrumb as BreadcrumbItemProps[]).push({
name: to.name!,
label: to.meta.label as string,
type: to.meta.type as MenuItemType
})
}
/* 从这里结束处理面包屑 */
}
}
router.beforeEach((to, from, next) => {
handleKeepAlive(to)
next()
})
2. 创建面包屑导航PathBreadbrcumb组件
贴代码,样式部分就省略了,然后这部分比较简单,没啥需要细讲的:
<template>
<el-breadcrumb separator="/" class="pathBreadcrumb">
<el-breadcrumb-item v-for="item in breadcrumb" :key="item.name">
<!-- 如果item.type = 'item',说明是可点击跳转的路由,即AsideBar菜单中的可点击跳转的菜单项 -->
<router-link v-if="item.type === 'item'" :to="{ name: item.name }">
{{ item.label }}
</router-link>
<span v-else class="no-redirect">{{ item.label }}</span>
</el-breadcrumb-item>
</el-breadcrumb>
</template>
<script lang="ts">
import { MenuItemType } from '@/layout/components/AsideBar/typings'
import { computed, defineComponent } from 'vue'
import { useRoute } from 'vue-router'
import { BreadcrumbItemProps } from '../../typings'
export default defineComponent({
name: 'PathBreadcrumb',
setup() {
const route = useRoute()
const breadcrumb = computed<BreadcrumbItemProps[]>(() => {
// 取出我们之前处理好的路由记录数组
let result = route.meta.breadcrumb as BreadcrumbItemProps[]
// 如果result中是undefined,说明当前路由是工作台workbench
if (!result) {
result = [{ name: 'workbench', label: '工作台', type: 'item' as MenuItemType }]
}
// 如果路由记录数组中的第一个路由不是工作台,那就把工作台放在第一位
if (result[0].name !== 'workbench') {
result.unshift({ name: 'workbench', label: '工作台', type: 'item' as MenuItemType })
}
return result
})
return {
breadcrumb
}
}
})
</script>
三、自定义指令——全屏
其实这个全屏功能用自定义指令去实现是有点多余的,因为我们这里是整个document都直接全屏,如果是仅需要某个元素全屏显示的话,倒是很有必要使用自定义指令。所以这里我还是通过自定义指令去实现这个功能。
1. 什么是自定义指令?
详细了解可移步官方文档-教程-自定义指令
虽然在vue中,并不提倡开发者进行实际DOM的操作,而是通过组件的形式去进行代码复用和抽象。然而,在很多情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令,因为在自定义指令中是可以很方便的操作DOM元素的。
2. 开始行动吧
在定义指令之前,我们需要先定义一个控制全屏和退出全屏的方法:
public static fullscreen(element: htmlElement | Element, action: 'enable' | 'cancel'): void {
// 检测当前浏览器是否支持直接全屏操作,若不支持则调用F11键的全屏功能
if (typeof window.ActiveXObject !== 'undefined') {
const wscript = new ActiveXObject('WScript.Shell')
wscript !== null && wscript.SendKeys('{F11}')
return
}
// 分别对传进来的DOM进行全屏和退出全屏操作
let requestMethod
switch (action) {
case 'enable':
requestMethod = element.requestFullscreen
if (requestMethod) {
requestMethod.call(element)
return
}
break
case 'cancel':
document.fullscreenElement && document.exitFullscreen()
return
}
// 如果能走到这里说明这个浏览器太low了,直接弹消息吧
window.$toast('warning', '您当前使用的浏览器不支持全屏功能,推荐使用谷歌浏览器访问')
store.commit(Consts.MutationKey.SET_FULLSCREEN, false)
}
然后我们就可以去定义全屏指令了,我在src/directive/fullscreen
目录下新建文件index.ts
:
import { App } from 'vue'
import Utils from '@/utils'
import store from '@/store'
const vFullscreen = (app: App<Element>) => {
app.directive('fullscreen', (el: HTMLElement | Element, binding) => {
// 将下面的document.documentElement改成el,即可实现仅对指令所在DOM元素进行全屏
Utils.fullscreen(document.documentElement, binding.value ? 'enable' : 'cancel')
})
document.onfullscreenchange = () => {
store.commit('appModule/SET_FULLSCREEN', Boolean(document.fullscreenElement))
}
}
export default vFullscreen
在src/directive
目录下新建文件index.ts
:
import { App } from 'vue'
// 自动引入directive目录下的所有自定义指令
const directiveFiles = require.context('.', true, /\\.ts$/)
const directives = directiveFiles
.keys()
.filter((key) => key !== './index.ts')
.map((key) => directiveFiles(key).default)
const directive = {
install: (app: App<Element>) => {
directives.forEach((directive) => directive(app))
}
}
export default directive
在main.ts
中use一下:
import directive from './directive'
app.use(directive)
这样,我们的全屏指令v-fullscreen
就可以使用了,现在我们去定义一个app模块,存放一下控制全屏的变量。在src/store/modules
目录下新建app.ts
:
import { Module } from 'vuex'
import { AppStateProps, RootStateProps } from '../typings'
const appModule: Module<AppStateProps, RootStateProps> = {
namespaced: true,
state: {
fullscreen: Boolean(document.fullscreenElement) || false
},
mutations: {
SET_FULLSCREEN(state, status: boolean) {
state.fullscreen = status
}
}
}
export default appModule
由于我们之前已经实现过模块自顶导入了,所以现在我们就可以去Layout中使用v-fullscreen
了:
<template>
<div v-fullscreen="fullscreen" class="wrapper">
<!-- 中间的内容省略 -->
</div>
</template>
<script>
/* import部分省略 */
export default defineComponent({
name: 'Index',
/* 其他内容省略 */
setup() {
const store = useStore()
const fullscreen = computed({
get: () => store.state.appModule.fullscreen,
set: (value) => store.commit(Consts.MutationKey.SET_FULLSCREEN, value)
})
return {
fullscreen
}
}
})
</script>
至此,我们的全屏指令就完成了,想要修改全屏状态的时候,只需要调用app模块中名叫的SET_FULLSCREEN
的mutation方法就可以了,也就是fullscreen.value = !fullscreen.value
。
标签栏与页面刷新将会在下一章中讲解,敬请关注!
下一篇预告:vue3.0+ts+element-plus多页签应用模板:头部工具栏(中)
以上是关于vue3.0+ts+element-plus多页签应用模板:头部工具栏(上)的主要内容,如果未能解决你的问题,请参考以下文章
vue3.0+ts+element-plus多页签应用模板:头部工具栏(中)
vue3.0+ts+element-plus多页签应用模板:侧边导航菜单(上)
vue3.0+ts+element-plus多页签应用模板:多级路由缓存
vue3.0+ts+element-plus多页签应用模板:如何优雅地使用Svg图标