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