openxiangda 1.0.258 → 1.0.260
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/package.json +2 -2
- package/packages/sdk/dist/runtime/index.cjs +62 -62
- 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 +62 -62
- package/packages/sdk/dist/runtime/react.cjs +59 -59
- package/packages/sdk/dist/runtime/react.d.mts +64 -1
- package/packages/sdk/dist/runtime/react.d.ts +64 -1
- package/packages/sdk/dist/runtime/react.mjs +65 -65
- package/packages/sdk/source/admin-list/AdminList.tsx +30 -5
- package/packages/sdk/source/admin-list/dataSources.ts +92 -14
- package/packages/sdk/source/admin-list/index.ts +1 -0
- package/packages/sdk/source/admin-list/types.ts +8 -0
|
@@ -748,6 +748,35 @@ export function AdminList<Row>(props: AdminListProps<Row>) {
|
|
|
748
748
|
[dataSource, notificationApi],
|
|
749
749
|
)
|
|
750
750
|
|
|
751
|
+
const downloadExportTask = async (task: AdminListExportTask) => {
|
|
752
|
+
try {
|
|
753
|
+
if (dataSource.downloadExportTask) {
|
|
754
|
+
await dataSource.downloadExportTask(task, { fileName })
|
|
755
|
+
return
|
|
756
|
+
}
|
|
757
|
+
if (
|
|
758
|
+
typeof window !== "undefined" &&
|
|
759
|
+
task.downloadUrl &&
|
|
760
|
+
(/^(https?:)?\/\//i.test(task.downloadUrl) ||
|
|
761
|
+
/^(blob|data):/i.test(task.downloadUrl))
|
|
762
|
+
) {
|
|
763
|
+
window.open(task.downloadUrl, "_blank", "noopener")
|
|
764
|
+
return
|
|
765
|
+
}
|
|
766
|
+
throw new Error("当前数据源未配置导出文件下载能力")
|
|
767
|
+
} catch (downloadError) {
|
|
768
|
+
notificationApi.error({
|
|
769
|
+
key: `admin-list-export-download:${task.id}`,
|
|
770
|
+
title: "下载失败",
|
|
771
|
+
description:
|
|
772
|
+
downloadError instanceof Error
|
|
773
|
+
? downloadError.message
|
|
774
|
+
: String(downloadError || "请稍后重试"),
|
|
775
|
+
duration: 0,
|
|
776
|
+
})
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
751
780
|
const executeExport = async (scope: "selected" | "all") => {
|
|
752
781
|
if (!dataSource.createExportTask) return
|
|
753
782
|
setExporting(true)
|
|
@@ -806,11 +835,7 @@ export function AdminList<Row>(props: AdminListProps<Row>) {
|
|
|
806
835
|
<Button
|
|
807
836
|
size="small"
|
|
808
837
|
type="primary"
|
|
809
|
-
onClick={() =>
|
|
810
|
-
if (typeof window !== "undefined") {
|
|
811
|
-
window.open(finished.downloadUrl, "_blank", "noopener")
|
|
812
|
-
}
|
|
813
|
-
}}
|
|
838
|
+
onClick={() => void downloadExportTask(finished)}
|
|
814
839
|
>
|
|
815
840
|
下载文件
|
|
816
841
|
</Button>
|
|
@@ -11,6 +11,61 @@ import type {
|
|
|
11
11
|
AdminListResult,
|
|
12
12
|
} from "./types"
|
|
13
13
|
import { createAdminListPreferenceStore } from "./preferences"
|
|
14
|
+
import type { PageBinaryResponse } from "openxiangda/runtime"
|
|
15
|
+
|
|
16
|
+
const getHeaderValue = (
|
|
17
|
+
headers: PageBinaryResponse["headers"],
|
|
18
|
+
name: string,
|
|
19
|
+
) => {
|
|
20
|
+
const match = Object.entries(headers || {}).find(
|
|
21
|
+
([key]) => key.toLowerCase() === name.toLowerCase(),
|
|
22
|
+
)
|
|
23
|
+
return match?.[1] || ""
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const decodeFileName = (value: string) => {
|
|
27
|
+
try {
|
|
28
|
+
return decodeURIComponent(value)
|
|
29
|
+
} catch {
|
|
30
|
+
return value
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const parseContentDispositionFileName = (value: string) => {
|
|
35
|
+
const utf8Match = value.match(/filename\*=UTF-8''([^;]+)/i)
|
|
36
|
+
if (utf8Match?.[1]) return decodeFileName(utf8Match[1].trim())
|
|
37
|
+
const plainMatch = value.match(/filename="?([^";]+)"?/i)
|
|
38
|
+
return plainMatch?.[1] ? decodeFileName(plainMatch[1].trim()) : ""
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const resolveExportFileName = (
|
|
42
|
+
response: PageBinaryResponse,
|
|
43
|
+
fallbackName: string,
|
|
44
|
+
) => {
|
|
45
|
+
const disposition =
|
|
46
|
+
getHeaderValue(response.headers, "content-disposition") ||
|
|
47
|
+
(response.fileName?.includes("filename") ? response.fileName : "")
|
|
48
|
+
const candidate =
|
|
49
|
+
parseContentDispositionFileName(disposition) ||
|
|
50
|
+
response.fileName ||
|
|
51
|
+
fallbackName
|
|
52
|
+
return candidate.split(/[\\/]/).pop() || fallbackName
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const saveExportBlob = (
|
|
56
|
+
response: PageBinaryResponse,
|
|
57
|
+
fallbackName: string,
|
|
58
|
+
) => {
|
|
59
|
+
if (typeof window === "undefined" || typeof document === "undefined") return
|
|
60
|
+
const objectUrl = window.URL.createObjectURL(response.blob)
|
|
61
|
+
const anchor = document.createElement("a")
|
|
62
|
+
anchor.href = objectUrl
|
|
63
|
+
anchor.download = resolveExportFileName(response, fallbackName)
|
|
64
|
+
document.body.appendChild(anchor)
|
|
65
|
+
anchor.click()
|
|
66
|
+
anchor.remove()
|
|
67
|
+
window.URL.revokeObjectURL(objectUrl)
|
|
68
|
+
}
|
|
14
69
|
|
|
15
70
|
const nonEmpty = (value: unknown) =>
|
|
16
71
|
value !== undefined &&
|
|
@@ -99,6 +154,7 @@ const createExportClient = (
|
|
|
99
154
|
source: Record<string, unknown>,
|
|
100
155
|
) => {
|
|
101
156
|
const appType = options.appType || options.sdk.context.app.appType || ""
|
|
157
|
+
const requestedFileNames = new Map<string, string>()
|
|
102
158
|
const basePath = `/openxiangda-api/v1/apps/${encodeURIComponent(
|
|
103
159
|
appType,
|
|
104
160
|
)}/admin-list/exports`
|
|
@@ -110,6 +166,7 @@ const createExportClient = (
|
|
|
110
166
|
typeof rowId === "number" ? rowId : String(rowId),
|
|
111
167
|
),
|
|
112
168
|
}
|
|
169
|
+
let task: AdminListExportTask
|
|
113
170
|
if (options.sdk.export?.create) {
|
|
114
171
|
const response = await options.sdk.export.create({
|
|
115
172
|
appType,
|
|
@@ -122,21 +179,24 @@ const createExportClient = (
|
|
|
122
179
|
if (!response.result) {
|
|
123
180
|
throw new Error("创建导出任务失败:服务端未返回任务")
|
|
124
181
|
}
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
182
|
+
task = response.result
|
|
183
|
+
} else {
|
|
184
|
+
const response = await options.sdk.request<AdminListExportTask>({
|
|
185
|
+
path: basePath,
|
|
186
|
+
method: "post",
|
|
187
|
+
body: {
|
|
188
|
+
listKey: options.listKey,
|
|
189
|
+
source,
|
|
190
|
+
...normalizedInput,
|
|
191
|
+
},
|
|
192
|
+
})
|
|
193
|
+
if (!response.result) {
|
|
194
|
+
throw new Error("创建导出任务失败:服务端未返回任务")
|
|
195
|
+
}
|
|
196
|
+
task = response.result
|
|
138
197
|
}
|
|
139
|
-
|
|
198
|
+
if (input.fileName) requestedFileNames.set(task.id, input.fileName)
|
|
199
|
+
return task
|
|
140
200
|
},
|
|
141
201
|
getExportTask: async (taskId: string) => {
|
|
142
202
|
if (options.sdk.export?.get) {
|
|
@@ -155,6 +215,24 @@ const createExportClient = (
|
|
|
155
215
|
}
|
|
156
216
|
return response.result
|
|
157
217
|
},
|
|
218
|
+
downloadExportTask: async (
|
|
219
|
+
task: AdminListExportTask,
|
|
220
|
+
downloadOptions: { fileName?: string } = {},
|
|
221
|
+
) => {
|
|
222
|
+
if (!task.downloadUrl) {
|
|
223
|
+
throw new Error("导出任务未返回下载地址")
|
|
224
|
+
}
|
|
225
|
+
const response = await options.sdk.transport.download({
|
|
226
|
+
path: task.downloadUrl,
|
|
227
|
+
method: "get",
|
|
228
|
+
})
|
|
229
|
+
const fallbackName =
|
|
230
|
+
downloadOptions.fileName ||
|
|
231
|
+
requestedFileNames.get(task.id) ||
|
|
232
|
+
`${options.listKey}.xlsx`
|
|
233
|
+
saveExportBlob(response, fallbackName)
|
|
234
|
+
requestedFileNames.delete(task.id)
|
|
235
|
+
},
|
|
158
236
|
}
|
|
159
237
|
}
|
|
160
238
|
|
|
@@ -187,6 +187,10 @@ export interface AdminListExportTask {
|
|
|
187
187
|
expiresAt?: string
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
export interface AdminListExportDownloadOptions {
|
|
191
|
+
fileName?: string
|
|
192
|
+
}
|
|
193
|
+
|
|
190
194
|
export interface AdminListDataSource<Row> {
|
|
191
195
|
key?: string
|
|
192
196
|
query: (query: AdminListQuery) => Promise<AdminListResult<Row>>
|
|
@@ -195,6 +199,10 @@ export interface AdminListDataSource<Row> {
|
|
|
195
199
|
input: AdminListExportInput,
|
|
196
200
|
) => Promise<AdminListExportTask>
|
|
197
201
|
getExportTask?: (taskId: string) => Promise<AdminListExportTask>
|
|
202
|
+
downloadExportTask?: (
|
|
203
|
+
task: AdminListExportTask,
|
|
204
|
+
options?: AdminListExportDownloadOptions,
|
|
205
|
+
) => Promise<void>
|
|
198
206
|
}
|
|
199
207
|
|
|
200
208
|
export interface AdminListDataSourceOptions {
|