提供脱离组件树以函数方式完成一次表单校验/提交的能力,类似 ElMessage 用法: 传入 config/initValues 等 props 后内部临时挂载 Form 实例, 初始化完成即调用 submitForm,校验通过 resolve 表单值、失败 reject, 最后自动卸载,并支持 appContext 继承、timeout 与 native 透传。 同步补充单元测试、API 文档及侧边栏入口。 Co-authored-by: Cursor <cursoragent@cursor.com>
11 KiB
submitForm 函数
以命令式方式调用 MForm 组件完成一次表单校验/提交,类似 ElMessage 的用法。
调用时函数内部会临时挂载一个不可见的 MForm 实例,把入参作为 props 透传给它,等待初始化完成后调用其 submitForm 方法。校验通过则 resolve 表单值,校验失败则 reject 错误信息,最后自动卸载实例并清理 DOM。
适用于一些没有合适的容器、但又需要复用 MForm 校验逻辑的场景,例如:
- 通过快捷菜单/命令面板触发一次性表单
- 在脚本/服务层完成一次表单值校验后再发请求
- 把
config配置当作"可执行的校验规则"使用
签名
function submitForm(options: SubmitFormOptions): Promise<any>;
参数
options 与 MForm 组件的 props 基本对齐,额外提供了 native、appContext、timeout 三个参数。
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
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.submitForm。true 时返回内部响应式 values,否则返回 cloneDeep(toRaw(values)) |
appContext |
AppContext | null |
null |
父级 Vue 应用上下文。需要继承全局组件、指令、provide 等时传入,常通过 app._context 或 getCurrentInstance()?.appContext 获取 |
timeout |
number |
10000 |
等待表单初始化的最长时间(毫秒)。超时将以错误 reject。设为 <= 0 时关闭超时兜底 |
返回值
校验通过—Promise<any>resolve 当前表单值(native决定是否克隆)校验失败—Promise<any>reject 一个Error,message中包含逐条字段错误信息(格式${text} -> ${message},多条用<br>分隔)初始化超时—Promise<any>rejectError('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-plus 或 tdesign),需要宿主应用先完成相应的 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-validator(element-plus 内部用的就是它),绕开整个 Vue 渲染层 :::
类型定义
::: details 查看 SubmitFormOptions 类型定义
<<< @/../packages/form/src/submitForm.ts#SubmitFormOptions{ts}
:::