Skip to content

Tabs 标签页

标签页组件,用于在不同的内容区域之间进行切换。

组件源码
vue
<script lang="ts" setup>
import { computed } from 'vue';

const $props = withDefaults(
  defineProps<{
    name?: string;
    tabs: { label: string; value: string; dot?: string | number }[];
  }>(),
  {
    name: 'tabs-' + Math.random().toString(36).substring(2)
  }
);

const checkedValue = defineModel<string>();
const onChangeTab = (e: Event) => {
  const target = e.target as HTMLInputElement;
  if (target.tagName === 'INPUT') {
    checkedValue.value = target.value;
  }
};

const gliderStyle = computed(() => {
  const index = $props.tabs.findIndex(
    (item) => item.value == checkedValue.value
  );
  if (index === -1) {
    return {
      opacity: 0
    };
  }

  return {
    transform: `translateX(${index * 100}%)`
  };
});
</script>

<template>
  <div class="tabs" @click="onChangeTab">
    <template v-for="(item, index) in tabs">
      <input
        type="radio"
        :id="`${name}-radio${index}`"
        :name
        :value="item.value"
        :checked="checkedValue === item.value" />
      <label class="tab" :for="`${name}-radio${index}`">
        {{ item.label }}
        <span class="notification" v-if="item.dot">{{ item.dot }}</span>
      </label>
    </template>
    <span class="glider" :style="gliderStyle"></span>
  </div>
</template>

<style lang="scss" scoped>
@mixin item {
  padding: 2px 4px;
  height: 30px;
  width: 50px;
  border-radius: 17px;
}

$color-light: #185ee0;
$color: #e6eef9;

.tabs {
  display: flex;
  background-color: #fff;
  box-shadow: 0 0 1px 0 rgba(24, 94, 224, 0.15),
    0 6px 12px 0 rgba(24, 94, 224, 0.15);
  padding: 10px;
  border-radius: 99px;
}

.tabs * {
  z-index: 2;
}

.tabs input[type='radio'] {
  display: none;
}

.tab {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  @include item;
  font-size: 0.8rem;
  color: #000;
  font-weight: 500;
  cursor: pointer;
  transition: color 0.15s ease-in;
}

.notification {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 0.8rem;
  height: 0.8rem;
  position: absolute;
  top: -2px;
  right: 2px;
  font-size: 10px;
  border-radius: 50%;
  margin: 0px;
  background-color: $color;
  transition: 0.15s ease-in;
}

.tabs input[type='radio']:checked + label {
  color: $color-light;
}

.tabs input[type='radio']:checked + label > .notification {
  background-color: $color-light;
  color: #fff;
  margin: 0px;
}

.glider {
  position: absolute;
  display: flex;
  @include item;
  background-color: $color;
  z-index: 1;
  transition: 0.25s ease-out;
}
</style>

基本用法

示例代码
vue
<template>
  <div class="container">
    <base-tabs :tabs="tabs1" v-model="checkedValue1" />

    <base-tabs name="tabs2" :tabs="tabs2" v-model="checkedValue2" />
  </div>
</template>

<script lang="ts" setup>
import BaseTabs from '@/BaseTabs/BaseTabs.vue';
import { ref } from 'vue';

const tabs1 = ref([
  { label: 'Tab 1', value: 'tab1' },
  { label: 'Tab 2', value: 'tab2' },
  { label: 'Tab 3', value: 'tab3' }
]);
const checkedValue1 = ref('tab1');

const tabs2 = ref([
  { label: '香蕉', value: '1' },
  { label: '苹果', value: '2', dot: 5 },
  { label: '橘子', value: '3' }
]);
const checkedValue2 = ref('2');
</script>

<style lang="scss" scoped>
.container {
  display: flex;
  gap: 40px;
}
</style>

API

Props

参数名说明类型默认值
modelValue(v-model)绑定值string-
name名称string随机字符串

Events

事件名说明参数
update:modelValue切换标签页时触发modelValue: string

Released under the MIT License.