openxiangda 2.15.0 → 2.17.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.
Files changed (36) hide show
  1. package/dist/browser/application.d.ts.map +1 -1
  2. package/dist/browser/application.js +13 -0
  3. package/dist/browser/application.js.map +1 -1
  4. package/dist/browser/components/platform-fields/resource-query.d.ts +2 -0
  5. package/dist/browser/components/platform-fields/resource-query.d.ts.map +1 -1
  6. package/dist/browser/components/platform-fields/resource-query.js +3 -0
  7. package/dist/browser/components/platform-fields/resource-query.js.map +1 -1
  8. package/dist/browser/components/resource/StandardUserResourcePages.d.ts +36 -0
  9. package/dist/browser/components/resource/StandardUserResourcePages.d.ts.map +1 -0
  10. package/dist/browser/components/resource/StandardUserResourcePages.js +142 -0
  11. package/dist/browser/components/resource/StandardUserResourcePages.js.map +1 -0
  12. package/dist/browser/components/resource/useResourceFormDrafts.d.ts.map +1 -1
  13. package/dist/browser/platform-client.d.ts.map +1 -1
  14. package/dist/browser/platform-client.js +100 -43
  15. package/dist/browser/platform-client.js.map +1 -1
  16. package/dist/browser/route-manifest.d.ts.map +1 -1
  17. package/dist/browser/route-manifest.js +22 -8
  18. package/dist/browser/route-manifest.js.map +1 -1
  19. package/dist/nest.d.ts +1 -0
  20. package/dist/nest.d.ts.map +1 -1
  21. package/dist/nest.js +1 -0
  22. package/dist/nest.js.map +1 -1
  23. package/documentation/backend.md +35 -0
  24. package/documentation/declarations-cheatsheet.md +191 -0
  25. package/documentation/frontend.md +16 -1
  26. package/documentation/getting-started.md +7 -7
  27. package/documentation/manifest.json +10 -4
  28. package/package.json +24 -25
  29. package/releases/2.16.0.json +41 -0
  30. package/releases/2.17.0.json +39 -0
  31. package/skills/manifest.json +1 -1
  32. package/skills/openxiangda-v2/SKILL.md +5 -4
  33. package/skills/openxiangda-v2/references/backend.md +35 -0
  34. package/skills/openxiangda-v2/references/declarations-cheatsheet.md +191 -0
  35. package/skills/openxiangda-v2/references/frontend.md +16 -1
  36. package/skills/openxiangda-v2/references/getting-started.md +7 -7
@@ -0,0 +1,191 @@
1
+ # 声明速查:一次写对 openxiangda.config.ts {#cheatsheet}
2
+
3
+ 按"错误码 → 规则 → 正确片段"组织。这些规则全部来自真实返工:先扫一遍本页,再写声明,能省掉绝大多数首轮校验迭代。普通 CRUD 的完整可过检骨架见文末。
4
+
5
+ ## 模块与 CRUD 视图
6
+
7
+ | 规则 | 正确写法 |
8
+ | --- | --- |
9
+ | `crud[].model` 必须引用本模块已声明的模型 | `crud: [{ model: 'repair-requests', ... }]` |
10
+ | `user: true` 一行声明即可生成用户端标准面(“我的记录”列表 + 提交表单,双端),并自动成为登录落地页 | `crud: [{ model: 'supply-requests', user: true, ... }]`;多资源时 `user: { home: true }` 指定落地资源,“仅本人”是展示过滤,行级隔离仍用 dataPolicies |
11
+ | 视图 `list`/`form`/`detail` 的 `model` 可省略(继承视图模型);显式声明时必须与 `crud[].model` 一致 | `list: { fields: [...] }` 即可,不必写 `model` |
12
+ | 每个模型最多 20 个命名视图;命名视图需要稳定 `code` + `name` | `crud: [{ model: 'x', code: 'x-active', name: '进行中', ... }]` |
13
+ | 新建视图的 `form.fields` 必须覆盖全部无默认必填字段,或显式 `generated.create: false` | 检查器会列出缺失字段 |
14
+
15
+ ## 字段声明
16
+
17
+ | 规则 | 正确片段 |
18
+ | --- | --- |
19
+ | `option.*` 字段的值是 `{ label, value }` 快照(比较键是 `value`) | `options: [{ label: '教学设备', value: 'teaching' }]` |
20
+ | `number.integer` 可省略精度;只允许 `precision`(位数),不允许 `scale` | `{ code: 'qty', type: 'number.integer' }` |
21
+ | `number.decimal` 的 `precision` 必填、`scale` 0 到 precision | `{ type: 'number.decimal', precision: 12, scale: 2 }` |
22
+ | `audit.read` 可写 `true`(绑定本资源读能力)或能力数组 | `audit: { read: true }` |
23
+ | `resource-ref.*` 必须带 `source` 来源协议 | `{ type: 'resource-ref.single', source: { kind: 'resource', resourceCode: 'repair-requests', labelField: 'title', searchFields: ['title'], pageSize: 20, loadMode: 'search' } }` |
24
+ | `labelField` 必须指向目标资源的 `text.short` / `text.long` 字段 | 不要用流水号/选项字段当 label |
25
+ | 每个字段都必须带中文/业务 `label`(含子表外键与排序字段) | `{ code: 'requestId', type: 'uuid', label: '所属申请', required: true }` |
26
+ | 子表 `subtable` 的外键是子资源的 **uuid** 字段,排序字段是**可写 number.integer** | 子资源:`{ code: 'requestId', type: 'uuid', required: true }` + `{ code: 'sortOrder', type: 'number.integer', required: true }`;父表:`subtable: { resourceCode: 'repair-items', foreignKey: 'requestId', orderField: 'sortOrder', maxRows: 20 }` |
27
+ | 图片/附件的 `file` 限定数量与大小 | `file: { maxCount: 3, maxSizeMb: 10, accept: ['image/png', 'image/jpeg'] }` |
28
+
29
+ ## 权限声明
30
+
31
+ | 规则 | 正确片段 |
32
+ | --- | --- |
33
+ | 平台保留能力(如 `app:<app>:directory:read`)**不能**在 `capabilities` 里重复声明,直接在角色中引用即可 | `const directoryRead = \`app:\${APP_CODE}:directory:read\`` → `roles: [{ code: 'admin', capabilities: [directoryRead] }]` |
34
+ | 资源 CRUD 能力码用 `resourceCapabilityCodes(appCode, resourceCode)` 生成 | `const crud = resourceCapabilityCodes(APP_CODE, 'repair-requests')` → `capabilities: [crud.read, crud.create]` |
35
+ | `authenticatedUserRoleCode` 是平台登录用户的基线角色 | `authz: { authenticatedUserRoleCode: 'app-user', ... }` |
36
+
37
+ ## 后端操作(Nest)
38
+
39
+ | 规则 | 正确片段 |
40
+ | --- | --- |
41
+ | 写操作的 `ai.sideEffects` 至少一条具体副作用 | `ai: { name: '受理派单', ..., risk: 'write', sideEffects: ['更新报修单状态为处理中', '写入一条派工记录'] }` |
42
+ | GET 操作的 `ai.risk` 只能是 `read`;写操作不能是 `read` | `risk: 'read'` ↔ `method: 'GET'` |
43
+ | controller 路由必须绑定 `@OpenXiangdaOperation(appOperations.<code>)`,普通 CRUD 不写 controller | check 门禁会拒绝未绑定路由 |
44
+ | 事务守卫 `errorCode` 必须匹配 `^OPENXIANGDA_[A-Z0-9_]{1,96}$` | `errorCode: 'OPENXIANGDA_REPAIR_REQUEST_NOT_PENDING'` |
45
+
46
+ ## 事务写入快照字段
47
+
48
+ option / user / department / resource-ref 字段在事务和普通写入里都必须写快照对象,不能写裸字符串:
49
+
50
+ ```ts
51
+ import { optionSnapshot, userSnapshot, resourceSnapshot } from 'openxiangda/nest';
52
+
53
+ await this.data.transaction(idempotentTransaction(input.idempotencyKey, [
54
+ {
55
+ operation: 'update',
56
+ resourceCode: 'repair-requests',
57
+ id: input.requestId,
58
+ expectedRevision: revision,
59
+ data: {
60
+ status: optionSnapshot('处理中', 'processing'), // 不是 'processing'
61
+ assignedTechnician: userSnapshot(input.technicianId), // 不是裸 userId
62
+ },
63
+ },
64
+ {
65
+ operation: 'create',
66
+ resourceCode: 'repair-assignments',
67
+ data: {
68
+ requestId: resourceSnapshot('repair-requests', input.requestId, requestTitle),
69
+ technician: userSnapshot(input.technicianId),
70
+ },
71
+ },
72
+ ]));
73
+ ```
74
+
75
+ 读取时状态判断用 `record.data.status?.value === 'pending'`(存储值是快照对象)。
76
+
77
+ ## 幂等冲突复核模式
78
+
79
+ update 事务必须携带 `expectedRevision`;重试时 revision 已前进会让同一幂等键的内容指纹漂移,平台返回 409 `OPENXIANGDA_NATIVE_DATA_IDEMPOTENCY_CONFLICT`(而非 `replayed: true`)。标准处理:
80
+
81
+ ```ts
82
+ import { isIdempotencyConflict } from 'openxiangda/nest';
83
+
84
+ try {
85
+ result = await this.data.transaction(idempotentTransaction(key, operations, guards));
86
+ } catch (error) {
87
+ if (isIdempotencyConflict(error)) {
88
+ const current = await this.data.get('repair-requests', input.requestId);
89
+ if (/* 状态已离开 pending,说明本键的效果已生效 */) {
90
+ return { idempotencyKey: key, replayed: true, ... 当前状态 };
91
+ }
92
+ }
93
+ throw error;
94
+ }
95
+ ```
96
+
97
+ 不要用新生成的幂等键重试冲突——那会绕过幂等保护重复执行业务动作。
98
+
99
+ ## 两模型起步骨架(可直接改造)
100
+
101
+ ```ts
102
+ import {
103
+ adminNavigationGroup, adminResourcePage, defineAdminNavigation,
104
+ defineApplicationModule, defineOpenXiangdaApp, resourceCapabilityCodes,
105
+ } from 'openxiangda/config';
106
+
107
+ const APP_CODE = 'my-app';
108
+ const requestCrud = resourceCapabilityCodes(APP_CODE, 'requests');
109
+
110
+ const requests = {
111
+ code: 'requests', name: '申请单',
112
+ audit: { read: true },
113
+ fields: [
114
+ { code: 'title', type: 'text.short', label: '标题', required: true },
115
+ { code: 'category', type: 'option.single', label: '类别', required: true,
116
+ options: [{ label: '普通', value: 'normal' }, { label: '紧急', value: 'urgent' }] },
117
+ { code: 'status', type: 'option.single', label: '状态', required: true,
118
+ options: [{ label: '待受理', value: 'pending' }, { label: '已完成', value: 'done' }] },
119
+ { code: 'photo', type: 'image', label: '照片', file: { maxCount: 3, maxSizeMb: 10 } },
120
+ ],
121
+ };
122
+ const items = {
123
+ code: 'request-items', name: '明细',
124
+ fields: [
125
+ // 所有字段(含子表外键/排序)都必须声明中文 label。
126
+ { code: 'requestId', type: 'uuid', label: '所属申请', required: true },
127
+ { code: 'sortOrder', type: 'number.integer', label: '排序', required: true },
128
+ { code: 'name', type: 'text.short', label: '名称', required: true },
129
+ { code: 'qty', type: 'number.integer', label: '数量' },
130
+ { code: 'price', type: 'number.decimal', label: '单价', precision: 12, scale: 2 },
131
+ ],
132
+ };
133
+
134
+ export default defineOpenXiangdaApp({
135
+ app: { code: APP_CODE, name: '我的应用' },
136
+ frontend: {
137
+ root: 'apps/web',
138
+ devicePolicy: { kind: 'viewport-family', mobileMaxWidthPx: 900, desktopMinWidthPx: 901 },
139
+ routes: [
140
+ { code: 'application-home', path: '/home', label: '应用首页', surface: 'user' },
141
+ { code: 'application-home-mobile', path: '/m/home', label: '移动首页', surface: 'user' },
142
+ ],
143
+ authentication: {
144
+ accountMode: 'existing-platform-users-only',
145
+ registration: { mode: 'reject' },
146
+ methods: [{ code: 'password', type: 'password', label: '账号密码登录', presentation: 'primary', required: true }],
147
+ surfaces: {
148
+ desktop: { routeCode: 'application-login', path: '/login', defaultRouteCode: 'application-home' },
149
+ mobile: { routeCode: 'application-login-mobile', path: '/m/login', defaultRouteCode: 'application-home-mobile' },
150
+ },
151
+ },
152
+ admin: {
153
+ navigation: defineAdminNavigation([
154
+ adminNavigationGroup('main', '业务管理', [adminResourcePage('requests', { label: '申请单' })], { icon: 'database', order: 100 }),
155
+ ]),
156
+ },
157
+ },
158
+ modules: [defineApplicationModule({
159
+ code: 'main',
160
+ models: [requests, items],
161
+ crud: [
162
+ {
163
+ model: 'requests',
164
+ list: {
165
+ fields: ['title', 'category', 'status'],
166
+ filterFields: ['category', 'status'],
167
+ searchableFields: ['title'],
168
+ defaultPageSize: 20,
169
+ defaultSort: { field: 'title', order: 'asc' },
170
+ },
171
+ mobile: { enabled: true },
172
+ },
173
+ ],
174
+ })],
175
+ authz: {
176
+ authenticatedUserRoleCode: 'app-user',
177
+ capabilities: [],
178
+ roles: [
179
+ { code: 'app-user', name: '应用用户', capabilities: [requestCrud.read, requestCrud.create] },
180
+ { code: 'admin', name: '管理员', capabilities: [requestCrud.read, requestCrud.create, requestCrud.update, requestCrud.delete] },
181
+ ],
182
+ scopeDimensions: [], scopeSources: [], dataPolicies: [], authorizationTransitions: [],
183
+ },
184
+ });
185
+ ```
186
+
187
+ `request-items` 不出现在 `crud` 里:子表行随 `requests` 表单的 `subtable` 字段写入(在 `requests.fields` 里补 `{ code: 'items', type: 'subtable', subtable: { resourceCode: 'request-items', foreignKey: 'requestId', orderField: 'sortOrder', maxRows: 20 } }`)。
188
+
189
+ ## 图片上传的像素上限
190
+
191
+ image/signature/富文本图片字段在上传计划(initiate)里返回 `maxPixels`;超过上限的图片会被标准组件自动压缩后重新发起上传。用 API 直传时自行按 `maxPixels` 预检。当前平台上限覆盖主流手机主摄(48/50/64MP);超出会得到带实际尺寸的 `OPENXIANGDA_NATIVE_DATA_IMAGE_PIXEL_LIMIT_EXCEEDED` 错误。
@@ -63,7 +63,22 @@ capability、应用角色和数据策略。前端只根据当前登录用户完
63
63
  可替换 Data API adapter。不要调用自定义 Nest CRUD、Function 或 Workflow 来绕过
64
64
  Data API。只有真正需要事务或外部系统的动作才使用同源 `/api`。
65
65
 
66
- ### 自定义页面消费平台数据 {#data-access}
66
+ ### 生成式用户标准面 {#user-surface}
67
+
68
+ 对普通用户(登录基线角色)开放提交的 CRUD 资源,在 CRUD 视图上声明 `user: true` 即可获得
69
+ 开箱即用的用户端标准页,无需编写任何 React 代码:
70
+
71
+ - “我的记录”列表(`/my/<resource>`,移动端 `/m/my/<resource>`):默认仅显示当前登录用户
72
+ 创建的记录(`created_by` 展示过滤),支持分页、行详情抽屉与“提交”入口。
73
+ - “提交”表单(`/my/<resource>/submit`):复用标准字段渲染与文件上传,成功后回到列表。
74
+ - 首页接线:首个启用资源自动成为登录落地页与根路径;多资源时用 `user: { home: true }` 显式指定;
75
+ 声明的 `/home` 占位路由保留可用,也可以继续用自定义页面覆盖用户端体验。
76
+ - 能力接线自动完成:列表要求资源 read,提交要求 create;无能力的用户看到标准 403 面。
77
+
78
+ 注意:“仅本人”是页面展示过滤,不是授权边界。需要服务端强制行级隔离时按[数据与权限](data-authz.md)
79
+ 声明 dataPolicies。
80
+
81
+ ## 自定义页面消费平台数据 {#data-access}
67
82
 
68
83
  自定义报表、工具页和用户端页面从 `openxiangda/core` 导入 `createNativeResourceClient`,
69
84
  用生成契约里的 surface 直接获得该资源的权威读写客户端;行、字段与操作授权由平台在
@@ -66,10 +66,10 @@ MCP 服务随项目根包一起安装,AI 客户端的 stdio 连接仍需配置
66
66
  以下命令的版本占位符由随包资料替换为该根包的精确版本。网站源码阅读者应先确认要使用的发行版本。
67
67
 
68
68
  ```bash
69
- pnpm dlx openxiangda@2.15.0 skill install --force
70
- pnpm dlx openxiangda@2.15.0 auth status --base-url <平台地址> --json
71
- pnpm dlx openxiangda@2.15.0 login --cwd my-app --base-url https://platform.example.com
72
- pnpm dlx openxiangda@2.15.0 create my-app --base-url https://platform.example.com
69
+ pnpm dlx openxiangda@2.17.0 skill install --force
70
+ pnpm dlx openxiangda@2.17.0 auth status --base-url <平台地址> --json
71
+ pnpm dlx openxiangda@2.17.0 login --cwd my-app --base-url https://platform.example.com
72
+ pnpm dlx openxiangda@2.17.0 create my-app --base-url https://platform.example.com
73
73
  cd my-app
74
74
  pnpm openxiangda context --json
75
75
  pnpm openxiangda dev
@@ -171,9 +171,9 @@ MCP 的 `docs_read` 可以读取本说明,当前没有独立的源码操作 MC
171
171
  无需本地工作区,使用本 Skill 随包精确版本或已安装的对应 CLI:
172
172
 
173
173
  ```bash
174
- pnpm dlx openxiangda@2.15.0 auth status --base-url <平台> --json
175
- pnpm dlx openxiangda@2.15.0 source resolve <仓库URL> --base-url <平台> --json
176
- pnpm dlx openxiangda@2.15.0 source clone <仓库URL> <新目录> --base-url <平台> --json
174
+ pnpm dlx openxiangda@2.17.0 auth status --base-url <平台> --json
175
+ pnpm dlx openxiangda@2.17.0 source resolve <仓库URL> --base-url <平台> --json
176
+ pnpm dlx openxiangda@2.17.0 source clone <仓库URL> <新目录> --base-url <平台> --json
177
177
  ```
178
178
 
179
179
  登录缺失或站点不匹配时,先按该平台执行 login。resolve 根据平台已经登记的绑定返回