效果图


一、创建目录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> |
| |
| <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) |
| } |
| }, |
| ) |
| |
| 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) |
| const { tabsViewList } = storeToRefs(TabsStore) |
| const tabsRef = ref(null) |
| const sortableInstance = ref(null) |
| |
| const contextmenuParmas = ref({ |
| left: 0, |
| top: 0, |
| }) |
| const currentPath = ref('') |
| const visible = ref(false) |
| const tabActive = ref('') |
| |
| const openMenu = ({ x, y }: MouseEvent, fullPath: string) => { |
| contextmenuParmas.value = { |
| left: x, |
| top: y, |
| } |
| visible.value = true |
| currentPath.value = fullPath |
| } |
| |
| const initTabs = () => { |
| |
| UserStore.flatMenuListGet.forEach((item) => { |
| if (item.meta?.isAffix) { |
| TabsStore.addTabsView({ |
| fullPath: item.path, |
| meta: item.meta, |
| name: item.name, |
| }) |
| } |
| }) |
| } |
| initTabs() |
| onMounted(() => { |
| tabsViewDrop() |
| }) |
| |
| watch( |
| () => Route.fullPath, |
| () => { |
| |
| 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: [], |
| } |
| }, |
| 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) { |
| |
| this.tabsViewList = this.tabsViewList.filter((item) => { |
| return item.meta.noClosable || item.fullPath === fullPath |
| }) |
| }, |
| |
| closeTabsViewOnSide(fullPath: string, type: 'left' | 'right') { |
| |
| let currentIndex = this.tabsViewList.findIndex( |
| (item) => item.fullPath === fullPath, |
| ) |
| |
| |
| if (currentIndex !== -1) { |
| let range = |
| type === 'left' |
| ? [0, currentIndex] |
| : [currentIndex + 1, this.tabsViewList.length] |
| |
| |
| 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 |
| }) |
| }, |
| |
| 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 |
复制