动态表单
一个基于 Vue3 的动态表单组件,支持自定义表单配置、验证规则和样式,适用于分步注册、分步信息收集等场景
示例
NOTE
- 当表单项 ① 选择为公司 1 ----> 表单项 ② 为 input 输入框
- 表单项 ① 选择为公司 2 ----> 表单项 ② 为 select 下拉选择框
- 表单项 ① 选择为公司 3 ----> 表单项 ② 为 select 下拉选择框,下拉框赋值为默认物料并禁止选择
- 表单项 ① 选择为公司 4 ----> 表单项 ② 为 select 下拉选择框,选项内取消默认物料
- 当表单项 ② 选择为默认物料 ----> 展示表单项 ③ 上传组件和表单项 ④checkbox 选择框
- 表单项 ④ 同时选择选项 2 和选项 3 ----> 展示表单项 ⑤ 文本域输入框
- 表单项 ⑤ 输入数字 ----> 展示表单项 ⑥ 自定义表单项
loading
配置项说明:
DynamicForm 组件 Props
参数 | 类型 | 必填 | 默认值 | 说明 |
---|---|---|---|---|
formStatus | object | 是 | null | 表单配置对象,通过 createFormItem 创建的表单项,只传入第一个 createFormItem 生成的表单项就行,后续通过next 函数返回下一个表单项 |
formState | object | 是 | {} | 表单数据对象,必须是响应式对象(ref 或 reactive) |
formConfig | object | 否 | undefined | Element Plus 的 Form 组件配置项,如 rules、label-width 等(如果不传入,组件只渲染 el-form-item,当前组件最外层需要使用 el-form 包裹,如果传入,组件内部会自动使用 el-form 包裹) |
layoutConfig | object | 否 | { cols: 1, gutter: 20 } | 表单布局配置,包含 cols(列数)和 gutter(间距) |
createFormItem 函数参数配置
参数 | 类型 | 必填 | 说明 |
---|---|---|---|
type | string | function | null | 是 | 表单项类型,可以是字符串或返回字符串的函数。支持:'input'、'select'、'checkbox'、'radio'、'datePicker'、'inputNumber'、'switch'、'upload'、'rate'、'timeSelect'。如果设置为 null 或不设置,可以通过 payload.customSlot 传入自定义组件 |
payload | object | 是 | 表单项的配置信息 |
next | function | 是 | 返回下一个表单项的函数,返回 null 表示没有下一项, 函数的形参current 是当前表单项的配置信息, 可以通过current.parent 获取到上一个表单项 |
payload 配置项
参数 | 类型 | 必填 | 说明 |
---|---|---|---|
label | string | 是 | 表单项标签文本 |
prop | string | 是 | 表单项的属性名,对应 formState 中的键名 |
value | any | 否 | 表单项的默认值 |
span | number | 否 | 表单项占据的列宽(1-24),不设置时根据 layoutConfig.cols 自动计算(比如 layoutConfig.cols 设置的 3,默认就是一行三个,所有表单项 span 默认为 8,如果同一行的所有表单项 span 相加大于 24,会自动进行换行) |
id | string | 否 | 表单项的唯一标识,用于 getAcientById 获取表单项 |
attr | object | 否 | 对应 Element Plus 官方组件的所有 props 配置项(比如 input 组件的 placeholder,clearable 等) |
options | array | computed | 否 | 选项数组,用于 select/checkbox/radio 等组件,每项格式:{ label, value, attr? } |
formItemAttr | object | 否 | 对应 ElFormItem 组件的所有配置项(比如 label-width,label-position 等) |
slot | object | 否 | 自定义插槽配置,格式:{ [slotName]: ({ props, payload }) => VNode }(比如 input 组件的 prepend,append 等) |
customSlot | function | 否 | 自定义组件渲染函数,当 type 为 null 或不设置时生效,格式:({ props, payload }) => VNode |
layoutConfig 配置项
参数 | 类型 | 必填 | 默认值 | 说明 |
---|---|---|---|---|
cols | number | 否 | 1 | 每行显示的列数 |
gutter | number | 否 | 20 | 列间距(px) |
options 数组项配置
参数 | 类型 | 必填 | 说明 |
---|---|---|---|
label | string | 是 | 选项的显示文本 |
value | any | 是 | 选项的值 |
attr | object | 否 | 选项对应的 Element Plus 组件属性配置 |
配置选项
createFormItem 参数
参数 | 类型 | 必填 | 说明 |
---|---|---|---|
type | string | null | 否 | 表单项类型,对应 Element Plus 组件名称,如 input、select 等。设置为 null 时可使用自定义组件 |
payload | object | 是 | 表单项配置对象 |
next | function | 否 | 联动函数,当前表单项值变化时触发 |
payload 配置项
参数 | 类型 | 必填 | 说明 |
---|---|---|---|
label | string | 是 | 表单项标签 |
prop | string | 是 | 表单项属性名,用于收集表单数据 |
value | any | 否 | 表单项初始值 |
span | number | 否 | 表单项占据的列数,默认为 1 |
attr | object | 否 | 传递给 Element Plus 组件的属性 |
rules | array | 否 | 表单验证规则 |
slot | object | 否 | 插槽配置,可以包含 default、prefix、suffix 等 |
customSlot | function | 否 | 自定义渲染函数,用于完全自定义表单项的渲染 |
id | string | 否 | 表单项唯一标识,用于获取祖先表单项 |
hidden | boolean | function | 否 | 是否隐藏表单项,可以是布尔值或返回布尔值的函数 |
layoutConfig 配置项
参数 | 类型 | 必填 | 说明 |
---|---|---|---|
cols | number | 否 | 表单布局的列数,默认为 3 |
labelWidth | string | number | 否 | 表单项标签宽度 |
labelPosition | 'left' | 'right' | 'top' | 否 | 表单项标签位置 |
size | 'large' | 'default' | 'small' | 否 | 表单项尺寸 |
1. 基础使用
1.1 组件引入
javascript
import DynamicForm from './components/DynamicForm'
import { createFormItem } from './components/DynamicForm'
1.2 基本用法
vue
<template>
<el-form :model="formState" label-width="100px">
<!-- form-status 是第一个createFormItem生成的表单项,后续通过next函数返回下一个表单项 -->
<DynamicForm :form-status="InputItem" :form-state="formState" :layout-config="layoutConfig" />
</el-form>
</template>
<script setup>
import { ref } from 'vue'
const formState = ref({}) // 表单数据对象
const layoutConfig = {
cols: 2, // 每行显示的列数
gutter: 20 // 列间距
}
const InputItem = createFormItem({
type: 'input',
payload: {
// ...配置
},
next: current => (current.payload.value === 'showSelect' ? SelectItem : null) // 下一个表单项
})
const SelectItem = createFormItem({
type: 'select',
payload: {
// ...配置
},
next: current => null // 下一个表单项
})
</script>
2. 表单项配置
组件内部已经处理了当前支持的所有表单项
v-model
的双向绑定,比如 upload 组件的v-model:file-list
,直接在 payload 中配置 value 即可,不需要再手动绑定,监听事件直接在payload.attr
中配置即可,比如@change
事件,需要写成onChange
2.1 基础表单项
javascript
// 基础配置
const basicInput = createFormItem({
type: 'input',
payload: {
label: '输入框:', // 标签文本
prop: 'inputField', // 表单项的属性名
}
next: current => null // 下一个表单项
})
// 基础表单项完整配置
const basicSelect = createFormItem({
type: 'select', // 表单项类型
payload: {
label: '下拉选择框:', // 标签文本
prop: 'selectField', // 表单项的属性名
id: 'selectField', // 表单项的唯一标识,用于getAcientById获取表单项
span: 12, // 表单项占据的列宽(1-24)
value: '', // 默认值
attr: { // el官方组件属性
placeholder: '请选择',
clearable: true,
onChange: (value) => {
console.log('onChange', value)
}
},
// 只有select、checkbox、radio组件需要配置options,其他组件不需要配置
options: [
{ label: '选项1', value: '1' },
{ label: '选项2', value: '2' }
],
formItemAttr: {
labelWidth: '100px'
},
slot: {
prepend: ({ props, payload }) => {
return <ElIcon><Edit /></ElIcon>
}
}
},
next: current => null // 下一个表单项
})
2.2 支持的表单项类型
input
: 输入框select
: 下拉选择框checkbox
: 多选框radio
: 单选框datePicker
: 日期选择器inputNumber
: 数字输入框switch
: 开关upload
: 上传组件rate
: 评分组件timeSelect
: 时间选择器
2.3 动态类型
javascript
const dynamicInput = createFormItem({
type: formStatus => {
// 根据条件返回不同的组件类型
return formStatus.parent.payload.value === '100' ? 'input' : 'select'
},
payload: {
// ...配置
}
})
3. 表单联动
3.1 基于 next 函数的联动
javascript
const firstItem = createFormItem({
type: 'select',
payload: {
// ...配置
},
next: current => {
// 根据当前值决定下一个表单项
if (current.payload.value === 'showNext') {
return secondItem
}
return null
}
})
3.2 基于 computed 的动态属性
注意,动态属性需要使用
computed
,否则不会自动更新
javascript
import { computed } from 'vue'
const dynamicItem = createFormItem({
type: 'select',
payload: {
attr: computed(() => ({
disabled: someCondition,
placeholder: `请${someCondition ? '输入' : '选择'}内容`
})),
options: computed(() => {
// 动态选项
return [
/* ... */
]
})
}
})
4. 布局配置
4.1 全局布局
已经做了自动换行的处理,比如
cols
设置为 2,默认一行两个表单项,如果这两个表单项设置的span
相加大于 24,会自动进行换行
javascript
const layoutConfig = {
cols: 2, // 每行显示的列数
gutter: 20 // 列间距
}
4.2 单个表单项布局
javascript
const wideItem = createFormItem({
type: 'input',
payload: {
span: 24 // 占据整行,不设置时根据layoutConfig.cols自动计算
// ...其他配置
}
})
5. 表单验证
同官方组件的使用方式,可以配置
el-form
的rules
,也可以在payload.formItemAttr.rules
配置每个表单项的rules
javascript
const validatedItem = createFormItem({
type: 'input',
payload: {
formItemAttr: {
rules: [
{ required: true, message: '请输入内容', trigger: 'blur' },
{ min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
]
}
}
})
6. 自定义插槽
javascript
const uploadItem = createFormItem({
type: 'upload',
payload: {
slot: {
default: ({ props, payload }) => {
return (
<ElButton type='primary'>
<ElIcon>
<UploadFilled />
</ElIcon>
<span>点击上传</span>
</ElButton>
)
}
}
}
})
7. 自定义组件
如果需要使用自定义组件,可以将 type 设置为 null 或不设置,然后通过 payload.customSlot 传入自定义组件
javascript
const customItem = createFormItem({
type: null, // 或者不设置 type
payload: {
label: '自定义组件:',
prop: 'custom',
value: '',
customSlot: ({ props, payload }) => {
// props 包含了通过 createBaseProps 处理后的属性,包括 v-model 的处理
// payload 是表单项的完整配置信息
return (
<div class='custom-component'>
<MyCustomComponent v-model={props.modelValue} {...props} />
<div class='custom-footer'>
<ElButton>自定义按钮</ElButton>
</div>
</div>
)
}
}
})
8. 高级特性
8.1 祖先表单项关联
通过设置
id
属性,可以获取到任意祖先表单项,比如获取到formStatus
javascript
const item = createFormItem({
payload: {
id: 'unique-id' // 设置唯一ID
}
})
// 在其他地方通过 ID 获取表单项
import { getAcientById } from './DynamicForm'
const relatedItem = getAcientById('unique-id')
注意事项
响应式处理
formState
必须是响应式对象(使用ref
或reactive
)- 动态属性(如
attr
)建议使用computed
处理,以提高性能 - 表单项的值变化会自动同步到
formState
表单项配置
- 表单项的
prop
属性必须唯一 id
在使用getAncestorById
时必须唯一hidden
属性支持动态计算,可以是布尔值或返回布尔值的函数
- 表单项的
联动处理
next
函数在表单项值变化时触发- 避免在
next
函数中创建循环引用 - 复杂的联动逻辑建议使用
computed
属性
性能优化
- 使用
computed
处理复杂的动态属性 - 避免在
next
函数中进行耗时操作 - 合理使用
span
属性控制表单项布局
- 使用
组件使用
- Element Plus 组件会被异步加载
- 自定义组件需要全局注册或在使用时导入
- 使用插槽时注意作用域插槽的数据结构
表单验证
- 支持 Element Plus 的所有验证规则
- 可以通过
formRef.value.validate()
触发表单验证 - 验证规则支持异步验证函数
样式定制
- 可以通过
layoutConfig
统一配置表单样式 - 支持通过 CSS 变量覆盖默认样式
- 自定义组件可以使用 scoped 样式
- 可以通过
js 组件
loading