基础组件,功能是提供一组备选项供用户选择,只能单选。
属性
功能 | 说明 |
---|---|
v-model/value | 绑定值 |
禁用 | 禁止使用 |
单选框组 | 提供一组选项给用户,v-model绑定在父级 |
带有边框 | 样式增强,并且提供四种尺寸 |
按钮样式 | 样式增加,提供四种尺寸 |
事件
事件名称 | 说明 | 回调参数 |
---|---|---|
change | 绑定值变化时的触发事件 | radio的value值 |
src/packages
目录下新建radio
文件夹,文件夹内创建radio.vue
和index.js
。
src/styles
目录下心新建radio.scss
,并在src/styles/index.scss
中引入。
我们用label
标签将input[type=radio]
及span
包裹在一起。目的是让input
的鼠标事件扩散到整个label
上,这样即使隐藏了input
元素,也能正常使用它的功能。
// radio.vue
<template>
<label class="my-radio" :class="{ ['my-radio-selected']: selected }">
<input class="my-radio-input" type="radio" @click="onClick" />
<span
class="my-radio-icon"
:class="{ ['my-radio-icon-selected']: selected }"
>
</span>
<span class="my-radio-label">
<slot></slot>
</span>
</label>
</template>
<script>
export default {
name: 'myRadio',
data() {
return {
selected: false, // 是否被选中
}
},
props: {},
methods: {
onClick() {
this.selected = true
console.log('点击事件触发')
},
},
}
</script>
复制代码
// radio.scss
@charset "UTF-8";
@import "common/var";
@import "mixins/mixins";
@include b(radio) {
display: inline-block;
box-sizing: border-box;
vertical-align: top;
font-size: $--font-size-large;
line-height: 20px;
height: 20px;
margin-right: 20px;
cursor: pointer;
&-input {
display: none;
}
&-icon {
box-sizing: border-box;
border: 1px solid #ddd;
height: 14px;
width: 14px;
border-radius: 50%;
background-color: #fff;
display: inline-block;
position: relative;
top: 2px;
&:after {
content: "";
position: absolute;
width: 4px;
height: 4px;
background-color: #fff;
left: 50%;
top: 50%;
border-radius: 50%;
transition: transform 0.2s;
transform: translate(-50%,-50%) scale(0);
}
&-selected {
background-color: $--color-primary;
&:after {
transform: translate(-50%,-50%) scale(1);
}
}
}
&-label {
display: inline-block;
margin-left: 3px;
}
&-selected {
color: $--color-primary;
}
}
复制代码
首先是实现v-model
和label
两个属性
v-model
语法糖拆开后实际上是v-bind:value
和$emit("input")
两个功能组成的。
考虑到初始化时v-model
有可能有值,因此watch
时需要immediate
设置为true
,这样radio
初始化后能执行一次值同步。
// radio.vue
<template>
<label class="my-radio" :class="{ ['my-radio-selected']: selected }">
<input class="my-radio-input" type="radio" @click="onClick" />
<span
class="my-radio-icon"
:class="{ ['my-radio-icon-selected']: selected }"
>
</span>
<span class="my-radio-label">
<slot></slot>
</span>
</label>
</template>
<script>
export default {
name: 'myRadio',
data() {
return {
selected: false, // 是否被选中
}
},
props: {
value: {
type: [String, Number, Boolean],
default: ""
},
label: {
type: [String, Number, Boolean],
default: ""
}
},
watch: {
// 初始化判断是否已被选中
value: {
handler(newVal) {
this.selected = this.value && this.value === this.label
},
immediate: true
},
},
methods: {
onClick() {
this.selected = true
this.$emit("input", this.label);
},
},
}
</script>
复制代码
利用原生radio
的disabled
即可。
// button.vue 部分代码省略
<template>
<label
class="my-radio"
:class="{ 'my-radio-selected': selected, 'my-radio-disabled': disabled }"
>
<input
class="my-radio-input"
type="radio"
@click="onClick"
:disabled="disabled"
/>
<span
class="my-radio-icon"
:class="{
'my-radio-icon-selected': selected,
'my-radio-icon-disabled': disabled
}"
>
</span>
<span class="my-radio-label">
<slot></slot>
</span>
</label>
</template>
<script>
......
props: {
// 禁用状态
disabled: {
type: Boolean,
default: false,
},
}
......
</script>
复制代码
radio-group
组件将radio
包裹,radio
功能由父级接管。
$dispatch
和$broadcast
,前者向上派发事件,后者向下广播事件。
由内而外
的:父created -> 子created -> 子mounted -> 父mounted
,父组件要在created
中监听事件,不能在mounted
中监听。radio-group
的disabled
具体逻辑比较简单,只需根据disabled
值来调整radio
组件内的myDisabled
属性即可。而input[type="radio"]
的disabled
属性由myDisabled
和disabled
共同决定。
创建radio-group
组件:
src/packages
目录下新建radio-group
文件夹,文件夹内创建radio-group.vue
和index.js
。
src/styles
目录下心新建radio-group.scss
,并在src/styles/index.scss
中引入。
// radio-group.vue
<template>
<div class="my-radio-group">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'myRadioGroup',
data() {
return {
options: [], // 选项
}
},
props: {
value: {
type: [Boolean, String, Number],
default: false,
},
disabled: {
type: Boolean,
default: false,
},
},
watch: {
value: {
handler(newVal) {
this.syncValue(newVal)
},
immediate: true,
},
// 禁用状态
disabled: {
handler(newVal) {
this.syncOptionsDisable(newVal)
},
immediate: true,
},
},
created() {
// 监听on-radio-add事件,将radio实例存到options上
this.$on('on-radio-add', (radio) => {
this.options.push(radio)
this.syncValue(this.value)
this.syncOptionsDisable(this.disabled);
})
// 监听on-radio-remove事件,将radio实例从options中移除
this.$on('on-radio-remove', (radio) => {
this.options.splice(this.options.indexOf(radio), 1)
})
// 监听radio的选中事件,做value值同步
this.$on('on-radio-select', (radio) => {
this.syncValue(radio.label)
})
},
methods: {
/**
* @description value值同步
* @param {Boolean/String/Number} focusVal 选中值,为radio组件的label属性
*/
syncValue(focusVal) {
this.$emit('input', focusVal)
this.options.forEach((d) => {
d.selected = d.label === focusVal
})
},
/**
* @description 设置子选项的myDisabled属性
* @param {Boolean} disabled 是否禁用
*/
syncOptionsDisable(disabled) {
this.options.forEach((d) => {
d.myDisabled = disabled
})
},
},
}
</script>
复制代码
调整radio组件代码,抛出事件给radio-group。
// radio.vue 省略部分代码
......
<template>
<label
class="my-radio"
:class="{ 'my-radio-selected': selected, 'my-radio-disabled': disabled || myDisabled }"
>
<input
class="my-radio-input"
type="radio"
@click="onClick"
:disabled="disabled || myDisabled"
/>
<span
class="my-radio-icon"
:class="{
'my-radio-icon-selected': selected,
'my-radio-icon-disabled': disabled || myDisabled,
}"
>
</span>
<span class="my-radio-label">
<slot></slot>
</span>
</label>
</template>
<script>
......
data() {
return {
selected: false, // 是否被选中
myDisabled: false, // 内部的禁用属性,交由父级控制
}
},
mounted() {
// 通知myRadioGroup组件调用on-radio-add方法,参数为当前radio实例
this.dispatch('myRadioGroup', 'on-radio-add', this)
},
beforeDestroy() {
// 移除时,调用myRadioGroup组件的on-radio-remove方法
this.dispatch('myRadioGroup', 'on-radio-remove', this)
},
methods: {
onClick() {
this.selected = true
this.$emit('input', this.label)
this.dispatch("myRadioGroup", 'on-radio-select', this)
},
},
</script>
......
复制代码
radio
组件增加border
属性,可以渲染带有边框的选项,此功能主要是对css
的操作。border
生效时,size
同样生效。需开发4种尺寸,此功能也是对css
的操作。// radio.vue 省略部分代码
<template>
......
<label
class="my-radio"
:class="{
'my-radio-selected': selected,
'my-radio-disabled': disabled || myDisabled,
[`my-radio-${size}-border`]: border
}"
>
......
</label>
</template>
<script>
// 工具函数,用于判断传入的值是否符合条件
import { oneOf } from '../../utils/assist';
export default {
props: {
......
// 是否绘制边框
border: {
type: Boolean,
default: false,
},
// 尺寸
size: {
validator(value) {
return oneOf(value, ['large', 'medium', 'small', 'mini'])
},
type: String,
default: 'medium',
},
......
}
}
</script>
复制代码
// radio.scss 省略部分代码
@charset "UTF-8";
@import "common/var";
@import "mixins/mixins";
@include b(radio) {
......
// size和border相关样式
&-large-border {
height: 40px;
padding: 8px 8px 12px 8px;
border: 1px solid $--border-color;
border-radius: 4px;
}
&-medium-border {
height: 36px;
padding: 6px 8px 10px 8px;
border: 1px solid $--border-color;
border-radius: 4px;
}
&-small-border {
height: 32px;
padding: 4px 8px 8px 8px;
border: 1px solid $--border-color;
border-radius: 4px;
font-size: $--font-size-medium;
}
&-mini-border {
height: 28px;
padding: 2px 8px 6px 8px;
border: 1px solid $--border-color;
border-radius: 4px;
font-size: $--font-size-medium;
}
}
复制代码
// radio.vue 省略部分代码
<template>
<label
class="my-radio"
:class="{
'my-radio-selected': selected,
'my-radio-disabled': disabled || myDisabled,
[`my-radio-${size}-border`]: border,
[`my-radio-${size}-button`]: button,
'my-radio-selected-button': selected && button,
}"
>
<input
class="my-radio-input"
type="radio"
@click="onClick"
:disabled="disabled || myDisabled"
/>
<span
class="my-radio-icon"
:class="{
'my-radio-icon-selected': selected,
'my-radio-icon-disabled': disabled || myDisabled,
'my-radio-icon-button': button
}"
>
</span>
<span class="my-radio-label">
<slot></slot>
</span>
</label>
</template>
<script>
......
data() {
return {
button: false, //按钮样式,由父级控制
}
}
</script>
复制代码
// radio-group.vue 省略部分代码
<script>
......
props: {
......
// 是否启用按钮样式
button: {
type: Boolean,
default: false
}
},
watch: {
......
// 是否使用按钮样式
button: {
handler(newVal) {
this.syncOptionsButtonStyle(newVal)
},
immediate: true,
}
},
created() {
// 监听on-radio-add事件,将radio实例存到options上
this.$on('on-radio-add', (radio) => {
......
this.syncOptionsButtonStyle(this.button);
})
},
methods: {
/**
* @description 设置子选项的button属性,用以控制按钮样式
* @param {Boolean} value 是否设置
*/
syncOptionsButtonStyle(value) {
this.options.forEach((d) => {
d.button = value
})
}
}
</script>
复制代码
// radio.scss 省略部分代码
@charset "UTF-8";
@import "common/var";
@import "mixins/mixins";
@include b(radio) {
......
// 按钮样式相关样式
&-large-button {
float: left;
background-color: #fff;
height: 40px;
line-height: 38px;
padding: 0 15px 0 12px;
margin: 0;
border: 1px solid $--border-color;
}
&-medium-button {
float: left;
background-color: #fff;
height: 36px;
line-height: 34px;
padding: 0 15px 0 12px;
margin: 0;
border: 1px solid $--border-color;
}
&-small-button {
float: left;
background-color: #fff;
height: 32px;
line-height: 30px;
padding: 0 15px 0 12px;
margin: 0;
border: 1px solid $--border-color;
font-size: $--font-size-medium;
}
&-mini-button {
float: left;
background-color: #fff;
height: 28px;
line-height: 26px;
padding: 0 15px 0 12px;
margin: 0;
border: 1px solid $--border-color;
font-size: $--font-size-medium;
}
}
复制代码
以上便是单选框组件的部分功能开发过程,单选框组功能中我们创建了新的组件radio-group
作为父级,运用组件通讯中的广播与派发机制来协调父子之间的相互调用,以此完成v-model
,disabled
功能的实现。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。