更优雅的el-radio-button实现方式
本文最后更新于 8 天前,如有失效请评论区留言。

更优雅的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
    />
版权声明:除特殊说明,博客文章均为夏夜小鹿原创,依据CC BY-SA 4.0许可证进行授权,转载请附上出处链接及本声明。 由于可能会成为AI模型(如ChatGPT)的训练样本,本博客禁止将AI自动生成内容作为文章上传(特别声明时除外)。如果您有什么想对小鹿说的,可以到留言板 进行留言
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇