基于Element实现动态表单DynamicForm

555 阅读1分钟

背景:系统中表单的使用频率高,为了减少表单页面的搭建而频繁编写标签,使用组件,通过配置参数实现表单的搭建,可以有效提高开发效率。

意义:提高开发效率,方便维护

代码:vue组件,可直接使用,内置input、select、radio、checkbox、datetimerange、text、slot等,可根据自己的系统丰富控件类型。

1. 组件封装

<template>
  <el-form
    ref="dynamicForm"
    class="form-wrap"
    :model="tempForm"
    :rules="rules"
    :label-width="labelWidth"
    :label-position="labelPosition"
  >
    <div v-if="title" class="icon-title">{{ title }}</div>
    <el-row :gutter="24">
      <el-col
        v-for="(item,index) in formConfig"
        :key="index" :span="item.span"
      >
        <el-form-item
          :label="item.label"
          :prop="item.key"
          :rules="item.rules"
          :label-width="item.labelWidth"
        >
          <el-input
            v-if="item.type === 'input'"
            v-model.trim="tempForm[item.key]"
            :clearable="item.clearable === undefined ? true : item.clearable"
            :disabled="item.disabled"
            :placeholder="item.placeholder || '请输入'"
            :maxlength="item.maxlength"
            :show-word-limit="item.maxlength &&!(item.hasOwnProperty('showWordLimit') && !item.showWordLimit)"
            :show-password="item.showPassword"
            @input="change(item)"
          />
          <el-input
            v-if="item.type === 'textarea'"
            v-model="tempForm[item.key]"
            type="textarea"
            :disabled="item.disabled"
            :placeholder="item.placeholder || '请输入'"
            :maxlength="item.maxlength"
            :show-word-limit="item.maxlength &&!(item.hasOwnProperty('showWordLimit') && !item.showWordLimit)"
            :rows="item.rows"
            :autosize="item.autosize"
            @input="change(item)"
          />
          <el-select
            v-else-if="item.type === 'select'"
            v-model.trim="tempForm[item.key]"
            :clearable="item.clearable === undefined ? true : item.clearable"
            :disabled="item.disabled"
            :placeholder="item.placeholder || '请选择'"
            :collapse-tags="item.multiple || true"
            :multiple="item.multiple || false"
            :allow-create="item.allowCreate"
            :filterable="!!(item.allowCreate || item.filterable)"
            default-first-option
            @change="change(item)"
          >
            <el-option
              v-for="(option, selIndex) in item.options"
              :key="selIndex"
              :label="option.name"
              :value="option.id"
            />
          </el-select>
          <el-cascader
            v-else-if="item.type === 'cascader'"
            v-model="tempForm[item.key]"
            :placeholder="item.placeholder || '请选择'"
            :options="item.options"
            :show-all-levels="!!item.showAllLevels"
            :clearable="item.clearable === undefined ? true : item.clearable"
            filterable
            @change="change(item)"
          />
          <el-date-picker
            v-else-if="['datetrange','date','datetime','datetimerange'].includes(item.type)"
            v-model="tempForm[item.key]"
            :type="item.type"
            range-separator="至"
            :start-placeholder="item.startPlaceholder || '开始日期'"
            :end-placeholder="item.endPlaceholder || '结束日期'"
            :clearable="item.clearable === undefined ? true : item.clearable"
            :value-format="item.valueFormat || 'timestamp'"
            :disabled="item.disabled"
            :picker-options="item.pickerOptions"
            @change="change(item)"
          />
          <el-radio-group
            v-else-if="item.type === 'radio'"
            v-model="tempForm[item.key]"
            @change="change(item)"
          >
            <el-radio
              v-for="(radio,radioIndex) in item.options" :key="radioIndex"
              :disabled="item.disabled" :label="radio.id"
            >{{ radio.name }}</el-radio>
          </el-radio-group>
          <el-checkbox-group
            v-else-if="item.type === 'checkbox'"
            v-model="tempForm[item.key]"
            @change="change(item)"
          >
            <el-checkbox
              v-for="(checkbox,checkboxIndex) in item.options" :key="checkboxIndex"
              :disabled="item.disabled" :label="checkbox.id"
            >{{ checkbox.name }}</el-checkbox>
          </el-checkbox-group>
          <div v-else-if="item.type === 'inputNumber'" class="flex-between">
            <el-input-number
              v-model="tempForm[item.key]"
              :controls-position="item.controlsPosition || 'right'" :min="item.min || 0"
              :placeholder="item.placeholder || '请输入'"
              :max="item.max" @change="change(item)"
            />
            <div v-if="item.unit" class="ml-3">{{ item.unit }}</div>
          </div>
          <template v-else-if="item.type === 'text'">
            {{ tempForm[item.key] }}
          </template>
          <template v-else-if="item.type === 'slot'">
            <slot :name="item.slotName"/>
          </template>
        </el-form-item>
      </el-col>
    </el-row>
  </el-form>
</template>

<script>
export default {
  name: 'DynamicForm',
  model: {
    prop: 'form',
    event: 'change'
  },
  props: {
    title: {
      type: String,
      default: ''
    },
    labelWidth: {
      type: String,
      default: '100px'
    },
    labelPosition: {
      type: String,
      default: ''
    },
    rules: {
      type: Object,
      default: () => {
        return {}
      }
    },
    formConfig: {
      type: Array,
      default: () => {
        return []
      }
    },
    form: {
      type: Object,
      default: () => {
        return {}
      }
    }
  },
  watch: {
    form: {
      handler (val) {
        this.tempForm = _.cloneDeep(val)
      },
      deep: true,
      immediate: true
    }
  },
  data () {
    return {
      tempForm: {}
    }
  },
  methods: {
    change (item) {
      this.$nextTick(() => {
        if (item && item.change) {
          this.$emit(item.change, item, this.tempForm[item.key])
        }
        this.$emit('change', this.tempForm)
      })
    },
    validate (callback) {
      return this.$refs.dynamicForm.validate(callback)
    },
    clearValidate () {
      this.$refs.dynamicForm.clearValidate()
    },
    validateField (field) {
      this.$refs.dynamicForm.validateField(field)
    },
    resetFields () {
      this.$refs.dynamicForm.resetFields()
      this.$emit('change', this.tempForm)
    }
  }
}
</script>

<style lang="scss" scoped>
  .form-wrap{
    .unify-height{
      height: 40px;
    }
    .el-cascader{
      width: 100%;
    }
    /deep/.el-date-editor .el-range-separator{
      min-width: 30px;
    }
    .el-date-editor{
      width: 100%;
    }
    .el-input-number{
      width: auto;
      flex: 1;
    }
    /deep/.el-input{
      .el-input__inner{
        padding: 0 46px 0 15px;
      }
      &.el-date-editor{
        .el-input__inner{
          padding: 0 46px 0 30px;
        }
      }
    }
  }
</style>

2. 组件使用

// html
<DynamicForm
  ref="dynamicForm"
  title="DynamicForm演示"
  v-model="form"
  :rules="rules"
  :form-config="formConfig"
></DynamicForm>

//参数配置
const componentConstant={
    departmentOptions: [
      {id: '研发部', name: '研发部'},
      {id: '测试部', name: '测试部'},
      {id: '财务部', name: '财务部'},
      {id: '人事部', name: '人事部'}
    ],
    genderOptions: [
      {id: '0', name: '男'},
      {id: '1', name: '女'}
    ],
    technologyOptions: [
      {id: 'vue', name: 'vue'},
      {id: 'java', name: 'java'},
      {id: 'webpack', name: 'webpack'}
    ]
}
form: {
  technology: [],
  time: []
},
formConfig: [
  {label: '姓名', key: 'name', type: 'input'},
  {label: '部门', key: 'department', type: 'select', options: componentConstant.departmentOptions},
  {label: '性别', key: 'gender', type: 'radio', options: componentConstant.genderOptions},
  {label: '技术', key: 'technology', type: 'checkbox', options: componentConstant.technologyOptions},
  {label: '入职时间', key: 'time', type: 'datetimerange'},
  {label: '描述', key: 'description', type: 'textarea', placeholder: '请输入描述,限制在500字以内', rows: 4, maxlength: 500, showWordLimit: true}
],
rules: {
  name: [{required: true, message: '请输入姓名', trigger: 'blur'}],
  gender: [{required: true, message: '请选择性别', trigger: 'blur'}],
  department: [{required: true, message: '请选择部门', trigger: 'blur'}]
}

3.效果截图

image.png