<style>
.eagle-form {
  padding: 0px;
}
.eagle-form__group-title {
  font-weight: bold;
  padding: 15px 5px;
  border-bottom: 1px solid #d9d9d9;
  margin-bottom: 30px;
}
.eagle-form__group-icon {
  padding-right: 10px;
}
.eagle-form__group-content {
  margin: 15px 0px;
}
</style>

<template>
  <el-form class="eagle-form" ref="form" :model="model" v-bind="formProps">
    <el-row :gutter="15">
      <template v-for="(data, index) in listOption.dataList">
        <template v-if="listOption.isGroup">
          <el-tooltip :disabled="!data.tip" v-bind="bindItemTip(data.tip)" :key="data.key">
            <slot v-if="$scopedSlots[data.key] || $slots[data.key]" :name="data.key" v-bind="data"></slot>
            <el-col v-else class="eagle-form__group-title" :span="24">
              <i v-if="data.icon" :class="`el-icon-${data.icon} eagle-form__group-icon`"></i>
              <span>{{ data.label }}</span>
            </el-col>
          </el-tooltip>
        </template>
        <el-row :class="{ 'eagle-form__group-content': listOption.isGroup }" :key="'group-content-' + index" :gutter="15">
          <template v-for="(item, index) in data.list">
            <el-col v-if="bindItemVisible(item.visible)" v-show="bindItemShow(item.show)" :key="index + 'data'" :span="!item.span ? span : item.span">
              <el-form-item :label="item.label" :label-width="item.label ? undefined : item.labelWidth || '0px'" :prop="item.key" :rules="item.rules">
                <el-tooltip :disabled="!item.tip" v-bind="bindItemTip(item.tip)">
                  <slot v-if="$scopedSlots[`item-${item.key}`] || $slots[`item-${item.key}`]" :name="`item-${item.key}`" :model="model" v-bind="item"></slot>
                  <component v-else :is="item.type || 'el-input'" v-model="model[item.key]" v-bind="bindItemProps(item)" v-on="bindItemEvent(item)" :style="bindItemStyle(item.style)"></component>
                </el-tooltip>
              </el-form-item>
            </el-col>
          </template>
        </el-row>
      </template>
    </el-row>
    <slot v-if="$scopedSlots['footer'] || $slots['footer']" name="footer" :model="model" :submit="handleSubmit" :cancel="handleCancel"></slot>
    <el-row :gutter="15" v-else :style="footerStyle">
      <el-button type="primary" size="small" :loading="submitting" @click="handleSubmit">确定</el-button>
      <el-button plain size="small" @click="handleCancel" style="margin-left: 8px">取消</el-button>
    </el-row>
  </el-form>
</template>

<script>
export default {
  name: 'Form',
  props: {
    // 用于实例化本组件绑定v-model的值
    value: {
      type: Object,
      default: () => {
        return {};
      }
    },
    // 配置列表
    list: {
      type: Array,
      required: true
    },
    // 提交加载状态
    submitting: Boolean,
    // 表单参数
    formProps: {
      type: Object,
      default() {
        return {
          size: 'small',
          'label-width': '70px'
        }
      }
    },
    // 纯净提交
    submitPure: {
      type: Boolean,
      default: false
    },
    // 底部样式
    footerStyle: {
      type: [String, Object],
      default: 'text-align: center'
    },
    // 表单项占位
    span: {
      type: Number,
      default: 24
    }
  },
  data() {
    return {
      // 编辑器表单模型
      model: {}
    };
  },
  created() {
    // 初始化表单模型
    this.initModel(this.list);
  },
  mounted() {
    this.setModelValue(this.value);
  },
  computed: {
    // 配置列表键值对形式
    listKeySet() {
      let result = {};
      this.list.forEach(item => {
        result[item.key] = item;
      });
      return result;
    },
    // 配置列表解析为渲染配置项
    listOption() {
      let groupSet = {};
      this.list.forEach(data => {
        if (data.group) {
          if (typeof data.group === 'object') {
            if (!groupSet[`group-${data.group.key}`]) {
              groupSet[`group-${data.group.key}`] = {
                icon: data.group.icon,
                label: data.group.label,
                tip: data.group.tip,
                list: []
              };
            }
            groupSet[`group-${data.group.key}`].list.push(data);
          } else if (typeof data.group === 'string') {
            if (!groupSet[data.group]) {
              groupSet[data.group] = {
                label: data.group,
                list: []
              };
            }
            groupSet[data.group].list.push(data);
          }
        } else {
          if (!groupSet['group-default']) {
            groupSet['group-default'] = {
              label: '基本信息',
              list: []
            };
          }
          groupSet['group-default'].list.push(data);
        }
      });
      const isGroup = Object.keys(groupSet).length > 1;
      const dataList = Object.keys(groupSet).map(key => {
        return { key, ...groupSet[key] };
      })
      return { isGroup, dataList };
    }
  },
  watch: {
    // 组件外部v-model值更新后同步刷新model
    value(val) {
      this.setModelValue(val);
    },
    // 配置列表有改动时初始化表单模型
    list(value) {
      this.initModel(value);
    },
    model: {
      handler(val) {
        this.$emit("input", val);
        this.$emit("change", val);
      },
      deep: true
    }
  },
  methods: {
    // 设置表单值
    setModelValue(value) {
      Object.keys(this.model).forEach(key => {
        this.model[key] = value ? value[key] : undefined;
      });
      this.$nextTick(() => {
        this.$refs.form.clearValidate();
      });
    },
    // 绑定提示组件参数
    bindItemTip(tip) {
      if (typeof tip === 'string') {
        return { content: tip, effect: 'light' };
      } else if (typeof tip === 'object') {
        return tip;
      } else {
        return {};
      }
    },
    // 绑定组件事件
    bindItemEvent(item) {
      if (item.on) {
        if (typeof item.on === 'function') {
          return item.on(this.model);
        } else {
          return item.on
        }
      } else {
        return undefined
      }
    },
    // 初始化表单模型
    initModel(list) {
      list.forEach(item => {
        this.$set(this.model, item.key, item.default || undefined)
      });
    },
    // 判断列表项是否存在
    isItemVisible(key) {
      let visible = true;
      const item = this.listKeySet[key] || {};
      if (typeof item.visible === 'function') {
        visible = item.visible({ ...this.model }); // 返回model的复制结果，判断类属性禁止改变model，防止循环导致内存溢出
      } else {
        visible = item.visible === undefined ? true : item.visible; // 没有定义visible时返回true，否则返回visible定义的值。【注意：不可写成“ !item.visible ”】
      }
      return visible;
    },
    // 绑定组件v-if状态
    bindItemVisible(visible = true) {
      let result = visible;
      if (typeof visible === 'function') {
        result = visible(this.model);
      }
      return result;
    },
    // 绑定组件v-show状态
    bindItemShow(show = true) {
      let result = show;
      if (typeof show === 'function') {
        result = show(this.model);
      }
      return result;
    },
    // 绑定组件参数
    bindItemProps(item) {
      const { props = {} } = item;
      let result = { ...props };
      Object.keys(result).forEach(key => {
        if (typeof result[key] === 'function') {
          result[key] = result[key](this.model);
        }
      });
      return result;
    },
    // 绑定组件样式
    bindItemStyle(style = {}) {
      return {
        width: "100%",
        ...style
      };
    },
    // 点击确定提交表单的操作
    handleSubmit(name) {
      this.$refs.form.validate(valid => {
        if (valid) {
          const result = this.submitPure ? this.getPureModel() : JSON.parse(JSON.stringify(this.model));
          this.$emit("submit", result);
        }
      });
    },
    // 重置表单
    reset() {
      Object.keys(this.model).forEach(key => {
        this.model[key] = this.listKeySet[key] ? this.listKeySet[key].default : undefined;
      });
      this.$nextTick(() => {
        this.$refs.form.clearValidate();
      });
    },
    // 获取以初始list为准的纯净model值
    getPureModel() {
      const result = {};
      Object.keys(this.listKeySet).forEach(key => {
        if (this.isItemVisible(key)) {
          let value = this.model[key];
          if (this.model[key] === null) {
            value = undefined;
          }
          result[key] = value;
        }
      });
      return result;
    },
    // 点击取消的操作
    handleCancel() {
      this.$emit("cancel");
    }
  }
};
</script>