vue2封装一个表单

420 阅读3分钟

vue2封装一个表单

在某些项目中,我们可能会遇到一些结构类似的表单,为了不重复写样式结构,所以进行单独的封装。如果没有大量重复的表单结构,也就不需要封装了,直接写多好的。

大概的思路,为了相对灵活,所以将数据和描述项单独抽离,数据只是为了提交给后端,如果连同描述项一起绑定在一起,那么提交给后端的数据就包含了一些不必要的东西。所以单独使用描述项去描述每个字段,描述项包括:这个字段的 label,这个字段的校验规则,这个字段的组件类型(可能是输入框,可能是选择框等),这个字段如果为选择框,那么还需要包含对应的选择项。

Demo 用的 UI 库是 iview。

编码

封装表单组件

  • src/views/FormDemo/BaseForm/components/TestForm.vue
<template>
  <div class="form-container">
    <!-- 可以写表单统一的样式结构,这里就不写样式了 -->
    <!-- 一旦封装了样式结构,那么对应的描述数据项就相对要复杂一些了,所以要有所取舍 -->
    <Form :model="form" :rules="rules" ref="form">
      <Row :gutter="16">
        <Col v-for="item in formKeys" :key="item.key" :xl="4" :md="12" :xs="24">
          <FormItem
            :label="item.label || ''"
            :prop="item.key"
          >
            <component :is="item.inputType" v-model="form[item.key]">
              <template v-if="item.inputType === 'Select'">
                <Option v-for="o in item.options" :value="o.value" :key="o.value">{{ o.label }}</Option>
              </template>
              <template v-if="item.inputType === 'CheckboxGroup'">
                <Checkbox v-for="o in item.options" :label="o" :key="o"></Checkbox>
              </template>
            </component>
          </FormItem>
        </Col>
      </Row>
    </Form>
  </div>
</template>

<script lang="ts">
import Vue from 'vue';

export default Vue.extend({
  name: 'BaseForm',

  model: {
    prop: 'value',
    event: 'change'
  },

  props: {
    value: {
      type: Object,
      required: true
    },
    descFields: {
      type: Object,
      required: true
    }
  },

  data() {
    return {
      form: {},
      formKeys: [],
      rules: {}
    }
  },

  methods: {
    // 表单验证
    formValidate() {
      let flag = false
      this.$refs.form.validate(valid => {
        if (valid) {
          // 验证通过,将表单数据改变
          this.$emit('change', this.form)
          flag = true
        } else {
          flag = false
        }
      })
      return flag
    }
  },

  created() {
    this.form = JSON.parse(JSON.stringify(this.value))

    const keys = Object.keys(this.form)
    this.formKeys = keys.map(key => {
      if (!this.rules[key]) {
        this.rules[key] = this.descFields[key].rule
      }
      return {
        key,
        label: this.descFields[key].label,
        inputType: this.descFields[key].inputType,
        options: this.descFields[key].options,
      }
    })

    // console.log(this.form, keys)
    // console.log(this.formKeys)
  }
})
</script>

<style lang="scss" scoped>
.form-container {
  & ::v-deep .ivu-form-item {
    display: flex;
    flex-direction: column;
  }

  & ::v-deep .ivu-form-item-label {
    text-align: left;
  }
  & ::v-deep .ivu-checkbox {
    margin-right: 5px;
  }
}
</style>

引入组件使用

  • src/views/FormDemo/BaseForm/index.vue
<template>
  <Card>
    <Form :model="form">
      <FormItem label="测试外层表单字段1">
        <Input v-model="form.test1"></Input>
      </FormItem>

      <TestForm v-model="form.form1" :descFields="descFields1" ref="testForm1" />

      <FormItem label="测试外层表单字段2">
        <Input v-model="form.test2"></Input>
      </FormItem>

      <TestForm v-model="form.form2" :descFields="descFields2" ref="testForm2" />

      <FormItem label="测试外层表单字段3">
        <Input v-model="form.test3"></Input>
      </FormItem>
      <FormItem label="测试外层表单字段4">
        <Input v-model="form.test4"></Input>
      </FormItem>

      <TestForm v-model="form.form3" :descFields="descFields3" ref="testForm3" />
    </Form>
    <Button @click="handleSave">提交</Button>
  </Card>
</template>

<script lang="ts">
import Vue from 'vue';
import TestForm from './components/TestForm.vue'

export default Vue.extend({
  name: 'BaseForm',

  components: {
    TestForm
  },

  data() {
    return {
      form: {
        // 一般是具有一定关联关系的字段放在同一个对象下面,
        // 用于描述一个特定的对象
        form1: {
          field1: '',
          field2: '',
          field3: '',
          field4: '',
        },
        form2: {
          field1: '',
          field2: '',
          field3: '',
          field4: '',
          field5: '',
        },
        form3: {
          field1: '',
          field2: '',
          field3: [],
        },
        test1: '',
        test2: '',
        test3: '',
        test4: '',
      },

      descFields1: {
        field1: {
          label: '字段1',
          inputType: 'Input',
          rule: [{ required: true, message: '该项为必填', trigger: 'blur' }],
        },
        field2: {
          label: '字段2',
          inputType: 'Input',
          rule: [{ required: true, message: '该项为必填', trigger: 'blur' }],
        },
        field3: {
          label: '字段3',
          inputType: 'Input',
          rule: [{ required: true, message: '该项为必填', trigger: 'blur' }],
        },
        field4: {
          label: '字段4',
          inputType: 'Input',
          rule: [{ required: true, message: '该项为必填', trigger: 'blur' }],
        },
      },

      descFields2: {
        field1: {
          label: '测试字段1',
          inputType: 'Input',
        },
        field2: {
          label: '测试字段2',
          inputType: 'Select',
          options: [
            { label: '选项1', value: '1' },
            { label: '选项2', value: '2' },
          ],
          rule: [{ required: true, message: '该项为必选', trigger: 'change' }],
        },
        field3: {
          label: '测试字段3',
          inputType: 'Input',
          rule: [{ required: true, message: '该项为必填', trigger: 'blur' }],
        },
        field4: {
          label: '测试字段4',
          inputType: 'Input',
          rule: [{ required: true, message: '该项为必填', trigger: 'blur' }],
        },
        field5: {
          label: '测试字段5',
          inputType: 'Input'
        },
      },

      descFields3: {
        field1: {
          label: '测试字段1',
          inputType: 'Input',
        },
        field2: {
          label: '测试字段2',
          inputType: 'Select',
          options: [
            { label: '选项1', value: '1' },
            { label: '选项2', value: '2' },
          ],
          rule: [{ required: true, message: '该项为必选', trigger: 'change' }],
        },
        field3: {
          label: '测试字段3',
          inputType: 'CheckboxGroup',
          rule: [{ required: true, message: '该项为必填', trigger: 'blur' }],
          options: ['多选1', '多选2', '多选3']
        },
      }
    }
  },

  methods: {
    // 保存数据
    handleSave() {
      const refs = ['testForm1', 'testForm2', 'testForm3']
      const validateArr = refs.map(ref => {
        return this.$refs[ref].formValidate()
      })
      if (validateArr.every(flag => flag)) {
        console.log(this.form)
      }
    },
  }
})
</script>

<style lang="scss" scoped>
</style>