工作区应用 · 界面描述规范(完整版)
读者:需要手写或审阅应用界面描述的人(含应用开发助手改草案时对照)
来源 sdk/appsdk/界面描述规范.md
文档版本:1.0(对应 app.json / AppSpec v1) 读者:需要手写或审阅应用界面描述的人(含应用开发助手改草案时对照) 关联:appsdk 目录说明 · 视图与布局 · 手工搭建指南 · 工作区应用机制
1. 核心原则
- 界面由描述生成,不是手写网页。平台 Web 端的标准渲染器读取
app.json,按views画出列表与表单。 - 一份描述三件事:数据长什么样(
entities)→ 页面有哪些块(views)→ 按钮调什么(actions)。 - 用户可见文案用
label;name/id/action是程序标识(小写、下划线等),不要拿去当界面标题。 - 当前仅三种视图:
list|form|action_form。无独立布局模型;扩展与「按图做界面」见 视图与布局。
打开应用时,渲染器会按 views 数组顺序从上到下依次渲染每一块。
2. 文件与包位置
| 文件 | 作用 |
|---|---|
app.json | 界面与数据模型描述(本规范主体) |
manifest.json | 应用清单:显示名、程序集、ui_mode=metadata 等(一般由平台写入) |
data/app.db | 由 entities 生成的业务库(与 Cadau 主库分离) |
logic/handlers.py | impl=script 的自定义动作实现(界面提交后调用) |
schema/001_init.sql | 由实体自动生成的建表 SQL(创建/更新规格时维护) |
手写界面时,通常只改 app.json;若新增了自定义动作,还需在 handlers.py 中实现对应 action id。
3. 根对象
{
"version": 1,
"entities": [ /* 至少一个 */ ],
"views": [ /* 至少一个 */ ],
"actions": [ /* action_form 引用的动作必须在此声明 */ ]
}
| 字段 | 必填 | 说明 |
|---|---|---|
version | 建议写 | 当前为 1;省略时平台写入时会补为 1 |
entities | 是 | 业务对象(表)定义;至少 1 个 |
views | 是 | 界面块;至少 1 个 |
actions | 条件 | 只要有 action_form,就必须声明其引用的动作 |
校验失败时无法保存/发布(见 §10)。
4. 实体 entities[]
表示应用内一张业务表(SQLite)。平台会自动增加主键列 id(自增),不必在 fields 里声明 id。
{
"name": "profile_reports",
"label": "画像记录",
"fields": [
{ "name": "emp_name", "label": "员工姓名", "type": "text", "required": true },
{ "name": "status", "label": "报告状态", "type": "select", "default": "待生成",
"options": ["待生成", "已生成", "失败"] },
{ "name": "created_at", "label": "生成时间", "type": "datetime", "read_only": true, "auto": "now" }
]
}
4.1 实体属性
| 字段 | 必填 | 规则 |
|---|---|---|
name | 是 | 表名;^[a-z][a-z0-9_]*$(小写字母开头,仅小写、数字、下划线) |
label | 是 | 用户可见名称(如「画像记录」) |
fields | 是 | 至少 1 个字段(不含系统 id) |
4.2 字段 fields[]
| 字段 | 必填 | 说明 |
|---|---|---|
name | 是 | 列名;规则同实体名风格;不要用 id(系统保留) |
label | 是 | 表头 / 表单标签 |
type | 建议 | 见下表;空字符串按 text 处理 |
required | 否 | true 时库列 NOT NULL;表单标 * |
default | 否 | 默认值(写入 DDL;表单初始值视控件而定) |
min | 否 | 仅对 number:HTML min |
read_only | 否 | true:不出现在可编辑表单 |
auto | 否 | 目前支持 "now":创建时由前端写入 UTC ISO(不展示输入框) |
hidden | 否 | true:默认不出现在列表列与表单 |
options | 否 | select 的选项字符串列表 |
字段类型与界面控件:
type | 控件 | SQLite 列类型 |
|---|---|---|
text(默认) | 单行输入 | TEXT |
number | type=number | INTEGER |
textarea | 多行文本 | TEXT |
datetime | datetime-local(按账号显示时区墙钟) | TEXT(存 UTC ISO) |
select | 下拉;须配 options | TEXT |
时间约定(与产品「时间显示与时区偏好」一致):
- 存盘:
datetime/auto=now均为 UTC 绝对时刻字符串;不要在库里存「无时区的北京时间假本地」。 - 列表展示:平台按当前用户的显示时区、日期格式与 12/24 小时制格式化;不是浏览器系统时区。
- 表单输入:用户按显示时区填墙钟;提交时转为 UTC ISO。
- 账号偏好本身不能写进
app.json;引导用户到「我的 → 偏好设置 → 时间显示」。
4.3 列表上的特殊约定
若实体含字段名 upload_id(存附件编号),列表在「操作」列会为有值的行提供 打开 / 下载(用于员工画像等 HTML 报告)。这是渲染器约定,不是单独的视图类型。
5. 视图 views[](界面怎么画)
每个元素是一块界面。type 决定画什么。
5.1 列表 list
展示某实体的表格;数据来自内置动作 crud.list。
{
"type": "list",
"id": "history",
"label": "历史画像",
"entity": "profile_reports",
"columns": ["emp_name", "report_title", "status", "created_at"],
"limit": 100,
"allow_delete": true
}
| 字段 | 必填 | 说明 |
|---|---|---|
type | 是 | "list" |
entity | 是 | 必须是已声明的实体 name |
label | 建议 | 区块标题;默认「列表」 |
id | 否 | 前端 key;建议填写便于稳定 |
columns | 否 | 列顺序(字段 name);省略则用实体全部非 hidden、非 id 字段 |
limit | 否 | 拉取条数;默认 100 |
allow_delete | 否 | 省略或 true:显示删除;false:隐藏删除。删除前弹出产品风格确认框(非浏览器原生) |
界面行为:标题旁有「刷新」;删除调用 crud.delete。
5.2 实体表单 form
向某实体插入一行;提交调用 crud.create。
{
"type": "form",
"label": "新建记事",
"entity": "notes",
"mode": "create",
"fields": ["title", "body"]
}
| 字段 | 必填 | 说明 |
|---|---|---|
type | 是 | "form" |
entity | 是 | 目标实体 |
mode | 建议 | 当前实现按 创建 处理;写 "create" |
fields | 否 | 要显示的字段名子集;省略则显示实体中所有可编辑字段 |
label | 建议 | 区块标题;默认「新增」 |
不会出现在表单里的字段:id、hidden=true、read_only=true、auto=now(auto=now 在提交时由前端写入时间,不展示输入框)。
5.3 动作表单 action_form
收集参数并调用自定义动作(通常 impl=script)。
{
"type": "action_form",
"id": "generate",
"label": "生成员工画像",
"action": "profile.generate"
}
| 字段 | 必填 | 说明 |
|---|---|---|
type | 是 | "action_form" |
action | 是 | 必须对应 actions[].id |
label | 建议 | 区块标题 |
id | 否 | 建议填写 |
fields | 不驱动控件 | 见下方「重要」 |
重要(与直觉不同):标准渲染器画动作表单时,输入项来自 actions[].params,不是 views[].fields。 views[].fields 可作文档/助手提示,但当前前端不会用它生成控件。手写时请把表单字段写在对应动作的 params 里。
提交后:调用 invoke(action, params)。若返回结果含 upload_id(及可选文件名),界面可提供打开/下载报告(与员工画像一致)。
6. 动作 actions[]
{
"id": "profile.generate",
"label": "生成员工画像",
"impl": "script",
"params": [
{ "name": "emp_name", "label": "员工姓名", "type": "text", "required": true },
{ "name": "emp_id", "label": "员工ID(重名时填写)", "type": "text" }
]
}
| 字段 | 必填 | 说明 |
|---|---|---|
id | 是 | 动作标识;建议 领域.动词(如 profile.generate) |
label | 是 | 用户可见名称 |
impl | 是 | "script":走 logic/handlers.py;"crud.create":内置创建(少见,一般用 form 视图即可) |
params | 建议 | 动作表单的字段定义;决定 action_form 上有哪些输入框 |
6.1 参数 params[]
| 字段 | 说明 |
|---|---|
name | 提交给脚本的参数名 |
label | 表单标签 |
type | 同字段类型:text / number / …;默认 text |
required | 是否必填 |
select 在动作参数上:若需下拉,当前动作参数结构未带 options 字段;下拉选项请放在实体字段上,用 form/list;动作表单目前以 text/number/textarea/datetime 为主(与渲染器 resolveActionFields 一致)。需要枚举时可用 text 并在脚本内校验,或后续扩展契约。
6.2 内置动作(不必写进 actions)
由平台提供,列表/表单会自动调用:
| action | 用途 |
|---|---|
crud.list | 列表加载 |
crud.create | 实体表单提交 |
crud.update | (API 有;标准 UI 尚未提供编辑视图) |
crud.delete | 列表删除 |
schema.tables | 列出业务表 |
ping | 健康检查 |
自定义动作未写在 actions 却被 action_form 引用 → 校验失败。
6.3 与 handlers.py 的对应
impl=script 时,运行时调用包内脚本入口,大致约定:
handle(action, params, ctx) -> dict
action等于actions[].id(如profile.generate)params为表单提交的键值- 返回中建议含
ok;失败时含error;可选upload_id/warnings等供界面展示
脚本细节与平台桥(取数、存附件)见机制文档「invoke」;界面能否出现按钮,仍以本文件的 actions + action_form 为准。
7. 手写最小示例(记事本)
{
"version": 1,
"entities": [
{
"name": "notes",
"label": "记事",
"fields": [
{ "name": "title", "label": "标题", "type": "text", "required": true },
{ "name": "body", "label": "内容", "type": "textarea" },
{ "name": "created_at", "label": "创建时间", "type": "datetime", "read_only": true, "auto": "now" }
]
}
],
"views": [
{
"type": "form",
"label": "新建记事",
"entity": "notes",
"mode": "create",
"fields": ["title", "body"]
},
{
"type": "list",
"label": "全部记事",
"entity": "notes",
"columns": ["title", "created_at"],
"allow_delete": true
}
]
}
无需 actions:只有 form + list,全部走内置 CRUD。
8. 手写带自定义动作示例(示意)
{
"version": 1,
"entities": [
{
"name": "jobs",
"label": "任务记录",
"fields": [
{ "name": "keyword", "label": "关键词", "type": "text", "required": true },
{ "name": "status", "label": "状态", "type": "select", "options": ["待处理", "完成", "失败"] },
{ "name": "upload_id", "label": "附件ID", "type": "text", "hidden": true },
{ "name": "created_at", "label": "时间", "type": "datetime", "read_only": true, "auto": "now" }
]
}
],
"views": [
{
"type": "action_form",
"id": "run",
"label": "执行任务",
"action": "job.run"
},
{
"type": "list",
"label": "历史",
"entity": "jobs",
"columns": ["keyword", "status", "created_at"]
}
],
"actions": [
{
"id": "job.run",
"label": "执行任务",
"impl": "script",
"params": [
{ "name": "keyword", "label": "关键词", "type": "text", "required": true }
]
}
]
}
同时需在 logic/handlers.py 实现 job.run(生成结果、可选写 jobs 表)。仅改界面描述而没有脚本,提交会失败。
9. 员工画像对照(现网结构)
| 用户看到的 | 描述里对应 |
|---|---|
| 「生成员工画像」表单 | views 中 type=action_form,action=profile.generate;字段来自该动作的 params |
| 「历史画像」表格 | views 中 type=list,entity=profile_reports |
| 删除历史 | allow_delete 未设为 false |
| 打开/下载报告 | 实体含 upload_id,且行内有值 |
| 点提交后的取数与 HTML | 不属于界面描述;在 logic/ + references/pipeline.json 等 |
完整样例可直接打开运行时应用包中的 app.json(员工画像模板)。
10. 校验清单(手写后自检)
- [ ]
entities≥ 1,且每个有name/label/fields - [ ] 实体名、字段名符合小写标识规则;字段有
label;类型合法 - [ ]
views≥ 1;type仅为list|form|action_form - [ ]
list/form的entity存在 - [ ] 每个
action_form的action在actions中存在 - [ ] 每个
actions[]有id/label;impl为script或crud.create - [ ] 动作表单字段写在
actions[].params,不要只写在views[].fields - [ ] 自定义动作已在
handlers.py实现(若impl=script) - [ ] 用户可见文案都在
label,没有把英文 id 直接当标题
实现侧校验函数:workspaceapp.ValidateSpec。
11. 明确不在本规范内
| 需求 | 去向 |
|---|---|
| 自由布局、自有 CSS/路由的完整 Web | 平台插件 SDK · 视图与布局 |
| 取数步骤、HTML 模板、组装脚本 | 取数与流水线、动作与handlers |
| 对话里「做一个应用」的流程 | 工作区应用.md §对话创建 |
| 产品帮助(给最终用户) | help/product-features/workspace-apps.md |
| 从零手工搭包 | 手工搭建指南 · 应用包结构 |
12. 修订约定
- 契约变更(新增视图类型、字段属性、动作参数 options 等)须同步:本文件、
spec.go校验、WorkspaceAppRenderer.tsx渲染行为,并 bump 本节文档版本号。 - 机制总览 工作区应用.md 可保留短摘要,完整手写说明以本文为准。