Skip to content

Tabs

一个轻量级且易于使用的 Vue 3 标签页组件,支持动态切换、自定义插槽和动画效果。

特性

  • 🚀 轻量级 - 简洁高效的实现
  • 🎨 动画效果 - 平滑的滑动指示器动画
  • 🖼️ 图标支持 - 支持标签图标显示
  • 🔧 自定义插槽 - 完全自定义标签内容
  • 📱 响应式 - 自适应不同屏幕尺寸
  • 💫 Vue 3 - 基于 Composition API 构建

安装

bash
npm install vue3-xm

在线演示

基础演示

基础标签页

这是一个基础的标签页示例,展示默认配置和基本功能。

  • 折线图
  • 表格视图
  • 图表分析
  • 数据统计

当前选中

选中的标签值:line

功能说明

  • 🖱️ 点击标签切换
  • 🎨 动态滑动指示器
  • 🖼️ 支持图标显示
  • 🔧 自定义插槽内容
查看代码
vue
<template>
    <div class="demo-container">
        <h3>基础标签页</h3>
        <p>这是一个基础的标签页示例,展示默认配置和基本功能。</p>

        <div class="tabs-wrapper">
            <Tabs v-model="activeTab" :tabs="basicTabs" @change="onTabChange" />
        </div>

        <div class="info-panel">
            <h4>当前选中</h4>
            <p>
                选中的标签值:<strong>{{ activeTab }}</strong>
            </p>

            <h4>功能说明</h4>
            <ul>
                <li>🖱️ 点击标签切换</li>
                <li>🎨 动态滑动指示器</li>
                <li>🖼️ 支持图标显示</li>
                <li>🔧 自定义插槽内容</li>
            </ul>
        </div>
    </div>
</template>

<script setup>
import { ref } from "vue";
import { Tabs } from "vue3-xm";

// 当前选中的标签
const activeTab = ref("line");

// 基础标签数据
const basicTabs = ref([
    {
        value: "line",
        label: "折线图",
        url: "https://imagecdn.ymm56.com/ymmfile/static/resource/aecd539e-515f-41e8-b126-7ac38102cafd.webp",
    },
    {
        value: "table",
        label: "表格视图",
        url: "https://imagecdn.ymm56.com/ymmfile/static/resource/13428291-7a45-4430-8fa9-70f8d0dea937.webp",
    },
    {
        value: "chart",
        label: "图表分析",
    },
    {
        value: "data",
        label: "数据统计",
    },
]);

// 标签切换事件
const onTabChange = (value) => {
    console.log("标签切换到:", value);
};
</script>

<style scoped>
.demo-container {
    padding: 24px;
    border: 1px solid #e0e0e0;
    border-radius: 12px;
    margin: 20px 0;
    background: #f8f9fa;
}

.tabs-wrapper {
    margin: 20px 0;
    background: white;
    padding: 20px;
    border-radius: 8px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.info-panel {
    margin-top: 20px;
    padding: 16px;
    background: white;
    border-radius: 8px;
    border-left: 4px solid #007bff;
}

.info-panel h4 {
    margin: 0 0 12px 0;
    color: #333;
    font-size: 16px;
}

.info-panel p {
    margin: 8px 0;
    color: #666;
}

.info-panel ul {
    margin: 0;
    padding-left: 20px;
}

.info-panel li {
    margin: 4px 0;
    color: #666;
}
</style>

高级配置演示

高级配置演示

这个示例展示了自定义插槽、动态标签和事件处理等高级功能。

  • 首页
  • 关于我们
  • 联系我们

标签内容区域

🏠 首页内容

这里是首页的内容区域,可以放置任何你想要的内容。

当前状态

  • 当前选中:home
  • 标签总数:3
  • 自定义模板:关闭
查看代码
vue
<template>
    <div class="demo-container">
        <h3>高级配置演示</h3>
        <p>这个示例展示了自定义插槽、动态标签和事件处理等高级功能。</p>

        <div class="controls">
            <button @click="addTab" class="control-btn">➕ 添加标签</button>
            <button @click="removeTab" class="control-btn" :disabled="customTabs.length <= 1">➖ 删除标签</button>
            <button @click="toggleTemplate" class="control-btn">🎨 切换模板</button>
        </div>

        <div class="tabs-wrapper">
            <Tabs v-model="activeCustomTab" :tabs="customTabs" @change="onCustomTabChange">
                <template #default="{ item, index }" v-if="useCustomTemplate">
                    <div class="custom-tab-content">
                        <span class="tab-badge">{{ index + 1 }}</span>
                        <div class="tab-info">
                            <div class="tab-title">{{ item.label }}</div>
                            <div class="tab-subtitle">{{ item.subtitle || "标签页" }}</div>
                        </div>
                    </div>
                </template>
            </Tabs>
        </div>

        <div class="content-area">
            <h4>标签内容区域</h4>
            <div class="tab-content">
                <div v-if="activeCustomTab === 'home'" class="content-panel">
                    <h5>🏠 首页内容</h5>
                    <p>这里是首页的内容区域,可以放置任何你想要的内容。</p>
                </div>
                <div v-else-if="activeCustomTab === 'about'" class="content-panel">
                    <h5>📖 关于我们</h5>
                    <p>这里是关于页面的内容,介绍公司或产品信息。</p>
                </div>
                <div v-else-if="activeCustomTab === 'contact'" class="content-panel">
                    <h5>📞 联系方式</h5>
                    <p>这里是联系页面的内容,提供联系方式和表单。</p>
                </div>
                <div v-else class="content-panel">
                    <h5>🔥 {{ getCurrentTabLabel() }}</h5>
                    <p>这是动态添加的标签页内容。</p>
                </div>
            </div>
        </div>

        <div class="info-panel">
            <h4>当前状态</h4>
            <ul>
                <li>
                    当前选中:<strong>{{ activeCustomTab }}</strong>
                </li>
                <li>
                    标签总数:<strong>{{ customTabs.length }}</strong>
                </li>
                <li>
                    自定义模板:<strong>{{ useCustomTemplate ? "开启" : "关闭" }}</strong>
                </li>
            </ul>
        </div>
    </div>
</template>

<script setup>
import { ref } from "vue";
import { Tabs } from "vue3-xm";

// 当前选中的标签
const activeCustomTab = ref("home");

// 是否使用自定义模板
const useCustomTemplate = ref(false);

// 标签计数器
let tabCounter = 1;

// 自定义标签数据
const customTabs = ref([
    {
        value: "home",
        label: "首页",
        subtitle: "主页面",
    },
    {
        value: "about",
        label: "关于我们",
        subtitle: "公司介绍",
    },
    {
        value: "contact",
        label: "联系我们",
        subtitle: "联系方式",
    },
]);

// 标签切换事件
const onCustomTabChange = (value) => {
    console.log("自定义标签切换到:", value);
};

// 添加标签
const addTab = () => {
    tabCounter++;
    const newTab = {
        value: `tab${tabCounter}`,
        label: `标签${tabCounter}`,
        subtitle: `动态标签${tabCounter}`,
    };
    customTabs.value.push(newTab);
};

// 删除标签
const removeTab = () => {
    if (customTabs.value.length > 1) {
        const removed = customTabs.value.pop();
        // 如果删除的是当前选中的标签,切换到第一个
        if (removed && activeCustomTab.value === removed.value) {
            activeCustomTab.value = customTabs.value[0].value;
        }
    }
};

// 切换模板
const toggleTemplate = () => {
    useCustomTemplate.value = !useCustomTemplate.value;
};

// 获取当前标签的标题
const getCurrentTabLabel = () => {
    const currentTab = customTabs.value.find((tab) => tab.value === activeCustomTab.value);
    return currentTab ? currentTab.label : "";
};
</script>

<style scoped>
.demo-container {
    padding: 24px;
    border: 1px solid #e0e0e0;
    border-radius: 12px;
    margin: 20px 0;
    background: #f8f9fa;
}

.controls {
    display: flex;
    gap: 12px;
    margin-bottom: 20px;
    flex-wrap: wrap;
}

.control-btn {
    padding: 8px 16px;
    border: 1px solid #ddd;
    border-radius: 6px;
    background: white;
    color: #333;
    cursor: pointer;
    font-size: 14px;
    transition: all 0.2s;
}

.control-btn:hover:not(:disabled) {
    background: #007bff;
    color: white;
    border-color: #007bff;
}

.control-btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

.tabs-wrapper {
    margin: 20px 0;
    background: white;
    padding: 20px;
    border-radius: 8px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.custom-tab-content {
    display: flex;
    align-items: center;
    gap: 8px;
}

.tab-badge {
    background: #007bff;
    color: white;
    border-radius: 50%;
    width: 20px;
    height: 20px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 12px;
    font-weight: bold;
}

.tab-info {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
}

.tab-title {
    font-weight: 500;
    font-size: 14px;
}

.tab-subtitle {
    font-size: 12px;
    color: #666;
    margin-top: 2px;
}

.content-area {
    margin: 20px 0;
    background: white;
    border-radius: 8px;
    overflow: hidden;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.content-area h4 {
    margin: 0;
    padding: 16px 20px;
    background: #f8f9fa;
    border-bottom: 1px solid #e0e0e0;
    color: #333;
}

.tab-content {
    padding: 20px;
}

.content-panel h5 {
    margin: 0 0 12px 0;
    color: #333;
    font-size: 18px;
}

.content-panel p {
    margin: 0;
    color: #666;
    line-height: 1.5;
}

.info-panel {
    margin-top: 20px;
    padding: 16px;
    background: white;
    border-radius: 8px;
    border-left: 4px solid #28a745;
}

.info-panel h4 {
    margin: 0 0 12px 0;
    color: #333;
    font-size: 16px;
}

.info-panel ul {
    margin: 0;
    padding-left: 20px;
}

.info-panel li {
    margin: 4px 0;
    color: #666;
}
</style>

API

Props

参数类型默认值说明
modelValuestring"line"v-model 当前选中的标签值
tabsTabItem[][]标签页数据数组

TabItem 接口

typescript
interface TabItem {
  value: string; // 标签的唯一值
  label: string; // 标签显示文本
  url?: string; // 可选的图标URL
}

Events

事件名参数说明
update:modelValue(value: string)当前选中值发生变化时触发
change(value: string)标签切换时触发

Slots

插槽名参数说明
default{ item: TabItem, index: number }自定义标签内容

基础用法

vue
<template>
  <Tabs v-model="activeTab" :tabs="tabs" @change="onTabChange" />
</template>

<script setup>
import { ref } from "vue";
import { Tabs } from "vue3-xm";

const activeTab = ref("tab1");

const tabs = ref([
  {
    value: "tab1",
    label: "标签1",
    url: "https://example.com/icon1.png",
  },
  {
    value: "tab2",
    label: "标签2",
    url: "https://example.com/icon2.png",
  },
]);

const onTabChange = (value) => {
  console.log("切换到标签:", value);
};
</script>

自定义插槽

vue
<template>
  <Tabs v-model="activeTab" :tabs="tabs">
    <template #default="{ item, index }">
      <div class="custom-tab">
        <span class="badge">{{ index + 1 }}</span>
        <div class="tab-info">
          <div class="title">{{ item.label }}</div>
          <div class="subtitle">标签页 {{ item.value }}</div>
        </div>
      </div>
    </template>
  </Tabs>
</template>

样式定制

组件提供了以下 CSS 类名供自定义样式:

css
/* 主容器 */
.tabs-wraper {
  background: #f5f5f5;
  position: relative;
}

/* 标签项 */
.item-tab {
  height: 32px;
  margin: 10px;
  white-space: nowrap;
  color: #8d8d8d;
  position: relative;
  z-index: 2;
}

/* 激活状态 */
.item-tab.active {
  color: #161616;
}

/* 悬停状态 */
.item-tab:hover {
  color: #161616;
}

注意事项

  1. 唯一值: 确保每个标签的 value 是唯一的
  2. 动画性能: 滑动指示器使用 CSS transform 实现,性能优秀
  3. 图标大小: 建议图标尺寸为 16x16 像素
  4. 响应式: 组件会自动适应容器宽度

浏览器兼容性

  • Chrome >= 60
  • Firefox >= 60
  • Safari >= 12
  • Edge >= 79

Released under the MIT License.