diff --git a/src/main/resources/static/css/style.css b/src/main/resources/static/css/style.css
index 0e5962c..ac16ec0 100644
--- a/src/main/resources/static/css/style.css
+++ b/src/main/resources/static/css/style.css
@@ -44,18 +44,29 @@ body {
min-height: 44px;
}
-/* 上传进度条 */
-.upload-progress {
- display: inline-block;
+/* 传输进度(上传/下载/跨面板传输) */
+.transfer-progress {
+ display: inline-flex;
+ align-items: center;
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;
overflow: hidden;
}
-.upload-progress .progress-bar {
+.transfer-progress .progress-bar {
transition: width 0.3s ease;
font-size: 11px;
line-height: 20px;
diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html
index 318db5d..80467c0 100644
--- a/src/main/resources/static/index.html
+++ b/src/main/resources/static/index.html
@@ -33,10 +33,11 @@
-
-
-
diff --git a/src/main/resources/static/js/app.js b/src/main/resources/static/js/app.js
index 9bf8542..c0d1992 100644
--- a/src/main/resources/static/js/app.js
+++ b/src/main/resources/static/js/app.js
@@ -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 为数组时渲染,避免错误数据结构被当成文件显示)
function renderFileList(panelId, files) {
const fileList = $(`#${panelId}-file-list`);
@@ -131,6 +144,8 @@ function renderFileList(panelId, files) {
return;
}
+ files = sortFileList(files);
+
files.forEach(function(file) {
// 确保 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) {
const sourcePanelId = data.panelId;
@@ -335,6 +360,10 @@ function handleFileDrop(data, targetPanelId) {
const sourceSessionId = panelState[sourcePanelId].sessionId;
const targetSessionId = panelState[targetPanelId].sessionId;
+ const fileName = getFileNameFromPath(sourcePath);
+
+ showTransferCountProgress(0, 1, fileName);
+ updateTransferProgress(0, '传输中 ' + fileName);
$.ajax({
url: API_BASE + 'api/files/transfer',
@@ -347,6 +376,7 @@ function handleFileDrop(data, targetPanelId) {
targetPath: targetPath
}),
success: function(response) {
+ showTransferProgress(false);
if (response.success) {
updateStatus('传输成功');
loadFiles(targetPanelId);
@@ -355,6 +385,7 @@ function handleFileDrop(data, targetPanelId) {
}
},
error: function(xhr, status, error) {
+ showTransferProgress(false);
const errMsg = xhr.responseJSON && xhr.responseJSON.message ? xhr.responseJSON.message : error;
alert('传输失败: ' + errMsg);
}
@@ -897,7 +928,8 @@ function uploadSingleFile(file, targetSessionId, targetPath, targetPanelId) {
formData.append('targetSessionId', targetSessionId);
formData.append('targetPath', targetPath);
- showUploadProgress(true);
+ showTransferProgress(true, '上传: ' + file.name + ' 0%');
+ updateTransferProgress(0, '上传: ' + file.name + ' 0%');
$.ajax({
url: API_BASE + 'api/files/upload',
@@ -910,7 +942,7 @@ function uploadSingleFile(file, targetSessionId, targetPath, targetPanelId) {
xhr.upload.addEventListener('progress', function(e) {
if (e.lengthComputable) {
const percent = Math.round((e.loaded / e.total) * 100);
- updateUploadProgress(percent);
+ updateTransferProgress(percent, '上传: ' + file.name + ' ' + percent + '%');
}
});
return xhr;
@@ -928,30 +960,89 @@ function uploadSingleFile(file, targetSessionId, targetPath, targetPanelId) {
alert('上传失败: ' + errMsg);
},
complete: function() {
- showUploadProgress(false);
+ showTransferProgress(false);
}
});
}
-// 显示上传进度
-function showUploadProgress(show) {
- const progressDiv = $('#upload-progress');
- progressDiv.css('display', show ? 'inline-block' : 'none');
+// 显示/隐藏传输进度区域(上传、下载、跨面板传输共用)
+function showTransferProgress(show, label) {
+ const progressDiv = $('#transfer-progress');
+ progressDiv.css('display', show ? 'inline-flex' : 'none');
+ if (label !== undefined) {
+ $('#transfer-progress-label').text(label || '');
+ }
}
-// 更新上传进度
-function updateUploadProgress(percent) {
- const progressBar = $('#upload-progress .progress-bar');
+// 更新传输进度条与标签
+function updateTransferProgress(percent, label) {
+ const progressBar = $('#transfer-progress-bar');
progressBar.css('width', 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) {
const url = API_BASE + 'api/files/download?sessionId=' +
encodeURIComponent(sessionId) +
'&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;
const total = selectedFiles.length;
- selectedFiles.forEach(function(sourcePath) {
+ showTransferCountProgress(0, total, '');
+ updateTransferProgress(0, '传输中 (0/' + total + ')');
+
+ selectedFiles.forEach(function(sourcePath, index) {
$.ajax({
url: API_BASE + 'api/files/transfer',
method: 'POST',
@@ -1009,36 +1103,40 @@ function transferFiles() {
success: function(response) {
if (response.success) {
completed++;
- if (completed + failed === total) {
- if (failed === 0) {
- updateStatus('所有文件传输成功');
- } else {
- updateStatus(`传输完成:成功 ${completed},失败 ${failed}`);
- }
- loadFiles(targetPanelId);
- }
} else {
failed++;
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) {
failed++;
const errMsg = xhr.responseJSON && xhr.responseJSON.message ? xhr.responseJSON.message : error;
alert('传输失败: ' + errMsg);
- if (completed + failed === total) {
- updateStatus(`传输完成:成功 ${completed},失败 ${failed}`);
+ 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);
+ updateStatus('传输完成:成功 ' + completed + ',失败 ' + failed);
loadFiles(targetPanelId);
}
}
});
});
- updateStatus(`正在传输 ${total} 个文件...`);
+ updateStatus('正在传输 ' + total + ' 个文件...');
}
// ========== 文件删除功能(模块06)==========