openxiangda 1.0.148 → 1.0.150
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 +1 -1
- package/openxiangda-skills/SKILL.md +1 -1
- package/openxiangda-skills/references/automation-v3.md +1 -1
- package/openxiangda-skills/references/connector-resources.md +1 -1
- package/openxiangda-skills/references/pages/page-sdk.md +19 -0
- package/openxiangda-skills/references/resource-manifest-cheatsheet.md +2 -2
- package/openxiangda-skills/references/workflow-v3.md +1 -1
- package/openxiangda-skills/skills/openxiangda-workflow-automation/SKILL.md +1 -1
- package/package.json +1 -1
- package/packages/sdk/dist/{ProcessPreview-BoblxCUt.d.mts → ProcessPreview-BOCARAvP.d.mts} +9 -1
- package/packages/sdk/dist/{ProcessPreview-BoblxCUt.d.ts → ProcessPreview-BOCARAvP.d.ts} +9 -1
- package/packages/sdk/dist/components/index.cjs +33 -21
- package/packages/sdk/dist/components/index.cjs.map +1 -1
- package/packages/sdk/dist/components/index.d.mts +4 -4
- package/packages/sdk/dist/components/index.d.ts +4 -4
- package/packages/sdk/dist/components/index.mjs +33 -21
- package/packages/sdk/dist/components/index.mjs.map +1 -1
- package/packages/sdk/dist/{dataManagementApi-BCzfV88G.d.mts → dataManagementApi-CLMqf79O.d.mts} +1 -1
- package/packages/sdk/dist/{dataManagementApi-_FFNPv2e.d.ts → dataManagementApi-DhpRKmlp.d.ts} +1 -1
- package/packages/sdk/dist/runtime/index.cjs +670 -60
- package/packages/sdk/dist/runtime/index.cjs.map +1 -1
- package/packages/sdk/dist/runtime/index.d.mts +3 -3
- package/packages/sdk/dist/runtime/index.d.ts +3 -3
- package/packages/sdk/dist/runtime/index.mjs +672 -62
- package/packages/sdk/dist/runtime/index.mjs.map +1 -1
- package/packages/sdk/dist/runtime/react.cjs +299 -22
- package/packages/sdk/dist/runtime/react.cjs.map +1 -1
- package/packages/sdk/dist/runtime/react.d.mts +41 -2
- package/packages/sdk/dist/runtime/react.d.ts +41 -2
- package/packages/sdk/dist/runtime/react.mjs +299 -22
- package/packages/sdk/dist/runtime/react.mjs.map +1 -1
- package/templates/openxiangda-react-spa/AGENTS.md +7 -0
- package/templates/openxiangda-react-spa/src/app/router.tsx +6 -1
- package/templates/openxiangda-react-spa/src/pages/defaults/FilePreviewRoutePage.tsx +18 -33
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/js-code-nodes/sync_roles_to_platform/index.ts +1 -1
package/README.md
CHANGED
|
@@ -375,7 +375,7 @@ const PublicAccessError = ({ error }: { error: { message?: string } }) => (
|
|
|
375
375
|
|
|
376
376
|
多表只读查询和固定口径统计优先声明 `src/resources/data-views/*.json` 数据视图,而不是在页面里手写多次单表查询再拼数据。默认 `storageMode: "materialized"` 会创建 PostgreSQL materialized view,适合读多写少和可接受刷新延迟的列表/报表;`storageMode: "live"` 每次查询实时编译逻辑视图,适合强实时但数据量可控的复杂查询。`viewType: "aggregate"` 是统计聚合视图,适合按客户、状态、月份等维度聚合 count/sum/avg/min/max。发布时 CLI 会把 `formCode` 解析为当前 profile 的 `formUuid`;页面通过 `sdk.dataView.query(code, params)` 查询行级视图,通过 `sdk.dataView.stats(code, params)` 查询聚合视图,也可以用 `sdk.dataSource.run()` 路由 `dataView.query` / `dataView.stats`。materialized 模式应为常用筛选、排序、统计维度和时间桶声明 `indexes`,并确认用户能接受的刷新延迟;live 模式忽略 `indexes`,不需要刷新。
|
|
377
377
|
|
|
378
|
-
后端业务逻辑优先声明为 App Function:源码放在 `src/functions/<functionCode>/index.ts`,资源 manifest 放在 `src/resources/functions/<functionCode>.json`。函数运行在 trusted_node 中,通过 `ctx.form.queryOne/queryMany/getById/createOne/updateOne/updateById`、`ctx.dataView`、`ctx.connector`、`ctx.notification`、`ctx.organization`、`ctx.platform.api` 等受控 API 访问平台能力;自动化、流程和运行时接口都可以调用同一个 function
|
|
378
|
+
后端业务逻辑优先声明为 App Function:源码放在 `src/functions/<functionCode>/index.ts`,资源 manifest 放在 `src/resources/functions/<functionCode>.json`。函数运行在 trusted_node 中,通过 `ctx.form.queryOne/queryMany/getById/createOne/updateOne/updateById`、`ctx.dataView`、`ctx.connector`、`ctx.notification`、`ctx.organization`、`ctx.platform.roles`、`ctx.platform.api` 等受控 API 访问平台能力;自动化、流程和运行时接口都可以调用同一个 function。角色查询和角色成员维护优先使用 `ctx.platform.roles.list/findByCode/addUsers/removeUser`,底层 `ctx.platform.api` 返回 HTTP 包装和平台 envelope,需要业务代码自行解包。运行时页面调用会在 `ctx.operator`、`ctx.currentUser`、`ctx.permissions` 中注入可信的 `roleCodes`、`currentRoleCode`、`hasFullAccess`、`isAppAdmin`、`isPlatformAdmin`,敏感动作必须读取这些服务端上下文做授权,不要信任页面 input 里传入的角色字段。`ctx.form.createOne/updateOne/updateById` 是后端受控写入,授权边界是函数 manifest 的 `resources.forms` 绑定和函数调用授权,不是页面用户对目标表单的直接提交入口;报名、签到、福利选择等内部多表写入应走 App Function,不要为了写内部表单给普通用户开放原始 submit 权限。JS_CODE V2 仍兼容,但新逻辑建议写成 function,再由 `function_call` 节点、`sdk.function.invoke(code, { input })` 或 `/:appType/v1/functions/:code/invoke.json` 调用。直接运行时接口默认要求调用者具备应用自动化管理权限;普通页面调用必须在函数 `definitionJson.runtimeInvoke.audience` 声明 `authenticated`、`page_permission_group`、`app_roles` 或 `scope_policy`,`roleCodes` 只用于当前应用角色精确匹配,不支持 `"*"` / `"all-app-roles"`。自动化/流程内部调用走服务端受控上下文。内部业务表单需要关闭原始写入接口时,在表单 settings 中设置 `runtimeWrite.mode="function_only"`。
|
|
379
379
|
|
|
380
380
|
平台部门和账号管理走 app-scoped organization 能力。调用者必须在目标应用拥有 `app:organization:manage`;平台管理员天然可用,普通应用角色需要显式授权。Runtime service principal 不会直接放行,`ctx.organization` 会按真实操作人 / audit actor 校验权限。新接口只提供创建、更新、查询和密码专用操作,不提供删除;`account-update` 不能携带 `password`,重置他人密码必须走 `account-reset-password`,当前用户改密用 SDK / `ctx.organization.accounts.changeMyPassword({ oldPassword, newPassword })`。
|
|
381
381
|
|
|
@@ -164,7 +164,7 @@ When the user provides a root domain such as `https://yida.wisejob.cn/`, use it
|
|
|
164
164
|
- Put engineering-managed resources in `src/resources/` and use `openxiangda resource validate|plan|publish|pull`. `workspace publish` publishes workspace forms/pages first, then runs non-destructive resource upsert. Only pass `--prune` when the user explicitly wants local manifests to delete platform-side extras.
|
|
165
165
|
- For repeated read-only multi-form queries or predefined dashboard metrics, create a data view manifest in `src/resources/data-views/`. Use `storageMode: "materialized"` for refreshed lists/reports that tolerate delay; use `storageMode: "live"` for bounded real-time joins where source changes must appear immediately. Use row views plus `sdk.dataView.query` for joined lists/lookups; use `viewType: "aggregate"` plus `sdk.dataView.stats` for count/sum/avg/min/max statistics. Add `indexes` only for materialized views. Do not use data views for single-form CRUD, simple linkedForm selects, writes, write-back, unbounded realtime joins, or ad-hoc BI. Read `references/data-views.md` before designing one.
|
|
166
166
|
- For external APIs, create a connector manifest in `src/resources/connectors/` and call it from pages through `sdk.connector`; never put third-party API keys in page source.
|
|
167
|
-
- For reusable backend logic shared by pages, automations, and workflows, create an App Function in `src/resources/functions/<functionCode>.json` plus `src/functions/<functionCode>/index.ts`. Call it from pages with `sdk.function.invoke`, from automation/workflow graphs with `function_call`, or from the runtime endpoint. Direct runtime invocation defaults to app automation management permission; ordinary page callers must use `definitionJson.runtimeInvoke.audience` (`authenticated`, `page_permission_group`, `app_roles`, or `scope_policy`). `roleCodes` remains only for current app-role grants; never use `"*"` or `"all-app-roles"` there. Current App Function MVP exposes controlled helpers such as `ctx.resources`, `ctx.form.queryOne/queryMany/getById/createOne/updateOne/updateById`, `ctx.dataView`, `ctx.connector`, `ctx.notification`, `ctx.organization`, and `ctx.platform.api`; it does not expose raw SQL or Redis. Runtime functions also receive trusted role context on `ctx.operator.roleCodes`, `ctx.operator.currentRoleCode`, `ctx.operator.hasFullAccess`, `ctx.currentUser`, and `ctx.permissions`; use these for server-side business authorization. Do not trust role codes sent in page input for sensitive actions. App Function form writes are trusted backend writes governed by declared `resources.forms` and function invocation authorization; do not grant normal users raw submit permission to internal forms just to let `ctx.form.createOne/updateOne/updateById` run. For internal function-only forms, publish form settings with `runtimeWrite.mode="function_only"` to close raw write endpoints.
|
|
167
|
+
- For reusable backend logic shared by pages, automations, and workflows, create an App Function in `src/resources/functions/<functionCode>.json` plus `src/functions/<functionCode>/index.ts`. Call it from pages with `sdk.function.invoke`, from automation/workflow graphs with `function_call`, or from the runtime endpoint. Direct runtime invocation defaults to app automation management permission; ordinary page callers must use `definitionJson.runtimeInvoke.audience` (`authenticated`, `page_permission_group`, `app_roles`, or `scope_policy`). `roleCodes` remains only for current app-role grants; never use `"*"` or `"all-app-roles"` there. Current App Function MVP exposes controlled helpers such as `ctx.resources`, `ctx.form.queryOne/queryMany/getById/createOne/updateOne/updateById`, `ctx.dataView`, `ctx.connector`, `ctx.notification`, `ctx.organization`, `ctx.platform.roles`, and `ctx.platform.api`; it does not expose raw SQL or Redis. Use `ctx.platform.roles.list/findByCode/addUsers/removeUser` for app role lookup and membership changes; raw `ctx.platform.api` returns an HTTP response plus platform envelope, so unwrap it explicitly if you use it. Runtime functions also receive trusted role context on `ctx.operator.roleCodes`, `ctx.operator.currentRoleCode`, `ctx.operator.hasFullAccess`, `ctx.currentUser`, and `ctx.permissions`; use these for server-side business authorization. Do not trust role codes sent in page input for sensitive actions. App Function form writes are trusted backend writes governed by declared `resources.forms` and function invocation authorization; do not grant normal users raw submit permission to internal forms just to let `ctx.form.createOne/updateOne/updateById` run. For internal function-only forms, publish form settings with `runtimeWrite.mode="function_only"` to close raw write endpoints.
|
|
168
168
|
- For app-managed platform departments/accounts, use `sdk.organization.departments.*`, `sdk.organization.accounts.*`, `ctx.organization.departments.*`, or `ctx.organization.accounts.*`. The real current user/audit actor must hold `app:organization:manage` on the app; runtime service principals do not bypass this permission. Do not call legacy `/user` or `/department` write endpoints from apps. Do not include `password` in account updates; reset target accounts with `resetPassword`, and change the current user's password with `changeMyPassword({ oldPassword, newPassword })`.
|
|
169
169
|
- For account, role, data-scope, organization-account, RBAC, or query-param authorization designs, read `references/permission-design-patterns.md` and choose a permission mode before writing forms/pages/resources. Formal backends should use platform roles, page permission groups, form permission groups, public-access grants, and App Function role/scope checks; frontend hiding is display only.
|
|
170
170
|
- When a role such as 校区管理员 will create app roles, assign role members, grant role permissions, maintain permission groups, or manage organization accounts, its role manifest must declare the needed `apiPermissionCodes` such as `app:role:manage`, `app:page-permission-group:manage`, `app:form-permission-group:manage`, and `app:organization:manage`. If that administrator creates another administrator role, the new role must also declare its own `apiPermissionCodes`; permissions are not inherited from the creator.
|
|
@@ -291,7 +291,7 @@ Author source in `src/js-code-nodes/<scriptCode>/index.ts`. AI-authored source m
|
|
|
291
291
|
|
|
292
292
|
The backend runs the snapshot in the trusted Node runtime, applies the node timeout (`30000` ms by default), stores execution logs, and writes the returned value to the node output and `variables.node_<nodeId>`. Scripts may use `export default async function (ctx) {}`, `module.exports = async (ctx) => {}`, `require`, `process`, `Buffer`, arbitrary HTTP, and `platform.api` for `/openxiangda-api/v1`.
|
|
293
293
|
|
|
294
|
-
Runtime context includes `ctx.triggerEvent`, `ctx.formData`, `ctx.workflowData`, `ctx.operator`, `ctx.app`, `ctx.variables`, and `ctx.node`. Prefer the higher-level resource helpers when available: `ctx.resources.resolveForm/resolveDataView/resolveConnector`, `ctx.form.queryOne/queryMany/getById/createOne/updateOne/updateById`, `ctx.dataView.query/stats`, `ctx.connector.call/invoke`, `ctx.notification.*`, and `ctx.platform.api.*`. App Function and trusted-node form writes are backend operations over declared resources, not direct page-user submit access; keep internal forms closed to raw user submission unless the business requires it. Legacy data/process bridge methods remain available as `ctx.methods.queryOneData`, `queryManyData`, `getDataByFormInstanceId`, `updateOneData`, `updateDataByFormInstanceId`, `updateManyData`, `createOneData`, `terminateProcess`, and `getAllParentDepartments`.
|
|
294
|
+
Runtime context includes `ctx.triggerEvent`, `ctx.formData`, `ctx.workflowData`, `ctx.operator`, `ctx.app`, `ctx.variables`, and `ctx.node`. Prefer the higher-level resource helpers when available: `ctx.resources.resolveForm/resolveDataView/resolveConnector`, `ctx.form.queryOne/queryMany/getById/createOne/updateOne/updateById`, `ctx.dataView.query/stats`, `ctx.connector.call/invoke`, `ctx.notification.*`, `ctx.platform.roles.*`, and `ctx.platform.api.*`. Use `ctx.platform.roles.findByCode/addUsers/removeUser` for app role member synchronization; raw `ctx.platform.api.*` returns an HTTP response and platform envelope. App Function and trusted-node form writes are backend operations over declared resources, not direct page-user submit access; keep internal forms closed to raw user submission unless the business requires it. Legacy data/process bridge methods remain available as `ctx.methods.queryOneData`, `queryManyData`, `getDataByFormInstanceId`, `updateOneData`, `updateDataByFormInstanceId`, `updateManyData`, `createOneData`, `terminateProcess`, and `getAllParentDepartments`.
|
|
295
295
|
|
|
296
296
|
Code automation also exposes `ctx.logger.debug/info/warn/error(message, data?)`. AI-authored code should log input parsing, query conditions, external calls, writes, branch decisions, and caught errors. Logs are stored as full raw execution data by default. Use CLI diagnosis commands:
|
|
297
297
|
|
|
@@ -32,7 +32,7 @@ Notification manifests live under `src/resources/notifications/` and contain `te
|
|
|
32
32
|
|
|
33
33
|
Data view manifests live under `src/resources/data-views/` and define read-only joined or aggregate query resources. Use `storageMode: "materialized"` for refreshed lists/reports where lag is acceptable, and `storageMode: "live"` for bounded real-time query shapes. Use row views with `sdk.dataView.query` and aggregate views with `sdk.dataView.stats`. Before generating one, confirm freshness tolerance, query bounds, and indexes for materialized filters/sort fields/dimensions/date buckets. Do not use them for single-form CRUD, simple linkedForm selects, writes, write-back, or ad-hoc BI. See `data-views.md` before generating one.
|
|
34
34
|
|
|
35
|
-
App Function manifests live under `src/resources/functions/`, with source in `src/functions/<functionCode>/index.ts`. Use them for reusable server-side logic that pages, automations, and workflows can share through `sdk.function.invoke` or `function_call` nodes. App Functions expose controlled runtime helpers such as `ctx.resources`, `ctx.form.queryOne/queryMany/getById/createOne/updateOne/updateById`, `ctx.dataView`, `ctx.connector`, `ctx.notification`, `ctx.organization`, and `ctx.platform.api`; they do not expose raw SQL or Redis in the current MVP. Runtime page invocations also expose trusted role context on `ctx.operator.roleCodes`, `ctx.operator.currentRoleCode`, `ctx.operator.hasFullAccess`, `ctx.currentUser`, and `ctx.permissions`; use that context for server-side authorization, and never trust page-submitted role codes for sensitive actions. Page-call grants should use `definitionJson.runtimeInvoke.audience` (`authenticated`, `page_permission_group`, `app_roles`, or `scope_policy`); `roleCodes` is only for current app-role grants and does not support `"*"` or `"all-app-roles"`. App Function form writes are trusted backend writes governed by declared `resources.forms` and function invocation authorization, not by direct page-user submit access to the target form. For internal function-only forms, publish form settings with `runtimeWrite.mode="function_only"` so raw write endpoints are closed. App Function organization writes require `app:organization:manage` on the real operator/audit actor. Use JS_CODE V2 only for node-local workflow/automation scripts.
|
|
35
|
+
App Function manifests live under `src/resources/functions/`, with source in `src/functions/<functionCode>/index.ts`. Use them for reusable server-side logic that pages, automations, and workflows can share through `sdk.function.invoke` or `function_call` nodes. App Functions expose controlled runtime helpers such as `ctx.resources`, `ctx.form.queryOne/queryMany/getById/createOne/updateOne/updateById`, `ctx.dataView`, `ctx.connector`, `ctx.notification`, `ctx.organization`, `ctx.platform.roles`, and `ctx.platform.api`; they do not expose raw SQL or Redis in the current MVP. Use `ctx.platform.roles.list/findByCode/addUsers/removeUser` for app role lookup and membership changes; raw `ctx.platform.api` returns an HTTP response plus platform envelope. Runtime page invocations also expose trusted role context on `ctx.operator.roleCodes`, `ctx.operator.currentRoleCode`, `ctx.operator.hasFullAccess`, `ctx.currentUser`, and `ctx.permissions`; use that context for server-side authorization, and never trust page-submitted role codes for sensitive actions. Page-call grants should use `definitionJson.runtimeInvoke.audience` (`authenticated`, `page_permission_group`, `app_roles`, or `scope_policy`); `roleCodes` is only for current app-role grants and does not support `"*"` or `"all-app-roles"`. App Function form writes are trusted backend writes governed by declared `resources.forms` and function invocation authorization, not by direct page-user submit access to the target form. For internal function-only forms, publish form settings with `runtimeWrite.mode="function_only"` so raw write endpoints are closed. App Function organization writes require `app:organization:manage` on the real operator/audit actor. Use JS_CODE V2 only for node-local workflow/automation scripts.
|
|
36
36
|
|
|
37
37
|
Auth manifests live under `src/resources/auth/`. Use them to enable app-level login methods and bind phone-code/CAS/custom providers to App Functions. Auth provider functions are called only by the platform auth flow. They validate external credentials and return identity assertions such as `phone`, `email`, `externalId`, or `unionId`; they must not issue tokens, set cookies, or mutate platform user/binding tables.
|
|
38
38
|
|
|
@@ -16,6 +16,7 @@ Guidelines:
|
|
|
16
16
|
- Use `sdk.function.invoke(code, { input })` for reusable backend business logic declared under `src/resources/functions/` and `src/functions/`. Do not implement multi-form orchestration, connector fan-out, notification orchestration, or permission-sensitive backend rules directly in a page component.
|
|
17
17
|
- Use `sdk.connector.invoke`, `sdk.connector.call("connector.api")`, or `sdk.connector.download` for external services. The SDK calls the platform runtime connector endpoint; it must not call third-party domains directly.
|
|
18
18
|
- Use `sdk.notification.sendByType` and `batchSendByType` for reusable business messages. Custom notification types must be declared in `src/resources/notifications/` and published with `openxiangda resource publish`.
|
|
19
|
+
- Use `sdk.createFileAccessTicket(bucketName, objectName, fileName, "preview")` when a custom React SPA page needs a user-openable attachment preview link. Open `response.result.previewPageUrl`; do not use `previewUrl` or `/service/file/preview-by-ticket/:ticket` as the page entry.
|
|
19
20
|
- Use `sdk.organization.departments.*` and `sdk.organization.accounts.*` only for admin-style app pages that intentionally manage platform departments/accounts. The current user needs `app:organization:manage` on the app. Do not call legacy `/user` or `/department` write endpoints from pages.
|
|
20
21
|
- Use `sdk.workCenter.listItems({ boxType })` and `sdk.workCenter.getStats()` for current-user work center lists and counts. `boxType` is one of `todo`, `done`, `cc`, or `initiated`. This is the end-user task/handled/cc/initiated surface; do not build todo pages by reading workflow operation logs, automation logs, or raw process-task tables.
|
|
21
22
|
- `sdk.form.create` returns identifiers and generated serial number values only. Treat `formInstId` / `formInstanceId` as the required success contract; `serialNumber` / `serialNumbers` are present only when the form has `SerialNumberField`. Do not expect the save response to contain the full row, formula results, or other server-generated field values. Call `sdk.form.getDetail` explicitly after create when those values are needed.
|
|
@@ -100,6 +101,24 @@ const response = await sdk.dataSource.run("tickets", {
|
|
|
100
101
|
|
|
101
102
|
Data view filters, having, and fields use output aliases such as `customerName` or `ticketCount`, not source references such as `customer.name`. For option-like fields, prefer scalar aliases such as `statusValue` in the view definition and page filters. If the page only needs one form, prefer `sdk.form.advancedSearch`. If the page only needs a simple one-form dropdown, prefer linkedForm options.
|
|
102
103
|
|
|
104
|
+
File preview ticket:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
const ticketResponse = await sdk.createFileAccessTicket(
|
|
108
|
+
"lowcode",
|
|
109
|
+
"attachments/contracts/demo.docx",
|
|
110
|
+
"demo.docx",
|
|
111
|
+
"preview",
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
const previewPageUrl = ticketResponse.result?.previewPageUrl
|
|
115
|
+
if (previewPageUrl) {
|
|
116
|
+
window.open(previewPageUrl, "_blank")
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`previewPageUrl` is the final browser page URL, normally `/view/:appType/file-preview?ticket=...`. `previewUrl` is the file content stream for the preview renderer and should not be used as the user-facing page entry.
|
|
121
|
+
|
|
103
122
|
App Function invocation:
|
|
104
123
|
|
|
105
124
|
```ts
|
|
@@ -646,7 +646,7 @@ const result = await sdk.function.invoke("reservation_reminder_summary", {
|
|
|
646
646
|
}
|
|
647
647
|
```
|
|
648
648
|
|
|
649
|
-
适用边界:可复用后端业务逻辑、跨页面/自动化/流程共享的查询编排、连接器调用、通知编排、受控平台 API 调用。App Function 支持 `ctx.form.queryOne/queryMany/getById/createOne/updateOne/updateById`、`ctx.dataView`、`ctx.connector`、`ctx.notification`、`ctx.platform.api` 等受控 helper,当前 MVP 不暴露原始 SQL/Redis
|
|
649
|
+
适用边界:可复用后端业务逻辑、跨页面/自动化/流程共享的查询编排、连接器调用、通知编排、受控平台 API 调用。App Function 支持 `ctx.form.queryOne/queryMany/getById/createOne/updateOne/updateById`、`ctx.dataView`、`ctx.connector`、`ctx.notification`、`ctx.platform.roles`、`ctx.platform.api` 等受控 helper,当前 MVP 不暴露原始 SQL/Redis。应用角色查询和成员维护优先使用 `ctx.platform.roles.list/findByCode/addUsers/removeUser`;底层 `ctx.platform.api` 返回 HTTP 包装与平台 envelope,需要自行解包。`ctx.form.createOne/updateOne/updateById` 是后端受控写入,权限边界是 function manifest 的 `resources.forms` 和函数调用授权,不是页面用户对目标表单的直接提交权限;内部业务表单不要为了函数写入而开放普通用户原始 submit。运行时接口默认需要应用自动化管理权限;普通用户页面要调用时,用 `definitionJson.runtimeInvoke.audience` 声明 `authenticated`、`page_permission_group`、`app_roles` 或 `scope_policy`,不要把 `"*"`、`"all-app-roles"` 写进 `roleCodes`。自动化/流程内部调用走服务端上下文,不受页面运行时授权限制。若表单只能由函数/流程写入,在 `src/resources/settings/forms/<formCode>.json` 设置 `runtimeWrite.mode="function_only"` 关闭原始写入接口。
|
|
650
650
|
|
|
651
651
|
## 5. Workflow — `src/resources/workflows/<code>/workflow.json`(manifest)+ `src/workflows/<code>/workflow.ts`(代码优先)
|
|
652
652
|
|
|
@@ -746,7 +746,7 @@ export default async function (ctx) {
|
|
|
746
746
|
|
|
747
747
|
构建:`pnpm build-js-code --script sync_customer`(先 `tsc` 再打包到 `dist/js-code-nodes/<code>/index.cjs`)。CLI validate/create/publish 时会上传快照、用 `{ bucketName, objectName, sha256 }` 替换 `sourceFile.localPath`。
|
|
748
748
|
|
|
749
|
-
`ctx.methods.*` 仍可用于兼容旧脚本;新脚本优先使用 `ctx.resources`、`ctx.form.queryOne/queryMany/getById/createOne/updateOne/updateById`、`ctx.dataView`、`ctx.connector`、`ctx.notification` 和 `ctx.platform.api`。
|
|
749
|
+
`ctx.methods.*` 仍可用于兼容旧脚本;新脚本优先使用 `ctx.resources`、`ctx.form.queryOne/queryMany/getById/createOne/updateOne/updateById`、`ctx.dataView`、`ctx.connector`、`ctx.notification`、`ctx.platform.roles` 和 `ctx.platform.api`。
|
|
750
750
|
|
|
751
751
|
## 8. Role — `src/resources/roles/<code>.json`
|
|
752
752
|
|
|
@@ -389,7 +389,7 @@ For reusable backend logic that should be shared by pages, automations, and work
|
|
|
389
389
|
|
|
390
390
|
Use JS_CODE V2 when the script is local to one workflow node.
|
|
391
391
|
|
|
392
|
-
Scripts can export `export default async function (ctx) {}` or `module.exports = async (ctx) => {}`. The runtime exposes `ctx.triggerEvent`, `ctx.formData`, `ctx.workflowData`, `ctx.operator`, `ctx.app`, `ctx.variables`, `ctx.resources`, `ctx.form`, `ctx.dataView`, `ctx.connector`, `ctx.notification`, `ctx.organization`, `ctx.platform.api.*`, `ctx.utils`, `require`, `process`, and `Buffer`. Prefer `ctx.resources.resolveForm/resolveDataView/resolveConnector`, `ctx.form.queryOne/queryMany/getById/createOne/updateOne/updateById`, `ctx.dataView.query/stats`,
|
|
392
|
+
Scripts can export `export default async function (ctx) {}` or `module.exports = async (ctx) => {}`. The runtime exposes `ctx.triggerEvent`, `ctx.formData`, `ctx.workflowData`, `ctx.operator`, `ctx.app`, `ctx.variables`, `ctx.resources`, `ctx.form`, `ctx.dataView`, `ctx.connector`, `ctx.notification`, `ctx.organization`, `ctx.platform.roles.*`, `ctx.platform.api.*`, `ctx.utils`, `require`, `process`, and `Buffer`. Prefer `ctx.resources.resolveForm/resolveDataView/resolveConnector`, `ctx.form.queryOne/queryMany/getById/createOne/updateOne/updateById`, `ctx.dataView.query/stats`, `ctx.connector.call/invoke`, and `ctx.platform.roles.findByCode/addUsers/removeUser` for platform-side work. Use `ctx.organization.departments.*` and `ctx.organization.accounts.*` only for intentional organization-management flows; the real operator/audit actor must hold `app:organization:manage`. App Function and trusted-node form writes are backend operations over declared resources, not direct page-user submit access; keep internal forms closed to raw user submission unless the business requires it. Raw `ctx.platform.api.*` calls return HTTP response plus platform envelope. Legacy data/process bridge methods remain available as `ctx.methods.queryOneData/queryManyData/getDataByFormInstanceId/updateOneData/updateDataByFormInstanceId/updateManyData/createOneData/terminateProcess/getAllParentDepartments`.
|
|
393
393
|
|
|
394
394
|
Example `src/js-code-nodes/sync_customer/index.ts`:
|
|
395
395
|
|
|
@@ -140,7 +140,7 @@ Inside the TypeScript script, prefer `export default async function (ctx) {}` or
|
|
|
140
140
|
- Context: `ctx.triggerEvent`, `ctx.formData`, `ctx.workflowData`, `ctx.operator`, `ctx.app`, `ctx.variables`, and `ctx.node`.
|
|
141
141
|
- Data/process methods: `ctx.methods.queryOneData`, `queryManyData`, `getDataByFormInstanceId`, `updateOneData`, `updateDataByFormInstanceId`, `updateManyData`, `createOneData`, `terminateProcess`, and `getAllParentDepartments`.
|
|
142
142
|
- Notification bridge: `ctx.notification.sendByType`, `batchSendByType`, `findConfig`, and `previewTemplate`. Declare templates in `src/resources/notifications/` before using custom `notificationType`.
|
|
143
|
-
- Platform API bridge: `ctx.platform.api.get/post/put/patch/delete/request` for `/openxiangda-api/v1`.
|
|
143
|
+
- Platform API bridge: `ctx.platform.api.get/post/put/patch/delete/request` for `/openxiangda-api/v1`; role helpers: `ctx.platform.roles.list/findByCode/addUsers/removeUser`.
|
|
144
144
|
- Resource helpers: `ctx.resources.resolveForm/resolveDataView/resolveConnector`, `ctx.form.queryOne/queryMany/getById/createOne/updateOne/updateById`, `ctx.dataView.query/stats`, and `ctx.connector.call/invoke`.
|
|
145
145
|
- Node runtime helpers: `require`, `process`, `Buffer`, `ctx.utils`, `ctx.utils.http`, and `ctx.console`.
|
|
146
146
|
|
package/package.json
CHANGED
|
@@ -269,7 +269,15 @@ interface FormRuntimeApi {
|
|
|
269
269
|
success: boolean;
|
|
270
270
|
}>;
|
|
271
271
|
createDownloadTicket: (bucketName: string, objectName: string, fileName?: string) => Promise<any>;
|
|
272
|
-
|
|
272
|
+
/**
|
|
273
|
+
* Creates a protected file access ticket.
|
|
274
|
+
*
|
|
275
|
+
* previewPageUrl is the page URL that can be opened directly.
|
|
276
|
+
* previewUrl is the raw file content stream URL for iframe/img/PDF viewers.
|
|
277
|
+
*/
|
|
278
|
+
createFileAccessTicket: (bucketName: string, objectName: string, fileName?: string, purpose?: 'download' | 'preview' | 'onlyoffice', options?: {
|
|
279
|
+
appType?: string;
|
|
280
|
+
}) => Promise<any>;
|
|
273
281
|
getUserById: (id: string) => Promise<any>;
|
|
274
282
|
getUserList: (params?: Record<string, any>) => Promise<any[]>;
|
|
275
283
|
getDepartmentRoots: () => Promise<any[]>;
|
|
@@ -269,7 +269,15 @@ interface FormRuntimeApi {
|
|
|
269
269
|
success: boolean;
|
|
270
270
|
}>;
|
|
271
271
|
createDownloadTicket: (bucketName: string, objectName: string, fileName?: string) => Promise<any>;
|
|
272
|
-
|
|
272
|
+
/**
|
|
273
|
+
* Creates a protected file access ticket.
|
|
274
|
+
*
|
|
275
|
+
* previewPageUrl is the page URL that can be opened directly.
|
|
276
|
+
* previewUrl is the raw file content stream URL for iframe/img/PDF viewers.
|
|
277
|
+
*/
|
|
278
|
+
createFileAccessTicket: (bucketName: string, objectName: string, fileName?: string, purpose?: 'download' | 'preview' | 'onlyoffice', options?: {
|
|
279
|
+
appType?: string;
|
|
280
|
+
}) => Promise<any>;
|
|
273
281
|
getUserById: (id: string) => Promise<any>;
|
|
274
282
|
getUserList: (params?: Record<string, any>) => Promise<any[]>;
|
|
275
283
|
getDepartmentRoots: () => Promise<any[]>;
|
|
@@ -3317,6 +3317,11 @@ function canPreview(item) {
|
|
|
3317
3317
|
const ext = getFileExtension(item.name);
|
|
3318
3318
|
return isImageFile(item.name, item.contentType) || ["mp4", "webm", "ogg", "mov", "m4v"].includes(ext) || PREVIEW_PAGE_EXTENSIONS.includes(ext);
|
|
3319
3319
|
}
|
|
3320
|
+
function appendTicketIfMissing(url, ticket) {
|
|
3321
|
+
if (!url || !ticket || /[?&]ticket=/.test(url)) return url;
|
|
3322
|
+
const separator = url.includes("?") ? "&" : "?";
|
|
3323
|
+
return `${url}${separator}ticket=${encodeURIComponent(ticket)}`;
|
|
3324
|
+
}
|
|
3320
3325
|
function AttachmentFieldPC({
|
|
3321
3326
|
fieldId,
|
|
3322
3327
|
behavior,
|
|
@@ -3432,16 +3437,15 @@ function AttachmentFieldPC({
|
|
|
3432
3437
|
item.bucketName || bucketName,
|
|
3433
3438
|
item.objectName,
|
|
3434
3439
|
item.name,
|
|
3435
|
-
"preview"
|
|
3440
|
+
"preview",
|
|
3441
|
+
{ appType: item.appType || config.appType }
|
|
3436
3442
|
);
|
|
3437
3443
|
const ticketValue = typeof ticket === "string" ? { previewUrl: ticket } : ticket;
|
|
3438
|
-
if (ticketValue?.previewPageUrl
|
|
3439
|
-
|
|
3440
|
-
return `${ticketValue.previewPageUrl}${separator}ticket=${encodeURIComponent(ticketValue.ticket)}`;
|
|
3444
|
+
if (ticketValue?.previewPageUrl) {
|
|
3445
|
+
return appendTicketIfMissing(ticketValue.previewPageUrl, ticketValue.ticket);
|
|
3441
3446
|
}
|
|
3442
3447
|
if (ticketValue?.ticket && PREVIEW_PAGE_EXTENSIONS.includes(getFileExtension(item.name))) {
|
|
3443
|
-
|
|
3444
|
-
return `${previewPagePath}${separator}ticket=${encodeURIComponent(ticketValue.ticket)}`;
|
|
3448
|
+
return appendTicketIfMissing(previewPagePath, ticketValue.ticket);
|
|
3445
3449
|
}
|
|
3446
3450
|
return ticketValue?.previewUrl || ticketValue?.url || "";
|
|
3447
3451
|
}
|
|
@@ -3683,6 +3687,11 @@ function canPreview2(item) {
|
|
|
3683
3687
|
const ext = getFileExtension(item.name);
|
|
3684
3688
|
return isImageFile(item.name, item.contentType) || ["mp4", "webm", "ogg", "mov", "m4v"].includes(ext) || PREVIEW_PAGE_EXTENSIONS2.includes(ext);
|
|
3685
3689
|
}
|
|
3690
|
+
function appendTicketIfMissing2(url, ticket) {
|
|
3691
|
+
if (!url || !ticket || /[?&]ticket=/.test(url)) return url;
|
|
3692
|
+
const separator = url.includes("?") ? "&" : "?";
|
|
3693
|
+
return `${url}${separator}ticket=${encodeURIComponent(ticket)}`;
|
|
3694
|
+
}
|
|
3686
3695
|
function AttachmentFieldMobile({
|
|
3687
3696
|
fieldId,
|
|
3688
3697
|
behavior,
|
|
@@ -3831,16 +3840,15 @@ function AttachmentFieldMobile({
|
|
|
3831
3840
|
item.bucketName || bucketName,
|
|
3832
3841
|
item.objectName,
|
|
3833
3842
|
item.name,
|
|
3834
|
-
"preview"
|
|
3843
|
+
"preview",
|
|
3844
|
+
{ appType: item.appType || config.appType }
|
|
3835
3845
|
);
|
|
3836
3846
|
const ticketValue = typeof ticket === "string" ? { previewUrl: ticket } : ticket;
|
|
3837
|
-
if (ticketValue?.previewPageUrl
|
|
3838
|
-
|
|
3839
|
-
return `${ticketValue.previewPageUrl}${separator}ticket=${encodeURIComponent(ticketValue.ticket)}`;
|
|
3847
|
+
if (ticketValue?.previewPageUrl) {
|
|
3848
|
+
return appendTicketIfMissing2(ticketValue.previewPageUrl, ticketValue.ticket);
|
|
3840
3849
|
}
|
|
3841
3850
|
if (ticketValue?.ticket && PREVIEW_PAGE_EXTENSIONS2.includes(getFileExtension(item.name))) {
|
|
3842
|
-
|
|
3843
|
-
return `${previewPagePath}${separator}ticket=${encodeURIComponent(ticketValue.ticket)}`;
|
|
3851
|
+
return appendTicketIfMissing2(previewPagePath, ticketValue.ticket);
|
|
3844
3852
|
}
|
|
3845
3853
|
return ticketValue?.previewUrl || ticketValue?.url || "";
|
|
3846
3854
|
}
|
|
@@ -3971,6 +3979,11 @@ function canPreview3(item) {
|
|
|
3971
3979
|
const ext = getFileExtension(item.name);
|
|
3972
3980
|
return isImageFile(item.name, item.contentType) || ["mp4", "webm", "ogg", "mov", "m4v"].includes(ext) || PREVIEW_PAGE_EXTENSIONS3.includes(ext);
|
|
3973
3981
|
}
|
|
3982
|
+
function appendTicketIfMissing3(url, ticket) {
|
|
3983
|
+
if (!url || !ticket || /[?&]ticket=/.test(url)) return url;
|
|
3984
|
+
const separator = url.includes("?") ? "&" : "?";
|
|
3985
|
+
return `${url}${separator}ticket=${encodeURIComponent(ticket)}`;
|
|
3986
|
+
}
|
|
3974
3987
|
function AttachmentFieldReadonly({
|
|
3975
3988
|
fieldId,
|
|
3976
3989
|
readonlyClassName,
|
|
@@ -3981,7 +3994,7 @@ function AttachmentFieldReadonly({
|
|
|
3981
3994
|
showFileTypeBadge = false,
|
|
3982
3995
|
previewPagePath = "/file-preview"
|
|
3983
3996
|
}) {
|
|
3984
|
-
const { formData, api } = useFormContext();
|
|
3997
|
+
const { formData, api, config } = useFormContext();
|
|
3985
3998
|
const { isMobile } = useDeviceDetect();
|
|
3986
3999
|
const value = formData[fieldId] ?? [];
|
|
3987
4000
|
if (value.length === 0) {
|
|
@@ -4006,15 +4019,14 @@ function AttachmentFieldReadonly({
|
|
|
4006
4019
|
item.bucketName || bucketName,
|
|
4007
4020
|
item.objectName,
|
|
4008
4021
|
item.name,
|
|
4009
|
-
"preview"
|
|
4022
|
+
"preview",
|
|
4023
|
+
{ appType: item.appType || config.appType }
|
|
4010
4024
|
);
|
|
4011
4025
|
const ticketValue = typeof ticket === "string" ? { previewUrl: ticket } : ticket;
|
|
4012
|
-
if (ticketValue?.previewPageUrl
|
|
4013
|
-
|
|
4014
|
-
url = `${ticketValue.previewPageUrl}${separator}ticket=${encodeURIComponent(ticketValue.ticket)}`;
|
|
4026
|
+
if (ticketValue?.previewPageUrl) {
|
|
4027
|
+
url = appendTicketIfMissing3(ticketValue.previewPageUrl, ticketValue.ticket);
|
|
4015
4028
|
} else if (ticketValue?.ticket && PREVIEW_PAGE_EXTENSIONS3.includes(getFileExtension(item.name))) {
|
|
4016
|
-
|
|
4017
|
-
url = `${previewPagePath}${separator}ticket=${encodeURIComponent(ticketValue.ticket)}`;
|
|
4029
|
+
url = appendTicketIfMissing3(previewPagePath, ticketValue.ticket);
|
|
4018
4030
|
} else {
|
|
4019
4031
|
url = getTicketUrl(ticketValue, url);
|
|
4020
4032
|
}
|
|
@@ -10817,11 +10829,11 @@ function createFormRuntimeApi(config) {
|
|
|
10817
10829
|
});
|
|
10818
10830
|
return normalizeFileTicketResult(baseUrl, response.data || response.result);
|
|
10819
10831
|
},
|
|
10820
|
-
createFileAccessTicket: async (bucketName, objectName, fileName, purpose = "preview") => {
|
|
10832
|
+
createFileAccessTicket: async (bucketName, objectName, fileName, purpose = "preview", options = {}) => {
|
|
10821
10833
|
const response = await request({
|
|
10822
10834
|
url: "/file/access-ticket",
|
|
10823
10835
|
method: "post",
|
|
10824
|
-
data: { bucketName, objectName, fileName, purpose }
|
|
10836
|
+
data: { bucketName, objectName, fileName, purpose, appType: options.appType }
|
|
10825
10837
|
});
|
|
10826
10838
|
return normalizeFileTicketResult(baseUrl, response.data || response.result);
|
|
10827
10839
|
},
|