Administrator
Administrator
Published on 2025-04-26 / 4 Visits
0
0

Switch实现

1.需求分析

  • 类似于checkbox

  • 样式与checkbox不同

  • 支持switch不同状态标签展示

2.编码实现

2.1属性定义

export type SwitchValueType = boolean | string | number;
export interface SwtichProps {
  modelValue: SwitchValueType;
  disabled?: boolean;
  activeText?: string;
  inactiveText?: string;
  activeValue?: SwitchValueType;
  inactiveValue?: SwitchValueType;
  name?: string;
  id?: string;
  size?: 'small' | 'large';
}

export interface SwtichEmits {
  (e: 'update:modelValue', value: SwitchValueType) : void;
  (e: 'change', value: SwitchValueType): void;
}

2.2模板逻辑

直接使用div组合填充,内容通过判断状态显示传入的activeText、inactiveText。

<template>
  <div class="yd-switch"
  @click = switchValue
  :class="{
    [`yd-switch--${size}`]:size,
    'is-disabled':disabled,
    'is-checked':check
  }"
  >
    <div class="yd-switch__core">
      <div class="yd-switch__core-inner">
        <span v-if="activeText || inactiveText" class="yd-switch__core-inner-text">
          {{ check ? activeText : inactiveText }}
        </span>
      </div>
      <div class="yd-switch__core-action">
      </div>
    </div>
    <input
    ref="input"
    class="yd-switch__input"
    type="checkbox"
    role="switch"
    :name="name"
    focus-visible
    :disabled="disabled"
    @keydown.enter="switchValue">
  </div>
</template>

2.3事件逻辑

主要有事件切换,组件状态,同时修改显示的标签值。核心为修改标签值的过程,有属性modelValue外部绑定值,activeText、inactiveText激活和未激活的标签文本, activeValue、 inactiveValue激活和未激活使的值。modelValue绑定的值可能并不为boolean可能为string,切换时需要通过判断check状态来赋值同时触发对应事件,在初始化时也需要判断默认激活状态,check也为计算属性,通过判断modelValue是否等于activeValue来赋值。需要注意,在使用props来生成其他属性时,需要通过watch来监视生成的属性变化,因为props为只读属性,如果有异步操作,数据会变化,但是页面将无法同步更新。

<script setup lang='ts'>
import { computed, onMounted, ref, watch } from 'vue';
import type { SwtichProps,SwtichEmits } from './types'
defineOptions({
  name:'YdSwitch',
  inheritAttrs:false
})
const props = withDefaults(defineProps<SwtichProps>(),{
  activeValue:true,
  inactiveValue:false
})
const emits = defineEmits<SwtichEmits>()
const innerValue = ref(props.modelValue)
const check = computed(()=>innerValue.value === props.activeValue)
const input = ref<HTMLInputElement>()
onMounted(()=>{
  input.value!.checked = Boolean(check.value)
})
watch(check,(val)=>{
  input.value!.checked = Boolean(val)
})
watch(()=>props.modelValue,(newValue)=>{
  innerValue.value = newValue
})
const switchValue = ()=>{
  if(props.disabled){
    return
  }
  const newValue = check.value ? props.inactiveValue : props.activeValue
  innerValue.value = newValue
  emits('update:modelValue',newValue)
  emits('change',newValue)
}
</script>

2.4样式逻辑

.yd-switch {
  --yd-switch-on-color: var(--yd-color-primary);
  --yd-switch-off-color: var(--yd-border-color);
  --yd-switch-on-border-color: var(--yd-color-primary);
  --yd-switch-off-border-color: var(--yd-border-color);
}

.yd-switch {
  display: inline-flex;
  align-items: center;
  font-size: 14px;
  line-height: 20px;
  height: 32px;
  .yd-switch__input {
    position: absolute;
    width: 0;
    height: 0;
    opacity: 0;
    margin: 0;
    &:focus-visible {
      & ~ .yd-switch__core {
        outline: 2px solid var(--yd-switch-on-color);
        outline-offset: 1px;
      }
    }
  }
  &.is-disabled {
    opacity: .6;
    .yd-switch__core {
      cursor: not-allowed;
    }
  }
  &.is-checked {
    .yd-switch__core {
      border-color:var(--yd-switch-on-border-color);
      background-color: var(--yd-switch-on-color);
      .yd-switch__core-action {
        left: calc(100% - 17px);
      }
      .yd-switch__core-inner {
        padding: 0 18px 0 4px;
      }
    }
  }
}
.yd-switch--large {
  font-size: 14px;
  line-height: 24px;
  height: 40px;
  .yd-switch__core {
    min-width: 50px;
    height: 24px;
    border-radius: 12px;
    .yd-switch__core-action {
      width: 20px;
      height: 20px;
    }
  }
  &.is-checked {
    .yd-switch__core .yd-switch__core-action {
      left: calc(100% - 21px);
      color: var(--yd-switch-on-color);
    }
  }
}
.yd-switch--small {
  font-size: 12px;
  line-height: 16px;
  height: 24px;
  .yd-switch__core {
    min-width: 30px;
    height: 16px;
    border-radius: 8px;
    .yd-switch__core-action {
      width: 12px;
      height: 12px;
    }
  }
  &.is-checked {
    .yd-switch__core .yd-switch-core-action {
      left: calc(100% - 13px);
      color: var(--yd-switch-on-color);
    }
  }
}
.yd-switch__core {
  display: inline-flex;
  align-items: center;
  position: relative;
  height: 20px;
  min-width: 40px;
  border: 1px solid var(--yd-switch-off-border-color);
  outline: none;
  border-radius: 10px;
  box-sizing: border-box;
  background: var(--yd-switch-off-color);
  cursor: pointer;
  transition: border-color var(--yd-transition-duration),background-color var(--yd-transition-duration);
  .yd-switch__core-action {
    position: absolute;
    left: 1px;
    border-radius: var(--yd-border-radius-circle);
    width: 16px;
    height: 16px;
    background-color: var(--yd-color-white);
    transition: all var(--yd-transition-duration);
  }
  .yd-switch__core-inner {
    width: 100%;
    transition: all var(--yd-transition-duration);
    height: 16px;
    display: flex;
    justify-content: center;
    align-items: center;
    overflow: hidden;
    padding: 0 4px 0 18px;
    .yd-switch__core-inner-text {
      font-size: 12px;
      color: var(--yd-color-white);
      user-select: none;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }
  }
}


Comment