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;
}
}
}