v1.1.0: better-sqlite3→sql.js迁移 + 6项UI/代码修复

迁移:
- better-sqlite3(原生C++) → sql.js(WASM纯JS), 兼容Node 24
- database-service.ts: PreparedStatement包装器保持API兼容
- main.ts: initialize()改为async/await
- 移除@electron/rebuild和@types/better-sqlite3依赖
- 新增.npmrc阿里云镜像加速

UI/代码修复(任务1-6):
1. TerminalView: 清理111行调试日志(645→534行)
2. 设计系统: cyan/slate→accent-blue/dark-*颜色统一(FileRow/TransferItem/Breadcrumb/ContextMenu)
3. StatusBar: 重写接入实时连接状态,三区布局
4. SessionDialog: 私钥浏览按钮添加onClick处理
5. WelcomeView: 真实session数据替换硬编码假数据
6. SFTPView: emoji→Lucide图标(FolderOpen/Upload/Download/Trash2)

构建验证: tsc零错误 + vite build成功(Node 24.14.0)
This commit is contained in:
OpsXShell Dev committed 2026-04-18 15:40:19 +08:00
1 parent c5243df040
commit 53274e2291
14 files changed
+1784 -2141

No files matched your search

+3 -114
View File
@@ -12,51 +12,7 @@ import type { TerminalViewProps } from './terminal/types'
const logger = createLogger('TerminalView')
// 调试用 - 大量日志
const DEBUG = true
function debug(...args: any[]) {
if (DEBUG) {
console.log('[TerminalView]', ...args)
}
}
// 彩色高亮日志
function logHeader(msg: string) {
console.log(
`%c${msg}`,
'background: #41b883; color: #fff; padding: 4px 8px; border-radius: 4px; font-weight: bold;'
)
}
function logInfo(label: string, value: any) {
console.log(
`%c${label}:%c ${value}`,
'background: #35495e; color: #fff; padding: 2px 6px; border-radius: 3px;',
'color: #41b883; font-weight: bold;'
)
}
function logSuccess(msg: string) {
console.log(
`%c✓ ${msg}`,
'color: #41b883; font-weight: bold;'
)
}
function logError(msg: string) {
console.log(
`%c✗ ${msg}`,
'color: #ff4757; font-weight: bold;'
)
}
// 使用 logError 避免 TS 错误
if (false) logError('')
function logWarning(msg: string) {
console.log(
`%c⚠ ${msg}`,
'color: #ffa502; font-weight: bold;'
)
}
export default function TerminalView({ tabId, isActive = true }: TerminalViewProps) {
debug('>>> RENDER tabId:', tabId, 'isActive:', isActive)
const terminalRef = useRef<HTMLDivElement>(null)
const xtermRef = useRef<Terminal | null>(null)
const fitAddonRef = useRef<FitAddon | null>(null)
@@ -86,13 +42,10 @@ export default function TerminalView({ tabId, isActive = true }: TerminalViewPro
// 初始化 xterm
useEffect(() => {
debug('>>> useEffect[init] START - terminalRef.current:', !!terminalRef.current, 'xtermRef.current:', !!xtermRef.current)
// 修复:只检查 terminalRef,不检查 xtermRef
// 因为在严格模式下,组件会 unmount/remount,xtermRef 可能还存在
// 但监听器需要重新注册
if (!terminalRef.current) {
debug('>>> useEffect[init] EARLY RETURN - terminalRef is null')
return
}
@@ -107,8 +60,6 @@ export default function TerminalView({ tabId, isActive = true }: TerminalViewPro
// 如果 xterm 不存在,创建它
if (!xtermRef.current) {
debug('>>> useEffect[init] Creating Terminal...')
const term = new Terminal({
cursorBlink: true,
cursorStyle: 'block',
@@ -144,11 +95,9 @@ export default function TerminalView({ tabId, isActive = true }: TerminalViewPro
term.open(terminalRef.current)
fitAddon.fit()
debug('>>> Terminal opened, cols:', term.cols, 'rows:', term.rows)
xtermRef.current = term
fitAddonRef.current = fitAddon
debug('>>> xtermRef set:', !!xtermRef.current)
// 处理终端输入
term.onData((data) => {
@@ -160,8 +109,6 @@ export default function TerminalView({ tabId, isActive = true }: TerminalViewPro
window.electronAPI.sshShellWrite(shellIdRef.current, data)
}
})
} else {
debug('>>> useEffect[init] xterm already exists, skipping creation')
}
window.addEventListener('resize', handleResize)
@@ -182,18 +129,8 @@ export default function TerminalView({ tabId, isActive = true }: TerminalViewPro
handleShellDataRef.current = (_event: unknown, _connId: string, shellId: string, data: Buffer) => {
const text = data.toString('utf-8')
// ===== 大量调试日志 =====
logHeader('📝 SHELL DATA RECEIVED')
logInfo('shellId (received)', shellId)
logInfo('shellId (expected)', shellIdRef.current)
logInfo('xterm exists', !!xtermRef.current)
logInfo('data length', data.length)
logInfo('MATCH', shellId === shellIdRef.current)
console.log(' Data preview:', text.substring(0, 200).replace(/\n/g, '\\n'))
// 使用 ref 获取最新的 shellId,避免闭包问题
if (shellId === shellIdRef.current && xtermRef.current) {
logSuccess('WRITING TO XTERM')
// 处理数据,高亮 IP 地址(Last login 或 from IP 行)
let processedData = data
@@ -206,7 +143,6 @@ export default function TerminalView({ tabId, isActive = true }: TerminalViewPro
// 写入 xterm
xtermRef.current.write(processedData)
console.log('>>> DATA WRITTEN TO XTERM <<<')
// 更新传输统计
const connId = connectionIdRef.current
@@ -216,30 +152,16 @@ export default function TerminalView({ tabId, isActive = true }: TerminalViewPro
conn.stats.bytesTransferred += data.length
}
}
} else {
logWarning('SKIPPED: shellId mismatch or xterm not ready')
logInfo('Expected', shellIdRef.current)
logInfo('Received', shellId)
logInfo('xterm exists', !!xtermRef.current)
}
}
// 只注册一次监听器(防止严格模式下重复注册)
if (!listenerRegisteredRef.current) {
debug('>>> Registering onShellData listener')
const handler = (event: any, connectionId: string, shellId: string, data: Buffer) => {
console.log('=== IPC onShellData RAW received ===')
console.log(' connectionId:', connectionId)
console.log(' shellId:', shellId)
console.log(' data type:', typeof data)
console.log(' data length:', data?.length)
handleShellDataRef.current?.(event, connectionId, shellId, data)
}
window.electronAPI?.onShellData(handler)
listenerRegisteredRef.current = true
debug('>>> onShellData listener registered')
} else {
debug('>>> onShellData listener already registered, skipping')
}
return () => {
@@ -285,23 +207,14 @@ export default function TerminalView({ tabId, isActive = true }: TerminalViewPro
// 连接管理
const connect = useCallback(async () => {
logHeader('CONNECT() CALLED')
logInfo('tabId', tabId)
logInfo('isConnectingRef', isConnectingRef.current)
logInfo('hasConnectedRef', hasConnectedRef.current)
logInfo('xtermRef', !!xtermRef.current)
if (isConnectingRef.current) {
console.log('>>> CONNECT: Already connecting, skipping <<<')
logger.debug('[Terminal] Connection already in progress, skipping')
return
}
isConnectingRef.current = true
console.log('>>> isConnectingRef set to true <<<')
// 直接使用 getTabById,避免 useCallback 循环问题
const currentTab = getTabById(tabId)
console.log(' currentTab:', currentTab ? 'found' : 'NOT FOUND')
if (!currentTab) {
logger.debug('[Terminal] Tab not found:', tabId)
isConnectingRef.current = false
@@ -309,7 +222,6 @@ export default function TerminalView({ tabId, isActive = true }: TerminalViewPro
}
logger.debug('[Terminal] Starting connection for tab:', tabId, 'sessionId:', currentTab.sessionId)
console.log('>>> Starting connection for tab:', tabId, 'sessionId:', currentTab.sessionId)
// 检查是否已有连接
if (connectionIdRef.current) {
@@ -364,12 +276,9 @@ export default function TerminalView({ tabId, isActive = true }: TerminalViewPro
if (!config) {
throw new Error('无可用连接信息')
}
logSuccess(`Config ready: ${config.host}:${config.port} (${config.username})`)
// 创建连接
console.log('>>> Calling createConnection...')
const result = await getConnectionStore().createConnection(tabId, config)
console.log('>>> createConnection result:', result)
if (!result.success || !result.connectionId) {
throw new Error(result.error || '连接失败')
@@ -377,25 +286,20 @@ export default function TerminalView({ tabId, isActive = true }: TerminalViewPro
const connectionId = result.connectionId
connectionIdRef.current = connectionId
logSuccess(`connectionId set: ${connectionId}`)
// 创建 Shell - 先生成前端 shellId,确保数据不会丢失
const pendingShellId = `shell-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
logger.debug('[Terminal] Pre-assigning shellId:', pendingShellId)
shellIdRef.current = pendingShellId
logSuccess(`shellIdRef set to: ${pendingShellId}`)
logger.debug('[Terminal] Creating shell for connection:', connectionId, 'with shellId:', pendingShellId)
console.log('>>> Calling sshCreateShell...')
const shellResult = await window.electronAPI?.sshCreateShell(connectionId, {
cols: xtermRef.current?.cols || 80,
rows: xtermRef.current?.rows || 24,
}, pendingShellId)
console.log('>>> sshCreateShell result:', shellResult)
if (!shellResult?.success || !shellResult.shellId) {
logger.error('[Terminal] Shell creation failed:', shellResult?.error)
console.error('>>> Shell creation failed:', shellResult?.error)
shellIdRef.current = null
await getConnectionStore().disconnectConnection(connectionId, 'shell creation failed')
throw new Error(shellResult?.error || '创建终端失败')
@@ -404,7 +308,6 @@ export default function TerminalView({ tabId, isActive = true }: TerminalViewPro
// shellId 应该和 pendingShellId 相同(由后端确认)
const confirmedShellId = shellResult.shellId
logger.debug('[Terminal] Shell confirmed with ID:', confirmedShellId)
logSuccess(`Shell confirmed: ${confirmedShellId}`)
// shellIdRef 已经是 confirmedShellId(等于 pendingShellId)
getConnectionStore().addShell(connectionId, confirmedShellId)
@@ -496,23 +399,15 @@ export default function TerminalView({ tabId, isActive = true }: TerminalViewPro
// 初始连接
useEffect(() => {
logHeader('INITIAL CONNECT useEffect')
logInfo('hasConnectedRef', hasConnectedRef.current)
logInfo('isActive', isActive)
if (!hasConnectedRef.current && isActive) {
// 检查是否有快照
const hasSnapshot = snapshotStore.hasSnapshot(tabId)
logInfo('hasSnapshot', hasSnapshot)
if (hasSnapshot && isActive) {
// 先恢复快照
restoreSnapshot()
}
logSuccess('Calling connect() from initial useEffect')
connect()
} else {
logWarning('SKIPPED: hasConnectedRef=true or isActive=false')
}
}, []) // 只在挂载时执行
@@ -564,18 +459,12 @@ export default function TerminalView({ tabId, isActive = true }: TerminalViewPro
// 组件卸载时清理监听器
useEffect(() => {
logHeader('📺 TerminalView MOUNT')
logInfo('tabId', tabId)
return () => {
logHeader('📺 TerminalView UNMOUNT')
logInfo('tabId', tabId)
// 重置连接状态,防止严格模式下 remount 时状态混乱
isConnectingRef.current = false
listenerRegisteredRef.current = false // 重置监听器注册标记
logSuccess('Reset isConnectingRef and listenerRegisteredRef to false')
// 组件真正卸载时移除所有监听器
window.electronAPI?.removeAllListeners('ssh:shellData')
logSuccess('Removed all ssh:shellData listeners')
}
}, [])
@@ -598,7 +487,7 @@ export default function TerminalView({ tabId, isActive = true }: TerminalViewPro
</div>
<button
onClick={resumeConnection}
className="px-4 py-2 bg-cyan-600 hover:bg-cyan-500 text-white rounded transition-colors mr-2"
className="px-4 py-2 bg-accent-blue hover:bg-blue-500 text-white rounded transition-colors mr-2"
>
恢复连接
</button>
@@ -620,7 +509,7 @@ export default function TerminalView({ tabId, isActive = true }: TerminalViewPro
<div className="text-gray-400 text-sm mb-4">{statusMessage}</div>
<button
onClick={handleReconnect}
className="px-4 py-2 bg-cyan-600 hover:bg-cyan-500 text-white rounded transition-colors"
className="px-4 py-2 bg-accent-blue hover:bg-blue-500 text-white rounded transition-colors"
>
重新连接
</button>
@@ -631,7 +520,7 @@ export default function TerminalView({ tabId, isActive = true }: TerminalViewPro
{/* 连接中状态 */}
{connectionState === 'connecting' && (
<div className="absolute inset-0 bg-dark-900/50 flex items-center justify-center pointer-events-none z-10">
<div className="text-cyan-400 animate-pulse">连接中...</div>
<div className="text-accent-blue animate-pulse">连接中...</div>
</div>
)}