index.vue 3.83 KB
<template>
  <z-form ref="form" v-model="model" v-bind="schema.props" v-on="schema.props">
    <template v-for="(item, index) in schema.items">
      <template v-if="item.is">
        <z-form-item v-bind="keywordFilter(item)" :key="index">
          <slot
            v-if="$scopedSlots[item.prop]"
            :name="`${item.prop}`"
            :value="get(model, item.prop)"
            :onInput="value => onComponentInput({ value, item })"
            :props="{ value: get(model, item.prop) }"
            :listeners="{ input: value => onComponentInput({ value, item }) }"
          >
          </slot>
          <component v-else :is="item.is" :value="get(model, item.prop)" @input="value => onComponentInput({ value, item })" v-bind="item.props">
            <template v-if="item.render">
              <dynamic-render
                v-if="typeof item.render === 'function'"
                :render="
                  item.render($createElement, {
                    model,
                    value: get(model, item.prop),
                    onInput: value => onComponentInput({ value, item }),
                  })
                "
              ></dynamic-render>
              <static-render v-else :render="item.render"></static-render>
            </template>
            <template v-if="item.children">
              <childrens-render :value="item.children"></childrens-render>
            </template>
          </component>
          <slot :name="`label-${item.prop}`" slot="label"></slot>
          <slot :name="`error-${item.prop}`" slot="error"></slot>
        </z-form-item>
      </template>
      <template v-else>
        <z-form-item v-bind="keywordFilter(item)" :key="index">
          <slot :name="item.prop"></slot>
        </z-form-item>
      </template>
    </template>
    <slot v-if="schema.footer !== false" name="footer">
      <z-form-item>
        <el-button type="primary" @click="onSubmit">确定</el-button>
        <el-button plain @click="onCancel">取消</el-button>
      </z-form-item>
    </slot>
  </z-form>
</template>

<script>
import { cloneDeep, get, set } from '../utils';

export default {
  name: 'SchemaForm',
  components: {
    DynamicRender: {
      functional: true,
      render(h, context) {
        return context.props.render;
      },
    },
    StaticRender: {
      functional: true,
      render(h, context) {
        return h('span', [context.props.render]);
      },
    },
    ChildrensRender: {
      functional: true,
      render(h, context) {
        return context.props.value.map(item => {
          if (Array.isArray(item.children) && item.children.length > 0) {
            return h('childrens-render', { props: { value: item.children } });
          } else {
            return h(item.is, { ...item });
          }
        });
      },
    },
  },
  props: {
    value: {
      type: Object,
      default() {
        return {};
      },
    },
    schema: {
      type: Object,
      required: true,
    },
  },
  data() {
    return {
      model: this.value,
      originData: {},
    };
  },
  computed: {
    schemaProps() {
      return this.schema.props;
    },
  },
  created() {
    const { originData, ...other } = this._data;
    this.originData = cloneDeep(other);
  },
  watch: {
    value(val = {}) {
      this.model = val;
    },
    model: {
      handler(val) {
        this.$emit('input', val);
      },
      deep: true,
    },
  },
  methods: {
    get,
    keywordFilter(val = {}) {
      const { children, is, ...other } = val;
      return other;
    },
    onComponentInput({ value, item }) {
      set(this.model, item.prop, value);
    },
    onSubmit() {
      this.$refs.form.validate(valid => {
        if (valid) {
          this.$emit('submit', this.model);
        }
      });
    },
    onCancel() {
      this.model = cloneDeep(this.originData).model;
      this.$emit('cancel');
    },
  },
};
</script>