feat: 添加终端右键菜单逻辑.
This commit is contained in:
@@ -8,6 +8,8 @@ import lombok.Builder;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 终端偏好模型
|
* 终端偏好模型
|
||||||
*
|
*
|
||||||
@@ -33,6 +35,9 @@ public class TerminalPreferenceModel implements PreferenceModel {
|
|||||||
@Schema(description = "操作栏设置")
|
@Schema(description = "操作栏设置")
|
||||||
private JSONObject actionBarSetting;
|
private JSONObject actionBarSetting;
|
||||||
|
|
||||||
|
@Schema(description = "右键菜单设置")
|
||||||
|
private List<String> rightMenuSetting;
|
||||||
|
|
||||||
@Schema(description = "交互设置")
|
@Schema(description = "交互设置")
|
||||||
private JSONObject interactSetting;
|
private JSONObject interactSetting;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.orion.ops.module.infra.handler.preference.strategy;
|
package com.orion.ops.module.infra.handler.preference.strategy;
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.orion.lang.utils.collect.Lists;
|
||||||
import com.orion.net.host.ssh.TerminalType;
|
import com.orion.net.host.ssh.TerminalType;
|
||||||
import com.orion.ops.module.infra.handler.preference.model.TerminalPreferenceModel;
|
import com.orion.ops.module.infra.handler.preference.model.TerminalPreferenceModel;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
@@ -63,6 +64,7 @@ public class TerminalPreferenceStrategy implements IPreferenceStrategy<TerminalP
|
|||||||
.theme(new JSONObject())
|
.theme(new JSONObject())
|
||||||
.displaySetting(JSONObject.parseObject(defaultDisplaySetting))
|
.displaySetting(JSONObject.parseObject(defaultDisplaySetting))
|
||||||
.actionBarSetting(new JSONObject())
|
.actionBarSetting(new JSONObject())
|
||||||
|
.rightMenuSetting(Lists.of("copy", "paste", "checkAll", "search", "clear"))
|
||||||
.interactSetting(JSONObject.parseObject(defaultInteractSetting))
|
.interactSetting(JSONObject.parseObject(defaultInteractSetting))
|
||||||
.pluginsSetting(JSONObject.parseObject(defaultPluginsSetting))
|
.pluginsSetting(JSONObject.parseObject(defaultPluginsSetting))
|
||||||
.sessionSetting(JSONObject.parseObject(defaultSessionSetting))
|
.sessionSetting(JSONObject.parseObject(defaultSessionSetting))
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import type {
|
|||||||
import type { AuthorizedHostQueryResponse } from '@/api/asset/asset-authorized-data';
|
import type { AuthorizedHostQueryResponse } from '@/api/asset/asset-authorized-data';
|
||||||
import { getCurrentAuthorizedHost } from '@/api/asset/asset-authorized-data';
|
import { getCurrentAuthorizedHost } from '@/api/asset/asset-authorized-data';
|
||||||
import type { HostQueryResponse } from '@/api/asset/host';
|
import type { HostQueryResponse } from '@/api/asset/host';
|
||||||
import type { TerminalTheme } from '@/api/asset/host-terminal';
|
import type { TerminalTheme, TerminalThemeSchema } from '@/api/asset/host-terminal';
|
||||||
import { getTerminalThemes } from '@/api/asset/host-terminal';
|
import { getTerminalThemes } from '@/api/asset/host-terminal';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { getPreference, updatePreference } from '@/api/user/preference';
|
import { getPreference, updatePreference } from '@/api/user/preference';
|
||||||
@@ -26,10 +26,14 @@ export const TerminalPreferenceItem = {
|
|||||||
NEW_CONNECTION_TYPE: 'newConnectionType',
|
NEW_CONNECTION_TYPE: 'newConnectionType',
|
||||||
// 终端主题
|
// 终端主题
|
||||||
THEME: 'theme',
|
THEME: 'theme',
|
||||||
|
// 快捷键设置
|
||||||
|
SHORTCUT_SETTING: 'shortcutSetting',
|
||||||
// 显示设置
|
// 显示设置
|
||||||
DISPLAY_SETTING: 'displaySetting',
|
DISPLAY_SETTING: 'displaySetting',
|
||||||
// 操作栏设置
|
// 操作栏设置
|
||||||
ACTION_BAR_SETTING: 'actionBarSetting',
|
ACTION_BAR_SETTING: 'actionBarSetting',
|
||||||
|
// 右键菜单设置
|
||||||
|
RIGHT_MENU_SETTING: 'rightMenuSetting',
|
||||||
// 交互设置
|
// 交互设置
|
||||||
INTERACT_SETTING: 'interactSetting',
|
INTERACT_SETTING: 'interactSetting',
|
||||||
// 插件设置
|
// 插件设置
|
||||||
@@ -42,9 +46,12 @@ export default defineStore('terminal', {
|
|||||||
state: (): TerminalState => ({
|
state: (): TerminalState => ({
|
||||||
preference: {
|
preference: {
|
||||||
newConnectionType: 'group',
|
newConnectionType: 'group',
|
||||||
theme: {} as TerminalTheme,
|
theme: {
|
||||||
|
schema: {} as TerminalThemeSchema
|
||||||
|
} as TerminalTheme,
|
||||||
displaySetting: {} as TerminalDisplaySetting,
|
displaySetting: {} as TerminalDisplaySetting,
|
||||||
actionBarSetting: {} as TerminalActionBarSetting,
|
actionBarSetting: {} as TerminalActionBarSetting,
|
||||||
|
rightMenuSetting: [],
|
||||||
interactSetting: {} as TerminalInteractSetting,
|
interactSetting: {} as TerminalInteractSetting,
|
||||||
pluginsSetting: {} as TerminalPluginsSetting,
|
pluginsSetting: {} as TerminalPluginsSetting,
|
||||||
sessionSetting: {} as TerminalSessionSetting,
|
sessionSetting: {} as TerminalSessionSetting,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { ITerminalSessionManager, ITerminalTabManager } 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';
|
|
||||||
import type { AuthorizedHostQueryResponse } from '@/api/asset/asset-authorized-data';
|
import type { AuthorizedHostQueryResponse } from '@/api/asset/asset-authorized-data';
|
||||||
|
import type { TerminalTheme } from '@/api/asset/host-terminal';
|
||||||
|
|
||||||
export interface TerminalState {
|
export interface TerminalState {
|
||||||
preference: TerminalPreference;
|
preference: TerminalPreference;
|
||||||
@@ -15,6 +15,7 @@ export interface TerminalPreference {
|
|||||||
theme: TerminalTheme;
|
theme: TerminalTheme;
|
||||||
displaySetting: TerminalDisplaySetting;
|
displaySetting: TerminalDisplaySetting;
|
||||||
actionBarSetting: TerminalActionBarSetting;
|
actionBarSetting: TerminalActionBarSetting;
|
||||||
|
rightMenuSetting: Array<string>,
|
||||||
interactSetting: TerminalInteractSetting;
|
interactSetting: TerminalInteractSetting;
|
||||||
pluginsSetting: TerminalPluginsSetting;
|
pluginsSetting: TerminalPluginsSetting;
|
||||||
sessionSetting: TerminalSessionSetting;
|
sessionSetting: TerminalSessionSetting;
|
||||||
|
|||||||
@@ -131,11 +131,13 @@ body .host-layout, .arco-modal-container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// arco 暗色配色
|
// arco 暗色配色
|
||||||
|
body[terminal-theme='dark'],
|
||||||
body[terminal-theme='dark'] .host-layout,
|
body[terminal-theme='dark'] .host-layout,
|
||||||
body[terminal-theme='dark'] .arco-modal-container {
|
body[terminal-theme='dark'] .arco-modal-container {
|
||||||
--color-white: rgba(255, 255, 255, 0.9);
|
--color-white: rgba(255, 255, 255, 0.9);
|
||||||
--color-black: #000000;
|
--color-black: #000000;
|
||||||
--color-border: #333335;
|
--color-border: #333335;
|
||||||
|
--color-bg-popup: var(--color-bg-5);
|
||||||
--color-bg-1: #17171a;
|
--color-bg-1: #17171a;
|
||||||
--color-bg-2: #232324;
|
--color-bg-2: #232324;
|
||||||
--color-bg-3: #2a2a2b;
|
--color-bg-3: #2a2a2b;
|
||||||
@@ -260,7 +262,6 @@ body[terminal-theme='dark'] .arco-modal-container {
|
|||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
&.block-body {
|
&.block-body {
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
border: 1px solid var(--color-fill-4);
|
border: 1px solid var(--color-fill-4);
|
||||||
@@ -280,3 +281,17 @@ body[terminal-theme='dark'] .arco-modal-container {
|
|||||||
.terminal-tooltip-arrow {
|
.terminal-tooltip-arrow {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 终端右键菜单
|
||||||
|
.terminal-context-menu {
|
||||||
|
.arco-dropdown-option {
|
||||||
|
padding: 0 6px;
|
||||||
|
line-height: 32px;
|
||||||
|
|
||||||
|
&-content {
|
||||||
|
width: 120px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -126,6 +126,7 @@
|
|||||||
color: var(--color-content-text-1);
|
color: var(--color-content-text-1);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: transform 0.3s ease;
|
transition: transform 0.3s ease;
|
||||||
|
will-change: transform;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
transform: scale(1.04);
|
transform: scale(1.04);
|
||||||
|
|||||||
@@ -11,10 +11,10 @@
|
|||||||
<div class="terminal-sidebar-icon"
|
<div class="terminal-sidebar-icon"
|
||||||
:class="[
|
:class="[
|
||||||
iconClass,
|
iconClass,
|
||||||
action.disabled !== false ? '' : 'disabled-item',
|
action.disabled === true ? 'disabled-item' : '',
|
||||||
action.checked === true ? 'checked-item' : '',
|
action.checked === true ? 'checked-item' : '',
|
||||||
]"
|
]"
|
||||||
@click="action.disabled !== false ? action.click() : false">
|
@click="action.disabled === true ? false : action.click()">
|
||||||
<component :is="action.icon" :style="action?.iconStyle" />
|
<component :is="action.icon" :style="action?.iconStyle" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
<terminal-display-block />
|
<terminal-display-block />
|
||||||
<!-- 顶部工具栏 -->
|
<!-- 顶部工具栏 -->
|
||||||
<terminal-action-bar-block />
|
<terminal-action-bar-block />
|
||||||
|
<!-- 右键菜单 -->
|
||||||
|
<terminal-right-menu-block />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -20,6 +22,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import TerminalDisplayBlock from './terminal-display-block.vue';
|
import TerminalDisplayBlock from './terminal-display-block.vue';
|
||||||
import TerminalActionBarBlock from './terminal-action-bar-block.vue';
|
import TerminalActionBarBlock from './terminal-action-bar-block.vue';
|
||||||
|
import TerminalRightMenuBlock from './terminal-right-menu-block.vue';
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,227 @@
|
|||||||
|
<template>
|
||||||
|
<div class="terminal-setting-block">
|
||||||
|
<!-- 顶部 -->
|
||||||
|
<div class="terminal-setting-subtitle-wrapper">
|
||||||
|
<h3 class="terminal-setting-subtitle">
|
||||||
|
右键菜单设置
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<!-- 提示 -->
|
||||||
|
<a-alert class="mb16">修改后会立刻保存, 重新打开终端后生效 (无需刷新页面)</a-alert>
|
||||||
|
<!-- 内容区域 -->
|
||||||
|
<div class="terminal-setting-body block-body setting-body">
|
||||||
|
<!-- 功能项 -->
|
||||||
|
<div class="actions-container">
|
||||||
|
<div class="setting-label">功能</div>
|
||||||
|
<!-- 功能项列表 -->
|
||||||
|
<div class="actions-wrapper">
|
||||||
|
<a-row :gutter="[8, 8]">
|
||||||
|
<a-col :span="12"
|
||||||
|
v-for="(action, index) in ActionBarItems"
|
||||||
|
:key="index">
|
||||||
|
<div class="action-item" @click="clickAction(action.item)">
|
||||||
|
<!-- 图标 -->
|
||||||
|
<div class="action-icon">
|
||||||
|
<component :is="action.icon" />
|
||||||
|
</div>
|
||||||
|
<!-- 描述 -->
|
||||||
|
<div class="action-desc">
|
||||||
|
{{ action.content }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 菜单预览容器 -->
|
||||||
|
<div class="preview-container">
|
||||||
|
<div class="setting-label">菜单预览</div>
|
||||||
|
<div ref="popupContainer" />
|
||||||
|
</div>
|
||||||
|
<!-- 预览下拉菜单 -->
|
||||||
|
<a-dropdown v-if="popupContainer"
|
||||||
|
:popup-visible="true"
|
||||||
|
:popup-container="popupContainer"
|
||||||
|
:popup-max-height="false">
|
||||||
|
<template #content v-if="rightActions.length">
|
||||||
|
<a-doption v-for="(action, index) in rightActions"
|
||||||
|
:key="index">
|
||||||
|
<div class="preview-action">
|
||||||
|
<!-- 图标 -->
|
||||||
|
<div class="preview-icon">
|
||||||
|
<component :is="action.icon" />
|
||||||
|
</div>
|
||||||
|
<!-- 文本 -->
|
||||||
|
<div>{{ action.content }}</div>
|
||||||
|
</div>
|
||||||
|
<!-- 关闭按钮 -->
|
||||||
|
<div class="close-icon" @click="clickAction(action.item)">
|
||||||
|
<icon-close />
|
||||||
|
</div>
|
||||||
|
</a-doption>
|
||||||
|
</template>
|
||||||
|
<!-- 空数据 -->
|
||||||
|
<template #content v-else>
|
||||||
|
<a-doption>
|
||||||
|
点击左侧功能添加
|
||||||
|
</a-doption>
|
||||||
|
</template>
|
||||||
|
</a-dropdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'terminalRightMenuBlock'
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { ContextMenuItem } from '../../types/terminal.type';
|
||||||
|
import { computed, ref, watch } from 'vue';
|
||||||
|
import { useTerminalStore } from '@/store';
|
||||||
|
import { TerminalPreferenceItem } from '@/store/modules/terminal';
|
||||||
|
import { ActionBarItems } from '../../types/terminal.const';
|
||||||
|
|
||||||
|
const { preference, updateTerminalPreference } = useTerminalStore();
|
||||||
|
|
||||||
|
const popupContainer = ref();
|
||||||
|
const rightActionItems = ref<Array<string>>([...preference.rightMenuSetting]);
|
||||||
|
|
||||||
|
// // 监听同步
|
||||||
|
watch(rightActionItems, (v) => {
|
||||||
|
// 同步
|
||||||
|
updateTerminalPreference(TerminalPreferenceItem.RIGHT_MENU_SETTING, v, true);
|
||||||
|
}, { deep: true });
|
||||||
|
|
||||||
|
// 实际操作项
|
||||||
|
const rightActions = computed<Array<ContextMenuItem>>(() => {
|
||||||
|
return rightActionItems.value
|
||||||
|
.map(s => ActionBarItems.find(i => i.item === s) as ContextMenuItem)
|
||||||
|
.filter(Boolean);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 添加操作项
|
||||||
|
const clickAction = (item: string) => {
|
||||||
|
if (rightActionItems.value.includes(item)) {
|
||||||
|
// 移除
|
||||||
|
rightActionItems.value.splice(rightActionItems.value.indexOf(item), 1);
|
||||||
|
} else {
|
||||||
|
// 添加
|
||||||
|
rightActionItems.value.push(item);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
|
||||||
|
.setting-body {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-label {
|
||||||
|
display: flex;
|
||||||
|
max-width: 100%;
|
||||||
|
color: var(--color-text-2);
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
padding: 0;
|
||||||
|
line-height: 1.5715;
|
||||||
|
white-space: normal;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions-container {
|
||||||
|
width: 418px;
|
||||||
|
height: auto;
|
||||||
|
|
||||||
|
.actions-wrapper {
|
||||||
|
padding-right: 8px;
|
||||||
|
margin-right: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-item {
|
||||||
|
display: flex;
|
||||||
|
padding: 6px;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--color-fill-2);
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
will-change: transform;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.04);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-icon {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-right: 8px;
|
||||||
|
background-color: var(--color-fill-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-desc {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 14px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-container {
|
||||||
|
width: 242px;
|
||||||
|
height: auto;
|
||||||
|
|
||||||
|
:deep(.arco-dropdown-option-content) {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.close-icon {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-action {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-icon {
|
||||||
|
font-size: 18px;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-icon {
|
||||||
|
display: none;
|
||||||
|
padding: 3px;
|
||||||
|
border-radius: 12px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: .2s;
|
||||||
|
font-size: 15px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-fill-3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.arco-trigger-popup) {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -1,15 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- 终端右键菜单 -->
|
<!-- 终端右键菜单 -->
|
||||||
<a-dropdown trigger="contextMenu"
|
<a-dropdown class="terminal-context-menu"
|
||||||
|
trigger="contextMenu"
|
||||||
|
:popup-max-height="false"
|
||||||
position="bl"
|
position="bl"
|
||||||
alignPoint>
|
alignPoint>
|
||||||
<!-- 终端插槽 -->
|
<!-- 终端插槽 -->
|
||||||
<slot />
|
<slot />
|
||||||
<!-- 右键菜单 -->
|
<!-- 右键菜单 -->
|
||||||
<template v-if="preference.interactSetting.enableRightClickMenu" #content>
|
<template v-if="preference.interactSetting.enableRightClickMenu" #content>
|
||||||
<a-doption>Option 1</a-doption>
|
<a-doption v-for="(action, index) in actions"
|
||||||
<a-doption>Option 2</a-doption>
|
:key="index"
|
||||||
<a-doption>Option 3</a-doption>
|
:disabled="enabledStatus[action.item] === false"
|
||||||
|
@click="emits('click', action.item)">
|
||||||
|
<!-- 图标 -->
|
||||||
|
<div class="action-icon">
|
||||||
|
<component :is="action.icon" />
|
||||||
|
</div>
|
||||||
|
<!-- 文本 -->
|
||||||
|
<div>{{ action.content }}</div>
|
||||||
|
</a-doption>
|
||||||
</template>
|
</template>
|
||||||
</a-dropdown>
|
</a-dropdown>
|
||||||
</template>
|
</template>
|
||||||
@@ -21,14 +31,31 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import type { ContextMenuItem } from '../../types/terminal.type';
|
||||||
|
import { ActionBarItems } from '../../types/terminal.const';
|
||||||
import { useTerminalStore } from '@/store';
|
import { useTerminalStore } from '@/store';
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
enabledStatus: Record<string, boolean | undefined>
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emits = defineEmits(['click']);
|
||||||
|
|
||||||
const { preference } = useTerminalStore();
|
const { preference } = useTerminalStore();
|
||||||
|
|
||||||
// TODO 颜色 配置 触发事件
|
const actions: Array<ContextMenuItem> = !preference.interactSetting.enableRightClickMenu
|
||||||
|
? []
|
||||||
|
: preference.rightMenuSetting
|
||||||
|
.map(s => ActionBarItems.find(i => i.item === s) as ContextMenuItem)
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|
||||||
|
.action-icon {
|
||||||
|
font-size: 16px;
|
||||||
|
margin: 0 8px 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -37,7 +37,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 终端右键菜单 -->
|
<!-- 终端右键菜单 -->
|
||||||
<terminal-context-menu>
|
<terminal-context-menu :enabled-status="actionsEnabledStatus"
|
||||||
|
@click="action => actionsClickHandler[action] && actionsClickHandler[action]()">
|
||||||
<!-- 终端容器 -->
|
<!-- 终端容器 -->
|
||||||
<div class="terminal-wrapper"
|
<div class="terminal-wrapper"
|
||||||
:style="{ background: preference.theme.schema.background }">
|
:style="{ background: preference.theme.schema.background }">
|
||||||
@@ -92,9 +93,7 @@
|
|||||||
const session = ref<ITerminalSession>();
|
const session = ref<ITerminalSession>();
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
// 右键菜单补充 enableRightClickMenu 粘贴逻辑
|
// 设置快捷键 粘贴逻辑 禁用
|
||||||
// 设置快捷键 粘贴逻辑
|
|
||||||
// 读取快捷键并且禁用快捷键
|
|
||||||
// 截屏
|
// 截屏
|
||||||
// sftp
|
// sftp
|
||||||
|
|
||||||
@@ -124,14 +123,14 @@
|
|||||||
session.value?.find(word, next, options);
|
session.value?.find(word, next, options);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 操作禁用状态
|
// 操作启用状态
|
||||||
const actionsDisableStatus = computed<Record<string, boolean | undefined>>(() => {
|
const actionsEnabledStatus = computed<Record<string, boolean | undefined>>(() => {
|
||||||
return {
|
return {
|
||||||
paste: session.value?.canWrite,
|
paste: !!session.value?.canWrite,
|
||||||
interrupt: session.value?.canWrite,
|
interrupt: !!session.value?.canWrite,
|
||||||
enter: session.value?.canWrite,
|
enter: !!session.value?.canWrite,
|
||||||
commandEditor: session.value?.canWrite,
|
commandEditor: !!session.value?.canWrite,
|
||||||
disconnect: session.value?.connected,
|
disconnect: !!session.value?.connected,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -143,18 +142,16 @@
|
|||||||
toBottom: () => session.value?.toBottom(),
|
toBottom: () => session.value?.toBottom(),
|
||||||
// 全选
|
// 全选
|
||||||
checkAll: () => session.value?.selectAll(),
|
checkAll: () => session.value?.selectAll(),
|
||||||
// 复制选中部分
|
|
||||||
copy: () => session.value?.copySelection(),
|
|
||||||
// 搜索
|
// 搜索
|
||||||
search: () => searchModal.value.toggle(),
|
search: () => searchModal.value.toggle(),
|
||||||
|
// 复制选中部分
|
||||||
|
copy: () => session.value?.copySelection(),
|
||||||
// 粘贴
|
// 粘贴
|
||||||
paste: async () => session.value?.pasteTrimEnd(await readText()),
|
paste: async () => session.value?.pasteTrimEnd(await readText()),
|
||||||
// ctrl + c
|
// ctrl + c
|
||||||
interrupt: () => session.value?.paste(String.fromCharCode(3)),
|
interrupt: () => session.value?.paste(String.fromCharCode(3)),
|
||||||
// 回车
|
// 回车
|
||||||
enter: () => session.value?.paste(String.fromCharCode(13)),
|
enter: () => session.value?.paste(String.fromCharCode(13)),
|
||||||
// 命令编辑器
|
|
||||||
commandEditor: () => editorModal.value.open('', ''),
|
|
||||||
// 增大字号
|
// 增大字号
|
||||||
fontSizePlus: () => {
|
fontSizePlus: () => {
|
||||||
if (session.value) {
|
if (session.value) {
|
||||||
@@ -175,6 +172,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// 命令编辑器
|
||||||
|
commandEditor: () => editorModal.value.open('', ''),
|
||||||
// 清空
|
// 清空
|
||||||
clear: () => session.value?.clear(),
|
clear: () => session.value?.clear(),
|
||||||
// 断开连接
|
// 断开连接
|
||||||
@@ -190,7 +189,7 @@
|
|||||||
icon: s.icon,
|
icon: s.icon,
|
||||||
content: s.content,
|
content: s.content,
|
||||||
visible: preference.actionBarSetting[s.item] !== false,
|
visible: preference.actionBarSetting[s.item] !== false,
|
||||||
disabled: actionsDisableStatus.value[s.item] !== false,
|
disabled: actionsEnabledStatus.value[s.item] === false,
|
||||||
click: () => {
|
click: () => {
|
||||||
actionsClickHandler[s.item] && actionsClickHandler[s.item]();
|
actionsClickHandler[s.item] && actionsClickHandler[s.item]();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ export default class TerminalTabManager implements ITerminalTabManager {
|
|||||||
public items: Array<TerminalTabItem>;
|
public items: Array<TerminalTabItem>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
// fixme
|
||||||
|
// this.active = InnerTabs.SHORTCUT_SETTING.key;
|
||||||
|
// this.items = [InnerTabs.SHORTCUT_SETTING];
|
||||||
this.active = InnerTabs.NEW_CONNECTION.key;
|
this.active = InnerTabs.NEW_CONNECTION.key;
|
||||||
this.items = [InnerTabs.NEW_CONNECTION];
|
this.items = [InnerTabs.NEW_CONNECTION];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
import { ref, onBeforeMount, onUnmounted, onMounted } from 'vue';
|
import { ref, onBeforeMount, onUnmounted, onMounted } from 'vue';
|
||||||
import { dictKeys, InnerTabs } from './types/terminal.const';
|
import { dictKeys, InnerTabs } from './types/terminal.const';
|
||||||
import { useCacheStore, useDictStore, useTerminalStore } from '@/store';
|
import { useCacheStore, useDictStore, useTerminalStore } from '@/store';
|
||||||
|
import useLoading from '@/hooks/loading';
|
||||||
import TerminalHeader from './components/layout/terminal-header.vue';
|
import TerminalHeader from './components/layout/terminal-header.vue';
|
||||||
import TerminalLeftSidebar from './components/layout/terminal-left-sidebar.vue';
|
import TerminalLeftSidebar from './components/layout/terminal-left-sidebar.vue';
|
||||||
import TerminalRightSidebar from './components/layout/terminal-right-sidebar.vue';
|
import TerminalRightSidebar from './components/layout/terminal-right-sidebar.vue';
|
||||||
@@ -42,7 +43,6 @@
|
|||||||
import LoadingSkeleton from './components/layout/loading-skeleton.vue';
|
import LoadingSkeleton from './components/layout/loading-skeleton.vue';
|
||||||
import './assets/styles/layout.less';
|
import './assets/styles/layout.less';
|
||||||
import 'xterm/css/xterm.css';
|
import 'xterm/css/xterm.css';
|
||||||
import useLoading from '@/hooks/loading';
|
|
||||||
|
|
||||||
const terminalStore = useTerminalStore();
|
const terminalStore = useTerminalStore();
|
||||||
const dictStore = useDictStore();
|
const dictStore = useDictStore();
|
||||||
|
|||||||
@@ -80,18 +80,18 @@ export const ActionBarItems = [
|
|||||||
item: 'checkAll',
|
item: 'checkAll',
|
||||||
icon: 'icon-expand',
|
icon: 'icon-expand',
|
||||||
content: '全选',
|
content: '全选',
|
||||||
|
}, {
|
||||||
|
item: 'search',
|
||||||
|
icon: 'icon-find-replace',
|
||||||
|
content: '搜索',
|
||||||
}, {
|
}, {
|
||||||
item: 'copy',
|
item: 'copy',
|
||||||
icon: 'icon-copy',
|
icon: 'icon-copy',
|
||||||
content: '复制选中部分',
|
content: '复制',
|
||||||
}, {
|
}, {
|
||||||
item: 'paste',
|
item: 'paste',
|
||||||
icon: 'icon-paste',
|
icon: 'icon-paste',
|
||||||
content: '粘贴',
|
content: '粘贴',
|
||||||
}, {
|
|
||||||
item: 'search',
|
|
||||||
icon: 'icon-find-replace',
|
|
||||||
content: '搜索',
|
|
||||||
}, {
|
}, {
|
||||||
item: 'interrupt',
|
item: 'interrupt',
|
||||||
icon: 'icon-formula',
|
icon: 'icon-formula',
|
||||||
@@ -100,10 +100,6 @@ export const ActionBarItems = [
|
|||||||
item: 'enter',
|
item: 'enter',
|
||||||
icon: 'icon-play-arrow-fill',
|
icon: 'icon-play-arrow-fill',
|
||||||
content: '回车',
|
content: '回车',
|
||||||
}, {
|
|
||||||
item: 'commandEditor',
|
|
||||||
icon: 'icon-code-square',
|
|
||||||
content: '命令编辑器',
|
|
||||||
}, {
|
}, {
|
||||||
item: 'fontSizePlus',
|
item: 'fontSizePlus',
|
||||||
icon: 'icon-zoom-in',
|
icon: 'icon-zoom-in',
|
||||||
@@ -112,6 +108,10 @@ export const ActionBarItems = [
|
|||||||
item: 'fontSizeSubtract',
|
item: 'fontSizeSubtract',
|
||||||
icon: 'icon-zoom-out',
|
icon: 'icon-zoom-out',
|
||||||
content: '减小字号',
|
content: '减小字号',
|
||||||
|
}, {
|
||||||
|
item: 'commandEditor',
|
||||||
|
icon: 'icon-code-square',
|
||||||
|
content: '命令编辑器',
|
||||||
}, {
|
}, {
|
||||||
item: 'clear',
|
item: 'clear',
|
||||||
icon: 'icon-delete',
|
icon: 'icon-delete',
|
||||||
@@ -123,7 +123,7 @@ export const ActionBarItems = [
|
|||||||
}, {
|
}, {
|
||||||
item: 'close',
|
item: 'close',
|
||||||
icon: 'icon-close',
|
icon: 'icon-close',
|
||||||
content: '关闭',
|
content: '关闭终端',
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,13 @@ export interface CombinedHandlerItem {
|
|||||||
host?: HostQueryResponse;
|
host?: HostQueryResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 右键菜单元素
|
||||||
|
export interface ContextMenuItem {
|
||||||
|
item: string;
|
||||||
|
icon: string;
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
// ssh 额外配置
|
// ssh 额外配置
|
||||||
export interface SshExtraModel {
|
export interface SshExtraModel {
|
||||||
authType?: string;
|
authType?: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user