').text(name).html() + '');
});
// 恢复当前面板的选中连接,避免左右两侧被同步成同一个
if (currentSessionId && activeConnections[currentSessionId]) {
select.val(currentSessionId);
} else {
select.val('');
}
}
// 刷新面板(先更新活跃连接,再加载左右面板文件,避免竞态和错误数据)
function refreshPanels() {
$.ajax({
url: API_BASE + 'api/connection/active',
method: 'GET',
success: function(response) {
if (response.success && response.data) {
activeConnections = response.data;
updateConnectionSelect('left');
updateConnectionSelect('right');
['left', 'right'].forEach(function(panelId) {
if (panelState[panelId].mode === 'sftp') {
showConnectionStatus(panelId, panelState[panelId].sessionId);
}
});
}
loadFiles('left');
loadFiles('right');
updateStatus('已刷新');
},
error: function() {
loadFiles('left');
loadFiles('right');
updateStatus('已刷新');
}
});
}
// 切换是否展示隐藏文件
function toggleShowHidden() {
showHiddenFiles = !showHiddenFiles;
const btn = $('#btn-show-hidden');
if (showHiddenFiles) {
btn.removeClass('btn-outline-secondary').addClass('btn-secondary');
btn.text('隐藏文件已显示');
} else {
btn.removeClass('btn-secondary').addClass('btn-outline-secondary');
btn.text('显示隐藏文件');
}
loadFiles('left');
loadFiles('right');
updateStatus(showHiddenFiles ? '已显示隐藏文件' : '已隐藏隐藏文件');
}
// 键盘事件
function bindKeyboardEvents() {
$(document).on('keydown', function(e) {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
if (e.key === 'Backspace') {
goUp(activePanelId);
e.preventDefault();
}
if (e.key === 'F5') {
refreshPanels();
e.preventDefault();
}
if (e.key === 'Escape') {
deselectAll(activePanelId);
e.preventDefault();
}
// Ctrl+A: 全选
if (e.ctrlKey && (e.key === 'a' || e.key === 'A')) {
selectAll(activePanelId);
e.preventDefault();
}
if (e.key === 'Delete') {
deleteSelectedFiles(activePanelId);
e.preventDefault();
}
if (e.key === 'F2') {
showRenameDialog();
e.preventDefault();
}
// Ctrl+Shift+N: 新建文件夹
if (e.ctrlKey && e.shiftKey && e.key === 'N') {
showMkdirDialog(activePanelId);
e.preventDefault();
}
});
}
// 获取当前活动面板ID
function getActivePanelId() {
return activePanelId;
}
// 刷新当前面板(删除后刷新列表)
function refreshCurrentPanel() {
loadFiles(activePanelId);
}
// 从路径获取文件名
function getFileNameFromPath(path) {
if (!path) return '';
const idx = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'));
if (idx === -1) return path;
return path.substring(idx + 1);
}
// ========== 连接管理(模块03)==========
function ensureBootstrap() {
if (typeof bootstrap === 'undefined') {
alert('Bootstrap 未加载,请检查网络连接或暂时关闭广告拦截器后刷新页面');
return false;
}
return true;
}
function showConnectionDialog() {
loadConnectionList();
if (!ensureBootstrap()) return;
new bootstrap.Modal(document.getElementById('connectionModal')).show();
}
function loadConnectionList() {
$.ajax({
url: API_BASE + 'api/connection/list',
method: 'GET',
success: function(response) {
const list = $('#connection-list');
list.empty();
if (response.success && response.data && response.data.length > 0) {
response.data.forEach(function(conn) {
const activeSession = findSessionByConnectionId(conn.id);
const connName = $('
').text(conn.name || '').html();
const connInfo = (conn.host || '') + ':' + (conn.port || 22);
let actions = '';
if (activeSession) {
const sessionId = activeSession[0];
actions = '
' +
'
';
} else {
actions = '
' +
'
';
}
list.append(
'
' +
'
' + connName + '' +
'
' + connInfo + '
' +
'
' + actions + '
'
);
});
} else {
list.html('
暂无已保存的连接,请添加连接
');
}
}
});
}
// 根据连接 ID 查找活跃会话 [sessionId, conn]
function findSessionByConnectionId(connectionId) {
const id = Number(connectionId);
const entries = Object.entries(activeConnections || {});
for (let i = 0; i < entries.length; i++) {
const sessionId = entries[i][0];
const conn = entries[i][1];
if (conn && (conn.id === id || conn.id === connectionId)) {
return [sessionId, conn];
}
}
return null;
}
// 删除已保存的连接
function deleteConnection(id) {
if (!confirm('确定要删除此连接吗?')) return;
$.ajax({
url: API_BASE + 'api/connection/' + id,
method: 'DELETE',
success: function(response) {
if (response.success) {
updateStatus('删除成功');
loadConnectionList();
loadActiveConnections();
updateConnectionSelect('left');
updateConnectionSelect('right');
} else {
alert('删除失败: ' + (response.message || '未知错误'));
}
},
error: function(xhr) {
alert('删除失败: ' + (xhr.responseJSON && xhr.responseJSON.message || xhr.statusText));
}
});
}
// 断开 SFTP 连接
function disconnectFromSftp(sessionId) {
updateStatus('正在断开连接...');
$.ajax({
url: API_BASE + 'api/connection/disconnect',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({ sessionId: sessionId }),
success: function(response) {
if (response.success) {
delete activeConnections[sessionId];
updatePanelsAfterDisconnect(sessionId);
updateConnectionSelect('left');
updateConnectionSelect('right');
['left', 'right'].forEach(function(panelId) {
showConnectionStatus(panelId, panelState[panelId].sessionId);
});
loadConnectionList();
updateStatus('已断开连接');
} else {
alert('断开失败: ' + (response.message || '未知错误'));
}
},
error: function(xhr) {
alert('断开失败: ' + (xhr.responseJSON && xhr.responseJSON.message || xhr.statusText));
}
});
}
// 断开连接后更新使用该会话的面板为本地模式
function updatePanelsAfterDisconnect(sessionId) {
['left', 'right'].forEach(function(panelId) {
if (panelState[panelId].sessionId === sessionId) {
panelState[panelId].sessionId = 'local';
panelState[panelId].mode = 'local';
panelState[panelId].currentPath = '';
$('#' + panelId + '-mode').val('local');
$('#' + panelId + '-connection').hide().val('');
$('#' + panelId + '-status').hide();
initPanelPath(panelId);
}
});
}
// 连接成功后只更新当前活动面板为新连接,另一侧保持原选择,便于左右选不同 SFTP
function updatePanelStateWithConnection(sessionId, conn) {
const panelId = activePanelId;
if (panelState[panelId].mode === 'sftp') {
panelState[panelId].sessionId = sessionId;
$('#' + panelId + '-connection').val(sessionId);
initPanelPath(panelId);
showConnectionStatus(panelId, sessionId);
}
// 另一侧保持原有连接选择不变
}
function showAddConnectionDialog() {
document.getElementById('connection-form').reset();
if (!ensureBootstrap()) return;
new bootstrap.Modal(document.getElementById('addConnectionModal')).show();
}
function saveConnection() {
const form = document.getElementById('connection-form');
const data = {
name: form.name.value,
host: form.host.value,
port: parseInt(form.port.value, 10) || 22,
username: form.username.value,
password: form.password.value || null,
privateKeyPath: form.privateKeyPath.value || null,
passPhrase: form.passPhrase.value || null
};
$.ajax({
url: API_BASE + 'api/connection/save',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify(data),
success: function(response) {
if (response.success) {
if (typeof bootstrap !== 'undefined') {
bootstrap.Modal.getInstance(document.getElementById('addConnectionModal')).hide();
}
loadConnectionList();
} else {
alert('保存失败: ' + (response.message || '未知错误'));
}
},
error: function(xhr) {
alert('保存失败: ' + (xhr.responseJSON && xhr.responseJSON.message || xhr.statusText));
}
});
}
function connectToServer(connId) {
$.ajax({
url: API_BASE + 'api/connection/' + connId,
method: 'GET',
success: function(response) {
if (response.success && response.data) {
const conn = response.data;
if (findSessionByConnectionId(conn.id)) {
alert('该连接已在使用中');
return;
}
updateStatus('正在连接 ' + (conn.name || conn.host) + '...');
$.ajax({
url: API_BASE + 'api/connection/connect',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({
id: conn.id,
host: conn.host,
port: conn.port,
username: conn.username
}),
success: function(res) {
if (res.success) {
const sessionId = res.data;
activeConnections[sessionId] = conn;
updateConnectionSelect('left');
updateConnectionSelect('right');
updatePanelStateWithConnection(sessionId, conn);
loadConnectionList();
if (typeof bootstrap !== 'undefined') {
bootstrap.Modal.getInstance(document.getElementById('connectionModal')).hide();
}
updateStatus('已连接到 ' + (conn.name || conn.host));
} else {
alert('连接失败: ' + (res.message || '未知错误'));
updateStatus('连接失败');
}
},
error: function(xhr) {
alert('连接失败: ' + (xhr.responseJSON && xhr.responseJSON.message || xhr.statusText));
updateStatus('连接失败');
}
});
} else {
alert('连接不存在');
}
},
error: function(xhr) {
alert('获取连接失败: ' + (xhr.responseJSON && xhr.responseJSON.message || xhr.statusText));
}
});
}
// ========== 文件上传下载功能(模块05)==========
// 获取目标面板ID(当前活动面板)
function getTargetPanelId() {
return activePanelId;
}
// 获取源面板ID(当前活动面板)
function getSourcePanelId() {
return activePanelId;
}
// 上传文件
function uploadFiles() {
document.getElementById('file-input').click();
}
// 初始化文件上传功能
$(document).ready(function() {
// 文件选择
$('#file-input').on('change', function(e) {
const files = e.target.files;
if (files && files.length > 0) {
uploadFilesToTarget(files);
}
// 清空input,以便可以重复选择同一文件
$(this).val('');
});
// 拖拽上传 - 为每个面板添加拖拽支持
['left', 'right'].forEach(function(panelId) {
const panel = $(`#${panelId}-panel`);
panel.on('dragover', function(e) {
e.preventDefault();
e.stopPropagation();
$(this).addClass('drag-over');
});
panel.on('dragleave', function(e) {
e.preventDefault();
e.stopPropagation();
$(this).removeClass('drag-over');
});
panel.on('drop', function(e) {
e.preventDefault();
e.stopPropagation();
$(this).removeClass('drag-over');
const files = e.originalEvent.dataTransfer.files;
if (files && files.length > 0) {
activePanelId = panelId;
uploadFilesToTarget(files);
}
});
});
});
// 上传文件到目标面板
function uploadFilesToTarget(files) {
const targetPanelId = getTargetPanelId();
const targetSessionId = panelState[targetPanelId].sessionId;
const targetPath = panelState[targetPanelId].currentPath;
if (!targetPath) {
alert('请先选择目标目录');
return;
}
Array.from(files).forEach(function(file) {
uploadSingleFile(file, targetSessionId, targetPath, targetPanelId);
});
}
// 上传单个文件
function uploadSingleFile(file, targetSessionId, targetPath, targetPanelId) {
const formData = new FormData();
formData.append('file', file);
formData.append('targetSessionId', targetSessionId);
formData.append('targetPath', targetPath);
showTransferProgress(true, '上传: ' + file.name + ' 0%');
updateTransferProgress(0, '上传: ' + file.name + ' 0%');
$.ajax({
url: API_BASE + 'api/files/upload',
method: 'POST',
data: formData,
processData: false,
contentType: false,
xhr: function() {
const xhr = new window.XMLHttpRequest();
xhr.upload.addEventListener('progress', function(e) {
if (e.lengthComputable) {
const percent = Math.round((e.loaded / e.total) * 100);
updateTransferProgress(percent, '上传: ' + file.name + ' ' + percent + '%');
}
});
return xhr;
},
success: function(response) {
if (response.success) {
updateStatus(file.name + ' 上传成功');
loadFiles(targetPanelId);
} else {
alert('上传失败: ' + (response.message || '未知错误'));
}
},
error: function(xhr, status, error) {
const errMsg = xhr.responseJSON && xhr.responseJSON.message ? xhr.responseJSON.message : error;
alert('上传失败: ' + errMsg);
},
complete: function() {
showTransferProgress(false);
}
});
}
// 显示/隐藏传输进度区域(上传、下载、跨面板传输共用)
function showTransferProgress(show, label) {
const progressDiv = $('#transfer-progress');
progressDiv.css('display', show ? 'inline-flex' : 'none');
if (label !== undefined) {
$('#transfer-progress-label').text(label || '');
}
}
// 更新传输进度条与标签
// indeterminate: 当后端无流式进度时(如跨面板传输 API),设为 true 显示动画进度条
function updateTransferProgress(percent, label, indeterminate) {
const progressBar = $('#transfer-progress-bar');
if (indeterminate) {
progressBar.css('width', '100%');
progressBar.text('');
progressBar.addClass('progress-bar-striped progress-bar-animated');
} else {
progressBar.removeClass('progress-bar-striped progress-bar-animated');
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);
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();
}
// 下载选中的文件
function downloadFiles() {
const panelId = getSourcePanelId();
const selectedFiles = panelState[panelId].selectedFiles || [];
const sessionId = panelState[panelId].sessionId;
if (selectedFiles.length === 0) {
alert('请先选择要下载的文件');
return;
}
selectedFiles.forEach(function(item) {
if (!item || !item.path) return;
if (item.isDirectory) {
if (!confirm('当前选中包含文件夹,下载时将递归包含其所有子目录和文件,可能耗时较长,是否继续?')) {
return;
}
}
downloadFile(sessionId, item.path);
});
}
// 按方向执行跨面板传输(源面板 -> 目标面板)
function doTransfer(sourcePanelId, targetPanelId) {
const sourceSessionId = panelState[sourcePanelId].sessionId;
const targetSessionId = panelState[targetPanelId].sessionId;
const targetPath = panelState[targetPanelId].currentPath;
const selectedFiles = panelState[sourcePanelId].selectedFiles || [];
if (selectedFiles.length === 0) {
alert('请在' + (sourcePanelId === 'left' ? '左侧' : '右侧') + '面板选择要传输的文件');
return;
}
if (!targetPath) {
alert('目标面板路径无效,请先选择目标目录');
return;
}
let completed = 0;
let failed = 0;
const total = selectedFiles.length;
showTransferCountProgress(0, total, '');
updateTransferProgress(0, '传输中 (0/' + total + ')', true); // 后端无流式进度,使用动画条
const sourcePaths = selectedFiles
.filter(function(item) { return item && item.path; })
.map(function(item) { return item.path; });
const hasDirectory = selectedFiles.some(function(item) { return item && item.isDirectory; });
const recursive = hasDirectory; // 若包含目录,则自动递归
$.ajax({
url: API_BASE + 'api/files/transfer',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({
sourceSessionId: sourceSessionId,
sourcePaths: sourcePaths,
targetSessionId: targetSessionId,
targetPath: targetPath,
recursive: recursive
}),
success: function(response) {
showTransferProgress(false);
if (response.success) {
updateStatus('传输成功');
} else {
alert('传输失败: ' + (response.message || '未知错误'));
updateStatus('传输失败');
}
loadFiles(targetPanelId);
},
error: function(xhr, status, error) {
showTransferProgress(false);
const errMsg = xhr.responseJSON && xhr.responseJSON.message ? xhr.responseJSON.message : error;
alert('传输失败: ' + errMsg);
updateStatus('传输失败');
}
});
updateStatus('正在传输 ' + total + ' 个项目...');
}
// 传输到右侧:左侧面板选中的文件 -> 右侧面板
function transferToRight() {
doTransfer('left', 'right');
}
// 传输到左侧:右侧面板选中的文件 -> 左侧面板
function transferToLeft() {
doTransfer('right', 'left');
}
// ========== 文件删除功能(模块06)==========
// 工具栏删除:删除当前活动面板中选中的文件
function deleteFiles() {
deleteSelectedFiles(activePanelId);
}
// 删除指定面板中选中的文件(含确认)
function deleteSelectedFiles(panelId) {
const selectedFiles = panelState[panelId].selectedFiles;
if (selectedFiles.length === 0) {
alert('请先选择要删除的文件');
return;
}
const sessionId = panelState[panelId].sessionId;
let message;
if (selectedFiles.length === 1) {
const fileName = getFileNameFromPath(selectedFiles[0]);
message = '确定要删除 "' + fileName + '" 吗?此操作不可恢复。';
} else {
message = '确定要删除选中的 ' + selectedFiles.length + ' 个文件/文件夹吗?此操作不可恢复。';
}
if (confirm(message)) {
performDelete(sessionId, selectedFiles, panelId);
}
}
// 执行删除请求(单个走 DELETE,多个走批量 POST)
function performDelete(sessionId, paths, panelId) {
if (!panelId) panelId = activePanelId;
if (paths.length === 1) {
$.ajax({
url: API_BASE + 'api/files/delete',
method: 'DELETE',
data: {
sessionId: sessionId,
path: paths[0]
},
success: function(response) {
if (response.success) {
updateStatus('删除成功');
loadFiles(panelId);
} else {
alert('删除失败: ' + (response.message || '未知错误'));
}
},
error: function(xhr, status, error) {
const errMsg = xhr.responseJSON && xhr.responseJSON.message ? xhr.responseJSON.message : error;
alert('删除失败: ' + errMsg);
}
});
} else {
$.ajax({
url: API_BASE + 'api/files/batch-delete',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({
sessionId: sessionId,
paths: paths
}),
success: function(response) {
if (response.success) {
const result = response.data;
let msg = '成功删除 ' + result.successCount + ' 个';
if (result.failCount > 0) {
msg += ',失败 ' + result.failCount + ' 个。\n失败详情:\n' + (result.failedFiles || []).join('\n');
}
alert(msg);
updateStatus(msg);
loadFiles(panelId);
} else {
alert('批量删除失败: ' + (response.message || '未知错误'));
}
},
error: function(xhr, status, error) {
const errMsg = xhr.responseJSON && xhr.responseJSON.message ? xhr.responseJSON.message : error;
alert('批量删除失败: ' + errMsg);
}
});
}
}
// 文件列表右键菜单
function showContextMenu(event, panelId, path, isDirectory) {
$('.context-menu').remove();
const menu = $('');
menu.css({
position: 'fixed',
left: event.clientX + 'px',
top: event.clientY + 'px',
zIndex: 1050
});
// 跨面板传输(根据当前面板添加对应方向的菜单项)
if (panelId === 'left') {
const transferRightItem = $('
传输到右侧');
transferRightItem.on('click', function(e) {
e.preventDefault();
e.stopPropagation();
menu.remove();
transferSingleBetweenPanels('left', 'right', path, isDirectory);
});
menu.append(transferRightItem);
} else if (panelId === 'right') {
const transferLeftItem = $('
传输到左侧');
transferLeftItem.on('click', function(e) {
e.preventDefault();
e.stopPropagation();
menu.remove();
transferSingleBetweenPanels('right', 'left', path, isDirectory);
});
menu.append(transferLeftItem);
}
menu.append('
');
const deleteItem = $('
删除');
deleteItem.on('click', function(e) {
e.preventDefault();
e.stopPropagation();
menu.remove();
deleteFileByPath(panelId, path);
});
menu.append(deleteItem);
const renameItem = $('
重命名');
renameItem.on('click', function(e) {
e.preventDefault();
e.stopPropagation();
menu.remove();
showRenameDialog(panelId, path);
});
menu.append(renameItem);
const mkdirItem = $('
新建文件夹');
mkdirItem.on('click', function(e) {
e.preventDefault();
e.stopPropagation();
menu.remove();
showMkdirDialog(panelId);
});
menu.append(mkdirItem);
$('body').append(menu);
$(document).one('click', function() {
menu.remove();
});
}
// 右键菜单:单个文件跨面板传输
function transferSingleBetweenPanels(sourcePanelId, targetPanelId, path, isDirectory) {
if (!path) {
return;
}
const sourceSessionId = panelState[sourcePanelId].sessionId;
const targetSessionId = panelState[targetPanelId].sessionId;
const targetPath = panelState[targetPanelId].currentPath;
if (!targetPath) {
alert('目标面板路径无效,请先选择目标目录');
return;
}
const fileName = getFileNameFromPath(path);
const isDir = isDirectory === true || isDirectory === 'true' || isDirectory === 'True';
const recursive = isDir; // 若为目录,则递归传输
showTransferCountProgress(0, 1, fileName);
updateTransferProgress(0, '传输中 ' + (fileName || ''), true); // 无流式进度,使用动画条
$.ajax({
url: API_BASE + 'api/files/transfer',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({
sourceSessionId: sourceSessionId,
sourcePaths: [path],
targetSessionId: targetSessionId,
targetPath: targetPath,
recursive: recursive
}),
success: function(response) {
showTransferProgress(false);
if (response.success) {
updateStatus('传输成功');
} else {
alert('传输失败: ' + (response.message || '未知错误'));
updateStatus('传输失败');
}
loadFiles(targetPanelId);
},
error: function(xhr, status, error) {
showTransferProgress(false);
const errMsg = xhr.responseJSON && xhr.responseJSON.message ? xhr.responseJSON.message : error;
alert('传输失败: ' + errMsg);
updateStatus('传输失败');
}
});
updateStatus('正在传输 "' + (fileName || path) + '" ...');
}
// 右键菜单:删除单个文件
function deleteFileByPath(panelId, path) {
const sessionId = panelState[panelId].sessionId;
const fileName = getFileNameFromPath(path);
if (confirm('确定要删除 "' + fileName + '" 吗?此操作不可恢复。')) {
performDelete(sessionId, [path], panelId);
}
}
// ========== 文件重命名功能(模块07)==========
// 验证文件名(前端)
function validateFileName(fileName) {
if (!fileName || fileName.trim() === '') {
alert('文件名不能为空');
return false;
}
const illegalChars = /[\\/:*?"<>|]/;
if (illegalChars.test(fileName)) {
alert('文件名不能包含以下字符: \\ / : * ? " < > |');
return false;
}
if (fileName.length > 255) {
alert('文件名过长(最大 255 个字符)');
return false;
}
return true;
}
// 获取选中项路径(仅当选中一个时返回,否则提示并返回 null)
function getSelectedPath(panelId) {
const selectedFiles = panelState[panelId].selectedFiles;
if (selectedFiles.length === 0) {
alert('请先选择一个文件或文件夹');
return null;
}
if (selectedFiles.length > 1) {
alert('一次只能重命名一个文件或文件夹');
return null;
}
return selectedFiles[0];
}
// 显示重命名对话框(无参时从当前活动面板取选中项;有参时用于右键菜单)
function showRenameDialog(panelId, path) {
if (path === undefined) {
panelId = getActivePanelId();
path = getSelectedPath(panelId);
if (!path) return;
}
const sessionId = panelState[panelId].sessionId;
const oldName = getFileNameFromPath(path);
const newName = prompt('请输入新名称:', oldName);
if (newName === null) return; // 用户取消
if (newName.trim() === '') {
alert('名称不能为空');
return;
}
if (newName.trim() === oldName) {
updateStatus('名称未更改');
return;
}
if (!validateFileName(newName.trim())) return;
doRename(sessionId, path, newName.trim(), panelId);
}
// 执行重命名请求
function doRename(sessionId, oldPath, newName, panelId) {
if (!panelId) panelId = getActivePanelId();
$.ajax({
url: API_BASE + 'api/files/rename',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({
sessionId: sessionId,
oldPath: oldPath,
newName: newName
}),
success: function(response) {
if (response.success) {
updateStatus('重命名成功');
loadFiles(panelId);
} else {
alert('重命名失败: ' + (response.message || '未知错误'));
}
},
error: function(xhr, status, error) {
const errMsg = xhr.responseJSON && xhr.responseJSON.message ? xhr.responseJSON.message : error;
alert('重命名失败: ' + errMsg);
}
});
}
// ========== 新建文件夹功能(模块08)==========
// 显示新建文件夹对话框(无参时对当前活动面板;有参时用于右键菜单等)
function showMkdirDialog(panelId) {
if (panelId === undefined) panelId = getActivePanelId();
const sessionId = panelState[panelId].sessionId;
const currentPath = panelState[panelId].currentPath;
if (!currentPath) {
alert('当前路径无效,请先选择目录');
return;
}
const folderName = prompt('请输入文件夹名称:', '新建文件夹');
if (folderName === null) return;
if (folderName.trim() === '') {
alert('文件夹名称不能为空');
return;
}
if (!validateDirectoryName(folderName.trim())) return;
createDirectory(sessionId, currentPath, folderName.trim(), panelId);
}
// 创建目录
function createDirectory(sessionId, parentPath, folderName, panelId) {
if (!validateDirectoryName(folderName)) return;
var fullPath = joinPath(parentPath, folderName, sessionId);
$.ajax({
url: API_BASE + 'api/files/mkdir',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({
sessionId: sessionId,
path: fullPath
}),
success: function(response) {
if (response.success) {
updateStatus('文件夹创建成功');
loadFiles(panelId || getActivePanelId());
} else {
alert('创建失败: ' + (response.message || '未知错误'));
}
},
error: function(xhr, status, error) {
var errMsg = xhr.responseJSON && xhr.responseJSON.message ? xhr.responseJSON.message : error;
alert('创建失败: ' + errMsg);
}
});
}
// 拼接路径(父路径 + 子名称)
function joinPath(parentPath, childName, sessionId) {
childName = (childName || '').trim();
var isLocal = sessionId === 'local';
if (isLocal) {
if (parentPath.endsWith('\\') || parentPath.endsWith('/')) {
return parentPath + childName;
}
return parentPath + '\\' + childName;
}
if (parentPath.endsWith('/')) {
return parentPath + childName;
}
return parentPath + '/' + childName;
}
// 验证目录名(前端)
function validateDirectoryName(dirName) {
if (!dirName || dirName.trim() === '') {
alert('文件夹名称不能为空');
return false;
}
dirName = dirName.trim();
var illegalChars = /[\\/:*?"<>|]/;
if (illegalChars.test(dirName)) {
alert('文件夹名称不能包含以下字符: \\ / : * ? " < > |');
return false;
}
if (dirName.startsWith('.') || dirName.endsWith('.')) {
alert('文件夹名称不能以点开头或结尾');
return false;
}
if (dirName.length > 255) {
alert('文件夹名称过长(最大 255 个字符)');
return false;
}
var upperName = dirName.toUpperCase();
var reservedNames = ['CON', 'PRN', 'AUX', 'NUL',
'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9',
'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9'];
if (reservedNames.indexOf(upperName) !== -1) {
alert('文件夹名称不能使用系统保留名称');
return false;
}
return true;
}