feat: 终端头部指令.

This commit is contained in:
lijiahangmax
2024-01-09 01:19:51 +08:00
parent fa14ff58f9
commit 0bb7b5bc62
10 changed files with 173 additions and 49 deletions

View File

@@ -189,7 +189,12 @@ body[terminal-theme='dark'] .host-layout {
&:hover {
background: var(--color-sidebar-icon-bg);
}
&.disabled-item {
cursor: not-allowed;
}
}
}
// 终端设置容器

View File

@@ -7,10 +7,10 @@
content-class="terminal-tooltip-content"
arrow-class="terminal-tooltip-arrow"
:content="action.content">
<div class="terminal-sidebar-icon-wrapper">
<div class="terminal-sidebar-icon-wrapper" v-if="action.visible !== false">
<div class="terminal-sidebar-icon"
:class="iconClass"
@click="action.click">
:class="[iconClass, action.disabled !== false ? '' : 'disabled-item']"
@click="action.disabled !== false ? action.click() : false">
<component :is="action.icon" :style="action?.iconStyle" />
</div>
</div>

View File

@@ -3,7 +3,7 @@
<!-- 头部 -->
<div class="terminal-header"
:style="{
background: adjustColor(preference.themeSchema.background, -10)
background: adjustColor(themeSchema.background, -12)
}">
<!-- 左侧操作 -->
<div class="terminal-header-left">
@@ -17,16 +17,27 @@
</div>
<!-- 右侧操作 -->
<div class="terminal-header-right">
<!-- 代码输入框 -->
<a-textarea class="command-input mr8"
v-model="commandInput"
:auto-size="{ minRows: 1, maxRows: 1 }"
placeholder="F8 发送命令"
allow-clear
@keyup="writeCommandInput" />
<!-- 操作按钮 -->
<icon-actions class="terminal-header-right-icon-actions"
:actions="rightActions"
position="bottom" />
<!-- 状态 -->
<a-badge style="margin: 0 2px 0 8px;"
:status="getDictValue(connectStatusKey, session ? session.status : 0, 'status')"
:text="getDictValue(connectStatusKey, session ? session.status : 0)" />
</div>
</div>
<!-- 终端 -->
<div class="terminal-wrapper"
:style="{
background: preference.themeSchema.background
background: themeSchema.background
}">
<div class="terminal-inst" ref="terminalRef" />
</div>
@@ -41,11 +52,11 @@
<script lang="ts" setup>
import type { ITerminalSession, TerminalTabItem } from '../../types/terminal.type';
import { onMounted, onUnmounted, ref } from 'vue';
import { useTerminalStore } from '@/store';
import { computed, onMounted, onUnmounted, ref } from 'vue';
import { useDictStore, useTerminalStore } from '@/store';
import useCopy from '@/hooks/copy';
import IconActions from '@/views/host/terminal/components/layout/icon-actions.vue';
import { SidebarAction } from '@/views/host/terminal/types/terminal.const';
import { connectStatusKey, SidebarAction } from '@/views/host/terminal/types/terminal.const';
import { adjustColor } from '@/utils';
const props = defineProps<{
@@ -53,79 +64,127 @@
}>();
const { copy, readText } = useCopy();
const { getDictValue } = useDictStore();
const { preference, sessionManager } = useTerminalStore();
const commandInput = ref();
const themeSchema = preference.themeSchema;
const terminalRef = ref();
const session = ref<ITerminalSession>();
// FIXME
// 最近连接记录
// 防止 background 自动变更
// 去顶部 去底部 ctrl+c 重新连接 command-input 状态
// (未连接禁用点击)
// (改成可配置)
// 命令编辑器 搜索
// (改成可配置/拆分)
// 自定义 font siderBar 颜色, 集成到主题里面, 现在的问题是切换主题字体颜色就变了
// 右键菜单补充
// 搜索插件, link插件
// 发送命令
const writeCommandInput = async (e: KeyboardEvent) => {
const value = commandInput.value;
if (value && e.code === 'F8' && session.value?.canWrite) {
session.value.paste(value);
commandInput.value = undefined;
}
};
// 右侧操作
const rightActions: Array<SidebarAction> = [
const rightActions = computed<Array<SidebarAction>>(() => [
{
icon: 'icon-up',
content: '去顶部',
click: () => {
session.value?.toTop();
}
}, {
icon: 'icon-down',
content: '去底部',
click: () => {
session.value?.toBottom();
}
}, {
icon: 'icon-expand',
content: '全选',
click: () => {
session.value?.selectAll();
session.value?.focus();
}
}, {
icon: 'icon-copy',
content: '复制选中部分',
click: () => {
copy(session.value?.getSelection(), '已复制');
session.value?.focus();
}
}, {
icon: 'icon-paste',
content: '粘贴',
disabled: session.value?.canWrite,
click: async () => {
if (session.value?.canWrite) {
session.value?.paste(await readText());
}
session.value?.paste(await readText());
}
}, {
icon: 'icon-formula',
content: 'ctrl + c',
disabled: session.value?.canWrite,
click: () => {
session.value?.paste(String.fromCharCode(3));
}
}, {
icon: 'icon-play-arrow-fill',
content: '回车',
disabled: session.value?.canWrite,
click: () => {
session.value?.paste(String.fromCharCode(13));
}
}, {
icon: 'icon-code-square',
content: '命令编辑器',
disabled: session.value?.canWrite,
click: () => {
}
}, {
icon: 'icon-search',
content: '搜索',
click: () => {
}
}, {
icon: 'icon-zoom-in',
content: '增大字号',
click: () => {
if (session.value?.connected) {
if (session.value) {
session.value.setOption('fontSize', session.value.getOption('fontSize') + 1);
session.value.fit();
session.value.focus();
if (session.value.connected) {
session.value.fit();
session.value.focus();
}
}
}
}, {
icon: 'icon-zoom-out',
content: '减小字号',
click: () => {
if (session.value?.connected) {
if (session.value) {
session.value.setOption('fontSize', session.value.getOption('fontSize') - 1);
session.value.fit();
session.value.focus();
if (session.value.connected) {
session.value.fit();
session.value.focus();
}
}
}
}, {
icon: 'icon-eraser',
icon: 'icon-delete',
content: '清空',
click: () => {
session.value?.clear();
session.value?.focus();
}
}, {
icon: 'icon-poweroff',
content: '关闭',
disabled: session.value?.connected,
click: () => {
if (session.value?.connected) {
session.value.logout();
}
session.value?.logout();
}
},
];
]);
// 初始化会话
onMounted(async () => {
@@ -190,6 +249,10 @@
&-right {
justify-content: flex-end;
.command-input {
width: 36%;
}
}
&-right-icon-actions {

View File

@@ -5,6 +5,7 @@ import {
OutputPayload
} from '../types/terminal.type';
import { InputProtocol } from '../types/terminal.protocol';
import { TerminalStatus } from '../types/terminal.const';
// 终端输出消息体处理器实现
export default class TerminalOutputProcessor implements ITerminalOutputProcessor {
@@ -25,6 +26,7 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
// 未成功展示错误信息
if (!success) {
session.write(`${msg || ''}`);
session.status = TerminalStatus.CLOSED;
return;
}
// 发送 connect 命令
@@ -38,6 +40,7 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
// 未成功展示错误信息
if (!success) {
session.write(`${msg || ''}`);
session.status = TerminalStatus.CLOSED;
return;
}
// 设置可写
@@ -54,6 +57,7 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
// 提示消息
session.write(`\r\n${msg || ''}`);
// 设置状态
session.status = TerminalStatus.CLOSED;
session.connected = false;
// 设置不可写
session.setCanWrite(false);

View File

@@ -1,6 +1,6 @@
import type { ITerminalChannel, ITerminalSession } from '../types/terminal.type';
import { useTerminalStore } from '@/store';
import { fontFamilySuffix } from '../types/terminal.const';
import { fontFamilySuffix, TerminalStatus } from '../types/terminal.const';
import { InputProtocol } from '../types/terminal.protocol';
import { ITerminalOptions, Terminal } from 'xterm';
import { FitAddon } from 'xterm-addon-fit';
@@ -23,6 +23,8 @@ export default class TerminalSession implements ITerminalSession {
public canWrite: boolean;
public status: number;
private readonly sessionId: string;
private readonly channel: ITerminalChannel;
@@ -37,6 +39,7 @@ export default class TerminalSession implements ITerminalSession {
this.channel = channel;
this.connected = false;
this.canWrite = false;
this.status = TerminalStatus.CONNECTING;
this.inst = undefined as unknown as Terminal;
this.addons = {} as TerminalAddons;
}
@@ -65,6 +68,7 @@ export default class TerminalSession implements ITerminalSession {
// 设置已连接
connect(): void {
this.status = TerminalStatus.CONNECTED;
this.connected = true;
// 注册输入事件
this.inst.onData(s => {
@@ -98,7 +102,7 @@ export default class TerminalSession implements ITerminalSession {
}
// 写入数据
write(value: string): void {
write(value: string | Uint8Array): void {
this.inst.write(value);
}
@@ -128,11 +132,26 @@ export default class TerminalSession implements ITerminalSession {
// 选中全部
selectAll(): void {
this.inst.selectAll();
this.inst.focus();
}
// 获取选中
getSelection(): string {
return this.inst.getSelection();
const selection = this.inst.getSelection();
this.inst.focus();
return selection;
}
// 去顶部
toTop(): void {
this.inst.scrollToTop();
this.inst.focus();
}
// 去底部
toBottom(): void {
this.inst.scrollToBottom();
this.inst.focus();
}
// 获取配置

View File

@@ -5,6 +5,8 @@ import { getUUID } from '@/utils';
export interface SidebarAction {
icon: string;
content: string;
visible?: boolean;
disabled?: boolean;
iconStyle?: CSSProperties;
click: () => void;
}
@@ -60,6 +62,16 @@ export const ExtraSshAuthType = {
CUSTOM_IDENTITY: 'CUSTOM_IDENTITY',
};
// 终端状态
export const TerminalStatus = {
// 连接中
CONNECTING: 0,
// 已连接
CONNECTED: 1,
// 已断开
CLOSED: 2
};
// 获取会话id
export const nextSessionId = (): string => {
return getUUID().replaceAll('-', '').substring(0, 10);
@@ -87,15 +99,18 @@ export const fontWeightKey = 'terminalFontWeight';
export const cursorStyleKey = 'terminalCursorStyle';
// 终端新建连接类型
export const NewConnectionTypeKey = 'terminalNewConnectionType';
export const newConnectionTypeKey = 'terminalNewConnectionType';
// 终端新建连接类型
export const extraSshAuthTypeKey = 'hostExtraSshAuthType';
// 终端状态
export const connectStatusKey = 'terminalConnectStatus';
// 加载的字典值
export const dictKeys = [
darkThemeKey, fontFamilyKey,
fontSizeKey, fontWeightKey,
cursorStyleKey, NewConnectionTypeKey,
extraSshAuthTypeKey
cursorStyleKey, newConnectionTypeKey,
extraSshAuthTypeKey, connectStatusKey
];

View File

@@ -97,6 +97,8 @@ export interface ITerminalSession {
connected: boolean;
// 是否可写
canWrite: boolean;
// 状态
status: number;
// 初始化
init: (dom: HTMLElement) => void;
@@ -105,7 +107,7 @@ export interface ITerminalSession {
// 设置是否可写
setCanWrite: (canWrite: boolean) => void;
// 写入数据
write: (value: string) => void;
write: (value: string | Uint8Array) => void;
// 自适应
fit: () => void;
// 聚焦
@@ -118,6 +120,10 @@ export interface ITerminalSession {
selectAll: () => void;
// 获取选中
getSelection: () => string;
// 去顶部
toTop: () => void;
// 去底部
toBottom: () => void;
// 获取配置
getOption: (option: string) => any;
// 设置配置