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多页签应用模板:头部工具栏(下)
一、标签栏功能分析
我们这个多页签系统模板的灵魂就是这个标签栏组件了。类似于我们经常使用的浏览器的标签栏,它能够容纳所有已经打开的标签页,并进行切换路由、关闭路由、拖拽排序等操作。
我们先来设想一下标签栏应该包括那些功能:
- 显示所有已访问的路由标签
- 点击标签可切换路由
- 标签可拖拽排序
- 标签拥有右键菜单,包括:刷新、关闭、关闭右侧、关闭其他、关闭所有
这些就是我们标签栏组件所要实现的功能了,话不多说,开始行动吧。
二、获取已访问路由信息
既然我们需要展示出所有已经访问的路由标签,我们就需要在每次切换路由的时候,将路由信息添加进一个数组中。于是,我们需要先在vuex中定义这样一个状态。由于我们之前已经在vuex中定义了tag模块(大家忘了的可以回去看一下同系列文章:多级路由缓存),所以这里我们在tag.ts中新增一个viewList
状态和activeView
状态(代码只展示相关部分 ):
import { Module } from 'vuex'
import { RouteRecord, RouteRecordName } from 'vue-router'
import { TagStateProps, RootStateProps, ViewListItemProps } from '../typings'
import router from '@/router'
import Utils from '@/utils'
const clearCache = (state: TagStateProps) => {
// 由于我在编写各个页面的时候,就规定组件名是对应路由名的首字母大写,
// 所以这里的组件名只需要封装一个处理方法,将路由名的首字母大写即可 ↓↓↓
state.cachedList = state.viewList.map((view) => Utils.getBigName(view.name as string))
}
const tagModule: Module<TagStateProps, RootStateProps> = {
namespaced: true,
state: {
activeView: 'workbench',
viewList: [{ name: 'workbench', label: '工作台' }]
},
mutations: {
ADD_VIEW(state, route: RouteRecord) {
const routeName = route.name!
const routeLabel = route.meta!.label as string
// 判断路由是否已经存在
const isViewExist = state.viewList.some((view) => view.name === routeName)
// 路由不存在则将路由加入viewList
if (!isViewExist) {
state.viewList.push({ name: routeName, label: routeLabel })
}
// 将激活路由切换为该路由
state.activeView = routeName
},
REMOVE_VIEW(state, routeName: string | RouteRecordName) {
// 如果传进来的路由是工作台,则不继续进行
if (routeName === 'workbench') return
// 获取该view的索引值
const viewIndex = state.viewList.findIndex((view) => view.name === routeName)
// 删除该路由
state.viewList.splice(viewIndex, 1)
// 如果该路由并未激活,则无需关心后续问题
if (state.activeView !== routeName) {
return
}
if (viewIndex >= state.viewList.length) {
// 如果该路由的索引值不小于删除后的viewList长度,则将激活路由设置为viewList中最后一个路由
state.activeView = state.viewList[state.viewList.length - 1].name
} else {
// 反之,仍将激活路由设置为原索引值对应的路由
state.activeView = state.viewList[viewIndex].name
}
// 清除路由缓存
clearCache(state)
// 跳转路由
router.push({ name: state.activeView })
}
}
}
export default tagModule
之后,我们在router的入口文件中修改beforeEach
:
router.beforeEach((to, from, next) => {
if (!to.meta.noKeepAlive) {
// 添加标签
store.commit('tagModule/ADD_VIEW', to)
// 将路由缓存
store.commit('tagModule/ADD_CACHED_VIEW', to.name)
}
// 路由扁平化
handleKeepAlive(to)
next()
})
到这里,我们就已经可以拿到所有已访问路由的信息了。
三、创建组件
现在让我们来编写一下组件吧(组件样式就不贴了,大家自己觉得咋样好看就咋写):
<template>
<div class="tagsView" v-bind="$attrs">
<div
v-for="view in viewList"
:key="view.name"
class="route-tag"
:class="{ active: view.name === activeView }"
@click="onTagClick(view.name)"
>
<!-- 路由label -->
<span class="tag-text">{{ view.label }}</span>
<!-- 关闭图标 -->
<i
v-if="view.name !== 'workbench'"
class="el-icon el-icon-close tag-close"
@click.stop="onTagClose(view.name)"
/>
</div>
</div>
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue'
import { RouteRecordName, useRouter } from 'vue-router'
import { useStore } from '@/store'
export default defineComponent({
name: 'TagsView',
setup() {
const router = useRouter()
const store = useStore()
const activeView = computed(() => store.state.tagModule.activeView)
const viewList = computed(() => store.state.tagModule.viewList)
const onTagClick = (viewName: RouteRecordName) => {
router.push({ name: viewName })
}
const onTagClose = (viewName: RouteRecordName) => {
store.commit('tagModule/REMOVE_VIEW', viewName)
}
return {
activeView,
viewList,
onTagClick,
onTagClose
}
}
})
</script>
这样,我们的前两个功能(显示已访问路由以及点击切换路由)就实现完了。
四、拖拽排序
参考资料:
中文文档:http://www.itxst.com/vue-draggable/tutorial.html
Github:https://github.com/anish2690/vue-draggable-next
关于vue中的拖拽排序,有一个非常好用的插件,在vue2.x中它叫vue-draggable
,在vue3.x中它叫vue-draggable-next
:
npm i vue-draggable-next --save
安装好后,我们在TagsView中引入这个插件(为节省篇幅,仅贴相关ts代码、html标签以及属性):
<template>
<div class="tagsView" v-bind="$attrs">
<draggable
v-model="viewList"
filter=".no-drag"
animation="300"
ghostClass="tag-ghost"
:move="onTagMove"
>
<transition-group>
<div
class="route-tag"
:class="[{ active: view.name === activeView }, { 'no-drag': view.name === 'workbench' }]"
>
<span class="tag-text">{{ view.label }}</span>
<i class="el-icon el-icon-close tag-close" />
</div>
</transition-group>
</draggable>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, inject, reactive } from 'vue'
import { RouteRecordName, useRouter } from 'vue-router'
import { VueDraggableNext } from 'vue-draggable-next'
import { useStore } from '@/store'
import { ContextMenuItemProps } from '@/components/common/Contextmenu/typings'
import { Reload } from '@/layout/typings'
import Consts from '@/consts'
export default defineComponent({
name: 'TagsView',
components: {
draggable: VueDraggableNext
},
setup() {
const store = useStore()
const activeView = computed(() => store.state.tagModule.activeView)
const viewList = computed({
get: () => store.state.tagModule.viewList,
set: (value) => store.commit(Consts.MutationKey.SET_VIEW_LIST, value)
})
const onTagMove = (e: any) => {
// 如果是工作台标签,则不允许拖拽
if (e.relatedContext.element.name == 'workbench') return false
if (e.draggedContext.element.name == 'workbench') return false
}
return {
activeView,
viewList,
onTagMove
}
}
})
</script>
<style lang="scss">
.tag-ghost {
opacity: 0;
}
</style>
到这里,拖拽排序就写完了,看一下效果图:
余下的右键菜单会在下一篇中详述,敬请期待!
下一篇预告: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图标