@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.
@@ -0,0 +1,181 @@
1
+ import { axios } from "@hzab/data-model";
2
+ import { formatDirStr, mergeDirStr, getFileNameByFileObj } from "./uploadUtils";
3
+
4
+ /**
5
+ * getSignature 配置
6
+ */
7
+ export interface IGetSignatureOpt {
8
+ /** 获取配置的接口地址 */
9
+ serverUrl?: string;
10
+ /** axios 实例 */
11
+ axios?: typeof axios;
12
+ /** axios 配置参数 */
13
+ axiosConf?: Object;
14
+ /** 获取配置的接口自定义入参 */
15
+ params?: {
16
+ /** 文件路径 */
17
+ dir?: string;
18
+ /** 是否是公开库 */
19
+ isPublic?: number;
20
+ };
21
+ }
22
+
23
+ /**
24
+ * OssUpload props 参数
25
+ */
26
+ export interface IOssUploadProps {
27
+ /** axios 实例 */
28
+ axios?: typeof axios;
29
+ /** axios 配置参数 */
30
+ axiosConf?: Object;
31
+ /** 获取配置的接口地址 */
32
+ serverUrl?: string;
33
+ /** 获取配置的接口自定义入参 */
34
+ signatureParams?: {
35
+ /** 文件路径 */
36
+ dir?: string;
37
+ /** 是否是公开库 */
38
+ isPublic?: number;
39
+ };
40
+ }
41
+
42
+ export interface IUploadOpt {
43
+ /** axios 实例 */
44
+ axios?: typeof axios;
45
+ /** axios 配置参数 */
46
+ axiosConf?: Object;
47
+ /** 获取配置的接口地址 */
48
+ serverUrl?: string;
49
+ /** 获取配置的接口自定义入参 */
50
+ params?: {
51
+ /** 文件路径 */
52
+ dir?: string;
53
+ /** 是否是公开库 */
54
+ isPublic?: number;
55
+ };
56
+ /** 文件上传的接口自定义入参 */
57
+ ossParams?: {};
58
+ /** 是否使用 hash 文件名 */
59
+ useHashName?: boolean;
60
+ }
61
+
62
+ export function getSignature(opt: IGetSignatureOpt = {}) {
63
+ const { serverUrl = "/api/v1/user/oss/getWebOssConfig" } = opt;
64
+ // 减 10 秒,避免发起请求时 刚好过期的情况
65
+ if (
66
+ window._$ossSignatureRes &&
67
+ serverUrl === window._$ossSignatureRes.serverUrl &&
68
+ Date.now() - window._$ossSignatureRes.__saveTime < window._$ossSignatureRes.expireTimeMilles - 10000
69
+ ) {
70
+ return Promise.resolve(window._$ossSignatureRes);
71
+ }
72
+ const { axios: _ax = axios, params = {}, axiosConf } = opt;
73
+ // 处理 dir 格式,必须为非 / 开头, / 结尾。如: test/
74
+ params.dir = formatDirStr(params.dir);
75
+
76
+ return _ax
77
+ .get(serverUrl, {
78
+ ...axiosConf,
79
+ params: {
80
+ isPublic: 1,
81
+ ...params,
82
+ },
83
+ })
84
+ .then((res) => {
85
+ window._$ossSignatureRes = res?.data?.data ?? res?.data ?? res;
86
+ if (window._$ossSignatureRes) {
87
+ window._$ossSignatureRes.__saveTime = Date.now();
88
+ window._$ossSignatureRes.serverUrl = serverUrl;
89
+ }
90
+ return window._$ossSignatureRes;
91
+ });
92
+ }
93
+
94
+ /**
95
+ * oss 上传工具类
96
+ */
97
+ class OssUpload {
98
+ /** axios 实例 */
99
+ axios: typeof axios;
100
+ /** axios 配置参数 */
101
+ axiosConf: Object;
102
+ /** 获取配置的接口地址 */
103
+ serverUrl: string;
104
+ /** 获取配置的接口自定义入参 */
105
+ signatureParams?: {
106
+ /** 文件路径 */
107
+ dir?: string;
108
+ /** 是否是公开库 */
109
+ isPublic?: number;
110
+ };
111
+
112
+ constructor(props: IOssUploadProps = {}) {
113
+ this.axios = props.axios || axios;
114
+ this.axiosConf = props.axiosConf || {};
115
+ this.serverUrl = props.serverUrl || "/api/v1/user/oss/getWebOssConfig";
116
+ this.signatureParams = props.signatureParams || {};
117
+ }
118
+
119
+ getSignature(serverUrl = this.serverUrl, opt) {
120
+ // dir 前缀 oss-upload 文件目录
121
+ opt.params.dir = mergeDirStr("web-upload/", opt.params.dir);
122
+ return getSignature({
123
+ ...opt,
124
+ serverUrl,
125
+ axios: opt?.axios || this.axios,
126
+ axiosConf: { ...this.axiosConf, ...opt?.axiosConf },
127
+ });
128
+ }
129
+
130
+ upload(file, opt: IUploadOpt = {}) {
131
+ return new Promise(async (resolve, reject) => {
132
+ const ossParams = await this.getSignature(opt.serverUrl || this.serverUrl, {
133
+ ...opt,
134
+ params: { ...this.signatureParams, ...opt.params },
135
+ });
136
+
137
+ const { ossParams: propOssParams } = opt || {};
138
+ const formData = new FormData();
139
+ // key 表示上传到 Bucket 内的 Object 的完整路径,例如 exampledir/exampleobject.txtObject,完整路径中不能包含 Bucket 名称。
140
+ // filename 表示待上传的本地文件名称。
141
+ const filename = getFileNameByFileObj(file, opt);
142
+ const key = `${ossParams?.dir}${filename}`;
143
+ formData.set("key", key);
144
+ formData.set("OSSAccessKeyId", ossParams.accessid);
145
+ formData.set("policy", ossParams.policy);
146
+ formData.set("Signature", ossParams.signature);
147
+ if (ossParams.callback) {
148
+ formData.set("callback", ossParams.callback);
149
+ }
150
+ // @ts-ignore
151
+ formData.set("success_action_status", 200);
152
+ formData.set("file", file);
153
+
154
+ if (propOssParams) {
155
+ for (const key in propOssParams) {
156
+ if (Object.hasOwnProperty.call(propOssParams, key)) {
157
+ formData.set(key, propOssParams[key]);
158
+ }
159
+ }
160
+ }
161
+
162
+ const _axios = opt?.axios || this.axios;
163
+
164
+ return _axios
165
+ .post(ossParams.host, formData, { ...this.axiosConf, ...opt?.axiosConf })
166
+ .then((res) => {
167
+ resolve(res);
168
+ return res;
169
+ })
170
+ .catch((err) => {
171
+ console.error("oss upload err", err);
172
+ reject(err);
173
+ return Promise.reject(err);
174
+ });
175
+ });
176
+ }
177
+ }
178
+
179
+ export { axios };
180
+
181
+ export default OssUpload;
@@ -0,0 +1,118 @@
1
+ import { getArr } from "../array";
2
+
3
+ import { nanoidNumALetters } from "../nanoid";
4
+ import { mergeFileName, getFullFileName } from "../file/fileName";
5
+
6
+ /**
7
+ * 格式化 dir 字符串,必须为非 / 开头, / 结尾。如: test/
8
+ * @param {*} dir
9
+ * @returns
10
+ */
11
+ export const formatDirStr = function (dir) {
12
+ return dir?.replace(/^\/(.*)\/*$/, "$1").replace(/(.?=*)\/*$/, "$1/");
13
+ };
14
+
15
+ /**
16
+ * 合并 dir 字符串
17
+ * @param {*} dir
18
+ */
19
+ export const mergeDirStr = function (dir1, dir2) {
20
+ return `${formatDirStr(dir1 || "")}${formatDirStr(dir2 || "")}`;
21
+ };
22
+
23
+ /**
24
+ * 限制文件数量
25
+ * @param fileList
26
+ * @param maxCount
27
+ * @returns
28
+ */
29
+ export function handleMaxCount(fileList, maxCount) {
30
+ let list = getArr(fileList);
31
+ if (maxCount > 0 && list.length > maxCount) {
32
+ list = list.slice(0, maxCount);
33
+ }
34
+ return list;
35
+ }
36
+
37
+ /**
38
+ * 获取预览 url
39
+ * @param {*} uri
40
+ * @param {*} previewConfig
41
+ * @returns
42
+ */
43
+ export function getPreviewUrl(uri, previewConfig) {
44
+ const isUriPattern = /http[s]?:\/\/[^\s]+/.test(uri);
45
+ const query = previewConfig?.query ? "?" + previewConfig?.query : "";
46
+
47
+ if (isUriPattern) return `${uri}${query}`?.trim();
48
+
49
+ if (previewConfig) return `${previewConfig?.url}/${uri}${query}`?.trim();
50
+
51
+ return uri;
52
+ }
53
+
54
+ /**
55
+ * 根据 file 对象获取文件名称,文件名称包含:初始名称、类型
56
+ * 数据存储格式 ~k[key]-[data]~ 如: ~ktime-1743649562530~
57
+ * key、data 中的 ~ 转为 _
58
+ * @param file
59
+ */
60
+ export const getFileNameByFileObj = (file, opt: any) => {
61
+ const { useHashName = true } = opt || {};
62
+ const _fileName = file.name || getFullFileName(file.url);
63
+ if (_fileName.indexOf("~kid-") >= 0 || !useHashName) {
64
+ return _fileName;
65
+ }
66
+ // id、时间、文件类型
67
+ const id = file.id || file.uid || nanoidNumALetters();
68
+ return mergeFileName(
69
+ _fileName,
70
+ "-" +
71
+ setFileNameObj({
72
+ id,
73
+ time: file.createTime ?? Date.now(),
74
+ type: (file.type || file.contentType)?.replace("/", "_"),
75
+ }),
76
+ );
77
+ };
78
+
79
+ /**
80
+ * 解析
81
+ * @param url
82
+ * @returns
83
+ */
84
+ export const getFileNameObj = function (url): any {
85
+ const list = url.match(/~k([^-~]+)-([^~]*)~/g);
86
+ const res = {};
87
+ list?.forEach((it) => {
88
+ const arr = it?.match(/~k([^-~]+)-([^~]*)~/);
89
+ if (arr?.length >= 3) {
90
+ res[arr[1]] = arr[2];
91
+ }
92
+ });
93
+ return res;
94
+ };
95
+
96
+ /**
97
+ * 解析
98
+ * @param url
99
+ * @returns
100
+ */
101
+ export function setFileNameObj(obj) {
102
+ return Object.keys(obj)
103
+ .map((key) => {
104
+ return `~k${key?.replace(/~/g, "_")}-${obj[key]?.toString()?.replace(/~/g, "_")}~`;
105
+ })
106
+ .join("");
107
+ }
108
+
109
+ /**
110
+ * 通过 key 获取对应的值
111
+ * @param url
112
+ * @param key
113
+ * @returns
114
+ */
115
+ export const getValByKey = function (url, key) {
116
+ const reg = new RegExp(`~k${key?.replace(/~/g, "_")}-\([^~]*\)~`);
117
+ return url.match(reg)?.[1];
118
+ };
package/src/url.ts ADDED
@@ -0,0 +1,120 @@
1
+ /**
2
+ * 替换 search 字符串
3
+ * @param url
4
+ * @param search
5
+ * @returns
6
+ */
7
+ export function replaceSearch(url, search) {
8
+ if (!url || typeof url !== "string") {
9
+ console.warn("Warn handleUrlQuery: 请传入正确的 url 字符串");
10
+ return url;
11
+ }
12
+ let _url = fixUrlQuery(url);
13
+
14
+ const hashIdx = _url.indexOf("#");
15
+ const queryIdx = _url.indexOf("?");
16
+ let hash = "";
17
+ let resUrl = _url;
18
+ if (queryIdx >= 0) {
19
+ resUrl = _url.slice(0, queryIdx);
20
+ }
21
+ if (hashIdx >= 0) {
22
+ hash = _url.slice(hashIdx);
23
+ }
24
+
25
+ resUrl += search?.indexOf("?") >= 0 ? "" : "?";
26
+ resUrl += search;
27
+ resUrl += hash;
28
+
29
+ return resUrl;
30
+ }
31
+
32
+ /**
33
+ * 把对象转为 query 参数
34
+ * @param query
35
+ */
36
+ export function objToSearch(query, hasQuery) {
37
+ if (!query || typeof query !== "object") {
38
+ console.warn("Warn objToSearch: 请传入正确的对象");
39
+ return;
40
+ }
41
+ let search = hasQuery ? "?" : "";
42
+ Object.keys(query).forEach((key) => {
43
+ if (search && search != "?") {
44
+ search += "&";
45
+ }
46
+ search += key;
47
+ search += "=";
48
+ search += query[key];
49
+ });
50
+ return search;
51
+ }
52
+
53
+ /**
54
+ * 修复 query,解决 query 在 hash 后面的情况
55
+ * @param url
56
+ * @returns
57
+ * 源数据:/#/login?a=1&b=2
58
+ * 结果:/?a=1&b=2#/login
59
+ */
60
+ export function fixUrlQuery(url) {
61
+ if (!url || typeof url !== "string") {
62
+ console.warn("Warn handleUrlQuery: 请传入正确的 url 字符串");
63
+ return url;
64
+ }
65
+ const hashIdx = url.indexOf("#");
66
+ const queryIdx = url.indexOf("?");
67
+
68
+ // 双 query 情况
69
+ if (queryIdx < hashIdx) {
70
+ let hash = url.slice(hashIdx);
71
+ const hashQueryIdx = hash.indexOf("?");
72
+ if (hashQueryIdx >= 0) {
73
+ const _url = url.slice(0, queryIdx);
74
+ const urlQuery = url.slice(queryIdx, hashIdx)?.replace(/\/$/, "");
75
+ const hashQuery = hash.slice(hashQueryIdx)?.replace("?", "")?.replace(/\/$/, "");
76
+ hash = hash.slice(0, hashQueryIdx);
77
+ return `${_url}${urlQuery}&${hashQuery}${hash}`;
78
+ }
79
+ return url;
80
+ }
81
+
82
+ // query 在 hash # 之后
83
+ if (queryIdx >= 0 && hashIdx >= 0 && queryIdx > hashIdx) {
84
+ const hash = url.slice(hashIdx, queryIdx);
85
+ const search = url.slice(queryIdx);
86
+ return `${url.slice(0, hashIdx)}${search}${hash}`;
87
+ }
88
+ return url;
89
+ }
90
+
91
+ /**
92
+ * 获取 url 中的 query 值的对象
93
+ * @param query
94
+ */
95
+ export function searchToObj(url) {
96
+ if (!url || typeof url !== "string") {
97
+ console.warn("Warn getSearchParams: 请传入正确的 url 字符串.");
98
+ return;
99
+ }
100
+ let _url = fixUrlQuery(url)
101
+ // 解决 query 在 /# 之前导致 query 参数多了 / 的问题
102
+ .replace("/#", "#");
103
+ const queryIdx = _url.indexOf("?");
104
+ const hashIdx = _url.indexOf("#");
105
+ if (queryIdx < 0) {
106
+ return;
107
+ }
108
+ // 去除 hash 相关数据及 query 的 "?"
109
+ if (hashIdx >= 0 && queryIdx >= 0) {
110
+ _url = _url.slice(queryIdx + 1, hashIdx);
111
+ } else if (queryIdx >= 0) {
112
+ _url = _url.slice(queryIdx + 1);
113
+ }
114
+ const res = {};
115
+ _url.split("&")?.forEach((it) => {
116
+ const arr = it.split("=");
117
+ res[arr[0]] = arr[1];
118
+ });
119
+ return res;
120
+ }