<template>
  <div :style="'height: ' + (height ? height : '100%;')">
    <slot name="title"></slot>
    <el-form ref="form" :model="formData" :rules="formRules" class="zy-form" :disabled="checkDisabled(mode)"
             :label-width="labelWidth" v-bind="$attrs" :validate-on-rule-change="false" label-position="right"
             :class="{'searchable': searchable}">
      <el-row>
        <el-col v-for="(filter, index) in formFilters" :key="index" :span="computeSpan(filter)"
                :style="{'float: left;': filter.key === 'image'}">
          <el-form-item v-if="filter.component === 'blank'"></el-form-item>
          <el-divider v-else-if="filter.key.includes('divider')"
                      :content-position="filter.position ? filter.position : 'left'">
            {{ filter.label }}
          </el-divider>
          <el-form-item v-else :label="computeLabel(filter)" :prop="filter.key"
                        :label-width="filter.labelWidth || labelWidth"
                        :class="{'readonly':filter.readonly, 'break-line': filter.inline === false}">
            <el-input v-if="filter.component === 'input'"
                      v-model="formData[filter.key]"
                      v-bind="filter"
                      :disabled="checkDisabled(filter)"
                      @input="(value)=>change(filter, value)">
            </el-input>
            <el-input v-if="filter.component === 'input_area'"
                      v-model="formData[filter.key]"
                      type="textarea" :rows="filter.rows || 3"
                      v-bind="filter" :resize="!checkDisabled(filter) && !filter.readonly ? 'vertical' : 'none'"
                      :disabled="checkDisabled(filter)"
                      @input="(value)=>change(filter, value)">
            </el-input>
            <el-input v-if="filter.component === 'input_number'"
                      v-model="formData[filter.key]"
                      type="number" :controls="false"
                      v-bind="filter"
                      :disabled="checkDisabled(filter)"
                      @input="(value)=>change(filter, value)">
            </el-input>
            <el-input v-if="filter.component === 'input_password'"
                      v-model="formData[filter.key]"
                      type="password" :controls="false"
                      v-bind="filter"
                      :disabled="checkDisabled(filter)"
                      @focus="()=>change(filter, '')"
                      @blur="()=>passwordBlur(filter)"
                      @input="(value)=>change(filter, value)">
            </el-input>
            <zy-select v-if="filter.component === 'select'"
                       v-model="formData[filter.key]"
                       v-bind="filter"
                       filterable style="width: 100%"
                       :filter-method="query=>filterMethod(query, filter)"
                       :disabled="checkDisabled(filter)"
                       @change="value=>change(filter, value)"
                       @visible-change="flag=>visibleChange(flag, filter)">
              <el-option v-for="option in options[filter.key]"
                         :key="option['key']"
                         :value="option['key']"
                         :label="option['label']"></el-option>
            </zy-select>
            <el-date-picker v-if="filter.component === 'date'" type="date"
                            v-model="formData[filter.key]"
                            v-bind="filter"
                            :disabled="checkDisabled(filter)"
                            value-format="yyyy-MM-dd"
                            @change="value=>change(filter, value)"
                            style="width: 100%;">
            </el-date-picker>
            <el-date-picker v-if="filter.component === 'datetime'" type="datetime"
                            v-model="formData[filter.key]"
                            v-bind="filter"
                            :disabled="checkDisabled(filter)"
                            value-format="yyyy-MM-dd HH:mm:ss"
                            @change="value=>change(filter, value)"
                            style="width: 100%;">
            </el-date-picker>
            <el-date-picker v-if="filter.component === 'month'" type="month"
                            v-model="formData[filter.key]"
                            v-bind="filter"
                            :disabled="checkDisabled(filter)"
                            value-format="yyyy-MM"
                            @change="value=>change(filter, value)"
                            style="width: 100%;">
            </el-date-picker>
            <el-time-select v-if="filter.component === 'time'" type="time"
                            v-model="formData[filter.key]"
                            v-bind="filter"
                            :disabled="checkDisabled(filter)"
                            value-format="HH:mm"
                            @change="value=>change(filter, value)"
                            style="width: 100%;"
                            :picker-options="{start: '00:00', step: '00:05', end: '23:55'}">
            </el-time-select>
            <el-radio-group v-if="filter.component === 'radio'"
                            v-model="formData[filter.key]"
                            :disabled="checkDisabled(filter)"
                            @change="value=>change(filter, value)">
              <el-radio v-for="option in options[filter.key]" :key="option.key" :label="option.key">
                {{ option.label }}
              </el-radio>
            </el-radio-group>
            <el-checkbox v-if="filter.component === 'checkbox'"
                         v-model="formData[filter.key]"
                         :disabled="checkDisabled(filter)"
                         @change="value=>change(filter, value)">
              {{ filter.label }}
            </el-checkbox>
          </el-form-item>
        </el-col>
      </el-row>
    </el-form>
    <el-form v-if="searchable" class="form-footer">
      <el-form-item class="form-action">
        <el-button type="primary" @click="search">查询</el-button>
        <el-button @click="resetFields">重置</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>
<script>
  import {WebpageQueryService, WebpageDataService} from "../../../assets/webpage";
  import utils from '../../../assets/utils'

  export default {
    name: 'ZyForm',
    components: {},
    props: {
      filters: {type: Array, default: () => []},
      labelWidth: {type: String, default: '80px'},
      cols: {type: Number, default: 1},
      searchable: {type: Boolean, default: false},
      height: {type: String, default: '100%'},
      mode: {type: String, default: 'create'},
    },
    inject: {
      pageData: {
        type: Function, default: () => {
          return () => new Object({selection: [], params: {}, button: {}})
        }
      }
    },
    data() {
      return {
        formFilters: [],
        formData: {},
        formRules: {},
        options: {},
      }
    },
    created() {
    },
    mounted() {
    },
    watch: {
      filters: {
        handler: function (nv) {
          this.formFilters = _.cloneDeep(nv);
          this.initFormData();
          this.initFormRules();
        },
        deep: true,
        immediate: true
      }
    },
    computed: {},
    methods: {
      initFormData() {
        let _this = this;
        _this.formFilters.forEach(filter => {
          filter.placeholder = this.computePlaceholder(filter);
          filter.clearable = Object.prototype.hasOwnProperty.call(filter, 'clearable') ? filter['clearable'] : true;
          if (filter.component === 'checkbox') filter.defaultValue = !!filter.defaultValue;
          _this.$set(_this.formData, filter.key, filter.defaultValue);
          if (['select', 'radio'].includes(filter.component)) _this.loadOptions(filter);
        });
      },
      loadOptions(filter) {
        let _this = this;
        if (filter.source) {
          _this.getOptions(filter, _this.getParams(filter));
        } else {
          if (filter.component === 'radio' && _.isEmpty(filter.options)) {
            filter.options = [{key: true, label: '是'}, {key: false, label: '否'}];
          }
          _this.$set(_this.options, filter.key, (filter.options || []).slice(0, 50));
          _this.addSelectedOption(filter);
        }
      },
      getOptions(filter, params) {
        let _this = this;
        if (filter.parents instanceof Array) {
          for (let p of filter.parents) {
            if (!params[p]) {
              filter.options = [];
              _this.$set(_this.options, filter.key, filter.options.slice(0, 50));
              return;
            }
          }
        }
        WebpageQueryService.webpage_query_filter_options(_.merge({
          _ids: _.map(this.pageData().selection, 'id')
        }, params), (res) => {
          filter.options = res.content;
          _this.$set(_this.options, filter.key, filter.options.slice(0, 50));
          _this.addSelectedOption(filter);
        })
      },
      addSelectedOption(filter) {
        let _this = this;
        let originValue = _this.formData[filter.key];
        if (filter.multiple) {
          originValue instanceof Array && originValue.forEach(v => {
            let option = _.find(filter.options, option => option.key === v);
            if (option) _this.options[filter.key] = _.union(_this.options[filter.key], [option]);
          })
        } else {
          let option = _.find(filter.options, option => option.key === originValue);
          if (option) _this.options[filter.key] = _.union(_this.options[filter.key], [option]);
        }
      },
      getParams(filter) {
        let _this = this;
        let params = {_code: filter.source};
        if (filter.sourceParams) {
          _.merge(params, filter.sourceParams);
        }
        _.union(filter.parents, filter.extParents).forEach(p => {
          let parent = _.find(this.formFilters, q => q.key === p);
          if (parent) {
            params[parent.cascadingKey || p] = _this.formData[p];
          } else {
            params[p] = _this.pageData().params[p];
          }
        });
        return params;
      },
      initFormRules() {
        let _this = this;
        _this.formFilters.forEach(filter => {
          if (!filter.component || this.checkDisabled(filter) || filter.readonly) return;
          let message = filter.component.includes('input') ? '请输入' : '请选择';
          _this.$set(_this.formRules, filter.key, [{
            required: !!filter.required,
            trigger: filter.component === 'select' ? 'change' : 'blur',
            validator: filter.validator instanceof Function
                ? (rule, value, callback) => filter.validator(rule, value, callback, this.formData)
                : (rule, value, callback) => {
                  if (rule.required && filter.component === 'input_number_range') {
                    if (typeof this.formData[filter.keys instanceof Array ? filter.keys[0] : 'start' + filter.key[0].toUpperCase() + filter.key.slice(1)] !== 'number'
                        || typeof this.formData[filter.keys instanceof Array ? filter.keys[1] : 'end' + filter.key[0].toUpperCase() + filter.key.slice(1)] !== 'number') {
                      callback(new Error(message + filter.label));
                    }
                    callback();
                  } else if (rule.required && (value === undefined || value === null || value === '' || value instanceof Array && value.length === 0)) {
                    callback(new Error(message + filter.label));
                  } else {
                    callback();
                  }
                }
          }]);
        })
      },
      checkDisabled(filter) {
        return !filter;
      },
      computeSpan(filter) {
        return filter.span || 24;
      },
      computeLabel(filter) {
        return filter.component === 'checkbox' ? '' : (filter.label ? (filter.label + ':') : '');
      },
      computePlaceholder(filter) {
        return !this.checkDisabled(filter) && !filter.readonly ? (filter.placeholder || ('请输入' + filter.label)) : '';
      },
      visibleChange(flag, filter) {
        let _this = this;
        if (flag && !_this.formData[filter.key]) {
          _this.options[filter.key] = filter.options instanceof Array ? filter.options.slice(0, 50) : [];
        }
      },
      filterMethod(query, filter) {
        let _this = this;
        if (query) {
          let queryArr = query.replace(/\s+/g, ' ').split(" ");
          _this.options[filter.key] = (filter.options || []).filter(i => {
            let matched = true;
            for (let q of queryArr) {
              matched = matched && i.label.toLowerCase().includes(q.toLowerCase());
            }
            return matched;
          }).slice(0, 50);
        } else {
          _this.options[filter.key] = filter.options instanceof Array ? filter.options.slice(0, 50) : [];
        }
        let selected = (filter.options || []).filter(i => {
          if (!_this.formData[filter.key]) {
            return false;
          } else if (_this.formData[filter.key] instanceof Array) {
            return _this.formData[filter.key].includes(i.key);
          } else {
            return _this.formData[filter.key] === i.key;
          }
        });
        if (selected) {
          selected.forEach(i => {
            if (!_this.options[filter.key].includes(i)) {
              _this.options[filter.key].unshift(i);
            }
          })
        }
      },
      passwordBlur(filter) {
        let _this = this;
        if (_this.formData[filter.key]) {
          _this.formData[filter.key] = utils.md5(_this.formData[filter.key])
        }
      },
      change(filter, value, ignoreReload) {
        let _this = this;
        if (filter.component === 'input_number') value = +value;
        if (filter.component !== 'input_number_range') _this.$set(_this.formData, filter.key, value);
        if (filter.component === 'select' && !value) _this.filterMethod('', filter);
        if (filter.children instanceof Array) {
          filter.children.forEach(i => {
            _this.formData[i] = '';
            let childFilter = _.find(_this.formFilters, j => j.key === i);
            _this.change(childFilter, '', ignoreReload);
            if (childFilter && childFilter.source) {
              let params = _this.getParams(childFilter);
              _this.getOptions(childFilter, params);
            }
          })
        }
        if (filter.changeScript) {
          let change = new Function('d', filter.changeScript);
          change(this.formData);
        } else if (filter.change instanceof Function) {
          filter.change(value, this.formData, this.setData, this.changeRequired, this.clearValidate)
        }
        if (!ignoreReload && filter.reload) {
          let params = {buttonCode: this.pageData().button.code};
          params[this.pageData().button.forms[0]] = this.formData;
          WebpageDataService.webpage_data_reload(params, res => {
            _.map(this.formItems, 'key').map(key => this.setData(key, res.content[this.pageData().button.forms[0]][key], false, true))
          })
        }
      },
      setData(key, value, silent, ignoreReload) {
        let filter = _.find(this.formFilters, i => i.key === key);
        this.$set(this.formData, key, value)
        if (filter && !silent) {
          this.change(filter, value, ignoreReload);
        }
      },
      changeRequired(key, required) {
        this.formRules[key][0].required = required
      },
      clearValidate(keys) {
        this.$refs['form'].clearValidate(keys)
      },
      validateForm(valid) {
        let _this = this;
        _this.$refs['form'].validate(valid);
      },
      search() {
        let _this = this;
        _this.$refs['form'].validate(valid => {
          if (valid) {
            let data = _.cloneDeep(_this.formData);
            _this.$emit("search", data);
          }
        });
      },
      resetFields() {
        for (let key in this.formData) {
          if (Object.prototype.hasOwnProperty.call(this.formData, key)) {
            this.formData[key] = undefined;
          }
        }
        this.$refs['form'].resetFields();
      },
    },
  }
</script>
<style scoped lang="scss">
  .zy-form {
    padding-bottom: 10px;
    height: 100%;
    overflow-y: auto;
    overflow-x: hidden;

    &.searchable {
      height: calc(100% - 45px);
    }

    .readonly {
      ::v-deep input, ::v-deep textarea {
        border: none;
        border-bottom: 1px solid #dddddd;
        background-color: #FFF;
        border-radius: 0;
        color: #606266;
        cursor: text;
        font-size: 14px;
        padding: 0 5px;
      }
    }

    .break-line {
      ::v-deep.el-form-item__label {
        float: none;
        display: block;
        white-space: pre;
      }
    }
  }

  .form-footer {
    border-top: 1px solid #dddddd;

    .form-action {
      text-align: right;
      margin-bottom: 0 !important;
      white-space: nowrap;
      overflow: hidden;
    }
  }
</style>
