优化文件加载速度,采用懒加载方法

This commit is contained in:
2026-02-06 13:38:51 +06:00
parent d7cfb044d5
commit e3f6ddf851
5 changed files with 392 additions and 71 deletions

View File

@@ -401,7 +401,7 @@ function setupOtherEventListeners() {
}
// ============ 加载动画控制 ============
function showLoading(text = '正在分析文件...', subtext = '生成缩略图中,请稍候') {
function showLoading(text = '正在分析文件...', subtext = '') {
if (elements.loadingOverlay) {
const textEl = elements.loadingOverlay.querySelector('.loading-text');
const subtextEl = elements.loadingOverlay.querySelector('.loading-subtext');
@@ -426,8 +426,8 @@ async function handleFilePaths(paths) {
console.log('处理文件:', paths);
// 显示加载动画
showLoading(`正在分析 ${paths.length} 个文件...`, '生成缩略图中,请稍候');
// 显示加载动画(不再提示生成缩略图)
showLoading(`正在分析 ${paths.length} 个文件...`);
try {
console.log('调用 analyze_files...');
@@ -442,6 +442,10 @@ async function handleFilePaths(paths) {
groupFilesByType();
renderFileTypeCards();
switchPage('config-page');
// 异步懒加载编码信息和图片(不阻塞 UI
lazyLoadCodecInfo();
lazyLoadImages();
} catch (error) {
console.error('分析文件失败:', error);
alert('分析文件失败: ' + (error.message || error));
@@ -671,12 +675,20 @@ function renderPreviews(files, type) {
};
return files.map(file => {
if (file.thumbnail) {
return `<div class="preview-item"><img src="${file.thumbnail}" alt="${file.name}" loading="lazy"></div>`;
// 对于图片类型,显示占位符,然后异步加载
if (type === 'image' && file.path) {
// 如果已经有缓存的图片数据,直接使用
if (file.imageData) {
return `<div class="preview-item" data-file-id="${file.id}"><img src="${file.imageData}" alt="${file.name}" loading="lazy"></div>`;
}
// 否则显示占位符,稍后异步加载
const icon = previewIcons.image;
return `<div class="preview-item image" data-file-id="${file.id}" data-loading="true">${icon}</div>`;
}
// 根据类型显示不同的 SVG 图标
// 视频和音频:只显示图标,不生成缩略图
const icon = previewIcons[type] || previewIcons.image;
return `<div class="preview-item ${type}">${icon}</div>`;
return `<div class="preview-item ${type}" data-file-id="${file.id}">${icon}</div>`;
}).join('');
}
@@ -1162,49 +1174,29 @@ function openFilesModal(fileType) {
image: `<svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.5"><rect width="18" height="18" x="3" y="3" rx="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></svg>`,
};
// 生成编码参数显示文本
function getCodecInfoText(file) {
if (!file.codec_info) {
return '-';
}
const info = file.codec_info;
const parts = [];
if (fileType === 'video') {
// 视频文件显示:分辨率 + 视频编码 + 帧率
if (info.resolution) parts.push(info.resolution);
if (info.video_codec) parts.push(info.video_codec);
if (info.frame_rate) parts.push(info.frame_rate);
if (info.duration) parts.push(info.duration);
} else if (fileType === 'audio') {
// 音频文件显示:音频编码 + 采样率 + 声道
if (info.audio_codec) parts.push(info.audio_codec);
if (info.sample_rate) parts.push(info.sample_rate);
if (info.channels) parts.push(info.channels);
if (info.duration) parts.push(info.duration);
} else {
// 图片等其他类型
if (info.resolution) parts.push(info.resolution);
}
return parts.length > 0 ? parts.join(' · ') : '-';
}
elements.filesList.innerHTML = files.map(file => {
let thumbnail = '';
if (file.thumbnail) {
thumbnail = `<img src="${file.thumbnail}" alt="${file.name}" loading="lazy">`;
// 对于图片类型,使用缓存的图片数据或显示占位符
if (fileType === 'image' && file.path) {
if (file.imageData) {
thumbnail = `<img src="${file.imageData}" alt="${file.name}" loading="lazy">`;
} else {
// 显示占位符
const icon = fileListIcons.image;
thumbnail = `<span class="file-list-icon ${fileType}">${icon}</span>`;
}
} else {
// 默认 SVG 图标
// 视频和音频:只显示图标,不生成缩略图
const icon = fileListIcons[fileType] || fileListIcons.image;
thumbnail = `<span class="file-list-icon ${fileType}">${icon}</span>`;
}
const codecInfo = getCodecInfoText(file);
// 编码信息显示(如果还没加载,显示加载中)
const codecInfo = file.codec_info ? getCodecInfoTextForFile(file) : '加载中...';
return `
<div class="file-list-item" data-type="${fileType}">
<div class="file-list-item" data-type="${fileType}" data-file-id="${file.id}">
<div class="file-col-thumb">
<div class="file-list-thumbnail">${thumbnail}</div>
</div>
@@ -1225,6 +1217,9 @@ function openFilesModal(fileType) {
}).join('');
elements.filesModal.classList.add('active');
// 打开弹窗后,异步加载还没有编码信息的文件
loadCodecInfoForFiles(files);
}
function closeFilesModal() {
@@ -1635,3 +1630,172 @@ async function loadTemplates() {
console.error('加载模板失败:', error);
}
}
// ============ 缩略图功能已移除 ============
// 视频和音频文件不再生成缩略图,只显示图标
// 图片文件直接显示原图,无需生成缩略图
// 这样可以大幅提升加载速度,特别是处理大量文件时
// ============ 懒加载编码信息 ============
async function lazyLoadCodecInfo() {
console.log('开始懒加载编码信息...');
// 需要获取编码信息的文件
const filesToLoad = state.files.filter(file =>
!file.codec_info && (file.file_type === 'video' || file.file_type === 'audio' || file.file_type === 'image')
);
if (filesToLoad.length === 0) {
console.log('没有需要加载的编码信息');
return;
}
console.log(`需要加载 ${filesToLoad.length} 个文件的编码信息`);
// 并发加载编码信息(限制并发数为 5比缩略图可以更多
const concurrency = 5;
for (let i = 0; i < filesToLoad.length; i += concurrency) {
const batch = filesToLoad.slice(i, i + concurrency);
await Promise.all(batch.map(file => loadSingleCodecInfo(file)));
}
console.log('所有编码信息加载完成');
}
async function loadSingleCodecInfo(file) {
try {
const codecInfo = await window.tauriInvoke('get_file_info', {
path: file.path,
fileType: file.file_type
});
// 更新文件对象
file.codec_info = codecInfo;
// 更新 DOM 显示(如果文件列表弹窗已打开)
updateCodecInfoInDOM(file);
console.log(`✅ 编码信息加载成功: ${file.name}`);
} catch (error) {
console.warn(`⚠️ 编码信息加载失败: ${file.name}`, error);
}
}
function updateCodecInfoInDOM(file) {
// 查找文件列表中对应的元素
const fileListItems = document.querySelectorAll(`.file-list-item[data-file-id="${file.id}"]`);
fileListItems.forEach(item => {
const codecEl = item.querySelector('.file-list-codec');
if (codecEl && file.codec_info) {
const codecInfo = getCodecInfoTextForFile(file);
codecEl.textContent = codecInfo;
codecEl.title = codecInfo;
}
});
}
// 为指定的文件列表加载编码信息
async function loadCodecInfoForFiles(files) {
const filesToLoad = files.filter(file => !file.codec_info);
if (filesToLoad.length === 0) {
return;
}
console.log(`为文件列表加载 ${filesToLoad.length} 个编码信息`);
// 并发加载
const concurrency = 5;
for (let i = 0; i < filesToLoad.length; i += concurrency) {
const batch = filesToLoad.slice(i, i + concurrency);
await Promise.all(batch.map(file => loadSingleCodecInfo(file)));
}
}
// ============ 懒加载图片 ============
async function lazyLoadImages() {
console.log('开始懒加载图片...');
// 需要加载的图片文件
const imagesToLoad = state.files.filter(file =>
file.file_type === 'image' && !file.imageData
);
if (imagesToLoad.length === 0) {
console.log('没有需要加载的图片');
return;
}
console.log(`需要加载 ${imagesToLoad.length} 个图片`);
// 并发加载图片(限制并发数为 3
const concurrency = 3;
for (let i = 0; i < imagesToLoad.length; i += concurrency) {
const batch = imagesToLoad.slice(i, i + concurrency);
await Promise.all(batch.map(file => loadSingleImage(file)));
}
console.log('所有图片加载完成');
}
async function loadSingleImage(file) {
try {
const imageData = await window.tauriInvoke('read_image_as_base64', {
path: file.path
});
// 缓存图片数据
file.imageData = imageData;
// 更新 DOM 显示
updateImageInDOM(file.id, imageData);
console.log(`✅ 图片加载成功: ${file.name}`);
} catch (error) {
console.warn(`⚠️ 图片加载失败: ${file.name}`, error);
}
}
function updateImageInDOM(fileId, imageData) {
// 更新预览网格中的图片
const previewItems = document.querySelectorAll(`.preview-item[data-file-id="${fileId}"]`);
previewItems.forEach(item => {
if (item.dataset.loading === 'true') {
item.innerHTML = `<img src="${imageData}" alt="" loading="lazy">`;
item.dataset.loading = 'false';
item.classList.remove('image');
}
});
// 更新文件列表弹窗中的图片
const fileListItems = document.querySelectorAll(`.file-list-item[data-file-id="${fileId}"] .file-list-thumbnail`);
fileListItems.forEach(item => {
item.innerHTML = `<img src="${imageData}" alt="" loading="lazy">`;
});
}
function getCodecInfoTextForFile(file) {
if (!file.codec_info) {
return '-';
}
const info = file.codec_info;
const parts = [];
const fileType = file.file_type;
if (fileType === 'video') {
if (info.resolution) parts.push(info.resolution);
if (info.video_codec) parts.push(info.video_codec);
if (info.frame_rate) parts.push(info.frame_rate);
if (info.duration) parts.push(info.duration);
} else if (fileType === 'audio') {
if (info.audio_codec) parts.push(info.audio_codec);
if (info.sample_rate) parts.push(info.sample_rate);
if (info.channels) parts.push(info.channels);
if (info.duration) parts.push(info.duration);
} else if (fileType === 'image') {
if (info.resolution) parts.push(info.resolution);
}
return parts.length > 0 ? parts.join(' · ') : '-';
}