diff --git a/web/static/css/style.css b/web/static/css/style.css index 7c6eceae..aa2b88cd 100644 --- a/web/static/css/style.css +++ b/web/static/css/style.css @@ -8661,7 +8661,7 @@ header { .webshell-main { flex: 1; - min-width: 0; + min-width: 380px; display: flex; flex-direction: column; overflow: hidden; @@ -8675,6 +8675,7 @@ header { display: flex; flex-direction: column; min-height: 0; + min-width: 0; } .webshell-workspace-placeholder { @@ -8736,13 +8737,17 @@ header { .webshell-pane { display: none; - flex: 1; + flex: 1 1 0; min-height: 0; + min-width: 0; flex-direction: column; + overflow: hidden; + width: 100%; } .webshell-pane.active { display: flex; + width: 100%; } .webshell-terminal-toolbar { @@ -8847,20 +8852,28 @@ header { background: var(--bg-secondary); border-radius: 10px; border: 1px solid var(--border-color); + width: 100%; + min-width: 0; + box-sizing: border-box; } .webshell-file-toolbar label { - display: flex; + display: inline-flex; align-items: center; gap: 10px; font-weight: 500; color: var(--text-primary); - flex: 1; + flex: 1 1 200px; min-width: 0; } +.webshell-file-toolbar label span { + white-space: nowrap; + flex-shrink: 0; +} + .webshell-file-toolbar input.form-control { - min-width: 200px; + min-width: 0; flex: 1; max-width: 480px; padding: 8px 12px; @@ -8874,14 +8887,16 @@ header { border-radius: 8px; font-weight: 500; transition: all 0.2s ease; + flex-shrink: 0; } .webshell-file-breadcrumb { width: 100%; - flex: 1 1 100%; + flex: 0 0 100%; font-size: 0.875rem; color: var(--text-secondary); margin-bottom: 4px; + min-width: 0; } .webshell-file-breadcrumb a.webshell-breadcrumb-item { color: var(--accent-color); @@ -8893,8 +8908,9 @@ header { background: rgba(0, 102, 255, 0.08); } .webshell-file-filter { - min-width: 140px !important; - max-width: 200px !important; + min-width: 0 !important; + flex: 0 1 140px; + max-width: 200px; } .webshell-col-check { width: 36px; @@ -8909,12 +8925,15 @@ header { .webshell-file-list { flex: 1; + min-height: 0; + min-width: 0; overflow: auto; border: 1px solid var(--border-color); border-radius: 12px; padding: 0; background: var(--bg-primary); box-shadow: var(--shadow-sm); + width: 100%; } .webshell-file-table { diff --git a/web/static/js/webshell.js b/web/static/js/webshell.js index feb4829d..bc961404 100644 --- a/web/static/js/webshell.js +++ b/web/static/js/webshell.js @@ -2,6 +2,8 @@ const WEBSHELL_SIDEBAR_WIDTH_KEY = 'webshell_sidebar_width'; const WEBSHELL_DEFAULT_SIDEBAR_WIDTH = 360; +/** 右侧主区域(终端/文件管理)最小宽度,避免拖到中间时右边变形 */ +const WEBSHELL_MAIN_MIN_WIDTH = 380; const WEBSHELL_PROMPT = 'shell> '; let webshellConnections = []; let currentWebshellId = null; @@ -16,6 +18,8 @@ let webshellRunning = false; let webshellHistoryByConn = {}; let webshellHistoryIndex = -1; const WEBSHELL_HISTORY_MAX = 100; +// 清屏防重入:一次点击只执行一次(避免多次绑定或重复触发导致多个 shell>) +let webshellClearInProgress = false; // 从服务端(SQLite)拉取连接列表 function getWebshellConnections() { @@ -92,8 +96,31 @@ function wsT(key) { return fallback[key] || key; } +// 全局只绑定一次:清屏 = 销毁终端并重新创建,保证只出现一个 shell>(不依赖 xterm.clear(),避免某些环境下 clear 不生效或重复写入) +function bindWebshellClearOnce() { + if (window._webshellClearBound) return; + window._webshellClearBound = true; + document.body.addEventListener('click', function (e) { + var btn = e.target && (e.target.id === 'webshell-terminal-clear' ? e.target : e.target.closest ? e.target.closest('#webshell-terminal-clear') : null); + if (!btn || !webshellCurrentConn) return; + e.preventDefault(); + e.stopPropagation(); + if (webshellClearInProgress) return; + webshellClearInProgress = true; + try { + destroyWebshellTerminal(); + webshellLineBuffer = ''; + webshellHistoryIndex = -1; + initWebshellTerminal(webshellCurrentConn); + } finally { + setTimeout(function () { webshellClearInProgress = false; }, 100); + } + }, true); +} + // 初始化 WebShell 管理页面(从 SQLite 拉取连接列表) function initWebshellPage() { + bindWebshellClearOnce(); destroyWebshellTerminal(); webshellCurrentConn = null; currentWebshellId = null; @@ -125,7 +152,11 @@ function setWebshellSidebarWidth(px) { function applyWebshellSidebarWidth() { const sidebar = document.getElementById('webshell-sidebar'); - if (sidebar) sidebar.style.width = getWebshellSidebarWidth() + 'px'; + if (!sidebar) return; + const parentW = sidebar.parentElement ? sidebar.parentElement.offsetWidth : 0; + let w = getWebshellSidebarWidth(); + if (parentW > 0) w = Math.min(w, Math.max(260, parentW - WEBSHELL_MAIN_MIN_WIDTH)); + sidebar.style.width = w + 'px'; } function initWebshellSidebarResize() { @@ -137,8 +168,9 @@ function initWebshellSidebarResize() { function onMove(e) { const dx = e.clientX - startX; let w = Math.round(startW + dx); + const parentW = sidebar.parentElement ? sidebar.parentElement.offsetWidth : 800; const min = 260; - const max = Math.min(800, Math.floor((sidebar.parentElement && sidebar.parentElement.offsetWidth || 800) * 0.6)); + const max = Math.min(800, parentW - WEBSHELL_MAIN_MIN_WIDTH); w = Math.max(min, Math.min(max, w)); sidebar.style.width = w + 'px'; } @@ -322,15 +354,7 @@ function selectWebshell(id) { webshellFileListDir(webshellCurrentConn, pathInput.value || '.'); }); - // 清屏 - var clearBtn = document.getElementById('webshell-terminal-clear'); - if (clearBtn) clearBtn.addEventListener('click', function () { - if (webshellTerminalInstance) { - webshellTerminalInstance.clear(); - webshellLineBuffer = ''; - webshellTerminalInstance.write(WEBSHELL_PROMPT); - } - }); + // 清屏由 bindWebshellClearOnce 统一事件委托处理,此处不再绑定,避免重复绑定导致一次点击出现多个 shell> // 快捷命令:点击后执行并输出到终端 workspace.querySelectorAll('.webshell-quick-cmd').forEach(function (btn) { btn.addEventListener('click', function () {