动态表单组件ReForm
组件实现基于
Vue3
+Element Plus
+Typescript
,同时引用vueUse
+lodash-es
+tailwindCss
(不影响功能,可忽略)
基于ElForm封装的动态表单组件,可通过配置对象自动渲染表单内容,可配置表单内容、表单校验规则、表单联动、折叠表单、多列表单,满足大部分表单应用场景。
表单内容组件基于动态组件(component)渲染,因此,你可以随意配置想要的表单控件(全局注册),同时也支持通过插槽(slot)配置自定义表单控件。
内置几个特殊表单控件,如多行文本框(textarea/el-textarea)、下拉列表(el-select)、单选组(el-radio-group)、多选组(el-checkbox-group),后面三个有固定关联子组件,相比其他组件有增加几个配置字段,需要特别留意,若是还有其他相似的组件,目前建议通过插槽slot走自定义渲染。
思路
基于ElFormItem的属性增加渲染组件说明字段,整合成一个动态ElFormItem渲染说明对象,通过v-for循环渲染实现一个简单的表单动态渲染。
考虑到 分组功能
,使用嵌套数据格式进行渲染,动态渲染中会存在组件的嵌套渲染。
整体设计:
- Form:表单容器,处理表单配置配置,收集数据和校验规则,通过 renderFormItem 组件渲染同级表单项
- renderFormItems:同级表单项渲染,调用v-for遍历表单配置进行单个表单项 renderFormItem 的渲染,当遇到分组配置,需要嵌套调用 renderFormItems 渲染分组表单内容
- renderFormItem:渲染单个表单项,处理表单控件渲染,控件属性、事件等问题
难点
- 插槽渲染:通过动态插槽渲染方式
- 数据绑定:需要自行挂载 modelValue + update:modelValue
基础应用
通过 items
字段配置表单,每个配置项表示一个表单字段,ReForm会基于表单配置自动初始化表单数据,可以通过 v-model
实现表单数据的双向绑定,不需要特殊处理表单数据的话可以完全忽略,直接在 submit
事件中通过第一个参数获取当前表单值进行接口提交处理。
const formItems = ref([
{
label: "ID",
field: "id",
defaultValue: "just text content",
type: "text",
customClass: "is-required" // 通过样式类展示必填ico,不直接绑定required属性,会有校验问题
},
{
label: "Name",
field: "name",
defaultValue: "",
comp: "el-input",
// labelSlot: "name-label", 默认字段标签名插槽命名规则 [field]-label,也可以自定义
tooltip: "这是tooltip",
props: {
clearable: true
},
rules: [{ required: true, message: "不能为空" }]
},
{
label: "Age",
field: "age",
comp: "el-input",
props: {
type: "number",
min: 1,
max: 9999
},
events: {
focus: handleNumberFocus,
blur: handleNumberBlur
}
},
{
label: "Remark",
field: "remark",
comp: "el-textarea",
props: {
rows: 4
}
},
{
label: "Birthday",
field: "birthday",
comp: "el-date-picker",
tooltip: "这是tooltip",
props: {
type: "date",
format: "YYYY/MM/DD",
class: "w-full"
}
},
{
label: "Subject",
field: "subject",
comp: "el-select",
tooltip: "这是tooltip",
tips: "这是显眼的tips",
options: [
{
label: "Subject1",
value: "1"
},
{
label: "Subject2",
value: "2"
},
{
label: "Subject3",
value: "3"
}
],
rules: [{ required: true, message: "不能为空" }],
props: {
clearable: true
}
},
{
label: "Hobby",
field: "hobby",
comp: "el-checkbox-group",
labelKey: "name",
valueKey: "id",
options: [
{
name: "hobby1",
id: "1"
},
{
name: "hobby2",
id: "2"
},
{
name: "hobby3",
id: "3"
}
],
defaultValue: ["1"],
props: {
clearable: true
}
},
{
label: "Marry",
field: "marry",
comp: "el-radio-group",
options: [
{
label: "married",
value: "1"
},
{
label: "none",
value: "2"
}
],
defaultValue: "2",
props: {
clearable: true
}
}
]);
查看 /demo/form/basic.md
只读展示
设置 editable
字段为 false
,表单会按只读模式展示,可用于一些详情页展示,与 disabled
不同的是,disabled
只是禁用表单控件,不影响表单内容。
查看 /demo/form/readonly.md
响应网格布局
ReForm可以通过设置 cols
字段指定响应网格数量,默认为 1
,每个表单配置项可以通过 span
设置网格大小,默认为 1
。
查看 /demo/form/grid.md
分组表单项
表单配置项允许通过 type=group
指定当前配置项为分组配置项,需要指定 children
字段配置组内的表单配置项,同 items
配置项一致,正常分组不超过两级,也不建议超过两级。
查看 /demo/form/group.md
字段联动/隐藏
表单配置项允许通过 visible
字段控制表单项的隐藏,也支持配置联动规则由ReForm控制隐藏。
查看 /demo/form/relation.md
自定义表单控件
每个配置项默认支持两种种插槽 labelSlot
、slot
,slot
用于自定义控件,默认 [field]-control
,labelSlot
用于自定义标签,默认 [field]-label
。labelSlot指定插槽为命名插槽,slot指定插槽为作用域插槽,带有作用域字段item
,为格式化后的表单配置项,可以用于绑定属性 v-bind="item.props"
以及绑定事件 v-on=item.events
,props 内置 modelValue
属性关联字段值、events内置 update:modelValue
事件更新字段值。
如果是分组配置项,还支持 groupSlot
配置分组触发器,带 collpased
作用域插槽字段,用于判断分组折叠状态,配合 handleSwitchCollapsed
方法自定义控制分组折叠状态。
查看 /demo/form/slots.md
自定义表单按钮
ReForm组件默认展示 提交 、 取消 两个按钮,并提供 btns
插槽用于自定义按钮,需要重新这两个按钮,配合ReForm组件实例方法进行组件状态控制。
查看 /demo/form/btn-slot.md
ReForm属性
字段 | 说明 | 类型 | 默认值 |
---|---|---|---|
formRef | ref函数,获取 ElForm 实例 | (form: InstanceType\<typeof ElForm\> | null) => void | - |
items | 必填,表单配置项集合 | Array<ReFormItem> | [] |
modelValue | 表单数据 | ReFormModelValue | - |
cols | 表单网格布局列数 | number | ReGridResponsive | 1 |
colGap | 表单网格布局水平间距 | number | 16 |
size | 表单尺寸 | “large” | “default” | “small” | “default” |
disabled | 表单是否禁用 | boolean | false |
editable | 表单是否可编辑 | boolean | true |
scrollToError | 校验失败是否自动定位第一个校验失败字段 | boolean | true |
autoCollapseInValidate | 校验失败是否自动展开分组,并定位分组内的第一个校验失败字段 | boolean | true |
ignoreBtnLabel | 是否忽略表单按钮组标签宽度 | boolean | true |
btnSpan | 表单按钮组网格大小 | number | ReGridResponsive | 1 |
btnSpanStyle | 表单按钮组内联样式 | string | - |
submitBtnText | 提交按钮文字内容 | string | “确定” |
cancelBtnText | 取消按钮文字内容 | string | “取消” |
submitBtnProps | 提交按钮自定义属性 | Partial<ButtonProps> | - |
cancelBtnProps | 取消按钮自定义属性 | Partial<ButtonProps> | - |
tooltipProps | 表单默认提示语自定义样式 | Partial<TooltipProps> | - |
hideBtns | 隐藏表单按钮组 | boolean | false |
emptyText | 只读情况下空内容展示占位符 | string | “-” |
request | 表单提交远程请求方法 | (model: Record\<string, any\>) => Promise\<any\> | - |
ReForm事件
事件名 | 说明 | 格式 | |
---|---|---|
change | 表单控件字段值发生变化时触发 | (field: string, value: any, model: MaybeRef<ReFormModelValue>) => void |
update:modelValue | 表单数据更新后触发 | (model: MaybeRef<ReFormModelValue>) => void |
submit | 点击提交按钮触发,只有未自定义 btns 插槽时有效 | (formData: ReFormModelValue) => void |
cancel | 点击取消按钮触发,只有未自定义 btns 插槽时有效 | () => void |
success | 表单提交成功后触发,只有指定 request 时有效 | (res: any) => void |
error | 表单提交失败后触发,只有指定 request 时有效 | (res: any) => void |
type ReFormModelValue = Record<string, any>
:::info 小TIP
除了上述事件之外,表单控件的具体事件可以通过表单配置项字段 events
进行绑定。
:::
ReForm插槽
插槽名 | 说明 |
---|---|
btns | 自定义表单按钮组,提交和取消需要自定义 |
[field]-label | 每个表单配置项都自带一个标签插槽,默认按 [field]-label 格式提供,可通过 labelSlot 自定义插槽名 |
[field]-control | 每个表单配置项都自带一个控件插槽,默认按 [field]-control 格式提供,可通过 slot 自定义插槽名 |
[field]-group | 表单分组配置项自带一个分组触发器插槽,默认按 [field]-group 格式提供,可通过 groupSlot 自定义插槽名 |
ReForm Expose
字段 | 说明 | 类型 |
---|---|---|
submiting | 表单提交状态 | boolean |
reFormRef | ElForm组件示例 | InstanceType<typeof ElForm> |
formData | 表单数据对象 | ShallowRef<Record<string, any>> |
formRules | 表单校验规则集合 | ShallowRef<Partial<ReFormRules>> |
formItems | 表单配置对象集合 | ShallowRef<ReFormItem[]> |
formVisible | 表单字段显示状态集合 | Ref<Record<string, boolean>> |
formCollapsed | 表单分组项展开/折叠状态集合 | Ref<Record<string, boolean>> |
validate | 同ElForm,校验整个表单 | (callback: FormValidateCallback) => void |
clearValidate | 同ElForm,清除表单校验状态 | () => void |
resetFields | 同ElForm,重置表单字段和校验状态 | (props?: Arrayable\<string\> | undefined) => void |
handleSwitchCollapsed | 切换分组展开/折叠状态 | (field: string) => void |
autoCollapseByErrors | 如果表单带有分组配置项,可基于校验失败项自动展开分组并定位到第一个错误项 | (errors?: Record\<string, any\>) => void |
ReFormItem配置项
字段 | 说明 | 类型 | 默认值 |
---|---|---|---|
type | 表单项类型,text表示纯文本,comp表示组件控件,group表示分组 | “text” | “comp” | “group” | “comp” |
label | type不为"group"下必填,表单项标签名,type=“group” 无效 | string | - |
field | 必填,表单项绑定字段名,type=“group” 也指定一个唯一值,不会作为表单数据项 | string | - |
labelWidth | 表单项标签名宽度大小,默认取 ReForm 配置的labelWidth | number | - |
labelPosition | 表单项标签名位置,默认取 ReForm 配置的labelPosition | string | - |
customClass | 表单项容器自定义样式名 | string | - |
controlClass | 表单控件自定义样式名 | string | - |
span | 表单项网格大小 | number | ReGridResponsive | 1 |
defaultValue | 表单控件默认值,用于 ReForm 初始化表单 | any | - |
tooltip | 表单控件问好提示语,受 labelSlot 插槽影响 | string | - |
tips | 表单控件下方提示文字内容 | string | - |
tipsClass | 表单控件下方提示文字样式 | string | - |
comp | type="comp"下必填,表单控件组件名,支持全局注册组件 | string | - |
childComp | 表单控件嵌套子组件名 | string | el-option | el-radio | el-checkbox |
options | 表单控件需要的数据集,如 el-select、el-radio-group、el-checkbox-group | Array<{[labelKey]: string, [valueKey]: any}> | - |
labelKey | options中数据键名名称 | string | ‘label’ |
valueKey | options中数据键值名称 | string | ‘value’ |
modelProp | 表单控件数据绑定属性名 | string | ‘modelValue’ |
modelEvent | 表单控件数据绑定事件名 | string | ‘update:modelValue’ |
events | 表单项自定义绑定事件,不需要带 on | Record<string, Function> | - |
slot | 表单控件插槽 | string | [field]-control |
labelSlot | 字段名插槽 | string | [field]-label |
visible | 表单项是否显示或是字段联动展示配置 | boolean | ReFormItemVisibleRule | ReFormItemVisibleRuleCondition | - |
以下是仅在 type="group"
下有效:
字段 | 说明 | 类型 | 默认值 |
---|---|---|---|
children | 分组表单配置项,只在 type="group" 时有效 | Array<ReFormItem> | - |
groupSlot | 分组触发器的自定义插槽名 | string | [field]-group |
defaultCollapsed | 分组触发器默认展开/收起状态 | boolean | false |
collapsedText | 分组触发器默认展开/收起的文本 | boolean | ["展开", "收起"] |
collapsedTriggerProps | 分组触发器按钮属性 | Partial<ButtonProps> | - |
collapsedTriggerIndex | 分组触发器是否跟随 labelWidth 缩进,只在 labelPosition 为 left /right 有效 | boolean | false |
ReForm Types
export type ReFormModelValue = Record<string, any>;
// 响应式网格配置对象
export interface ReGridResponsive {
[key: string]: number | undefined; // 自定义尺寸
xs?: number;
sm?: number;
md?: number;
lg?: number;
xl?: number;
}
// 字段联动配置对象 - 多个匹配
export interface ReFormItemVisibleRule {
type: "&" | "|"; // 多个匹配条件逻辑关系
conditions: ReFormItemVisibleRuleCondition[]; // 多个匹配条件
}
// 字段联动配置对象 - 单个匹配
export interface ReFormItemVisibleRuleCondition {
field: string; // 关联字段名
value: any; // 关联字段值 formData[field] 与 value的比较 formData[field].includes(value)
ignoreCase?: boolean;
type?: // 关联字段判断方式 =(等于),!(非),.(包含),^(开头),$(结尾),&(全部匹配),|(部分匹配)
"=" | "!=" | "." | "!." | "^=" | "=$" | "!^=" | "!=$" | "&." | "!&." | "|.";
}
扩展
基于动态表单,扩展了 动态搜索条件表单、抽屉表单、模态框表单
抽屉表单
基于ReForm封装的抽屉表单组件,隐藏内置的按钮组,基于抽屉页脚重新声明按钮组。
整体表单配置与ReForm相同,可以快速上手配置。
通过抽屉表单可以用同样的表单配置直接获得一个抽屉展示的交互方式,省去抽屉实现。可以直接配置远程请求方法request实现自动提交接口。
ReDrawerForm属性
字段 | 说明 | 类型 | 默认值 |
---|---|---|---|
items | 必填,表单配置项集合 | Array<ReFormItem> | [] |
formData | 表单数据 | ReFormModelValue | - |
formProps | ReForm属性配置 | Partial<ExtendFormProps> | - |
modelValue | 是否显示抽屉 | boolean | - |
disabled | 是否禁用表单 | boolean | false |
resetAfterClosed | 是否在关闭抽屉后重置表单 | boolean | true |
request | 表单提交接口请求方法 | ReFormProps[“request”] | - |
ReDrawerForm事件
事件名 | 说明 | 格式 |
---|---|---|
open | 抽屉打开时触发 | () => void |
opened | 抽屉打开后触发 | () => void |
close | 抽屉关闭时触发 | () => void |
close | 抽屉关闭后触发 | () => void |
close-auto-focus | 输入焦点聚焦在 Drawer 内容时的回调 | () => void |
open-auto-focus | 输入焦点从 Drawer 内容失焦时的回调 | () => void |
update:modelValue | 抽屉打开/关闭时触发 | (visible: boolean) => void |
change | 表单控件字段值发生变化时触发 | (field: string, value: any, model: MaybeRef\<FormModelValue\>) => void |
update:formData | 表单数据更新后触发 | (model: MaybeRef\<FormModelValue\>) => void |
submit | 点击确定按钮触发,只有未自定义 footer 插槽时有效 | (formData: FormModelValue) => void |
cancel | 点击取消按钮触发,只有未自定义 footer 插槽时有效 | () => void |
success | 表单提交接口请求成功后触发,只有设置 request 属性时有效 | (res: unknow) => void |
error | 表单提交接口请求失败后触发,只有设置 request 属性时有效 | (res: unknow) => void |
type FormModelValue = Record<string, any>
:::info 小TIP
除了上述事件之外,表单控件的具体事件可以通过表单配置项字段 events
进行绑定。
:::
ReDrawerForm插槽
插槽名 | 说明 |
---|---|
header | ElDrawer抽屉头部插槽 |
footer | ElDrawer抽屉页脚插槽,会覆盖默认的页脚表单按钮,需要自行实现表单提交重置 |
default-header | 抽屉主体内容头部,放置在表单之前 |
default-content | 抽屉主体内容部分,默认时ReForm,可自定义整个ReForm |
default-footer | 抽屉主体内容页脚,放在表单之后 |
除了上述插槽,ReForm支持的表单插槽也都支持
ReDrawerForm Expose
字段 | 说明 | 类型 |
---|---|---|
reFormRef | ElForm组件示例 | InstanceType<typeof ElForm> |
innerFormRef | ReForm组件示例 | InstanceType<typeof ReForm> |
formData | 表单数据 | MaybeRef<Record<string, any>> |
resetForm | 重置表单 | () => void |
模态框表单
与抽屉表单类似,通过模态框的形式展示,省去模态框的实现。基本与抽屉表单实现一致。
源代码
可以通过查看具体实现,如果遇到问题可以留言或者提出issue。