vue自定义radio组件

原生html的radio标签太丑了,实际开发中都需要自定义radio。如何实现呢?
思路很简单,我们需要另外做一套好看的样式,然后把原来的radio标签隐藏掉即可。

静态结构

components/myradio.vue:

<template>
    <div class="my-radio">
        <span class="my-radio__input">
            <span class="my-radio__inner"></span>
            <!--真实的radio,被隐藏,去掉.my-radio__original可以看到-->
            <input class="my-radio__original" type="radio" />
        </span>
        <span class="my-radio__label">
          说明文字
        </span>
    </div>
</template>

<script>
    export default {
        name: "MyRadio"
    }
</script>

<style lang="scss">
    .my-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;
        -ms-user-select: none;

        .my-radio__input {
            white-space: nowrap;
            cursor: pointer;
            outline: none;
            display: inline-block;
            line-height: 1;
            position: relative;
            vertical-align: middle;

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

            .my-radio__original {
                opacity: 0;
                outline: none;
                position: absolute;
                z-index: -1;
                top: 0;
                left: 0;
                right: 0;
                bottom: 0;
                margin: 0;
            }
        }

        .my-radio__label {
            font-size: 14px;
            padding-left: 10px;
        }
    }
</style>

main.js:

import Vue from 'vue'
import App from './App.vue'
import MyRadio from './components/myradio'
Vue.config.productionTip = false

Vue.component(MyRadio.name,MyRadio)
new Vue({
  render: h => h(App),
}).$mount('#app')

App.vue:

<template>
  <div id="app">
    <my-radio v-model="sex" label="01" disabled @click="sexClick"></my-radio>
  </div>
</template>

功能实现

<template>
  <div id="app">
    <my-radio label="0" v-model="sex">男</my-radio>
    <my-radio label="1" v-model="sex">女</my-radio>
  </div>
</template>
<script>
  export default {
    data(){
      return {
        sex: '0'
      }
    }
  }

</script>

需要实现的功能:

1、接收props

input一般都有name,所以一块加上。

<template>
    <div class="my-radio">

        <span class="my-radio__input">
            <span class="my-radio__inner"></span>
            <!--真实的radio,被隐藏,去掉.my-radio__original可以看到-->
            <input :name="name" :value="label" type="radio" class="my-radio__original" />
        </span>
        <span class="my-radio__label">
           说明文字
        </span>
    </div>
</template>

<script>
    export default {
        name: "MyRadio",
        props:{
            label:{ //接收label
                type:[String,Number,Boolean],
                default:''
            },
            value:null, // 接收v-model
            name:{ // 有可能传name
                type:String,
                default:''
            }


        }
    }
</script>

<style lang="scss">
    .my-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;
        -ms-user-select: none;

        .my-radio__input {
            white-space: nowrap;
            cursor: pointer;
            outline: none;
            display: inline-block;
            line-height: 1;
            position: relative;
            vertical-align: middle;

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

            .my-radio__original {
                opacity: 0;
                outline: none;
                position: absolute;
                z-index: -1;
                top: 0;
                left: 0;
                right: 0;
                bottom: 0;
                margin: 0;
            }
        }

        .my-radio__label {
            font-size: 14px;
            padding-left: 10px;
        }
    }
</style>

2、获取父组件的label文字

这里用slot插槽就行,不过考虑到这个label文字可能不传,直接用label的值,所以需要做个判断。

        <span class="my-radio__label">
           <slot></slot>
            <!--如果没传内容,就用label的值-->
            <template v-if="!$slots.default">{{label}}</template>
        </span>

3、v-model的处理

这里需要注意的是v-model,根据单向数据流动的原则,父组件传给子组件的value,子组件不能直接修改。但单选框肯定是要改value的,怎么办呢?
解决方案是:
子组件另外定义一个属性,不过不能在data里面直接定义,因为这个属性是跟着value走的,我们可以定义一个计算属性,并定义它的get和set,其中set里面用$emit触发input事件即可:

<template>
    <div class="my-radio">

        <span class="my-radio__input">
            <span class="my-radio__inner"></span>
            <!--真实的radio,被隐藏,去掉.my-radio__original可以看到-->
            <input v-model="model" :name="name" :value="label" class="my-radio__original" type="radio" />
        </span>
        <span class="my-radio__label">
           <slot></slot>
            <!--如果没传内容,就用label的值-->
            <template v-if="!$slots.default">{{label}}</template>
        </span>
    </div>
</template>

<script>
    export default {
        name: "MyRadio",
        computed:{
            model:{
                get(){
                    return this.value
                },
                set(val){
                    console.log(val)
                    this.$emit('input', val)
                }
            },
        },
        props:{
            label:{ //接收label
                type:[String,Number,Boolean],
                default:''
            },
            value:null, // 接收v-model
            name:{ // 有可能传name
                type:String,
                default:''
            }


        }
    }
</script>

<style lang="scss">
   ...
</style>

这里有个坑。这段代码测试是无效的,当点击radio时,set函数根本无法触发。为啥呢?
因为外层标签是div的话,实际上input已经被隐藏了,因此当你点击div的时候,并无法点到input上面去,导致v-model的双向绑定动作无法触发。怎么办呢?
我们可以将最外层标签换成label,因为label标签有个for属性,是指定需绑定哪个表单元素的。如果for为空,则会默认绑定到与label相关的表单元素上。
这里label标签包裹了input,因此就会默认绑定到input上面。

4、选中效果

    .is-checked {
        .my-radio__input{
            .my-radio__inner{
                border-color:#409eff;
                background: #409eff;
                &:after{
                    transform: translate(-50%,-50%) scale(1);
                }
            }
        }
        .my-radio__label{
            color:#409eff;
        }
    }

判断如果label和value的值是相等的,那就是选中状态。

<label class="my-radio" :class="{'is-checked': label === value}">
   ...
</label>

5、最终代码

<template>
  <label
    class="my-radio"
    :class="{'is-checked': label === value,'disabled':disabled}"
    @click="clickRadio"
  >
    <span class="my-radio__input">
      <span class="my-radio__inner"></span>
      <!--真实的radio,被隐藏,去掉.my-radio__original可以看到-->
      <input
        v-model="model"
        :name="name"
        :value="label"
        class="my-radio__original"
        type="radio"
        :disabled="disabled"
      />
    </span>
    <span class="my-radio__label">
      <slot></slot>
      <!--如果没传内容,就用label的值-->
      <template v-if="!$slots.default">{{label}}</template>
    </span>
  </label>
</template>

<script>
export default {
  name: "MyRadio",
  computed: {
    model: {
      get () {
        return this.value
      },
      set (val) {
        console.log(val)
        this.$emit('input', val)
      }
    },
  },
  props: {
    label: { //接收label
      type: [String, Number, Boolean],
      default: ''
    },
    value: null, // 接收v-model
    name: { // 有可能传name
      type: String,
      default: ''
    },
    disabled: {//是否禁用
      type: Boolean,
      default: false
    }
  },
  methods: {
    clickRadio (e) {
      // if (e.target.tagName === 'INPUT') return 
      // 获取当前点击的值
       //点击时会运行两遍、下面是当e.target.tagName时运行的。还有一个是label的过滤
      if (e.target.tagName === 'INPUT') {
        console.log(e.target.value)
        this.$emit('click', e.target.value)
      }

    }
  }
}
</script>
<style lang="scss" scoped>
.my-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;
  -ms-user-select: none;

  .my-radio__input {
    white-space: nowrap;
    cursor: pointer;
    outline: none;
    display: inline-block;
    line-height: 1;
    position: relative;
    vertical-align: middle;

    .my-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;
    }
    .my-radio__inner: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 0.15s ease-in;
    }

    .my-radio__original {
      opacity: 0;
      outline: none;
      position: absolute;
      z-index: -1;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      margin: 0;
    }
  }

  .my-radio__label {
    font-size: 14px;
    padding-left: 10px;
  }
}

/*增加选中的样式*/
.is-checked {
  .my-radio__input {
    .my-radio__inner {
      border-color: #409eff;
      background: #409eff;
      &:after {
        transform: translate(-50%, -50%) scale(1);
      }
    }
  }
  .my-radio__label {
    color: #409eff;
  }
}
.disabled {
  .my-radio__input {
    .my-radio__inner {
      &:after {
        transform: translate(-50%, -50%) scale(1);
      }
    }
  }
  .my-radio__label {
    color: #ccc;
  }
}
</style>
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
vxe-table是一个基于vue的表格组件,支持增删改查、虚拟滚动、懒加载、快捷菜单、数据校验、树形结构、打印导出、表单渲染、数据分页、模态窗口、自定义模板、灵活的配置项、丰富的扩展插件等... 设计理念: 面向现代浏览器,高效的简洁 API 设计 模块化表格、按需加载、插件化扩展 为单行编辑表格而设计,支持增删改查及更多扩展,强大的功能的同时兼具性能 功能: Basic table (基础表格) Grid (高级表格) Size (尺寸) Striped (斑马线条纹) Table with border (带边框) Cell style (单元格样式) Column resizable (列宽拖动) Maximum table height (最大高度) Resize height and width (响应式宽高) Fixed column (固定列) Grouping table head (表头分组) Highlight row and column (高亮行、列) Table sequence (序号) Radio (单选) Checkbox (多选) Sorting (排序) Filter (筛选) Rowspan and colspan (合并行或列) Footer summary (表尾合计) Import (导入) Export (导出) Print (打印) Show/Hide column (显示/隐藏列) Loading (加载中) Formatted content (格式化内容) Custom template (自定义模板) Context menu(快捷菜单) Virtual Scroller(虚拟滚动) Expandable row (展开行) Pager(分页) Form(表单) Toolbar(工具栏) Tree table (树形表格) Editable CRUD(增删改查) Validate(数据校验) Data Proxy(数据代理) Keyboard navigation(键盘导航) Modal window(模态窗口) Charts(图表工具)   更新日志: v2.10.22 修复已知问题

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值