mcpField
F
fcode-forms

fcode-forms

github.com/factorialco开发1396 次安装2 周前更新
代码仓库

关于 fcode-forms

fcode-forms 是 Factorial Code 的表单技能,可将流程参数表单嵌入网页,并在提交时启动流程。它说明表单配置、版本固定、结果处理及异步执行方式。

主要功能

  1. 01将流程表单嵌入网页
  2. 02通过参数 Schema 定义表单
  3. 03使用 stable 固定流程版本
  4. 04处理提交结果、错误和重定向
  5. 05将耗时任务改为异步执行

适用场景

  1. 01在网站中收集数据并启动 Factorial 流程
  2. 02在 React 页面嵌入表单
  3. 03通过 stable 控制表单使用的流程版本
  4. 04将长时间运行的工作移至异步流程

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/factorialco/factorial-code-skills --skill fcode-forms

安装后在 Claude Code 中输入/fcode-forms确认已加载。

附带文件references/advanced.md

详细介绍

Factorial Code — 表单

Factorial Code 表单可将流程的输入参数表单嵌入任意网页。 每次提交都会使用表单数据启动一次流程执行,结果会在页面内处理(消息、重定向、回调)。有关架构本身,请参阅 fcode-json-schema。

注意事项

  • 表单就是流程的 parametersSchema.json——没有单独的表单定义。若要更改字段/验证/标签,请编辑架构,而不是 嵌入代码。
  • 每个嵌入项都必须同时指定 team 和 process/processId,且两者都必须使用代号,而不是每个工作区的 UUID。
  • 始终将嵌入项固定到 stable 别名 (data-fcode-form-process-version="stable"/processVersion: "stable")。 未固定版本的表单会运行当前版本,因此每次 fcode push 都会立即更改它。详情见下文。
  • 版本或别名未知时,表单不会报错——而是静默运行当前版本(见“将表单固定到某个版本”)。
  • 必须启用 Forms 标志——在流程控制面板的从 Factorial 触发部分启用,或在流程的 metadata.json 中设置 factorial.form.enabled(旧版写法: "form": { "enabled": true }),然后执行 fcode push;否则嵌入表单不会呈现。
  • 表单需要 Factorial 身份,除非你将其公开发布。 默认情况下,表单通过 Factorial 访问;Factorial 会验证用户身份并注入可信上下文,而只有团队和流程代号的匿名访客无法读取架构或提交表单。对于确实需要公开的表单,可通过 factorial.form.public 关闭此限制。无论哪种情况,表单背后的流程都会限制调用者可以执行的操作(见下文)。
  • 从 Factorial 内部打开的表单通过 factorial.form 配置,即 Factorial Action 模块(fcode-factorial-actions)。form.enabled 仍然有效,并会保持同步。向用户展示在 App 自有页面上的表单由 factorial.form.appTool 指定;安装/设置/卸载表单则由 form.appRole 命名;如果流程未声明角色,则使用保留代号(fcode-factorial-actions)。对于面向 Factorial 之外用户的表单,factorial.form.public 会将其公开给任何持有链接的人。
  • 架构不能包含可执行的 JavaScript。 embedFormOptions.onChange 和字段 transformFn 已移除,消息使用 Markdown 格式——不会呈现原始 HTML。客户端行为应由嵌入表单的网页实现。
  • 切勿将密钥放入嵌入代码或 options 中——它们会在浏览器中运行。
  • 连接第三方账户(Slack、GitHub 等)是架构功能——使用 "ui:widget": "oauth",并配置一个 fcode.oauth 流程;该流程完成后会重定向到 SDK 的回调页面。若服务商提供 OAuth,切勿要求用户在表单中粘贴 API 令牌。请参阅“连接外部账户”。
  • 表单文本通过架构中的 fcode.i18n("key") 令牌翻译,并在向客户端提供架构前由服务器替换。请参阅 fcode-i18n。
  • 表单提交会受到请求超时限制(约一分钟)——请确保同步流程运行迅速,或将耗时工作异步执行(见下文)。
  • 优先通过流程返回值驱动用户体验(见下文);仅将 onSuccess/onError 用于客户端逻辑。

启用表单

  1. 创建流程并定义其输入参数(这些参数会成为字段)。
  2. 启用 Forms 标志——可在流程 Dashboard 的 Trigger from Factorial 部分启用,也可在 CLI 工作区的 processes/<slug>/metadata.json 中启用,然后运行 fcode push:
{
  "name": "Contact request",
  "tags": [],
  "factorial": { "enabled": true, "form": { "enabled": true } }
}

表单是 Factorial Action 的一个入口点——同一个 factorial 区块还包含按钮、Factorial One 工具和后端任务 (fcode-factorial-actions,字段参考见 fcode-cli)。旧版顶层 "form": { "enabled": true } 仍然有效,并会与 factorial.form.enabled 保持同步;新代码中请优先使用 factorial。

Marketplace 应用的生命周期表单是指使用保留 slug install、settings 和 uninstall 的流程(sync 是第四个保留 slug,用于集成同步流程);平台会根据 slug 判断其角色。

应用在 Factorial 中的页面上向自己的用户提供的表单,配置为 "factorial": { "form": { "enabled": true, "appTool": true } }。在新流程中,切勿编写 form.appRole: USER_FACING_FORM——这正是 appTool 设置的值,平台会让两者保持同步。其他三个值则不同:此区块没有对应的开关,因此安装、设置或卸载表单要么通过 form.appRole 命名,要么使用保留 slug。新流程请优先使用 slug;fcode-factorial-actions 中说明了相关规则,以及如何在已发布的应用中采用该设置。

应用配置完成后,install 或 settings 表单会再次打开,因此应显示当前值,而不是空表单。关于如何实现,可参阅 references/advanced.md 中的 preRenderProcess 模式。

在流程代码中像读取其他参数一样读取已提交的值: const { context: { parameters } } = fcode;

表单可以信任什么

用户通过 Factorial 访问表单时,Factorial 会对其进行身份验证:安装/设置/卸载页面、应用在自身页面上提供的表单,以及 UI 触发器对话框(fcode-ui-triggers)都通过 Factorial 的后端提交,并且在执行任何操作之前都会强制检查 requiredPolicies(fcode-factorial-actions)。仅持有团队和流程 slug 的匿名访客无法访问任何内容。

除非表单设置了 factorial.form.public。该标志会让任何持有链接的人都能访问表单,此时调用者完全未经身份验证:此类提交携带的 company_id 和 access_id 都是页面发送的值。只有当表单面向 Factorial 之外的人员时,才启用此标志;并且应将以下内容视为表单仅有的保护措施。

这解决的是谁在调用的问题,但无法确保他们提交了什么,因此表单背后的流程仍然是防护措施:

  • 将每个已提交参数都视为由调用者控制——包括嵌入页面预填的参数(company_id、triggered_from_location、access_id)。切勿根据这些参数授权。
  • 不要返回调用者本不应已拥有的信息——提交结果以及 preRenderProcess 的 variables 都会发送给打开 URL 的人。
  • 凭据只允许单向流动:设置表单只能报告是否已设置密钥,绝不能返回密钥的值(references/advanced.md)。

嵌入表单

两个必填输入项,均为 slug,另外还应始终添加版本固定值:

  • fcode-team-slug — 来自 https://code.factorialhr.com/platform/<fcode-team-slug>
  • fcode-process-slug — 流程 Dashboard 上的 Slug 字段(例如 send-welcome-email)
  • 流程版本 — 固定为 stable 别名(见下一节)

请使用 slug,而不是流程 ID。 ID 是每个工作区独有的 UUID,因此基于 ID 的嵌入代码在不同工作区之间迁移时会失效;slug 则不会(现有基于 ID 的嵌入代码仍可正常工作,而且 React 属性仍名为 processId——请传入 slug)。slug 可以编辑,但旧值不会重定向,因此在分发嵌入代码前请确定 slug——重命名会导致所有已粘贴到页面中的嵌入代码失效。

只需加载一次 SDK(数据属性和 Fcode.initForm 方法都需要它):

<script defer src="https://code.factorialhr.com/sdk/forms.js"></script>

方法 1 — 数据属性(SDK 会替换该元素):

<div
  data-fcode-form-team="<fcode-team-slug>"
  data-fcode-form-process="<fcode-process-slug>"
  data-fcode-form-process-version="stable"
></div>

方法 2 — Fcode.initForm(选择器或 DOM 元素):

<div id="my-fcode-form"></div>
<script>
  Fcode.initForm("#my-fcode-form", {
    team: "<fcode-team-slug>",
    process: "<fcode-process-slug>",
    processVersion: "stable",
  });
</script>

方法 3 — FcodeForm React 组件(React 17/18;安装 @factorialco/fcode-react-forms):

import FcodeForm from "@factorialco/fcode-react-forms";

const MyComponent = () => (
  <FcodeForm
    team={"<fcode-team-slug>"}
    processId={"<fcode-process-slug>"}
    processVersion={"stable"}
  />
);

在 SSR 框架(例如 Next.js)中,通过动态导入并设置 ssr: false。

将表单固定到某个版本

版本固定值(data-fcode-form-process-version / processVersion)可以是已发布的版本标签(v1.0.0)或别名——请使用 stable,这样只需移动别名即可发布和回滚,无需编辑嵌入代码所在的页面(别名模式见 fcode-core-concepts;发布命令见 fcode-cli)。该固定值适用于表单发出的两种请求——加载定义和提交表单。流程 Dashboard 会为你填写该值:在嵌入代码旁的选择器中选择版本或别名,然后复制生成的代码片段。

未知版本或别名会静默地运行当前版本——平台只会记录一条服务器端警告,webhook 也是如此(参见 fcode-cli)。属性中的拼写错误不会显现出来:如果提交行为异常,请检查执行记录中的版本。直接调用表单端点时,也可以使用 fcode-cli 中说明的 version_tag 查询参数。

处理提交结果

默认情况下:执行期间会显示加载遮罩;成功时表单会替换为成功消息,出错时则替换为错误消息。

回调(所有方法的格式相同):

Fcode.initForm("#my-fcode-form", {
  team: "<fcode-team-slug>",
  process: "<fcode-process-slug>",
  onSuccess: (formId, processExecutionResult, formSubmittedData) => {},
  onError: (formId, error, formSubmittedData) => {},
});

使用数据属性时,通过全局函数指定处理程序: data-fcode-form-on-success="HANDLER_NAME"、 data-fcode-form-on-next-step="..." 和 data-fcode-form-on-error="..."。

根据流程返回值驱动行为(无需客户端代码):

return { message: "Thanks, **we received your request**." };                // success message (markdown)
return { message: "| Item | Status |\n|---|---|\n| Sync | Done |" };        // GFM tables work too
return { status: 400, body: { formErrors: {                                 // inline validation errors
  fields: { email: "Invalid email." }, global: ["A global error."] } } };
return { status: 400, body: { errorMessage: "**Sync failed** — retry." } }; // error message (markdown)
return { redirect: { url: "https://example.com", timeout: 2000 } };         // redirect after submit

消息使用 Markdown,而非 HTML。 message、errorMessage 以及 schema 中的 markdown.before / markdown.after 块会按 GitHub Flavored Markdown 渲染——包括表格、标题、链接、图片、代码块和任务列表。原始 HTML 会被直接丢弃,不会渲染(因此不会执行任何编写的内容);请改用成功 / 下一步 / 错误回调来实现行为。

保持快速,或改用异步

提交会等待进程完成,并受请求超时限制(约一分钟)。在行内执行的繁重工作——缓慢的 API 调用、大型导出、多记录同步——会导致超时,提交失败。

如果工作可能耗时,请改用异步:

  • 嵌入 async: true——提交会立即返回 201 和执行 ID,而不是等待结果(参见 references/advanced.md)。
  • 交由另一个进程处理——使用 fcode.processes.run("process-identifier", options) 启动繁重工作(参见 fcode-javascript / fcode-python),然后快速返回确认信息(message / redirect),不要在行内等待它完成。

仅当请求确实很快,或数据必须在步骤之间流转时,才保持同步。传递数据时,不要阻塞提交,而应使用:

  • preRenderProcess 在表单呈现前于服务端计算 variables(参见 references/advanced.md)。
  • 多步骤链式表单通过 nextProcessId + variables 传递状态(见下文)。如果这些步骤只有视觉上的区分——步骤之间不执行任何工作——请改用 ui:steps,并保留单个进程。

多步骤表单

有两种机制,选择合适的一种很重要:

机制进程执行适用情况
ui:steps——单个表单中的步骤一个最后一步之后执行一次仅在视觉上拆分;所有逻辑都在一个进程中
nextProcessId——链式流程每步一个每步执行一次中间步骤必须实际执行某些操作(例如外部验证、为下一个表单计算 variables)

两者可以组合使用:链式流程中的表单本身也可以声明 ui:steps。

当表单变得较长——大约有十个或更多字段时——请用 ui:steps 拆分,将相关字段分组到各个步骤中,并将必填字段放在前面。

单个表单中的步骤(ui:steps)

在架构的根 ui 节点中声明步骤,并将每个属性分配给一个步骤。SDK 一次显示一个步骤,并提供“下一步”按钮;只有最后一步的按钮会提交表单并启动(单次)执行。该执行会接收所有收集到的参数,与单页表单完全相同——无需更改进程代码。

"ui": {
  "ui:steps": {
    "config": { "layout": "tabs" },
    "steps": [
      { "title": "About you", "description": "Optional text", "fields": ["name"] },
      { "title": "Details", "fields": ["age", "subscribe"] }
    ]
  }
}
  • config(可选):layout——"tabs"(默认)或 "sidebar"; nextLabel / backLabel——按钮标签,默认为 "Next" / "Back"。 ui:submitButtonOptions 适用于最后一步的按钮。
  • “下一步”只会验证当前显示的字段(包括该步骤所需的 required);返回按钮和步骤导航可以重新访问已完成的步骤,且不会丢失输入;尚未到达的步骤则保持禁用状态。
  • 未列入任何步骤的属性会追加到最后一步;无法使用的 ui:steps 会回退到普通单页表单,并在控制台显示警告。
  • 支持根级别的 properties + required + dependencies;根级别的 oneOf / allOf 组合不会拆分为多个步骤。SDK 版本要求见 references/advanced.md。
链接进程(nextProcessId)

每个步骤都是独立的进程。返回下一个进程的slug以继续:

return { nextProcessId: "collect-shipping-address" };

字段名仍为 nextProcessId,它接受 slug 或 ID——请使用 slug,这样同一条链就能在每个工作区中运行。

接下来,SDK 会呈现 nextProcessId 对应的表单。每个后续步骤都会在 fcode.context.parameters 的 steps 数组中接收前面所有步骤的数据和结果。返回与 nextProcessId 并列的 variables 节点,即可向后续步骤传递状态。

连接外部账户(OAuth)

表单可以要求用户先授权第三方服务提供商,再提交表单。声明一个 string 或 boolean 属性,并设置 "ui:widget": "oauth"(object 属性则应改用 "ui:field": "oauth"——rjsf 不会读取对象上的 ui:widget):

"github_account": {
  "title": "GitHub account",
  "description": "You will be asked to authorize in a new window.",
  "type": "string",
  "ui": {
    "ui:widget": "oauth",
    "ui:options": {
      "authorizationUrl": { "$ref": "#/variables/authorizeUrl" },
      "connectLabel": "Connect GitHub",
      "connectedLabel": { "$ref": "#/variables/connectedLabel" },
      "onComplete": "reload"
    }
  }
}

SDK 会渲染一个 连接 按钮,在小型 弹出窗口中打开 authorizationUrl,等待流程结束,关闭弹出窗口并填充字段。该 属性的 title / description 是按钮周围的说明(和其他地方一样支持 Markdown, 较长的文案可使用 markdown.before/after)。

ui:options默认值用途
authorizationUrl—fcode.oauth.start() 返回的平台授权 URL。必填;URL 无效时会停用按钮并显示控制台警告
connectLabel / connectedLabel / pendingLabelConnect / Connected / Waiting for authorization…流程开始前、结束后和进行期间的按钮文字
onCompletenone连接成功后表单的处理方式(见下文)
popup{ "width": 600, "height": 700 }弹出窗口的尺寸,在页面中居中显示
closeDelay3000完成后弹出窗口保持打开的毫秒数,以便用户看到确认信息

让它正常工作的三条规则:

  1. authorizationUrl 来自 fcode.oauth.start(),该函数在 preRenderProcess 中调用,并通过 {"$ref": "#/variables/…"} 注入——普通的 {{mustache}} 标记会经过 HTML 转义(/ → &#x2F;,& → &amp;), SDK 会拒绝此 URL。预渲染约定见 references/advanced.md。

    const flow = await fcode.oauth.start({
      authorizeUrl: "https://github.com/login/oauth/authorize",
      clientId: fcode.env.GITHUB_CLIENT_ID,
      scope: ["public_repo"],
      onComplete: "github-oauth-callback",
      data: { companyId },
    });
    return { variables: { authorizeUrl: flow.authorizationUrl } };
    

    切勿手动构建授权 URL、state 或 PKCE 密钥对。平台持有这三者,以及向提供商注册的唯一 redirect_uri——这是整个流程能够正常工作的唯一方式,因为提供商要求 URI 必须预先注册,而且每次安装都有自己的 deploy- 工作区。

  2. 完成流程不需要 webhook。 平台验证并使用 state 后,会自行调用 onComplete,因此无需公开端点,也无需进行任何身份验证。调用时会传入 code、 codeVerifier、redirectUri(在令牌交换中原样传回——提供商会逐字节比较该值)、data 和 state;如果提供商拒绝请求,则会传入 error / errorDescription。流程会交换授权码,在服务端存储令牌(存入敏感变量,或使用加密标记的 datastore——参见 fcode-javascript / fcode-python),最后将 弹出窗口重定向到 SDK 的回调页面:

    const { state } = fcode.context.parameters;
    return {
      status: 302,
      headers: {
        Location: "https://code.factorialhr.com/sdk/oauth-callback.html"
          + "?status=success&value=" + encodeURIComponent(login)
          + "&state=" + encodeURIComponent(state),
      },
    };
    

    该页面会告知表单流程结果——表单只信任来自它所打开窗口的消息——然后自行关闭。查询参数:status (success;其他任何值都视为错误)、value(成为字段值)、state(必填,从回调参数中原样传回)、message (出错时显示在按钮下方),以及 object 字段应接收的任何额外参数。

    每种退出情况都要传回 state,包括错误情况。它用于将处理结果绑定到表单打开的弹出窗口;如果通过备用通道返回时未包含该值,完成结果就会被丢弃——onComplete: "reload" 用于恢复因 COOP 断开连接的情况。即使令牌交换成功且令牌已存储,流程仍会以取消状态结束。

  3. value 是不透明句柄(例如账户登录名、连接 ID)——绝不能是令牌:它会传到浏览器,并随提交内容一起发送。已连接状态只是向用户发出的信号,不是凭证:接收提交内容的流程必须在自身侧验证连接。 字段值。 boolean → true;string → value 参数; object → 除 status / message 外的每个参数。连接前字段值为空, 因此将字段标记为 required 可避免账户连接前提交表单。提前关闭弹出窗口 会被视为取消,并重新启用按钮。发生错误时,会在按钮下方显示错误的 message,并重新加载表单定义,无论 onComplete 的设置是什么:回调已经运行, 可能已消耗授权 URL 中的一次性 state,因此只有重新运行 preRenderProcess,才能为按钮提供有效 URL。输入的值和消息会在重新加载后保留, 不会提交任何内容,用户只需重试。若重新加载失败,表单将保持原样。

设置 default 会将按钮渲染为已连接状态。 只要字段有值,按钮就处于已连接 状态,因此,如果 preRenderProcess 检测到现有连接,就会返回带有 default 的属性,并将连接句柄设为其值——按钮会渲染为已连接(禁用),该句柄也会像其他 默认值一样随提交一起传递。用户再次返回、在关闭弹出窗口后完成连接,或使用 onComplete: "reload" 时,都会进入已连接视图;以此方式传入的值也会清除较早的 错误消息。

onComplete — 连接后要执行的操作:

  • none — 按钮显示已连接状态;用户照常提交,流程会将该值与其他参数一并接收。
  • submit — 立即提交表单,因此返回值中的 message / redirect / nextProcessId 会立即生效。在 ui:steps 表单中,这会进入下一步。
  • reload — 重新获取表单定义,再次运行 preRenderProcess,以便服务器检查连接并渲染已连接状态(不同的文案、更多字段, 或通过 default 将按钮变为“已连接为 …”)。其他字段中输入的值会保留。 由于服务器是此处的事实来源,即使关闭弹出窗口时尚未到达回调页面,表单也会重新加载—— 这是唯一会在取消时重新加载的模式,因为回调并未运行,URL 仍然有效。

需要注意的限制:弹出窗口由点击操作直接打开,因此默认的弹出窗口拦截器会允许打开—— 如果浏览器设置为阻止所有弹出窗口,则会在按钮下方显示一条消息;嵌入页面或提供方上的 Cross-Origin-Opener-Policy 标头可能会切断与弹出窗口的连接,此时流程会以取消结束, 而 onComplete: "reload" 是恢复方式。 需要托管版 SDK 或 @factorialco/fcode-react-forms ≥ 3.3.0(f0 控件: @factorialco/rjsf-f0 ≥ 2.3.0)。

fcode-examples (references/oauth-connect.md)中提供了完整示例,涵盖启动流程的预渲染、 完成流程,以及验证连接的表单流程。

自动文件上传

文件字段是 "type": "string",并且在属性内部包含 "ui": { "ui:widget": "file" } 键:

{
  "properties": {
    "inputFile": { "type": "string", "ui": { "ui:widget": "file" } }
  }
}

(也会合并以字段名为键的根级 ui 映射——这是 rjsf 的 uiSchema 约定——但每个属性上的 ui 才是文档所述的形式;根级 ui 主要用于 ui:submitButtonOptions 等表单级选项。 对于机密输入,建议在属性上使用 "isSensitive": true——它会自动渲染为密码控件; 参见 fcode-json-schema。)

提交时,文件会在流程启动前上传到 Storage,参数则以 fcode.storage://… 引用的形式传入(允许多个文件时为数组)。去掉前缀即可下载:

const { context: { parameters } } = fcode;
const stream = await fcode.storage.download(
  parameters.inputFile.replace("fcode.storage://", "")
);

上传的文件会计入存储限制(1,000 个文件,每个 50 MB—— fcode-core-concepts › 计划与限制);如果文件仅需临时使用,请在流程结束时删除。

高级

有关样式和两种主题(包括在 Factorial 内的 React 应用中嵌入时使用的 f0 主题)、使用 f0 组件渲染 Markdown 消息(markdownComponents)、初始值/隐藏值、异步提交、自定义请求头、API 主机覆盖、变量替换、将当前值预渲染到安装/设置表单中、响应来自你自己页面的用户输入(React onChange 属性、fcode-forms-* DOM 事件)以及模态框渲染,请参阅 references/advanced.md。

评论

支持 Markdown