Initial commit

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
liu
2026-02-03 10:10:11 +08:00
commit 14289beb66
45 changed files with 15479 additions and 0 deletions

View File

@@ -0,0 +1,714 @@
# 模块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
- 标准间距16px1rem
- 组件内边距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状态管理
```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 模式切换逻辑
```javascript
// 模式切换
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 连接列表加载
```javascript
// 加载已保存的连接列表
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 连接管理对话框
```javascript
// 显示连接管理对话框
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连接
```javascript
// 连接到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 连接状态指示器
```html
<!-- 在面板头部添加状态指示器 -->
<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 状态样式
```css
/* 连接状态 */
.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 状态更新逻辑
```javascript
// 更新连接状态显示
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 断开连接
```javascript
// 断开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);
});
}
```
## 实施步骤
1. **更新app.js**:添加模式切换逻辑
2. **更新index.html**:添加连接管理对话框和状态指示器
3. **测试功能**
- 测试本地/SFTP模式切换
- 测试连接管理功能
- 测试多连接管理
- 测试连接状态显示
## 注意事项
1. **状态同步**:确保前后端状态同步
2. **错误处理**:连接失败时的友好提示
3. **会话管理**正确管理多个SFTP会话
4. **性能优化**:避免频繁刷新连接列表
5. **用户体验**:提供清晰的状态反馈
## 下一步
完成模块10后继续模块11API接口设计规范