+
+
+
+
+
+
+ {((selectedFiles instanceof Set && selectedFiles.size > 0) ||
+ selectedFiles === 'all') && (
+ <>
+ }
+ >
+ (
+ {selectedFiles instanceof Set ? selectedFiles.size : files.length}
+ )
+
+
+ >
+ )}
+
+ {currentPath.split('/').map((part, index, parts) => (
+ {
+ const newPath = parts.slice(0, index + 1).join('/')
+ navigate(`/file_manager#${encodeURIComponent(newPath)}`)
+ }}
+ >
+ {part}
+
+ ))}
+
+ setJumpPath(e.target.value)}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter' && jumpPath.trim() !== '') {
+ navigate(`/file_manager#${encodeURIComponent(jumpPath.trim())}`)
+ }
+ }}
+ className="ml-auto w-64"
+ />
+
+
+
{
+ setRenamingFile(name)
+ setNewFileName(name)
+ setIsRenameModalOpen(true)
+ }}
+ onMoveRequest={handleMoveClick}
+ onCopyPath={handleCopyPath}
+ onDelete={handleDelete}
+ />
+
+ setEditingFile(null)}
+ onSave={handleSave}
+ onContentChange={(newContent) =>
+ setEditingFile((prev) =>
+ prev ? { ...prev, content: newContent ?? '' } : null
+ )
+ }
+ />
+
+ setNewFileName(e.target.value)}
+ onClose={() => setIsCreateModalOpen(false)}
+ onCreate={handleCreate}
+ />
+
+ setNewFileName(e.target.value)}
+ onClose={() => setIsRenameModalOpen(false)}
+ onRename={handleRename}
+ />
+
+ 0
+ ? `${selectedFiles.size} 个项目`
+ : renamingFile
+ }
+ onClose={() => setIsMoveModalOpen(false)}
+ onMove={() =>
+ selectedFiles instanceof Set && selectedFiles.size > 0
+ ? handleBatchMove()
+ : handleMove(renamingFile)
+ }
+ onSelect={(dir) => setMoveTargetPath(dir)} // 替换原有 onTargetChange
+ />
+
+ )
+}
diff --git a/napcat.webui/src/pages/dashboard/terminal.tsx b/napcat.webui/src/pages/dashboard/terminal.tsx
new file mode 100644
index 00000000..c95e0ee1
--- /dev/null
+++ b/napcat.webui/src/pages/dashboard/terminal.tsx
@@ -0,0 +1,171 @@
+import {
+ DndContext,
+ DragEndEvent,
+ PointerSensor,
+ closestCenter,
+ useSensor,
+ useSensors
+} from '@dnd-kit/core'
+import {
+ SortableContext,
+ arrayMove,
+ horizontalListSortingStrategy
+} from '@dnd-kit/sortable'
+import { Button } from '@heroui/button'
+import { useEffect, useState } from 'react'
+import toast from 'react-hot-toast'
+import { IoAdd, IoClose } from 'react-icons/io5'
+
+import { TabList, TabPanel, Tabs } from '@/components/tabs'
+import { SortableTab } from '@/components/tabs/sortable_tab.tsx'
+import { TerminalInstance } from '@/components/terminal/terminal-instance'
+
+import terminalManager from '@/controllers/terminal_manager'
+
+interface TerminalTab {
+ id: string
+ title: string
+}
+
+export default function TerminalPage() {
+ const [tabs, setTabs] = useState