jinbi-utils 1.0.20 → 1.0.21

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.
Files changed (83) hide show
  1. package/.babelrc +19 -0
  2. package/.cz-config.js +55 -0
  3. package/.dockerignore +3 -0
  4. package/.editorconfig +12 -0
  5. package/.eslintignore +8 -0
  6. package/.eslintrc.js +54 -0
  7. package/.versionrc.json +9 -0
  8. package/CHANGELOG.md +49 -49
  9. package/CHUNK_OPTIMIZER_USAGE.md +132 -0
  10. package/Dockerfile +3 -0
  11. package/QUICK_RELEASE.md +85 -0
  12. package/README.md +189 -189
  13. package/RELEASE_GUIDE.md +243 -0
  14. package/api-extractor.json +15 -0
  15. package/commitlint.config.js +3 -0
  16. package/jest.config.js +15 -0
  17. package/package.json +82 -109
  18. package/rollup.config.chunk-optimizer.js +32 -0
  19. package/rollup.config.js +73 -0
  20. package/src/array/index.ts +85 -0
  21. package/src/build/chunk-optimizer/ARCHITECTURE.md +347 -0
  22. package/src/build/chunk-optimizer/QUICK_START.md +370 -0
  23. package/src/build/chunk-optimizer/README.md +240 -0
  24. package/src/build/chunk-optimizer/core/chunk-generator.ts +166 -0
  25. package/src/build/chunk-optimizer/core/classifier.ts +148 -0
  26. package/src/build/chunk-optimizer/core/dependency-reader.ts +138 -0
  27. package/src/build/chunk-optimizer/examples/basic-usage.ts +234 -0
  28. package/src/build/chunk-optimizer/index.ts +166 -0
  29. package/src/build/chunk-optimizer/rules/common-rules.ts +131 -0
  30. package/src/build/chunk-optimizer/rules/framework-rules.ts +93 -0
  31. package/src/build/chunk-optimizer/rules/index.ts +27 -0
  32. package/src/build/chunk-optimizer/test.ts +94 -0
  33. package/src/build/chunk-optimizer/types.ts +128 -0
  34. package/src/color/index.ts +58 -0
  35. package/src/common/index.ts +353 -0
  36. package/src/constant/common.constant.ts +13 -0
  37. package/src/date/index.ts +143 -0
  38. package/src/dom/index.ts +198 -0
  39. package/src/file/index.ts +319 -0
  40. package/src/http/apiBuilder/README.md +648 -0
  41. package/src/http/apiBuilder/api-builder.ts +502 -0
  42. package/src/http/apiBuilder/example.ts +243 -0
  43. package/src/http/apiBuilder/index.ts +1 -0
  44. package/src/http/apiBuilder//345/277/253/351/200/237/345/217/202/350/200/203.md +199 -0
  45. package/src/http/http.ts +79 -0
  46. package/src/http/httpEnums.ts +61 -0
  47. package/src/iam/index.ts +46 -0
  48. package/src/index.ts +20 -0
  49. package/src/middleware/requestLogger.middware.ts +371 -0
  50. package/src/middleware/requestLoggerUnified.ts +371 -0
  51. package/src/number/index.ts +362 -0
  52. package/src/object/index.ts +54 -0
  53. package/src/print/index.ts +102 -0
  54. package/src/string/index.ts +189 -0
  55. package/src/utils/curl.ts +108 -0
  56. package/src/validate/index.ts +100 -0
  57. package/src/websocket/emitter.ts +39 -0
  58. package/src/websocket/index.ts +6 -0
  59. package/src/websocket/manager.ts +151 -0
  60. package/src/websocket/pinia-store.ts +91 -0
  61. package/src/websocket/service.ts +34 -0
  62. package/src/websocket/types.ts +45 -0
  63. package/test/common/index.test.ts +19 -0
  64. package/test/date/index.test.ts +107 -0
  65. package/test/file/index.test.ts +104 -0
  66. package/test/number/index.test.ts +108 -0
  67. package/test/object/index.test.ts +20 -0
  68. package/test/string/index.test.ts +82 -0
  69. package/tsconfig.json +39 -0
  70. package/typedoc.json +12 -0
  71. package/types/file/index.d.ts +7 -0
  72. package/types/index.d.ts +1 -0
  73. package/types/websocket/emitter.d.ts +16 -0
  74. package/types/websocket/index.d.ts +6 -0
  75. package/types/websocket/manager.d.ts +36 -0
  76. package/types/websocket/pinia-store.d.ts +25 -0
  77. package/types/websocket/service.d.ts +13 -0
  78. package/types/websocket/types.d.ts +34 -0
  79. package/dist/chunk-optimizer.cjs +0 -703
  80. package/dist/index.esm.js +0 -2791
  81. package/dist/index.esm.min.js +0 -15
  82. package/dist/index.umd.js +0 -2899
  83. package/dist/index.umd.min.js +0 -16
@@ -0,0 +1,143 @@
1
+ /**
2
+ * 时间处理相关
3
+ * @packageDocumentation
4
+ * @module Date
5
+ * @preferred
6
+ */
7
+ import { isEmpty } from '../common';
8
+
9
+ /**
10
+ * 解决ios不支持 new Date('2020-02-02')问题
11
+ #### 使用说明
12
+ ```
13
+ compatibleDate('2020-03-04') 返回 2020/03/04
14
+ compatibleDate(Date.now()) 返回 Date.now()
15
+ compatibleDate(new Date()) 返回 new Date()
16
+ ```
17
+ */
18
+ export function compatibleDate(date: string | number | Date) {
19
+ if (typeof date === 'string') {
20
+ return date.replace(/-/g, '/');
21
+ }
22
+ return date;
23
+ }
24
+
25
+ /**
26
+ * 调用原生 new Date 返回 Date实例 解决ios不支持 yyyy-MM-dd HH:mm:ss 格式new Date
27
+ #### 使用说明
28
+ ```
29
+ newDate() 返回 new Date()
30
+ newDate('2020-03-04') 返回 new Date('2020-03-04')
31
+ ```
32
+ */
33
+ export function newDate(date?: string | number | Date): Date {
34
+ if (!date) return new Date();
35
+ return new Date(compatibleDate(date));
36
+ }
37
+
38
+ /**
39
+ * 格式化时间
40
+ #### 使用说明
41
+ ```
42
+ formatTime() 返回 yyyy-MM-dd HH:mm:ss 格式的当前时间
43
+ formatTime('2018-02-02 00:00:00') 返回 2018-02-02 00:00:00
44
+ formatTime('2018-02-02', 'yyyy/MM/dd') 返回 2018/02/02
45
+ formatTime('2018-02-02 23:50:50', 'yyyy/MM/dd HH:mm:ss') 返回 2018/02/02 23:50:50
46
+ formatTime('2018-02-02 23:50:50', 'MM/dd HH:mm:ss') 返回 02/02 23:50:50
47
+ formatTime(new Date('2018-02-02 23:50:50') + 200, 'yyyy/MM/dd HH:mm:ss S') 返回 2018/02/02 23:50:50 200
48
+ ```
49
+ */
50
+ export function formatTime(date: string | number | Date = Date.now(), fmt = 'yyyy-MM-dd HH:mm:ss') {
51
+ date = compatibleDate(date);
52
+
53
+ date = new Date(date);
54
+ const o: any = {
55
+ 'M+': date.getMonth() + 1, // 月份
56
+ 'd+': date.getDate(), // 日
57
+ 'h+': date.getHours() % 12 === 0 ? 12 : date.getHours() % 12, // 小时
58
+ 'H+': date.getHours(), // 小时
59
+ 'm+': date.getMinutes(), // 分
60
+ 's+': date.getSeconds(), // 秒
61
+ 'q+': Math.floor((date.getMonth() + 3) / 3), // 季度
62
+ S: date.getMilliseconds(), // 毫秒
63
+ };
64
+
65
+ if (/(y+)/.test(fmt)) {
66
+ fmt = fmt.replace(RegExp.$1, (`${date.getFullYear()}`).substr(4 - RegExp.$1.length));
67
+ }
68
+
69
+ Object.keys(o).forEach((k) => {
70
+ if (new RegExp(`(${k})`).test(fmt)) {
71
+ fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : ((`00${o[k]}`).substr((`${o[k]}`).length)));
72
+ }
73
+ });
74
+
75
+ return fmt;
76
+ }
77
+
78
+ /**
79
+ * 格式化相对时间
80
+ #### 使用说明
81
+ ```
82
+ fromNow('') 返回 -
83
+ fromNow(Date.now() - 2000) 返回 刚刚
84
+ fromNow(now - 3420 * 1000) 返回 58分钟前面
85
+ fromNow(now - 3600 * 1000 * 3) 返回 3小时前
86
+ fromNow(now - 3600 * 1000 * 24 * 1.5) 返回 1天前
87
+ fromNow(now - day5) 返回 当前时间的5天前日期 格式 yyyy年MM月dd日 HH时mm分ss秒
88
+ fromNow(Date.now() + 2000) 返回 Date.now() + 2000
89
+ fromNow('', '', 'defaultValue') 等于 defaultValue
90
+ ```
91
+ */
92
+ export function fromNow(time: Date | string | number, fmt: string = 'yyyy年MM月dd日 HH时mm分ss秒', defaultValue = '-') {
93
+ if (isEmpty(time)) {
94
+ return defaultValue;
95
+ }
96
+ time = compatibleDate(time);
97
+ const d = new Date(time);
98
+ const now = Date.now();
99
+ const diff = (now - d.getTime()) / 1000;
100
+ if (diff < 0) {
101
+ return time;
102
+ }
103
+
104
+ if (diff < 30) {
105
+ return '刚刚';
106
+ }
107
+ if (diff < 3600) {
108
+ return `${Math.ceil(diff / 60)}分钟前`;
109
+ }
110
+ if (diff < 3600 * 24) {
111
+ return `${Math.floor(diff / 3600)}小时前`;
112
+ }
113
+ if (diff < 3600 * 24 * 2) {
114
+ return '1天前';
115
+ }
116
+
117
+ return formatTime(time, fmt);
118
+ }
119
+
120
+ /**
121
+ * 增加日期天数
122
+ #### 使用说明
123
+ ```
124
+ addDays('') 返回 -
125
+ addDays('2018-02-02') 返回 2018-02-02
126
+ addDays('2018-02-02', 2) 返回 2018-02-04
127
+ addDays('2018-02-02', 2, 'yyyy/MM/dd') 返回 2018/02/04
128
+ addDays(new Date('2020-03-04'), 2, 'yyyy/MM/dd') 返回 2020/03/06
129
+ addDays(Date.now(), 2, 'yyyy/MM/dd') 返回 今天 + 2天
130
+ ```
131
+ */
132
+ export function addDays(date: Date | string | number, days: string | number = 0, fmt: string = 'yyyy-MM-dd') {
133
+ if (isEmpty(date)) {
134
+ return '-';
135
+ }
136
+ date = compatibleDate(date);
137
+ const d = new Date(date);
138
+ d.setDate(d.getDate() + Number(days));
139
+
140
+ return formatTime(d, fmt);
141
+ }
142
+
143
+ // 获取当前时间月份
@@ -0,0 +1,198 @@
1
+ /**
2
+ * DOM 操作相关
3
+ * @packageDocumentation
4
+ * @module DOM
5
+ * @preferred
6
+ */
7
+
8
+ /**
9
+ * 检查元素是否包含指定的 class
10
+ * @param el - DOM 元素
11
+ * @param cls - class 名称
12
+ * @returns 是否包含该 class
13
+ */
14
+ export function hasClass(el: Element | null, cls: string): boolean {
15
+ if (typeof window === 'undefined' || !el) return false;
16
+ return !!el.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'));
17
+ }
18
+
19
+ /**
20
+ * 添加 class 到元素
21
+ * @param el - DOM 元素
22
+ * @param cls - class 名称
23
+ */
24
+ export function addClass(el: Element | null, cls: string): void {
25
+ if (typeof window === 'undefined' || !el) return;
26
+ if (!hasClass(el, cls)) {
27
+ el.className += ' ' + cls;
28
+ }
29
+ }
30
+
31
+ /**
32
+ * 从元素移除 class
33
+ * @param el - DOM 元素
34
+ * @param cls - class 名称
35
+ */
36
+ export function removeClass(el: Element | null, cls: string): void {
37
+ if (typeof window === 'undefined' || !el) return;
38
+ if (hasClass(el, cls)) {
39
+ const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)');
40
+ el.className = el.className.replace(reg, ' ').trim();
41
+ }
42
+ }
43
+
44
+ /**
45
+ * 切换元素的 class
46
+ * @param flag - 是否添加 class
47
+ * @param clsName - class 名称
48
+ * @param target - 目标元素,默认为 document.body
49
+ */
50
+ export function toggleClass(
51
+ flag: boolean,
52
+ clsName: string,
53
+ target?: HTMLElement
54
+ ): void {
55
+ if (typeof window === 'undefined') return;
56
+
57
+ const targetEl = target || document.body;
58
+ let { className } = targetEl;
59
+ className = className.replace(clsName, '').trim();
60
+ targetEl.className = flag ? `${className} ${clsName}`.trim() : className;
61
+ }
62
+
63
+ /**
64
+ * 打开链接
65
+ * @param url - URL 地址
66
+ * @param target - 打开方式,默认 "_blank"
67
+ */
68
+ export function openLink(url: string, target: string = '_blank'): void {
69
+ if (typeof window === 'undefined') return;
70
+
71
+ const link = document.createElement('a');
72
+ link.setAttribute('href', url);
73
+ link.setAttribute('target', target);
74
+ link.setAttribute('rel', 'noreferrer noopener');
75
+ link.setAttribute('id', 'external');
76
+
77
+ const existingLink = document.getElementById('external');
78
+ if (existingLink) {
79
+ document.body.removeChild(existingLink);
80
+ }
81
+
82
+ document.body.appendChild(link);
83
+ link.click();
84
+ link.remove();
85
+ }
86
+
87
+ /**
88
+ * 复制文本到剪贴板
89
+ * @param text - 要复制的文本
90
+ * @returns 是否复制成功
91
+ */
92
+ export function copyTextToClipboard(text: string): boolean {
93
+ if (typeof window === 'undefined' || typeof document === 'undefined') {
94
+ return false;
95
+ }
96
+
97
+ // 创建临时文本框
98
+ const textarea = document.createElement('textarea');
99
+ const activeElement = document.activeElement as HTMLElement;
100
+
101
+ textarea.value = text;
102
+ textarea.setAttribute('readonly', '');
103
+ textarea.style.position = 'absolute';
104
+ textarea.style.left = '-9999px';
105
+ textarea.style.fontSize = '12pt';
106
+ textarea.style.border = '0';
107
+ textarea.style.padding = '0';
108
+ textarea.style.margin = '0';
109
+
110
+ // 保存当前选区
111
+ const selection = document.getSelection();
112
+ let originalRange: Range | null = null;
113
+ if (selection && selection.rangeCount > 0) {
114
+ originalRange = selection.getRangeAt(0);
115
+ }
116
+
117
+ // 添加到 DOM 并选中
118
+ document.body.appendChild(textarea);
119
+ textarea.select();
120
+ textarea.selectionStart = 0;
121
+ textarea.selectionEnd = text.length;
122
+
123
+ // 执行复制
124
+ let success = false;
125
+ try {
126
+ success = document.execCommand('copy');
127
+ } catch (err) {
128
+ console.error('复制失败:', err);
129
+ }
130
+
131
+ // 清理
132
+ textarea.remove();
133
+
134
+ // 恢复原选区
135
+ if (originalRange && selection) {
136
+ selection.removeAllRanges();
137
+ selection.addRange(originalRange);
138
+ }
139
+
140
+ // 恢复焦点
141
+ if (activeElement) {
142
+ activeElement.focus();
143
+ }
144
+
145
+ return success;
146
+ }
147
+
148
+ /**
149
+ * SVG 信息接口
150
+ */
151
+ export interface SvgInfo {
152
+ width: number;
153
+ height: number;
154
+ body: string;
155
+ }
156
+
157
+ /**
158
+ * 解析 SVG 字符串,提取宽度、高度和 body 内容
159
+ * @param svg - SVG 字符串
160
+ * @returns SVG 信息对象或原字符串
161
+ */
162
+ export function getSvgInfo(svg: string): SvgInfo | string {
163
+ if (typeof window === 'undefined' || typeof DOMParser === 'undefined') {
164
+ return svg;
165
+ }
166
+
167
+ try {
168
+ const parser = new DOMParser();
169
+ const doc = parser.parseFromString(svg, 'image/svg+xml');
170
+ const svgElement = doc.querySelector('svg');
171
+
172
+ if (!svgElement) {
173
+ return svg;
174
+ }
175
+
176
+ const viewBox = svgElement.getAttribute('viewBox');
177
+ if (!viewBox) {
178
+ throw new Error('Invalid SVG string: Missing viewBox attribute.');
179
+ }
180
+
181
+ const viewBoxParts = viewBox.split(' ');
182
+ const width = parseInt(viewBoxParts[2], 10);
183
+ const height = parseInt(viewBoxParts[3], 10);
184
+
185
+ const paths = Array.from(svgElement.querySelectorAll('path'));
186
+ const body = paths.map(path => path.outerHTML).join(' ');
187
+
188
+ return {
189
+ width,
190
+ height,
191
+ body,
192
+ };
193
+ } catch (error) {
194
+ console.error('解析 SVG 失败:', error);
195
+ return svg;
196
+ }
197
+ }
198
+
@@ -0,0 +1,319 @@
1
+ /**
2
+ * 文件处理相关
3
+ * @packageDocumentation
4
+ * @module File
5
+ * @preferred
6
+ */
7
+
8
+ import { isEmpty } from '../common';
9
+ import { ceil } from '../number';
10
+
11
+ export type FileSizeUnit = 'B' |'KB' | 'MB' | 'GB' | 'TB';
12
+
13
+ export interface FileSizeObject {
14
+ size: number;
15
+ unit: FileSizeUnit;
16
+ }
17
+ /**
18
+ * 计算文件大小
19
+ #### 使用说明
20
+ ```
21
+ calcFileSize(100) 返回 { size: 100, unit: 'B' }
22
+ calcFileSize(1024) 返回 { size: 1, unit: 'KB' }
23
+ calcFileSize(1024, 'KB') 返回 { size: 1, unit: 'MB' }
24
+ calcFileSize(1126.4, 'mb') 返回 { size: 1.1, unit: 'GB' }
25
+ calcFileSize(Math.pow(1024, 2), 'kb') 返回 { size: 1, unit: 'GB' }
26
+ ```
27
+ * @param size 单位 k
28
+ * @returns number
29
+ */
30
+ export function calcFileSize(size: number, unit: FileSizeUnit = 'B'): FileSizeObject {
31
+ const unitList: Array<FileSizeUnit> = ['B', 'KB', 'MB', 'GB', 'TB'];
32
+
33
+ let currentUnitIndex = unitList.indexOf(unit.toUpperCase() as FileSizeUnit);
34
+
35
+ currentUnitIndex = currentUnitIndex === -1 ? 0 : currentUnitIndex;
36
+
37
+ // todo 单位换算。
38
+ while (size >= 1024 && currentUnitIndex < unitList.length) {
39
+ size = size / 1024;
40
+ currentUnitIndex += 1;
41
+ }
42
+ return {
43
+ size,
44
+ unit: unitList[currentUnitIndex],
45
+ };
46
+ }
47
+
48
+ /**
49
+ * 文件大小转换
50
+ #### 使用说明
51
+ ```
52
+ fileSizeFormat('') 返回 -
53
+ fileSizeFormat('1024') 返回 1KB
54
+ fileSizeFormat('1024', 'KB') 返回 1MB
55
+ fileSizeFormat('2645', 'B') 返回 ceil(2645 / 1024, 2)MB
56
+ ```
57
+ * @param {string} str 字符串 单位k
58
+ */
59
+ export function fileSizeFormat(str: string | number, unit: FileSizeUnit = 'B', defaultValue = '-') {
60
+ if (isEmpty(str)) {
61
+ return defaultValue;
62
+ }
63
+ const o = calcFileSize(parseFloat(str.toString()), unit);
64
+ return `${ceil(o.size, 2)}${o.unit}`;
65
+ }
66
+
67
+ /**
68
+ * base64转换为file
69
+ * dataurl base64图片
70
+ * */
71
+ export function dataURLtoBlob(dataurl: string): Blob {
72
+ const mime = (dataurl.match(/:(.*?);/) as any)[1];
73
+ const bytes = window.atob(dataurl.split(',')[1]); // 去掉url的头,并转换为byte
74
+ // 处理异常,将ascii码小于0的转换为大于0
75
+ const ab = new ArrayBuffer(bytes.length);
76
+ const ia = new Uint8Array(ab);
77
+ for (let i = 0; i < bytes.length; i += 1) {
78
+ ia[i] = bytes.charCodeAt(i);
79
+ }
80
+ return new Blob([ab], { type: mime });
81
+ }
82
+
83
+ export function blobToDataURL(blob: Blob): any {
84
+ return new Promise((resolve, reject) => {
85
+ const reader = new FileReader();
86
+ reader.onload = (e) => {
87
+ if (!e || !e.target) {
88
+ reject(e);
89
+ } else {
90
+ resolve(e.target.result as string);
91
+ }
92
+ };
93
+ reader.onerror = (err) => {
94
+ reject(err);
95
+ };
96
+ reader.readAsDataURL(blob);
97
+ });
98
+ }
99
+
100
+ export interface CompressImgScaleCallback {
101
+ (w: number, h: number): number;
102
+ }
103
+ export interface CompressImgQualityCallback {
104
+ (fileSize: number, scale: number, w: number, h: number): number;
105
+ }
106
+
107
+ /**
108
+ * 压缩图片方法
109
+ * @param file 图片
110
+ * @param scaleCallback 宽高 压缩规则
111
+ * @param qualityCallback 质量 压缩规则
112
+ */
113
+ export function compressImg(
114
+ file: File,
115
+ scaleCallback?: CompressImgScaleCallback,
116
+ qualityCallback?: CompressImgQualityCallback,
117
+ ): Promise<Blob> {
118
+ return new Promise((resolve, reject) => {
119
+ const fileSize = parseFloat((file.size / 1024 / 1024).toString());
120
+ const read = new FileReader();
121
+ read.onload = (e: ProgressEvent<FileReader>) => {
122
+ const img = new Image();
123
+ img.onload = () => {
124
+ // 读取图片宽高
125
+ const w = img.width;
126
+ const h = img.height;
127
+ // 生成canvas
128
+ const canvas = document.createElement('canvas');
129
+ const ctx = canvas.getContext('2d');
130
+
131
+ // 处理函数返回的不是 number & NaN 类型的情况
132
+ let scale = scaleCallback ? scaleCallback(w, h) : 1;
133
+ if (Number.isNaN(scale) || typeof scale !== 'number') {
134
+ scale = 1;
135
+ }
136
+ let quality = qualityCallback ? qualityCallback(fileSize, scale, w, h) : 1;
137
+ if (Number.isNaN(quality) || typeof quality !== 'number') {
138
+ quality = 1;
139
+ }
140
+
141
+ // 图片 缩放规则
142
+ if (ctx) {
143
+ const scaleW = parseInt((w * scale).toString(), 10);
144
+ const scaleH = parseInt((h * scale).toString(), 10);
145
+ // 创建属性节点
146
+ canvas.setAttribute('width', scaleW.toString());
147
+ canvas.setAttribute('height', scaleH.toString());
148
+ ctx.drawImage(img, 0, 0, scaleW, scaleH);
149
+ }
150
+ // 图片质量 压缩
151
+ const base64 = canvas.toDataURL(file.type, quality);
152
+
153
+ resolve(dataURLtoBlob(base64));
154
+ };
155
+ img.onerror = (evt: Event | string) => {
156
+ reject(evt);
157
+ };
158
+
159
+ if (e.target) {
160
+ img.src = e.target.result as string;
161
+ }
162
+ };
163
+
164
+ read.onerror = (e: ProgressEvent<FileReader>) => {
165
+ reject(e);
166
+ };
167
+ read.readAsDataURL(file);
168
+ });
169
+ }
170
+
171
+ export type Method =
172
+ | 'get' | 'GET'
173
+ | 'delete' | 'DELETE'
174
+ | 'head' | 'HEAD'
175
+ | 'options' | 'OPTIONS'
176
+ | 'post' | 'POST'
177
+ | 'put' | 'PUT'
178
+ | 'patch' | 'PATCH'
179
+ | 'link' | 'LINK'
180
+ | 'unlink' | 'UNLINK'
181
+
182
+ export interface ExportByBlobParams {
183
+ /**
184
+ * 请求方式
185
+ */
186
+ method?: Method;
187
+ /**
188
+ * 接口地址
189
+ */
190
+ url: string;
191
+ /**
192
+ * post接口参数
193
+ */
194
+ data?: any;
195
+ /**
196
+ * get接口参数
197
+ */
198
+ params?: any;
199
+ /**
200
+ * 导出的文件名称
201
+ */
202
+ filename?: string;
203
+ }
204
+
205
+
206
+ export interface GenExportByBlobParams {
207
+ /**
208
+ * axios 请求函数
209
+ */
210
+ axiosRequest: any;
211
+ /**
212
+ * 不使用 withCredentials 域名
213
+ */
214
+ notWithCredentials?: string[];
215
+ }
216
+ /**
217
+ * 生成导出函数
218
+ */
219
+ export function genExportByBlob({
220
+ axiosRequest,
221
+ notWithCredentials = [],
222
+ }) {
223
+ return function exportByBlob(config: ExportByBlobParams) {
224
+ let {
225
+ filename,
226
+ } = config;
227
+ const {
228
+ url,
229
+ data = {},
230
+ params = {},
231
+ method = 'post',
232
+ } = config;
233
+ return new Promise((resolve, reject) => {
234
+ axiosRequest({
235
+ method,
236
+ url,
237
+ data,
238
+ params,
239
+ responseType: 'blob',
240
+ config: {
241
+ withCredentials: notWithCredentials.some((str) => {
242
+ return !url.includes(str);
243
+ }),
244
+ },
245
+ // eslint-disable-next-line consistent-return
246
+ }).then(async (res) => {
247
+ const response = res?.data;
248
+ if (response.type === 'application/json') {
249
+ const reader = new FileReader();
250
+ reader.readAsText(response, 'utf-8');
251
+ reader.onload = () => {
252
+ let result: any = {};
253
+ try {
254
+ result = JSON.parse(reader.result as string);
255
+ } catch (e) {
256
+ result = response;
257
+ }
258
+ // Message.error((result && result.message) || '导出失败');
259
+ reject(result);
260
+ };
261
+ } else {
262
+ try {
263
+ const { headers } = res; // 下载后文件名
264
+ const contentDisposition = headers['content-disposition'];
265
+ const responseFilename = decodeURIComponent(contentDisposition.split(';')[1].split('filename=')[1]);
266
+ // 有传文件名称&不带后缀 使用服务端的文件拓展名
267
+ if (filename && !filename.includes('.')) {
268
+ const splitFileName = responseFilename.split('.');
269
+ const suffix = splitFileName[splitFileName.length - 1];
270
+ filename += `.${suffix}`;
271
+ } else if (!filename) {
272
+ filename = responseFilename;
273
+ }
274
+ } catch (e) {
275
+ // 默认名称
276
+ if (!filename) {
277
+ filename = 'download.xlsx';
278
+ }
279
+ }
280
+ const blob = new Blob([res.data]);
281
+ const downloadElement = document.createElement('a');
282
+ const href = await blobToDataURL(blob); // 创建下载的链接
283
+ downloadElement.href = href;
284
+ downloadElement.download = filename;
285
+ document.body.appendChild(downloadElement);
286
+ downloadElement.click(); // 点击下载
287
+ document.body.removeChild(downloadElement); // 下载完成移除元素
288
+ // window.URL.revokeObjectURL(href);
289
+ resolve(res);
290
+ }
291
+ }).catch((e: Error) => {
292
+ reject(e);
293
+ });
294
+ });
295
+ };
296
+ }
297
+ /**
298
+ * 将 base64 字符串转换为 File 或 Blob
299
+ * @param {string} base64 - 带有 data URI 的 base64 字符串
300
+ * @param {string} [filename] - 如果提供了,则返回 File;否则返回 Blob
301
+ * @returns {File|Blob}
302
+ */
303
+ export async function base64ToFileOrBlob(base64, filename) {
304
+ const [meta, content] = base64.split(',')
305
+ const mime = meta.match(/:(.*?);/)[1]
306
+ const bstr = atob(content)
307
+ const len = bstr.length
308
+ const u8arr = new Uint8Array(len)
309
+
310
+ for (let i = 0; i < len; i++) {
311
+ u8arr[i] = bstr.charCodeAt(i)
312
+ }
313
+
314
+ if (filename) {
315
+ return new File([u8arr], filename, { type: mime })
316
+ } else {
317
+ return new Blob([u8arr], { type: mime })
318
+ }
319
+ }