Skip to content

Radio 单选框

单选框组件,用于在多个备选项中选中单个状态。

组件源码
vue
<script lang="ts" setup>
withDefaults(
  defineProps<{
    name?: string;
    data: { label: string; value: string; disabled?: boolean }[];
    layout?: 'horizontal' | 'vertical';
  }>(),
  {
    name: 'radio-' + Math.random().toString(36).substring(2),
    layout: 'horizontal'
  }
);

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

<template>
  <div
    class="radio-button-container"
    :class="{ vertical: layout === 'vertical' }"
    @click="onChangeTab">
    <div v-for="(item, index) in data" class="radio-button" :key="index">
      <input
        type="radio"
        class="radio-button__input"
        :id="`${name}-item${index}`"
        :name
        :value="item.value"
        :disabled="item.disabled"
        :checked="checkedValue === item.value" />
      <label class="radio-button__label" :for="`${name}-item${index}`">
        <span class="radio-button__custom"></span>
        {{ item.label }}
      </label>
    </div>
  </div>
</template>

<style lang="scss" scoped>
@mixin transition {
  transition: all 0.4s ease;
}

$active-color: #4c8bf5;
$inactive-color: #333;
$disabled-color: #ccc;

.radio-button-container {
  display: flex;
  gap: 24px;
}

.vertical {
  flex-direction: column;
}

.radio-button {
  position: relative;
}

.radio-button__input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;

  &:checked {
    & + .radio-button__label .radio-button__custom {
      border-color: $active-color;

      &::after {
        background-color: $active-color;
      }
    }

    & + .radio-button__label {
      color: $active-color;
    }
  }

  &:disabled {
    & + .radio-button__label {
      color: $disabled-color;
      cursor: not-allowed;
    }

    & + .radio-button__label .radio-button__custom {
      border-color: $disabled-color;
    }
  }
}

.radio-button__label {
  display: inline-block;
  position: relative;
  padding-left: 30px;
  font-size: 16px;
  color: $inactive-color;
  user-select: none;
  cursor: pointer;
  @include transition;

  .radio-button__custom {
    position: absolute;
    top: 0;
    left: 0;
    box-sizing: border-box;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    border: 2px solid $inactive-color;
    @include transition;

    &::after {
      content: '';
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      width: 12px;
      height: 12px;
      border-radius: 50%;
      background-color: transparent;
      @include transition;
    }
  }

  &:hover {
    color: $active-color;

    .radio-button__custom {
      border-color: $active-color;
    }
  }
}
</style>

基础用法

水平布局:
垂直布局:
示例代码
vue
<template>
  <div class="container">
    水平布局:
    <base-radio v-model="checkedValue" :data="data" />
    垂直布局:
    <base-radio v-model="checkedValue2" name="radio-1" :data="data" layout="vertical" />
  </div>
</template>

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

const data = [
  { label: 'Option 1', value: '1' },
  { label: 'Option 2', value: '2' },
  { label: 'Option 3', value: '3', disabled: true }
];
const checkedValue = ref('1');
const checkedValue2 = ref('1');
</script>

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

API

Props

参数名说明类型默认值
modelValue(v-model)绑定值string-
name单选组名称string随机字符串
data备选项{ label: string, value: string, disabled?: boolean }[]-
layout布局方式'horizontal'|'vertical'horizontal

Events

事件名说明回调参数
update:modelValue选中值发生改变时触发value: string

Released under the MIT License.