需求分析
关键性需求(缺失会影响组件使用或用户体验)
- 针对后端传来的验证码图片格式进行相应处理
- 本组件是基于ant-design的input组件进行封装,封装后要对原组件的属性、插槽选择性继承
- 点击图片发起请求更新验证码,在此期间input框应处于禁止填写状态,点击一次后也应该防止多次点击
- 封装的新组件也应保持数据双向绑定功能
- 对外提供获取验证码图片的url接口,并进行相关参数处理,同时支持post、get方式
非关键性需求(为了追求更好的交互等)
- 组件要具备一定可扩展性,既提供多种大小,开发人员也可以自行设置
- spin加载的相关交互
实现方式
总体结构上有四种实现方式(两类):
- input框 -> spin悬浮 -> img验证码
- spin悬浮 -> input框 -> img验证码
- input框 & (spin悬浮 -> img验证码)
- spin悬浮 -> (input框 & img验证码)
其中方式1和方式2是将验证码图片作为input输入框的一部分(suffix),方式3和方式4是将验证码图片和input输入框并列排放。在实现组件封装时,由于方式3和方式4中的input框和验证码相互独立,会出现样式不同步的情况,需要对样式进行大量调整,本文采用的是方式2实现方式。 方式2如图:
核心代码分析
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>
- 为什么要slots继承两次?虽然vue官方文档中指出scopedSlots中,但以这种形式(作用域插槽继承所有)继承,即使拿到具名插槽,也没办法处理其提供的作用域,故而还需要再继承一下具名插槽
- 下划线是占位符
- 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、get | string | get |
| captchaOnly | 是否只显示验证码,无input框 | boolean | false |
源码地址
总结
有待完善 or 尚未解决
- loading————闪跳?应该是Mock数据的问题
- 外部修改uuid,阻止多次点击(当前考虑不多)
- 模式之前思考的不太对——普通模式、表单模式、只有验证码模式(后续投入使用继续完善)
- 禁止多次点击发起请求——目前只是依赖loading控制(网络请求相关还需要再完善)
- 组件整体的长度高度一定但受图片长度的影响,图片过长会撑满(也会一定程度上影响placeholder显示)
- 只有验证码模式还没完善好,用谁占位、长宽设置、代码优化等
相关知识
后端图片的存储方式
- 独立存图片,把路径放到数据库中
- 将图片转换为二进制流,直接存到数据库的image类型字段中(不建议)
前端图片的显示格式
图片在前端显示有三种方式:url、base64、blob
- url:后端传来的是图片路径(比较建议)
- base64:如果图片较大、色彩丰富就不建议(字符串太大了,影响性能);如果是loading或者表格线这样比较小又占据一次网络请求的比较合适
- 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的格式如下:
图片来源于网络——侵删
在封装验证码组件时,为了方便开发者使用和统一风格,get、post均使用quary传参方式,若有参数也直接放在url上。默认提供get请求方式,如需修改类型为post的请求,修改method属性值为post即可。
input框的input、change事件
在进行双向绑定时遇到input、change事件的取舍问题,其中input作为event、value作为props是使用model进行双向绑定的默认配置,在某些情况下两者(input、change)在结果上的表现一致,但二者还是有根本区别的,未完待续。。