openxiangda 1.0.148 → 1.0.149

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 (26) hide show
  1. package/openxiangda-skills/references/pages/page-sdk.md +19 -0
  2. package/package.json +1 -1
  3. package/packages/sdk/dist/{ProcessPreview-BoblxCUt.d.mts → ProcessPreview-BOCARAvP.d.mts} +9 -1
  4. package/packages/sdk/dist/{ProcessPreview-BoblxCUt.d.ts → ProcessPreview-BOCARAvP.d.ts} +9 -1
  5. package/packages/sdk/dist/components/index.cjs +33 -21
  6. package/packages/sdk/dist/components/index.cjs.map +1 -1
  7. package/packages/sdk/dist/components/index.d.mts +4 -4
  8. package/packages/sdk/dist/components/index.d.ts +4 -4
  9. package/packages/sdk/dist/components/index.mjs +33 -21
  10. package/packages/sdk/dist/components/index.mjs.map +1 -1
  11. package/packages/sdk/dist/{dataManagementApi-BCzfV88G.d.mts → dataManagementApi-CLMqf79O.d.mts} +1 -1
  12. package/packages/sdk/dist/{dataManagementApi-_FFNPv2e.d.ts → dataManagementApi-DhpRKmlp.d.ts} +1 -1
  13. package/packages/sdk/dist/runtime/index.cjs +375 -30
  14. package/packages/sdk/dist/runtime/index.cjs.map +1 -1
  15. package/packages/sdk/dist/runtime/index.d.mts +3 -3
  16. package/packages/sdk/dist/runtime/index.d.ts +3 -3
  17. package/packages/sdk/dist/runtime/index.mjs +377 -32
  18. package/packages/sdk/dist/runtime/index.mjs.map +1 -1
  19. package/packages/sdk/dist/runtime/react.cjs +12 -0
  20. package/packages/sdk/dist/runtime/react.cjs.map +1 -1
  21. package/packages/sdk/dist/runtime/react.d.mts +24 -2
  22. package/packages/sdk/dist/runtime/react.d.ts +24 -2
  23. package/packages/sdk/dist/runtime/react.mjs +12 -0
  24. package/packages/sdk/dist/runtime/react.mjs.map +1 -1
  25. package/templates/openxiangda-react-spa/AGENTS.md +7 -0
  26. package/templates/openxiangda-react-spa/src/pages/defaults/FilePreviewRoutePage.tsx +18 -33
@@ -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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda",
3
- "version": "1.0.148",
3
+ "version": "1.0.149",
4
4
  "description": "OpenXiangda CLI, workspace build tools, runtime SDK, and form components.",
5
5
  "private": false,
6
6
  "bin": {
@@ -269,7 +269,15 @@ interface FormRuntimeApi {
269
269
  success: boolean;
270
270
  }>;
271
271
  createDownloadTicket: (bucketName: string, objectName: string, fileName?: string) => Promise<any>;
272
- createFileAccessTicket: (bucketName: string, objectName: string, fileName?: string, purpose?: 'download' | 'preview' | 'onlyoffice') => Promise<any>;
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
- createFileAccessTicket: (bucketName: string, objectName: string, fileName?: string, purpose?: 'download' | 'preview' | 'onlyoffice') => Promise<any>;
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 && ticketValue?.ticket) {
3439
- const separator = ticketValue.previewPageUrl.includes("?") ? "&" : "?";
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
- const separator = previewPagePath.includes("?") ? "&" : "?";
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 && ticketValue?.ticket) {
3838
- const separator = ticketValue.previewPageUrl.includes("?") ? "&" : "?";
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
- const separator = previewPagePath.includes("?") ? "&" : "?";
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 && ticketValue?.ticket) {
4013
- const separator = ticketValue.previewPageUrl.includes("?") ? "&" : "?";
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
- const separator = previewPagePath.includes("?") ? "&" : "?";
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
  },