构建与修改动态表单配置

SKILL效率

原名 pacp-build-form(动态表单构建器) · 标识 pacp-build-form

效率 未标注权限 许可证不明确
安装这个技能

读取表单规格文档或模板编号,组装表单配置并通过接口提交到远程平台,用于新建或修改已有动态表单。

一句话选型结论

按资料字段整理

构建与修改动态表单配置是效率类的 Agent Skill,安装方式未标注,权限级别未标注,许可证不明确,有可直接复制的安装命令。技能本质是给模型读的说明书,效果取决于模型与客户端;装完先用一个小任务验证,再放进正式流程。

分类效率 安装方式未标注 权限级别未标注权限 许可证许可证不明确
累计安装 未标注 本周 未标注
GitHub 星标 未标注 仓库 未标注
下载量 未标注 技能市场下载数
权限级别 未标注权限 安装前请确认风险范围
许可证 许可证不明确 未说明

安装

未标注
在支持 Skills 的客户端里执行,装完问一句「你有哪些技能」验证
skillhub install user_ee9b2ae1/pacp-build-form
  1. 1
    确认客户端支持

    确认你的客户端支持 Skills(Claude、Codex、Cursor 等),并升级到较新的版本。

  2. 2
    拿到技能文件

    执行上面的命令安装;也可以把仓库里的 SKILL.md 手动放进客户端的 skills 目录。

  3. 3
    刷新技能列表

    重新打开客户端,或刷新技能列表,让新放进来的技能被加载。

  4. 4
    验证是否生效

    问一句「你有哪些技能」,确认列表里出现它,再拿一个小任务试跑。

技能档案

效率
字段来自技能仓库与公开索引,中文说明由本站标注
作者 / 维护方user_ee9b2ae1仓库 未标注 · 版本 未标注
宿主客户端未标注技能可被这些客户端加载
安装方式未标注安装包 community
权限级别未标注权限安装前请确认风险范围
许可证未说明许可证不明确,商用前请自行确认条款
仓库可用性available技能仓库当前是否可访问
最近核验2026-11-06上游最后核验时间 · 数据更新 2026-11-06
正文体量80.8 KBSKILL.md 原文大小,越大说明说明书写得越细

能做什么

0 项能力
上游给出的能力概览与适用场景
按规格新建动态表单修改已有表单配置批量提交表单结构

典型使用场景

3 类
按分类整理的常见用法,具体以 SKILL.md 为准
整理待办与日程

把零散任务收进清单,排出优先级。

批量处理重复任务

把固定动作做成模板,重复执行。

把流程写成清单

把口头流程整理成可复用的步骤。

选择建议

装之前先看这几条
按条目字段给出的判断,不替你决定用不用

值得选的地方

  • 有可直接复制的安装命令,不用自己翻文档找入口。

先想清楚的地方

  • 没有标注权限级别,实际能做什么要装好后确认。
  • 许可证不明确,商用或二次分发前先到仓库确认条款。
  • 没有做站内核验,内容以作者仓库为准。
  • 技能只是说明书,实际效果取决于模型与客户端,装完先用小任务试一遍。

更适合

  • 需要在效率场景里给模型补一套做法的人。
  • 愿意先读一遍 SKILL.md、确认它会让模型做什么的人。

不太适合

  • 对许可证要求很严、必须明确可商用的人。
  • 只想装完就自动变强、不打算看说明书的人。

同类对比

6 个同类
同分类里挑最接近的几条,字段以各自详情页为准
技能权限级别安装方式累计安装宿主客户端安装命令
构建与修改动态表单配置当前 未标注权限 未标注 未标注 未标注 有
Nano PDF 文档 联网访问 ClawHub 市场 424.9 万 OpenClaw 有
自动更新程序 浏览器控制 ClawHub 市场 296.4 万 OpenClaw 有
Gemini 助手 联网访问 ClawHub 市场 254.7 万 OpenClaw 有
word DOCX 文档 联网访问 ClawHub 市场 239.6 万 OpenClaw 有
Excel XLSX 前端 联网访问 ClawHub 市场 227.0 万 OpenClaw 有

SKILL.md 全文

80.8 KB
模型实际读到的说明书原文,来自技能仓库

Build Form(动态表单构建器)

读取 form-spec.md(新增)或 templateId(修改),构建表单配置并提交平台。

> ⚠️ 本技能是远程配置操作,不是代码开发任务。 通过 HTTP API 在远程 PowerData 平台上创建/修改表单配置数据,与当前项目源码完全无关。

🔴 HARD-GATE:新增必须 spec,修改必须 templateId

> [!important] 硬约束,不可协商
> 【新增入口】唯一输入是 form-spec.md。禁止从用户口语/图片直接推断并构建表单。
> 用户没给 spec/业务名 → 停止,引导去 pacp-design-form。
> 【修改入口】唯一输入是 templateId + 修改意图。必须有 templateId 才能改。
> 两条入口都不接受"凭口语直接构建"。

反合理化表(AI 跳过 spec 的借口及反驳):

AI 内心独白(Red Flag) 反驳
"这表单就几个字段,直接生成了吧" 几个字段也要 spec——字段类型/必填/布局没确认,生成必跑偏
"用户描述很清楚了,我直接构建" 口语清晰 ≠ spec 完整。代码集编号?主键?布局范式?没确认就是赌
"我先建个草稿再让用户改" 草稿赌错方向,用户改的成本 > 先出 spec 对齐的成本
"spec 阶段太慢,用户等不及" spec 是一次性对齐,构建是落子无悔。慢在前面省在后面
"用户要加外部数据对象,信息不太全但我先配上" dataObjects 修改走门禁——缺数据来源(QUERYXH/数据源表)或缺取值方式就停,不靠推断硬配。配完无效比慢更糟

统一工作根目录:.pacp/

> [!important] 所有 pacp-* skill 共用 .pacp/ 作为工作根目录

定位方式:AI 运行时的 cwd(当前工作目录)下的 .pacp/。

> [!warning] cwd 边界
> "别探查项目结构"约束依然成立——AI 只往 .pacp/ 读写工作文件,不触碰项目内其他任何文件。

{cwd}/.pacp/
  specs/                    ← form-spec.md(pacp-design-form 产出,本 skill 读取)
    动态渲染/                 ← design 产出的隔离子文件夹(优先查找)
      {业务名}__表单.md         ← 新命名(带后缀,优先匹配)
      {业务名}.md              ← 旧命名(无后缀,兜底兼容)
    {业务名}__表单.md           ← 旧 spec 兜底路径(根目录,兼容更早的历史 spec)
    {业务名}.md
  session.json              ← 共享会话凭证(本 skill 启动时读取复用)
  active/                   ← 工作区(trace、构建脚本、payload)
    {业务名}_{时间戳}/

> [!important] session 跨 skill 共享
> 启动时先读 .pacp/session.json,有效则直接复用;过期(401)则重新调 pacp-system-login 并更新。session.json 是可选缓存优化。

入口分流 + 文件存在性闸门

启动第一件事——按入口类型分流:

启动分流:
1. 修改入口(用户给了 templateId + 修改意图)
   → 走修改旁路:getFormConfig → 推断改动并生成 diff → 用户确认 → 提交
   → 不走 spec 闸门

2. 新增入口(默认)
   → 文件存在性闸门:
   a. 用户给了业务名 → 按优先级查 spec 文件是否存在(命中即停,后者仅兜底):
      ① .pacp/specs/动态渲染/{业务名}__表单.md(新命名,design 产出,最优先)
      ② .pacp/specs/动态渲染/{业务名}.md(旧命名兜底,兼容迁移前的 spec)
      ③ .pacp/specs/{业务名}__表单.md(根目录兜底,兼容更早的历史 spec)
      ④ .pacp/specs/{业务名}.md(根目录旧命名兜底)
      ├─ 任一存在 → 轻量校验(三段存在)→ 有效则继续
      └─ 都不存在 → 停止,引导去 pacp-design-form
   b. 用户直接给了 spec 文件路径 → 读取,轻量校验
   c. 用户什么都没给 → 停止,提示:
      「需要先有规格文档。请用 pacp-design-form 生成。」

> [!note] 轻量校验
> 闸门只校验三段存在(元信息段存在、字段清单段存在且非空、布局段存在),不校验段内细节。段内细节由 design 的质量门保障。
> 用户手写 spec(未经 design 质量门)直接喂给 build 时,提示「未经 design 质量门校验,可能有缺项」但不拦截。

> [!note] spec 文件名兼容四级查找
> design 产出统一用 {业务名}__表单.md(与列表配置 {业务名}__列表.md 区分)。build 查 spec 时按 ①②③④ 四级优先级查找,覆盖新命名、旧命名、子文件夹、根目录四种组合,确保历史 spec 不失效。

> [!important] 修改模式为何跳过 spec
> spec 质量门是为“从无到有”的新增场景设计的。修改场景的输入是已有表单配置(结构化、事实基准),不需要绕一圈反推成自然语言 spec 再改回去。
>
> 例外:外部数据对象(dataObjects)修改走门禁。其他改动(字段、布局、events 脚本等)维持旁路不变,但涉及 dataobjectsmanagecontroller 系列 API 的修改(对应配置页「java外部数据对象」按钮)必须走下方「🔴 外部数据对象修改门禁」章节的四步强制流程——缺数据来源/取值方式就停,不靠推断硬配。
>
> 模糊改动意图的处理:收到模糊意图(如“重新排当事人信息区”)时,自己推断具体改动并生成 diff,直接展示给用户确认。猜错用户再纠正——一轮交互,效率优先,不反问澄清。

数据来源

  • 数据来源只有三个:① 远程 API(表结构、字段元数据、代码集)② 本 SKILL.md ③ 用户提供(spec 或 templateId)。本地代码库里没有任何你需要的东西。

操作模式

  • 新增:用户描述含"新增/添加/创建"等关键词
  • 修改:用户描述含"修改/编辑/更新"等关键词,需提供 templateId

重要约束:远程配置任务,不读任何源码(含当前项目)

> 本技能是在远程平台上创建/修改配置数据,不是在本地项目里开发代码。禁止读取任何源码——既包括本 Skill 的 lib/scripts,也包括当前项目(cwd)里的业务代码。

本 Skill 的 lib/、scripts/ 目录下的 .js 文件是运行时代码,由 execute_code 或 CLI node 命令通过 require() 加载执行。禁止使用 read_file、cat、search_files 等工具查看这些文件的内容——所有 API 签名、参数说明、示例代码都在本 SKILL.md 中,无需阅读源码实现。

当前项目的业务代码同样不要读:即使 cwd 是某个项目目录,也不要执行 ls / rg --files / module_report / read_symbol / ast_grep / lsp_* 去探查项目结构或代码——这些操作与本技能无关,只会白白消耗 token 和时间。本技能的全部输入来源是:远程 API + 本 SKILL.md + 用户描述。

行为约束

# 约束 说明
1 只用 CLI 命令操作 所有操作必须通过本 Skill 提供的 CLI 命令(form_creator.js)或 BwaiClient API 完成。禁止自行编写新脚本绕过 CLI。如果 CLI 报错,直接将错误信息报告给用户,不尝试自行修复或绕过
2 临时文件路径 所有生成的文件(JSON 配置、探查结果、临时脚本、临时数据)必须写入 .pacp/active/{业务名}_{时间戳}/ 工作区目录内。禁止在其他位置创建文件——包括子仓库根目录、当前工作目录、/tmp 等。CLI 的 --config 参数必须指向工作区内的文件路径
3 表名必须用户明确提供 自动建表(--no-table)时,用户必须给出明确的表名。AI 不得自行编造表名,只能提供建议(如根据业务语义推荐命名规则)
4 主动确认表名 如果用户没有明确提供表名,AI 必须主动向用户确认:是使用已有的表(需指定表名),还是创建新表(需给出新表名)。不得自行推测表名
5 公共代码集必须用户指定 涉及公共代码集(CODESET / commonCode)时,LYZ 必须由用户明确指定。禁止 AI 根据字段语义自行推断代码集编号——推断太慢且容易出错
6 --no-table 模式下 type 必填 使用 --no-table 时,elements 中每个字段的 type 必须显式指定(如 INPUT、DATE 等),无法从数据库自动推断。缺少 type 会导致创建失败
7 主动交互等待 需要用户确认或提供信息时,优先提供选项让用户选择,使用主动提问的方式让用户明确知道 AI 在等待输入。如果有可用的提问/交互工具(如 clarify、ask_user、human_input、request_input 等),应通过工具发起而非纯文本等待。适用场景:预览确认、代码集编号确认、表名确认等
8 缓存主动失效 CLI 会自动缓存表结构等查询结果(默认 7 天过期)。当 AI 执行了修改操作(新增/修改代码集、修改表结构等)后,应使用 --clear-cache --base-url <url> 清除对应缓存,确保后续查询拿到最新数据
9 子表必须有 PXH 列 使用已有表作为 GROUP 子表时,必须确认该表有 PXH(INT)列。平台加载子表数据时硬编码 ORDER BY PXH,缺少该列会导致编辑表单报错。--no-table 模式下平台自动创建,无需手动处理
10 ID_GENERATOR 必须指定系统编号ID 使用 ID_GENERATOR(ID 自动生成)组件时,seqNumber(系统编号ID)为必填项。系统编号有业务含义(前缀格式、流水号规则等),AI 不得自行编造,必须由用户提供或经用户明确确认。获取方式:(1) 查询已有编号 --list-seq-numbers,从返回的 SXBH 中选择;(2) 如果没有合适的,向用户确认编号名称(BHMC)、前缀格式(QZGS,如"PWXK-#YEAR#-")、流水号格式(LSHGS,如"000"=3位补零)后,通过 BwaiClient.createSeqRule() 创建,返回的 SXBH 填入 seqNumber
11 外部数据对象全量覆盖 使用 BwaiClient.saveDataObjects(templateId, objects) 保存外部数据对象时,后端按 FORMBH 全量覆盖——传入什么就保留什么,未传入的会被删除。修改已有配置前必须先调用 getDataObjects(templateId) 取回现有列表,在其基础上增删改后再整体回写。QUERYXH(数据查询接口序号)必须由用户提供或从查询配置中确认,AI 不得编造。涉及 dataObjects 的修改完整流程见「🔴 外部数据对象修改门禁」章节(先查事实基准 → 不符则停 → 写增量 spec 硬门槛:数据来源 + 取值方式 → 合并回写)

🔴 外部数据对象修改门禁

> [!important] dataObjects 修改的强制门禁(对应行为约束 #11 的完整流程)
> 修改模式默认走旁路,但涉及外部数据对象(dataObjects)的修改例外——必须走本章节的四步强制流程。这是为了避免实验已证实的真根因:编造 QUERYXH、配完无效(心智模型错位——dataObjects 只预加载到上下文,不改字段组件形态,要让字段可选需配套 DYNAMIC_DATA_SELECT)。

触发条件(二值判定)

判定规则:本次修改是否要调用 dataobjectsmanagecontroller 系列 API(savedataobjectsmanages / querydataobjectsmanages)。
- 是 → 触发门禁,走四步强制流程。
- 否 → 维持现状(现有修改旁路 getFormConfig→diff→确认→提交,不变)。

> 判定依据用 API 端点 + UI 入口(动态表单配置页面的“java外部数据对象”按钮)锚定,不用模糊术语,不靠 AI 判风险等级。可程序化。

四步强制流程

flowchart TD
    A[修改请求] --> B{要调 dataobjectsmanagecontroller<br/>系列 API?}
    B -->|否| C[走现有修改旁路<br/>getFormConfig→diff→确认]
    B -->|是| D[门禁触发]
    D --> E["【1】getDataObjects 查平台事实基准"]
    E --> F{"【2】现状与作者预期不符?"}
    F -->|是| G["🚫 停, 让作者确认<br/>不自动覆盖"]
    F -->|否| H["【3】写增量 spec 片段<br/>硬门槛: 数据来源 + 取值方式"]
    H --> I{"取值方式 = 配套选择器?"}
    I -->|是| J["🚫 边界拦截<br/>dataObjects 单独无效<br/>引导走 DYNAMIC_DATA_SELECT"]
    I -->|否, 默认值回填/脚本取用| K["【4】标注操作类型<br/>+ 合并事实基准"]
    K --> L[全量回写 saveDataObjects]

> spec 定位:spec 是“意图描述 + 变更记录”,平台 DYNAMICFORM_DATAOBJECTS 表才是事实基准。保存前必须用平台现状校验意图的前提是否成立——不符就停。

增量 spec 片段结构

写增量 spec 片段时用以下结构(只写本次改动的部分,不反推整表 spec):

### 外部数据对象:{语义名}
- 目标表单:{表单名}(templateId: {uuid})
- OBJECT_NAME:{英文键名}                    ← 可 AI 推断
- 中文名:{OBJECT_NAME_CN}                    ← 可 AI 推断
- 数据来源:【硬门槛,二选一必填】
  - [ ] 已有查询接口,QUERYXH = ____________
  - [ ] 无现成接口,需先基于数据源表 ____________ 建查询配置
- 运行参数(PARAMS):无 / ____________       ← 可 AI 推断
- ⭐ 取值方式:【硬门槛,必填,见下方取值方式边界规则】
  - [ ] 默认值回填:取首条记录字段 ___ 填入表单字段 ___
  - [ ] 脚本取用:在 onDataLoaded 里读取 dataObjects.{OBJECT_NAME}(说明:___)
  - [ ] 配套选择器:字段需改为 DYNAMIC_DATA_SELECT(→ 触发边界拦截)
- 操作类型:【全量覆盖语义下必填】
  - 新增第 N 条 / 修改第 N 条(原 OBJECT_NAME=___)/ 删除第 N 条(OBJECT_NAME=___)
- 影响字段:{field 列表}                        ← 可 AI 推断

硬门槛 vs 可推断的分档:

字段 定性 理由
数据来源(QUERYXH 或数据源表) 🔴 硬门槛 缺了就是实验里的卡点;约束 #11 明令不可编造
取值方式 🔴 硬门槛 防心智模型错位(配完无效)
OBJECT_NAME / 中文名 / PARAMS / 影响字段 🟢 可 AI 推断 命名/低风险,错了可改,不影响功能

取值方式边界规则

取值方式选项 性质 门禁行为
默认值回填 ✅ dataObjects 独立达成 边界内,放行,继续配
脚本取用(onDataLoaded) ✅ dataObjects 独立达成 边界内,放行,继续配
配套选择器 🚫 需求探测器 边界拦截:告知“你的真实需求是字段可选,需配套 DYNAMIC_DATA_SELECT,单独配 dataObjects 达不到目的。本门禁不配置 dataObjects(配了也无效),请先明确字段组件改动方案”

> 第三项的价值不是“让门禁去配选择器”,而是识别“光配 dataObjects 满足不了需求”的情况,阻止一次无效配置——正好守在实验真根因(“配完无效”)的关口。

系统编号(seqNumber)完整处理流程

> [!important] ID_GENERATOR 组件的 seqNumber 处理(优化5)
> 当 spec 字段表现形式为「自动生成」「ID 生成」等(映射到 ID_GENERATOR 组件)时,必须按以下流程处理 seqNumber。

快路径:spec 已给生成器标识则直接用

spec 文号字段的「生成器标识」行若有值(如 1784099117607013819904),跳过步骤1-3(查询/新建),直接把该值填入 seqNumber,进入步骤4回填。此路径用于用户已有编号生成器的场景,避免重复查询/误建。

  • spec 有「生成器标识」值 → 直接用,跳到步骤4
  • spec 无「生成器标识」→ 走下方步骤1-3(查询/新建)

步骤1:查询已有编号

node skills/pacp-build-form/scripts/form_creator.js \
  --base-url http://localhost:8080/basesystem \
  --session-id $SESSION_ID \
  --list-seq-numbers

返回 [{ SXBH, BHMC, QZGS, LSHGS, ... }]。SXBH 是系统编号ID(填入 seqNumber 的值)。

步骤2:判断是否需要新建

  • 已有编号中找到前缀格式符合业务需求的 → 直接用其 SXBH
  • 没有合适的 → 进入步骤3新建

步骤3:新建编号规则(需向用户确认参数)

> ⚠️ BHMC、QZGS、LSHGS 有业务含义,AI 不得编造,必须用户明确提供或确认。

向用户确认以下参数:

参数 含义 示例
BHMC 编号名称 "环保投诉编号"
QZGS 前缀格式(支持占位符) HDTS-#YEAR#-(#YEAR#/#MONTH#/#DATE#/#ORGID#等)
LSHGS 流水号格式 000=3位补零(001,002,...),0000=4位

确认后调用:

const rule = await client.createSeqRule({
  BHMC: '环保投诉编号',
  QZGS: 'HDTS-#YEAR#-',
  LSHGS: '000',        // 3位补零
  // HZGS: '',         // 后缀(可选)
  // ZXZ: 1,           // 流水号起始值(默认1)
  // ZDZ: 9999,        // 流水号上限(默认9999)
});
// 返回 rule.SXBH,填入字段的 seqNumber

步骤4:回填 + 回写 spec

  • seqNumber 填入字段配置
  • 新建的 SXBH + 编号规则(BHMC/QZGS/LSHGS)回写到 spec 对应字段的「说明」行(见阶段4 spec 回写)

代码结构

pacp-build-form/
  SKILL.md                       ← 本文档
  lib/
    bwai_client.js               ← BwaiClient 类(HTTP 封装、探查、表单 API)
  scripts/
    widgets/                    ← 自包含组件目录(每个组件独立文件,index.js 自动发现注册)
      index.js                  ← 自动发现+注册中心(inferType/resolve/create/listTypes)
      ├── 基础字段
      │   text.js               ← TEXT_ELEMENT 文本输入框
      │   number.js             ← NUMBER_ELEMENT 数字输入框
      │   date.js               ← DATE_ELEMENT 日期选择器
      │   textarea.js           ← TEXTAREA_ELEMENT 多行文本框
      │   select.js             ← SELECT_ELEMENT 下拉选择框(公共代码表)
      │   radio.js              ← RADIO_ELEMENT 单选框
      │   checkbox.js           ← CHECKBOX_ELEMENT 多选框
      │   codeset.js            ← CODESET_ELEMENT 代码集下拉
      │   password.js           ← PASSWORD_ELEMENT 加密文本
      │   id-generator.js       ← ID_GENERATOR_ELEMENT ID生成器
      │   ueditor.js            ← UEDITOR_ELEMENT 富文本编辑器
      ├── 附件/媒体
      │   attach.js             ← ATTACH_ELEMENT 附件上传
      │   simple-attach.js      ← SAMPLE_ATTACHMENT_UPLOAD 简易附件上传
      ├── 展示/布局
      │   title.js              ← TITLE_ELEMENT 标题
      │   description.js        ← DESCRIPTION_ELEMENT 描述文字
      │   customhtml.js         ← CUSTOM_HTML_ELEMENT 自定义HTML
      │   iframe.js             ← IFRAME_ELEMENT 内嵌页面
      │   button.js             ← BUTTON_ELEMENT 按钮
      ├── 业务选择器
      │   department.js         ← DEPARTMENT_ELEMENT 部门选择
      │   department-asyn.js    ← DEPARTMENT_ASYN_ELEMENT 部门选择(异步)
      │   person.js             ← PERSON_ELEMENT 用户选择
      │   regionselect.js       ← REGIONSELECT_ELEMENT 行政区划(四级)
      │   industry.js           ← INDUSTRY_ELEMENT 行业级联树
      │   industry-asyn.js      ← DIVISION_TREE_ASYN_ELEMENT 行业异步树
      │   industryselect.js     ← INDUSTRYSELECT_ELEMENT 行业四级级联
      │   treedataselect.js     ← TREE_DATA_SELECT_ELEMENT 树形数据选择
      │   dynamicdataselect.js  ← DYNAMIC_DATA_SELECT_ELEMENT 动态列表选择
      │   map-selector.js       ← LATITUDE_LONGITUDE_ELEMENT 地图经纬度
      └── 容器
          group.js              ← GROUP_ELEMENT 子表(一对多)
          table.js              ← TABLE_ELEMENT 表格布局
    form_builder.js              ← createFormConfig(client, opts)
  • lib/bwai_client.js — 底层 HTTP 客户端,提供会话管理、探查 API(getTables、getColumns、getPrimaryKey 等)及表单保存 API。
  • scripts/widgets/ — 自包含组件目录,每个组件一个独立文件,index.js 为自动发现+注册中心(提供 inferType/resolve/create/listTypes)。
  • scripts/form_builder.js — 表单编排器,createFormConfig(client, opts) 接收已连接的 client 和表单选项,完成组装、保存并返回 { templateId }。

API 速查

> 以下内容覆盖所有 agent 需要调用的函数签名、参数与返回值,无需再读源码。

require 引入

const { BwaiClient } = require('./lib/bwai_client');
const widgets = require('./scripts/widgets');
const { createFormConfig } = require('./scripts/form_builder');

BwaiClient 方法

方法签名 返回值 说明
new BwaiClient(baseUrl, sessionId?) 客户端实例 sessionId 可选,手动模式时传入
await client.getTables() [{ TABLE_NAME, TABLE_COMMENT }] 获取所有表
await client.getColumns(tableName) [{ COLUMN_NAME, COLUMN_COMMENT, DATA_TYPE }] 获取列信息
await client.getPrimaryKey(tableName) string (列名) 获取主键列名
await client.getCommonCodeClassList({ pageSize: 10000 }) { list: [{ DMJBH, DMJMC }] } 获取代码集分类
await client.getFormConfig(templateId) object 获取现有表单配置(修改模式)
await client.getDataObjects(templateId) { list, total } 查询表单的外部数据对象配置
await client.saveDataObjects(templateId, objects) object 保存外部数据对象配置(全量覆盖,详见 JSDoc)
await client.getElementProperties(type) object 获取元素属性定义
client.setTraceDir(dir) void 开启请求追踪,写入 api-trace.jsonl
client._writeSummary(obj) void 写入 trace-summary.jsonl
BwaiClient.createWorkspace(name) string (绝对路径) 创建隔离工作区目录(基于 .pacp/active/)
BwaiClient.uuid() string (UUID) 生成 UUID

makeFormElement(type, label, field, extraAttrs?)

extraAttrs 常用字段:{ required: '1', LYZ: 'XZCF_AJLY' }

运行 form_creator.js --list-types 查看所有可用组件类型、别名和配置示例。

返回值:

{
  attrbute: 'TEXT_ELEMENT',  // 映射后的组件类型
  id: 'uuid',
  attrbutevalues: {
    YSMC,      // 显示名 = label
    YSDYZD,    // 元素对应字段 = field
    FIELD,     // 绑定字段 = field
    dbcolum,   // 数据库列 = field
    label,     // 标签 = label
    // ...extraAttrs 展开
  }
}

createFormConfig(client, opts)

字段 类型 必填 默认值 说明
title string 是 - 表单标题 (BDBT)
templateName string 是 - 模板名称 (MBMC)
tableName string 是 - 关联表名 (GTB)
primaryKey string 否 自动获取 主键字段
elements array 是 - makeFormElement 返回值数组
czan string 否 'save,submit' 操作按钮(逗号分隔)
lx string 否 - 分类 ID
dataSource string 否 'jdbc/default' 数据源

返回值:{ templateId: string, warnings: string[], raw: object }

CLI 调用方式(推荐)

本 skill 提供命令行入口,无需编写 Node.js 脚本,通过 JSON 配置 + CLI 命令即可完成表单创建。

前置条件

本 skill 的所有 CLI 命令都需要 --session-id(即服务端的 JSESSIONID cookie)。

> [!important] 登录强制要求 chrome-devtools MCP(优化2)
> 获取 JSESSIONID 只接受两种方式(按优先级):
> 1. 用户直接提供 JSESSIONID → 直接使用,跳过登录
> 2. 通过 pacp-system-login skill(内部基于 chrome-devtools MCP)→ 自动获取
>
> ❌ 不接受其他方式(如 evaluate_script 读 cookie、手动猜测等)——不可靠,浪费时间。
>
> chrome-devtools MCP 不可用时:
> - 建议用户安装 chrome-devtools MCP
> - 或让用户直接提供 JSESSIONID(从浏览器开发者工具 Application > Cookies 复制)
> - 不要尝试其他方法浪费时间

# pacp-system-login skill 的核心流程(基于 chrome-devtools MCP):
# 1. 查 .pacp/session.json 缓存,有则校验有效性,有效直接复用
# 2. 无缓存或失效 → chrome-devtools 自动登录(fill 模拟键盘输入)
# 3. 取 JSESSIONID(主动 fetch 制造请求 → 读网络请求头 cookie)
# 4. 写入 .pacp/session.json 供下次复用

命令格式

# 查看可用组件类型
node skills/pacp-build-form/scripts/form_creator.js --list-types

# 查看可用系统编号(用于 ID_GENERATOR 的 seqNumber)
node skills/pacp-build-form/scripts/form_creator.js \
  --base-url http://localhost:8080/basesystem \
  --session-id $SESSION_ID \
  --list-seq-numbers

# 创建表单
node skills/pacp-build-form/scripts/form_creator.js \
  --base-url http://localhost:8080/basesystem \
  --session-id $SESSION_ID \
  --config form-config.json

# 仅探查表结构(不创建)— 2-step 模式

# Step 1: 获取列索引(只有列名+注释,精简)
node skills/pacp-build-form/scripts/form_creator.js \
  --base-url http://localhost:8080/basesystem \
  --session-id $SESSION_ID \
  --check-only \
  --config - <<'EOF'
{"tableName": "T_HJJC_WTJCGZDJ"}
EOF
# → 返回 78 列的 COLUMN_NAME + COLUMN_COMMENT + primaryKey

# Step 2(可选): 获取指定列的完整详情(DATA_TYPE、ZDLX 等)
# form_creator 内置类型推断,elements[].type 可省略,CLI 自动根据 DATA_TYPE 推断控件类型。
# 只有需要提前确认列类型时才用。
node skills/pacp-build-form/scripts/form_creator.js \
  --base-url http://localhost:8080/basesystem \
  --session-id $SESSION_ID \
  --check-only \
  --config - <<'EOF'
{"tableName": "T_HJJC_WTJCGZDJ", "columns": ["RWBH", "SJCDWMC", "JCLB", "JCYY", "NCYJCRQ", "TJR", "TJDW", "WCZT", "BZ"]}
EOF
# → 只返回 9 列的完整属性

参数说明

参数 必填 说明
--base-url 是 服务地址
--session-id 是 有效的 sessionId
--config 是 JSON 配置文件路径,传 "-" 从 stdin 读取
--check-only 否 仅探查表结构不创建
--no-table 否 无表模式:表不存在时由平台自动建表(需 modifyDBStructure=true),primaryKey 默认 XH
--list-seq-numbers 否 查询已有系统编号规则列表(用于 ID_GENERATOR 的 seqNumber)。需配合 --base-url + --session-id,不需要 --config
--list-types 否 列出所有可用组件类型(不需要 auth)
--describe <type> 否 查看指定组件类型的详细属性(不需要 auth)

JSON 配置格式

{
  &quot;title&quot;: &quot;污染源委托监测登记&quot;,
  &quot;templateName&quot;: &quot;污染源委托监测登记&quot;,
  &quot;tableName&quot;: &quot;T_HJJC_WTJCGZDJ&quot;,
  &quot;primaryKey&quot;: &quot;RWBH&quot;,
  &quot;czan&quot;: &quot;save,submit&quot;,
  &quot;elements&quot;: [
    { &quot;type&quot;: &quot;INPUT&quot;, &quot;label&quot;: &quot;任务编号&quot;, &quot;field&quot;: &quot;RWBH&quot;, &quot;required&quot;: &quot;1&quot; },
    { &quot;type&quot;: &quot;CODESET&quot;, &quot;label&quot;: &quot;监测类别&quot;, &quot;field&quot;: &quot;JCLB&quot;, &quot;LYZ&quot;: &quot;DMJ_XX&quot; },
    { &quot;type&quot;: &quot;DATE&quot;, &quot;label&quot;: &quot;拟采样监测日期&quot;, &quot;field&quot;: &quot;NCYJCRQ&quot; },
    { &quot;type&quot;: &quot;TEXTAREA&quot;, &quot;label&quot;: &quot;备注&quot;, &quot;field&quot;: &quot;BZ&quot; }
  ],
  &quot;events&quot;: {
    &quot;onBeforeSave&quot;: &quot;const m = this.getFormModel(); if (!m.value.RWBH) { this.ElMessage.warning(&#x27;任务编号不能为空&#x27;); return false; }&quot;
  }
}

字段说明:

  • title/templateName/tableName: 必填
  • primaryKey: 可选,空=自动获取
  • czan: 可选,默认 save,submit
  • elements[].type: --no-table 模式下必填,普通模式可选。普通模式下 CLI 根据 DATA_TYPE 自动推断:datetime→DATE, decimal/int→NUMBER, text/clob→TEXTAREA, 其他→INPUT。--no-table 模式无法查询数据库列类型,每个 element 必须显式指定 type,否则平台不知道该创建什么类型的字段。显式指定时运行 --list-types 查看所有可选值
  • elements[].required: string "0"|"1",是否必填(注意:必须是字符串,不是 boolean)
  • elements[].LYZ: CODESET 类型的代码集编号(后端存储字段)
  • elements[].disabled: boolean,是否禁用
  • primaryKey 为空时会自动调用 getPrimaryKey() 获取
  • events: 可选,表单生命周期事件脚本,key 为钩子名,value 为 JS 代码字符串
  • jsTools: 可选,工具函数代码,events 中通过 this.getContext().fnName() 调用。⚠️ 必须用 var fn = function(){}; return {fn: fn} 模式(平台运行时只注册 return 对象字面量,function fn(){} 声明会静默失效),详见 references/script-guide.md §2.6
  • businessJs/formDataSaveImpl/externalJs/externalCss: 可选,后端脚本字段

子表(GROUP 容器)

当表单需要一对多子表时,在 elements 数组中使用 GROUP 类型。子表关联一张物理表(KZZB),通过外键(WJZD)与主表关联。子表字段嵌套在 elements 中。

{
  &quot;title&quot;: &quot;执法案件表单&quot;,
  &quot;templateName&quot;: &quot;执法案件表单&quot;,
  &quot;tableName&quot;: &quot;T_ZF_AJ&quot;,
  &quot;primaryKey&quot;: &quot;XH&quot;,
  &quot;elements&quot;: [
    { &quot;type&quot;: &quot;INPUT&quot;, &quot;label&quot;: &quot;案件编号&quot;, &quot;field&quot;: &quot;AJBH&quot;, &quot;required&quot;: &quot;1&quot; },
    { &quot;type&quot;: &quot;INPUT&quot;, &quot;label&quot;: &quot;案件名称&quot;, &quot;field&quot;: &quot;AJMC&quot;, &quot;required&quot;: &quot;1&quot; },
    { &quot;type&quot;: &quot;DATE&quot;, &quot;label&quot;: &quot;立案日期&quot;, &quot;field&quot;: &quot;LARQ&quot; },
    {
      &quot;type&quot;: &quot;GROUP&quot;,
      &quot;label&quot;: &quot;检查记录&quot;,
      &quot;KZZB&quot;: &quot;T_ZF_JCJL&quot;,
      &quot;WJZD&quot;: &quot;AJBH&quot;,
      &quot;elements&quot;: [
        { &quot;type&quot;: &quot;DATE&quot;, &quot;label&quot;: &quot;检查日期&quot;, &quot;field&quot;: &quot;JCRQ&quot;, &quot;required&quot;: &quot;1&quot; },
        { &quot;type&quot;: &quot;INPUT&quot;, &quot;label&quot;: &quot;检查人员&quot;, &quot;field&quot;: &quot;JCRY&quot;, &quot;required&quot;: &quot;1&quot; },
        { &quot;type&quot;: &quot;TEXTAREA&quot;, &quot;label&quot;: &quot;存在问题&quot;, &quot;field&quot;: &quot;CZWT&quot; }
      ]
    }
  ]
}

子表字段说明:

  • type: 必须为 GROUP(或别名 DYNAGROUP、GROUP_ELEMENT)
  • label: 子表显示名称(必填)
  • KZZB: 子表物理表名(必填)
  • WJZD: 子表中用于关联主表的外键字段名(必填)。编辑时后端用主表主键值去子表的此字段匹配加载数据。例如主表主键为 XH,子表通过 XKZBH 字段存主表 XH 的值,则 WJZD 填 XKZBH。不填会导致编辑时子表数据无法加载
  • query_primary_key: 子表主键字段(默认 ID)
  • elements: 子表字段数组,每个元素与普通字段格式相同
  • 注意:GROUP 没有 field 属性(容器不绑定数据库列)

⚠️ 子表必须有 PXH 列(排序号)

平台加载子表数据时硬编码了 ORDER BY PXH(见 FormData.queryBean()),如果子表没有 PXH 列,编辑表单时会报错:SQL 排序字段 [PXH] 不存在。

  • 新建子表时(--no-table 模式):平台会自动创建列,无需手动处理
  • 使用已有表作为子表时:必须确认该表有 PXH(INT 类型)列,如果没有,需要先执行 ALTER TABLE &lt;表名&gt; ADD COLUMN PXH INT DEFAULT 0 COMMENT &#x27;排序号&#x27;

无表模式(--no-table)

当目标表在数据库中不存在时,使用 --no-table 参数。平台在保存表单配置时会自动创建表和字段(需系统配置 modifyDBStructure=true)。

node form_creator.js \
  --base-url http://localhost:8080/basesystem \
  --session-id $SESSION_ID \
  --no-table \
  --config - &lt;&lt;&#x27;EOF&#x27;
{
  &quot;title&quot;: &quot;新业务表单&quot;,
  &quot;templateName&quot;: &quot;新业务表单&quot;,
  &quot;tableName&quot;: &quot;T_NEW_BIZ&quot;,
  &quot;elements&quot;: [
    { &quot;type&quot;: &quot;INPUT&quot;, &quot;label&quot;: &quot;名称&quot;, &quot;field&quot;: &quot;MC&quot;, &quot;required&quot;: &quot;1&quot; },
    { &quot;type&quot;: &quot;DATE&quot;, &quot;label&quot;: &quot;日期&quot;, &quot;field&quot;: &quot;RQ&quot; }
  ]
}
EOF

无表模式下:

  • 跳过所有依赖数据库的校验(主键检测、列名校验、类型推断)
  • primaryKey 默认为 XH(VARCHAR(50)),可在配置中覆盖
  • ⚠️ elements 中的 type 是必填项:由于无法查询数据库列信息,CLI 无法自动推断组件类型,每个 element 必须显式指定 type(如 INPUT、DATE、NUMBER 等)。运行 --list-types 查看所有可用类型。未指定 type 的 element 将导致创建失败
  • 永远不要通过其他手段自行建表,完全依赖平台的 modifyDBStructure 能力

脚本能力

> 🔴 必读:5.x 平台知识权威源
>
> 涉及以下主题时,优先加载 pacp-dynamic-wiki skill 的 references(本 skill 自带的 script-guide.md 为兜底副本):
>
> - 表单脚本写法(生命周期钩子、formApi、formModel、字段级 onChange、jsTools 约束、常见 patterns、后端脚本字段)→ wiki 的 5x/form-script-guide.md
> - 字段控制 API(显隐/必填控制、form-level/field-level 双 API、联动语义)→ wiki 的 5x/form-script-guide.md §6
> - 表单组件目录(30+ 组件的 type+aliases+核心属性)→ wiki 的 5x/form-component-catalog.md
> - 平台 API 端点(save/getformconfig/getDataObjects/...)→ wiki 的 5x/platform-api-endpoints.md
> - 平台数据结构(Template JSON/MODELJSON/formModel/attrbutevalues)→ wiki 的 5x/platform-data-structures.md
> - 平台机制(GROUP 子表、无表模式、ID_GENERATOR 系统编号、必填校验门、dataObjects、DYNAMIC_DATA_SELECT)→ wiki 的 5x/platform-mechanisms.md
> - 平台陷阱(tempateId 拼写、_ELEMENT 后缀、getcloumninfo 小写、主键 XH)→ wiki 的 5x/platform-pitfalls.md
> - Preview 渲染规范(--preview 输出格式)→ wiki 的 5x/preview-mode-design.md
>
> ZCode 会根据 description 自动触发 wiki skill(progressive disclosure)。
> 如未自动触发,AI 应主动通过 Skill 工具调用 pacp-dynamic-wiki。
>
> 5.x 平台升级时,wiki skill 是单一同步点,本 skill 不再单独维护这些知识。

动态表单支持两种脚本机制:

机制 作用域 适用场景
表单级 events 整个表单的生命周期 保存校验、多字段联动、数据初始化
字段级 onChange 单个字段 字段值变化的独立响应

快速查询能力清单:

# 列出所有生命周期钩子
node form_creator.js --describe-script list

# 查询脚本中可用的 API(this 指向的 formApi)
node form_creator.js --describe-script api

# 查询后端脚本字段
node form_creator.js --describe-script backend

完整的脚本编写指南(生命周期时序、formApi 用法、字段读写、onChange 配置、常见模式示例):见 references/script-guide.md。涉及脚本编写时必须先加载该文档。

外部数据对象(dataObjects):表单需要在渲染时从查询接口预加载数据(字段默认值回填、下拉联动等),通过 BwaiClient.saveDataObjects/getDataObjects 配置,脚本中用 globalVars.custom_(&#x27;dataObjects.xxx&#x27;) 访问。完整机制(四字段含义、渲染流程、PARAMS 与 useFormContext 配合)见 references/script-guide.md 第 4 章。涉及外部数据对象时必须先加载该文档。

动态列表选择器(DYNAMIC_DATA_SELECT):点击弹窗选择数据并回填表单字段的特殊组件。涉及 dynamicId、query_primary_key、query_column、name_column、query_column_array(键名大写)、dtcxcs(注意是 dtcxcs 不是 dtcxps)、callback 等属性配置时,完整属性表、三层回填机制、选择器列表约束(LX=2 + confirmClick)、callback 用法和完整示例见 references/script-guide.md 第 5 章。涉及该组件时必须先加载该文档。

字段控制 API(显隐/必填):表单支持按字段名动态控制显隐和必填状态。涉及 setFieldVisible、setFieldRequired(form-level,在 events 中使用)和 setVisible、setRequired(field-level,在 onChange 中使用)时,双层级 API 签名、联动语义(隐则非必填、改必填后自动 clearValidate)、条件显隐典型模式(onDataLoaded + $watch)见 references/script-guide.md 第 6 章。涉及字段显隐/必填控制时必须先加载该文档。

输出格式(JSON 到 stdout)

成功:

{
  &quot;success&quot;: true,
  &quot;templateId&quot;: &quot;xxx&quot;,
  &quot;templateName&quot;: &quot;xxx&quot;,
  &quot;tableName&quot;: &quot;xxx&quot;,
  &quot;primaryKey&quot;: &quot;xxx&quot;,
  &quot;elementCount&quot;: 9,
  &quot;formUrl&quot;: &quot;http://...basesystem/app/index.html#/form-render/xxx&quot;,
  &quot;warnings&quot;: []
}

失败:

{ &quot;success&quot;: false, &quot;error&quot;: &quot;错误信息&quot; }

check-only 输出(2-step 模式):

Step 1(不带 config.columns)— 精简列索引:

{
  &quot;tableName&quot;: &quot;T_HJJC_WTJCGZDJ&quot;,
  &quot;primaryKey&quot;: &quot;RWBH&quot;,
  &quot;columns&quot;: [
    { &quot;COLUMN_NAME&quot;: &quot;RWBH&quot;, &quot;COLUMN_COMMENT&quot;: &quot;任务编号&quot; },
    { &quot;COLUMN_NAME&quot;: &quot;SJCDWMC&quot;, &quot;COLUMN_COMMENT&quot;: &quot;受监测单位名称&quot; }
  ],
  &quot;columnCount&quot;: 78
}

Step 2(带 config.columns 白名单)— 完整列详情:

{
  &quot;tableName&quot;: &quot;T_HJJC_WTJCGZDJ&quot;,
  &quot;primaryKey&quot;: &quot;RWBH&quot;,
  &quot;columns&quot;: [
    { &quot;COLUMN_NAME&quot;: &quot;RWBH&quot;, &quot;COLUMN_COMMENT&quot;: &quot;任务编号&quot;, &quot;DATA_TYPE&quot;: &quot;varchar&quot;, &quot;ZDLX&quot;: &quot;string&quot; },
    { &quot;COLUMN_NAME&quot;: &quot;NCYJCRQ&quot;, &quot;COLUMN_COMMENT&quot;: &quot;拟采样监测日期&quot;, &quot;DATA_TYPE&quot;: &quot;datetime&quot;, &quot;ZDLX&quot;: &quot;date&quot; }
  ],
  &quot;columnCount&quot;: 9
}

进度信息输出到 stderr,不污染 stdout 的 JSON。

处理流程:spec 驱动(新增)+ templateId 驱动(修改)

入口分流已在上方「入口分流 + 文件存在性闸门」定义。以下为两条入口的具体执行流程。

新增入口:spec 驱动

阶段0 文件存在性闸门 → 阶段1 登录+工作区 → 阶段2 [按需]探查列/代码集 → 阶段3 构建 JSON → 阶段4 提交+打开页面

阶段0 文件存在性闸门

见上方「入口分流 + 文件存在性闸门」。按四级优先级查找 spec 文件(.pacp/specs/动态渲染/{业务名}__表单.md 优先),轻量校验三段存在。

阶段1 登录前置 + 工作区初始化

> 🔴 登录是第一件事:先读 .pacp/session.json 复用;无缓存或过期则通过 pacp-system-login skill 取 JSESSIONID。用户提供账号密码时,浏览器用 chrome_devtools_fill 模拟键盘输入(禁止 evaluate_script 设 value,会绕过前端加密)。

登录完成后,从 spec 元信息提取业务名,在 .pacp/active/ 下创建工作区子目录:

const workspace = path.join(process.cwd(), &#x27;.pacp&#x27;, &#x27;active&#x27;, businessName + &#x27;_&#x27; + timestamp);
fs.mkdirSync(workspace, { recursive: true });

创建 BwaiClient 实例后,立即开启请求追踪:

const { BwaiClient } = require(&#x27;./lib/bwai_client&#x27;);
const client = new BwaiClient(BASE_URL, SESSION_ID);
client.setTraceDir(workspace);

阶段2 [按需] 探查列/代码集

> 按需执行:仅当 spec 字段需核对列细节、或代码集字段标了「待定」时才执行。

使用 lib/bwai_client.js 的 BwaiClient 类探查:

const columns = await client.getColumns(spec.关联表);
// 排除系统字段 → 选择业务字段 → 核对/补全 spec 的表现形式与必填

> [!important] 阶段2 探查时遇到"代码值:待定"的处理
> spec 里标"待定"的代码集字段,停下来问用户要 LYZ 编号(保持"LYZ 必须用户给,AI 不猜"铁律)。
> - 用户给了编号 → 核对编号存在性(可查 ggdmz/list)→ 回填 spec → 继续
> - 用户给不出 → 字段先按普通文本输入构建,构建后提醒用户自行配置代码集

代码集编号核验——spec 里代码集字段若标了「待定」或用户给了编号,核对编号是否存在 + 选项是否与字段语义匹配(查 ggdmz/list,详见「代码集查询」章节);不符告知用户定夺,相符则回填 spec 的代码值。

将列信息和代码集核验结果保存到工作区 table-info.md。

阶段3 构建 JSON

> [!important] 前置步骤:spec→config 逐字段对照(优化4)
> 构建 JSON 前,必须先产出一份字段对照表,写在工作区 .pacp/active/{任务}/field-mapping.md:
>
> | spec 字段 | 表现形式 | →组件 | 布局语义 | →XSGS | 必填 | 代码集 | 备注 |
> |----------|---------|-------|---------|-------|------|--------|------|
> | AJBH 案件编号 | 单行文本 | INPUT | 半行 | 2 | 是 | - | 主键 |
> | CLZT 处理状态 | 下拉选择 | CODESET | 半行 | 2 | 否 | XZCF_CLZT | - |
>
> 强制要求:
> - 每个 spec 字段必须出现在对照表中(不允许遗漏)
> - XSGS 值必须显式写出,标注来源(spec 布局段对应行),不允许使用默认值
> - 自定义组件(spec 标⚠️)必须标注"查风格包"并写出最终映射结果
>
> 目的:构建过程可见、可审计。产物布局不对时,对照表能快速定位是 spec 写错还是 build 映射错。

> 指定风格时,先读对应风格包(stylepacks/{风格名}.md),严格按风格约定构建(组件用法 / 布局 / 样式 / 数据类型)。
>
> ⚠️ 复杂表单(TABLE_JSON 网格、签名组件等)超出 form_creator 能力时,用 BwaiClient 编程直接调 save 接口(见末尾「复杂表单编程构建」)。

基于 spec 构建的核心:表现形式词汇 → _ELEMENT 组件映射。

> [!important] 表现形式词汇 → 组件映射(spec 驱动构建的核心)
> spec 的「表现形式」用词汇描述,构建时映射到平台组件。映射优先级:
> 1. 标准词汇 → 内置映射表(单行文本→INPUT、下拉选择→SELECT、多选→CHECKBOX、单选→RADIO、日期→DATE...)
> 2. 自定义组件(spec 标⚠️) → 查 stylepack 风格包找映射(如签名→XZCF-QM-TABLE-ELEMENT)
> 3. 都映射不到 → AI 基于语义自行决定(兜底,不卡住)
>
> [!important] 选择类组件优先用标准组件(SELECT/RADIO/CHECKBOX)
> 「下拉选择」「单选」「多选」默认映射到 SELECT / RADIO / CHECKBOX(标准组件)。这三个标准组件同样支持通过 LYZ 加载公共代码表选项。
>
> CODESET 不作为默认选择——公共代码值组件目前尚不稳定,仅在 spec 显式标注「公共代码值组件」时才使用。默认一律走标准组件 + LYZ。

类word 文书表单:基于模板流构建(有模板时优先)

> [!important] 模板流构建 vs 逐字段拼接
> spec 布局段含模板时,必须基于模板文本流构建,不逐字段拼接。
>
> 构建步骤:
> 1. 取模板文本流(spec 布局段的模板内容)
> 2. 按字段清单语义匹配每个 ___,替换为 ${{&quot;formKey&quot;:&quot;字段名&quot;,&quot;type&quot;:&quot;text|date&quot;,&quot;value&quot;:&quot;...&quot;}}
> 3. 同行多个 ___ 保持在一行内,不拆行
> 4. 模板末尾签名区整体替换为签名组件(XZCF-QM-TABLE-ELEMENT),不逐个 ___
> 5. 按语义区块切分 EDITABLE-TEXT-ELEMENT(见下方切分规则)
> 6. 用 CUSTOM_HTML 承载样式骨架(公共 CSS + 网格线 + 标题 HTML)

> [!important] EDITABLE-TEXT-ELEMENT 切分单位 = 语义区块
> 切分单位是语义区块,不是单个字段,也不是整个文档。
>
> 规则:
> 1. 按语义区块切分——基本信息区、权利告知区、现场情况区、确认意见区各自一个 EDITABLE-TEXT-ELEMENT
> 2. 同一区块内多段落 → 一个元素,段落间用换行(&lt;br&gt;)分隔
> 3. 多段落指向同一个 formKey 时 → 必须合并到一个元素(一个 formKey 不能跨多个元素)

标准表单构建

用 makeFormElement() 构建元素,createFormConfig() 一步完成组装与保存:

const elements = [
  makeFormElement(&#x27;INPUT&#x27;, &#x27;编号&#x27;, &#x27;BH&#x27;, { required: &#x27;1&#x27; }),
  makeFormElement(&#x27;INPUT&#x27;, &#x27;名称&#x27;, &#x27;CSMC&#x27;, { required: &#x27;1&#x27; }),
  makeFormElement(&#x27;DATE&#x27;, &#x27;创建时间&#x27;, &#x27;CJSJ&#x27;),
];

const result = await createFormConfig(client, {
  title: &#x27;系统参数配置&#x27;,
  templateName: &#x27;SYS_PARAM_CONFIG_FORM&#x27;,
  tableName: &#x27;SYS_PARAM_CONFIG&#x27;,
  elements,
});
// result.templateId

> [!important] 占位符括号 lint:含 EDITABLE-TEXT 的表单提交前必检(优化7)
> 构建 JSON 完成后、提交平台前,扫描所有 EDITABLE-TEXT-ELEMENT 的 html_content。${{...}} 占位符的字面双花括号是书写约定,正确拼接只有 $ + 一个 {(JSON 开始);错误的三层括号 ${{{ 是 $ 后紧跟两个 {。
>
> 若 html_content 出现 ${{($ 后两个左花括号紧挨),直接报错停止提交——这是占位符拼接多写了一层括号,前端正则 /\$\{({.*?})\}/g 匹配不到,会以原始代码文本渲染。
>
> javascript &gt; // lint 伪代码:扫描所有 EDITABLE-TEXT 元素 &gt; for (const el of elements) { &gt; const a = el.attrbutevalues || el; &gt; if ((el.attrbute || a.type) === &#x27;EDITABLE-TEXT-ELEMENT&#x27; &amp;&amp; a.html_content &amp;&amp; /\$\{\{/.test(a.html_content)) { &gt; throw new Error(`占位符三层括号错误:元素 ${el.id || a.YSMC} 的 html_content 含 \${{,应用 PH() 函数拼接(&#x27;${&#x27; + JSON.stringify(opts) + &#x27;}&#x27;)`); &gt; } &gt; } &gt;
>
> 正则 /\$\{\{/ 原理:正确占位符 ${&quot;name&quot;:...} 在 $ 后只有一个 {,不命中;错误的 ${{ 有两个 {,命中即错。正确拼接用风格包的 PH() 工具函数:PH(opts) 返回 &#x27;${&#x27; + JSON.stringify(opts) + &#x27;}&#x27;。

> [!important] 必填属性校验门:提交 save 前必检(优化7·校验门)
> 与占位符 lint 并列,构建 JSON 完成后、提交 save 前,自动校验每个元素的必填属性是否非空。本机制已内嵌在 createFormConfig(form_builder.js),CLI/编程调用都自动生效,无需手动调用。
>
> 解决的问题:表单构建时若漏填某些必填属性(如 CUSTOM_HTML 的 YSMC、文本字段的 ZDCD),渲染正常但进设计器二次编辑时报错——平台在编辑时才校验完整性。校验门把这个拦截点前移到保存前。
>
> 机制:通过平台元数据接口获取每个元素类型的必填属性集,逐元素核对。
>
> 数据源:平台「元数据管理 → 关联属性」(#/admin/metadata-list)。接口 listYsSxgxdy(YSDYLX) 返回该类型的属性池,其中 XH非空 &amp;&amp; ISMUST===&#x27;1&#x27; 的属性即为该类型的必填属性集。
>
> &gt; 校验流程(内嵌在 createFormConfig,saveForm 前): &gt; 1. 扁平化收集所有元素(含 GROUP 子表子元素) &gt; 2. 归一化元素类型码(别名→标准码,如 INPUT→TEXT_ELEMENT) &gt; 3. 批量拉取各类型必填属性集(走 URL 隔离缓存,见下方) &gt; 4. 逐元素逐属性核对:必填属性在 attrbutevalues 里是否非空 &gt; 5. 有缺失 → 抛错停止提交,错误报告精确到「元素X缺属性Y」 &gt;
>
> 缓存:元数据按 baseUrl 哈希隔离缓存到 .pacp/metadata-cache/{hash}.json,TTL 7 天。多系统互不干扰。用户在元数据管理页改了配置后,删除该缓存文件即可强制刷新(或调 client.getMustProps(types, {refresh:true}))。
>
> 缺失处理(AI 收到校验报错后):
> 校验门纯拦截不兜底。拦截后 AI 按 3.3 节流程分类补全:
> - A 类(spec 该给但漏了,如 ZDCD):回 spec 补声明(单行文本 100 / 多行文本 2000),重新构建
> - B 类(构建层职责,如骨架元素 YSMC):构建 JSON 层补——优先从 html_content 提取可见文本,提取不到用「区块骨架{N}」
> - 语义不明/规则覆盖不了:问用户
>
> 降级:元数据接口拉取失败时,校验门降级为「放行 + 告警」(校验是增强,不是硬依赖),不阻塞构建。
>
> 相关文件:
> - scripts/must_prop_check.js — 校验逻辑(checkMustProps + normalizeType)
> - lib/bwai_client.js — listYsSxgxdy / getMustProps(含缓存)

阶段4 提交 + 打开页面供用户确认

提交成功后,打开表单页面让用户自行查看:

  1. 打开表单页(两步导航,见下方刷新策略):先导航到中转页,再进 form-render/{templateId}。若无浏览器工具,把 URL 给用户自行打开。
  2. 告知用户:「表单已提交并在浏览器打开,请查看渲染是否正常。有问题告诉我,没问题即可。」
  3. 等用户反馈:不主动做快照验证、不自动修复。

> [!important] 浏览器预览刷新策略(Vue Router hash 模式,优化6)
> 平台前端用 Vue Router hash 模式。直接从 form-render/A 改 URL 到 form-render/B,Vue Router 检测到"同组件 param 变化",不触发组件重载——页面显示的还是旧表单。
>
> 正确操作(两步导航):
> 1. 先导航到中转页(任何非 form-render 的页面)
> 2. 再导航到目标表单页 #/form-render/{templateId}
> 3. 两步之间无需等待,Vue Router 因路由组件切换而强制重新挂载
>
> javascript &gt; // 伪代码 &gt; navigate_page(baseUrl + &#x27;#/penalty/case/todo&#x27;) // 第1步:中转 &gt; navigate_page(baseUrl + &#x27;#/form-render/&#x27; + templateId) // 第2步:目标 &gt;

交付提醒:主动告知用户本次未确定的配置(代码集选项内容、特殊组件配置、布局微调、字段顺序),提示在配置页自行调整或下一轮迭代。

> [!important] spec 回写(闭环,优化1)
> 提交成功后,向 spec 回写事实信息(不回写组件/XSGS 等实现细节,保持 spec 纯需求层):
>
> 回写内容:
> | 回写项 | 写在哪 |
> |--------|--------|
> | templateId + formUrl(访问地址)+ 构建时间 | spec 末尾新增 ## 构建记录 段 |
> | 代码集编号(spec 标「待定」→ build 问到后填入) | 字段清单对应字段的「代码值」行(待定 → 实际编号) |
> | 系统编号(build 新建的 seqNumber/SXBH) | 字段清单对应字段的「说明」行补充 |
>
> 示例——spec 末尾新增:
> markdown &gt; ## 构建记录 &gt; - templateId:1b8e72e6-11d3-4ad2-bcd3-a4c623c00d89 &gt; - 访问地址:http://localhost:8080/basesystem/app/index.html#/form-render/1b8e72e6... &gt; - 构建时间:2026-07-15T15:30:00 &gt;
>
> 不回写的:组件映射(CODESET/INPUT 等)、XSGS 值、布局微调——这些是实现细节,回写会污染 spec 的纯需求语义。

修改入口:templateId 驱动

getFormConfig(templateId) → 推断改动并生成 diff → 用户确认 → 提交

详见下方「修改模式」章节。

数据结构

Template JSON(保存格式)

{
  ForeignKey: &#x27;&#x27;,
  ISPRINT: &#x27;&#x27;,
  attrbute: {
    BDBT: &#x27;表单标题&#x27;,         // 表单标题
    LX: &#x27;TABLE_ELEMENT&#x27;,      // 类型
    MBMC: &#x27;模板名称&#x27;,          // 模板名称
    PRIMARYKEY: &#x27;BH&#x27;,          // 主键字段
    dataSource: &#x27;jdbc/default&#x27;,
    GTB: &#x27;TABLE_NAME&#x27;,         // 关联表名
    CZAN: &#x27;save,submit&#x27;,       // 操作按钮(逗号分隔)
    elementchild: [
      { BUSINESSJS: &#x27;&#x27; },       // 业务 JS
      { FORMDATASAVEIMPL: &#x27;&#x27; }, // 保存实现
      { EXTERNALJS: &#x27;&#x27; },       // 外部 JS
      { EXTERNALCSS: &#x27;&#x27; },      // 外部 CSS
    ],
    events: &#x27;{}&#x27;,              // 事件配置 JSON
    bdysl: &#x27;&#x27;,                 // 表单元素数量
    jsTools: &#x27;&#x27;,               // JS 工具代码
  },
  elements: [
    {
      attrbute: &#x27;INPUT&#x27;,       // 组件类型
      id: &#x27;uuid&#x27;,              // 元素唯一 ID
      attrbutevalues: {
        YSMC: &#x27;字段名&#x27;,        // 显示名
        FIELD: &#x27;FIELD_NAME&#x27;,   // 绑定字段
        disabled: false,
        required: &#x27;0&#x27;,
        elementchild: [],
        // ... 其他属性根据组件类型
      },
    },
  ],
  tempateId: &#x27;uuid&#x27;,  // 注意:后端拼写是 tempateId(非 templateId)
}

⚠️ 关键注意事项:

  • tempateId 必须是有效的 UUID,空字符串会导致 120340 错误
  • elementsSize 必须与 elements.length 一致
  • attrbutevalues 中属性是直接键值对,不是 {value: ...} 嵌套格式

布局规范

动态表单使用 12 列 CSS Grid 布局,通过元素配置中的 XSGS 属性控制每个字段的显示占比。

XSGS 含义 视觉效果 适用场景
&#x27;4&#x27; 一行 4 个 每字段占 1/4 宽 较窄字段(编号、类别等)
&#x27;3&#x27; 一行 3 个 每字段占 1/3 宽 中等宽度字段
&#x27;2&#x27; 一行 2 个 每字段占 1/2 宽 普通输入、日期、下拉选择等(默认)
&#x27;1&#x27; 或空 独占整行 每字段占整行 标题、富文本、表格布局、附件等

> [!important] spec 布局语义 → XSGS 映射(优化3)
> spec 布局段用语义描述式(逐字段声明),build 必须按 spec 的布局语义映射 XSGS,不允许使用默认值:
>
> | spec 布局语义 | → XSGS |
> |-------------|--------|
> | 独占行 | 1 |
> | 半行(一行2个) | 2 |
> | 三分之一行(一行3个) | 3 |
> | 四分之一行(一行4个) | 4 |
>
> 逐字段对照表(阶段3 前置步骤)中,每个字段的 XSGS 必须标注来源(spec 布局段对应行)。

默认布局建议(仅 spec 未声明布局语义时使用):

  • 标题组件(TITLE):使用 XSGS: &#x27;1&#x27;(独占一行),用于区分表单的不同业务区域
  • 普通字段(INPUT / NUMBER / DATE / SELECT / RADIO / CHECKBOX / PASSWORD 等):默认 XSGS: &#x27;2&#x27;(一行2个字段)
  • 占位较大的字段(UEDITOR / ATTACH / SIMPLEATTACH / MAPSELECTOR / REGIONSELECT / INDUSTRY 等):默认 XSGS: &#x27;2&#x27;(一行2个字段)
  • 容器组件(GROUP / TABLE / IFRAME / CUSTOMHTML):默认 XSGS: &#x27;1&#x27;(独占一行)

用户可能有个性化布局要求时,按用户要求调整 XSGS 值。

注意:widget 工厂方法已内置默认 XSGS 值(默认 &#x27;2&#x27;),AI 生成表单时无需显式指定 XSGS,除非用户有特殊布局要求。

> [!warning] 编程构建时属性名必须小写
> 上述 XSGS 大写写法仅适用于 form_creator CLI 的 JSON 配置(CLI 内部会转换)。编程构建(BwaiClient 直接调 save)时,EMPTY() 工厂里属性全小写(xsgs),组件工厂 Object.assign 覆盖时必须用小写 xsgs:&#x27;1&#x27;。混用大小写会导致主键冲突(见陷阱 9)。

配置示例:

{
  &quot;elements&quot;: [
    { &quot;type&quot;: &quot;TITLE&quot;, &quot;label&quot;: &quot;基本信息&quot; },
    { &quot;type&quot;: &quot;INPUT&quot;, &quot;label&quot;: &quot;姓名&quot;, &quot;field&quot;: &quot;XM&quot;, &quot;XSGS&quot;: &quot;2&quot; },
    { &quot;type&quot;: &quot;DATE&quot;, &quot;label&quot;: &quot;日期&quot;, &quot;field&quot;: &quot;RQ&quot;, &quot;XSGS&quot;: &quot;2&quot; },
    { &quot;type&quot;: &quot;SELECT&quot;, &quot;label&quot;: &quot;类型&quot;, &quot;field&quot;: &quot;LX&quot;, &quot;LYZ&quot;: &quot;BDLX&quot;, &quot;XSGS&quot;: &quot;2&quot; },
    { &quot;type&quot;: &quot;NUMBER&quot;, &quot;label&quot;: &quot;数量&quot;, &quot;field&quot;: &quot;SL&quot;, &quot;XSGS&quot;: &quot;2&quot; },
    { &quot;type&quot;: &quot;TITLE&quot;, &quot;label&quot;: &quot;详细信息&quot; },
    { &quot;type&quot;: &quot;UEDITOR&quot;, &quot;label&quot;: &quot;描述&quot;, &quot;field&quot;: &quot;MS&quot;, &quot;XSGS&quot;: &quot;1&quot; },
    { &quot;type&quot;: &quot;ATTACH&quot;, &quot;label&quot;: &quot;附件&quot;, &quot;field&quot;: &quot;FJ&quot;, &quot;XSGS&quot;: &quot;1&quot; }
  ]
}

组件选择指南

根据字段特征选择合适的组件,不要无脑全部用 INPUT。

文本类:INPUT / TEXTAREA / UEDITOR / PASSWORD

场景 推荐组件 判断依据
短文本(姓名、编号、电话、地址等) INPUT 内容≤200字符,一行可显示完
长文本(备注、说明、描述、意见等) TEXTAREA 内容可能超200字符,需要多行输入
富文本(带格式的通知、公告、报告等) UEDITOR 需要加粗、列表、图片等富文本排版
密码、加密字段 PASSWORD 内容需要加密存储,输入时显示星号

常见错误:备注字段用 INPUT → 用户只能输一行,体验差。应判断:字段名含"备注/说明/描述/意见/详情/内容"且数据库字段类型是 text/clob → 用 TEXTAREA。

数字类:NUMBER / INPUT

场景 推荐组件 判断依据
数值型(金额、数量、年龄、比例等) NUMBER 需要数学运算、范围校验、步进控制
编号型(虽然由数字组成但不参与运算) INPUT 如手机号、身份证号、邮政编码等,不参与数学运算

日期类:DATE

场景 推荐组件 判断依据
日期/时间(出生日期、登记时间、截止日期等) DATE 字段名含"日期/时间/年月日"或数据库类型为 datetime/date/timestamp

选择类:CODESET / SELECT / RADIO / CHECKBOX

场景 推荐组件 判断依据
从平台代码集中选择(性别、状态、类型等) SELECT 标准下拉,通过 LYZ 加载公共代码表选项(默认首选)
单选且选项固定且少(≤5个) RADIO 直接在界面上展示所有选项,一目了然
多选 CHECKBOX 可以勾选多个值
下拉选择(选项较多或节省空间) SELECT 选项>5个,或表单空间紧凑时用下拉
公共代码值组件(spec 显式指定时) CODESET 公共代码值专用组件,当前不稳定,仅 spec 明确要求时使用

SELECT vs CODESET:默认用 SELECT(标准下拉,支持 LYZ 加载公共代码表,稳定)。CODESET 仅在 spec 显式标注「公共代码值组件」时使用——该组件当前尚不稳定。SELECT/RADIO/CHECKBOX 均支持 LYZ 绑定公共代码表。

文件类:ATTACH / SIMPLE_ATTACH

场景 推荐组件 判断依据
需要上传多个文件或图片(附件列表) ATTACH 支持多文件、分类管理
只需上传单个文件(简单场景) SIMPLE_ATTACH 轻量,一个文件就够了

业务选择器

场景 推荐组件
选择部门 DEPARTMENT
选择用户/人员 PERSON
选择行政区划(省市区街道) REGIONSELECT
选择行业分类 INDUSTRY / INDUSTRYSELECT
从动态数据列表中弹窗选择并回填 DYNAMIC_DATA_SELECT
树形结构选择(如组织树、分类树) TREE_DATA_SELECT
选择地图位置(经纬度) LATITUDE_LONGITUDE

组件类型

> [!warning] 组件 attrbute 一律带 _ELEMENT 后缀(坑1·最严重铁律)
> 下表「类型」列给的是逻辑名(CHECKBOX/RADIO/CODESET/SELECT...),但 save 时 attrbute 字段实际值必须带 _ELEMENT 后缀:CHECKBOX_ELEMENT/RADIO_ELEMENT/CODESET_ELEMENT/SELECT_ELEMENT(TEXT/DATE/TABLE 等同理)。
>
> ⚠️ 写成无后缀逻辑名:save 返回 status:000(成功),但 formconfig/{id} 读取时后端按 type 找组件处理器找不到 → NPE → 500「未知异常」。这是「save 成功却 500」的首要嫌疑。getElementProperties(type) 返回 [] 也说明该 type 不在注册表。
>
> form_creator CLI 会自动补后缀,但编程直接调 save 时必须手写完整 _ELEMENT。

> 运行 form_creator.js --list-types 查看所有可用组件的类型、别名和配置示例。
> 以下按分类列出平台支持的全部组件。

基础字段

类型 别名 说明 特有属性
INPUT TEXT 文本输入(兜底类型) MRZ, ZDCD, XSGS, DW
NUMBER 数字输入 min, max, step
DATE 日期/时间选择 dateType, dateFormat
TEXTAREA 多行文本 rows
CODESET SELECT, CODESET 代码集下拉(需 LYZ) LYZ, DMJKJLX
SELECT 下拉选择(公共代码表,需 LYZ) LYZ
RADIO 单选框(公共代码表,需 LYZ) LYZ
CHECKBOX 多选框(公共代码表,需 LYZ) LYZ
PASSWORD TEXT_ENCRYPTION 加密文本
ID_GENERATOR IDGEN ID 自动生成 seqNumber(必填,系统编号ID,通过 --list-seq-numbers 获取), generate, automatic

| UEDITOR | RICHTEXT, QUILL | 富文本编辑器 | |

附件/媒体

类型 别名 说明 特有属性
ATTACH ATTACHMENT 附件上传(多文件/图片) uploadFileBigClass
SIMPLE_ATTACH SIMPLE_ATTACHMENT 简易附件上传

展示/布局

类型 别名 说明 特有属性
TITLE 分区标题(非数据字段)
DESCRIPTION DESC 描述文字(非数据字段) html_content
CUSTOMHTML CUSTOM_HTML 自定义 HTML(非数据字段) html_content
EDITABLETEXT EDITABLE_TEXT, EDITABLE-TEXT 可编辑文本(文书正文,类word「正文+可填位」,${{...}} 占位嵌字段,详见「文书表单构建」) html_content, ALIGN
IFRAME 内嵌页面 iframe_content
BUTTON 按钮(非数据字段) onClick

业务选择器

类型 别名 说明 特有属性
DEPARTMENT 部门选择 name_column
DEPARTMENT_ASYN 部门选择(异步弹窗) name_column
PERSON PERSON_ASYN 用户选择 name_column
REGIONSELECT DIVISION_TREE_ASYNCHRONOUS 行政区划(四级:省/市/县/街道) province_column, city_column, area_column, town_column
INDUSTRY 行业级联树(同步)
DIVISION_TREE_ASYN 行业级联树(异步懒加载)
INDUSTRYSELECT 行业四级级联(四独立 select) province_column, city_column, area_column, town_column
TREE_DATA_SELECT TREESELECT, CASCADER 树形/级联选择器 dynamicId
DYNAMIC_DATA_SELECT DYNAMICSELECT 动态列表选择(弹窗回填) dynamicId, query_primary_key, query_column, name_column, query_column_array(键名大写), SFDX, dtcxcs(注意不是dtcxps), callback
LATITUDE_LONGITUDE MAP_SELECTOR 地图经纬度选择 longitude_column, latitude_column

容器

类型 别名 说明 特有属性
GROUP DYNAGROUP 子表容器(一对多),子元素在 elements 数组 KZZB(子表名), WJZD(外键), query_primary_key(子表主键)
TABLE 表格布局(行列容器),支持单元格合并 rows, cols

> 类型推断规则:datetime/date/timestamp → DATE,decimal/int/float → NUMBER,text/clob → TEXTAREA,其他 → INPUT。业务选择器和容器类不参与推断,必须显式指定 type。

操作按钮(CZAN)

逗号分隔:save, submit, delete, print, export

修改模式

  1. 用 client.getFormConfig(templateId) 获取现有配置
  2. 解析 MODELJSON 中的 elements
  3. 对比变更(diff 分析)
  4. 传入已有的 templateId

方式一:CLI --template-id 参数(推荐)

node form_creator.js \
  --base-url http://host:port/contextPath \
  --session-id $SESSION_ID \
  --template-id 1b8e72e6-11d3-4ad2-bcd3-a4c623c00d89 \
  --config form-config.json

方式二:JSON 配置中写 templateId

{
  &quot;templateId&quot;: &quot;1b8e72e6-11d3-4ad2-bcd3-a4c623c00d89&quot;,
  &quot;title&quot;: &quot;...&quot;,
  &quot;...&quot;: &quot;...&quot;
}

方式三:编程 API

const result = await createFormConfig(client, {
  templateId: existingTemplateId,  // 传入已有 templateId 进入更新模式
  // ... 同新增的参数
});

> 优先级:--template-id 命令行参数 > config.templateId JSON 字段 > 空(新建)

修改模式下的 MODELJSON 结构

通过 getFormConfig(templateId) 获取的 MODELJSON 是一个 JSON 字符串,解析后结构如下:

{
  templateName: &quot;表单名称&quot;,
  templateTable: &quot;T_MAIN_TABLE&quot;,
  primarykey: &quot;XH&quot;,
  BDBT: &quot;表单标题&quot;,
  dataSource: &quot;jdbc/default&quot;,
  events: { onBeforeSave: &quot;...&quot;, onDataLoaded: &quot;...&quot; },
  child: [                          // ← 扁平数组,包含所有字段和 GROUP
    {
      type: &quot;TEXT_ELEMENT&quot;,
      id: &quot;uuid-1&quot;,
      YSMC: &quot;名称&quot;, YSDYZD: &quot;MC&quot;, FIELD: &quot;MC&quot;,
      // ...40+ 属性
    },
    {
      type: &quot;GROUP_ELEMENT&quot;,
      id: &quot;uuid-2&quot;,
      YSMC: &quot;子表名称&quot;,
      KZZB: &quot;T_SUB_TABLE&quot;,          // 子表物理表名
      WJZD: &quot;MAIN_ID&quot;,              // 外键字段
      query_primary_key: &quot;XH&quot;,      // 子表主键
      child: [                      // ← 子字段在 child 中(注意:不是 elements)
        {
          type: &quot;TEXT_ELEMENT&quot;,
          id: &quot;uuid-3&quot;,
          YSMC: &quot;费用名称&quot;, YSDYZD: &quot;FYMC&quot;,
          // ...
        },
      ],
    },
  ],
}

MODELJSON ↔ save 请求的关键差异:

方面 MODELJSON(读取) save 请求(写入)
子表子字段位置 group.child[] groupElement.elements[]
属性容器 属性直接在节点上 属性在 attrbutevalues 对象中
子表子字段 key child elements(后端 FormDesignerService.saveListElements 第 805 行要求)

编程修改表单的正确方式:

// 1. 获取现有配置
const cfgResp = await client._post(&#x27;/api/v2/dynamicform/dynamicformcontroller/formconfig/&#x27; + templateId);
const model = JSON.parse(cfgResp.MODELJSON);  // 返回顶层 MODELJSON,非 data.MODELJSON

// 2. 修改 model(如修改某个字段属性、修改 events 等)
model.child.find(c =&gt; c.YSDYZD === &#x27;YYJE&#x27;).SFZD = &#x27;1&#x27;;

// 3. 用 createFormConfig 保存(form_builder.js 的 childToElement 自动处理 child→elements 转换)
const result = await createFormConfig(client, {
  templateId,
  title: model.BDBT || model.templateName,
  templateName: model.templateName,
  tableName: model.templateTable,
  primaryKey: model.primarykey,
  dataSource: model.dataSource || &#x27;jdbc/default&#x27;,
  elements: [],     // 空数组 → createFormConfig 从 model.child 重建 elements
  events: model.events,
});

> ⚠️ 已知问题:updateFormElement / _internalMutateForm 在有 GROUP 子表的表单上可能报 120340 错误。如果遇到,改用上面的 createFormConfig 方式绕过。

复杂表单编程构建(TABLE_JSON 网格 / 签名组件等)

文书表单构建(EDITABLE-TEXT-ELEMENT,类word 范式)

> [!important] 可编辑文本 = 文书范式核心
> EDITABLE-TEXT-ELEMENT 实现 word 风格「正文+可填位」:html_content 写文书正文,可编辑位用 ${{...}} 占位嵌字段绑定,渲染后变成正文里的输入框。用于决定书/笔录/告知书等文书表单。
>
> **完整 ${{...}}$ 语法(3种type + 属性)+ 属性模板 + 实现范式见风格包执法处罚-文书.md的「EDITABLE-TEXT-ELEMENT 详解」**。源码实证:XZCF_QD/src/components/dynamic-form/widget/field-widget/hooks/use-editable-text.js`。

`${{...}}$ 速查(html_content 里嵌可编辑位):

${{&quot;name&quot;:&quot;显示名&quot;,&quot;type&quot;:&quot;text|date|fixedText&quot;,&quot;formKey&quot;:&quot;数据库字段&quot;,&quot;value&quot;:&quot;默认值&quot;,&quot;readonly&quot;:bool}}
  • text 文本输入 / date 日期(含 dateType)/ fixedText 固定文本不可编辑
  • formKey 绑定数据库字段(必填);name 显示名;value 默认值;readonly 只读
  • 文书里固定法律文字用 fixedText 或纯文本写死,当事人填写位用 text/date

> [!note] 文书表单 = CUSTOM_HTML(样式骨架) + 多个 EDITABLE-TEXT-ELEMENT(正文段)。复杂文书直接调 save 接口,元素属性是抽平结构(type/YSMC/html_content/ALIGN 直接在元素上,非 attrbutevalues 嵌套)。实证源:表单 3a3efbac(不予行政处罚决定书,16 个 EDITABLE-TEXT)。

当表单含 TABLE_ELEMENT 网格布局、平台特有组件(如 XZCF-QM-TABLE-ELEMENT 签名)等超出 form_creator 能力时,绕过 createFormConfig,用 BwaiClient 编程直接调 save 接口。以下为实证进出的硬知识。

save 接口精确 payload

接口:POST /api/v2/dynamicform/dynamicformcontroller/save

const payload = {
  template: JSON.stringify({
    ForeignKey: &#x27;&#x27;, ISPRINT: &#x27;&#x27;,
    attrbute: {
      BDBT, DYNACODE:&#x27;&#x27;, FOREIGNKEY:&#x27;&#x27;, CZAN:&#x27;save&#x27;, GTB, ISPRINT:&#x27;&#x27;,
      LX:&#x27;1692778465032026730496&#x27;, MBMC, PRIMARYKEY, dataSource:&#x27;jdbc/default&#x27;,
      elementchild: [{BUSINESSJS:&#x27;&#x27;},{FORMDATASAVEIMPL:&#x27;&#x27;},{EXTERNALJS:&#x27;&#x27;},{EXTERNALCSS:&#x27;&#x27;}],
      events:&#x27;{}&#x27;, bdysl:String(elements.length), jsTools:&#x27;&#x27;
    },
    elements,
    tempateId            // ⚠️ 拼写:少一个 l
  }),
  isSave: &#x27;1&#x27;,
  elementsSize: String(elements.length)
};
const resp = await client._post(&#x27;/api/v2/dynamicform/dynamicformcontroller/save&#x27;, payload);
// 成功: {status:&#x27;000&#x27;, data:{NEWTEMPLATEID:&#x27;...&#x27;}}

elements 完整格式(区别于 form_creator 的简化 {type,label,field})

const elements = child.map(c =&gt; {
  const { type, id, ...rest } = c;
  return { attrbute: type, id, attrbutevalues: rest };
});

⚠️ 每个元素 attrbutevalues 必含 elementchild: [],否则报 JSONObject[&quot;elementchild&quot;] not found(120340)。

TABLE_JSON 网格结构

{ trList: [{ index: 0, tdList: [{
  colspan: &#x27;1&#x27;,            // ⚠️ 字符串,不是数字
  rowspan: &#x27;1&#x27;,            // ⚠️ 字符串
  index: 0,                // 数字
  element: &#x27;子元素id&#x27; 或 &#x27;NONELEMENT&#x27;,   // 被合并格用 NONELEMENT
  hide: null               // null 或 {sourceRow,sourceCol} 对象(被合并格)
}]}] }
  • 子元素平铺在顶层 elements,TABLE_ELEMENT 不嵌套子元素,TABLE_JSON 靠 element 字段引用 id
  • 一个单元格只能放一个组件(td.element 数组实际不生效)——标题与内容须分两个单元格
  • id 可用 14位时间戳+32hex(实证风格)或标准 UUID

各组件 attrbutevalues 要点(实证模板)

  • 通用必备:YSMC, label, dbcolum, YSDYZD, elementchild:[], rowspans:&#x27;1&#x27;,外加组件特有属性
  • TEXT_AREA 默认值:defaultvalue 单独不生效,须同时设 MRZ + value
  • CUSTOM_HTML 标题:ISSHOWLABEL:&#x27;true&#x27;, SFXS:&#x27;1&#x27;, isshow:&#x27;1&#x27;, html_content
  • CHECKBOX 多选分两列:CSS .container .el-checkbox-group{display:grid;grid-template-columns:1fr 1fr}

getFormConfig 返回结构(修正)

getFormConfig(templateId) 返回顶层对象,MODELJSON 在顶层(不是 data.MODELJSON):

const cfg = await client.getFormConfig(templateId);
// cfg = { TEMPLATEID, BTNS, MODELJSON(JSON字符串), LX, RELATIONS, EXTERNAL_FILE }
const model = JSON.parse(cfg.MODELJSON);   // 不是 cfg.data.MODELJSON

代码集查询(仅在必须核对选项时)

  • 分类:POST /api/v2/platform/system/commoncode/ggdmj/query(参数 pageNum/pageSize/searchValue)
  • 选项明细:POST /api/v2/platform/system/commoncode/ggdmz/query(参数含 DMJBH 过滤);旧接口 findcommoncodes 返回全量需客户端按 DMJBH 过滤
  • 明细字段:DMJBH(代码集编号) / DM(代码值,不是 DMZ) / DMMC(名称) / PXH(排序) / SFYX(有效)。取代码值用 c.DM(坑6)

> 原则:LYZ 编号必须用户给(AI 不猜),但用户提供后可核对选项匹配度,不符告知用户定夺(坑4)。

渲染验证缓存

Vue Router 同组件 param 变化(form-render/A → form-render/B)不触发重载。验证时须先导航到中转页(如 todo)再进入目标表单,强制重新挂载。

save 成功但 formconfig 500 诊断法(坑2/5)

save 返回 status:000 但 formconfig/{id} 持续 500 时,按以下顺序排查(切勿只轮询等缓存):

  1. 先排除缓存延迟:ensureFormCacheSynced(id) + 轮询 3-4 次(每次 2.5s)。某次恢复 → 纯缓存延迟(第一轮坑8)。
  2. 持续 500 = 配置缺陷:getTables 确认表已建(排除建表失败)。
  3. 二分定位组件:生成最小变体表单(全局标题 + 2行1列表格 + 单个被测组件),逐组件测试,一次跑多个变体瞬间锁定问题组件。
  4. 首要嫌疑:type 后缀:被锁定组件的 attrbute 是否漏 _ELEMENT?getElementProperties(type) 返回 [] 的类型(CHECKBOX/QM 等)最易踩此坑。

> 实战:CHECKBOX 漏后缀 → save 成功 → formconfig 500 → 轮询 45s 无果 → 二分 5 变体锁定 → 补后缀重跑秒过。开局用此法可省整个诊断循环。

构建脚本模板(EMPTY 工厂 + 组件工厂 + 程序化 TABLE_JSON)

// 1. 实证 40 字段基础集(内置 elementchild,杜绝 120340)
function EMPTY(){return{ysdymb:&#x27;&#x27;,yddysdymb:&#x27;&#x27;,pbdysdumb:&#x27;&#x27;,xsbl:&#x27;&#x27;,xsgs:&#x27;&#x27;,sjlx:&#x27;&#x27;,
  yssjjylx:&#x27;&#x27;,dmjkjlx:&#x27;&#x27;,isshow:&#x27;&#x27;,readonly:&#x27;&#x27;,defaultSearchCloum:&#x27;&#x27;,
  defaultAssociatedTable:&#x27;&#x27;,defaultAssociatedCloum:&#x27;&#x27;,must:&#x27;&#x27;,dbcolum:&#x27;&#x27;,dbtable:&#x27;&#x27;,
  explain:&#x27;&#x27;,maxlength:&#x27;&#x27;,history:&#x27;&#x27;,isjcr:&#x27;&#x27;,datasource:&#x27;&#x27;,sourcevalue:&#x27;&#x27;,
  isListCloum:&#x27;&#x27;,value:&#x27;&#x27;,dw:&#x27;&#x27;,defaultvalue:&#x27;&#x27;,bootstrap_col:&#x27;&#x27;,table_head_width:&#x27;&#x27;,
  YSZ:&#x27;&#x27;,XSZ:&#x27;&#x27;,rowspans:&#x27;1&#x27;,elementchild:[]}}

// 2. 组件工厂(attrbute 一律 _ELEMENT;网格字段 ISSHOWLABEL:false)
const CB=(l,f,lyz)=&gt;({attrbute:&#x27;CHECKBOX_ELEMENT&#x27;,id:uuid(),   // ← 后缀铁律
  attrbutevalues:Object.assign(EMPTY(),{YSMC:l,label:l,YSDYZD:f,dbcolum:f,FIELD:f,LYZ:lyz,ISSHOWLABEL:&#x27;false&#x27;,SFXS:&#x27;1&#x27;})});
const TA=(l,f,dv=&#x27;&#x27;)=&gt;({attrbute:&#x27;TEXT_AREA_ELEMENT&#x27;,id:uuid(),
  attrbutevalues:Object.assign(EMPTY(),{YSMC:l,label:l,YSDYZD:f,dbcolum:f,ISSHOWLABEL:&#x27;false&#x27;,defaultvalue:dv,MRZ:dv,value:dv})});
// 签名组件:顶层独立元素,不嵌网格,放 TABLE 之后
const mkQmTable=(f)=&gt;({attrbute:&#x27;XZCF-QM-TABLE-ELEMENT&#x27;,id:uuid(),
  attrbutevalues:Object.assign(EMPTY(),{&#x27;XZCF-QM-KD&#x27;:&#x27;180px&#x27;,xsgs:&#x27;1&#x27;,ISSHOWLABEL:&#x27;false&#x27;})});

// 3. 程序化 TABLE_JSON(逻辑单元格 → 网格 → tdList,绝不手写)
const cells=[{r:0,c:0,el:tbAjly,colspan:1,rowspan:2}, /* ...声明布局 */];
const grid=Array.from({length:ROWS},()=&gt;Array(COLS).fill(null));
for(const c of cells){for(let r=c.r;r&lt;c.r+c.rowspan;r++)for(let cc=c.c;cc&lt;c.c+c.colspan;cc++)
  grid[r][cc]={origin:c,merged:!(r===c.r&amp;&amp;cc===c.c)}};
const trList=grid.map((row,r)=&gt;({index:r,tdList:row.map((g,c)=&gt;
  g.merged?{colspan:&#x27;1&#x27;,rowspan:&#x27;1&#x27;,index:c,element:&#x27;NONELEMENT&#x27;,hide:{sourceRow:g.origin.r,sourceCol:g.origin.c}}
  :{colspan:String(g.origin.colspan),rowspan:String(g.origin.rowspan),index:c,element:g.origin.el.id,hide:null})}));

> [!note] 更新模式语义(坑9,已确认)
> 更新模式复用 templateId 时,元素 id 可全部重建,save 按整体替换语义处理,TABLE_JSON 引用同步更新即可,表结构不重建。

API 端点(已验证 ✅)

端点 方法 说明 状态
/api/v2/dynamicform/dynamicformcontroller/save POST 保存表单 ✅
/api/v2/dynamicform/dynamicformcontroller/formconfig/{id} POST 获取表单配置 ⚠️
/api/v2/element/elementcontroller/property POST 元素属性定义 ✅
/api/.../getcloumninfo POST 列信息 ✅
/api/v2/platform/database/tables POST 表列表 ✅

连接

必须先确定连接地址再创建客户端。

获取连接地址

BwaiClient 需要 baseUrl(格式:http://{host}:{port}/{contextPath})。按以下优先级获取:

  1. 从上下文推导:检查当前会话中是否已出现过可用的后端地址(如用户之前提到过项目部署地址、其他技能使用了某个地址等)
  2. 向用户询问:如果上下文无法推导,主动提问:
    > "请提供后端服务地址(格式如 http://host:port/contextPath)。"

无论哪种方式获取到地址,使用前必须向用户确认:

> "确认使用后端地址 {baseUrl} 是否正确?"

用户确认后才可创建客户端。

Session

const client = new BwaiClient(baseUrl, &#x27;JSESSIONID&#x27;);

Session 过期时(401),提示用户提供新 JSESSIONID。

工作区文件

文件 用途
api-trace.jsonl 所有 API 请求/响应的完整追踪(每行一个 JSON,含请求体、响应体、耗时)
trace-summary.jsonl 高级方法(createFormConfig)的结构化入参/出参摘要
table-info.md 数据表结构信息
form-params.json AI 输出的表单参数
existing-form.json 已有配置(修改模式)

⚠️ 已知陷阱(必读)

以下陷阱均来自实际调试中遇到的血泪教训。创建表单前必须逐条检查。

# 陷阱 正确做法 症状
1 tempateId 拼写错误 后端字段名是 tempateId(少一个 l),不是 templateId 空字符串 → 错误码 120340
2 PRIMARYKEY 用占位符 必须是实际的主键列名(如 BH、XH),不能用 *** 或空字符串 表单渲染成功但保存数据失败 (120111)
3 LX 用了元素类型 LX 是分类 ID,不是元素类型。默认值 1692778465032026730496 不影响保存但分类不正确
4 getcloumninfo 参数大小写 参数名是全小写 tablename,不是 tableName API 调用失败
5 attrbutevalues 嵌套格式 属性是直接键值对 {FIELD: &#x27;xxx&#x27;},不是 {FIELD: {value: &#x27;xxx&#x27;}} 后端解析异常
6 attrbutevalues 缺少 dbcolum/YSDYZD 每个元素必须包含 dbcolum、YSDYZD、YSMC、label、FIELD 五个属性,值都等于字段名 渲染时字段名变为 undefined,保存时字段映射丢失
7 表主键不是 XH 不同表的主键不同,必须用 getPrimaryKey(tableName) 查询实际主键 指定错误主键 → 保存失败 (120111)
8 formReload 异步延迟 保存后缓存同步是异步的(新线程),立即读取可能失败 formconfig/{id} 偶尔返回错误,等待 2-3 秒后重试
9 属性名大小写冲突 EMPTY() 工厂里属性全小写(如 xsgs),组件工厂里必须也用小写(xsgs:&#x27;1&#x27;),禁止用大写 XSGS。Windows MySQL 不区分列名大小写,Object.assign 后两个键共存,后端写入属性表时同一属性插入两次 → 主键冲突 save 报 Duplicate entry &#x27;...-XSGS&#x27; for key &#x27;PRIMARY&#x27;(错误码 120340)
10 必填属性缺失导致二次编辑报错 每个元素类型的必填属性(如 TEXT 的 ZDCD、CUSTOM_HTML 的 YSMC)必须非空。校验门已在 saveForm 前自动拦截(见上方「必填属性校验门」)。AI 收到拦截报错后按分类补全:A 类(ZDCD)回 spec 补(单行100/多行2000),B 类(YSMC)构建层补(从 html_content 提取或用「区块骨架N」) 渲染正常,但进设计器编辑报「属性X不能为空」

跨 Skill 协作

与 design-list / build-list 联动

用户要求"增删改查/CRUD"时,需要先创建表单(本 skill),再设计列表 spec(pacp-design-list)并构建关联(pacp-build-list)。

两个 skill 的 BwaiClient 是独立的,方法集不同,必须各自创建各自的实例,不可共享。但两个 CLI 共用同一个 session-id。

// ─── 各自引入各自的模块 ───────────────────────────────────
const { BwaiClient: FormClient } = require(&#x27;./skills/pacp-build-form/lib/bwai_client&#x27;);
const widgets = require(&#x27;./skills/pacp-build-form/scripts/widgets&#x27;);
const { createFormConfig } = require(&#x27;./skills/pacp-build-form/scripts/form_builder&#x27;);

const { BwaiClient: ListClient } = require(&#x27;./skills/pacp-build-list/lib/bwai_client&#x27;);
const colWidgets = require(&#x27;./skills/pacp-build-list/scripts/col_widgets&#x27;);
const { createListConfig, associateForm } = require(&#x27;./skills/pacp-build-list/scripts/list_builder&#x27;);

// ─── 各自创建各自的 client,使用同一个 sessionId ──────────
const fClient = new FormClient(baseUrl, SESSION_ID);
const lClient = new ListClient(baseUrl, SESSION_ID);

// ─── 第1步:用 fClient 创建表单 ──────────────────────────
const formResult = await createFormConfig(fClient, {
  title: &#x27;...&#x27;, templateName: &#x27;...&#x27;, tableName: &#x27;...&#x27;,
  elements: [ makeFormElement(&#x27;INPUT&#x27;, &#x27;名称&#x27;, &#x27;MC&#x27;), ... ],
});
const templateId = formResult.templateId;

// ─── 第2步:用 lClient 创建列表 ──────────────────────────
const listResult = await createListConfig(lClient, {
  title: &#x27;...&#x27;, sqlSelect: &#x27;* FROM TABLE_NAME&#x27;,
  columns: [ makeSeqColumn(&#x27;序号&#x27;, 1), makeColumn(&#x27;MC&#x27;, &#x27;名称&#x27;, &#x27;string&#x27;, 2, &#x27;20%&#x27;), ... ],
});

// ─── 第3步:用 lClient 关联表单 ──────────────────────────
const assocResult = await associateForm(lClient, listResult.XH, templateId, {
  primaryKey: &#x27;XH&#x27;,
});
// assocResult.listUrl, assocResult.formUrl

⚠️ 关键约束:

  • fClient 和 lClient 是不同类的实例,方法集不同,不可混用
  • fClient 有 saveForm() 但没有 saveQueryConfig()
  • lClient 有 saveQueryConfig() 但没有 saveForm()
  • 表单操作(createFormConfig)传 fClient,列表操作(createListConfig / associateForm)传 lClient

判断逻辑:

  • 用户要求"做某个表的增删改查/CRUD" → 先执行本 skill 创建表单,再由 pacp-design-list 设计列表 spec、pacp-build-list 构建并关联
  • 用户只要求"创建表单" → 仅执行本 skill,不涉及列表

常见问题

5 问
答案由本站依据条目字段整理
构建与修改动态表单配置怎么安装?

在支持 Skills 的客户端里执行:skillhub install user_ee9b2ae1/pacp-build-form。安装方式为未标注。装完刷新技能列表或重开客户端。

构建与修改动态表单配置支持哪些客户端?

资料没有标注宿主客户端,但只要客户端支持 Skills(Claude、Codex、Cursor 等),一般都能加载。

构建与修改动态表单配置权限高吗,安全吗?

权限级别为未标注权限。安装前先看它会读取或写入哪些内容。

构建与修改动态表单配置收费吗?

本站不代收费用,也没有标注具体价格。是否收费取决于技能本身和所用的模型或客户端,正式使用前按作者说明确认。

构建与修改动态表单配置用什么许可证?

许可证标注为 未说明(许可证不明确)。商用或二次分发前请到仓库确认条款原文。

热门专题

按场景挑技能
全部专题 →

技能内容以作者仓库为准;分类、权限级别与许可证状态的中文说明为中文整理,最近核验 2026-11-06。