Vue中双向绑定实现的表单选择组件(按钮样式)

写在前面

表单中经常会用到选择框的组件,HTML自带的样式太丑了,第三方的组件也不满足项目的使用需求,而且样式也不好看,于是自己就撸了一个,代码很简单,可以实现单选和多选,且选择后不需要再对选择的数据进行处理。
我写的这个组件是按钮的样式,如果需要图标+文字的哪种,自行更改样式即可。

效果图

在这里插入图片描述

选择子组件代码

<template>
  <div class="selectBtn">
    <!-- 标题 -->
    <h1 v-if='title'>{{title}}</h1>
    <!-- 单选框 -->
    <ul :class='{readonly}'>
      <li v-for="(item, index) in checkArr" :key="index" @click="change(item)">
        <button :class="{selected: item.selected}" :readonly='readonly'>{{item[itemName]}}</button>
      </li>
    </ul>
    <slot/>
    <p class="tips" v-if="tips">{{tips}}</p>
  </div>
</template>

<script>
// 超出选择限制弹窗提示,请自行引入自己的弹窗
import { toast } from '../../../mixins/popup'
export default {
  props: {
   	// 必传,第一项是name,第二项是value
    propName: Array,
    // 非必传,限选项数
    limit: {
      type: Number,
      default: 999
    },
    // 非必传,只读属性
    readonly: Boolean,
    // 非必传,图中的红色提示文字
    tips: String,
    // 非必传,默认单选,多选传checkbox
    type: {
      type: String,
      default () {
        return 'radio'
      }
    },
    // 非必传,标题
    title: String,
    // 必传,选项数据仅支持数组形式,如[{name: '选项一', value: 1}],
    // name和value即为propName中的第一个和第二项
    options: {
      type: Array,
      default: []
    },
    // 必传,选中的值,支持三种类型,单选可传String、Number类型,
    // 多选只能传Array类型
    value: [String, Array, Number]
  },
  data () {
    return {}
  },
  computed: {
  	// 处理父组件传过来的数组,与双向绑定的vlaue值比较,
  	// 选中项的要加上selected为true的属性
    checkArr () {
      let checkArr = JSON.parse(JSON.stringify(this.options))
      checkArr.forEach(item => {
      	// 默认添加上selected为false的属性
        this.$set(item, 'selected', false)
        // 单选
        if (this.type === 'radio') {
          if (item[this.itemVal] === this.value) {
            item.selected = true
          }
        } else if (this.type === 'checkbox') {
          // 多选
          this.value.forEach(val => {
            if (item[this.itemVal] === val[this.itemVal]) {
              item.selected = true
            }
          })
        }
      })
      return checkArr
    },
    // 选项名,使用computed转换一下,方便处理
    itemName () {
      return this.propName[0]
    },
    // 选项值,使用computed转换一下,方便处理
    itemVal () {
      return this.propName[1]
    }
  },
  methods: {
    change (item) {
      // 只读属性则不可点击
      if (this.readonly) return
      // 选中的值
      let value = item[this.itemVal]
      // 单选
      if (this.type === 'radio') {
        this.$emit('input', value)
        console.log('单选选中的值', value)
        return
      }
      // 多选
      if (this.type === 'checkbox') {
        let newValue = JSON.parse(JSON.stringify(this.value))
        if (item.selected) {
          let index = 0
          newValue.forEach((newVal, i) => {
            if (value === newVal[this.itemVal]) {
              index = i
            }
          })
          newValue.splice(index, 1)
        } else {
          // 超出选择
          if (this.value.length === this.limit) {
          	console.log(`最多选择${this.limit}个`)
            return toast('warn', `最多选择${this.limit}个`)
          }
          newValue.push(item)
        }
        console.log('多选选中的值', newValue)
        this.$emit('input', newValue)
      }
    }
  }
}
</script>

<style lang='less' scoped>
.selectBtn{
  padding: 0 24/75rem;
  // 标题
  h1{
    font-size:28/75rem;
    color:#222222;
    line-height:28/75rem;
    background: #fff;
    padding-top: 30/75rem;
    display: flex;
    align-items: cneter;
    background:#fff;
    margin-bottom: 6/75rem;
    &:before {
      display: inline-block;
      content: '*';
      width: 6/75rem;
      color: #ff6633;
      // background: #ff6633;
      height: 25/75rem;
      border-radius: 2/75rem;
      margin-right: 12/75rem;
      margin-top: 4/75rem;
    }
  }
  ul{
    &.readonly{
      opacity: 0.65;
    }
    min-height: 84/75rem;
    display: flex;
    align-items: center;
    padding-left:6/75rem;
    flex-wrap: wrap;
    li{
      margin-right: 20/75rem;
      margin-top: 18/75rem;
      button{
        padding: 0 20/75rem;
        height: 54/75rem;
        border-radius: 4/75rem;
        color: fff;
        font-size: 26/75rem;
        background: #fff;
        border: 1px solid #ccc;
        color: #666;
        &.selected{
          color: #fff;
          border: 1px solid #079ff7;
          background: #079ff7;
        }
      }
    }
  }
  .tips{
    color: #ff6633;
    font-size: 26/75rem;
    line-height: 36/75rem;
    margin-top: 12/75rem;
    padding-left:6/75rem;
  }
}
</style>

父组件调用代码

<template>
     <me-select
     	type='checkbox' // 多选的话,必须传
     	:limit='3' // 有限制的话需要传
     	v-model="selectData" // 必传
     	title="选择分类" // 非必传
     	:propName="['name', 'value']" // 必传,数组第一项key是页面中按钮的文字,第二项key是按钮的值
     	:options='options' // 总数据,必传
     	tips='最多选择3个分类'
     />
</template>

<script>
// 引入选择框组件
import MeSelect from './MeSelect'

export default {
  data () {
    return {
      // 单选
      // selectData: 1,
      // 多选
      selectData: [{name: '选项一', value: 1}],
      options: [
      {name: '选项一', value: 1},
      {name: '选项二', value: 2},
      {name: '选项三', value: 3},
      {name: '选项四', value: 4},
      {name: '选项五', value: 5}
      ]
    }
  },
  components: {
    MeSelect
  }
}
</script>

<style lang="less" scoped></style>

有问题就评论问我吧,(●ˇ∀ˇ●)
个人联系方式(添加请备注):
QQ:332983810
微信:hu_jiliang

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值