From bdbec1a3107334b00a91de5326c74ef8a5598318 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AC=E5=8F=B8git?= <240241002@qq.com> Date: Sun, 26 Apr 2026 10:21:43 +0800 Subject: [PATCH] =?UTF-8?q?```=20feat(js):=20=E6=94=B9=E8=BF=9B=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E4=B8=8B=E8=BD=BD=E5=8A=9F=E8=83=BD=E5=B9=B6=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E6=96=87=E4=BB=B6=E5=90=8D=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加downloadFilenameSerial变量用于生成唯一的下载文件名序列号 - 实现getImageExtension函数来根据content-type获取正确的文件扩展名 - 创建buildImageFilename函数来构建安全的文件名,支持从URL解析原始名称 - 实现buildDownloadProxyUrl函数来构建下载代理URL - 重构downloadImage函数,增加错误处理和进度提示 - 添加静默下载选项,避免批量下载时重复显示提示 - 改进批量下载逻辑,统计成功和失败数量并显示相应提示 - 增强文件名安全性,过滤特殊字符和路径遍历字符 ``` --- static/js/main.js | 93 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 86 insertions(+), 7 deletions(-) diff --git a/static/js/main.js b/static/js/main.js index bd7641b..5297ee5 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -78,6 +78,7 @@ let currentGeneratedUrls = []; let currentMode = 'trial'; // 'trial' 或 'key' let uploadedFiles = []; // 存储当前待上传的参考图 let isSetterActive = false; // 是否激活了拍摄角度设置器模式 +let downloadFilenameSerial = 0; function switchMode(mode) { currentMode = mode; @@ -104,23 +105,92 @@ function switchMode(mode) { updateCostPreview(); // 切换模式时同步计费预览 } -async function downloadImage(url) { +function getImageExtension(contentType) { + const type = (contentType || '').split(';')[0].trim().toLowerCase(); + const extensionMap = { + 'image/jpeg': 'jpg', + 'image/jpg': 'jpg', + 'image/png': 'png', + 'image/webp': 'webp', + 'image/gif': 'gif', + 'image/bmp': 'bmp', + 'image/avif': 'avif' + }; + return extensionMap[type] || 'png'; +} + +function buildImageFilename(url, contentType = '') { + downloadFilenameSerial += 1; + const fallbackName = `ai-vision-image-${Date.now()}-${downloadFilenameSerial}.${getImageExtension(contentType)}`; + try { - const response = await fetch(url); + const parsed = new URL(url, window.location.href); + const rawName = decodeURIComponent(parsed.pathname.split('/').filter(Boolean).pop() || ''); + const safeName = rawName + .replace(/[\\/:*?"<>|]/g, '_') + .replace(/[^\x20-\x7E]/g, '_') + .replace(/\s+/g, '_'); + const extMatch = safeName.match(/\.(png|jpe?g|webp|gif|bmp|avif)$/i); + + if (!extMatch) return fallbackName; + + const ext = extMatch[0].toLowerCase(); + const baseName = safeName + .slice(0, safeName.length - ext.length) + .replace(/^\.+|\.+$/g, '') + .slice(0, 80) || 'ai-vision-image'; + + return `${baseName}${ext}`; + } catch (e) { + return fallbackName; + } +} + +function buildDownloadProxyUrl(url, filename) { + const params = new URLSearchParams(); + params.set('url', url); + params.set('filename', filename); + return `/api/download_proxy?${params.toString()}`; +} + +async function downloadImage(url, options = {}) { + if (!url) return false; + + const silent = options.silent === true; + + try { + if (!silent) showToast('正在准备下载...', 'info'); + + const filenameHint = buildImageFilename(url); + const response = await fetch(buildDownloadProxyUrl(url, filenameHint), { + credentials: 'same-origin' + }); + + if (!response.ok) { + let message = `HTTP ${response.status}`; + const responseType = response.headers.get('Content-Type') || ''; + if (responseType.includes('application/json')) { + const data = await response.json(); + message = data.error || message; + } + throw new Error(message); + } + const blob = await response.blob(); + const filename = buildImageFilename(url, blob.type); const blobUrl = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = blobUrl; - // 从 URL 提取文件名 - const filename = url.split('/').pop().split('?')[0] || 'ai-vision-image.png'; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); - window.URL.revokeObjectURL(blobUrl); + setTimeout(() => window.URL.revokeObjectURL(blobUrl), 1000); + return true; } catch (e) { console.error('下载失败:', e); - showToast('下载失败,请尝试右键保存', 'error'); + if (!silent) showToast('下载失败,请稍后重试', 'error'); + return false; } } @@ -246,11 +316,20 @@ async function init() { downloadAllBtn.onclick = async () => { if (currentGeneratedUrls.length === 0) return; showToast(`正在准备下载 ${currentGeneratedUrls.length} 张作品...`, 'info'); + let successCount = 0; for (const url of currentGeneratedUrls) { - await downloadImage(url); + const ok = await downloadImage(url, { silent: true }); + if (ok) successCount += 1; // 稍微延迟一下,防止浏览器拦截 await new Promise(r => setTimeout(r, 300)); } + + const failedCount = currentGeneratedUrls.length - successCount; + if (failedCount === 0) { + showToast('下载已开始,请留意浏览器下载列表', 'success'); + } else { + showToast(`${failedCount} 张下载失败,请稍后重试`, 'error'); + } }; }