Vue业务组件封装之--快码下拉

101 阅读2分钟

1 背景

快速编码(Lookup Code)在开发业务功能中十分常见,用来作为下拉值提供给用户选择。

比如:任务状态(TASK_STATUS)。

codeSelect.gif

然而每次使用快码都需要根据对应的快码值调用一次后端查询获取数据,十分麻烦。

本文介绍如何将ant-design-vue中的Select二次封装为CodeSelect,达到快速使用的目的。

2 二次封装组件的基本知识点

  1. props:是用来定义组件的动态属性,二次封装中,需要额外增加的属性可以定义在这。

  2. $attrs:当我们给组件传递属性时,若某些属性不在组件的props的定义范围中,vue会将这些属性打包到$attrs,传递到组件中。

在二次封装时使用v-bind="$attrs",就可以绑定原组件的所有属性,十分便捷。

  1. $listeners:vue会将组件上绑定的所有事件打包到$listeners,传递到组件中。

在二次封装时使用v-on="$listeners",就可以绑定原组件的所有事件。

  1. $slots:vue中的插槽是通过$slots传递到组件内部。

  2. $scopdSlots:vue中的作用域插槽,也就是带参数,是通过$scopdSlots传递到组件内部。

  3. v-model:由于我们要封装的快码下拉是一个表单组件,因此需要自己实现v-model,方便用户调用。

3 封装思考步骤

3.1 确定组件有哪些props属性

  • 最简单的思路,既然是快码下拉组件,那么肯定需要传递一个快码,我们定义为code,组件根据这个code,在创建时自动调用后端接口,将返回的数据塞入到select中。
  • 另外由于我们这个是表单组件,必不可少的还需要传入value,代表这个表单组件的值。

那么props就是如下这样

props: {
    value: {
      type: String,
      default: null,
    },
    code: {
      type: String,
      default: 'RESULT_STATUS',
    }
}

3.2 确定组件需要哪些data变量

  • 由于该组件的下拉值需要自行查询后塞入select中,所以options就是我们data中所需要的变量。

如下所示:

  data () {
    return {
      options: []
    };
  }

3.3 接下来就可以写template模板

  • 需要利用到上文提到的$attrs,$listeners,$slots,$scopedSlots
  • 需要注意,由于value是从父组件传进来的,所以不能直接在a-select中使用v-model绑定value,不然会报警告。

代码如下所示:

<template>
  <a-select
    :value="value"
    v-bind="$attrs"
    :options="options"
    v-on="$listeners"
  >
    <template v-for="(index, name) in $slots" :slot="name">
      <slot :name="name" />
    </template>
    <template #[slotName]="slotProps" v-for="(slot, slotName) in $scopedSlots">
      <slot :name="slotName" v-bind="slotProps" />
    </template>
  </a-select>
</template>

3.4 补充v-model剩余内容

  • value的值需要产生变化时,也就是change事件发生时,需要将值传回父组件,此时需要调用父组件的input监听事件,该事件时v-model自动传入的。

代码如下:

methods: {
    onChange (value) {
      this.$emit('input', value);
    }
}
// onChange事件需要绑定到a-select的change事件上
<a-select
    :value="value"
    v-bind="$attrs"
    :options="options"
    v-on="$listeners"
    @change="onChange"
  >
  ...省略插槽部分内容
</a-select>
  

3.5 最后补充自动查询快码数据的内容

async created () {
    // request为模拟请求方法,各位可以改成自己公司对应的获取快码方法
    const request = async () => {
      return new Promise((resolve) => {
        setTimeout(() => {
          if (code === 'RESULT_STATUS') {
            resolve([
              { label: '成功', value: '0' },
              { label: '失败', value: '1' },
            ]);
          } else if (code === 'TASK_STATUS') {
            resolve([
              { label: '未开始', value: '1' },
              { label: '进行中', value: '2' },
              { label: '完成', value: '3' },
            ]);
          }
        }, 1000);
      });
    };
    try {
      this.options = await request(this.code);
    } catch(e){
      //此处错误处理可省略,如果报错,下拉值就是为空 
    }
}

3.6 最后再完善亿点点小细节

  • 组件完整的代码如下:
<template>
    <!-- 增加加载数据时的loading效果 -->
    <a-spin :spinning="spinning">
      <a-select
        :value="value"
        v-bind="{
          allowClear: true, //设置常用属性的初始值,
          ...$attrs,
        }"
        :options="options"
        @change="onChange"
        v-on="$listeners"
        style="min-width: 80px"
      >
        <template v-for="(index, name) in $slots" :slot="name">
          <slot :name="name" />
        </template>
        <template #[slotName]="slotProps" v-for="(slot, slotName) in $scopedSlots">
          <slot :name="slotName" v-bind="slotProps" />
        </template>
      </a-select>
    </a-spin>   
</template>
<script>
export default {
  name: 'CodeSelect',
  inheritAttrs: false, //根节点不继承非props属性
  model: {
    prop: 'value',
    event: 'change',
  },
  props: {
    value: {
      type: String,
      default: null,
    },
    code: {
      type: String,
      default: 'RESULT_STATUS',
    },
  },
  data() {
    return {
      options: [],
      spinning:false,
    };
  },
  async created() {
    // request为模拟请求方法,各位可以改成自己公司对应的获取快码方法
    const request = async (code) => {
      return new Promise((resolve) => {
        setTimeout(() => {
          if (code === 'RESULT_STATUS') {
            resolve([
              { label: '成功', value: '0' },
              { label: '失败', value: '1' },
            ]);
          } else if (code === 'TASK_STATUS') {
            resolve([
              { label: '未开始', value: '1' },
              { label: '进行中', value: '2' },
              { label: '完成', value: '3' },
            ]);
          }
        }, 1000);
      });
    };
    this.spinning=true
    try {
      this.options = await request(this.code);
    } catch(e) {
      //此处错误处理可省略,如果报错,下拉值就是为空
    } finally {
      this.spinning=false
    }
  },
  methods: {
    onChange(value) {
      this.$emit('input', value);
    }
  },
};
</script>

  • 调用示例,是不是非常的简单呢!
<CodeSelect v-model="status" code="TASK_STATUS"></CodeSelect>

4 后话

啥!看完了之后你跟我说你们的快码都是直接写死在代码里的,那当我没说哈,拜拜!