sy-page-sdk 0.1.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/README.md +444 -0
- package/dist/chunk-R77DYKUJ.js +1034 -0
- package/dist/index.cjs +1060 -0
- package/dist/index.d.cts +729 -0
- package/dist/index.d.ts +729 -0
- package/dist/index.js +6 -0
- package/dist/react/index.cjs +1383 -0
- package/dist/react/index.d.cts +77 -0
- package/dist/react/index.d.ts +77 -0
- package/dist/react/index.js +307 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
# sy-page-sdk
|
|
2
|
+
|
|
3
|
+
面向 `custom_code_page` 的前端 SDK。当前版本以“统一 transport + 业务模块 API”为中心,不再推荐页面代码直接拼平台 HTTP 路径、手写 query 字符串,或依赖旧的 `form.queryList` 心智。
|
|
4
|
+
|
|
5
|
+
规范源码以 [src](./src) 为准,README 示例对应的可类型检查版本见 [readme.examples.tsx](./src/readme.examples.tsx)。
|
|
6
|
+
|
|
7
|
+
## 开发前先读
|
|
8
|
+
|
|
9
|
+
`page-sdk` 负责帮页面屏蔽平台 query 序列化和 envelope 差异,但它不是平台语义本身。
|
|
10
|
+
|
|
11
|
+
在写页面前,建议先按下面顺序阅读:
|
|
12
|
+
|
|
13
|
+
1. [../../docs/platform-form-api.md](../../docs/platform-form-api.md)
|
|
14
|
+
2. [../../docs/field-value-shapes.md](../../docs/field-value-shapes.md)
|
|
15
|
+
3. [../../docs/simple-search.md](../../docs/simple-search.md)
|
|
16
|
+
4. [../../docs/advanced-search.md](../../docs/advanced-search.md)
|
|
17
|
+
5. 本 README
|
|
18
|
+
|
|
19
|
+
这样可以避免几个高频错误:
|
|
20
|
+
|
|
21
|
+
- 猜错 `SelectField` / `MultiSelectField` / 人员字段的值形态
|
|
22
|
+
- 手写 `searchFieldJson`、`filters`、`order` 字符串
|
|
23
|
+
- 把 `result.data`、`data.data`、`response.data` 混用
|
|
24
|
+
- 把 `formInstId` 错当成 `id`
|
|
25
|
+
|
|
26
|
+
## 模块地图
|
|
27
|
+
|
|
28
|
+
- `sdk.form`
|
|
29
|
+
- 表单详情、增删改、变更记录、simple search、advanced search、导入导出、数据管理页配置
|
|
30
|
+
- `sdk.user`
|
|
31
|
+
- 用户创建、更新、删除、查询、当前用户、按用户名读取、检索、按部门查询
|
|
32
|
+
- `sdk.role`
|
|
33
|
+
- 角色 CRUD、角色用户列表、角色分配、切换当前平台或应用角色
|
|
34
|
+
- `sdk.permission.formGroup`
|
|
35
|
+
- 表单权限组 CRUD、列表、查看态字段权限读取
|
|
36
|
+
- `sdk.permission.pageGroup`
|
|
37
|
+
- 页面权限组 CRUD、列表、当前用户菜单权限读取
|
|
38
|
+
- `sdk.permission.api`
|
|
39
|
+
- API 权限 CRUD、列表、分配、按角色读取、反查角色
|
|
40
|
+
- `sdk.permission.ui`
|
|
41
|
+
- UI 权限 CRUD、列表、分配、读取当前平台或应用权限
|
|
42
|
+
- `sdk.process`
|
|
43
|
+
- 流程实例读取、终止、审批、回调触发
|
|
44
|
+
- `sdk.dataSource.run`
|
|
45
|
+
- 页面便捷层。会读取 `context.page.dataSources`,再路由到 `sdk.form.*`
|
|
46
|
+
- `sdk.request`
|
|
47
|
+
- JSON 请求底层接口
|
|
48
|
+
- `sdk.download`
|
|
49
|
+
- 二进制下载底层接口
|
|
50
|
+
|
|
51
|
+
## React 入口
|
|
52
|
+
|
|
53
|
+
保留的 React API:
|
|
54
|
+
|
|
55
|
+
- `createReactPage`
|
|
56
|
+
- `PageProvider`
|
|
57
|
+
- `usePageSdk`
|
|
58
|
+
- `usePageContext`
|
|
59
|
+
- `useNavigation`
|
|
60
|
+
- `useMessage`
|
|
61
|
+
- `useModal`
|
|
62
|
+
- `usePageProps`
|
|
63
|
+
- `usePageRoute`
|
|
64
|
+
- `useDataSource`
|
|
65
|
+
- `useCurrentUser`
|
|
66
|
+
- `useFormViewPermissions`
|
|
67
|
+
|
|
68
|
+
`useDataSource` 仍然可用,但现在只是 `sdk.dataSource.run` 的轻量封装,不再是 SDK 的核心模型。对于已经声明了 `page.dataSources` 的读页面,它仍然是默认首选,因为它天然带有 `loading / error / refresh`。
|
|
69
|
+
|
|
70
|
+
## 最小示例
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
import type { PageListResult } from "sy-page-sdk"
|
|
74
|
+
import { createReactPage, useDataSource, usePageSdk } from "sy-page-sdk/react"
|
|
75
|
+
|
|
76
|
+
type CustomerRecord = {
|
|
77
|
+
formInstanceId: string
|
|
78
|
+
customerName: string
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function CustomerPage() {
|
|
82
|
+
const sdk = usePageSdk()
|
|
83
|
+
const { data, loading, refresh } = useDataSource<
|
|
84
|
+
PageListResult<CustomerRecord>
|
|
85
|
+
>("customerList", {
|
|
86
|
+
immediate: true,
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
const handleOpenDetail = async () => {
|
|
90
|
+
const firstCustomerId = data?.data[0]?.formInstanceId
|
|
91
|
+
if (!firstCustomerId) return
|
|
92
|
+
|
|
93
|
+
await sdk.form.getDetail({
|
|
94
|
+
formUuid: "FORM_CUSTOMER",
|
|
95
|
+
formInstanceId: firstCustomerId,
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<section>
|
|
101
|
+
<button type="button" onClick={() => refresh()}>
|
|
102
|
+
刷新
|
|
103
|
+
</button>
|
|
104
|
+
<button type="button" onClick={() => void handleOpenDetail()}>
|
|
105
|
+
读取详情
|
|
106
|
+
</button>
|
|
107
|
+
<div>{loading ? "loading" : `count: ${data?.totalCount || 0}`}</div>
|
|
108
|
+
</section>
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export const { mount, unmount, update } = createReactPage(CustomerPage)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Transport 模型
|
|
116
|
+
|
|
117
|
+
页面正常情况下优先使用业务模块;只有在平台新增了 README 尚未封装的接口时,才直接用底层 transport。
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
const profile = await sdk.request<{ id: string; name: string }>({
|
|
121
|
+
path: "/user/current",
|
|
122
|
+
method: "get",
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
const exportFile = await sdk.download({
|
|
126
|
+
path: `/${sdk.context.app.appType}/v1/form/advancedExport.xlsx`,
|
|
127
|
+
method: "get",
|
|
128
|
+
query: {
|
|
129
|
+
formUuid: "FORM_CUSTOMER",
|
|
130
|
+
exportAll: "y",
|
|
131
|
+
},
|
|
132
|
+
})
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`sdk.request` / `sdk.download` 都会:
|
|
136
|
+
|
|
137
|
+
- 自动序列化 `query`
|
|
138
|
+
- 统一兼容平台的 `result` 型和 `data + message` 型 envelope
|
|
139
|
+
- 对下载接口补齐 `blob`、`fileName`、`contentType`
|
|
140
|
+
- 复用宿主运行时的鉴权、报错和 request 客户端能力
|
|
141
|
+
|
|
142
|
+
## 表单查询
|
|
143
|
+
|
|
144
|
+
推荐默认使用 `advancedSearch`,页面代码传结构化对象,SDK 负责把 `filters`、`order`、`searchFieldJson` 等参数序列化为平台要求的格式。
|
|
145
|
+
|
|
146
|
+
如果你还不确定某个组件的真实查询值结构,先看:
|
|
147
|
+
|
|
148
|
+
- [../../docs/field-value-shapes.md](../../docs/field-value-shapes.md)
|
|
149
|
+
- [../../docs/simple-search.md](../../docs/simple-search.md)
|
|
150
|
+
- [../../docs/advanced-search.md](../../docs/advanced-search.md)
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
import type { SearchGroup, SearchSortItem } from "sy-page-sdk"
|
|
154
|
+
|
|
155
|
+
const filters: SearchGroup = {
|
|
156
|
+
logic: "AND",
|
|
157
|
+
rules: [
|
|
158
|
+
{
|
|
159
|
+
key: "status",
|
|
160
|
+
componentName: "SelectField",
|
|
161
|
+
operator: "EQ",
|
|
162
|
+
value: "active",
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
key: "detailList",
|
|
166
|
+
componentName: "SubFormField",
|
|
167
|
+
operator: "EXISTS",
|
|
168
|
+
value: {
|
|
169
|
+
logic: "AND",
|
|
170
|
+
rules: [
|
|
171
|
+
{
|
|
172
|
+
key: "sku",
|
|
173
|
+
componentName: "TextField",
|
|
174
|
+
operator: "CONTAINS",
|
|
175
|
+
value: "license",
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
conditions: [
|
|
182
|
+
{
|
|
183
|
+
logic: "OR",
|
|
184
|
+
rules: [
|
|
185
|
+
{
|
|
186
|
+
key: "level",
|
|
187
|
+
componentName: "SelectField",
|
|
188
|
+
operator: "EQ",
|
|
189
|
+
value: "A",
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
key: "owner",
|
|
193
|
+
componentName: "TextField",
|
|
194
|
+
operator: "EQ",
|
|
195
|
+
value: "Alice",
|
|
196
|
+
},
|
|
197
|
+
],
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const order: SearchSortItem[] = [
|
|
203
|
+
{ id: "modifiedTime", isAsc: "n" },
|
|
204
|
+
{ id: "customerName", isAsc: "y" },
|
|
205
|
+
]
|
|
206
|
+
|
|
207
|
+
const list = await sdk.form.advancedSearch({
|
|
208
|
+
formUuid: "FORM_CUSTOMER",
|
|
209
|
+
currentPage: 1,
|
|
210
|
+
pageSize: 20,
|
|
211
|
+
filters,
|
|
212
|
+
order,
|
|
213
|
+
instanceStatus: "running",
|
|
214
|
+
})
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
simple search 和 ID 搜索仍然保留:
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
const idList = await sdk.form.searchIds<string>({
|
|
221
|
+
formUuid: "FORM_CUSTOMER",
|
|
222
|
+
currentPage: 1,
|
|
223
|
+
pageSize: 50,
|
|
224
|
+
search: {
|
|
225
|
+
logic: "AND",
|
|
226
|
+
rules: [
|
|
227
|
+
{
|
|
228
|
+
key: "customerName",
|
|
229
|
+
componentName: "TextField",
|
|
230
|
+
operator: "CONTAINS",
|
|
231
|
+
value: "星云",
|
|
232
|
+
},
|
|
233
|
+
],
|
|
234
|
+
},
|
|
235
|
+
dynamicOrder: {
|
|
236
|
+
id: "modifiedTime",
|
|
237
|
+
isAsc: "n",
|
|
238
|
+
},
|
|
239
|
+
})
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
列表查询成功后,页面通常这样读取结果:
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
const rows = list.result?.data || []
|
|
246
|
+
const totalCount = list.result?.totalCount || 0
|
|
247
|
+
const currentPage = list.result?.currentPage || 1
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
如果走 `useDataSource("customerList")`,则默认直接读取:
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
const { data } = useDataSource<PageListResult<CustomerRecord>>("customerList")
|
|
254
|
+
|
|
255
|
+
const rows = data?.data || []
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## 用户、角色与权限
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
import { useCurrentUser, useFormViewPermissions } from "sy-page-sdk/react"
|
|
262
|
+
|
|
263
|
+
const { user, isGuest, isInternalUser } = useCurrentUser()
|
|
264
|
+
const viewPermissions = useFormViewPermissions("FORM_CUSTOMER")
|
|
265
|
+
|
|
266
|
+
const canEdit = viewPermissions.can("edit")
|
|
267
|
+
const canDelete = viewPermissions.can("delete")
|
|
268
|
+
const ownerPermission = viewPermissions.getFieldPermission("owner")
|
|
269
|
+
|
|
270
|
+
const users = await sdk.user.search("alice")
|
|
271
|
+
|
|
272
|
+
const role = await sdk.role.create({
|
|
273
|
+
name: "客户经理",
|
|
274
|
+
code: "customer-manager",
|
|
275
|
+
scope: "app",
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
if (users.result?.[0]?.id && role.result?.id) {
|
|
279
|
+
await sdk.role.assignRoles({
|
|
280
|
+
userId: users.result[0].id,
|
|
281
|
+
roleIds: [role.result.id],
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
await sdk.permission.pageGroup.getUserMenuPermissions(sdk.context.app.appType)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const formGroup = await sdk.permission.formGroup.create({
|
|
288
|
+
appType: sdk.context.app.appType,
|
|
289
|
+
formUuid: "FORM_CUSTOMER",
|
|
290
|
+
name: "客户表单查看组",
|
|
291
|
+
type: "view",
|
|
292
|
+
roles: [role.result?.id || ""],
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
const pageGroup = await sdk.permission.pageGroup.list({
|
|
296
|
+
appType: sdk.context.app.appType,
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
const viewFieldPermissions =
|
|
300
|
+
await sdk.permission.formGroup.getViewFieldPermissions({
|
|
301
|
+
formUuid: "FORM_CUSTOMER",
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
const viewPermissionSummary =
|
|
305
|
+
await sdk.permission.formGroup.getViewPermissionSummary({
|
|
306
|
+
formUuid: "FORM_CUSTOMER",
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
const myPagePermissions = await sdk.permission.pageGroup.getUserMenuPermissions(
|
|
310
|
+
sdk.context.app.appType,
|
|
311
|
+
)
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
公开页会通过平台创建或复用游客账号。页面不要自行推导游客规则,优先读取
|
|
315
|
+
`PageContext.user.isGuest` / `PageContext.user.userType`,并通过表单查看态权限汇总控制编辑、删除、变更记录和流程入口。
|
|
316
|
+
|
|
317
|
+
## 消息通知
|
|
318
|
+
|
|
319
|
+
页面即时触发通知时,可以使用按通知类型发送的 API。审批节点、定时提醒、表单事件通知仍应优先由 workflow / automation 承接。
|
|
320
|
+
|
|
321
|
+
```ts
|
|
322
|
+
await sdk.notification.sendByType({
|
|
323
|
+
notificationType: "customer_follow_up",
|
|
324
|
+
recipientId: user.id,
|
|
325
|
+
payload: {
|
|
326
|
+
customerName: "杭州星云科技",
|
|
327
|
+
},
|
|
328
|
+
channels: ["inapp"],
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
await sdk.notification.batchSendByType({
|
|
332
|
+
notificationType: "customer_follow_up",
|
|
333
|
+
recipients: [
|
|
334
|
+
{
|
|
335
|
+
recipientId: "user-1",
|
|
336
|
+
payload: {
|
|
337
|
+
customerName: "杭州星云科技",
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
],
|
|
341
|
+
})
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
不要在页面里硬编码钉钉、第三方待办地址、密钥或模板编号;这些由平台消息配置决定。
|
|
345
|
+
|
|
346
|
+
## 流程能力
|
|
347
|
+
|
|
348
|
+
```ts
|
|
349
|
+
const instance = await sdk.process.getInstance({
|
|
350
|
+
instanceId: "PROC-2026-0001",
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
await sdk.process.approveTask({
|
|
354
|
+
instanceId: "PROC-2026-0001",
|
|
355
|
+
formUuid: "FORM_CUSTOMER",
|
|
356
|
+
action: "approved",
|
|
357
|
+
comments: "审批通过",
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
await sdk.process.terminateInstance({
|
|
361
|
+
processInstanceId: "PROC-2026-0001",
|
|
362
|
+
reason: "测试结束",
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
await sdk.process.triggerCallbackTask({
|
|
366
|
+
taskId: "TASK-CALLBACK-001",
|
|
367
|
+
payload: {
|
|
368
|
+
source: "custom-page",
|
|
369
|
+
},
|
|
370
|
+
})
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## 数据管理页配置
|
|
374
|
+
|
|
375
|
+
```ts
|
|
376
|
+
const current = await sdk.form.getDataManagementConfig({
|
|
377
|
+
formUuid: "FORM_CUSTOMER",
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
await sdk.form.saveDataManagementConfig({
|
|
381
|
+
formUuid: "FORM_CUSTOMER",
|
|
382
|
+
config: {
|
|
383
|
+
...current.result,
|
|
384
|
+
showFields: ["customerName", "owner", "status", "level"],
|
|
385
|
+
sort: [{ id: "modifiedTime", isAsc: "n" }],
|
|
386
|
+
},
|
|
387
|
+
})
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
## 文件流
|
|
391
|
+
|
|
392
|
+
SDK 不会自动触发浏览器下载,只会返回统一的二进制对象,由页面决定何时下载或预览。
|
|
393
|
+
|
|
394
|
+
```ts
|
|
395
|
+
const preview = await sdk.form.importPreview({
|
|
396
|
+
formUuid: "FORM_CUSTOMER",
|
|
397
|
+
fileBase64,
|
|
398
|
+
fileName: "customers.xlsx",
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
const exportFile = await sdk.form.advancedExport({
|
|
402
|
+
formUuid: "FORM_CUSTOMER",
|
|
403
|
+
filters,
|
|
404
|
+
order,
|
|
405
|
+
exportAll: "y",
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
const failedFile = await sdk.form.downloadImportFailed({
|
|
409
|
+
recordId: "IMP-2026-0001",
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
const exportUrl = URL.createObjectURL(exportFile.blob)
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
`PageBinaryResponse` 结构:
|
|
416
|
+
|
|
417
|
+
```ts
|
|
418
|
+
type PageBinaryResponse = {
|
|
419
|
+
blob: Blob
|
|
420
|
+
fileName?: string
|
|
421
|
+
contentType?: string
|
|
422
|
+
headers?: Record<string, string | undefined>
|
|
423
|
+
raw?: unknown
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
## 什么时候用 `useDataSource`
|
|
428
|
+
|
|
429
|
+
适合:
|
|
430
|
+
|
|
431
|
+
- 页面已经有 `page.dataSources`
|
|
432
|
+
- 页面主要是列表、报表、dashboard 这类读场景
|
|
433
|
+
- 只是想复用页面配置里的数据源 key
|
|
434
|
+
- 想要拿到现成的 `loading / error / refresh`
|
|
435
|
+
|
|
436
|
+
不适合:
|
|
437
|
+
|
|
438
|
+
- 用户、角色、权限、流程这类业务模块
|
|
439
|
+
- 需要导入导出、数据管理配置、文件下载
|
|
440
|
+
- 你已经明确知道要调哪个平台能力
|
|
441
|
+
|
|
442
|
+
这类场景直接调用 `usePageSdk()` 后使用 `sdk.form / sdk.user / sdk.role / sdk.permission / sdk.process` 会更清晰。
|
|
443
|
+
|
|
444
|
+
无论使用 `useDataSource` 还是 `usePageSdk()`,正式页面都应展示真实的 empty / error state,不要在运行时回退样例数据。
|