From b29ed998189c3de5e408f37d61802dfa30e4f9a8 Mon Sep 17 00:00:00 2001
From: Tevin <tingquanren@163.com>
Date: Tue, 01 Dec 2020 22:01:50 +0800
Subject: [PATCH] 优化表单模块数据传递机制,不再污染数据源

---
 forms/textarea/CTextArea.vue       |   14 +-
 forms/form/CFormItem.vue           |   32 ++++---
 forms/imagePicker/CImagePicker.vue |   14 +-
 forms/switch/CSwitch.vue           |   12 +-
 forms/input/CInputPhoneCode.vue    |   44 ++++++----
 forms/chinaArea/CChinaArea.vue     |   16 ++--
 forms/form/CForm.vue               |   54 +++++++++++-
 forms/input/CInput.vue             |   15 ++-
 forms/select/CSelect.vue           |   16 ++--
 forms/form/CFormAgreement.vue      |    6 
 10 files changed, 138 insertions(+), 85 deletions(-)

diff --git a/forms/chinaArea/CChinaArea.vue b/forms/chinaArea/CChinaArea.vue
index 6a6d577..9efc4b7 100644
--- a/forms/chinaArea/CChinaArea.vue
+++ b/forms/chinaArea/CChinaArea.vue
@@ -16,10 +16,10 @@
             <view @tap="evt=>handleOpen(evt)">
                 <AtInput
                     ref="input"
-                    :name="itemData.name"
-                    :title="itemData.label"
-                    :required="itemData.required"
-                    :error="itemData.error"
+                    :name="itemRes.name"
+                    :title="itemRes.label"
+                    :required="itemRes.required"
+                    :error="itemRes.error"
                     :placeholder="placeholder"
                     :value="selected"
                 >
@@ -49,7 +49,7 @@
             default: false,
         },
         placeholder: String,
-        itemData: Object,
+        itemRes: Object,
     },
     data() {
         return {
@@ -59,7 +59,7 @@
     },
     computed: {
         selected() {
-            const curVal = this.itemData.formData[this.itemData.name];
+            const curVal = this.itemRes.formData[this.itemRes.name];
             if (curVal && curVal.length === 3) {
                 return getRegionNames(curVal).join(' / ');
             } else {
@@ -74,7 +74,7 @@
                 evt.preventDefault();
                 return;
             }
-            const curVal = this.itemData.formData[this.itemData.name];
+            const curVal = this.itemRes.formData[this.itemRes.name];
             const range = [
                 locationTree,
                 locationTree[0].children,
@@ -136,7 +136,7 @@
             codes[1] = city.value;
             const area = city.children[detail[2]];
             codes[2] = area.value;
-            this.itemData.onChange(codes);
+            this.itemRes.onChange(codes);
         },
     },
     mounted() {},
diff --git a/forms/form/CForm.vue b/forms/form/CForm.vue
index a13df65..6008449 100644
--- a/forms/form/CForm.vue
+++ b/forms/form/CForm.vue
@@ -8,7 +8,7 @@
         class="c-form"
         @submit="evt=>handleSubmit()"
     >
-        <slot :formData="formData" />
+        <slot :formRes="formRes" />
     </form>
 </template>
 
@@ -25,6 +25,9 @@
     },
     data() {
         return {
+            formRes: {
+                formData: this.formData,
+            },
             validators: {},
         };
     },
@@ -32,10 +35,14 @@
         handleSubmit() {
             const checklist = [];
             Object.keys(this.validators).forEach((key) => {
-                checklist.push(this.validators[key]());
+                // 仍有效的验证器
+                if (this.validators[key]) {
+                    checklist.push(this.validators[key]());
+                }
             });
             Promise.all(checklist).then((validations) => {
                 for (let validation of validations) {
+                    // 第一个不通过项提示
                     if (!validation.passed) {
                         Taro.showToast({
                             title: validation.msg,
@@ -50,19 +57,52 @@
                 this.onFinish && this.onFinish();
             });
         },
+        preCheckValidators(callback) {
+            const checklist = [];
+            Object.keys(this.validators).forEach((key) => {
+                // 仍有效的验证器
+                if (this.validators[key]) {
+                    checklist.push(this.validators[key]('msgOnly'));
+                }
+            });
+            Promise.all(checklist).then((validations) => {
+                for (let validation of validations) {
+                    // 跳过同意协议
+                    if (validation.name === '$agreement') {
+                        continue;
+                    }
+                    if (!validation.passed) {
+                        Taro.showToast({
+                            title: validation.msg,
+                            icon: 'none',
+                            mask: false,
+                            duration: 2000,
+                        });
+                        // 检查失败
+                        callback && callback(false);
+                        return;
+                    }
+                }
+                // 检查通过
+                callback && callback(true);
+            });
+        },
     },
     mounted() {
-        this.formData.$handleChange = (evt = []) => {
+        // 当表单项变化时
+        this.formRes.$handleChange = (evt = []) => {
             Object.keys(evt).forEach((key) => {
-                if (typeof this.formData[key] === 'undefined') {
-                    this.$set(this.formData, key, evt[key]);
+                // 直接改值
+                if (typeof this.formRes.formData[key] === 'undefined') {
+                    this.$set(this.formRes.formData, key, evt[key]);
                 } else {
-                    this.formData[key] = evt[key];
+                    this.formRes.formData[key] = evt[key];
                 }
             });
             this.onChange && this.onChange(evt);
         };
-        this.formData.$regItemValidator = (name, cb) => {
+        // 注册表单验证器
+        this.formRes.$regItemValidator = (name, cb) => {
             this.validators[name] = cb;
         };
     },
diff --git a/forms/form/CFormAgreement.vue b/forms/form/CFormAgreement.vue
index 2fe2408..31d6392 100644
--- a/forms/form/CFormAgreement.vue
+++ b/forms/form/CFormAgreement.vue
@@ -38,12 +38,12 @@
         AtFloatLayout,
     },
     props: {
-        formData: Object,
+        formRes: Object,
         protocol: String,
     },
     data() {
         return {
-            name: 'agreement',
+            name: '$agreement',
             protocolShow: false,
             checked: false,
         };
@@ -61,7 +61,7 @@
     },
     mounted() {
         this.$nextTick(() => {
-            this.formData.$regItemValidator(this.name, () => {
+            this.formRes.$regItemValidator(this.name, () => {
                 if (this.checked) {
                     return Promise.resolve({
                         name: this.name,
diff --git a/forms/form/CFormItem.vue b/forms/form/CFormItem.vue
index ffbf570..2d0ed74 100644
--- a/forms/form/CFormItem.vue
+++ b/forms/form/CFormItem.vue
@@ -6,7 +6,7 @@
 
 <template>
     <view class="c-form-item">
-        <slot :itemData="itemData" />
+        <slot :itemRes="itemRes" />
     </view>
 </template>
 
@@ -21,7 +21,7 @@
         label: String,
         required: Boolean,
         rules: Array,
-        formData: Object,
+        formRes: Object,
     },
     data() {
         return {
@@ -29,9 +29,9 @@
         };
     },
     computed: {
-        itemData() {
+        itemRes() {
             return {
-                formData: this.formData,
+                formData: this.formRes.formData,
                 name: this.name,
                 label: this.label,
                 required: this.isRequired,
@@ -71,10 +71,10 @@
                 }
             }
             // 未改变值不触发
-            if (this.formData[this.name] === evt) {
+            if (this.formRes.formData[this.name] === evt) {
                 return;
             }
-            this.formData.$handleChange({
+            this.formRes.$handleChange({
                 [this.name]: evt,
             });
         },
@@ -83,7 +83,7 @@
         this.$nextTick(() => {
             // 未设置验证
             if (!this.required && !this.rules) {
-                this.formData.$regItemValidator(this.name, () => {
+                this.formRes.$regItemValidator(this.name, (validateType) => {
                     this.error = false;
                     return Promise.resolve({
                         name: this.name,
@@ -105,10 +105,10 @@
                 validator.messages(validateMsgs);
                 // 注册验证
                 let errTimer = null;
-                this.formData.$regItemValidator(this.name, () => {
+                this.formRes.$regItemValidator(this.name, (validateType) => {
                     return validator
                         .validate({
-                            [this.name]: this.formData[this.name],
+                            [this.name]: this.formRes.formData[this.name],
                         })
                         .then(
                             (res) => {
@@ -119,11 +119,13 @@
                                 };
                             },
                             ({ errors, fields }) => {
-                                this.error = true;
-                                clearTimeout(errTimer);
-                                errTimer = setTimeout(() => {
-                                    this.error = false;
-                                }, 5000);
+                                if (validateType !== 'msgOnly') {
+                                    this.error = true;
+                                    clearTimeout(errTimer);
+                                    errTimer = setTimeout(() => {
+                                        this.error = false;
+                                    }, 5000);
+                                }
                                 return {
                                     name: this.name,
                                     passed: false,
@@ -139,7 +141,7 @@
         });
     },
     beforeDestroy() {
-        this.formData.$regItemValidator(this.name, null);
+        this.formRes.$regItemValidator(this.name, null);
     },
 };
 </script>
\ No newline at end of file
diff --git a/forms/imagePicker/CImagePicker.vue b/forms/imagePicker/CImagePicker.vue
index 1ed467b..0e32565 100644
--- a/forms/imagePicker/CImagePicker.vue
+++ b/forms/imagePicker/CImagePicker.vue
@@ -7,10 +7,10 @@
     <view class="c-image-picker">
         <AtInput
             ref="input"
-            :name="itemData.name"
-            :title="itemData.label"
-            :required="itemData.required"
-            :error="itemData.error"
+            :name="itemRes.name"
+            :title="itemRes.label"
+            :required="itemRes.required"
+            :error="itemRes.error"
         />
         <AtImagePicker
             ref="picker"
@@ -51,7 +51,7 @@
         AtCurtain,
     },
     props: {
-        itemData: Object,
+        itemRes: Object,
     },
     data() {
         return {
@@ -61,7 +61,7 @@
     },
     computed: {
         files() {
-            const value = this.itemData.formData[this.itemData.name];
+            const value = this.itemRes.formData[this.itemRes.name];
             let files = [];
             if (Object.prototype.toString.call(value) === '[object String]') {
                 files = value.split(',').map((url) => ({ url }));
@@ -83,7 +83,7 @@
                 }
                 value.push(file.url);
             });
-            this.itemData.onChange(value);
+            this.itemRes.onChange(value);
         },
         handleImgView(index, file) {
             this.curtainImg = file.url;
diff --git a/forms/input/CInput.vue b/forms/input/CInput.vue
index c09081c..86acc3e 100644
--- a/forms/input/CInput.vue
+++ b/forms/input/CInput.vue
@@ -5,14 +5,14 @@
 
 <template>
     <AtInput
-        :name="itemData.name"
-        :title="itemData.label"
+        :name="itemRes.name"
+        :title="itemRes.label"
         :type="type"
         :placeholder="placeholder"
-        :required="itemData.required"
-        :error="itemData.error"
-        :value="itemData.formData[itemData.name]"
-        :onChange="evt=>itemData.onChange(evt)"
+        :required="itemRes.required"
+        :error="itemRes.error"
+        :value="itemRes.formData[itemRes.name]"
+        :onChange="evt=>itemRes.onChange(evt)"
     >
         <slot />
     </AtInput>
@@ -29,7 +29,8 @@
     props: {
         type: String,
         placeholder: String,
-        itemData: Object,
+        itemRes: Object,
     },
+    mounted() {},
 };
 </script>
\ No newline at end of file
diff --git a/forms/input/CInputPhoneCode.vue b/forms/input/CInputPhoneCode.vue
index 161473e..9f00532 100644
--- a/forms/input/CInputPhoneCode.vue
+++ b/forms/input/CInputPhoneCode.vue
@@ -6,14 +6,14 @@
 <template>
     <view class="c-input-phone-code">
         <AtInput
-            :name="itemData.name"
-            :title="itemData.label"
+            :name="itemRes.name"
+            :title="itemRes.label"
             type="text"
             :placeholder="placeholder"
-            :required="itemData.required"
-            :error="itemData.error"
-            :value="itemData.formData[itemData.name]"
-            :onChange="evt=>itemData.onChange(evt)"
+            :required="itemRes.required"
+            :error="itemRes.error"
+            :value="itemRes.formData[itemRes.name]"
+            :onChange="evt=>itemRes.onChange(evt)"
         >
             <AtButton
                 class="c-input-phone-btn"
@@ -38,7 +38,8 @@
     },
     props: {
         placeholder: String,
-        itemData: Object,
+        itemRes: Object,
+        autoStart: { type: Boolean, default: true },
         onCallPhoneCode: Function,
     },
     data() {
@@ -61,16 +62,25 @@
             if (this.holding) {
                 return;
             }
-            this.holding = true;
-            this.cd = 60;
-            this.onCallPhoneCode();
-            const timer = setInterval(() => {
-                this.cd -= 1;
-                if (this.cd === 0) {
-                    this.holding = false;
-                    this.cd = 60;
-                }
-            }, 1000);
+            const startCD = () => {
+                this.holding = true;
+                this.cd = 60;
+                setInterval(() => {
+                    this.cd -= 1;
+                    if (this.cd === 0) {
+                        this.holding = false;
+                        this.cd = 60;
+                    }
+                }, 1000);
+            };
+            if (this.autoStart) {
+                startCD();
+                this.onCallPhoneCode(() => null);
+            } else {
+                this.onCallPhoneCode(() => {
+                    startCD();
+                });
+            }
         },
     },
 };
diff --git a/forms/select/CSelect.vue b/forms/select/CSelect.vue
index c7f0381..eb3e4d2 100644
--- a/forms/select/CSelect.vue
+++ b/forms/select/CSelect.vue
@@ -14,10 +14,10 @@
         >
             <AtInput
                 ref="input"
-                :name="itemData.name"
-                :title="itemData.label"
-                :required="itemData.required"
-                :error="itemData.error"
+                :name="itemRes.name"
+                :title="itemRes.label"
+                :required="itemRes.required"
+                :error="itemRes.error"
                 :placeholder="placeholder"
                 :value="selected"
             >
@@ -39,7 +39,7 @@
     props: {
         options: Array,
         placeholder: String,
-        itemData: Object,
+        itemRes: Object,
     },
     data() {
         return {};
@@ -49,7 +49,7 @@
             return typeof (this.options[0] || {}).value === 'undefined' ? 'id' : 'value';
         },
         current() {
-            const curVal = this.itemData.formData[this.itemData.name];
+            const curVal = this.itemRes.formData[this.itemRes.name];
             for (let i = 0, item; (item = this.options[i]); i++) {
                 if (curVal === item[this.optionKey]) {
                     return i;
@@ -58,7 +58,7 @@
             return -1;
         },
         selected() {
-            const curVal = this.itemData.formData[this.itemData.name];
+            const curVal = this.itemRes.formData[this.itemRes.name];
             for (let i = 0, item; (item = this.options[i]); i++) {
                 if (curVal === item[this.optionKey]) {
                     return item.name;
@@ -70,7 +70,7 @@
     methods: {
         handleChange(evt) {
             const item = this.options[evt.value];
-            this.itemData.onChange(item[this.optionKey]);
+            this.itemRes.onChange(item[this.optionKey]);
         },
     },
     mounted() {},
diff --git a/forms/switch/CSwitch.vue b/forms/switch/CSwitch.vue
index 6171b54..a5620c1 100644
--- a/forms/switch/CSwitch.vue
+++ b/forms/switch/CSwitch.vue
@@ -6,9 +6,9 @@
 <template>
     <view :class="['c-switch', className]">
         <AtSwitch
-            :title="itemData.label"
-            :checked="itemData.formData[itemData.name]"
-            :onChange="evt=>itemData.onChange(evt)"
+            :title="itemRes.label"
+            :checked="itemRes.formData[itemRes.name]"
+            :onChange="evt=>itemRes.onChange(evt)"
         />
     </view>
 </template>
@@ -21,7 +21,7 @@
     name: 'CSwitch',
     components: { AtSwitch },
     props: {
-        itemData: Object,
+        itemRes: Object,
     },
     data() {
         return {};
@@ -29,10 +29,10 @@
     computed: {
         className() {
             let className = '';
-            if (this.itemData.required) {
+            if (this.itemRes.required) {
                 className += 'c-switch-required ';
             }
-            if (this.itemData.error) {
+            if (this.itemRes.error) {
                 className += 'c-switch-error ';
             }
             return className;
diff --git a/forms/textarea/CTextArea.vue b/forms/textarea/CTextArea.vue
index 3a592ba..69f97d8 100644
--- a/forms/textarea/CTextArea.vue
+++ b/forms/textarea/CTextArea.vue
@@ -7,19 +7,19 @@
     <view class="c-textarea">
         <AtInput
             ref="input"
-            :name="itemData.name"
-            :title="itemData.label"
-            :required="itemData.required"
-            :error="itemData.error"
+            :name="itemRes.name"
+            :title="itemRes.label"
+            :required="itemRes.required"
+            :error="itemRes.error"
         />
         <textarea
             ref="textarea"
             class="textarea"
             :style="{height: height || '2rem'}"
             :placeholder="placeholder"
-            :value="itemData.formData[itemData.name]"
+            :value="itemRes.formData[itemRes.name]"
             :autoFocus="true"
-            @input="evt=>itemData.onChange(evt.detail.value)"
+            @input="evt=>itemRes.onChange(evt.detail.value)"
         />
     </view>
 </template>
@@ -37,7 +37,7 @@
     props: {
         height: String,
         placeholder: String,
-        itemData: Object,
+        itemRes: Object,
     },
     data() {
         return {};

--
Gitblit v1.9.1