openxiangda 1.0.153 → 1.0.155
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/openxiangda-skills/SKILL.md +1 -0
- package/openxiangda-skills/references/component-guide.md +2 -0
- package/openxiangda-skills/references/pages/page-sdk.md +19 -0
- package/openxiangda-skills/references/troubleshooting.md +18 -5
- package/openxiangda-skills/skills/openxiangda-form/SKILL.md +1 -0
- package/openxiangda-skills/skills/openxiangda-page/SKILL.md +1 -0
- package/package.json +1 -1
- package/packages/sdk/dist/components/index.cjs +41 -1
- package/packages/sdk/dist/components/index.cjs.map +1 -1
- package/packages/sdk/dist/components/index.d.mts +2 -1
- package/packages/sdk/dist/components/index.d.ts +2 -1
- package/packages/sdk/dist/components/index.mjs +41 -1
- package/packages/sdk/dist/components/index.mjs.map +1 -1
- package/packages/sdk/dist/runtime/index.cjs +846 -796
- package/packages/sdk/dist/runtime/index.cjs.map +1 -1
- package/packages/sdk/dist/runtime/index.d.mts +1 -1
- package/packages/sdk/dist/runtime/index.d.ts +1 -1
- package/packages/sdk/dist/runtime/index.mjs +232 -182
- package/packages/sdk/dist/runtime/index.mjs.map +1 -1
- package/packages/sdk/dist/runtime/react.cjs +274 -224
- package/packages/sdk/dist/runtime/react.cjs.map +1 -1
- package/packages/sdk/dist/runtime/react.d.mts +10 -2
- package/packages/sdk/dist/runtime/react.d.ts +10 -2
- package/packages/sdk/dist/runtime/react.mjs +146 -96
- package/packages/sdk/dist/runtime/react.mjs.map +1 -1
- package/templates/openxiangda-react-spa/AGENTS.md +1 -0
|
@@ -59,6 +59,7 @@ OpenXiangda supports two workspace modes. Classic `sy-lowcode-app-workspace` pub
|
|
|
59
59
|
- ✅ In React SPA apps, keep imports on public package entrypoints such as `openxiangda`, `openxiangda/runtime`, and `openxiangda/runtime/react`. The default template already groups large vendor chunks; if a bundle is too large, use route-level lazy loading and Vite `manualChunks`, not `openxiangda/packages/sdk/dist/...` internal paths.
|
|
60
60
|
- ✅ For image thumbnails or lightweight previews, use `imageCompression` on `ImageField` or `AttachmentField`. Keep the original `url` for the source file and use `thumbUrl` / `previewUrl` / `variants` for smaller images.
|
|
61
61
|
- ✅ In form context, let `ImageField` and `AttachmentField` own preview UX. In standalone React SPA pages, use `AttachmentPreviewList`, `ImagePreviewGrid`, or `useFilePreview` from `openxiangda/runtime/react`; do not create a fake form context or import internal `FilePreviewContent` / `useFilePreviewController`. All paths must honor platform capability checks, and apps must not maintain local previewable-extension allowlists.
|
|
62
|
+
- ✅ When a custom editable React SPA surface genuinely needs `FormProvider`, set `config.api` from `usePageFormRuntimeApi()`; use `createPageFormRuntimeApi(sdk)` outside hooks. Never implement the bridge as `api.request: config => sdk.request(...)`, because JSON transport drops `responseType: "blob"` and breaks binary attachment previews.
|
|
62
63
|
- ✅ For data delivery with files, use `openxiangda form export <formCode> --mode xlsx|xlsx-images|package --profile <name>`. `package` writes a zip with `data.xlsx`, `attachments/`, and `manifest.json`; do not hand-write export URLs or stream binary content to stdout.
|
|
63
64
|
- ✅ List pages, linked options, remote selectors, and report drill-downs must use paginated server-side queries with explicit searchable fields. Do not fetch a large page and filter in local state.
|
|
64
65
|
- ✅ Treat workflow forms as an exception. Ordinary ticket/order/task/asset lifecycles use status fields, state machines, action logs, permissions, and automations; workflows are for real approval tasks.
|
|
@@ -89,6 +89,8 @@ OSS 浏览器直传所需的 CORS 规则在 `src/resources/storage/<code>.json`
|
|
|
89
89
|
|
|
90
90
|
预览能力也由运行时统一接管:表单上下文使用 `ImageField` / `AttachmentField`;自定义 React SPA 页面展示任意业务数据中的只读附件时,从 `openxiangda/runtime/react` 使用 `ImagePreviewGrid` / `AttachmentPreviewList`,或使用 `useFilePreview({ items })` 接入自定义卡片、表格和详情操作。不要为只读附件伪造 `FormProvider`。
|
|
91
91
|
|
|
92
|
+
自定义编辑页确需嵌入平台字段时,使用 `usePageFormRuntimeApi()` 生成 `FormProvider` 的 `config.api`;非 Hook 场景使用 `createPageFormRuntimeApi(sdk)`。不要把所有请求手工转给 `sdk.request()`,因为附件预览的 `responseType: "blob"` 必须走 PageSdk 二进制下载通道。
|
|
93
|
+
|
|
92
94
|
```tsx
|
|
93
95
|
import { AttachmentPreviewList, ImagePreviewGrid } from "openxiangda/runtime/react"
|
|
94
96
|
|
|
@@ -134,6 +134,25 @@ export function RecordFiles({ record }) {
|
|
|
134
134
|
|
|
135
135
|
These APIs must render below `OpenXiangdaProvider` and `OpenXiangdaPageProvider`. `useFilePreview` returns `canPreview`, `getCapability`, `open`, `download`, `isOpening`, and `host`. Render `host` once in the component tree. Do not import the internal `FilePreviewContent` or `useFilePreviewController`, create a fake `FormProvider`, or keep a local previewable-extension list.
|
|
136
136
|
|
|
137
|
+
For a real custom edit surface that embeds platform form fields, adapt PageSdk with the public form runtime API instead of hand-writing a request bridge:
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
import { AttachmentField, FormProvider } from "openxiangda"
|
|
141
|
+
import { usePageFormRuntimeApi } from "openxiangda/runtime/react"
|
|
142
|
+
|
|
143
|
+
const api = usePageFormRuntimeApi()
|
|
144
|
+
|
|
145
|
+
<FormProvider
|
|
146
|
+
config={{ api, appType, formUuid: "training_lesson", mode: "edit" }}
|
|
147
|
+
schema={schema}
|
|
148
|
+
initialValues={values}
|
|
149
|
+
>
|
|
150
|
+
<AttachmentField fieldId="materials" label="Materials" />
|
|
151
|
+
</FormProvider>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Use `createPageFormRuntimeApi(sdk)` when hooks are unavailable. Both APIs preserve JSON envelopes and route `responseType: "blob"` through `sdk.transport.download`. Never forward every form request to `sdk.request`; that method is the JSON channel and cannot return the Blob required by Office, image, audio, and video preview parsers.
|
|
155
|
+
|
|
137
156
|
Shareable file preview ticket:
|
|
138
157
|
|
|
139
158
|
```ts
|
|
@@ -14,12 +14,25 @@
|
|
|
14
14
|
| 6 | Tailwind 样式被清除(PurgeCSS) | 生产构建后样式丢失 |
|
|
15
15
|
| 7 | React 多实例冲突 | `Invalid hook call` |
|
|
16
16
|
| 8 | 移动端 antd-mobile 组件样式不生效 | 组件渲染但无样式 |
|
|
17
|
-
| 9 | 数据格式理解错误导致展示异常 | 页面显示 `[object Object]` |
|
|
18
|
-
| 10 | workspace publish 环境变量缺失 | `OPENXIANGDA_ACCESS_TOKEN is not set` |
|
|
17
|
+
| 9 | 数据格式理解错误导致展示异常 | 页面显示 `[object Object]` |
|
|
18
|
+
| 10 | workspace publish 环境变量缺失 | `OPENXIANGDA_ACCESS_TOKEN is not set` |
|
|
19
|
+
| 11 | DOCX 二进制契约异常 | 真实字节不是 `PK`,或自定义 FormProvider 把 Blob 请求错误转给 JSON 通道 |
|
|
19
20
|
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
## 问题:
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 问题:DOCX 响应是 Base64 文本
|
|
24
|
+
|
|
25
|
+
**症状**:ticket metadata 正常返回 `renderMode: "docx-html"` 和 `canPreview: true`,`/service/file/preview-by-ticket/:ticket` 也是 HTTP 200,但 Word 预览仍然解析失败或提示“文件内容响应不是二进制数据”。
|
|
26
|
+
|
|
27
|
+
**判定**:DOCX 本质是 ZIP,真实原始字节应以 `PK`(十六进制 `50 4B 03 04`)开头。Chrome DevTools 可能把二进制响应显示成 `UEsDB...`,这只是展示形式;使用 `response.arrayBuffer()`、下载文件或服务端探针检查真实前四字节。只有真实字节是 ASCII `UEsDB` 时,才是 Base64 包装对象。
|
|
28
|
+
|
|
29
|
+
**解决方案**:若真实字节是 `UEsDB`,升级平台服务端和 OpenXiangda SDK,使用严格 ZIP 校验后的兼容解码。若真实字节已经是 `PK`,检查自定义编辑页的 `FormProvider`:`config.api` 必须来自 `usePageFormRuntimeApi()`,非 Hook 场景使用 `createPageFormRuntimeApi(sdk)`。不要手写 `api.request: config => sdk.request(...)`,也不要在业务页面自行调用 `atob`。
|
|
30
|
+
|
|
31
|
+
**验证方式**:真实响应字节以 `PK` 开头;组件的 Blob 请求进入 `sdk.transport.download`,并最终向 DOCX 渲染器传入 `Blob`。若下载后的文件真实字节仍是 ASCII `UEsDB`,再检查服务端版本和多节点部署一致性。
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 问题:antd/antd-mobile 弹层样式丢失
|
|
23
36
|
|
|
24
37
|
**症状**:Select / Dropdown / Popup / Modal 等组件的弹出层没有样式,直接跑到屏幕外,或呈现为无样式的裸 HTML。
|
|
25
38
|
|
|
@@ -164,6 +164,7 @@ Read these references only when writing or reviewing schema:
|
|
|
164
164
|
- Always provide `options` for option components. `SelectField`, `MultiSelectField`, `RadioField`, `CheckboxField`, and `CascadeSelectField` must not be emitted with `options` undefined. See `references/component-guide.md` for the full list and `references/platform-data-model.md` for the `{label, value}` storage contract.
|
|
165
165
|
- Do not emit raw native form controls in app/workspace source. For basic entry use `TextField`, `TextAreaField`, `NumberField`, `DateField`, `SelectField`, `RadioField`, etc.; for platform-specific entry use `UserSelectField`, `DepartmentSelectField`, `AttachmentField`, `ImageField`, `EditorField`, `DigitalSignatureField`, and `LocationField`.
|
|
166
166
|
- For image thumbnails or lightweight detail previews, configure `imageCompression` on `ImageField` or `AttachmentField`. The submitted value still keeps the original `url`; compressed variants are written to `thumbUrl`, `previewUrl`, and `variants`.
|
|
167
|
+
- React SPA custom edit surfaces that render `FormProvider` must set `config.api` from `usePageFormRuntimeApi()` or `createPageFormRuntimeApi(sdk)`. Do not map all field requests to `sdk.request`, because attachment and image preview require the PageSdk Blob download channel.
|
|
167
168
|
- For OSS-backed image or attachment fields, declare a storage resource first, then set `uploadProvider: "oss"` and `storageCode`. Do not hardcode OSS URLs or credentials in schema/page source; if compressed output uses `webp`, storage `allowedExtensions` must include `webp`.
|
|
168
169
|
- Workflow form pages use the same workspace form structure plus workflow v3 configuration. In legacy workspaces, publish the form page bundle through workspace publish before treating it as complete. In React SPA workspaces, `workspace publish --form` syncs schema only, and the SPA default process pages render through `runtime deploy`.
|
|
169
170
|
- After editing one form, use `workspace publish --form <formCode>` or preview `--changed --dry-run`; do not run full workspace publish by default.
|
|
@@ -128,5 +128,6 @@ Read these references only when editing page code:
|
|
|
128
128
|
- Pick components per `references/component-guide.md`: prefer the platform component, fall back to Ant Design / antd-mobile wrappers, and only build a custom component when neither fits. In app/workspace page code, do not emit raw `<input>`, `<select>`, `<textarea>`, file inputs, hand-written pickers, or hand-written uploaders.
|
|
129
129
|
- When a page misbehaves (lost styles, `options is undefined`, option labels showing raw values, etc.), consult `references/troubleshooting.md` before patching symptoms.
|
|
130
130
|
- For custom portal pages and business modals, do not temporarily embed a single `FormProvider` field component from the standard form runtime. If a standard component is required, navigate to a full standard form page or render a complete `StandardFormPage` inside a dedicated carrier route.
|
|
131
|
+
- If a complete custom edit surface intentionally embeds platform fields in `FormProvider`, set `config.api` from `usePageFormRuntimeApi()` (or `createPageFormRuntimeApi(sdk)` outside hooks). Never hand-write a JSON-only `sdk.request` bridge; it breaks Blob preview and download contracts.
|
|
131
132
|
- Keep PC and mobile portal styles separate. Do not share one form UI implementation across both viewports unless the surrounding shell, modal layer, date picker, and bottom-sheet behavior have been tested in both contexts.
|
|
132
133
|
- After introducing Ant Design overlays, date/datetime fields, or mobile bottom-sheet selectors, verify that global button, modal, picker, and sheet styles do not leak into the host business page.
|
package/package.json
CHANGED
|
@@ -139,6 +139,7 @@ __export(components_exports, {
|
|
|
139
139
|
normalizeDataManagementList: () => normalizeDataManagementList,
|
|
140
140
|
normalizeFieldBehaviors: () => normalizeFieldBehaviors,
|
|
141
141
|
normalizeOperation: () => normalizeOperation,
|
|
142
|
+
normalizePreviewBlobContent: () => normalizePreviewBlobContent,
|
|
142
143
|
normalizePreviewBlobResponse: () => normalizePreviewBlobResponse,
|
|
143
144
|
normalizeRichTextHtml: () => normalizeRichTextHtml,
|
|
144
145
|
prepareFilePreview: () => prepareFilePreview,
|
|
@@ -3391,6 +3392,45 @@ var normalizePreviewBlobResponse = (response) => {
|
|
|
3391
3392
|
if (blob) return blob;
|
|
3392
3393
|
throw new Error(getBinaryResponseMessage(payload) || "\u6587\u4EF6\u5185\u5BB9\u54CD\u5E94\u4E0D\u662F\u4E8C\u8FDB\u5236\u6570\u636E");
|
|
3393
3394
|
};
|
|
3395
|
+
var isZipBytes = (bytes) => bytes.length >= 4 && bytes[0] === 80 && bytes[1] === 75 && (bytes[2] === 3 && bytes[3] === 4 || bytes[2] === 5 && bytes[3] === 6 || bytes[2] === 7 && bytes[3] === 8);
|
|
3396
|
+
var decodeBase64ZipBlob = async (blob) => {
|
|
3397
|
+
const prefix = (await blob.slice(0, 96).text()).replace(/^\uFEFF/, "").replace(/\s+/g, "");
|
|
3398
|
+
if (!prefix.startsWith("UEsDB")) return blob;
|
|
3399
|
+
if (blob.size > 32 * 1024 * 1024) {
|
|
3400
|
+
throw new Error("Base64 \u6587\u4EF6\u8D85\u8FC7\u6D4F\u89C8\u5668\u517C\u5BB9\u89E3\u7801\u4E0A\u9650");
|
|
3401
|
+
}
|
|
3402
|
+
const base64 = (await blob.text()).replace(/^\uFEFF/, "").replace(/\s+/g, "");
|
|
3403
|
+
if (!/^[A-Za-z0-9+/]+={0,2}$/.test(base64)) {
|
|
3404
|
+
throw new Error("\u6587\u4EF6\u5185\u5BB9\u662F\u65E0\u6548\u7684 Base64 \u4E8C\u8FDB\u5236\u6570\u636E");
|
|
3405
|
+
}
|
|
3406
|
+
let binary = "";
|
|
3407
|
+
try {
|
|
3408
|
+
binary = globalThis.atob(base64);
|
|
3409
|
+
} catch {
|
|
3410
|
+
throw new Error("\u6587\u4EF6\u5185\u5BB9\u662F\u65E0\u6548\u7684 Base64 \u4E8C\u8FDB\u5236\u6570\u636E");
|
|
3411
|
+
}
|
|
3412
|
+
const bytes = new Uint8Array(binary.length);
|
|
3413
|
+
for (let index = 0; index < binary.length; index += 1) {
|
|
3414
|
+
bytes[index] = binary.charCodeAt(index);
|
|
3415
|
+
}
|
|
3416
|
+
if (!isZipBytes(bytes)) {
|
|
3417
|
+
throw new Error("Base64 \u6587\u4EF6\u89E3\u7801\u540E\u4E0D\u662F\u6709\u6548\u7684 Office \u6587\u6863");
|
|
3418
|
+
}
|
|
3419
|
+
if (globalThis.btoa(binary).replace(/=+$/, "") !== base64.replace(/=+$/, "")) {
|
|
3420
|
+
throw new Error("\u6587\u4EF6\u5185\u5BB9\u662F\u65E0\u6548\u7684 Base64 \u4E8C\u8FDB\u5236\u6570\u636E");
|
|
3421
|
+
}
|
|
3422
|
+
return new Blob([bytes], { type: blob.type || "application/zip" });
|
|
3423
|
+
};
|
|
3424
|
+
var normalizePreviewBlobContent = async (response) => {
|
|
3425
|
+
const blob = normalizePreviewBlobResponse(response);
|
|
3426
|
+
if (String(blob.type || "").toLowerCase().includes("application/json")) {
|
|
3427
|
+
const payload = await blob.text().then((value) => JSON.parse(value)).catch(() => null);
|
|
3428
|
+
if (payload) {
|
|
3429
|
+
throw new Error(getBinaryResponseMessage(payload) || "\u6587\u4EF6\u5185\u5BB9\u8BF7\u6C42\u5931\u8D25");
|
|
3430
|
+
}
|
|
3431
|
+
}
|
|
3432
|
+
return decodeBase64ZipBlob(blob);
|
|
3433
|
+
};
|
|
3394
3434
|
var isDirectStorageItem = (item) => item.provider === "oss" || item.uploadProvider === "oss" || item.uploadProvider === "builtin-oss" || Boolean(item.storageCode);
|
|
3395
3435
|
var getPreviewItemKey = (item, index = 0) => getAttachmentItemIdentity(item) || item.id || item.uid || `${item.name || "file"}-${index}`;
|
|
3396
3436
|
var getPreviewSourceUrl = (item) => item.previewUrl || item.publicUrl || item.url || "";
|
|
@@ -3564,7 +3604,7 @@ var prepareFilePreview = async (options) => {
|
|
|
3564
3604
|
};
|
|
3565
3605
|
var loadPreviewBlob = async (request, url) => {
|
|
3566
3606
|
const response = await request({ url, method: "get", responseType: "blob" });
|
|
3567
|
-
return
|
|
3607
|
+
return normalizePreviewBlobContent(response);
|
|
3568
3608
|
};
|
|
3569
3609
|
var convertHeicPreview = async (request, url) => {
|
|
3570
3610
|
const source = await loadPreviewBlob(request, url);
|