@hzab/form-render 1.5.0 → 1.5.1-beta
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/CHANGELOG.md +8 -0
- package/package.json +1 -1
- package/src/components/Upload/README.md +13 -0
- package/src/components/Upload/common/OfflineUpload.ts +335 -0
- package/src/components/Upload/common/customRequest.ts +46 -9
- package/src/components/Upload/common/fileName.ts +71 -2
- package/src/components/Upload/common/handleIOFileList.ts +133 -45
- package/src/components/Upload/common/nanoid.ts +1 -1
- package/src/components/Upload/common/utils.js +0 -40
- package/src/components/Upload/uploader-input.jsx +1 -1
- package/src/components/Upload/uploader.jsx +55 -6
- package/src/index.tsx +11 -10
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
@@ -32,6 +32,7 @@
|
|
32
32
|
| beforeUploadCheck | Function | 否 | - | 追加的 beforeUpload 检查,返回 Promise.reject() 进行拦截 |
|
33
33
|
| templateUrl | string | 否 | - | 模板下载的地址 |
|
34
34
|
| templateDownloadText | string | 否 | 模板下载 | 模板下载按钮的文案 |
|
35
|
+
| uploadParams | Object | 否 | {} | 文件上传接口入参 |
|
35
36
|
|
36
37
|
### fileListConf 结果数据配置
|
37
38
|
|
@@ -49,3 +50,15 @@
|
|
49
50
|
| axiosConf | Object | 否 | - | axios 配置 |
|
50
51
|
| signatureParams | Object | 否 | - | 请求 oss 上传地址接口的入参 |
|
51
52
|
| serverUrl | string | 否 | - | 请求 oss 上传地址的接口,默认 /api/v1/user/oss/getWebOssConfig |
|
53
|
+
|
54
|
+
#### signatureParams
|
55
|
+
|
56
|
+
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|
57
|
+
| ------- | ------ | ---- | ------ | ----------------------------------------------------------------------------- |
|
58
|
+
| fileAcl | string | 否 | - | 是否是私有域上传 private-私有,public-read-公共读,public-read-write-公共读写 |
|
59
|
+
|
60
|
+
### uploadParams
|
61
|
+
|
62
|
+
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|
63
|
+
| ------- | ------ | ---- | ------ | ----------------------------------------------------------------------------- |
|
64
|
+
| fileAcl | string | 否 | - | 是否是私有域上传 private-私有,public-read-公共读,public-read-write-公共读写 |
|
@@ -0,0 +1,335 @@
|
|
1
|
+
import { axios } from "@hzab/data-model";
|
2
|
+
|
3
|
+
import { getFileNameByFileObj } from "./fileName";
|
4
|
+
import { formatDirStr, mergeDirStr } from "./utils";
|
5
|
+
|
6
|
+
/**
|
7
|
+
* 获取上传相关配置入参
|
8
|
+
*/
|
9
|
+
export interface IGetSignatureOpt {
|
10
|
+
/**
|
11
|
+
* 配置接口地址
|
12
|
+
*/
|
13
|
+
serverUrl?: string;
|
14
|
+
/**
|
15
|
+
* axios 实例
|
16
|
+
*/
|
17
|
+
axios?: Object;
|
18
|
+
/**
|
19
|
+
* axios 配置
|
20
|
+
*/
|
21
|
+
axiosConf?: Object;
|
22
|
+
/**
|
23
|
+
* 请求入参
|
24
|
+
*/
|
25
|
+
params?: {
|
26
|
+
path?: string;
|
27
|
+
};
|
28
|
+
}
|
29
|
+
|
30
|
+
export interface IOfflineUploadProps {
|
31
|
+
/**
|
32
|
+
* axios 实例
|
33
|
+
*/
|
34
|
+
axios?: Object;
|
35
|
+
/**
|
36
|
+
* axios 配置
|
37
|
+
*/
|
38
|
+
axiosConf?: Object;
|
39
|
+
/**
|
40
|
+
* 配置接口地址
|
41
|
+
*/
|
42
|
+
serverUrl?: string;
|
43
|
+
/**
|
44
|
+
* 获取配置接口请求入参
|
45
|
+
*/
|
46
|
+
signatureParams?: {
|
47
|
+
path?: string;
|
48
|
+
};
|
49
|
+
}
|
50
|
+
|
51
|
+
/**
|
52
|
+
* 文件上传入参
|
53
|
+
*/
|
54
|
+
export interface IUploadOpt {
|
55
|
+
/**
|
56
|
+
* 配置接口地址
|
57
|
+
*/
|
58
|
+
serverUrl?: string;
|
59
|
+
/**
|
60
|
+
* 文件上传接口地址
|
61
|
+
*/
|
62
|
+
uploadUrl?: string;
|
63
|
+
/**
|
64
|
+
* 获取配置接口请求入参
|
65
|
+
*/
|
66
|
+
params?: {
|
67
|
+
path?: string;
|
68
|
+
// 兼容 老数据
|
69
|
+
dir?: string;
|
70
|
+
};
|
71
|
+
/**
|
72
|
+
* 文件上传请求入参
|
73
|
+
*/
|
74
|
+
ossParams?: {
|
75
|
+
path?: string;
|
76
|
+
// 兼容 老数据
|
77
|
+
dir?: string;
|
78
|
+
};
|
79
|
+
/**
|
80
|
+
* axios 实例
|
81
|
+
*/
|
82
|
+
axios?: Object;
|
83
|
+
/**
|
84
|
+
* axios 配置
|
85
|
+
*/
|
86
|
+
axiosConf?: Object;
|
87
|
+
}
|
88
|
+
|
89
|
+
/**
|
90
|
+
* 获取上传配置
|
91
|
+
* @param opt
|
92
|
+
* @returns
|
93
|
+
*/
|
94
|
+
export function getSignature(opt: IGetSignatureOpt = {}) {
|
95
|
+
const { serverUrl = "/api/v1/upload/init" } = opt;
|
96
|
+
// 减 10 秒,避免发起请求时 刚好过期的情况
|
97
|
+
const { axios: _ax = axios, params = {}, axiosConf } = opt;
|
98
|
+
if (
|
99
|
+
window._$offlineSignatureRes &&
|
100
|
+
window._$offlineSignatureRes.data &&
|
101
|
+
serverUrl + JSON.stringify(params) === window._$offlineSignatureRes.serverUrlParams &&
|
102
|
+
Date.now() - window._$offlineSignatureRes.__saveTime < window._$offlineSignatureRes.expireTimeMillis - 10000
|
103
|
+
) {
|
104
|
+
return Promise.resolve(window._$offlineSignatureRes.data);
|
105
|
+
}
|
106
|
+
// 处理 path 格式,必须为非 / 开头, / 结尾。如: test/
|
107
|
+
params.path = formatDirStr(params.path);
|
108
|
+
|
109
|
+
return _ax
|
110
|
+
.post(
|
111
|
+
serverUrl,
|
112
|
+
{
|
113
|
+
...params,
|
114
|
+
},
|
115
|
+
{
|
116
|
+
...axiosConf,
|
117
|
+
},
|
118
|
+
)
|
119
|
+
.then((res) => {
|
120
|
+
if (res?.data?.code === 500) {
|
121
|
+
return Promise.reject(res.data);
|
122
|
+
}
|
123
|
+
if (res?.data?.code === 200) {
|
124
|
+
window._$offlineSignatureRes = {
|
125
|
+
data: res?.data?.data,
|
126
|
+
};
|
127
|
+
if (window._$offlineSignatureRes) {
|
128
|
+
window._$offlineSignatureRes.__saveTime = Date.now();
|
129
|
+
window._$offlineSignatureRes.serverUrlParams = serverUrl + JSON.stringify(params);
|
130
|
+
}
|
131
|
+
return window._$offlineSignatureRes.data;
|
132
|
+
}
|
133
|
+
});
|
134
|
+
}
|
135
|
+
|
136
|
+
export interface IGetPreviewUrlsOpt {
|
137
|
+
previewUrl?: string;
|
138
|
+
params?: Object;
|
139
|
+
axios?: Object;
|
140
|
+
axiosConf?: Object;
|
141
|
+
}
|
142
|
+
|
143
|
+
/**
|
144
|
+
* 获取预览地址-批量
|
145
|
+
* @param urlList
|
146
|
+
* @param opt
|
147
|
+
* @returns
|
148
|
+
*/
|
149
|
+
export const getPreviewUrls = function (urlList, opt: IGetPreviewUrlsOpt = {}) {
|
150
|
+
return new Promise(async (resolve, reject) => {
|
151
|
+
const { axios: _ax = axios, axiosConf, previewUrl } = opt || {};
|
152
|
+
|
153
|
+
const _axios = _ax;
|
154
|
+
|
155
|
+
let _list = urlList;
|
156
|
+
if (_list && !Array.isArray(_list)) {
|
157
|
+
_list = [_list];
|
158
|
+
}
|
159
|
+
|
160
|
+
return _axios
|
161
|
+
.post(previewUrl || "/api/v1/upload/preview", _list, { ...axiosConf })
|
162
|
+
.then((res) => {
|
163
|
+
if (res?.data?.code == 200) {
|
164
|
+
resolve(res?.data?.data);
|
165
|
+
} else {
|
166
|
+
reject(res);
|
167
|
+
}
|
168
|
+
return res;
|
169
|
+
})
|
170
|
+
.catch((err) => {
|
171
|
+
console.error("offline upload err", err);
|
172
|
+
reject(err);
|
173
|
+
return Promise.reject(err);
|
174
|
+
});
|
175
|
+
});
|
176
|
+
};
|
177
|
+
|
178
|
+
/**
|
179
|
+
* 获取预览地址
|
180
|
+
* @param url
|
181
|
+
* @param opt
|
182
|
+
* @returns
|
183
|
+
*/
|
184
|
+
export const getPreviewUrl = function (url, opt: IGetPreviewUrlsOpt = {}) {
|
185
|
+
return new Promise(async (resolve, reject) => {
|
186
|
+
const { axios: _ax = axios, axiosConf, previewUrl } = opt || {};
|
187
|
+
|
188
|
+
const _axios = _ax;
|
189
|
+
|
190
|
+
return _axios
|
191
|
+
.post(previewUrl || "/api/v1/upload/preview", [url], { ...axiosConf })
|
192
|
+
.then((res) => {
|
193
|
+
if (res?.data?.code == 200) {
|
194
|
+
resolve(res?.data?.data?.[0]);
|
195
|
+
} else {
|
196
|
+
reject(res);
|
197
|
+
}
|
198
|
+
return res;
|
199
|
+
})
|
200
|
+
.catch((err) => {
|
201
|
+
console.error("offline upload err", err);
|
202
|
+
reject(err);
|
203
|
+
return Promise.reject(err);
|
204
|
+
});
|
205
|
+
});
|
206
|
+
};
|
207
|
+
|
208
|
+
/**
|
209
|
+
* 私有部署版文件上传
|
210
|
+
*/
|
211
|
+
export class OfflineUpload {
|
212
|
+
axios;
|
213
|
+
axiosConf;
|
214
|
+
serverUrl;
|
215
|
+
signatureParams;
|
216
|
+
constructor(props: IOfflineUploadProps = {}) {
|
217
|
+
this.axios = props.axios || axios;
|
218
|
+
this.axiosConf = props.axiosConf || {};
|
219
|
+
this.serverUrl = props.serverUrl || "/api/v1/upload/init";
|
220
|
+
this.signatureParams = props.signatureParams || {};
|
221
|
+
}
|
222
|
+
|
223
|
+
getSignature(serverUrl = this.serverUrl, opt) {
|
224
|
+
// path 前缀 oss-upload 文件目录
|
225
|
+
opt.params.path = mergeDirStr("web-upload/", opt.params.path ?? opt.params.dir);
|
226
|
+
return getSignature({
|
227
|
+
...opt,
|
228
|
+
serverUrl,
|
229
|
+
axios: opt?.axios || this.axios,
|
230
|
+
axiosConf: { ...this.axiosConf, ...opt?.axiosConf },
|
231
|
+
});
|
232
|
+
}
|
233
|
+
|
234
|
+
upload(file, opt: IUploadOpt = {}) {
|
235
|
+
return new Promise(async (resolve, reject) => {
|
236
|
+
// filename 表示待上传的本地文件名称。
|
237
|
+
const filename = getFileNameByFileObj(file);
|
238
|
+
try {
|
239
|
+
const fileInfo = await this.getSignature(opt.serverUrl || this.serverUrl, {
|
240
|
+
...opt,
|
241
|
+
params: {
|
242
|
+
/** 最终保存的文件名称(不传默认文件名) */
|
243
|
+
filename,
|
244
|
+
/** 存储路径(不传默认基础路径) */
|
245
|
+
path: opt.params?.dir || this.signatureParams?.dir,
|
246
|
+
/** 文件存储平台(1、s3,2、本地存储,3、FTP,默认s3) */
|
247
|
+
platformCode: undefined,
|
248
|
+
...this.signatureParams,
|
249
|
+
...opt.params,
|
250
|
+
},
|
251
|
+
});
|
252
|
+
const { uploadUrl, ossParams: propOssParams } = opt || {};
|
253
|
+
const formData = new FormData();
|
254
|
+
formData.set("fileInfo", fileInfo);
|
255
|
+
formData.set("file", file);
|
256
|
+
|
257
|
+
if (propOssParams) {
|
258
|
+
for (const key in propOssParams) {
|
259
|
+
if (Object.hasOwnProperty.call(propOssParams, key)) {
|
260
|
+
formData.set(key, propOssParams[key]);
|
261
|
+
}
|
262
|
+
}
|
263
|
+
}
|
264
|
+
|
265
|
+
const _axios = opt?.axios || this.axios;
|
266
|
+
|
267
|
+
return _axios
|
268
|
+
.post(uploadUrl || "/api/v1/upload/execute", formData, { ...this.axiosConf, ...opt?.axiosConf })
|
269
|
+
.then((res) => {
|
270
|
+
if (res?.data?.code == 200) {
|
271
|
+
resolve(res?.data?.data);
|
272
|
+
} else {
|
273
|
+
reject(res);
|
274
|
+
}
|
275
|
+
return res;
|
276
|
+
})
|
277
|
+
.catch((err) => {
|
278
|
+
console.error("offline upload err", err);
|
279
|
+
reject(err);
|
280
|
+
return Promise.reject(err);
|
281
|
+
});
|
282
|
+
} catch (error) {
|
283
|
+
reject(error);
|
284
|
+
return Promise.reject(error);
|
285
|
+
}
|
286
|
+
});
|
287
|
+
}
|
288
|
+
|
289
|
+
/**
|
290
|
+
* 获取预览地址
|
291
|
+
* @param urlList
|
292
|
+
* @param opt
|
293
|
+
* @returns
|
294
|
+
*/
|
295
|
+
getPreviewUrls(urlList, opt) {
|
296
|
+
return getPreviewUrls(urlList, {
|
297
|
+
...opt,
|
298
|
+
axios: opt?.axios || this.axios,
|
299
|
+
axiosConf: { ...this.axiosConf, ...opt?.axiosConf },
|
300
|
+
});
|
301
|
+
}
|
302
|
+
}
|
303
|
+
|
304
|
+
/**
|
305
|
+
* 批量处理预览地址
|
306
|
+
* @param fileList
|
307
|
+
* @param opt
|
308
|
+
* @returns
|
309
|
+
*/
|
310
|
+
export const handlePreviewUrls = async function (fileList, opt: IGetPreviewUrlsOpt) {
|
311
|
+
const res = await getPreviewUrls(
|
312
|
+
fileList?.map((it) => it.storeUrl || it.url),
|
313
|
+
{
|
314
|
+
...opt,
|
315
|
+
},
|
316
|
+
);
|
317
|
+
|
318
|
+
fileList.forEach((it, i) => {
|
319
|
+
// TODO: storeUrl 来源于【本地 url 直接使用预览地址,解决 previewFile 无法正常使用 previewUrl 的问题】
|
320
|
+
if (!it.storeUrl) {
|
321
|
+
it.storeUrl = it.url;
|
322
|
+
}
|
323
|
+
// TODO: 本地 url 直接使用预览地址,解决 previewFile 无法正常使用 previewUrl 的问题
|
324
|
+
it.url = res[i].presignedUrl;
|
325
|
+
|
326
|
+
// 常规预览地址写法
|
327
|
+
it.previewUrl = res[i].presignedUrl;
|
328
|
+
if (it.uploadInfo) {
|
329
|
+
it.uploadInfo.previewUrl = it.previewUrl;
|
330
|
+
}
|
331
|
+
});
|
332
|
+
return fileList;
|
333
|
+
};
|
334
|
+
|
335
|
+
export default OfflineUpload;
|
@@ -1,19 +1,57 @@
|
|
1
1
|
import LUploadOss from "./ossUpload";
|
2
|
+
import OfflineUpload from "./OfflineUpload";
|
2
3
|
|
3
|
-
|
4
|
+
/**
|
5
|
+
* 私有化部署文件上传
|
6
|
+
* @param opt
|
7
|
+
* @returns
|
8
|
+
*/
|
9
|
+
export const getOfflineUploadRequest = (opt) => {
|
10
|
+
const {
|
11
|
+
/** 是否是 私有域 存储,预览地址需要特殊处理 */
|
12
|
+
isPrivateStore,
|
13
|
+
UploadOss,
|
14
|
+
offlineServerUrl,
|
15
|
+
offlineUploadUrl,
|
16
|
+
offlinePreviewUrl,
|
17
|
+
ossOpt,
|
18
|
+
params,
|
19
|
+
} = opt || {};
|
4
20
|
return ({ action, data, file, filename, headers, method, onSuccess, onProgress, onError }) => {
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
21
|
+
const _UploadOss = UploadOss || OfflineUpload;
|
22
|
+
const offlineUpload = new _UploadOss({
|
23
|
+
serverUrl: offlineServerUrl,
|
24
|
+
previewUrl: offlinePreviewUrl,
|
25
|
+
...ossOpt,
|
26
|
+
});
|
27
|
+
file.ossPromise = offlineUpload
|
28
|
+
.upload(file, {
|
29
|
+
uploadUrl: offlineUploadUrl,
|
30
|
+
params: {
|
31
|
+
...(params || {}),
|
32
|
+
},
|
33
|
+
})
|
34
|
+
.then((res) => {
|
35
|
+
file.uploadInfo = res;
|
36
|
+
/** 上传存储的地址,用于获取对应的预览地址 */
|
37
|
+
file.storeUrl = res.url;
|
38
|
+
/** 预览地址 */
|
39
|
+
file.previewUrl = res.presignedUrl || res.url;
|
40
|
+
/** FIXME: 本地 url 直接使用预览地址,解决 previewFile 无法正常使用 previewUrl 的问题 */
|
41
|
+
file.url = file.previewUrl;
|
42
|
+
// file.url = file.storeUrl;
|
43
|
+
onSuccess(res);
|
44
|
+
return res;
|
12
45
|
})
|
13
46
|
.catch(onError);
|
14
47
|
};
|
15
48
|
};
|
16
49
|
|
50
|
+
/**
|
51
|
+
* 阿里云 oss 文件上传
|
52
|
+
* @param opt
|
53
|
+
* @returns
|
54
|
+
*/
|
17
55
|
export const getOssUploadRequest = (opt) => {
|
18
56
|
const { UploadOss, ossServerUrl, ossOpt, params } = opt || {};
|
19
57
|
return ({ action, data, file, filename, headers, method, onSuccess, onProgress, onError }) => {
|
@@ -25,7 +63,6 @@ export const getOssUploadRequest = (opt) => {
|
|
25
63
|
file.ossPromise = ossUpload
|
26
64
|
.upload(file, {
|
27
65
|
params: {
|
28
|
-
isPublic: 1,
|
29
66
|
...(params || {}),
|
30
67
|
},
|
31
68
|
})
|
@@ -1,5 +1,21 @@
|
|
1
1
|
import { nanoid } from "./nanoid";
|
2
2
|
|
3
|
+
/**
|
4
|
+
* 获取文件后缀
|
5
|
+
* @param fileUrl
|
6
|
+
* @returns
|
7
|
+
*/
|
8
|
+
export function getFileExt(fileUrl) {
|
9
|
+
if (typeof fileUrl !== "string") {
|
10
|
+
return;
|
11
|
+
}
|
12
|
+
const res = fileUrl?.match(/([^\/]+?)\.([^\/\.]+)$/);
|
13
|
+
if (res && res.length > 2) {
|
14
|
+
return res[2];
|
15
|
+
}
|
16
|
+
return fileUrl;
|
17
|
+
}
|
18
|
+
|
3
19
|
/**
|
4
20
|
* 从 url 获取文件名称,不包含后缀
|
5
21
|
* @param {*} fileName
|
@@ -55,11 +71,13 @@ export const mergeFileName = function (fileName, str = "") {
|
|
55
71
|
* @returns
|
56
72
|
*/
|
57
73
|
export const getUFileName = function (fileName) {
|
58
|
-
return mergeFileName(getFileName(fileName) || "rc-upload",
|
74
|
+
return mergeFileName(getFileName(fileName) || "rc-upload", "-" + `~kid-${nanoid()}~` + `${Date.now()}`);
|
59
75
|
};
|
60
76
|
|
61
77
|
/**
|
62
78
|
* 根据 file 对象获取文件名称,文件名称包含:初始名称、类型
|
79
|
+
* 数据存储格式 ~k[key]-[data]~ 如: ~ktime-1743649562530~
|
80
|
+
* key、data 中的 ~ 转为 _
|
63
81
|
* @param file
|
64
82
|
*/
|
65
83
|
export const getFileNameByFileObj = (file) => {
|
@@ -67,5 +85,56 @@ export const getFileNameByFileObj = (file) => {
|
|
67
85
|
if (_fileName.indexOf("--Cs--") >= 0) {
|
68
86
|
return _fileName;
|
69
87
|
}
|
70
|
-
|
88
|
+
// id、时间、文件类型
|
89
|
+
const id = file.id || file.uid || nanoid();
|
90
|
+
return mergeFileName(
|
91
|
+
_fileName,
|
92
|
+
"-" +
|
93
|
+
setFileNameObj({
|
94
|
+
id,
|
95
|
+
time: file.createTime ?? Date.now(),
|
96
|
+
type: (file.type || file.contentType)?.replace("/", "_"),
|
97
|
+
}),
|
98
|
+
);
|
99
|
+
};
|
100
|
+
|
101
|
+
/**
|
102
|
+
* 解析
|
103
|
+
* @param url
|
104
|
+
* @returns
|
105
|
+
*/
|
106
|
+
export const getFileNameObj = function (url): any {
|
107
|
+
const list = url.match(/~k([^-~]+)-([^~]*)~/g);
|
108
|
+
const res = {};
|
109
|
+
list?.forEach((it) => {
|
110
|
+
const arr = it?.match(/~k([^-~]+)-([^~]*)~/);
|
111
|
+
if (arr?.length >= 3) {
|
112
|
+
res[arr[1]] = arr[2];
|
113
|
+
}
|
114
|
+
});
|
115
|
+
return res;
|
116
|
+
};
|
117
|
+
|
118
|
+
/**
|
119
|
+
* 解析
|
120
|
+
* @param url
|
121
|
+
* @returns
|
122
|
+
*/
|
123
|
+
export function setFileNameObj(obj) {
|
124
|
+
return Object.keys(obj)
|
125
|
+
.map((key) => {
|
126
|
+
return `~k${key?.replace(/~/g, "_")}-${obj[key]?.toString()?.replace(/~/g, "_")}~`;
|
127
|
+
})
|
128
|
+
.join("");
|
129
|
+
}
|
130
|
+
|
131
|
+
/**
|
132
|
+
* 通过 key 获取对应的值
|
133
|
+
* @param url
|
134
|
+
* @param key
|
135
|
+
* @returns
|
136
|
+
*/
|
137
|
+
export const getValByKey = function (url, key) {
|
138
|
+
const reg = new RegExp(`~k${key?.replace(/~/g, "_")}-\([^~]*\)~`);
|
139
|
+
return url.match(reg)?.[1];
|
71
140
|
};
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { getFileURL, getFileType, isBase64Str } from "./utils";
|
2
|
-
import { getUFileName, getFileName, getFullFileName } from "./fileName";
|
2
|
+
import { getUFileName, getFileName, getFullFileName, getFileExt, getFileNameObj } from "./fileName";
|
3
3
|
import { nanoid } from "./nanoid";
|
4
4
|
|
5
5
|
/**
|
@@ -14,14 +14,29 @@ import { nanoid } from "./nanoid";
|
|
14
14
|
* 出入参数据子项处理配置项
|
15
15
|
*/
|
16
16
|
export interface IFile {
|
17
|
+
uid?: string;
|
17
18
|
/** 唯一 id */
|
18
|
-
|
19
|
-
/** 存储的文件名称 */
|
20
|
-
name: string;
|
21
|
-
/** 上传的文件名称 */
|
22
|
-
sourceName: string;
|
19
|
+
id: string;
|
23
20
|
/** 文件地址 */
|
24
21
|
url: string;
|
22
|
+
/** 存储的文件地址 */
|
23
|
+
storeUrl: string;
|
24
|
+
/** 存储的文件名称 */
|
25
|
+
name?: string;
|
26
|
+
/** 存储的文件名称 */
|
27
|
+
filename?: string;
|
28
|
+
/** 上传的文件名称 */
|
29
|
+
originalFilename: string;
|
30
|
+
/** 基础路径 */
|
31
|
+
basePath?: string;
|
32
|
+
/** 存储路径 */
|
33
|
+
path?: string;
|
34
|
+
/** 文件后缀 */
|
35
|
+
ext?: string;
|
36
|
+
/** 文件类型 */
|
37
|
+
contentType: string;
|
38
|
+
/** 上传平台 */
|
39
|
+
platform: string;
|
25
40
|
/** 文件预览地址 */
|
26
41
|
previewUrl?: string;
|
27
42
|
/** 文件类型 */
|
@@ -30,6 +45,8 @@ export interface IFile {
|
|
30
45
|
size: number;
|
31
46
|
/** 原始 file 文件 */
|
32
47
|
originFileObj?: File;
|
48
|
+
/** 文件上传时间 */
|
49
|
+
createTime?: string;
|
33
50
|
/** 最后更新时间戳 */
|
34
51
|
lastModified?: number;
|
35
52
|
}
|
@@ -50,21 +67,39 @@ export interface IHandlerOpt {
|
|
50
67
|
|
51
68
|
/** 内部文件格式 */
|
52
69
|
const fileTpl: IFile = {
|
70
|
+
/** 唯一 id */
|
53
71
|
uid: "",
|
54
|
-
|
55
|
-
sourceName: "",
|
56
|
-
/** 存储的文件名称 */
|
57
|
-
name: "",
|
72
|
+
id: "",
|
58
73
|
/** 文件地址 */
|
59
74
|
url: "",
|
60
|
-
/**
|
61
|
-
|
75
|
+
/** 存储的文件地址 */
|
76
|
+
storeUrl: "",
|
77
|
+
/** 存储的文件名称 */
|
78
|
+
name: "",
|
79
|
+
/** 存储的文件名称 */
|
80
|
+
filename: "",
|
81
|
+
/** 上传的文件名称 */
|
82
|
+
originalFilename: "",
|
83
|
+
/** 基础路径 */
|
84
|
+
basePath: "",
|
85
|
+
/** 存储路径 */
|
86
|
+
path: "",
|
87
|
+
/** 文件后缀 */
|
88
|
+
ext: "",
|
62
89
|
/** 文件类型 */
|
63
90
|
type: "",
|
91
|
+
/** 文件类型 */
|
92
|
+
contentType: "",
|
93
|
+
/** 文件预览地址 */
|
94
|
+
previewUrl: undefined,
|
64
95
|
/** 文件大小 */
|
65
96
|
size: undefined,
|
97
|
+
/** 上传平台 */
|
98
|
+
platform: undefined,
|
66
99
|
/** 最后更新时间戳 */
|
67
100
|
lastModified: undefined,
|
101
|
+
/** 文件上传时间 */
|
102
|
+
createTime: undefined,
|
68
103
|
/** 源文件 */
|
69
104
|
originFileObj: undefined,
|
70
105
|
};
|
@@ -97,10 +132,16 @@ export const objStrToObj = function (objStr) {
|
|
97
132
|
* @returns
|
98
133
|
*/
|
99
134
|
export function mergePreviewConfig(uri, previewConfig) {
|
100
|
-
|
101
|
-
|
135
|
+
let _uri = uri;
|
136
|
+
let basePath = "";
|
137
|
+
if (typeof _uri === "object") {
|
138
|
+
basePath = _uri.basePath;
|
139
|
+
_uri = _uri.previewUrl || _uri.url;
|
102
140
|
}
|
103
|
-
|
141
|
+
if (typeof _uri !== "string") {
|
142
|
+
return _uri;
|
143
|
+
}
|
144
|
+
_uri = _uri?.trim();
|
104
145
|
// base64 直接返回
|
105
146
|
if (isBase64Str(_uri)) {
|
106
147
|
return _uri;
|
@@ -138,32 +179,61 @@ export const handleInputItem = (data, opt: IHandlerOpt) => {
|
|
138
179
|
// 对象字符串 转为 对象
|
139
180
|
const _data = objStrToObj(data);
|
140
181
|
if (typeof _data === "string") {
|
182
|
+
const obj = getFileNameObj(_data);
|
183
|
+
file.id = obj?.id;
|
184
|
+
file.uid = file.id;
|
141
185
|
file.url = _data;
|
142
|
-
file.
|
143
|
-
file.name = file.
|
144
|
-
file.
|
186
|
+
file.storeUrl = _data;
|
187
|
+
file.name = getFullFileName(file.url);
|
188
|
+
file.filename = file.name;
|
189
|
+
file.originalFilename = getFullFileName(file.url);
|
190
|
+
file.type = getFileType(file.url);
|
191
|
+
file.ext = getFileExt(file.url);
|
145
192
|
} else if (typeof _data === "object") {
|
146
193
|
const sourceData = _data.originFileObj || _data;
|
147
|
-
file.
|
194
|
+
file.id = _data.id || _data.uid;
|
148
195
|
file.url = sourceData.url || sourceData.ossUrl;
|
149
|
-
file.
|
150
|
-
file.
|
151
|
-
file.
|
152
|
-
file.
|
196
|
+
file.storeUrl = file.url;
|
197
|
+
file.originalFilename = _data.originalFilename || getFullFileName(file.url) || _data.name;
|
198
|
+
file.name = _data.filename || _data.name || getFullFileName(file.url);
|
199
|
+
file.filename = file.name;
|
153
200
|
file.originFileObj = data instanceof File ? data : undefined;
|
154
201
|
file.lastModified = _data.lastModified;
|
202
|
+
/** 基础路径 */
|
203
|
+
file.basePath = _data.basePath;
|
204
|
+
/** 存储路径 */
|
205
|
+
file.path = _data.path;
|
206
|
+
/** 文件后缀 */
|
207
|
+
file.ext = getFileExt(file.url);
|
208
|
+
/** 文件类型 */
|
209
|
+
file.type = _data.contentType || _data.type;
|
210
|
+
/** 文件类型 */
|
211
|
+
file.contentType = file.type;
|
212
|
+
/** 文件大小 */
|
213
|
+
file.size = _data.size;
|
214
|
+
/** 上传平台 */
|
215
|
+
file.platform = _data.platform;
|
216
|
+
/** 上传平台 */
|
217
|
+
file.createTime = _data.createTime;
|
155
218
|
}
|
156
|
-
if (!file.
|
157
|
-
file.
|
219
|
+
if (!file.id) {
|
220
|
+
file.id =
|
221
|
+
file.uid || `${getFileName(file.url || file.name || file.filename) || "rc-upload"}-${Date.now()}-${nanoid()}`;
|
158
222
|
}
|
159
223
|
if (!file.name) {
|
160
|
-
file.name = file.
|
224
|
+
file.name = file.id;
|
225
|
+
}
|
226
|
+
if (!file.filename) {
|
227
|
+
file.filename = file.name;
|
228
|
+
}
|
229
|
+
if (!file.storeUrl) {
|
230
|
+
file.storeUrl = file.url;
|
161
231
|
}
|
162
232
|
|
163
233
|
const { previewConfig } = opt || {};
|
164
234
|
|
165
235
|
// 合并预览地址?
|
166
|
-
file.previewUrl = mergePreviewConfig(file
|
236
|
+
file.previewUrl = mergePreviewConfig(file, previewConfig);
|
167
237
|
|
168
238
|
return file;
|
169
239
|
};
|
@@ -180,17 +250,8 @@ export const handleInputFileList = (fileList, opt: IHandlerOpt) => {
|
|
180
250
|
let _fileList = fileList;
|
181
251
|
const { listMode, split } = opt || {};
|
182
252
|
|
183
|
-
// 统一成数组格式
|
184
|
-
if (_fileList !== null && _fileList !== undefined && !Array.isArray(_fileList)) {
|
185
|
-
_fileList = [_fileList];
|
186
|
-
}
|
187
|
-
|
188
253
|
// splitStr jsonStr 模式下 Upload 自动将字符串嵌套了数组,需要手动处理
|
189
254
|
if (listMode === "splitStr" || listMode === "jsonStr") {
|
190
|
-
if (Array.isArray(_fileList) && _fileList.length === 1 && typeof _fileList[0] === "string") {
|
191
|
-
// @ts-ignore
|
192
|
-
_fileList = _fileList[0];
|
193
|
-
}
|
194
255
|
if (typeof _fileList === "string") {
|
195
256
|
if (listMode === "splitStr") {
|
196
257
|
// @ts-ignore
|
@@ -203,6 +264,11 @@ export const handleInputFileList = (fileList, opt: IHandlerOpt) => {
|
|
203
264
|
}
|
204
265
|
}
|
205
266
|
|
267
|
+
// 统一成数组格式
|
268
|
+
if (_fileList !== null && _fileList !== undefined && !Array.isArray(_fileList)) {
|
269
|
+
_fileList = [_fileList];
|
270
|
+
}
|
271
|
+
|
206
272
|
if (Array.isArray(_fileList)) {
|
207
273
|
const arrRes = [];
|
208
274
|
_fileList.forEach((it) => {
|
@@ -222,29 +288,51 @@ export const handleFileName = function () {};
|
|
222
288
|
* @returns
|
223
289
|
*/
|
224
290
|
export const formatItem = function (data) {
|
225
|
-
|
291
|
+
// TODO: storeUrl 来源于【本地 url 直接使用预览地址,解决 previewFile 无法正常使用 previewUrl 的问题】
|
292
|
+
const url = data.storeUrl || data.url || (data instanceof File ? getFileURL(data) : data.url);
|
293
|
+
const uid = data.uid ?? data.id ?? getUFileName(data.name || data.filename);
|
294
|
+
const filename = data.filename || data.uploadInfo?.filename || (data.url ? getFullFileName(data.url) : data.name);
|
226
295
|
const res = {
|
296
|
+
uid: uid,
|
227
297
|
/** 唯一 id */
|
228
|
-
|
229
|
-
/** 上传的文件名称 */
|
230
|
-
sourceName: data.sourceName || data.name,
|
231
|
-
/** 存储的文件名称 */
|
232
|
-
name: data.url ? getFullFileName(data.url) : data.name,
|
298
|
+
id: uid,
|
233
299
|
/** 文件地址 */
|
234
300
|
url: url,
|
301
|
+
/** 存储的文件名称 */
|
302
|
+
name: filename,
|
303
|
+
/** 存储的文件名称 */
|
304
|
+
filename: filename,
|
305
|
+
/** 上传的文件名称 */
|
306
|
+
originalFilename: data.originalFilename || data.uploadInfo?.originalFilename || data.name || data.filename,
|
307
|
+
/** 基础路径 */
|
308
|
+
basePath: data.basePath || data.uploadInfo?.basePath,
|
309
|
+
/** 存储路径 */
|
310
|
+
path: data.path || data.uploadInfo?.path,
|
311
|
+
/** 文件后缀 */
|
312
|
+
ext: data.ext || data.uploadInfo?.ext || getFileExt(url),
|
235
313
|
/** 文件类型 */
|
236
314
|
type: data.type,
|
315
|
+
/** 文件类型 */
|
316
|
+
contentType: data.type,
|
237
317
|
/** 文件大小 */
|
238
318
|
size: data.size,
|
319
|
+
/** 上传平台 */
|
320
|
+
platform: data.platform || data.uploadInfo?.platform,
|
239
321
|
/** 最后更新时间戳 */
|
240
322
|
lastModified: data.lastModified,
|
323
|
+
/** 文件上传时间 */
|
324
|
+
createTime: data.createTime || data.uploadInfo?.createTime,
|
325
|
+
/** 源文件 */
|
241
326
|
originFileObj: data instanceof File ? data : undefined,
|
242
327
|
};
|
243
328
|
if (!res.name) {
|
244
|
-
res.name = res.uid;
|
329
|
+
res.name = res.id ?? res.uid;
|
330
|
+
}
|
331
|
+
if (!res.filename) {
|
332
|
+
res.filename = res.id ?? res.uid;
|
245
333
|
}
|
246
|
-
if (!res.
|
247
|
-
res.
|
334
|
+
if (!res.originalFilename) {
|
335
|
+
res.originalFilename = res.name || res.filename;
|
248
336
|
}
|
249
337
|
return res;
|
250
338
|
};
|
@@ -192,43 +192,3 @@ export function mergePreviewConfig(uri, previewConfig) {
|
|
192
192
|
|
193
193
|
return uri;
|
194
194
|
}
|
195
|
-
|
196
|
-
/**
|
197
|
-
* 处理传入的数据,数据格式统一转成 Array<fileObject>
|
198
|
-
* @param {Array} fileList Array<fileObject|string>
|
199
|
-
* @param {number} maxCount
|
200
|
-
* @returns Array<fileObject>
|
201
|
-
*/
|
202
|
-
export function handleInputFileList(_fileList, maxCount = 1, isFileObj, isOssUpload, previewConfig) {
|
203
|
-
const fileList = handleMaxCount(getArr(_fileList), maxCount);
|
204
|
-
if (fileList.length === 0) {
|
205
|
-
return fileList;
|
206
|
-
}
|
207
|
-
let resList = fileList;
|
208
|
-
if (!Array.isArray(resList)) {
|
209
|
-
resList = [resList];
|
210
|
-
}
|
211
|
-
resList = fileList.map((file, i) => {
|
212
|
-
if (typeof file === "string") {
|
213
|
-
const uid = nanoid();
|
214
|
-
return {
|
215
|
-
name: uid,
|
216
|
-
uid: uid,
|
217
|
-
ossUrl: file,
|
218
|
-
url: file,
|
219
|
-
// size: Infinity,
|
220
|
-
};
|
221
|
-
}
|
222
|
-
if (isFileObj && isOssUpload) {
|
223
|
-
file.url = mergePreviewConfig(file.ossUrl, previewConfig);
|
224
|
-
}
|
225
|
-
if (file && !file.uid) {
|
226
|
-
file.uid = nanoid();
|
227
|
-
}
|
228
|
-
if (file && !file.name) {
|
229
|
-
file.name = file.uid;
|
230
|
-
}
|
231
|
-
return file;
|
232
|
-
});
|
233
|
-
return resList;
|
234
|
-
}
|
@@ -72,7 +72,7 @@ function Uploader(props) {
|
|
72
72
|
}
|
73
73
|
|
74
74
|
const [loading, setLoading] = useState(false);
|
75
|
-
const [fileList, setFileList] = useState(
|
75
|
+
const [fileList, setFileList] = useState([]);
|
76
76
|
useEffect(() => {
|
77
77
|
setFileList(handleInputFileList(value, maxCount));
|
78
78
|
}, [value]);
|
@@ -13,6 +13,7 @@ import { handleInputFileList, handleOutputFileList } from "./common/handleIOFile
|
|
13
13
|
|
14
14
|
import PreviewModal, { hasPreviewRender, hasPreviewMedium } from "./components/PreviewModal";
|
15
15
|
import { getOssUploadRequest, getOfflineUploadRequest } from "./common/customRequest";
|
16
|
+
import { handlePreviewUrls } from "./common/OfflineUpload";
|
16
17
|
|
17
18
|
import "./uploader.less";
|
18
19
|
|
@@ -62,6 +63,8 @@ export function Uploader({ onChange, ...props }) {
|
|
62
63
|
isFileObj = false,
|
63
64
|
isFileJson = false,
|
64
65
|
} = props;
|
66
|
+
const isPrivateStore =
|
67
|
+
props.uploadParams?.fileAcl === "private" || props.ossOpt?.signatureParams?.fileAcl === "private";
|
65
68
|
|
66
69
|
/**
|
67
70
|
* 数据格式配置
|
@@ -69,14 +72,14 @@ export function Uploader({ onChange, ...props }) {
|
|
69
72
|
const fileListConf = { ...defaultFileListConf, ...props.fileListConf };
|
70
73
|
|
71
74
|
// 参数归一化,兼容老参数
|
72
|
-
const uploadMode = isOssUpload === true ? "oss" : _uploadMode;
|
73
|
-
if (isStrRes === true && isFileObj === false) {
|
75
|
+
const uploadMode = isOssUpload === true && _uploadMode === undefined ? "oss" : _uploadMode;
|
76
|
+
if (!fileListConf.itemMode && isStrRes === true && isFileObj === false) {
|
74
77
|
fileListConf.itemMode = "url";
|
75
78
|
}
|
76
|
-
if (isFileJson === true) {
|
79
|
+
if (!fileListConf.listMode && isFileJson === true) {
|
77
80
|
fileListConf.listMode = "jsonStr";
|
78
81
|
}
|
79
|
-
if (isFileObj === true) {
|
82
|
+
if (!fileListConf.itemMode && isFileObj === true) {
|
80
83
|
fileListConf.itemMode = "object";
|
81
84
|
}
|
82
85
|
|
@@ -101,7 +104,14 @@ export function Uploader({ onChange, ...props }) {
|
|
101
104
|
for (let i = 0; i < _files.length; i++) {
|
102
105
|
let _file = _files[i]?.originFileObj || _files[i];
|
103
106
|
if (!_file.url) {
|
104
|
-
|
107
|
+
const promiseRes = await _file.ossPromise;
|
108
|
+
_file.url =
|
109
|
+
typeof promiseRes === "object"
|
110
|
+
? promiseRes.url
|
111
|
+
: promiseRes || _file.ossUrl || typeof _file === "string"
|
112
|
+
? _file
|
113
|
+
: "";
|
114
|
+
|
105
115
|
if (typeof _file.url === "string") {
|
106
116
|
_files[i].url = _file.url;
|
107
117
|
} else if (typeof _file?.url?.ossUrl === "string") {
|
@@ -128,7 +138,22 @@ export function Uploader({ onChange, ...props }) {
|
|
128
138
|
if (!value) {
|
129
139
|
return;
|
130
140
|
}
|
131
|
-
|
141
|
+
|
142
|
+
const list = handleInputFileList(value, { ...fileListConf, maxCount, previewConfig });
|
143
|
+
|
144
|
+
async function handlePreview() {
|
145
|
+
await handlePreviewUrls(list, {
|
146
|
+
...props.ossOpt?.previewOpt,
|
147
|
+
previewUrl: props.offlinePreviewUrl,
|
148
|
+
});
|
149
|
+
setFileList(list);
|
150
|
+
}
|
151
|
+
|
152
|
+
if (uploadMode === "offline" && isPrivateStore) {
|
153
|
+
handlePreview();
|
154
|
+
} else {
|
155
|
+
setFileList(list);
|
156
|
+
}
|
132
157
|
}, [value]);
|
133
158
|
|
134
159
|
const onRemove = (file) => {
|
@@ -141,12 +166,17 @@ export function Uploader({ onChange, ...props }) {
|
|
141
166
|
if (uploadMode === "oss") {
|
142
167
|
customRequest = getOssUploadRequest({
|
143
168
|
...props,
|
169
|
+
isPrivateStore,
|
170
|
+
params: props.uploadParams || props.params,
|
144
171
|
ossServerUrl: ossServerUrl || props.ossUrl,
|
145
172
|
});
|
146
173
|
} else if (uploadMode === "offline") {
|
147
174
|
customRequest = getOfflineUploadRequest({
|
148
175
|
...props,
|
176
|
+
isPrivateStore,
|
177
|
+
params: props.uploadParams,
|
149
178
|
ossServerUrl: ossServerUrl || props.ossUrl,
|
179
|
+
offlinePreviewUrl: props.offlinePreviewUrl,
|
150
180
|
});
|
151
181
|
}
|
152
182
|
|
@@ -231,6 +261,25 @@ export function Uploader({ onChange, ...props }) {
|
|
231
261
|
window.open(fileUrl);
|
232
262
|
}
|
233
263
|
},
|
264
|
+
// previewFile(file) {
|
265
|
+
// let promise = Promise.resolve(file?.previewUrl || file?.url || file);
|
266
|
+
|
267
|
+
// // TODO: 文件上传之后执行了两次,但第二次的值没有生效并渲染出来。首次执行 file.ossPromise 为 undefined
|
268
|
+
// // console.log("previewFile file", file);
|
269
|
+
// // console.log("previewFile file.ossPromise", file.ossPromise);
|
270
|
+
// // const { uploadParams, ossOpt } = props;
|
271
|
+
// // // 私有域名获取预览地址
|
272
|
+
// // if (file.url && uploadMode === "offline" && isPrivateStore) {
|
273
|
+
// // promise = getPreviewUrl(file.url, {
|
274
|
+
// // ...ossOpt?.previewOpt,
|
275
|
+
// // previewUrl: props.offlinePreviewUrl,
|
276
|
+
// // }).then((res) => {
|
277
|
+
// // return Promise.resolve(res.presignedUrl);
|
278
|
+
// // });
|
279
|
+
// // }
|
280
|
+
|
281
|
+
// return promise;
|
282
|
+
// },
|
234
283
|
...(props || {}),
|
235
284
|
};
|
236
285
|
|
package/src/index.tsx
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import React, { useEffect, useMemo, useImperativeHandle, forwardRef, useCallback } from "react";
|
1
|
+
import React, { useEffect, useMemo, useImperativeHandle, forwardRef, useCallback, useRef } from "react";
|
2
2
|
import { createForm } from "@formily/core";
|
3
3
|
import { createSchemaField } from "@formily/react";
|
4
4
|
import {
|
@@ -67,15 +67,16 @@ const antdComponents = {
|
|
67
67
|
ArrayCards,
|
68
68
|
};
|
69
69
|
|
70
|
-
/** schema scope 解决父级无 schema Scope 导致 scope 对象刷新的问题 */
|
71
|
-
let _schemaScope = { _$tempData: {} };
|
72
|
-
|
73
70
|
const FormRender = forwardRef((props: any, parentRef) => {
|
74
|
-
|
75
|
-
|
71
|
+
/** schema scope 解决父级无 schema Scope 导致 scope 对象刷新的问题 */
|
72
|
+
const schemaScopeRef = useRef<{ _$tempData: Object }>();
|
73
|
+
if (props.schemaScope && !schemaScopeRef.current) {
|
74
|
+
schemaScopeRef.current = props.schemaScope;
|
75
|
+
} else if (!schemaScopeRef.current) {
|
76
|
+
schemaScopeRef.current = { _$tempData: {} };
|
76
77
|
}
|
77
|
-
if (!
|
78
|
-
|
78
|
+
if (!schemaScopeRef.current?._$tempData) {
|
79
|
+
schemaScopeRef.current._$tempData = {};
|
79
80
|
}
|
80
81
|
const SchemaField = useCallback(
|
81
82
|
createSchemaField({
|
@@ -111,7 +112,7 @@ const FormRender = forwardRef((props: any, parentRef) => {
|
|
111
112
|
...customComponents,
|
112
113
|
...props.components,
|
113
114
|
},
|
114
|
-
scope:
|
115
|
+
scope: schemaScopeRef.current,
|
115
116
|
}),
|
116
117
|
[],
|
117
118
|
);
|
@@ -159,7 +160,7 @@ const FormRender = forwardRef((props: any, parentRef) => {
|
|
159
160
|
|
160
161
|
const schema = useMemo(() => {
|
161
162
|
return bindOnChange(props.schema, {
|
162
|
-
schemaScope:
|
163
|
+
schemaScope: schemaScopeRef.current,
|
163
164
|
onChange: props.onChange,
|
164
165
|
formRender,
|
165
166
|
});
|