/** * 终端组件 - 集成 xterm.js */ app.component('terminal-page', { setup() { const { ref, onMounted, onUnmounted } = Vue; const terminal = ref(null); const fitAddon = ref(null); const socket = ref(null); const isConnected = ref(false); const sessionId = ref(''); // 初始化终端 function initTerminal() { if (typeof Terminal === 'undefined') { console.error('xterm.js 未加载'); return; } terminal.value = new Terminal({ fontSize: 14, fontFamily: '"Cascadia Code", "Fira Code", "JetBrains Mono", Consolas, monospace', cursorBlink: true, cursorStyle: 'block', theme: { background: '#1a1b26', foreground: '#c0caf5', cursor: '#c0caf5', selection: 'rgba(122, 162, 247, 0.3)', black: '#15161e', red: '#f7768e', green: '#9ece6a', yellow: '#e0af68', blue: '#7aa2f7', magenta: '#bb9af7', cyan: '#7dcfff', white: '#a9b1d6' }, scrollback: 10000 }); fitAddon.value = new FitAddon.FitAddon(); terminal.value.loadAddon(fitAddon.value); const container = document.getElementById('webui-terminal'); if (container) { terminal.value.open(container); fitAddon.value.fit(); // 显示欢迎信息 showWelcome(); } // 监听窗口大小变化 window.addEventListener('resize', handleResize); // 监听终端输入 terminal.value.onData(data => { if (socket.value && socket.value.readyState === WebSocket.OPEN) { socket.value.send(JSON.stringify({ type: 'input', data: data })); } }); } // 显示欢迎信息 function showWelcome() { const lines = [ '\x1b[1;34m', ' __ __ _ _ _ _ ____ _ ___ ', ' | \\/ (_)_ __ ___| \\ | | / \\ / ___| / \\ |_ _|', ' | |\\/| | | \'_ \\ / _ \\ \\| | / _ \\ \\___ \\ / _ \\ | | ', ' | | | | | | | | __/ |\\ |/ ___ \\ ___) / ___ \\ | | ', ' |_| |_|_|_| |_|\\___|_| \\_/_/ \\_\\____/_/ \\_\\___|', '\x1b[0m', '', '\x1b[33mWeb Terminal - 集成在 WebUI 中\x1b[0m', '', '点击 \x1b[1;32m连接\x1b[0m 按钮开始使用', '' ]; lines.forEach(line => terminal.value.writeln(line)); } // 处理窗口大小变化 function handleResize() { if (fitAddon.value) { fitAddon.value.fit(); sendResize(); } } // 发送终端大小 function sendResize() { if (socket.value && socket.value.readyState === WebSocket.OPEN && fitAddon.value) { const dims = fitAddon.value.proposeDimensions(); if (dims) { socket.value.send(JSON.stringify({ type: 'resize', cols: dims.cols, rows: dims.rows })); } } } // 连接到终端 function connect() { if (isConnected.value) return; terminal.value.writeln('\x1b[33m正在连接...\x1b[0m'); const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = `${protocol}//${window.location.host}/ws/terminal`; try { socket.value = new WebSocket(wsUrl); socket.value.onopen = () => { // 发送认证(使用匿名模式) socket.value.send(JSON.stringify({ type: 'auth', token: 'anonymous' })); }; socket.value.onmessage = (event) => { const msg = JSON.parse(event.data); handleMessage(msg); }; socket.value.onclose = (event) => { isConnected.value = false; if (event.code !== 1000) { terminal.value.writeln(`\x1b[31m连接已断开 (${event.code})\x1b[0m`); } }; socket.value.onerror = () => { terminal.value.writeln('\x1b[31m连接错误\x1b[0m'); }; } catch (e) { terminal.value.writeln(`\x1b[31m连接失败: ${e.message}\x1b[0m`); } } // 处理消息 function handleMessage(msg) { switch (msg.type) { case 'auth_ok': isConnected.value = true; sessionId.value = msg.session_id; terminal.value.writeln('\x1b[32m已连接到终端\x1b[0m\r\n'); sendResize(); break; case 'auth_error': terminal.value.writeln(`\x1b[31m认证失败: ${msg.message}\x1b[0m`); break; case 'output': terminal.value.write(msg.data); break; case 'error': terminal.value.writeln(`\x1b[31m错误: ${msg.message}\x1b[0m`); break; case 'ping': socket.value.send(JSON.stringify({ type: 'pong' })); break; } } // 断开连接 function disconnect() { if (socket.value) { socket.value.close(1000, 'User disconnect'); socket.value = null; } isConnected.value = false; terminal.value.writeln('\r\n\x1b[33m已断开连接\x1b[0m'); } // 清屏 function clear() { if (terminal.value) { terminal.value.clear(); } } // 生命周期 onMounted(() => { // 延迟初始化,确保 DOM 已渲染 setTimeout(initTerminal, 100); }); onUnmounted(() => { window.removeEventListener('resize', handleResize); if (socket.value) { socket.value.close(); } if (terminal.value) { terminal.value.dispose(); } }); return { isConnected, sessionId, connect, disconnect, clear }; }, template: `
终端 {{ sessionId.slice(0, 8) }}
清屏 {{ isConnected ? '断开' : '连接' }}
` });