🔨 批量上传.
This commit is contained in:
@@ -7,14 +7,19 @@ import qs from 'query-string';
|
||||
* 上传任务创建请求
|
||||
*/
|
||||
export interface UploadTaskCreateRequest {
|
||||
userId?: number;
|
||||
username?: string;
|
||||
remotePath?: string;
|
||||
description?: string;
|
||||
status?: string;
|
||||
extraInfo?: string;
|
||||
startTime?: string;
|
||||
endTime?: string;
|
||||
hostIdList?: Array<number>;
|
||||
files?: Array<UploadTaskFileCreateRequest>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传任务文件创建请求
|
||||
*/
|
||||
export interface UploadTaskFileCreateRequest {
|
||||
fileId?: string;
|
||||
filePath?: string;
|
||||
fileSize?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<!-- 表头 -->
|
||||
<div class="panel-header">
|
||||
<h3>文件列表</h3>
|
||||
<!-- 操作 -->
|
||||
<a-button-group size="small">
|
||||
<a-button @click="clear">清空</a-button>
|
||||
<!-- 选择文件 -->
|
||||
<a-upload v-model:file-list="fileList"
|
||||
:auto-upload="false"
|
||||
:show-file-list="false"
|
||||
:multiple="true">
|
||||
<template #upload-button>
|
||||
<a-button type="primary">选择文件</a-button>
|
||||
</template>
|
||||
</a-upload>
|
||||
<!-- 选择文件夹 -->
|
||||
<a-upload v-model:file-list="fileList"
|
||||
:auto-upload="false"
|
||||
:show-file-list="false"
|
||||
:directory="true">
|
||||
<template #upload-button>
|
||||
<a-button type="primary">选择文件夹</a-button>
|
||||
</template>
|
||||
</a-upload>
|
||||
</a-button-group>
|
||||
</div>
|
||||
<!-- 文件列表 -->
|
||||
<div v-if="fileList.length" class="files-container">
|
||||
<a-upload class="files-wrapper"
|
||||
:class="['waiting-files-wrapper']"
|
||||
v-model:file-list="fileList"
|
||||
:auto-upload="false"
|
||||
:show-cancel-button="false"
|
||||
:show-remove-button="true"
|
||||
:show-file-list="true">
|
||||
<template #upload-button />
|
||||
<template #file-name="{ fileItem }">
|
||||
<div class="file-name-wrapper">
|
||||
<!-- 文件名称 -->
|
||||
<a-tooltip position="left"
|
||||
:mini="true"
|
||||
:content="fileItem.file.webkitRelativePath || fileItem.file.name">
|
||||
<!-- 文件名称 -->
|
||||
<span class="file-name text-ellipsis">
|
||||
{{ fileItem.file.webkitRelativePath || fileItem.file.name }}
|
||||
</span>
|
||||
</a-tooltip>
|
||||
<!-- 文件大小 -->
|
||||
<span class="file-size span-blue">
|
||||
{{ getFileSize(fileItem.file.size) }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</a-upload>
|
||||
</div>
|
||||
<!-- 未选择文件 -->
|
||||
<a-result v-else
|
||||
class="usn"
|
||||
status="404"
|
||||
subtitle="请先点击上方按钮选择文件" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'batchUploadFiles'
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { FileItem } from '@arco-design/web-vue';
|
||||
import { ref } from 'vue';
|
||||
import { getFileSize } from '@/utils/file';
|
||||
|
||||
const fileList = ref<FileItem[]>([]);
|
||||
|
||||
// 清空
|
||||
const clear = () => {
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@file-size-width: 82px;
|
||||
|
||||
.files-container {
|
||||
width: 100%;
|
||||
height: calc(100% - 36px);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
:deep(.waiting-files-wrapper) {
|
||||
.arco-upload-list {
|
||||
padding: 0 6px 0 0 !important;
|
||||
}
|
||||
|
||||
.arco-upload-list-item-name {
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
|
||||
.arco-upload-list-item .arco-upload-progress {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.uploading-files-wrapper) {
|
||||
.arco-upload-list {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.arco-upload-list-item-name {
|
||||
margin-right: 10px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.files-wrapper {
|
||||
:deep(.arco-upload-wrapper) {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
:deep(.arco-upload) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:deep(.arco-upload-list) {
|
||||
max-height: 100%;
|
||||
padding: 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
:deep(.arco-upload-list-item-name-text) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.arco-upload-list-item:first-of-type) {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.file-name-wrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.file-name {
|
||||
color: var(--color-text-1);
|
||||
display: inline-block;
|
||||
width: calc(100% - @file-size-width);
|
||||
}
|
||||
|
||||
.file-size {
|
||||
font-size: 13px;
|
||||
display: inline-block;
|
||||
width: @file-size-width;
|
||||
text-align: end;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<!-- 表头 -->
|
||||
<div class="panel-header">
|
||||
<h3>批量上传</h3>
|
||||
<!-- 操作 -->
|
||||
<a-button-group size="small">
|
||||
<a-button>重置</a-button>
|
||||
<a-button type="primary">上传</a-button>
|
||||
</a-button-group>
|
||||
</div>
|
||||
<!-- 表单 -->
|
||||
<a-form :model="formModel"
|
||||
ref="formRef"
|
||||
class="form-wrapper"
|
||||
label-align="right"
|
||||
:auto-label-width="true"
|
||||
:rules="formRules">
|
||||
<!-- 上传描述 -->
|
||||
<a-form-item field="description" label="上传描述">
|
||||
<a-input v-model="formModel.description"
|
||||
placeholder="请输入上传描述"
|
||||
allow-clear />
|
||||
</a-form-item>
|
||||
<!-- 上传路径 -->
|
||||
<a-form-item field="remotePath" label="上传路径">
|
||||
<a-input v-model="formModel.remotePath"
|
||||
placeholder="请输入上传路径"
|
||||
allow-clear />
|
||||
</a-form-item>
|
||||
<!-- 上传主机 -->
|
||||
<a-form-item field="hostIdList" label="上传主机">
|
||||
<div class="selected-host">
|
||||
<!-- 已选择数量 -->
|
||||
<span class="usn" v-if="formModel.hostIdList?.length">
|
||||
已选择<span class="selected-host-count span-blue">{{ formModel.hostIdList?.length }}</span>台主机
|
||||
</span>
|
||||
<span class="usn pointer span-blue" @click="openSelectHost">
|
||||
{{ formModel.hostIdList?.length ? '重新选择' : '选择主机' }}
|
||||
</span>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'batchUploadForm'
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { UploadTaskCreateRequest } from '@/api/exec/upload-task';
|
||||
import { ref } from 'vue';
|
||||
import formRules from '../types/form.rules';
|
||||
import useLoading from '@/hooks/loading';
|
||||
|
||||
const defaultForm = (): UploadTaskCreateRequest => {
|
||||
return {
|
||||
description: '',
|
||||
remotePath: '',
|
||||
hostIdList: [],
|
||||
files: []
|
||||
};
|
||||
};
|
||||
|
||||
const { loading, setLoading } = useLoading();
|
||||
|
||||
const formRef = ref<any>();
|
||||
const formModel = ref<UploadTaskCreateRequest>({ ...defaultForm() });
|
||||
const hostModal = ref<any>();
|
||||
|
||||
// 打开选择主机
|
||||
const openSelectHost = () => {
|
||||
hostModal.value.open(formModel.value.hostIdList);
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
.selected-host {
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
padding: 0 12px;
|
||||
border-radius: 2px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
color: var(--color-text-2);
|
||||
background: var(--color-fill-2);
|
||||
transition: all 0.3s;
|
||||
|
||||
&-count {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
display: inline-block;
|
||||
margin: 0 6px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--color-fill-3);
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<div class="panel-container">
|
||||
<!-- 上传步骤 -->
|
||||
<batch-upload-step class="panel-item step-panel-container"
|
||||
:step="step"
|
||||
:status="stepStatus" />
|
||||
<!-- 上传表单 -->
|
||||
<batch-upload-form class="panel-item form-panel-container" />
|
||||
<!-- 上传文件 -->
|
||||
<batch-upload-files class="panel-item files-panel-container" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'batchUploadPanel'
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { UploadStep, UploadStepStatus } from '../types/const';
|
||||
import BatchUploadStep from './batch-upload-step.vue';
|
||||
import BatchUploadForm from './batch-upload-form.vue';
|
||||
import BatchUploadFiles from '@/views/exec/batch-upload/components/batch-upload-files.vue';
|
||||
|
||||
const step = ref(UploadStep.PREPARATION);
|
||||
const stepStatus = ref(UploadStepStatus.PROCESS);
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@step-width: 258px;
|
||||
@center-width: 398px;
|
||||
@files-width: calc(100% - @step-width - 16px - @center-width - 16px);
|
||||
|
||||
.panel-container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
position: relative;
|
||||
|
||||
.panel-item {
|
||||
height: 100%;
|
||||
padding: 16px;
|
||||
border-radius: 4px;
|
||||
margin-right: 16px;
|
||||
background: var(--color-bg-2);
|
||||
}
|
||||
|
||||
.step-panel-container {
|
||||
width: @step-width;
|
||||
}
|
||||
|
||||
.form-panel-container {
|
||||
width: @center-width;
|
||||
}
|
||||
|
||||
.files-panel-container {
|
||||
margin-right: 0;
|
||||
width: @files-width;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.panel-header) {
|
||||
width: 100%;
|
||||
height: 28px;
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
|
||||
h3, > span {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<a-steps :current="step"
|
||||
:status="status as any"
|
||||
direction="vertical">
|
||||
<a-step description="创建上传任务">创建任务</a-step>
|
||||
<a-step description="将文件上传到临时分区">上传文件</a-step>
|
||||
<a-step description="将文件分发到目标服务器">分发文件</a-step>
|
||||
<a-step description="上传完成并释放资源">上传完成</a-step>
|
||||
</a-steps>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'batchUploadStep'
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
defineProps<{
|
||||
step: number;
|
||||
status: string;
|
||||
}>();
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
</style>
|
||||
@@ -1,17 +1,37 @@
|
||||
<template>
|
||||
<div>批量上传</div>
|
||||
<div class="layout-container upload-container">
|
||||
<!-- 上传面板 -->
|
||||
<batch-upload-panel />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'index'
|
||||
name: 'batchUpload'
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted } from 'vue';
|
||||
import { useDictStore } from '@/store';
|
||||
import { dictKeys } from './types/const';
|
||||
import BatchUploadPanel from './components/batch-upload-panel.vue';
|
||||
|
||||
// 加载字典值
|
||||
onMounted(async () => {
|
||||
const dictStore = useDictStore();
|
||||
await dictStore.loadKeys(dictKeys);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
.upload-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
42
orion-ops-ui/src/views/exec/batch-upload/types/const.ts
Normal file
42
orion-ops-ui/src/views/exec/batch-upload/types/const.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
// 上传任务状态
|
||||
export const UploadTaskStatus = {
|
||||
// 准备中
|
||||
PREPARATION: 'PREPARATION',
|
||||
// 上传中
|
||||
UPLOADING: 'UPLOADING',
|
||||
// 已完成
|
||||
FINISHED: 'FINISHED',
|
||||
// 已取消
|
||||
CANCELED: 'CANCELED',
|
||||
};
|
||||
|
||||
// 上传步骤
|
||||
export const UploadStep = {
|
||||
// 准备中
|
||||
PREPARATION: 1,
|
||||
// 请求中
|
||||
REQUESTING: 2,
|
||||
// 分发中
|
||||
UPLOADING: 3,
|
||||
// 已完成
|
||||
FINISHED: 4,
|
||||
};
|
||||
|
||||
// 上传步骤状态
|
||||
export const UploadStepStatus = {
|
||||
// 处理中
|
||||
PROCESS: 'process',
|
||||
// 上传完成
|
||||
FINISH: 'finish',
|
||||
// 上传失败
|
||||
ERROR: 'error',
|
||||
};
|
||||
|
||||
// 上传任务状态 字典项
|
||||
export const taskStatusKey = 'uploadTaskStatus';
|
||||
|
||||
// 上传任务文件状态 字典项
|
||||
export const fileStatusKey = 'uploadTaskFileStatus';
|
||||
|
||||
// 加载的字典值
|
||||
export const dictKeys = [taskStatusKey, fileStatusKey];
|
||||
31
orion-ops-ui/src/views/exec/batch-upload/types/form.rules.ts
Normal file
31
orion-ops-ui/src/views/exec/batch-upload/types/form.rules.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import type { FieldRule } from '@arco-design/web-vue';
|
||||
|
||||
export const description = [{
|
||||
maxLength: 128,
|
||||
message: '描述长度不能大于128位'
|
||||
}] as FieldRule[];
|
||||
|
||||
export const hostIdList = [{
|
||||
required: true,
|
||||
message: '请选择上传主机'
|
||||
}] as FieldRule[];
|
||||
|
||||
export const remotePath = [{
|
||||
required: true,
|
||||
message: '请输入上传路径'
|
||||
}, {
|
||||
maxLength: 1024,
|
||||
message: '上传路径长度不能大于1024位'
|
||||
}] as FieldRule[];
|
||||
|
||||
export const files = [{
|
||||
required: true,
|
||||
message: '请选择文件'
|
||||
}] as FieldRule[];
|
||||
|
||||
export default {
|
||||
description,
|
||||
hostIdList,
|
||||
remotePath,
|
||||
files,
|
||||
} as Record<string, FieldRule | FieldRule[]>;
|
||||
@@ -21,11 +21,11 @@
|
||||
<div class="selected-host">
|
||||
<!-- 已选择数量 -->
|
||||
<span class="usn" v-if="formModel.hostIdList?.length">
|
||||
已选择<span class="selected-host-count span-blue">{{ formModel.hostIdList?.length }}</span>台主机
|
||||
</span>
|
||||
已选择<span class="selected-host-count span-blue">{{ formModel.hostIdList?.length }}</span>台主机
|
||||
</span>
|
||||
<span class="usn pointer span-blue" @click="openSelectHost">
|
||||
{{ formModel.hostIdList?.length ? '重新选择' : '选择主机' }}
|
||||
</span>
|
||||
{{ formModel.hostIdList?.length ? '重新选择' : '选择主机' }}
|
||||
</span>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
@@ -38,34 +38,31 @@
|
||||
</a-upload>
|
||||
</a-space>
|
||||
<!-- 文件列表 -->
|
||||
<a-upload v-if="fileList.length" class="file-list-uploader"
|
||||
<a-upload v-if="fileList.length"
|
||||
class="file-list-uploader"
|
||||
v-model:file-list="fileList"
|
||||
:auto-upload="false"
|
||||
:show-file-list="true">
|
||||
<template #upload-button />
|
||||
<template #file-name="{ fileItem }">
|
||||
<!-- 上传文件夹 -->
|
||||
<template v-if="fileItem.file.webkitRelativePath">
|
||||
<a-tooltip position="top"
|
||||
<div class="file-name-wrapper">
|
||||
<!-- 文件名称 -->
|
||||
<a-tooltip position="left"
|
||||
:mini="true"
|
||||
:auto-fix-position="false"
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content"
|
||||
:content="fileItem.file.webkitRelativePath">
|
||||
<span>{{ fileItem.file.webkitRelativePath }}</span>
|
||||
:content="fileItem.file.webkitRelativePath || fileItem.file.name">
|
||||
<!-- 文件名称 -->
|
||||
<span class="file-name text-ellipsis">
|
||||
{{ fileItem.file.webkitRelativePath || fileItem.file.name }}
|
||||
</span>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<!-- 上传文件 -->
|
||||
<template v-else>
|
||||
<a-tooltip position="top"
|
||||
:mini="true"
|
||||
:auto-fix-position="false"
|
||||
content-class="terminal-tooltip-content"
|
||||
arrow-class="terminal-tooltip-content"
|
||||
:content="fileItem.file.name">
|
||||
<span>{{ fileItem.file.name }}</span>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<!-- 文件大小 -->
|
||||
<span class="file-size span-blue">
|
||||
{{ getFileSize(fileItem.file.size) }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</a-upload>
|
||||
</div>
|
||||
@@ -79,17 +76,19 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { FileItem } from '@arco-design/web-vue';
|
||||
import { ref } from 'vue';
|
||||
import { useTerminalStore } from '@/store';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import useVisible from '@/hooks/visible';
|
||||
import { getFileSize } from '@/utils/file';
|
||||
|
||||
const { visible, setVisible } = useVisible();
|
||||
const { transferManager } = useTerminalStore();
|
||||
|
||||
const hostId = ref();
|
||||
const parentPath = ref('');
|
||||
const fileList = ref<any[]>([]);
|
||||
const fileList = ref<FileItem[]>([]);
|
||||
|
||||
// 打开
|
||||
const open = (host: number, parent: string) => {
|
||||
@@ -131,6 +130,8 @@
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@file-size-width: 82px;
|
||||
|
||||
.upload-container {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -162,6 +163,14 @@
|
||||
padding: 0 12px 0 0;
|
||||
}
|
||||
|
||||
:deep(.arco-upload-list-item-name) {
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.arco-upload-list-item-name-text) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.arco-upload-list-item:first-of-type) {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
@@ -170,4 +179,23 @@
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.file-name-wrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.file-name {
|
||||
color: var(--color-text-1);
|
||||
display: inline-block;
|
||||
width: calc(100% - @file-size-width);
|
||||
}
|
||||
|
||||
.file-size {
|
||||
font-size: 13px;
|
||||
display: inline-block;
|
||||
width: @file-size-width;
|
||||
text-align: end;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user