<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> 
 |