feat: 命令片段.

This commit is contained in:
lijiahang
2024-01-24 19:19:26 +08:00
parent 7d614f0bdc
commit af4d4d99a6
13 changed files with 333 additions and 220 deletions

View File

@@ -49,7 +49,7 @@ public class CodeGenerators {
.disableUnitTest()
.cache("command:snippet:group:{}", "命令片段分组 ${userId}")
.expire(1, TimeUnit.DAYS)
.vue("host", "command-snippet")
.vue("host", "command-snippet-group")
.build(),
};
// jdbc 配置 - 使用配置文件

View File

@@ -6,7 +6,6 @@ Authorization: {{token}}
{
"groupId": "",
"name": "",
"prefix": "",
"command": ""
}
@@ -20,7 +19,6 @@ Authorization: {{token}}
"id": "",
"groupId": "",
"name": "",
"prefix": "",
"command": ""
}

View File

@@ -50,7 +50,7 @@ public class CommandSnippetGroupController {
}
@IgnoreLog(IgnoreLogMode.RET)
@PostMapping("/list")
@GetMapping("/list")
@Operation(summary = "查询全部命令片段分组")
public List<CommandSnippetGroupVO> getCommandSnippetGroupList() {
return commandSnippetGroupService.getCommandSnippetGroupList();

View File

@@ -0,0 +1,52 @@
import axios from 'axios';
/**
* 命令片段分组创建请求
*/
export interface CommandSnippetGroupCreateRequest {
name?: string;
}
/**
* 命令片段分组更新请求
*/
export interface CommandSnippetGroupUpdateRequest extends CommandSnippetGroupCreateRequest {
id?: number;
}
/**
* 命令片段分组查询响应
*/
export interface CommandSnippetGroupQueryResponse {
id: number;
name: string;
}
/**
* 创建命令片段分组
*/
export function createCommandSnippetGroup(request: CommandSnippetGroupCreateRequest) {
return axios.post('/asset/command-snippet-group/create', request);
}
/**
* 更新命令片段分组
*/
export function updateCommandSnippetGroup(request: CommandSnippetGroupUpdateRequest) {
return axios.put('/asset/command-snippet-group/update', request);
}
/**
* 查询全部命令片段分组
*/
export function getCommandSnippetGroupList() {
return axios.get<Array<CommandSnippetGroupQueryResponse>>('/asset/command-snippet-group/list');
}
/**
* 删除命令片段分组
*/
export function deleteCommandSnippetGroup(id: number) {
return axios.delete('/asset/command-snippet-group/delete', { params: { id } });
}

View File

@@ -0,0 +1,65 @@
import type { CommandSnippetGroupQueryResponse } from './command-snippet-group';
import axios from 'axios';
/**
* 命令片段创建请求
*/
export interface CommandSnippetCreateRequest {
groupId?: number;
name?: string;
command?: string;
}
/**
* 命令片段更新请求
*/
export interface CommandSnippetUpdateRequest extends CommandSnippetCreateRequest {
id?: number;
}
/**
* 命令片段查询响应
*/
export interface CommandSnippetQueryResponse {
id: number;
groupId: number;
name: string;
command: string;
expand?: boolean;
}
/**
* 命令片段查询响应
*/
export interface CommandSnippetWrapperResponse {
groups: Array<CommandSnippetGroupQueryResponse>;
items: Array<CommandSnippetQueryResponse>;
}
/**
* 创建命令片段
*/
export function createCommandSnippet(request: CommandSnippetCreateRequest) {
return axios.post('/asset/command-snippet/create', request);
}
/**
* 更新命令片段
*/
export function updateCommandSnippet(request: CommandSnippetUpdateRequest) {
return axios.put('/asset/command-snippet/update', request);
}
/**
* 查询全部命令片段
*/
export function getCommandSnippetList() {
return axios.get<Array<CommandSnippetQueryResponse>>('/asset/command-snippet/list');
}
/**
* 删除命令片段
*/
export function deleteCommandSnippet(id: number) {
return axios.delete('/asset/command-snippet/delete', { params: { id } });
}

View File

@@ -5,6 +5,7 @@ body {
--color-bg-content: #FEFEFE;
--color-sidebar-icon: #737070;
--color-sidebar-icon-bg: #D7D8DB;
--color-sidebar-icon-checked: #CBCCCF;
--color-sidebar-tooltip-text: rgba(255, 255, 255, .9);
--color-sidebar-tooltip-bg: rgb(29, 33, 41);
--color-content-text-1: rgba(0, 0, 0, .8);
@@ -25,8 +26,9 @@ body[terminal-theme='dark'] {
--color-bg-header: #232323;
--color-bg-sidebar: #2C2E31;
--color-bg-content: #1A1B1C;
--color-sidebar-icon: #C3C8CE;
--color-sidebar-icon-bg: #43444C;
--color-sidebar-icon: #C3C6C9;
--color-sidebar-icon-bg: #3D3E3F;
--color-sidebar-icon-checked: #51525C;
--color-sidebar-tooltip-text: rgba(255, 255, 255, .9);
--color-sidebar-tooltip-bg: var(--color-sidebar-icon-bg);
--color-content-text-1: rgba(255, 255, 255, .8);
@@ -214,7 +216,7 @@ body[terminal-theme='dark'] .arco-modal-container {
}
&.checked-item {
background: var(--color-sidebar-icon-bg);
background: var(--color-sidebar-icon-checked);
}
&.disabled-item {

View File

@@ -99,15 +99,20 @@
</script>
<style lang="less" scoped>
@transform-x: 8px;
@container-width: 406px;
@container-height: 448px;
@handler-height: 44px;
.combined-container {
padding: 12px;
margin: 64px auto;
width: 398px;
height: 448px;
width: @container-width;
height: @container-height;
display: flex;
flex-direction: column;
align-items: center;
box-sizing: content-box;
overflow: hidden;
&:hover {
@@ -116,20 +121,19 @@
}
.combined-handler {
width: 100%;
width: calc(@container-width - @transform-x);
height: @handler-height;
border-radius: 4px;
margin-bottom: 6px;
color: var(--color-content-text-1);
background-color: var(--color-fill-2);
display: flex;
align-items: center;
color: var(--color-content-text-1);
cursor: pointer;
transition: transform 0.3s ease;
will-change: transform;
transition: all 0.2s;
&:hover {
transform: scale(1.04);
width: @container-width;
}
&-icon {

View File

@@ -84,7 +84,6 @@
</script>
<style lang="less" scoped>
.form-item-actions {
display: flex;
background-color: var(--color-fill-2);

View File

@@ -119,7 +119,8 @@
<style lang="less" scoped>
@container-width: 418px;
@wrapper-margin-r: 32px;
@wrapper-width: (@container-width - @wrapper-margin-r) / 2px;
@transform-x: 8px;
@item-width: (@container-width - @wrapper-margin-r) / 2;
.setting-body {
display: flex;
@@ -130,23 +131,26 @@
height: auto;
.actions-wrapper {
width: @wrapper-width;
padding-right: 8px;
margin-right: @wrapper-margin-r;
}
.action-item-wrapper {
transition: all 0.2s;
width: 185px;
border-radius: 4px;
width: calc((@item-width) - @transform-x);
&:hover {
width: 192px;
width: calc(@item-width);
padding: 4px 0 !important;
.action-item{
.action-item {
background: var(--color-fill-3);
}
.action-icon {
background: var(--color-fill-4);
}
}
}

View File

@@ -12,48 +12,23 @@
<!-- 命令容器 -->
<div class="snippet-container">
<!-- 命令头部 -->
<div class="snippet-header-container">
<!-- 头部操作 -->
<div class="snippet-header">
<a-button @click="toggle">新建</a-button>
<a-button>搜索</a-button>
</div>
<!-- 提示 -->
<a-alert v-if="isNotTipped(snippetTipsKey)"
class="snippet-tips"
:closable="true"
@on-close="closeTips">
双击命令直接运行
</a-alert>
<div class="snippet-header">
<!-- 创建命令 -->
<span class="click-icon-wrapper snippet-header-icon" title="创建命令">
<icon-plus />
</span>
<!-- 搜索框 -->
<a-input-search class="snippet-header-input"
placeholder="名称"
allow-clear />
</div>
<!-- 命令片段 -->
<div class="snippet-list-container">
<a-collapse v-if="snippet.groups.length"
:bordered="false">
<a-collapse-item v-for="group in snippet.groups"
:key="group.id"
:header="group.name">
<!-- 总量 -->
<template #extra>
{{ group.idList.length }}
</template>
{{ group }}
</a-collapse-item>
</a-collapse>
<snippet-group :snippet="snippet" />
<div>
<div v-for="item in snippet.snippets"
:key="item.id"
class="snippet-item-wrapper"
:class="[loading&&item.id===3 ? 'snippet-item-wrapper-expand' : '']">
<div class="snippet-item">
<span class="snippet-item-title">
{{ item.name }}
</span>
<span class="snippet-item-command">
{{ item.command }}
</span>
</div>
</div>
<snippet-item v-for="item in snippet.items"
:key="item.id"
:item="item" />
</div>
</div>
</div>
@@ -67,96 +42,38 @@
</script>
<script lang="ts" setup>
import { ref } from 'vue';
export interface SnippetGroupResponse {
groups: Array<SnippetGroup>;
snippets: Array<Snippet>;
}
export interface SnippetGroup {
id: number;
name: string;
idList: Array<number>;
}
export interface Snippet {
id: number;
name: string;
command: string;
}
import { useTipsStore } from '@/store';
import type { CommandSnippetWrapperResponse } from '@/api/asset/command-snippet';
import { onMounted, ref } from 'vue';
import useVisible from '@/hooks/visible';
import { snippetTipsKey } from '../../types/terminal.const';
import useLoading from '@/hooks/loading';
import SnippetItem from './snippet-item.vue';
import SnippetGroup from './snippet-group.vue';
const { isNotTipped, setTipped } = useTipsStore();
const { loading, toggle } = useLoading();
const { visible, setVisible } = useVisible(true);
const snippet = ref<SnippetGroupResponse>({
groups: [{
id: 1,
name: 'group1',
idList: [1, 2]
}, {
id: 2,
name: 'group2',
idList: [3, 4]
}],
snippets: [{
id: 1,
name: 'command1command1command1command1command1command1command1command1command1command1command1command1command1command1command1',
command: 'echo Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ad adipisci aliquid, atque cupiditate doloribus eligendi enim fugiat itaque iusto laborum magnam maiores natus nemo, neque quae, reprehenderit sed ullam voluptatem?'
}, {
id: 2,
name: 'command2',
command: 'echo Lorem'
}, {
id: 3,
name: 'command3',
command: 'echo Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ad adipisci aliquid, atque cupiditate doloribus eligendi enim fugiat itaque iusto laborum magnam maiores natus nemo, neque quae, reprehenderit sed ullam voluptatem?'
}, {
id: 4,
name: 'command4',
command: 'echo Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ad adipisci aliquid, atque cupiditate doloribus eligendi enim fugiat itaque iusto laborum magnam maiores natus nemo, neque quae, reprehenderit sed ullam voluptatem?'
}, {
id: 5,
name: 'command5',
command: 'echo Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ad adipisci aliquid, atque cupiditate doloribus eligendi enim fugiat itaque iusto laborum magnam maiores natus nemo, neque quae, reprehenderit sed ullam voluptatem?'
}, {
id: 6,
name: 'command6',
command: 'echo Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ad adipisci aliquid, atque cupiditate doloribus eligendi enim fugiat itaque iusto laborum magnam maiores natus nemo, neque quae, reprehenderit sed ullam voluptatem?'
}]
const { visible, setVisible } = useVisible();
const snippet = ref<CommandSnippetWrapperResponse>({
groups: [],
items: []
});
// 打开
const open = () => {
setVisible(true);
console.log('loading');
// loading
};
// 关闭提示
const closeTips = () => {
console.log('close');
};
defineExpose({ open });
onMounted(() => {
open();
});
</script>
<style lang="less" scoped>
@transform-x: 8px;
@drawer-width: 388px;
@item-wrapper-p-y: 4px;
@item-wrapper-p-x: 12px;
@item-p: 8px;
@item-width: @drawer-width - @item-wrapper-p-x * 2;
@item-width-transform: @item-width + @transform-x;
@item-inline-width: @item-width - @item-p * 2;
.snippet-drawer-title {
font-size: 14px;
}
@@ -166,104 +83,30 @@
background: var(--color-bg-2);
height: 100%;
.snippet-header-container {
.snippet-header {
padding: 12px;
//height: 104px;
height: 56px;
display: flex;
align-items: center;
justify-content: space-between;
.snippet-header {
&-icon {
width: 32px;
height: 32px;
font-size: 16px;
}
.snippet-tips {
margin-top: 8px;
&-input {
width: 220px;
}
}
}
.snippet-list-container {
position: relative;
//height: calc(100% - 104px);
height: calc(100% - 56px);
overflow: auto;
}
.snippet-item-wrapper {
padding: @item-wrapper-p-y 0;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
&-expand {
.snippet-item {
width: @item-width-transform !important;
background: var(--color-fill-3) !important;
:hover {
}
.snippet-item-command {
color: var(--color-text-1);
text-overflow: unset;
word-break: break-all;
white-space: unset;
}
}
}
.snippet-item {
display: flex;
flex-direction: column;
padding: @item-p;
background: var(--color-fill-2);
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
width: @item-width;
&:hover {
width: @item-width-transform;
background: var(--color-fill-3);
}
&-title {
color: var(--color-text-1);
margin-bottom: 8px;
overflow: hidden;
text-overflow: ellipsis;
width: @item-inline-width;
}
&-command {
color: var(--color-text-2);
font-size: 12px;
overflow: hidden;
text-overflow: ellipsis;
white-space: pre;
width: @item-inline-width;
}
}
}
:deep(.arco-collapse-item) {
border: none;
.arco-collapse-item-header-title {
user-select: none;
}
.arco-collapse-item-header {
border: none;
}
.arco-collapse-item-content {
background-color: unset;
padding: 0;
}
.arco-collapse-item-content-box {
padding: 0;
}
padding-bottom: 4px;
}
</style>

View File

@@ -0,0 +1,54 @@
<template>
<a-collapse :bordered="false">
<a-collapse-item v-for="group in snippet.groups"
:key="group.id"
:header="group.name">
<!-- 总量 -->
<template #extra>
{{ 1 }}
</template>
<snippet-item v-for="item in snippet.items"
:key="item.id"
:item="item" />
</a-collapse-item>
</a-collapse>
</template>
<script lang="ts">
export default {
name: 'snippetGroup'
};
</script>
<script lang="ts" setup>
import type { CommandSnippetWrapperResponse } from '@/api/asset/command-snippet';
import SnippetItem from './snippet-item.vue';
defineProps<{
snippet: CommandSnippetWrapperResponse
}>();
</script>
<style lang="less" scoped>
:deep(.arco-collapse-item) {
border: none;
.arco-collapse-item-header-title {
user-select: none;
}
.arco-collapse-item-header {
border: none;
}
.arco-collapse-item-content {
background-color: unset;
padding: 0;
}
.arco-collapse-item-content-box {
padding: 0;
}
}
</style>

View File

@@ -0,0 +1,95 @@
<template>
<div class="snippet-item-wrapper"
:class="[!!item.expand ? 'snippet-item-wrapper-expand' : '']">
<div class="snippet-item">
<span class="snippet-item-title">
{{ item.name }}
</span>
<span class="snippet-item-command">
{{ item.command }}
</span>
</div>
</div>
</template>
<script lang="ts">
export default {
name: 'snippetItem'
};
</script>
<script lang="ts" setup>
import type { CommandSnippetQueryResponse } from '@/api/asset/command-snippet';
defineProps<{
item: CommandSnippetQueryResponse
}>();
</script>
<style lang="less" scoped>
@transform-x: 8px;
@drawer-width: 388px;
@item-wrapper-p-y: 4px;
@item-wrapper-p-x: 12px;
@item-p: 8px;
@item-width: @drawer-width - @item-wrapper-p-x * 2;
@item-width-transform: @item-width + @transform-x;
@item-inline-width: @item-width - @item-p * 2;
.snippet-item-wrapper {
padding: @item-wrapper-p-y 0;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
&-expand {
.snippet-item {
width: @item-width-transform !important;
background: var(--color-fill-3) !important;
.snippet-item-command {
color: var(--color-text-1);
text-overflow: unset;
word-break: break-all;
white-space: unset;
}
}
}
.snippet-item {
display: flex;
flex-direction: column;
padding: @item-p;
background: var(--color-fill-2);
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
width: @item-width;
&:hover {
width: @item-width-transform;
background: var(--color-fill-3);
}
&-title {
color: var(--color-text-1);
margin-bottom: 8px;
overflow: hidden;
text-overflow: ellipsis;
width: @item-inline-width;
}
&-command {
color: var(--color-text-2);
font-size: 12px;
overflow: hidden;
text-overflow: ellipsis;
white-space: pre;
width: @item-inline-width;
}
}
}
</style>

View File

@@ -208,9 +208,6 @@ export const TerminalShortcutItems: Array<ShortcutKeyItem> = [
},
];
// 命令片段操作提示
export const snippetTipsKey = 'snippet:opt';
// 打开 sshModal key
export const openSshModalKey = Symbol();