本文摘要
Ones 万能组件是 E10 表单组件,不是 EB 页面组件。它在表单设计器组件管理中创建,配置 4 项:单文件 HTML、运行 JS、调试、AI 生成(默认折叠)。运行态用 Shadow DOM 渲染,脚本只注入 ctx;ctx 统一封装 WeFormSDK 与 formStore,提供字段读写、只读/必填/隐藏、初始值、选项显示名映射、明细 rowId、字段变化/属性变化/保存前 hook。AI 面板用内置模板 + 需求 + 字段清单生成提示词,AI 返回的 HTML 粘回即可。V1 已实测渲染、字段读写、只读拦截、固定选项 valueKey 写值、明细写入、保存拦截、hook 去重;打印模板、整表单只读、真实移动端 H5、超大配置、多实例压力未完成。源码与示例在 GitHub:OnesZhang/e10-ones-ufc。

为需求而生,为通用立命,让组件变成任何你想要的样子~

🔗 源码与示例:https://github.com/OnesZhang/e10-ones-ufc

📌 先说清楚:Ones 万能组件是 E10 表单组件,不是 EB 页面组件。

它从表单设计器的组件管理创建,拖入的是表单设计器,不是 e-builder 页面设计器。因为它运行在表单页面里,所以能拿到表单上下文,才能读写当前表单的字段。

界面

配置界面:代码 / 运行 JS / 调试 / AI 高级(默认折叠)

效果示例:Observation Type 双卡片选择器

为什么做这个组件

做过泛微 E10 表单二开的人大概都遇到过这种需求:

  • 表单里要一张「Safe / Positive vs Unsafe / Negative」的双卡片选择器;
  • 要一个指标卡、时间轴、步骤面板、特殊的联动说明块;
  • 要一段完全自定义的 HTML 布局,但必须读写当前表单字段;
  • 每个需求都写一次 ecode、发一次版,周期长、复用低;
  • 直接写 EB 页面组件,又拿不到表单字段上下文,或者把表单页面搞得很重。

平台的标准表单组件很稳,但形态是固定的;自定义 HTML 的自由和表单字段的契约,一直是两件事。

Ones 万能组件想做的,是把这两件事合起来:管理员只配一个单文件 HTML,字段读写、只读、必填、初始值、明细、错误兜底由组件桥接层负责。

它长什么样

配置面板只有 4 项:

  1. 组件代码(单文件 HTML):可以写 <div>...</div><style>...</style><script>...</script>,也可以粘完整 HTML 文档;
  2. 运行 JS:关闭后只渲染 HTML/CSS,不执行脚本;
  3. 调试模式:显示脚本错误和字段解析提示;
  4. AI 生成(高级):默认折叠;展开后填需求 + 字段清单,一键生成并复制提示词。

AI 闭环:

写需求 → 生成并复制提示词 → 粘给任意 AI → AI 返回一个 HTML 代码块 → 粘回「组件代码」→ 保存 → 预览

提示词由组件内置模板生成,包含运行环境约束、字段规则、ctx API、data-ufc-*、输出格式和自检清单。AI 不需要知道 WeFormSDK、formStore 的差异。

核心设计:一个代码框 + 一层薄契约

1. 单文件 HTML,Shadow DOM 渲染

组件把 HTML 归一化后放进 Shadow DOM,CSS 天然隔离,不污染表单页面;脚本只执行一次,字段变化只更新绑定,不重跑整段代码。

  • 设计态:只静态渲染 HTML/CSS,不执行脚本;
  • 运行态:执行脚本,通过 ctx 读写字段;
  • 打印态:不执行脚本,保留静态骨架和绑定值;
  • 只读态:ctx.set 被拦截。

2. ctx 字段桥接

AI 代码只面向这一层:

ctx.get('字段') // 原值
ctx.text('字段') // 显示文本(人员/选项自动取名称)
ctx.set('字段', '值') // 写值
ctx.attr('字段') // { readOnly, required, hidden, editable }
ctx.on('字段', fn) // 字段值变化
ctx.onAttr('字段', fn) // 只读/必填/隐藏变化
ctx.onBeforeSave(fn) // 保存前校验
ctx.rows('明细表') // 明细行
ctx.request(...) // 平台统一请求

底层优先取 window.WeFormSDK.getWeFormInstance(),不可用时降级 props.formStore。AI 只写 ctx,兼容性由组件负责。

3. 声明式绑定 data-ufc-*

常见展示/输入不写 JS:

<span data-ufc-text="客户名称" data-ufc-empty="—"></span>
<input data-ufc-input="备注" />
<button data-ufc-if-edit="客户名称">同步</button>
<span data-ufc-if-required="客户名称">*</span>

字段值变化、属性变化、联动/钻取后,绑定自动刷新。

4. 字段状态与初始值

  • 只读:输入禁用、data-ufc-if-edit 隐藏、ctx.set 拒绝;
  • 必填:data-ufc-if-required 显示 *,ctx.onBeforeSave 兜底;
  • 隐藏:相关 UI 不渲染;
  • 关联/浏览/选项字段:用 text() 显示名称,空值时显示占位;
  • 初始值只从运行数据读取,不写死示例;等表单就绪后刷新,联动/钻取后自动更新。

5. 明细字段必须带 rowId

这是实测踩出来的坑:主表字段 mark 是 field{fieldId},明细字段是 field{fieldId}_{rowId}。明细字段不带 rowId 写值,会把所有行写成同一个值。

桥接层现在强制要求 rowId,缺失直接抛错;ctx.rows('明细表') 负责拿行数据。

6. 固定选项:显示名与写值

固定选项的 getFieldObj().specialObj[].name 可能是空的,显示名要从字段 options 按 selectionId/valueKey 映射。

写值也有坑:有些字段认 valueKey(比如 0/1/2),有些认 selectionId;写错时 store 变了但原生下拉不刷新。组件内部提示词和文档都要求:写值前用 ctx.obj / 字段配置确认可识别值,写入后检查原生控件是否显示。

一个真实示例:Observation Type

V1 仓库里带了一个示例 examples/observation-type.html:

  • 绑定字段:Observation Type,数据 key obstyp;
  • Positive (0) / Negative (1);
  • 点击整张卡片写入并同步勾选;
  • 只读/隐藏/打印态不响应;
  • 样式保持原稿:双列卡片、彩色头部、圆形图标、勾选状态、底部双语提示,无外边框;
  • 为了 Shadow DOM 只做了必要映射::root → :host,body → .ufc-root。

效果就是一张「Safe / Positive vs Unsafe / Negative」的选择卡片,选中项显示绿色/红色勾选,另一侧是灰色空心圆。

V1 实测结果

已在 E10 目标环境验证:

  • Shadow DOM 渲染、data-ufc-*;
  • 字段读写、只读拦截;
  • 固定选项显示名与 valueKey 写值;
  • 明细 ctx.rows、明细字段带 rowId 写入;
  • 缺 rowId 抛错;
  • 保存前校验阻断 + 正常保存放行;
  • 配置保存/重开;
  • hook 去重(保存/字段/属性/动作);
  • AI 提示词生成。

未完成:打印模板场景、整表单只读、真实移动端 H5、超大配置体积、多实例压力。

安全边界

组件会执行管理员配置的 JS,信任边界等同平台「页面源码 / 表单 ecode」。不要把它开放给不可信的低权限编辑者;正式发布前按你们的 ecode-security-review 流程确认直出/沙箱策略。

字段值、用户输入、接口返回永远不要拼进 HTML 字符串或 eval;用 textContent / ctx.text。

源码与版本

v1.0.0(2026-10-21):首个版本。


为需求而生,为通用立命,让组件变成任何你想要的样子~

—— Ones