更优雅的el-radio-button实现方式

更优雅的el-radio-button实现方式

小鹿最近想要做一个类似于el-radio-button的一个组件,但是我发现官方的组件并不支持我自定义其中的内容,官方文档只给了一个label属性让我填入我所需要展示的label,所以我在这里自己写了一个公共组件,支持高度自定义,参数都可以传入。

效果展示

iShot_2025-03-06_10.32.59

代码实现

<template>
  <div class="binary-selector">
    <div v-if="label" class="selector-label">
      <span v-if="required" class="required">*</span>
      {{ label }}:
    </div>
    <el-radio-group
      v-model="selectedValue"
      @change="handleChange"
      class="radio-group"
      :style="{ width: width, height: height }"
    >
      <el-radio-button
        v-for="option in options"
        :key="option.value"
        :label="option.value"
        class="custom-radio-button"
      >
        <div class="radio-content">
          {{ option.label }}
          <div v-if="selectedValue === option.value" class="selected-corner">
            <img class="corner-triangle" src="../assets/icons/corner-triangle.svg" alt="" />
            <img class="check-mark" src="../assets/icons/check-mark.svg" alt="" />
          </div>
        </div>
      </el-radio-button>
    </el-radio-group>
  </div>
</template>

<script lang="ts" setup>
  import { ref, defineProps, defineEmits, onMounted } from 'vue';

  interface Option {
    label: string;
    value: string | number | boolean;
  }

  interface Props {
    label?: string;
    required?: boolean;
    modelValue?: Option['value'];
    options: Option[];
    width?: string;
    height?: string;
  }

  const props = withDefaults(defineProps<Props>(), {
    label: '',
    required: false,
    modelValue: '',
    options: () => [],
    width: '360px',
    height: '32px',
  });

  const emit = defineEmits(['update:modelValue', 'change']);

  const selectedValue = ref(props.modelValue);

  // 组件挂载时,如果没有初始值,则选中第一项
  onMounted(() => {
    if (!props.modelValue && props.options.length > 0) {
      const firstValue = props.options[0].value;
      selectedValue.value = firstValue;
      emit('update:modelValue', firstValue);
    }
  });

  const handleChange = (value: Option['value']) => {
    emit('update:modelValue', value);
    emit('change', value);
  };
</script>

<style scoped>
  .binary-selector {
    padding: 10px;
  }

  .selector-label {
    margin-right: 6px;
    line-height: 32px;
  }

  .required {
    color: #f56c6c;
    margin-right: 4px;
  }

  .radio-group {
    flex: 1;
    display: flex;
  }

  :deep(.el-radio-button) {
    flex: 1;
  }

  .radio-content {
    position: relative;
    padding: 0 8px;
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    box-sizing: border-box;
  }

  .selected-corner {
    position: absolute;
    right: 0;
    bottom: 0;
    width: 24px;
    height: 23px;
  }

  .corner-triangle {
    position: absolute;
    right: 0;
    bottom: 0;
    width: 24px;
    height: 23px;
  }

  .check-mark {
    position: absolute;
    right: 3px;
    bottom: 3px;
    width: 10px;
    height: 8.33px;
    z-index: 1;
  }

  /* 基础样式:所有按钮默认无圆角 */
  :deep(.el-radio-button__inner) {
    width: 100%;
    min-width: 80px;
    text-align: center;
    border-width: 1px 1px 1px 0px !important;
    border-style: solid !important;
    border-color: #8e8e8e !important;
    padding: 0;
    height: 32px !important;
    display: flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
    border-radius: 0 !important;
    transition: none !important;
  }

  /* 第一个按钮:左侧圆角 */
  :deep(.el-radio-button:first-child .el-radio-button__inner) {
    border-left-width: 1px !important;
    border-radius: 4px 0 0 4px !important;
  }

  /* 最后一个按钮:右侧圆角 */
  :deep(.el-radio-button:last-child .el-radio-button__inner) {
    border-radius: 0 4px 4px 0 !important;
  }

  /* 选中状态的按钮样式 */
  :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) {
    background-color: #eff6ff !important;
    color: #1c69d4 !important;
    border: 2px solid #1c69d4 !important;
    position: relative;
    z-index: 1;
    height: 32px !important;
    box-sizing: border-box;
    margin: 0;
    transition: none !important;
  }

  /* 根据位置设置选中状态的圆角 */
  /* 第一个按钮选中时 */
  :deep(
    .el-radio-button:first-child .el-radio-button__original-radio:checked + .el-radio-button__inner
  ) {
    border-radius: 4px 0 0 4px !important;
  }

  /* 最后一个按钮选中时 */
  :deep(
    .el-radio-button:last-child .el-radio-button__original-radio:checked + .el-radio-button__inner
  ) {
    border-radius: 0 4px 4px 0 !important;
  }

  /* 中间按钮选中时 */
  :deep(
    .el-radio-button:not(:first-child):not(:last-child)
      .el-radio-button__original-radio:checked
      + .el-radio-button__inner
  ) {
    border-radius: 0 !important;
  }

  :deep(.el-radio-button__inner:hover) {
    color: inherit;
    position: static;
    z-index: auto;
  }

  :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner:hover) {
    color: #1c69d4 !important;
  }

  /* 确保所有状态变化都没有过渡效果 */
  :deep(.el-radio-button__inner),
  :deep(.el-radio-button__inner:hover),
  :deep(.el-radio-button__inner:active),
  :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) {
    transition: none !important;
  }
</style>

调用demo

 <BinarySelector
      label="是否数据安全评估"
      :options="[
        { label: '否', value: 1 },
        { label: '是', value: 2 },
        { label: '未知', value: 3 },
      ]"
      required
    />
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

露露唯一真爱粉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值