mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-12-15 20:26:36 +08:00
feat: 设置功能新增ssh终端样式自定义
This commit is contained in:
@@ -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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|
||||||
|
|
||||||
/* 后端控制路由
|
/* 后端控制路由
|
||||||
------------------------------- */
|
------------------------------- */
|
||||||
// 是否开启后端控制路由
|
// 是否开启后端控制路由
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user