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