| <template> | 
|     <view class="uni-forms" :class="{ 'uni-forms--top': !border }"> | 
|         <form @submit.stop="submitForm" @reset="resetForm"> | 
|             <slot></slot> | 
|         </form> | 
|     </view> | 
| </template> | 
|   | 
| <script> | 
|     // #ifndef VUE3 | 
|     import Vue from 'vue'; | 
|     Vue.prototype.binddata = function(name, value, formName) { | 
|         if (formName) { | 
|             this.$refs[formName].setValue(name, value); | 
|         } else { | 
|             let formVm; | 
|             for (let i in this.$refs) { | 
|                 const vm = this.$refs[i]; | 
|                 if (vm && vm.$options && vm.$options.name === 'uniForms') { | 
|                     formVm = vm; | 
|                     break; | 
|                 } | 
|             } | 
|             if (!formVm) return console.error('当前 uni-froms 组件缺少 ref 属性'); | 
|             formVm.setValue(name, value); | 
|         } | 
|     }; | 
|     // #endif | 
|   | 
|   | 
|   | 
|     import Validator from './validate.js'; | 
|     /** | 
|      * Forms 表单 | 
|      * @description 由输入框、选择器、单选框、多选框等控件组成,用以收集、校验、提交数据 | 
|      * @tutorial https://ext.dcloud.net.cn/plugin?id=2773 | 
|      * @property {Object} rules    表单校验规则 | 
|      * @property {String} validateTrigger = [bind|submit]    校验触发器方式 默认 submit | 
|      * @value bind        发生变化时触发 | 
|      * @value submit    提交时触发 | 
|      * @property {String} labelPosition = [top|left]    label 位置 默认 left | 
|      * @value top        顶部显示 label | 
|      * @value left    左侧显示 label | 
|      * @property {String} labelWidth    label 宽度,默认 65px | 
|      * @property {String} labelAlign = [left|center|right]    label 居中方式  默认 left | 
|      * @value left        label 左侧显示 | 
|      * @value center    label 居中 | 
|      * @value right        label 右侧对齐 | 
|      * @property {String} errShowType = [undertext|toast|modal]    校验错误信息提示方式 | 
|      * @value undertext    错误信息在底部显示 | 
|      * @value toast            错误信息toast显示 | 
|      * @value modal            错误信息modal显示 | 
|      * @event {Function} submit    提交时触发 | 
|      */ | 
|   | 
|     export default { | 
|         name: 'uniForms', | 
|         components: {}, | 
|         emits:['input','reset','validate','submit'], | 
|         props: { | 
|             // 即将弃用 | 
|             value: { | 
|                 type: Object, | 
|                 default () { | 
|                     return {}; | 
|                 } | 
|             }, | 
|             // 替换 value 属性 | 
|             modelValue: { | 
|                 type: Object, | 
|                 default () { | 
|                     return {}; | 
|                 } | 
|             }, | 
|             // 表单校验规则 | 
|             rules: { | 
|                 type: Object, | 
|                 default () { | 
|                     return {}; | 
|                 } | 
|             }, | 
|             // 校验触发器方式,默认 关闭 | 
|             validateTrigger: { | 
|                 type: String, | 
|                 default: '' | 
|             }, | 
|             // label 位置,可选值 top/left | 
|             labelPosition: { | 
|                 type: String, | 
|                 default: 'left' | 
|             }, | 
|             // label 宽度,单位 px | 
|             labelWidth: { | 
|                 type: [String, Number], | 
|                 default: '' | 
|             }, | 
|             // label 居中方式,可选值 left/center/right | 
|             labelAlign: { | 
|                 type: String, | 
|                 default: 'left' | 
|             }, | 
|             errShowType: { | 
|                 type: String, | 
|                 default: 'undertext' | 
|             }, | 
|             border: { | 
|                 type: Boolean, | 
|                 default: false | 
|             } | 
|         }, | 
|         data() { | 
|             return { | 
|                 formData: {} | 
|             }; | 
|         }, | 
|         computed: { | 
|             dataValue() { | 
|                 if (JSON.stringify(this.modelValue) === '{}') { | 
|                     return this.value | 
|                 } else { | 
|                     return this.modelValue | 
|                 } | 
|             } | 
|         }, | 
|         watch: { | 
|             rules(newVal) { | 
|                 // 如果规则发生变化,要初始化组件 | 
|                 this.init(newVal); | 
|             }, | 
|             labelPosition() { | 
|                 this.childrens.forEach(vm => { | 
|                     vm.init() | 
|                 }) | 
|             } | 
|         }, | 
|         created() { | 
|             // #ifdef VUE3 | 
|             let getbinddata = getApp().$vm.$.appContext.config.globalProperties.binddata | 
|             if (!getbinddata) { | 
|                 getApp().$vm.$.appContext.config.globalProperties.binddata = function(name, value, formName) { | 
|                     if (formName) { | 
|                         this.$refs[formName].setValue(name, value); | 
|                     } else { | 
|                         let formVm; | 
|                         for (let i in this.$refs) { | 
|                             const vm = this.$refs[i]; | 
|                             if (vm && vm.$options && vm.$options.name === 'uniForms') { | 
|                                 formVm = vm; | 
|                                 break; | 
|                             } | 
|                         } | 
|                         if (!formVm) return console.error('当前 uni-froms 组件缺少 ref 属性'); | 
|                         formVm.setValue(name, value); | 
|                     } | 
|                 } | 
|             } | 
|             // #endif | 
|   | 
|             // 存放watch 监听数组 | 
|             this.unwatchs = []; | 
|             // 存放子组件数组 | 
|             this.childrens = []; | 
|             // 存放 easyInput 组件 | 
|             this.inputChildrens = []; | 
|             // 存放 dataCheckbox 组件 | 
|             this.checkboxChildrens = []; | 
|             // 存放规则 | 
|             this.formRules = []; | 
|             this.init(this.rules); | 
|         }, | 
|         // mounted() { | 
|         //     this.init(this.rules) | 
|         // }, | 
|         methods: { | 
|             init(formRules) { | 
|                 // 判断是否有规则 | 
|                 if (Object.keys(formRules).length === 0) { | 
|                     this.formData = this.dataValue | 
|                     return | 
|                 }; | 
|                 this.formRules = formRules; | 
|                 this.validator = new Validator(formRules); | 
|                 this.registerWatch(); | 
|             }, | 
|             // 监听 watch | 
|             registerWatch() { | 
|                 // 取消监听,避免多次调用 init 重复执行 $watch | 
|                 this.unwatchs.forEach(v => v()); | 
|                 this.childrens.forEach((v) => { | 
|                     v.init() | 
|                 }) | 
|                 // watch 每个属性 ,需要知道具体那个属性发变化 | 
|                 Object.keys(this.dataValue).forEach(key => { | 
|                     let watch = this.$watch( | 
|                         'dataValue.' + key, | 
|                         value => { | 
|                             if (!value) return | 
|                             // 如果是对象 ,则平铺内容 | 
|                             if (value.toString() === '[object Object]') { | 
|                                 for (let i in value) { | 
|                                     let name = `${key}[${i}]`; | 
|                                     this.formData[name] = this._getValue(name, value[i]); | 
|                                 } | 
|                             } else { | 
|                                 this.formData[key] = this._getValue(key, value); | 
|                             } | 
|                         }, | 
|                         { | 
|                             deep: true, | 
|                             immediate: true | 
|                         } | 
|                     ); | 
|                     this.unwatchs.push(watch); | 
|                 }); | 
|             }, | 
|             /** | 
|              * 公开给用户使用 | 
|              * 设置校验规则 | 
|              * @param {Object} formRules | 
|              */ | 
|             setRules(formRules) { | 
|                 this.init(formRules); | 
|             }, | 
|             /** | 
|              * 公开给用户使用 | 
|              * 设置自定义表单组件 value 值 | 
|              *  @param {String} name 字段名称 | 
|              *  @param {String} value 字段值 | 
|              */ | 
|             setValue(name, value, callback) { | 
|                 let example = this.childrens.find(child => child.name === name); | 
|                 if (!example) return null; | 
|                 value = this._getValue(example.name, value); | 
|                 this.formData[name] = value; | 
|                 example.val = value; | 
|                 return example.triggerCheck(value, callback); | 
|             }, | 
|   | 
|             /** | 
|              * 表单重置 | 
|              * @param {Object} event | 
|              */ | 
|             resetForm(event) { | 
|                 this.childrens.forEach(item => { | 
|                     item.errMsg = ''; | 
|                     const inputComp = this.inputChildrens.find(child => child.rename === item.name); | 
|                     if (inputComp) { | 
|                         inputComp.errMsg = ''; | 
|                         // fix by mehaotian 不触发其他组件的 setValue | 
|                         inputComp.is_reset = true | 
|                         inputComp.$emit('input', inputComp.multiple ? [] : ''); | 
|                         inputComp.$emit('update:modelValue', inputComp.multiple ? [] : ''); | 
|                     } | 
|                 }); | 
|   | 
|                 this.childrens.forEach(item => { | 
|                     if (item.name) { | 
|                         this.formData[item.name] = this._getValue(item.name, ''); | 
|                     } | 
|                 }); | 
|   | 
|                 this.$emit('reset', event); | 
|             }, | 
|   | 
|             /** | 
|              * 触发表单校验,通过 @validate 获取 | 
|              * @param {Object} validate | 
|              */ | 
|             validateCheck(validate) { | 
|                 if (validate === null) validate = null; | 
|                 this.$emit('validate', validate); | 
|             }, | 
|             /** | 
|              * 校验所有或者部分表单 | 
|              */ | 
|             async validateAll(invalidFields, type, keepitem, callback) { | 
|                 let childrens = [] | 
|                 for (let i in invalidFields) { | 
|                     const item = this.childrens.find(v => v.name === i) | 
|                     if (item) { | 
|                         childrens.push(item) | 
|                     } | 
|                 } | 
|   | 
|                 if (!callback && typeof keepitem === 'function') { | 
|                     callback = keepitem; | 
|                 } | 
|   | 
|                 let promise; | 
|                 if (!callback && typeof callback !== 'function' && Promise) { | 
|                     promise = new Promise((resolve, reject) => { | 
|                         callback = function(valid, invalidFields) { | 
|                             !valid ? resolve(invalidFields) : reject(valid); | 
|                         }; | 
|                     }); | 
|                 } | 
|   | 
|                 let results = []; | 
|                 let newFormData = {}; | 
|                 if (this.validator) { | 
|                     for (let key in childrens) { | 
|                         const child = childrens[key]; | 
|                         let name = child.isArray ? child.arrayField : child.name; | 
|                         if (child.isArray) { | 
|                             if (child.name.indexOf('[') !== -1 && child.name.indexOf(']') !== -1) { | 
|                                 const fieldData = child.name.split('['); | 
|                                 const fieldName = fieldData[0]; | 
|                                 const fieldValue = fieldData[1].replace(']', ''); | 
|                                 if (!newFormData[fieldName]) { | 
|                                     newFormData[fieldName] = {}; | 
|                                 } | 
|                                 newFormData[fieldName][fieldValue] = this._getValue(name, invalidFields[name]); | 
|                             } | 
|                         } else { | 
|                             newFormData[name] = this._getValue(name, invalidFields[name]); | 
|                         } | 
|                         const result = await child.triggerCheck(invalidFields[name], true); | 
|                         if (result) { | 
|                             results.push(result); | 
|                             if (this.errShowType === 'toast' || this.errShowType === 'modal') break; | 
|                         } | 
|                     } | 
|                 } else { | 
|                     newFormData = invalidFields | 
|                 } | 
|                 if (Array.isArray(results)) { | 
|                     if (results.length === 0) results = null; | 
|                 } | 
|   | 
|                 if (Array.isArray(keepitem)) { | 
|                     keepitem.forEach(v => { | 
|                         newFormData[v] = this.dataValue[v]; | 
|                     }); | 
|                 } | 
|   | 
|                 if (type === 'submit') { | 
|                     this.$emit('submit', { | 
|                         detail: { | 
|                             value: newFormData, | 
|                             errors: results | 
|                         } | 
|                     }); | 
|                 } else { | 
|                     this.$emit('validate', results); | 
|                 } | 
|   | 
|                 callback && typeof callback === 'function' && callback(results, newFormData); | 
|   | 
|                 if (promise && callback) { | 
|                     return promise; | 
|                 } else { | 
|                     return null; | 
|                 } | 
|             }, | 
|             submitForm() {}, | 
|             /** | 
|              * 外部调用方法 | 
|              * 手动提交校验表单 | 
|              * 对整个表单进行校验的方法,参数为一个回调函数。 | 
|              */ | 
|             submit(keepitem, callback, type) { | 
|                 for (let i in this.dataValue) { | 
|                     const itemData = this.childrens.find(v => v.name === i); | 
|                     if (itemData) { | 
|                         if (this.formData[i] === undefined) { | 
|                             this.formData[i] = this._getValue(i, this.dataValue[i]); | 
|                         } | 
|                     } | 
|                 } | 
|                 if (!type) { | 
|                     console.warn('submit 方法即将废弃,请使用validate方法代替!'); | 
|                 } | 
|                 return this.validateAll(this.formData, 'submit', keepitem, callback); | 
|             }, | 
|   | 
|             /** | 
|              * 外部调用方法 | 
|              * 校验表单 | 
|              * 对整个表单进行校验的方法,参数为一个回调函数。 | 
|              */ | 
|             validate(keepitem, callback) { | 
|                 return this.submit(keepitem, callback, true); | 
|             }, | 
|   | 
|             /** | 
|              * 部分表单校验 | 
|              * @param {Object} props | 
|              * @param {Object} cb | 
|              */ | 
|             validateField(props, callback) { | 
|                 props = [].concat(props); | 
|                 let invalidFields = {}; | 
|                 this.childrens.forEach(item => { | 
|                     if (props.indexOf(item.name) !== -1) { | 
|                         invalidFields = Object.assign({}, invalidFields, { | 
|                             [item.name]: this.formData[item.name] | 
|                         }); | 
|                     } | 
|                 }); | 
|                 return this.validateAll(invalidFields, 'submit', [], callback); | 
|             }, | 
|   | 
|             /** | 
|              * 对整个表单进行重置,将所有字段值重置为初始值并移除校验结果 | 
|              */ | 
|             resetFields() { | 
|                 this.resetForm(); | 
|             }, | 
|   | 
|             /** | 
|              * 移除表单项的校验结果。传入待移除的表单项的 prop 属性或者 prop 组成的数组,如不传则移除整个表单的校验结果 | 
|              */ | 
|             clearValidate(props) { | 
|                 props = [].concat(props); | 
|                 this.childrens.forEach(item => { | 
|                     const inputComp = this.inputChildrens.find(child => child.rename === item.name); | 
|                     if (props.length === 0) { | 
|                         item.errMsg = ''; | 
|                         if (inputComp) { | 
|                             inputComp.errMsg = ''; | 
|                         } | 
|                     } else { | 
|                         if (props.indexOf(item.name) !== -1) { | 
|                             item.errMsg = ''; | 
|                             if (inputComp) { | 
|                                 inputComp.errMsg = ''; | 
|                             } | 
|                         } | 
|                     } | 
|                 }); | 
|             }, | 
|             /** | 
|              * 把 value 转换成指定的类型 | 
|              * @param {Object} key | 
|              * @param {Object} value | 
|              */ | 
|             _getValue(key, value) { | 
|                 const rules = (this.formRules[key] && this.formRules[key].rules) || []; | 
|                 const isRuleNum = rules.find(val => val.format && this.type_filter(val.format)); | 
|                 const isRuleBool = rules.find(val => (val.format && val.format === 'boolean') || val.format === 'bool'); | 
|                 // 输入值为 number | 
|                 if (isRuleNum) { | 
|                     value = isNaN(value) ? value : value === '' || value === null ? null : Number(value); | 
|                 } | 
|                 // 简单判断真假值 | 
|                 if (isRuleBool) { | 
|                     value = !value ? false : true; | 
|                 } | 
|                 return value; | 
|             }, | 
|             /** | 
|              * 过滤数字类型 | 
|              * @param {Object} format | 
|              */ | 
|             type_filter(format) { | 
|                 return format === 'int' || format === 'double' || format === 'number' || format === 'timestamp'; | 
|             } | 
|         } | 
|     }; | 
| </script> | 
|   | 
| <style lang="scss" > | 
|     .uni-forms { | 
|         // overflow: hidden; | 
|         // padding: 10px 15px; | 
|     } | 
|   | 
|     .uni-forms--top { | 
|         // padding: 10px 15px; | 
|         // padding-top: 22px; | 
|     } | 
| </style> |