fix: 修复SSH终端还原布局溢出

- 约束xterm容器最小宽度并隐藏旧画布溢出
- 在布局绘制前同步终端与远端PTY尺寸
This commit is contained in:
Logan.Li committed 2026-07-26 18:03:33 +08:00
1 parent 198139648d
commit 82b53c043b
1 file changed
+20 -8
@@ -1,4 +1,4 @@
import { useState, useRef, useEffect } from "react"; import { useState, useRef, useEffect, useLayoutEffect } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import {
Dialog, Dialog,
@@ -173,11 +173,12 @@ export function TerminalDialog({
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [open, serverId, authReady]); }, [open, serverId, authReady]);
// 容器尺寸变化时 fit 并同步远端 PTY // Logan.Li // 在浏览器绘制前完成 fit,避免最大化还原时短暂显示旧宽度画布。 // Logan.Li
useEffect(() => { useLayoutEffect(() => {
if (!open || !authReady) return; if (!open || !authReady) return;
let cleanup: (() => void) | undefined; let cleanup: (() => void) | undefined;
let fitFrame = 0;
let retry = 0; let retry = 0;
const attach = () => { const attach = () => {
@@ -190,15 +191,23 @@ export function TerminalDialog({
return; return;
} }
cleanup = attachTerminalResize(container, fit, term, ws); cleanup = attachTerminalResize(container, fit, term, ws);
const applyFit = () => {
try { try {
fitTerminal(fit, term, ws); fitTerminal(fit, term, ws);
} catch { } catch {
/* noop */ /* noop */
} }
}; };
applyFit();
// 下一帧再校准一次,兼容 Radix Dialog 全屏样式刚提交的场景。 // Logan.Li
fitFrame = requestAnimationFrame(applyFit);
};
attach(); attach();
return () => cleanup?.(); return () => {
if (fitFrame) cancelAnimationFrame(fitFrame);
cleanup?.();
};
}, [open, authReady, fullscreen]); }, [open, authReady, fullscreen]);
return ( return (
@@ -208,7 +217,7 @@ export function TerminalDialog({
// 禁用 Radix Dialog 默认的 ESC 关闭:终端内 vi/top 等会用到 ESC,关闭只能点头部 X 按钮 // Logan.Li // 禁用 Radix Dialog 默认的 ESC 关闭:终端内 vi/top 等会用到 ESC,关闭只能点头部 X 按钮 // Logan.Li
onEscapeKeyDown={(e) => e.preventDefault()} onEscapeKeyDown={(e) => e.preventDefault()}
className={cn( className={cn(
"bg-[var(--dg-bg-secondary)] border-[var(--dg-border)] p-0 gap-0", "min-w-0 overflow-hidden bg-[var(--dg-bg-secondary)] border-[var(--dg-border)] p-0 gap-0",
// 禁用 CSS 过渡:终端窗口应该即时缩放,不需要动画 // 禁用 CSS 过渡:终端窗口应该即时缩放,不需要动画
"!duration-0 !transition-none", "!duration-0 !transition-none",
fullscreen fullscreen
@@ -239,7 +248,7 @@ export function TerminalDialog({
</div> </div>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<button <button
onClick={() => setFullscreen(!fullscreen)} onClick={() => setFullscreen((current) => !current)}
className="p-1.5 rounded hover:bg-[var(--dg-bg-hover)] text-[var(--dg-text-secondary)]" className="p-1.5 rounded hover:bg-[var(--dg-bg-hover)] text-[var(--dg-text-secondary)]"
> >
{fullscreen ? ( {fullscreen ? (
@@ -301,10 +310,13 @@ export function TerminalDialog({
{/* 终端容器 */} {/* 终端容器 */}
{authReady && ( {authReady && (
<div <div
className="relative bg-[#0a0e14]" className="relative min-w-0 overflow-hidden bg-[#0a0e14]"
style={{ height: fullscreen ? "calc(100vh - 49px)" : "520px" }} style={{ height: fullscreen ? "calc(100vh - 49px)" : "520px" }}
> >
<div ref={containerRef} className="xterm-terminal-host h-full" /> <div
ref={containerRef}
className="xterm-terminal-host h-full min-w-0"
/>
<SessionWatermark className="opacity-60" /> <SessionWatermark className="opacity-60" />
<TerminalLoadingOverlay <TerminalLoadingOverlay
show={!hasOutput} show={!hasOutput}