refactor: 优化终端交互.

This commit is contained in:
lijiahang
2024-01-10 19:30:25 +08:00
parent f9069d08d3
commit b5cdd0b362
17 changed files with 490 additions and 83 deletions

View File

@@ -1,8 +1,51 @@
import axios from 'axios';
// 终端主题
export interface TerminalTheme {
name: string;
dark: boolean;
schema: TerminalThemeSchema;
}
// 终端主题 schema
export interface TerminalThemeSchema {
background: string;
foreground: string;
cursor: string;
cursorAccent?: string;
selectionBackground?: string;
selectionForeground?: string;
selectionInactiveBackground?: string;
black: string;
red: string;
green: string;
yellow: string;
blue: string;
magenta: string;
cyan: string;
white: string;
brightBlack: string;
brightRed: string;
brightGreen: string;
brightYellow: string;
brightBlue: string;
brightMagenta: string;
brightCyan: string;
brightWhite: string;
[key: string]: unknown;
}
/**
* 获取主机终端主题
*/
export function getTerminalThemes() {
return axios.get<Array<TerminalTheme>>('/asset/host-terminal/themes');
}
/**
* 获取主机终端 accessToken
*/
export function getHostTerminalAccessToken() {
export function getTerminalAccessToken() {
return axios.get<string>('/asset/host-terminal/access');
}

View File

@@ -43,6 +43,10 @@
type: Boolean,
default: false
},
autoFocus: {
type: Boolean,
default: false
},
language: {
type: String,
default: 'json',
@@ -76,6 +80,10 @@
};
// 创建编辑器
editor = monaco.editor.create(editorContainer.value, options);
// 自动聚焦
if (props.autoFocus) {
editor.focus();
}
// 监听值的变化
editor.onDidChangeModelContent(() => {
const value = editor.getValue();

View File

@@ -14,6 +14,7 @@
<div :style="{ width: '100%', 'height': height }">
<editor v-model="value"
language="shell"
:auto-focus="true"
:theme="dark ? 'vs-dark' : 'vs'" />
</div>
</a-modal>

View File

@@ -3,9 +3,10 @@ import { defineStore } from 'pinia';
import { getPreference, updatePreference } from '@/api/user/preference';
import { Message } from '@arco-design/web-vue';
import { useDark } from '@vueuse/core';
import { DEFAULT_SCHEMA } from '@/views/host/terminal/types/terminal.theme';
import TerminalTabManager from '@/views/host/terminal/handler/terminal-tab-manager';
import TerminalSessionManager from '@/views/host/terminal/handler/terminal-session-manager';
import type { TerminalTheme } from '@/api/asset/host-terminal';
import { getTerminalThemes } from '@/api/asset/host-terminal';
// 暗色主题
export const DarkTheme = {
@@ -28,7 +29,8 @@ export default defineStore('terminal', {
darkTheme: 'auto',
newConnectionType: 'group',
displaySetting: {} as TerminalDisplaySetting,
themeSchema: {} as TerminalThemeSchema
themeSchema: {} as TerminalThemeSchema,
theme: {} as TerminalTheme
},
tabManager: new TerminalTabManager(),
sessionManager: new TerminalSessionManager()
@@ -38,25 +40,21 @@ export default defineStore('terminal', {
// 加载终端偏好
async fetchPreference() {
try {
// 加载偏好
const { data } = await getPreference<TerminalPreference>('TERMINAL');
// 设置默认终端主题
if (!data.themeSchema?.name) {
data.themeSchema = DEFAULT_SCHEMA;
// theme 不存在则默认加载第一个
if (!data.theme) {
const { data: themes } = await getTerminalThemes();
data.theme = themes[0];
}
this.preference = data;
// 设置暗色主题
const userDarkTheme = data.darkTheme;
if (userDarkTheme === DarkTheme.AUTO) {
this.isDarkTheme = data.themeSchema?.dark === true;
} else {
this.isDarkTheme = userDarkTheme === DarkTheme.DARK;
}
} catch (e) {
Message.error('配置加载失败');
}
},
// 修改暗色主题
// FIXME 删除 terminalDarkTheme
async changeDarkTheme(darkTheme: string) {
this.preference.darkTheme = darkTheme;
if (darkTheme === DarkTheme.DARK) {

View File

@@ -1,5 +1,6 @@
import type { Ref } from 'vue';
import type { ITerminalTabManager, ITerminalSessionManager } from '@/views/host/terminal/types/terminal.type';
import type { ITerminalSessionManager, ITerminalTabManager } from '@/views/host/terminal/types/terminal.type';
import type { TerminalTheme } from '@/api/asset/host-terminal';
export interface TerminalState {
isDarkTheme: Ref<boolean>;
@@ -13,6 +14,7 @@ export interface TerminalPreference {
darkTheme: string;
newConnectionType: string;
displaySetting: TerminalDisplaySetting;
theme: TerminalTheme;
themeSchema: TerminalThemeSchema;
}

View File

@@ -30,11 +30,30 @@
<script lang="ts" setup>
import { TabType, InnerTabs } from '../../types/terminal.const';
import { useTerminalStore } from '@/store';
import { watch } from 'vue';
import TerminalViewSetting from '../view-setting/terminal-view-setting.vue';
import NewConnectionView from '../new-connection/new-connection-view.vue';
import TerminalView from '../xterm/terminal-view.vue';
const { tabManager } = useTerminalStore();
import TerminalView from '../xterm/terminal-view.vue';
const { tabManager, sessionManager } = useTerminalStore();
// 监听 tab 修改
watch(() => tabManager.active, active => {
if (!active) {
return;
}
// 获取 tab
const tab = tabManager.items.find(s => s.key === active);
if (!tab) {
return;
}
// 修改标题
document.title = tab.title;
// terminal 自动聚焦
if (tab?.type === TabType.TERMINAL) {
sessionManager.getSession(active)?.focus();
}
});
</script>

View File

@@ -31,11 +31,11 @@
term.value.open(terminal.value);
term.value.write(
'[root@OrionServer usr]#\r\n' +
'dr-xr-xr-x. 2 root root bin\r\n' +
'dr-xr-xr-x. 2 root root sbin\r\n' +
'dr-xr-xr-x. 43 root root lib\r\n' +
'dr-xr-xr-x. 62 root root lib64\r\n' +
'lrwxrwxrwx. 1 root root tmp'
'dr-xr-xr-x. 2 root root bin\r\n' +
'dr-xr-xr-x. 2 root root sbin\r\n' +
'drwxr-xr-x. 89 root root share\r\n' +
'drwxr-xr-x. 4 root root src\r\n' +
'lrwxrwxrwx. 1 root root tmp -> ../var/tmp'
);
});

View File

@@ -47,7 +47,8 @@
:body-style="{ padding: '16px 16px 16px 0' }"
:dark="themeSchema.dark"
cancel-text="关闭"
@ok="writeCommand(modal.getValue())" />
@ok="writeCommand(modal.getValue())"
@cancel="focus" />
</div>
</template>
@@ -83,17 +84,18 @@
const session = ref<ITerminalSession>();
// FIXME
// 卸载 最外层 terminal 组件, 卸载 style
// 调教 theme
// terminal themes 改成非同步 style
// 从后端获取 theme
// (改成可配置/拆分)
// 自定义 font siderBar 颜色, 集成到主题里面, 现在的问题是切换主题字体颜色就变了
// 是否开启 link, url 匹配策略
// 是否开启 link
// 是否开启 image
// search color 配置
// 右键菜单补充
// 搜索
// 搜索插件, link插件
// 截屏
// 最近连接逻辑 偏好逻辑
// 发送命令
const writeCommandInput = async (e: KeyboardEvent) => {
@@ -111,6 +113,11 @@
}
};
// 聚焦
const focus = () => {
session.value?.focus();
};
// 右侧操作
const rightActions = computed<Array<SidebarAction>>(() => [
{
@@ -310,7 +317,7 @@
width: 100%;
height: 100%;
::-webkit-scrollbar {
::-webkit-scrollbar-track {
display: none;
}
}

View File

@@ -1,13 +1,6 @@
import type {
InputPayload,
ITerminalChannel,
ITerminalOutputProcessor,
ITerminalSessionManager,
OutputPayload,
Protocol,
} from '../types/terminal.type';
import type { InputPayload, ITerminalChannel, ITerminalOutputProcessor, ITerminalSessionManager, OutputPayload, Protocol, } from '../types/terminal.type';
import { OutputProtocol } from '../types/terminal.protocol';
import { getHostTerminalAccessToken } from '@/api/asset/host-terminal';
import { getTerminalAccessToken } from '@/api/asset/host-terminal';
import { Message } from '@arco-design/web-vue';
import { sleep } from '@/utils';
import TerminalOutputProcessor from './terminal-output-processor';
@@ -28,7 +21,7 @@ export default class TerminalChannel implements ITerminalChannel {
// 初始化
async init() {
// 获取 access
const { data: accessToken } = await getHostTerminalAccessToken();
const { data: accessToken } = await getTerminalAccessToken();
// 打开会话
this.client = new WebSocket(`${wsBase}/host/terminal/${accessToken}`);
this.client.onerror = event => {

View File

@@ -70,6 +70,7 @@ export default class TerminalSession implements ITerminalSession {
connect(): void {
this.status = TerminalStatus.CONNECTED;
this.connected = true;
this.inst.focus();
// 注册输入事件
this.inst.onData(s => {
if (!this.canWrite) {

View File

@@ -43,6 +43,7 @@
const dictStore = useDictStore();
const cacheStore = useCacheStore();
const originTitle = document.title;
const render = ref(false);
// 关闭视口处理
@@ -54,6 +55,9 @@
// 加载用户终端偏好
onBeforeMount(async () => {
await terminalStore.fetchPreference();
// 设置系统主题配色
const dark = terminalStore.preference.theme.dark;
document.body.setAttribute('terminal-theme', dark ? 'dark' : 'light');
render.value = true;
});
@@ -73,6 +77,10 @@
cacheStore.reset('authorizedHostKeys', 'authorizedHostIdentities');
// 移除关闭视口事件
window.removeEventListener('beforeunload', handleBeforeUnload);
// 去除 body style
document.body.removeAttribute('terminal-theme');
// 重置 title
document.title = originTitle;
});
</script>