openxiangda-skill-kit 2.0.0-alpha.99 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +2 -7
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -19
- package/dist/index.js.map +1 -1
- package/dist/workspace-guidance.d.ts +13 -0
- package/dist/workspace-guidance.d.ts.map +1 -0
- package/dist/workspace-guidance.js +67 -0
- package/dist/workspace-guidance.js.map +1 -0
- package/package.json +12 -3
- package/skills/manifest.json +2 -2
- package/skills/openxiangda-v2/SKILL.md +63 -46
- package/skills/openxiangda-v2/agents/openai.yaml +2 -2
- package/skills/openxiangda-v2/references/administration.md +27 -0
- package/skills/openxiangda-v2/references/application-foundation.md +162 -0
- package/skills/openxiangda-v2/references/appspec.md +132 -47
- package/skills/openxiangda-v2/references/backend.md +101 -237
- package/skills/openxiangda-v2/references/cli.md +27 -0
- package/skills/openxiangda-v2/references/concepts.md +61 -0
- package/skills/openxiangda-v2/references/data-authz.md +36 -244
- package/skills/openxiangda-v2/references/delivery.md +110 -42
- package/skills/openxiangda-v2/references/development.md +32 -0
- package/skills/openxiangda-v2/references/field-components.md +236 -0
- package/skills/openxiangda-v2/references/frontend.md +269 -101
- package/skills/openxiangda-v2/references/getting-started.md +66 -0
- package/skills/openxiangda-v2/references/interaction-patterns.md +56 -0
- package/skills/openxiangda-v2/references/mcp.md +649 -0
- package/skills/openxiangda-v2/references/product-design.md +142 -0
- package/skills/openxiangda-v2/references/public-access.md +167 -0
- package/skills/openxiangda-v2/references/testing.md +45 -48
- package/skills/openxiangda-v2/references/upgrading.md +39 -0
- package/skills/openxiangda-v2/references/workflow-events.md +152 -151
- package/skills/openxiangda-v2/references/architecture.md +0 -7
- package/skills/openxiangda-v2/references/commands.md +0 -21
- package/skills/openxiangda-v2/references/discovery.md +0 -15
- package/skills/openxiangda-v2/references/workspace.md +0 -25
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# OpenXiangda 2.0 字段组件协议
|
|
2
|
+
|
|
3
|
+
OpenXiangda 2.0 只声明业务语义字段。字段的 TypeScript 值、JSON Schema、
|
|
4
|
+
PostgreSQL 物理列、索引、查询运算符、权限路径和桌面/移动组件都由编译器从同一份声明生成,
|
|
5
|
+
应用不能另外声明存储类型或第二套字段元数据。
|
|
6
|
+
|
|
7
|
+
完整的架构约束、验收矩阵和进度证据见
|
|
8
|
+
[数据与权限](data-authz.md)。
|
|
9
|
+
|
|
10
|
+
## 字段与存储
|
|
11
|
+
|
|
12
|
+
| 语义类型 | 标准组件 | Data API 存储/读取值 | PostgreSQL |
|
|
13
|
+
| --- | --- | --- | --- |
|
|
14
|
+
| `text.short` | 单行、邮箱、手机号 | `string` | `varchar(length)` |
|
|
15
|
+
| `text.long` | 多行文本 | `string` | `text` |
|
|
16
|
+
| `text.rich` | 富文本 | 清洗后的 HTML `string` | `text` |
|
|
17
|
+
| `number.integer` | 整数 | `number` | `bigint` |
|
|
18
|
+
| `number.decimal` | 小数、金额、百分比 | `number` | `numeric(p,s)` |
|
|
19
|
+
| `boolean` | 是/否选择 | `boolean` | `boolean` |
|
|
20
|
+
| `date` | 日期 | `YYYY-MM-DD` | `date` |
|
|
21
|
+
| `time` | 时间,可声明分钟或秒精度 | `HH:mm:ss` | `time(0)` |
|
|
22
|
+
| `datetime` | 日期时间 | RFC3339 instant | `timestamptz` |
|
|
23
|
+
| `date-range` | 日期范围 | `{start,end}` | `daterange` |
|
|
24
|
+
| `datetime-range` | 日期时间范围 | `{start,end}` | `tstzrange` |
|
|
25
|
+
| `option.single` | 静态单选下拉、单选按钮 | `{label,value,...}` | `jsonb` |
|
|
26
|
+
| `option.multiple` | 静态多选下拉、复选框 | `{label,value,...}[]` | `jsonb` |
|
|
27
|
+
| `cascade.single` | 单路径级联 | `{label,value,...}[]` | `jsonb` |
|
|
28
|
+
| `cascade.multiple` | 多路径级联 | `{label,value,...}[][]` | `jsonb` |
|
|
29
|
+
| `user.single` | 成员单选 | 完整成员快照或 `null` | `jsonb` |
|
|
30
|
+
| `user.multiple` | 成员多选 | 完整成员快照数组 | `jsonb` |
|
|
31
|
+
| `department.single` | 部门单选 | 完整部门/路径快照或 `null` | `jsonb` |
|
|
32
|
+
| `department.multiple` | 部门多选 | 完整部门/路径快照数组 | `jsonb` |
|
|
33
|
+
| `resource-ref.single` | 动态下拉、单选按钮、资源选择 | 资源 `{label,value,resourceCode,snapshot}` | `jsonb` |
|
|
34
|
+
| `resource-ref.multiple` | 动态多选、复选框、资源选择 | 资源快照数组 | `jsonb` |
|
|
35
|
+
| `file` | 附件 | `DataFileRef[]` | `jsonb` |
|
|
36
|
+
| `image` | 图片 | 带尺寸、缩略图和预览地址的 `DataImageRef[]` | `jsonb` |
|
|
37
|
+
| `signature` | 手写业务签名 | 托管 PNG、签署人、时间、轨迹和哈希 | `jsonb` |
|
|
38
|
+
| `address` | 行政区划地址 | 行政区划标签路径、详细地址和完整地址 | `jsonb` |
|
|
39
|
+
| `location` | 精确定位 | 钉钉/浏览器 WGS84 经纬度和只读服务快照 | `jsonb` |
|
|
40
|
+
| `json` | JSON 编辑器 | 有界 JSON | `jsonb` |
|
|
41
|
+
| `serial-number` | 流水号只读框 | 平台生成 `string` | `varchar(255)` |
|
|
42
|
+
| `uuid` | UUID 业务字段 | 校验 UUID 格式,可按权限修改 | `uuid` |
|
|
43
|
+
| `subtable` | 子表 | 普通子资源行集合 | 独立子表 |
|
|
44
|
+
|
|
45
|
+
单值空值统一使用 `null`;多值、附件和图片统一使用 `[]`。`subtable` 不在父表保存
|
|
46
|
+
JSON,而是通过标准 Data API 事务维护普通子资源。
|
|
47
|
+
|
|
48
|
+
布尔字段没有默认值时显示“未选择”,不会把未填写当成“否”。PC 和移动端可以直接选择“否”并提交 `false`;`false` 是有效值,不是必填校验中的空值。只有明确声明默认值时才初始化对应布尔值。
|
|
49
|
+
|
|
50
|
+
## 图片、缩略图和附件读取
|
|
51
|
+
|
|
52
|
+
文件本体保存于平台对象存储,业务字段保存 `DataFileRef[]` 或 `DataImageRef[]`,不把 Base64 图片、文件字节、浏览器 `blob:` 地址写入业务字段。`previewUrl` 和 `thumbnailUrl` 是平台按当前应用、环境和文件权限生成的读取地址,不是永久公开 OSS 地址;不要自行替换域名、删除环境参数或拼接对象存储路径。
|
|
53
|
+
|
|
54
|
+
图片上传完成后,平台保留原文件,并生成最长边 480 像素、保持比例、不放大小图的 WebP 缩略图,编码质量参数为 82。列表、活动卡片、小封面和头像优先用缩略图;大图预览和下载按需读取原文件。当前原生文件端点只支持原文件和 `variant=thumbnail`,不能自行拼接 `width`、`quality` 等未声明参数。附件字段中的图片不保证具有缩略图,需要缩略图能力时声明 `image` 字段。
|
|
55
|
+
|
|
56
|
+
标准图片/附件展示可以直接使用 Field Kit:
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
import { AttachmentFileList } from 'openxiangda/field-kit';
|
|
60
|
+
|
|
61
|
+
<AttachmentFileList
|
|
62
|
+
files={record.cover ?? []}
|
|
63
|
+
resourceCode="activities"
|
|
64
|
+
imageTiles
|
|
65
|
+
mobile={isMobile}
|
|
66
|
+
/>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
组件按文件引用选择缩略图,预览和下载经过平台接口。自定义活动封面同样先选择 `cover.thumbnailUrl`;只有平台引用没有缩略图时才回退 `cover.previewUrl`。不要写 `previewUrl || thumbnailUrl`,否则小卡片也会下载完整原图。保留真实宽高或稳定的封面比例,非首屏图片使用懒加载;不得在列表渲染时预取所有原图。
|
|
70
|
+
|
|
71
|
+
受限文件的缓存必须保留权限核验。配套平台支持私有条件缓存时,浏览器可以保存文件响应,再次访问由平台先核对当前权限,内容未变化返回 304,从本地复用字节;`no-cache` 表示复用前校验,和 `no-store` 禁止保存不同。缺少可靠实体标识或请求失败时仍禁止缓存。不应由应用添加长期免校验缓存、公开 CDN 缓存或跨账号 Blob 缓存来绕过该规则。公开长期缓存需要独立、明确的公开发布契约,不能仅因字段名叫封面就视为公开。
|
|
72
|
+
|
|
73
|
+
验收时同时记录原图/缩略图字节、列表实际请求的变体、重复访问传输量和权限撤销结果。仅看到 `blob:` 地址不能判断用了 Base64,HTTP 200 也不能证明命中了缓存。
|
|
74
|
+
|
|
75
|
+
## 声明示例
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
{
|
|
79
|
+
code: 'status',
|
|
80
|
+
type: 'option.single',
|
|
81
|
+
label: '状态',
|
|
82
|
+
widget: 'radio',
|
|
83
|
+
required: true,
|
|
84
|
+
indexed: true,
|
|
85
|
+
filter: true,
|
|
86
|
+
options: [
|
|
87
|
+
{ label: '草稿', value: 'draft', color: 'default' },
|
|
88
|
+
{ label: '已提交', value: 'submitted', color: 'blue' },
|
|
89
|
+
],
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
前端提交并读取完整快照,例如 `{label:'草稿',value:'draft'}`。后端信任显示快照,
|
|
94
|
+
只做有界结构校验;查询和权限以 `value` 为稳定比较键。选项后来改名不会改变历史记录的显示值。
|
|
95
|
+
|
|
96
|
+
动态选项使用同应用资源引用:
|
|
97
|
+
|
|
98
|
+
`source.labelField` 必须指向目标资源的 `text.short` 或 `text.long` 字段。流水号字段可以放进
|
|
99
|
+
`searchFields`、`descriptionFields` 或 `snapshotFields`,但不能作为显示标签。
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
{
|
|
103
|
+
code: 'customer',
|
|
104
|
+
type: 'resource-ref.single',
|
|
105
|
+
label: '客户',
|
|
106
|
+
widget: 'select',
|
|
107
|
+
indexed: true,
|
|
108
|
+
filter: true,
|
|
109
|
+
source: {
|
|
110
|
+
kind: 'resource',
|
|
111
|
+
resourceCode: 'customers',
|
|
112
|
+
labelField: 'name',
|
|
113
|
+
searchFields: ['name', 'code'],
|
|
114
|
+
descriptionFields: ['code'],
|
|
115
|
+
snapshotFields: ['code', 'level'],
|
|
116
|
+
pageSize: 20,
|
|
117
|
+
loadMode: 'search',
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
来源端点接受当前表单绑定值、关键字和游标;页大小由 `source.pageSize` 固定。
|
|
123
|
+
标准流程的具名动作发起表单由 `WorkflowSubmissionPage` 自动传递 `launch: { workflowCode, operationCode }`。
|
|
124
|
+
平台核对当前环境已部署的流程、动作、输入字段映射和当前用户动作权限;不要求额外授予宿主表单 CRUD 权限。
|
|
125
|
+
普通 CRUD 表单不传此绑定,继续检查对应创建/修改权限。自定义选择器可通过 `searchResource` 的同名选项传递
|
|
126
|
+
已声明的发起绑定,不能用它扩大目标资源的读取范围或执行动作。
|
|
127
|
+
使用此功能的编译包自动要求平台能力 `workflow.named-input-sources` 的 `1.0.0` 版本;先检查目标平台能力,配套升级后再发布。
|
|
128
|
+
平台按来源资源的字段权限和 PostgreSQL RLS 查询并返回完整资源快照。来源记录改名或删除后,
|
|
129
|
+
已经保存的 `{label,value,resourceCode,snapshot}` 仍可直接展示,不需要再次查询。
|
|
130
|
+
|
|
131
|
+
成员和部门同样保存完整显示快照:
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
{ code: 'owner', type: 'user.single', label: '负责人', required: true }
|
|
135
|
+
{ code: 'participants', type: 'user.multiple', label: '参与人' }
|
|
136
|
+
{ code: 'college', type: 'department.single', label: '学院', indexed: true, filter: true }
|
|
137
|
+
{ code: 'supportDepartments', type: 'department.multiple', label: '协作部门' }
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
成员快照可包含头像、工号、职务、手机号、邮箱和所属部门;部门快照可包含完整路径、
|
|
141
|
+
路径节点和父部门。凭证、Token 和认证秘密永远不能进入快照。
|
|
142
|
+
|
|
143
|
+
仅选择时间时使用 `time`,并显式决定精度:
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
{ code: 'reminderMinute', type: 'time', label: '提醒时间', timePrecision: 'minute' }
|
|
147
|
+
{ code: 'checkpointSecond', type: 'time', label: '检查时间', timePrecision: 'second' }
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
定位只支持钉钉定位或浏览器 Geolocation 采集 WGS84 经纬度。组件没有地址输入、
|
|
151
|
+
手工定位或地图选点;服务商返回的地址/POI 只能作为该坐标的只读显示快照。
|
|
152
|
+
|
|
153
|
+
## 移动选择交互
|
|
154
|
+
|
|
155
|
+
`MobileSurfaceFieldControl` 为成员、部门、级联和动态资源字段提供统一的移动弹层。
|
|
156
|
+
直接使用目录选择组件时也可传 `mobile`;显式移动界面不会因窗口较宽而切回 PC 控件。
|
|
157
|
+
应用继续声明同一份业务字段,使用平台组件即可,无需自己拼目录树或搜索接口。
|
|
158
|
+
|
|
159
|
+
- 成员按部门逐层浏览,部门支持逐层选择和下钻;搜索、部门层级和成员列表均按页读取。
|
|
160
|
+
- 级联通过路径导航选择末级项;多选或较多候选支持完整路径搜索,多选保留每条完整路径快照。
|
|
161
|
+
- 当前已选项独立显示,可以移除或清空;切换路径、搜索和翻页不丢失尚未确认的选择。
|
|
162
|
+
- 单选和多选均点击“确定”才写回表单;“取消”或“关闭”放弃本次弹层修改。候选读取失败可重试。
|
|
163
|
+
|
|
164
|
+
界面使用平台封装的 Ant Design Mobile 组件;样式与弹层留在移动组件作用域内。
|
|
165
|
+
存储快照、来源过滤和权限仍遵循上述标准契约,不为移动端增加第二套数据或权限接口。
|
|
166
|
+
|
|
167
|
+
## 查询与权限
|
|
168
|
+
|
|
169
|
+
列表、聚合、导出、动态来源和事务断言共用 `openxiangda.data-query/v2` 的有界 `where`:
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
{
|
|
173
|
+
schemaVersion: 'openxiangda.data-query/v2',
|
|
174
|
+
where: {
|
|
175
|
+
and: [
|
|
176
|
+
{ field: 'status', operator: 'eq', value: 'submitted' },
|
|
177
|
+
{ field: 'customer', path: 'snapshot.level', operator: 'eq', value: 'A' },
|
|
178
|
+
],
|
|
179
|
+
},
|
|
180
|
+
order: [{ field: 'status', direction: 'asc' }],
|
|
181
|
+
limit: 20,
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
客户端不能发送 SQL、PostgREST 表达式或任意 JSONPath。编译器只接受字段类型允许的运算符
|
|
186
|
+
和已声明的快照路径,所有值都使用 SQL 参数。标量索引使用 BTREE,单快照 `value/label`
|
|
187
|
+
使用表达式 BTREE,多值/JSON 使用 GIN,范围使用 GiST,模糊搜索使用 trigram GIN。
|
|
188
|
+
|
|
189
|
+
资源 capability 决定能否执行 read/create/update/delete;数据策略决定该角色能操作哪些行;
|
|
190
|
+
字段策略决定字段可读、可创建和可更新范围。当前用户字段和学院等业务范围都由同一声明生成
|
|
191
|
+
PostgreSQL RLS,列表、详情、聚合、导出、来源查询、审计和事务不能绕过。
|
|
192
|
+
|
|
193
|
+
## 验证
|
|
194
|
+
|
|
195
|
+
只检查应用时运行统一入口,随后按实际变化补充真实浏览器验收:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
pnpm openxiangda check
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
平台维护者负责字段存储、查询运算符、索引和 RLS 的内核回归。应用开发者验证自己声明的字段、角色和业务交互,详见[检查与验收](testing.md)。
|
|
203
|
+
|
|
204
|
+
## 移动附件和图片
|
|
205
|
+
|
|
206
|
+
移动附件显示图标、文件名、大小和预览/下载/移除按钮;图片使用缩略图与加号上传格。
|
|
207
|
+
文件数量和大小限制沿用字段声明。上传失败保留文件并显示完整错误,支持重试;移除上传中的文件后,
|
|
208
|
+
迟到的结果不会回填表单。关闭编辑器同样使旧上传结果失效,已发出的请求可能继续在
|
|
209
|
+
服务端完成。移除字段引用不等于删除存储文件。
|
|
210
|
+
|
|
211
|
+
图片使用作用域内的移动图片预览,支持手势浏览、缩放和关闭;普通文件继续使用平台
|
|
212
|
+
预览路由。文件值仍为 `DataFileRef[]`,上传与下载继续由原有平台接口鉴权。
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
## 移动字段呈现与分组
|
|
216
|
+
|
|
217
|
+
移动字段自身提供无边框输入、上下标签、行分隔和错误提示;标准表单与自定义页面使用同一
|
|
218
|
+
字段组件。页面负责把相关字段组织在浅色背景上的白色分组中,不另设移动主题配置。
|
|
219
|
+
|
|
220
|
+
- 单选、复选直接展示选项;下拉单选/复选使用可搜索的底部弹层,多选展示已选数量。
|
|
221
|
+
- 日期使用月历,日期时间可切换时间滚轮;区间依次选择开始和结束,可返回上一步修改。
|
|
222
|
+
- 地址采用地区路径逐层选择,详细地址单独输入。定位沿用当前可用的钉钉或浏览器能力。
|
|
223
|
+
- 子表单直接展开行内字段,支持折叠、添加和删除;父表提交校验所有行,包括折叠的行。
|
|
224
|
+
权限、最大行数、原子事务和已存行的 revision 继续由原有资源契约约束。
|
|
225
|
+
- 签名在底部画布手写并保存;图片、签名和附件仍通过托管文件接口上传与鉴权读取。
|
|
226
|
+
- 手机富文本编辑降级为多行文本。未修改时保留已有 HTML;修改后将纯文本转为转义后的
|
|
227
|
+
段落 HTML,继续使用原有 `text.rich` 数据类型。
|
|
228
|
+
|
|
229
|
+
评分是整数的可选控件,默认五颗星,声明示例:
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
{ code: 'score', type: 'number.integer', label: '评分', widget: 'rating', min: 0, max: 5 }
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
`rating` 需要支持该控件的编译器、前端包与服务端 Surface 校验组合;存储、查询和校验仍为
|
|
236
|
+
整数。它不会修改已有 `number.integer` 字段的默认数值输入控件。
|
|
@@ -1,101 +1,269 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
`
|
|
100
|
-
|
|
101
|
-
|
|
1
|
+
# 前端架构
|
|
2
|
+
|
|
3
|
+
状态:Vite/Refine 已成为 OpenXiangda 2.0 唯一默认前端栈。决策与实测见
|
|
4
|
+
[核心架构](concepts.md)。
|
|
5
|
+
|
|
6
|
+
## 默认技术栈
|
|
7
|
+
|
|
8
|
+
- Vite 7:开发服务器与生产构建,只监听 `127.0.0.1`;
|
|
9
|
+
- React 19 + React Router:普通应用路由;
|
|
10
|
+
- Refine Core:资源查询、分页、排序和 mutation 状态;
|
|
11
|
+
- Ant Design 6:B 端页面组件。
|
|
12
|
+
|
|
13
|
+
不维护 Umi/Pro 双栈,也不在新模板中依赖已退休的前端框架包或旧身份 Provider。
|
|
14
|
+
|
|
15
|
+
## 数据和权限
|
|
16
|
+
|
|
17
|
+
`modules/` 中的业务模型由编译器派生 DataResource 契约,`openxiangda.config.ts` 声明页面
|
|
18
|
+
capability、应用角色和数据策略。前端只根据当前登录用户完整应用角色并集的
|
|
19
|
+
`capabilityCodes` 隐藏页面、按钮和只读字段;这些只是展示保护,平台 Data API
|
|
20
|
+
每次请求仍按同一角色并集做权威的行、字段和操作授权。
|
|
21
|
+
|
|
22
|
+
自定义角色管理页只调用 `openxiangda/core` 的角色管理 SDK。目录搜索使用
|
|
23
|
+
`searchRoleManagementUsers`,页面按钮按 `loadRoleManagementCatalog()` 返回的
|
|
24
|
+
`roleManagement` 投影显示,但前端显示不能替代服务端复核。不要在应用中复制角色表、
|
|
25
|
+
权限表或通过 NestJS 转发开发者凭据。
|
|
26
|
+
|
|
27
|
+
列表查询必须使用服务端过滤、排序和分页;新增、读取、更新、删除和文件上传都经过
|
|
28
|
+
可替换 Data API adapter。不要调用自定义 Nest CRUD、Function 或 Workflow 来绕过
|
|
29
|
+
Data API。只有真正需要事务或外部系统的动作才使用同源 `/api`。
|
|
30
|
+
|
|
31
|
+
默认仪器模块有 30 个字段,其中 `id/revision` 是 Data API 系统字段,28 个业务
|
|
32
|
+
字段由资源声明。新增、编辑和详情共用同一份字段元数据。五个边界字段使用五个独立
|
|
33
|
+
capability,不使用角色名或影子字段判断。
|
|
34
|
+
|
|
35
|
+
字段策略用 `create` / `update` 分别声明能力;显式空数组表示拒绝。学校管理员通过
|
|
36
|
+
`unrestrictedRoleCodes` 跳过行谓词,但仍受资源和字段能力
|
|
37
|
+
约束;学院管理员创建时可填写五个边界字段,更新时禁止修改 `collegeId`;仪器管理员
|
|
38
|
+
更新时禁止修改这五个字段。表单提交必须从 payload 删除无权字段。
|
|
39
|
+
|
|
40
|
+
DataQuery 使用有界 where 条件树,支持 and/or/not。标准列表的筛选、关键词、分页和导出共用已声明字段与同一查询条件,不在页面重写查询协议。
|
|
41
|
+
|
|
42
|
+
## 标准后台扩展
|
|
43
|
+
|
|
44
|
+
默认 CRUD、统一 Shell、current-user 权限和 Data Provider 都由 `openxiangda/react`
|
|
45
|
+
维护;应用拥有后台信息架构声明。页面实现、路由可达、菜单可见是三个不同合同:
|
|
46
|
+
|
|
47
|
+
- `resource.generated` 与 `frontend.routes` 决定有哪些标准页或 operation route;
|
|
48
|
+
- 编译器把全部可达页生成到 `adminPages`,detail/new/edit/handoff 可以存在但默认不进菜单;
|
|
49
|
+
- `frontend.admin.navigation` 是菜单的唯一权威声明,Shell 只渲染其中引用的页面,最后再按
|
|
50
|
+
current-user 权限过滤。权限不能发现或创建菜单项。
|
|
51
|
+
|
|
52
|
+
生成式资源后台固定使用 `/admin/resources/<resourceCode>` 命名空间,detail、create、
|
|
53
|
+
update 分别追加 `/:id`、`/new`、`/:id/edit`;独立移动后台面使用同一编译目录投影出的
|
|
54
|
+
`/m/admin/resources/<resourceCode>...`。所有桌面生成页由平台放进唯一 `Shell`,页面内部
|
|
55
|
+
跳转也只消费生成路径。普通 `/activities`、`/m/activities` 等产品路径留给 `user`
|
|
56
|
+
route。显式 route 与任何平台生成 route 的路径形状冲突时编译失败;即使动态参数名不同,
|
|
57
|
+
例如 `/:id` 与 `/:recordId`,也视为同一路径。不要添加旧根路径别名、重定向或通过注册顺序
|
|
58
|
+
解决冲突。
|
|
59
|
+
|
|
60
|
+
使用 typed helper 声明业务分组、用户名称、顺序、图标与页面引用,不维护 raw JSON:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import {
|
|
64
|
+
adminNavigationGroup,
|
|
65
|
+
adminOperationPage,
|
|
66
|
+
adminResourcePage,
|
|
67
|
+
defineAdminNavigation,
|
|
68
|
+
defineOpenXiangdaApp,
|
|
69
|
+
} from 'openxiangda/config';
|
|
70
|
+
|
|
71
|
+
export default defineOpenXiangdaApp({
|
|
72
|
+
// ...
|
|
73
|
+
frontend: {
|
|
74
|
+
root: 'apps/web',
|
|
75
|
+
routes: [{
|
|
76
|
+
code: 'instrument-import',
|
|
77
|
+
path: '/admin/operations/instrument-import',
|
|
78
|
+
label: '仪器导入',
|
|
79
|
+
surface: 'admin',
|
|
80
|
+
}],
|
|
81
|
+
user: { applicationTodoCenter: true },
|
|
82
|
+
admin: {
|
|
83
|
+
access: { anyOf: ['app:example:admin:view'] },
|
|
84
|
+
navigation: defineAdminNavigation([
|
|
85
|
+
adminNavigationGroup('instrument-center', '仪器管理', [
|
|
86
|
+
adminResourcePage('instruments'),
|
|
87
|
+
adminOperationPage('instrument-import'),
|
|
88
|
+
], { icon: 'database', order: 100 }),
|
|
89
|
+
]),
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
AI 先读取 `openxiangda://workspace/contracts` 的有界索引,再调用
|
|
96
|
+
`contract_describe` 并传入 `{ "selector": "navigation" }`,从 `data.selection.adminNavigationAuthoring.suggestion` 取得确定性的首次建议:
|
|
97
|
+
`proposal` 是有界机器可读声明,`imports` 是 `openxiangda/config` 的 typed helper,
|
|
98
|
+
`expression` 可一次性写入
|
|
99
|
+
`frontend.admin.navigation`,然后由应用正常编辑。该结果明确标记
|
|
100
|
+
`applyMode: "copy-once"` 和 `automaticRuntimeDiscovery: false`;compiler 和 runtime
|
|
101
|
+
从不调用建议器,也不会在以后新增内部资源时偷偷扩展生产菜单。直接使用 compiler API 的
|
|
102
|
+
tooling 也可调用 `renderAdminNavigationSuggestion(config)` 获得同一 proposal。
|
|
103
|
+
|
|
104
|
+
用 `defineApplicationContributions` 将每个生成的 `appRoutes` route 精确绑定到一个本地
|
|
105
|
+
React page。`admin` page 由 runtime 放入唯一的 `Shell`,component 不再次嵌套;`user`
|
|
106
|
+
page 不套 admin Shell,可独立实现移动/用户端布局,但仍共享当前用户、权限和 Router:
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
import {
|
|
110
|
+
defineApplicationContributions,
|
|
111
|
+
OpenXiangdaApplication,
|
|
112
|
+
} from 'openxiangda/react';
|
|
113
|
+
import {
|
|
114
|
+
adminNavigation,
|
|
115
|
+
adminAccess,
|
|
116
|
+
adminPages,
|
|
117
|
+
appRoutes,
|
|
118
|
+
routeManifest,
|
|
119
|
+
} from '@app/contracts/generated';
|
|
120
|
+
import { InstrumentCalibrationPage } from './operations/InstrumentCalibrationPage';
|
|
121
|
+
|
|
122
|
+
const contributions = defineApplicationContributions(appRoutes, {
|
|
123
|
+
pages: {
|
|
124
|
+
instrumentCalibration: InstrumentCalibrationPage,
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
<OpenXiangdaApplication
|
|
129
|
+
adminNavigation={adminNavigation}
|
|
130
|
+
adminAccess={adminAccess}
|
|
131
|
+
adminPages={adminPages}
|
|
132
|
+
routeManifest={routeManifest}
|
|
133
|
+
contributions={contributions}
|
|
134
|
+
/>;
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`pages` 的键必须与生成的 `appRoutes` 完全一致。隐藏 route 仍执行同一 `capability` 或
|
|
138
|
+
`access.allOf/anyOf`,子 route 同时继承全部祖先约束。页面代码随应用不可变前端制品构建,
|
|
139
|
+
不能从平台下载 component/module URL。admin operation page 固定使用
|
|
140
|
+
`/admin/operations` 或其子路径;带参数 route 只可直接访问,不能被导航引用。
|
|
141
|
+
`defineAdminContributions` 是保留的 admin-only helper,会主动拒绝 `user` routes。
|
|
142
|
+
|
|
143
|
+
应用登录使用可选 `frontend.authentication` 声明:仅允许现有平台用户、拒绝注册,桌面
|
|
144
|
+
固定 `/login`、移动固定 `/m/login`,并分别引用同设备的静态 user 默认 route。登录面由
|
|
145
|
+
编译器生成到独立 `authenticationSurfaces`,不进入受保护 `appRoutes`。应用通过
|
|
146
|
+
`defineApplicationContributions({ routes: appRoutes, authenticationSurfaces },
|
|
147
|
+
{ pages, authentication })` 绑定独立 PC/移动 renderer;renderer 只拥有品牌视觉和本地
|
|
148
|
+
展示状态,并调用 `ApplicationLoginSurfaceProps` 的平台回调。密码、租户 provider、一次性
|
|
149
|
+
OAuth state/callback、Secure HttpOnly 会话与 refresh family、当前身份和 AuthZ 始终由平台
|
|
150
|
+
唯一持有。禁止调用 v1 auth API、在浏览器保存 Token、把登录页塞入受保护路由或创建第二
|
|
151
|
+
Router/identity provider。生成的 `platformAuthManifest` 是独立登录 QA 清单,不改变用户路由
|
|
152
|
+
覆盖数。
|
|
153
|
+
|
|
154
|
+
## 匿名公开用户页
|
|
155
|
+
|
|
156
|
+
没有平台账号的外部用户不进入应用登录或角色并集。此类页面使用专门的
|
|
157
|
+
[`frontend.publicAccess` 匿名公开访问合同](public-access.md),由平台生成 HttpOnly 浏览器凭证,
|
|
158
|
+
并通过 `createAnonymousPublicClient` 提供草稿、附件、具名重复校验、幂等提交和同一浏览器的
|
|
159
|
+
本人列表/详情。不要创建 guest 账号、公开一般 Data API、保存本地身份或使用 IP/指纹判断所有权。
|
|
160
|
+
|
|
161
|
+
这也是完整应用的一等组合边界:`OpenXiangdaApplication` 始终唯一持有
|
|
162
|
+
`BrowserRouter`、`RuntimeBoundary`、Refine、generated resource/workflow routes
|
|
163
|
+
和后台 `Shell`。应用不要把它包在第二个 Router/Shell 中。桌面/移动用户端可以绑定不同
|
|
164
|
+
组件并拥有各自布局,但仍共享同一个 runtime identity 和 capability ancestor guard。
|
|
165
|
+
|
|
166
|
+
通用应用级待办中心通过用户 Surface 声明开启:
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
frontend: {
|
|
170
|
+
user: { applicationTodoCenter: true },
|
|
171
|
+
// ...
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
编译器生成 `/todos` 和 `/m/todos`。页面只读取当前登录用户的
|
|
176
|
+
Notification Hub 收件人投影;不调用 management API,不复制消息状态库。桌面端使用
|
|
177
|
+
无常驻详情的全宽列表,移动端使用独立卡片列表;`查看详情` 统一进入平台解析后的
|
|
178
|
+
Workflow/custom application route。
|
|
179
|
+
|
|
180
|
+
编译器同时生成必填的 `routeManifest`。它是 Workflow/Todo 标准页的唯一桌面/移动成对路由来源,
|
|
181
|
+
每个 entry 都携带稳定 `routeCode`、参数名、访问能力和 `requiresAuthentication: true`,顶层
|
|
182
|
+
`digest` 绑定完整 catalog。模板把该产物直接传给 `OpenXiangdaApplication`;runtime 在注册
|
|
183
|
+
Router 和页面前校验成对 user surface、参数与访问元数据。应用不得重新声明 `/todos`、
|
|
184
|
+
`/m/todos` 或标准 Workflow 路径,也不得添加别名、重定向或第二份
|
|
185
|
+
路由状态。digest 的计算与合同校验由 compiler/platform preflight 负责,浏览器只接受有效格式并
|
|
186
|
+
fail-closed 校验 entry/pair 元数据。
|
|
187
|
+
|
|
188
|
+
标准 Workflow 详情页直接渲染 Surface 的 `presentation.businessDetail`、`summary`、
|
|
189
|
+
typed timeline 和 operation descriptors。业务字段继续使用平台 `SurfaceFieldValue` 语义;
|
|
190
|
+
父子表、附件、富文本图片和签名不由应用另写 renderer 或拼接 Data API 文件 URL。
|
|
191
|
+
PC canonical 路径为 `/tasks/:taskId` 和 `/workflows/:instanceId`,使用独立全屏页面而不进入
|
|
192
|
+
后台 Shell;旧 admin 详情路径不存在。桌面和移动使用独立
|
|
193
|
+
renderer,但共享同一授权与命令生命周期。主决策操作固定在底部,
|
|
194
|
+
低频操作统一进入“更多操作”;意见输入延迟到动作确认层,页面不常驻空白意见表单。
|
|
195
|
+
页面只显示非系统业务字段与节点内操作,不展示 UUID、revision、事件序列或技术信息区;
|
|
196
|
+
`stale` 不产生常驻提示,真实命令冲突才显示刷新提示。
|
|
197
|
+
应用只有在业务交互确实不能由标准页表达时才声明成对的 custom detail route。
|
|
198
|
+
|
|
199
|
+
标准流程提交使用浏览器客户端的 `loadBusinessProcessReceipt(commandId)` 和
|
|
200
|
+
`pollBusinessProcessCommand(commandId, afterRevision)`(Nest 使用对应的
|
|
201
|
+
`OpenXiangdaBusinessProcessService.receipt/poll`)。首个回执可能是 `accepted`;按
|
|
202
|
+
`nextPoll` 继续读取直到 `terminal`,再消费 typed command/surface。不要把 accepted 当作提交失败,
|
|
203
|
+
也不要对平台端点发起未类型化的 `fetch`。
|
|
204
|
+
|
|
205
|
+
资源用 `mutationOwner: 'native' | 'action' | 'readonly' | 'workflow'` 声明 mutation owner,
|
|
206
|
+
并可用 `generated.list/detail/create/update/delete` 精确选择标准 surface。非 Native owner
|
|
207
|
+
不能生成或向应用角色授予 Native mutation;零可写业务字段不能开放 create/update。
|
|
208
|
+
Workflow definition 用 `launch.mode` 声明 `standalone`、`custom-page`、`hidden-handoff` 或
|
|
209
|
+
`work-center-only`;只有 `standalone` 可进入菜单,`hidden-handoff` 保留同一标准 PC/移动路由
|
|
210
|
+
但不进菜单。缺省 submission 使用 compiler 生成的标准 process operation。action-owned 资源
|
|
211
|
+
则在 `standalone`/`hidden-handoff` 上声明 `submission.kind: 'named-operation'`,把 create/existing
|
|
212
|
+
表单字段、平台幂等键、当前用户、subject id/revision 和响应结果显式绑定到原 named operation。
|
|
213
|
+
标准页仍使用 generated field components,但最终由应用服务端完成业务校验、原子写入并决定
|
|
214
|
+
是否返回 durable process command;缺少或返回 null command 表示本次无需审批。浏览器不提供
|
|
215
|
+
save callback,也不直接调用 prepare/start。`custom-page` 仍只能由 verified Named Action 注入
|
|
216
|
+
`OpenXiangdaBusinessProcessService` 发起。
|
|
217
|
+
|
|
218
|
+
标准资源页提供 `toolbar`、`row`、`detail` 三类 action slot。slot 声明稳定 code、label、
|
|
219
|
+
可选 order 和 capability/access,render 只获得当前 resource、已授权 record/selection 与
|
|
220
|
+
受控 `refresh()`:
|
|
221
|
+
|
|
222
|
+
```tsx
|
|
223
|
+
const contributions = defineApplicationContributions(appRoutes, {
|
|
224
|
+
pages: { instrumentCalibration: InstrumentCalibrationPage },
|
|
225
|
+
resources: {
|
|
226
|
+
instruments: {
|
|
227
|
+
row: [{
|
|
228
|
+
code: 'calibrate',
|
|
229
|
+
label: '校准',
|
|
230
|
+
capability: 'instrument.calibration.run',
|
|
231
|
+
render: ({ record, refresh }) => (
|
|
232
|
+
<CalibrationButton recordId={String(record.id)} onDone={refresh} />
|
|
233
|
+
),
|
|
234
|
+
}],
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
slot 只扩展动作区域,不接管查询、字段策略、revision、保存或审计。跨资源事务、外部副作用
|
|
241
|
+
和业务不变量仍调用有 `@OpenXiangdaOperation` 门禁的 Nest App Operation;普通 CRUD 继续
|
|
242
|
+
直接使用 Native Data API。UI 隐藏不是服务端授权。
|
|
243
|
+
|
|
244
|
+
学院是应用自有 `colleges` Native Resource,仪器 `collegeId` 保存该资源的系统 UUID。
|
|
245
|
+
人员和部门选择器调用平台 Directory 的分页 search,并用 exact resolve 恢复已选 ID;学院
|
|
246
|
+
选择器调用 membership-bound scope-values search/resolve。空结果和错误直接展示,不回退
|
|
247
|
+
静态人员、部门或学院。组织部门与学院属于不同 owner,不能互相推断。
|
|
248
|
+
|
|
249
|
+
发布态 app/environment 只读取平台为每个 index 注入的
|
|
250
|
+
`openxiangda-runtime-base`、`openxiangda-app-code` 和
|
|
251
|
+
`openxiangda-environment` meta;不得使用 Vite 构建变量或手填环境覆盖它。
|
|
252
|
+
当前用户使用同源 `openxiangda.runtime-authorization/v2` 合同,每个 Data API query/body
|
|
253
|
+
都显式携带 environmentKey。标准客户端读取当前用户角色并集,不保存 Token,也不持久化
|
|
254
|
+
授权结果。该角色并集合同未部署的平台版本
|
|
255
|
+
必须在应用写入前 fail closed。
|
|
256
|
+
|
|
257
|
+
## 本地与浏览器门禁
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
openxiangda dev
|
|
261
|
+
pnpm --filter @app/web check
|
|
262
|
+
pnpm --filter @app/web test
|
|
263
|
+
pnpm --filter @app/web test:e2e
|
|
264
|
+
pnpm --filter @app/web build
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
测试必须真实断言 Vite LAN 不可达、production 警示 DOM 常驻、Data/Directory/App API
|
|
268
|
+
使用同一当前用户角色并集、Perspective 读取投影协议,并约束源码文件数、LOC、构建
|
|
269
|
+
体积和 gzip 体积。
|