使用vue表单验证库async-validator封装Form表单组件

 

src/components/data/seller/create/contract.vue

<template>
  <create-portlet title="合同信息">
    <j-form :model="model" ref="form">
      <div class="row">

        <div class="col-xs-4">
          <j-form-item
            prop="contract.cooperationTypeMap"
            :rules="[
            {validator: selectRequired, message: '合作方式必须', trigger: 'change'}
            ]"
          >
            <j-select
              v-model="model.contract.cooperationTypeMap"
              :options="methodCooperation"
            >
              <span slot="left" class="input-group-addon">合作方式:</span>
              <span slot="right" class="input-group-addon"><span class="font-red">*</span></span>
            </j-select>
          </j-form-item>
        </div>

        <div class="col-xs-4">
          <j-form-item
            prop="contract.isSignContract"
            :rules="[
              {required: true, message: '是否签合同必须', trigger: 'change'}
            ]"
          >
            <div class="input-group j-input-group">
              <span class="input-group-addon">是否签合同:</span>
              <j-radio-group v-model="model.contract.isSignContract">
                <j-radio checked-value="1">是</j-radio>
                <j-radio checked-value="0">否</j-radio>
              </j-radio-group>
              <span slot="right" class="input-group-addon"><span class="font-red">*</span></span>
            </div>
          </j-form-item>
        </div>

        <template v-if="model.contract.isSignContract == 1">
          <div class="col-xs-4">
            <j-form-item
              prop="contract.startTime"
              :rules="[
                    {required: true, message: '合同结束时间必须', trigger: 'change'}
                  ]"
            >
              <j-date-group
                v-model="model.contract.startTime"
                :end-date="model.contract.endTime"
              >
                <span slot="left" class="input-group-addon">合同开始时间:</span>
                <span slot="right" class="input-group-addon font-red">*</span>
              </j-date-group>
            </j-form-item>
          </div>

          <div class="col-xs-4">
            <j-form-item
              prop="contract.endTime"
              :rules="[
                    {required: true, message: '合同结束时间必须', trigger: 'change'}
                  ]"
            >
              <j-date-group
                v-model="model.contract.endTime"
                :start-date="model.contract.startTime"
              >
                <span slot="left" class="input-group-addon">合同结束时间:</span>
                <span slot="right" class="input-group-addon"><span class="font-red">*</span></span>
              </j-date-group>
            </j-form-item>
          </div>

          <div class="col-xs-4">
            <j-form-item
              prop="contract.contractNo"
              :rules="
            [
              {required: true, message: '合同编号必须', trigger: 'blur'},
              {validator: range, min: 1, max: 20, isString: true, message: '合同编号必须1-20字', trigger: 'blur'},
              {validator: eliminateSpace, message: '合同编号不能有空格', trigger: 'blur'}
            ]"
            >
              <j-input-group
                v-model="model.contract.contractNo"
              >
                <span slot="left" class="input-group-addon">合同编号:</span>
                <span slot="right" class="input-group-addon"><span class="font-red">*</span></span>
              </j-input-group>
            </j-form-item>
          </div>
        </template>


      </div>

    </j-form>
  </create-portlet>
</template>
<script>
  import createPortlet from 'components/base/createPortlet'
  import {
    isPhone,
    selectRequired,
    arrayRequired,
    isNumber,
    requiredIf,
    range,
    isText,
    eliminateSpace
  } from 'assets/scripts/base/validator'
  import {unixTimestampFilter} from 'assets/scripts/base/filters'
  import {getMerchantCooperationWay} from 'assets/scripts/business/store'
  import {mapGetValue, isEqual, dotData} from 'assets/scripts/base/functions'
  import cascadeAddress from 'components/base/cascadeAddress'
  import jFormItem from 'components/base/form/jFormItem'
  import jInputGroup from 'components/base/form/jInputGroup'
  import jForm from 'components/base/form/jForm'
  import jSelect from 'components/base/form/jSelect'
  import jDateGroup from 'components/base/form/jDateGroup'
  import jAddressGroup from 'components/base/form/jAddressGroup'
  import jCheckbox from 'components/base/form/jCheckbox'
  import jCheckboxGroup from 'components/base/form/jCheckboxGroup'
  import jRadio from 'components/base/form/jRadio'
  import jRadioGroup from 'components/base/form/jRadioGroup'
  export default {
    name: 'dataSellerCreateCompany',
    components: {
      createPortlet,
      cascadeAddress,
      jFormItem,
      jInputGroup,
      jForm,
      jSelect,
      jDateGroup,
      jAddressGroup,
      jCheckbox,
      jCheckboxGroup,
      jRadio,
      jRadioGroup
    },
    props: {
      detail: {
        type: Object,
        required: true
      }
    },
    data () {
      return {
        model: {
          contract: {
            startTime: '',
            endTime: '',
            isSignContract: '1',
            cooperationTypeMap: {value: '', text: ''},
            contractNo: ''
          }
        },
        methodCooperation: []
      }
    },
    watch: {
      detail (val, oldVal) {
        if (isEqual(val, oldVal)) {
          return
        }
        // 合作方式
        const cooperationType = dotData(val, 'merchantContractInfo.cooperationType') || dotData(val, 'contract.cooperationType')
        if (cooperationType) {
          this.model.contract.cooperationTypeMap = {value: cooperationType, text: ''}
        }
        // 合同
        let isSignContract = dotData(val, 'merchantContractInfo.isSignContract')
        if (isSignContract !== 0 || isSignContract !== 1) {
          isSignContract = dotData(val, 'contract.isSignContract')
        }
        isSignContract = String(isSignContract)
        if (isSignContract === '0' || isSignContract === '1') {
          this.model.contract.isSignContract = isSignContract
        }
        // 开始时间
        const startTime = dotData(val, 'merchantContractInfo.startTime') || dotData(val, 'contract.startTime')
        this.model.contract.startTime = unixTimestampFilter(startTime, 'YYYY-MM-DD')

        const endTime = dotData(val, 'merchantContractInfo.endTime') || dotData(val, 'contract.endTime')
        if (endTime) {
          this.model.contract.endTime = unixTimestampFilter(endTime, 'YYYY-MM-DD')
        }

        const contractNo = dotData(val, 'merchantContractInfo.contractNo') || dotData(val, 'contract.contractNo')
        this.model.contract.contractNo = contractNo
        this.model.contract.id = dotData(val, 'merchantContractInfo.id') || dotData(val, 'contract.id')
      }
    },
    created () {
      getMerchantCooperationWay().then(arr => {
        this.methodCooperation = arr
      })
    },
    methods: {
      isPhone,
      selectRequired,
      arrayRequired,
      isNumber,
      requiredIf,
      range,
      isText,
      eliminateSpace,
      // 验证并获取数据
      getModel () {
        return this.$refs.form.validate().then((result) => {
          if (result) {
            let data = this.model
            return mapGetValue(data)
          }
          return result
        })
      },
      // 获取数据没有验证
      getModelWithoutValidation () {
        let data = this.model
        return mapGetValue(data)
      }
    }
  }
</script>

点击 公司地址输入框:弹出地址选择 组件:

src/components/data/seller/create/company.vue

<template>
  <create-portlet title="公司信息">
    <j-form :model="model" ref="form">
      <div class="row">

        <div class="col-xs-3 col-sm-3">
          <j-form-item
            prop="company"
            :rules="[
              {validator: range, min: 1, max: 16, isString: true, message: '公司名称必须1-16字', trigger: 'blur'},
              {validator: eliminateSpace,  message: '公司名称不能有空格', trigger: 'blur'}
            ]"
          >
            <j-input-group
              v-model="model.company"
            >
              <span slot="left" class="input-group-addon">公司名称:</span>
            </j-input-group>
          </j-form-item>
        </div>

        <div class="col-xs-3 col-sm-3">
          <j-form-item
            prop="legalPerson"
            :rules="[
              {validator: range, min: 1, max: 16, isString: true, message: '法人代表必须1-16字', trigger: 'blur'},
              {validator: eliminateSpace,  message: '法人代表不能有空格', trigger: 'blur'}
            ]"
          >
            <j-input-group
              v-model="model.legalPerson"
            >
              <span slot="left" class="input-group-addon">法人代表:</span>
            </j-input-group>
          </j-form-item>
        </div>


      </div>

      <div class="row">

        <div class="col-xs-6 col-sm-6">
          <j-form-item
          >
            <j-address-group
              :level="3"
              v-model="model.addressArr"
            >
              <span slot="left" class="input-group-addon">公司地址:</span>
            </j-address-group>
          </j-form-item>
        </div>

        <div class="col-xs-6 col-sm-6">
          <j-form-item
            prop="hqAddress"
            :rules="[
            {validator: range, min: 1, max: 50, isString: true, message: '详细地址必须少于50字', trigger: 'blur'}
           ]"
          >
            <j-input-group
              v-model="model.hqAddress"
            >
              <span slot="left" class="input-group-addon">详细地址:</span>
            </j-input-group>
          </j-form-item>
        </div>

      </div>

    </j-form>
  </create-portlet>
</template>
<script>
  import createPortlet from 'components/base/createPortlet'
  import {
    isPhone,
    selectRequired,
    arrayRequired,
    isNumber,
    requiredIf,
    range,
    isText,
    eliminateSpace
  } from 'assets/scripts/base/validator'
  import {dotData, copy, isEqual} from 'assets/scripts/base/functions'
  import cascadeAddress from 'components/base/cascadeAddress'
  import jFormItem from 'components/base/form/jFormItem'
  import jInputGroup from 'components/base/form/jInputGroup'
  import jForm from 'components/base/form/jForm'
  import jSelect from 'components/base/form/jSelect'
  import jDateGroup from 'components/base/form/jDateGroup'
  import jAddressGroup from 'components/base/form/jAddressGroup'
  import jCheckbox from 'components/base/form/jCheckbox'
  import jCheckboxGroup from 'components/base/form/jCheckboxGroup'
  import jRadio from 'components/base/form/jRadio'
  import jRadioGroup from 'components/base/form/jRadioGroup'
  export default {
    name: 'dataSellerCreateCompany',
    components: {
      createPortlet,
      cascadeAddress,
      jFormItem,
      jInputGroup,
      jForm,
      jSelect,
      jDateGroup,
      jAddressGroup,
      jCheckbox,
      jCheckboxGroup,
      jRadio,
      jRadioGroup
    },
    props: {
      detail: {
        type: Object,
        required: true
      }
    },
    data () {
      return {
        model: {
          company: '',
          legalPerson: '',
          addressArr: [],
          hqAddress: ''
        }
      }
    },
    watch: {
      detail (val, oldVal) {
        if (isEqual(val, oldVal)) {
          return
        }
        this.setEditValue(val)
      }
    },
    created () {
      this.setEditValue(this.detail)
    },
    methods: {
      isPhone,
      selectRequired,
      arrayRequired,
      isNumber,
      requiredIf,
      range,
      isText,
      eliminateSpace,
      // 验证并获取数据
      getModel () {
        return this.$refs.form.validate().then((result) => {
          if (result) {
            let data = copy(this.model, true)
            data.hqProvinceNo = dotData(data, 'addressArr.0.value')
            data.hqCityNo = dotData(data, 'addressArr.1.value')
            data.hqDistrictNo = dotData(data, 'addressArr.2.value')
            delete data.addressArr
            return data
          }
          return result
        })
      },
      // 获取数据没有验证
      getModelWithoutValidation () {
        let data = copy(this.model, true)
        data.hqProvinceNo = dotData(data, 'addressArr.0.value')
        data.hqCityNo = dotData(data, 'addressArr.1.value')
        data.hqDistrictNo = dotData(data, 'addressArr.2.value')
        delete data.addressArr
        return data
      },
      setEditValue (val) {
        // 公司名称
        const company = dotData(val, 'company')
        this.model.company = company
        // 法人代表
        const legalPerson = dotData(val, 'legalPerson')
        this.model.legalPerson = legalPerson
        // 公司地址
        const hqProvinceNo = dotData(val, 'hqProvinceNo')
        const hqProvince = dotData(val, 'hqProvince')

        const hqCityNo = dotData(val, 'hqCityNo')
        const hqCity = dotData(val, 'hqCity')

        const hqDistrictNo = dotData(val, 'hqDistrictNo')
        const hqDistrict = dotData(val, 'hqDistrict')
        if (hqProvinceNo && hqProvince) {
          this.model.addressArr = [
            {text: hqProvince, value: hqProvinceNo},
            {text: hqCity, value: hqCityNo},
            {text: hqDistrict, value: hqDistrictNo}
          ]
        }
        const hqAddress = dotData(val, 'hqAddress')
        this.model.hqAddress = hqAddress
      }
    }
  }
</script>

封装 表单块: jFormItem:   src/components/base/form/jFormItem.vue

<template>
  <div class="form-group j-form-item" :class="{'has-error': hasError}">
    <slot></slot>
    <span v-if="hasError" class="error-block">{{ error }}</span>
  </div>
</template>
<script>
  import parent from 'assets/scripts/mixins/parent'
  import AsyncValidator from 'async-validator'
  export default {
    name: 'jFormItem',
    componentName: 'jFormItem',
    mixins: [parent],
    props: {
      rules: {
        type: [Array, Object, Boolean],
        default () {
          return []
        }
      },
      prop: {
        type: String,
        default: 'key'
      }
    },
    data () {
      return {
        error: '',
        value: '',
        unique: null
      }
    },
    computed: {
      hasError () {
        return this.error.length !== 0
      },
      jForm () {
        return this.findParentByComponentName('jForm')
      }
    },
    created () {
      this.$on('j-form-validate', this.validate)
      if (this.jForm !== null) {
        this.unique = Symbol('j-form-item')
        this.jForm.$emit.apply(this.jForm, ['j.form.addField'].concat([this, this.unique]))
      }
      // 一个传递value
      this.$on('j.form.addValueFiled', (field, key) => {
        this.jForm.$emit.apply(this.jForm, ['j.form.addValueFiled'].concat([field, key]))
      })
      this.$on('j.form.removeValueField', (key) => { // 这里以前是 addValueFiled
        this.jForm.$emit.apply(this.jForm, ['j.form.removeValueField'].concat([key]))
      })
    },
    destroyed () {
      if (this.jForm !== null) {
        this.jForm.$emit.apply(this.jForm, ['j.form.removeField'].concat([this.unique]))
      }
    },
    methods: {
      validate (event = null) {
        return new Promise((resolve, reject) => {
          const rules = this.getRules(event)
          if (rules.length > 0) {
            let descriptor = {}
            descriptor[this.prop] = rules
            let obj = {}
            obj[this.prop] = this.getValue(this.prop)
            const validator = new AsyncValidator(descriptor)
            validator.validate(obj, {firstFields: true}, (errors, fields) => {
              this.error = errors ? errors[0].message : ''
              resolve(!this.hasError)
            })
          } else {
            resolve(true)
          }
        })
      },
      // 获取验证规则
      getRules (type) {
        if (type) {
          return this.rules.filter(rule => {
            return !rule.trigger || rule.trigger.indexOf(type) !== -1
          })
        }
        return this.rules
      },
      // 重置
      resetField () {
        this.error = []
      },
      getValue (name) {
        return this.getPropByPath(this.jForm.model, name).v
      },
      // 根据path 获取值
      getPropByPath (obj, path) {
        let tempObj = obj
        path = path.replace(/\[(\w+)\]/g, '.$1')
        path = path.replace(/^\./, '')
        let keyArr = path.split('.')
        let i = 0
        for (let len = keyArr.length; i < len - 1; ++i) {
          let key = keyArr[i]
          if (key in tempObj) {
            tempObj = tempObj[key]
          } else {
            throw new Error('please transfer a valid prop path to form item!')
          }
        }
        return {
          o: tempObj,
          k: keyArr[i],
          v: tempObj[keyArr[i]]
        }
      }
    }
  }
</script>

封装 下拉选项:src/components/base/form/jSelect.vue

<template>
  <div
    class="input-group j-select-group dropdown"
    @keyup.down.stop.prevent.capture="down"
    @keyup.up.stop.prevent="up"
    @keyup.enter.stop.prevent="enter"
    @click.prevent="selectClick"
    :class="{open: isOpen}"
  >
    <slot name="left"></slot>
    <div class="j-select-group-cell">
      <div class="j-select-group-box on-popper" v-if="multiple">
        <span v-for="item of selectedOptions" class="tag label label-info j-select-group-label">
          {{ item.text }}
          <span data-role="remove" @click.prevent.stop="removeMultiple(item)">x</span>
        </span>
        &nbsp;
      </div>
      <input v-else type="text" class="form-control on-popper"
             :readonly="readonly"
             v-model="inputValue"
             :tabindex="tabindex"
             @keyup.prevent="inputHandle"
             :placeholder="placeholder"
      />
      <span class="j-input-placeholder-span" v-if="!supportPlaceHolder && isFocus === false && !inputValue"
            @click.prevent="labelClick">{{ placeholder }}</span>
      <i class="fa fa-angle-down fa-j-angle-down font-green"></i>
    </div>
    <ul class="dropdown-menu dropdown-menu-default" style="width: 100%;">
      <li>
        <ul class="dropdown-menu-ul j-select-down" :data-height="slimHeight">
          <li v-for="(option, index) in innerOptions" @click.prevent="choose(option, index, $event)"
              :class="{active: option.isSelected, disabled: option.disabled}">
            <a href="javascript: void (0);">
              {{ option.text }}
            </a>
          </li>
        </ul>
      </li>
    </ul>
    <slot name="right"></slot>
    <slot name="right-btn"></slot>
  </div>
</template>
<script>
  import {supportPlaceHolder, copy, isEqual, dotData} from 'assets/scripts/base/functions'
  import parent from 'assets/scripts/mixins/parent'
  import lodash from 'lodash'
  export default {
    name: 'JSelect',
    mixins: [parent],
    props: {
      options: {
        type: Array,
        default () {
          return []
        }
      },
      slimHeight: {
        type: Number,
        default: 250
      },
      multiple: {
        type: Boolean,
        default: false
      },
      remote: {
        type: [Function, Boolean],
        default: false
      },
      placeholder: {
        type: String,
        default: ''
      },
      value: {
        type: [String, Object, Array],
        default: ''
      },
      forceReadonly: {
        type: Boolean,
        default: false
      },
      forbiddenInputUndefined: {
        type: Boolean,
        default: false
      },
      returnItem: {
        type: Object,
        default: function () {
          return {}
        }
      }
    },
    data () {
      return {
        isOpen: false,
        initSlimScroll: false,
        innerOptions: copy(this.options, true),
        inputValue: '',
        isFocus: false,
        unique: '',
        downUpIndex: null
      }
    },
    watch: {
      options (val, oldVal) {
        if (isEqual(val, oldVal)) {
          return
        }
        this.innerOptions = copy(val, true)
        this.initSlimScroll = false
        this.getInnerOptions().then(options => {
          this.selectValue(options, this.value)
        })
      },
      value (val, oldVal) {
        if (isEqual(val, oldVal)) {
          return
        }
        if ((!val.value && this.multiple === false) || (this.multiple === true && Array.isArray(val) && val.length === 0)) {
//          this.resetField()
          this.inputValue = ''
        }
        if (lodash.isFunction(this.remote)) {
          const text = dotData(this.value, 'text')
          if (text) {
            this.setInputValue(text)
          }
        } else {
          this.getInnerOptions().then(options => {
            this.selectValue(options, val)
          })
        }
      }
    },
    computed: {
      jFormItem () {
        return this.findParentByComponentName('jFormItem')
      },
      jFormTableItem () {
        return this.findParentByComponentName('jFormTableItem')
      },
      hiddenOpen () {
        return () => {
          this.isOpen = false
        }
      },
      keyUp () {
        return (event) => {
//          event.preventDefault()
        }
      },
      selectedOptions () {
        let temp = []
        for (let v of this.innerOptions) {
          if (v.isSelected) {
            temp.push({value: v.value, text: v.text})
          }
        }
        return temp
      },
      readonly () {
        return !lodash.isFunction(this.remote)
      },
      supportPlaceHolder () {
        return supportPlaceHolder()
      },
      jForm () {
        return this.findParentByComponentName('jForm')
      },
      tabindex () {
        if (this.disabled) {
          return '-1'
        }
        return false
      }
    },
    created () {
      document.addEventListener('click', this.hiddenOpen, true)
      this.getInnerOptions().then(options => {
        this.selectValue(options, this.value)
      })
      if (this.jForm !== null) {
        this.unique = Symbol('j-form-value')
        this.jForm.$emit.apply(this.jForm, ['j.form.addValueFiled'].concat([this, this.unique]))
      }
      // 设置默认值
      if (lodash.isFunction(this.remote)) {
        const text = dotData(this.value, 'text')
        if (text) {
          this.setInputValue(text)
        }
      }
    },
    destroyed () {
      document.removeEventListener('click', this.hiddenOpen, true)
      if (!this.forbiddenInputUndefined) {
        this.$emit('input', {value: '', text: ''})
      }
      if (this.jForm !== null) {
        this.jForm.$emit.apply(this.jForm, ['j.form.removeValueField'].concat([this.unique]))
      }
    },
    methods: {
      close () {
        this.isOpen = false
        document.removeEventListener('keydown', this.keyUp)
      },
      inputHandle () {
        this.$emit('remoteValue', this.inputValue)
        this.getRemoteOptions()
      },
      choose (option, index, $event) {
        if (option.disabled) {
          return
        }
        if (this.multiple) {
          if (option.isSelected) {
            this.$set(option, 'isSelected', false)
          } else {
            this.$set(option, 'isSelected', true)
          }
          this.$emit('choose', this.selectedOptions)
        } else {
          if (option.isSelected) {
            $event.stopPropagation()
            return
          }
          this.clearSelected()
          this.$set(option, 'isSelected', true)
          $event.stopPropagation()
          this.$emit('choose', this.selectedOptions[0], this.returnItem)
        }
        this.emitAndSetInputValue()
        this.emitValidate()
      },
      emitValidate () {
        if (this.jFormItem) {
          this.jFormItem.$emit('j-form-validate', 'change')
        }
        if (this.jFormTableItem) {
          this.jFormTableItem.$emit('j-form-validate', 'change')
        }
      },
      selectClick () {
        if (this.forceReadonly) {
          return
        }
        if (lodash.isFunction(this.remote)) {
          this.$el.querySelector('input').focus()
          this.getRemoteOptions()
        } else {
          this.openMenu()
        }
      },
      openMenu () {
        this.isOpen = true
        this.$nextTick(_ => {
          const height = this.$el.querySelector('.dropdown-menu-ul').clientHeight
          if (height >= this.slimHeight) {
            window.App.initSlimScroll(this.$el.querySelector('.j-select-down'))
          }
        })
      },
      clearSelected () {
        this.innerOptions.forEach(v => {
          this.$set(v, 'isSelected', false)
        })
      },
      removeMultiple (item) {
        const value = item.value
        for (let v of this.innerOptions) {
          if (v.value === value) {
            this.$set(v, 'isSelected', false)
            this.$emit('input', this.selectedOptions)
            this.emitValidate()
            return
          }
        }
      },
      handleBlur () {
        this.isFocus = false
      },
      handleFocus () {
        this.isFocus = true
      },

      labelClick () {
        this.$el.querySelector('input').focus()
      },
      getInnerOptions () {
        return Promise.resolve(this.innerOptions)
      },
      selectValue (options, value) {
        this.clearSelected()
        const type = (typeof value).toLowerCase()
        if (type === 'string' || type === 'number') {
          for (let option of options) {
            if (Array.isArray(value)) {
              value.forEach(v => {
                if (option.value === v.value) {
                  this.$set(option, 'isSelected', true)
                }
              })
            } else {
              if (option.value === value.value) {
                this.$set(option, 'isSelected', true)
              }
            }
          }
        } else if (type === 'object') {
          for (let option of options) {
            if (Array.isArray(value)) {
              value.forEach(v => {
                if (option.value === v.value) {
                  this.$set(option, 'isSelected', true)
                }
              })
            } else {
              if (option.value === value.value) {
                this.$set(option, 'isSelected', true)
              }
            }
          }
        }
        this.emitAndSetInputValue()
      },
      emitAndSetInputValue () {
        if (this.selectedOptions.length === 0) {
          return
        }
        if (this.multiple) {
          this.$emit('input', this.selectedOptions)
        } else {
          this.$emit('input', this.selectedOptions[0])
          const text = dotData(this.selectedOptions[0], 'text')
          this.inputValue = text
        }
      },
      // 设置inputValue
      setInputValue (value) {
        this.inputValue = value
      },
      getRemoteOptions: lodash.debounce(function () {
        if (lodash.isFunction(this.remote)) {
          if (!this.inputValue) {
            this.$emit('input', [])
            return
          }
          this.remote(this.inputValue).then(options => {
            if (options.length === 0) {
              this.innerOptions = [{value: null, text: '无数据', disabled: true}]
            } else {
              this.innerOptions = copy(options, true)
            }
            this.openMenu()
          })
        }
      }, 800),
      // 清空表单
      resetField () {
        this.clearSelected()
        if (this.multiple) {
          this.$emit('input', [])
        } else {
          this.$emit('input', {text: '', value: ''})
          this.inputValue = ''
        }
      },
      // 向下或向上
      setDownUpIndex (type) {
        if (type === 1) { // 向下
          if (this.downUpIndex === null) {
            this.downUpIndex = 0
          } else {
            if (this.downUpIndex + 1 === this.innerOptions.length) {
            } else {
              this.downUpIndex++
            }
          }
        } else if (type === 2) {
          if (this.downUpIndex === null) {
            this.downUpIndex = 0
          } else {
            if (this.downUpIndex === 0) {
            } else {
              this.downUpIndex--
            }
          }
        }
        return this.downUpIndex
      },
      down ($event) {
        $event.preventDefault()
        const index = this.setDownUpIndex(1)
        this.clearSelected()
        this.$set(this.innerOptions[index], 'isSelected', true)
      },
      up ($event) {
        $event.preventDefault()
        const index = this.setDownUpIndex(2)
        this.clearSelected()
        this.$set(this.innerOptions[index], 'isSelected', true)
      },
      enter ($event) {
        this.$emit('choose', this.selectedOptions[0], this.returnItem)
        this.emitAndSetInputValue()
        this.emitValidate()
        this.close()
      }
    }
  }
</script>

封装 “地址选择”表单组件:src/components/base/form/jAddressGroup.vue

<template>
  <div class="dropdown j-input-group input-group j-address-group readonly" @click.prevent="openMenu" v-clickoutside="handleClose" :class="{open: isOpen}">
    <slot name="left"></slot>
    <div class="j-address-group-box" style="display: table-cell; position: relative">
      <input type="text" class="form-control"
             readonly
             :value="inputText"
             :placeholder="placeholder"
             style="border-style: solid; cursor: pointer;"
      />
      <i class="fa fa-angle-down fa-j-angle-down font-green"></i>
    </div>

    <div class="dropdown-menu address-drop-down-tab" :style="{width: dropDownWidth}">
      <ul class="nav nav-tabs">
        <li :class="{disabled: !province.length, active: current == 1}" @click.prevent="changeCurrent(province,1)">
          <a href="javascript:void (0);">
            省
          </a>
        </li>
        <li v-if="level >= 2" :class="{disabled: !city.length, active: current == 2}"
            @click.prevent="changeCurrent(city,2)">
          <a href="javascript:void (0);">
            市
          </a>
        </li>
        <li v-if="level >= 3" :class="{disabled: !area.length, active: current == 3}"
            @click.prevent="changeCurrent(area,3)">
          <a href="javascript:void (0);">
            区
          </a>
        </li>
        <li v-if="level >= 4" :class="{disabled: !street.length, active: current == 4}"
            @click.prevent="changeCurrent(street,4)">
          <a href="javascript:void (0);">
            街道
          </a>
        </li>
      </ul>
      <div class="tab-content address-drop-content scroller" data-height="250px">
        <div class="tab-pane fade" :class="{'in active': current == 1}">
          <div v-if="canChangeProvince === false" class="alert alert-danger"><strong>提示!</strong> 不可选择 </div>
          <label class="label"
                 :class="[provinceNo == item.value ? 'label-info' : 'label-success']"
                 v-for="item in province"
                 v-text="item.text"
                 @click.prevent.stop="chooseProvince(item, $event)">
          </label>
        </div>
        <div class="tab-pane fade" v-if="level >= 2" :class="{'in active': current == 2}">
          <div v-if="canChangeCity === false" class="alert alert-danger"><strong>提示!</strong> 不可选择 </div>
          <label class="label"
                 :class="[cityNo == item.value ? 'label-info' : 'label-success']"
                 v-for="item in city"
                 v-text="item.text"
                 @click.prevent.stop="chooseCity(item, $event)">
          </label>
        </div>
        <div class="tab-pane fade" v-if="level >= 3" :class="{'in active': current == 3}">
          <div v-if="canChangeDistrict === false" class="alert alert-danger"><strong>提示!</strong> 不可选择 </div>
          <label class="label"
                 :class="[areaNo == item.value ? 'label-info' : 'label-success']"
                 v-for="item in area"
                 v-text="item.text"
                 @click.prevent.stop="chooseArea(item, $event)">
          </label>
        </div>
        <div class="tab-pane fade" v-if="level >= 4" :class="{'in active': current == 4}">
          <div v-if="canChangeStreet === false" class="alert alert-danger"><strong>提示!</strong> 不可选择 </div>
          <label class="label"
                 :class="[streetNo == item.value ? 'label-info' : 'label-success']"
                 v-for="item in street"
                 v-text="item.text"
                 @click.prevent.stop="chooseStreet(item, $event)">
          </label>
        </div>
      </div>
    </div>
    <slot name="right"></slot>
  </div>
</template>
<script>
  import parent from 'assets/scripts/mixins/parent'
  import {arrayColumn, isEqual, dotData} from 'assets/scripts/base/functions'
  import {mapActions} from 'vuex'
  import clickoutside from 'assets/directive/clickoutside'
  export default {
    name: 'JAddressGroup',
    mixins: [parent],
    directives: { clickoutside },
    props: {
      value: {
        type: [Array, String],
        default () {
          return []
        }
      },
      level: {
        type: Number,
        default: 4
      },
      changeOnSelect: {
        type: Boolean,
        default: false
      },
      separator: {
        type: String,
        default: '/'
      },
      dropDownWidth: {
        type: String,
        default: '500px'
      },
      forbiddenInputUndefined: {
        type: Boolean,
        default: false
      },
      isChange: {
        type: Boolean,
        default: false
      },
      canChangeProvince: {
        type: [String, Boolean],
        default: ''
      },
      canChangeCity: {
        type: [String, Boolean],
        default: ''
      },
      canChangeDistrict: {
        type: [String, Boolean],
        default: ''
      },
      canChangeStreet: {
        type: [String, Boolean],
        default: ''
      },
      readonly: {
        type: Boolean,
        default: false
      },
      placeholder: {
        type: String,
        default: ''
      },
      returnItem: {
        type: Object,
        default: function () {
          return {}
        }
      }
    },
    data () {
      return {
        choose: Array.isArray(this.value) ? this.value : [],
        province: [],
        city: [],
        area: [],
        street: [],
        current: 1,
        spanText: '',
        pullRight: false,
        isOpen: false,
        inputText: ''
      }
    },
    watch: {
      value (val, oldVal) {
        if (isEqual(val, oldVal)) {
          return
        }
        this.initValue(val)
      }
    },
    computed: {
      jFormItem () {
        return this.findParentByComponentName('jFormItem')
      },
      hiddenOpen () {
        return () => {
          this.isOpen = false
        }
      },
      provinceNo () {
        return dotData(this.choose, '0.value') || ''
      },
      cityNo () {
        return dotData(this.choose, '1.value') || ''
      },
      areaNo () {
        return dotData(this.choose, '2.value') || ''
      },
      streetNo () {
        return dotData(this.choose, '3.value') || ''
      }
    },
    mounted () {
      window.App.initSlimScroll('.scroller')
      if (Array.isArray(this.value) && this.value.length > 0) {
        this.setInputText(this.value)
      }
      // 获取初始的city area street
      this.initValue(this.value)
    },
    destroyed () {
      if (!this.forbiddenInputUndefined) {
        this.$emit('input', undefined)
      }
    },
    methods: {
      initValue (val) {
        if (val && typeof val === 'string') {
          val = JSON.parse(val)
        }
        if (Array.isArray(val)) {
          this.choose = val
          this.setInputText(this.choose)
        }
        if (Array.isArray(val) && val.length === 0) {
          this.current = 1
          this.province = []
          this.city = []
          this.area = []
          this.street = []
        }
      },
      openMenu () {
        if (this.readonly) {
          return
        }
        if (this.isChange) {
          this.isOpen = false
        } else {
          this.isOpen = true
          if (this.province.length < 1) {
            this.getAddressData({value: '10000'}).then(arr => {
              this.province = arr
            })
          }
          // 获取市
          if (this.provinceNo) {
            this.getAddressData({value: this.provinceNo}).then(arr => {
              this.city = arr
            })
          }
          // 获取区
          if (this.cityNo) {
            this.getAddressData({value: this.cityNo}).then(arr => {
              this.area = arr
            })
          }
          // 获取街道
          if (this.areaNo) {
            this.getAddressData({value: this.areaNo}).then(arr => {
              this.street = arr
            })
          }
        }
      },
      // 关闭
      handleClose () {
        this.isOpen = false
      },
      chooseProvince (data, $event) {
        if (this.canChangeProvince === false) {
          return
        }
        this.$emit('chooseP', this.choose)
        this.chooseAddress(0, data)
        const nextLevel = 2
        this.city = []
        this.area = []
        this.street = []
        if (this.level === 1) {
          this.closeAddress($event)
          return
        }
        this.getAddressData({value: data.value}).then((city) => {
          this.city = city
          this.current = nextLevel
        })
      },
      chooseCity (data, $event) {
        if (this.canChangeCity === false) {
          return
        }
        this.$emit('chooseC', this.choose)
        this.chooseAddress(1, data)
        const nextLevel = 3
        this.area = []
        this.street = []
        if (this.level === 2) {
          this.closeAddress($event)
          return
        }
        this.getAddressData({value: data.value}).then((area) => {
          this.area = area
          this.current = nextLevel
        })
      },
      chooseArea (data, $event) {
        if (this.canChangeDistrict === false) {
          return
        }
        this.$emit('chooseA', this.choose)
        this.chooseAddress(2, data)
        const nextLevel = 4
        if (this.level === 3) {
          this.closeAddress($event)
          return
        }
        this.street = []
        this.getAddressData({value: data.value}).then((street) => {
          this.street = street
          this.current = nextLevel
        })
      },
      chooseStreet (data, $event) {
        if (this.canChangeDistrict === false) {
          return
        }
        this.$emit('canChangeStreet', this.choose)
        this.chooseAddress(3, data)
        if (this.level === 4) {
          this.closeAddress($event)
          return
        }
      },
      changeCurrent (data, level) {
        if (data.length) {
          this.current = level
        }
      },
      closeAddress ($event) {
        $event.stopPropagation()
        this.isOpen = false
        this.current = 1
        this.$emit('input', this.choose)
        this.$emit('change', this.choose, this.returnItem)
        this.setInputText(this.choose)
        this.emitValidate('change')
      },
      setInputText (choose) {
        this.inputText = arrayColumn(choose, 'text').join(this.separator)
      },
      chooseAddress (index, data) {
        this.choose[index] = data
        if (index < 1) {
          this.$emit('chooseProvinceData', this.choose, this.returnItem)
        }
        this.choose.splice(index + 1)
        if (this.changeOnSelect) {
          this.$emit('input', this.choose)
          this.$emit('change', this.choose, this.returnItem)
          this.setInputText(this.choose)
          this.emitValidate('change')
        }
      },
      // 发起验证
      emitValidate (type) {
        if (this.jFormItem) {
          this.jFormItem.$emit('j-form-validate', type)
        }
      },
      refreshInputText () {
        this.setInputText(this.choose)
      },
      ...mapActions([
        'getAddressData'
      ])
    }
  }
</script>

封装时间日期选择  组件:

components/base/form/jDateGroup
<template>
  <div class="input-group j-input-group readonly">
    <slot name="left"></slot>
    <input type="text"
           :value="value"
           class="form-control j-date-group"
           :readonly="true"
           style="border-style: solid; cursor: pointer;"
    />
    <slot name="right"></slot>
  </div>
</template>
<script>
  import moment from 'moment'
  import parent from 'assets/scripts/mixins/parent'
  export default {
    name: 'JDateGroup',
    mixins: [parent],
    props: {
      autoclose: {
        type: Boolean,
        default: true
      },
      format: {
        type: String,
        default: 'yyyy-mm-dd'
      },
      minView: {
        type: Number,
        default: 2
      },
      maxView: {
        type: Number,
        default: 3
      },
      minuteStep: {
        type: Number,
        default: 5
      },
      startDate: {
        type: String,
        default: ''
      },
      endDate: {
        type: String,
        default: ''
      },
      value: {
        type: String
      },
      pickerPosition: {
        type: String,
        default: 'bottom-right'
      },
      forbiddenInputUndefined: {
        type: Boolean,
        default: false
      },
      readonly: {
        type: Boolean,
        default: false
      }
    },
    data () {
      return {
        inputElement: '',
        unique: ''
      }
    },
    computed: {
      jFormItem () {
        return this.findParentByComponentName('jFormItem')
      },
      jFormTableItem () {
        return this.findParentByComponentName('jFormTableItem')
      },
      jForm () {
        return this.findParentByComponentName('jForm')
      }
    },
    watch: {
      startDate (val, oldVal) {
        if (val === oldVal) {
          return
        }
        const inputElement = this.getInputElement()
        window.$(inputElement).datetimepicker('setStartDate', val)
      },
      endDate (val, oldVal) {
        if (val === oldVal) {
          return
        }
        const inputElement = this.getInputElement()
        window.$(inputElement).datetimepicker('setEndDate', val)
      }
    },
    created () {
      if (this.jForm !== null) {
        this.unique = Symbol('j-form-value')
        this.jForm.$emit.apply(this.jForm, ['j.form.addValueFiled'].concat([this, this.unique]))
      }
    },
    destroyed () {
      if (!this.forbiddenInputUndefined) {
        this.$emit('input', undefined)
      }
      if (this.jForm !== null) {
        this.jForm.$emit.apply(this.jForm, ['j.form.removeValueField'].concat([this.unique]))
      }
    },
    mounted () {
      if (this.readonly === false) {
        const inputElement = this.getInputElement()
        let options = {
          autoclose: this.autoclose,
          language: 'zh-CN',
          format: this.format,
          minuteStep: this.minuteStep,
          minView: this.minView,
          maxView: this.maxView,
          pickerPosition: this.pickerPosition
        }
        if (this.startDate) {
          options.startDate = this.startDate
        }
        if (this.endDate) {
          options.endDate = this.endDate
        }
        if (this.value) {
          options.initialDate = this.value
        }
        window.$(inputElement).datetimepicker(options).on('changeDate', (ev) => {
          const momentFormat = this.format.replace(/y/g, 'Y').replace(/m/g, 'M').replace(/d/g, 'D').replace(/i/g, 'm')
          this.$emit('input', moment(ev.date).subtract(8, 'h').format(momentFormat))
          this.emitValidate('change')
        })
      }
      if (this.value) {
        this.setInputText(this.value)
      }
    },
    methods: {
      getInputElement () {
        this.inputElement = this.inputElement ? this.inputElement : this.$el.querySelector('.j-date-group')
        return this.inputElement
      },
      emitValidate (type) {
        if (this.jFormItem) {
          this.jFormItem.$emit('j-form-validate', type)
        }
        if (this.jFormTableItem) {
          this.jFormTableItem.$emit('j-form-validate', type)
        }
      },
      // 设置文本内容
      setInputText (text) {
        const inputElement = this.getInputElement()
        window.$(inputElement).val(text)
      },
      // 重置表单
      resetField () {
        this.setInputText('')
        this.$emit('input', undefined)
      }
    }
  }
</script>

猜你喜欢

转载自blog.csdn.net/qq_37126704/article/details/86692510