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

+2
View File
@@ -12,6 +12,7 @@ export interface AppSettings {
// 外观
theme: 'dark' | 'light' | 'system'
language: 'zh-CN' | 'en-US'
sidebarWidth: number
// 终端
fontFamily: string
@@ -42,6 +43,7 @@ const defaultSettings: AppSettings = {
showNotifications: false,
theme: 'dark',
language: 'zh-CN',
sidebarWidth: 256,
fontFamily: 'JetBrains Mono',
fontSize: 14,
terminalTheme: 'dracula',
+52
View File
@@ -63,7 +63,11 @@ interface TabState {
closeLeftTabs: (tabId: string) => Tab[]
closeAllTabs: () => Tab[]
activateTab: (tabId: string) => void
activateTabByIndex: (index: number) => void
switchToNextTab: () => void
switchToPrevTab: () => void
updateTabTitle: (tabId: string, title: string) => void
moveTab: (fromIndex: number, toIndex: number) => void
getTabById: (tabId: string) => Tab | undefined
}
@@ -230,6 +234,31 @@ export const useTabStore = create<TabState>()(
activeTabId: tabId,
})
},
activateTabByIndex: (index) => {
const { tabs } = get()
if (index < 0 || index >= tabs.length) return
const targetTab = tabs[index]
if (targetTab) {
get().activateTab(targetTab.id)
}
},
switchToNextTab: () => {
const { tabs, activeTabId } = get()
if (tabs.length <= 1) return
const currentIndex = tabs.findIndex(t => t.id === activeTabId)
const nextIndex = (currentIndex + 1) % tabs.length
get().activateTab(tabs[nextIndex].id)
},
switchToPrevTab: () => {
const { tabs, activeTabId } = get()
if (tabs.length <= 1) return
const currentIndex = tabs.findIndex(t => t.id === activeTabId)
const prevIndex = (currentIndex - 1 + tabs.length) % tabs.length
get().activateTab(tabs[prevIndex].id)
},
updateTabTitle: (tabId, title) => {
const { tabs } = get()
@@ -239,6 +268,29 @@ export const useTabStore = create<TabState>()(
set({ tabs: updatedTabs })
},
moveTab: (fromIndex, toIndex) => {
const { tabs, activeTabId } = get()
if (
fromIndex === toIndex ||
fromIndex < 0 ||
toIndex < 0 ||
fromIndex >= tabs.length ||
toIndex >= tabs.length
) {
return
}
// Immutable reorder
const newTabs = [...tabs]
const [moved] = newTabs.splice(fromIndex, 1)
newTabs.splice(toIndex, 0, moved)
// Re-apply isActive based on activeTabId
const reordered = newTabs.map(t => ({
...t,
isActive: t.id === activeTabId,
}))
set({ tabs: reordered })
},
getTabById: (tabId) => {
return get().tabs.find(t => t.id === tabId)
},