index.vue 8.47 KB
<style scoped>
.edit-form {
  padding-right: 30px;
}
.group-title {
  font-weight: bold;
  padding: 15px 5px;
  border-bottom: 1px solid #d9d9d9;
  margin-bottom: 30px;
  /* margin-top: 15px;
  margin-bottom: 30px; */
}
/* .group-title:first-child {
  margin-top: 0px;
} */

.group-content {
  margin: 15px 0px;
}
</style>

<template>
  <el-form class="edit-form" size="small" ref="form" :model="model" :label-width="labelWidth">
    <template v-if="groupList.group">
      <!-- 这里可以合为一个代码段判断 -->
      <template v-for="(data, index) in groupList.data">
        <template v-if="data.list.length > 0">
          <!-- 这里可以判断是否满足分组条件决定显示title,另外下面content的class也可以判断,div可以换满span的col代替 -->
          <!-- 加上选项tips提示判断,类似avue -->
          <div class="group-title" :key="index + 'title'">{{ data.title }}</div>
          <el-row class="group-content" :key="index + 'list'" :gutter="15">
            <template v-for="(item, index) in data.list">
              <el-col v-if="componentVisible(item.visible)" v-show="componentShow(item.show)" :key="index + 'data'" :span="!item.span ? 24 : item.span">
                <el-form-item :label="item.label" :label-width="item.label ? undefined : item.labelWidth || '0px'" :prop="item.key" :rules="item.rules">
                  <component @input="(e) => handleComponentInput(e, item)" :is="item.type" @change="(e) => handleComponentChange(e, item)" v-on="handleComponentOn(item)" :style="componentStyle(item.style)" v-model="model[item.key]" v-bind="componentProps(item)"></component>
                </el-form-item>
              </el-col>
            </template>
          </el-row>
        </template>
      </template>
    </template>
    <template v-else>
      <el-row :gutter="15">
        <template v-for="(item, index) in list">
          <el-col v-if="componentVisible(item.visible)" v-show="componentShow(item.show)" :key="index" :span="!item.span ? 24 : item.span">
            <el-form-item :label="item.label" :label-width="item.label ? undefined : item.labelWidth || '0px'" :prop="item.key" :rules="item.rules">
              <component @input="(e) => handleComponentInput(e, item)" :is="item.type" @change="(e) => handleComponentChange(e, item)" v-on="handleComponentOn(item)" :style="componentStyle(item.style)" v-model="model[item.key]" v-bind="componentProps(item)"></component>
            </el-form-item>
          </el-col>
        </template>
      </el-row>
    </template>
    <!-- 使用slot,不用slot时可定义对齐位置 -->
    <div style="text-align: right">
      <el-button type="primary" size="small" :loading="submitLoading" @click="handleSubmit">确定</el-button>
      <el-button plain size="small" @click="handleCancel" style="margin-left: 8px">取消</el-button>
    </div>
  </el-form>
</template>

<script>
export default { // ------------------------------------ 解决外部的v-model绑定值失效问题
  name: 'Cform',
  props: {
    // 用于实例化本组件绑定v-model的值
    value: {
      type: Object,
      default: () => {
        return {};
      }
    },
    // 配置列表 --------------------------------------------可换成对象,传list的同时传其它参数?是否跟formProps重复???
    list: {
      type: Array,
      required: true
    },
    // 提交加载状态
    submitLoading: Boolean, // --------------------------------- isLoading 或者loading,考虑骨架屏?
    // 表单模式,一般结合外部模态框判断新增或修改使用 ------------------------------尽量去掉mode,在外部隐藏时销毁组件,避免历史值遗留问题
    mode: String,
    // 标签宽度 ------------------------------- form的props可以用单独的字段如formProps表示,动态传入,使用v-bind绑定
    labelWidth: {
      type: String,
      default: '90px'
    }
  },
  data() {
    return {
      // 编辑器表单模型
      model: {}
    };
  },
  watch: {
    value: function(val) {
      if (val) {
        Object.keys(this.model).forEach(key => {
          this.model[key] = val[key];
        });
      }
    },
    // 配置列表又改动时初始化表单模型
    list: function(value) {
      this.initModel(value);
      const obj = {};
      this.list.forEach(data => {
        if (data.group) {
          obj[data.group] = true;
        }
      });
    }
  },
  computed: {
    groupList() {
      const obj = {};
      this.list.forEach(data => {
        if (data.group) {
          if (!obj[data.group]) {
            obj[data.group] = [];
          }
          obj[data.group].push(data);
        }
      });
      return {
        group: Object.keys(obj).length > 0,
        data: Object.keys(obj).map(key => {
          return { title: key, list: obj[key] };
        })
      };
    }
  },
  created() {
    // 初始化表单模型
    this.initModel(this.list);
  },
  methods: {
    handleComponentInput(e, item) {
      if (item.oninput) {
        item.oninput(e, this.model, this.mode);
      }
      this.emitModel();
    },
    handleComponentChange(e, item) {
      if (item.onchange) {
        item.onchange(e, this.model, this.mode);
      }
    },
    handleComponentOn(item) {
      if (item.on) {
        if (typeof item.on === 'function') {
          return item.on(this.model, this.mode);
        } else {
          return item.on
        }
      } else {
        return undefined
      }
    },
    // 向上推送表单值到实例化的组件的v-model中
    emitModel() {
      const result = {};
      Object.keys(this.model).forEach(key => {
        if (this.componentIsVisible(key)) {
          let value = this.model[key];
          if (this.model[key] === null) {
            value = undefined;
          }
          result[key] = value;
        }
      });
      this.$emit("input", result);
    },
    // 初始化表单模型
    initModel(list) {
      const model = {};
      list.forEach(item => {
        model[item.key] = item.default;
      });
      this.model = model;
      this.emitModel();
    },
    // 设置表单模型值
    setValue(value) {
      this.reset();
      Object.keys(value).forEach(key => {
        this.model[key] = value[key];
      });
      this.emitModel();
    },
    // 判断名称为key的组件v-if是否为true
    componentIsVisible(key) {
      let visible = true;
      this.list.forEach((item) => {
        if (item.key === key) {
          if (typeof item.visible === 'function') {
            visible = item.visible(this.model, this.mode);
          } else {
            visible = item.visible === undefined ? true : item.visible;
          }
        }
      });
      return visible;
    },
    // 判断组件v-if状态
    componentVisible(visible = true) {
      let componentVisible = visible;
      if (typeof visible === 'function') {
        componentVisible = visible(this.model, this.mode);
      }
      return componentVisible;
    },
    // 判断组件v-show状态
    componentShow(show = true) {
      let componentShow = show;
      if (typeof show === 'function') {
        componentShow = show(this.model, this.mode);
      }
      return componentShow;
    },
    // 判断组件参数
    componentProps(item) {
      const { props = {}, propsEditor = {} } = item;
      let connectedProps = { ...props, ...propsEditor };
      Object.keys(connectedProps).forEach(key => {
        if (typeof connectedProps[key] === 'function') {
          connectedProps[key] = connectedProps[key](this.model, this.mode);
        }
      });
      return connectedProps;
    },
    // 判断组件样式
    componentStyle(style = {}) {
      return {
        width: "100%",
        ...style
      };
    },
    // 点击确定提交表单的操作
    handleSubmit(name) {
      this.$refs.form.validate(valid => {
        if (valid) {
          const result = {};
          Object.keys(this.model).forEach(key => {
            if (this.componentIsVisible(key)) {
              let value = this.model[key];
              if (this.model[key] === null) {
                value = undefined;
              }
              result[key] = value;
            }
          });
          this.$emit("submit", result);
        }
      });
    },
    // 重置表单
    reset() {
      Object.keys(this.model).forEach(key => {
        this.model[key] = (this.list.find(item => item.key === key) || {}).default;
      });
      this.$nextTick(() => {
        this.$refs.form.clearValidate();
      });
    },
    // 点击取消的操作
    handleCancel() {
      this.$emit("cancel");
    }
  }
};
</script>