Skip to content

Tabs 标签页

用于同层级内容分组切换。

何时使用

  • 页面中有多个平级视图,需要在同一区域切换展示。
  • 内容之间相互独立,切换时希望保留或按需销毁面板状态。
  • 需要可关闭、可新增或横向滚动的页签导航。

基础用法

组件能力总览
当前标签:overview
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>

受控值和事件

任务面板
当前标签:task
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>

可编辑标签

需求 内容
当前标签:1;列表:需求 / 设计 / 开发
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>

懒加载和销毁

首次面板 已挂载,切换后会按需销毁
当前标签:first;挂载次数:0;销毁次数:0
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 触发

鼠标移入即可切换
当前标签:preview
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当前激活标签 keystring | numberundefined
defaultActiveKey默认激活标签 keystring | numberundefined
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是否开启可编辑模式,仅 linecardcard-gutter 显示关闭/新增入口booleanfalse
showAddButton是否显示新增按钮booleanfalse
destroyOnHide隐藏时是否销毁内容booleanfalse
lazyLoad是否首次展示时再挂载内容booleanfalse
justify是否撑满容器booleanfalse
animation是否开启内容切换动画booleanfalse
headerPadding线条和文本类型下是否保留头部水平边距booleantrue
autoSwitch新增标签后是否自动切到最后一个标签booleanfalse
hideContent是否隐藏内容区域booleanfalse
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是否禁用booleanfalse
closable是否允许关闭booleantrue
destroyOnHide隐藏时是否销毁内容,优先级高于 Tabs 的全局设置booleanfalse

Slots

组件插槽名说明
TabsdefaultTabPane 列表
Tabsextra标签栏额外内容
TabPanedefault标签页内容
TabPanetitle自定义标签标题