Home

Awesome

vue3-tab-demo

GitHub Workflow Status (branch) thanks

License NaiveUI Version Vue Version Vite Version

简介

🗂️ 一个基于 Vite5 + Vue3 + Naive UI + Pinia + TS + ESLint(v9) + Unplugin + Husky 的 Tab 切换选项卡演示项目,其内部抽象出了一个比较贴近实战项目管理系统的业务场景,虽不涉及特别复杂的业务逻辑但也不失灵活,旨在更好地理解和展示如何使用 Tab 标签页组件,项目基于原子化 UnoCSS 框架配置主题,还自带一个模块化的组件开发环境,使页面组件、路由组件、状态管理和样式等模块可以根据 Modules 目录进行解耦,它是一个开箱即用的解决方案,也适合作为快速开发中后台前端,可用于学习和参考

🌈 Live Demo 在线体验

🌱 技术栈

🎉 Tab 核心特性

前置条件

安装和运行

pnpm i
pnpm dev

项目示例图

<img width="1680" alt="image" src="https://github.com/pdsuwwz/vue3-tab-demo/assets/19891724/90af35bb-af44-4d38-8545-67093535f06b"> <img width="1680" alt="image" src="https://github.com/pdsuwwz/vue3-tab-demo/assets/19891724/81dc718e-86c9-4abd-adcd-aed9d874577f"> <img width="1680" alt="image" src="https://github.com/pdsuwwz/vue3-tab-demo/assets/19891724/c3a7eede-1969-443b-a145-0f6a4b0c47d9"> <img width="1680" alt="image" src="https://github.com/pdsuwwz/vue3-tab-demo/assets/19891724/9ec11946-eccc-4162-b0ab-28a7ef32e27b"> <img width="1680" alt="image" src="https://github.com/pdsuwwz/vue3-tab-demo/assets/19891724/f1453c56-780c-4afd-a961-ed82e87e2298">

🧪 使用示例

const router = useTabRouter()
router.push({
  name: 'Xxxxxx',
  params: {
    datasetId: row.xxxId
  }
}, `自定义名称-${row.xxxId}`)
const router = useTabRouter()
router.pushMultiple(
  // 路由一级动态ID
  'xxxxprojectId',
  [
    {
      to: {
        name: 'Xxxx1',
        params: {
          datasetId: row.id
        },
        query: {
          query1: '123456'
        }
      },
      tabName: '自定义名称1'
    },
    {
      to: {
        name: 'Xxxx2',
      },
      tabName: '自定义名称2'
    },
  ]
)

或直接使用全路径:

// 路由一级动态ID
const prefixKey = route.params.projectId // 'xxxxprojectId'
router.pushMultiple(
  prefixKey,
  [
    `/group-project/${prefixKey}/dashboard/monitor`,
    `/group-project/${prefixKey}/work-platform`,
    `/group-project/${prefixKey}/work-platform/nested-level/level-1-2`,
  ]
)

🛠️ 缓存空间设计

由于每个 WorkTab 代表一个路由页面,所以在设计初期就已经将以下核心概念引入到路由中,以方便更好的理解组件的设计理念:

<details> <summary>路由 Layout 布局配置</summary><br>

Tab 组件本身已经解耦了 TabContent 区域和 TabsController 区域,所以只需要将两者简单结合封装即可完成路由布局的配置

本示例项目主要到涉及两个 Tab 路由布局, 感兴趣的可以直接看源码: 路由布局组件1, 路由布局组件2

为确保 Tab 和路由不会有较强的耦合关系,缓存空间 Key 则以可插拔的形式绑定到路由元信息 meta 中,形如以下路由配置代码:

// src/router/frontend/test-routes.ts
export const testRoutesExample = {
  path: 'example-component',
  name: 'ExampleComponentRoot',
  component: LayoutWork, // 配置 Tab 路由布局
  redirect: {
    name: 'ExampleComponentBasic'
  },
  children: [
    {
      path: 'basic',
      name: 'ExampleComponentBasic',
      meta: {
        title: '组件示例-基础组件',
        cacheSpaceKey: CacheSpaceKeys.exampleComponent // 配置缓存空间 Key
      },
      component: () => import('@/modules/ExampleComponent/pages/basic.vue')
    },
    {
      path: 'form',
      name: 'ExampleComponentForm',
      meta: {
        title: '组件示例-表单',
        cacheSpaceKey: CacheSpaceKeys.exampleComponent // 配置缓存空间 Key
      },
      component: () => import('@/modules/ExampleComponent/pages/form.vue')
    },
    // ...
  ]
}

这样也就能确保具有相同缓存空间 Key 的路由(WorkTab)能够被归集到同一个缓存空间 CacheSpace

<br></details>

<details> <summary>缓存空间与 Tab 的缓存</summary><br>

为尽可能地保证使用者无感知的使用体验并避免重复编码,这里通过监听 Vue Router 中 route.fullPath 的变化来实现自动触发 Tab 缓存的添加、切换等逻辑。具体的实现细节可以看源码

为了满足同时打开一个或多个自定义命名 Tab 的需求,并克服 Vue Router 自身的限制,项目中封装了 useTabRouter Hook 方法,用于替代原生的 useRouter 中的 pushreplace 方法(也可以根据需要进行扩展) 在 useTabRouter 内部,采用了单例模式来处理每个Tab的自定义命名。感兴趣的可以深究对应源码

其核心就是利用 Vue Router 的 API 解决缓存空间的创建时机、缓存空间于 Tab 的关联以及 Tab 的自定义命名等问题。

以下是 Tab 缓存空间的核心结构设计:

export interface CacheSpace {
  cacheSpaceKey: string
  tabs: Array<WorkTab>
  activeTabKey: string | null
}
Map<cacheSpaceKey, CacheSpace>
export interface WorkTab {
  // 取自 meta.title 的 tab 标题名称
  label: string
  // 自定义 tab 标题名称
  customLabel: string
  tabKey: string
  link: string
  routeName: RouteRecordName
}

<br></details>

💡 注意事项

说明

License

MIT License | Copyright © 2023-PRESENT Wisdom