20 KiB
20 KiB
模块10:模式切换功能
🎨 UI设计系统概览
完整设计系统文档请参考:
UI设计系统.md
核心设计原则
- 现代简约:界面清晰,层次分明
- 专业高效:减少操作步骤,提升工作效率
- 一致性:统一的视觉语言和交互模式
- 可访问性:符合WCAG 2.1 AA标准
关键设计令牌
颜色系统:
- 主色:
#0d6efd(操作按钮、选中状态) - 成功:
#198754(连接成功状态) - 危险:
#dc3545(删除操作、错误提示) - 深灰:
#212529(导航栏背景) - 浅灰:
#e9ecef(工具栏背景)
字体系统:
- 字体族:系统字体栈(-apple-system, Segoe UI, Roboto等)
- 正文:14px,行高1.5
- 标题:20-32px,行高1.2-1.4
- 小号文字:12px(文件大小、日期等)
间距系统:
- 基础单位:8px
- 标准间距:16px(1rem)
- 组件内边距:8px-16px
组件规范:
- 导航栏:高度48px,深色背景
- 工具栏:浅灰背景,按钮间距8px
- 文件项:最小高度44px,悬停效果150ms
- 按钮:圆角4px,过渡150ms
交互规范:
- 悬停效果:150ms过渡
- 触摸目标:最小44x44px
- 键盘导航:Tab、Enter、Delete、F2、F5、Esc
- 焦点状态:2px蓝色轮廓
响应式断点:
- 移动端:< 768px(双面板垂直排列)
- 平板:768px - 1024px
- 桌面:> 1024px(标准布局)
10.1 功能概述
实现左侧和右侧面板在"本地文件"和"SFTP服务器"模式之间切换,支持同时连接多个SFTP服务器。
10.2 模式状态管理
10.2.1 JavaScript状态管理
// 面板状态管理
const panelState = {
left: {
mode: 'local', // 'local' 或 'sftp'
sessionId: 'local', // 'local' 或 SFTP会话ID
currentPath: '', // 当前路径
selectedFiles: [] // 选中的文件
},
right: {
mode: 'local',
sessionId: 'local',
currentPath: '',
selectedFiles: []
}
};
// 活跃连接映射
const activeConnections = {};
// 已保存的连接列表
let savedConnections = [];
10.2.2 模式切换逻辑
// 模式切换
function onModeChange(panelId) {
const mode = $(`#${panelId}-mode`).val();
panelState[panelId].mode = mode;
if (mode === 'local') {
switchToLocalMode(panelId);
} else {
switchToSftpMode(panelId);
}
}
// 切换到本地模式
function switchToLocalMode(panelId) {
updateStatus(`正在切换到本地模式...`);
// 隐藏SFTP连接选择器
$(`#${panelId}-connection`).hide();
// 更新会话ID
panelState[panelId].sessionId = 'local';
// 设置默认路径
panelState[panelId].currentPath = getDefaultLocalPath();
// 加载本地文件列表
loadFiles(panelId);
updateStatus('已切换到本地模式');
}
// 切换到SFTP模式
function switchToSftpMode(panelId) {
updateStatus(`正在切换到SFTP模式...`);
// 显示SFTP连接选择器
$(`#${panelId}-connection`).show();
// 加载已保存的连接列表
loadSavedConnections(panelId);
// 检查是否有活跃的SFTP连接
const activeSessionId = findActiveSftpSession(panelId);
if (activeSessionId) {
// 使用已有连接
panelState[panelId].sessionId = activeSessionId;
$(`#${panelId}-connection`).val(activeSessionId);
loadSftpCurrentPath(panelId, activeSessionId);
} else {
// 提示用户选择或创建连接
alert('请选择一个SFTP连接或创建新连接');
}
updateStatus('已切换到SFTP模式');
}
// 获取默认本地路径
function getDefaultLocalPath() {
return System.getProperty('user.home');
}
// 查找活跃的SFTP会话
function findActiveSftpSession(panelId) {
const connectionSelect = $(`#${panelId}-connection`);
const sessionId = connectionSelect.val();
if (sessionId && sessionId !== 'local') {
return sessionId;
}
return null;
}
// 加载SFTP当前路径
function loadSftpCurrentPath(panelId, sessionId) {
$.ajax({
url: '/api/files/path',
method: 'GET',
data: {sessionId: sessionId},
success: function(response) {
if (response.success) {
panelState[panelId].currentPath = response.data.path;
loadFiles(panelId);
} else {
alert('获取路径失败: ' + response.message);
}
},
error: handleError
});
}
10.3 连接管理UI
10.3.1 连接列表加载
// 加载已保存的连接列表
function loadSavedConnections(panelId) {
$.ajax({
url: '/api/connection/list',
method: 'GET',
success: function(response) {
if (response.success) {
savedConnections = response.data;
updateConnectionSelect(panelId, savedConnections);
} else {
alert('加载连接列表失败: ' + response.message);
}
},
error: handleError
});
}
// 更新连接选择器
function updateConnectionSelect(panelId, connections) {
const select = $(`#${panelId}-connection`);
select.empty();
// 添加选项
if (connections.length === 0) {
select.append('<option value="">请选择连接</option>');
} else {
connections.forEach(conn => {
const option = $('<option>');
option.val(conn.id);
option.text(`${conn.name} (${conn.username}@${conn.host})`);
select.append(option);
});
}
}
// 加载活跃连接列表
function loadActiveConnections() {
$.ajax({
url: '/api/connection/active',
method: 'GET',
success: function(response) {
if (response.success) {
activeConnections = response.data;
updateConnectionPanels();
} else {
console.error('加载活跃连接失败:', response.message);
}
},
error: handleError
});
}
// 更新连接面板
function updateConnectionPanels() {
Object.keys(panelState).forEach(panelId => {
if (panelState[panelId].mode === 'sftp') {
updateConnectionSelect(panelId, savedConnections);
// 选中当前活跃的连接
const currentSessionId = panelState[panelId].sessionId;
if (currentSessionId !== 'local') {
// 找到对应的连接ID并选中
Object.entries(activeConnections).forEach(([sessionId, conn]) => {
if (sessionId === currentSessionId) {
$(`#${panelId}-connection`).val(conn.id);
}
});
}
}
});
}
10.3.2 连接管理对话框
// 显示连接管理对话框
function showConnectionDialog() {
$('#connectionModal').modal('show');
loadConnectionList();
}
// 加载连接列表
function loadConnectionList() {
const connectionList = $('#connection-list');
connectionList.empty();
if (savedConnections.length === 0) {
connectionList.html('<p class="text-muted text-center">暂无保存的连接</p>');
return;
}
savedConnections.forEach(conn => {
const item = $(`
<div class="connection-item" data-id="${conn.id}">
<div>
<div class="connection-name">${conn.name}</div>
<div class="connection-info">${conn.username}@${conn.host}:${conn.port}</div>
</div>
<div class="connection-actions">
<button class="btn btn-sm btn-primary" onclick="connectToSftp(${conn.id})">连接</button>
<button class="btn btn-sm btn-danger" onclick="deleteConnection(${conn.id})">删除</button>
</div>
</div>
`);
connectionList.append(item);
});
}
// 显示添加连接对话框
function showAddConnectionDialog() {
$('#connectionModal').modal('hide');
$('#addConnectionModal').modal('show');
$('#connection-form')[0].reset();
}
// 保存连接
function saveConnection() {
const formData = $('#connection-form').serializeObject();
$.ajax({
url: '/api/connection/save',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify(formData),
success: function(response) {
if (response.success) {
alert('连接保存成功');
$('#addConnectionModal').modal('hide');
$('#connectionModal').modal('show');
loadSavedConnections('left');
loadSavedConnections('right');
} else {
alert('保存失败: ' + response.message);
}
},
error: handleError
});
}
// 删除连接
function deleteConnection(id) {
if (confirm('确定要删除此连接吗?')) {
$.ajax({
url: '/api/connection/' + id,
method: 'DELETE',
success: function(response) {
if (response.success) {
alert('删除成功');
loadConnectionList();
loadSavedConnections('left');
loadSavedConnections('right');
} else {
alert('删除失败: ' + response.message);
}
},
error: handleError
});
}
}
// 序列化表单为对象
$.fn.serializeObject = function() {
const o = {};
const a = this.serializeArray();
$.each(a, function() {
if (o[this.name] !== undefined) {
if (!o[this.name].push) {
o[this.name] = [o[this.name]];
}
o[this.name].push(this.value || '');
} else {
o[this.name] = this.value || '';
}
});
return o;
};
10.3.3 SFTP连接
// 连接到SFTP服务器
function connectToSftp(connectionId) {
// 查找连接配置
const connection = savedConnections.find(c => c.id === connectionId);
if (!connection) {
alert('连接不存在');
return;
}
// 检查是否已连接
const existingSession = findSessionByConnectionId(connectionId);
if (existingSession) {
alert('该连接已在使用中');
return;
}
updateStatus(`正在连接到 ${connection.name}...`);
$.ajax({
url: '/api/connection/connect',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify(connection),
success: function(response) {
if (response.success) {
const sessionId = response.data;
// 更新活跃连接列表
activeConnections[sessionId] = connection;
// 更新选择器
$(`#left-connection`).val(connectionId);
$(`#right-connection`).val(connectionId);
// 更新面板状态
updatePanelStateWithConnection(sessionId, connection);
// 加载活跃连接到选择器
updateActiveConnectionOptions();
alert('连接成功');
updateStatus('已连接到 ' + connection.name);
} else {
alert('连接失败: ' + response.message);
updateStatus('连接失败: ' + response.message);
}
},
error: function(xhr, status, error) {
alert('连接失败: ' + error);
updateStatus('连接失败: ' + error);
}
});
}
// 查找会话
function findSessionByConnectionId(connectionId) {
return Object.entries(activeConnections).find(([sessionId, conn]) => conn.id === connectionId);
}
// 更新面板状态
function updatePanelStateWithConnection(sessionId, connection) {
// 更新左侧面板(如果是SFTP模式)
if (panelState.left.mode === 'sftp') {
panelState.left.sessionId = sessionId;
$(`#left-connection`).val(connection.id);
loadSftpCurrentPath('left', sessionId);
}
// 更新右侧面板(如果是SFTP模式)
if (panelState.right.mode === 'sftp') {
panelState.right.sessionId = sessionId;
$(`#right-connection`).val(connection.id);
loadSftpCurrentPath('right', sessionId);
}
}
// 更新活跃连接选项
function updateActiveConnectionOptions() {
Object.keys(panelState).forEach(panelId => {
if (panelState[panelId].mode === 'sftp') {
const select = $(`#${panelId}-connection`);
select.empty();
// 添加选项
if (Object.keys(activeConnections).length === 0) {
select.append('<option value="">请先连接</option>');
} else {
Object.entries(activeConnections).forEach(([sessionId, conn]) => {
const option = $('<option>');
option.val(sessionId);
option.text(`${conn.name} (${conn.username}@${conn.host})`);
select.append(option);
});
}
// 选中的是当前会话
if (panelState[panelId].sessionId !== 'local') {
select.val(panelState[panelId].sessionId);
}
}
});
}
// 连接切换
function onConnectionChange(panelId) {
const sessionId = $(`#${panelId}-connection`).val();
if (!sessionId) {
return;
}
panelState[panelId].sessionId = sessionId;
loadSftpCurrentPath(panelId, sessionId);
}
10.4 连接状态显示
10.4.1 连接状态指示器
<!-- 在面板头部添加状态指示器 -->
<div class="panel-header">
<select class="form-select form-select-sm panel-mode" id="left-mode" onchange="onModeChange('left')">
<option value="local">本地文件</option>
<option value="sftp">SFTP服务器</option>
</select>
<div class="connection-status" id="left-status" style="display:none;">
<span class="status-dot" data-status="connected"></span>
<span class="status-text">已连接</span>
</div>
<select class="form-select form-select-sm connection-select" id="left-connection" style="display:none;" onchange="onConnectionChange('left')">
</select>
</div>
10.4.2 状态样式
/* 连接状态 */
.connection-status {
display: flex;
align-items: center;
padding: 0 8px;
}
.status-dot {
width: 10px;
height: 10px;
border-radius: 50%;
display: inline-block;
margin-right: 5px;
}
.status-dot[data-status="connected"] {
background-color: #28a745;
}
.status-dot[data-status="disconnected"] {
background-color: #dc3545;
}
.status-dot[data-status="connecting"] {
background-color: #ffc107;
animation: pulse 1s infinite;
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
.status-text {
font-size: 12px;
color: #666;
}
.connection-status.connected .status-text {
color: #28a745;
}
.connection-status.disconnected .status-text {
color: #dc3545;
}
.connection-status.connecting .status-text {
color: #ffc107;
}
10.4.3 状态更新逻辑
// 更新连接状态显示
function updateConnectionStatus(panelId, status, text) {
const statusDiv = $(`#${panelId}-status`);
if (statusDiv.length === 0) {
return;
}
const statusDot = statusDiv.find('.status-dot');
const statusText = statusDiv.find('.status-text');
statusDiv.removeClass('connected disconnected connecting');
statusDiv.addClass(status);
statusDot.attr('data-status', status);
statusText.text(text);
}
// 显示连接状态
function showConnectionStatus(panelId, sessionId) {
const statusDiv = $(`#${panelId}-status`);
if (statusDiv.length === 0) {
return;
}
if (sessionId === 'local') {
statusDiv.hide();
} else {
statusDiv.show();
const connection = activeConnections[sessionId];
if (connection) {
updateConnectionStatus(panelId, 'connected', `已连接: ${connection.name}`);
} else {
updateConnectionStatus(panelId, 'disconnected', '未连接');
}
}
}
// 更新模式切换时的状态
function onModeChange(panelId) {
const mode = $(`#${panelId}-mode`).val();
panelState[panelId].mode = mode;
if (mode === 'local') {
switchToLocalMode(panelId);
showConnectionStatus(panelId, 'local');
} else {
switchToSftpMode(panelId);
}
}
10.5 断开连接
// 断开SFTP连接
function disconnectFromSftp(sessionId) {
updateStatus('正在断开连接...');
$.ajax({
url: '/api/connection/disconnect',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({sessionId: sessionId}),
success: function(response) {
if (response.success) {
// 从活跃连接中移除
delete activeConnections[sessionId];
// 更新面板状态
updatePanelsAfterDisconnect(sessionId);
// 更新连接选项
updateActiveConnectionOptions();
alert('已断开连接');
updateStatus('连接已断开');
} else {
alert('断开连接失败: ' + response.message);
}
},
error: handleError
});
}
// 更新断开连接后的面板
function updatePanelsAfterDisconnect(sessionId) {
Object.keys(panelState).forEach(panelId => {
if (panelState[panelId].sessionId === sessionId) {
panelState[panelId].sessionId = 'local';
panelState[panelId].mode = 'local';
$(`#${panelId}-mode`).val('local');
onModeChange(panelId);
}
});
}
// 在连接项中添加断开按钮
function loadConnectionList() {
const connectionList = $('#connection-list');
connectionList.empty();
if (savedConnections.length === 0) {
connectionList.html('<p class="text-muted text-center">暂无保存的连接</p>');
return;
}
savedConnections.forEach(conn => {
// 查找是否有活跃会话
const activeSession = findSessionByConnectionId(conn.id);
const item = $(`
<div class="connection-item" data-id="${conn.id}">
<div>
<div class="connection-name">${conn.name}</div>
<div class="connection-info">${conn.username}@${conn.host}:${conn.port}</div>
</div>
<div class="connection-actions">
${activeSession ?
'<button class="btn btn-sm btn-warning" onclick="disconnectFromSftp(\'' +
activeSession[0] + '\')">断开</button>' :
'<button class="btn btn-sm btn-primary" onclick="connectToSftp(' +
conn.id + ')">连接</button>'
}
<button class="btn btn-sm btn-danger" onclick="deleteConnection(${conn.id})">删除</button>
</div>
</div>
`);
connectionList.append(item);
});
}
实施步骤
-
更新app.js:添加模式切换逻辑
-
更新index.html:添加连接管理对话框和状态指示器
-
测试功能:
- 测试本地/SFTP模式切换
- 测试连接管理功能
- 测试多连接管理
- 测试连接状态显示
注意事项
- 状态同步:确保前后端状态同步
- 错误处理:连接失败时的友好提示
- 会话管理:正确管理多个SFTP会话
- 性能优化:避免频繁刷新连接列表
- 用户体验:提供清晰的状态反馈
下一步
完成模块10后,继续模块11:API接口设计规范