<template>
  <b-form novalidate class="container-fluid richform-components" @submit="search" ref="richform" :class="{ 'was-validated': wasValidated }">
    <b-row>
      <template v-for="c in form">
        <b-col v-if="'button' !== c.type && 'option' !== c.type" :cols="c.labelCol || 1" :offset="c.labelOffset || 0" class="text-right pr-2" :key="c.name + 'label'">
          <label :for="c.name" class="mb-4" :class="{ 'd-none': c.hide }"><span v-if="c.required" class="text-danger">*</span>{{c.label}}</label>
        </b-col>
        <b-col v-if="'button' !== c.type && 'option' !== c.type" :cols="c.inputCol || 2" :offset="c.inputOffset || 0" :key="c.name + 'input'">
          <template v-if="'text,password,email,number,url,tel,range,color'.indexOf(c.type) > -1">
            <b-form-input autocomplete="off" v-model.trim="c.value" :disabled="c.disabled" :aria-describedby="'inputLiveFeedback' + c.name" :pattern="c.pattern" :maxlength="c.maxlength" :min="c.min" :max="c.max" :class="{ 'd-none': c.hide }" :id="c.name" :name="c.name" :type="c.type" :required="c.required" :placeholder="c.placeholder" :formatter="c.format" />
            <b-form-invalid-feedback :id="'inputLiveFeedback' + c.name" v-if="c.msg">{{c.msg}}</b-form-invalid-feedback>
          </template>
          <template v-else-if="'time,date,datetime'.indexOf(c.type) > -1">
            <datepicker ref="datepicker" class="datepicker__custom" :disabled="c.disabled" :class="{ 'd-none': c.hide }" inputClass="form-control" v-model="c.value" :name="c.name" :required="c.required" :placeholder="c.placeholder" :format="c.config ? (c.config.format || 'YYYY-MM-DD') : 'YYYY-MM-DD'" :range-separator="c.config ? (c.config.rangeSeparator || '~') : '~'" :disabled-date="disabledDate.bind(this, c.config)" clearable />
          </template>
          <template v-else-if="'select,singleselect'.indexOf(c.type) > -1">
            <multiselect ref="customSelect" class="multi-select__custom" :disabled="c.disabled" v-model="c.value" :class="{ [`multi-select-inputId__${c.name}`]: true, 'd-none': c.hide }" :id="c.name" :name="c.name" :options="getOption(c)" :searchable="false" :label="c.optionName || 'text'" :track-by="c.optionName || 'text'" selectLabel="" selectedLabel="" deselectLabel="" :required="c.required" placeholder="" @input="customSelectInput(c)" />
          </template>
          <template v-else-if="'multiselect' === c.type">
            <multiselect ref="customSelect" class="multi-select__custom" :disabled="c.disabled" :class="{ [`multi-select-inputId__${c.name}`]: true, 'd-none': c.hide }" :id="c.name" :name="c.name" v-model="c.value" :options="getOption(c)" multiple :searchable="false" :close-on-select="false" :placeholder="c.placeholder" :label="c.optionName || 'text'" :track-by="c.optionName || 'text'" selectLabel="" selectedLabel="" deselectLabel="" :required="c.required" @input="customSelectInput(c)" />
          </template>
          <template v-else-if="'search,singlesearch'.indexOf(c.type) > -1">
            <multiselect ref="customSelect" class="multi-select__custom" :disabled="c.disabled" :class="{ [`multi-select-inputId__${c.name}`]: true, 'd-none': c.hide }" v-model="c.value" :id="c.name" :name="c.name" :label="c.optionName || 'text'" :track-by="c.optionName || 'text'" :placeholder="c.placeholder" selectLabel="" selectedLabel="" deselectLabel="" :options="getOption(c)" searchable :loading="c.isLoading" :max-height="600" :required="c.required" @input="customSelectInput(c)" @search-change="asyncFindOption(c)">
              <span slot="noResult">无搜索结果</span>
            </multiselect>
          </template>
          <template v-else-if="'multisearch' === c.type">
            <multiselect ref="customSelect" class="multi-select__custom" :disabled="c.disabled" :class="{ [`multi-select-inputId__${c.name}`]: true, 'd-none': c.hide }" v-model="c.value" :id="c.name" :name="c.name" :label="c.optionName || 'text'" :track-by="c.optionName || 'text'" :placeholder="c.placeholder" multiple :close-on-select="false" selectLabel="" selectedLabel="" deselectLabel="" :options="getOption(c)" searchable :loading="c.isLoading" :max-height="600" :required="c.required" @input="customSelectInput(c)" @search-change="asyncFindOption(c)">
              <span slot="noResult">无搜索结果</span>
            </multiselect>
          </template>
          <template v-else-if="'radios' === c.type">
            <b-form-radio-group class="radios-checkboxes__custom" :aria-describedby="'inputLiveFeedback' + c.name" :class="{ 'd-none': c.hide }" v-model="c.value" :disabled="c.disabled" :options="c.radios" :value-field="c.valueField || 'value'" :text-field="c.textField || 'text'" :required="c.required" :name="c.name" />
            <b-form-invalid-feedback :id="'inputLiveFeedback' + c.name" v-if="c.msg">{{c.msg}}</b-form-invalid-feedback>
          </template>
          <template v-else-if="'checkboxes' === c.type">
            <b-form-checkbox-group class="radios-checkboxes__custom" :aria-describedby="'inputLiveFeedback' + c.name" :class="{ 'd-none': c.hide }" v-model="c.value" :disabled="c.disabled" :options="c.checkboxes" :value-field="c.valueField || 'value'" :text-field="c.textField || 'text'" :required="c.required" :name="c.name" />
            <b-form-invalid-feedback :id="'inputLiveFeedback' + c.name" v-if="c.msg">{{c.msg}}</b-form-invalid-feedback>
          </template>
          <template v-else-if="'textarea' === c.type">
            <b-form-textarea autocomplete="off" :id="c.name" :aria-describedby="'inputLiveFeedback' + c.name" :maxlength="c.maxlength" :class="{ 'd-none': c.hide }" :name="c.name" v-model.trim="c.value" :disabled="c.disabled" :required="c.required" :placeholder="c.placeholder" :rows="c.rows || 3" :max-rows="c.maxRows || 6" :no-resize="c.noResize" />
            <b-form-invalid-feedback :id="'inputLiveFeedback' + c.name" v-if="c.msg">{{c.msg}}</b-form-invalid-feedback>
          </template>
        </b-col>
        <b-col class="button-list__custom mb-4" v-else-if="'button' === c.type" :cols="c.col || 1" :style="{ textAlign: c.align || 'right' }" :key="c.name + 'button'">
          <b-button type="submit" variant="primary" :class="{ 'd-none': c.hide }">{{submitName}}</b-button>
          <b-button variant="danger" @click="reset" :class="{ 'd-none': c.hide }" v-if="form.length !== 0">重置</b-button>
          <b-button v-for="(b, index) in c.buttons" :class="{ 'd-none': b.hide }" :variant="b.variant || 'secondary'" :key="index" @click="b.click"><i v-if="b.icon" :class="`mr-2 fa fa-${b.icon}`"></i>{{b.name}}</b-button>
        </b-col>
        <b-col class="button-list__custom mb-4" v-else-if="'option' === c.type" :cols="c.col || 1" :style="{ textAlign: c.align || 'right' }" :key="c.name + 'option'">
          <b-button v-for="(b, index) in c.options" :class="{ 'd-none': b.hide }" :variant="b.variant || 'secondary'" :key="index" @click="b.click"><i v-if="b.icon" :class="`mr-2 fa fa-${b.icon}`"></i>{{b.name}}</b-button>
        </b-col>
      </template>
      <slot></slot>
    </b-row>
  </b-form>
</template>
<script>
import $ from 'jquery'
export default {
  props: {
    form: {
      type: Array,
      required: true,
      validator(value) {
        const v = value.map(item => {
          return item.name || ''
        })
        return new Set(v).size === value.length
      }
    },
    submitName: {
      type: String,
      default: '查询'
    },
    setData: null
  },
  data() {
    return {
      wasValidated: false,
      datepicker: new Date()
    }
  },
  mounted() {
    this.initForm()
  },
  methods: {
    initForm() {
      // 用于重置表单恢复初始值
      this.formDefaultVal = this.getSubmitData()
      this.form.filter(item => (!!item.type && (item.type.indexOf('search') > -1 || item.type.indexOf('select') > -1) && !!item.url && Array.isArray(item.linkage))).forEach(item => {
        item.linkage.forEach(linkage => {
          let select = this.form.find(item => item.name === linkage.split('-')[0])
          if (!select) return
          if (Array.isArray(select.child)) {
            if (select.child.includes(item)) {
              select.child.push(item)
            }
          } else {
            select.child = [item]
          }
        })
      })
      this.$nextTick(() => {
        this.$refs.datepicker && this.$refs.datepicker.forEach(item => {
          $(item.$el).find('input')
            .attr('required', $(item.$el).attr('required'))
            .attr('id', $(item.$el).find('input').attr('name'))
            .attr('autocomplete', 'off')
            .attr('type', 'text')
            .attr('aria-required', true)
            .removeAttr('readonly')
          $(item.$el).removeAttr('required')
        })
        let list = this.form.filter(item => (!!item.type && (item.type.indexOf('search') > -1 || item.type.indexOf('select') > -1) && !!item.url && !item.likeSearch)),
          postList = []
        for (let i = 0; i < list.length; i++) {
          this.$set(list[i], 'isLoading', true)
          postList.push(
            this.$post(list[i].url, list[i].param || {}, { loading: false }).then(results => {
              (results && results.status === 0) && this.$set(list[i], 'options', Array.isArray(results.data) ? results.data : [])
            }).finally(() => {
              list[i].isLoading = false
            })
          )
        }
        this.axios.all(postList).then(() => {
          this.$emit('mounted')
        })
      })
    },
    search(event = null) {
      if (event) {
        event.preventDefault()
        event.stopPropagation()
      }
      this.wasValidated = true
      this.$refs.customSelect && this.$refs.customSelect.forEach((item, index) => {
        if (!$(item.$el).attr('required')) {
          $(item.$el).find('.multiselect__tags').removeClass('invalid').addClass('valid')
          return
        }
        const id = this.$refs.customSelect[index].id,
          value = this.form.find(item => item.name == id).value
        if (!!value && ((typeof value === 'string' && !!value) || (Array.isArray(value) && value.length !== 0) || (!Array.isArray(value) && typeof value === 'object'))) {
          $(item.$el).find('.multiselect__tags').removeClass('invalid').addClass('valid')
        } else {
          $(item.$el).find('.multiselect__tags').addClass('invalid').removeClass('valid')
        }
      })
      if (!this.$refs.richform.checkValidity()) return
      if ($('.multi-select__custom .multiselect__tags.invalid').length > 0) return
      this.$emit('submit', this.getSubmitData())
    },
    getSubmitData() {
      let data = {}
      this.form.forEach(item => {
        if (!item.type || item.value === undefined) return
        if ((item.value && typeof item.value === 'object')) {
          if (item.value.constructor === Date) {
            data[item.name] = this.formatDate(item.value, item.config ? (item.config.format || 'YYYY-MM-DD') : 'YYYY-MM-DD')
          } else if (Array.isArray(item.value)) {
            if (item.value.length === 2 && item.value[0].constructor === Date && item.value[1].constructor === Date) {
              const f = item.config ? (item.config.format || 'YYYY-MM-DD') : 'YYYY-MM-DD'
              data[item.name] = [this.formatDate(item.value[0], f), this.formatDate(item.value[1], f)]
              return
            }
            if (item.optionValue) {
              const val = item.value.reduce((prev, cur) => (prev + (prev ? ',' : '') + cur[item.optionValue]), '')
              data[item.name] = val ? val.split(',') : []
            } else {
              data[item.name] = JSON.parse(JSON.stringify(item.value))
            }
          } else {
            if (item.optionValue) {
              data[item.name] = item.value[item.optionValue] === 0 ? 0 : (item.value[item.optionValue] || '')
            } else {
              data[item.name] = JSON.parse(JSON.stringify(item.value))
            }
          }
        } else {
          data[item.name] = !item.value && item.value !== 0 ? '' : item.value
        }
      })
      return data
    },
    disabledDate(config, time) {
      if (!config) return false
      // time = new Date(this.formatDate(time, 'YYYY-MM-DD HH:mm:ss'))
      const max = this.form.find(item => item.name == config.maxDate_name)
      // if (max && max.value && new Date(this.formatDate(max.value, 'YYYY-MM-DD HH:mm:ss')) < time) return true
      if (max && max.value && new Date(max.value) < time) return true
      const min = this.form.find(item => item.name == config.minDate_name)
      // if (min && min.value && new Date(this.formatDate(min.value, 'YYYY-MM-DD HH:mm:ss')) > time) return true
      if (min && min.value && new Date(min.value) > time) return true
      // if (!!config.minDate && new Date(this.formatDate(config.minDate, 'YYYY-MM-DD HH:mm:ss')) > time) return true
      if (!!config.minDate && new Date(config.minDate) > time) return true
      // if (!!config.maxDate && new Date(this.formatDate(config.maxDate, 'YYYY-MM-DD HH:mm:ss')) < time) return true
      if (!!config.maxDate && new Date(config.maxDate) < time) return true
    },
    customSelectInput(select) {
      const hasVal = !!select.value && ((Array.isArray(select.value) && select.value.length !== 0) || (typeof select.value === 'object' && !!select.value[select.optionName || 'text']))
      Array.isArray(select.child) && select.child.forEach(async child => {
        let param = child.param || {},
          key = child.linkage.find(item => item.startsWith(select.name)).split('-')[1]
        child.value = ''
        this.$set(child, 'isLoading', true)
        if (hasVal) {
          if (key && key.split(":").length === 2) {
            param[key.split(":")[1]] = (Array.isArray(select.value) ? select.value[select.value.length - 1][key.split(":")[0]] : select.value[key.split(":")[0]]) || ''
          } else {
            param[key] = (Array.isArray(select.value) ? select.value[select.value.length - 1][key] : select.value[key]) || ''
          }
        } else {
          if (key && key.split(":").length === 2) {
            delete param[key.split(":")[1]]
          } else {
            delete param[key]
          }
        }
        const results = await this.$post(child.url, param, { loading: false })
        if (results && results.status === 0) {
          this.$set(child, 'options', Array.isArray(results.data) ? results.data : [])
        } else {
          this.$set(child, 'options', [])
        }
        child.isLoading = false
      })
      if (!this.wasValidated) return
      const el = $(`.multi-select-inputId__${select.name}`)
      if (!el.attr('required')) return
      if (hasVal) {
        el.find('.multiselect__tags').removeClass('invalid').addClass('valid')
      } else {
        el.find('.multiselect__tags').addClass('invalid').removeClass('valid')
      }
    },
    reset() {
      this.wasValidated = false
      $('.multi-select__custom .multiselect__tags').removeClass('invalid').removeClass('valid')
      this.form.forEach(item => {
        item.value = this.formDefaultVal[item.name]
      })
      this.$emit('update:setData', null)
    },
    // 做like查询
    async asyncFindOption(param) {
      if (!param.url || param.type.indexOf('search') === -1 || !param.likeSearch) return
      this.$set(param, 'isLoading', true)
      const results = await this.$post(param.url, {}, { loading: false })
      if (results && results.status === 0) this.$set(param, 'options', Array.isArray(results.data) ? results.data : [])
      param.isLoading = false
    },
    getOption(select) {
      return select.options || []
    },
    setForm() {
      if (this.setData && this.setData.constructor === Object) {
        for (let key in this.setData) {
          let input = this.form.find(item => item.name === key)
          if (!input) continue
          if ('select,singleselect,search,singlesearch'.indexOf(input.type) > -1) {
            if (!Array.isArray(input.options) || !input.optionValue) continue
            input.value = input.options.find(item => item[input.optionValue] === this.setData[key]) || {}
          } else if ('multiselect,multisearch'.indexOf(input.type) > -1) {
            if (!Array.isArray(input.options) || !input.optionValue) continue
            const v = Array.isArray(this.setData[key]) ? this.setData[key].join(',') : this.setData[key]
            input.value = input.options.filter(item => {
              if (v.indexOf(item[input.optionValue]) > -1) return true
            })
          } else {
            input.value = this.setData[key]
          }
        }
      } else {
        this.reset()
      }
    }
  },
  watch: {
    'setData': {
      handler: function() {
        this.setForm()
      },
      deep: true
    }
  }
}
</script>
<style lang="less">
.multi-select__custom {
  min-height: calc(2.25rem + 2px);
  .multiselect__tags {
    min-height: calc(2.25rem + 2px);
    padding: 0.375rem 40px 0.375rem 0.5rem;
    border-radius: 0.2rem;
    border: 1px solid #ced4da;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
  }
  .multiselect__select {
    height: calc(2.25rem + 1px);
    padding: 0 8px;
  }
  .multiselect__tags.invalid {
    border-color: #dc3545;
  }
  .multiselect__tags.valid {
    border-color: #ced4da;
  }
  .multiselect__single {
    padding-left: 0;
    margin-bottom: 0;
    font-size: 1rem;
    color: #495057;
    line-height: 1.5;
  }
  .multiselect__option {
    padding: 10px 0.5rem;
    min-height: 2.25rem;
    font-size: 0.875rem;
  }
  .multiselect__tag {
    display: inherit;
    padding: 2px 26px 2px 10px;
  }
  .multiselect__input {
    margin-bottom: 0;
    margin-top: 1px;
  }
  .multiselect__spinner {
    height: 1.8125rem;
  }
}

.datepicker__custom {
  width: 100%;
  input {
    height: calc(2.25rem + 2px);
    padding: 0.375rem 0.75rem;
    font-size: 1rem;
    border: 1px solid #ced4da;
    color: #495057;
  }
  .datepicker-popup {
    box-sizing: content-box;
  }
}

.button-list__custom>button:not(:last-child) {
  margin-right: 0.5rem;
}

.radios-checkboxes__custom {
  padding-top: 7px;
  label {
    padding-top: 0!important;
  }
}

.richform-components.was-validated {
  .custom-control-input:valid~.custom-control-label,
  .custom-control-input.is-valid~.custom-control-label {
    color: #495057;
  }

  .custom-control-input:valid~.custom-control-label::before,
  .custom-control-input.is-valid~.custom-control-label::before {
    background-color: #dee2e6;
  }

  .form-control:valid,
  .form-control.is-valid,
  .custom-select:valid,
  .custom-select.is-valid {
    border: 1px solid #ced4da;
  }

  .custom-control-input:valid:checked~.custom-control-label::before,
  .custom-control-input.is-valid:checked~.custom-control-label::before {
    background-color: #007bff;
  }
}

.richform-components .invalid-feedback {
  margin-top: 0;
  line-height: 18px;
}

.richform-components>.row>div {
  padding: 0;
  label {
    padding-top: 6px; // font-size: 15px;
  }
}

// .multi-select__custom {
//   min-height: calc(1.8125rem + 2px);
//   .multiselect__tags {
//     min-height: calc(1.8125rem + 2px);
//     padding: 0.25rem 40px 0.25rem 0.5rem;
//     border-radius: 0.2rem;
//     border: 1px solid #ced4da;
//     overflow: hidden;
//     white-space: nowrap;
//     text-overflow: ellipsis;
//   }
//   .multiselect__select {
//     height: 1.8125rem;
//     padding: 0 8px;
//   }
//   .multiselect__tags.invalid {
//     border-color: #dc3545;
//   }
//   .multiselect__tags.valid {
//     border-color: #ced4da;
//   }
//   .multiselect__single {
//     padding-left: 0;
//     margin-bottom: 0;
//     font-size: 0.875rem;
//     color: #495057;
//   }
//   .multiselect__option {
//     padding: 7px 0.5rem;
//     min-height: calc(1.8125rem + 2px);
//     font-size: 0.875rem;
//   }
//   .multiselect__tag {
//     display: inherit;
//     padding: 2px 26px 2px 10px;
//   }
//   .multiselect__input {
//     margin-bottom: 0;
//     margin-top: 1px;
//   }
//   .multiselect__spinner {
//     height: 1.8125rem;
//   }
// }
// .datepicker__custom {
//   width: 100%;
//   input {
//     height: calc(1.8125rem + 2px);
//     padding: 0.25rem 0.5rem;
//     font-size: 0.875rem;
//     border: 1px solid #ced4da;
//     color: #495057;
//   }
//   .datepicker-popup {
//     box-sizing: content-box;
//   }
// }
// .button-list__custom>button:not(:last-child) {
//   margin-right: 0.5rem;
// }
// .radios-checkboxes__custom {
//   padding-top: 3px;
// }
// .richform-components.was-validated {
//   .custom-control-input:valid~.custom-control-label,
//   .custom-control-input.is-valid~.custom-control-label {
//     color: #495057;
//   }
//   .custom-control-input:valid~.custom-control-label::before,
//   .custom-control-input.is-valid~.custom-control-label::before {
//     background-color: #dee2e6;
//   }
//   .form-control:valid,
//   .form-control.is-valid,
//   .custom-select:valid,
//   .custom-select.is-valid {
//     border: 1px solid #ced4da;
//   }
//   .custom-control-input:valid:checked~.custom-control-label::before,
//   .custom-control-input.is-valid:checked~.custom-control-label::before {
//     background-color: #007bff;
//   }
// }
// .richform-components .invalid-feedback {
//   margin-top: 0;
//   line-height: 18px;
// }
// .richform-components>.row>div {
//   padding: 0;
//   label {
//     padding-top: 3px;
//     font-size: 15px;
//   }
// }
</style>