今天过系统的时候发现,表单提交的时候写的必填校验,用户只需要敲一个空格就通过了。这就导致在统计数据的时候,出现了异常情况。除此之外还有可能用户在拷贝内容到输入框时,会带上很多无用的空格,那么如何自动来处理空格这种情况呢?
首先说一下我们系统使用的是ant-design-vue1.x 的版本,表单使用a-form
。
解决方案
我大概想到了以下三种解决方案:
- 使用
a-form
提供的自定义校验规则,当用户输入的内容首尾有空格的时候,校验不通过。 - 监听
a-input
输入框的change
事件,去掉首尾的空格然后重新赋值。 - 封装一个首尾不能输入空格的组件
最后我们采用的是第三种方案,因为在已经快完成的系统里无论是使用第一种还是第二种,工作量都是巨大的。
封装一个和a-input
具有相同api
的组件,再进行标签的批量替换,这是相对容易得多的方案了。
三种方案具体实现
-
自定义校验规则
使用正则验证首尾是否有空格
validateBlank(rule, value, callback){ if(/(^\s+)|(\s+$)/.test(value)){ callback("输入内容首尾不能存在空格!"); }else{ callback(); } }
-
去掉首尾的空格然后重新赋值
template
代码<a-form-item label="测试"> <a-input @change="handleInputChange" v-decorator="['test', { rules: [{ required: true, message: '请输入内容' }] }]" > </a-input> </a-form-item>
handleInputChange
方法handleInputChange(e) { let value = e.target.value.trim(); this.$nextTick(() => { this.form.setFieldsValue({ test: value, }); }); },
需要注意的是这里重新设置值的代码必须要在
$nextTick
里,保证在原值修改之后再设置新值。 -
封装一个首尾不能输入空格的组件x-input
-
属性传递
使用
v-bind="$attrs"
加上inheritAttrs:false
将x-input
上的参数全部传递到a-input
上 -
事件传递
因为组件内部重新处理change事件,所以传递的时候要过滤掉
change
,不然我们自己的change
事件不会生效。<a-input v-on="_listeners" > </a-input>
computed: { _listeners() { let $listeners = this.$listeners; return Object.keys($listeners) .filter(key => key !== "change") .reduce((res, key) => { res[key] = $listeners[key]; return res; }, {}); }, },
-
插槽传递
<slot v-for="(_,name) in $slots" :name="name" :slot="name"></slot>
原理是在
x-input
标签中写了的插槽全部在组件中声明一遍,再传递到a-input
中,由a-input
负责处理。 -
change事件处理
<a-input @change="handleFilterValue"> </a-input>
props: { filterHandler: PropTypes.func.def(val => val.trim()), },
methods: { handleFilterValue(e) { let value = this.filterHandler(e.target.value); this.$emit("change", value); this.$forceUpdate(); }, },
定义一个默认是trim的过滤函数
filterHandler
,如果不需要trim
的话,则使用传递一个处理函数:filterHandler=val=>val
即可。值得一提的是:这里使用到了
$forceUpdate
来强制刷新,因为一直输入空格的话,值被trim
处理之后,组件的value属性一直不变,但是a-input显示的值却一直在增加空格。有兴趣的同学可以测试一下。 -
v-model支持
model: { prop: "value", event: "change", },
完整代码如下:
<template> <div> <a-input v-bind="$attrs" v-on="_listeners" @change="handleFilterValue"> <slot v-for="(_,name) in $slots" :name="name" :slot="name"></slot> </a-input> </div> </template> <script> import PropTypes from "ant-design-vue/lib/_util/vue-types"; export default { name: "XInput", inheritAttrs: false, model: { prop: "value", event: "change", }, computed: { _listeners() { let $listeners = this.$listeners; return Object.keys($listeners) .filter(key => key !== "change") .reduce((res, key) => { res[key] = $listeners[key]; return res; }, {}); }, }, props: { filterHandler: PropTypes.func.def(val => val.trim()), }, methods: { handleFilterValue(e) { let value = this.filterHandler(e.target.value); this.$emit("change", value); this.$forceUpdate(); }, }, }; </script>
本以为组件封装到这就结束了,但是放到
a-form
里去使用才发现还存在问题。如果只是单纯的使用
v-model
绑定值,是没有问题的。但是在
a-form
里,表单是使用v-decorator
绑定值,使用过的小伙伴应该明白,表单里的值都是受form进行控制的。如果组件里不显式声明一个value
属性,表单里的值完全不受控。完整代码如下:<template> <a-input :value="value" v-bind="$attrs" v-on="_listeners" @change="handleFilterValue" > <slot v-for="(_,name) in $slots" :name="name" :slot="name"></slot> </a-input> </template> <script> import PropTypes from "ant-design-vue/es/_util/vue-types"; export default { name: "XInput", inheritAttrs: false, props: { //默认是会将值trim掉的,如果不需要则val=>val即可 filterHandler: PropTypes.func.def(val => val.trim()), value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), }, model: { prop: "value", event: "change", }, computed: { _listeners() { let $listeners = this.$listeners; return Object.keys($listeners) .filter(key => key !== "change") .reduce((res, key) => { res[key] = $listeners[key]; return res; }, {}); }, }, methods: { handleFilterValue(e) { // 如果传入了过滤函数,则使用过滤函数,否则使用trim进行过滤 let emitValue = this.filterHandler(e.target.value); this.$emit("change", emitValue); this.$forceUpdate(); }, }, }; </script>
-
总结
虽然整个组件封装的代码确实不多,但是里面涉及到属性传递、事件传递时过滤掉change
、插槽传递、以及a-form表单组件封装的注意事项。传入不同的filterHandler
,可以实现特殊字符过滤、大小写转换等功能。
能力有限,不足之处,望多多指正。