如何封装一款单选组件
前提
本次封装使用less预处理语言
1.属性
1.1 title
- 选框组的标题
- 值为字符串类型
1.2 options
- 单选项目数组
- 必填
数组中对象示例:{ value: ‘01’, label: '男', disabled:false}
- value: 值
- label: 显示的文字
- disabled: 是否可用
1.3 value
- 当前选中值
1.4 disabled
- 是否可用
- 值为布尔类型
2.实例
2.1 示例
<script setup>
import mRadio from '@/components/mBtns/mRadio.vue';
const options = [
{ value: '01', label: '张三', disabled: false },
{ value: '02', label: '李四', disabled: false },
{ value: '03', label: '王五', disabled: true },
]
</script>
<template>
<mRadio
title="测试单选框"
:options="options"
value="01"
>
</mRadio>
</template>
2.2 实现 radio.vue
<template>
<div class="radio-list" :class="disabled ? 'disabled': ''">
<div v-if="title" class="radio-title">{{ title }}</div>
<div class="radio-item" v-for="option in options" :key="option.value">
<label>
<input type="radio" :name="groupName" v-model="newValue" :disabled="disabled || option.disabled" :value="option.value"/>
<span class="input-box">
<span class="input-box-circle"></span>
</span>
<span class="input-span">{{ option.label }}</span>
</label>
</div>
</div>
</template>
<script setup>
const props = defineProps({
title: {
type: String,
default: ''
},
options: {
type: Array,
default: () => []
},
value: {
type: [ String, Number ],
default: '' | 0
},
disabled: {
type: Boolean,
default: false
}
})
const emits = defineEmits(['input', 'change'])
const newValue = computed({
get () {
return props.value
},
set (val) {
emits('input', val)
emits('change', val)
}
})
const groupName = generateUUID()
function generateUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
</script>
<style lang="less" scoped>
@selected-box-bg: #1967F2;
@selected-box-color: #161E30;
@select-box-bg: #FFF;
@select-box-color: #B8B8B8;
@prohibit-box-color: #8D8D8D;
@border-color: #D7D9DC;
.radio-list{
.radio-title{
display: inline-block;
// vertical-align: middle;
margin: 0 15px;
color: #333;
}
.radio-item{
position: relative;
display: inline-block;
padding: 0 30px 0 0;
vertical-align: middle;
white-space: nowrap;
&:last-of-type {
padding-right: 0;
}
label {
display: inline-block;
align-items: center;
position: relative;
cursor: pointer;
input {
position: absolute;
top: 50%;
left: 0;
width: 1em;
height: 1em;
transform: translate(0, -50%);
font-size: inherit;
opacity: 0;
z-index: 1;
&:checked {
& + .input-box {
background-color: @selected-box-bg;
.input-box-circle {
width: 10px;
height: 10px;
opacity: 1;
}
& + .input-span {
color: @selected-box-color;
}
}
}
&:disabled {
& + .input-box {
background-color: @prohibit-box-color;
cursor: not-allowed;
.input-box-circle {
width: 10px;
height: 10px;
opacity: 1;
background-color: @select-box-bg;
}
& + .input-span {
color: @prohibit-box-color;
cursor: not-allowed;
}
}
}
}
.input-box {
box-sizing: border-box;
position: relative;
display: inline-block;
border: solid 1px @border-color;
border-radius: 50%;
padding: 0;
width: 20px;
height: 20px;
vertical-align: middle;
overflow: hidden;
user-select: none;
margin-right: 10px;
margin-top: -3px;
flex: none;
}
.input-box-circle {
position: absolute;
display: block;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 0;
height: 0;
background-color: @select-box-bg;
border-radius: 50%;
opacity: 0;
transition: width 0.15s ease-in, height 0.15s ease-in, margin 0.15s ease-in;
}
.input-span{
display: inline-block;
color: @select-box-color;
}
}
}
&.disabled {
cursor: not-allowed;
label {
cursor: not-allowed;
}
}
}
</style>