Skip to content

UseElForm - 表单

组件介绍

UseElForm 是基于 Element Plus 的 ElForm 二次封装的表单组件,专注于为表单提供开箱即用的校验增强、数据格式化、联动处理能力,同时标准化扩展配置项管理,完整兼容原生 ElForm 核心属性,可直接替换原生 ElForm 使用,大幅降低表单校验、数据处理场景的重复开发成本。

核心能力

  1. 增强型表单校验能力

内置精细化校验配置项(validate),支持自定义校验触发时机、校验提示类型、异步校验防抖延迟,无需手动编写重复的校验逻辑,未配置时默认使用 300ms 异步校验防抖延迟、触发提交时全量校验的策略,将 asyncDebounce 设为 0 可关闭异步校验防抖,兼顾即时校验与性能优化需求。

  1. 标准化的数据格式化能力

支持配置表单数据格式化规则(format),显式设置 format.rules 即可开启数据格式化,支持 String/Number/Date/Array 等常见类型的格式化处理(如日期转指定格式字符串、数组转逗号分隔字符串、空值自动替换为默认值),格式化触发时机可自定义(输入时 / 提交前),满足不同场景下的数据规范要求。

  1. 灵活的表单联动处理能力

内置表单字段联动配置项(linkage),支持配置字段间的联动规则(如某字段值变化时自动修改另一字段的禁用状态 / 可选值 / 默认值),联动触发方式支持即时触发 / 失焦触发,无需手动监听字段变化事件,降低表单联动逻辑的开发复杂度。

  1. 配置优先级与全局统一管理

所有扩展配置遵循「组件局部配置 > 全局默认配置 > 内置默认值」优先级规则,组件传入的 extConfig 优先覆盖 defaultExtConfig 中的默认配置(如校验提示默认类型为 message、日期格式化默认格式为 YYYY-MM-DD),未配置时自动兜底,兼顾全局表单风格统一与局部个性化调整。

  1. 原生表单完全兼容

基于 ElForm 封装,完整保留原生 model、rules、labelWidth 等核心属性,表单字段(formItems)通过标准化配置管理,插槽完全透传,可直接替换原生 ElForm 使用,无迁移成本,同时补充原生表单缺失的校验增强、数据格式化能力。

  1. 完善的空值与异常处理

针对表单提交前的空值做标准化处理:支持配置必填字段空值提示、非必填字段空值自动过滤 / 替换,内置数据类型异常校验(如数字字段输入非数字内容时即时提示),避免无效数据提交,提升表单数据准确性。

  1. 样式兼容与问题修复

内置样式修复逻辑:解决 ElForm 在 el-dialog 嵌套使用时表单项标签对齐异常的问题,修复某些场景下 el-form-item 全局样式污染导致的校验提示位置偏移问题,保证组件在不同布局场景下的样式一致性。

  1. 扩展配置标准化与生命周期管理

通过 ExtConfig 接口标准化校验、格式化、联动等扩展配置项,提供 defaultExtConfig 全局默认扩展配置,业务侧可灵活覆盖配置项(如自定义日期格式化规则、联动触发时机);组件挂载时自动初始化表单默认值、绑定字段监听,卸载时自动移除监听、清空校验状态,避免内存泄漏,保证动态渲染场景下的稳定性。

使用

UseElForm

Props

ts
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

ts
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

js
defineExpose({
  validate,
  resetFields,
  clearValidate,
  showQueryForm,
  handleDisplayQueryForm,
  processApiData,
  getProcessedFormModel
})

基于 MIT 许可发布