好多年没有写博客了,突然写一篇。
最近学习AntdVue2.x+Vue3.x ,据说antdv 特别优秀,更新也及时还适配了vue3,所以,选择了antdv,而不是elementui,虽然elementui也有新版本了,但是总觉得那不是同一套体系了
在学习到Form表单验证的时候,发现一个问题,来这里吐个槽,看看大家有没有遇到这样的问题。
问题的起因是,Antdv表单项间距特别高,我想他这是为了可以放错误提示信息,不仅官方文档这样写,提供的pro框架也是这样的,参考了很多的第三方框架也是遵循官方的提示样式,可能大家都觉得这样挺好的,表单项下放置错误提示信息。
凡事总有个但是,但是我觉得这样不好,间距太高了,提示信息太占位置了,页面比较紧凑时,更是让人难以接受,于是,我想改一下这个显示方式,使用 鼠标悬停气泡显示错误提示信息。
表单项验证错误时,显示红框,并在后面显示红色的X,再辅助鼠标悬停气泡提示,这样就能够适应绝大多数场景。
毕竟是学习框架,看着文档发现了这个form事件validate,在每个表单项被验证后会触发事件,我就真的写了,但是他竟然不生效
我本地不生效,然后还特地写了最小验证环境 CodeSandBox ,提交到了官方github
不过,总不能这个事件不生效,就放弃了,如果这是项目要求,总得想办法实现的。经过一天的奋战,总算解决了。
解决思路如下:
1、借用验证规则中的自定义验证,所有的规则,不管是用户自定义的验证规则,还是使用已定义的,我都再进行一层包装,包装内,自己调用验证
2、css调整表单项间距
3、悬浮气泡提示信息,借用第三方提供的(tippy.js),由于antdv并没有提供方法调用方式的tooltip
最终实现效果如下:
核心代码如下:
1 <template> 2 <a-form 3 name="custom-validation" 4 class="lan-form" 5 ref="formRef" 6 :labelCol="{ span: 24 }" 7 :model="user" 8 :rules="rules" 9 :layout="formlayout" 10 @submit="mySubmit" 11 @validate="myValidateForm" 12 @validateField="myValidateFormFields" 13 @finish="myFinish" 14 @finishFailed="myFinishFailed" 15 > 16 <a-form-item 17 has-feedback 18 label="姓名" 19 name="name" 20 :labelCol="{ span: 2 }" 21 :validateFirst="true" 22 class="name" 23 > 24 <a-input v-model:value="user.name" type="text" autocomplete="off" /> 25 </a-form-item> 26 <a-form-item 27 has-feedback 28 label="年龄" 29 name="age" 30 :labelCol="{ span: 2 }" 31 :validateFirst="true" 32 > 33 <!-- <a-input-number v-model:value="user.age" class="nohandler"></a-input-number> --> 34 <a-input v-model:value.number="user.age"></a-input> 35 </a-form-item> 36 <a-form-item 37 has-feedback 38 label="定量" 39 name="num" 40 :labelCol="{ span: 2 }" 41 :validateFirst="true" 42 > 43 <!-- <a-input-number v-model:value="user.age" class="nohandler"></a-input-number> --> 44 <a-input v-model:value.number="user.num"></a-input> 45 </a-form-item> 46 <a-form-item 47 has-feedback 48 label="远程" 49 name="remote" 50 :labelCol="{ span: 2 }" 51 :validateFirst="true" 52 > 53 <a-input v-model:value.number="user.remote"></a-input> 54 </a-form-item> 55 <a-form-item> 56 <a-button type="primary" html-type="submit" @click="submitForm" 57 >提交</a-button 58 > 59 <a-button type="default" @click="resetForm">重置</a-button> 60 61 <a-button type="default" @click="loadRules">重置</a-button> 62 <a-button type="default" @click="validate2">重置</a-button> 63 <a-button type="default" @click="validate3">设置验证</a-button> 64 </a-form-item> 65 </a-form> 66 </template> 67 <script> 68 import Schema from "async-validator"; 69 import tippy from "tippy.js"; 70 export default { 71 name: "App", 72 data: function () { 73 return { 74 user: { 75 name: "", 76 age: 0, 77 num: "", 78 remote: "", 79 }, 80 rules: { 81 name: [ 82 { 83 required: true, 84 min: 3, 85 max: 5, 86 message: "请输入3——5个字符", 87 }, 88 ], 89 age: [ 90 { 91 type: "number", 92 min: 3, 93 max: 5, 94 message: "只能输入大于3小于5的数字", 95 }, 96 ], 97 num: [ 98 { 99 required: true, 100 type: "enum", 101 enum: [5, 6], 102 message: "输入数字5或者6", 103 }, 104 { 105 validator: function (rule, value, callback) { 106 debugger; 107 if (value == 5) { 108 return callback(); 109 } 110 return callback("请输入数字5(同步验证)"); 111 }, 112 }, 113 ], 114 remote: [ 115 { 116 type: "number", 117 //异步验证 118 min: 3, 119 max: 9, 120 message: "只能输入大于3小于9的数字", 121 }, 122 { 123 type: "number", 124 asyncValidator: function (rule, value, callback) { 125 setTimeout(function () { 126 if (value == 5) { 127 return callback(); 128 } 129 return callback("只能输入数字5(异步验证)"); 130 }, 1000); 131 }, 132 }, 133 ], 134 }, 135 }; 136 }, 137 methods: { 138 validateField: function ( 139 model, 140 rules, 141 isFirst, 142 successCallback, 143 failCallback, 144 preCallback 145 ) { 146 let validator = new Schema(rules); 147 let option = isFirst ? { firstFields: true } : {}; 148 return validator 149 .validate(model, option) 150 .then(() => { 151 // 校验通过 152 successCallback(model); 153 preCallback(); 154 }) 155 .catch(({ fields, errors }) => { 156 failCallback(model, errors); 157 preCallback(errors); 158 }); 159 }, 160 validateSucessCallback: function (argmodel) { 161 try { 162 for (let field in argmodel) { 163 let formElement = document.getElementById( 164 "custom-validation_" + field 165 ); 166 let formitemWrapper = 167 formElement.parentElement.parentElement.parentElement; 168 let lanTippy2 = formitemWrapper.lanTippy; 169 if (lanTippy2) { 170 lanTippy2.hide(); 171 lanTippy2.disable(); 172 } 173 } 174 } catch (e) { 175 console.error(e); 176 } 177 }, 178 validateFailCallback: function (model, errors) { 179 let errorMessages = ""; 180 for (let { field, message } of errors) { 181 errorMessages += message + "<br/>"; 182 } 183 let formElement = document.getElementById( 184 "custom-validation_" + Object.keys(model)[0] 185 ); 186 let formitemWrapper = 187 formElement.parentElement.parentElement.parentElement; 188 let lanTippy2 = formitemWrapper.lanTippy; 189 if (lanTippy2) { 190 lanTippy2.setContent(errorMessages); 191 lanTippy2.enable(); 192 lanTippy2.show(); 193 } else { 194 let lanTippy = tippy(formitemWrapper, { 195 content: errorMessages, 196 allowHTML: true, 197 }); 198 lanTippy.show(); 199 formitemWrapper.lanTippy = lanTippy; 200 } 201 }, 202 setFormFieldValidate: function (formRef, fields) { 203 let self = this; 204 let formModel = formRef.model; 205 let formRules = formRef.rules; 206 if (!formModel) { 207 throw "表单未设置model"; 208 } 209 if (!formRules) { 210 throw "表单未设置rules"; 211 } 212 if (formRef.$el.className.indexOf("lan-form") == -1) { 213 formRef.$el.className = formRef.$el.className + " lan-form"; 214 } 215 216 let model = {}; 217 if (fields && fields instanceof Array) { 218 for (let i = 0; i < fields.length; i++) { 219 let f = fields[i]; 220 model[f] = formModel[f]; 221 } 222 } else if (fields && fields instanceof String) { 223 model[fields] = formModel[fields]; 224 } else { 225 model = formModel; 226 } 227 for (let key in model) { 228 let ruleItemArr = formRules[key]; 229 if (!ruleItemArr) { 230 continue; 231 } 232 let miniRuleArr = []; 233 for (let i = 0; i < ruleItemArr.length; i++) { 234 let orginalRuleInfo = ruleItemArr[i]; 235 let ruleInfo = JSON.parse(JSON.stringify(orginalRuleInfo)); 236 237 let miniValidateField = function (rule, value, callback) { 238 // if(rule&&(rule.field=='validator'||rule.field=='asyncValidator')){debugger; 239 // return; 240 // } 241 let ruleObj = {}; 242 ruleObj[this.key] = this.rule; 243 let modelObj = {}; 244 modelObj[this.key] = value; 246 self.validateField( 247 modelObj, 248 ruleObj, 249 false, 250 self.validateSucessCallback, 251 self.validateFailCallback, 252 callback 253 ); 254 }; 255 if (orginalRuleInfo.validator) { 256 ruleInfo.validator = orginalRuleInfo.validator; 257 orginalRuleInfo.validator = miniValidateField.bind({ 258 key: key, 259 rule: ruleInfo, 260 }); 261 } else if (orginalRuleInfo.asyncValidator) { 262 ruleInfo.asyncValidator = orginalRuleInfo.asyncValidator; 263 orginalRuleInfo.asyncValidator = miniValidateField.bind({ 264 key: key, 265 rule: ruleInfo, 266 }); 267 } else { 268 orginalRuleInfo.validator = miniValidateField.bind({ 269 key: key, 270 rule: ruleInfo, 271 }); 272 } 273 } 274 } 275 }, 276 }, 277 278 mounted: function () { 279 this.setFormFieldValidate(this.$refs.formRef); 280 }, 281 }; 282 </script> 283 284 <style lang="less"> 285 .lan-form { 286 .ant-form-item-control { 287 border-radius: 5px; 288 .ant-input, 289 .ant-input-number { 290 border-radius: 5px; 291 } 292 } 293 .ant-form-item { 294 margin-bottom: 5px; 295 } 296 .ant-form-explain { 297 display: none; 298 } 299 .ant-input-number { 300 width: 100%; 301 .ant-input-number-handler-wrap { 302 display: none; 303 } 304 } 305 } 306 </style>
官方文档Form有这么一个事件,validate,任一表单项被校验后触发