feat: 添加代码片段.

This commit is contained in:
lijiahangmax
2024-01-26 00:35:05 +08:00
parent ac2376842a
commit 6497124554
11 changed files with 271 additions and 55 deletions

View File

@@ -24,6 +24,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
@@ -107,6 +108,7 @@ public class CommandSnippetGroupServiceImpl implements CommandSnippetGroupServic
// 转换
return list.stream()
.map(CommandSnippetGroupConvert.MAPPER::to)
.sorted(Comparator.comparing(CommandSnippetGroupVO::getId))
.collect(Collectors.toList());
}

View File

@@ -3,7 +3,6 @@ package com.orion.ops.module.asset.service.impl;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.orion.lang.utils.collect.Lists;
import com.orion.ops.framework.common.constant.ErrorMessage;
import com.orion.ops.framework.common.utils.Valid;
import com.orion.ops.framework.redis.core.utils.RedisMaps;
@@ -26,7 +25,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.Iterator;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
@@ -138,6 +137,7 @@ public class CommandSnippetServiceImpl implements CommandSnippetService {
// 转换
return list.stream()
.map(CommandSnippetConvert.MAPPER::to)
.sorted(Comparator.comparing(CommandSnippetVO::getId))
.collect(Collectors.toList());
}

View File

@@ -45,15 +45,20 @@ export interface Options {
disableLayerHinting?: boolean;
// 行亮
renderLineHighlight?: RenderLineHighlight;
// 显示行号
// 点击行号时是否应该选择相应的行
selectOnLineNumbers?: boolean;
// 行号最小字符
lineNumbersMinChars?: number;
// 输入提示
placeholder?: string;
// 小地图
minimap?: {
// 关闭小地图
enabled?: boolean;
[key: string]: unknown;
};
// 字体大小
fontSize?: number;
// 取消代码后面一大段空白
scrollBeyondLastLine?: boolean;
@@ -80,6 +85,7 @@ export const createDefaultOptions = (): Options => {
showFoldingControls: 'always',
renderLineHighlight: 'line',
selectOnLineNumbers: true,
lineNumbersMinChars: 2,
disableLayerHinting: true,
minimap: {
enabled: false,

View File

@@ -12,11 +12,13 @@ import { getHostList } from '@/api/asset/host';
import { getHostGroupTree } from '@/api/asset/host-group';
import { getMenuList } from '@/api/system/menu';
import { getCurrentAuthorizedHostIdentity, getCurrentAuthorizedHostKey } from '@/api/asset/asset-authorized-data';
import { getCommandSnippetGroupList } from '@/api/asset/command-snippet-group';
export type CacheType = 'users' | 'menus' | 'roles'
| 'hosts' | 'hostGroups' | 'hostKeys' | 'hostIdentities'
| 'dictKeys'
| 'authorizedHostKeys' | 'authorizedHostIdentities'
| 'commandSnippetGroups'
| string
export default defineStore('cache', {
@@ -111,5 +113,10 @@ export default defineStore('cache', {
return await this.load('authorizedHostIdentities', getCurrentAuthorizedHostIdentity, force);
},
// 获取命令片段分组
async loadCommandSnippetGroups(force = false) {
return await this.load('commandSnippetGroups', getCommandSnippetGroupList, force);
},
}
});

View File

@@ -1,7 +1,7 @@
<template>
<a-drawer v-model:visible="visible"
:title="title"
:width="488"
:width="388"
:mask-closable="false"
:unmount-on-close="true"
:ok-button-props="{ disabled: loading }"
@@ -16,18 +16,20 @@
:label-col-props="{ span: 6 }"
:wrapper-col-props="{ span: 18 }"
:rules="formRules">
<!-- 分组 -->
<a-form-item field="groupId" label="命令分组">
<a-input-number v-model="formModel.groupId"
placeholder="请输入命令分组"
hide-button />
</a-form-item>
<!-- 名称 -->
<a-form-item field="name" label="名称">
<a-input v-model="formModel.name" placeholder="请输入名称" allow-clear />
<a-input v-model="formModel.name"
placeholder="请输入名称"
allow-clear />
</a-form-item>
<!-- 分组 -->
<a-form-item field="groupId" label="命令分组">
<command-snippet-group-select v-model="formModel.groupId" />
</a-form-item>
<!-- 代码片段 -->
<a-form-item field="command" label="代码片段">
<a-form-item field="command"
label="代码片段"
style="margin: 0;">
<editor v-model="formModel.command"
containerClass="command-editor"
language="shell"
@@ -50,10 +52,11 @@
import { ref } from 'vue';
import useLoading from '@/hooks/loading';
import useVisible from '@/hooks/visible';
import formRules from '../types/form.rules';
import { createCommandSnippet, updateCommandSnippet } from '@/api/asset/command-snippet';
import formRules from '../types/form.rules';
import { Message } from '@arco-design/web-vue';
import { useTerminalStore } from '@/store';
import CommandSnippetGroupSelect from './command-snippet-group-select.vue';
const { preference } = useTerminalStore();
const { visible, setVisible } = useVisible();
@@ -110,14 +113,15 @@
}
if (isAddHandle.value) {
// 新增
await createCommandSnippet(formModel.value);
const { data: id } = await createCommandSnippet(formModel.value);
formModel.value.id = id;
Message.success('创建成功');
emits('added');
emits('added', formModel.value);
} else {
// 修改
await updateCommandSnippet(formModel.value);
Message.success('修改成功');
emits('updated');
emits('updated', formModel.value);
}
// 清空
handlerClear();
@@ -146,7 +150,7 @@
}
.command-editor {
height: 340px;
height: calc(100vh - 330px);
}
</style>

View File

@@ -0,0 +1,106 @@
<template>
<a-select v-model:model-value="value"
placeholder="请选择命令分组"
:options="optionData"
:loading="loading"
:allow-create="true"
allow-clear />
</template>
<script lang="ts">
export default {
name: 'commandSnippetGroupSelect'
};
</script>
<script lang="ts" setup>
import type { SelectOptionData } from '@arco-design/web-vue';
import type { CommandSnippetGroupQueryResponse } from '@/api/asset/command-snippet-group';
import { ref, computed, onBeforeMount } from 'vue';
import { useCacheStore } from '@/store';
import useLoading from '@/hooks/loading';
import { createCommandSnippetGroup } from '@/api/asset/command-snippet-group';
const props = defineProps<{
modelValue: number | undefined
}>();
const emits = defineEmits(['update:modelValue']);
const { loading, setLoading } = useLoading();
const cacheStore = useCacheStore();
const value = computed<number | string>({
get() {
return props.modelValue as number;
},
async set(e) {
const val = await checkCreateGroup(e);
emits('update:modelValue', val);
}
});
const optionData = ref<SelectOptionData[]>([]);
// 检查是否可以创建分组
const checkCreateGroup = async (val: string | number): Promise<number> => {
// 清空
if (!val) {
return undefined as unknown as number;
}
// 为 number 代表为 id 已存在
if (typeof val === 'number') {
return val;
}
// 已存在则跳过
const find = optionData.value.find((o) => o.label === val);
if (find) {
return find.value as number;
}
// 不存在则创建
setLoading(true);
try {
return await doCreateGroup(val);
} catch (e) {
return undefined as unknown as number;
} finally {
setLoading(false);
}
};
// 创建分组
const doCreateGroup = async (name: string) => {
const { data: id } = await createCommandSnippetGroup({
name,
});
// 插入缓存
const groups = await cacheStore.loadCommandSnippetGroups();
groups && groups.push({ id, name } as CommandSnippetGroupQueryResponse);
// 插入 options
optionData.value.push({
label: name,
value: id,
});
return id;
};
// 初始化选项
onBeforeMount(async () => {
setLoading(true);
try {
const tags = await cacheStore.loadCommandSnippetGroups();
optionData.value = tags.map(s => {
return {
label: s.name,
value: s.id,
};
});
} catch (e) {
} finally {
setLoading(false);
}
});
</script>
<style lang="less" scoped>
</style>

View File

@@ -45,19 +45,21 @@
<!-- 命令片段 -->
<div v-else class="snippet-list-container">
<!-- 命令片段组 -->
<command-snippet-group :snippet="snippet" />
<command-snippet-list-group :snippet="snippet" />
<!-- 未分组命令片段 -->
<div class="ungrouped-snippet-container">
<template v-for="item in snippet.ungroupedItems">
<command-snippet-item v-if="item.visible"
:key="item.id"
:item="item" />
<command-snippet-list-item v-if="item.visible"
:key="item.id"
:item="item" />
</template>
</div>
</div>
</div>
<!-- 命令编辑抽屉 -->
<command-snippet-form-drawer ref="formDrawer" />
<command-snippet-form-drawer ref="formDrawer"
@added="onAdded"
@updated="onUpdated" />
</a-drawer>
</template>
@@ -73,15 +75,16 @@
import useVisible from '@/hooks/visible';
import useLoading from '@/hooks/loading';
import { deleteCommandSnippet, getCommandSnippetList } from '@/api/asset/command-snippet';
import { useTerminalStore } from '@/store';
import { useCacheStore, useTerminalStore } from '@/store';
import { openUpdateSnippetKey, removeSnippetKey } from '../types/const';
import CommandSnippetItem from './command-snippet-item.vue';
import CommandSnippetGroup from './command-snippet-group.vue';
import CommandSnippetListItem from './command-snippet-list-item.vue';
import CommandSnippetListGroup from './command-snippet-list-group.vue';
import CommandSnippetFormDrawer from './command-snippet-form-drawer.vue';
const { loading, setLoading } = useLoading();
const { visible, setVisible } = useVisible();
const { getCurrentTerminalSession } = useTerminalStore();
const cacheStore = useCacheStore();
const formDrawer = ref();
const filterValue = ref<string>();
@@ -135,45 +138,133 @@
formDrawer.value.openAdd();
};
// 打开修改
// 查找并且删除
const findAndSplice = (id: number, items: Array<CommandSnippetQueryResponse>) => {
if (items) {
const index = items.findIndex(s => s.id === id);
if (index !== -1) {
items.splice(index, 1);
return true;
}
return false;
}
};
// 暴露 修改抽屉
provide(openUpdateSnippetKey, (e: CommandSnippetQueryResponse) => {
formDrawer.value.openUpdate(e);
});
// 删除
// 暴露 删除
provide(removeSnippetKey, async (id: number) => {
if (!snippet.value) {
return;
}
// 查找并且删除
function findAndSplice(items: Array<CommandSnippetQueryResponse>) {
if (items) {
const index = items.findIndex(s => s.id === id);
if (index !== -1) {
items.splice(index, 1);
return true;
}
return false;
}
}
// 删除
await deleteCommandSnippet(id);
// 查找并且删除未分组的数据
if (findAndSplice(snippet.value.ungroupedItems)) {
if (findAndSplice(id, snippet.value.ungroupedItems)) {
return;
}
// 查找并且删除分组内数据
for (let group of snippet.value.groups) {
if (findAndSplice(group.items)) {
if (findAndSplice(id, group.items)) {
return;
}
}
});
// 关闭
// 添加回调
const onAdded = async (item: CommandSnippetQueryResponse) => {
if (item.groupId) {
let group = snippet.value?.groups.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) {
snippet.value?.groups.push({
id: item.groupId,
name: findGroup.name,
items: [item]
});
}
}
} else {
snippet.value?.ungroupedItems.push(item);
}
// 重置过滤
filterSnippet();
};
// 修改回调
const onUpdated = async (item: CommandSnippetQueryResponse) => {
if (!snippet.value) {
return;
}
// 查找原始数据
let originItem;
const findInUngrouped = snippet.value.ungroupedItems.find(s => s.id === item.id);
if (findInUngrouped) {
originItem = findInUngrouped;
} else {
for (let group of snippet.value.groups) {
const find = group.items.find(s => s.id === item.id);
if (find) {
originItem = find;
break;
}
}
}
if (!originItem) {
return;
}
// 检查分组是否存在
const findGroup = snippet.value.groups.find(s => s.id === item.groupId);
if (!findGroup) {
const cacheGroups = await cacheStore.loadCommandSnippetGroups();
const cacheGroup = cacheGroups.find(s => s.id === item.groupId);
if (cacheGroup) {
snippet.value.groups.push({
id: item.groupId,
name: cacheGroup.name,
items: []
});
}
}
// 设置数据
const originGroupId = originItem.groupId;
originItem.groupId = item.groupId;
originItem.name = item.name;
originItem.command = item.command;
// 移动分组
if (item.groupId !== originGroupId) {
// 从原始分组移除
if (originGroupId) {
const findGroup = snippet.value.groups.find(s => s.id === originGroupId);
if (findGroup) {
findAndSplice(item.id, findGroup.items);
}
} else {
// 从未分组数据中移除
findAndSplice(item.id, snippet.value.ungroupedItems);
}
// 添加到新分组
if (item.groupId) {
const findGroup = snippet.value.groups.find(s => s.id === item.groupId);
if (findGroup) {
findGroup.items.push(item);
}
} else {
snippet.value.ungroupedItems.push(originItem);
}
}
// 重置过滤
filterSnippet();
};
// 关闭回调
const onClose = () => {
// 聚焦终端
getCurrentTerminalSession(false)?.focus();

View File

@@ -10,9 +10,9 @@
</template>
<!-- snippet -->
<template v-for="item in group.items">
<command-snippet-item v-if="item.visible"
:key="item.id"
:item="item" />
<command-snippet-list-item v-if="item.visible"
:key="item.id"
:item="item" />
</template>
</a-collapse-item>
</template>
@@ -28,7 +28,7 @@
<script lang="ts" setup>
import type { CommandSnippetGroupQueryResponse } from '@/api/asset/command-snippet-group';
import type { CommandSnippetWrapperResponse } from '@/api/asset/command-snippet';
import CommandSnippetItem from './command-snippet-item.vue';
import CommandSnippetListItem from './command-snippet-list-item.vue';
defineProps<{
snippet: CommandSnippetWrapperResponse

View File

@@ -131,10 +131,10 @@
let clickCount = 0;
//
const openUpdateSnippet = inject<(item: CommandSnippetQueryResponse) => void>(openUpdateSnippetKey);
const openUpdateSnippet = inject(openUpdateSnippetKey) as (item: CommandSnippetQueryResponse) => void;
//
const removeSnippet = inject<(id: number) => void>(removeSnippetKey);
const removeSnippet = inject(removeSnippetKey) as (id: number) => void;
//
const clickItem = () => {
@@ -210,7 +210,7 @@
color: var(--color-text-1);
text-overflow: unset;
word-break: break-all;
white-space: unset;
white-space: pre-wrap;
user-select: unset;
}
}
@@ -268,7 +268,7 @@
font-size: 12px;
overflow: hidden;
text-overflow: ellipsis;
white-space: pre;
white-space: nowrap;
width: @item-inline-width;
user-select: none;
}

View File

@@ -218,8 +218,8 @@
}
};
// 打开配置 FIXME
const openSetting = inject<(record: HostQueryResponse) => void>(openSshModalKey);
// 打开配置
const openSetting = inject(openSshModalKey) as (record: HostQueryResponse) => void;
// 设置收藏
const setFavorite = async (item: HostQueryResponse) => {

View File

@@ -93,7 +93,7 @@
onUnmounted(() => {
// 卸载时清除 cache
cacheStore.reset('authorizedHostKeys', 'authorizedHostIdentities');
cacheStore.reset('authorizedHostKeys', 'authorizedHostIdentities', 'commandSnippetGroups');
// 移除关闭视口事件
window.removeEventListener('beforeunload', handleBeforeUnload);
// 去除 body style