一、封装radio组件
- radio组件基本框架和样式:
<template>
<label class="one-radio" :class="{'is-checked':label == value}">
<span class="one-radio_input">
<!-- <span class="one-radio_inner"> -->
<input type="radio"
class="one-radio_label"
:value="label"
:name="name"
v-model="model">
<!-- </span> -->
</span>
<span class="one-radio_label">
<slot></slot>
<!-- 如果没有传值,就把label作为文本显示 -->
<template v-if="!$slots.default">{{label}}</template>
</span>
</label>
</template>
<script>// 定义一个value属性,value值是接收到的label
export default {
name: 'oneRadio',
data(){
return{
}
},
props:{
label: {
type: String,
default:'0'
},
value: {
type: String,
default: ''
},
name :{
type: String,
default: ''
}
},
computed: {
// 如果用双向绑定一个计算属性,必须提供一个get和set;需要写成一个对象
model:{
get(){
// model的值是父组件传过来的value
console.log('value');
return this.value
},
set(value){
// 触发父组件给当前组件注册的input事件
this.$emit('input',value)
}
}
}
}</script>
<style lang="scss" scoped>.one-radio{
color: #606266;
font-weight: 500;
line-height: 1;
position: relative;
cursor: pointer;
display: inline-block;
white-space: nowrap;
outline: none;
font-size: 14px;
margin-right: 30px;
-moz-user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
.one-radio_input{
white-space: nowrap;
cursor: pointer;
outline: none;
display: inline-block;
line-height: 1;
position: relative;
vertical-align: middle;
.one-radio_inner{
border: 1px solid #dcdfe6;
border-radius: 100%;
width: 14px;
height: 14px;
background-color: #fff;
position: relative;
cursor: pointer;
display: inline-block;
box-sizing: border-box;
&:after{
width: 4px;
height: 4px;
border-radius: 100%;
background-color: #fff;
content: "";
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%,-50%) scale(0);
transition: transform .15s ease-in;
}
}
.one-radio_original{
opacity: 0;
outline: none;
position: absolute;
z-index: -1;
top: 0;
left: 0px;
right: 0;
bottom: 0;
margin: 0;
}
}
.one-radio_label{
font-size: 14px;
padding-left: 10px;;
}
}
// 选中的样式
.one-radio.is-checked{
.one-radio_input{
.one-radio_inner{
border-color: #409eff;
background-color: #409eff;
&:after{
transform: translate(-50%,-50%) scale(1);
}
}
}
.one-radio_label{
color:#409eff;
}
}</style>
- radio组件的数据双向绑定:
需要父组件传递label值和value值,其中value值使用v-model来绑定。
<one-radio
v-model="gender" label="0">男</one-radio>
<one-radio
v-model="gender" label="1">女</one-radio>
子组件接收数据后,对数据进行处理:
①当radio组件被点击时,绑定的数据应该变为该组件的label值,因此我们将子组件中的input标签的value属性绑定为传入的label值;并声明一个计算属性model双向绑定到input组件上(model通过get获取值–>父组件传过来的value,通过set方法将值回调给父组件)。
②改变样式:
当我们在点击radio组件时,让被选中的组件添加选中样式,可以通过label和value的比较来判断,:class="{'is-checked':label == value}"
,如果相同,则显示选中样式。
二、封装RadioGroup组件
因为存在两种①radio不被RadioGroup包裹和②radio被RadioGroup包裹的情况。第二种情况,如果radio被RadioGroup包裹时,我们在上面封装的组件就不能用了。因此,我们需要考虑radio是否被RadioGroup所包裹。
为了知晓radio是否被包裹,可以使用provide方法(在祖先组件中负责向子孙组件发送数据)和inject(在子孙组件中负责接收来自祖先组件的数据)。
在redio组件中,添加inject 来接收传入的值;
而且,我们想要拿到value值,并一定是从父组件中拿到(使用this.value),有可能是从祖先组件中拿到value值,因此,我们在computed计算属性中添加一个isGroup方法,用于判断是否radio被radioGroup所包裹。
当radio被radioGroup包裹时,不能用this.value,只能用this.radioGroup.value,因此,return this.isGroup ? this.RadioGroup.value : this.value;
,根据isGroup来决定如何获取value的值,修改值set中相应地也要进行修改,this.isGroup ? this.RadioGroup.$emit('input',value) : this.$emit('input',value)
。
<template>
<label class="one-radio" :class="{'is-checked':label == value}">
<span class="one-radio_input">
<!-- <span class="one-radio_inner"> -->
<input type="radio"
class="one-radio_label"
:value="label"
:name="name"
v-model="model">
<!-- </span> -->
</span>
<span class="one-radio_label">
<slot></slot>
<!-- 如果没有传值,就把label作为文本显示 -->
<template v-if="!$slots.default">{{label}}</template>
</span>
</label>
</template>
<script>// 定义一个value属性,value值是接收到的label
export default {
name: 'oneRadio',
data(){
return{
}
},
inject: {
RadioGroup:{
default: ''
}
},
computed: {
// 如果用双向绑定一个计算属性,必须提供一个get和set;需要写成一个对象
model:{
get(){
// model的值是父组件传过来的value
// return this.value
return this.isGroup ? this.RadioGroup.value : this.value;
},
set(value){
// 触发祖先组件RadioGroup给当前组件注册的input事件
//this.$emit('input',value)
this.isGroup ? this.RadioGroup.$emit('input',value) : this.$emit('input',value)
}
},
isGroup(){
// 用于判断radio是否被radioGroup所包裹(使用2个感叹号,将其改为布尔值)
return !!this.RadioGroup
}
}
}</script>
最终效果:
这里附上 完整代码
- radioGroup.vue:
<template>
<div class="one-radio-group">
<slot></slot>
</div>
</template>
<script>export default {
name:'HmRadioGroup',
provide(){
return {
RadioGroup: this
}
},
props:{
value: null
},
}</script>
<style>
</style>
- radio.vue
<template>
<label class="one-radio" :class="{'is-checked':label === model}">
<span class="one-radio_input">
<!-- <span class="one-radio_inner"> -->
<input type="radio"
class="one-radio_label"
:value="label"
:name="name"
v-model="model">
<!-- </span> -->
</span>
<span class="one-radio_label">
<slot></slot>
<!-- 如果没有传值,就把label作为文本显示 -->
<template v-if="!$slots.default">{{label}}</template>
</span>
</label>
</template>
<script>// 定义一个value属性,value值是接收到的label
export default {
name: 'oneRadio',
data(){
return{
}
},
inject: {
RadioGroup:{
default: ''
}
},
props:{
label: {
type: String,
default:'0'
},
value: {
type: String,
default: ''
},
name :{
type: String,
default: ''
}
},
computed: {
// 如果用双向绑定一个计算属性,必须提供一个get和set;需要写成一个对象
model:{
get(){
// model的值是父组件传过来的value
console.log('value');
// return this.value
return this.isGroup ? this.RadioGroup.value: this.value
},
set(value){
// 触发父组件给当前组件注册的input事件
// this.$emit('input',value)
this.isGroup ? this.RadioGroup.$emit('input',value) : this.$emit('input',value)
}
},
isGroup(){
// 用于判断radio是否被radioGroup所包裹(使用2个感叹号,将其改为布尔值)
return !!this.RadioGroup
}
}
}</script>
<style lang="scss" scoped>.one-radio{
color: #606266;
font-weight: 500;
line-height: 1;
position: relative;
cursor: pointer;
display: inline-block;
white-space: nowrap;
outline: none;
font-size: 14px;
margin-right: 30px;
-moz-user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
.one-radio_input{
white-space: nowrap;
cursor: pointer;
outline: none;
display: inline-block;
line-height: 1;
position: relative;
vertical-align: middle;
.one-radio_inner{
border: 1px solid #dcdfe6;
border-radius: 100%;
width: 14px;
height: 14px;
background-color: #fff;
position: relative;
cursor: pointer;
display: inline-block;
box-sizing: border-box;
&:after{
width: 4px;
height: 4px;
border-radius: 100%;
background-color: #fff;
content: "";
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%,-50%) scale(0);
transition: transform .15s ease-in;
}
}
.one-radio_original{
opacity: 0;
outline: none;
position: absolute;
z-index: -1;
top: 0;
left: 0px;
right: 0;
bottom: 0;
margin: 0;
}
}
.one-radio_label{
font-size: 14px;
padding-left: 10px;;
}
}
// 选中的样式
.one-radio.is-checked{
.one-radio_input{
.one-radio_inner{
border-color: #409eff;
background-color: #409eff;
&:after{
transform: translate(-50%,-50%) scale(1);
}
}
}
.one-radio_label{
color:#409eff;
}
}</style>
- App.vue 中使用
<template>
<div id="app">
<one-radio
v-model="gender" label="0">男</one-radio>
<one-radio
v-model="gender" label="1">女</one-radio>
<hm-radio-group v-model="gender">
<one-radio label="0">男</one-radio>
<one-radio label="1">女</one-radio>
</hm-radio-group>
</div>
</template>
<script>export default {
name: 'App',
components: {
},
data(){
return {
money: 100,
visible: false,
active: true,
password: '',
swFlag: false,
swFlag2: false,
gender:''
}
}
,
mounted(){
},
methods:{
// 父组件在使用时定义自己的点击事件,其本质是子组件中的点击事件触发父组件中的点击事件
getInfo() {
console.log('获取信息!!'); // 获取子传父的信息
},
// 父组件控制dialog的显示与隐藏
// switchDialog(value){
// this.visible = value
// },
db(value){
this.money = value
}
}
}</script>
<style>
</style>