201 lines
5.1 KiB
Vue
201 lines
5.1 KiB
Vue
<script lang="ts" setup>
|
|
import {useMainLayoutStore} from "~/strores/UseMainLayoutStore";
|
|
import {HubConnectionBuilder} from "@microsoft/signalr";
|
|
import {FitAddon} from 'xterm-addon-fit'
|
|
import { ArrowDownRight } from 'lucide-vue-next';
|
|
//导入xtrem
|
|
import {Terminal} from "@xterm/xterm";
|
|
import "@xterm/xterm/css/xterm.css";
|
|
import _ from "lodash";
|
|
|
|
var term: Terminal;
|
|
const terminal = ref<HTMLDivElement|null>(null);
|
|
const props=defineProps(
|
|
{
|
|
isPage:{
|
|
type:Boolean,
|
|
default:false
|
|
}
|
|
}
|
|
)
|
|
const resizeObserver =_.debounce(()=>{
|
|
if(terminal.value===null) return;
|
|
const cols = Math.floor(terminal.value.clientWidth / 9);
|
|
const rows = Math.floor(terminal.value.clientHeight / 17);
|
|
term.resize(cols-2, rows-1)
|
|
connection.invoke("ResizeTerminal", rows-1, cols-2)
|
|
term.focus();
|
|
},500)
|
|
onMounted(()=>{
|
|
term = new Terminal({smoothScrollDuration:100,scrollback:1000,theme:{
|
|
background: '#00000000',
|
|
}});
|
|
term.open(document.getElementById("terminal") as HTMLElement);
|
|
if(terminal.value===null) return;
|
|
let cols = Math.floor(terminal.value.clientWidth / 9);
|
|
let rows = Math.floor(terminal.value.clientHeight / 17);
|
|
if (props.isPage) {
|
|
cols-=1
|
|
}
|
|
term.resize(cols-2, rows-1)
|
|
connection.invoke("ResizeTerminal", rows-1, cols-2)
|
|
term.focus();
|
|
|
|
//绑定输入事件
|
|
term.onData((data) => {
|
|
connection.invoke("SendMessage", data)
|
|
.catch(err => {
|
|
console.log("Error while sending message: " + err);
|
|
});
|
|
})
|
|
})
|
|
|
|
|
|
|
|
const mainLayoutStore = useMainLayoutStore()
|
|
const connection = new HubConnectionBuilder()
|
|
.withUrl(`${useRuntimeConfig().public.baseUrl}/TerminalHub`,{
|
|
accessTokenFactory:()=> `${useCookie('token').value}`
|
|
})
|
|
.withAutomaticReconnect()
|
|
.build();
|
|
onMounted(async () => {
|
|
connection.on("ReceiveMessage", (message) => {
|
|
if(message.startsWith("stty cols")||message.startsWith("stty rows")) return;
|
|
term.write(message)
|
|
})
|
|
await connection.start()
|
|
.then(() => {
|
|
console.log("Connection started");
|
|
})
|
|
.catch(err => {
|
|
console.log("Error while starting connection: " + err);
|
|
});
|
|
await connection.invoke("CreateTerminal", mainLayoutStore.SelectServer.value)
|
|
.then(() => {
|
|
console.log("Terminal created")
|
|
})
|
|
// setTimeout(()=>{
|
|
// connection.invoke("SendMessage","neofetch\n")
|
|
// },200)
|
|
})
|
|
const resize=()=>{
|
|
|
|
//监听鼠标位置
|
|
const onMouseMove=(e:MouseEvent)=>{
|
|
// 获取鼠标位置
|
|
const x = e.clientX;
|
|
const y = e.clientY;
|
|
// 获取terminal的宽高
|
|
if(terminal.value===null) return;
|
|
let width = x - terminal.value.getBoundingClientRect().left;
|
|
let height = y - terminal.value.getBoundingClientRect().top;
|
|
// 设置terminal的宽高
|
|
if(!props.isPage){
|
|
terminal.value.style.width = `${width}px`;
|
|
}
|
|
terminal.value.style.height = `${height}px`;
|
|
}
|
|
document.addEventListener('mousemove', onMouseMove, { passive: true });
|
|
// 监听鼠标抬起事件,用于停止监听鼠标移动
|
|
const onMouseUp = () => {
|
|
if(terminal.value===null) return;
|
|
let width = terminal.value.getBoundingClientRect().width;
|
|
width = Math.round(width / 9) * 9+20;
|
|
let height = terminal.value.getBoundingClientRect().height;
|
|
height = Math.round(height / 17) * 17+20;
|
|
if(!props.isPage){
|
|
terminal.value.style.width = `${width}px`;
|
|
}
|
|
terminal.value.style.height = `${height}px`;
|
|
resizeObserver()
|
|
// 停止监听鼠标移动事件
|
|
document.removeEventListener('mousemove', onMouseMove);
|
|
// 停止监听鼠标抬起事件
|
|
document.removeEventListener('mouseup', onMouseUp);
|
|
};
|
|
|
|
// 监听鼠标抬起事件
|
|
document.addEventListener('mouseup', onMouseUp, { passive: true });
|
|
}
|
|
|
|
</script>
|
|
|
|
<template>
|
|
<div :class="{'terminal-box':true,'terminal-box-page':isPage}" ref="terminal">
|
|
<div id="terminal" @resize="resizeObserver" >
|
|
</div>
|
|
<ArrowDownRight class="arrow" @mousedown="resize" />
|
|
<svg
|
|
class="bg2"
|
|
xmlns='http://www.w3.org/2000/svg'
|
|
style="background: black;filter:grayscale(1);"
|
|
>
|
|
|
|
<filter id='noiseFilter'>
|
|
<feTurbulence
|
|
type='fractalNoise'
|
|
baseFrequency='0.65'
|
|
numOctaves='3'
|
|
stitchTiles='stitch' />
|
|
</filter>
|
|
|
|
<rect
|
|
width='100%'
|
|
height='100%'
|
|
filter='url(#noiseFilter)' />
|
|
</svg>
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
<style lang="scss" scoped>
|
|
@import "base";
|
|
|
|
.terminal-box {
|
|
padding: 10px;
|
|
width: 800px;
|
|
height: 500px;
|
|
max-width: 80vw;
|
|
max-height: 80vh;
|
|
min-width: 400px;
|
|
min-height: 300px;
|
|
border-radius: $radius*2;
|
|
backdrop-filter: blur(20px);
|
|
border: 2px solid #fff;
|
|
will-change: contents;
|
|
}
|
|
.terminal-box-page{
|
|
border: unset;
|
|
padding: unset;
|
|
width: 100%;
|
|
max-width: unset;
|
|
}
|
|
.bg2{
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
z-index: -1;
|
|
pointer-events: none;
|
|
opacity: 0.1;
|
|
}
|
|
#terminal {
|
|
cursor: pointer;
|
|
&::-webkit-scrollbar{
|
|
width:0;
|
|
}
|
|
}
|
|
:deep(.xterm-viewport::-webkit-scrollbar){
|
|
width: 0;
|
|
}
|
|
.arrow{
|
|
position: absolute;
|
|
right: 0;
|
|
bottom: 0;
|
|
color: $light-bg-underline-color;
|
|
cursor: nw-resize;
|
|
}
|
|
</style> |