好多年沒有寫博客了,突然寫一篇。
最近學習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,任一表單項被校驗後觸發