为了使组件使用起来更简单或者简洁,我们在vue项目中可能需要自定义组件,并且为它实现v-model,以下介绍具体的实现(此处是对element-ui组件el-select的二次封装)
v-model实现原理: v-model其实是一个语法糖,绑定了一个叫value的属性,然后处理了一个叫input的事件,事件中把返回值重新赋给了value,如下:

<input v-model="val"/>
<!--等价于-->
<input :value="val" @input="val = $event.target.value"/>

当然,value和input的名字已经支持自定义了,在组件中添加model的属性去设置:

model: {//model选项可以自定义prop名称和event名称
  prop: 'valueeee',
  event: 'inputtt'
},
data(){return{}},
methods:{},
//...

所以我们的自定义组件需要接收一个叫value的属性,并对外暴露一个叫input的事件去更新绑定值。

1.组件template部分:

<template>
  <el-select 
  	filterable 
  	placeholder="请选择" 
  	v-model="innerValue" 
  	@change="changeHandler" 
  	:multiple="multiple" 
  	:collapse-tags="true">
   
    <el-option
      v-for="item in optionList"
      :key="item.key"
      :label="item.value"
      :value="item.key">
    </el-option>
   
  </el-select>
</template>

2.组件js部分:

<script>
  export default {
    name: "vvSelect",

    data(){
      return{
        optionList: [], //下拉列表数据
        optionMap: {}, //下拉列表映射集,往往对组件的使用者很有用
        innerValue: null //v-model实现 step3.子组件中不允许直接修改prop数据
      }
    },

    props: {
      url: {//请求地址
      	type: String,
      	//这个默认值可以是通用的数据字典的api,在使用数据字典作为下拉时可减少配置
      	default: "/dict/getListByTypeCode" 
      },
      params:{//查询参数(对象or数组),配合url使用
        type:[Object, Array, String], //string时为数据字典的类型标识(typeCode)
        default:function(){
          return {}; //默认空对象
        }
      },
      options: { //直接指定下拉列表(optionList)的数据, 优先级高于url
      	type: [Object, Array], //为Object时需指定path
      	default:function(){
          return null;
        }
      },
      value: [String,Number], //v-model实现,step1.需要绑定value
      keyName: { //选项value属性名
      	type: String,
      	//这个默认值可以是数据字典的code属性名称,使用数据字典作为下拉时可减少配置
      	default: "codeName" 
      }, 
      valueName: { //选项label属性名
      	type: String,
      	//这个默认值可以是数据字典的value属性名称,使用数据字典作为下拉时可减少配置
      	default: "codeValue"
      },
      path:{ //xx.yy的格式指定从返回结果解析数组的路径
        type: String,
        default: ''
      },
      refresh: { //该值变化会引起 选项刷新 建议:refresh = !refresh
        type:Boolean,
        default: true //默认第一次自动加载,在父组件中指定false则第一次不会加载
      },
      multiple:{ //是否多选,默认发生false
        type: Boolean,
        default: false
      },
      labelGroup:{ //下拉项是否展示成 value[key] 的形式
        type: Boolean,
        default: false
      }
    },

    watch:{
      refresh(){ //更新标识观测
        this.loadOptionList();
      },
      value(val){ //将value同步到innerValue
        this.innerValue = val;
      }
    },

    methods:{
      //change事件处理
      changeHandler(val){
        this.$emit('input',val) //v-model实现 step2.需要触发input
        this.$emit('change',val) //将原change事件暴露
      },

      //加载下拉选项列表
      //加载下拉列表有两种方式: 通过指定是数据options或者url远程请求
      //options的优先级高于url的方式
      loadOptionList(){
      	if(this.options){
      	  this.setOptionList(this.options);
		}else{
		  let reqParams = this.params;
		  if(typeof this.params === "string"){
		  	reqParams = {typeCode : this.params};//数据字典typeCode设置
		  }
		  //$postApi为项目中自定义的全局ajax请求Promise函数
          this.$postApi(this.url, reqParams).then(resp => {
            const {result, msg, data } = resp;
            if(result){
          	  this.setOptionList(data);
            }else{
              this.$message.error(msg);
            }
          }); 
		}
      }
      
      setOptionList(data){
        let list = data;
        if(this.path){//通过path寻找数据列表
          const pathArr = this.path.split('.');
          for(let subPath of pathArr){
            list = list[subPath];
          }
        }
        this.optionList = [];
        if(list && list.length){
          for(let item of list){
            const key = item[this.keyName];
            let value = item[this.valueName];
            value = this.labelGroup ? (value+"["+key+"]") : value;
            this.optionList.push({key, value });
            this.optionMap[key] = item;
          }
        }
        this.$emit('getOptions',this.optionMap,this.optionList); //父组件可获得相关数据
      }
    },

    mounted(){
      if(this.refresh){//第一次加载, refresh=false则第一次加载不会执行
        this.loadOptionList();
      }
    }
  }
</script>

3.使用(使用前记得注册组件,全局或者局部注册都可以):

1.可传属性:
url:string 下拉列表请求地址
params:object/array 请求参数
options:object/array 下拉列表数据
path:string 提取下拉选项数组的路径,根路径为 data 格式: xxx.yyy.zzz
keyName:string 字典码属性名
valueName:string 字典值属性名
refresh:boolean 刷新标识 建议:refresh = !refresh 来刷新列表
multiple:boolean 是否多选,默认false
labelGroup:boolean 下拉项的值是否展示成 value[key] 的形式 默认false

2.v-model: 可绑定一个值

3.可用事件:
@change 等价于原组件的change事件
@getOptions(arg1,arg2) 获取下拉选项的以key为键的对象(map)以及数组
arg1:object 下拉选项的map
arg2:array 下拉选项数组(值包含key和value属性)

主要适用于常见的3种场景,以下举例:

<!--数据源为数据字典的下拉框 如性别 返回数据data为: []-->
<vv-select v-model="user.sex" params="SEX_TYPE" labelGroup></vv-select>

<!--数据源为远程非数据字典的下拉框 如某分类, 返回数据data为: {classify: { list: [] } }-->
<!--优先级高于上一个(即未指定url的情况)-->
<vv-select 
    v-model="user.classify"
	url="aaa/listClassifyByType" 
	:params="{type: 1}"
	keyName="classifyId" 
	valueName="classifyName"
	path="classify.list">
</vv-select>

<!--指定options的场景(optionsData为数组),优先级高于前面的两个-->
<vv-select 
    v-model="user.xxx"
	:options="optionsData" 
	keyName="code" 
	valueName="value">
</vv-select>

<!--在给定的options或者url请求返回的数据不是下拉数组的数据的时候,需要指定path来找到下拉列表的数据-->
Logo

前往低代码交流专区

更多推荐