| <template> | 
|   <view | 
|     class="tn-form-item-class tn-form-item" | 
|     :class="{ | 
|       'tn-border-solid-bottom': elBorderBottom, | 
|       'tn-form-item__border-bottom--error': validateState === 'error' && showError('border-bottom') | 
|     }" | 
|   > | 
|     <view | 
|       class="tn-form-item__body" | 
|       :style="{ | 
|         flexDirection: elLabelPosition == 'left' ? 'row' : 'column' | 
|       }" | 
|     > | 
|       <!-- 处理微信小程序中设置属性的问题,不设置值的时候会变成true --> | 
|       <view | 
|         class="tn-form-item--left" | 
|         :style="{ | 
|           width: wLabelWidth, | 
|           flex: `0 0 ${wLabelWidth}`, | 
|           marginBottom: elLabelPosition == 'left' ? 0 : '10rpx' | 
|         }" | 
|       > | 
|         <!-- 块对齐 --> | 
|         <view v-if="required || leftIcon || label" class="tn-form-item--left__content" | 
|           :style="[leftContentStyle]" | 
|         > | 
|           <!-- nvue不支持伪元素before --> | 
|           <view v-if="leftIcon" class="tn-form-item--left__content__icon"> | 
|             <view :class="[`tn-icon-${leftIcon}`]" :style="leftIconStyle"></view> | 
|           </view> | 
|           <!-- <view | 
|             class="tn-form-item--left__content__label" | 
|             :style="[elLabelStyle, { | 
|               'justify-content': elLabelAlign === 'left' ? 'flex-satrt' : elLabelAlign === 'center' ? 'center' : 'flex-end' | 
|             }]" | 
|           > | 
|             {{label}} | 
|           </view> --> | 
|           <view | 
|             class="tn-form-item--left__content__label" | 
|             :style="[elLabelStyle]" | 
|           > | 
|             {{label}} | 
|           </view> | 
|           <text v-if="required" class="tn-form-item--left__content--required">*</text> | 
|         </view> | 
|       </view> | 
|        | 
|       <view class="tn-form-item--right tn-flex"> | 
|         <view class="tn-form-item--right__content"> | 
|           <view class="tn-form-item--right__content__slot"> | 
|             <slot></slot> | 
|           </view> | 
|           <view v-if="$slots.right || rightIcon" class="tn-form-item--right__content__icon tn-flex"> | 
|             <view v-if="rightIcon" :class="[`tn-icon-${rightIcon}`]" :style="rightIconStyle"></view> | 
|             <slot name="right"></slot> | 
|           </view> | 
|         </view> | 
|       </view> | 
|     </view> | 
|      | 
|     <view | 
|       v-if="validateState === 'error' && showError('message')" | 
|       class="tn-form-item__message" | 
|       :style="{ | 
|         paddingLeft: elLabelPosition === 'left' ? elLabelWidth + 'rpx' : '0' | 
|       }" | 
|     > | 
|       {{validateMessage}} | 
|     </view> | 
|   </view> | 
| </template> | 
|   | 
| <script> | 
|   import Emitter from '../../libs/utils/emitter.js' | 
|   import schema from '../../libs/utils/async-validator.js' | 
|   // 去除警告信息 | 
|   schema.warning = function() {} | 
|    | 
|   export default { | 
|     mixins: [Emitter], | 
|     name: 'tn-form-item', | 
|     inject: { | 
|       tnForm: { | 
|         default() { | 
|           return null | 
|         } | 
|       } | 
|     }, | 
|     props: { | 
|       // label提示语 | 
|       label: { | 
|         type: String, | 
|         default: '' | 
|       }, | 
|       // 绑定的值 | 
|       prop: { | 
|         type: String, | 
|         default: '' | 
|       }, | 
|       // 是否显示表单域的下划线边框 | 
|       borderBottom: { | 
|         type:Boolean, | 
|         default: true | 
|       }, | 
|       // label(标签名称)的位置 | 
|       // left - 左边 | 
|       // top - 上边 | 
|       labelPosition: { | 
|         type: String, | 
|         default: '' | 
|       }, | 
|       // label的宽度 | 
|       labelWidth: { | 
|         type: Number, | 
|         default: 0 | 
|       }, | 
|       // label的对齐方式 | 
|       // left - 左对齐 | 
|       // top - 上对齐 | 
|       // right - 右对齐 | 
|       // bottom - 下对齐 | 
|       labelAlign: { | 
|         type: String, | 
|         default: '' | 
|       }, | 
|       // label 的样式 | 
|       labelStyle: { | 
|         type: Object, | 
|         default() { | 
|           return {} | 
|         } | 
|       }, | 
|       // 左侧图标 | 
|       leftIcon: { | 
|         type: String, | 
|         default: '' | 
|       }, | 
|       // 右侧图标 | 
|       rightIcon: { | 
|         type: String, | 
|         default: '' | 
|       }, | 
|       // 左侧图标样式 | 
|       leftIconStyle: { | 
|         type: Object, | 
|         default() { | 
|           return {} | 
|         } | 
|       }, | 
|       // 右侧图标样式 | 
|       rightIconStyle: { | 
|         type: Object, | 
|         default() { | 
|           return {} | 
|         } | 
|       }, | 
|       // 是否显示必填项的*,不做校验用途 | 
|       required: { | 
|         type: Boolean, | 
|         default: false | 
|       } | 
|     }, | 
|     computed: { | 
|       // 处理微信小程序label的宽度 | 
|       wLabelWidth() { | 
|         // 如果用户设置label为空字符串(微信小程序空字符串最终会变成字符串的'true'),意味着要将label的位置宽度设置为auto | 
|         return this.elLabelPosition === 'left' ? (this.label === 'true' || this.label === '' ? 'auto' : this.elLabelWidth + 'rpx') : '100%' | 
|       }, | 
|       // 是否显示错误提示 | 
|       showError() { | 
|         return type => { | 
|           if (this.errorType.indexOf('none') >= 0) return false | 
|           else if (this.errorType.indexOf(type) >= 0) return true | 
|           else return false | 
|         } | 
|       }, | 
|       // label的宽度(默认值为90) | 
|       elLabelWidth() { | 
|         return this.labelWidth != 0 ? this.labelWidth : (this.parentData.labelWidth != 0 ? this.parentData.labelWidth : 90) | 
|       }, | 
|       // label的样式 | 
|       elLabelStyle() { | 
|         return Object.keys(this.labelStyle).length ? this.labelStyle : (Object.keys(this.parentData.labelStyle).length ? this.parentData.labelStyle : {}) | 
|       }, | 
|       // label显示位置 | 
|       elLabelPosition() { | 
|         return this.labelPosition ? this.labelPosition : (this.parentData.labelPosition ? this.parentData.labelPosition : 'left') | 
|       }, | 
|       // label对齐方式 | 
|       elLabelAlign() { | 
|         return this.labelAlign ? this.labelAlign : (this.parentData.labelAlign ? this.parentData.labelAlign : 'left') | 
|       }, | 
|       // label下划线 | 
|       elBorderBottom() { | 
|         return this.borderBottom !== '' ? this.borderBottom : (this.parentData.borderBottom !== '' ? this.parentData.borderBottom : true) | 
|       }, | 
|       leftContentStyle() { | 
|         let style = {} | 
|         if (this.elLabelPosition === 'left') { | 
|           switch(this.elLabelAlign) { | 
|             case 'left': | 
|               style.justifyContent = 'flex-start' | 
|               break | 
|             case 'center': | 
|               style.justifyContent = 'center' | 
|               break | 
|             default: | 
|               style.justifyContent = 'flex-end' | 
|               break | 
|           } | 
|         } | 
|          | 
|         return style | 
|       } | 
|     }, | 
|     data() { | 
|       return { | 
|         // 默认值 | 
|         initialValue: '', | 
|         // 是否校验成功 | 
|         validateState: '', | 
|         // 校验失败提示信息 | 
|         validateMessage: '', | 
|         // 错误的提示方式(参考form组件) | 
|         errorType: ['message'], | 
|         // 当前子组件输入的值 | 
|         fieldValue: '', | 
|         // 父组件的参数 | 
|         // 由于再computed中无法得知this.parent的变化,所以放在data中 | 
|         parentData: { | 
|           borderBottom: true, | 
|           labelWidth: 90, | 
|           labelPosition: 'left', | 
|           labelAlign: 'left', | 
|           labelStyle: {}, | 
|         } | 
|       } | 
|     }, | 
|     watch: { | 
|       validateState(val) { | 
|         this.broadcastInputError() | 
|       }, | 
|       "tnForm.errorType"(val) { | 
|         this.errorType = val | 
|         this.broadcastInputError() | 
|       } | 
|     }, | 
|     mounted() { | 
|       // 组件创建完成后,保存当前实例到form组件中 | 
|       // 支付宝、头条小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环应用\ | 
|       this.parent = this.$t.$parent.call(this, 'tn-form') | 
|       if (this.parent) { | 
|         // 遍历parentData属性,将parent中同名的属性赋值给parentData | 
|         Object.keys(this.parentData).map(key => { | 
|           this.parentData[key] = this.parent[key] | 
|         }) | 
|         // 如果没有传入prop或者tnForm为空(单独使用form-item组件的时候),就不进行校验 | 
|         if (this.prop) { | 
|           // 将本实例添加到父组件中 | 
|           this.parent.fields.push(this) | 
|           this.errorType = this.parent.errorType | 
|           // 设置初始值 | 
|           this.initialValue = this.fieldValue | 
|           // 添加表单校验,这里必须要写在$nextTick中,因为tn-form的rules是通过ref手动传入的 | 
|           // 不在$nextTick中的话,可能会造成执行此处代码时,父组件还没通过ref把规则给tn-form,导致规则为空 | 
|           this.$nextTick(() => { | 
|             this.setRules() | 
|           }) | 
|         } | 
|       } | 
|     }, | 
|     beforeDestroy() { | 
|       // 组件销毁前,将实例从tn-form的缓存中移除 | 
|       // 如果当前没有prop的话表示当前不进行删除 | 
|       if (this.parent && this.prop) { | 
|         this.parent.fields.map((item, index) => { | 
|           if (item === this) this.parent.fields.splice(index, 1) | 
|         }) | 
|       } | 
|     }, | 
|     methods: { | 
|       // 向input组件发出错误事件 | 
|       broadcastInputError() { | 
|         this.broadcast('tn-input', 'on-form-item-error', this.validateState === 'error' && this.showError('border')) | 
|       }, | 
|       // 设置校验规则 | 
|       setRules() { | 
|         let that = this | 
|         // 从父组件tn-form拿到当前tn-form-item需要验证 的规则 | 
|         // let rules = this.getRules() | 
|         // if (rules.length) { | 
|         //     this.isRequired = rules.some(rule => { | 
|         //         // 如果有必填项,就返回,没有的话,就是undefined | 
|         //         return rule.required | 
|         //     }) | 
|         // } | 
|          | 
|         // blur事件 | 
|         this.$on('on-form-blur', that.onFieldBlur) | 
|         // change事件 | 
|         this.$on('on-form-change', that.onFieldChange) | 
|       }, | 
|       // 从form的rules属性中取出当前form-item的校验规则 | 
|       getRules() { | 
|         let rules = this.parent.rules | 
|         rules = rules ? rules[this.prop] : [] | 
|          | 
|         // 返回数值形式的值 | 
|         return [].concat(rules || []) | 
|       }, | 
|       // blur事件时进行表单认证 | 
|       onFieldBlur() { | 
|         this.validation('blur') | 
|       }, | 
|       // change事件时进行表单认证 | 
|       onFieldChange() { | 
|         this.validation('change') | 
|       }, | 
|       // 过滤出符合要求的rule规则 | 
|       getFilterRule(triggerType = '') { | 
|         let rules = this.getRules() | 
|         // 整体验证表单时,triggerType为空字符串,此时返回所有规则进行验证 | 
|         if (!triggerType) return rules | 
|         // 某些场景可能的判断规则,可能不存在trigger属性,故先判断是否存在此属性 | 
|         // 历遍判断规则是否有对应的事件,比如blur,change触发等的事件 | 
|         // 使用indexOf判断,是因为某些时候设置的验证规则的trigger属性可能为多个,比如['blur','change'] | 
|         return rules.filter(rule => rule.trigger && rule.trigger.indexOf(triggerType) !== -1) | 
|       }, | 
|       // 校验数据 | 
|       validation(trigger, callback = ()=>{}) { | 
|         // 校验之前先获取需要校验的值 | 
|         this.fieldValue = this.parent.model[this.prop] | 
|         // blur和change是否有当前方式的校验规则 | 
|         let rules = this.getFilterRule(trigger) | 
|         // 判断是否有验证规则,如果没有规则,也调用回调方法,否则父组件tn-form会因为 | 
|         // 对count变量的统计错误而无法进入上一层的回调  | 
|         if (!rules || rules.length === 0) { | 
|           return callback('') | 
|         } | 
|         // 设置当前为校验中 | 
|         this.validateState = 'validating' | 
|         // 调用async-validator的方法 | 
|         let validator = new schema({ | 
|           [this.prop]: rules | 
|         }) | 
|         validator.validate({ | 
|           [this.prop]: this.fieldValue | 
|         }, { | 
|           firstFields: true | 
|         }, (errors, fields) => { | 
|           // 记录状态和报错信息 | 
|           this.validateState = !errors ? 'success' : 'error' | 
|           this.validateMessage = errors ? errors[0].message : '' | 
|            | 
|           callback(this.validateMessage) | 
|         }) | 
|       }, | 
|        | 
|       // 清空当前item信息 | 
|       resetField() { | 
|         this.parent.model[this.prop] = this.initialValue | 
|         // 清空错误标记 | 
|         this.validateState = 'success' | 
|       } | 
|     } | 
|   } | 
| </script> | 
|   | 
| <style lang="scss" scoped> | 
|   .tn-form-item { | 
|     display: flex; | 
|     flex-direction: column; | 
|     padding: 20rpx 0; | 
|     font-size: 28rpx; | 
|     color: $tn-font-color; | 
|     box-sizing: border-box; | 
|     line-height: $tn-form-item-height; | 
|      | 
|     &__border-bottom--error:after { | 
|       border-color: $tn-color-red; | 
|     } | 
|      | 
|     &__body { | 
|       display: flex; | 
|       flex-direction: row; | 
|     } | 
|      | 
|     &--left { | 
|       display: flex; | 
|       flex-direction: row; | 
|       align-items: center; | 
|        | 
|       &__content { | 
|         display: flex; | 
|         flex-direction: row; | 
|         position: relative; | 
|         align-items: center; | 
|         padding-right: 18rpx; | 
|         flex: 1; | 
|          | 
|         &--required { | 
|           position: relative; | 
|           right: 0; | 
|           vertical-align: middle; | 
|           color: $tn-color-red; | 
|         } | 
|          | 
|         &__icon { | 
|           color: $tn-font-sub-color; | 
|           margin-right: 8rpx; | 
|         } | 
|          | 
|         &__label { | 
|           // display: flex; | 
|           // flex-direction: row; | 
|           // align-items: center; | 
|           // flex: 1; | 
|         } | 
|       } | 
|     } | 
|      | 
|     &--right { | 
|       flex: 1; | 
|        | 
|       &__content { | 
|         display: flex; | 
|         flex-direction: row; | 
|         align-items: center; | 
|         flex: 1; | 
|          | 
|         &__slot { | 
|           flex: 1; | 
|           /* #ifndef MP */ | 
|           display: flex; | 
|           flex-direction: row; | 
|           align-items: center; | 
|           /* #endif */ | 
|         } | 
|          | 
|         &__icon { | 
|           margin-left: 10rpx; | 
|           color: $tn-font-sub-color; | 
|           font-size: 30rpx; | 
|         } | 
|       } | 
|     } | 
|      | 
|     &__message { | 
|       font-size: 24rpx; | 
|       line-height: 24rpx; | 
|       color: $tn-color-red; | 
|       margin-top: 12rpx; | 
|     } | 
|   } | 
| </style> |