From 650673e2db8bb5b91994ba6a52e0c17034f97e9b Mon Sep 17 00:00:00 2001 From: xlsea Date: Thu, 26 Jun 2025 14:14:02 +0800 Subject: [PATCH] =?UTF-8?q?feat(hooks):=20=E9=87=8D=E6=9E=84=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=E6=96=B9=E6=B3=95=EF=BC=8C=E6=94=AF=E6=8C=81=E6=B5=81?= =?UTF-8?q?=E5=BC=8F=E4=B8=8B=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 4 +- pnpm-lock.yaml | 124 ++++++++++++--------- src/hooks/business/download.ts | 196 ++++++++++++++++++++------------- 3 files changed, 192 insertions(+), 132 deletions(-) diff --git a/package.json b/package.json index 055dc519..6dbfa906 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "@sa/materials": "workspace:*", "@sa/tinymce": "workspace:*", "@sa/utils": "workspace:*", + "@types/streamsaver": "^2.0.5", "@vueuse/core": "13.4.0", "clipboard": "2.0.11", "dayjs": "1.11.13", @@ -69,6 +70,7 @@ "naive-ui": "2.42.0", "nprogress": "0.2.0", "pinia": "3.0.3", + "streamsaver": "^2.0.6", "tailwind-merge": "3.3.1", "vue": "3.5.17", "vue-advanced-cropper": "^2.8.9", @@ -85,7 +87,7 @@ "@types/node": "24.0.4", "@types/nprogress": "0.2.3", "@unocss/eslint-config": "66.2.3", - "@unocss/preset-icons": "66.3.0", + "@unocss/preset-icons": "66.2.3", "@unocss/preset-uno": "66.2.3", "@unocss/transformer-directives": "66.2.3", "@unocss/transformer-variant-group": "66.2.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ce5191e5..aafd82c7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ importers: '@sa/utils': specifier: workspace:* version: link:packages/utils + '@types/streamsaver': + specifier: ^2.0.5 + version: 2.0.5 '@vueuse/core': specifier: 13.4.0 version: 13.4.0(vue@3.5.17(typescript@5.8.3)) @@ -57,7 +60,7 @@ importers: specifier: 2.2.3 version: 2.2.3 monaco-editor: - specifier: ^0.52.0 + specifier: ^0.52.2 version: 0.52.2 naive-ui: specifier: 2.42.0 @@ -68,6 +71,9 @@ importers: pinia: specifier: 3.0.3 version: 3.0.3(typescript@5.8.3)(vue@3.5.17(typescript@5.8.3)) + streamsaver: + specifier: ^2.0.6 + version: 2.0.6 tailwind-merge: specifier: 3.3.1 version: 3.3.1 @@ -103,8 +109,8 @@ importers: specifier: 1.6.1 version: 1.6.1(@unocss/eslint-config@66.2.3(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-plugin-vue@10.2.0(eslint@9.29.0(jiti@2.4.2))(vue-eslint-parser@10.1.4(eslint@9.29.0(jiti@2.4.2))))(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)(vue-eslint-parser@10.1.4(eslint@9.29.0(jiti@2.4.2))) '@types/node': - specifier: 24.0.3 - version: 24.0.3 + specifier: 24.0.4 + version: 24.0.4 '@types/nprogress': specifier: 0.2.3 version: 0.2.3 @@ -125,13 +131,13 @@ importers: version: 66.2.3 '@unocss/vite': specifier: 66.2.3 - version: 66.2.3(vite@7.0.0(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3)) + version: 66.2.3(vite@7.0.0(@types/node@24.0.4)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3)) '@vitejs/plugin-vue': specifier: 6.0.0 - version: 6.0.0(vite@7.0.0(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3)) + version: 6.0.0(vite@7.0.0(@types/node@24.0.4)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3)) '@vitejs/plugin-vue-jsx': specifier: 5.0.0 - version: 5.0.0(vite@7.0.0(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3)) + version: 5.0.0(vite@7.0.0(@types/node@24.0.4)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3)) consola: specifier: 3.4.2 version: 3.4.2 @@ -164,22 +170,22 @@ importers: version: 28.7.0(@babel/parser@7.27.5)(vue@3.5.17(typescript@5.8.3)) vite: specifier: 7.0.0 - version: 7.0.0(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0) + version: 7.0.0(@types/node@24.0.4)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0) vite-plugin-monaco-editor: specifier: ^1.1.0 version: 1.1.0(monaco-editor@0.52.2) vite-plugin-progress: specifier: 0.0.7 - version: 0.0.7(vite@7.0.0(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)) + version: 0.0.7(vite@7.0.0(@types/node@24.0.4)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)) vite-plugin-static-copy: - specifier: ^3.0.0 - version: 3.0.2(vite@7.0.0(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)) + specifier: ^3.0.2 + version: 3.0.2(vite@7.0.0(@types/node@24.0.4)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)) vite-plugin-svg-icons: specifier: 2.0.1 - version: 2.0.1(vite@7.0.0(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)) + version: 2.0.1(vite@7.0.0(@types/node@24.0.4)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)) vite-plugin-vue-devtools: specifier: 7.7.7 - version: 7.7.7(rollup@4.44.0)(vite@7.0.0(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3)) + version: 7.7.7(rollup@4.44.0)(vite@7.0.0(@types/node@24.0.4)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3)) vue-eslint-parser: specifier: 10.1.4 version: 10.1.4(eslint@9.29.0(jiti@2.4.2)) @@ -291,12 +297,12 @@ importers: packages/tinymce: dependencies: tinymce: - specifier: 7.8.0 - version: 7.8.0 + specifier: 7.9.1 + version: 7.9.1 devDependencies: '@tinymce/tinymce-vue': - specifier: 6.1.0 - version: 6.1.0(tinymce@7.8.0)(vue@3.5.17(typescript@5.8.3)) + specifier: 6.2.0 + version: 6.2.0(tinymce@7.9.1)(vue@3.5.17(typescript@5.8.3)) packages/uno-preset: {} @@ -1112,8 +1118,8 @@ packages: vue-eslint-parser: optional: true - '@tinymce/tinymce-vue@6.1.0': - resolution: {integrity: sha512-7JdaKMOaohuFWpjKwRmaZJbT/eNVUUYHG93R7+lUf7SUN+hSqd2spbuqZcki+tG9kaSAGd2ZmvJIsmzWDNAzpw==} + '@tinymce/tinymce-vue@6.2.0': + resolution: {integrity: sha512-HiXKB+M3mJnWO6/8kY0HsP255+8zLZw5JMqHKVUvsXvzYyHW+splXXwYDYOkCYqf39R5nBqQaK2l2WL9rz3y5w==} peerDependencies: tinymce: ^7.0.0 || ^6.0.0 || ^5.5.1 vue: ^3.0.0 @@ -1158,8 +1164,8 @@ packages: '@types/node@10.17.60': resolution: {integrity: sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==} - '@types/node@24.0.3': - resolution: {integrity: sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg==} + '@types/node@24.0.4': + resolution: {integrity: sha512-ulyqAkrhnuNq9pB76DRBTkcS6YsmDALy6Ua63V8OhrOBgbcYt6IOdzpw5P1+dyRIyMerzLkeYWBeOXPpA9GMAA==} '@types/nprogress@0.2.3': resolution: {integrity: sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA==} @@ -1170,6 +1176,9 @@ packages: '@types/sortablejs@1.15.8': resolution: {integrity: sha512-b79830lW+RZfwaztgs1aVPgbasJ8e7AXtZYHTELNXZPsERt4ymJdjV4OccDbHQAvHrCcFpbF78jkm0R6h/pZVg==} + '@types/streamsaver@2.0.5': + resolution: {integrity: sha512-93o0zjV8swEhR2YI57h/2ytbJF8bJh7sI9GNB02TLJHdM4fWDxZuChwfWhyD8vt2ub4kw4rsfZ0C0yAUX+3gcg==} + '@types/svgo@2.6.4': resolution: {integrity: sha512-l4cmyPEckf8moNYHdJ+4wkHvFxjyW6ulm9l4YGaOxeyBWPhBOT0gvni1InpFPdzx1dKf/2s62qGITwxNWnPQng==} @@ -3745,6 +3754,9 @@ packages: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} + streamsaver@2.0.6: + resolution: {integrity: sha512-LK4e7TfCV8HzuM0PKXuVUfKyCB1FtT9L0EGxsFk5Up8njj0bXK8pJM9+Wq2Nya7/jslmCQwRK39LFm55h7NBTw==} + strict-uri-encode@1.1.0: resolution: {integrity: sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==} engines: {node: '>=0.10.0'} @@ -3851,8 +3863,8 @@ packages: resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} engines: {node: '>=12.0.0'} - tinymce@7.8.0: - resolution: {integrity: sha512-MUER5MWV9mkOB4expgbWknh/C5ZJvOXQlMVSx4tJxTuYtcUCDB6bMZ34fWNOIc8LvrnXmGHGj0eGQuxjQyRgrA==} + tinymce@7.9.1: + resolution: {integrity: sha512-zaOHwmiP1EqTeLRXAvVriDb00JYnfEjWGPdKEuac7MiZJ5aiDMZ4Unc98Gmajn+PBljOmO1GKV6G0KwWn3+k8A==} to-object-path@0.3.0: resolution: {integrity: sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==} @@ -4994,11 +5006,11 @@ snapshots: - '@types/eslint' - supports-color - '@tinymce/tinymce-vue@6.1.0(tinymce@7.8.0)(vue@3.5.17(typescript@5.8.3))': + '@tinymce/tinymce-vue@6.2.0(tinymce@7.9.1)(vue@3.5.17(typescript@5.8.3))': dependencies: vue: 3.5.17(typescript@5.8.3) optionalDependencies: - tinymce: 7.8.0 + tinymce: 7.9.1 '@trysound/sax@0.2.0': {} @@ -5033,7 +5045,7 @@ snapshots: '@types/node@10.17.60': {} - '@types/node@24.0.3': + '@types/node@24.0.4': dependencies: undici-types: 7.8.0 @@ -5043,9 +5055,11 @@ snapshots: '@types/sortablejs@1.15.8': {} + '@types/streamsaver@2.0.5': {} + '@types/svgo@2.6.4': dependencies: - '@types/node': 24.0.3 + '@types/node': 24.0.4 '@types/unist@3.0.3': {} @@ -5263,7 +5277,7 @@ snapshots: dependencies: '@unocss/core': 66.2.3 - '@unocss/vite@66.2.3(vite@7.0.0(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3))': + '@unocss/vite@66.2.3(vite@7.0.0(@types/node@24.0.4)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3))': dependencies: '@ampproject/remapping': 2.3.0 '@unocss/config': 66.2.3 @@ -5274,7 +5288,7 @@ snapshots: pathe: 2.0.3 tinyglobby: 0.2.14 unplugin-utils: 0.2.4 - vite: 7.0.0(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0) + vite: 7.0.0(@types/node@24.0.4)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0) transitivePeerDependencies: - vue @@ -5337,21 +5351,21 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.9.2': optional: true - '@vitejs/plugin-vue-jsx@5.0.0(vite@7.0.0(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3))': + '@vitejs/plugin-vue-jsx@5.0.0(vite@7.0.0(@types/node@24.0.4)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3))': dependencies: '@babel/core': 7.27.4 '@babel/plugin-transform-typescript': 7.27.1(@babel/core@7.27.4) '@rolldown/pluginutils': 1.0.0-beta.19 '@vue/babel-plugin-jsx': 1.4.0(@babel/core@7.27.4) - vite: 7.0.0(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0) + vite: 7.0.0(@types/node@24.0.4)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0) vue: 3.5.17(typescript@5.8.3) transitivePeerDependencies: - supports-color - '@vitejs/plugin-vue@6.0.0(vite@7.0.0(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3))': + '@vitejs/plugin-vue@6.0.0(vite@7.0.0(@types/node@24.0.4)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3))': dependencies: '@rolldown/pluginutils': 1.0.0-beta.19 - vite: 7.0.0(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0) + vite: 7.0.0(@types/node@24.0.4)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0) vue: 3.5.17(typescript@5.8.3) '@volar/language-core@2.4.15': @@ -5436,14 +5450,14 @@ snapshots: dependencies: '@vue/devtools-kit': 7.7.7 - '@vue/devtools-core@7.7.7(vite@7.0.0(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3))': + '@vue/devtools-core@7.7.7(vite@7.0.0(@types/node@24.0.4)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3))': dependencies: '@vue/devtools-kit': 7.7.7 '@vue/devtools-shared': 7.7.7 mitt: 3.0.1 nanoid: 5.1.5 pathe: 2.0.3 - vite-hot-client: 2.0.4(vite@7.0.0(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)) + vite-hot-client: 2.0.4(vite@7.0.0(@types/node@24.0.4)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)) vue: 3.5.17(typescript@5.8.3) transitivePeerDependencies: - vite @@ -7996,6 +8010,8 @@ snapshots: es-errors: 1.3.0 internal-slot: 1.1.0 + streamsaver@2.0.6: {} + strict-uri-encode@1.1.0: {} string-width@4.2.3: @@ -8122,7 +8138,7 @@ snapshots: fdir: 6.4.6(picomatch@4.0.2) picomatch: 4.0.2 - tinymce@7.8.0: {} + tinymce@7.9.1: {} to-object-path@0.3.0: dependencies: @@ -8366,11 +8382,11 @@ snapshots: evtd: 0.2.4 vue: 3.5.17(typescript@5.8.3) - vite-hot-client@2.0.4(vite@7.0.0(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)): + vite-hot-client@2.0.4(vite@7.0.0(@types/node@24.0.4)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)): dependencies: - vite: 7.0.0(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0) + vite: 7.0.0(@types/node@24.0.4)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0) - vite-plugin-inspect@0.8.9(rollup@4.44.0)(vite@7.0.0(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)): + vite-plugin-inspect@0.8.9(rollup@4.44.0)(vite@7.0.0(@types/node@24.0.4)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)): dependencies: '@antfu/utils': 0.7.10 '@rollup/pluginutils': 5.2.0(rollup@4.44.0) @@ -8381,7 +8397,7 @@ snapshots: perfect-debounce: 1.0.0 picocolors: 1.1.1 sirv: 3.0.1 - vite: 7.0.0(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0) + vite: 7.0.0(@types/node@24.0.4)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0) transitivePeerDependencies: - rollup - supports-color @@ -8390,23 +8406,23 @@ snapshots: dependencies: monaco-editor: 0.52.2 - vite-plugin-progress@0.0.7(vite@7.0.0(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)): + vite-plugin-progress@0.0.7(vite@7.0.0(@types/node@24.0.4)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)): dependencies: picocolors: 1.1.1 progress: 2.0.3 rd: 2.0.1 - vite: 7.0.0(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0) + vite: 7.0.0(@types/node@24.0.4)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0) - vite-plugin-static-copy@3.0.2(vite@7.0.0(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)): + vite-plugin-static-copy@3.0.2(vite@7.0.0(@types/node@24.0.4)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)): dependencies: chokidar: 3.6.0 fs-extra: 11.3.0 p-map: 7.0.3 picocolors: 1.1.1 tinyglobby: 0.2.14 - vite: 7.0.0(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0) + vite: 7.0.0(@types/node@24.0.4)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0) - vite-plugin-svg-icons@2.0.1(vite@7.0.0(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)): + vite-plugin-svg-icons@2.0.1(vite@7.0.0(@types/node@24.0.4)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)): dependencies: '@types/svgo': 2.6.4 cors: 2.8.5 @@ -8416,27 +8432,27 @@ snapshots: pathe: 0.2.0 svg-baker: 1.7.0 svgo: 2.8.0 - vite: 7.0.0(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0) + vite: 7.0.0(@types/node@24.0.4)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0) transitivePeerDependencies: - supports-color - vite-plugin-vue-devtools@7.7.7(rollup@4.44.0)(vite@7.0.0(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3)): + vite-plugin-vue-devtools@7.7.7(rollup@4.44.0)(vite@7.0.0(@types/node@24.0.4)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3)): dependencies: - '@vue/devtools-core': 7.7.7(vite@7.0.0(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3)) + '@vue/devtools-core': 7.7.7(vite@7.0.0(@types/node@24.0.4)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0))(vue@3.5.17(typescript@5.8.3)) '@vue/devtools-kit': 7.7.7 '@vue/devtools-shared': 7.7.7 execa: 9.6.0 sirv: 3.0.1 - vite: 7.0.0(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0) - vite-plugin-inspect: 0.8.9(rollup@4.44.0)(vite@7.0.0(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)) - vite-plugin-vue-inspector: 5.3.2(vite@7.0.0(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)) + vite: 7.0.0(@types/node@24.0.4)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0) + vite-plugin-inspect: 0.8.9(rollup@4.44.0)(vite@7.0.0(@types/node@24.0.4)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)) + vite-plugin-vue-inspector: 5.3.2(vite@7.0.0(@types/node@24.0.4)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)) transitivePeerDependencies: - '@nuxt/kit' - rollup - supports-color - vue - vite-plugin-vue-inspector@5.3.2(vite@7.0.0(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)): + vite-plugin-vue-inspector@5.3.2(vite@7.0.0(@types/node@24.0.4)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0)): dependencies: '@babel/core': 7.27.4 '@babel/plugin-proposal-decorators': 7.27.1(@babel/core@7.27.4) @@ -8447,11 +8463,11 @@ snapshots: '@vue/compiler-dom': 3.5.17 kolorist: 1.8.0 magic-string: 0.30.17 - vite: 7.0.0(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0) + vite: 7.0.0(@types/node@24.0.4)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0) transitivePeerDependencies: - supports-color - vite@7.0.0(@types/node@24.0.3)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0): + vite@7.0.0(@types/node@24.0.4)(jiti@2.4.2)(sass@1.89.2)(tsx@4.20.3)(yaml@2.8.0): dependencies: esbuild: 0.25.5 fdir: 6.4.6(picomatch@4.0.2) @@ -8460,7 +8476,7 @@ snapshots: rollup: 4.44.0 tinyglobby: 0.2.14 optionalDependencies: - '@types/node': 24.0.3 + '@types/node': 24.0.4 fsevents: 2.3.3 jiti: 2.4.2 sass: 1.89.2 diff --git a/src/hooks/business/download.ts b/src/hooks/business/download.ts index cc20ff46..210e1253 100644 --- a/src/hooks/business/download.ts +++ b/src/hooks/business/download.ts @@ -1,109 +1,151 @@ +import StreamSaver from 'streamsaver'; import { errorCodeRecord } from '@/constants/common'; import { localStg } from '@/utils/storage'; import { getServiceBaseURL } from '@/utils/service'; import { transformToURLSearchParams } from '@/utils/common'; +interface RequestConfig { + method: 'GET' | 'POST'; + url: string; + params?: Record; + filename?: string; + contentType?: string; +} + export function useDownload() { const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y'; const { baseURL } = getServiceBaseURL(import.meta.env, isHttpProxy); - function downloadByData(data: BlobPart, filename: string, type: string = 'application/octet-stream') { - const blobData = [data]; - const blob = new Blob(blobData, { type }); + /** 获取通用请求头 */ + const getCommonHeaders = (contentType = 'application/octet-stream') => ({ + Authorization: `Bearer ${localStg.get('token')}`, + Clientid: import.meta.env.VITE_APP_CLIENT_ID!, + 'Content-Type': contentType + }); + /** 通用下载方法 */ + function downloadByData(data: BlobPart, filename: string, type = 'application/octet-stream') { + const blob = new Blob([data], { type }); const blobURL = window.URL.createObjectURL(blob); - const tempLink = document.createElement('a'); - tempLink.style.display = 'none'; - tempLink.href = blobURL; - tempLink.setAttribute('download', filename); + + const tempLink = Object.assign(document.createElement('a'), { + style: { display: 'none' }, + href: blobURL, + download: filename + }); + if (typeof tempLink.download === 'undefined') { tempLink.setAttribute('target', '_blank'); } + document.body.appendChild(tempLink); tempLink.click(); document.body.removeChild(tempLink); window.URL.revokeObjectURL(blobURL); } - function download(url: string, params: Record, fileName: string) { - window.$loading?.startLoading('正在下载数据,请稍候...'); - const token = localStg.get('token'); - const clientId = import.meta.env.VITE_APP_CLIENT_ID; - const now = Date.now(); - const searchParams = transformToURLSearchParams(params); + /** 流式下载 */ + async function downloadByStream( + readableStream: ReadableStream, + filename: string, + contentLength?: number + ): Promise { + window.$loading?.endLoading(); + const fileStream = StreamSaver.createWriteStream(filename, { size: contentLength }); - fetch(`${baseURL}${url}?t=${now}`, { - method: 'post', - body: searchParams, - headers: { - Authorization: `Bearer ${token}`, - Clientid: clientId!, - 'Content-Type': 'application/x-www-form-urlencoded' - } - }) - .then(async response => { - if (response.headers.get('Content-Type')?.includes('application/json')) { - const res = await response.json(); - const code = res.code as CommonType.ErrorCode; - throw new Error(errorCodeRecord[code] || res.msg || errorCodeRecord.default); - } - return response.blob(); - }) - .then(data => downloadByData(data, fileName, 'application/zip')) - .catch(err => window.$message?.error(err.message)) - .finally(() => window.$loading?.endLoading()); + if (window.WritableStream && readableStream?.pipeTo) { + await readableStream.pipeTo(fileStream); + window.$message?.success('下载完成'); + return; + } + + // 降级处理 + const writer = fileStream.getWriter(); + const reader = readableStream.getReader(); + + const pump = async (): Promise => { + const { done, value } = await reader.read(); + if (done) return writer.close(); + await writer.write(value); + return pump(); + }; + + await pump(); } - function oss(ossId: CommonType.IdType) { - window.$loading?.startLoading('正在下载数据,请稍候...'); - const token = localStg.get('token'); - const clientId = import.meta.env.VITE_APP_CLIENT_ID; - const url = `/resource/oss/download/${ossId}`; - const now = Date.now(); - let fileName = String(`${ossId}-${now}`); - fetch(`${baseURL}${url}?t=${now}`, { - method: 'get', - headers: { - Authorization: `Bearer ${token}`, - Clientid: clientId!, - 'Content-Type': 'application/octet-stream' - } - }) - .then(async response => { - fileName = String(response.headers.get('Download-Filename')); - return response.blob(); - }) - .then(data => downloadByData(data, fileName)) - .catch(err => window.$message?.error(err.message)) - .finally(() => window.$loading?.endLoading()); + /** 处理响应 */ + async function handleResponse(response: Response) { + if (response.headers.get('Content-Type')?.includes('application/json')) { + const res = await response.json(); + const code = res.code as CommonType.ErrorCode; + throw new Error(errorCodeRecord[code] || res.msg || errorCodeRecord.default); + } } - function zip(url: string, fileName: string) { + /** 核心下载逻辑 */ + async function executeDownload(config: RequestConfig): Promise { + const { method, url, params, filename, contentType } = config; + const timestamp = Date.now(); + const fullUrl = `${baseURL}${url}${url.includes('?') ? '&' : '?'}t=${timestamp}`; + window.$loading?.startLoading('正在下载数据,请稍候...'); - const token = localStg.get('token'); - const clientId = import.meta.env.VITE_APP_CLIENT_ID; - const now = Date.now(); - fetch(`${baseURL}${url}${url.includes('?') ? '&' : '?'}t=${now}`, { - method: 'get', - headers: { - Authorization: `Bearer ${token}`, - Clientid: clientId!, - 'Content-Type': 'application/octet-stream' + + try { + const requestOptions: RequestInit = { + method, + headers: getCommonHeaders(contentType) + }; + + if (method === 'POST' && params) { + requestOptions.body = transformToURLSearchParams(params); + requestOptions.headers = { + ...requestOptions.headers, + 'Content-Type': 'application/x-www-form-urlencoded' + }; } - }) - .then(async response => { - if (response.headers.get('Content-Type')?.includes('application/json')) { - const res = await response.json(); - const code = res.code as CommonType.ErrorCode; - throw new Error(errorCodeRecord[code] || res.msg || errorCodeRecord.default); - } - return response.blob(); - }) - .then(data => downloadByData(data, fileName, 'application/zip')) - .catch(err => window.$message?.error(err.message)) - .finally(() => window.$loading?.endLoading()); + + const response = await fetch(fullUrl, requestOptions); + + await handleResponse(response); + + const finalFilename = filename || response.headers.get('Download-Filename') || `download-${timestamp}`; + + if (response.body) { + const contentLength = Number(response.headers.get('Content-Length')); + await downloadByStream(response.body, finalFilename, contentLength); + return; + } + + const responseContentType = response.headers.get('Content-Type'); + const mainType = responseContentType?.split(';')[0]?.trim() || 'application/octet-stream'; + downloadByData(await response.blob(), finalFilename, mainType); + } catch (error: any) { + window.$message?.error(error.message); + } finally { + window.$loading?.endLoading(); + } } + /** 公共下载接口 */ + const download = (url: string, params: Record, filename: string) => + executeDownload({ method: 'POST', url, params, filename }); + + /** OSS文件下载 */ + const oss = (ossId: CommonType.IdType) => + executeDownload({ + method: 'GET', + url: `/resource/oss/download/${ossId}` + }); + + /** ZIP文件下载 */ + const zip = (url: string, filename: string) => + executeDownload({ + method: 'GET', + url, + filename, + contentType: 'application/octet-stream' + }); + return { oss, zip,