```
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 currentMode = 'trial'; // 'trial' 或 'key'
|
||||||
let uploadedFiles = []; // 存储当前待上传的参考图
|
let uploadedFiles = []; // 存储当前待上传的参考图
|
||||||
let isSetterActive = false; // 是否激活了拍摄角度设置器模式
|
let isSetterActive = false; // 是否激活了拍摄角度设置器模式
|
||||||
|
let downloadFilenameSerial = 0;
|
||||||
|
|
||||||
function switchMode(mode) {
|
function switchMode(mode) {
|
||||||
currentMode = mode;
|
currentMode = mode;
|
||||||
@ -104,23 +105,92 @@ function switchMode(mode) {
|
|||||||
updateCostPreview(); // 切换模式时同步计费预览
|
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 {
|
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 blob = await response.blob();
|
||||||
|
const filename = buildImageFilename(url, blob.type);
|
||||||
const blobUrl = window.URL.createObjectURL(blob);
|
const blobUrl = window.URL.createObjectURL(blob);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = blobUrl;
|
a.href = blobUrl;
|
||||||
// 从 URL 提取文件名
|
|
||||||
const filename = url.split('/').pop().split('?')[0] || 'ai-vision-image.png';
|
|
||||||
a.download = filename;
|
a.download = filename;
|
||||||
document.body.appendChild(a);
|
document.body.appendChild(a);
|
||||||
a.click();
|
a.click();
|
||||||
document.body.removeChild(a);
|
document.body.removeChild(a);
|
||||||
window.URL.revokeObjectURL(blobUrl);
|
setTimeout(() => window.URL.revokeObjectURL(blobUrl), 1000);
|
||||||
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('下载失败:', e);
|
console.error('下载失败:', e);
|
||||||
showToast('下载失败,请尝试右键保存', 'error');
|
if (!silent) showToast('下载失败,请稍后重试', 'error');
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,11 +316,20 @@ async function init() {
|
|||||||
downloadAllBtn.onclick = async () => {
|
downloadAllBtn.onclick = async () => {
|
||||||
if (currentGeneratedUrls.length === 0) return;
|
if (currentGeneratedUrls.length === 0) return;
|
||||||
showToast(`正在准备下载 ${currentGeneratedUrls.length} 张作品...`, 'info');
|
showToast(`正在准备下载 ${currentGeneratedUrls.length} 张作品...`, 'info');
|
||||||
|
let successCount = 0;
|
||||||
for (const url of currentGeneratedUrls) {
|
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));
|
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