效果图


 

一、创建目录tagView文件里index.vue文件


 1.html部分

 <div class="tag-view">
    <el-tabs
      v-model="tabActive"
      type="card"
      ref="tabsRef"
      class="tag-view-content user-none"
      :class="{ ['tag-view-content-' + layout.tabsBarStyle]: true }"
      @tab-change="handleTabChange"
      @tab-remove="handleTabsDelete"
    >
      <el-tab-pane
        v-for="item in tabsViewList"
        :key="item.fullPath"
        :name="item.fullPath"
        :closable="!item.meta.noClosable"
      >
        <template #label>
          <div
            @contextmenu.prevent="openMenu($event, item.fullPath)"
            style="display: flex; align-items: center; justify-content: center"
          >
            <el-icon v-if="layout.isTagsviewIcon">
              <SvgIcon :name="item.meta.icon" />
            </el-icon>
            &nbsp;
            <span>{{ item.meta.title }}</span>
          </div>
        </template>
      </el-tab-pane>
    </el-tabs>
    //右侧更多菜单
    <TagMenu
      :list="openMenuList"
      :tagViewLenght="tabsViewList.length"
      @handleClick="handleOption"
    />
    //右键下拉菜单
    <Contextmenu
      :list="openMenuList"
      :tagViewLenght="tabsViewList.length"
      :="contextmenuParmas"
      v-model:visible="visible"
      @handleClick="handleOption"
    />
  </div>
1.1 TagMenu部分
<template>
  <el-dropdown
    placement="bottom-end"
    popper-class="tagview-more-dropdown"
    @visible-change="handleVisibleChange"
    @command="handleCommand"
  >
    <span
      class="tagview-contents-more"
      :class="{ 'tagview-contents-more-active': active }"
    >
      <span class="tagview-contents-more-icon">
        <i class="box box-t"></i>
        <i class="box box-b"></i>
      </span>
    </span>

    <template #dropdown>
      <el-dropdown-menu>
        <template v-for="(item, index) in list" :key="item.name">
          <el-dropdown-item
            :command="index"
            :disabled="index === 1 && tagViewLenght === 1"
          >
            <SvgIcon :showDefault="false" size="16px" :name="item.icon" />
            {{ item.name }}
          </el-dropdown-item>
        </template>
      </el-dropdown-menu>
    </template>
  </el-dropdown>
</template>

<script setup lang="ts">
import { ref } from 'vue'
withDefaults(
  defineProps<{
    list: { icon: string; name: string }[]
    tagViewLenght: number
  }>(),
  {},
)
const $emit = defineEmits(['handleClick'])
const active = ref(false)
const handleVisibleChange = (type: boolean) => (active.value = type) //菜单下拉框事件
const handleCommand = (type: boolean) => $emit('handleClick', type, false) //点击某一项执行的方法
</script>

 1.2 Contextmenu部分
 
<template>
<ul
    v-if="visible"
    class="contextmenu el-dropdown-menu"
    :style="{ left: left + 'px', top: top + 'px' }"
  >
    <li
      class="el-dropdown-menu__item user-none"
      v-for="(item, index) in list"
      @click="$emit('handleClick', index, true)"
      :key="item.icon"
      v-show="index === 1 && tagViewLenght === 1 ? false : true"
    >
      <SvgIcon :showDefault="false" size="16" :name="item.icon" />
      {{ item.name }}
    </li>
  </ul>
</template>

<script setup lang="ts">
import { watch } from 'vue'
const $props = withDefaults(
  defineProps<{
    list: { icon: string; name: string }[]
    tagViewLenght: number
    visible: boolean
    left: number
    top: number
  }>(),
  {
    visible: false,
  },
)
const $emit = defineEmits(['update:visible', 'handleClick'])
watch(
  () => $props.visible,
  (newValue) => {
    if (newValue) {
      document.body.addEventListener('click', closeMenu)
    } else {
      document.body.removeEventListener('click', closeMenu)
    }
  },
)
//关闭右键事情Tabs
const closeMenu = () => $emit('update:visible', false)

2.js部分 

import { ref, computed, watch, onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { storeToRefs } from 'pinia'
import { useSortable } from '@/utils/sortablejs'
import useSettingStore from '@/store/modules/setting/index'
import TagMenu from './tagMenu.vue'
import Contextmenu from './contextmenu.vue'
import useTabsStore from '~/store/modules/tabs/index'
import useUserStore from '~/store/modules/user/index'
const Router = useRouter()
const Route = useRoute()
const SettingStore = useSettingStore()
const TabsStore = useTabsStore()
const UserStore = useUserStore()
const { refsh, layout } = storeToRefs(SettingStore) // 需要pinia 转换为响应式数据
const { tabsViewList } = storeToRefs(TabsStore)
const tabsRef = ref(null) //tabs实例对象
const sortableInstance = ref(null) //拖拽的实例对象
// 存储x轴y轴
const contextmenuParmas = ref({
  left: 0,
  top: 0,
})
const currentPath = ref('') // 当前存起路径
const visible = ref(false) //鼠标右键Tabs菜单
const tabActive = ref('')
//双击右键事件Tabs
const openMenu = ({ x, y }: MouseEvent, fullPath: string) => {
  contextmenuParmas.value = {
    left: x, // X轴
    top: y, // Y轴
  }
  visible.value = true //打开菜单
  currentPath.value = fullPath // 路径存起
}
// 初始化tabs导航栏标签
const initTabs = () => {
  // 菜单栏  item.meta?.isAffix 是否固定的标签栏(多数情况默认是首页后续根据isAffix来配置)
  UserStore.flatMenuListGet.forEach((item) => {
    if (item.meta?.isAffix) {
      TabsStore.addTabsView({
        fullPath: item.path,
        meta: item.meta,
        name: item.name,
      })
    }
  })
}
initTabs() //初始化执行Tabs
onMounted(() => {
  tabsViewDrop() //初始化执行拖拽功能
})
// 监听路由完整路径
watch(
  () => Route.fullPath,
  () => {
    // 如果tabHidden为true不执行
    if (Route.meta.tabHidden) return
    let { meta, name, fullPath } = Route
    tabActive.value = fullPath //路径赋值
    TabsStore.addTabsView({
      fullPath,
      meta,
      name,
    })
  },
  {
    immediate: true,
  },
)
// 跳转方法
const handleTabChange = (fullPath: string) => Router.push(fullPath)
// 移除事件
const handleTabsDelete = (fullPath: string) => {
  TabsStore.removeTabsView(
    fullPath,
    Route.fullPath === fullPath,
    handleTabChange,
  )
}
//所有方法
const handleOption = (index: number, isHas: boolean) => {
  let fullPath = isHas ? currentPath.value : Route.fullPath //当前路由参数
  switch (index) {
    case 0:
      handleTabChange(fullPath)
      refsh.value = !refsh.value //刷新事件
      break
    case 1:
      TabsStore.closeOtherTabsView(fullPath)
      handleTabChange(fullPath) //跳转到对应的路由
      break
    case 2:
      TabsStore.closeTabsViewOnSide(fullPath, 'left')
      handleTabChange(fullPath) //跳转到对应的路由
      break
    case 3:
      TabsStore.closeTabsViewOnSide(fullPath, 'right')
      handleTabChange(fullPath) //跳转到对应的路由
      break
    case 4:
      TabsStore.closeAllTabsView() //关闭全部
      handleTabChange('/home')
      break
  }
}
//双击右键值数据
const openMenuList = ref<{ icon: string; name: string }[]>([
  {
    icon: 'Refresh',
    name: '刷新',
  },
  {
    icon: 'Close',
    name: '关闭其他',
  },
  {
    icon: 'Back',
    name: '关闭左侧',
  },
  {
    icon: 'Right',
    name: '关闭右侧',
  },
  {
    icon: 'Close',
    name: '关闭全部',
  },
])

二、创建pinia仓库


 


 index.ts文件

import { defineStore } from 'pinia'
import type { TagsViewType, TabsStore } from './type'

const useTabsStore = defineStore('useTabsStore', {
  state(): TabsStore {
    return {
      tabsViewList: [], //tabsView数据存储
    }
  },
  actions: {
    // 添加导航标签方法
    addTabsView(tabItem: TagsViewType) {
      let row = this.tabsViewList.find((v) => v.fullPath === tabItem.fullPath)
      if (!row) this.tabsViewList.push(tabItem)
    },
    // 删除导航标签方法
    removeTabsView(
      fullPath: string,
      isCurrent: boolean,
      callback: (val: string) => void,
    ) {
      //如果不符合直接就删除
      if (isCurrent) {
        this.tabsViewList.forEach((item, index) => {
          if (item.fullPath === fullPath) {
            let navIndex =
              this.tabsViewList[index + 1] || this.tabsViewList[index - 1]
            if (navIndex) callback(navIndex.fullPath) //跳转到对应的路由
          }
        })
      }
      this.tabsViewList = this.tabsViewList.filter(
        (v) => v.fullPath !== fullPath,
      ) //删除面包屑每一项
    },
    // 关闭其他
    closeOtherTabsView(fullPath: string) {
      // noClosable是true和当前路由留下
      this.tabsViewList = this.tabsViewList.filter((item) => {
        return item.meta.noClosable || item.fullPath === fullPath
      })
    },
    // 关闭左侧Or右侧
    closeTabsViewOnSide(fullPath: string, type: 'left' | 'right') {
      // 找到当前index
      let currentIndex = this.tabsViewList.findIndex(
        (item) => item.fullPath === fullPath,
      )

      // 判断一下必须存在才会执行
      if (currentIndex !== -1) {
        let range =
          type === 'left'
            ? [0, currentIndex]
            : [currentIndex + 1, this.tabsViewList.length]

        // 是左侧还是右侧 item.meta.noClosable固定留下的
        this.tabsViewList = this.tabsViewList.filter((item, index) => {
          return index < range[0] || index >= range[1] || item.meta.noClosable
        })
      }
    },
    //关闭全部
    closeAllTabsView() {
      this.tabsViewList = this.tabsViewList.filter((item) => {
        return item.meta.noClosable
      })
    },
    //从新设置tabsViewList
    setTabsViewList(list: TagsViewType[]) {
      this.tabsViewList = list
    },
    // 设置标签栏标题
    setTabsViewTitle(fullPath: string, title: string) {
      this.tabsViewList.forEach((item) => {
        if (item.fullPath === fullPath) item.meta.title = title
      })
    },
  },
  getters: {},
})
export default useTabsStore


 

Logo

前往低代码交流专区

更多推荐