Tabs 标签页
用于同层级内容分组切换。
何时使用
- 页面中有多个平级视图,需要在同一区域切换展示。
- 内容之间相互独立,切换时希望保留或按需销毁面板状态。
- 需要可关闭、可新增或横向滚动的页签导航。
基础用法
组件能力总览
vue
<template>
<x-tabs v-model:active-key="activeKey">
<x-tab-pane key="overview" title="概览">组件能力总览</x-tab-pane>
<x-tab-pane key="api" title="API">属性、事件与插槽</x-tab-pane>
<x-tab-pane key="guide" title="指南">使用建议</x-tab-pane>
</x-tabs>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const activeKey = ref('overview');
</script>受控值和事件
任务面板
vue
<template>
<x-tabs v-model:active-key="activeKey" @change="handleChange" @tab-click="handleTabClick">
<x-tab-pane key="task" title="任务">任务面板</x-tab-pane>
<x-tab-pane key="member" title="成员">成员面板</x-tab-pane>
<x-tab-pane key="disabled" title="禁用" disabled>禁用面板</x-tab-pane>
</x-tabs>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const activeKey = ref('task');
const handleChange = (key: string | number) => {
console.log('change', key);
};
const handleTabClick = (key: string | number) => {
console.log('tab-click', key);
};
</script>类型和尺寸
线条样式
vue
<template>
<x-radio-group v-model="type" type="button" :options="typeOptions" />
<x-radio-group v-model="size" type="button" :options="sizeOptions" />
<x-tabs v-model:active-key="activeKey" :type="type" :size="size">
<x-tab-pane key="line" title="线条">线条样式</x-tab-pane>
<x-tab-pane key="card" title="卡片">卡片样式</x-tab-pane>
<x-tab-pane key="capsule" title="胶囊">胶囊样式</x-tab-pane>
</x-tabs>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const activeKey = ref('line');
const type = ref('card-gutter');
const size = ref('large');
const typeOptions = [
{ label: 'Line', value: 'line' },
{ label: 'Card', value: 'card' },
{ label: 'Card Gutter', value: 'card-gutter' },
{ label: 'Text', value: 'text' },
{ label: 'Rounded', value: 'rounded' },
{ label: 'Capsule', value: 'capsule' },
];
const sizeOptions = [
{ label: 'Mini', value: 'mini' },
{ label: 'Small', value: 'small' },
{ label: 'Medium', value: 'medium' },
{ label: 'Large', value: 'large' },
];
</script>标签位置
vue
<template>
<x-radio-group v-model="position" type="button" :options="positionOptions" />
<x-tabs :position="position" type="card">
<x-tab-pane key="overview" title="概览">当前标签栏位置:{{ position }}</x-tab-pane>
<x-tab-pane key="config" title="配置">左侧和右侧会切换为纵向导航</x-tab-pane>
<x-tab-pane key="log" title="日志">底部标签栏会显示在内容之后</x-tab-pane>
</x-tabs>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const position = ref('left');
const positionOptions = [
{ label: 'Top', value: 'top' },
{ label: 'Right', value: 'right' },
{ label: 'Bottom', value: 'bottom' },
{ label: 'Left', value: 'left' },
];
</script>可编辑标签
需求 内容
vue
<template>
<x-tabs
v-model:active-key="activeKey"
type="card-gutter"
editable
show-add-button
auto-switch
@add="addTab"
@delete="removeTab"
>
<x-tab-pane v-for="item in tabs" :key="item.key" :title="item.title">
{{ item.title }} 内容
</x-tab-pane>
</x-tabs>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const activeKey = ref('1');
const seed = ref(3);
const tabs = ref([
{ key: '1', title: '需求' },
{ key: '2', title: '设计' },
]);
const addTab = () => {
const key = String(seed.value++);
tabs.value.push({ key, title: `新标签 ${key}` });
};
const removeTab = (key: string | number) => {
tabs.value = tabs.value.filter((item) => item.key !== key);
};
</script>插槽和额外内容
额外操作
自定义标题插槽
vue
<template>
<x-tabs v-model:active-key="activeKey">
<template #extra>
<button>额外操作</button>
</template>
<x-tab-pane key="message">
<template #title>消息</template>
自定义标题插槽
</x-tab-pane>
<x-tab-pane key="notice">
<template #title>通知</template>
标签栏右侧可以放置额外内容
</x-tab-pane>
</x-tabs>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const activeKey = ref('message');
</script>懒加载和销毁
首次面板 已挂载,切换后会按需销毁
vue
<template>
<x-tabs v-model:active-key="activeKey" lazy-load destroy-on-hide animation>
<x-tab-pane key="first" title="首次">
<LifecyclePanel name="首次面板" />
</x-tab-pane>
<x-tab-pane key="second" title="销毁">
<LifecyclePanel name="销毁面板" />
</x-tab-pane>
<x-tab-pane key="third" title="动画">
<LifecyclePanel name="动画面板" />
</x-tab-pane>
</x-tabs>
<p>挂载次数:{{ mountCount }};销毁次数:{{ unmountCount }}</p>
</template>
<script setup lang="ts">
import { defineComponent, h, onBeforeUnmount, onMounted, ref } from 'vue';
const activeKey = ref('first');
const mountCount = ref(0);
const unmountCount = ref(0);
const LifecyclePanel = defineComponent({
props: {
name: {
type: String,
required: true,
},
},
setup(props) {
onMounted(() => {
mountCount.value += 1;
});
onBeforeUnmount(() => {
unmountCount.value += 1;
});
return () => h('span', `${props.name} 已挂载`);
},
});
</script>Hover 触发
鼠标移入即可切换
vue
<template>
<x-tabs v-model:active-key="activeKey" trigger="hover">
<x-tab-pane key="preview" title="预览">鼠标移入即可切换</x-tab-pane>
<x-tab-pane key="detail" title="详情">适合轻量预览场景</x-tab-pane>
</x-tabs>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const activeKey = ref('preview');
</script>滚动标签
标签 8 内容
vue
<template>
<x-radio-group v-model="position" type="button" :options="positionOptions" />
<x-radio-group v-model="scrollPosition" type="button" :options="scrollPositionOptions" />
<x-button size="small" @click="handleChangeActive">Change: {{ activeKey }}</x-button>
<x-tabs
v-model:active-key="activeKey"
:position="position"
:scroll-position="scrollPosition"
style="width: 100%; height: 300px"
>
<x-tab-pane v-for="item in tabs" :key="item.key" :title="item.title">
{{ item.title }} 内容
</x-tab-pane>
</x-tabs>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const activeKey = ref('tab-8');
const position = ref('top');
const scrollPosition = ref('auto');
const positionOptions = [
{ label: 'Left', value: 'left' },
{ label: 'Top', value: 'top' },
{ label: 'Right', value: 'right' },
{ label: 'Bottom', value: 'bottom' },
];
const scrollPositionOptions = [
{ label: 'auto', value: 'auto' },
{ label: 'start', value: 'start' },
{ label: 'center', value: 'center' },
{ label: 'end', value: 'end' },
];
const tabs = Array.from({ length: 30 }, (_, index) => ({
key: `tab-${index + 1}`,
title: `标签 ${index + 1}`,
}));
const handleChangeActive = () => {
activeKey.value = `tab-${Math.floor(Math.random() * tabs.length) + 1}`;
};
</script>按需导入
ts
import { Tabs, TabPane } from 'x-next';Tabs Props
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
activeKey | 当前激活标签 key | string | number | undefined |
defaultActiveKey | 默认激活标签 key | string | number | undefined |
position | 标签位置 | 'left' | 'right' | 'top' | 'bottom' | 'top' |
size | 标签尺寸 | 'mini' | 'small' | 'medium' | 'large' | 跟随全局配置 |
type | 标签类型 | 'line' | 'card' | 'card-gutter' | 'text' | 'rounded' | 'capsule' | 'line' |
direction | 方向,设置为 vertical 时等同左侧标签栏 | 'horizontal' | 'vertical' | 'horizontal' |
editable | 是否开启可编辑模式,仅 line、card、card-gutter 显示关闭/新增入口 | boolean | false |
showAddButton | 是否显示新增按钮 | boolean | false |
destroyOnHide | 隐藏时是否销毁内容 | boolean | false |
lazyLoad | 是否首次展示时再挂载内容 | boolean | false |
justify | 是否撑满容器 | boolean | false |
animation | 是否开启内容切换动画 | boolean | false |
headerPadding | 线条和文本类型下是否保留头部水平边距 | boolean | true |
autoSwitch | 新增标签后是否自动切到最后一个标签 | boolean | false |
hideContent | 是否隐藏内容区域 | boolean | false |
trigger | 触发方式 | 'hover' | 'click' | 'click' |
scrollPosition | 激活标签滚动位置 | 'start' | 'end' | 'center' | 'auto' | number | 'auto' |
Tabs Events
| 事件名 | 说明 | 回调参数 |
|---|---|---|
update:activeKey | 激活标签变化时触发 | key |
change | 激活标签变化时触发 | key |
tab-click | 点击或触发标签时触发 | key, event |
add | 点击新增按钮时触发 | event |
delete | 点击删除按钮时触发 | key, event |
TabPane Props
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
title | 标签标题 | string | - |
disabled | 是否禁用 | boolean | false |
closable | 是否允许关闭 | boolean | true |
destroyOnHide | 隐藏时是否销毁内容,优先级高于 Tabs 的全局设置 | boolean | false |
Slots
| 组件 | 插槽名 | 说明 |
|---|---|---|
Tabs | default | TabPane 列表 |
Tabs | extra | 标签栏额外内容 |
TabPane | default | 标签页内容 |
TabPane | title | 自定义标签标题 |