1
0
mirror of synced 2026-05-22 02:48:17 +08:00
Files
tmagic-editor/docs/api/form/submit-form.md
roymondchen 638c3e9f3c feat(form): 新增 submitForm 命令式提交函数
提供脱离组件树以函数方式完成一次表单校验/提交的能力,类似 ElMessage 用法:
传入 config/initValues 等 props 后内部临时挂载 Form 实例,
初始化完成即调用 submitForm,校验通过 resolve 表单值、失败 reject,
最后自动卸载,并支持 appContext 继承、timeout 与 native 透传。

同步补充单元测试、API 文档及侧边栏入口。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-21 15:55:28 +08:00

11 KiB
Raw Permalink Blame History

submitForm 函数

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

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

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

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

签名

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

参数

optionsMForm 组件的 props 基本对齐,额外提供了 nativeappContexttimeout 三个参数。

名称 类型 默认值 说明
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))
appContext AppContext | null null 父级 Vue 应用上下文。需要继承全局组件、指令、provide 等时传入,常通过 app._contextgetCurrentInstance()?.appContext 获取
timeout number 10000 等待表单初始化的最长时间(毫秒)。超时将以错误 reject。设为 <= 0 时关闭超时兜底

返回值

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

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

基础用法

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);
}

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

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} :::