Tab 标签页下拉菜单相关操作逻辑处理

右击 Tab 标签页显示下拉菜单项,例如:刷新当前页、关闭当前页、关闭左侧、关闭右侧、关闭其他、关闭所有等操作。

useTabs 实现

  1. 对外暴露方法
// src/hooks/web/useTabs.ts
enum TableActionEnum { REFRESH, CLOSE_ALL, CLOSE_LEFT, CLOSE_RIGHT, CLOSE_OTHER, CLOSE_CURRENT, CLOSE }

export function useTabs(_router?: Router) {

  return {
    refreshPage: () => handleTabAction(TableActionEnum.REFRESH),
    closeAll: () => handleTabAction(TableActionEnum.CLOSE_ALL),
    closeLeft: () => handleTabAction(TableActionEnum.CLOSE_LEFT),
    closeRight: () => handleTabAction(TableActionEnum.CLOSE_RIGHT),
    closeOther: () => handleTabAction(TableActionEnum.CLOSE_OTHER),
    closeCurrent: () => handleTabAction(TableActionEnum.CLOSE_CURRENT),
    close: (tab?: RouteLocationNormalized) => handleTabAction(TableActionEnum.CLOSE)
  }
}
  1. handleTabAction 方法处理 tab 点击事件
// src/hooks/web/useTabs.ts
import { useRouter } from 'vue-router'
import { useMultipleTabStore } from '/@/store/modules/multipleTab'

export function useTabs(_router?: Router) {
  const tabStore = useMultipleTabStore()
  
  const router = _router || useRouter()
  const { currentRoute } = router

  // 当前 tab 栏标签
  function getCurrentTab() {
    const route = unref(currentRoute)
    return tabStore.getTabList.find((item) => item.fullPath === route.fullPath)!
  }

  async function handleTabAction(action: TableActionEnum, tab?: RouteLocationNormalized) {
    const currentTab = getCurrentTab()
    switch (action) {
      case TableActionEnum.REFRESH:
        await tabStore.refreshPage(router)
        break
      case TableActionEnum.CLOSE_ALL:
        await tabStore.closeAllTab(router)
        break
      case TableActionEnum.CLOSE_LEFT:
        await tabStore.closeLeftTabs(currentTab, router)
        break
      case TableActionEnum.CLOSE_RIGHT:
        await tabStore.closeRightTabs(currentTab, router)
        break
      case TableActionEnum.CLOSE_OTHER:
        await tabStore.closeOtherTabs(currentTab, router)
        break
      case TableActionEnum.CLOSE_CURRENT:
      case TableActionEnum.CLOSE:
        await tabStore.closeTab(tab || currentTab, router)
        break
    }
  }
}
  1. useMultipleTabStore 方法

声明一个 store 实例 useMultipleTabStore,用来存储打开标签页。

// src/store/modules/multipleTab.ts
import { defineStore } from 'pinia';

export const useMultipleTabStore = defineStore({
  id: 'app-multiple-tab',
  state: () => ({}),
  getters: {},
  actions: {}})
  1. 定义 State 和 Getter
// src/store/modules/multipleTab.ts
import { defineStore } from 'pinia';
import { Persistent } from '/@/utils/cache/persistent';

export const useMultipleTabStore = defineStore({
  id: 'app-multiple-tab',
  state: (): MultipleTabState => ({
    cacheTabList: new Set(),
    // 优先加载本地缓存
    tabList: cacheTab ? Persistent.getLocal(MULTIPLE_TABS_KEY) || [] : [],
    lastDragEndIndex: 0,
  }),
  getters: {
    // 获取路由列表标签
    getTabList(): RouteLocationNormalized[] {
      return this.tabList
    },
    // 获取带缓存标签路由名称列表
    getCachedTabList(): string[] {
      return Array.from(this.cacheTabList)
    },
    // 获取最后一次拖动标签的索引
    getLastDragEndIndex(): number {
      return this.lastDragEndIndex
    }
  },
})
  1. store 中多标签页处理方法
  • 打开标签页
async addTab(route: RouteLocationNormalized) {
  const { path, name, fullPath, params query, meta } = getRawRoute(route)
  // 错误处理页面 登录 重定向 等页面
  if (
    path === PageEnum.ERROR_PAGE ||
    path === PageEnum.BASE_LOGIN ||
    !name ||
    [REDIRECT_ROUTE.name, PAGE_NOT_FOUND_ROUTE.name].includes(name as string)
  ) {
    return;
  }

  let updateIndex = -1;
  // 标签已存在,不在重复添加标签
  const tabHasExits = this.tabList.some((tab, index) => {
    updateIndex = index
    return (tab.fullPath || tab.path) === (fullPath || path)
  })

  // 更新标签
  if (tabHasExits) {
    const curTab = toRaw(this.tabList)[updateIndex]
    if (!curTab) {
      return
    }
    curTab.params = params || curTab.params
    curTab.query = query || curTab.query
    curTab.fullPath = fullPath || curTab.fullPath
    // 替换原有的标签页路由记录
    this.tabList.splice(updateIndex, 1, curTab)
  } else {
    // 添加标签: 获取动态路由打开数,超过 0 即代表需要控制打开数
    const dynamicLevel = meta?.dynamicLevel ?? -1
    if (dynamicLevel > 0) {
      // 如果动态路由层级大于 0 了,那么就要限制该路由的打开数限制了
      // 首先获取到真实的路由,使用配置方式减少计算开销.
      // const realName: string = path.match(/(\S*)\//)![1];
      const realPath = meta?.realPath ?? '';
      // 获取到已经打开的动态路由数, 判断是否大于某一个值
      if (
        this.tabList.filter((e) => e.meta?.realPath ?? '' === realPath).length >= dynamicLevel
      ) {
        // 关闭第一个
        const index = this.tabList.findIndex((item) => item.meta.realPath === realPath);
        index !== -1 && this.tabList.splice(index, 1);
      }
    }
  }
  this.updateCacheTab()
  // 使用本地存储持久化
  cacheTab && Persistent.setLocal(MULTIPLE_TABS_KEY, this.tabList)
}
  • 更新缓存
// 根据当前打开的选项卡更新缓存
async updateCacheTab() {
  const cacheMap: Set<string> = new Set()

  for (const tab of this.tabList) {
    // 缓存
    const needCache = !item.meta?.ignoreKeepAlive
    if (!needCache) {
      continue
    }
    const name = item.name as string
    cacheMap.add(name)
  }
  // 存储路由记录的 Set 集合
  this.cacheTabList = cacheMap
}
  • 清空缓存
clearCacheTabs(): void {
  this.cacheTabList = new Set()
}
  • 关闭 Tab 操作

组件提供了很多关闭方法(所有、当前、左侧、右侧等)。关闭操作本质上就是将对应标签页路由信息从 tabList 中删除。

closeAllTab

关闭所有非 affix 的 tab,并跳转到首页。

async closeAllTab(router: Router) {
  this.tabList = this.tabList.filter((item) => item?.meta?.affix ?? false)
  // 清空缓存列表
  this.clearCacheTabs()
  // 跳转首页
  this.goToPage(router)
}
goToPage (router: Router) {}

closeLeftTabs

关闭指定路由左侧(非固定)标签页。

async closeLeftTabs(route: RouteLocationNormalized, router: Router) {
  // 获取标签页索引值
  const index = this.tabList.findIndex((item) => item.path === route.path)

  if (index > 0) {
    const leftTabs = this.tabList.slice(0, index)
    const pathList: string[] = []
    for (const item of leftTabs) {
      const affix = item?.meta?.affix ?? false
      if (!affix) {
        pathList.push(item.fullPath)
      }
    }
    // 批量关闭列表路由
    this.bulkCloseTabs(pathList)
  }
  this.updateCacheTab()
  this.handleGotoPage(router)
}

// 批量关闭标签页
async bulkCloseTabs(pathList: string[]) {
  this.tabList = this.tabList.filter(item => !pathList.includes(item.fullPath))
}

// 路由跳转
async handleGotoPage(router: Router) {
  const go = useGo(router)
  go(unref(router.currentRoute).path, true)
}

closeRightTabs

与 closeLeftTabs 逻辑差不多,关闭指定路由右侧(非固定)标签页。

async closeRightTabs(route: RouteLocationNormalized, router: Router) {
  const index = this.tabList.findIndex((item) => item.fullPath === route.fullPath)

  if (index >= 0 && index < this.tabList.length - 1) {
    const rightTabs = this.tabList.slice(index + 1, this.tabList.length)

    const pathList: string[] = [];
    for (const item of rightTabs) {
      const affix = item?.meta?.affix ?? false;
      if (!affix) {
        pathList.push(item.fullPath);
      }
    }
    this.bulkCloseTabs(pathList);
  }
  this.updateCacheTab();
  handleGotoPage(router);
}

closeOtherTabs

关闭指定路由之外的其他(非固定标签页)

async closeOtherTabs(route: RouteLocationNormalized, router: Router) {
  const closePathList = this.tabList.map((item) => item.fullPath)

  const pathList: string[] = [];
  for (const path of closePathList) {
    // 指定路由之外(非固定)标签页都会被删除
    if (path !== route.fullPath) {
      const closeItem = this.tabList.find(item => item.path === path)
      if (!closeItem) {
        continue;
      }

      const affix = closeItem?.meta?.affix ?? false;
      if (!affix) {
        pathList.push(closeItem.fullPath);
      }
    }
  }
  this.bulkCloseTabs(pathList);
  this.updateCacheTab();
  handleGotoPage(router);
}

closeTab

关闭指定标签页。

import { useUserStore } from '/@/store/modules/user'

async closeTab(tab: RouteLocationNormalized, router: Router) {
  // 内部方法,关闭指定路由(非固定)标签页
  const close = (route: RouteLocationNormalized) => {
    const { fullPath, meta: { affix } = {} } = route
    if (affix) {
      return
    }
    const index = this.tabList.findIndex(item => item.fullPath === fullPath)
    index !== -1 && this.tabList.splice(index, 1)
  }

  const { currentRoute, replace } = router
  const { path } = unref(currentRoute)
  // 判断关闭标签是否当前激活状态
  if(path !== tab.path) {
    // 直接关闭退出
    close(tab)
    return
  }

  // 关闭当前激活状态的标签页
  let toTarget: RouteLocationRaw = {}
  const index = this.tabList.findIndex(item => item.path === path)

  // 关闭最左侧的标签
  if (index === 0) {
    // 只有一个标签,跳到主页,否则跳到右侧标签
    if (this.tabList.length === 1) {
      const userStore = useUserStore()
      toTarget = userStore.getUserInfo.homePath || PageEnum.BASE_HOME
    } else {
      // 跳到右侧标签
      const page = this.tabList[index + 1]
      toTarget = getToTarget(page)
    }
  } else {
    // 非最左侧标签,关闭后跳转到左侧标签页
    const page = this.tabList[index - 1]
    toTarget = getToTarget(page)
  }
  close(currentRoute.value)
  // 导航不会留下历史记录
  await replace(toTarget)
}

// 路由地址格式化处理
const getToTarget = (tabItem: ) => {
  const { params, path, query } = tabItem
  return { path, params: params || {}, query: query || {} }
}

refreshPage

刷新 tab 标签。

async refreshPage(router: Router) {
  const { currentRoute } = router
  const route = unref(currentRoute)

  const findTab = this.getCachedTabList.find((item) => item === route.name)
  if (findTab) {
    this.cacheTabList.delete(findTab)
  }
  const redo = useRedo(router)
  await redo()
},
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值