| <template> | 
|     <view class="uni-forms"> | 
|         <form> | 
|             <slot></slot> | 
|         </form> | 
|     </view> | 
| </template> | 
|   | 
| <script> | 
|     import Validator from './validate.js'; | 
|     import { | 
|         deepCopy, | 
|         getValue, | 
|         isRequiredField, | 
|         setDataValue, | 
|         getDataValue, | 
|         realName, | 
|         isRealName, | 
|         rawData, | 
|         isEqual | 
|     } from './utils.js' | 
|   | 
|     // #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 | 
|     /** | 
|      * Forms 表单 | 
|      * @description 由输入框、选择器、单选框、多选框等控件组成,用以收集、校验、提交数据 | 
|      * @tutorial https://ext.dcloud.net.cn/plugin?id=2773 | 
|      * @property {Object} rules    表单校验规则 | 
|      * @property {String} validateTrigger = [bind|submit|blur]    校验触发器方式 默认 submit | 
|      * @value bind        发生变化时触发 | 
|      * @value submit    提交时触发 | 
|      * @value blur      失去焦点时触发 | 
|      * @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    提交时触发 | 
|      * @event {Function} validate    校验结果发生变化触发 | 
|      */ | 
|     export default { | 
|         name: 'uniForms', | 
|         emits: ['validate', 'submit'], | 
|         options: { | 
|             virtualHost: true | 
|         }, | 
|         props: { | 
|             // 即将弃用 | 
|             value: { | 
|                 type: Object, | 
|                 default () { | 
|                     return null; | 
|                 } | 
|             }, | 
|             // vue3 替换 value 属性 | 
|             modelValue: { | 
|                 type: Object, | 
|                 default () { | 
|                     return null; | 
|                 } | 
|             }, | 
|             // 1.4.0 开始将不支持 v-model ,且废弃 value 和 modelValue | 
|             model: { | 
|                 type: Object, | 
|                 default () { | 
|                     return null; | 
|                 } | 
|             }, | 
|             // 表单校验规则 | 
|             rules: { | 
|                 type: Object, | 
|                 default () { | 
|                     return {}; | 
|                 } | 
|             }, | 
|             //校验错误信息提示方式 默认 undertext 取值 [undertext|toast|modal] | 
|             errShowType: { | 
|                 type: String, | 
|                 default: 'undertext' | 
|             }, | 
|             // 校验触发器方式 默认 bind 取值 [bind|submit] | 
|             validateTrigger: { | 
|                 type: String, | 
|                 default: 'submit' | 
|             }, | 
|             // label 位置,默认 left 取值  top/left | 
|             labelPosition: { | 
|                 type: String, | 
|                 default: 'left' | 
|             }, | 
|             // label 宽度 | 
|             labelWidth: { | 
|                 type: [String, Number], | 
|                 default: '' | 
|             }, | 
|             // label 居中方式,默认 left 取值 left/center/right | 
|             labelAlign: { | 
|                 type: String, | 
|                 default: 'left' | 
|             }, | 
|             border: { | 
|                 type: Boolean, | 
|                 default: false | 
|             } | 
|         }, | 
|         provide() { | 
|             return { | 
|                 uniForm: this | 
|             } | 
|         }, | 
|         data() { | 
|             return { | 
|                 // 表单本地值的记录,不应该与传如的值进行关联 | 
|                 formData: {}, | 
|                 formRules: {} | 
|             }; | 
|         }, | 
|         computed: { | 
|             // 计算数据源变化的 | 
|             localData() { | 
|                 const localVal = this.model || this.modelValue || this.value | 
|                 if (localVal) { | 
|                     return deepCopy(localVal) | 
|                 } | 
|                 return {} | 
|             } | 
|         }, | 
|         watch: { | 
|             // 监听数据变化 ,暂时不使用,需要单独赋值 | 
|             // localData: {}, | 
|             // 监听规则变化 | 
|             rules: { | 
|                 handler: function(val, oldVal) { | 
|                     this.setRules(val) | 
|                 }, | 
|                 deep: true, | 
|                 immediate: true | 
|             } | 
|         }, | 
|         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 | 
|   | 
|             // 子组件实例数组 | 
|             this.childrens = [] | 
|             // TODO 兼容旧版 uni-data-picker ,新版本中无效,只是避免报错 | 
|             this.inputChildrens = [] | 
|             this.setRules(this.rules) | 
|         }, | 
|         methods: { | 
|             /** | 
|              * 外部调用方法 | 
|              * 设置规则 ,主要用于小程序自定义检验规则 | 
|              * @param {Array} rules 规则源数据 | 
|              */ | 
|             setRules(rules) { | 
|                 // TODO 有可能子组件合并规则的时机比这个要早,所以需要合并对象 ,而不是直接赋值,可能会被覆盖 | 
|                 this.formRules = Object.assign({}, this.formRules, rules) | 
|                 // 初始化校验函数 | 
|                 this.validator = new Validator(rules); | 
|             }, | 
|   | 
|             /** | 
|              * 外部调用方法 | 
|              * 设置数据,用于设置表单数据,公开给用户使用 , 不支持在动态表单中使用 | 
|              * @param {Object} key | 
|              * @param {Object} value | 
|              */ | 
|             setValue(key, value) { | 
|                 let example = this.childrens.find(child => child.name === key); | 
|                 if (!example) return null; | 
|                 this.formData[key] = getValue(key, value, (this.formRules[key] && this.formRules[key].rules) || []) | 
|                 return example.onFieldChange(this.formData[key]); | 
|             }, | 
|   | 
|             /** | 
|              * 外部调用方法 | 
|              * 手动提交校验表单 | 
|              * 对整个表单进行校验的方法,参数为一个回调函数。 | 
|              * @param {Array} keepitem 保留不参与校验的字段 | 
|              * @param {type} callback 方法回调 | 
|              */ | 
|             validate(keepitem, callback) { | 
|                 return this.checkAll(this.formData, keepitem, callback); | 
|             }, | 
|   | 
|             /** | 
|              * 外部调用方法 | 
|              * 部分表单校验 | 
|              * @param {Array|String} props 需要校验的字段 | 
|              * @param {Function} 回调函数 | 
|              */ | 
|             validateField(props = [], callback) { | 
|                 props = [].concat(props); | 
|                 let invalidFields = {}; | 
|                 this.childrens.forEach(item => { | 
|                     const name = realName(item.name) | 
|                     if (props.indexOf(name) !== -1) { | 
|                         invalidFields = Object.assign({}, invalidFields, { | 
|                             [name]: this.formData[name] | 
|                         }); | 
|                     } | 
|                 }); | 
|                 return this.checkAll(invalidFields, [], callback); | 
|             }, | 
|   | 
|             /** | 
|              * 外部调用方法 | 
|              * 移除表单项的校验结果。传入待移除的表单项的 prop 属性或者 prop 组成的数组,如不传则移除整个表单的校验结果 | 
|              * @param {Array|String} props 需要移除校验的字段 ,不填为所有 | 
|              */ | 
|             clearValidate(props = []) { | 
|                 props = [].concat(props); | 
|                 this.childrens.forEach(item => { | 
|                     if (props.length === 0) { | 
|                         item.errMsg = ''; | 
|                     } else { | 
|                         const name = realName(item.name) | 
|                         if (props.indexOf(name) !== -1) { | 
|                             item.errMsg = ''; | 
|                         } | 
|                     } | 
|                 }); | 
|             }, | 
|   | 
|             /** | 
|              * 外部调用方法 ,即将废弃 | 
|              * 手动提交校验表单 | 
|              * 对整个表单进行校验的方法,参数为一个回调函数。 | 
|              * @param {Array} keepitem 保留不参与校验的字段 | 
|              * @param {type} callback 方法回调 | 
|              */ | 
|             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.checkAll(this.formData, keepitem, callback, 'submit'); | 
|             }, | 
|   | 
|             // 校验所有 | 
|             async checkAll(invalidFields, keepitem, callback, type) { | 
|                 // 不存在校验规则 ,则停止校验流程 | 
|                 if (!this.validator) return | 
|                 let childrens = [] | 
|                 // 处理参与校验的item实例 | 
|                 for (let i in invalidFields) { | 
|                     const item = this.childrens.find(v => realName(v.name) === i) | 
|                     if (item) { | 
|                         childrens.push(item) | 
|                     } | 
|                 } | 
|   | 
|                 // 如果validate第一个参数是funciont ,那就走回调 | 
|                 if (!callback && typeof keepitem === 'function') { | 
|                     callback = keepitem; | 
|                 } | 
|   | 
|                 let promise; | 
|                 // 如果不存在回调,那么使用 Promise 方式返回 | 
|                 if (!callback && typeof callback !== 'function' && Promise) { | 
|                     promise = new Promise((resolve, reject) => { | 
|                         callback = function(valid, invalidFields) { | 
|                             !valid ? resolve(invalidFields) : reject(valid); | 
|                         }; | 
|                     }); | 
|                 } | 
|   | 
|                 let results = []; | 
|                 // 避免引用错乱 ,建议拷贝对象处理 | 
|                 let tempFormData = JSON.parse(JSON.stringify(invalidFields)) | 
|                 // 所有子组件参与校验,使用 for 可以使用  awiat | 
|                 for (let i in childrens) { | 
|                     const child = childrens[i] | 
|                     let name = realName(child.name); | 
|                     const result = await child.onFieldChange(tempFormData[name]); | 
|                     if (result) { | 
|                         results.push(result); | 
|                         // toast ,modal 只需要执行第一次就可以 | 
|                         if (this.errShowType === 'toast' || this.errShowType === 'modal') break; | 
|                     } | 
|                 } | 
|   | 
|   | 
|                 if (Array.isArray(results)) { | 
|                     if (results.length === 0) results = null; | 
|                 } | 
|                 if (Array.isArray(keepitem)) { | 
|                     keepitem.forEach(v => { | 
|                         let vName = realName(v); | 
|                         let value = getDataValue(v, this.localData) | 
|                         if (value !== undefined) { | 
|                             tempFormData[vName] = value | 
|                         } | 
|                     }); | 
|                 } | 
|   | 
|                 // TODO submit 即将废弃 | 
|                 if (type === 'submit') { | 
|                     this.$emit('submit', { | 
|                         detail: { | 
|                             value: tempFormData, | 
|                             errors: results | 
|                         } | 
|                     }); | 
|                 } else { | 
|                     this.$emit('validate', results); | 
|                 } | 
|   | 
|                 // const resetFormData = rawData(tempFormData, this.localData, this.name) | 
|                 let resetFormData = {} | 
|                 resetFormData = rawData(tempFormData, this.name) | 
|                 callback && typeof callback === 'function' && callback(results, resetFormData); | 
|   | 
|                 if (promise && callback) { | 
|                     return promise; | 
|                 } else { | 
|                     return null; | 
|                 } | 
|   | 
|             }, | 
|   | 
|             /** | 
|              * 返回validate事件 | 
|              * @param {Object} result | 
|              */ | 
|             validateCheck(result) { | 
|                 this.$emit('validate', result); | 
|             }, | 
|             _getValue: getValue, | 
|             _isRequiredField: isRequiredField, | 
|             _setDataValue: setDataValue, | 
|             _getDataValue: getDataValue, | 
|             _realName: realName, | 
|             _isRealName: isRealName, | 
|             _isEqual: isEqual | 
|         } | 
|     }; | 
| </script> | 
|   | 
| <style lang="scss"> | 
|     .uni-forms {} | 
| </style> |