feat(v1.1): Toast通知系统 + 自定义对话框 + 终端搜索 + SFTP真实实现 + 标签持久化

任务7: Toast通知系统 — ToastProvider+4类型(success/error/warning/info)+右上角堆叠动画
任务8: 替换alert/confirm/prompt — ConfirmDialog+PromptDialog+useToast替换7处原生对话框
任务9: 终端搜索 — xterm SearchAddon Ctrl+F搜索栏(上一个/下一个/关闭)
任务10: SFTP上传/删除 — useSFTP.ts upload/delete方法实现
任务11: 会话恢复 — tabStore持久化到localStorage,启动恢复tabs

14 files changed, 1218 insertions(+), 241 deletions(-)
This commit is contained in:
OpsXShell Dev committed 2026-04-18 16:21:10 +08:00
1 parent 53274e2291
commit d344900118
14 files changed
+1224 -247

No files matched your search

+40 -3
View File
@@ -8,6 +8,7 @@ import { useState, useEffect } from 'react'
import { useTabStore } from '../stores/tabStore'
import { useConnectionPool } from '../stores/connection'
import { ArrowRightLeft, Globe, Server, Plus, Trash2, Play, Square } from 'lucide-react'
import { useConfirmDialog } from './common/ConfirmDialog'
interface PortForwardViewProps {
tabId: string
@@ -25,10 +26,12 @@ interface ForwardRule {
}
export default function PortForwardView({ tabId, isActive: _isActive = true }: PortForwardViewProps) {
const { tabs } = useTabStore()
const { tabs, updateTab } = useTabStore()
const pool = useConnectionPool()
const { confirm, ConfirmDialogComponent } = useConfirmDialog()
const currentTab = tabs.find(t => t.id === tabId)
const needsReconnect = currentTab?.needsReconnect
const connectionId = currentTab?.connectionInstance?.connectionId
const connection = connectionId ? pool.getConnection(connectionId) : null
@@ -146,14 +149,46 @@ export default function PortForwardView({ tabId, isActive: _isActive = true }: P
}
// Delete rule
const deleteRule = (ruleId: string) => {
if (confirm('确定删除此转发规则?')) {
const deleteRule = async (ruleId: string) => {
const confirmed = await confirm({
title: '确认删除',
message: '确定删除此转发规则?',
danger: true,
confirmText: '删除',
})
if (confirmed) {
saveRules(rules.filter(r => r.id !== ruleId))
}
}
return (
<>
<div className="h-full flex flex-col bg-dark-900">
{/* 会话恢复 - 需要重新连接覆盖层 */}
{needsReconnect && currentTab?.sessionId && (
<div className="absolute inset-0 bg-dark-900/90 flex items-center justify-center z-50">
<div className="text-center">
<div className="w-16 h-16 bg-accent-blue/20 rounded-2xl flex items-center justify-center mx-auto mb-4">
<ArrowRightLeft className="w-8 h-8 text-accent-blue" />
</div>
<div className="text-gray-200 mb-2 text-lg">会话已恢复</div>
<div className="text-gray-400 text-sm mb-6">
上次关闭时的端口转发标签已恢复<br />
需要重新建立 SSH 连接才能使用
</div>
<button
onClick={() => {
updateTab(tabId, { needsReconnect: false })
// 刷新页面或触发连接逻辑
}}
className="px-6 py-2.5 bg-accent-blue hover:bg-blue-500 text-white rounded-lg transition-colors"
>
重新连接
</button>
</div>
</div>
)}
{/* Header */}
<div className="flex items-center justify-between px-6 py-4 border-b border-dark-700">
<div className="flex items-center gap-3">
@@ -383,5 +418,7 @@ export default function PortForwardView({ tabId, isActive: _isActive = true }: P
</div>
)}
</div>
{ConfirmDialogComponent}
</>
)
}