@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 CHANGED
@@ -1,3 +1,11 @@
1
+ # @hzab/form-render@1.6.0
2
+
3
+ feat: 私有部署文件上传
4
+
5
+ # @hzab/form-render@1.5.1
6
+
7
+ fix: schemaScope 使用 Ref 缓存,解决 onChange tempData 更新问题
8
+
1
9
  # @hzab/form-render@1.5.0
2
10
 
3
11
  feat: ossUpload 文件目录配置 dir 前缀 oss-upload 文件目录;dir 格式格式化
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hzab/form-render",
3
- "version": "1.5.0",
3
+ "version": "1.5.1-beta",
4
4
  "description": "",
5
5
  "main": "src",
6
6
  "scripts": {
@@ -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
- export const getOfflineUploadRequest = (props) => {
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
- // TODO: 处理请求逻辑
6
- file.ossPromise = new Promise((resolve) => {
7
- resolve(file);
8
- })
9
- .then((file) => {
10
- onSuccess(file);
11
- return file;
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", `-${Date.now()}-${nanoid()}`);
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
- return mergeFileName(_fileName, `--Cs--${file.type?.replace(/[\/\-\+]/g, "_")}-${Date.now()}-${nanoid()}`);
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
- uid: string;
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
- previewUrl: "",
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
- if (typeof uri !== "string") {
101
- return uri;
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
- let _uri = uri?.trim();
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.sourceName = getFullFileName(file.url);
143
- file.name = file.sourceName;
144
- file.type = getFileType(_data);
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.uid = _data.uid;
194
+ file.id = _data.id || _data.uid;
148
195
  file.url = sourceData.url || sourceData.ossUrl;
149
- file.sourceName = _data.sourceName || getFullFileName(file.url) || _data.name;
150
- file.name = _data.name || getFullFileName(file.url);
151
- file.type = _data.type;
152
- file.size = _data.size;
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.uid) {
157
- file.uid = `${getFileName(file.url || file.name) || "rc-upload"}-${Date.now()}-${nanoid()}`;
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.uid;
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.url, previewConfig);
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
- let url = data.url || (data instanceof File ? getFileURL(data) : data.url);
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
- uid: data.uid || getUFileName(data.name),
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.sourceName) {
247
- res.sourceName = res.name;
334
+ if (!res.originalFilename) {
335
+ res.originalFilename = res.name || res.filename;
248
336
  }
249
337
  return res;
250
338
  };
@@ -4,4 +4,4 @@ import { customAlphabet } from "nanoid";
4
4
  */
5
5
  export const alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
6
6
 
7
- export const nanoid = customAlphabet(alphabet, 10);
7
+ export const nanoid = customAlphabet(alphabet, 8);
@@ -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(handleInputFileList(value, maxCount));
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
- _file.url = (await _file.ossPromise) || _file.ossUrl || typeof _file === "string" ? _file : "";
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
- setFileList(handleInputFileList(value, { ...fileListConf, maxCount, previewConfig }));
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
- if (props.schemaScope) {
75
- _schemaScope = props.schemaScope;
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 (!props.schemaScope?._$tempData) {
78
- _schemaScope._$tempData = {};
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: _schemaScope,
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: _schemaScope,
163
+ schemaScope: schemaScopeRef.current,
163
164
  onChange: props.onChange,
164
165
  formRender,
165
166
  });