Skip to content

AtForm

沈孟平
最后更新于 2026-04-23 19:53:53

一个声明式的支持自动处理联动依赖关系的表单封装。

  • 完备的类型提示
  • 嵌套表单,支持对象或者对象数组的形式,可选择是以表格还是块状形式渲染
  • 校验错误可以自动滚动定位到第一个错误项,通过 scrollToFirstError 属性控制
  • 通过 apiFn 配置,支持自动获取下拉数据
  • 支持自动处理表单项之间依赖和外部依赖,仅需声明依赖和依赖处理函数即可
  • 支持不可编辑状态,通过 editable 属性控制 v0.12.35
  • 支持 可编辑表格形式渲染 嵌套表单 objArrayTable
  • 支持 按分块模式渲染 嵌套表单 objArrayBlock

心智模型

理解 AtForm 最重要的是把它看成一个“声明式字段渲染引擎”,而不是普通表单容器:

  1. model 负责承载数据。
  2. configs 负责描述字段如何渲染、何时隐藏、依赖谁、怎么联动。
  3. type + props + children 决定单个字段或容器字段的渲染方式。
  4. deps + listener 让字段之间的动态关系变成配置,而不是散落在模板里的手写监听。

什么时候适合用它

  • 表单字段较多,且未来会持续增长。
  • 表单项之间存在联动、显隐、异步选项加载。
  • 同一套配置需要在普通表单、弹窗表单、搜索表单之间复用。
  • 需要对象块、数组块、数组表格等复杂嵌套结构。

和其它组件的关系

  • AtSearch 本质上就是基于 AtForm 的搜索壳。
  • AtModal 常和 AtForm 组合,用于新增、编辑、查看详情。
  • AtTreeFilterAtUploadAtCronInputAtMonacoEditor 等也都可以作为 AtForm 字段类型的一部分。

代码演示

支持的所有表单类型

支持的类型(包括插槽):inputswitchinputNumberselectcascaderdatePickertimePickertreeSelectradiouploadcolorPickercheckboxcronInputtitleBarobjArrayBlockobjArrayTablebasicArrayobjBlockmonacoEditorcustomtitle v0.12.37

三级联动

该例子展示了如何使用声明依赖的方式deps + listener来进行表单之间的联动控制。

异步级联或者异步树形表单组件值回显

注意 确保自己的数据是响应式的

ts
// 表单项配置文件
const orgConfigs: AtFormItemConfig[] = [
    // ...
    {
        label: '机构位置',
        field: 'area_code1',
        type: 'cascader',
        props: {
            labelField: 'name',
            valueField: 'code',
        },
        onChange({value, ...rest}) {
            // const option = rest[0]
            const pathValues = rest[1]
            orgModel.area_code = pathValues.map((item: any) => item.code)
        },
        deps: [areaCodeOptions, loadingAreaCodeOptions],
        listener({ config }) {
            config.props = {
                options: areaCodeOptions.value,
                loading: loadingAreaCodeOptions.value,
                labelField: 'name',
                valueField: 'code',
                remote: true,
                checkStrategy: 'child',
                onLoad: async (option: any) => {
                    // 这里演示如何异步加载
                    // 注: 如果这里你使用的是`useRequest`的返回数据, 会发现响应式丢失, 因为默认是**shallowRef**, 需要将useRequest的options.shallow 设置为 `false`, 返回深层响应式数据
                    const { data } = await ARC_MANAGE_API.areaList({ level: +option.level + 1, code: option.code })
                    option.children = data.data.map((item: any) => ({ ...item, isLeaf: +option.level > 3 }))
                },
            }
        },
    },
    // ...
]
ts
// 演示如何回显
async function getAreaCodeOptions(params: any) {
    const { data } = await ARC_MANAGE_API.areaList(params)
    return data.data.map((item: any) => ({ ...item, isLeaf: params.level > 3 })) || []
}
const loadingAreaCodeOptions = ref(false)
// 级联回显函数
async function cascadeEcho(areaCodes: string[]): Promise<any[]> {
    loadingAreaCodeOptions.value = true
    // 如果没有需要回显的区域代码,直接返回顶层选项
    if (!areaCodes.length) {
        const data = await getAreaCodeOptions({})
        loadingAreaCodeOptions.value = false
        return data
    }

    async function loadChildren(options: any[], codes: string[], currentIndex: number): Promise<void> {
        if (currentIndex >= codes.length)
            return

        const currentCode = codes[currentIndex]
        for (const option of options) {
            if (option.code === currentCode) {
            // 获取下一级选项
                const level = +option.level + 1
                const children = await getAreaCodeOptions({ code: currentCode, level })
                option.children = children
                // 递归加载下一级
                await loadChildren(children, codes, currentIndex + 1)
                break
            }
        }
    }

    const result = await getAreaCodeOptions({})
    await loadChildren(result, areaCodes, 0)
    loadingAreaCodeOptions.value = false
    console.log('loadingAreaCodeOptions: ', loadingAreaCodeOptions.value)
    return result
}

响应式表单(不使用 deps|listener 方式)

如果你的表单不涉及到比较复杂的动态控制,推荐使用这个,写起来更简单一些。使用 computed 包裹后,deps|listener 方式会失效。

显隐控制

如果表单要控制某一项的显示隐藏,则可以通过依赖声明,并在 listener 中通过参数 config 控制即可。

可编辑表格

  1. 可以通过 tableProps.maxHeight 来启用表格内滚动条,每次新增会自动定位到最新一条。
  2. 嵌套表单中,config 参数中会多一个 index 属性,表示当前表单项的索引,还有一个 parentModel 属性,表示当前表单项的父级 model。

外部表单项控制表格表单渲染

API

TIP

AtFormItemConfig 继承自 NaiveUI FormItem Props

AtForm Props

名称类型默认值说明版本
configsarray必填项表单配置项
modelobject必填项表单数据对象
init-valueobjectundefined默认数据(设置后,重置表单会恢复到绑定值状态)
layoutGridProps{ xGap: 12 }栅格配置(v0.13.0 起由 NRow 迁移至 NGridv0.13.0
scroll-to-first-errorbooleantrue是否滚动到第一个出现错误的位置
n-form-propsFormPropsundefinedNaiveUI Form Props

AtFormItemConfig Properties

名称类型默认值说明版本
typestring必填项支持的所有表单类型
fieldstring必填项表单项的key
labelstring | Componentundefined标签信息
auto-requiredboolean | RuleTypeundefined是否必填校验v0.12.36
props根据type获取对应的类型undefined控件对应的Props
on-changefunctionundefined表单项变化回调, 类型参考 类型声明
call-change-on-mountedbooleanundefined控件挂载的时候是否调用 onChange
spannumber | ResponsiveDescription24栅格占据的列数
hidebooleanfalse是否隐藏
api-fnApiFnundefined需要自动发起 api 请求
refresh-api-when-showbooleanundefined当hide 属性变为 true 时,是否重新调用 apiFn 函数
componentComponentundefinedtypecustom时的组件
deepbooleanundefined是否在对依赖处理的时候需要深度监听
deps(string | Ref<any>)[]undefined声明监听依赖
listenerfunctionundefined当依赖变化时调用
no-call-listener-on-mountedbooleanundefined控件挂载的时候是否调用 listener
observe-parent-modelbooleanundefined当存在多层级表单时,是否监听父级表单的
childrenarrayundefined嵌套表单项
editablebooleantrue是否可编辑v0.12.35
slotsobjectundefined控件对应的插槽v0.12.39
multiple-configmultipleConfig Propertiesundefined针对混合表单(数组)
basic-arrayOmit<AtFormItemConfig, 'field' | 'children' | 'multipleConfig'>undefined针对基础数组表单
table-propsDataTableProps & { operationConfig?: object }undefined针对表格表单

multipleConfig Properties

名称类型默认值说明版本
default-itemArray<any> | objectundefined默认取数组第一行作为初始值,如果是 arrayBlock 则必须传
editablebooleantrue是否可编辑
limitnumberundefined表示数组至少要有几项
hide-num-prefixbooleanfalse是否隐藏行号
num-prefixstring | functionundefined嵌套表单的数字前缀
hide-add-btnbooleanfalse是否隐藏添加按钮
hide-remove-btnbooleanfalse是否隐藏删除按钮
add-btn-textstringundefined添加一行的按钮文本
add-btn-propsAtIconButtonPropsundefined添加一行的按钮 props
on-add-button-clickfunctionundefined点击添加一行按钮执行的回调函数
on-remove-button-clickfunctionundefined点击每一项的删除按钮触发的回调函数
item-classstringundefined每一项的 class

容器型表单项的 props.layout v0.13.0

objBlock / objArrayBlock / basicArray 这三种容器型表单项内部使用 NGrid 渲染子字段,可通过 props.layout 透传 GridProps 自定义内层栅格行为(如调整 xGap / yGap / cols 等)。

名称类型默认值说明
layoutGridPropsbasicArray{ xGap: 8, yGap: 8 }
objBlock / objArrayBlock{ xGap: 8 }
透传给内层 NGrid 的栅格配置,会覆盖上述默认值

TIP

容器内部默认带 layout-shift-disabled,以兼容 field 组件作为 NGrid 直接子节点的场景(NGrid 在响应模式下会过滤非 NGi 的直接子节点)。如果显式传 { layoutShiftDisabled: false, ... },子字段会被全部隐藏,请勿这样使用。

示例:

ts
const configs: AtFormItemConfig[] = [
  {
    type: 'objBlock',
    field: 'profile',
    label: '个人信息',
    props: {
      showBorder: true,
      layout: { xGap: 16, yGap: 8 },
    },
    children: [
      { type: 'input', field: 'name', label: '姓名', span: 12 },
      { type: 'input', field: 'phone', label: '电话', span: 12 },
    ],
  },
  {
    type: 'objArrayBlock',
    field: 'addresses',
    props: { layout: { xGap: 12 } },
    children: [
      { type: 'input', field: 'city', label: '城市', span: 8 },
      { type: 'input', field: 'street', label: '街道', span: 16 },
    ],
  },
  {
    type: 'basicArray',
    field: 'tags',
    props: { layout: { xGap: 4, yGap: 4 } },
    basicArray: { type: 'input', span: 8 },
  },
]

AtForm Expose

名称类型说明版本
validate(...args: any[]) => Promise<any>手动触发校验表单
reset-value() => void重置表单数据为初始化值
set-value(newValue: any) => void手动设置表单数据
restore-validation() => void还原到未校验的状态
get-el() => HTMLElement获取表单dom
get-final-configs() => AtFormItemConfig[]获取动态变化后的表单项配置v0.12.38

类型声明

显示类型声明
Typescript
interface ListenerOptions<P> {
  refreshApi?: () => Promise<any>
  config: AtFormItemConfig<P>
  model: Recordable
  index?: number
  parentModel?: any
}

interface OnChangeOptions<P> {
  /** 当前表单项的 apiFn */
  refreshApi?: () => Promise<any>
  /** 当前表单项的配置,支持直接修改 */
  config: AtFormItemConfig<P>
  /** 当前 model,嵌套表单为父级 */
  model: Recordable
  /** 嵌套表单时对应的数组下标 */
  index?: number
  /** 嵌套表单时对应的父级 model */
  parentModel?: any
  /** 当前表单项变化后的值 */
  value: any
  /** 其他 naive表单变化时传的参数,如 option|pathValue等,打印出来通过下标访问(剩余参数收集) */
  [key: string]: any
}

type ApiFn = (apiFnOptions: { config?: AtFormItemConfig, model?: any }) => Promise<any[]>

破坏性变更 v0.13.0

Caution

v0.13.0 起 AtForm 内部由 NRow / NCol 全量迁移至 NGrid / NGi,对外 API 不再兼容旧写法。升级时请按下面的对照做改动。

1. AtFormlayout 属性类型调整

  • 类型:RowPropsGridProps
  • 默认值:{ gutter: 12 }{ xGap: 12 }
  • NRow.gutter / NCol.span 的 API 在 NGrid 侧没有完全等价字段,请按 naive-ui 官方字段名调整。

常用字段对照:

v0.12.x(NRowv0.13.0(NGrid说明
gutter: NxGap: N水平间距
gutter: [x, y]xGap: x, yGap: y水平 / 垂直间距
cols栅格总列数,默认 24
layoutShiftDisabled内部默认开启,详见下方“注意事项”

迁移示例:

diff
- <AtForm :layout="{ gutter: 8 }" :model="model" :configs="configs" />
+ <AtForm :layout="{ xGap: 8 }" :model="model" :configs="configs" />

2. 容器型表单项新增 props.layout

objBlock / objArrayBlock / basicArrayprops 新增可选的 layout?: GridProps,用于自定义内层 NGrid,详见 容器型表单项的 props.layout

3. 直接依赖 NRow / NCol 的外部代码需同步迁移

如果你在自己的模板里直接使用过 <NRow> / <NCol> 且其 props 来自 AtFormProps['layout'] / AtFormItemConfig['span'],请改用 <NGrid> / <NGi>AtFormItemConfig.span 的类型已由 ColProps['span'] 改为 GridItemProps['span'],实际取值(number | ResponsiveDescription)保持不变,一般无需改动。

注意事项

Caution

  • 注意:新版本若使用 computed 包裹表单配置项,deps|listener 方式去修改当前 config 会失效。
  • 也就是说如果使用 computed 包裹表单配置项,则不要使用 deps|listener 方式去修改当前 config
  • 当涉及到表单间联动的时候推荐可以采用 deps 声明依赖,使用 listener 进行依赖变更后处理,使用方法看这里 或者 看那里
  • 注意 deps 监听字符串只能针对同级表单生效,如果需要监听非平级表单,则需要包裹 computed 进行监听,例如 model.a.b 监听 model.a 的值, 则需要包裹 computed 进行监听,例如 computed(() => model.a)注意
  • 表单验证出现 valuenumber 或其他类型的时候,在配置项中的 rule 需要指定 typenumber或者指定类型, 否则会报错。
  • autoRequired 开始支持指定类型简单校验。v0.12.36
  • 注意!!! objArrayTableobjArrayBlock在超多行多列下会表现卡顿,推荐设置editablefalse去自行处理编辑。v0.12.30

Warning

  • 注意当使用 apiFn 的时候,props 中的 options 会覆盖 apiFn 返回的 options
  • 注意当使用 apiFn 的时候,它返回的值采用的是 shallowRef 包裹,如果你使用例如 cascader 组件,并且需要使用异步加载(onLoadremote),则需要设置 apiFn 的返回值为 reactive(options)
  • 当使用自定义组件,也就是 typecustom 的时候,需要使用 markRaw 包裹该组件。

changelog

af4d1 - feat(AtForm): 将`NRow`迁移到`NGrid` on 2026/4/22
a4d32 - feat(components): 组件`AtForm`所有类型组件支持插槽 on 2025/10/24
be9a6 - feat(components): 增加`AtForm`提供获取真实`configs`配置方法 on 2025/10/24
eaf16 - fix(components): 修复`AtForm`中`resetValue`处理对象问题 on 2025/10/24
333b1 - feat(components): `AtForm`新增支持类型`title` on 2025/10/16
d6f21 - feat(components): `autoRequired`支持指定类型简单校验 on 2025/10/15
ddd9a - feat(components): `AtForm`全面支持`editable`设置不可编辑状态 on 2025/9/25
08c23 - fix(components): 移除`useDeps`函数中的`editable`参数 on 2025/9/22
783c2 - feat(components): 添加可编辑属性`editable`支持,优化表单字段组件的编辑逻辑 on 2025/9/22
c0a4f - fix(components): `AtForm` objArrayTable's onAddButtonClick should be called on 2025/7/9

贡献者

Last updated: