@giszhc/file-utils 0.0.1 → 0.0.4

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/README.md CHANGED
@@ -8,8 +8,9 @@
8
8
  - **文件下载**:支持从 URL、Blob、Base64 等多种方式下载文件
9
9
  - **文件转换**:支持文件格式转换(如 Base64 转 Blob、File 转 Base64 等)
10
10
  - **文件压缩**:支持图片压缩处理
11
- - **文件生成**:支持生成 TXT、CSV、JSON 格式文件
11
+ - **文件生成**:支持生成 TXT、CSV、JSON 格式文件,可选择返回 File 对象而不下载
12
12
  - **文件验证**:提供完整的文件验证和类型判断功能
13
+ - **剪贴板操作**:支持一键复制文本到剪贴板,并支持成功/失败回调
13
14
  - **自动清理**:自动管理临时创建的 Blob URL,防止内存泄漏
14
15
  - **TypeScript**:完善的类型定义支持
15
16
 
@@ -167,6 +168,12 @@ generateTxtFile('Hello World!', 'hello.txt');
167
168
  // 生成多行文本
168
169
  const lines = ['第一行', '第二行', '第三行'].join('\n');
169
170
  generateTxtFile(lines, 'multiline.txt');
171
+
172
+ // 生成文件但不下载,返回 File 对象
173
+ const file = generateTxtFile('Hello World!', 'hello.txt', { download: false });
174
+ console.log(file.name); // "hello.txt"
175
+ console.log(file.type); // "text/plain;charset=utf-8"
176
+ console.log(file.size); // 文件大小(字节)
170
177
  ```
171
178
 
172
179
  ### 9. 生成 CSV 文件
@@ -192,6 +199,12 @@ generateCsvFile(data, 'data.csv');
192
199
 
193
200
  // 自定义分隔符(制表符 TSV)
194
201
  generateCsvFile(data, 'data.tsv', { separator: '\t' });
202
+
203
+ // 生成文件但不下载,返回 File 对象
204
+ const csvFile = generateCsvFile(users, 'users.csv', { download: false });
205
+ console.log(csvFile.name); // "users.csv"
206
+ console.log(csvFile.type); // "text/csv;charset=utf-8"
207
+ console.log(csvFile.size); // 文件大小(字节)
195
208
  ```
196
209
 
197
210
  ### 10. 生成 JSON 文件
@@ -215,6 +228,12 @@ generateJsonFile(data, 'data.min.json', { pretty: false });
215
228
 
216
229
  // 自定义缩进
217
230
  generateJsonFile(data, 'data.json', { spaces: 4 });
231
+
232
+ // 生成文件但不下载,返回 File 对象
233
+ const file = generateJsonFile(data, 'user.json', { download: false });
234
+ console.log(file.name); // "user.json"
235
+ console.log(file.type); // "application/json;charset=utf-8"
236
+ console.log(file.size); // 文件大小(字节)
218
237
  ```
219
238
 
220
239
  ### 11. 获取文件扩展名和文件名
@@ -336,6 +355,31 @@ console.log(data.name);
336
355
  console.log(data.age);
337
356
  ```
338
357
 
358
+ ### 19. 一键复制文本到剪贴板
359
+
360
+ ```ts
361
+ import { copyToClipboard } from '@giszhc/file-utils';
362
+
363
+ // 基本使用
364
+ await copyToClipboard('Hello World!');
365
+
366
+ // 带回调的使用
367
+ await copyToClipboard('这是一段文本', {
368
+ onSuccess: () => console.log('复制成功!'),
369
+ onError: (error) => console.error('复制失败:', error)
370
+ });
371
+ ```
372
+
373
+ ### 20. 从剪贴板读取文本
374
+
375
+ ```ts
376
+ import { pasteFromClipboard } from '@giszhc/file-utils';
377
+
378
+ // 读取剪贴板内容
379
+ const text = await pasteFromClipboard();
380
+ console.log('剪贴板内容:', text);
381
+ ```
382
+
339
383
  ------
340
384
 
341
385
  ## 使用方法
@@ -1,7 +1,7 @@
1
- function p(e, t = "text") {
2
- return new Promise((n, r) => {
1
+ function y(e, r = "text") {
2
+ return new Promise((t, n) => {
3
3
  const o = new FileReader();
4
- switch (t) {
4
+ switch (r) {
5
5
  case "text":
6
6
  o.readAsText(e);
7
7
  break;
@@ -19,203 +19,252 @@ function p(e, t = "text") {
19
19
  }
20
20
  o.onload = (i) => {
21
21
  const a = i.target?.result;
22
- a !== void 0 ? n(a) : r(new Error("文件读取失败"));
22
+ a !== void 0 ? t(a) : n(new Error("文件读取失败"));
23
23
  }, o.onerror = () => {
24
- r(new Error("文件读取错误"));
24
+ n(new Error("文件读取错误"));
25
25
  }, o.onabort = () => {
26
- r(new Error("文件读取被中止"));
26
+ n(new Error("文件读取被中止"));
27
27
  };
28
28
  });
29
29
  }
30
- async function S(e, t = "utf-8") {
31
- return new Promise((n, r) => {
30
+ async function T(e, r = "utf-8") {
31
+ return new Promise((t, n) => {
32
32
  const o = new FileReader();
33
- o.readAsText(e, t), o.onload = (i) => {
33
+ o.readAsText(e, r), o.onload = (i) => {
34
34
  const a = i.target?.result;
35
- a !== void 0 ? n(a) : r(new Error("文件读取失败"));
35
+ a !== void 0 ? t(a) : n(new Error("文件读取失败"));
36
36
  }, o.onerror = () => {
37
- r(new Error("文件读取错误"));
37
+ n(new Error("文件读取错误"));
38
38
  }, o.onabort = () => {
39
- r(new Error("文件读取被中止"));
39
+ n(new Error("文件读取被中止"));
40
40
  };
41
41
  });
42
42
  }
43
- async function W(e, t = {}) {
44
- const { separator: n = ",", hasHeader: r = !0 } = t, o = await p(e, "text");
43
+ async function $(e, r = {}) {
44
+ const { separator: t = ",", hasHeader: n = !0 } = r, o = await y(e, "text");
45
45
  if (!o.trim())
46
46
  return [];
47
47
  const i = o.split(/\r?\n/).filter((h) => h.trim() !== "");
48
48
  if (i.length === 0)
49
49
  return [];
50
50
  let a = [], s = 0;
51
- r && (a = b(i[0], n), s = 1);
52
- const d = [];
51
+ n && (a = F(i[0], t), s = 1);
52
+ const l = [];
53
53
  for (let h = s; h < i.length; h++) {
54
- const l = b(i[h], n);
55
- if (r && a.length > 0) {
56
- const u = {};
57
- a.forEach((c, f) => {
58
- u[c] = l[f] || "";
59
- }), d.push(u);
54
+ const w = F(i[h], t);
55
+ if (n && a.length > 0) {
56
+ const d = {};
57
+ a.forEach((u, c) => {
58
+ d[u] = w[c] || "";
59
+ }), l.push(d);
60
60
  } else
61
- d.push(l);
61
+ l.push(w);
62
62
  }
63
- return d;
63
+ return l;
64
64
  }
65
- function b(e, t) {
66
- const n = [];
67
- let r = "", o = !1;
65
+ function F(e, r) {
66
+ const t = [];
67
+ let n = "", o = !1;
68
68
  for (let i = 0; i < e.length; i++) {
69
69
  const a = e[i], s = e[i + 1];
70
- a === '"' ? o && s === '"' ? (r += '"', i++) : o = !o : a === t && !o ? (n.push(r.trim()), r = "") : r += a;
70
+ a === '"' ? o && s === '"' ? (n += '"', i++) : o = !o : a === r && !o ? (t.push(n.trim()), n = "") : n += a;
71
71
  }
72
- return n.push(r.trim()), n;
72
+ return t.push(n.trim()), t;
73
73
  }
74
- async function k(e) {
75
- const t = await p(e, "text");
74
+ async function W(e) {
75
+ const r = await y(e, "text");
76
76
  try {
77
- return JSON.parse(t);
78
- } catch (n) {
79
- throw new Error(`JSON 解析失败:${n instanceof Error ? n.message : "未知错误"}`);
77
+ return JSON.parse(r);
78
+ } catch (t) {
79
+ throw new Error(`JSON 解析失败:${t instanceof Error ? t.message : "未知错误"}`);
80
80
  }
81
81
  }
82
- async function F(e, t, n) {
82
+ async function v(e, r, t) {
83
83
  try {
84
- const r = await fetch(e, n?.fetchOptions);
85
- if (!r.ok)
86
- throw new Error(`下载失败:${r.status} ${r.statusText}`);
87
- const o = await r.blob();
88
- let i = t;
84
+ const n = await fetch(e, t?.fetchOptions);
85
+ if (!n.ok)
86
+ throw new Error(`下载失败:${n.status} ${n.statusText}`);
87
+ const o = await n.blob();
88
+ let i = r;
89
89
  if (!i) {
90
- const a = r.headers.get("Content-Disposition");
90
+ const a = n.headers.get("Content-Disposition");
91
91
  if (a) {
92
92
  const s = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(a);
93
93
  s && s[1] && (i = decodeURIComponent(s[1].replace(/['"]/g, "")));
94
94
  }
95
95
  i || (i = e.split("/").pop() || "download");
96
96
  }
97
- g(o, i, n);
98
- } catch (r) {
99
- throw console.error("文件下载失败:", r), r;
97
+ f(o, i, t);
98
+ } catch (n) {
99
+ throw console.error("文件下载失败:", n), n;
100
100
  }
101
101
  }
102
- function g(e, t, n) {
103
- const r = URL.createObjectURL(e), o = document.createElement("a");
104
- o.href = r, o.download = t, n?.newWindow && (o.target = "_blank"), document.body.appendChild(o), o.click(), document.body.removeChild(o), URL.revokeObjectURL(r);
102
+ function f(e, r, t) {
103
+ const n = URL.createObjectURL(e), o = document.createElement("a");
104
+ o.href = n, o.download = r, t?.newWindow && (o.target = "_blank"), document.body.appendChild(o), o.click(), document.body.removeChild(o), URL.revokeObjectURL(n);
105
105
  }
106
- function $(e, t, n) {
107
- e.startsWith("data:") ? (n = e.split(",")[0].split(":")[1].split(";")[0], e = e.split(",")[1]) : n || (n = "application/octet-stream");
106
+ function k(e, r, t) {
107
+ e.startsWith("data:") ? (t = e.split(",")[0].split(":")[1].split(";")[0], e = e.split(",")[1]) : t || (t = "application/octet-stream");
108
108
  const o = atob(e), i = new Array(o.length);
109
- for (let d = 0; d < o.length; d++)
110
- i[d] = o.charCodeAt(d);
111
- const a = new Uint8Array(i), s = new Blob([a], { type: n });
112
- g(s, t);
109
+ for (let l = 0; l < o.length; l++)
110
+ i[l] = o.charCodeAt(l);
111
+ const a = new Uint8Array(i), s = new Blob([a], { type: t });
112
+ f(s, r);
113
113
  }
114
- async function I(e, t = 500) {
115
- for (let n = 0; n < e.length; n++)
114
+ async function I(e, r = 500) {
115
+ for (let t = 0; t < e.length; t++)
116
116
  try {
117
- await F(e[n]), n < e.length - 1 && await new Promise((r) => setTimeout(r, t));
118
- } catch (r) {
119
- console.error(`第 ${n + 1} 个文件下载失败:`, r);
117
+ await v(e[t]), t < e.length - 1 && await new Promise((n) => setTimeout(n, r));
118
+ } catch (n) {
119
+ console.error(`第 ${t + 1} 个文件下载失败:`, n);
120
120
  }
121
121
  }
122
122
  function L(e) {
123
- return p(e, "dataURL");
123
+ return y(e, "dataURL");
124
124
  }
125
- function O(e, t) {
126
- e.startsWith("data:") ? (t = e.split(",")[0].split(":")[1].split(";")[0], e = e.split(",")[1]) : t || (t = "application/octet-stream");
127
- const r = atob(e), o = new Array(r.length);
128
- for (let a = 0; a < r.length; a++)
129
- o[a] = r.charCodeAt(a);
125
+ function O(e, r) {
126
+ e.startsWith("data:") ? (r = e.split(",")[0].split(":")[1].split(";")[0], e = e.split(",")[1]) : r || (r = "application/octet-stream");
127
+ const n = atob(e), o = new Array(n.length);
128
+ for (let a = 0; a < n.length; a++)
129
+ o[a] = n.charCodeAt(a);
130
130
  const i = new Uint8Array(o);
131
- return new Blob([i], { type: t });
131
+ return new Blob([i], { type: r });
132
132
  }
133
- function R(e, t, n = "utf-8") {
134
- const r = new Blob([e], { type: `text/plain;charset=${n}` });
135
- g(r, t);
133
+ async function R(e, r) {
134
+ try {
135
+ if (navigator.clipboard && navigator.clipboard.writeText) {
136
+ await navigator.clipboard.writeText(e), r?.onSuccess && r.onSuccess(e);
137
+ return;
138
+ }
139
+ const t = document.createElement("textarea");
140
+ t.value = e, t.style.position = "fixed", t.style.top = "0", t.style.left = "0", t.style.width = "2em", t.style.height = "2em", t.style.padding = "0", t.style.border = "none", t.style.outline = "none", t.style.boxShadow = "none", t.style.background = "transparent", t.style.opacity = "0", document.body.appendChild(t), t.focus(), t.select();
141
+ try {
142
+ if (document.execCommand("copy"))
143
+ r?.onSuccess && r.onSuccess(e);
144
+ else
145
+ throw new Error("execCommand 复制失败");
146
+ } catch {
147
+ throw new Error("无法使用 execCommand 复制文本");
148
+ } finally {
149
+ document.body.removeChild(t);
150
+ }
151
+ } catch (t) {
152
+ const n = t instanceof Error ? t : new Error("未知错误");
153
+ throw r?.onError ? r.onError(n) : console.error("复制到剪贴板失败:", n), n;
154
+ }
155
+ }
156
+ async function j() {
157
+ try {
158
+ if (navigator.clipboard && navigator.clipboard.readText)
159
+ return await navigator.clipboard.readText();
160
+ throw new Error("当前浏览器不支持读取剪贴板");
161
+ } catch (e) {
162
+ throw console.error("从剪贴板读取失败:", e), new Error("无法从剪贴板读取内容,请检查浏览器权限");
163
+ }
136
164
  }
137
- function w(e, t) {
165
+ function z(e, r, t) {
166
+ const {
167
+ download: n = !0,
168
+ encoding: o = "utf-8"
169
+ } = t || {}, i = new Blob([e], { type: `text/plain;charset=${o}` });
170
+ if (n) {
171
+ f(i, r);
172
+ return;
173
+ }
174
+ return new File([i], r, { type: `text/plain;charset=${o}` });
175
+ }
176
+ function p(e, r) {
138
177
  if (e == null)
139
178
  return "";
140
- const n = String(e);
141
- return n.includes(t) || n.includes('"') || n.includes(`
142
- `) || n.includes("\r") ? `"${n.replace(/"/g, '""')}"` : n;
179
+ const t = String(e);
180
+ return t.includes(r) || t.includes('"') || t.includes(`
181
+ `) || t.includes("\r") ? `"${t.replace(/"/g, '""')}"` : t;
143
182
  }
144
- function T(e, t, n = {}) {
183
+ function U(e, r, t = {}) {
145
184
  const {
146
- separator: r = ",",
185
+ separator: n = ",",
147
186
  includeHeader: o = !0,
148
- encoding: i = "utf-8"
149
- } = n;
187
+ download: i = !0,
188
+ encoding: a = "utf-8"
189
+ } = t;
150
190
  if (e.length === 0)
151
191
  throw new Error("数据不能为空");
152
- let a = "";
192
+ let s = "";
153
193
  if (typeof e[0] == "object" && !Array.isArray(e[0])) {
154
- const l = e, u = Array.from(
155
- new Set(l.flatMap((c) => Object.keys(c)))
194
+ const d = e, u = Array.from(
195
+ new Set(d.flatMap((c) => Object.keys(c)))
156
196
  );
157
- o && (a += u.join(r) + `
158
- `), l.forEach((c) => {
159
- const f = u.map((x) => {
160
- const C = c[x];
161
- return w(C, r);
197
+ o && (s += u.join(n) + `
198
+ `), d.forEach((c) => {
199
+ const g = u.map((b) => {
200
+ const A = c[b];
201
+ return p(A, n);
162
202
  });
163
- a += f.join(r) + `
203
+ s += g.join(n) + `
164
204
  `;
165
205
  });
166
206
  } else {
167
- const l = e;
168
- if (o && l.length > 0) {
169
- const u = l[0].map(
170
- (c) => w(c, r)
207
+ const d = e;
208
+ if (o && d.length > 0) {
209
+ const u = d[0].map(
210
+ (c) => p(c, n)
171
211
  );
172
- a += u.join(r) + `
212
+ s += u.join(n) + `
173
213
  `;
174
- for (let c = 1; c < l.length; c++) {
175
- const f = l[c].map(
176
- (x) => w(x, r)
214
+ for (let c = 1; c < d.length; c++) {
215
+ const g = d[c].map(
216
+ (b) => p(b, n)
177
217
  );
178
- a += f.join(r) + `
218
+ s += g.join(n) + `
179
219
  `;
180
220
  }
181
- } else o || l.forEach((u) => {
221
+ } else o || d.forEach((u) => {
182
222
  const c = u.map(
183
- (f) => w(f, r)
223
+ (g) => p(g, n)
184
224
  );
185
- a += c.join(r) + `
225
+ s += c.join(n) + `
186
226
  `;
187
227
  });
188
228
  }
189
- a = a.trimEnd();
190
- const d = "\uFEFF", h = new Blob([d + a], {
191
- type: `text/csv;charset=${i}`
229
+ s = s.trimEnd();
230
+ const h = "\uFEFF", w = new Blob([h + s], {
231
+ type: `text/csv;charset=${a}`
192
232
  });
193
- g(h, t);
233
+ if (i) {
234
+ f(w, r);
235
+ return;
236
+ }
237
+ return new File([w], r, { type: `text/csv;charset=${a}` });
194
238
  }
195
- function z(e, t, n = {}) {
239
+ function H(e, r, t = {}) {
196
240
  const {
197
- pretty: r = !0,
241
+ pretty: n = !0,
198
242
  spaces: o = 2,
199
- encoding: i = "utf-8"
200
- } = n;
243
+ download: i = !0,
244
+ encoding: a = "utf-8"
245
+ } = t;
201
246
  try {
202
- const a = r ? JSON.stringify(e, null, o) : JSON.stringify(e), s = new Blob([a], {
203
- type: `application/json;charset=${i}`
247
+ const s = n ? JSON.stringify(e, null, o) : JSON.stringify(e), l = new Blob([s], {
248
+ type: `application/json;charset=${a}`
204
249
  });
205
- g(s, t);
206
- } catch (a) {
207
- throw console.error("JSON 序列化失败:", a), new Error("数据无法序列化为 JSON");
250
+ if (i) {
251
+ f(l, r);
252
+ return;
253
+ }
254
+ return new File([l], r, { type: `application/json;charset=${a}` });
255
+ } catch (s) {
256
+ throw console.error("JSON 序列化失败:", s), new Error("数据无法序列化为 JSON");
208
257
  }
209
258
  }
210
- function y(e) {
211
- const t = typeof e == "string" ? e : e.name, n = t.lastIndexOf(".");
212
- return n === -1 || n === t.length - 1 ? "" : t.slice(n).toLowerCase();
259
+ function x(e) {
260
+ const r = typeof e == "string" ? e : e.name, t = r.lastIndexOf(".");
261
+ return t === -1 || t === r.length - 1 ? "" : r.slice(t).toLowerCase();
213
262
  }
214
- function U(e) {
215
- const t = typeof e == "string" ? e : e.name, n = t.lastIndexOf(".");
216
- return n === -1 ? t : t.slice(0, n);
263
+ function J(e) {
264
+ const r = typeof e == "string" ? e : e.name, t = r.lastIndexOf(".");
265
+ return t === -1 ? r : r.slice(0, t);
217
266
  }
218
- function j(e) {
267
+ function M(e) {
219
268
  return {
220
269
  name: e.name,
221
270
  size: e.size,
@@ -223,127 +272,132 @@ function j(e) {
223
272
  lastModified: e.lastModified
224
273
  };
225
274
  }
226
- function v(e, t) {
227
- if (!e || !t || t.length === 0)
275
+ function E(e, r) {
276
+ if (!e || !r || r.length === 0)
228
277
  return !1;
229
- const n = e.type.toLowerCase(), r = y(e).toLowerCase();
230
- return t.some((o) => {
278
+ const t = e.type.toLowerCase(), n = x(e).toLowerCase();
279
+ return r.some((o) => {
231
280
  const i = o.toLowerCase();
232
281
  if (i.endsWith("/*")) {
233
282
  const a = i.split("/")[0];
234
- return n.startsWith(`${a}/`);
283
+ return t.startsWith(`${a}/`);
235
284
  }
236
- return i.startsWith(".") ? r === i : n === i;
285
+ return i.startsWith(".") ? n === i : t === i;
237
286
  });
238
287
  }
239
- function E(e, t) {
240
- return e ? e.size <= t : !1;
288
+ function C(e, r) {
289
+ return e ? e.size <= r : !1;
241
290
  }
242
291
  function m(e) {
243
292
  if (!e)
244
293
  return !1;
245
294
  if (e.type.startsWith("image/"))
246
295
  return !0;
247
- const t = [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".svg", ".ico"], n = y(e);
248
- return t.includes(n);
296
+ const r = [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".svg", ".ico"], t = x(e);
297
+ return r.includes(t);
249
298
  }
250
299
  function B(e) {
251
- return new Promise((t, n) => {
300
+ return new Promise((r, t) => {
252
301
  if (!m(e)) {
253
- n(new Error("文件不是有效的图片格式"));
302
+ t(new Error("文件不是有效的图片格式"));
254
303
  return;
255
304
  }
256
- const r = new FileReader();
257
- r.onload = (o) => {
305
+ const n = new FileReader();
306
+ n.onload = (o) => {
258
307
  const i = new Image();
259
308
  i.onload = () => {
260
- t({
309
+ r({
261
310
  width: i.width,
262
311
  height: i.height
263
312
  });
264
313
  }, i.onerror = () => {
265
- n(new Error("图片加载失败"));
314
+ t(new Error("图片加载失败"));
266
315
  }, i.src = o.target?.result;
267
- }, r.onerror = () => {
268
- n(new Error("文件读取失败"));
269
- }, r.readAsDataURL(e);
316
+ }, n.onerror = () => {
317
+ t(new Error("文件读取失败"));
318
+ }, n.readAsDataURL(e);
270
319
  });
271
320
  }
272
- async function H(e, t = {}) {
273
- const n = [];
274
- if (t.acceptTypes && t.acceptTypes.length > 0 && (v(e, t.acceptTypes) || n.push(`不支持的文件类型。接受:${t.acceptTypes.join(", ")}`)), t.maxSize !== void 0 && !E(e, t.maxSize)) {
275
- const r = A(t.maxSize);
276
- n.push(`文件过大。最大允许:${r}`);
321
+ async function N(e, r = {}) {
322
+ const t = [];
323
+ if (r.acceptTypes && r.acceptTypes.length > 0 && (E(e, r.acceptTypes) || t.push(`不支持的文件类型。接受:${r.acceptTypes.join(", ")}`)), r.maxSize !== void 0 && !C(e, r.maxSize)) {
324
+ const n = S(r.maxSize);
325
+ t.push(`文件过大。最大允许:${n}`);
277
326
  }
278
- if (t.mustBeImage && !m(e) && n.push("文件必须是图片格式"), m(e) && (t.minWidth !== void 0 || t.maxWidth !== void 0 || t.minHeight !== void 0 || t.maxHeight !== void 0))
327
+ if (r.mustBeImage && !m(e) && t.push("文件必须是图片格式"), m(e) && (r.minWidth !== void 0 || r.maxWidth !== void 0 || r.minHeight !== void 0 || r.maxHeight !== void 0))
279
328
  try {
280
- const r = await B(e);
281
- t.minWidth !== void 0 && r.width < t.minWidth && n.push(`图片宽度过小。最小宽度:${t.minWidth}px`), t.maxWidth !== void 0 && r.width > t.maxWidth && n.push(`图片宽度过大。最大宽度:${t.maxWidth}px`), t.minHeight !== void 0 && r.height < t.minHeight && n.push(`图片高度过小。最小高度:${t.minHeight}px`), t.maxHeight !== void 0 && r.height > t.maxHeight && n.push(`图片高度过大。最大高度:${t.maxHeight}px`);
329
+ const n = await B(e);
330
+ r.minWidth !== void 0 && n.width < r.minWidth && t.push(`图片宽度过小。最小宽度:${r.minWidth}px`), r.maxWidth !== void 0 && n.width > r.maxWidth && t.push(`图片宽度过大。最大宽度:${r.maxWidth}px`), r.minHeight !== void 0 && n.height < r.minHeight && t.push(`图片高度过小。最小高度:${r.minHeight}px`), r.maxHeight !== void 0 && n.height > r.maxHeight && t.push(`图片高度过大。最大高度:${r.maxHeight}px`);
282
331
  } catch {
283
- n.push("无法获取图片尺寸");
332
+ t.push("无法获取图片尺寸");
284
333
  }
285
334
  return {
286
- valid: n.length === 0,
287
- errors: n
335
+ valid: t.length === 0,
336
+ errors: t
288
337
  };
289
338
  }
290
- function A(e, t = 2) {
339
+ function S(e, r = 2) {
291
340
  if (e === 0) return "0 B";
292
- const n = ["B", "KB", "MB", "GB", "TB"], r = 1024, o = Math.floor(Math.log(e) / Math.log(r));
293
- return parseFloat((e / Math.pow(r, o)).toFixed(t)) + " " + n[o];
341
+ const t = ["B", "KB", "MB", "GB", "TB"], n = 1024, o = Math.floor(Math.log(e) / Math.log(n));
342
+ return parseFloat((e / Math.pow(n, o)).toFixed(r)) + " " + t[o];
294
343
  }
295
- const J = {
344
+ const D = {
296
345
  // 读取
297
- readFile: p,
298
- readTxtFile: S,
299
- readCsvFile: W,
300
- readJsonFile: k,
346
+ readFile: y,
347
+ readTxtFile: T,
348
+ readCsvFile: $,
349
+ readJsonFile: W,
301
350
  // 下载
302
- downloadFile: F,
303
- downloadBlob: g,
304
- downloadBase64: $,
351
+ downloadFile: v,
352
+ downloadBlob: f,
353
+ downloadBase64: k,
305
354
  downloadMultiple: I,
306
355
  // 转换
307
356
  fileToBase64: L,
308
357
  base64ToBlob: O,
358
+ // 剪贴板
359
+ copyToClipboard: R,
360
+ pasteFromClipboard: j,
309
361
  // 生成
310
- generateTxtFile: R,
311
- generateCsvFile: T,
312
- generateJsonFile: z,
362
+ generateTxtFile: z,
363
+ generateCsvFile: U,
364
+ generateJsonFile: H,
313
365
  // 验证
314
- checkFileType: v,
315
- checkFileSize: E,
366
+ checkFileType: E,
367
+ checkFileSize: C,
316
368
  isImage: m,
317
369
  getImageDimensions: B,
318
- validateFile: H,
319
- formatFileSize: A,
370
+ validateFile: N,
371
+ formatFileSize: S,
320
372
  // 工具
321
- getFileExtension: y,
322
- getFileNameWithoutExtension: U,
323
- getFileInfo: j
373
+ getFileExtension: x,
374
+ getFileNameWithoutExtension: J,
375
+ getFileInfo: M
324
376
  };
325
377
  export {
326
378
  O as base64ToBlob,
327
- E as checkFileSize,
328
- v as checkFileType,
329
- J as default,
330
- $ as downloadBase64,
331
- g as downloadBlob,
332
- F as downloadFile,
379
+ C as checkFileSize,
380
+ E as checkFileType,
381
+ R as copyToClipboard,
382
+ D as default,
383
+ k as downloadBase64,
384
+ f as downloadBlob,
385
+ v as downloadFile,
333
386
  I as downloadMultiple,
334
387
  L as fileToBase64,
335
- A as formatFileSize,
336
- T as generateCsvFile,
337
- z as generateJsonFile,
338
- R as generateTxtFile,
339
- y as getFileExtension,
340
- j as getFileInfo,
341
- U as getFileNameWithoutExtension,
388
+ S as formatFileSize,
389
+ U as generateCsvFile,
390
+ H as generateJsonFile,
391
+ z as generateTxtFile,
392
+ x as getFileExtension,
393
+ M as getFileInfo,
394
+ J as getFileNameWithoutExtension,
342
395
  B as getImageDimensions,
343
396
  m as isImage,
344
- W as readCsvFile,
345
- p as readFile,
346
- k as readJsonFile,
347
- S as readTxtFile,
348
- H as validateFile
397
+ j as pasteFromClipboard,
398
+ $ as readCsvFile,
399
+ y as readFile,
400
+ W as readJsonFile,
401
+ T as readTxtFile,
402
+ N as validateFile
349
403
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@giszhc/file-utils",
3
- "version": "0.0.1",
3
+ "version": "0.0.4",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -0,0 +1,43 @@
1
+ /**
2
+ * 剪贴板复制相关方法
3
+ */
4
+ import type { CopyOptions } from './types';
5
+ /**
6
+ * 一键复制文本到剪贴板
7
+ *
8
+ * @param text - 要复制的文本内容
9
+ * @param options - 复制选项配置(可选)
10
+ * @returns Promise<void>
11
+ *
12
+ * @example
13
+ * // 基本使用
14
+ * copyToClipboard('Hello World!');
15
+ *
16
+ * @example
17
+ * // 带回调的使用
18
+ * copyToClipboard('Hello World!', {
19
+ * onSuccess: () => console.log('复制成功!'),
20
+ * onError: (error) => console.error('复制失败:', error)
21
+ * });
22
+ *
23
+ * @remarks
24
+ * - 优先使用现代的 navigator.clipboard API
25
+ * - 自动降级到传统的 execCommand 方式以兼容旧浏览器
26
+ * - 需要用户授权才能访问剪贴板
27
+ */
28
+ export declare function copyToClipboard(text: string, options?: CopyOptions): Promise<void>;
29
+ /**
30
+ * 从剪贴板读取文本
31
+ *
32
+ * @returns Promise<string> - 剪贴板中的文本内容
33
+ *
34
+ * @example
35
+ * // 读取剪贴板内容
36
+ * const text = await pasteFromClipboard();
37
+ * console.log('剪贴板内容:', text);
38
+ *
39
+ * @remarks
40
+ * - 需要用户授权才能访问剪贴板
41
+ * - 在某些浏览器中可能需要 HTTPS 环境
42
+ */
43
+ export declare function pasteFromClipboard(): Promise<string>;
@@ -1,12 +1,17 @@
1
1
  /**
2
2
  * 文件生成相关方法
3
3
  */
4
+ import type { GenerateOptions } from './types';
4
5
  /**
5
6
  * 生成并下载 TXT 文件
6
7
  *
7
8
  * @param content - 文本内容
8
9
  * @param filename - 文件名(包含 .txt 扩展名)
9
- * @param encoding - 字符编码,默认 'utf-8'
10
+ * @param options - 生成选项配置(可选)
11
+ * - download: 是否直接下载,默认 true
12
+ * - encoding: 字符编码,默认 'utf-8'
13
+ *
14
+ * @returns 如果 options.download 为 false,返回 File 对象;否则返回 void
10
15
  *
11
16
  * @example
12
17
  * // 生成简单的文本文件
@@ -16,8 +21,12 @@
16
21
  * // 生成多行文本
17
22
  * const lines = ['第一行', '第二行', '第三行'].join('\n');
18
23
  * generateTxtFile(lines, 'multiline.txt');
24
+ *
25
+ * @example
26
+ * // 生成文件但不下载,返回 File 对象
27
+ * const file = generateTxtFile('Hello World!', 'hello.txt', { download: false });
19
28
  */
20
- export declare function generateTxtFile(content: string, filename: string, encoding?: string): void;
29
+ export declare function generateTxtFile(content: string, filename: string, options?: GenerateOptions): File | void;
21
30
  /**
22
31
  * 生成并下载 CSV 文件
23
32
  *
@@ -26,8 +35,11 @@ export declare function generateTxtFile(content: string, filename: string, encod
26
35
  * @param options - CSV 选项配置(可选)
27
36
  * - separator: 字段分隔符,默认 ','
28
37
  * - includeHeader: 是否包含表头,默认 true
38
+ * - download: 是否直接下载,默认 true
29
39
  * - encoding: 字符编码,默认 'utf-8'
30
40
  *
41
+ * @returns 如果 options.download 为 false,返回 File 对象;否则返回 void
42
+ *
31
43
  * @example
32
44
  * // 使用对象数组生成 CSV
33
45
  * const users = [
@@ -49,12 +61,17 @@ export declare function generateTxtFile(content: string, filename: string, encod
49
61
  * @example
50
62
  * // 自定义分隔符(制表符)
51
63
  * generateCsvFile(data, 'data.tsv', { separator: '\t' });
64
+ *
65
+ * @example
66
+ * // 生成文件但不下载,返回 File 对象
67
+ * const file = generateCsvFile(users, 'users.csv', { download: false });
52
68
  */
53
69
  export declare function generateCsvFile(data: Record<string, any>[] | any[][], filename: string, options?: {
54
70
  separator?: string;
55
71
  includeHeader?: boolean;
72
+ download?: boolean;
56
73
  encoding?: string;
57
- }): void;
74
+ }): File | void;
58
75
  /**
59
76
  * 生成并下载 JSON 文件
60
77
  *
@@ -63,8 +80,11 @@ export declare function generateCsvFile(data: Record<string, any>[] | any[][], f
63
80
  * @param options - JSON 选项配置(可选)
64
81
  * - pretty: 是否格式化输出,默认 true
65
82
  * - spaces: 缩进空格数,默认 2
83
+ * - download: 是否直接下载,默认 true
66
84
  * - encoding: 字符编码,默认 'utf-8'
67
85
  *
86
+ * @returns 如果 options.download 为 false,返回 File 对象;否则返回 void
87
+ *
68
88
  * @example
69
89
  * // 生成简单的 JSON 文件
70
90
  * const data = { name: '张三', age: 25 };
@@ -85,9 +105,14 @@ export declare function generateCsvFile(data: Record<string, any>[] | any[][], f
85
105
  * @example
86
106
  * // 自定义缩进
87
107
  * generateJsonFile(data, 'data.json', { spaces: 4 });
108
+ *
109
+ * @example
110
+ * // 生成文件但不下载,返回 File 对象
111
+ * const file = generateJsonFile(data, 'user.json', { download: false });
88
112
  */
89
113
  export declare function generateJsonFile(data: any, filename: string, options?: {
90
114
  pretty?: boolean;
91
115
  spaces?: number;
116
+ download?: boolean;
92
117
  encoding?: string;
93
- }): void;
118
+ }): File | void;
package/types/index.d.ts CHANGED
@@ -4,16 +4,18 @@
4
4
  * 提供常用的文件处理功能:读取、下载、转换、生成、验证等
5
5
  * 支持 Tree Shaking,按需引入
6
6
  */
7
- export type { ReadFileType, CompressOptions, DownloadOptions, IFileInfo, IValidationResult, IFileValidationOptions } from './types';
7
+ export type { ReadFileType, CompressOptions, DownloadOptions, CopyOptions, IFileInfo, IValidationResult, IFileValidationOptions } from './types';
8
8
  export { readFile, readTxtFile, readCsvFile, readJsonFile } from './read';
9
9
  export { downloadFile, downloadBlob, downloadBase64, downloadMultiple } from './download';
10
10
  export { fileToBase64, base64ToBlob } from './convert';
11
+ export { copyToClipboard, pasteFromClipboard } from './copy';
11
12
  export { generateTxtFile, generateCsvFile, generateJsonFile } from './generate';
12
13
  export { checkFileType, checkFileSize, isImage, getImageDimensions, validateFile, formatFileSize } from './validators';
13
14
  export { getFileExtension, getFileNameWithoutExtension, getFileInfo } from './utils';
14
15
  import { readFile, readTxtFile, readCsvFile, readJsonFile } from './read';
15
16
  import { downloadFile, downloadBlob, downloadBase64, downloadMultiple } from './download';
16
17
  import { fileToBase64, base64ToBlob } from './convert';
18
+ import { copyToClipboard, pasteFromClipboard } from './copy';
17
19
  import { generateTxtFile, generateCsvFile, generateJsonFile } from './generate';
18
20
  import { checkFileType, checkFileSize, isImage, getImageDimensions, validateFile, formatFileSize } from './validators';
19
21
  import { getFileExtension, getFileNameWithoutExtension, getFileInfo } from './utils';
@@ -28,6 +30,8 @@ declare const _default: {
28
30
  downloadMultiple: typeof downloadMultiple;
29
31
  fileToBase64: typeof fileToBase64;
30
32
  base64ToBlob: typeof base64ToBlob;
33
+ copyToClipboard: typeof copyToClipboard;
34
+ pasteFromClipboard: typeof pasteFromClipboard;
31
35
  generateTxtFile: typeof generateTxtFile;
32
36
  generateCsvFile: typeof generateCsvFile;
33
37
  generateJsonFile: typeof generateJsonFile;
@@ -0,0 +1,165 @@
1
+ /**
2
+ * 文件读取类型定义
3
+ * - text: 读取为文本内容
4
+ * - arrayBuffer: 读取为 ArrayBuffer(二进制数据)
5
+ * - dataURL: 读取为 Data URL(Base64 编码)
6
+ * - binaryString: 读取为二进制字符串
7
+ */
8
+ export type ReadFileType = 'text' | 'arrayBuffer' | 'dataURL' | 'binaryString';
9
+ /**
10
+ * 图片压缩选项配置
11
+ */
12
+ export interface CompressOptions {
13
+ /**
14
+ * 压缩质量
15
+ * 范围:0.1 - 1.0
16
+ * 默认值:0.8(80% 质量)
17
+ * @example 0.5 表示 50% 质量
18
+ */
19
+ quality?: number;
20
+ /**
21
+ * 最大宽度(像素)
22
+ * 如果原图宽度超过此值,将等比例缩放
23
+ * 默认值:无限制
24
+ */
25
+ maxWidth?: number;
26
+ /**
27
+ * 最大高度(像素)
28
+ * 如果原图高度超过此值,将等比例缩放
29
+ * 默认值:无限制
30
+ */
31
+ maxHeight?: number;
32
+ /**
33
+ * 输出图片的 MIME 类型
34
+ * 默认值:'image/jpeg'
35
+ * 支持的值:'image/jpeg', 'image/png', 'image/webp' 等
36
+ */
37
+ mimeType?: string;
38
+ }
39
+ /**
40
+ * 下载选项配置
41
+ */
42
+ export interface DownloadOptions {
43
+ /**
44
+ * 目标文件名(包含扩展名)
45
+ * 如果不指定,将尝试从 URL 或 Content-Disposition 头中提取
46
+ */
47
+ filename?: string;
48
+ /**
49
+ * 是否在新窗口打开
50
+ * 默认值:false(直接下载)
51
+ */
52
+ newWindow?: boolean;
53
+ /**
54
+ * Fetch API 的 RequestInit 配置
55
+ * 可用于设置请求头、授权信息、请求方法等
56
+ * @example
57
+ * // 设置授权请求头
58
+ * fetchOptions: {
59
+ * headers: {
60
+ * 'Authorization': 'Bearer token123'
61
+ * }
62
+ * }
63
+ *
64
+ * @example
65
+ * // 设置 POST 请求和请求体
66
+ * fetchOptions: {
67
+ * method: 'POST',
68
+ * body: JSON.stringify({ id: 123 })
69
+ * }
70
+ */
71
+ fetchOptions?: RequestInit;
72
+ }
73
+ /**
74
+ * 文件生成选项配置
75
+ * 用于控制文件生成后的行为
76
+ */
77
+ export interface GenerateOptions {
78
+ /**
79
+ * 是否直接下载生成的文件
80
+ * 默认值:true(直接下载)
81
+ * 如果设置为 false,将返回 File 对象而不触发下载
82
+ */
83
+ download?: boolean;
84
+ /**
85
+ * 字符编码
86
+ * 默认值:'utf-8'
87
+ */
88
+ encoding?: string;
89
+ }
90
+ /**
91
+ * 文件信息接口
92
+ * 用于描述文件的基本属性
93
+ */
94
+ export interface IFileInfo {
95
+ /** 文件名 */
96
+ name: string;
97
+ /** 文件大小(字节) */
98
+ size: number;
99
+ /** MIME 类型 */
100
+ type: string;
101
+ /** 最后修改时间戳 */
102
+ lastModified: number;
103
+ }
104
+ /**
105
+ * 文件验证结果接口
106
+ * 用于返回文件验证的结果
107
+ */
108
+ export interface IValidationResult {
109
+ /** 是否通过验证 */
110
+ valid: boolean;
111
+ /** 错误信息列表 */
112
+ errors: string[];
113
+ }
114
+ /**
115
+ * 文件验证选项配置
116
+ * 用于指定文件验证的规则
117
+ */
118
+ export interface IFileValidationOptions {
119
+ /**
120
+ * 接受的文件类型数组
121
+ * 支持:MIME 类型('image/jpeg')、通配符('image/*')、扩展名('.jpg')
122
+ */
123
+ acceptTypes?: string[];
124
+ /**
125
+ * 最大文件大小(字节)
126
+ * @example 2 * 1024 * 1024 表示 2MB
127
+ */
128
+ maxSize?: number;
129
+ /**
130
+ * 是否必须为图片
131
+ * 默认值:false
132
+ */
133
+ mustBeImage?: boolean;
134
+ /**
135
+ * 最小宽度(像素,仅对图片有效)
136
+ */
137
+ minWidth?: number;
138
+ /**
139
+ * 最大宽度(像素,仅对图片有效)
140
+ */
141
+ maxWidth?: number;
142
+ /**
143
+ * 最小高度(像素,仅对图片有效)
144
+ */
145
+ minHeight?: number;
146
+ /**
147
+ * 最大高度(像素,仅对图片有效)
148
+ */
149
+ maxHeight?: number;
150
+ }
151
+ /**
152
+ * 复制选项配置
153
+ */
154
+ export interface CopyOptions {
155
+ /**
156
+ * 复制成功时的回调函数
157
+ * @param text - 被复制的文本
158
+ */
159
+ onSuccess?: (text: string) => void;
160
+ /**
161
+ * 复制失败时的回调函数
162
+ * @param error - 错误信息
163
+ */
164
+ onError?: (error: Error) => void;
165
+ }