移动端逻辑:终端滚动与上传按钮

This commit is contained in:
beibeizi committed 2026-03-16 16:09:56 +08:00
1 parent 6f5123c307
commit be92ddd55d
2 files changed
+85 -15

No files matched your search

+49 -14
View File
@@ -148,6 +148,7 @@ export function FileBrowser({ sessionId, isDark, currentDir, onFileSelect, netwo
const [dialogInput, setDialogInput] = React.useState("");
const [chmodRecursive, setChmodRecursive] = React.useState(false);
const [chmodLoading, setChmodLoading] = React.useState(false);
const fileInputRef = React.useRef<HTMLInputElement | null>(null);
const directoryCacheRef = React.useRef<Map<string, DirectoryCacheItem>>(new Map());
const treeRef = React.useRef<TreeNode[]>(tree);
const selectedPathRef = React.useRef<string>(selectedPath);
@@ -517,11 +518,11 @@ export function FileBrowser({ sessionId, isDark, currentDir, onFileSelect, netwo
[mapBatchFailures, uploadBatchWithRetry, t]
);
// 处理文件拖拽上传
const handleDrop = React.useCallback(async (e: React.DragEvent) => {
e.preventDefault();
setIsDragging(false);
const handleUploadFiles = React.useCallback(async (allFiles: Array<{ file: File; path: string }>) => {
if (allFiles.length === 0) {
push(t("没有可上传的文件", "No files to upload"));
return;
}
setUploading(true);
setUploadProgress([]);
setLastUploadFailures([]);
@@ -530,15 +531,6 @@ export function FileBrowser({ sessionId, isDark, currentDir, onFileSelect, netwo
setUploadStats(INITIAL_UPLOAD_STATS);
try {
// 读取所有文件(包括文件夹内容)
const dropSnapshot = snapshotDropPayload(e.dataTransfer);
const allFiles = await collectDroppedFiles(dropSnapshot);
if (allFiles.length === 0) {
push(t("没有可上传的文件", "No files to upload"));
return;
}
const totalBytes = calculateTotalBytes(allFiles);
setUploadStats({ current: 0, total: allFiles.length, uploadedBytes: 0, totalBytes });
@@ -639,6 +631,34 @@ export function FileBrowser({ sessionId, isDark, currentDir, onFileSelect, netwo
}
}, [sessionId, selectedPath, compress, push, loadDirectory, uploadFilesByBatch, t, language]);
// 处理文件拖拽上传
const handleDrop = React.useCallback(async (e: React.DragEvent) => {
e.preventDefault();
setIsDragging(false);
try {
// 读取所有文件(包括文件夹内容)
const dropSnapshot = snapshotDropPayload(e.dataTransfer);
const allFiles = await collectDroppedFiles(dropSnapshot);
await handleUploadFiles(allFiles);
} catch (err) {
push(err instanceof Error ? err.message : t("上传失败", "Upload failed"));
}
}, [handleUploadFiles, push, t]);
const handleFileInputChange = React.useCallback(async (event: React.ChangeEvent<HTMLInputElement>) => {
const filesList = event.target.files;
if (!filesList || filesList.length === 0) {
return;
}
const allFiles = Array.from(filesList).map((file) => ({
file,
path: file.webkitRelativePath || file.name,
}));
await handleUploadFiles(allFiles);
event.target.value = "";
}, [handleUploadFiles]);
const handleCancelUpload = React.useCallback(() => {
uploadCancelledRef.current = true;
}, []);
@@ -1158,6 +1178,14 @@ export function FileBrowser({ sessionId, isDark, currentDir, onFileSelect, netwo
>
{t("新建文件夹", "New folder")}
</button>
<button
type="button"
onClick={() => fileInputRef.current?.click()}
disabled={uploading}
className={`rounded-full px-3 py-2 ${uploading ? "opacity-50 cursor-not-allowed" : ""} ${isDark ? "bg-slate-800 text-slate-200" : "bg-white text-slate-700 border border-slate-200"}`}
>
{t("上传文件", "Upload")}
</button>
<button
type="button"
onClick={() => handleActionForFiles("touch", [])}
@@ -1205,6 +1233,13 @@ export function FileBrowser({ sessionId, isDark, currentDir, onFileSelect, netwo
>
{t("取消选择", "Clear")}
</button>
<input
ref={fileInputRef}
type="file"
multiple
className="hidden"
onChange={handleFileInputChange}
/>
</div>
) : null}
+36 -1
View File
@@ -27,6 +27,15 @@ export function TerminalMobile({ state, onBack }: TerminalMobileProps) {
term.focus();
}, [activeTab, syncTerminalSize, terminalInstance]);
const handleScroll = React.useCallback((direction: "up" | "down") => {
const term = terminalInstance.current;
if (!term) {
return;
}
const lines = direction === "up" ? -8 : 8;
term.scrollLines(lines);
}, [terminalInstance]);
const actions = [
{
key: "language",
@@ -132,13 +141,39 @@ export function TerminalMobile({ state, onBack }: TerminalMobileProps) {
<div className="flex-1 min-h-0 pb-16">
<div className={`${activeTab === "terminal" ? "flex" : "hidden"} h-full flex-col`}>
<div className="flex-1 p-3 min-h-0">
<div className="flex-1 p-3 min-h-0 relative">
<div
ref={state.terminalRef}
className={`h-full w-full rounded-lg border ${state.isDark ? "border-slate-800" : "border-slate-300 xterm-light"} ${state.sessionInfo?.enhanced_enabled ? "xterm-tmux-enhanced" : ""}`}
tabIndex={-1}
onClick={() => state.terminalInstance.current?.focus()}
/>
<div className="absolute right-4 bottom-4 flex flex-col gap-2">
<button
type="button"
onClick={() => handleScroll("up")}
className={`h-10 w-10 rounded-full border text-base ${
state.isDark
? "border-slate-700 bg-slate-900/90 text-slate-200"
: "border-slate-200 bg-white text-slate-700"
}`}
aria-label={state.t("向上滚动终端", "Scroll terminal up")}
>
↑
</button>
<button
type="button"
onClick={() => handleScroll("down")}
className={`h-10 w-10 rounded-full border text-base ${
state.isDark
? "border-slate-700 bg-slate-900/90 text-slate-200"
: "border-slate-200 bg-white text-slate-700"
}`}
aria-label={state.t("向下滚动终端", "Scroll terminal down")}
>
↓
</button>
</div>
</div>
</div>