Quasar 框架 QTabPanels 与 QTabPanel 组件完全指南:多面板切换、滑动导航与无障碍实践
【免费下载链接】quasarQuasar Framework - Build high-performance VueJS user interfaces in record time项目地址: https://gitcode.com/gh_mirrors/qu/quasar
导读
QTabPanels / QTabPanel 是 Quasar Framework(Vue 组件库)中用于"用更少的窗口空间展示更多信息"的核心面板组件:通过v-model绑定当前激活面板,支持动画过渡、触摸滑动、无限循环、keep-alive 缓存与深色模式。本指南以官方文档 tab-panels.md 为主线,结合 QTabPanels.js、QTabPanel.js 与底层 use-panel.js 源码及测试用例,带你掌握组件的全部配置项、与 QTabs 的组合模式、自定义过渡、滑动交互以及从 v2.25 起提供的 ARIA 无障碍实践。
[!TIP] 本文聚焦 QTabPanels 组件本身。它常与 QTabs(QTabs.js)配合使用,但并非必须——这一点在源码与文档中都被反复强调。
一、组件定位:两个组件,各司其职
QTabPanels 家族由两个组件组成:
| 组件 | 角色 | 源码位置 |
|---|---|---|
QTabPanels | 容器(面板宿主),管理激活状态、动画、滑动、keep-alive | QTabPanels.js |
QTabPanel | 单个面板(内容单元),声明name与可选disable | QTabPanel.js |
从源码看,QTabPanels的实现非常精简——它把全部行为逻辑抽取到了可复用的组合式函数usePanel中(use-panel.js)。同一套usePanel也被用于 QStepper 与 QCarousel 的面板部分,因此你在学习本文内容时掌握的 props 与行为,很大程度可以迁移到这两个组件上。
// QTabPanels.js 核心结构(节选) props: { ...usePanelProps, // modelValue / animated / swipeable / infinite / keepAlive 等 ...useDarkProps // dark }, emits: usePanelEmits // update:modelValue / beforeTransition / transitionQTabPanel则只是一个纯粹的展示单元,渲染为一个<div>,并将默认插槽内容放入其中:
h('div', { class: 'q-tab-panel', role: 'tabpanel', tabindex: 0 }, hSlot(slots.default))核心结论(来自文档警告):不要被 "QTabPanels" 的名字误导——面板不依赖 QTabs,完全可以独立使用,也可以放在页面任意位置,不一定要紧挨着 QTabs。
二、基础用法:独立使用 QTabPanels
即使没有 QTabs,你也可以通过任意方式来切换面板,例如用q-option-group或按钮绑定同一个v-model。以下是最简单的独立用法(对应示例 Basic.vue):
<template> <div class="q-pa-md"> <div class="q-gutter-y-md" style="max-width: 350px"> <q-option-group v-model="panel" inline :options="[ { label: 'Mails', value: 'mails' }, { label: 'Alarms', value: 'alarms' }, { label: 'Movies', value: 'movies' } ]" /> <q-tab-panels v-model="panel" animated class="shadow-2 rounded-borders"> <q-tab-panel name="mails"> <div class="text-h6">Mails</div> Lorem ipsum dolor sit amet consectetur adipisicing elit. </q-tab-panel> <q-tab-panel name="alarms"> <div class="text-h6">Alarms</div> Lorem ipsum dolor sit amet consectetur adipisicing elit. </q-tab-panel> <q-tab-panel name="movies"> <div class="text-h6">Movies</div> Lorem ipsum dolor sit amet consectetur adipisicing elit. </q-tab-panel> </q-tab-panels> </div> </div> </template> <script setup> import { ref } from 'vue' const panel = ref('mails') </script>要点说明:
v-model(即modelValue)为必填 prop,用于指定当前激活面板的name。从 use-panel.js 看,modelValue被声明为required: true,且通过getContentKey()兼容字符串与数字类型的name。- 每个
QTabPanel的name是必需的,且不能为空字符串、null或undefined(isValidPanelName()校验逻辑)。 - 渲染时,未激活面板的内容默认不会渲染(仅在
keep-alive开启时保留缓存实例)。
三、与 QTabs 组合使用
3.1 经典组合
文档明确指出:"Works great along with QTabs, a component which offers a nice way to select the active tab panel to display." 两者通过共享同一个v-model值(即name)建立联系。完整示例见 WithQTabs.vue:
<template> <div class="q-pa-md"> <div class="q-gutter-y-md" style="max-width: 600px"> <q-card> <q-tabs v-model="tab" dense class="text-grey" active-color="primary" indicator-color="primary" align="justify" narrow-indicator > <q-tab name="mails" label="Mails" /> <q-tab name="alarms" label="Alarms" /> <q-tab name="movies" label="Movies" /> </q-tabs> <q-separator /> <q-tab-panels v-model="tab" animated> <q-tab-panel name="mails"> <div class="text-h6">Mails</div> ... </q-tab-panel> <q-tab-panel name="alarms"> <div class="text-h6">Alarms</div> ... </q-tab-panel> <q-tab-panel name="movies"> <div class="text-h6">Movies</div> ... </q-tab-panel> </q-tab-panels> </q-card> </div> </div> </template> <script setup> import { ref } from 'vue' const tab = ref('mails') </script>WithQTabs.vue中还演示了第二个卡片:面板可以放在 QTabs 的上方(先 panels 后 tabs),进一步印证了二者相对位置完全自由。
3.2 嵌套 QTabs
对于更复杂的布局,官方提供了嵌套示例 WithNestedQTabs.vue,即在一个外层QTabPanel内部再放一组QTabPanels + QTabs,形成多级选项卡结构。实现时只需保证内层使用独立的v-model变量即可。
3.3 与垂直 QTabs、QSplitter 组合
文档提供了一个经典"侧边栏 + 内容区"布局:QSplitter左侧放垂直QTabs,右侧放QTabPanels,并设置vertical让滑动方向与过渡变为垂直。完整代码见 TabsAndSplitter.vue,核心片段:
<q-splitter v-model="splitterModel" style="height: 250px"> <template #before> <q-tabs v-model="tab" vertical class="text-teal"> <q-tab name="mails" icon="mail" label="Mails" /> <q-tab name="alarms" icon="alarm" label="Alarms" /> <q-tab name="movies" icon="movie" label="Movies" /> </q-tabs> </template> <template #after> <q-tab-panels v-model="tab" animated swipeable vertical transition-prev="jump-up" transition-next="jump-up" > <q-tab-panel name="mails">...</q-tab-panel> <q-tab-panel name="alarms">...</q-tab-panel> <q-tab-panel name="movies">...</q-tab-panel> </q-tab-panels> </template> </q-splitter>四、Props 与事件完整速查(基于源码)
以下是usePanelProps(use-panel.js)定义的全部配置项,即 QTabPanels 支持的全部核心 props:
| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
modelValue | Any(必填) | — | 当前激活面板的name,支持字符串与数字 |
animated | Boolean | false | 是否在切换面板时播放过渡动画 |
infinite | Boolean | false | 到达首/末面板后是否循环(配合swipeable或方法调用) |
swipeable | Boolean | false | 是否允许鼠标/触摸滑动切换面板 |
vertical | Boolean | false | 面板是否为垂直方向(影响滑动方向与默认过渡) |
transition-prev | String | 见下文 | 切换到上一个面板时使用的过渡名称 |
transition-next | String | 见下文 | 切换到下一个面板时使用的过渡名称 |
transition-duration | String/Number | 300 | 过渡时长(毫秒),通过 CSS 变量--q-transition-duration注入 |
keep-alive | Boolean | false | 是否缓存已渲染面板的组件实例 |
keep-alive-include | String/Array/RegExp | — | 限定哪些面板进入缓存(与 Vue KeepAlive 一致) |
keep-alive-exclude | String/Array/RegExp | — | 限定哪些面板不缓存 |
keep-alive-max | Number | — | 最大缓存实例数量 |
dark | Boolean | — | 是否启用深色模式样式(q-tab-panels--dark q-dark) |
[!NOTE] 除了以上 props,
QTabPanel自身还有name(必填)与disable(Boolean,禁用该面板,禁用面板在切换与滑动时会被跳过)。
默认过渡规则(来自源码getTransitionPrev/getTransitionNext):
- 水平模式:上一个 →
slide-right,下一个 →slide-left; - 垂直模式(
vertical):上一个 →slide-down,下一个 →slide-up; - 在 RTL(从右到左)语言环境下,水平模式的默认过渡方向会镜像反转(
$q.lang.rtl判断)。
事件(emits):
update:modelValue— 请求切换面板时触发;beforeTransition— 过渡开始前触发((newVal, oldVal));transition— 过渡结束后触发(延时transitionDuration毫秒)。
实例方法(由usePanel通过Object.assign(proxy, ...)暴露):
next()— 切换到下一个已启用面板;previous()— 切换到上一个已启用面板;goTo(name)— 跳转到指定name的面板。
这些方法可由模板 ref 调用,例如this.$refs.panels.next()。
五、着色(Coloring):用 Quasar 工具类快速定制外观
面板本质是普通 DOM 容器,因此可以自由使用 Quasar 的辅助类(背景色、文字色、阴影、圆角等)进行着色。官方示例 Coloring.vue 展示了三种风格:
- 卡片风格:
q-tab-panels上加class="bg-primary text-white",配合上方的 QTabs 使用active-color/indicator-color统一配色; - 整体着色:
class="bg-purple text-white"让整个面板区域统一着色; - 逐面板着色:对单个
q-tab-panel添加类,如class="bg-grey-9 text-white"、class="bg-lime-1 text-dark"——注意QTabPanel的class属性会透传到其渲染出的<div>上。
同时,通过darkprop 或在$q.dark激活时,面板会自动添加q-tab-panels--dark q-dark类以适配深色主题(见 QTabPanels.js 中useDark的处理)。
六、自定义过渡(Custom transitions)
启用animated后,默认使用滑动过渡;你可以通过transition-prev与transition-next替换为 Quasar 提供的任意过渡名称(完整列表见 Transitions)。
官方示例 Transition.vue 演示了三种自定义组合:
<!-- 缩放过渡 --> <q-tab-panels v-model="tab" animated transition-prev="scale" transition-next="scale" class="bg-purple text-white text-center" > ... </q-tab-panels> <!-- 淡入淡出过渡 --> <q-tab-panels v-model="tab" animated transition-prev="fade" transition-next="fade" class="bg-orange text-white text-center" > ... </q-tab-panels> <!-- 跳跃过渡(prev 与 next 可不同) --> <q-tab-panels v-model="tab" animated transition-prev="jump-up" transition-next="jump-down" class="bg-teal text-white text-center" > ... </q-tab-panels>源码层面的实现:在 use-panel.js 中,面板切换时通过updatePanelTransition(direction)计算q-transition--<name>类,并用 Vue 的<Transition>组件包裹面板内容;transitionDuration则通过内联样式--q-transition-duration: ${props.transitionDuration}ms注入到面板容器,Quasar 的过渡 CSS 会消费这个变量。也就是说:过渡仅在你同时传入animated且发生面板切换时才会启用。
七、滑动与无限循环(Swipeable & infinite)
7.1 水平滑动
在示例 Swipeable.vue 中,同时开启animated、swipeable、infinite:
<q-tab-panels v-model="panel" animated swipeable infinite class="bg-purple text-white shadow-2 rounded-borders" > ... </q-tab-panels>swipeable:允许用户用鼠标拖拽(mouse: true)或在触摸设备上手指滑动来切换面板;infinite:在第一个面板向左滑(或最后一个面板向右滑)时,直接循环到另一端,不会停在边界。
底层实现:usePanel通过panelDirectives计算属性动态挂载TouchSwipe指令(TouchSwipe.js)。onSwipe根据vertical判断滑动手势方向(水平为left,垂直为up),并结合 RTL 语言环境取反方向,然后调用goToPanelByOffset(±1)。该方法会跳过disable的面板,并在到达边界时根据infinite决定是否循环。值得注意的细节:TouchSwipe只有在值(value)为函数时才采集手势,源码注释提到这是为了避免在切换swipeable时因重新挂载指令而重建所有面板(issue #12668)。
[!TIP] 文档提示:如果面板内容中包含图片,并且你想用滑动操作导航,建议给这些图片加上
draggable="false",否则浏览器的原生拖拽行为可能会干扰滑动手势。
7.2 垂直滑动
在 VerticalSwipeable.vue 中,只需额外加上vertical即可让滑动方向与默认过渡变为上下方向,常用于上下滑动的"故事/卡片流"场景:
<q-tab-panels v-model="panel" animated swipeable vertical infinite class="bg-purple text-white shadow-2 rounded-borders" > ... </q-tab-panels>八、Keep-alive 缓存:正确姿势与常见陷阱
8.1 为什么需要 keep-alive
默认情况下,切走的面板会被销毁、切回时重新创建,面板内部的组件状态(如表单输入、滚动位置、计数器)会丢失。QTabPanels提供了布尔 propkeep-alive,启用后由组件内部对面板内容应用 Vue 的<KeepAlive>组件进行实例缓存。
<q-tab-panels v-model="tab" keep-alive> <q-tab-panel name="mails">...</q-tab-panel> </q-tab-panels>测试用例(QTabPanels.test.js)验证了该行为:挂载带keepAlive: true的面板 → 点击计数器 → 切到 panel-b → 切回 panel-a,计数器的值仍为1,证明实例被正确缓存。
8.2 官方警告:不要使用 Vue 原生<keep-alive>包裹 QTabPanel
[!CAUTION]
- 请务必使用 QTabPanels 的
keep-aliveprop。不要用 Vue 原生的<keep-alive>组件去包裹QTabPanel。- 如果还需要
keep-alive-include或keep-alive-exclude,则 QTabPanel 的name必须是合法的 Vue 组件名(不能包含空格、不能以数字开头)。
8.3 include/exclude 的源码级细节
keepAliveInclude/keepAliveExclude/keepAliveMax会被收集为keepAliveProps直接传给 Vue 的<KeepAlive>。这里有一个微妙实现:当使用include/exclude时,KeepAlive的匹配目标是组件名,因此usePanel会通过useRenderCache为每个面板生成一个带唯一name的包装组件(needsUniqueKeepAliveWrapper判断),确保include/exclude能按面板name精确匹配——这也是文档要求name必须符合组件命名规范的根本原因。所以请记住:
name使用简洁的 kebab-case 或 camelCase 标识符,例如mails、alarms、settings-tab;- 不要使用
"my tab"(含空格)或"123abc"(数字开头)这类命名。
九、无障碍(Accessibility):ARIA 角色与键盘可达性(v2.25+)
从 v2.25 起,QTabPanel 在无障碍方面有了正式改进。其源码(QTabPanel.js)明确写了:
h('div', { class: 'q-tab-panel', role: 'tabpanel', // per the WAI-ARIA tabs pattern, so that keyboard users can // reach the panel content even when nothing in it is focusable tabindex: 0 }, ...)即每个 QTabPanel 都会:
- 渲染
role="tabpanel"ARIA 角色; - 自带
tabindex="0",即使面板内部没有任何可聚焦元素,键盘与屏幕阅读器用户也能将焦点移入面板内容。
对应的测试(QTabPanel.test.js)断言了role="tabpanel"与tabindex="0"的渲染,并验证 ARIA 属性可以被覆盖与扩展(例如传入tabindex="-1"或aria-labelledby)。
实践建议:
- 如果面板以自身的可聚焦内容开头(如表单输入框),为了避免多余的 Tab 停靠点,可以给面板加
tabindex="-1"; - 由于 QTabPanels 不强制要求 QTabs(两者可以放在页面任意位置),组件无法自动为你建立"tab 与 panel"之间的 ARIA 关联。当你将它们配对使用时,需要手动补齐
aria-controls/aria-labelledby/id:
<q-tabs v-model="tab"> <q-tab name="mails" id="mails-tab" aria-controls="mails-panel" label="Mails" /> </q-tabs> <q-tab-panels v-model="tab"> <q-tab-panel name="mails" id="mails-panel" aria-labelledby="mails-tab"> ... </q-tab-panel> </q-tab-panels>这样,屏幕阅读器用户就能在 tab 与 panel 之间获得完整的语义关联(WAI-ARIA tabs pattern)。
十、综合实战清单与常见问题
10.1 快速决策表
| 需求 | 配置 |
|---|---|
| 纯内容切换(无动画) | v-model即可 |
| 平滑过渡 | 加animated(可配transition-prev/next) |
| 手势滑动 | 加swipeable |
| 首尾循环 | 加infinite |
| 上下滑动/垂直布局 | 加vertical |
| 保留面板状态 | 加keep-alive(配合 include/exclude/max) |
| 跳过某些面板 | 对应QTabPanel加disable |
| 深色主题 | 加dark或依赖$q.dark |
| 无 QTabs 独立使用 | 完全支持,用任意控件绑定同一个v-model |
10.2 常见误区
- 误以为必须搭配 QTabs:不是。QTabPanels 是独立的容器组件;
- 用 Vue 原生
<keep-alive>包 QTabPanel:应使用组件的keep-aliveprop; name命名不规范:需要keep-alive-include/exclude时,name必须是合法组件名;- 滑动时图片干扰:给面板内图片加
draggable="false"; - 忘记
animated:transition-prev/next、transition-duration只有在animated开启时才会生效(见updatePanelTransition源码逻辑)。
10.3 深入学习入口
- 组件文档:tab-panels.md
- 组件实现:QTabPanels.js、QTabPanel.js
- 核心逻辑(可复用于 QStepper/QCarousel 面板):use-panel.js
- 完整可运行示例:QTabPanels 示例目录(Basic、WithQTabs、WithNestedQTabs、Coloring、TabsAndSplitter、Transition、Swipeable、VerticalSwipeable)
- 测试用例:QTabPanels.test.js、QTabPanel.test.js、use-panel.test.js
- 相关组件:QTabs 文档 tabs.md
至此,你已经掌握了 QTabPanels 从基础用法到源码级原理的全部关键点:独立或配对使用、全套 props 与事件、着色定制、过渡与滑动交互、keep-alive 缓存,以及 v2.25+ 的无障碍规范。下一步就可以在自己的 Quasar 应用中,用更少的空间呈现更多信息了。
【免费下载链接】quasarQuasar Framework - Build high-performance VueJS user interfaces in record time项目地址: https://gitcode.com/gh_mirrors/qu/quasar
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考