美文网首页
Vue3 + Element Plus 实现动态标签页及右键菜单

Vue3 + Element Plus 实现动态标签页及右键菜单

作者: YanaDH | 来源:发表于2022-08-29 10:43 被阅读0次

    1 前言

    1.1 目的

    Tabs 动态标签页实现右键菜单【关闭当前标签页】、【关闭左侧标签页】、【关闭右侧标签页】、【关闭其他标签页】、【关闭全部标签页】功能


    2022081101.png

    1.2 普通右键菜单

    网上使用比较多的是v-contextmenu插件实现右键菜单,但该插件对于v-for循环生成的元素失效,插件内部右键菜单显示执行的是emit('show')未传入当前元素节点(可能后续会修复),且样式需要自行修改

    1.3 本文右键菜单方式

    本文使用element-plus自带的el-dropdown实现右键菜单

    2 生成动态标签页

    2.1 准备变量容器

    <script setup lang="ts">
    import { ref } from 'vue'
    interface TabType {
      title: string //标签页显示名称
      componentName: string //动态组件名
      data: any //动态组件传参
    }
    interface TabListType extends TabType {
      name: string //标签页唯一标识,添加标签页时根据 componentName 自动生成
    }
    const tabList = ref<TabListType[]>([]) //存放标签页数组
    const tabValue = ref('home') //存放当前激活标签页,默认激活首页
    </script>
    

    2.2 构造标签页

    • 可动态添加标签页
    • 除【首页】外,可动态移除标签页
    <template>
      <el-tabs v-model="tabValue" type="card" @tab-remove="removeTab">
        <el-tab-pane label="首页" name="home">
          <Home />
        </el-tab-pane>
        <el-tab-pane v-for="item in tabList" :name="item.name" :key="item.name" closable>
          <component :is="item.componentName" v-bind="item.data">
          </component>
        </el-tab-pane>
      </el-tabs>
    </template>
    

    2.3 动态添加标签页

    const addTab = (tab: TabType) => {
       //保证相同组件路径标签页 name 标识唯一
      const name = `${tab.componentName}_${Date.now()}`
      tabList.value.push({
        ...tab,
        name
      })
      tabValue.value = name
    }
    
    addTab({
      title: '标签1',
      componentName: 'tag1',
      data: {
        test: '这是测试数据'
      }
    })
    

    2.4 动态移除标签页

    const removeTab = (targetName: string) => {
      const index = tabList.value.findIndex((item) => item.name === targetName)
      tabList.value.splice(index, 1)
      //当前激活标签页与触发右键菜单标签页是同一页
      if (targetName === tabValue.value) {
        //当前激活标签页是标签页数组的第一个,则将激活标签页设置为 home
        //当前激活标签页不是标签页数组的第一个,则将激活标签页设置为当前激活标签页的前一页
        tabValue.value = index === 0 ? 'home' : tabList.value[index - 1].name
      }
    }
    
    removeTab('tag1')
    

    3 生成右键菜单

    3.1 扩展标签页

    <template>
      <el-tabs v-model="tabValue" type="card" @tab-remove="removeTab">
        <el-tab-pane label="首页" name="home">
          <Home />
        </el-tab-pane>
        <el-tab-pane v-for="item in tabList" :name="item.name" :key="item.name" closable>
          <!-- 右键菜单开始:自定义标签页显示名称,保证每个标签页都能实现右键菜单 -->
          <template #label>
            <el-dropdown
              trigger="contextmenu"
              :id="item.name"
              @visible-change="handleChange($event, item.name)"
              ref="dropdownRef"
            >
              <span :class="tabValue === item.name ? 'label' : ''">{{ item.title }}</span>
              <template #dropdown>
                <el-dropdown-menu>
                  <el-dropdown-item @click="removeTab(item.name)">
                    <el-icon><Close /></el-icon>关闭当前标签页
                  </el-dropdown-item>
                  <el-dropdown-item
                    @click="removeTab(item.name, 'left')"
                    v-if="show(item.name, 'left')"
                  >
                    <el-icon><DArrowLeft /></el-icon>关闭左侧标签页
                  </el-dropdown-item>
                  <el-dropdown-item
                    @click="removeTab(item.name, 'right')"
                    v-if="show(item.name, 'right')"
                  >
                    <el-icon><DArrowRight /></el-icon>关闭右侧标签页
                  </el-dropdown-item>
                  <el-dropdown-item
                    @click="removeTab(item.name, 'other')"
                    v-if="tabList.length > 1"
                  >
                    <el-icon><Operation /></el-icon>关闭其他标签页
                  </el-dropdown-item>
                  <el-dropdown-item @click="removeTab(item.name, 'all')">
                    <el-icon><Minus /></el-icon>关闭全部标签页
                  </el-dropdown-item>
                </el-dropdown-menu>
              </template>
            </el-dropdown>
          </template>
          <!-- 右键菜单结束 -->
          <component :is="item.componentName" v-bind="item.data">
          </component>
        </el-tab-pane>
      </el-tabs>
    </template>
    

    3.2 增加 show 方法

    • 触发右键菜单标签页为第一个时,不展示【关闭左侧标签页】
    • 触发右键菜单标签页为最后一个时,不展示【关闭右侧标签页】
    const show = (name: string, type: string) => {
      const index = tabList.value.findIndex((item) => name === item.name)
      return type === 'left' ? index !== 0 : index !== tabList.value.length - 1
    }
    

    3.3 扩展 removeTab 方法

    const removeTab = (targetName: string, type?: string) => {
      const index = tabList.value.findIndex((item) => item.name === targetName) //查找触发右键菜单所在标签页index
      const currentIndex = tabList.value.findIndex((item) => item.name === tabValue.value) //查找当前激活标签页index,存在当前激活标签页与触发右键菜单标签页不是同一个的情况
      switch (type) {
        case 'all': //关闭全部标签页
          tabList.value = [] //清空除【首页】外所有标签页
          tabValue.value = 'home' //修改标签激活页
          break
        case 'other': //关闭其他标签页
          tabList.value = [tabList.value[index]]
          if (targetName !== tabValue.value) {
            tabValue.value = targetName
          }
          break
        case 'left': //关闭左侧标签页
          tabList.value.splice(0, index)
          if (currentIndex < index) {
            tabValue.value = targetName
          }
          break
        case 'right': //关闭右侧标签页
          tabList.value.splice(index + 1)
          if (currentIndex > index) {
            tabValue.value = targetName
          }
          break
        default: //默认关闭当前标签页
          tabList.value.splice(index, 1)
          //当前激活标签页与触发右键菜单标签页是同一页
          if (targetName === tabValue.value) {
            //当前激活标签页是标签页数组的第一个,则将激活标签页设置为 home
            //当前激活标签页不是标签页数组的第一个,则将激活标签页设置为当前激活标签页的前一页
            tabValue.value = index === 0 ? 'home' : tabList.value[index - 1].name
          }
          break
      }
    }
    

    3.4 解决重复出现菜单问题

    • 当连续在多个标签页触发右键时,会出现多个菜单,解决方案为:在触发右键菜单后,关闭其他右键菜单
    const dropdownRef = ref()
    const handleChange = (visible: boolean, name: string) => {
      if (!visible) return
      dropdownRef.value.forEach((item: { id: string; handleClose: () => void }) => {
        if (item.id === name) return
        item.handleClose()
      })
    }
    

    3.5 解决自定义标签样式问题

    <style lang="scss" scoped>
    .label {
      color: var(--el-color-primary); //激活标签页高亮
    }
    :deep(.el-tabs__item) {
      &:hover {
        span {
          color: var(--el-color-primary); //鼠标移到标签页高亮
        }
      }
      .el-dropdown {
        line-height: inherit; // 统一标签页显示名称行高
      }
    }
    </style>
    

    相关文章

      网友评论

          本文标题:Vue3 + Element Plus 实现动态标签页及右键菜单

          本文链接:https://www.haomeiwen.com/subject/pdvhnrtx.html