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多页签应用模板:头部工具栏(下)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

W先生-SirW

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值