Form

封装表单组件实现通过配置动态生成表单

提示

适用于可以通过 v-model 绑定值的任意组件,例如:

  • 支持 el-input el-switch input
  • 不支持 el-upload … (需要将相关组件重新封装为支持 v-model 的组件)

使用

基础用法

当 columns 绑定的是一个具有响应式的数组时,数组的变动会影响表单变动(及动态表单)。如果不需要动态表单推荐绑定一个普通数组

Name
Address
<template>
  <pro-form
    v-model="form"
    :columns="columns"
    label-width="100px"
    @submit="submit"
  />
</template>

<script>
import { defineComponent, ref } from 'vue'
import { ElMessage } from 'element-plus'

export default defineComponent({
  setup() {
    const form = ref({})
    const columns = [
      {
        label: 'Name',
        prop: 'name',
        component: 'el-input',
      },
      {
        label: 'Address',
        prop: 'address',
        component: 'el-input',
      },
    ]
    const submit = (done, isValid, invalidFields) => {
      ElMessage(`submit: ${isValid}`)
      console.log(form.value, isValid, invalidFields)
      setTimeout(() => {
        done()
      }, 1000)
    }

    return {
      form,
      columns,
      submit,
    }
  },
})
</script>

智能提示

通过辅助函数 defineFormColumns defineFormMenuColumns defineFormSubmit defineComponentProps 提供智能提示

Name
Address
<template>
  <pro-form
    v-model="form"
    :columns="columns"
    :menu="menu"
    label-width="100px"
    @submit="submit"
  />
</template>

<script>
import { defineComponent, ref } from 'vue'
import { ElMessage } from 'element-plus'
import {
  defineFormColumns,
  defineFormMenuColumns,
  defineFormSubmit,
} from 'element-pro-components'

export default defineComponent({
  setup() {
    const menu = defineFormMenuColumns({
      submitText: 'Create',
      reset: false,
    })
    const form = ref({})
    const columns = defineFormColumns([
      {
        label: 'Name',
        prop: 'name',
        component: 'el-input',
      },
      {
        label: 'Address',
        prop: 'address',
        component: 'el-input',
      },
    ])
    const submit = defineFormSubmit((done, isValid, invalidFields) => {
      ElMessage(`submit: ${isValid}`)
      console.log(form.value, isValid, invalidFields)
      setTimeout(() => {
        done()
      }, 1000)
    })

    return {
      menu,
      form,
      columns,
      submit,
    }
  },
})
</script>

嵌套键值

支持直接对具有嵌套结构的对象或数组进行赋值,仅需要配置 prop

{}

Break
Object
Array

prop 存在默认值时会阻止对嵌套键值赋值

<template>
  <p>{{ form }}</p>
  <pro-form
    v-model="form"
    :columns="columns"
    label-width="100px"
    @submit="submit"
  />
</template>

<script>
import { defineComponent, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { defineFormColumns, defineFormSubmit } from 'element-pro-components'

export default defineComponent({
  setup() {
    const form = ref({ 'a.b': undefined })
    const columns = defineFormColumns([
      {
        label: 'Break',
        prop: 'a.b',
        component: 'el-input',
      },
      {
        label: 'Object',
        prop: 'a.b.c',
        component: 'el-input',
      },
      {
        label: 'Array',
        prop: 'b[0]',
        component: 'el-input',
      },
    ])
    const submit = defineFormSubmit((done, isValid, invalidFields) => {
      ElMessage(`submit: ${isValid}`)
      console.log(form.value, isValid, invalidFields)
      setTimeout(() => {
        done()
      }, 1000)
    })

    return {
      form,
      columns,
      submit,
    }
  },
})
</script>

指定对应的组件

通过 columns 的 component 定义该项生成什么组件,要求对应组件可以通过 v-model 绑定值。通过 props 可以向组件中传值,通过 props 里面的 slots 可以向组件传递简单的 渲染函数

input
Search
input-tag
radio
checkbox
select
请选择
<template>
  <pro-form
    v-model="form"
    :columns="columns"
    label-width="100px"
    @submit="submit"
  />
</template>

<script>
import { defineComponent, markRaw, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { Search } from '@element-plus/icons-vue'
import { defineFormColumns, defineFormSubmit } from 'element-pro-components'

export default defineComponent({
  setup() {
    const form = ref({})
    const list = ref([
      { value: 'Go', label: 'go' },
      { value: 'JavaScript', label: 'javascript' },
      { value: 'Python', label: 'python' },
      { value: 'Dart', label: 'dart' },
      { value: 'V', label: 'v' },
    ])
    const columns = defineFormColumns([
      {
        label: 'input',
        prop: 'input',
        component: 'el-input',
        props: {
          clearable: true,
          placeholder: 'Please input',
          prefixIcon: markRaw(Search),
          slots: {
            append: () => 'Search',
          },
        },
      },
      {
        label: 'input-tag',
        prop: 'inputTag',
        component: 'pro-input-tag',
        props: {
          placeholder: 'Please click the space button after input',
        },
      },
      {
        label: 'radio',
        prop: 'radio',
        component: 'pro-radio',
        props: {
          data: list.value,
        },
      },
      {
        label: 'checkbox',
        prop: 'checkbox',
        component: 'pro-checkbox',
        props: {
          data: list.value,
        },
      },
      {
        label: 'select',
        prop: 'select',
        component: 'pro-select',
        props: {
          data: list.value,
        },
      },
    ])
    const submit = defineFormSubmit((done, isValid, invalidFields) => {
      ElMessage(`submit: ${isValid}`)
      console.log(form.value, isValid, invalidFields)
      setTimeout(() => {
        done()
      }, 1000)
    })

    return {
      form,
      columns,
      submit,
    }
  },
})
</script>

使用局部组件

通过 component 也可以直接传入局部组件 (请使用 markRaw 标记)

radio
switch
<template>
  <pro-form
    v-model="form"
    :columns="columns"
    :gutter="20"
    label-width="100px"
    @submit="submit"
  />
</template>

<script>
import { defineComponent, ref, markRaw } from 'vue'
import { ElSwitch, ElMessage } from 'element-plus'
import { defineFormColumns, defineFormSubmit } from 'element-pro-components'

export default defineComponent({
  setup() {
    const list = ref([
      { value: 'Go', label: 'go' },
      { value: 'JavaScript', label: 'javascript' },
      { value: 'Python', label: 'python' },
      { value: 'Dart', label: 'dart' },
      { value: 'V', label: 'v' },
    ])
    const form = ref({})
    const columns = ref(
      defineFormColumns([
        {
          label: 'radio',
          prop: 'radio',
          component: 'pro-radio',
          props: {
            data: list,
          },
        },
        {
          label: 'switch',
          prop: 'switch',
          component: markRaw(ElSwitch),
        },
      ])
    )
    const submit = defineFormSubmit((done, isValid, invalidFields) => {
      ElMessage(`submit: ${isValid}`)
      console.log(form.value, isValid, invalidFields)
      setTimeout(() => {
        done()
      }, 1000)
    })

    return {
      form,
      columns,
      submit,
    }
  },
})
</script>

配置组件绑定的 v-model 参数

默认情况下 ProForm 组件仅支持使用 v-model 绑定值的组件,如果需要使用其他参数绑定值,可以通过 modelKey 配置

Use outside the ProForm
Use in the ProForm

modelKey 除了支持字符串,还支持传入 [prop, event] (prop 用于配置绑定值的参数,event 用于配置绑定值的事件)

<template>
  <div>
    <span class="el-form-item__label">Use outside the ProForm</span>
    <my-input v-model:value="form.value" />
  </div>
  <pro-form
    v-model="form"
    :columns="columns"
    inline
    @submit="submit"
  />
</template>

<script>
import { defineComponent, h, markRaw, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { defineFormColumns, defineFormSubmit } from 'element-pro-components'

const MyInput = {
  name: 'MyInput',
  props: {
    value: {
      type: String,
      default: '',
    },
  },
  emits: ['update:value'],
  setup(props, { emit }) {
    return () =>
      h('input', {
        value: props.value,
        onInput: (e) => emit('update:value', e.target.value),
      })
  },
}

export default defineComponent({
  components: { MyInput },
  setup() {
    const form = ref({})
    const columns = defineFormColumns([
      {
        label: 'Use in the ProForm',
        prop: 'value',
        component: markRaw(MyInput),
        modelKey: 'value', // or ['value', 'onUpdate:value']
      },
    ])
    const submit = defineFormSubmit((done, isValid, invalidFields) => {
      ElMessage(`submit: ${isValid}`)
      console.log(form.value, isValid, invalidFields)
      setTimeout(() => {
        done()
      }, 1000)
    })

    return {
      form,
      columns,
      submit,
    }
  },
})
</script>

使用插槽

提示

1.2.0 起,[prop] 相关插槽需要增加前缀 form- 使用

直接在模版中增加带 form-[prop] 相关的插槽即可使用。虽然在启用插槽后可以通过 v-model="form.slot" 这种方式绑定值,但更推荐使用 valuesetValue

picture
<template>
  <pro-form
    v-model="form"
    :columns="columns"
    label-width="100px"
    @submit="submit"
  >
    <template #form-slot-label>
      <picture-rounded class="icon-picture" />
      <span>picture</span>
    </template>
    <template #form-slot="{ value, setValue }">
      <el-upload
        :show-file-list="false"
        :before-upload="(file) => beforeUpload(file, setValue)"
        action=""
        class="avatar-uploader"
      >
        <img
          v-if="value"
          :src="value"
          class="avatar"
        >
        <plus
          v-else
          class="icon-uploader"
        />
      </el-upload>
    </template>
  </pro-form>
</template>

<script>
import { defineComponent, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { Plus, PictureRounded } from '@element-plus/icons-vue'
import { defineFormColumns, defineFormSubmit } from 'element-pro-components'

export default defineComponent({
  components: { Plus, PictureRounded },
  setup() {
    const form = ref({})
    const columns = defineFormColumns([
      {
        prop: 'slot',
      },
    ])
    const submit = defineFormSubmit((done, isValid, invalidFields) => {
      ElMessage(`submit: ${isValid}`)
      console.log(form.value, isValid, invalidFields)
      setTimeout(() => {
        done()
      }, 1000)
    })

    function beforeUpload(file, setValue) {
      // Simulate uploading pictures
      const fileReader = new FileReader()
      fileReader.onloadend = (e) => setValue(e.target.result)
      fileReader.readAsDataURL(file)
      return false
    }

    return {
      form,
      columns,
      submit,
      beforeUpload,
    }
  },
})
</script>

<style>
.icon-picture {
  margin-right: 6px;
  width: 16px;
  height: 16px;
}
.avatar-uploader .el-upload {
  width: 178px;
  height: 178px;
  border: 1px dashed #d9d9d9;
  border-radius: 6px;
  overflow: hidden;
  text-align: center;
  line-height: 200px;
}
.avatar-uploader .el-upload:hover {
  border-color: #409eff;
}
.avatar-uploader .icon-uploader {
  width: 50px;
  height: 50px;
  color: #8c939d;
}
.avatar-uploader .avatar {
  display: block;
  width: 100%;
  height: 100%;
}
</style>

配置按钮

通过 menu 配置按钮显示、文字、参数

按钮文字也可以通过 国际化 来配置

Name
Address
<template>
  <pro-form
    v-model="form"
    :columns="columns"
    :menu="menu"
    label-width="100px"
    @submit="submit"
  />
</template>

<script>
import { defineComponent, ref } from 'vue'
import { ElMessage } from 'element-plus'
import {
  defineFormColumns,
  defineFormMenuColumns,
  defineFormSubmit,
} from 'element-pro-components'

export default defineComponent({
  setup() {
    const menu = defineFormMenuColumns({
      submitText: 'Create',
      reset: false,
    })
    const form = ref({})
    const columns = defineFormColumns([
      {
        label: 'Name',
        prop: 'name',
        component: 'el-input',
      },
      {
        label: 'Address',
        prop: 'address',
        component: 'el-input',
      },
    ])
    const submit = defineFormSubmit((done, isValid, invalidFields) => {
      ElMessage(`submit: ${isValid}`)
      console.log(form.value, isValid, invalidFields)
      setTimeout(() => {
        done()
      }, 1000)
    })

    return {
      menu,
      form,
      columns,
      submit,
    }
  },
})
</script>

配置子表单

通过 columns 的 children 配置子表单,当然你也可以配置多层的 children 结构实现反复套娃

Goods
Spec

通过 max 限制子表单的最大数量

<template>
  <pro-form
    v-model="form"
    :columns="columns"
    label-width="100px"
    @submit="submit"
  />
</template>

<script>
import { defineComponent, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { defineFormColumns, defineFormSubmit } from 'element-pro-components'

export default defineComponent({
  setup() {
    const form = ref({})
    const columns = defineFormColumns([
      {
        label: 'Goods',
        prop: 'name',
        component: 'el-input',
      },
      {
        label: 'Spec',
        prop: 'spec',
        size: 'small',
        max: 3,
        children: [
          {
            label: 'Weight',
            prop: 'weight',
            component: 'el-input',
          },
          {
            label: 'Size',
            prop: 'size',
            max: 1,
            children: [
              {
                label: 'Length',
                prop: 'length',
                component: 'el-input',
              },
              {
                label: 'Width',
                prop: 'width',
                component: 'el-input',
              },
              {
                label: 'Height',
                prop: 'height',
                component: 'el-input',
              },
            ],
          },
        ],
      },
    ])
    const submit = defineFormSubmit((done, isValid, invalidFields) => {
      ElMessage(`submit: ${isValid}`)
      console.log(form.value, isValid, invalidFields)
      setTimeout(() => {
        done()
      }, 1000)
    })

    return {
      form,
      columns,
      submit,
    }
  },
})
</script>

表单验证

像 el-form 一样可以通过 rules 配置表单验证。对于子表单更推荐使用 columns 里面的 rules 字段实现验证。否则你需要通过 ${父级的 prop}.${当前项的 index}.${当前的 prop} 这种方式配置子表单的验证

Date
User

submit 方法第二个参数表示是否通过表单验证

<template>
  <pro-form
    v-model="form"
    :columns="columns"
    :rules="rules"
    label-width="100px"
    @submit="submit"
    @reset="resetForm"
  />
</template>

<script>
import { defineComponent, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { defineFormColumns, defineFormSubmit } from 'element-pro-components'

export default defineComponent({
  setup() {
    const form = ref({})
    const rules = {
      date: { required: true, message: 'Please input date', trigger: 'blur' },
      user: { required: true, message: 'Please input user', trigger: 'blur' },
    }
    const columns = defineFormColumns([
      {
        label: 'Date',
        prop: 'date',
        component: 'el-input',
      },
      {
        label: 'User',
        prop: 'user',
        max: 3,
        size: 'small',
        children: [
          {
            label: 'Name',
            prop: 'name',
            component: 'el-input',
            rules: {
              required: true,
              message: 'Please input Name',
              trigger: 'blur',
            },
          },
          {
            label: 'Address',
            prop: 'address',
            component: 'el-input',
          },
        ],
      },
    ])
    const submit = defineFormSubmit((done, isValid, invalidFields) => {
      ElMessage(`submit: ${isValid}`)
      console.log(form.value, isValid, invalidFields)
      setTimeout(() => {
        done()
      }, 1000)
    })

    function resetForm() {
      ElMessage('reset form')
      console.log('reset form')
    }

    return {
      form,
      rules,
      columns,
      submit,
      resetForm,
    }
  },
})
</script>

动态表单

如果传入的 columns 是一个响应性数据,动态的修改 columns 表单也会随之改变

Label-0
<template>
  <pro-form
    v-model="form"
    :columns="columns"
    :menu="menu"
    label-width="100px"
    @submit="submit"
  >
    <template #menu-left>
      <el-button
        v-show="columns.length < 5"
        @click="add"
      >
        Add One
      </el-button>
    </template>
    <template #menu-right>
      <el-button
        v-show="columns.length"
        @click="del"
      >
        Delete One
      </el-button>
    </template>
  </pro-form>
</template>

<script>
import { defineComponent, ref } from 'vue'
import { ElMessage } from 'element-plus'
import {
  defineFormColumns,
  defineFormMenuColumns,
  defineFormSubmit,
} from 'element-pro-components'

export default defineComponent({
  setup() {
    const menu = defineFormMenuColumns({
      submit: false,
      reset: false,
    })
    const count = ref(0)
    const form = ref({})
    const columns = ref(
      defineFormColumns([
        {
          label: 'Label-0',
          prop: 'prop0',
          component: 'el-input',
        },
      ])
    )
    const submit = defineFormSubmit((done, isValid, invalidFields) => {
      ElMessage(`submit: ${isValid}`)
      console.log(form.value, isValid, invalidFields)
      setTimeout(() => {
        done()
      }, 1000)
    })

    function add() {
      count.value++
      columns.value.push({
        label: 'Label-' + count.value,
        prop: 'prop' + count.value,
        component: 'el-input',
      })
    }

    function del() {
      const index = Math.floor(Math.random() * columns.value.length)
      columns.value.splice(index, 1)
    }

    return {
      menu,
      form,
      columns,
      submit,
      add,
      del,
    }
  },
})
</script>

联动表单

通过控制 columns 中的 show 字段可以动态控制表单的显示,很容易实现联动表单

Plan
<template>
  <pro-form
    v-model="form"
    :columns="columns"
    label-width="100px"
    @submit="submit"
  />
</template>

<script>
import { computed, defineComponent, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { defineFormColumns, defineFormSubmit } from 'element-pro-components'

export default defineComponent({
  setup() {
    const form = ref({})
    const columns = computed(() =>
      defineFormColumns([
        {
          label: 'Plan',
          prop: 'plan',
          component: 'pro-radio',
          rules: {
            required: true,
            message: 'Please select a plan',
            trigger: 'blue',
          },
          props: {
            data: [
              { label: 'Basic', value: 'basic' },
              { label: 'Pro', value: 'pro' },
            ],
          },
        },
        {
          label: 'Name',
          prop: 'name',
          component: 'el-input',
          show: !!form.value.plan,
        },
        getContentColumn(form.value.plan === 'basic'),
        {
          label: 'List',
          prop: 'list',
          max: 5,
          show: form.value.plan === 'pro',
          children: [
            {
              label: 'Date',
              prop: 'date',
              component: 'el-date-picker',
            },
            getContentColumn(form.value.plan === 'pro'),
          ],
        },
      ])
    )
    const submit = defineFormSubmit((done, isValid, invalidFields) => {
      ElMessage(`submit: ${isValid}`)
      console.log(form.value, isValid, invalidFields)
      setTimeout(() => {
        done()
      }, 1000)
    })

    function getContentColumn(show) {
      return {
        label: 'Content',
        prop: 'content',
        component: 'el-input',
        show,
        props: {
          type: 'textarea',
          rows: 2,
        },
      }
    }

    return {
      form,
      columns,
      submit,
    }
  },
})
</script>

栅格布局

与使用 el-rowel-col 组件相同 (el-row 对应 pro-formel-col 对应 columns),通过相关配置可以自由地组合布局。inlinetrue 时无效

Goods
Weight
Count
Length
Width
Height
Price
MarketPrice
<template>
  <pro-form
    v-model="form"
    :columns="columns"
    :gutter="20"
    label-width="100px"
    @submit="submit"
  />
</template>

<script>
import { defineComponent, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { defineFormColumns, defineFormSubmit } from 'element-pro-components'

export default defineComponent({
  setup() {
    const form = ref({})
    const columns = defineFormColumns([
      {
        label: 'Goods',
        prop: 'name',
        component: 'el-input',
        span: 24,
      },
      {
        label: 'Weight',
        prop: 'weight',
        component: 'el-input',
        xs: 24,
        md: 12,
      },
      {
        label: 'Count',
        prop: 'count',
        component: 'el-input',
        xs: 24,
        md: 12,
      },
      {
        label: 'Length',
        prop: 'length',
        component: 'el-input',
        xs: 24,
        md: 8,
      },
      {
        label: 'Width',
        prop: 'width',
        component: 'el-input',
        xs: 24,
        md: 8,
      },
      {
        label: 'Height',
        prop: 'height',
        component: 'el-input',
        xs: 24,
        md: 8,
      },
      {
        label: 'Price',
        prop: 'price',
        component: 'el-input',
        xs: 24,
        md: 12,
      },
      {
        label: 'MarketPrice',
        prop: 'marketPrice',
        component: 'el-input',
        xs: 24,
        md: 12,
      },
    ])
    const submit = defineFormSubmit((done, isValid, invalidFields) => {
      ElMessage(`submit: ${isValid}`)
      console.log(form.value, isValid, invalidFields)
      setTimeout(() => {
        done()
      }, 1000)
    })

    return {
      form,
      columns,
      submit,
    }
  },
})
</script>

行内表单

通过设置 inline 属性为 true 可以让表单域变为行内的表单域

Name
Address
<template>
  <pro-form
    v-model="form"
    :columns="columns"
    :menu="menu"
    inline
    @submit="submit"
  />
</template>

<script>
import { defineComponent, ref } from 'vue'
import { ElMessage } from 'element-plus'
import {
  defineFormColumns,
  defineFormMenuColumns,
  defineFormSubmit,
} from 'element-pro-components'

export default defineComponent({
  setup() {
    const menu = defineFormMenuColumns({
      submitText: 'Search',
      reset: false,
    })
    const form = ref({})
    const columns = defineFormColumns([
      {
        label: 'Name',
        prop: 'name',
        component: 'el-input',
      },
      {
        label: 'Address',
        prop: 'address',
        component: 'el-input',
      },
    ])
    const submit = defineFormSubmit((done, isValid, invalidFields) => {
      ElMessage(`submit: ${isValid}`)
      console.log(form.value, isValid, invalidFields)
      setTimeout(() => {
        done()
      }, 1000)
    })

    return {
      menu,
      form,
      columns,
      submit,
    }
  },
})
</script>

对齐方式

通过设置 label-position 属性可以改变表单域标签的位置

Name
Address

当断点是 xs 时,默认为 top;其余则是 right

<template>
  <pro-radio-button
    v-model="labelPosition"
    :data="data"
    style="margin-bottom: 18px"
  />
  <pro-form
    v-model="form"
    :columns="columns"
    :label-position="labelPosition"
    label-width="100px"
    @submit="submit"
  />
</template>

<script>
import { defineComponent, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { defineFormColumns, defineFormSubmit } from 'element-pro-components'

export default defineComponent({
  setup() {
    const labelPosition = ref('left')
    const form = ref({})
    const columns = defineFormColumns([
      {
        label: 'Name',
        prop: 'name',
        component: 'el-input',
      },
      {
        label: 'Address',
        prop: 'address',
        component: 'el-input',
      },
    ])
    const data = [
      { label: 'Left', value: 'left' },
      { label: 'Right', value: 'right' },
      { label: 'Top', value: 'top' },
    ]
    const submit = defineFormSubmit((done, isValid, invalidFields) => {
      ElMessage(`submit: ${isValid}`)
      console.log(form.value, isValid, invalidFields)
      setTimeout(() => {
        done()
      }, 1000)
    })

    return {
      labelPosition,
      data,
      form,
      columns,
      submit,
    }
  },
})
</script>

数组表单

数组表单与子表单相同,用于处理需要输入一个数组的情况。仅需配置 array 即可启用

通过 max 控制表单的最大数量

<template>
  <pro-form
    v-model="form"
    :columns="columns"
    :max="3"
    array
    label-width="100px"
    @submit="submit"
  />
</template>

<script>
import { defineComponent, ref } from 'vue'
import { ElMessage } from 'element-plus'

export default defineComponent({
  setup() {
    const form = ref()
    const columns = [
      {
        label: 'Name',
        prop: 'name',
        component: 'el-input',
      },
      {
        label: 'Address',
        prop: 'address',
        component: 'el-input',
      },
    ]
    const submit = (done, isValid, invalidFields) => {
      ElMessage(`submit: ${isValid}`)
      console.log(form.value, isValid, invalidFields)
      setTimeout(() => {
        done()
      }, 1000)
    }

    return {
      form,
      columns,
      submit,
    }
  },
})
</script>

分组表单

分组表单用于将普通表单进行分组展示

Name
First Name
Last Name
Address
City
Address
<template>
  <pro-form
    v-model="form"
    :columns="columns"
    label-width="100px"
    @submit="submit"
  />
</template>

<script>
import { defineComponent, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { defineFormColumns, defineFormSubmit } from 'element-pro-components'

export default defineComponent({
  setup() {
    const form = ref({})
    const columns = defineFormColumns([
      {
        label: 'Name',
        type: 'group',
        children: [
          {
            label: 'First Name',
            prop: 'firstName',
            component: 'el-input',
            md: 12,
          },
          {
            label: 'Last Name',
            prop: 'lastName',
            component: 'el-input',
            md: 12,
          },
        ],
      },
      {
        label: 'Address',
        type: 'group',
        children: [
          {
            label: 'City',
            prop: 'city',
            component: 'el-input',
            md: 12,
          },
          {
            label: 'Address',
            prop: 'address',
            component: 'el-input',
            md: 12,
          },
        ],
      },
    ])
    const submit = defineFormSubmit((done, isValid, invalidFields) => {
      ElMessage(`submit: ${isValid}`)
      console.log(form.value, isValid, invalidFields)
      setTimeout(() => {
        done()
      }, 1000)
    })

    return {
      form,
      columns,
      submit,
    }
  },
})
</script>

可折叠的分组表单

可折叠显示的分组表单

<template>
  <pro-form
    v-model="form"
    :columns="columns"
    label-width="100px"
    @submit="submit"
  />
</template>

<script>
import { defineComponent, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { defineFormColumns, defineFormSubmit } from 'element-pro-components'

export default defineComponent({
  setup() {
    const form = ref({})
    const columns = defineFormColumns([
      {
        label: 'Name',
        type: 'collapse',
        children: [
          {
            label: 'First Name',
            prop: 'firstName',
            component: 'el-input',
            md: 12,
          },
          {
            label: 'Last Name',
            prop: 'lastName',
            component: 'el-input',
            md: 12,
          },
        ],
      },
      {
        label: 'Address',
        type: 'collapse',
        children: [
          {
            label: 'City',
            prop: 'city',
            component: 'el-input',
            md: 12,
          },
          {
            label: 'Address',
            prop: 'address',
            component: 'el-input',
            md: 12,
          },
        ],
      },
    ])
    const submit = defineFormSubmit((done, isValid, invalidFields) => {
      ElMessage(`submit: ${isValid}`)
      console.log(form.value, isValid, invalidFields)
      setTimeout(() => {
        done()
      }, 1000)
    })

    return {
      form,
      columns,
      submit,
    }
  },
})
</script>

带有标签的表单

通过标签页切换不同的表单

Name
Password
<template>
  <pro-form
    ref="formRef"
    v-model="form"
    :columns="columns"
    :rules="rules"
    label-position="top"
    class="docs-tabs-form"
    @submit="submit"
    @tab-change="tabChange"
  />
</template>

<script>
import { defineComponent, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { defineFormColumns, defineFormSubmit } from 'element-pro-components'

export default defineComponent({
  setup() {
    const loginRules = {
      username: {
        required: true,
        message: 'Please input name',
        trigger: 'blur',
      },
      password: {
        required: true,
        message: 'Please input password',
        trigger: 'blur',
      },
    }
    const registerRules = {
      name: { required: true, message: 'Please input name', trigger: 'blur' },
      email: { required: true, message: 'Please input email', trigger: 'blur' },
      password: {
        required: true,
        message: 'Please input password',
        trigger: 'blur',
      },
    }

    const form = ref({})
    const formRef = ref()
    const rules = ref(loginRules)

    const columns = defineFormColumns([
      {
        label: 'Sign in',
        prop: 'login',
        type: 'tabs',
        children: [
          {
            label: 'Name',
            prop: 'username',
            component: 'el-input',
            props: {
              clearable: true,
              placeholder: 'Please input name',
            },
          },
          {
            label: 'Password',
            prop: 'password',
            component: 'el-input',
            props: {
              type: 'password',
              clearable: true,
              showPassword: true,
              placeholder: 'Please input password',
            },
          },
        ],
      },
      {
        label: 'Sign up',
        prop: 'register',
        type: 'tabs',
        children: [
          {
            label: 'Name',
            prop: 'name',
            component: 'el-input',
            props: {
              clearable: true,
              placeholder: 'Please input name',
            },
          },
          {
            label: 'Email',
            prop: 'email',
            component: 'el-input',
            props: {
              clearable: true,
              placeholder: 'Please input email',
            },
          },
          {
            label: 'Password',
            prop: 'password',
            component: 'el-input',
            props: {
              clearable: true,
              placeholder: 'Please input password',
            },
          },
        ],
      },
    ])

    const submit = defineFormSubmit((done, isValid, invalidFields) => {
      ElMessage(`submit: ${isValid}`)
      console.log(form.value, isValid, invalidFields)
      setTimeout(() => {
        done()
      }, 1000)
    })

    const tabChange = (prop) => {
      rules.value = prop === 'login' ? loginRules : registerRules
      setTimeout(() => {
        formRef.value.resetFields()
      })
    }

    return {
      form,
      formRef,
      columns,
      rules,
      submit,
      tabChange,
    }
  },
})
</script>

<style scoped>
.docs-tabs-form {
  margin: 0 auto;
  width: 50%;
  min-width: 300px;
  max-width: 500px;
}
.docs-tabs-form :deep(.pro-tabs-form .el-tabs__header) {
  text-align: center;
}
.docs-tabs-form :deep(.pro-tabs-form .el-tabs__header .el-tabs__nav-wrap) {
  --el-font-size-base: 20px;
  display: inline-block;
}
</style>

分步表单

将一个普通的表单分割成几步输入

0
Name
0
Address
First Name
Last Name
<template>
  <pro-form
    v-model="form"
    :columns="columns"
    label-width="100px"
    @submit="submit"
  />
</template>

<script>
import { defineComponent, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { defineFormColumns, defineFormSubmit } from 'element-pro-components'

export default defineComponent({
  setup() {
    const form = ref({})
    const columns = defineFormColumns([
      {
        label: 'Name',
        type: 'steps',
        children: [
          {
            label: 'First Name',
            prop: 'firstName',
            component: 'el-input',
            rules: { required: true, message: 'Please input', trigger: 'blur' },
          },
          {
            label: 'Last Name',
            prop: 'lastName',
            component: 'el-input',
            rules: { required: true, message: 'Please input', trigger: 'blur' },
          },
        ],
      },
      {
        label: 'Address',
        type: 'steps',
        children: [
          {
            label: 'City',
            prop: 'city',
            component: 'el-input',
            rules: { required: true, message: 'Please input', trigger: 'blur' },
          },
          {
            label: 'Address',
            prop: 'address',
            component: 'el-input',
            rules: { required: true, message: 'Please input', trigger: 'blur' },
          },
        ],
      },
    ])
    const submit = defineFormSubmit((done, isValid, invalidFields) => {
      ElMessage(`submit: ${isValid}`)
      console.log(form.value, isValid, invalidFields)
      setTimeout(() => {
        done()
      }, 1000)
    })

    return {
      form,
      columns,
      submit,
    }
  },
})
</script>

异步表单

想要实现异步表单 columns 必须绑定一个动态数组

<template>
  <div style="margin-bottom: 20px">
    <el-button
      type="primary"
      @click="createForm"
    >
      Load Form
    </el-button>
    <el-button
      type="info"
      @click="createDict"
    >
      Load Dict
    </el-button>
    <el-button
      type="danger"
      @click="destroyForm"
    >
      Destroy
    </el-button>
  </div>
  <pro-form
    v-model="form"
    :columns="columns"
    label-width="100px"
    @submit="submit"
  />
</template>

<script>
import { defineComponent, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { defineFormColumns, defineFormSubmit } from 'element-pro-components'

export default defineComponent({
  setup() {
    const list = ref([])
    const form = ref({})
    const columns = ref(defineFormColumns([]))
    const submit = defineFormSubmit((done, isValid, invalidFields) => {
      ElMessage(`submit: ${isValid}`)
      console.log(form.value, isValid, invalidFields)
      setTimeout(() => {
        done()
      }, 1000)
    })
    const createForm = () => {
      columns.value = defineFormColumns([
        {
          label: 'Name',
          prop: 'name',
          component: 'el-input',
        },
        {
          label: 'Language',
          prop: 'language',
          component: 'pro-radio',
          props: {
            data: list,
          },
        },
      ])
    }
    const createDict = () => {
      list.value = [
        { value: 'Go', label: 'go' },
        { value: 'JavaScript', label: 'javascript' },
        { value: 'Python', label: 'python' },
        { value: 'Dart', label: 'dart' },
        { value: 'V', label: 'v' },
      ]
    }
    const destroyForm = () => {
      columns.value = []
      list.value = []
    }

    return {
      form,
      columns,
      submit,
      createForm,
      createDict,
      destroyForm,
    }
  },
})
</script>

TypeScript

defineFormColumns 支持传入一个泛型用来推断 prop 值;defineComponentProps 支持传入一个泛型用来辅助输入 props

Name
Search
Status
<template>
  <pro-form
    v-model="form"
    :columns="columns"
    label-width="100px"
    @submit="submit"
  />
</template>

<script setup lang="ts">
import { markRaw, ref } from 'vue'
import { ElMessage, ElSwitch } from 'element-plus'
import {
  defineFormColumns,
  defineFormSubmit,
  defineComponentProps,
} from 'element-pro-components'

interface Form {
  name?: string
  status?: boolean
}

const form = ref<Form>({})
const columns = defineFormColumns<Form>([
  {
    label: 'Name',
    prop: 'name',
    component: 'ElInput',
    props: defineComponentProps<'ElInput'>({
      clearable: true,
      placeholder: 'Please input your name',
      slots: {
        append: () => 'Search',
      },
    }),
  },
  {
    label: 'Status',
    prop: 'status',
    component: markRaw(ElSwitch),
    props: defineComponentProps<typeof ElSwitch>({
      inlinePrompt: true,
      activeText: 'Y',
      inactiveText: 'N',
    }),
  },
])
const submit = defineFormSubmit((done, isValid, invalidFields) => {
  ElMessage(`submit: ${isValid}`)
  console.log(form.value, isValid, invalidFields)
  setTimeout(() => {
    done()
  }, 1000)
})
</script>

配置

参数说明类型可选值默认值
v-model绑定值object / array--
columns表单配置参考下面 columnsarray--
menu按钮配置参考下面 menuobject--
rules表单验证规则object--
inline行内表单模式boolean-false
array是否启用数组表单boolean-
max限制数组表单的最大数量number--
label-position表单域标签的位置,如果值为 left 或者 right 时,则需要设置 label-widthstringright / left / topright
label-width表单域标签的宽度,例如 ‘50px’ 或 ‘auto’string--
label-suffix表单域标签的后缀string--
hide-required-asterisk是否显示必填字段的标签旁边的红色星号boolean-false
show-message是否显示校验错误信息boolean-true
inline-message是否以行内形式展示校验信息boolean-false
status-icon是否在输入框中显示校验结果反馈图标boolean-false
validate-on-rule-change是否在 rules 属性改变后立即触发一次验证boolean-true
size用于控制该表单内组件的尺寸stringlarge / default /small-
disabled是否禁用该表单内的所有组件boolean-false
scroll-to-error当校验失败时,滚动到第一个错误表单项boolean-false
gutter栅格间隔number-0
justifyflex 布局下的水平排列方式stringstart / end / center / space-around / space-between / spacing-evenlystart
alignflex 布局下的垂直排列方式stringtop / middle / bottomtop

columns

参数说明类型可选值默认值
propv-model 绑定的字段名 (需要是唯一值)string--
label标签文本string--
component当前项对应的组件,可以直接传入局部组件string / Component--
props传递的对应的组件的参数object--
modelKey当前项对应的组件的 v-model 绑定的字段名string / [string, string]--
children分组表单或子表单内容array--
typechildren 内部表单的类型stringarray / group / tabs / collapse / stepsarray
max限制 type=array 时子表单的最大数量number--
show是否在表单中显示当前项boolean-true
labelWidth表单域标签的宽度,例如 ‘50px’ 或 ‘auto’string--
required是否必填,如不设置,则会根据校验规则自动生成boolean-false
rules表单验证规则object / array--
error表单域验证错误信息, 设置该值会使表单验证状态变为error,并显示该错误信息string--
showMessage是否显示校验错误信息boolean-true
inlineMessage以行内形式展示校验信息boolean-false
size用于控制该表单域下组件的尺寸stringlarge / default /small-
span栅格占据的列数number-24
offset栅格左侧的间隔格数number-0
push栅格向右移动格数number-0
pull栅格向左移动格数number-0
xs<768px 响应式栅格数或者栅格属性对象number / object--
sm≥768px 响应式栅格数或者栅格属性对象number / object--
md≥992px 响应式栅格数或者栅格属性对象number / object--
lg≥1200px 响应式栅格数或者栅格属性对象number / object--
xl≥1920px 响应式栅格数或者栅格属性对象number / object--
disabled是否禁用, 当 type=tabs 或者 type=collapse 时生效booleanfalse
closable标签是否可关闭, 当 type=tabs 时生效booleanfalse
lazy标签是否延迟渲染, 当 type=tabs 时生效booleanfalse
description分步描述文案, 当 type=steps 时生效string
icon自定义分步图标, 当 type=steps 时生效string / Component
status设置当前步骤的状态, 当 type=steps 时生效stringwait / process / finish / error / success

关于 props

props 的属性将全部传递给 component 指定的组件

  • 对于存在连字符的属性,可以通过字符串包裹或者转换为驼峰结构
  • 通过 slots 可以向组件传递简单的渲染函数
  • 对于事件需要通过 on[Event] 驼峰这种形式绑定。如:change -> onChange, input -> onInput
props: {
  clearable: true,
  'prefix-icon': 'el-icon-search',
  suffixIcon: 'el-icon-date',
  slots: {
    prefix: () => h('i', { className: 'el-input__icon el-icon-search' }),
    append: () => '搜索'
  },
  onChange: e => console.log(e),
}
参数说明类型默认值
submit是否显示 submit 按钮booleantrue
submitTextsubmit 按钮显示的文字stringSubmit
submitPropssubmit 按钮的配置,参考 el-buttonobject{ type: ‘primary’ }
reset是否显示 reset 按钮booleantrue
resetTextreset 按钮显示的文字stringReset
resetPropsreset 按钮的配置,参考 el-buttonobject-
prevTextprev 按钮显示的文字stringPrev
prevPropsprev 按钮的配置,参考 el-buttonobject-
nextTextnext 按钮显示的文字stringNext
nextPropsnext 按钮的配置,参考 el-buttonobject-

事件

事件名说明参数
submitsubmit 被点击后触发done, isValid, invalidFields
resetreset 按钮被点击后触发-
validate任一表单项被校验后触发prop, isValid, invalidFields
add-itemadd 被点击后触发indexes: number[]
remove-itemremove 被点击后触发indexes: number[]
collapse-changecollapse 改变时触发active: CollapseModelValue
tab-changetab 改变时触发name: TabPaneName
step-changestep 改变时触发active: string | number

方法

方法名说明参数
validate对整个表单进行校验的方法,参数为一个回调函数。该回调函数会在校验结束后被调用,并传入两个参数:是否校验成功和未通过校验的字段。若不传入回调函数,则会返回一个 promiseFunction(callback: Function(boolean, object))
validateField对部分表单字段进行校验的方法Function(props: array | string, callback: Function(errorMessage: string))
resetFields对整个表单进行重置,将所有字段值重置为初始值并移除校验结果-
scrollToField滚动到指定表单字段Function(prop: string)
clearValidate移除表单项的校验结果。传入待移除的表单项的 prop 属性或者 prop 组成的数组,如不传则移除整个表单的校验结果Function(props: array | string)

插槽

名称说明类型
-在底部菜单前插入的任意内容-
menu-left表单底部按钮左侧{ loading: boolean }
menu-right表单底部按钮右侧{ loading: boolean }
form-[prop]当前这项的 Form Item 的内容{ item: object, indexes?: number[], value: any, setValue: (value: any) => void }
form-[prop]-label当前这项的标签文本的内容{ item: object, indexes?: number[] }
form-[prop]-error当前这项的自定义表单校验信息的显示方式{ error, item: object, indexes?: number[] }

提示

[prop] 为 columns 中定义的 prop