tmagic-editor/docs/api/form/submit-form.md
roymondchen 12069e0937 feat(form): submitForm 支持返回 changeRecords
新增 returnChangeRecords 选项,开启后 resolve { values, changeRecords },
便于命令式调用时获取表单变更记录,并同步更新文档与单测。
2026-06-02 16:43:07 +08:00

12 KiB
Raw Blame History

submitForm 函数

以命令式方式调用 MForm 组件完成一次表单校验/提交,类似 ElMessage 的用法。

调用时函数内部会临时挂载一个不可见的 MForm 实例,把入参作为 props 透传给它,等待初始化完成后调用其 submitForm 方法。校验通过则 resolve 表单值,校验失败则 reject 错误信息,最后自动卸载实例并清理 DOM。

适用于一些没有合适的容器、但又需要复用 MForm 校验逻辑的场景,例如:

  • 通过快捷菜单/命令面板触发一次性表单
  • 在脚本/服务层完成一次表单值校验后再发请求
  • config 配置当作"可执行的校验规则"使用

签名

function submitForm(options: SubmitFormOptions): Promise<any>;

参数

optionsMForm 组件的 props 基本对齐,额外提供了 nativereturnChangeRecordsappContexttimeout 等参数。

名称 类型 默认值 说明
config FormConfig 必填,表单配置
initValues Record<string, any> {} 表单初始值
lastValues Record<string, any> {} 需对比的值(开启对比模式时传入)
isCompare boolean false 是否开启对比模式
parentValues Record<string, any> {} 父级 values透传给字段的回调
labelWidth string '200px' label 宽度
disabled boolean false 是否禁用
height string 'auto' 表单高度
stepActive string | number 1 步骤表单当前激活步骤
size 'small' | 'default' | 'large' 组件尺寸
inline boolean false 是否行内表单
labelPosition string 'right' label 对齐方式
keyProp string '__key' 配置项的唯一 key
popperClass string 弹层 className
preventSubmitDefault boolean 是否阻止表单原生 submit
extendState (state: FormState) => Record<string, any> | Promise<Record<string, any>> 扩展 formState
native boolean false 透传给 Form.submitFormtrue 时返回内部响应式 values,否则返回 cloneDeep(toRaw(values))
returnChangeRecords boolean false true 时 resolve 结果为 { values, changeRecords },携带表单变更记录;否则仅 resolve values
appContext AppContext | null null 父级 Vue 应用上下文。需要继承全局组件、指令、provide 等时传入,常通过 app._contextgetCurrentInstance()?.appContext 获取
timeout number 10000 等待表单初始化的最长时间(毫秒)。超时将以错误 reject。设为 <= 0 时关闭超时兜底

返回值

  • 校验通过Promise<any> resolve 当前表单值(native 决定是否克隆);当 returnChangeRecordstrueresolve { values, changeRecords }
  • 校验失败Promise<any> reject 一个 Errormessage 中包含逐条字段错误信息(格式 ${text} -> ${message},多条用 <br> 分隔)
  • 初始化超时Promise<any> reject Error('submitForm timeout after ${timeout}ms: form is not initialized.')

无论成功或失败,函数都会在最后自动 unmount 内部 app 并移除挂载用的 DOM 容器,无需调用方手动清理。

::: tip 关于 changeRecords changeRecords 记录的是表单挂载后发生的字段变更(由各字段的 change 事件累积而来)。在 submitForm 这种命令式、无用户交互的场景下,通常为空数组;只有在 extendState 或字段联动等逻辑中触发了变更时才会有内容。MForm 内部的 submitForm 在校验通过后会清空变更记录,因此本函数会在调用前先对其做快照再返回。 :::

基础用法

import { submitForm } from '@tmagic/form';

try {
  const values = await submitForm({
    config: [
      {
        type: 'text',
        name: 'username',
        text: '用户名',
        rules: [{ required: true, message: '请输入用户名' }],
      },
    ],
    initValues: { username: '' },
  });
  console.log('提交成功', values);
} catch (e) {
  console.error('校验失败', e);
}

同时获取变更记录changeRecords

设置 returnChangeRecords: trueresolve 的结果会从单纯的 values 变为 { values, changeRecords }

import { submitForm } from '@tmagic/form';

const { values, changeRecords } = await submitForm({
  config: [{ type: 'text', name: 'username', text: '用户名' }],
  initValues: { username: 'foo' },
  returnChangeRecords: true,
});

console.log(values); // { username: 'foo' }
console.log(changeRecords); // ChangeRecord[]

在组件中继承父级应用上下文

MForm 内部使用 @tmagic/design 的组件(背后可能是 element-plustdesign),需要宿主应用先完成相应的 app.use(...) 安装。在 submitForm 这种脱离常规组件树的命令式调用中,可通过 appContext 把父级应用上下文带过去:

<script setup lang="ts">
import { getCurrentInstance } from 'vue';

import { submitForm } from '@tmagic/form';

const { appContext } = getCurrentInstance()!;

const onClick = async () => {
  const values = await submitForm({
    config: [{ type: 'text', name: 'text', text: '文本' }],
    initValues: { text: 'hello' },
    appContext,
  });
  console.log(values);
};
</script>

也可以在初始化 app 时把上下文缓存下来,再在任意位置复用:

import { createApp } from 'vue';
import ElementPlus from 'element-plus';
import MagicForm, { type SubmitFormOptions, submitForm as rawSubmitForm } from '@tmagic/form';

import App from './App.vue';

const app = createApp(App);
app.use(ElementPlus);
app.use(MagicForm);
app.mount('#app');

export const submitForm = (options: Omit<SubmitFormOptions, 'appContext'>) =>
  rawSubmitForm({ ...options, appContext: app._context });

处理校验错误

校验失败时 reject 的 Error.message 已经把出错字段拼好,可以直接展示到用户:

import { tMagicMessage } from '@tmagic/design';

try {
  const values = await submitForm({ config, initValues });
  await save(values);
} catch (e: any) {
  tMagicMessage.error({
    dangerouslyUseHTMLString: true,
    message: e.message,
  });
}

运行环境

submitForm 内部依赖 document / window 来挂载临时 Vue 实例,因此只能在浏览器或具备 DOM 环境的运行时中使用

环境 是否可用 说明
浏览器 / Electron 渲染进程 / 浏览器扩展 直接可用
Vitest / Jest + happy-dom / jsdom 项目自身的单测就跑在这种环境下
纯 Node.js / Bun / Deno无 DOM polyfill 模块顶层就会读 document,会抛 document is not defined
Node.js + 手动注入 happy-dom / jsdom ⚠️ 可用,需要在 import @tmagic/form 之前完成全局变量注入;校验行为不一定与浏览器完全一致

在 Node.js 中使用(需要先准备 DOM

下面是一个在 Node 脚本里调用 submitForm 的完整例子,使用 happy-dom 作为 DOM polyfill

// scripts/check-form.ts
import { Window } from 'happy-dom';

const window = new Window();
Object.assign(globalThis, {
  window,
  document: window.document,
  navigator: window.navigator,
  HTMLElement: window.HTMLElement,
});

// 注意DOM polyfill 必须先注入到 globalThis再用动态 import
// 加载业务模块,否则 @tmagic/design 等模块顶层执行时就会读 document
const { createApp } = await import('vue');
const ElementPlus = (await import('element-plus')).default;
const MagicForm = (await import('@tmagic/form')).default;
const { submitForm } = await import('@tmagic/form');

const parentApp = createApp({ render: () => null });
parentApp.use(ElementPlus);
parentApp.use(MagicForm);

const values = await submitForm({
  config: [{ type: 'text', name: 'username', text: '用户名' }],
  initValues: { username: 'foo' },
  appContext: parentApp._context,
});

console.log(values);

::: warning 注意

  • DOM polyfill 必须在 import 业务模块之前 注入到 globalThis,否则模块顶层执行时仍会失败
  • happy-dom / jsdom 中,element-plus 的部分 validate() 行为不一定能 1:1 复现真实浏览器(例如某些场景下必填规则可能不触发),建议关键校验使用自定义 validator 函数确保稳定
  • 如果只是想在 Node 端做一次纯校验,更稳妥的做法是直接复用 async-validatorelement-plus 内部用的就是它),绕开整个 Vue 渲染层 :::

类型定义

::: details 查看 SubmitFormOptions 类型定义 <<< @/../packages/form/src/submitForm.ts#SubmitFormOptions{ts} :::

::: details 查看 SubmitFormResult 类型定义 <<< @/../packages/form/src/submitForm.ts#SubmitFormResult{ts} :::