New file |
| | |
| | | /** |
| | | * 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> |
New file |
| | |
| | | /** |
| | | * 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> |
New file |
| | |
| | | /** |
| | | * form |
| | | * @author Tevin |
| | | */ |
| | | |
| | | import CForm from '@components/forms/form/CForm.vue'; |
| | | import CFormItem from '@components/forms/form/CFormItem.vue'; |
| | | |
| | | export { |
| | | CForm, |
| | | CFormItem, |
| | | } |
New file |
| | |
| | | /** |
| | | * 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输入格式有误', |
| | | }, |
| | | }; |