使用 element-ui 来实现表单的自定义配置

156 阅读3分钟

项目介绍

项目地址:github.com/liaojunhong…

在企业级后台系统中,表单是最高频的交互组件之一。有些后台管理系统用户希望可以自主的进行表单字段的配置,再结合前端开发,实现用户自己定义的表单字段配置。

此项目是一个基于 Element UI 的自定义表单组件库,提供了一系列可配置的表单组件,支持表单设计、配置和预览功能。

功能特点

  • 支持多种表单组件类型
  • 组件配置可视化
  • 实时预览
  • 表单验证
  • 配置自定义组件

项目介绍

表单配置

如下图所示,实现表单配置的可视化,提供新增、编辑、删除表单项的功能。

image.png

新增表单项

新增表单项时,提供表单项的项目可选类型。

支持的表单配置项如下:

  • 文本输入框 (Input Textarea)
  • 数字输入框 (InputNumber)
  • 开关 (Switch)
  • 单选框 (Radio)
  • 复选框 (Checkbox)
  • 下拉选择框 (Select)
  • 日期选择器 (DatePicker)
  • 时间选择器 (TimePicker)
  • 日期时间选择器 (DateTimePicker)
  • 自定义组件(CustomItem)

image.png

表单展示

使用动态渲染is的方式渲染表单组件。

  • 通过组件的type类型去渲染不同的组件。
  • created钩子里面将表单数据的key通过$set的方式放入变量form对象中,以便后续数据的响应式。
  • 当组件内部将值改变时,用getValue方法获取最新的值。
<template>
  <div>
    <section class="tips" v-if="(formSettings || []).length === 0">请先在表单配置页面进行配置,然后即可展示配置的表单。</section>
    <el-form ref="form" v-else :model="form" :rules="rules" label-width="200px">
      <el-form-item
        v-for="item in formSettings" 
        :key="item.enName"
        :label="`${item.name}:`"
        :prop="item.enName"
      >
        <component 
          :is="item.type !== 'CustomItem' ? `Cu${item.type}` : item.customName"
          :item-settings="item"
          :item-value="form[item.enName]"
          @get-value="getValue"
        ></component>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="submitForm('form')">提交</el-button>
        <el-button type="primary" plain @click="clearValidate('form')">清空校验</el-button>
        <el-button @click="resetForm('form')">重置</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>
<script>
import { mapState } from 'vuex';

export default {
  computed: {
    ...mapState(['formSettings'])
  },
  data() {
    return {
      form: {},
      rules: {},
    }
  },
  created() {
    this.formSettings.map((item) => {
      // 处理表单的初始值
      this.$set(this.form, item.enName, undefined);
      // 处理表单验证
      this.$set(this.rules, item.enName, []);
      // 必填
      if (item.isRequired) {
        this.rules[item.enName].push({
          required: true,
          message: `请输入${item.name}`,
          trigger: 'blur'
        })
      }
      // 字数
      if (item.isShowWordLimit) {
        let message = '';
        let limitLength = {}
        if (item.maxlength && !item.minlength) {
          limitLength.max = item.maxlength;
          message = `长度不能超过${item.maxlength}个字符`;
        }
        if (!item.maxlength && item.minlength) {
          limitLength.min = item.minlength;
          message = `长度不能少于${item.minlength}个字符`;
        }
        if (item.maxlength && item.minlength) {
          limitLength = {
            max: item.maxlength,
            min: item.minlength
          }
          message = `长度需要在${item.minlength}-${item.maxlength}个字符之间`;
        }
        this.rules[item.enName].push({
            ...limitLength,
            message: message,
            trigger: 'blur'
          })
      }
    })
    this.$nextTick(() => {
      this.$refs.form && this.$refs.form.clearValidate();
    })
  },
  methods: {
    getValue(enName, value) {
      this.form[enName] = value;
    },
    // 表单-提交
    submitForm(formName) {
      this.$refs[formName].validate((valid) => {
        // 单独处理固定时间(范围)的必填验证
        const timePickerRequiredList = this.formSettings.filter((item) => item.type === 'TimePicker' && item.timePickerType === 2 && item.isRange && item.isRequired);
        timePickerRequiredList.forEach((item) => {
          const targetValue = this.form[item.enName];
          if (Array.isArray(targetValue) && (!targetValue[0] || !targetValue[1])) {
            const targetField = this.$refs[formName].fields.find(field => field.prop === item.enName);
            if (targetField) {
              targetField.validateState = 'error';
              targetField.validateMessage = '请输入' + item.name;
            }
          }
        })
        if (valid) {
          alert('提交');
        } else {
          return false;
        }
      });
    },
    // 表单-重置
    resetForm(formName) {
      this.$refs[formName].resetFields();
    },
    // 表单-清空校验
    clearValidate(formName) {
      this.$refs[formName].clearValidate();
    }
  }
}
</script>

对每一个element-ui的组件进行二次封装,以Input组件为例:

  • 组件内部设置value变量,接收父组件传入的值,如果传入的值为undefined,就使用默认值。
  • 监听value值的变化,当value改变时,使用$emitform派发最新的value值。
<template>
  <el-input
    v-model="value"
    :disabled="itemSettings.isDisabled"
    :clearable="itemSettings.isClearable"
    :show-word-limit="itemSettings.isShowWordLimit"
    :show-password="itemSettings.isShowPassword"
    v-bind="attrs"
    :placeholder="itemSettings.placeholder ? itemSettings.placeholder : `请输入${itemSettings.name}`"
  >
    <template v-if="itemSettings.prependSlot" slot="prepend">{{ itemSettings.prependSlot }}</template>
    <template v-if="itemSettings.appendSlot" slot="append">{{ itemSettings.appendSlot }}</template>
  </el-input>
</template>
<script>
export default {
  name: 'CuInput',
  props: ['itemSettings', 'itemValue'],
  data() {
    return {
      attrs: {},
      value: ''
    }
  },
  watch: {
    itemValue(newVal) {
      this.value = newVal;
    },
    value() {
      this.emitVal();
    },
  },
  mounted() {
    // 初始值处理
    this.value = this.itemValue !== undefined ? this.itemValue : this.getDefaultValue();
    this.emitVal();

    // 字数属性处理
    if (this.itemSettings.isLimitLength) {
      // 最小字数
      if (this.itemSettings.minlength) {
        this.attrs.minlength = this.itemSettings.minlength;
      }
      // 最大字数
      if (this.itemSettings.maxlength) {
        this.attrs.maxlength = this.itemSettings.maxlength;
      }
    }
    // 图标属性处理
    if (this.itemSettings.prefixIcon) {
      this.attrs['prefix-icon'] = this.itemSettings.prefixIcon;
    }
    if (this.itemSettings.suffixIcon) {
      this.attrs['suffix-icon'] = this.itemSettings.suffixIcon;
    }
  },
  methods: {
    // 获取默认值
    getDefaultValue() {
      if (this.itemSettings.defaultValue) {
        return this.itemSettings.defaultValue;
      }
      return '';
    },
    // 向Form派发value值
    emitVal() {
      this.$emit('get-value', this.itemSettings.enName, this.value);
    }
  }
}
</script>

image.png

使用说明

  1. 安装依赖
npm install
  1. 运行项目
npm run serve
  1. 构建项目
npm run build

项目结构

src/
├── components/
│   ├── FormItem/          # 表单组件
│   │   ├── CuInput.vue
│   │   ├── CuInputNumber.vue
│   │   ├── CuRadio.vue
│   │   ├── CuCheckbox.vue
│   │   ├── CuSelect.vue
│   │   ├── CuSwitch.vue
│   │   ├── CuTimePicker.vue
│   │   ├── CuDatePicker.vue
│   │   └── CuDateTimePicker.vue
│   └── FormSettings/      # 组件配置面板
│       ├── FormInput.vue
│       ├── FormInputNumber.vue
│       ├── FormRadio.vue
│       ├── FormCheckbox.vue
│       ├── FormSelect.vue
│       ├── FormSwitch.vue
│       ├── FormTimePicker.vue
│       ├── FormDatePicker.vue
│       └── FormDateTimePicker.vue
├── constants/            # 常量定义
└── utils/               # 工具函数

使用指南

配置表单

  1. 在表单配置页面中配置表单项
  2. 在表单展示中可以看到配置的表单项

配置自定义组件

  1. 在配置表单项的“项目类型”中选择“CustomItem 自定义组件”,自定义组件名称需要与组件的 name 属性提供的组件名称一致
  2. FormCustom 目录下创建自定义组件文件

添加新组件

  1. FormItem 目录下创建组件文件
  2. FormSettings 目录下创建对应的配置面板
  3. 在组件中实现必要的属性和方法
  4. 在配置面板中实现组件的配置项

注意事项

  1. 所有组件都基于 Element UI 开发
  2. 组件配置面板中的验证规则需要根据实际需求进行配置
  3. 日期时间组件的格式需要符合 Element UI 的要求
  4. 数字输入框的精度和步长需要合理配置