diff --git a/package.json b/package.json index 178b8fe..00c21dc 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "@codemirror/lang-vue": "^0.1.3", "@codemirror/theme-one-dark": "^6.1.2", "@fingerprintjs/fingerprintjs": "^4.6.2", + "@types/file-saver": "^2.0.7", "@visactor/vchart": "^1.11.0", "@visactor/vchart-arco-theme": "^1.11.0", "@vueuse/core": "^12.4.0", @@ -47,8 +48,10 @@ "axios": "^1.6.8", "codemirror": "^6.0.1", "driver.js": "^1.3.1", + "file-saver": "^2.0.5", "fingerprintjs2": "^2.1.4", "jsbarcode": "^3.11.6", + "jszip": "^3.10.1", "nprogress": "^0.2.0", "pinia": "^2.3.0", "pinia-plugin-persistedstate": "^3.2.1", diff --git a/src/components.d.ts b/src/components.d.ts index 2a34503..a904fc1 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -7,7 +7,6 @@ export {} declare module 'vue' { export interface GlobalComponents { - copy: typeof import('./components/s-layout-search/index copy.vue')['default'] 'Index copy': typeof import('./components/s-layout-search/index copy.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] diff --git a/src/hooks/useImageDownloader.ts b/src/hooks/useImageDownloader.ts index bc82662..9fc2a49 100644 --- a/src/hooks/useImageDownloader.ts +++ b/src/hooks/useImageDownloader.ts @@ -1,4 +1,6 @@ import { ref, Ref } from "vue"; +import JSZip from "jszip"; +import { saveAs } from "file-saver"; type DownloadStatus = "idle" | "loading" | "success" | "error"; @@ -56,35 +58,48 @@ export function useImageDownloader(): any { }; // 批量下载(并行) - const downloadImages = async (urls: string[], customFileNames?: string[]): Promise => { - downloadStatus.value = "loading"; - batchResults.value = []; + const downloadImages = async (urls: string[], customFileNames?: string[]): Promise => { + // 可选:更新状态(如果你用 Vue 的 ref) + // downloadStatus.value = "loading"; - const promises = urls.map(async (url, index) => { - try { - const response = await fetch(url); - if (!response.ok) throw new Error(`HTTP ${response.status}`); - const blob = await response.blob(); - const link = document.createElement("a"); - link.href = URL.createObjectURL(blob); - link.download = customFileNames?.[index] || extractFileName(url); - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - URL.revokeObjectURL(link.href); - return { url, status: "success" } as DownloadResult; - } catch (error) { - console.error(`Failed to download ${url}:`, error); - return { url, status: "error", message: (error as Error).message } as DownloadResult; - } - }); + const zip = new JSZip(); + const errors: string[] = []; - const results = await Promise.all(promises); - batchResults.value = results; - downloadStatus.value = "success"; - return results; + // 并发获取所有图片(可加并发控制,但 30 个一般没问题) + await Promise.all( + urls.map(async (url, index) => { + try { + const res = await fetch(url); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + const blob = await res.blob(); + let fileName = customFileNames?.[index] || extractFileName(url); + // 防止文件名重复(简单处理) + if (zip.file(fileName)) { + const ext = fileName.includes(".") ? fileName.split(".").pop() : ""; + const name = fileName.replace(new RegExp(`\\.${ext}$`), ""); + fileName = `${name}_${Date.now()}.${ext || "jpg"}`; + } + zip.file(fileName, blob); + } catch (err) { + console.error("Download failed:", url, err); + errors.push(url); + } + }) + ); + + // 生成 ZIP Blob + const zipBlob = await zip.generateAsync({ type: "blob" }); + + // 触发下载 —— 这是安全的、用户可见的一次性下载 + saveAs(zipBlob, "车辆二维码.zip"); + + // 可选:提示错误 + if (errors.length > 0) { + alert(`成功打包 ${urls.length - errors.length} 张图片。\n失败:${errors.length} 张`); + } + + // downloadStatus.value = "success"; }; - return { downloadStatus, batchResults, diff --git a/src/views/QRManagement/carQRManagement/carQRManagement.vue b/src/views/QRManagement/carQRManagement/carQRManagement.vue index b927cdc..ffe0475 100644 --- a/src/views/QRManagement/carQRManagement/carQRManagement.vue +++ b/src/views/QRManagement/carQRManagement/carQRManagement.vue @@ -132,7 +132,8 @@ const handleDownloadImage = async (record: any) => { // 批量下载 const batchDownload = async () => { const urls = select_list.value.map(item => item.bikeQr); - const names = select_list.value.map(item => `电池二维码-${item.bikeQrCode}.png`); + const names = select_list.value.map(item => `车辆二维码-${item.bikeQrCode}.png`); + console.log(urls, names); await downloadImages(urls, names); }; diff --git a/src/views/system/imageManagement/imageManagement.vue b/src/views/system/imageManagement/imageManagement.vue index 87a4934..711757e 100644 --- a/src/views/system/imageManagement/imageManagement.vue +++ b/src/views/system/imageManagement/imageManagement.vue @@ -173,8 +173,11 @@ const handleOk = async () => { // 复制图片链接到剪贴板 async function copyFileUrl(record: any) { const { fileUrl } = record; - const res: any = await navigator.clipboard.writeText(fileUrl); - arcoMessage("success", "复制成功"); + console.log(navigator.clipboard); + if (navigator.clipboard) { + const res: any = await navigator.clipboard.writeText(fileUrl); + arcoMessage("success", "复制成功"); + } } async function getImageList() { diff --git a/src/views/urban/siteManagement/siteManagement.vue b/src/views/urban/siteManagement/siteManagement.vue index ab464f7..633eb1a 100644 --- a/src/views/urban/siteManagement/siteManagement.vue +++ b/src/views/urban/siteManagement/siteManagement.vue @@ -124,9 +124,9 @@ const handleUpdate = (record: any) => { }; const handleDelete = async (record: any) => { - const { regionId } = record; + const { siteId } = record; try { - const res: any = await deleteSiteAPI(regionId); + const res: any = await deleteSiteAPI(siteId); if (res.code === 200) { arcoMessage("success", "删除成功"); getRegionPage();