基于Vue+Antd的验证码组件封装

2,743 阅读5分钟

需求分析

关键性需求(缺失会影响组件使用或用户体验)

  1. 针对后端传来的验证码图片格式进行相应处理
  2. 本组件是基于ant-design的input组件进行封装,封装后要对原组件的属性、插槽选择性继承
  3. 点击图片发起请求更新验证码,在此期间input框应处于禁止填写状态,点击一次后也应该防止多次点击
  4. 封装的新组件也应保持数据双向绑定功能
  5. 对外提供获取验证码图片的url接口,并进行相关参数处理,同时支持post、get方式

非关键性需求(为了追求更好的交互等)

  1. 组件要具备一定可扩展性,既提供多种大小,开发人员也可以自行设置
  2. spin加载的相关交互

实现方式

总体结构上有四种实现方式(两类):

  1. input框 -> spin悬浮 -> img验证码
  2. spin悬浮 -> input框 -> img验证码
  3. input框 & (spin悬浮 -> img验证码)
  4. spin悬浮 -> (input框 & img验证码)

其中方式1和方式2是将验证码图片作为input输入框的一部分(suffix),方式3和方式4是将验证码图片和input输入框并列排放。在实现组件封装时,由于方式3和方式4中的input框和验证码相互独立,会出现样式不同步的情况,需要对样式进行大量调整,本文采用的是方式2实现方式。 方式2如图:

image.png

核心代码分析

mock模拟

const imgSrc = Random.image('1200x820', '#' + Random.string('number', 6), '#ffffff', Random.string('abcdefghijklmnpqrstuvwxyz123456789', 6))

mock模拟采用的是Random生成的随机图片,图片中字符范围、图片大小都可设置。使用组件时,对请求的模拟是使用router-mock组件, router-mock组件下载地址
router-mock文档地址

插槽继承

<template v-for="(_, slot) of $options.omit(['suffix'],$scopedSlots)" #[slot]="scope" >
    <slot :name="slot" v-bind="scope" />
</template>
<template v-for="(_, slot) of $slots" :slot="slot">
    <slot :name="slot"></slot>
</template>
  1. 为什么要scopedSlotsscopedSlots、slots继承两次?虽然vue官方文档中指出slots会作为函数暴露在slots会作为函数暴露在scopedSlots中,但以这种形式(作用域插槽继承所有)继承,即使拿到具名插槽,也没办法处理其提供的作用域,故而还需要再继承一下具名插槽
  2. 下划线是占位符
  3. omit是Ramda中的函数,通过import引入,在export时也要放入,对自定义组件的使用要用$options

图片格式转换

//如果发过来的是图片直接渲染
  if (/\.(png|jpe?g|gif|svg)(\?.*)?$/.test(res)) {
    this.captchaPath = res.data;
  } 
  //后端发过来的数据,如果是base64格式,可以直接赋值渲染
  else if (/^data:image\/(png|jpe?g|gif|svg);base64,/.test(res)) {
    this.captchaPath = res.data;
  }
  //如果是ArrayBuffer格式,需要处理
  else if (res instanceof ArrayBuffer) {
    this.captchaPath = `data:image/png;base64,${btoa(new Uint8Array(res).reduce((data, byte) => data + String.fromCharCode(byte), ''))}`;
  }
  //如果是Blob格式,需要处理————使用URL.createObjectURL()即可

  //如果都不是(这里是因为mock直接返回链接,直接渲染,走的这)
  else {
    setTimeout(() => {
      this.captchaPath = res.data;
    }, 2000);
  }

双向绑定

<a-input :value="value" @change="$emit('change', $event.target.value)">
</a-input>

model: {
    prop: 'value',
    event: 'change',
  },

model默认的prop是"value",event是"input",如果不是使用这套组合就要显性指明model

网络请求

//无论是get还是post这里都使用query的传参方式
//post方式将query参数序列化方式如下:
    const url= this.url.split("?")[0];  //获取url
    const data = qs.parse(this.url.split("?")[1])  //获取参数 进行序列化,这里先单独使用,可以在网络请求里封装下
    try {
      const res = await request({ url, method: this.method, data})();
      if(...) {} 
      }

这里引用qs帮助参数序列化

封装结果&使用

使用案例:

<captcha-input2
  :url="`/post/captcha?uuid=${uuid}`"
  v-model="captchaValue"
  method="post"
  size="small"
  allowClear
  >
  <template #prefix>
    <a-icon type="safety"></a-icon>
  </template>
</captcha-input2>

注意事项

组件API

该组件继承原input框所有组件(除suffix,验证码图片使用这个插槽)

参数说明类型默认
url获取验证码图片的请求string
method指明url请求类型,可选post、getstringget
captchaOnly是否只显示验证码,无input框booleanfalse

源码地址

组件地址
项目地址

总结

有待完善 or 尚未解决

  1. loading————闪跳?应该是Mock数据的问题
  2. 外部修改uuid,阻止多次点击(当前考虑不多)
  3. 模式之前思考的不太对——普通模式、表单模式、只有验证码模式(后续投入使用继续完善)
  4. 禁止多次点击发起请求——目前只是依赖loading控制(网络请求相关还需要再完善)
  5. 组件整体的长度高度一定但受图片长度的影响,图片过长会撑满(也会一定程度上影响placeholder显示)
  6. 只有验证码模式还没完善好,用谁占位、长宽设置、代码优化等

相关知识

后端图片的存储方式

  1. 独立存图片,把路径放到数据库中
  2. 将图片转换为二进制流,直接存到数据库的image类型字段中(不建议)

前端图片的显示格式

图片在前端显示有三种方式:url、base64、blob

  1. url:后端传来的是图片路径(比较建议)
  2. base64:如果图片较大、色彩丰富就不建议(字符串太大了,影响性能);如果是loading或者表格线这样比较小又占据一次网络请求的比较合适
  3. blob:二进制流

其中,路径和base64都可以直接赋值给src属性直接显示。对于第三种,则需要将二进制流交给blob对象处理,然后通过blob的API生成临时的URL赋值给src属性。

三者转换:

url ———> base64 <===> blob

get、post请求、query传参相关

首先,Http的请求方法根据其数据传输能力可以分为URL请求和BODY请求,URL请求包括但不限于GET、HEAD、OPTIONS、TRACE,BODY类请求包括但不限于POST、PUSH、PATCH、DELETE。对于URL类请求,客户端是不能写在body里的,服务端拿到的body也是空的。对于BODY类请求,可以把参数放到URL里,也可以通过body传送数据,在使用body传数据时,根据Content-Type的类型传送不同类型的数据。

url的格式如下: image.png 图片来源于网络——侵删

在封装验证码组件时,为了方便开发者使用和统一风格,get、post均使用quary传参方式,若有参数也直接放在url上。默认提供get请求方式,如需修改类型为post的请求,修改method属性值为post即可。

input框的input、change事件

在进行双向绑定时遇到input、change事件的取舍问题,其中input作为event、value作为props是使用model进行双向绑定的默认配置,在某些情况下两者(input、change)在结果上的表现一致,但二者还是有根本区别的,未完待续。。

参考文档1-Post方法参数写在body中和写在url中有什么区别?