Refactor file transfer progress handling in app.js and update UI elements in index.html and style.css. Introduced a unified transfer progress display for uploads, downloads, and cross-panel transfers, enhancing user experience and visual feedback.

This commit is contained in:
liu
2026-02-03 10:35:59 +08:00
parent 9f72ff5a48
commit 96f78dea87
3 changed files with 146 additions and 36 deletions

View File

@@ -44,18 +44,29 @@ body {
min-height: 44px; min-height: 44px;
} }
/* 上传进度条 */ /* 传输进度(上传/下载/跨面板传输) */
.upload-progress { .transfer-progress {
display: inline-block; display: inline-flex;
align-items: center;
vertical-align: middle; vertical-align: middle;
gap: 8px;
} }
.upload-progress .progress { .transfer-progress-label {
font-size: 12px;
color: #333;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.transfer-progress .progress {
border-radius: 4px; border-radius: 4px;
overflow: hidden; overflow: hidden;
} }
.upload-progress .progress-bar { .transfer-progress .progress-bar {
transition: width 0.3s ease; transition: width 0.3s ease;
font-size: 11px; font-size: 11px;
line-height: 20px; line-height: 20px;

View File

@@ -33,10 +33,11 @@
</div> </div>
<!-- 文件上传输入框(隐藏) --> <!-- 文件上传输入框(隐藏) -->
<input type="file" id="file-input" multiple style="display:none"> <input type="file" id="file-input" multiple style="display:none">
<!-- 上传进度条 --> <!-- 传输进度(上传/下载/跨面板传输) -->
<div class="upload-progress" id="upload-progress" style="display:none;"> <div class="transfer-progress" id="transfer-progress" style="display:none;">
<div class="progress" style="height: 20px; margin-left: 10px; width: 200px;"> <span class="transfer-progress-label" id="transfer-progress-label"></span>
<div class="progress-bar" role="progressbar" style="width: 0%">0%</div> <div class="progress transfer-progress-bar" style="height: 20px; margin-left: 10px; width: 220px;">
<div class="progress-bar" id="transfer-progress-bar" role="progressbar" style="width: 0%">0%</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -117,6 +117,19 @@ function loadFiles(panelId) {
}); });
} }
// 排序文件列表:文件夹在前,同类型按名称(不区分大小写)
function sortFileList(files) {
if (!Array.isArray(files) || files.length === 0) return files;
return files.slice().sort(function(a, b) {
const aDir = a.isDirectory === true || a.isDirectory === 'true' || a.directory === true || a.directory === 'true';
const bDir = b.isDirectory === true || b.isDirectory === 'true' || b.directory === true || b.directory === 'true';
if (aDir !== bDir) return aDir ? -1 : 1;
const nameA = (a.name || '').toLowerCase();
const nameB = (b.name || '').toLowerCase();
return nameA.localeCompare(nameB, 'zh-CN', { sensitivity: 'base' });
});
}
// 渲染文件列表(仅当 data 为数组时渲染,避免错误数据结构被当成文件显示) // 渲染文件列表(仅当 data 为数组时渲染,避免错误数据结构被当成文件显示)
function renderFileList(panelId, files) { function renderFileList(panelId, files) {
const fileList = $(`#${panelId}-file-list`); const fileList = $(`#${panelId}-file-list`);
@@ -131,6 +144,8 @@ function renderFileList(panelId, files) {
return; return;
} }
files = sortFileList(files);
files.forEach(function(file) { files.forEach(function(file) {
// 确保 isDirectory 字段存在且为布尔值 // 确保 isDirectory 字段存在且为布尔值
// 兼容多种可能的字段名和值类型 // 兼容多种可能的字段名和值类型
@@ -322,6 +337,16 @@ function initDragAndDrop() {
}); });
} }
// 显示跨面板传输进度(按文件个数)
function showTransferCountProgress(current, total, fileName) {
const label = total > 1
? '传输中 (' + current + '/' + total + ')' + (fileName ? ' ' + fileName : '')
: '传输中 ' + (fileName || '');
const percent = total > 0 ? Math.round((current / total) * 100) : 0;
showTransferProgress(true, label);
updateTransferProgress(percent, label);
}
// 处理文件拖放(同面板移动 / 跨面板传输) // 处理文件拖放(同面板移动 / 跨面板传输)
function handleFileDrop(data, targetPanelId) { function handleFileDrop(data, targetPanelId) {
const sourcePanelId = data.panelId; const sourcePanelId = data.panelId;
@@ -335,6 +360,10 @@ function handleFileDrop(data, targetPanelId) {
const sourceSessionId = panelState[sourcePanelId].sessionId; const sourceSessionId = panelState[sourcePanelId].sessionId;
const targetSessionId = panelState[targetPanelId].sessionId; const targetSessionId = panelState[targetPanelId].sessionId;
const fileName = getFileNameFromPath(sourcePath);
showTransferCountProgress(0, 1, fileName);
updateTransferProgress(0, '传输中 ' + fileName);
$.ajax({ $.ajax({
url: API_BASE + 'api/files/transfer', url: API_BASE + 'api/files/transfer',
@@ -347,6 +376,7 @@ function handleFileDrop(data, targetPanelId) {
targetPath: targetPath targetPath: targetPath
}), }),
success: function(response) { success: function(response) {
showTransferProgress(false);
if (response.success) { if (response.success) {
updateStatus('传输成功'); updateStatus('传输成功');
loadFiles(targetPanelId); loadFiles(targetPanelId);
@@ -355,6 +385,7 @@ function handleFileDrop(data, targetPanelId) {
} }
}, },
error: function(xhr, status, error) { error: function(xhr, status, error) {
showTransferProgress(false);
const errMsg = xhr.responseJSON && xhr.responseJSON.message ? xhr.responseJSON.message : error; const errMsg = xhr.responseJSON && xhr.responseJSON.message ? xhr.responseJSON.message : error;
alert('传输失败: ' + errMsg); alert('传输失败: ' + errMsg);
} }
@@ -897,7 +928,8 @@ function uploadSingleFile(file, targetSessionId, targetPath, targetPanelId) {
formData.append('targetSessionId', targetSessionId); formData.append('targetSessionId', targetSessionId);
formData.append('targetPath', targetPath); formData.append('targetPath', targetPath);
showUploadProgress(true); showTransferProgress(true, '上传: ' + file.name + ' 0%');
updateTransferProgress(0, '上传: ' + file.name + ' 0%');
$.ajax({ $.ajax({
url: API_BASE + 'api/files/upload', url: API_BASE + 'api/files/upload',
@@ -910,7 +942,7 @@ function uploadSingleFile(file, targetSessionId, targetPath, targetPanelId) {
xhr.upload.addEventListener('progress', function(e) { xhr.upload.addEventListener('progress', function(e) {
if (e.lengthComputable) { if (e.lengthComputable) {
const percent = Math.round((e.loaded / e.total) * 100); const percent = Math.round((e.loaded / e.total) * 100);
updateUploadProgress(percent); updateTransferProgress(percent, '上传: ' + file.name + ' ' + percent + '%');
} }
}); });
return xhr; return xhr;
@@ -928,30 +960,89 @@ function uploadSingleFile(file, targetSessionId, targetPath, targetPanelId) {
alert('上传失败: ' + errMsg); alert('上传失败: ' + errMsg);
}, },
complete: function() { complete: function() {
showUploadProgress(false); showTransferProgress(false);
} }
}); });
} }
// 显示上传进度 // 显示/隐藏传输进度区域(上传、下载、跨面板传输共用)
function showUploadProgress(show) { function showTransferProgress(show, label) {
const progressDiv = $('#upload-progress'); const progressDiv = $('#transfer-progress');
progressDiv.css('display', show ? 'inline-block' : 'none'); progressDiv.css('display', show ? 'inline-flex' : 'none');
if (label !== undefined) {
$('#transfer-progress-label').text(label || '');
}
} }
// 更新上传进度 // 更新传输进度条与标签
function updateUploadProgress(percent) { function updateTransferProgress(percent, label) {
const progressBar = $('#upload-progress .progress-bar'); const progressBar = $('#transfer-progress-bar');
progressBar.css('width', percent + '%'); progressBar.css('width', percent + '%');
progressBar.text(percent + '%'); progressBar.text(percent + '%');
if (label !== undefined) {
$('#transfer-progress-label').text(label);
}
} }
// 下载文件 // 显示上传进度(兼容旧调用)
function showUploadProgress(show) {
showTransferProgress(show);
}
// 更新上传进度(带文件名)
function updateUploadProgress(percent, fileName) {
const label = fileName ? '上传: ' + fileName + ' ' + percent + '%' : percent + '%';
updateTransferProgress(percent, label);
}
// 下载文件(带进度,使用 XHR
function downloadFile(sessionId, path) { function downloadFile(sessionId, path) {
const url = API_BASE + 'api/files/download?sessionId=' + const url = API_BASE + 'api/files/download?sessionId=' +
encodeURIComponent(sessionId) + encodeURIComponent(sessionId) +
'&path=' + encodeURIComponent(path); '&path=' + encodeURIComponent(path);
window.location.href = url; const fileName = getFileNameFromPath(path) || '下载';
showTransferProgress(true, '下载: ' + fileName + ' 0%');
updateTransferProgress(0, '下载: ' + fileName + ' 0%');
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'arraybuffer';
xhr.addEventListener('progress', function(e) {
if (e.lengthComputable && e.total > 0) {
const percent = Math.round((e.loaded / e.total) * 100);
updateTransferProgress(percent, '下载: ' + fileName + ' ' + percent + '%');
} else if (e.loaded > 0) {
const kb = (e.loaded / 1024).toFixed(1);
updateTransferProgress(0, '下载: ' + fileName + ' 已接收 ' + kb + ' KB');
}
});
xhr.addEventListener('load', function() {
showTransferProgress(false);
if (xhr.status !== 200) {
alert('下载失败: ' + (xhr.statusText || 'HTTP ' + xhr.status));
return;
}
const blob = new Blob([xhr.response]);
const blobUrl = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = blobUrl;
a.download = fileName;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(blobUrl);
updateStatus(fileName + ' 下载完成');
});
xhr.addEventListener('error', function() {
showTransferProgress(false);
alert('下载失败: 网络错误');
});
xhr.send();
} }
// 下载选中的文件 // 下载选中的文件
@@ -995,7 +1086,10 @@ function transferFiles() {
let failed = 0; let failed = 0;
const total = selectedFiles.length; const total = selectedFiles.length;
selectedFiles.forEach(function(sourcePath) { showTransferCountProgress(0, total, '');
updateTransferProgress(0, '传输中 (0/' + total + ')');
selectedFiles.forEach(function(sourcePath, index) {
$.ajax({ $.ajax({
url: API_BASE + 'api/files/transfer', url: API_BASE + 'api/files/transfer',
method: 'POST', method: 'POST',
@@ -1009,36 +1103,40 @@ function transferFiles() {
success: function(response) { success: function(response) {
if (response.success) { if (response.success) {
completed++; completed++;
if (completed + failed === total) {
if (failed === 0) {
updateStatus('所有文件传输成功');
} else {
updateStatus(`传输完成:成功 ${completed},失败 ${failed}`);
}
loadFiles(targetPanelId);
}
} else { } else {
failed++; failed++;
alert('传输失败: ' + (response.message || '未知错误')); alert('传输失败: ' + (response.message || '未知错误'));
if (completed + failed === total) {
updateStatus(`传输完成:成功 ${completed},失败 ${failed}`);
loadFiles(targetPanelId);
} }
const done = completed + failed;
showTransferCountProgress(done, total, getFileNameFromPath(sourcePath));
updateTransferProgress(total > 0 ? Math.round((done / total) * 100) : 0, '传输中 (' + done + '/' + total + ')');
if (done === total) {
showTransferProgress(false);
if (failed === 0) {
updateStatus('所有文件传输成功');
} else {
updateStatus('传输完成:成功 ' + completed + ',失败 ' + failed);
}
loadFiles(targetPanelId);
} }
}, },
error: function(xhr, status, error) { error: function(xhr, status, error) {
failed++; failed++;
const errMsg = xhr.responseJSON && xhr.responseJSON.message ? xhr.responseJSON.message : error; const errMsg = xhr.responseJSON && xhr.responseJSON.message ? xhr.responseJSON.message : error;
alert('传输失败: ' + errMsg); alert('传输失败: ' + errMsg);
if (completed + failed === total) { const done = completed + failed;
updateStatus(`传输完成:成功 ${completed},失败 ${failed}`); showTransferCountProgress(done, total, getFileNameFromPath(sourcePath));
updateTransferProgress(total > 0 ? Math.round((done / total) * 100) : 0, '传输中 (' + done + '/' + total + ')');
if (done === total) {
showTransferProgress(false);
updateStatus('传输完成:成功 ' + completed + ',失败 ' + failed);
loadFiles(targetPanelId); loadFiles(targetPanelId);
} }
} }
}); });
}); });
updateStatus(`正在传输 ${total} 个文件...`); updateStatus('正在传输 ' + total + ' 个文件...');
} }
// ========== 文件删除功能模块06========== // ========== 文件删除功能模块06==========