feat: v0.3.0 版本 - SSH终端、SFTP、端口转发功能实现

核心功能:
- SSH终端: 密码/密钥认证、XTerm.js终端、多主题配色、IP高亮
- SFTP管理: 双栏文件管理、上传下载、传输队列
- 端口转发: 本地/远程转发、SOCKS5动态代理、规则管理

架构改进:
- 拆分大型组件 (SFTPView/TabBar/Sidebar)
- 添加统一日志和错误处理
- 修复React严格模式下的终端黑屏问题

UI更新:
- 新增端口转发视图
- 更新UI预览文档
- 左侧栏右键菜单支持SFTP快捷打开
This commit is contained in:
Logan.Li committed 2026-04-06 01:20:47 +08:00
1 parent cf1c054381
commit 484d850abd
105 files changed
+25330 -64

No files matched your search

+117
View File
@@ -0,0 +1,117 @@
/**
* 模态框组件
*
* 统一的对话框 UI
*/
import { type ReactNode, useEffect } from 'react'
import { X } from 'lucide-react'
import Button from './Button'
interface ModalProps {
/** 是否显示 */
isOpen: boolean
/** 标题 */
title: string
/** 内容 */
children: ReactNode
/** 确认按钮文本 */
confirmText?: string
/** 取消按钮文本 */
cancelText?: string
/** 确认回调 */
onConfirm?: () => void
/** 关闭回调 */
onClose: () => void
/** 是否显示确认按钮 */
showConfirm?: boolean
/** 是否危险操作 */
isDanger?: boolean
/** 加载状态 */
loading?: boolean
}
/**
* 模态框组件
*
* @example
* <Modal
* isOpen={isOpen}
* title="确认删除"
* onConfirm={handleDelete}
* onClose={() => setIsOpen(false)}
* isDanger
* >
* 确定要删除此会话吗?
* </Modal>
*/
export default function Modal({
isOpen,
title,
children,
confirmText = '确认',
cancelText = '取消',
onConfirm,
onClose,
showConfirm = true,
isDanger = false,
loading = false,
}: ModalProps) {
// ESC 键关闭
useEffect(() => {
const handleEsc = (e: KeyboardEvent) => {
if (e.key === 'Escape' && isOpen) {
onClose()
}
}
window.addEventListener('keydown', handleEsc)
return () => window.removeEventListener('keydown', handleEsc)
}, [isOpen, onClose])
if (!isOpen) return null
return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
{/* 遮罩 */}
<div
className="absolute inset-0 bg-black/60 backdrop-blur-sm"
onClick={onClose}
/>
{/* 内容 */}
<div className="relative bg-dark-800 rounded-xl border border-dark-700 shadow-2xl w-full max-w-md">
{/* 头部 */}
<div className="flex items-center justify-between px-6 py-4 border-b border-dark-700">
<h3 className="text-lg font-semibold text-gray-200">{title}</h3>
<button
onClick={onClose}
className="p-1 hover:bg-dark-700 rounded-lg transition-colors"
>
<X className="w-5 h-5 text-gray-400" />
</button>
</div>
{/* 内容 */}
<div className="px-6 py-4">
{children}
</div>
{/* 底部按钮 */}
<div className="flex justify-end gap-3 px-6 py-4 border-t border-dark-700">
<Button variant="ghost" onClick={onClose}>
{cancelText}
</Button>
{showConfirm && (
<Button
variant={isDanger ? 'danger' : 'primary'}
onClick={onConfirm}
loading={loading}
>
{confirmText}
</Button>
)}
</div>
</div>
</div>
)
}