feat: 命令右键菜单.
This commit is contained in:
@@ -75,15 +75,19 @@ export interface TerminalShortcutSetting {
|
||||
}
|
||||
|
||||
// 终端快捷键
|
||||
export interface TerminalShortcutKey {
|
||||
item: string;
|
||||
enabled: boolean;
|
||||
export interface ShortcutKey {
|
||||
ctrlKey: boolean;
|
||||
shiftKey: boolean;
|
||||
altKey: boolean;
|
||||
code: string;
|
||||
}
|
||||
|
||||
// 终端快捷键
|
||||
export interface TerminalShortcutKey extends ShortcutKey {
|
||||
item: string;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
// 终端快捷键编辑
|
||||
export interface TerminalShortcutKeyEditable extends TerminalShortcutKey {
|
||||
editable: boolean;
|
||||
|
||||
@@ -294,4 +294,10 @@ body[terminal-theme='dark'] .arco-modal-container {
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
&-icon {
|
||||
font-size: 16px;
|
||||
margin: 0 8px 0 4px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
@close="onClose">
|
||||
<!-- 表头 -->
|
||||
<template #title>
|
||||
<span class="snippet-drawer-title">
|
||||
<span class="snippet-drawer-title usn">
|
||||
<icon-code />
|
||||
命令片段
|
||||
</span>
|
||||
|
||||
@@ -45,20 +45,24 @@
|
||||
:deep(.arco-collapse-item) {
|
||||
border: none;
|
||||
|
||||
.arco-collapse-item-header-title {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.arco-collapse-item-header {
|
||||
&-header {
|
||||
border: none;
|
||||
|
||||
&-title {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
&-extra {
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
.arco-collapse-item-content {
|
||||
&-content {
|
||||
background-color: unset;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.arco-collapse-item-content-box {
|
||||
&-content-box {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,47 +1,108 @@
|
||||
<template>
|
||||
<div class="snippet-item-wrapper"
|
||||
:class="[!!item.expand ? 'snippet-item-wrapper-expand' : '']"
|
||||
@click="expandItem">
|
||||
<div class="snippet-item">
|
||||
<div class="snippet-item-title">
|
||||
<!-- 名称 -->
|
||||
<span class="snippet-item-title-name">
|
||||
<a-dropdown class="terminal-context-menu"
|
||||
:popup-max-height="false"
|
||||
trigger="contextMenu"
|
||||
position="bl"
|
||||
alignPoint>
|
||||
<!-- 命令 -->
|
||||
<div class="snippet-item-wrapper"
|
||||
:class="[!!item.expand ? 'snippet-item-wrapper-expand' : '']"
|
||||
@click="clickItem">
|
||||
<div class="snippet-item">
|
||||
<div class="snippet-item-title">
|
||||
<!-- 名称 -->
|
||||
<span class="snippet-item-title-name">
|
||||
{{ item.name }}
|
||||
</span>
|
||||
<!-- 操作 -->
|
||||
<div class="snippet-item-title-actions">
|
||||
<a-space>
|
||||
<!-- 粘贴 -->
|
||||
<a-tag class="pointer usn"
|
||||
size="small"
|
||||
:checkable="true"
|
||||
:checked="true"
|
||||
@click.stop="paste">
|
||||
<template #icon>
|
||||
<icon-paste />
|
||||
</template>
|
||||
粘贴
|
||||
</a-tag>
|
||||
<!-- 执行 -->
|
||||
<a-tag class="pointer usn"
|
||||
size="small"
|
||||
:checkable="true"
|
||||
:checked="true"
|
||||
@click.stop="exec">
|
||||
<template #icon>
|
||||
<icon-thunderbolt />
|
||||
</template>
|
||||
执行
|
||||
</a-tag>
|
||||
</a-space>
|
||||
<!-- 操作 -->
|
||||
<div class="snippet-item-title-actions">
|
||||
<a-space>
|
||||
<!-- 粘贴 -->
|
||||
<a-tag class="pointer usn"
|
||||
size="small"
|
||||
:checkable="true"
|
||||
:checked="true"
|
||||
@click.stop.prevent="paste">
|
||||
<template #icon>
|
||||
<icon-paste />
|
||||
</template>
|
||||
粘贴
|
||||
</a-tag>
|
||||
<!-- 执行 -->
|
||||
<a-tag class="pointer usn"
|
||||
size="small"
|
||||
:checkable="true"
|
||||
:checked="true"
|
||||
@click.stop="exec">
|
||||
<template #icon>
|
||||
<icon-thunderbolt />
|
||||
</template>
|
||||
执行
|
||||
</a-tag>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 命令 -->
|
||||
<span class="snippet-item-command">
|
||||
<!-- 命令 -->
|
||||
<span class="snippet-item-command">
|
||||
{{ item.command }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右键菜单 -->
|
||||
<template #content>
|
||||
<!-- 复制 -->
|
||||
<a-doption @click="copyCommand">
|
||||
<div class="terminal-context-menu-icon">
|
||||
<icon-copy />
|
||||
</div>
|
||||
<div>复制</div>
|
||||
</a-doption>
|
||||
<!-- 粘贴 -->
|
||||
<a-doption @click="paste">
|
||||
<div class="terminal-context-menu-icon">
|
||||
<icon-paste />
|
||||
</div>
|
||||
<div>粘贴</div>
|
||||
</a-doption>
|
||||
<!-- 执行 -->
|
||||
<a-doption @click="exec">
|
||||
<div class="terminal-context-menu-icon">
|
||||
<icon-thunderbolt />
|
||||
</div>
|
||||
<div>执行</div>
|
||||
</a-doption>
|
||||
<!-- 修改 -->
|
||||
<a-doption @click="exec">
|
||||
<div class="terminal-context-menu-icon">
|
||||
<icon-edit />
|
||||
</div>
|
||||
<div>修改</div>
|
||||
</a-doption>
|
||||
<!-- 删除 -->
|
||||
<a-doption @click="exec">
|
||||
<div class="terminal-context-menu-icon">
|
||||
<icon-delete />
|
||||
</div>
|
||||
<div>删除</div>
|
||||
</a-doption>
|
||||
<!-- 展开 -->
|
||||
<a-doption v-if="!item.expand"
|
||||
@click="() => item.expand = true">
|
||||
<div class="terminal-context-menu-icon">
|
||||
<icon-expand />
|
||||
</div>
|
||||
<div>展开</div>
|
||||
</a-doption>
|
||||
<!-- 收起 -->
|
||||
<a-doption v-else
|
||||
@click="() => item.expand = false">
|
||||
<div class="terminal-context-menu-icon">
|
||||
<icon-shrink />
|
||||
</div>
|
||||
<div>收起</div>
|
||||
</a-doption>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -53,18 +114,44 @@
|
||||
<script lang="ts" setup>
|
||||
import type { CommandSnippetQueryResponse } from '@/api/asset/command-snippet';
|
||||
import { useTerminalStore } from '@/store';
|
||||
import { useDebounceFn } from '@vueuse/core';
|
||||
import useCopy from '@/hooks/copy';
|
||||
|
||||
const props = defineProps<{
|
||||
item: CommandSnippetQueryResponse
|
||||
}>();
|
||||
|
||||
const { copy } = useCopy();
|
||||
const { getCurrentTerminalSession } = useTerminalStore();
|
||||
|
||||
// TODO 右键菜单 复制 粘贴 删除 执行 修改
|
||||
// TODO 修改 删除 拼接有bug
|
||||
|
||||
// 展开命令
|
||||
const expandItem = () => {
|
||||
props.item.expand = !props.item.expand;
|
||||
let clickCount = 0;
|
||||
|
||||
// 点击命令
|
||||
const clickItem = () => {
|
||||
if (++clickCount == 2) {
|
||||
clickCount = 0;
|
||||
exec();
|
||||
} else {
|
||||
expandItem();
|
||||
}
|
||||
};
|
||||
|
||||
// 展开
|
||||
const expandItem = useDebounceFn(() => {
|
||||
setTimeout(() => {
|
||||
// 为 0 则代表为双击
|
||||
if (clickCount !== 0) {
|
||||
props.item.expand = !props.item.expand;
|
||||
clickCount = 0;
|
||||
}
|
||||
}, 50);
|
||||
});
|
||||
|
||||
// 复制命令
|
||||
const copyCommand = () => {
|
||||
copy(props.item.command, false);
|
||||
};
|
||||
|
||||
// 粘贴
|
||||
@@ -116,6 +203,7 @@
|
||||
text-overflow: unset;
|
||||
word-break: break-all;
|
||||
white-space: unset;
|
||||
user-select: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,6 +241,7 @@
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
user-select: none;
|
||||
|
||||
&-name {
|
||||
width: @item-inline-width;
|
||||
@@ -173,7 +262,9 @@
|
||||
text-overflow: ellipsis;
|
||||
white-space: pre;
|
||||
width: @item-inline-width;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
:disabled="!session.handler.enabledStatus(action.item)"
|
||||
@click="emits('click', action.item)">
|
||||
<!-- 图标 -->
|
||||
<div class="action-icon">
|
||||
<div class="terminal-context-menu-icon">
|
||||
<component :is="action.icon" />
|
||||
</div>
|
||||
<!-- 文本 -->
|
||||
@@ -53,9 +53,4 @@
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
.action-icon {
|
||||
font-size: 16px;
|
||||
margin: 0 8px 0 4px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -68,7 +68,7 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
|
||||
|
||||
// 处理 pong 消息
|
||||
processPong(payload: OutputPayload): void {
|
||||
console.log('pong');
|
||||
// console.log('pong');
|
||||
}
|
||||
|
||||
// 处理输出消息
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { TerminalInteractSetting, TerminalShortcutKey } from '@/store/modules/terminal/types';
|
||||
import type { ShortcutKey, TerminalInteractSetting, TerminalShortcutKey } from '@/store/modules/terminal/types';
|
||||
import type { ITerminalSession, ITerminalSessionHandler, ITerminalTabManager, TerminalDomRef } from '../types/terminal.type';
|
||||
import type { Terminal } from 'xterm';
|
||||
import useCopy from '@/hooks/copy';
|
||||
@@ -9,6 +9,21 @@ import { saveAs } from 'file-saver';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { dateFormat } from '@/utils';
|
||||
|
||||
// 组织默认行为的快捷键
|
||||
const preventKeys: Array<ShortcutKey> = [
|
||||
{
|
||||
ctrlKey: true,
|
||||
altKey: false,
|
||||
shiftKey: true,
|
||||
code: 'KeyV'
|
||||
}, {
|
||||
ctrlKey: false,
|
||||
altKey: false,
|
||||
shiftKey: true,
|
||||
code: 'Insert'
|
||||
},
|
||||
];
|
||||
|
||||
const { copy: copyValue, readText } = useCopy();
|
||||
|
||||
// 终端会话处理器实现
|
||||
@@ -37,6 +52,19 @@ export default class TerminalSessionHandler implements ITerminalSessionHandler {
|
||||
this.tabManager = tabManager;
|
||||
}
|
||||
|
||||
// 检测是否忽略默认行为
|
||||
checkPreventDefault(e: KeyboardEvent): boolean {
|
||||
if (e.type !== 'keydown') {
|
||||
return false;
|
||||
}
|
||||
return !!preventKeys.find(key => {
|
||||
return key.code === e.code
|
||||
&& key.altKey === e.altKey
|
||||
&& key.shiftKey === e.shiftKey
|
||||
&& key.ctrlKey === e.ctrlKey;
|
||||
});
|
||||
}
|
||||
|
||||
// 启用状态
|
||||
enabledStatus(option: string): boolean {
|
||||
switch (option) {
|
||||
@@ -187,7 +215,15 @@ export default class TerminalSessionHandler implements ITerminalSessionHandler {
|
||||
checkAppendMissing(value: string): void {
|
||||
// 获取最后一行数据
|
||||
const buffer = this.inst.buffer?.active;
|
||||
let lastLine = (buffer?.getLine(buffer?.viewportY + buffer?.cursorY)?.translateToString() || '').trimEnd();
|
||||
let lastLine = '';
|
||||
if (buffer) {
|
||||
for (let i = buffer.viewportY + buffer.cursorY; i >= 0; i--) {
|
||||
lastLine = (buffer.getLine(i)?.translateToString() || '').trimEnd() + lastLine;
|
||||
if (lastLine.length > value.length) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 边界检查
|
||||
const lastLineLen = lastLine.length;
|
||||
const spinPartLen = value.length;
|
||||
|
||||
@@ -82,7 +82,10 @@ export default class TerminalSession implements ITerminalSession {
|
||||
private registerShortcut(preference: UnwrapRef<TerminalPreference>) {
|
||||
// 处理自定义按键
|
||||
this.inst.attachCustomKeyEventHandler((e: KeyboardEvent) => {
|
||||
e.preventDefault();
|
||||
// 检测是否忽略默认行为
|
||||
if (this.handler.checkPreventDefault(e)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
// 触发快捷键检测
|
||||
if (e.type === 'keydown'
|
||||
&& preference.shortcutSetting.enabled
|
||||
|
||||
@@ -203,6 +203,8 @@ export interface ITerminalSession {
|
||||
|
||||
// 终端会话处理器定义
|
||||
export interface ITerminalSessionHandler {
|
||||
// 检测是否忽略默认行为
|
||||
checkPreventDefault: (e: KeyboardEvent) => boolean;
|
||||
// 启用状态
|
||||
enabledStatus: (option: string) => boolean;
|
||||
// 调用处理方法
|
||||
|
||||
Reference in New Issue
Block a user