@hzab/utils 0.0.1

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/src/options.ts ADDED
@@ -0,0 +1,226 @@
1
+ import { cloneDeep } from "lodash";
2
+
3
+ import { checkEmpty, handleEmptyVal, IHandleEmptyValOpt } from "./empty-val";
4
+
5
+ export interface IFieldNames {
6
+ value?: string;
7
+ label?: string;
8
+ children?: string;
9
+ }
10
+
11
+ export interface IFindOptionOpt extends IHandleEmptyValOpt {
12
+ fieldNames?: IFieldNames;
13
+ // 是否返回完整的路径数组
14
+ isPathList?: boolean;
15
+ pathList?: Array<any>;
16
+ labelList?: Array<string>;
17
+ parent?: any;
18
+ }
19
+
20
+ export interface IGetArrayLabels extends IFindOptionOpt {
21
+ split?: string;
22
+ }
23
+
24
+ export interface IGetValLabelOpt extends IHandleEmptyValOpt {
25
+ showEmpty?: boolean;
26
+ }
27
+
28
+ export type formatOptionsOptT = {
29
+ fieldNames?: IFieldNames;
30
+ sourceFieldNames?: IFieldNames;
31
+ /**
32
+ * 是否清除其他参数
33
+ */
34
+ isClear?: boolean;
35
+ };
36
+
37
+ export const DEF_FIELD_NAMES = {
38
+ value: "value",
39
+ label: "label",
40
+ children: "children",
41
+ };
42
+
43
+ /**
44
+ * 获取无限级数据的选中项
45
+ * @param val
46
+ * @param options
47
+ * @param opt
48
+ * @returns
49
+ */
50
+ export const findOptionByVal = function (
51
+ val,
52
+ options,
53
+ opt: IFindOptionOpt = {
54
+ // 是否返回完整的路径数组
55
+ isPathList: false,
56
+ pathList: [],
57
+ labelList: [],
58
+ parent: undefined,
59
+ fieldNames: {
60
+ label: "label",
61
+ value: "value",
62
+ children: "children",
63
+ },
64
+ },
65
+ ) {
66
+ if (!options) {
67
+ console.warn("getDeepData 请传入 options");
68
+ return;
69
+ }
70
+ const { fieldNames, isPathList, pathList = [], labelList = [] } = opt || {};
71
+ const { label = "label", value = "value", children = "children" } = fieldNames || {};
72
+ const res = options.find((it) => it[value] === val);
73
+ if (res) {
74
+ if (isPathList) {
75
+ pathList.unshift(res);
76
+ labelList.unshift(res[label]);
77
+ return {
78
+ last: res,
79
+ pathList,
80
+ labelList,
81
+ };
82
+ }
83
+ return res;
84
+ }
85
+ const len = options.length;
86
+ for (let i = 0; i < len; i++) {
87
+ const item = options[i];
88
+ const childList = item[children];
89
+ if (Array.isArray(childList) && childList.length > 0) {
90
+ const res = findOptionByVal(val, childList, {
91
+ ...opt,
92
+ parent: item,
93
+ pathList,
94
+ labelList,
95
+ });
96
+ if (res) {
97
+ if (isPathList) {
98
+ pathList.unshift(item);
99
+ labelList.unshift(item[label]);
100
+ return {
101
+ last: res.last,
102
+ labelList,
103
+ pathList,
104
+ };
105
+ }
106
+ return res;
107
+ }
108
+ }
109
+ }
110
+ };
111
+
112
+ /**
113
+ * 获取 options 中 val 对应的 label
114
+ * @param {*} val
115
+ * @param {Array<Object>} options
116
+ * @param {Object} opt
117
+ * @returns
118
+ */
119
+ export const getOptionLabel = function (val, options = [], opt: IFindOptionOpt = {}) {
120
+ const { fieldNames = DEF_FIELD_NAMES } = opt || {};
121
+ return findOptionByVal(val, options, opt)?.[fieldNames?.label || DEF_FIELD_NAMES.label];
122
+ };
123
+
124
+ /**
125
+ * 获取 options 中的指定项 的 label
126
+ * @param {*} val
127
+ * @param {*} options
128
+ * @param {*} opt
129
+ * @returns
130
+ */
131
+ export function getValLabel(val, options = [], opt: IGetValLabelOpt = {}) {
132
+ const { showEmpty } = opt;
133
+ if (showEmpty && checkEmpty(val, opt)) {
134
+ return handleEmptyVal(val, opt);
135
+ }
136
+ return getOptionLabel(val, options, opt);
137
+ }
138
+
139
+ /**
140
+ * 获取 options 中的指定项 的 label 数组
141
+ * @param {*} values
142
+ * @param {*} options
143
+ * @param {*} opt
144
+ * @returns
145
+ */
146
+ export function getArrayLabels(values, options, opt: IGetArrayLabels = {}) {
147
+ if (checkEmpty(values, opt)) {
148
+ return handleEmptyVal(values, opt);
149
+ }
150
+ const { split = "、" } = opt || {};
151
+ let _values = values;
152
+ if (!Array.isArray(_values)) {
153
+ _values = [_values];
154
+ }
155
+ return _values
156
+ ?.map((val) => getOptionLabel(val, options, opt) ?? val)
157
+ ?.filter((it) => (it ?? it) || false)
158
+ ?.join(split);
159
+ }
160
+
161
+ /**
162
+ * 格式化 options 数组
163
+ */
164
+ export const formatOptions = function (options, opt: formatOptionsOptT = {}) {
165
+ if (!Array.isArray(options)) {
166
+ console.warn("formatOptions Error: options not Array");
167
+ return options;
168
+ }
169
+
170
+ const {
171
+ fieldNames = DEF_FIELD_NAMES,
172
+ sourceFieldNames = DEF_FIELD_NAMES,
173
+ // 是否清除其他参数
174
+ isClear,
175
+ } = opt || {};
176
+
177
+ const { label, value, children } = fieldNames;
178
+ const { label: sLabel, value: sValue, children: sChildren } = sourceFieldNames;
179
+
180
+ const _options = cloneDeep(options);
181
+ const resOptions = [];
182
+ _options.forEach((it) => {
183
+ const _it = isClear ? {} : { ...it };
184
+ _it[value] = it[sValue];
185
+ _it[label] = it[sLabel];
186
+ resOptions.push(_it);
187
+ const childList = it[sChildren];
188
+ if (childList) {
189
+ _it[children] = formatOptions(childList, opt);
190
+ }
191
+ });
192
+ return resOptions;
193
+ };
194
+
195
+ /**
196
+ * 根据 val 获取对应的 options 项数据,并进行数据归一化处理
197
+ * value, label, children
198
+ * @param {string|number|boolean} val
199
+ * @param {Object} field
200
+ */
201
+ export const getOptItByVal = function (val, options, opt) {
202
+ const { fieldNames } = opt || {};
203
+ const { value = "value", label = "label", children = "children" } = fieldNames || {};
204
+ for (let i = 0; i < options.length; i++) {
205
+ const it = options[i];
206
+ if (it?.[value] === val) {
207
+ return {
208
+ ...it,
209
+ value: it?.[value],
210
+ label: it?.[label],
211
+ children: it?.[children],
212
+ };
213
+ }
214
+ // 递归处理
215
+ const _children = it?.[children];
216
+ if (Array.isArray(_children) && _children.length > 0) {
217
+ const child = getOptItByVal(val, _children, opt);
218
+ return {
219
+ ...child,
220
+ value: child?.[value],
221
+ label: child?.[label],
222
+ children: child?.[children],
223
+ };
224
+ }
225
+ }
226
+ };
package/src/string.ts ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * 是否是 base64 字符串
3
+ * @param {*} str
4
+ * @returns
5
+ */
6
+ export const isBase64Str = function (str) {
7
+ return /^data:\w+\/\w+;base64/i.test(str);
8
+ };
9
+
10
+ /**
11
+ * 字符串中划线转为小驼峰
12
+ * @param s
13
+ * @returns
14
+ */
15
+ export const convertToCamelCase = function (s) {
16
+ return s.replace(/-(\w)/g, function (all, letter) {
17
+ return letter.toUpperCase();
18
+ });
19
+ };
@@ -0,0 +1,338 @@
1
+ import { axios } from "@hzab/data-model";
2
+
3
+ import { formatDirStr, mergeDirStr, getFileNameByFileObj } from "./uploadUtils";
4
+
5
+ /**
6
+ * 获取上传相关配置入参
7
+ */
8
+ export interface IGetSignatureOpt {
9
+ /**
10
+ * 配置接口地址
11
+ */
12
+ serverUrl?: string;
13
+ /**
14
+ * axios 实例
15
+ */
16
+ axios?: Object;
17
+ /**
18
+ * axios 配置
19
+ */
20
+ axiosConf?: Object;
21
+ /**
22
+ * 请求入参
23
+ */
24
+ params?: {
25
+ path?: string;
26
+ };
27
+ }
28
+
29
+ export interface IOfflineUploadProps {
30
+ /**
31
+ * axios 实例
32
+ */
33
+ axios?: Object;
34
+ /**
35
+ * axios 配置
36
+ */
37
+ axiosConf?: Object;
38
+ /**
39
+ * 配置接口地址
40
+ */
41
+ serverUrl?: string;
42
+ /**
43
+ * 获取配置接口请求入参
44
+ */
45
+ signatureParams?: {
46
+ path?: string;
47
+ };
48
+ }
49
+
50
+ /**
51
+ * 文件上传入参
52
+ */
53
+ export interface IUploadOpt {
54
+ /**
55
+ * 配置接口地址
56
+ */
57
+ serverUrl?: string;
58
+ /**
59
+ * 是否开启 Hash 命名
60
+ */
61
+ useHashName?: boolean;
62
+ /**
63
+ * 文件上传接口地址
64
+ */
65
+ uploadUrl?: string;
66
+ /**
67
+ * 获取配置接口请求入参
68
+ */
69
+ params?: {
70
+ path?: string;
71
+ // 兼容 老数据
72
+ dir?: string;
73
+ };
74
+ /**
75
+ * 文件上传请求入参
76
+ */
77
+ ossParams?: {
78
+ path?: string;
79
+ // 兼容 老数据
80
+ dir?: string;
81
+ };
82
+ /**
83
+ * axios 实例
84
+ */
85
+ axios?: Object;
86
+ /**
87
+ * axios 配置
88
+ */
89
+ axiosConf?: Object;
90
+ }
91
+
92
+ /**
93
+ * 获取上传配置
94
+ * @param opt
95
+ * @returns
96
+ */
97
+ export function getSignature(opt: IGetSignatureOpt = {}) {
98
+ const { serverUrl = "/api/v1/upload/init" } = opt;
99
+ // 减 10 秒,避免发起请求时 刚好过期的情况
100
+ const { axios: _ax = axios, params = {}, axiosConf } = opt;
101
+ if (
102
+ window._$offlineSignatureRes &&
103
+ window._$offlineSignatureRes.data &&
104
+ serverUrl + JSON.stringify(params) === window._$offlineSignatureRes.serverUrlParams &&
105
+ Date.now() - window._$offlineSignatureRes.__saveTime < window._$offlineSignatureRes.expireTimeMilles - 10000
106
+ ) {
107
+ return Promise.resolve(window._$offlineSignatureRes.data);
108
+ }
109
+ // 处理 path 格式,必须为非 / 开头, / 结尾。如: test/
110
+ params.path = formatDirStr(params.path);
111
+
112
+ return _ax
113
+ .post(
114
+ serverUrl,
115
+ {
116
+ ...params,
117
+ },
118
+ {
119
+ ...axiosConf,
120
+ },
121
+ )
122
+ .then((res) => {
123
+ if (res?.data?.code === 500) {
124
+ return Promise.reject(res.data);
125
+ }
126
+ if (res?.data?.code === 200) {
127
+ window._$offlineSignatureRes = {
128
+ data: res?.data?.data,
129
+ };
130
+ if (window._$offlineSignatureRes) {
131
+ window._$offlineSignatureRes.__saveTime = Date.now();
132
+ window._$offlineSignatureRes.serverUrlParams = serverUrl + JSON.stringify(params);
133
+ }
134
+ return window._$offlineSignatureRes.data;
135
+ }
136
+ });
137
+ }
138
+
139
+ export interface IGetPreviewUrlsOpt {
140
+ previewUrl?: string;
141
+ params?: Object;
142
+ axios?: Object;
143
+ axiosConf?: Object;
144
+ }
145
+
146
+ /**
147
+ * 获取预览地址-批量
148
+ * @param urlList
149
+ * @param opt
150
+ * @returns
151
+ */
152
+ export const getPreviewUrls = function (urlList, opt: IGetPreviewUrlsOpt = {}) {
153
+ return new Promise(async (resolve, reject) => {
154
+ const { axios: _ax = axios, axiosConf, previewUrl } = opt || {};
155
+
156
+ const _axios = _ax;
157
+
158
+ let _list = urlList;
159
+ if (_list && !Array.isArray(_list)) {
160
+ _list = [_list];
161
+ }
162
+
163
+ return _axios
164
+ .post(previewUrl || "/api/v1/upload/preview", _list, { ...axiosConf })
165
+ .then((res) => {
166
+ if (res?.data?.code == 200) {
167
+ resolve(res?.data?.data);
168
+ } else {
169
+ reject(res);
170
+ }
171
+ return res;
172
+ })
173
+ .catch((err) => {
174
+ console.error("offline upload err", err);
175
+ reject(err);
176
+ return Promise.reject(err);
177
+ });
178
+ });
179
+ };
180
+
181
+ /**
182
+ * 获取预览地址
183
+ * @param url
184
+ * @param opt
185
+ * @returns
186
+ */
187
+ export const getPreviewUrl = function (url, opt: IGetPreviewUrlsOpt = {}) {
188
+ return new Promise(async (resolve, reject) => {
189
+ const { axios: _ax = axios, axiosConf, previewUrl } = opt || {};
190
+
191
+ const _axios = _ax;
192
+
193
+ return _axios
194
+ .post(previewUrl || "/api/v1/upload/preview", [url], { ...axiosConf })
195
+ .then((res) => {
196
+ if (res?.data?.code == 200) {
197
+ resolve(res?.data?.data?.[0]);
198
+ } else {
199
+ reject(res);
200
+ }
201
+ return res;
202
+ })
203
+ .catch((err) => {
204
+ console.error("offline upload err", err);
205
+ reject(err);
206
+ return Promise.reject(err);
207
+ });
208
+ });
209
+ };
210
+
211
+ /**
212
+ * 私有部署版文件上传
213
+ */
214
+ export class OfflineUpload {
215
+ axios;
216
+ axiosConf;
217
+ serverUrl;
218
+ signatureParams;
219
+ constructor(props: IOfflineUploadProps = {}) {
220
+ this.axios = props.axios || axios;
221
+ this.axiosConf = props.axiosConf || {};
222
+ this.serverUrl = props.serverUrl || "/api/v1/upload/init";
223
+ this.signatureParams = props.signatureParams || {};
224
+ }
225
+
226
+ getSignature(serverUrl = this.serverUrl, opt) {
227
+ // path 前缀 oss-upload 文件目录
228
+ opt.params.path = mergeDirStr("web-upload/", opt.params.path ?? opt.params.dir);
229
+ return getSignature({
230
+ ...opt,
231
+ serverUrl,
232
+ axios: opt?.axios || this.axios,
233
+ axiosConf: { ...this.axiosConf, ...opt?.axiosConf },
234
+ });
235
+ }
236
+
237
+ upload(file, opt: IUploadOpt = {}) {
238
+ return new Promise(async (resolve, reject) => {
239
+ // filename 表示待上传的本地文件名称。
240
+ const filename = getFileNameByFileObj(file, opt);
241
+ try {
242
+ const fileInfo = await this.getSignature(opt.serverUrl || this.serverUrl, {
243
+ ...opt,
244
+ params: {
245
+ /** 最终保存的文件名称(不传默认文件名) */
246
+ filename,
247
+ /** 存储路径(不传默认基础路径) */
248
+ path: opt.params?.dir || this.signatureParams?.dir,
249
+ /** 文件存储平台(1、s3,2、本地存储,3、FTP,默认s3) */
250
+ platformCode: undefined,
251
+ ...this.signatureParams,
252
+ ...opt.params,
253
+ },
254
+ });
255
+ const { uploadUrl, ossParams: propOssParams } = opt || {};
256
+ const formData = new FormData();
257
+ formData.set("fileInfo", fileInfo);
258
+ formData.set("file", file);
259
+
260
+ if (propOssParams) {
261
+ for (const key in propOssParams) {
262
+ if (Object.hasOwnProperty.call(propOssParams, key)) {
263
+ formData.set(key, propOssParams[key]);
264
+ }
265
+ }
266
+ }
267
+
268
+ const _axios = opt?.axios || this.axios;
269
+
270
+ return _axios
271
+ .post(uploadUrl || "/api/v1/upload/execute", formData, { ...this.axiosConf, ...opt?.axiosConf })
272
+ .then((res) => {
273
+ if (res?.data?.code == 200) {
274
+ resolve(res?.data?.data);
275
+ } else {
276
+ reject(res);
277
+ }
278
+ return res;
279
+ })
280
+ .catch((err) => {
281
+ console.error("offline upload err", err);
282
+ reject(err);
283
+ return Promise.reject(err);
284
+ });
285
+ } catch (error) {
286
+ reject(error);
287
+ return Promise.reject(error);
288
+ }
289
+ });
290
+ }
291
+
292
+ /**
293
+ * 获取预览地址
294
+ * @param urlList
295
+ * @param opt
296
+ * @returns
297
+ */
298
+ getPreviewUrls(urlList, opt) {
299
+ return getPreviewUrls(urlList, {
300
+ ...opt,
301
+ axios: opt?.axios || this.axios,
302
+ axiosConf: { ...this.axiosConf, ...opt?.axiosConf },
303
+ });
304
+ }
305
+ }
306
+
307
+ /**
308
+ * 批量处理预览地址
309
+ * @param fileList
310
+ * @param opt
311
+ * @returns
312
+ */
313
+ export const handlePreviewUrls = async function (fileList, opt: IGetPreviewUrlsOpt) {
314
+ const res = await getPreviewUrls(
315
+ fileList?.map((it) => it.storeUrl || it.url),
316
+ {
317
+ ...opt,
318
+ },
319
+ );
320
+
321
+ fileList.forEach((it, i) => {
322
+ // TODO: storeUrl 来源于【本地 url 直接使用预览地址,解决 previewFile 无法正常使用 previewUrl 的问题】
323
+ if (!it.storeUrl) {
324
+ it.storeUrl = it.url;
325
+ }
326
+ // TODO: 本地 url 直接使用预览地址,解决 previewFile 无法正常使用 previewUrl 的问题
327
+ it.url = res[i].presignedUrl;
328
+
329
+ // 常规预览地址写法
330
+ it.previewUrl = res[i].presignedUrl;
331
+ if (it.uploadInfo) {
332
+ it.uploadInfo.previewUrl = it.previewUrl;
333
+ }
334
+ });
335
+ return fileList;
336
+ };
337
+
338
+ export default OfflineUpload;