feat(js): 改进图片下载功能并添加文件名处理

- 添加downloadFilenameSerial变量用于生成唯一的下载文件名序列号
- 实现getImageExtension函数来根据content-type获取正确的文件扩展名
- 创建buildImageFilename函数来构建安全的文件名,支持从URL解析原始名称
- 实现buildDownloadProxyUrl函数来构建下载代理URL
- 重构downloadImage函数,增加错误处理和进度提示
- 添加静默下载选项,避免批量下载时重复显示提示
- 改进批量下载逻辑,统计成功和失败数量并显示相应提示
- 增强文件名安全性,过滤特殊字符和路径遍历字符
```
This commit is contained in:
公司git 2026-04-26 10:21:43 +08:00
parent 3df902963d
commit bdbec1a310

View File

@ -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');
}
};
}