UseElForm - 表单
组件介绍
UseElForm 是基于 Element Plus 的 ElForm 二次封装的表单组件,专注于为表单提供开箱即用的校验增强、数据格式化、联动处理能力,同时标准化扩展配置项管理,完整兼容原生 ElForm 核心属性,可直接替换原生 ElForm 使用,大幅降低表单校验、数据处理场景的重复开发成本。
核心能力
- 增强型表单校验能力
内置精细化校验配置项(validate),支持自定义校验触发时机、校验提示类型、异步校验防抖延迟,无需手动编写重复的校验逻辑,未配置时默认使用 300ms 异步校验防抖延迟、触发提交时全量校验的策略,将 asyncDebounce 设为 0 可关闭异步校验防抖,兼顾即时校验与性能优化需求。
- 标准化的数据格式化能力
支持配置表单数据格式化规则(format),显式设置 format.rules 即可开启数据格式化,支持 String/Number/Date/Array 等常见类型的格式化处理(如日期转指定格式字符串、数组转逗号分隔字符串、空值自动替换为默认值),格式化触发时机可自定义(输入时 / 提交前),满足不同场景下的数据规范要求。
- 灵活的表单联动处理能力
内置表单字段联动配置项(linkage),支持配置字段间的联动规则(如某字段值变化时自动修改另一字段的禁用状态 / 可选值 / 默认值),联动触发方式支持即时触发 / 失焦触发,无需手动监听字段变化事件,降低表单联动逻辑的开发复杂度。
- 配置优先级与全局统一管理
所有扩展配置遵循「组件局部配置 > 全局默认配置 > 内置默认值」优先级规则,组件传入的 extConfig 优先覆盖 defaultExtConfig 中的默认配置(如校验提示默认类型为 message、日期格式化默认格式为 YYYY-MM-DD),未配置时自动兜底,兼顾全局表单风格统一与局部个性化调整。
- 原生表单完全兼容
基于 ElForm 封装,完整保留原生 model、rules、labelWidth 等核心属性,表单字段(formItems)通过标准化配置管理,插槽完全透传,可直接替换原生 ElForm 使用,无迁移成本,同时补充原生表单缺失的校验增强、数据格式化能力。
- 完善的空值与异常处理
针对表单提交前的空值做标准化处理:支持配置必填字段空值提示、非必填字段空值自动过滤 / 替换,内置数据类型异常校验(如数字字段输入非数字内容时即时提示),避免无效数据提交,提升表单数据准确性。
- 样式兼容与问题修复
内置样式修复逻辑:解决 ElForm 在 el-dialog 嵌套使用时表单项标签对齐异常的问题,修复某些场景下 el-form-item 全局样式污染导致的校验提示位置偏移问题,保证组件在不同布局场景下的样式一致性。
- 扩展配置标准化与生命周期管理
通过 ExtConfig 接口标准化校验、格式化、联动等扩展配置项,提供 defaultExtConfig 全局默认扩展配置,业务侧可灵活覆盖配置项(如自定义日期格式化规则、联动触发时机);组件挂载时自动初始化表单默认值、绑定字段监听,卸载时自动移除监听、清空校验状态,避免内存泄漏,保证动态渲染场景下的稳定性。
使用
Props
import type { PropType } from 'vue'
import type { FormColumn, QueryConfig } from './types'
import type { DictMap, DictProps, RenderConfig } from '@/types'
/**
* 组件props
*/
export default {
/**
* 表单列 必传
*/
formColumns: {
type: Array as PropType<FormColumn[]>,
required: true
},
/**
* 表单模型 必传
*/
formModel: {
type: Object as PropType<Record<string, any>>,
required: true
},
/**
* 是否使用渲染器 默认否
*/
useRender: {
type: [Boolean, Object] as PropType<boolean | RenderConfig>,
default: false
},
/**
* 字典映射 数据结构为{ formColumns[][label | useDict | useDict.key]: Array<{ label: string, value: any }> }
*/
dictMap: {
type: Object as PropType<DictMap>,
default: () => ({})
},
/**
* 字典属性 默认为{ value: 'value', label: 'label', children: 'children' },可通过全局配置进行设置,为适配全局配置此处不再设置默认值
*/
dictProps: {
type: Object as PropType<DictProps>,
default: () => ({})
},
/**
* el-form-item宽度值
*/
formItemWidth: {
type: String,
default: '100%'
},
/**
* label后缀
*/
labelSuffix: {
type: String,
default: ''
},
/**
* label宽度
*/
labelWidth: {
type: String,
default: 'auto'
},
/**
* label定位
*/
labelPosition: {
type: String,
default: 'right',
validator: (val: string) => ['left', 'right', 'top'].includes(val)
},
/**
* el-row的gutter值
*/
rowGutter: {
type: Number,
default: 20
},
/**
* el-col的span值
*/
colSpan: {
type: Number,
default: 12,
validator: (val: number) => [24, 12, 8, 6, 4, 3, 2, 1, 0].includes(val)
},
/**
* 是否禁用
*/
disabled: {
type: Boolean,
default: false
},
/**
* 查询配置
*/
queryConfig: {
type: Object as PropType<QueryConfig>,
default: () => ({})
}
}Types
import type { ExtractPropTypes } from 'vue'
import type { FormInstance } from 'element-plus'
import componentProps from './props'
import type { RenderConfig, UseDict } from '@/types'
/**
* props类型
*/
export type Props = ExtractPropTypes<typeof componentProps>
/**
* emits类型
*/
export type Emits = {
/** dictKeys事件 */
(e: 'onDictKeys', value?: string[]): void
}
/**
* queryConfig接口
*/
export interface QueryConfig {
/**
* 查询表单模式 支持ROW和INLINE
* 不显式指定则默认为ROW(queryConfig为非空对象方可生效)
*/
mode?: 'ROW' | 'INLINE'
/**
* 查询表单mode为ROW的el-col的span
* 不显式设置则使用内部默认响应式
*/
colSpan?: 24 | 12 | 8 | 6 | 4 | 3 | 2 | 1 | 0
/**
* el-form-item宽度值
* 查询表单mode为ROW时formItemWidth默认为100%;查询表单mode为INLINE时formItemWidth默认为180px
*/
formItemWidth?: string
/**
* 是否显示底部border 默认显示
*/
showBottomBorber?: boolean
/**
* 开启查询按钮loading
* 注意:开启此配置项需回调onQuery事件第二个参数done,否则查询按钮loading无法关闭
*/
openQueryBtnLoading?: boolean
/**
* 查询表单事件
*/
onQuery?: Function
}
/**
* inputConfig接口
*/
export interface InputConfig {
useRender?: boolean | RenderConfig
type?:
| 'text'
| 'textarea'
| 'number'
| 'password'
| 'email'
| 'search'
| 'tel'
| 'url'
prefixIcon?: string
suffixIcon?: string
clearable?: boolean
maxlength?: string
formatter?: string
parser?: string
autosize?: boolean | { minRows?: number; maxRows?: number }
rows?: number
resize?: string
showPassword?: boolean
placeholder?: string
/** 默认el-input的width为100% 可设置width为''或具体宽度值,为''则使用默认的el-input宽度 */
width?: string
/** 为true则对el-input使用v-focus聚焦指令 */
autoFocus?: boolean
/** el-input头部插槽内容 */
prefixContent?: string | object
/** el-input尾部插槽内容 */
suffixContent?: string | object
/** el-input前置插槽内容 */
prependContent?: string | object
/** el-input后置插槽内容 */
appendContent?: string | object
/** 在el-input右外部显示单位 */
unit?: string | object
/** el-input的clear事件 */
onClear?: Function
/** el-input的input事件 */
inputEvent?: Function
/** el-input的keyup.enter事件 */
onKeyupEnter?: Function
}
/**
* inputNumberConfig接口
*/
export interface InputNumberConfig {
useRender?: boolean | RenderConfig
min?: number
max?: number
step?: number
precision?: number
placeholder?: string
disabledScientific?: boolean
/** 在el-input-number右外部显示单位 */
unit?: string | object
}
/**
* radioConfig接口
*/
export interface RadioConfig {
useRender?: boolean | RenderConfig
/** el-radio的选项集 */
options?: Array<{ label: string; value: string | number }>
/** el-radio的props配置项 */
props?: { value: string; label?: string }
/** 是否反转单选框和标签的位置 */
reverse?: boolean
/** el-radio的change事件 */
onChange?: Function
}
/**
* selectConfig接口
*/
export interface SelectConfig {
useRender?: boolean | RenderConfig
multiple?: boolean
clearable?: boolean
filterable?: boolean
placeholder?: string
options?: Array<{ label: string; value: string | number }>
props?: { value: string; label?: string }
/** 是否使用v2版本 */
useV2: boolean
/** 是否反转 */
reverse?: boolean
/** 是否将值为数组转为以逗号分隔的字符串 */
toJoin?: boolean
/** 默认el-select的width为100% 可设置width为''或具体宽度值,为''则使用默认的el-select宽度 */
width?: string
/** el-select的change事件 */
onChange?: Function
}
/**
* cascaderConfig接口
*/
export interface CascaderConfig {
useRender?: boolean | RenderConfig
clearable?: boolean
/** el-cascader的选项集 */
options?: Array<{ label: string; value: string | number }>
/** el-cascader的props配置项 */
props?: { value: string; label?: string }
/** 默认el-cascader的width为100% 可设置width为''或具体宽度值,为''则使用默认的el-cascader宽度 */
width?: string
/** el-select的change事件 */
onChange?: Function
}
/**
* datePickerConfig接口
*/
export interface DatePickerConfig {
useRender?: boolean | RenderConfig
type?:
| 'year'
| 'years'
| 'month'
| 'months'
| 'date'
| 'dates'
| 'datetime'
| 'week'
| 'datetimerange'
| 'daterange'
| 'monthrange'
| 'yearrange'
clearable?: boolean
disabledDate?: Function
placeholder?: string
format?: string
valueFormat?: string
startPlaceholder?: string
endPlaceholder?: string
rangeSeparator?: string
/** 默认el-date-picker的width为100% 可设置width为''或具体宽度值,为''则使用默认的el-date-picker宽度 */
width?: string
/** el-date-picker的calendar-change事件 */
onCalendarChange?: Function
/** el-date-picker的clear事件 */
onClear?: Function
/** el-date-picker的change事件 */
onChange?: Function
}
/**
* timePickerConfig接口
*/
export interface TimePickerConfig {
useRender?: boolean | RenderConfig
startPlaceholder?: string
endPlaceholder?: string
rangeSeparator?: string
isRange?: boolean
editable?: boolean
format?: string
valueFormat?: string
/** el-date-picker的change事件 */
onChange?: Function
}
/**
* timeSelectConfig接口
*/
export interface TimeSelectConfig {
useRender?: boolean | RenderConfig
/** 时间选择范围开始时间在formModel中的属性名 */
startProp: string
/** 时间选择范围结束时间在formModel中的属性名 */
endProp: string
}
/**
* switchConfig接口
*/
export interface SwitchConfig {
useRender?: boolean | RenderConfig
activeValue?: string | number | boolean
inactiveValue?: string | number | boolean
/** el-switch的change事件 */
onChange?: Function
}
/**
* inputRangeConfig接口
*/
export interface InputRangeConfig {
/** 输入范围开始值在formModel中的属性名 */
startProp: string
/** 输入范围结束值在formModel中的属性名 */
endProp: string
startPlaceholder?: string
endPlaceholder?: string
rangeSeparator?: string
onClear?: Function
}
/**
* treeSelectConfig接口
*/
export interface TreeSelectConfig {
useRender?: boolean | RenderConfig
data: Record<string, unknown>[]
/** 是否反转 */
reverse?: boolean
width?: string
placeholder?: string
props?: { value: string; label?: string }
checkStrictly?: boolean
filterable?: boolean
clearable?: boolean
defaultExpandedKeys?: Array<string | number>
}
/**
* 必填配置项
*/
export interface IRequired {
/** 是否必填 */
isRequired?: boolean
/** 内置校验器 */
validator: 'mobile' | 'nonNegativeDecimal'
/** 参数 */
parmas?: any
}
/**
* formColumn接口
*/
export interface FormColumn {
/**
* el-form-item的label
* 可不传,不传则不显示label且不占用label空间,同时不会显示必填项星号,但需要手动指定errorMessage以显示必填项错误提示信息
* 如果useDict为true则必须传入label以获取dictMap中的数据,否则无法使用字典功能
*/
label?: string
/**
* el-form-item的prop
* 一般情况下为必传 为适配UseCrud组件类型特将此属性设置为非必传
* 可传以逗号分隔的字符串,组件内部自动拆解,如'startDate,endDate' => { startDate: xxx, endDate: yyy }
*/
prop?: string
/**
* 是否为必填项
* 如为boolean则使用基础必填校验
* 如为IRequired则使用内置必填校验
* 可传对象数组进行自定义校验
*/
required?: boolean | IRequired
/**
* 必填项错误消息
* 如不指定则默认为“请输入xxx”或“请选择xxx”
*/
errorMessage?: string
/**
* 是否禁用
*/
disabled?: boolean
/**
* 使用字典
* 如为boolean则将label作为dictMap的key
* 如为string或dict.data为string则将dict或dict.data作为dictMap的key
*/
useDict?: boolean | string | UseDict
/**
* el-col的span
*/
span?: number
/**
* el-form-item的labelWidth
*/
labelWidth?: string
/**
* el-form-item的labelPosition
*/
labelPosition?: 'left' | 'right' | 'top'
/**
* 是否显示当前项
* 为false则不显示当前项 会占用formColumn空间,似v-show
*/
show?: boolean
/**
* 是否过滤当前项
* 为true则将当前项从formColumns中过滤掉 不会占用formColumn空间,似v-if
*/
filter?: boolean
/**
* el-form-item的class
*/
formItemClass?: string
/**
* 插槽
* 为string类型则使用slot值作为具名插槽值
* 为boolean类型则使用prop值作为具名插槽值
*/
slot?: string | boolean
/**
* 表单项为【UseRender】的配置项
*/
renderConfig?: RenderConfig
/**
* 表单项为【输入框】的配置项
*/
inputConfig?: InputConfig
/**
* 表单项为【数字输入框】的配置项
*/
inputNumberConfig?: InputNumberConfig
/**
* 表单项为【选择下拉框】的配置项
*/
selectConfig?: SelectConfig
/**
* 表单项为【树形选择下拉框】的配置项
*/
treeSelectConfig?: TreeSelectConfig
/**
* 表单项为【级联选择器】的配置项
*/
cascaderConfig?: CascaderConfig
/**
* 表单项为【日期选择器】的配置项
*/
datePickerConfig?: DatePickerConfig
/**
* 表单项为【时间选择器】的配置项
*/
timePickerConfig?: TimePickerConfig
/**
* 表单项为【时间选择范围】的配置项
*/
timeSelectConfig?: TimeSelectConfig
/**
* 表单项为【单选框】的配置项
*/
radioConfig?: RadioConfig
/**
* 表单项为【开关】的配置项
*/
switchConfig?: SwitchConfig
/**
* 表单项为【输入范围】的配置项
*/
inputRangeConfig?: InputRangeConfig
}
export interface FormComponentInstance {
efRef: FormInstance
showQueryForm: boolean
handleDisplayQueryForm: Function
}Expose
defineExpose({
validate,
resetFields,
clearValidate,
showQueryForm,
handleDisplayQueryForm,
processApiData,
getProcessedFormModel
})