<template> 
 | 
    <view class="uni-forms-item" 
 | 
        :class="['is-direction-' + localLabelPos ,border?'uni-forms-item--border':'' ,border && isFirstBorder?'is-first-border':'']"> 
 | 
        <slot name="label"> 
 | 
            <view class="uni-forms-item__label" :class="{'no-label':!label && !isRequired}" 
 | 
                :style="{width:localLabelWidth,justifyContent: localLabelAlign}"> 
 | 
                <text v-if="isRequired" class="is-required">*</text> 
 | 
                <text>{{label}}</text> 
 | 
            </view> 
 | 
        </slot> 
 | 
        <!-- #ifndef APP-NVUE --> 
 | 
        <view class="uni-forms-item__content"> 
 | 
            <slot></slot> 
 | 
            <view class="uni-forms-item__error" :class="{'msg--active':msg}"> 
 | 
                <text>{{msg}}</text> 
 | 
            </view> 
 | 
        </view> 
 | 
        <!-- #endif --> 
 | 
        <!-- #ifdef APP-NVUE --> 
 | 
        <view class="uni-forms-item__nuve-content"> 
 | 
            <view class="uni-forms-item__content"> 
 | 
                <slot></slot> 
 | 
            </view> 
 | 
            <view class="uni-forms-item__error" :class="{'msg--active':msg}"> 
 | 
                <text class="error-text">{{msg}}</text> 
 | 
            </view> 
 | 
        </view> 
 | 
        <!-- #endif --> 
 | 
    </view> 
 | 
</template> 
 | 
  
 | 
<script> 
 | 
    /** 
 | 
     * uni-fomrs-item 表单子组件 
 | 
     * @description uni-fomrs-item 表单子组件,提供了基础布局已经校验能力 
 | 
     * @tutorial https://ext.dcloud.net.cn/plugin?id=2773 
 | 
     * @property {Boolean} required 是否必填,左边显示红色"*"号 
 | 
     * @property {String }     label                 输入框左边的文字提示 
 | 
     * @property {Number }     labelWidth             label的宽度,单位px(默认65) 
 | 
     * @property {String }     labelAlign = [left|center|right] label的文字对齐方式(默认left) 
 | 
     *     @value left        label 左侧显示 
 | 
     *     @value center    label 居中 
 | 
     *     @value right    label 右侧对齐 
 | 
     * @property {String }     errorMessage         显示的错误提示内容,如果为空字符串或者false,则不显示错误信息 
 | 
     * @property {String }     name                 表单域的属性名,在使用校验规则时必填 
 | 
     * @property {String }     leftIcon             【1.4.0废弃】label左边的图标,限 uni-ui 的图标名称 
 | 
     * @property {String }     iconColor         【1.4.0废弃】左边通过icon配置的图标的颜色(默认#606266) 
 | 
     * @property {String} validateTrigger = [bind|submit|blur]    【1.4.0废弃】校验触发器方式 默认 submit 
 | 
     *     @value bind     发生变化时触发 
 | 
     *     @value submit 提交时触发 
 | 
     *     @value blur     失去焦点触发 
 | 
     * @property {String }     labelPosition = [top|left] 【1.4.0废弃】label的文字的位置(默认left) 
 | 
     *     @value top    顶部显示 label 
 | 
     *     @value left    左侧显示 label 
 | 
     */ 
 | 
  
 | 
    export default { 
 | 
        name: 'uniFormsItem', 
 | 
        options: { 
 | 
            virtualHost: true 
 | 
        }, 
 | 
        provide() { 
 | 
            return { 
 | 
                uniFormItem: this 
 | 
            } 
 | 
        }, 
 | 
        inject: { 
 | 
            form: { 
 | 
                from: 'uniForm', 
 | 
                default: null 
 | 
            }, 
 | 
        }, 
 | 
        props: { 
 | 
            // 表单校验规则 
 | 
            rules: { 
 | 
                type: Array, 
 | 
                default () { 
 | 
                    return null; 
 | 
                } 
 | 
            }, 
 | 
            // 表单域的属性名,在使用校验规则时必填 
 | 
            name: { 
 | 
                type: [String, Array], 
 | 
                default: '' 
 | 
            }, 
 | 
            required: { 
 | 
                type: Boolean, 
 | 
                default: false 
 | 
            }, 
 | 
            label: { 
 | 
                type: String, 
 | 
                default: '' 
 | 
            }, 
 | 
            // label的宽度 ,默认 80 
 | 
            labelWidth: { 
 | 
                type: [String, Number], 
 | 
                default: '' 
 | 
            }, 
 | 
            // label 居中方式,默认 left 取值 left/center/right 
 | 
            labelAlign: { 
 | 
                type: String, 
 | 
                default: '' 
 | 
            }, 
 | 
            // 强制显示错误信息 
 | 
            errorMessage: { 
 | 
                type: [String, Boolean], 
 | 
                default: '' 
 | 
            }, 
 | 
            // 1.4.0 弃用,统一使用 form 的校验时机 
 | 
            // validateTrigger: { 
 | 
            //     type: String, 
 | 
            //     default: '' 
 | 
            // }, 
 | 
            // 1.4.0 弃用,统一使用 form 的label 位置 
 | 
            // labelPosition: { 
 | 
            //     type: String, 
 | 
            //     default: '' 
 | 
            // }, 
 | 
            // 1.4.0 以下属性已经废弃,请使用  #label 插槽代替 
 | 
            leftIcon: String, 
 | 
            iconColor: { 
 | 
                type: String, 
 | 
                default: '#606266' 
 | 
            }, 
 | 
        }, 
 | 
        data() { 
 | 
            return { 
 | 
                errMsg: '', 
 | 
                isRequired: false, 
 | 
                userRules: null, 
 | 
                localLabelAlign: 'left', 
 | 
                localLabelWidth: '65px', 
 | 
                localLabelPos: 'left', 
 | 
                border: false, 
 | 
                isFirstBorder: false, 
 | 
            }; 
 | 
        }, 
 | 
        computed: { 
 | 
            // 处理错误信息 
 | 
            msg() { 
 | 
                return this.errorMessage || this.errMsg; 
 | 
            } 
 | 
        }, 
 | 
        watch: { 
 | 
            // 规则发生变化通知子组件更新 
 | 
            'form.formRules'(val) { 
 | 
                // TODO 处理头条vue3 watch不生效的问题 
 | 
                // #ifndef MP-TOUTIAO 
 | 
                this.init() 
 | 
                // #endif 
 | 
            }, 
 | 
            'form.labelWidth'(val) { 
 | 
                // 宽度 
 | 
                this.localLabelWidth = this._labelWidthUnit(val) 
 | 
  
 | 
            }, 
 | 
            'form.labelPosition'(val) { 
 | 
                // 标签位置 
 | 
                this.localLabelPos = this._labelPosition() 
 | 
            }, 
 | 
            'form.labelAlign'(val) { 
 | 
  
 | 
            } 
 | 
        }, 
 | 
        created() { 
 | 
            this.init(true) 
 | 
            if (this.name && this.form) { 
 | 
                // TODO 处理头条vue3 watch不生效的问题 
 | 
                // #ifdef MP-TOUTIAO 
 | 
                this.$watch('form.formRules', () => { 
 | 
                    this.init() 
 | 
                }) 
 | 
                // #endif 
 | 
  
 | 
                // 监听变化 
 | 
                this.$watch( 
 | 
                    () => { 
 | 
                        const val = this.form._getDataValue(this.name, this.form.localData) 
 | 
                        return val 
 | 
                    }, 
 | 
                    (value, oldVal) => { 
 | 
                        const isEqual = this.form._isEqual(value, oldVal) 
 | 
                        // 简单判断前后值的变化,只有发生变化才会发生校验 
 | 
                        // TODO  如果 oldVal = undefined ,那么大概率是源数据里没有值导致 ,这个情况不哦校验 ,可能不严谨 ,需要在做观察 
 | 
                        // fix by mehaotian 暂时取消 && oldVal !== undefined ,如果formData 中不存在,可能会不校验 
 | 
                        if (!isEqual) { 
 | 
                            const val = this.itemSetValue(value) 
 | 
                            this.onFieldChange(val, false) 
 | 
                        } 
 | 
                    }, { 
 | 
                        immediate: false 
 | 
                    } 
 | 
                ); 
 | 
            } 
 | 
  
 | 
        }, 
 | 
        // #ifndef VUE3 
 | 
        destroyed() { 
 | 
            if (this.__isUnmounted) return 
 | 
            this.unInit() 
 | 
        }, 
 | 
        // #endif 
 | 
        // #ifdef VUE3 
 | 
        unmounted() { 
 | 
            this.__isUnmounted = true 
 | 
            this.unInit() 
 | 
        }, 
 | 
        // #endif 
 | 
        methods: { 
 | 
            /** 
 | 
             * 外部调用方法 
 | 
             * 设置规则 ,主要用于小程序自定义检验规则 
 | 
             * @param {Array} rules 规则源数据 
 | 
             */ 
 | 
            setRules(rules = null) { 
 | 
                this.userRules = rules 
 | 
                this.init(false) 
 | 
            }, 
 | 
            // 兼容老版本表单组件 
 | 
            setValue() { 
 | 
                // console.log('setValue 方法已经弃用,请使用最新版本的 uni-forms 表单组件以及其他关联组件。'); 
 | 
            }, 
 | 
            /** 
 | 
             * 外部调用方法 
 | 
             * 校验数据 
 | 
             * @param {any} value 需要校验的数据 
 | 
             * @param {boolean} 是否立即校验 
 | 
             * @return {Array|null} 校验内容 
 | 
             */ 
 | 
            async onFieldChange(value, formtrigger = true) { 
 | 
                const { 
 | 
                    formData, 
 | 
                    localData, 
 | 
                    errShowType, 
 | 
                    validateCheck, 
 | 
                    validateTrigger, 
 | 
                    _isRequiredField, 
 | 
                    _realName 
 | 
                } = this.form 
 | 
                const name = _realName(this.name) 
 | 
                if (!value) { 
 | 
                    value = this.form.formData[name] 
 | 
                } 
 | 
                // fixd by mehaotian 不在校验前清空信息,解决闪屏的问题 
 | 
                // this.errMsg = ''; 
 | 
  
 | 
                // fix by mehaotian 解决没有检验规则的情况下,抛出错误的问题 
 | 
                const ruleLen = this.itemRules.rules && this.itemRules.rules.length 
 | 
                if (!this.validator || !ruleLen || ruleLen === 0) return; 
 | 
  
 | 
                // 检验时机 
 | 
                // let trigger = this.isTrigger(this.itemRules.validateTrigger, this.validateTrigger, validateTrigger); 
 | 
                const isRequiredField = _isRequiredField(this.itemRules.rules || []); 
 | 
                let result = null; 
 | 
                // 只有等于 bind 时 ,才能开启时实校验 
 | 
                if (validateTrigger === 'bind' || formtrigger) { 
 | 
                    // 校验当前表单项 
 | 
                    result = await this.validator.validateUpdate({ 
 | 
                            [name]: value 
 | 
                        }, 
 | 
                        formData 
 | 
                    ); 
 | 
  
 | 
                    // 判断是否必填,非必填,不填不校验,填写才校验 ,暂时只处理 undefined  和空的情况 
 | 
                    if (!isRequiredField && (value === undefined || value === '')) { 
 | 
                        result = null; 
 | 
                    } 
 | 
  
 | 
                    // 判断错误信息显示类型 
 | 
                    if (result && result.errorMessage) { 
 | 
                        if (errShowType === 'undertext') { 
 | 
                            // 获取错误信息 
 | 
                            this.errMsg = !result ? '' : result.errorMessage; 
 | 
                        } 
 | 
                        if (errShowType === 'toast') { 
 | 
                            uni.showToast({ 
 | 
                                title: result.errorMessage || '校验错误', 
 | 
                                icon: 'none' 
 | 
                            }); 
 | 
                        } 
 | 
                        if (errShowType === 'modal') { 
 | 
                            uni.showModal({ 
 | 
                                title: '提示', 
 | 
                                content: result.errorMessage || '校验错误' 
 | 
                            }); 
 | 
                        } 
 | 
                    } else { 
 | 
                        this.errMsg = '' 
 | 
                    } 
 | 
                    // 通知 form 组件更新事件 
 | 
                    validateCheck(result ? result : null) 
 | 
                } else { 
 | 
                    this.errMsg = '' 
 | 
                } 
 | 
                return result ? result : null; 
 | 
            }, 
 | 
            /** 
 | 
             * 初始组件数据 
 | 
             */ 
 | 
            init(type = false) { 
 | 
                const { 
 | 
                    validator, 
 | 
                    formRules, 
 | 
                    childrens, 
 | 
                    formData, 
 | 
                    localData, 
 | 
                    _realName, 
 | 
                    labelWidth, 
 | 
                    _getDataValue, 
 | 
                    _setDataValue 
 | 
                } = this.form || {} 
 | 
                // 对齐方式 
 | 
                this.localLabelAlign = this._justifyContent() 
 | 
                // 宽度 
 | 
                this.localLabelWidth = this._labelWidthUnit(labelWidth) 
 | 
                // 标签位置 
 | 
                this.localLabelPos = this._labelPosition() 
 | 
                this.isRequired = this.required 
 | 
                // 将需要校验的子组件加入form 队列 
 | 
                this.form && type && childrens.push(this) 
 | 
  
 | 
                if (!validator || !formRules) return 
 | 
                // 判断第一个 item 
 | 
                if (!this.form.isFirstBorder) { 
 | 
                    this.form.isFirstBorder = true; 
 | 
                    this.isFirstBorder = true; 
 | 
                } 
 | 
  
 | 
                // 判断 group 里的第一个 item 
 | 
                if (this.group) { 
 | 
                    if (!this.group.isFirstBorder) { 
 | 
                        this.group.isFirstBorder = true; 
 | 
                        this.isFirstBorder = true; 
 | 
                    } 
 | 
                } 
 | 
                this.border = this.form.border; 
 | 
                // 获取子域的真实名称 
 | 
                const name = _realName(this.name) 
 | 
                const itemRule = this.userRules || this.rules 
 | 
                if (typeof formRules === 'object' && itemRule) { 
 | 
                    // 子规则替换父规则 
 | 
                    formRules[name] = { 
 | 
                        rules: itemRule 
 | 
                    } 
 | 
                    validator.updateSchema(formRules); 
 | 
                } 
 | 
                // 注册校验规则 
 | 
                const itemRules = formRules[name] || {} 
 | 
                this.itemRules = itemRules 
 | 
                // 注册校验函数 
 | 
                this.validator = validator 
 | 
                // 默认值赋予 
 | 
                this.itemSetValue(_getDataValue(this.name, localData)) 
 | 
                this.isRequired = this._isRequired() 
 | 
  
 | 
            }, 
 | 
            unInit() { 
 | 
                if (this.form) { 
 | 
                    const { 
 | 
                        childrens, 
 | 
                        formData, 
 | 
                        _realName 
 | 
                    } = this.form 
 | 
                    childrens.forEach((item, index) => { 
 | 
                        if (item === this) { 
 | 
                            this.form.childrens.splice(index, 1) 
 | 
                            delete formData[_realName(item.name)] 
 | 
                        } 
 | 
                    }) 
 | 
                } 
 | 
            }, 
 | 
            // 设置item 的值 
 | 
            itemSetValue(value) { 
 | 
                const name = this.form._realName(this.name) 
 | 
                const rules = this.itemRules.rules || [] 
 | 
                const val = this.form._getValue(name, value, rules) 
 | 
                this.form._setDataValue(name, this.form.formData, val) 
 | 
                return val 
 | 
            }, 
 | 
  
 | 
            /** 
 | 
             * 移除该表单项的校验结果 
 | 
             */ 
 | 
            clearValidate() { 
 | 
                this.errMsg = ''; 
 | 
            }, 
 | 
  
 | 
            // 是否显示星号 
 | 
            _isRequired() { 
 | 
                // TODO 不根据规则显示 星号,考虑后续兼容 
 | 
                // if (this.form) { 
 | 
                //     if (this.form._isRequiredField(this.itemRules.rules || []) && this.required) { 
 | 
                //         return true 
 | 
                //     } 
 | 
                //     return false 
 | 
                // } 
 | 
                return this.required 
 | 
            }, 
 | 
  
 | 
            // 处理对齐方式 
 | 
            _justifyContent() { 
 | 
                if (this.form) { 
 | 
                    const { 
 | 
                        labelAlign 
 | 
                    } = this.form 
 | 
                    let labelAli = this.labelAlign ? this.labelAlign : labelAlign; 
 | 
                    if (labelAli === 'left') return 'flex-start'; 
 | 
                    if (labelAli === 'center') return 'center'; 
 | 
                    if (labelAli === 'right') return 'flex-end'; 
 | 
                } 
 | 
                return 'flex-start'; 
 | 
            }, 
 | 
            // 处理 label宽度单位 ,继承父元素的值 
 | 
            _labelWidthUnit(labelWidth) { 
 | 
  
 | 
                // if (this.form) { 
 | 
                //     const { 
 | 
                //         labelWidth 
 | 
                //     } = this.form 
 | 
                return this.num2px(this.labelWidth ? this.labelWidth : (labelWidth || (this.label ? 65 : 'auto'))) 
 | 
                // } 
 | 
                // return '65px' 
 | 
            }, 
 | 
            // 处理 label 位置 
 | 
            _labelPosition() { 
 | 
                if (this.form) return this.form.labelPosition || 'left' 
 | 
                return 'left' 
 | 
  
 | 
            }, 
 | 
  
 | 
            /** 
 | 
             * 触发时机 
 | 
             * @param {Object} rule 当前规则内时机 
 | 
             * @param {Object} itemRlue 当前组件时机 
 | 
             * @param {Object} parentRule 父组件时机 
 | 
             */ 
 | 
            isTrigger(rule, itemRlue, parentRule) { 
 | 
                //  bind  submit 
 | 
                if (rule === 'submit' || !rule) { 
 | 
                    if (rule === undefined) { 
 | 
                        if (itemRlue !== 'bind') { 
 | 
                            if (!itemRlue) { 
 | 
                                return parentRule === '' ? 'bind' : 'submit'; 
 | 
                            } 
 | 
                            return 'submit'; 
 | 
                        } 
 | 
                        return 'bind'; 
 | 
                    } 
 | 
                    return 'submit'; 
 | 
                } 
 | 
                return 'bind'; 
 | 
            }, 
 | 
            num2px(num) { 
 | 
                if (typeof num === 'number') { 
 | 
                    return `${num}px` 
 | 
                } 
 | 
                return num 
 | 
            } 
 | 
        } 
 | 
    }; 
 | 
</script> 
 | 
  
 | 
<style lang="scss"> 
 | 
    .uni-forms-item { 
 | 
        position: relative; 
 | 
        display: flex; 
 | 
        /* #ifdef APP-NVUE */ 
 | 
        // 在 nvue 中,使用 margin-bottom error 信息会被隐藏 
 | 
        padding-bottom: 22px; 
 | 
        /* #endif */ 
 | 
        /* #ifndef APP-NVUE */ 
 | 
        margin-bottom: 22px; 
 | 
        /* #endif */ 
 | 
        flex-direction: row; 
 | 
  
 | 
        &__label { 
 | 
            display: flex; 
 | 
            flex-direction: row; 
 | 
            align-items: center; 
 | 
            text-align: left; 
 | 
            font-size: 14px; 
 | 
            color: #606266; 
 | 
            height: 36px; 
 | 
            padding: 0 12px 0 0; 
 | 
            /* #ifndef APP-NVUE */ 
 | 
            vertical-align: middle; 
 | 
            flex-shrink: 0; 
 | 
            /* #endif */ 
 | 
  
 | 
            /* #ifndef APP-NVUE */ 
 | 
            box-sizing: border-box; 
 | 
  
 | 
            /* #endif */ 
 | 
            &.no-label { 
 | 
                padding: 0; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        &__content { 
 | 
            /* #ifndef MP-TOUTIAO */ 
 | 
            // display: flex; 
 | 
            // align-items: center; 
 | 
            /* #endif */ 
 | 
            position: relative; 
 | 
            font-size: 14px; 
 | 
            flex: 1; 
 | 
            /* #ifndef APP-NVUE */ 
 | 
            box-sizing: border-box; 
 | 
            /* #endif */ 
 | 
            flex-direction: row; 
 | 
  
 | 
            /* #ifndef APP || H5 || MP-WEIXIN || APP-NVUE */ 
 | 
            // TODO 因为小程序平台会多一层标签节点 ,所以需要在多余节点继承当前样式 
 | 
            &>uni-easyinput, 
 | 
            &>uni-data-picker { 
 | 
                width: 100%; 
 | 
            } 
 | 
  
 | 
            /* #endif */ 
 | 
  
 | 
        } 
 | 
  
 | 
        & .uni-forms-item__nuve-content { 
 | 
            display: flex; 
 | 
            flex-direction: column; 
 | 
            flex: 1; 
 | 
        } 
 | 
  
 | 
        &__error { 
 | 
            color: #f56c6c; 
 | 
            font-size: 12px; 
 | 
            line-height: 1; 
 | 
            padding-top: 4px; 
 | 
            position: absolute; 
 | 
            /* #ifndef APP-NVUE */ 
 | 
            top: 100%; 
 | 
            left: 0; 
 | 
            transition: transform 0.3s; 
 | 
            transform: translateY(-100%); 
 | 
            /* #endif */ 
 | 
            /* #ifdef APP-NVUE */ 
 | 
            bottom: 5px; 
 | 
            /* #endif */ 
 | 
  
 | 
            opacity: 0; 
 | 
  
 | 
            .error-text { 
 | 
                // 只有 nvue 下这个样式才生效 
 | 
                color: #f56c6c; 
 | 
                font-size: 12px; 
 | 
            } 
 | 
  
 | 
            &.msg--active { 
 | 
                opacity: 1; 
 | 
                transform: translateY(0%); 
 | 
            } 
 | 
        } 
 | 
  
 | 
        // 位置修饰样式 
 | 
        &.is-direction-left { 
 | 
            flex-direction: row; 
 | 
        } 
 | 
  
 | 
        &.is-direction-top { 
 | 
            flex-direction: column; 
 | 
  
 | 
            .uni-forms-item__label { 
 | 
                padding: 0 0 8px; 
 | 
                line-height: 1.5715; 
 | 
                text-align: left; 
 | 
                /* #ifndef APP-NVUE */ 
 | 
                white-space: initial; 
 | 
                /* #endif */ 
 | 
            } 
 | 
        } 
 | 
  
 | 
        .is-required { 
 | 
            // color: $uni-color-error; 
 | 
            color: #dd524d; 
 | 
            font-weight: bold; 
 | 
        } 
 | 
    } 
 | 
  
 | 
  
 | 
    .uni-forms-item--border { 
 | 
        margin-bottom: 0; 
 | 
        padding: 10px 0; 
 | 
        // padding-bottom: 0; 
 | 
        border-top: 1px #eee solid; 
 | 
  
 | 
        /* #ifndef APP-NVUE */ 
 | 
        .uni-forms-item__content { 
 | 
            flex-direction: column; 
 | 
            justify-content: flex-start; 
 | 
            align-items: flex-start; 
 | 
  
 | 
            .uni-forms-item__error { 
 | 
                position: relative; 
 | 
                top: 5px; 
 | 
                left: 0; 
 | 
                padding-top: 0; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        /* #endif */ 
 | 
  
 | 
        /* #ifdef APP-NVUE */ 
 | 
        display: flex; 
 | 
        flex-direction: column; 
 | 
  
 | 
        .uni-forms-item__error { 
 | 
            position: relative; 
 | 
            top: 0px; 
 | 
            left: 0; 
 | 
            padding-top: 0; 
 | 
            margin-top: 5px; 
 | 
        } 
 | 
  
 | 
        /* #endif */ 
 | 
  
 | 
    } 
 | 
  
 | 
    .is-first-border { 
 | 
        /* #ifndef APP-NVUE */ 
 | 
        border: none; 
 | 
        /* #endif */ 
 | 
        /* #ifdef APP-NVUE */ 
 | 
        border-width: 0; 
 | 
        /* #endif */ 
 | 
    } 
 | 
</style> 
 |