xj-form-exts — 迅捷平台功能扩展(当前能力:编写并上线平台静态页)
> 本 skill 是迅捷平台的功能扩展包,静态页是当前唯一能力;后续新增能力会在「业务场景索引」登记。
何时触发
- 用户想要一个统计分析报表页(多表统计、图表、看板、大屏)。
- 用户想要一个基于业务单据数据的展示页(只读列表/详情/可视化)。
- 用户想要一个完整功能页(页面里能查、能录、能改、能删,甚至发起流程)。
- 用户想要一个静态页表单视图:页面作为业务单据本身的编辑/审批页(单据在 FormEngine 里整页只显示这个静态页,字段双向映射、走系统流程发起/审批)。
- 用户想把已有 HTML 页面发布/上传到平台(挂不挂应用菜单由用户自行配置)。
- 用户要修改之前做过的静态页(此时先读同组
PAGE-CONTEXT.md恢复上下文,见第五节)。
不要触发:
- 创建/修改业务单据结构(加字段/明细表/布局)→ 用
xj-form-designerskill(完整功能页的前置建实体步骤除外,见对应场景 SOP)。 - 填单/查单/发起流程/审批等运行时操作 → 用
xj-form-agentskill。
业务场景索引
| 场景 | 触发词示例 | 场景 SOP |
|---|---|---|
| 统计分析报表页 | 帮我做个销售统计看板/多表统计报表页 | examples/report-dashboard/SKILL.md |
| 单据数据展示页 | 给报销单做个数据展示页/进度看板 | examples/entity-showcase/SKILL.md |
| 完整功能页 | 做一个设备报修登记页(录入+查询) | examples/full-feature-page/SKILL.md |
| 静态页表单视图 | 做一个入库单填报页,用系统流程审批(单据整页就是这个页面) | examples/form-view-page/SKILL.md |
接入方式
| 方式 | 适用宿主 | 认证 |
|---|---|---|
| A · MCP/WorkBuddy 连接器 | 宿主已连接 MCP 服务,能列出本 skill 的平台工具 | 由连接器完成认证注入,不要走下方 auth 流程 |
| B · 自托管 CLI | Claude Code 等非 MCP 宿主,走 scripts/agent.mjs |
按「配置与认证」手动绑定后调用 |
判定(先探测,不要假设自己处于哪种方式):查本会话是否连接了名为 xunjie 的 MCP 服务,或直接试调无参轻量工具 agent_page_get_api_docs——正常返回即方式 A,跳过配置认证节;否则走方式 B。
两种方式下工具名与参数完全一致,仅认证方式不同。
一、配置与认证(仅方式 B)
cd xj-form-exts
# 1. 把 baseUrl 写入 scripts/config.json(本地文件,勿提交)
# {"baseUrl": "http://<host>:<port>/<context>"}
# 2. 用 bindSecret 换取 token 并持久化(bindSecret 来自用户在 Web「第三方Agent绑定」创建的绑定)
node scripts/agent.mjs auth <bindSecret>
# 3. 验证
node scripts/agent.mjs status
node scripts/agent.mjs tools --full # 列出全部工具 + 最新参数 Schema(本文件的概要以它为准)
- bindSecret 等价登录凭证,
agent.mjs已加密持久化到scripts/.auth/,跨会话复用。 - 调用工具:
node scripts/agent.mjs call --name <工具名> --args '<json>'(大 payload 用--args-file <路径>或 stdin 管道)。
二、可用工具
> 本表为概要。参数 schema 会随平台演进,以 tools --full 实时拉取为准。
| 工具 | 用途 | 关键参数 |
|---|---|---|
agent_page_list_groups |
列静态页分组(选组/重名检查/上线前确认) | keyword?, includePages?, pageNo?, pageSize? |
agent_page_list_pages |
列某分组内全部页面文件 | groupId |
agent_page_upload_page |
上传/更新页面文件(组不存在则建;同页名替换并清旧附件);同时上传 PAGE-CONTEXT.md 记忆文件 |
groupName, files[]{fileName, contentBase64}, createGroupIfMissing? |
agent_page_delete_group |
删分组(不可恢复,删前必须用户确认) | groupId |
agent_page_get_api_docs |
获取页面平台能力与数据接口的完整契约文档(编写页面前必调) | 无 |
> 页面挂载到应用菜单、菜单授权:由用户在 Web 管理界面自行完成(应用菜单 → 静态页页签,菜单按「分组名/文件名」绑定),本 skill 不提供。
辅助(shared/借用):get_totp_status(TOTP 预检)、list_app_forms_tree(应用包下表单定义树,确认数据来源)、list_my_menus(列我的菜单,确认 appId)。
通用调用模板(方式 B):
node scripts/agent.mjs call --name agent_page_list_groups --args '{"keyword":"报表"}'
三、页面运行时硬约束(摘要)
页面 = 整页 HTML(可带 css/js),以 Vue 2 完整版渲染,在平台同域 iframe 中运行。写页面代码前必须先调 agent_page_get_api_docs,页面与平台对接的一切细节(凭证获取、数据查询/保存/删除接口、统计取数与 SQL 通道、高度自适应协议、多表混用注意事项)以该工具返回的文档为准。以下为硬约束:
- 自带 Vue:页面用
assets/vendor/vue.min.js(完整版,带模板编译器);不要复用宿主 Vue 渲染自己的模板(宿主是 runtime-only)。 - 凭证走 URL:页面从
location.search取token/tenantId;所有请求统一走page-bootstrap.js的bs.req(自动适配宿主请求封装并提供内置兜底,页面不感知差异)。子资源只转发token/tenantId/client-type,不要整段转发location.search——URL 上可能还带宿主上下文spCtx,整段转发会撑大子资源 URL(服务端 414/431)。 - 数据接口不下沉 skill:接口 URL/参数/响应结构一律取自
agent_page_get_api_docs返回内容;统计查询页首选内置固定 SQL(文档里有用法与安全约定)。 - vendor 随组上传:
assets/vendor/下的文件与页面一起传进同一分组(agent_page_upload_page一次多文件),页面内相对路径引用。 - 宿主通信走正式协议:被单据按钮/列表按钮/列表内嵌/表单内嵌字段打开时,上下文注入、数据变更通知、自关闭用
PageBootstrap.onContext/emitDataChanged/closeSelf(协议与 ctx 结构见agent_page_get_api_docs下发文档「宿主通信协议」章节);作为整页表单视图时额外遵守 fill 约定(viewMode/view/subForms、onDataRequest flush、明细整表替换,见文档 §14.8 与references/page-runtime-contract.md第 5 节);整页视图默认双端交付:PC 页面 + 移动端页面各一份(同组index.html/mobile.html),移动端页面单独适配,视图侧用add_static_page_view的mobile建移动端视图 - 附件上传/预览:宿主内 PC 页面优先复用平台原生上传弹窗
PageUploader.open({attachments, maxCount, maxSize, accept}, cb)(assets/vendor/page-uploader.js,带平台校验与拖拽列表);移动端(PageUploader.available()为 false)/无宿主/自定义 UI 用bs.uploadAttachment(file)/bs.loadAttachment(fieldValue)/bs.uuid()。预览/下载用bs.attachmentBlobUrl(att)(凭证走请求头,不进 URL),用完releaseAttachmentBlobUrl释放;bs.attachmentUrl仅兼容保留,其地址含会话凭证,不得分享/转发。契约与_attachment关系结构见文档 §12.1,大小/类型页面仍需自限(bootstrap 接口层不校验)。
四、典型 SOP:页面上线标准流程
- 查记忆:若是修改已有页面(或用户提到「之前做过」),先按第五节 5.3 定位分组并读取
PAGE-CONTEXT.md,恢复当初的口径与决策;没有则继续。 - 意图确认:明确页面类型(三场景之一)、数据来源(哪些单据/统计)。数据实体不存在 → 先走
xj-form-designer建实体。 - 预检:
get_totp_status未通过时提示用户登录系统完成一次迅捷令牌认证(本 skill 全部工具受保护)。 - 取契约:调
agent_page_get_api_docs,按返回文档确定页面要用的取数/存数方式。 - 写页面:单文件
index.html为主(较大逻辑拆 js);引用./vue.min.js、./page-bootstrap.js(按需./echarts.min.js/./page-picker.js/./page-uploader.js/./sm4-ecb.js),子资源带 token 动态加载(文档 §1)。 - 上传(需用户确认):先把文件清单 + 组名 + 更新影响(新建/替换哪些页面)展示给用户,确认后再调
agent_page_upload_page(groupName, files=[index.html, page-bootstrap.js, vue.min.js, PAGE-CONTEXT.md, ...]);组名建议含业务含义(如sales-dashboard)。上传后agent_page_list_pages(groupId)核对文件齐全(含PAGE-CONTEXT.md)。记忆文档与页面同一批上传(见第五节)。 - 用户后续操作:提示用户在 Web 管理界面「应用菜单 → 静态页页签」自行绑定菜单(按 分组名/文件名)并按需配置授权,然后从应用菜单打开页面验收。页面改动重传同名文件即生效。
> ⚠️ 更新页面 = 同组同名重传(后写覆盖先写);页面改名 = 新页面,用户在管理界面绑定的菜单按页名指向,改名后需用户自行调整菜单。
> ⚠️ 上传更新前先 agent_page_list_pages 核对现状;同组同名并发上传后写覆盖先写,一次会话内完成。
五、页面需求记忆(PAGE-CONTEXT.md,每次上线/修改都要做)
页面交付后往往隔一段时间才被要求修改,届时无法回忆起当初的沟通上下文。因此每个静态页分组都要随页面一起上传一份同组记忆文档 PAGE-CONTEXT.md(放同组、随组上传,不用另建分组),把与用户沟通需求的过程、敲定的口径、历次修改原因记下来,供后续 agent 快速恢复上下文。
5.1 文件格式(模板)
PAGE-CONTEXT.md:
# 页面需求记忆
- 分组:<groupName>
- 更新时间:YYYY-MM-DD HH:mm
- 页面清单:index.html(主页面)、page-bootstrap.js、vue.min.js
## 页面用途
<这页解决什么业务问题,一句话>
## 数据来源
- 单据/表:<单据名(表名)>
- 统计口径:<维度/指标/过滤/时间粒度>
## 已敲定的决策
- <与用户确认过的口径、交互、边界,逐条>
## 未决问题 / 已知限制
- <尚未确认或已知的约束>
## 与用户的沟通上下文
<粘贴需求讨论原文或要点,便于原样回溯>
## 修改记录
| 时间 | 改了什么 | 为什么改 |
|---|---|---|
| YYYY-MM-DD HH:mm | 初版上线 | — |
5.2 写入时机
- 首次上线:在 SOP 第 4 步写页面的同时生成记忆文档,写清需求、数据源、口径决策、未决问题与沟通上下文。
- 每次修改重传:先在「修改记录」表追加一行(改了什么、用户为什么要求改),再更新对应小节,最后与页面文件同一批上传。
- 用户明确说「不用记」时可跳过,但默认要写。
5.3 修改前先读记忆(必做)
收到对已有页面的修改请求时,先恢复上下文再动手:
agent_page_list_groups(keyword)定位分组 →agent_page_list_pages(groupId)看组内是否有PAGE-CONTEXT.md。- 有:拿该文件的
attachmentId调agent_form_get_attachment取downloadUrl下载读取(方式 B 也可node scripts/agent.mjs download <attachmentId> <本地路径>)。按「已敲定的决策」「沟通上下文」对齐口径,避免把已否掉的方案又做回去。 - 没有:照常向用户澄清需求,本次修改时补建记忆文档。
- 读到的记忆与当前页面代码冲突时,以代码现状为准,并向用户确认哪边才是预期。
> 记忆文档只写业务上下文与决策,不得写入 token、bindSecret、密码等凭证;沟通上下文里若含敏感内容先脱敏。
六、权限与安全边界(必读)
- 全部工具受迅捷令牌(TOTP)二次认证保护(含读与接口文档工具)。首次调用前用
get_totp_status预检;未绑定或未通过时,提示用户登录系统完成一次迅捷令牌口令认证,认证通过后即可正常调用(无需 Agent 侧做任何认证操作)。 agent_page_upload_page会替换同名页面并清理旧附件:更新已有页面(尤其是用户在管理界面配了菜单的组)前必须先agent_page_list_pages核对并向用户确认。agent_page_delete_group不可恢复:删除前必须向用户确认,并提示用户自行移除相关应用菜单。- 菜单绑定与授权不做:由用户在 Web 管理界面完成(应用菜单 → 静态页页签);用户反映菜单看不到时引导其检查绑定与授权。
- 页面以当前用户 token 发起数据请求,数据范围随该用户的权限;页面里的固定 SQL 不受菜单数据权限约束(详见契约文档安全约定),不得把用户输入拼进 SQL。
七、已知约束(联调记录)
- 单文件解码后 ≤20MB、单次 ≤20 个文件;可用扩展名
html htm js css json png jpg jpeg gif svg ico woff woff2 ttf eot md txt,任一文件不合法整批拒绝。 groupName/pageName不能包含/(会破坏「组名/页面名」页面标识解析)。- 移动端整页表单视图字段宿主监听页面高度上报(staticPageResize);移动端独立页/弹层宿主忽略上报,页面按容器自适应滚动。
- 整页表单视图在移动端只认
mobileView=true的移动端视图;只为单据建了 PC 视图时,移动端会沿用 PC 页面(错位)——交付时按「双端页面」出两份页面并双端绑定。 - 组内附件文件不随删组清理(与平台管理界面行为一致)。
八、接入调试
node scripts/agent.mjs status—— 确认绑定与 baseUrl。node scripts/agent.mjs tools --full—— 确认工具列表含 5 个agent_page_*;只有 shared 工具说明连接器 scope 未配page。- TOTP 报错 → 引导用户登录系统完成口令认证。
- 页面打不开(404)→ 核对组名/页面名与菜单 entityId 是否一致;401 → 检查 URL token/tenantId。
- 页面渲染但请求 401 → 检查
page-bootstrap.js是否随组上传并被页面正确加载。
九、配套文件
| 文件 | 说明 |
|---|---|
references/page-runtime-contract.md |
页面编写规范与自检清单(Vue 注意事项、单文件结构、vendor 引用、最小骨架);平台能力细节不在此,见 agent_page_get_api_docs |
references/page-upload-sop.md |
上线链路参考:组/页面语义、entityId 规则、更新/改名策略、菜单绑定的用户操作指引、删组影响面、故障排查、PAGE-CONTEXT.md 记忆文件约定 |
examples/report-dashboard/SKILL.md |
场景 SOP:统计分析报表页 |
examples/entity-showcase/SKILL.md |
场景 SOP:单据数据展示页 |
examples/full-feature-page/SKILL.md |
场景 SOP:完整功能页(含与 xj-form-designer 的协作边界) |
examples/form-view-page/SKILL.md |
场景 SOP:静态页表单视图(页面作为单据的编辑/审批页) |
assets/README.md |
vendor 资产清单/版本/来源/SHA256/升级约定 |
assets/vendor/ |
vue.min.js(2.7.16 完整版)、echarts.min.js(5.5.1)、page-bootstrap.js(内置引导/请求/高度/通信/附件接口)、page-picker.js(选人弹窗复用)、page-uploader.js(附件上传弹窗复用)、sm4-ecb.js(SQL 通道加密) |
scripts/agent.mjs |
统一执行入口(与 xj-form-agent/xj-form-designer 逐字节相同) |