gaoluyang
3 天以前 92230c9a97dc9ce9df3313d11d26999c04bb6b26
src/uni_modules/uni-forms/components/uni-forms-item/uni-forms-item.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,631 @@
<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>