```
feat(js): 改进图片下载功能并添加文件名处理 - 添加downloadFilenameSerial变量用于生成唯一的下载文件名序列号 - 实现getImageExtension函数来根据content-type获取正确的文件扩展名 - 创建buildImageFilename函数来构建安全的文件名,支持从URL解析原始名称 - 实现buildDownloadProxyUrl函数来构建下载代理URL - 重构downloadImage函数,增加错误处理和进度提示 - 添加静默下载选项,避免批量下载时重复显示提示 - 改进批量下载逻辑,统计成功和失败数量并显示相应提示 - 增强文件名安全性,过滤特殊字符和路径遍历字符 ```
This commit is contained in:
parent
3df902963d
commit
bdbec1a310
@ -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');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user