diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b2d5f4821..78c1b76e0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -52,6 +52,11 @@ jobs: with: node-version: '20' + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 9 + - name: Install Linux dependencies if: matrix.platform == 'ubuntu-22.04' run: | @@ -72,7 +77,7 @@ jobs: cache-all-crates: true - name: Install frontend dependencies - run: npm ci + run: pnpm install --frozen-lockfile - name: Build Tauri app uses: tauri-apps/tauri-action@v0 diff --git a/src/components/terminal/TerminalPanel.tsx b/src/components/terminal/TerminalPanel.tsx index 1b142334f..aaf6e8ef4 100644 --- a/src/components/terminal/TerminalPanel.tsx +++ b/src/components/terminal/TerminalPanel.tsx @@ -172,7 +172,14 @@ export function TerminalPanel({ return true; }, []); + // 使用 ref 存储回调,避免回调变化导致终端重建 + const onStatusChangeRef = useRef(onStatusChange); + useEffect(() => { + onStatusChangeRef.current = onStatusChange; + }, [onStatusChange]); + // 当 sessionId 变化时,创建 TermWrap + // 注意:只依赖 sessionId 和 handleTerminalKeydown,避免回调变化导致终端重建 useEffect(() => { const container = connectElemRef.current; if (!container || !sessionId) { @@ -183,10 +190,9 @@ export function TerminalPanel({ return; } - // 销毁旧的 TermWrap + // 如果已有 TermWrap 且 sessionId 相同,不重建 if (termWrapRef.current) { - termWrapRef.current.dispose(); - termWrapRef.current = null; + return; } // 清空容器 @@ -194,7 +200,7 @@ export function TerminalPanel({ // 创建新的 TermWrap const termWrap = new TermWrap(sessionId, container, { - onStatusChange: (status) => onStatusChange?.(status), + onStatusChange: (status) => onStatusChangeRef.current?.(status), themeName: loadThemePreference(), fontSize: loadFontSizePreference(), keydownHandler: handleTerminalKeydown, @@ -218,7 +224,7 @@ export function TerminalPanel({ termWrap.dispose(); rszObs.disconnect(); }; - }, [sessionId, handleTerminalKeydown, onStatusChange]); + }, [sessionId, handleTerminalKeydown]); // 组件卸载时关闭会话 useEffect(() => { diff --git a/src/components/terminal/TerminalView.tsx b/src/components/terminal/TerminalView.tsx index c38fbe935..ed139ffd8 100644 --- a/src/components/terminal/TerminalView.tsx +++ b/src/components/terminal/TerminalView.tsx @@ -59,6 +59,7 @@ import { type ThemeName, loadThemePreference, loadFontSizePreference, + getTheme, } from "@/lib/terminal/themes"; import "./terminal.css"; @@ -170,6 +171,13 @@ export const TerminalView: React.FC = ({ // Refs const connectElemRef = useRef(null); const termWrapRef = useRef(null); + // 使用 ref 存储回调,避免回调变化导致终端重建 + const onStatusChangeRef = useRef(onStatusChange); + + // 更新回调 ref + useEffect(() => { + onStatusChangeRef.current = onStatusChange; + }, [onStatusChange]); // 初始化主题和字体大小 useEffect(() => { @@ -299,16 +307,16 @@ export const TerminalView: React.FC = ({ // ============================================================================ // TermWrap 生命周期管理 + // 注意:只依赖 blockId,避免主题/字体/回调变化导致终端重建 // ============================================================================ useEffect(() => { const container = connectElemRef.current; if (!container) return; - // 销毁旧的 TermWrap + // 如果已有 TermWrap,不重建 if (termWrapRef.current) { - termWrapRef.current.dispose(); - termWrapRef.current = null; + return; } // 清空容器 @@ -317,7 +325,7 @@ export const TerminalView: React.FC = ({ // 创建新的 TermWrap const termWrap = new TermWrap(blockId, container, { onStatusChange: (status) => { - onStatusChange?.(status); + onStatusChangeRef.current?.(status); }, themeName: themeName as ThemeName, fontSize: fontSize, @@ -340,11 +348,27 @@ export const TerminalView: React.FC = ({ return () => { termWrap.dispose(); + termWrapRef.current = null; rszObs.disconnect(); }; - // 注意:handleTerminalKeydown 使用 useCallback 且无依赖,不会导致重新创建 // eslint-disable-next-line react-hooks/exhaustive-deps - }, [blockId, themeName, fontSize, onStatusChange]); + }, [blockId]); + + // 主题变化时更新终端(不重建) + useEffect(() => { + if (termWrapRef.current?.terminal) { + const themeConfig = getTheme(themeName as ThemeName); + termWrapRef.current.terminal.options.theme = themeConfig; + } + }, [themeName]); + + // 字体大小变化时更新终端(不重建) + useEffect(() => { + if (termWrapRef.current?.terminal) { + termWrapRef.current.terminal.options.fontSize = fontSize; + termWrapRef.current.handleResize_debounced(); + } + }, [fontSize]); // 清理 TermViewModel useEffect(() => {