From cf11971242e165adf92ef152cee188c0f1fbf70b Mon Sep 17 00:00:00 2001
From: Tevin <tingquanren@163.com>
Date: Wed, 25 Nov 2020 11:45:37 +0800
Subject: [PATCH] 实现表单第一部分:组件基本架构、表单验证

---
 forms/form/CFormItem.vue   |  114 ++++++++++++++++++++++
 forms/form/index.js        |   12 ++
 forms/form/validateMsgs.js |   51 ++++++++++
 forms/form/CForm.vue       |   65 +++++++++++++
 4 files changed, 242 insertions(+), 0 deletions(-)

diff --git a/forms/form/CForm.vue b/forms/form/CForm.vue
new file mode 100644
index 0000000..db0608b
--- /dev/null
+++ b/forms/form/CForm.vue
@@ -0,0 +1,65 @@
+/**
+ * CForm
+ * @author Tevin
+ */
+
+<template>
+    <form
+        class="c-form"
+        @submit="evt=>handleSubmit()"
+    >
+        <slot :formData="formData" />
+    </form>
+</template>
+
+<script>
+import Taro from '@tarojs/taro';
+
+export default {
+    name: 'CForm',
+    props: {
+        formData: Object,
+        onChange: Function,
+        onFinish: Function,
+    },
+    data() {
+        return {
+            validators: {},
+        };
+    },
+    methods: {
+        handleSubmit() {
+            const checklist = [];
+            Object.keys(this.validators).forEach((key) => {
+                checklist.push(this.validators[key]());
+            });
+            Promise.all(checklist).then((validations) => {
+                for (let validation of validations) {
+                    if (!validation.passed) {
+                        Taro.showToast({
+                            title: validation.msg,
+                            icon: 'none',
+                            mask: true,
+                            duration: 2000,
+                        });
+                        return;
+                    }
+                }
+                // 所有检查通过
+                this.onFinish && this.onFinish();
+            });
+        },
+    },
+    mounted() {
+        this.formData.$handleChange = (evt) => {
+            this.onChange && this.onChange(evt);
+        };
+        this.formData.$regItemValidator = (name, cb) => {
+            this.validators[name] = cb;
+        };
+    },
+    beforeDestroy() {
+        this.validators = {};
+    },
+};
+</script>
\ No newline at end of file
diff --git a/forms/form/CFormItem.vue b/forms/form/CFormItem.vue
new file mode 100644
index 0000000..a8e57fc
--- /dev/null
+++ b/forms/form/CFormItem.vue
@@ -0,0 +1,114 @@
+/**
+ * CFormItem
+ * @author Tevin
+ * @tutorial rules see https://github.com/yiminghe/async-validator#type
+ */
+
+<template>
+    <view class="c-form-item">
+        <view class="c-form-item-label">
+            <text
+                class="required"
+                v-if="required"
+            >*</text>
+            <text>{{label || name}}</text>
+        </view>
+        <slot
+            :value="formData[name]"
+            :onChange="evt=>onChange(evt)"
+        />
+    </view>
+</template>
+
+<script>
+import Schema from 'async-validator';
+import { validateMsgs } from './validateMsgs.js';
+
+export default {
+    name: 'CFormItem',
+    props: {
+        name: String,
+        label: String,
+        required: Boolean,
+        rules: Array,
+        formData: Object,
+    },
+    data() {
+        return {};
+    },
+    methods: {
+        onChange(evt) {
+            // 当类型为 object 时,必须为简单 object
+            if (Object.prototype.toString.call(evt) === '[object Object]') {
+                const hasOwn = Object.prototype.hasOwnProperty;
+                if (
+                    evt.constructor &&
+                    !hasOwn.call(evt, 'constructor') &&
+                    !hasOwn.call(evt.constructor.prototype, 'isPrototypeOf')
+                ) {
+                    throw new Error('错误的表单项 onChange 参数类型!(At: ' + this.name + ')');
+                    return;
+                }
+            }
+            // 未改变值不触发
+            if (this.formData[this.name] === evt) {
+                return;
+            }
+            this.formData.$handleChange({
+                [this.name]: evt,
+            });
+        },
+    },
+    mounted() {
+        this.$nextTick(() => {
+            // 未设置验证
+            if (!this.required && !this.rules) {
+                this.formData.$regItemValidator(this.name, () => {
+                    return Promise.resolve({
+                        name: this.name,
+                        passed: true,
+                    });
+                });
+            } else {
+                const descriptor = this.rules || [];
+                if (this.required) {
+                    descriptor.unshift({
+                        required: true,
+                    });
+                }
+                const validator = new Schema({
+                    [this.name]: descriptor,
+                });
+                validator.messages(validateMsgs); // 自定义验证消息
+                this.formData.$regItemValidator(this.name, () => {
+                    return validator
+                        .validate({
+                            [this.name]: this.formData[this.name],
+                        })
+                        .then(
+                            (res) => {
+                                return {
+                                    name: this.name,
+                                    passed: true,
+                                };
+                            },
+                            ({ errors, fields }) => {
+                                return {
+                                    name: this.name,
+                                    passed: false,
+                                    msg: errors[0].message.replace(
+                                        this.name,
+                                        this.label || this.name
+                                    ),
+                                };
+                            }
+                        );
+                });
+            }
+        });
+    },
+    beforeDestroy() {
+        this.formData.$regItemValidator(this.name, null);
+    },
+};
+</script>
\ No newline at end of file
diff --git a/forms/form/index.js b/forms/form/index.js
new file mode 100644
index 0000000..7176d3e
--- /dev/null
+++ b/forms/form/index.js
@@ -0,0 +1,12 @@
+/**
+ * form
+ * @author Tevin
+ */
+
+import CForm from '@components/forms/form/CForm.vue';
+import CFormItem from '@components/forms/form/CFormItem.vue';
+
+export {
+    CForm,
+    CFormItem,
+}
\ No newline at end of file
diff --git a/forms/form/validateMsgs.js b/forms/form/validateMsgs.js
new file mode 100644
index 0000000..661f123
--- /dev/null
+++ b/forms/form/validateMsgs.js
@@ -0,0 +1,51 @@
+/**
+ * custom validate message
+ */
+
+export const validateMsgs = {
+    default: '字段验证错误 %s',
+    required: '请输入%s',
+    enum: '%s必须是%s其中一个',
+    whitespace: '%s不能为空字符',
+    date: {
+        format: '%s日期格式无效',
+        parse: '%s不能转换为日期',
+        invalid: '%s是一个无效日期',
+    },
+    types: {
+        string: '%s不是一个有效的字符串',
+        method: '%s不是一个有效的函数',
+        array: '%s不是一个有效的数组',
+        object: '%s不是一个有效的对象',
+        number: '%s不是一个有效的数值',
+        date: '%s不是一个有效的日期',
+        boolean: '%s不是一个有效的布尔值',
+        integer: '%s不是一个有效的整数',
+        float: '%s不是一个有效的浮点数',
+        regexp: '%s不是一个有效的正则表达式',
+        email: '%s不是一个有效的邮件',
+        url: '%s不是一个有效的网址',
+        hex: '%s不是一个有效的16进制字符',
+    },
+    string: {
+        len: '%s须为%s个字符',
+        min: '%s最少%s个字符',
+        max: '%s最多%s个字符',
+        range: '%s字符数须在%s到%s之间',
+    },
+    number: {
+        len: '%s必须等于%s',
+        min: '%s最小值为%s',
+        max: '%s最大值为%s',
+        range: '%s须在%s到%s之间',
+    },
+    array: {
+        len: '%s的数量必须等于%s',
+        min: '%s的数量不能少于%s',
+        max: '%s的数量不能超过%s',
+        range: '%s数量须在%s到%s之间',
+    },
+    pattern: {
+        mismatch: '%s输入格式有误',
+    },
+};
\ No newline at end of file

--
Gitblit v1.9.1