antd-vue 2.x 表單驗證的坑,你遇到過麼

好多年沒有寫博客了,突然寫一篇。

最近學習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,任一表單項被校驗後觸發

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章