⚡ 优化命令片段处理逻辑.
This commit is contained in:
@@ -29,7 +29,6 @@ export interface CommandSnippetQueryResponse extends CommandSnippetQueryResponse
|
||||
|
||||
export interface CommandSnippetQueryResponseExtra {
|
||||
visible: boolean;
|
||||
expand?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -31,7 +31,6 @@ export interface PathBookmarkQueryResponse extends PathBookmarkQueryResponseExtr
|
||||
|
||||
export interface PathBookmarkQueryResponseExtra {
|
||||
visible: boolean;
|
||||
expand?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -227,7 +227,7 @@ export default class LogAppender implements ILogAppender {
|
||||
|
||||
// 复制
|
||||
copy(): void {
|
||||
copyText(this.current.terminal.getSelection(), '已复制');
|
||||
copyText(this.current.terminal.getSelection(), true);
|
||||
this.focus();
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
</template>
|
||||
<!-- 模板命令 -->
|
||||
<template #command="{ record }">
|
||||
<span class="copy-left" @click="copy(record.command, '已复制')">
|
||||
<span class="copy-left" @click="copy(record.command, true)">
|
||||
<icon-copy />
|
||||
</span>
|
||||
<span :title="record.command">{{ record.command }}</span>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'pathBookmarkGroupSelect'
|
||||
name: 'pathBookmarkGroupSelector'
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'commandSnippetGroupSelect'
|
||||
name: 'commandSnippetGroupSelector'
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
<template #append>
|
||||
<span class="allow-click span-blue"
|
||||
title="点击复制"
|
||||
@click="copy(inputValues.cron,'已复制')">
|
||||
@click="copy(inputValues.cron, true)">
|
||||
<icon-copy />
|
||||
</span>
|
||||
</template>
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<template #beforeValue="{ record }">
|
||||
<span class="copy-left"
|
||||
title="复制"
|
||||
@click="copy(record.beforeValue, '已复制')">
|
||||
@click="copy(record.beforeValue, true)">
|
||||
<icon-copy />
|
||||
</span>
|
||||
<span>{{ record.beforeValue }}</span>
|
||||
@@ -49,7 +49,7 @@
|
||||
<template #afterValue="{ record }">
|
||||
<span class="copy-left"
|
||||
title="复制"
|
||||
@click="copy(record.afterValue, '已复制')">
|
||||
@click="copy(record.afterValue, true)">
|
||||
<icon-copy />
|
||||
</span>
|
||||
<span>{{ record.afterValue }}</span>
|
||||
|
||||
@@ -7,7 +7,9 @@ export const copy = async (value: string | undefined, tips: string | boolean = `
|
||||
return;
|
||||
}
|
||||
await copyToClipboard(value);
|
||||
if (tips) {
|
||||
if (tips === true) {
|
||||
Message.success('已复制');
|
||||
} else if (tips) {
|
||||
Message.success(tips as string);
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
12
orion-visor-ui/src/store/modules/cache/index.ts
vendored
12
orion-visor-ui/src/store/modules/cache/index.ts
vendored
@@ -18,6 +18,8 @@ import { getCurrentAuthorizedHostIdentity, getCurrentAuthorizedHostKey } from '@
|
||||
import { getCommandSnippetGroupList } from '@/api/asset/command-snippet-group';
|
||||
import { getExecJobList } from '@/api/job/exec-job';
|
||||
import { getPathBookmarkGroupList } from '@/api/asset/path-bookmark-group';
|
||||
import { getCommandSnippetList } from '@/api/asset/command-snippet';
|
||||
import { getPathBookmarkList } from '@/api/asset/path-bookmark';
|
||||
|
||||
export default defineStore('cache', {
|
||||
state: (): CacheState => ({}),
|
||||
@@ -121,6 +123,16 @@ export default defineStore('cache', {
|
||||
return await this.load('pathBookmarkGroups', getPathBookmarkGroupList, force);
|
||||
},
|
||||
|
||||
// 获取命令片段列表
|
||||
async loadCommandSnippets(force = false) {
|
||||
return await this.load('commandSnippets', getCommandSnippetList, force);
|
||||
},
|
||||
|
||||
// 获取路径书签列表
|
||||
async loadPathBookmarks(force = false) {
|
||||
return await this.load('pathBookmarks', getPathBookmarkList, force);
|
||||
},
|
||||
|
||||
// 获取执行计划列表
|
||||
async loadExecJobs(force = false) {
|
||||
return await this.load('execJob', getExecJobList, force);
|
||||
|
||||
@@ -4,6 +4,7 @@ export type CacheType = 'users' | 'menus' | 'roles'
|
||||
| 'dictKeys'
|
||||
| 'authorizedHostKeys' | 'authorizedHostIdentities'
|
||||
| 'commandSnippetGroups' | 'pathBookmarkGroups'
|
||||
| 'commandSnippets' | 'pathBookmarks'
|
||||
| 'execJob'
|
||||
| string
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import type {
|
||||
TerminalShortcutSetting,
|
||||
TerminalState
|
||||
} from './types';
|
||||
import type { ITerminalSession, PanelSessionTabType, TerminalPanelTabItem } from '@/views/host/terminal/types/define';
|
||||
import type { ISshSession, ITerminalSession, PanelSessionTabType, TerminalPanelTabItem } from '@/views/host/terminal/types/define';
|
||||
import type { AuthorizedHostQueryResponse } from '@/api/asset/asset-authorized-data';
|
||||
import { getCurrentAuthorizedHost } from '@/api/asset/asset-authorized-data';
|
||||
import type { HostQueryResponse } from '@/api/asset/host';
|
||||
@@ -18,7 +18,7 @@ import { defineStore } from 'pinia';
|
||||
import { getPreference, updatePreference } from '@/api/user/preference';
|
||||
import { nextId } from '@/utils';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { TerminalTabs } from '@/views/host/terminal/types/const';
|
||||
import { PanelSessionType, TerminalTabs } from '@/views/host/terminal/types/const';
|
||||
import TerminalTabManager from '@/views/host/terminal/handler/terminal-tab-manager';
|
||||
import TerminalSessionManager from '@/views/host/terminal/handler/terminal-session-manager';
|
||||
import TerminalPanelManager from '@/views/host/terminal/handler/terminal-panel-manager';
|
||||
@@ -248,6 +248,22 @@ export default defineStore('terminal', {
|
||||
return this.sessionManager.getSession<T>(sessionTab.sessionId);
|
||||
},
|
||||
|
||||
// 拼接命令到当前会话
|
||||
appendCommandToCurrentSession(command: string, newLine: boolean = false) {
|
||||
this.appendCommandToSession(this.getCurrentSession<ISshSession>(PanelSessionType.SSH.type, true), command, newLine);
|
||||
},
|
||||
|
||||
// 拼接命令到会话
|
||||
appendCommandToSession(session: ISshSession | undefined, command: string, newLine: boolean = false) {
|
||||
const handler = session?.handler;
|
||||
if (handler && handler.enabledStatus('checkAppendMissing')) {
|
||||
if (newLine) {
|
||||
command = `${command}\r\n`;
|
||||
}
|
||||
handler.checkAppendMissing(command);
|
||||
}
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
@page-size-change="(size: number) => fetchTableData(1, size)">
|
||||
<!-- 模板命令 -->
|
||||
<template #command="{ record }">
|
||||
<span class="copy-left" @click="copy(record.command, '已复制')">
|
||||
<span class="copy-left" @click="copy(record.command, true)">
|
||||
<icon-copy />
|
||||
</span>
|
||||
<span :title="record.command">{{ record.command }}</span>
|
||||
|
||||
@@ -13,26 +13,26 @@
|
||||
<div class="snippet-container">
|
||||
<!-- 命令头部 -->
|
||||
<div class="snippet-header">
|
||||
<!-- 左侧按钮 -->
|
||||
<!-- 搜索框 -->
|
||||
<a-input-search class="snippet-header-input"
|
||||
v-model="filterValue"
|
||||
placeholder="请输入名称/命令"
|
||||
allow-clear />
|
||||
<!-- 右侧按钮 -->
|
||||
<a-space size="small">
|
||||
<!-- 创建命令 -->
|
||||
<span class="click-icon-wrapper snippet-header-icon"
|
||||
title="创建命令"
|
||||
@click="openAdd">
|
||||
<icon-plus />
|
||||
</span>
|
||||
<icon-plus />
|
||||
</span>
|
||||
<!-- 刷新 -->
|
||||
<span class="click-icon-wrapper snippet-header-icon"
|
||||
title="刷新"
|
||||
@click="fetchData(true)">
|
||||
<icon-refresh />
|
||||
</span>
|
||||
@click="fetchData">
|
||||
<icon-refresh />
|
||||
</span>
|
||||
</a-space>
|
||||
<!-- 搜索框 -->
|
||||
<a-input-search class="snippet-header-input"
|
||||
v-model="filterValue"
|
||||
placeholder="名称/命令"
|
||||
allow-clear />
|
||||
</div>
|
||||
<!-- 加载中 -->
|
||||
<a-skeleton v-if="loading"
|
||||
@@ -43,7 +43,7 @@
|
||||
:line-spacing="12" />
|
||||
</a-skeleton>
|
||||
<!-- 无数据 -->
|
||||
<a-empty v-else-if="!snippetValue || (snippetValue.groups.length === 0 && snippetValue.ungroupedItems.length === 0)"
|
||||
<a-empty v-else-if="snippetGroups.length === 0 && ungroupedItems.length === 0"
|
||||
style="padding: 28px 0">
|
||||
<span>暂无数据</span><br>
|
||||
<span>点击上方 '<icon-plus />' 添加一条数据吧~</span>
|
||||
@@ -51,13 +51,40 @@
|
||||
<!-- 命令片段 -->
|
||||
<div v-else class="snippet-list-container">
|
||||
<!-- 命令片段组 -->
|
||||
<command-snippet-list-group :value="snippetValue" />
|
||||
<a-collapse :bordered="false">
|
||||
<template v-for="group in snippetGroups">
|
||||
<a-collapse-item v-if="calcGroupTotal(group) > 0"
|
||||
:key="group.id"
|
||||
:header="group.name">
|
||||
<!-- 总量 -->
|
||||
<template #extra>
|
||||
{{ calcGroupTotal(group) }} 条
|
||||
</template>
|
||||
<!-- 代码片段 -->
|
||||
<template v-for="item in group.items">
|
||||
<command-snippet-item v-if="item.visible"
|
||||
:key="item.id"
|
||||
:item="item"
|
||||
@copy="(s: string) => copy(s, true)"
|
||||
@paste="(s: string) => appendCommandToCurrentSession(s)"
|
||||
@exec="(s: string) => appendCommandToCurrentSession(s, true)"
|
||||
@remove="remoteSnippet"
|
||||
@update="(s: CommandSnippetQueryResponse) => formDrawer.openUpdate(s)" />
|
||||
</template>
|
||||
</a-collapse-item>
|
||||
</template>
|
||||
</a-collapse>
|
||||
<!-- 未分组命令片段 -->
|
||||
<div class="ungrouped-snippet-container">
|
||||
<template v-for="item in snippetValue.ungroupedItems">
|
||||
<command-snippet-list-item v-if="item.visible"
|
||||
:key="item.id"
|
||||
:item="item" />
|
||||
<template v-for="item in ungroupedItems">
|
||||
<command-snippet-item v-if="item.visible"
|
||||
:key="item.id"
|
||||
:item="item"
|
||||
@copy="(s: string) => copy(s, true)"
|
||||
@paste="(s: string) => appendCommandToCurrentSession(s)"
|
||||
@exec="(s: string) => appendCommandToCurrentSession(s, true)"
|
||||
@remove="remoteSnippet"
|
||||
@update="(s: CommandSnippetQueryResponse) => formDrawer.openUpdate(s)" />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@@ -77,26 +104,29 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { ISshSession } from '../../types/define';
|
||||
import type { CommandSnippetWrapperResponse, CommandSnippetQueryResponse } from '@/api/asset/command-snippet';
|
||||
import { ref, watch, provide } from 'vue';
|
||||
import type { CommandSnippetQueryResponse } from '@/api/asset/command-snippet';
|
||||
import type { CommandSnippetGroupQueryResponse } from '@/api/asset/command-snippet-group';
|
||||
import { ref, watch } from 'vue';
|
||||
import useVisible from '@/hooks/visible';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import { deleteCommandSnippet, getCommandSnippetList } from '@/api/asset/command-snippet';
|
||||
import { deleteCommandSnippet } from '@/api/asset/command-snippet';
|
||||
import { useCacheStore, useTerminalStore } from '@/store';
|
||||
import { openUpdateSnippetKey, removeSnippetKey } from './types/const';
|
||||
import { PanelSessionType } from '../../types/const';
|
||||
import CommandSnippetListItem from './command-snippet-list-item.vue';
|
||||
import CommandSnippetListGroup from './command-snippet-list-group.vue';
|
||||
import { copy } from '@/hooks/copy';
|
||||
import CommandSnippetItem from './command-snippet-item.vue';
|
||||
import CommandSnippetFormDrawer from './command-snippet-form-drawer.vue';
|
||||
|
||||
const { loading, setLoading } = useLoading();
|
||||
const { visible, setVisible } = useVisible();
|
||||
const { getCurrentSession } = useTerminalStore();
|
||||
const { getCurrentSession, appendCommandToCurrentSession } = useTerminalStore();
|
||||
|
||||
const cacheStore = useCacheStore();
|
||||
|
||||
const formDrawer = ref();
|
||||
const filterValue = ref<string>();
|
||||
const snippetValue = ref<CommandSnippetWrapperResponse>();
|
||||
const filterValue = ref<string>('');
|
||||
|
||||
const snippetGroups = ref<Array<CommandSnippetGroupQueryResponse>>([]);
|
||||
const ungroupedItems = ref<Array<CommandSnippetQueryResponse>>([]);
|
||||
|
||||
// 打开
|
||||
const open = async () => {
|
||||
@@ -108,15 +138,13 @@
|
||||
defineExpose({ open });
|
||||
|
||||
// 加载数据
|
||||
const fetchData = async (force: boolean = false) => {
|
||||
if (snippetValue.value && !force) {
|
||||
return;
|
||||
}
|
||||
const fetchData = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
// 查询
|
||||
const { data } = await getCommandSnippetList();
|
||||
snippetValue.value = data;
|
||||
const data = await cacheStore.loadCommandSnippets(true);
|
||||
snippetGroups.value = data.groups;
|
||||
ungroupedItems.value = data.ungroupedItems;
|
||||
// 设置状态
|
||||
filterSnippet();
|
||||
} catch (e) {
|
||||
@@ -125,16 +153,21 @@
|
||||
}
|
||||
};
|
||||
|
||||
// 计算总量
|
||||
const calcGroupTotal = (group: CommandSnippetGroupQueryResponse) => {
|
||||
return group.items.filter(s => s.visible).length;
|
||||
};
|
||||
|
||||
// 过滤
|
||||
const filterSnippet = () => {
|
||||
snippetValue.value?.groups.forEach(g => {
|
||||
snippetGroups.value.forEach(g => {
|
||||
g.items?.forEach(s => {
|
||||
s.visible = !filterValue.value
|
||||
|| s.name.toLowerCase().includes(filterValue.value.toLowerCase())
|
||||
|| s.command.toLowerCase().includes(filterValue.value.toLowerCase());
|
||||
});
|
||||
});
|
||||
snippetValue.value?.ungroupedItems.forEach(s => {
|
||||
ungroupedItems.value.forEach(s => {
|
||||
s.visible = !filterValue.value
|
||||
|| s.name.toLowerCase().includes(filterValue.value.toLowerCase())
|
||||
|| s.command.toLowerCase().includes(filterValue.value.toLowerCase());
|
||||
@@ -161,41 +194,33 @@
|
||||
}
|
||||
};
|
||||
|
||||
// 暴露 修改抽屉
|
||||
provide(openUpdateSnippetKey, (e: CommandSnippetQueryResponse) => {
|
||||
formDrawer.value.openUpdate(e);
|
||||
});
|
||||
|
||||
// 暴露 删除
|
||||
provide(removeSnippetKey, async (id: number) => {
|
||||
if (!snippetValue.value) {
|
||||
return;
|
||||
}
|
||||
// 删除代码片段
|
||||
const remoteSnippet = async (id: number) => {
|
||||
// 删除
|
||||
await deleteCommandSnippet(id);
|
||||
// 查找并且删除未分组的数据
|
||||
if (findAndSplice(id, snippetValue.value.ungroupedItems)) {
|
||||
if (findAndSplice(id, ungroupedItems.value)) {
|
||||
return;
|
||||
}
|
||||
// 查找并且删除分组内数据
|
||||
for (let group of snippetValue.value.groups) {
|
||||
for (let group of snippetGroups.value) {
|
||||
if (findAndSplice(id, group.items)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 添加回调
|
||||
const onAdded = async (item: CommandSnippetQueryResponse) => {
|
||||
if (item.groupId) {
|
||||
let group = snippetValue.value?.groups.find(g => g.id === item.groupId);
|
||||
let group = snippetGroups.value.find(g => g.id === item.groupId);
|
||||
if (group) {
|
||||
group?.items.push(item);
|
||||
} else {
|
||||
const cacheGroups = await cacheStore.loadCommandSnippetGroups();
|
||||
const findGroup = cacheGroups.find(s => s.id === item.groupId);
|
||||
if (findGroup) {
|
||||
snippetValue.value?.groups.push({
|
||||
snippetGroups.value.push({
|
||||
id: item.groupId,
|
||||
name: findGroup.name,
|
||||
items: [item]
|
||||
@@ -203,7 +228,7 @@
|
||||
}
|
||||
}
|
||||
} else {
|
||||
snippetValue.value?.ungroupedItems.push(item);
|
||||
ungroupedItems.value.push(item);
|
||||
}
|
||||
// 重置过滤
|
||||
filterSnippet();
|
||||
@@ -211,16 +236,13 @@
|
||||
|
||||
// 修改回调
|
||||
const onUpdated = async (item: CommandSnippetQueryResponse) => {
|
||||
if (!snippetValue.value) {
|
||||
return;
|
||||
}
|
||||
// 查找原始数据
|
||||
let originItem;
|
||||
const findInUngrouped = snippetValue.value.ungroupedItems.find(s => s.id === item.id);
|
||||
const findInUngrouped = ungroupedItems.value.find(s => s.id === item.id);
|
||||
if (findInUngrouped) {
|
||||
originItem = findInUngrouped;
|
||||
} else {
|
||||
for (let group of snippetValue.value.groups) {
|
||||
for (let group of snippetGroups.value) {
|
||||
const find = group.items.find(s => s.id === item.id);
|
||||
if (find) {
|
||||
originItem = find;
|
||||
@@ -232,12 +254,12 @@
|
||||
return;
|
||||
}
|
||||
// 检查分组是否存在
|
||||
const findGroup = snippetValue.value.groups.find(s => s.id === item.groupId);
|
||||
const findGroup = snippetGroups.value.find(s => s.id === item.groupId);
|
||||
if (!findGroup) {
|
||||
const cacheGroups = await cacheStore.loadCommandSnippetGroups();
|
||||
const cacheGroup = cacheGroups.find(s => s.id === item.groupId);
|
||||
if (cacheGroup) {
|
||||
snippetValue.value.groups.push({
|
||||
snippetGroups.value.push({
|
||||
id: item.groupId,
|
||||
name: cacheGroup.name,
|
||||
items: []
|
||||
@@ -253,22 +275,22 @@
|
||||
if (item.groupId !== originGroupId) {
|
||||
// 从原始分组移除
|
||||
if (originGroupId) {
|
||||
const findGroup = snippetValue.value.groups.find(s => s.id === originGroupId);
|
||||
const findGroup = snippetGroups.value.find(s => s.id === originGroupId);
|
||||
if (findGroup) {
|
||||
findAndSplice(item.id, findGroup.items);
|
||||
}
|
||||
} else {
|
||||
// 从未分组数据中移除
|
||||
findAndSplice(item.id, snippetValue.value.ungroupedItems);
|
||||
findAndSplice(item.id, ungroupedItems.value);
|
||||
}
|
||||
// 添加到新分组
|
||||
if (item.groupId) {
|
||||
const findGroup = snippetValue.value.groups.find(s => s.id === item.groupId);
|
||||
const findGroup = snippetGroups.value.find(s => s.id === item.groupId);
|
||||
if (findGroup) {
|
||||
findGroup.items.push(item);
|
||||
}
|
||||
} else {
|
||||
snippetValue.value.ungroupedItems.push(originItem);
|
||||
ungroupedItems.value.push(originItem);
|
||||
}
|
||||
}
|
||||
// 重置过滤
|
||||
@@ -332,6 +354,32 @@
|
||||
&:hover::-webkit-scrollbar-thumb {
|
||||
background: var(--color-fill-4);
|
||||
}
|
||||
|
||||
:deep(.arco-collapse-item) {
|
||||
border: none;
|
||||
|
||||
&-header {
|
||||
border: none;
|
||||
|
||||
&-title {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
&-extra {
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
&-content {
|
||||
background-color: unset;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&-content-box {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.loading-skeleton {
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
</a-form-item>
|
||||
<!-- 分组 -->
|
||||
<a-form-item field="groupId" label="分组">
|
||||
<command-snippet-group-select v-model="formModel.groupId" />
|
||||
<command-snippet-group-selector v-model="formModel.groupId" />
|
||||
</a-form-item>
|
||||
<!-- 代码片段 -->
|
||||
<a-form-item field="command"
|
||||
@@ -49,12 +49,12 @@
|
||||
<script lang="ts" setup>
|
||||
import type { CommandSnippetUpdateRequest } from '@/api/asset/command-snippet';
|
||||
import { ref } from 'vue';
|
||||
import { createCommandSnippet, updateCommandSnippet } from '@/api/asset/command-snippet';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import useVisible from '@/hooks/visible';
|
||||
import { createCommandSnippet, updateCommandSnippet } from '@/api/asset/command-snippet';
|
||||
import formRules from './types/form.rules';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import CommandSnippetGroupSelect from './command-snippet-group-select.vue';
|
||||
import CommandSnippetGroupSelector from '@/components/host/command-snippte/gruop/selector/index.vue';
|
||||
|
||||
const { visible, setVisible } = useVisible();
|
||||
const { loading, setLoading } = useLoading();
|
||||
@@ -77,10 +77,10 @@
|
||||
const emits = defineEmits(['added', 'updated']);
|
||||
|
||||
// 打开新增
|
||||
const openAdd = () => {
|
||||
const openAdd = (command: string = '') => {
|
||||
title.value = '添加命令片段';
|
||||
isAddHandle.value = true;
|
||||
renderForm({ ...defaultForm() });
|
||||
renderForm({ ...defaultForm(), command });
|
||||
setVisible(true);
|
||||
};
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
alignPoint>
|
||||
<!-- 命令 -->
|
||||
<div class="snippet-item-wrapper"
|
||||
:class="[!!item.expand ? 'snippet-item-wrapper-expand' : '']"
|
||||
:class="[expand ? 'snippet-item-wrapper-expand' : '']"
|
||||
@click="clickItem">
|
||||
<div class="snippet-item">
|
||||
<div class="snippet-item-title">
|
||||
@@ -22,7 +22,7 @@
|
||||
size="small"
|
||||
:checkable="true"
|
||||
:checked="true"
|
||||
@click.stop.prevent="paste">
|
||||
@click.stop.prevent="emits('paste', item.command)">
|
||||
<template #icon>
|
||||
<icon-paste />
|
||||
</template>
|
||||
@@ -33,7 +33,7 @@
|
||||
size="small"
|
||||
:checkable="true"
|
||||
:checked="true"
|
||||
@click.stop="exec">
|
||||
@click.stop="emits('exec', item.command)">
|
||||
<template #icon>
|
||||
<icon-thunderbolt />
|
||||
</template>
|
||||
@@ -52,43 +52,43 @@
|
||||
<!-- 右键菜单 -->
|
||||
<template #content>
|
||||
<!-- 复制 -->
|
||||
<a-doption @click="copyCommand">
|
||||
<a-doption @click="emits('copy', item.command)">
|
||||
<div class="terminal-context-menu-icon">
|
||||
<icon-copy />
|
||||
</div>
|
||||
<div>复制</div>
|
||||
</a-doption>
|
||||
<!-- 粘贴 -->
|
||||
<a-doption @click="paste">
|
||||
<a-doption @click="emits('paste', item.command)">
|
||||
<div class="terminal-context-menu-icon">
|
||||
<icon-paste />
|
||||
</div>
|
||||
<div>粘贴</div>
|
||||
</a-doption>
|
||||
<!-- 执行 -->
|
||||
<a-doption @click="exec">
|
||||
<a-doption @click="emits('exec', item.command)">
|
||||
<div class="terminal-context-menu-icon">
|
||||
<icon-thunderbolt />
|
||||
</div>
|
||||
<div>执行</div>
|
||||
</a-doption>
|
||||
<!-- 修改 -->
|
||||
<a-doption @click="openUpdateSnippet(item)">
|
||||
<a-doption @click="emits('update', item)">
|
||||
<div class="terminal-context-menu-icon">
|
||||
<icon-edit />
|
||||
</div>
|
||||
<div>修改</div>
|
||||
</a-doption>
|
||||
<!-- 删除 -->
|
||||
<a-doption @click="removeSnippet(item.id)">
|
||||
<a-doption @click="emits('remove', item.id)">
|
||||
<div class="terminal-context-menu-icon">
|
||||
<icon-delete />
|
||||
</div>
|
||||
<div>删除</div>
|
||||
</a-doption>
|
||||
<!-- 展开 -->
|
||||
<a-doption v-if="!item.expand"
|
||||
@click="() => item.expand = true">
|
||||
<a-doption v-if="!expand"
|
||||
@click="() => expand = true">
|
||||
<div class="terminal-context-menu-icon">
|
||||
<icon-expand />
|
||||
</div>
|
||||
@@ -96,7 +96,7 @@
|
||||
</a-doption>
|
||||
<!-- 收起 -->
|
||||
<a-doption v-else
|
||||
@click="() => item.expand = false">
|
||||
@click="() => expand = false">
|
||||
<div class="terminal-context-menu-icon">
|
||||
<icon-shrink />
|
||||
</div>
|
||||
@@ -108,40 +108,31 @@
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'commandSnippetListItem'
|
||||
name: 'commandSnippetItem'
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { ISshSession } from '../../types/define';
|
||||
import type { CommandSnippetQueryResponse } from '@/api/asset/command-snippet';
|
||||
import { useTerminalStore } from '@/store';
|
||||
import { ref } from 'vue';
|
||||
import { useDebounceFn } from '@vueuse/core';
|
||||
import { copy } from '@/hooks/copy';
|
||||
import { inject } from 'vue';
|
||||
import { openUpdateSnippetKey, removeSnippetKey } from './types/const';
|
||||
import { PanelSessionType } from '../../types/const';
|
||||
|
||||
const props = defineProps<{
|
||||
item: CommandSnippetQueryResponse;
|
||||
}>();
|
||||
|
||||
const { getCurrentSession } = useTerminalStore();
|
||||
const emits = defineEmits(['remove', 'update', 'copy', 'exec', 'paste']);
|
||||
|
||||
const expand = ref(false);
|
||||
|
||||
let clickCount = 0;
|
||||
|
||||
// 修改
|
||||
const openUpdateSnippet = inject(openUpdateSnippetKey) as (item: CommandSnippetQueryResponse) => void;
|
||||
|
||||
// 删除
|
||||
const removeSnippet = inject(removeSnippetKey) as (id: number) => void;
|
||||
|
||||
// 点击命令
|
||||
const clickItem = () => {
|
||||
if (++clickCount == 2) {
|
||||
// 双击执行
|
||||
clickCount = 0;
|
||||
exec();
|
||||
emits('exec', props.item.command);
|
||||
} else {
|
||||
// 单击展开
|
||||
expandItem();
|
||||
@@ -153,7 +144,7 @@
|
||||
setTimeout(() => {
|
||||
// 为 0 则代表为双击
|
||||
if (clickCount !== 0) {
|
||||
props.item.expand = !props.item.expand;
|
||||
expand.value = !expand.value;
|
||||
clickCount = 0;
|
||||
}
|
||||
}, 50);
|
||||
@@ -161,7 +152,7 @@
|
||||
|
||||
// 点击命令
|
||||
const clickCommand = (e: Event) => {
|
||||
if (props.item.expand) {
|
||||
if (expand.value) {
|
||||
// 获取选中的文本
|
||||
const selectedText = window.getSelection()?.toString();
|
||||
if (selectedText) {
|
||||
@@ -170,29 +161,6 @@
|
||||
}
|
||||
};
|
||||
|
||||
// 复制命令
|
||||
const copyCommand = () => {
|
||||
copy(props.item.command, '已复制');
|
||||
};
|
||||
|
||||
// 粘贴
|
||||
const paste = () => {
|
||||
write(props.item.command);
|
||||
};
|
||||
|
||||
// 执行
|
||||
const exec = () => {
|
||||
write(props.item.command + '\r\n');
|
||||
};
|
||||
|
||||
// 写入命令
|
||||
const write = (command: string) => {
|
||||
const handler = getCurrentSession<ISshSession>(PanelSessionType.SSH.type, true)?.handler;
|
||||
if (handler && handler.enabledStatus('checkAppendMissing')) {
|
||||
handler.checkAppendMissing(command);
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@@ -1,69 +0,0 @@
|
||||
<template>
|
||||
<a-collapse :bordered="false">
|
||||
<template v-for="group in value.groups">
|
||||
<a-collapse-item v-if="calcTotal(group) > 0"
|
||||
:key="group.id"
|
||||
:header="group.name">
|
||||
<!-- 总量 -->
|
||||
<template #extra>
|
||||
{{ calcTotal(group) }} 条
|
||||
</template>
|
||||
<!-- snippet -->
|
||||
<template v-for="item in group.items">
|
||||
<command-snippet-list-item v-if="item.visible"
|
||||
:key="item.id"
|
||||
:item="item" />
|
||||
</template>
|
||||
</a-collapse-item>
|
||||
</template>
|
||||
</a-collapse>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'commandSnippetListGroup'
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { CommandSnippetGroupQueryResponse } from '@/api/asset/command-snippet-group';
|
||||
import type { CommandSnippetWrapperResponse } from '@/api/asset/command-snippet';
|
||||
import CommandSnippetListItem from './command-snippet-list-item.vue';
|
||||
|
||||
defineProps<{
|
||||
value: CommandSnippetWrapperResponse;
|
||||
}>();
|
||||
|
||||
// 计算总量
|
||||
const calcTotal = (group: CommandSnippetGroupQueryResponse) => {
|
||||
return group.items.filter(s => s.visible).length;
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.arco-collapse-item) {
|
||||
border: none;
|
||||
|
||||
&-header {
|
||||
border: none;
|
||||
|
||||
&-title {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
&-extra {
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
&-content {
|
||||
background-color: unset;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&-content-box {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +0,0 @@
|
||||
// 打开 updateSnippet key
|
||||
export const openUpdateSnippetKey = Symbol();
|
||||
|
||||
// 删除 snippet key
|
||||
export const removeSnippetKey = Symbol();
|
||||
@@ -13,26 +13,26 @@
|
||||
<div class="path-container">
|
||||
<!-- 路径头部 -->
|
||||
<div class="path-header">
|
||||
<!-- 左侧按钮 -->
|
||||
<!-- 搜索框 -->
|
||||
<a-input-search class="path-header-input"
|
||||
v-model="filterValue"
|
||||
placeholder="请输入名称/路径"
|
||||
allow-clear />
|
||||
<!-- 右侧侧按钮 -->
|
||||
<a-space size="small">
|
||||
<!-- 创建路径 -->
|
||||
<span class="click-icon-wrapper path-header-icon"
|
||||
title="创建路径"
|
||||
@click="openAdd">
|
||||
<icon-plus />
|
||||
</span>
|
||||
<icon-plus />
|
||||
</span>
|
||||
<!-- 刷新 -->
|
||||
<span class="click-icon-wrapper path-header-icon"
|
||||
title="刷新"
|
||||
@click="fetchData(true)">
|
||||
<icon-refresh />
|
||||
</span>
|
||||
@click="fetchData">
|
||||
<icon-refresh />
|
||||
</span>
|
||||
</a-space>
|
||||
<!-- 搜索框 -->
|
||||
<a-input-search class="path-header-input"
|
||||
v-model="filterValue"
|
||||
placeholder="名称/路径"
|
||||
allow-clear />
|
||||
</div>
|
||||
<!-- 加载中 -->
|
||||
<a-skeleton v-if="loading"
|
||||
@@ -43,7 +43,7 @@
|
||||
:line-spacing="12" />
|
||||
</a-skeleton>
|
||||
<!-- 无数据 -->
|
||||
<a-empty v-else-if="!pathBookmarkData || (pathBookmarkData.groups.length === 0 && pathBookmarkData.ungroupedItems.length === 0)"
|
||||
<a-empty v-else-if="bookmarkGroups.length === 0 && ungroupedItems.length === 0"
|
||||
style="padding: 28px 0">
|
||||
<span>暂无数据</span><br>
|
||||
<span>点击上方 '<icon-plus />' 添加一条数据吧~</span>
|
||||
@@ -51,13 +51,42 @@
|
||||
<!-- 路径书签 -->
|
||||
<div v-else class="path-list-container">
|
||||
<!-- 路径书签组 -->
|
||||
<path-bookmark-list-group :value="pathBookmarkData" />
|
||||
<a-collapse :bordered="false">
|
||||
<template v-for="group in bookmarkGroups">
|
||||
<a-collapse-item v-if="calcGroupTotal(group) > 0"
|
||||
:key="group.id"
|
||||
:header="group.name">
|
||||
<!-- 总量 -->
|
||||
<template #extra>
|
||||
{{ calcGroupTotal(group) }} 条
|
||||
</template>
|
||||
<!-- 路径 -->
|
||||
<template v-for="item in group.items">
|
||||
<path-bookmark-item v-if="item.visible"
|
||||
:key="item.id"
|
||||
:item="item"
|
||||
@copy="(s: string) => copy(s, true)"
|
||||
@paste="(s: string) => appendCommandToCurrentSession(s)"
|
||||
@exec="(s: string) => appendCommandToCurrentSession(s, true)"
|
||||
@change="(s: string) => changePath(s)"
|
||||
@update="(s: PathBookmarkQueryResponse) => formDrawer.openUpdate(s)"
|
||||
@remove="removeBookmark" />
|
||||
</template>
|
||||
</a-collapse-item>
|
||||
</template>
|
||||
</a-collapse>
|
||||
<!-- 未分组路径书签 -->
|
||||
<div class="ungrouped-path-container">
|
||||
<template v-for="item in pathBookmarkData.ungroupedItems">
|
||||
<path-bookmark-list-item v-if="item.visible"
|
||||
:key="item.id"
|
||||
:item="item" />
|
||||
<template v-for="item in ungroupedItems">
|
||||
<path-bookmark-item v-if="item.visible"
|
||||
:key="item.id"
|
||||
:item="item"
|
||||
@copy="(s: string) => copy(s, true)"
|
||||
@paste="(s: string) => appendCommandToCurrentSession(s)"
|
||||
@exec="(s: string) => appendCommandToCurrentSession(s, true)"
|
||||
@change="(s: string) => changePath(s)"
|
||||
@update="(s: PathBookmarkQueryResponse) => formDrawer.openUpdate(s)"
|
||||
@remove="removeBookmark" />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@@ -76,27 +105,28 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { ISshSession } from '../../types/define';
|
||||
import type { PathBookmarkWrapperResponse, PathBookmarkQueryResponse } from '@/api/asset/path-bookmark';
|
||||
import { ref, provide, watch } from 'vue';
|
||||
import type { ISftpSession, ISshSession } from '../../types/define';
|
||||
import type { PathBookmarkQueryResponse } from '@/api/asset/path-bookmark';
|
||||
import type { PathBookmarkGroupQueryResponse } from '@/api/asset/path-bookmark-group';
|
||||
import { ref, watch } from 'vue';
|
||||
import useVisible from '@/hooks/visible';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import { deletePathBookmark, getPathBookmarkList } from '@/api/asset/path-bookmark';
|
||||
import { deletePathBookmark } from '@/api/asset/path-bookmark';
|
||||
import { useCacheStore, useTerminalStore } from '@/store';
|
||||
import { PanelSessionType } from '../../types/const';
|
||||
import { openUpdatePathKey, removePathKey } from './types/const';
|
||||
import PathBookmarkListItem from './path-bookmark-list-item.vue';
|
||||
import PathBookmarkListGroup from './path-bookmark-list-group.vue';
|
||||
import { copy } from '@/hooks/copy';
|
||||
import PathBookmarkItem from './path-bookmark-item.vue';
|
||||
import PathBookmarkFormDrawer from './path-bookmark-form-drawer.vue';
|
||||
|
||||
const { loading, setLoading } = useLoading();
|
||||
const { visible, setVisible } = useVisible();
|
||||
const { getCurrentSession } = useTerminalStore();
|
||||
const { getCurrentSession, appendCommandToCurrentSession } = useTerminalStore();
|
||||
const cacheStore = useCacheStore();
|
||||
|
||||
const formDrawer = ref();
|
||||
const filterValue = ref<string>();
|
||||
const pathBookmarkData = ref<PathBookmarkWrapperResponse>();
|
||||
const bookmarkGroups = ref<Array<PathBookmarkGroupQueryResponse>>([]);
|
||||
const ungroupedItems = ref<Array<PathBookmarkQueryResponse>>([]);
|
||||
|
||||
// 打开
|
||||
const open = async () => {
|
||||
@@ -108,15 +138,13 @@
|
||||
defineExpose({ open });
|
||||
|
||||
// 加载数据
|
||||
const fetchData = async (force: boolean = false) => {
|
||||
if (pathBookmarkData.value && !force) {
|
||||
return;
|
||||
}
|
||||
const fetchData = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
// 查询
|
||||
const { data } = await getPathBookmarkList();
|
||||
pathBookmarkData.value = data;
|
||||
const data = await cacheStore.loadPathBookmarks(true);
|
||||
bookmarkGroups.value = data.groups;
|
||||
ungroupedItems.value = data.ungroupedItems;
|
||||
// 设置状态
|
||||
filterPath();
|
||||
} catch (e) {
|
||||
@@ -127,14 +155,14 @@
|
||||
|
||||
// 过滤
|
||||
const filterPath = () => {
|
||||
pathBookmarkData.value?.groups.forEach(g => {
|
||||
bookmarkGroups.value.forEach(g => {
|
||||
g.items?.forEach(s => {
|
||||
s.visible = !filterValue.value
|
||||
|| s.name.toLowerCase().includes(filterValue.value.toLowerCase())
|
||||
|| s.path.toLowerCase().includes(filterValue.value.toLowerCase());
|
||||
});
|
||||
});
|
||||
pathBookmarkData.value?.ungroupedItems.forEach(s => {
|
||||
ungroupedItems.value.forEach(s => {
|
||||
s.visible = !filterValue.value
|
||||
|| s.name.toLowerCase().includes(filterValue.value.toLowerCase())
|
||||
|| s.path.toLowerCase().includes(filterValue.value.toLowerCase());
|
||||
@@ -149,6 +177,11 @@
|
||||
formDrawer.value.openAdd();
|
||||
};
|
||||
|
||||
// 计算总量
|
||||
const calcGroupTotal = (group: PathBookmarkGroupQueryResponse) => {
|
||||
return group.items.filter(s => s.visible).length;
|
||||
};
|
||||
|
||||
// 查找并且删除
|
||||
const findAndSplice = (id: number, items: Array<PathBookmarkQueryResponse>) => {
|
||||
if (items) {
|
||||
@@ -161,41 +194,38 @@
|
||||
}
|
||||
};
|
||||
|
||||
// 暴露 修改抽屉
|
||||
provide(openUpdatePathKey, (e: PathBookmarkQueryResponse) => {
|
||||
formDrawer.value.openUpdate(e);
|
||||
});
|
||||
// 查询 sftp 文件列表
|
||||
const changePath = (path: string) => {
|
||||
getCurrentSession<ISftpSession>(PanelSessionType.SFTP.type, true)?.list(path);
|
||||
};
|
||||
|
||||
// 暴露 删除
|
||||
provide(removePathKey, async (id: number) => {
|
||||
if (!pathBookmarkData.value) {
|
||||
return;
|
||||
}
|
||||
// 删除书签路径
|
||||
const removeBookmark = async (id: number) => {
|
||||
// 删除
|
||||
await deletePathBookmark(id);
|
||||
// 查找并且删除未分组的数据
|
||||
if (findAndSplice(id, pathBookmarkData.value.ungroupedItems)) {
|
||||
if (findAndSplice(id, ungroupedItems.value)) {
|
||||
return;
|
||||
}
|
||||
// 查找并且删除分组内数据
|
||||
for (let group of pathBookmarkData.value.groups) {
|
||||
for (let group of bookmarkGroups.value) {
|
||||
if (findAndSplice(id, group.items)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 添加回调
|
||||
const onAdded = async (item: PathBookmarkQueryResponse) => {
|
||||
if (item.groupId) {
|
||||
let group = pathBookmarkData.value?.groups.find(g => g.id === item.groupId);
|
||||
let group = bookmarkGroups.value.find(g => g.id === item.groupId);
|
||||
if (group) {
|
||||
group?.items.push(item);
|
||||
} else {
|
||||
const cacheGroups = await cacheStore.loadPathBookmarkGroups();
|
||||
const findGroup = cacheGroups.find(s => s.id === item.groupId);
|
||||
if (findGroup) {
|
||||
pathBookmarkData.value?.groups.push({
|
||||
bookmarkGroups.value.push({
|
||||
id: item.groupId,
|
||||
name: findGroup.name,
|
||||
items: [item]
|
||||
@@ -203,7 +233,7 @@
|
||||
}
|
||||
}
|
||||
} else {
|
||||
pathBookmarkData.value?.ungroupedItems.push(item);
|
||||
ungroupedItems.value.push(item);
|
||||
}
|
||||
// 重置过滤
|
||||
filterPath();
|
||||
@@ -211,16 +241,13 @@
|
||||
|
||||
// 修改回调
|
||||
const onUpdated = async (item: PathBookmarkQueryResponse) => {
|
||||
if (!pathBookmarkData.value) {
|
||||
return;
|
||||
}
|
||||
// 查找原始数据
|
||||
let originItem;
|
||||
const findInUngrouped = pathBookmarkData.value.ungroupedItems.find(s => s.id === item.id);
|
||||
const findInUngrouped = ungroupedItems.value.find(s => s.id === item.id);
|
||||
if (findInUngrouped) {
|
||||
originItem = findInUngrouped;
|
||||
} else {
|
||||
for (let group of pathBookmarkData.value.groups) {
|
||||
for (let group of bookmarkGroups.value) {
|
||||
const find = group.items.find(s => s.id === item.id);
|
||||
if (find) {
|
||||
originItem = find;
|
||||
@@ -232,12 +259,12 @@
|
||||
return;
|
||||
}
|
||||
// 检查分组是否存在
|
||||
const findGroup = pathBookmarkData.value.groups.find(s => s.id === item.groupId);
|
||||
const findGroup = bookmarkGroups.value.find(s => s.id === item.groupId);
|
||||
if (!findGroup) {
|
||||
const cacheGroups = await cacheStore.loadPathBookmarkGroups();
|
||||
const cacheGroup = cacheGroups.find(s => s.id === item.groupId);
|
||||
if (cacheGroup) {
|
||||
pathBookmarkData.value.groups.push({
|
||||
bookmarkGroups.value.push({
|
||||
id: item.groupId,
|
||||
name: cacheGroup.name,
|
||||
items: []
|
||||
@@ -253,22 +280,22 @@
|
||||
if (item.groupId !== originGroupId) {
|
||||
// 从原始分组移除
|
||||
if (originGroupId) {
|
||||
const findGroup = pathBookmarkData.value.groups.find(s => s.id === originGroupId);
|
||||
const findGroup = bookmarkGroups.value.find(s => s.id === originGroupId);
|
||||
if (findGroup) {
|
||||
findAndSplice(item.id, findGroup.items);
|
||||
}
|
||||
} else {
|
||||
// 从未分组数据中移除
|
||||
findAndSplice(item.id, pathBookmarkData.value.ungroupedItems);
|
||||
findAndSplice(item.id, ungroupedItems.value);
|
||||
}
|
||||
// 添加到新分组
|
||||
if (item.groupId) {
|
||||
const findGroup = pathBookmarkData.value.groups.find(s => s.id === item.groupId);
|
||||
const findGroup = bookmarkGroups.value.find(s => s.id === item.groupId);
|
||||
if (findGroup) {
|
||||
findGroup.items.push(item);
|
||||
}
|
||||
} else {
|
||||
pathBookmarkData.value.ungroupedItems.push(originItem);
|
||||
ungroupedItems.value.push(originItem);
|
||||
}
|
||||
}
|
||||
// 重置过滤
|
||||
@@ -332,6 +359,31 @@
|
||||
&:hover::-webkit-scrollbar-thumb {
|
||||
background: var(--color-fill-4);
|
||||
}
|
||||
|
||||
:deep(.arco-collapse-item) {
|
||||
border: none;
|
||||
|
||||
&-header {
|
||||
border: none;
|
||||
|
||||
&-title {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
&-extra {
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
&-content {
|
||||
background-color: unset;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&-content-box {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loading-skeleton {
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
</a-form-item>
|
||||
<!-- 分组 -->
|
||||
<a-form-item field="groupId" label="分组">
|
||||
<path-bookmark-group-select v-model="formModel.groupId" />
|
||||
<path-bookmark-group-selector v-model="formModel.groupId" />
|
||||
</a-form-item>
|
||||
<!-- 类型 -->
|
||||
<a-form-item field="type" label="类型">
|
||||
@@ -54,13 +54,13 @@
|
||||
import { ref } from 'vue';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import useVisible from '@/hooks/visible';
|
||||
import { createPathBookmark, updatePathBookmark } from '@/api/asset/path-bookmark';
|
||||
import formRules from './types/form.rules';
|
||||
import { createPathBookmark, updatePathBookmark } from '@/api/asset/path-bookmark';
|
||||
import { PathBookmarkType } from './types/const';
|
||||
import { pathBookmarkTypeKey } from '../../types/const';
|
||||
import { useDictStore } from '@/store';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import PathBookmarkGroupSelect from './path-bookmark-group-select.vue';
|
||||
import { pathBookmarkTypeKey } from '../../types/const';
|
||||
import PathBookmarkGroupSelector from '@/components/host/bookmark-path/group/selector/index.vue';
|
||||
|
||||
const { visible, setVisible } = useVisible();
|
||||
const { loading, setLoading } = useLoading();
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
alignPoint>
|
||||
<!-- 路径 -->
|
||||
<div class="path-item-wrapper"
|
||||
:class="[!!item.expand ? 'path-item-wrapper-expand' : '']"
|
||||
:class="[expand ? 'path-item-wrapper-expand' : '']"
|
||||
@click="clickItem">
|
||||
<div class="path-item">
|
||||
<div class="path-item-title">
|
||||
@@ -33,7 +33,7 @@
|
||||
size="small"
|
||||
:checkable="true"
|
||||
:checked="true"
|
||||
@click.stop.prevent="copyPath">
|
||||
@click.stop.prevent="emits('copy', item.path)">
|
||||
<template #icon>
|
||||
<icon-copy />
|
||||
</template>
|
||||
@@ -58,36 +58,36 @@
|
||||
<div>进入</div>
|
||||
</a-doption>
|
||||
<!-- 复制 -->
|
||||
<a-doption @click="copyPath">
|
||||
<a-doption @click="emits('copy', item.path)">
|
||||
<div class="terminal-context-menu-icon">
|
||||
<icon-copy />
|
||||
</div>
|
||||
<div>复制</div>
|
||||
</a-doption>
|
||||
<!-- 粘贴 -->
|
||||
<a-doption @click="paste">
|
||||
<a-doption @click="emits('paste', item.path)">
|
||||
<div class="terminal-context-menu-icon">
|
||||
<icon-paste />
|
||||
</div>
|
||||
<div>粘贴</div>
|
||||
</a-doption>
|
||||
<!-- 修改 -->
|
||||
<a-doption @click="openUpdatePath(item)">
|
||||
<a-doption @click="emits('update', item)">
|
||||
<div class="terminal-context-menu-icon">
|
||||
<icon-edit />
|
||||
</div>
|
||||
<div>修改</div>
|
||||
</a-doption>
|
||||
<!-- 删除 -->
|
||||
<a-doption @click="removePath(item.id)">
|
||||
<a-doption @click="emits('remove', item.id)">
|
||||
<div class="terminal-context-menu-icon">
|
||||
<icon-delete />
|
||||
</div>
|
||||
<div>删除</div>
|
||||
</a-doption>
|
||||
<!-- 展开 -->
|
||||
<a-doption v-if="!item.expand"
|
||||
@click="() => item.expand = true">
|
||||
<a-doption v-if="!expand"
|
||||
@click="() => expand = true">
|
||||
<div class="terminal-context-menu-icon">
|
||||
<icon-expand />
|
||||
</div>
|
||||
@@ -95,7 +95,7 @@
|
||||
</a-doption>
|
||||
<!-- 收起 -->
|
||||
<a-doption v-else
|
||||
@click="() => item.expand = false">
|
||||
@click="() => expand = false">
|
||||
<div class="terminal-context-menu-icon">
|
||||
<icon-shrink />
|
||||
</div>
|
||||
@@ -107,48 +107,37 @@
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'pathBookmarkListItem'
|
||||
name: 'pathBookmarkItem'
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { ISftpSession, ISshSession } from '../../types/define';
|
||||
import type { PathBookmarkQueryResponse } from '@/api/asset/path-bookmark';
|
||||
import { ref } from 'vue';
|
||||
import { useTerminalStore } from '@/store';
|
||||
import { useDebounceFn } from '@vueuse/core';
|
||||
import { copy } from '@/hooks/copy';
|
||||
import { inject } from 'vue';
|
||||
import { getParentPath } from '@/utils/file';
|
||||
import { openUpdatePathKey, PathBookmarkType, removePathKey } from './types/const';
|
||||
import { PathBookmarkType } from './types/const';
|
||||
import { PanelSessionType } from '../../types/const';
|
||||
|
||||
const props = defineProps<{
|
||||
item: PathBookmarkQueryResponse;
|
||||
}>();
|
||||
|
||||
const { getCurrentSession, getCurrentSessionType } = useTerminalStore();
|
||||
const emits = defineEmits(['remove', 'update', 'copy', 'paste', 'exec', 'change']);
|
||||
|
||||
const { getCurrentSessionType } = useTerminalStore();
|
||||
|
||||
const expand = ref(false);
|
||||
|
||||
let clickCount = 0;
|
||||
|
||||
// 修改
|
||||
const openUpdatePath = inject(openUpdatePathKey) as (item: PathBookmarkQueryResponse) => void;
|
||||
|
||||
// 删除
|
||||
const removePath = inject(removePathKey) as (id: number) => void;
|
||||
|
||||
// 点击路径
|
||||
const clickItem = () => {
|
||||
if (++clickCount == 2) {
|
||||
clickCount = 0;
|
||||
// 双击
|
||||
const type = getCurrentSessionType(true);
|
||||
if (type === PanelSessionType.SSH.type) {
|
||||
// SSH 粘贴
|
||||
paste();
|
||||
} else if (type === PanelSessionType.SFTP.type) {
|
||||
// SFTP 切换目录
|
||||
listFiles();
|
||||
}
|
||||
changePath();
|
||||
} else {
|
||||
// 单击展开
|
||||
expandItem();
|
||||
@@ -160,7 +149,7 @@
|
||||
setTimeout(() => {
|
||||
// 为 0 则代表为双击
|
||||
if (clickCount !== 0) {
|
||||
props.item.expand = !props.item.expand;
|
||||
expand.value = !expand.value;
|
||||
clickCount = 0;
|
||||
}
|
||||
}, 50);
|
||||
@@ -168,7 +157,7 @@
|
||||
|
||||
// 点击路径
|
||||
const clickPath = (e: Event) => {
|
||||
if (props.item.expand) {
|
||||
if (expand.value) {
|
||||
// 获取选中的文本
|
||||
const selectedText = window.getSelection()?.toString();
|
||||
if (selectedText) {
|
||||
@@ -177,46 +166,21 @@
|
||||
}
|
||||
};
|
||||
|
||||
// 复制路径
|
||||
const copyPath = () => {
|
||||
copy(props.item.path, '已复制');
|
||||
};
|
||||
|
||||
// 粘贴
|
||||
const paste = () => {
|
||||
writeCommand(props.item.path);
|
||||
};
|
||||
|
||||
// 切换目录
|
||||
const changePath = () => {
|
||||
const type = getCurrentSessionType(true);
|
||||
if (type === PanelSessionType.SSH.type) {
|
||||
// SSH cd
|
||||
const path = props.item.type === PathBookmarkType.DIR
|
||||
? props.item.path
|
||||
: getParentPath(props.item.path);
|
||||
// SSH cd
|
||||
writeCommand('cd ' + path + '\r\n');
|
||||
emits('exec', `cd ${path}`);
|
||||
} else if (type === PanelSessionType.SFTP.type) {
|
||||
// SFTP 切换目录
|
||||
listFiles();
|
||||
}
|
||||
};
|
||||
|
||||
// 查询 sftp 文件列表
|
||||
const listFiles = () => {
|
||||
// 如果非文件夹则查询父文件夹
|
||||
const path = props.item.type === PathBookmarkType.DIR
|
||||
? props.item.path
|
||||
: getParentPath(props.item.path);
|
||||
// 查询列表
|
||||
getCurrentSession<ISftpSession>(PanelSessionType.SFTP.type, true)?.list(path);
|
||||
};
|
||||
|
||||
// 写入 ssh 命令
|
||||
const writeCommand = (command: string) => {
|
||||
const handler = getCurrentSession<ISshSession>(PanelSessionType.SSH.type, true)?.handler;
|
||||
if (handler && handler.enabledStatus('checkAppendMissing')) {
|
||||
handler.checkAppendMissing(command);
|
||||
const path = props.item.type === PathBookmarkType.DIR
|
||||
? props.item.path
|
||||
: getParentPath(props.item.path);
|
||||
emits('change', path);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
<template>
|
||||
<a-collapse :bordered="false">
|
||||
<template v-for="group in value.groups">
|
||||
<a-collapse-item v-if="calcTotal(group) > 0"
|
||||
:key="group.id"
|
||||
:header="group.name">
|
||||
<!-- 总量 -->
|
||||
<template #extra>
|
||||
{{ calcTotal(group) }} 条
|
||||
</template>
|
||||
<!-- 路径 -->
|
||||
<template v-for="item in group.items">
|
||||
<path-bookmark-list-item v-if="item.visible"
|
||||
:key="item.id"
|
||||
:item="item" />
|
||||
</template>
|
||||
</a-collapse-item>
|
||||
</template>
|
||||
</a-collapse>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'pathBookmarkListGroup'
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { PathBookmarkGroupQueryResponse } from '@/api/asset/path-bookmark-group';
|
||||
import type { PathBookmarkWrapperResponse } from '@/api/asset/path-bookmark';
|
||||
import PathBookmarkListItem from './path-bookmark-list-item.vue';
|
||||
|
||||
defineProps<{
|
||||
value: PathBookmarkWrapperResponse;
|
||||
}>();
|
||||
|
||||
// 计算总量
|
||||
const calcTotal = (group: PathBookmarkGroupQueryResponse) => {
|
||||
return group.items.filter(s => s.visible).length;
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.arco-collapse-item) {
|
||||
border: none;
|
||||
|
||||
&-header {
|
||||
border: none;
|
||||
|
||||
&-title {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
&-extra {
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
&-content {
|
||||
background-color: unset;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&-content-box {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -3,9 +3,3 @@ export const PathBookmarkType = {
|
||||
FILE: 'FILE',
|
||||
DIR: 'DIR',
|
||||
};
|
||||
|
||||
// 打开 updatePath key
|
||||
export const openUpdatePathKey = Symbol();
|
||||
|
||||
// 删除 path key
|
||||
export const removePathKey = Symbol();
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
content="点击复制">
|
||||
<a-tag class="sftp-path-container pointer"
|
||||
color="arcoblue"
|
||||
@click="copy(path, '已复制')">
|
||||
@click="copy(path, true)">
|
||||
<span>{{ name }}</span>
|
||||
</a-tag>
|
||||
</a-tooltip>
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
arrow-class="terminal-tooltip-content"
|
||||
content="复制路径">
|
||||
<span class="click-icon-wrapper row-action-icon"
|
||||
@click="copy(record.path, '已复制')">
|
||||
@click="copy(record.path, true)">
|
||||
<icon-copy />
|
||||
</span>
|
||||
</a-tooltip>
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
<template #expression="{ record }">
|
||||
<span class="copy-left"
|
||||
title="复制"
|
||||
@click="copy(record.expression, '已复制')">
|
||||
@click="copy(record.expression, true)">
|
||||
<icon-copy />
|
||||
</span>
|
||||
<span class="text-copy span-blue"
|
||||
@@ -103,7 +103,7 @@
|
||||
<template #command="{ record }">
|
||||
<span class="copy-left"
|
||||
title="复制"
|
||||
@click="copy(record.command, '已复制')">
|
||||
@click="copy(record.command, true)">
|
||||
<icon-copy />
|
||||
</span>
|
||||
<span>{{ record.command }}</span>
|
||||
|
||||
@@ -144,7 +144,7 @@
|
||||
<template #permission="{ record }">
|
||||
<span v-if="record.permission"
|
||||
class="text-copy"
|
||||
@click="copy(record.permission, '已复制')">
|
||||
@click="copy(record.permission, true)">
|
||||
{{ record.permission }}
|
||||
</span>
|
||||
</template>
|
||||
@@ -152,7 +152,7 @@
|
||||
<template #component="{ record }">
|
||||
<span v-if="record.component"
|
||||
class="text-copy"
|
||||
@click="copy(record.component, '已复制')">
|
||||
@click="copy(record.component, true)">
|
||||
{{ record.component }}
|
||||
</span>
|
||||
</template>
|
||||
@@ -160,7 +160,7 @@
|
||||
<template #path="{ record }">
|
||||
<span v-if="record.path"
|
||||
class="text-copy"
|
||||
@click="copy(record.path, '已复制')">
|
||||
@click="copy(record.path, true)">
|
||||
{{ record.path }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
:column="1">
|
||||
<!-- 机器码 -->
|
||||
<a-descriptions-item label="机器码">
|
||||
<span class="text-copy span-blue uuid-wrapper" @click="copy(app.uuid, '已复制')">
|
||||
<span class="text-copy span-blue uuid-wrapper" @click="copy(app.uuid, true)">
|
||||
{{ app.uuid }}
|
||||
</span>
|
||||
</a-descriptions-item>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
</template>
|
||||
<!-- 操作日志 -->
|
||||
<template #originLogInfo="{ record }">
|
||||
<icon-copy class="copy-left" @click="copy(record.originLogInfo, '已复制')" />
|
||||
<icon-copy class="copy-left" @click="copy(record.originLogInfo, true)" />
|
||||
<span v-html="replaceHtmlTag(record.logInfo)" />
|
||||
</template>
|
||||
<!-- 留痕地址 -->
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
</template>
|
||||
<!-- 操作日志 -->
|
||||
<template #originLogInfo="{ record }">
|
||||
<icon-copy class="copy-left" @click="copy(record.originLogInfo, '已复制')" />
|
||||
<icon-copy class="copy-left" @click="copy(record.originLogInfo, true)" />
|
||||
<span v-html="replaceHtmlTag(record.logInfo)" />
|
||||
</template>
|
||||
<!-- 留痕地址 -->
|
||||
|
||||
Reference in New Issue
Block a user