feat(v1.2): 体验升级 — 快捷键系统 + 标签拖拽 + SFTP拖拽上传 + 组件拆分 + 响应式

任务1: 全局快捷键管理器 — useKeyboardShortcuts hook
  Ctrl+Tab/Shift+Tab标签切换, Ctrl+1~9跳转, F11全屏, Ctrl+Shift+N新建标签

任务2: 标签拖拽排序 — tabStore.moveTab + TabBar HTML5 DnD
  不可变更新, 拖拽视觉反馈(opacity+指示线), 活跃标签底部accent-blue指示线

任务3: SFTP拖拽上传 + useSFTP调试清理
  FileListPanel拖拽覆盖层(Upload图标+虚线边框), 5个调试函数→createLogger,
  disconnect()正确调用IPC, 删除mock data

任务4: SFTP右键菜单完善 — 重命名/复制路径/属性/权限
  FilePropertiesDialog+ChmodDialog, sftpStat/sftpChmod IPC全栈实现

任务5-6: 侧边栏分组折叠动画 + 宽度可拖拽调整
  Grid 0fr→1fr折叠动画, ChevronRight旋转, resize handle 180~400px, settings记忆

任务7: 响应式布局 + 视觉优化V1~V5
  useMediaQuery hook, <800px侧边栏overlay模式, SFTP垂直堆叠, WelcomeView响应式grid

任务8: Loading/Skeleton骨架屏
  Skeleton/SkeletonLine/SkeletonCircle/SkeletonBlock + FileListSkeleton/SessionTreeSkeleton/TabContentSkeleton

任务9: 空状态设计
  EmptyState通用组件, SessionTree/FileListPanel/WelcomeView/SFTPView空状态引导

任务10: TerminalView组件拆分重构 711行→285行
  useTerminalConnection(300行) + useTerminalSearch(123行)
  + TerminalOverlay(98行) + TerminalSearchBar(74行)

33 files changed, 2682 insertions(+), 872 deletions(-)
This commit is contained in:
OpsXShell Dev committed 2026-04-18 18:35:49 +08:00
1 parent d344900118
commit caba5f4c0d
33 files changed
+2683 -873

No files matched your search

+121
View File
@@ -0,0 +1,121 @@
/**
* useKeyboardShortcuts - 全局快捷键管理 Hook
*
* 在 App.tsx 挂载,处理全局键盘快捷键:
* - Ctrl+Tab / Ctrl+Shift+Tab: 切换标签
* - Ctrl+1~9: 跳转到指定索引标签
* - Ctrl+W: 关闭当前标签
* - F11: 全屏切换
* - Ctrl+Shift+N: 新建标签
*/
import { useEffect } from 'react'
import { useTabStore } from '../stores/tabStore'
import { createLogger } from '../utils/logger'
const logger = createLogger('KeyboardShortcuts')
/** 检查事件目标是否为输入元素 */
function isInputElement(e: KeyboardEvent): boolean {
const target = e.target as HTMLElement
if (!target) return false
const tagName = target.tagName.toLowerCase()
return (
tagName === 'input' ||
tagName === 'textarea' ||
tagName === 'select' ||
target.isContentEditable
)
}
/**
* 判断快捷键是否应该在输入框中触发
* Ctrl+Tab/Ctrl+Shift+Tab 等导航类快捷键始终生效
*/
function shouldSkipInInput(e: KeyboardEvent): boolean {
// Ctrl+Tab 系列:始终生效(浏览器级别标签切换)
if ((e.ctrlKey || e.metaKey) && e.key === 'Tab') return false
// F11:始终生效
if ( e.key === 'F11') return false
// 其他快捷键在输入框中不触发
return true
}
export function useKeyboardShortcuts(): void {
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
const ctrlOrMeta = e.ctrlKey || e.metaKey
// ---- Ctrl+Tab: 切换到下一个标签 ----
if (ctrlOrMeta && e.key === 'Tab' && !e.shiftKey) {
e.preventDefault()
const { switchToNextTab } = useTabStore.getState()
switchToNextTab()
return
}
// ---- Ctrl+Shift+Tab: 切换到上一个标签 ----
if (ctrlOrMeta && e.key === 'Tab' && e.shiftKey) {
e.preventDefault()
const { switchToPrevTab } = useTabStore.getState()
switchToPrevTab()
return
}
// ---- Ctrl+1~9: 跳转到指定索引标签 ----
if (ctrlOrMeta && !e.shiftKey && !e.altKey) {
const num = parseInt(e.key, 10)
if (num >= 1 && num <= 9) {
e.preventDefault()
const { activateTabByIndex } = useTabStore.getState()
activateTabByIndex(num - 1) // 0-indexed
return
}
}
// ---- F11: 全屏切换 ----
if (e.key === 'F11') {
e.preventDefault()
// Electron 渲染进程通过 IPC 请求全屏切换
if (window.electronAPI?.settingsSet) {
window.electronAPI.settingsGet('fullscreen').then((res: any) => {
const isFullscreen = res?.value === true
window.electronAPI?.settingsSet('fullscreen', !isFullscreen)
}).catch(() => {
logger.warn('Failed to toggle fullscreen')
})
}
return
}
// ---- 以下快捷键在输入框中不触发 ----
if (isInputElement(e) && shouldSkipInInput(e)) {
return
}
// ---- Ctrl+W: 关闭当前标签 ----
if (ctrlOrMeta && e.key === 'w') {
e.preventDefault()
const { tabs, activeTabId, closeTab } = useTabStore.getState()
if (activeTabId && tabs.length > 1) {
logger.debug('Ctrl+W: closing tab', activeTabId)
closeTab(activeTabId)
}
return
}
// ---- Ctrl+Shift+N: 新建标签 ----
if (ctrlOrMeta && e.shiftKey && (e.key === 'n' || e.key === 'N')) {
e.preventDefault()
const { addTab } = useTabStore.getState()
addTab({ title: '新标签', type: 'welcome' })
return
}
}
window.addEventListener('keydown', handleKeyDown)
return () => window.removeEventListener('keydown', handleKeyDown)
}, [])
}
export default useKeyboardShortcuts