feat: 设置功能新增ssh终端样式自定义

This commit is contained in:
meilin.huang
2022-01-21 16:39:38 +08:00
parent db923eace7
commit 8f912ead2e
6 changed files with 167 additions and 78 deletions

View File

@@ -48,6 +48,10 @@ export interface ThemeConfigState {
globalViceTitle: string; globalViceTitle: string;
globalI18n: string; globalI18n: string;
globalComponentSize: string; globalComponentSize: string;
terminalForeground: string;
terminalBackground: string;
terminalCursor: string;
terminalFontSize: number;
}; };
} }

View File

@@ -106,6 +106,15 @@ const themeConfigModule: Module<ThemeConfigState, RootStateTypes> = {
// 默认布局,可选 1、默认 defaults 2、经典 classic 3、横向 transverse 4、分栏 columns // 默认布局,可选 1、默认 defaults 2、经典 classic 3、横向 transverse 4、分栏 columns
layout: 'classic', layout: 'classic',
// ssh终端字体颜色
terminalForeground: '#7e9192',
// ssh终端背景色
terminalBackground: '#002833',
// ssh终端cursor色
terminalCursor: '#268F81',
terminalFontSize: 15,
/* 后端控制路由 /* 后端控制路由
------------------------------- */ ------------------------------- */
// 是否开启后端控制路由 // 是否开启后端控制路由

View File

@@ -2,6 +2,42 @@
<div class="layout-breadcrumb-seting"> <div class="layout-breadcrumb-seting">
<el-drawer title="布局设置" v-model="getThemeConfig.isDrawer" direction="rtl" destroy-on-close size="240px" @close="onDrawerClose"> <el-drawer title="布局设置" v-model="getThemeConfig.isDrawer" direction="rtl" destroy-on-close size="240px" @close="onDrawerClose">
<el-scrollbar class="layout-breadcrumb-seting-bar"> <el-scrollbar class="layout-breadcrumb-seting-bar">
<!-- ssh终端主题 -->
<el-divider content-position="left">终端主题</el-divider>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">字体颜色</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-color-picker v-model="getThemeConfig.terminalForeground" size="small" @change="onColorPickerChange('terminalForeground')"> </el-color-picker>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">背景颜色</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-color-picker v-model="getThemeConfig.terminalBackground" size="small" @change="onColorPickerChange('terminalBackground')"> </el-color-picker>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">cursor颜色</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-color-picker v-model="getThemeConfig.terminalCursor" size="small" @change="onColorPickerChange('terminalCursor')"> </el-color-picker>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">字体大小</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-input-number
v-model="getThemeConfig.terminalFontSize"
controls-position="right"
:min="12"
:max="24"
@change="setLocalThemeConfig"
size="small"
style="width: 90px"
>
</el-input-number>
</div>
</div>
<!-- 全局主题 --> <!-- 全局主题 -->
<el-divider content-position="left">全局主题</el-divider> <el-divider content-position="left">全局主题</el-divider>
<div class="layout-breadcrumb-seting-bar-flex"> <div class="layout-breadcrumb-seting-bar-flex">

View File

@@ -12,16 +12,16 @@
<Account v-show="isTabPaneShow" /> <Account v-show="isTabPaneShow" />
</transition> </transition>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="手机号登录" name="mobile" :disabled="tabsActiveName === 'mobile'"> <!-- <el-tab-pane label="手机号登录" name="mobile" :disabled="tabsActiveName === 'mobile'">
<transition name="el-zoom-in-center"> <transition name="el-zoom-in-center">
<Mobile v-show="!isTabPaneShow" /> <Mobile v-show="!isTabPaneShow" />
</transition> </transition>
</el-tab-pane> </el-tab-pane> -->
</el-tabs> </el-tabs>
<div class="mt10"> <!-- <div class="mt10">
<el-button type="text" size="small">第三方登录</el-button> <el-button type="text" size="small">第三方登录</el-button>
<el-button type="text" size="small">友情链接</el-button> <el-button type="text" size="small">友情链接</el-button>
</div> </div> -->
</div> </div>
</div> </div>
<div class="login-copyright"> <div class="login-copyright">

View File

@@ -2,57 +2,83 @@
<div :style="{ height: height }" id="xterm" class="xterm" /> <div :style="{ height: height }" id="xterm" class="xterm" />
</template> </template>
<script> <script lang="ts">
import 'xterm/css/xterm.css'; import 'xterm/css/xterm.css';
import { Terminal } from 'xterm'; import { Terminal } from 'xterm';
import { FitAddon } from 'xterm-addon-fit'; import { FitAddon } from 'xterm-addon-fit';
import { getSession } from '@/common/utils/storage.ts'; import { getSession } from '@/common/utils/storage.ts';
import config from '@/common/config' import config from '@/common/config';
import { useStore } from '@/store/index.ts';
import { toRefs, watch, computed, reactive, defineComponent, onMounted, onBeforeUnmount } from 'vue';
export default { export default defineComponent({
name: 'Xterm', name: 'SshTerminal',
props: { props: {
machineId: Number, machineId: { type: Number },
cmd: String, cmd: { type: String },
height: String, height: { type: String },
}, },
watch: { setup(props: any) {
machineId(val) { const state = reactive({
if (val !== '') { machineId: 0,
this.initSocket(); cmd: '',
height: '',
term: null as any,
socket: null as any,
});
watch(props, (newValue) => {
console.log(newValue);
state.machineId = newValue.machineId;
state.cmd = newValue.cmd;
state.height = newValue.height;
if (state.machineId) {
initSocket();
} }
}, });
},
mounted() { onMounted(() => {
this.initSocket(); state.machineId = props.machineId;
// this.initTerm() state.height = props.height;
}, state.cmd = props.cmd;
beforeUnmount() { if (state.machineId) {
this.socket.close(); initSocket();
if (this.term) { }
this.term.dispose(); });
}
}, onBeforeUnmount(() => {
methods: { closeAll();
initXterm() { });
const term = new Terminal({
fontSize: 15, const store = useStore();
// 获取布局配置信息
const getThemeConfig: any = computed(() => {
return store.state.themeConfig.themeConfig;
});
function initXterm() {
const term: any = new Terminal({
fontSize: getThemeConfig.value.terminalFontSize,
cursorBlink: true, cursorBlink: true,
// cursorStyle: 'underline', //光标样式 // cursorStyle: 'underline', //光标样式
disableStdin: false, disableStdin: false,
theme: { theme: {
foreground: '#7e9192', //字体 // foreground: '#7e9192', //字体
background: '#002833', //背景色 // background: '#002833', //背景色
cursor: '#268F81', //设置光标 // cursor: '#268F81', //设置光标
lineHeight: 16, lineHeight: 16,
}, foreground: getThemeConfig.value.terminalForeground, //字体
background: getThemeConfig.value.terminalBackground, //背景色
cursor: getThemeConfig.value.terminalCursor, //设置光标
} as any,
}); });
const fitAddon = new FitAddon(); const fitAddon = new FitAddon();
term.loadAddon(fitAddon); term.loadAddon(fitAddon);
term.open(document.getElementById('xterm')); term.open(document.getElementById('xterm'));
fitAddon.fit(); fitAddon.fit();
term.focus(); term.focus();
this.term = term; state.term = term;
// / ** // / **
// *添加事件监听器,用于按下键时的事件。事件值包含 // *添加事件监听器,用于按下键时的事件。事件值包含
@@ -68,51 +94,59 @@ export default {
// * @返回一个IDisposable停止监听。 // * @返回一个IDisposable停止监听。
// * / // * /
// 支持输入与粘贴方法 // 支持输入与粘贴方法
term.onData((key) => { term.onData((key: any) => {
this.sendCmd(key); sendCmd(key);
}); });
// 为解决窗体resize方法才会向后端发送列数和行数所以页面加载时也要触发此方法 // 为解决窗体resize方法才会向后端发送列数和行数所以页面加载时也要触发此方法
this.send({ send({
type: 'resize', type: 'resize',
Cols: parseInt(term.cols), Cols: parseInt(term.cols),
Rows: parseInt(term.rows), Rows: parseInt(term.rows),
}); });
// 如果有初始要执行的命令,则发送执行命令 // 如果有初始要执行的命令,则发送执行命令
if (this.cmd) { if (state.cmd) {
this.sendCmd(this.cmd + ' \r'); sendCmd(state.cmd + ' \r');
} }
}, }
initSocket() {
this.socket = new WebSocket(`${config.baseWsUrl}/machines/${this.machineId}/terminal?token=${getSession('token')}`); function initSocket() {
state.socket = new WebSocket(`${config.baseWsUrl}/machines/${state.machineId}/terminal?token=${getSession('token')}`);
// 监听socket连接 // 监听socket连接
this.socket.onopen = this.open; state.socket.onopen = open;
// 监听socket错误信息 // 监听socket错误信息
this.socket.onerror = this.error; state.socket.onerror = error;
// 监听socket消息 // 监听socket消息
this.socket.onmessage = this.getMessage; state.socket.onmessage = getMessage;
// 发送socket消息 // 发送socket消息
this.socket.onsend = this.send; state.socket.onsend = send;
}, }
open: function () {
function open() {
console.log('socket连接成功'); console.log('socket连接成功');
this.initXterm(); initXterm();
//开启心跳 //开启心跳
// this.start(); // this.start();
}, }
error: function () {
function error() {
console.log('连接错误'); console.log('连接错误');
//重连 //重连
this.reconnect(); // reconnect();
}, }
close: function () {
this.socket.close(); function close() {
console.log('socket关闭'); if (state.socket) {
state.socket.close();
console.log('socket关闭');
}
//重连 //重连
// this.reconnect() // this.reconnect()
}, }
getMessage: function (msg) {
function getMessage(msg: string) {
// console.log(msg) // console.log(msg)
this.term.write(msg['data']); state.term.write(msg['data']);
//msg是返回的数据 //msg是返回的数据
// msg = JSON.parse(msg.data); // msg = JSON.parse(msg.data);
// this.socket.send("ping");//有事没事ping一下看看ws还活着没 // this.socket.send("ping");//有事没事ping一下看看ws还活着没
@@ -126,24 +160,30 @@ export default {
// } // }
//收到服务器信息,心跳重置 //收到服务器信息,心跳重置
// this.reset(); // this.reset();
}, }
send: function (msg) {
this.socket.send(JSON.stringify(msg));
},
sendCmd(key) { function send(msg: any) {
this.send({ state.socket.send(JSON.stringify(msg));
}
function sendCmd(key: any) {
send({
type: 'cmd', type: 'cmd',
msg: key, msg: key,
}); });
}, }
closeAll() {
this.close(); function closeAll() {
if (this.term) { close();
this.term.dispose(); if (state.term) {
this.term = null; state.term.dispose();
state.term = null;
} }
}, }
return {
...toRefs(state),
};
}, },
}; });
</script> </script>

View File

@@ -1,12 +1,12 @@
<template> <template>
<div class="file-manage"> <div>
<ssh-terminal ref="terminal" :machineId="machineId" :height="height + 'px'" /> <ssh-terminal ref="terminal" :machineId="machineId" :height="height + 'px'" />
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import SshTerminal from './SshTerminal.vue'; import SshTerminal from './SshTerminal.vue';
import { reactive, toRefs, onBeforeMount, defineComponent } from 'vue'; import { reactive, toRefs, onBeforeMount, defineComponent, onMounted } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
export default defineComponent({ export default defineComponent({
@@ -24,7 +24,7 @@ export default defineComponent({
height: 700, height: 700,
}); });
onBeforeMount(() => { onMounted(() => {
state.height = window.innerHeight; state.height = window.innerHeight;
state.machineId = Number.parseInt(route.query.id as string); state.machineId = Number.parseInt(route.query.id as string);
}); });