封装通用el-form表单(2种方式)

1、序言

         项目地址:git clone form-demo: 封装通用el-form

        一个后台管理系统最常见的是表单,表单最常见的是输入框、下拉选择、日期选择、单选、复选框等等, 系统添加若干模块,就复制粘贴若干个el-form、el-form-item,有一说一,完成需求快是快,但是代码冗余的部分太多了,能不能通过配置方式,自动生成el-form、el-form-item

        不封装代码前:

        封装代码后:

        两种封装方式的变量、方法名基本一致!

2、自定义组件方式封装el-form

        2.1、封装

        (1)新建commentForm文件夹,并创建index.vue文件

         (2)index.vue中

<template>
  <div>
    <el-form ref="form" :model="form" :rules="rules" :inline="inline" :label-width="labelWidth">
      <template v-for="(item, index) in formItemList">
        <!-- 输入框类型 -->
        <template v-if="item.type === 'input'">
          <el-form-item :label="item.label" :prop="item.model">
            <el-input v-model.trim="form[item.model]" :type="`${item.category || 'text'}`"
              :style="`width: ${item.width || '250px'}`" :clearable="item.clearable === undefined || item.clearable"
              filterable :placeholder="item.placeholder" />
          </el-form-item>
        </template>

        <!-- 下拉选择框 -->
        <template v-if="item.type === 'select'">
          <el-form-item :label="item.label" :prop="item.model">
            <el-select v-model.trim="form[item.model]" :style="`width:${item.width || '250px'}`" clearable
              :placeholder="item.placeholder || ''">
              <el-option v-for="(i, key) in  item.options" :key="i[item.value] || key" :label="i[item.label] || i.label"
                :value="i[item.value] || i.value">
              </el-option>
            </el-select>
          </el-form-item>
        </template>

        <!-- 日期选择器 -->
        <template v-if="item.type === 'date-picker'">
          <el-form-item :prop="item.model" :label="item.label">
            <el-date-picker v-model.trim="form[item.model]" type="daterange" range-separator="至" start-placeholder="开始日期"
              end-placeholder="结束日期">
            </el-date-picker>
          </el-form-item>
        </template>

        <!-- 开关 -->
        <template v-if="item.type === 'switch'">
          <el-form-item :prop="item.model" :label="item.label">
            <el-switch v-model="form[item.model]"></el-switch>
          </el-form-item>
        </template>

        <!-- 复选框 -->
        <template v-if="item.type === 'checkbox'">
          <el-form-item :prop="item.model" :label="item.label">
            <el-checkbox-group v-model="form[item.model]">
              <el-checkbox :label="item.label" :key="index" v-for="(item, index) in item.options"></el-checkbox>
            </el-checkbox-group>
          </el-form-item>
        </template>

        <!-- 单选框 -->
        <template v-if="item.type === 'radio'">
          <el-form-item :prop="item.model" :label="item.label">
            <el-radio-group v-model="form[item.model]">
              <el-radio :label="item.label" :key="index" v-for="(item, index) in item.options"></el-radio>
            </el-radio-group>
          </el-form-item>
        </template>

        <!-- 自己拓展... -->
      </template>
    </el-form>
  </div>
</template>

<script>
export default {
  props: {
    form: {
      type: Object,
      default: () => { }
    },
    // 生成el-form-item的数组
    formItemList: {
      type: Array,
      default: () => [],
    },
    // 是否行内表单模式
    inline: {
      type: Boolean,
      default: false,
    },
    // el-form-item的label宽度
    labelWidth: {
      type: String,
      default: '80px',
    },
    // 表单校验规则
    rules: {
      type: Object,
      default: () => { },
    },
  },
  data() {
    return {
    }
  },
  methods: {
    // 返回经过校验的表单
    returnForm() {
      // 表单校验 
      this.$refs['form'].validate((valid) => {
        if (valid) {
          this.$emit('getForm', this.form)
        }
      })
    },
    // 重置表单
    resetForm() {
      // 重置复选框
      this.$refs.form.resetFields();
    }
  }
}
</script>

<style scoped></style>

        (3)注意事项:

  •  el-checkbox中v-model绑定的值要事先存在,不然就会报出无法找到length属性的错误

            所以提前在data中声明一个数组,v-model绑定这个数组,返回表单给父组件时,将其添加到表单对象中,重置表单时,使这个数组为空即可实现重置表单功能!

  • 重置不成功的原因可能是:(1)未添加prop        (2)prop绑定的值与model绑定的对象所对应的属性不一致

        2.2、使用

重点关注部位:

(1)formItemList中的type决定生成什么类型的表单项,如输入框、下拉选项等等

(2)formItemList中的model表示表单项双向绑定的名称,也是子组件返回对象给父组件中对象的属性名

(3)formItemList中的options表示下拉选项、复选框、单选按钮的选项值

(4)@getForm绑定的事件可以获取子组件返回给父组件经过校验的表单,通常不在子组件进行网络请求,让父组件进行网络请求

(5)父组件通过$refs获取组件实例从而调用子组件重置表单、提交表单的方法

<template>
  <div id="app">
    <CommonForm ref="form" :form="form" :rules="rules" :formItemList="formItemList" @getForm="getForm">
    </CommonForm>
    <el-row>
      <el-button type="primary" @click="submitForm">提交</el-button>
      <el-button @click="resetForm">重置</el-button>
    </el-row>
  </div>
</template>

<script>
import CommonForm from '@/components/commonForm'
export default {
  name: 'App',
  components: {
    CommonForm,
  },
  data() {
    return {
      form: {},
      editForm: {
        name: '张三',
        region: 'shanghai',
        delivery: true,
        resource: '线上品牌商赞助',
        startTime: [
          '2023-05-10',
          '2023-05-11'
        ],
        types: '线下主题活动',
      },
      formItemList: [
        { label: '活动名称', type: 'input', model: 'name', placeholder: '请输入活动名称' },
        { label: '活动区域', type: 'select', model: 'region', placeholder: '请选择状态', options: [{ label: '上海', value: 'shanghai' }, { label: '北京', value: 'beijing' }] },
        { label: '活动时间', type: 'date-picker', model: 'startTime', },
        { label: '即时配送', type: 'switch', model: 'delivery' },
        { label: '活动性质', type: 'checkbox', model: 'type', modelString: 'types', options: [{ label: '美食/餐厅线上活动' }, { label: '地推活动' }, { label: '线下主题活动' }, { label: '单纯品牌曝光' },] },
        { label: '特殊资源', type: 'radio', model: 'resource', options: [{ label: '线上品牌商赞助' }, { label: '线下场地免费' }] },
      ],
      rules: {
        name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
      }
    }
  },
  created() {
    // 表单初始化
    this.formInit()
  },
  methods: {
    // 表单初始化
    formInit() {
      this.form = JSON.stringify(this.editForm) ? this.editForm : {}

      // checkbox类型的组件需要初始化数组
      this.formItemList.forEach(item => {
        if (['checkbox'].includes(item.type)) {
          if (!this.form[item.modelString]) {
            this.$set(this.form, item.model, [])
          } else {
            this.$set(this.form, item.model, this.form[item.modelString]?.split(','))
          }
        }
      })
    },
    // 接受子组件传回来的form表单内容
    getForm(val) {
      this.formItemList.forEach(item => {
        // 
        if (['checkbox'].includes(item.type)) {
          if (this.form[item.model] && this.form[item.model]?.length != 0) {
            val[item.modelString] = val[item.model]?.join(',')
          }
        }
      })
      console.log('val:', val);
      // 此处可以请求后台了
    },
    // 点击提交,触发表单校验
    submitForm() {
      this.$refs.form.returnForm();
    },
    // 重置表单
    resetForm() {
      this.$refs.form.resetForm();
    }
  }
}
</script>

<style>
#app {
  display: flex;
  flex-direction: column;
  align-items: center;
  color: #2c3e50;

}
</style>

        2.3、回显数据

        准备一个对象:editForm 模拟form已经有了数据,对editForm进行非空判断,如果有值则将其赋值给form,无值则赋值{}

this.form = JSON.stringify(this.editForm) ? this.editForm : {}

        刚刚说过elementUIcheckbox使用v-model绑定的值需要为真实存在的数组,若在form组件额外实例化一个变量专门处理checkbox显得不那么智能化 

        如何做到传一个{}对象给form组件,不报上述错误,并且还能回显数据,这就要在引用form组件的组件进行一下对象的初始化了

        (1)像elementuicheckbox复选框、Cascader 级联选择器、日期组件等等v-model绑定的是数组,而有时候后端接口文档不是数组,需要你手动处理,打个比方:

        前端将多个选中的值(这些值存在数组)使用字符串形式且里面的值用逗号隔开,比如:

[1, 2, 3, 4]  =>  "1,2,3,4",这样传给后端,后端返回“1,2,3,4”,要求你能转化成数组[1,2,3,4]渲染到elementui组件。

split(','):将字符串中逗号分隔的元素转成数组,[1, 2, 3, 4]  =>  "1,2,3,4"

join(','):将数组元素转成逗号分隔的字符串,   "1,2,3,4" =>  [1, 2, 3, 4]

$set(数组/对象,property/索引,具体值)vue2中使用Object.defineProperty监听响应式变化,对数组和对象的监听不是那么友好,很多时候数据变化了但是视图没发生变化,$set的出现解决了这个问题

 // 表单初始化
    formInit() {
      this.form = JSON.stringify(this.editForm) ? this.editForm : {}

      // checkbox类型的组件需要初始化数组
      this.formItemList.forEach(item => {
        if (['checkbox'].includes(item.type)) {
          if (!this.form[item.modelString]) {
            this.$set(this.form, item.model, [])
          } else {
            this.$set(this.form, item.model, this.form[item.modelString]?.split(','))
          }
        }
      })
    }
 // 接受子组件传回来的form表单内容
    getForm(val) {
      this.formItemList.forEach(item => {
        // 
        if (['checkbox'].includes(item.type)) {
          if (this.form[item.model] && this.form[item.model]?.length != 0) {
            val[item.modelString] = val[item.model]?.join(',')
          }
        }
      })
      console.log('val:', val);
      // 此处可以请求后台了
    },

3、jsx方式封装el-form

jsx封装方式借鉴了这篇文章:element-ui 通用表单封装及VUE JSX应用 - 掘金

        3.1、封装

(1)上篇博客有简单介绍jsx:两种方式对el-table二次封装_码上编程的博客-CSDN博客

(2) 新建jsxForm文件夹,并创建index.js文件

(3)封装代码:

代码逻辑图如下:

export default {
  name: 'jsxForm',
  props: {
    // 生成el-form-item的数组
    formItemList: {
      type: Array,
      default: () => [],
    },
    // 是否行内表单模式
    inline: {
      type: Boolean,
      default: false,
    },
    // el-form-item的label宽度
    labelWidth: {
      type: String,
      default: '100px',
    },
    // 表单校验规则
    rules: {
      type: Object,
      default: () => { },
    },
    // 按钮列表
    buttonList: {
      type: Array,
      default: () => [],
    }
  },
  data() {
    return {
      form: {},
      checkboxList: []
    }
  },
  methods: {
    // 生成选项
    generateOption(itemObj) {
      let options = []
      for (let index = 0; index < itemObj.options.length; index++) {
        const item = itemObj.options[index]
        switch (itemObj.type) {
          // 下拉菜单
          case 'select':
            options.push(<el-option label={item.label} value={item.value}></el-option>)
            break
          // 多选框
          case 'checkbox':
            options.push(<el-checkbox label={item.label}></el-checkbox>)
            break
          // 单选框
          case 'radio':
            options.push(<el-radio label={item.label}>{item.label}</el-radio>)
            break
        }
      }
      return options
    },
    // 生成下拉菜单
    generateSelect(item) {
      return <el-select v-model={this.form[item.model]}>{this.generateOption(item)}</el-select>
    },
    // 生成多选框
    generateCheckbox(item) {
      this.form[item.model] = this.checkboxList
      return <el-checkbox-group v-model={this.checkboxList}>{this.generateOption(item)}</el-checkbox-group>
    },
    // 生成单选
    generateRadio(item) {
      return <div>
        <el-radio-group v-model={this.form[item.model]}>{this.generateOption(item)}</el-radio-group>
      </div>
    },
    // 生成输入框
    generateInput(item) {
      return (<div>
        <el-input v-model={this.form[item.model]} style={
   
   { width: `${this.formItemContentWidth}` }}></el-input>
      </div>)
    },
    // 生成开关
    generateSwitch(item) {
      return <div>
        <el-switch v-model={this.form[item.model]}></el-switch>
      </div>
    },
    // 生成日期组件
    generateDate(item) {
      return <div>
        <el-date-picker v-model={this.form[item.model]} type={"daterange"} range-separator={"至"}
          start-placeholder={"开始日期"} end-placeholder={"结束日期"}>
        </el-date-picker>
      </div >
    },
    // 生成表单项
    generateFormItems(list = []) {
      let formItems = []
      list.forEach(item => {
        let formItemContent = ''
        switch (item.type) {
          // 下拉菜单
          case 'select':
            formItemContent = this.generateSelect(item)
            break
          // 单选框
          case 'radio':
            formItemContent = this.generateRadio(item)
            break
          // 输入框
          case 'input':
            formItemContent = this.generateInput(item)
            break;
          // 开关
          case 'switch':
            formItemContent = this.generateSwitch(item)
            break;
          // 日期
          case 'date-picker':
            formItemContent = this.generateDate(item)
            break;
          // 复选框
          case 'checkbox':
            formItemContent = this.generateCheckbox(item)
            break;
          default:
            break
        }
        formItems.push(<el-form-item label={item.label} prop={item.model}>{formItemContent}</el-form-item>)
      })
      return formItems
    },
    // 按钮列表
    generateBtnList() {
      let buttons = []

      this.buttonList?.forEach(item => {
        buttons.push(<el-button type={item.type} onClick={() => item.event()}>{item.text}</el-button>)
      })
      return buttons
    },
    // 重置表单
    resetForm() {
      // 重置复选框
      this.checkboxList = []
      this.$refs.form.resetFields();
    },

    // 返回经过校验的表单
    returnForm() {
      this.$refs['form'].validate((valid) => {
        if (valid) {
          this.$emit('submitForm', this.form)
        }
      })
    }
  },
  render() {
    return (
      <div>
        <el-form ref="form" props={
   
   { model: this.form }} rules={this.rules} inline={this.inline} label-width={this.labelWidth || '150px'}>
          {this.generateFormItems(this.formItemList)}
          <el-form-item>
            {this.generateBtnList()}
          </el-form-item>
        </el-form>
      </div>
    )
  }
}

        3.2、使用

<template>
  <div id="app">
    <jsxForm ref="form" :buttonList="buttonList" :rules="rules" :formItemList="formItemList" @submitForm="getForm">
    </jsxForm>
  </div>
</template>

<script>
import jsxForm from '@/components/jsxForm';
export default {
  name: 'App',
  components: {
    JsxForm,
  },
  data() {
    return {
      formItemList: [
        { label: '活动名称', type: 'input', model: 'name', placeholder: '请输入活动名称' },
        { label: '活动区域', type: 'select', model: 'region', placeholder: '请选择状态', options: [{ label: '上海', value: 'shanghai' }, { label: '北京', value: 'beijing' }] },
        { label: '活动时间', type: 'date-picker', model: 'startTime', },
        { label: '即时配送', type: 'switch', model: 'delivery' },
        { label: '活动性质', type: 'checkbox', model: 'type', options: [{ label: '美食/餐厅线上活动' }, { label: '地推活动' }, { label: '线下主题活动' }, { label: '单纯品牌曝光' },] },
        { label: '特殊资源', type: 'radio', model: 'resource', options: [{ label: '线上品牌商赞助' }, { label: '线下场地免费' }] },
      ],
      rules: {
        name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
      },
      buttonList: [
        { text: '提交', type: 'primary', event: this.submitForm },
        { text: '重置', event: this.resetForm },
      ]
    }
  },
  methods: {
    // 接受子组件传回来的form表单内容
    getForm(val) {
      console.log('val:', val);
    },
    // 点击提交,触发表单校验
    submitForm() {
      this.$refs.form.returnForm();
    },
    // 重置表单
    resetForm() {
      this.$refs.form.resetForm();
    }
  }
}
</script>

猜你喜欢

转载自blog.csdn.net/weixin_42375707/article/details/130177717