a2bei4-utils 1.0.0

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 (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2 -0
  3. package/dist/a2bei4.utils.cjs.js +1112 -0
  4. package/dist/a2bei4.utils.cjs.js.map +1 -0
  5. package/dist/a2bei4.utils.cjs.min.js +2 -0
  6. package/dist/a2bei4.utils.cjs.min.js.map +1 -0
  7. package/dist/a2bei4.utils.esm.js +1070 -0
  8. package/dist/a2bei4.utils.esm.js.map +1 -0
  9. package/dist/a2bei4.utils.esm.min.js +2 -0
  10. package/dist/a2bei4.utils.esm.min.js.map +1 -0
  11. package/dist/a2bei4.utils.umd.js +1118 -0
  12. package/dist/a2bei4.utils.umd.js.map +1 -0
  13. package/dist/a2bei4.utils.umd.min.js +2 -0
  14. package/dist/a2bei4.utils.umd.min.js.map +1 -0
  15. package/dist/arr.cjs +34 -0
  16. package/dist/arr.cjs.map +1 -0
  17. package/dist/arr.js +31 -0
  18. package/dist/arr.js.map +1 -0
  19. package/dist/browser.cjs +60 -0
  20. package/dist/browser.cjs.map +1 -0
  21. package/dist/browser.js +56 -0
  22. package/dist/browser.js.map +1 -0
  23. package/dist/common.cjs +391 -0
  24. package/dist/common.cjs.map +1 -0
  25. package/dist/common.js +373 -0
  26. package/dist/common.js.map +1 -0
  27. package/dist/date.cjs +195 -0
  28. package/dist/date.cjs.map +1 -0
  29. package/dist/date.js +188 -0
  30. package/dist/date.js.map +1 -0
  31. package/dist/download.cjs +70 -0
  32. package/dist/download.cjs.map +1 -0
  33. package/dist/download.js +64 -0
  34. package/dist/download.js.map +1 -0
  35. package/dist/evt.cjs +155 -0
  36. package/dist/evt.cjs.map +1 -0
  37. package/dist/evt.js +152 -0
  38. package/dist/evt.js.map +1 -0
  39. package/dist/id.cjs +75 -0
  40. package/dist/id.cjs.map +1 -0
  41. package/dist/id.js +72 -0
  42. package/dist/id.js.map +1 -0
  43. package/dist/timer.cjs +57 -0
  44. package/dist/timer.cjs.map +1 -0
  45. package/dist/timer.js +55 -0
  46. package/dist/timer.js.map +1 -0
  47. package/dist/tree.cjs +99 -0
  48. package/dist/tree.cjs.map +1 -0
  49. package/dist/tree.js +95 -0
  50. package/dist/tree.js.map +1 -0
  51. package/package.json +146 -0
  52. package/readme.txt +18 -0
  53. package/types/arr.d.ts +18 -0
  54. package/types/browser.d.ts +51 -0
  55. package/types/common.d.ts +170 -0
  56. package/types/date.d.ts +77 -0
  57. package/types/download.d.ts +39 -0
  58. package/types/evt.d.ts +52 -0
  59. package/types/id.d.ts +39 -0
  60. package/types/index.d.ts +499 -0
  61. package/types/timer.d.ts +32 -0
  62. package/types/tree.d.ts +30 -0
package/dist/date.js ADDED
@@ -0,0 +1,188 @@
1
+ /**
2
+ * 将任意值安全转换为 Date 对象。
3
+ * - 数字/数字字符串:视为时间戳
4
+ * - 字符串:尝试按 ISO/RFC 格式解析
5
+ * - 对象:优先 valueOf(),再 toString()
6
+ * - null / undefined / 无效值:返回 null
7
+ *
8
+ * @param {*} val - 待转换值
9
+ * @returns {Date | null} 有效 Date 或 null
10
+ */
11
+ function toDate(val) {
12
+ if (val == null) return null; // null / undefined
13
+ if (val instanceof Date) return isNaN(val) ? null : val; // 已是 Date,但需排除 Invalid Date
14
+
15
+ // 1. 数字或数字字符串 → 时间戳
16
+ if (typeof val === "number" || (typeof val === "string" && /^-?\d+(\.\d+)?$/.test(val.trim()))) {
17
+ const d = new Date(+val);
18
+ return isNaN(d) ? null : d;
19
+ }
20
+
21
+ // 2. 标准 ISO 8601 / RFC 2825 等合法字符串
22
+ if (typeof val === "string") {
23
+ const d = new Date(val);
24
+ return isNaN(d) ? null : d; // 非法格式返回 null
25
+ }
26
+
27
+ // 3. 对象带 valueOf / toString
28
+ if (typeof val === "object") {
29
+ // 优先调用 valueOf(期望返回数字时间戳)
30
+ const prim = val.valueOf ? val.valueOf() : Object.prototype.valueOf.call(val);
31
+ if (typeof prim === "number" && !isNaN(prim)) {
32
+ const d = new Date(prim);
33
+ return isNaN(d) ? null : d;
34
+ }
35
+ // 兜底用字符串
36
+ const str = val.toString ? val.toString() : String(val);
37
+ const d = new Date(str);
38
+ return isNaN(d) ? null : d;
39
+ }
40
+
41
+ // 4. 其余情况
42
+ return null;
43
+ }
44
+
45
+ /**
46
+ * 在闭区间 [date1, date2] 内随机生成一个日期(含首尾)。
47
+ * 若顺序相反则自动交换。
48
+ *
49
+ * @param {Date} date1 - 起始日期
50
+ * @param {Date} date2 - 结束日期
51
+ * @returns {Date} 随机日期
52
+ */
53
+ function randomDateInRange(date1, date2) {
54
+ let v1 = date1.getTime(),
55
+ v2 = date2.getTime();
56
+ if (v1 > v2) [v1, v2] = [v2, v1];
57
+ return new Date(v1 + Math.floor(Math.random() * (v2 - v1 + 1)));
58
+ }
59
+
60
+ /**
61
+ * 计算两个时间之间的剩余/已过时长(天-时-分-秒),返回带补零的展示对象。
62
+ *
63
+ * @param {string|number|Date} originalTime - 原始时间(可转 Date 的任意值)
64
+ * @param {Date} [currentTime=new Date()] - 基准时间,默认当前
65
+ * @returns {{days:number,hours:string,minutes:string,seconds:string}}
66
+ */
67
+ function calcTimeDifference(originalTime, currentTime = new Date()) {
68
+ // 计算时间差(毫秒)
69
+ const diffMs = currentTime - new Date(originalTime);
70
+
71
+ // 转换为天、小时、分钟、秒
72
+ const diffSeconds = Math.floor(diffMs / 1000);
73
+ const days = Math.floor(diffSeconds / (3600 * 24));
74
+ const hours = Math.floor((diffSeconds % (3600 * 24)) / 3600);
75
+ const minutes = Math.floor((diffSeconds % 3600) / 60);
76
+ const seconds = diffSeconds % 60;
77
+
78
+ const padZero = (num) => String(num).padStart(2, "0");
79
+
80
+ return {
81
+ days,
82
+ hours: padZero(hours),
83
+ minutes: padZero(minutes),
84
+ seconds: padZero(seconds)
85
+ };
86
+ }
87
+
88
+ /**
89
+ * 将总秒数格式化成人类可读的时间段文本。
90
+ * 固定进制:1 年=365 天,1 月=30 天。
91
+ *
92
+ * @param {number} totalSeconds - 非负总秒数
93
+ * @param {object} [options] - 格式化选项
94
+ * @param {Partial<{year:string,month:string,day:string,hour:string,minute:string,second:string}>} [options.labels] - 各单位的自定义文本
95
+ * @param {('year'|'month'|'day'|'hour'|'minute'|'second')} [options.maxUnit] - 最大输出单位
96
+ * @param {('year'|'month'|'day'|'hour'|'minute'|'second')} [options.minUnit] - 最小输出单位
97
+ * @param {boolean} [options.showZero] - 是否强制显示 0 秒
98
+ * @returns {string} 拼接后的时长文本,如“1天 02小时 30分钟”
99
+ * @throws {TypeError} 当 totalSeconds 为非数字或负数时抛出
100
+ */
101
+ function formatDuration(totalSeconds, options = {}) {
102
+ if (typeof totalSeconds !== "number" || totalSeconds < 0 || !isFinite(totalSeconds)) {
103
+ throw new TypeError("totalSeconds 必须是非负数字");
104
+ }
105
+
106
+ // 1. 默认中文单位
107
+ const DEFAULT_LABELS = {
108
+ year: "年",
109
+ month: "月",
110
+ day: "天",
111
+ hour: "小时",
112
+ minute: "分钟",
113
+ second: "秒"
114
+ };
115
+
116
+ // 2. 固定进制表(秒)
117
+ const UNIT_TABLE = [
118
+ { key: "year", seconds: 365 * 24 * 3600 },
119
+ { key: "month", seconds: 30 * 24 * 3600 },
120
+ { key: "day", seconds: 24 * 3600 },
121
+ { key: "hour", seconds: 3600 },
122
+ { key: "minute", seconds: 60 },
123
+ { key: "second", seconds: 1 }
124
+ ];
125
+
126
+ // 3. 合并用户自定义文本
127
+ const labels = Object.assign({}, DEFAULT_LABELS, options.labels);
128
+
129
+ // 4. 根据 maxUnit / minUnit 截取
130
+ let start = 0,
131
+ end = UNIT_TABLE.length;
132
+ if (options.maxUnit) {
133
+ const idx = UNIT_TABLE.findIndex((u) => u.key === options.maxUnit);
134
+ if (idx !== -1) start = idx;
135
+ }
136
+ if (options.minUnit) {
137
+ const idx = UNIT_TABLE.findIndex((u) => u.key === options.minUnit);
138
+ if (idx !== -1) end = idx + 1;
139
+ }
140
+ const units = UNIT_TABLE.slice(start, end);
141
+ if (!units.length) units.push(UNIT_TABLE[UNIT_TABLE.length - 1]); // 保底秒
142
+
143
+ // 5. 逐级计算
144
+ let rest = Math.floor(totalSeconds);
145
+ const parts = [];
146
+
147
+ for (const { key, seconds } of units) {
148
+ const val = Math.floor(rest / seconds);
149
+ rest %= seconds;
150
+
151
+ const shouldShow = val > 0 || (options.showZero && key === "second");
152
+ if (shouldShow || (parts.length === 0 && rest === 0)) {
153
+ parts.push(`${val}${labels[key]}`);
154
+ }
155
+ }
156
+
157
+ // 6. 兜底
158
+ if (parts.length === 0) {
159
+ parts.push(`0${labels[units[units.length - 1].key]}`);
160
+ }
161
+
162
+ return parts.join("");
163
+ }
164
+
165
+ /**
166
+ * 快捷调用 {@link formatDuration},最大单位到“天”。
167
+ *
168
+ * @param {number} totalSeconds
169
+ * @param {Omit<Parameters<typeof formatDuration>[1],'maxUnit'>} [options]
170
+ * @returns {string}
171
+ */
172
+ function formatDurationMaxDay(totalSeconds, options = {}) {
173
+ return formatDuration(totalSeconds, { ...options, maxUnit: "day" });
174
+ }
175
+
176
+ /**
177
+ * 快捷调用 {@link formatDuration},最大单位到“小时”。
178
+ *
179
+ * @param {number} totalSeconds
180
+ * @param {Omit<Parameters<typeof formatDuration>[1],'maxUnit'>} [options]
181
+ * @returns {string}
182
+ */
183
+ function formatDurationMaxHour(totalSeconds, options = {}) {
184
+ return formatDuration(totalSeconds, { ...options, maxUnit: "hour" });
185
+ }
186
+
187
+ export { calcTimeDifference, formatDuration, formatDurationMaxDay, formatDurationMaxHour, randomDateInRange, toDate };
188
+ //# sourceMappingURL=date.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"date.js","sources":["../src/source/date.js"],"sourcesContent":["/**\n * 将任意值安全转换为 Date 对象。\n * - 数字/数字字符串:视为时间戳\n * - 字符串:尝试按 ISO/RFC 格式解析\n * - 对象:优先 valueOf(),再 toString()\n * - null / undefined / 无效值:返回 null\n *\n * @param {*} val - 待转换值\n * @returns {Date | null} 有效 Date 或 null\n */\nexport function toDate(val) {\n if (val == null) return null; // null / undefined\n if (val instanceof Date) return isNaN(val) ? null : val; // 已是 Date,但需排除 Invalid Date\n\n // 1. 数字或数字字符串 → 时间戳\n if (typeof val === \"number\" || (typeof val === \"string\" && /^-?\\d+(\\.\\d+)?$/.test(val.trim()))) {\n const d = new Date(+val);\n return isNaN(d) ? null : d;\n }\n\n // 2. 标准 ISO 8601 / RFC 2825 等合法字符串\n if (typeof val === \"string\") {\n const d = new Date(val);\n return isNaN(d) ? null : d; // 非法格式返回 null\n }\n\n // 3. 对象带 valueOf / toString\n if (typeof val === \"object\") {\n // 优先调用 valueOf(期望返回数字时间戳)\n const prim = val.valueOf ? val.valueOf() : Object.prototype.valueOf.call(val);\n if (typeof prim === \"number\" && !isNaN(prim)) {\n const d = new Date(prim);\n return isNaN(d) ? null : d;\n }\n // 兜底用字符串\n const str = val.toString ? val.toString() : String(val);\n const d = new Date(str);\n return isNaN(d) ? null : d;\n }\n\n // 4. 其余情况\n return null;\n}\n\n/**\n * 在闭区间 [date1, date2] 内随机生成一个日期(含首尾)。\n * 若顺序相反则自动交换。\n *\n * @param {Date} date1 - 起始日期\n * @param {Date} date2 - 结束日期\n * @returns {Date} 随机日期\n */\nexport function randomDateInRange(date1, date2) {\n let v1 = date1.getTime(),\n v2 = date2.getTime();\n if (v1 > v2) [v1, v2] = [v2, v1];\n return new Date(v1 + Math.floor(Math.random() * (v2 - v1 + 1)));\n}\n\n/**\n * 计算两个时间之间的剩余/已过时长(天-时-分-秒),返回带补零的展示对象。\n *\n * @param {string|number|Date} originalTime - 原始时间(可转 Date 的任意值)\n * @param {Date} [currentTime=new Date()] - 基准时间,默认当前\n * @returns {{days:number,hours:string,minutes:string,seconds:string}}\n */\nexport function calcTimeDifference(originalTime, currentTime = new Date()) {\n // 计算时间差(毫秒)\n const diffMs = currentTime - new Date(originalTime);\n\n // 转换为天、小时、分钟、秒\n const diffSeconds = Math.floor(diffMs / 1000);\n const days = Math.floor(diffSeconds / (3600 * 24));\n const hours = Math.floor((diffSeconds % (3600 * 24)) / 3600);\n const minutes = Math.floor((diffSeconds % 3600) / 60);\n const seconds = diffSeconds % 60;\n\n const padZero = (num) => String(num).padStart(2, \"0\");\n\n return {\n days,\n hours: padZero(hours),\n minutes: padZero(minutes),\n seconds: padZero(seconds)\n };\n}\n\n/**\n * 将总秒数格式化成人类可读的时间段文本。\n * 固定进制:1 年=365 天,1 月=30 天。\n *\n * @param {number} totalSeconds - 非负总秒数\n * @param {object} [options] - 格式化选项\n * @param {Partial<{year:string,month:string,day:string,hour:string,minute:string,second:string}>} [options.labels] - 各单位的自定义文本\n * @param {('year'|'month'|'day'|'hour'|'minute'|'second')} [options.maxUnit] - 最大输出单位\n * @param {('year'|'month'|'day'|'hour'|'minute'|'second')} [options.minUnit] - 最小输出单位\n * @param {boolean} [options.showZero] - 是否强制显示 0 秒\n * @returns {string} 拼接后的时长文本,如“1天 02小时 30分钟”\n * @throws {TypeError} 当 totalSeconds 为非数字或负数时抛出\n */\nexport function formatDuration(totalSeconds, options = {}) {\n if (typeof totalSeconds !== \"number\" || totalSeconds < 0 || !isFinite(totalSeconds)) {\n throw new TypeError(\"totalSeconds 必须是非负数字\");\n }\n\n // 1. 默认中文单位\n const DEFAULT_LABELS = {\n year: \"年\",\n month: \"月\",\n day: \"天\",\n hour: \"小时\",\n minute: \"分钟\",\n second: \"秒\"\n };\n\n // 2. 固定进制表(秒)\n const UNIT_TABLE = [\n { key: \"year\", seconds: 365 * 24 * 3600 },\n { key: \"month\", seconds: 30 * 24 * 3600 },\n { key: \"day\", seconds: 24 * 3600 },\n { key: \"hour\", seconds: 3600 },\n { key: \"minute\", seconds: 60 },\n { key: \"second\", seconds: 1 }\n ];\n\n // 3. 合并用户自定义文本\n const labels = Object.assign({}, DEFAULT_LABELS, options.labels);\n\n // 4. 根据 maxUnit / minUnit 截取\n let start = 0,\n end = UNIT_TABLE.length;\n if (options.maxUnit) {\n const idx = UNIT_TABLE.findIndex((u) => u.key === options.maxUnit);\n if (idx !== -1) start = idx;\n }\n if (options.minUnit) {\n const idx = UNIT_TABLE.findIndex((u) => u.key === options.minUnit);\n if (idx !== -1) end = idx + 1;\n }\n const units = UNIT_TABLE.slice(start, end);\n if (!units.length) units.push(UNIT_TABLE[UNIT_TABLE.length - 1]); // 保底秒\n\n // 5. 逐级计算\n let rest = Math.floor(totalSeconds);\n const parts = [];\n\n for (const { key, seconds } of units) {\n const val = Math.floor(rest / seconds);\n rest %= seconds;\n\n const shouldShow = val > 0 || (options.showZero && key === \"second\");\n if (shouldShow || (parts.length === 0 && rest === 0)) {\n parts.push(`${val}${labels[key]}`);\n }\n }\n\n // 6. 兜底\n if (parts.length === 0) {\n parts.push(`0${labels[units[units.length - 1].key]}`);\n }\n\n return parts.join(\"\");\n}\n\n/**\n * 快捷调用 {@link formatDuration},最大单位到“天”。\n *\n * @param {number} totalSeconds\n * @param {Omit<Parameters<typeof formatDuration>[1],'maxUnit'>} [options]\n * @returns {string}\n */\nexport function formatDurationMaxDay(totalSeconds, options = {}) {\n return formatDuration(totalSeconds, { ...options, maxUnit: \"day\" });\n}\n\n/**\n * 快捷调用 {@link formatDuration},最大单位到“小时”。\n *\n * @param {number} totalSeconds\n * @param {Omit<Parameters<typeof formatDuration>[1],'maxUnit'>} [options]\n * @returns {string}\n */\nexport function formatDurationMaxHour(totalSeconds, options = {}) {\n return formatDuration(totalSeconds, { ...options, maxUnit: \"hour\" });\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * 通过动态创建 `<a>` 标签触发浏览器下载。
5
+ *
6
+ * @param {string} url - 任意可下载地址(同源或允许跨域)
7
+ * @param {string} [fileName] - 保存到本地的文件名;不传时使用时间戳
8
+ */
9
+ function downloadByUrl(url, fileName) {
10
+ const a = document.createElement("a");
11
+ a.style.display = "none";
12
+ a.rel = "noopener";
13
+ a.href = url;
14
+ a.download = fileName || Date.now();
15
+ document.body.appendChild(a);
16
+ a.click();
17
+ document.body.removeChild(a);
18
+ }
19
+
20
+ /**
21
+ * 把 Blob 转成临时 URL 并触发下载,下载完成后立即释放内存。
22
+ *
23
+ * @param {Blob} blob - 待下载的 Blob(含 File)
24
+ * @param {string} [fileName] - 保存到本地的文件名
25
+ */
26
+ function downloadByBlob(blob, fileName) {
27
+ const url = URL.createObjectURL(blob);
28
+ downloadByUrl(url, fileName);
29
+ setTimeout(() => URL.revokeObjectURL(url), 0);
30
+ }
31
+
32
+ /**
33
+ * 将任意数据包装成 Blob 并下载。
34
+ *
35
+ * @param {string | ArrayBufferView | ArrayBuffer | Blob} data - 要写入文件的数据
36
+ * @param {string} [fileName] - 保存到本地的文件名
37
+ * @param {string} [mimeType] - MIME 类型;默认 `application/octet-stream`
38
+ */
39
+ function downloadByData(data, fileName, mimeType = "application/octet-stream") {
40
+ downloadByBlob(new Blob([data], { type: mimeType }), fileName);
41
+ }
42
+
43
+ /**
44
+ * 快捷下载 Excel 文件(MIME 已固定)。
45
+ *
46
+ * @param {string | ArrayBufferView | ArrayBuffer | Blob} data - Excel 二进制或字符串内容
47
+ * @param {string} [fileName] - 保存到本地的文件名
48
+ */
49
+ function downloadExcel(data, fileName) {
50
+ downloadByData(data, fileName, "application/vnd.ms-excel");
51
+ }
52
+
53
+ /**
54
+ * 快捷下载 JSON 文件(MIME 已固定)。
55
+ * 若传入非字符串数据,会自行 `JSON.stringify`。
56
+ *
57
+ * @param {any} data - 要序列化的 JSON 数据
58
+ * @param {string} [fileName] - 保存到本地的文件名
59
+ */
60
+ function downloadJSON(data, fileName) {
61
+ // downloadByData(typeof data === "string" ? data : JSON.stringify(data, null, 4), fileName, "application/json");
62
+ downloadByData(data, fileName, "application/json");
63
+ }
64
+
65
+ exports.downloadByBlob = downloadByBlob;
66
+ exports.downloadByData = downloadByData;
67
+ exports.downloadByUrl = downloadByUrl;
68
+ exports.downloadExcel = downloadExcel;
69
+ exports.downloadJSON = downloadJSON;
70
+ //# sourceMappingURL=download.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"download.cjs","sources":["../src/source/download.js"],"sourcesContent":["/**\n * 通过动态创建 `<a>` 标签触发浏览器下载。\n *\n * @param {string} url - 任意可下载地址(同源或允许跨域)\n * @param {string} [fileName] - 保存到本地的文件名;不传时使用时间戳\n */\nexport function downloadByUrl(url, fileName) {\n const a = document.createElement(\"a\");\n a.style.display = \"none\";\n a.rel = \"noopener\";\n a.href = url;\n a.download = fileName || Date.now();\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n}\n\n/**\n * 把 Blob 转成临时 URL 并触发下载,下载完成后立即释放内存。\n *\n * @param {Blob} blob - 待下载的 Blob(含 File)\n * @param {string} [fileName] - 保存到本地的文件名\n */\nexport function downloadByBlob(blob, fileName) {\n const url = URL.createObjectURL(blob);\n downloadByUrl(url, fileName);\n setTimeout(() => URL.revokeObjectURL(url), 0);\n}\n\n/**\n * 将任意数据包装成 Blob 并下载。\n *\n * @param {string | ArrayBufferView | ArrayBuffer | Blob} data - 要写入文件的数据\n * @param {string} [fileName] - 保存到本地的文件名\n * @param {string} [mimeType] - MIME 类型;默认 `application/octet-stream`\n */\nexport function downloadByData(data, fileName, mimeType = \"application/octet-stream\") {\n downloadByBlob(new Blob([data], { type: mimeType }), fileName);\n}\n\n/**\n * 快捷下载 Excel 文件(MIME 已固定)。\n *\n * @param {string | ArrayBufferView | ArrayBuffer | Blob} data - Excel 二进制或字符串内容\n * @param {string} [fileName] - 保存到本地的文件名\n */\nexport function downloadExcel(data, fileName) {\n downloadByData(data, fileName, \"application/vnd.ms-excel\");\n}\n\n/**\n * 快捷下载 JSON 文件(MIME 已固定)。\n * 若传入非字符串数据,会自行 `JSON.stringify`。\n *\n * @param {any} data - 要序列化的 JSON 数据\n * @param {string} [fileName] - 保存到本地的文件名\n */\nexport function downloadJSON(data, fileName) {\n // downloadByData(typeof data === \"string\" ? data : JSON.stringify(data, null, 4), fileName, \"application/json\");\n downloadByData(data, fileName, \"application/json\");\n}\n"],"names":[],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,aAAa,CAAC,GAAG,EAAE,QAAQ,EAAE;AAC7C,IAAI,MAAM,CAAC,GAAG,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC;AACzC,IAAI,CAAC,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM;AAC5B,IAAI,CAAC,CAAC,GAAG,GAAG,UAAU;AACtB,IAAI,CAAC,CAAC,IAAI,GAAG,GAAG;AAChB,IAAI,CAAC,CAAC,QAAQ,GAAG,QAAQ,IAAI,IAAI,CAAC,GAAG,EAAE;AACvC,IAAI,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;AAChC,IAAI,CAAC,CAAC,KAAK,EAAE;AACb,IAAI,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;AAChC;;AAEA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE;AAC/C,IAAI,MAAM,GAAG,GAAG,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC;AACzC,IAAI,aAAa,CAAC,GAAG,EAAE,QAAQ,CAAC;AAChC,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AACjD;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,GAAG,0BAA0B,EAAE;AACtF,IAAI,cAAc,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,CAAC;AAClE;;AAEA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE;AAC9C,IAAI,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,0BAA0B,CAAC;AAC9D;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,YAAY,CAAC,IAAI,EAAE,QAAQ,EAAE;AAC7C;AACA,IAAI,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,kBAAkB,CAAC;AACtD;;;;;;;;"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * 通过动态创建 `<a>` 标签触发浏览器下载。
3
+ *
4
+ * @param {string} url - 任意可下载地址(同源或允许跨域)
5
+ * @param {string} [fileName] - 保存到本地的文件名;不传时使用时间戳
6
+ */
7
+ function downloadByUrl(url, fileName) {
8
+ const a = document.createElement("a");
9
+ a.style.display = "none";
10
+ a.rel = "noopener";
11
+ a.href = url;
12
+ a.download = fileName || Date.now();
13
+ document.body.appendChild(a);
14
+ a.click();
15
+ document.body.removeChild(a);
16
+ }
17
+
18
+ /**
19
+ * 把 Blob 转成临时 URL 并触发下载,下载完成后立即释放内存。
20
+ *
21
+ * @param {Blob} blob - 待下载的 Blob(含 File)
22
+ * @param {string} [fileName] - 保存到本地的文件名
23
+ */
24
+ function downloadByBlob(blob, fileName) {
25
+ const url = URL.createObjectURL(blob);
26
+ downloadByUrl(url, fileName);
27
+ setTimeout(() => URL.revokeObjectURL(url), 0);
28
+ }
29
+
30
+ /**
31
+ * 将任意数据包装成 Blob 并下载。
32
+ *
33
+ * @param {string | ArrayBufferView | ArrayBuffer | Blob} data - 要写入文件的数据
34
+ * @param {string} [fileName] - 保存到本地的文件名
35
+ * @param {string} [mimeType] - MIME 类型;默认 `application/octet-stream`
36
+ */
37
+ function downloadByData(data, fileName, mimeType = "application/octet-stream") {
38
+ downloadByBlob(new Blob([data], { type: mimeType }), fileName);
39
+ }
40
+
41
+ /**
42
+ * 快捷下载 Excel 文件(MIME 已固定)。
43
+ *
44
+ * @param {string | ArrayBufferView | ArrayBuffer | Blob} data - Excel 二进制或字符串内容
45
+ * @param {string} [fileName] - 保存到本地的文件名
46
+ */
47
+ function downloadExcel(data, fileName) {
48
+ downloadByData(data, fileName, "application/vnd.ms-excel");
49
+ }
50
+
51
+ /**
52
+ * 快捷下载 JSON 文件(MIME 已固定)。
53
+ * 若传入非字符串数据,会自行 `JSON.stringify`。
54
+ *
55
+ * @param {any} data - 要序列化的 JSON 数据
56
+ * @param {string} [fileName] - 保存到本地的文件名
57
+ */
58
+ function downloadJSON(data, fileName) {
59
+ // downloadByData(typeof data === "string" ? data : JSON.stringify(data, null, 4), fileName, "application/json");
60
+ downloadByData(data, fileName, "application/json");
61
+ }
62
+
63
+ export { downloadByBlob, downloadByData, downloadByUrl, downloadExcel, downloadJSON };
64
+ //# sourceMappingURL=download.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"download.js","sources":["../src/source/download.js"],"sourcesContent":["/**\n * 通过动态创建 `<a>` 标签触发浏览器下载。\n *\n * @param {string} url - 任意可下载地址(同源或允许跨域)\n * @param {string} [fileName] - 保存到本地的文件名;不传时使用时间戳\n */\nexport function downloadByUrl(url, fileName) {\n const a = document.createElement(\"a\");\n a.style.display = \"none\";\n a.rel = \"noopener\";\n a.href = url;\n a.download = fileName || Date.now();\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n}\n\n/**\n * 把 Blob 转成临时 URL 并触发下载,下载完成后立即释放内存。\n *\n * @param {Blob} blob - 待下载的 Blob(含 File)\n * @param {string} [fileName] - 保存到本地的文件名\n */\nexport function downloadByBlob(blob, fileName) {\n const url = URL.createObjectURL(blob);\n downloadByUrl(url, fileName);\n setTimeout(() => URL.revokeObjectURL(url), 0);\n}\n\n/**\n * 将任意数据包装成 Blob 并下载。\n *\n * @param {string | ArrayBufferView | ArrayBuffer | Blob} data - 要写入文件的数据\n * @param {string} [fileName] - 保存到本地的文件名\n * @param {string} [mimeType] - MIME 类型;默认 `application/octet-stream`\n */\nexport function downloadByData(data, fileName, mimeType = \"application/octet-stream\") {\n downloadByBlob(new Blob([data], { type: mimeType }), fileName);\n}\n\n/**\n * 快捷下载 Excel 文件(MIME 已固定)。\n *\n * @param {string | ArrayBufferView | ArrayBuffer | Blob} data - Excel 二进制或字符串内容\n * @param {string} [fileName] - 保存到本地的文件名\n */\nexport function downloadExcel(data, fileName) {\n downloadByData(data, fileName, \"application/vnd.ms-excel\");\n}\n\n/**\n * 快捷下载 JSON 文件(MIME 已固定)。\n * 若传入非字符串数据,会自行 `JSON.stringify`。\n *\n * @param {any} data - 要序列化的 JSON 数据\n * @param {string} [fileName] - 保存到本地的文件名\n */\nexport function downloadJSON(data, fileName) {\n // downloadByData(typeof data === \"string\" ? data : JSON.stringify(data, null, 4), fileName, \"application/json\");\n downloadByData(data, fileName, \"application/json\");\n}\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,aAAa,CAAC,GAAG,EAAE,QAAQ,EAAE;AAC7C,IAAI,MAAM,CAAC,GAAG,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC;AACzC,IAAI,CAAC,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM;AAC5B,IAAI,CAAC,CAAC,GAAG,GAAG,UAAU;AACtB,IAAI,CAAC,CAAC,IAAI,GAAG,GAAG;AAChB,IAAI,CAAC,CAAC,QAAQ,GAAG,QAAQ,IAAI,IAAI,CAAC,GAAG,EAAE;AACvC,IAAI,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;AAChC,IAAI,CAAC,CAAC,KAAK,EAAE;AACb,IAAI,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;AAChC;;AAEA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE;AAC/C,IAAI,MAAM,GAAG,GAAG,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC;AACzC,IAAI,aAAa,CAAC,GAAG,EAAE,QAAQ,CAAC;AAChC,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AACjD;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,GAAG,0BAA0B,EAAE;AACtF,IAAI,cAAc,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,CAAC;AAClE;;AAEA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE;AAC9C,IAAI,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,0BAA0B,CAAC;AAC9D;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,YAAY,CAAC,IAAI,EAAE,QAAQ,EAAE;AAC7C;AACA,IAAI,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,kBAAkB,CAAC;AACtD;;;;"}
package/dist/evt.cjs ADDED
@@ -0,0 +1,155 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * 简单、高性能的通用事件总线。
5
+ * - 支持命名空间事件
6
+ * - 支持一次性监听器
7
+ * - 返回唯一 flag,用于精确卸载
8
+ * - emit 时可选自定义 this 指向
9
+ */
10
+ class MyEvent {
11
+ constructor() {
12
+ this.evtPool = new Map();
13
+ }
14
+
15
+ /**
16
+ * 注册事件监听器。
17
+ * @param {string} name - 事件名
18
+ * @param {Function} fn - 回调函数
19
+ * @returns {string} flag - 唯一标识,用于 off
20
+ */
21
+ on(name, fn) {
22
+ let flag = Date.now() + "_" + parseInt(Math.random() * 1e8);
23
+ const evtItem = {
24
+ flag,
25
+ fn
26
+ };
27
+ if (this.evtPool.has(name)) {
28
+ this.evtPool.get(name).push(evtItem);
29
+ } else {
30
+ this.evtPool.set(name, [evtItem]);
31
+ }
32
+ return flag;
33
+ }
34
+
35
+ /**
36
+ * 注册一次性监听器,触发后自动移除。
37
+ * @param {string} name - 事件名
38
+ * @param {Function} fn - 回调函数
39
+ * @returns {string} flag - 唯一标识
40
+ */
41
+ once(name, fn) {
42
+ const _this = this;
43
+ let wrapper;
44
+ wrapper = function (data) {
45
+ _this.off(name, wrapper);
46
+ fn.call(this, data);
47
+ };
48
+ return this.on(name, wrapper);
49
+ }
50
+
51
+ /**
52
+ * 移除指定事件监听器。
53
+ * @param {string} name - 事件名
54
+ * @param {Function|string} fnOrFlag - 回调函数或 flag
55
+ */
56
+ off(name, fnOrFlag) {
57
+ if (!this.evtPool.has(name)) return;
58
+ const evtItems = this.evtPool.get(name);
59
+ const filtered = evtItems.filter((item) => item.fn !== fnOrFlag && item.flag !== fnOrFlag);
60
+ if (filtered.length === 0) {
61
+ this.evtPool.delete(name);
62
+ } else {
63
+ this.evtPool.set(name, filtered);
64
+ }
65
+ }
66
+
67
+ /**
68
+ * 触发事件(同步执行)。
69
+ * @param {string} name - 事件名
70
+ * @param {*} [data] - 任意载荷
71
+ * @param {*} [fnThis] - 回调内部 this 指向,默认 undefined
72
+ */
73
+ emit(name, data, fnThis) {
74
+ if (!this.evtPool.has(name)) return;
75
+ const evtItems = this.evtPool.get(name);
76
+ evtItems.forEach((item) => {
77
+ try {
78
+ item.fn.call(fnThis, data);
79
+ } catch (err) {
80
+ console.error(`Error in event listener for "${name}":`, err);
81
+ }
82
+ });
83
+ }
84
+ }
85
+
86
+ /**
87
+ * 跨页通信插件:通过 localStorage + storage 事件将当前实例的 emit 广播到其他同源页面。
88
+ * 支持节流、命名空间隔离。
89
+ */
90
+ const MyEvent_CrossPagePlugin = (() => {
91
+ const INSTALLED = new WeakSet(); // 防止重复安装
92
+
93
+ return {
94
+ /**
95
+ * 为指定 MyEvent 实例安装跨页插件。
96
+ * @param {MyEvent} bus - 事件总线实例
97
+ * @param {Options} [opts] - 配置项
98
+ */
99
+ install(bus, opts = {}) {
100
+ if (INSTALLED.has(bus)) return;
101
+ INSTALLED.add(bus);
102
+
103
+ const ns = `___my-event-cross-page-${opts.namespace || "default"}___`;
104
+ const delay = opts.throttle || 16;
105
+ let last = 0;
106
+
107
+ // 1、重写 emit
108
+ const rawEmit = bus.emit;
109
+ bus.emit = function (name, data, fnThis) {
110
+ rawEmit.call(bus, name, data, fnThis); // 本地先执行
111
+ const now = Date.now();
112
+ if (now - last < delay) return;
113
+ last = now;
114
+ const key = ns + name;
115
+ try {
116
+ localStorage.setItem(key, JSON.stringify({ name, data, ts: now }));
117
+ localStorage.removeItem(key); // 触发 storage 事件
118
+ } catch (e) {}
119
+ };
120
+
121
+ // 2、监听其他页广播
122
+ function onStorageHandler(e) {
123
+ if (!e.key || !e.key.startsWith(ns)) return;
124
+ let payload;
125
+ try {
126
+ payload = JSON.parse(e.newValue || "{}");
127
+ } catch {
128
+ return;
129
+ }
130
+ if (!payload.ts || payload.ts <= last) return;
131
+ rawEmit.call(bus, e.key.slice(ns.length), payload.data); // 仅本地
132
+ }
133
+ addEventListener("storage", onStorageHandler);
134
+
135
+ // 3、保存卸载器
136
+ bus._uninstallCrossPage = () => {
137
+ removeEventListener("storage", onStorageHandler);
138
+ bus.emit = rawEmit;
139
+ INSTALLED.delete(bus);
140
+ };
141
+ },
142
+
143
+ /**
144
+ * 卸载插件,恢复原始 emit 并停止监听。
145
+ * @param {MyEvent} bus - 事件总线实例
146
+ */
147
+ uninstall(bus) {
148
+ if (typeof bus._uninstallCrossPage === "function") bus._uninstallCrossPage();
149
+ }
150
+ };
151
+ })();
152
+
153
+ exports.MyEvent = MyEvent;
154
+ exports.MyEvent_CrossPagePlugin = MyEvent_CrossPagePlugin;
155
+ //# sourceMappingURL=evt.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evt.cjs","sources":["../src/source/evt.js"],"sourcesContent":["/**\r\n * 简单、高性能的通用事件总线。\r\n * - 支持命名空间事件\r\n * - 支持一次性监听器\r\n * - 返回唯一 flag,用于精确卸载\r\n * - emit 时可选自定义 this 指向\r\n */\r\nexport class MyEvent {\r\n constructor() {\r\n this.evtPool = new Map();\r\n }\r\n\r\n /**\r\n * 注册事件监听器。\r\n * @param {string} name - 事件名\r\n * @param {Function} fn - 回调函数\r\n * @returns {string} flag - 唯一标识,用于 off\r\n */\r\n on(name, fn) {\r\n let flag = Date.now() + \"_\" + parseInt(Math.random() * 1e8);\r\n const evtItem = {\r\n flag,\r\n fn\r\n };\r\n if (this.evtPool.has(name)) {\r\n this.evtPool.get(name).push(evtItem);\r\n } else {\r\n this.evtPool.set(name, [evtItem]);\r\n }\r\n return flag;\r\n }\r\n\r\n /**\r\n * 注册一次性监听器,触发后自动移除。\r\n * @param {string} name - 事件名\r\n * @param {Function} fn - 回调函数\r\n * @returns {string} flag - 唯一标识\r\n */\r\n once(name, fn) {\r\n const _this = this;\r\n let wrapper;\r\n wrapper = function (data) {\r\n _this.off(name, wrapper);\r\n fn.call(this, data);\r\n };\r\n return this.on(name, wrapper);\r\n }\r\n\r\n /**\r\n * 移除指定事件监听器。\r\n * @param {string} name - 事件名\r\n * @param {Function|string} fnOrFlag - 回调函数或 flag\r\n */\r\n off(name, fnOrFlag) {\r\n if (!this.evtPool.has(name)) return;\r\n const evtItems = this.evtPool.get(name);\r\n const filtered = evtItems.filter((item) => item.fn !== fnOrFlag && item.flag !== fnOrFlag);\r\n if (filtered.length === 0) {\r\n this.evtPool.delete(name);\r\n } else {\r\n this.evtPool.set(name, filtered);\r\n }\r\n }\r\n\r\n /**\r\n * 触发事件(同步执行)。\r\n * @param {string} name - 事件名\r\n * @param {*} [data] - 任意载荷\r\n * @param {*} [fnThis] - 回调内部 this 指向,默认 undefined\r\n */\r\n emit(name, data, fnThis) {\r\n if (!this.evtPool.has(name)) return;\r\n const evtItems = this.evtPool.get(name);\r\n evtItems.forEach((item) => {\r\n try {\r\n item.fn.call(fnThis, data);\r\n } catch (err) {\r\n console.error(`Error in event listener for \"${name}\":`, err);\r\n }\r\n });\r\n }\r\n}\r\n\r\n/**\r\n * 跨页通信插件:通过 localStorage + storage 事件将当前实例的 emit 广播到其他同源页面。\r\n * 支持节流、命名空间隔离。\r\n */\r\nexport const MyEvent_CrossPagePlugin = (() => {\r\n const INSTALLED = new WeakSet(); // 防止重复安装\r\n\r\n return {\r\n /**\r\n * 为指定 MyEvent 实例安装跨页插件。\r\n * @param {MyEvent} bus - 事件总线实例\r\n * @param {Options} [opts] - 配置项\r\n */\r\n install(bus, opts = {}) {\r\n if (INSTALLED.has(bus)) return;\r\n INSTALLED.add(bus);\r\n\r\n const ns = `___my-event-cross-page-${opts.namespace || \"default\"}___`;\r\n const delay = opts.throttle || 16;\r\n let last = 0;\r\n\r\n // 1、重写 emit\r\n const rawEmit = bus.emit;\r\n bus.emit = function (name, data, fnThis) {\r\n rawEmit.call(bus, name, data, fnThis); // 本地先执行\r\n const now = Date.now();\r\n if (now - last < delay) return;\r\n last = now;\r\n const key = ns + name;\r\n try {\r\n localStorage.setItem(key, JSON.stringify({ name, data, ts: now }));\r\n localStorage.removeItem(key); // 触发 storage 事件\r\n } catch (e) {}\r\n };\r\n\r\n // 2、监听其他页广播\r\n function onStorageHandler(e) {\r\n if (!e.key || !e.key.startsWith(ns)) return;\r\n let payload;\r\n try {\r\n payload = JSON.parse(e.newValue || \"{}\");\r\n } catch {\r\n return;\r\n }\r\n if (!payload.ts || payload.ts <= last) return;\r\n rawEmit.call(bus, e.key.slice(ns.length), payload.data); // 仅本地\r\n }\r\n addEventListener(\"storage\", onStorageHandler);\r\n\r\n // 3、保存卸载器\r\n bus._uninstallCrossPage = () => {\r\n removeEventListener(\"storage\", onStorageHandler);\r\n bus.emit = rawEmit;\r\n INSTALLED.delete(bus);\r\n };\r\n },\r\n\r\n /**\r\n * 卸载插件,恢复原始 emit 并停止监听。\r\n * @param {MyEvent} bus - 事件总线实例\r\n */\r\n uninstall(bus) {\r\n if (typeof bus._uninstallCrossPage === \"function\") bus._uninstallCrossPage();\r\n }\r\n };\r\n})();\r\n"],"names":[],"mappings":"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"}