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

+105
View File
@@ -0,0 +1,105 @@
/**
* ConfirmDialog — 基于 Promise 的确认对话框
*
* 用于替代 window.confirm(),支持异步等待结果
*
* @example
* const { confirm, ConfirmDialogComponent } = useConfirmDialog()
* const confirmed = await confirm({ title: '确认删除', message: '确定删除?', danger: true })
*
* // 在 JSX 中渲染:
* <ConfirmDialogComponent />
*/
import { useState, useCallback, useRef } from 'react'
import Modal from './Modal'
import { AlertTriangle } from 'lucide-react'
export interface ConfirmOptions {
/** 对话框标题 */
title: string
/** 对话框内容 */
message: string
/** 是否为危险操作(红色确认按钮) */
danger?: boolean
/** 确认按钮文本 */
confirmText?: string
/** 取消按钮文本 */
cancelText?: string
}
interface ConfirmState extends ConfirmOptions {
/** 是否显示 */
isOpen: boolean
/** resolve 回调 */
resolver: ((value: boolean) => void) | null
}
const initialState: ConfirmState = {
isOpen: false,
title: '',
message: '',
danger: false,
confirmText: '确认',
cancelText: '取消',
resolver: null,
}
/**
* ConfirmDialog 组件 + Hook
*
* 返回 confirm 函数和需要渲染的组件
*/
export function useConfirmDialog() {
const [state, setState] = useState<ConfirmState>(initialState)
const resolverRef = useRef<((value: boolean) => void) | null>(null)
const confirm = useCallback((options: ConfirmOptions): Promise<boolean> => {
return new Promise<boolean>((resolve) => {
resolverRef.current = resolve
setState({
...options,
isOpen: true,
danger: options.danger ?? false,
confirmText: options.confirmText ?? '确认',
cancelText: options.cancelText ?? '取消',
resolver: null,
})
})
}, [])
const handleClose = useCallback(() => {
setState((prev) => ({ ...prev, isOpen: false }))
resolverRef.current?.(false)
resolverRef.current = null
}, [])
const handleConfirm = useCallback(() => {
setState((prev) => ({ ...prev, isOpen: false }))
resolverRef.current?.(true)
resolverRef.current = null
}, [])
const ConfirmDialogComponent = (
<Modal
isOpen={state.isOpen}
title={state.title}
onConfirm={handleConfirm}
onClose={handleClose}
confirmText={state.confirmText}
cancelText={state.cancelText}
isDanger={state.danger}
>
<div className="flex items-start gap-3">
{state.danger && (
<AlertTriangle className="w-5 h-5 text-red-400 mt-0.5 flex-shrink-0" />
)}
<p className="text-gray-300">{state.message}</p>
</div>
</Modal>
)
return { confirm, ConfirmDialogComponent }
}
export default useConfirmDialog