mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-20 16:00:51 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7cdc4f476a |
@@ -1,4 +1,5 @@
|
|||||||
import http from '@/api';
|
import http from '@/api';
|
||||||
|
import type { AxiosRequestConfig } from 'axios';
|
||||||
import { ResPage, SearchWithPage } from '../interface';
|
import { ResPage, SearchWithPage } from '../interface';
|
||||||
import { Container } from '../interface/container';
|
import { Container } from '../interface/container';
|
||||||
import { TimeoutEnum } from '@/enums/http-enum';
|
import { TimeoutEnum } from '@/enums/http-enum';
|
||||||
@@ -23,10 +24,11 @@ export const loadContainerUsers = (name: string) => {
|
|||||||
export const listContainerFiles = (params: Container.ContainerFileReq) => {
|
export const listContainerFiles = (params: Container.ContainerFileReq) => {
|
||||||
return http.post<Array<Container.ContainerFileInfo>>(`/containers/files/search`, params, TimeoutEnum.T_40S);
|
return http.post<Array<Container.ContainerFileInfo>>(`/containers/files/search`, params, TimeoutEnum.T_40S);
|
||||||
};
|
};
|
||||||
export const uploadContainerFile = (params: FormData) => {
|
export const uploadContainerFile = (params: FormData, config?: AxiosRequestConfig & { skipErrorMessage?: boolean }) => {
|
||||||
return http.upload(`/containers/files/upload`, params, {
|
return http.upload(`/containers/files/upload`, params, {
|
||||||
headers: { 'Content-Type': 'multipart/form-data' },
|
|
||||||
timeout: TimeoutEnum.T_5M,
|
timeout: TimeoutEnum.T_5M,
|
||||||
|
...config,
|
||||||
|
headers: { 'Content-Type': 'multipart/form-data', ...config?.headers },
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
export const getContainerFileContent = (params: Container.ContainerFileReq) => {
|
export const getContainerFileContent = (params: Container.ContainerFileReq) => {
|
||||||
|
|||||||
@@ -32,11 +32,9 @@
|
|||||||
@blur="cancelPathEditing"
|
@blur="cancelPathEditing"
|
||||||
@keyup.enter.prevent="submitPathInput"
|
@keyup.enter.prevent="submitPathInput"
|
||||||
/>
|
/>
|
||||||
<el-upload ref="uploadRef" :auto-upload="false" :show-file-list="false" :on-change="onUploadChange">
|
<el-button class="mt-2" type="primary" plain @click="onUpload">
|
||||||
<el-button class="mt-2" :loading="uploading" type="primary" plain>
|
{{ $t('commons.button.upload') }}
|
||||||
{{ $t('commons.button.upload') }}
|
</el-button>
|
||||||
</el-button>
|
|
||||||
</el-upload>
|
|
||||||
<el-button class="mt-2 ml-3" plain :disabled="selectedRows.length === 0" @click="onBatchDelete">
|
<el-button class="mt-2 ml-3" plain :disabled="selectedRows.length === 0" @click="onBatchDelete">
|
||||||
{{ $t('commons.button.delete') }}
|
{{ $t('commons.button.delete') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
@@ -97,6 +95,7 @@
|
|||||||
</el-form>
|
</el-form>
|
||||||
</template>
|
</template>
|
||||||
</DrawerPro>
|
</DrawerPro>
|
||||||
|
<FileUpload ref="uploadRef" @close="loadContainerFiles" />
|
||||||
<DialogPro v-model="previewVisible" :title="previewTitle" size="w-70" :close-on-click-modal="true">
|
<DialogPro v-model="previewVisible" :title="previewTitle" size="w-70" :close-on-click-modal="true">
|
||||||
<el-alert
|
<el-alert
|
||||||
v-if="previewTruncated"
|
v-if="previewTruncated"
|
||||||
@@ -118,11 +117,11 @@ import {
|
|||||||
getContainerFileContent,
|
getContainerFileContent,
|
||||||
getContainerFileSize,
|
getContainerFileSize,
|
||||||
listContainerFiles,
|
listContainerFiles,
|
||||||
uploadContainerFile,
|
|
||||||
} from '@/api/modules/container';
|
} from '@/api/modules/container';
|
||||||
import { MsgError, MsgSuccess, MsgWarning } from '@/utils/message';
|
import { MsgError, MsgSuccess, MsgWarning } from '@/utils/message';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { ElInput, ElMessageBox, UploadFile, UploadInstance } from 'element-plus';
|
import { ElInput, ElMessageBox } from 'element-plus';
|
||||||
|
import FileUpload from '@/views/host/file-management/upload/index.vue';
|
||||||
import { Document, FolderOpened, HomeFilled } from '@element-plus/icons-vue';
|
import { Document, FolderOpened, HomeFilled } from '@element-plus/icons-vue';
|
||||||
import { computeSize2 } from '@/utils/size';
|
import { computeSize2 } from '@/utils/size';
|
||||||
const visible = ref(false);
|
const visible = ref(false);
|
||||||
@@ -130,9 +129,8 @@ const title = ref('');
|
|||||||
const containerID = ref('');
|
const containerID = ref('');
|
||||||
const filePath = ref('/');
|
const filePath = ref('/');
|
||||||
const containerFiles = ref<any[]>([]);
|
const containerFiles = ref<any[]>([]);
|
||||||
const uploadRef = ref<UploadInstance>();
|
const uploadRef = ref<InstanceType<typeof FileUpload>>();
|
||||||
const pathInputRef = ref<InstanceType<typeof ElInput>>();
|
const pathInputRef = ref<InstanceType<typeof ElInput>>();
|
||||||
const uploading = ref(false);
|
|
||||||
const previewVisible = ref(false);
|
const previewVisible = ref(false);
|
||||||
const previewTitle = ref('');
|
const previewTitle = ref('');
|
||||||
const previewContent = ref('');
|
const previewContent = ref('');
|
||||||
@@ -253,27 +251,8 @@ const submitPathInput = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onUploadChange = async (uploadFile: UploadFile) => {
|
const onUpload = () => {
|
||||||
if (!uploadFile.raw) {
|
uploadRef.value?.acceptParams({ path: filePath.value, containerID: containerID.value });
|
||||||
return;
|
|
||||||
}
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('containerID', containerID.value);
|
|
||||||
formData.append('path', filePath.value);
|
|
||||||
formData.append('file', uploadFile.raw);
|
|
||||||
uploading.value = true;
|
|
||||||
await uploadContainerFile(formData)
|
|
||||||
.then(async () => {
|
|
||||||
MsgSuccess(i18n.global.t('file.uploadSuccess'));
|
|
||||||
await loadContainerFiles();
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
MsgError(err?.message || i18n.global.t('commons.msg.operationFailed'));
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
uploading.value = false;
|
|
||||||
uploadRef.value?.clearFiles();
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onOpenFile = async (path: string) => {
|
const onOpenFile = async (path: string) => {
|
||||||
|
|||||||
@@ -119,8 +119,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, nextTick, reactive, ref } from 'vue';
|
import { computed, onMounted, onBeforeUnmount, ref } from 'vue';
|
||||||
import { UploadFile, UploadFiles, UploadInstance, UploadProps, UploadRawFile } from 'element-plus';
|
import { UploadFile, UploadFiles, UploadInstance, UploadProps, UploadRawFile, genFileId } from 'element-plus';
|
||||||
|
import { uploadContainerFile } from '@/api/modules/container';
|
||||||
import { batchCheckFiles, stopChunkUpload, uploadFileData } from '@/api/modules/files';
|
import { batchCheckFiles, stopChunkUpload, uploadFileData } from '@/api/modules/files';
|
||||||
import { CHUNK_SIZE, uploadChunkWithRetry } from '@/utils/chunk-upload';
|
import { CHUNK_SIZE, uploadChunkWithRetry } from '@/utils/chunk-upload';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
@@ -134,6 +135,7 @@ import { useGlobalStore } from '@/composables/useGlobalStore';
|
|||||||
interface UploadFileProps {
|
interface UploadFileProps {
|
||||||
path: string;
|
path: string;
|
||||||
isAppendOnly?: boolean;
|
isAppendOnly?: boolean;
|
||||||
|
containerID?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uploadRef = ref<UploadInstance>();
|
const uploadRef = ref<UploadInstance>();
|
||||||
@@ -154,6 +156,8 @@ const uploadOverallPercent = computed(() => {
|
|||||||
const open = ref(false);
|
const open = ref(false);
|
||||||
const isAppendOnly = ref(false);
|
const isAppendOnly = ref(false);
|
||||||
const path = ref();
|
const path = ref();
|
||||||
|
const containerID = ref('');
|
||||||
|
const targetNode = ref('');
|
||||||
let uploadHelper = ref('');
|
let uploadHelper = ref('');
|
||||||
const dialogExistFileRef = ref();
|
const dialogExistFileRef = ref();
|
||||||
const abortController = ref<AbortController | null>(null);
|
const abortController = ref<AbortController | null>(null);
|
||||||
@@ -213,9 +217,6 @@ const closePage = () => {
|
|||||||
clearFiles();
|
clearFiles();
|
||||||
em('close', false);
|
em('close', false);
|
||||||
};
|
};
|
||||||
const state = reactive({
|
|
||||||
uploadEle: null,
|
|
||||||
});
|
|
||||||
const uploaderFiles = ref<UploadFiles>([]);
|
const uploaderFiles = ref<UploadFiles>([]);
|
||||||
const hoverIndex = ref<number | null>(null);
|
const hoverIndex = ref<number | null>(null);
|
||||||
const tmpFiles = ref<UploadFiles>([]);
|
const tmpFiles = ref<UploadFiles>([]);
|
||||||
@@ -225,9 +226,11 @@ const upload = (command: string) => {
|
|||||||
if (uploadLocked.value) {
|
if (uploadLocked.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
state.uploadEle.webkitdirectory = command == 'dir';
|
const input = uploadRef.value?.$el.querySelector('input[type=file]') as HTMLInputElement | null;
|
||||||
uploadRef.value.$el.querySelector('input').value = '';
|
if (!input) return;
|
||||||
uploadRef.value.$el.querySelector('input').click();
|
input.webkitdirectory = command === 'dir';
|
||||||
|
input.value = '';
|
||||||
|
input.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeFile = (index: number) => {
|
const removeFile = (index: number) => {
|
||||||
@@ -267,7 +270,7 @@ const handleDrop = async (event: DragEvent) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const convertFileToUploadFile = (file: File, path: string): UploadFile => {
|
const convertFileToUploadFile = (file: File, path: string): UploadFile => {
|
||||||
const uid = Date.now();
|
const uid = genFileId();
|
||||||
|
|
||||||
const uploadRawFile: UploadRawFile = new File([file], file.name, {
|
const uploadRawFile: UploadRawFile = new File([file], file.name, {
|
||||||
type: file.type,
|
type: file.type,
|
||||||
@@ -294,12 +297,15 @@ const traverseFileTree = async (item: any, path = '') => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (item.isFile) {
|
if (item.isFile) {
|
||||||
if (tmpFiles.value.length > 1000) {
|
if (uploaderFiles.value.length + tmpFiles.value.length >= 1000) {
|
||||||
breakFlag.value = true;
|
breakFlag.value = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await new Promise<void>((resolve) => {
|
await new Promise<void>((resolve) => {
|
||||||
item.file((file: File) => {
|
item.file((file: File) => {
|
||||||
|
if (uploaderFiles.value.length + tmpFiles.value.length >= 1000) {
|
||||||
|
breakFlag.value = true;
|
||||||
|
}
|
||||||
if (!breakFlag.value) {
|
if (!breakFlag.value) {
|
||||||
tmpFiles.value.push(convertFileToUploadFile(file, path));
|
tmpFiles.value.push(convertFileToUploadFile(file, path));
|
||||||
}
|
}
|
||||||
@@ -365,7 +371,14 @@ const handleSuccess: UploadProps['onSuccess'] = (res, file) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
const files = uploaderFiles.value.slice();
|
if (uploadLocked.value) return;
|
||||||
|
const files = containerID.value
|
||||||
|
? uploaderFiles.value.filter((file) => file.status !== 'success')
|
||||||
|
: uploaderFiles.value.slice();
|
||||||
|
if (containerID.value) {
|
||||||
|
await uploadFile(files, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const fileNamesWithPath = Array.from(
|
const fileNamesWithPath = Array.from(
|
||||||
new Set(files.map((file) => `${path.value}/${file.raw.webkitRelativePath || file.name}`)),
|
new Set(files.map((file) => `${path.value}/${file.raw.webkitRelativePath || file.name}`)),
|
||||||
);
|
);
|
||||||
@@ -410,7 +423,8 @@ const uploadFile = async (files: any[], overwrite: boolean) => {
|
|||||||
upLoading.value = true;
|
upLoading.value = true;
|
||||||
uploadTotalCount.value = files.length;
|
uploadTotalCount.value = files.length;
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const uploadNode = currentNode.value;
|
const uploadNode = containerID.value ? targetNode.value : currentNode.value;
|
||||||
|
const uploadContainerID = containerID.value;
|
||||||
abortController.value = controller;
|
abortController.value = controller;
|
||||||
let successCount = 0;
|
let successCount = 0;
|
||||||
try {
|
try {
|
||||||
@@ -426,10 +440,27 @@ const uploadFile = async (files: any[], overwrite: boolean) => {
|
|||||||
|
|
||||||
let isSuccess = false;
|
let isSuccess = false;
|
||||||
try {
|
try {
|
||||||
isSuccess =
|
if (uploadContainerID) {
|
||||||
Number(file.size) === 0
|
const data = new FormData();
|
||||||
? await uploadEmptyFile(file, controller, uploadNode, overwrite)
|
data.append('containerID', uploadContainerID);
|
||||||
: await uploadChunkedFile(file, controller, uploadNode, overwrite);
|
data.append('path', getUploadPath(file));
|
||||||
|
data.append('file', file.raw, getFilenameFromPath(file.raw.webkitRelativePath || file.name));
|
||||||
|
await uploadContainerFile(data, {
|
||||||
|
headers: { CurrentNode: uploadNode },
|
||||||
|
signal: controller.signal,
|
||||||
|
skipErrorMessage: true,
|
||||||
|
timeout: 0,
|
||||||
|
onUploadProgress: ({ loaded, total }) => {
|
||||||
|
uploadPercent.value = total ? Math.round((loaded / total) * 100) : 0;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
isSuccess = true;
|
||||||
|
} else {
|
||||||
|
isSuccess =
|
||||||
|
Number(file.size) === 0
|
||||||
|
? await uploadEmptyFile(file, controller, uploadNode, overwrite)
|
||||||
|
: await uploadChunkedFile(file, controller, uploadNode, overwrite);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!controller.signal.aborted) {
|
if (!controller.signal.aborted) {
|
||||||
MsgError(getErrorMessage(error));
|
MsgError(getErrorMessage(error));
|
||||||
@@ -573,6 +604,9 @@ const getFilenameFromPath = (path: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const acceptParams = (props: UploadFileProps) => {
|
const acceptParams = (props: UploadFileProps) => {
|
||||||
|
if (uploadLocked.value) return;
|
||||||
|
containerID.value = props.containerID || '';
|
||||||
|
targetNode.value = currentNode.value;
|
||||||
path.value = props.path;
|
path.value = props.path;
|
||||||
isAppendOnly.value = Boolean(props.isAppendOnly);
|
isAppendOnly.value = Boolean(props.isAppendOnly);
|
||||||
open.value = true;
|
open.value = true;
|
||||||
@@ -581,22 +615,19 @@ const acceptParams = (props: UploadFileProps) => {
|
|||||||
uploadCurrentIndex.value = 0;
|
uploadCurrentIndex.value = 0;
|
||||||
uploadHelper.value = '';
|
uploadHelper.value = '';
|
||||||
canceling.value = false;
|
canceling.value = false;
|
||||||
|
|
||||||
nextTick(() => {
|
|
||||||
state.uploadEle = document.querySelector('.el-upload__input');
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
const beforeUnload = (event: BeforeUnloadEvent) => {
|
||||||
window.addEventListener('beforeunload', (e) => {
|
if (upLoading.value) {
|
||||||
if (upLoading.value) {
|
event.preventDefault();
|
||||||
if (upLoading.value) {
|
event.returnValue = i18n.global.t('file.cancelUploadHelper');
|
||||||
e.preventDefault();
|
}
|
||||||
e.returnValue = i18n.global.t('file.cancelUploadHelper');
|
};
|
||||||
return i18n.global.t('file.cancelUploadHelper');
|
onMounted(() => window.addEventListener('beforeunload', beforeUnload));
|
||||||
}
|
onBeforeUnmount(() => {
|
||||||
}
|
window.removeEventListener('beforeunload', beforeUnload);
|
||||||
});
|
abortController.value?.abort();
|
||||||
|
void cleanupActiveChunkUpload();
|
||||||
});
|
});
|
||||||
|
|
||||||
defineExpose({ acceptParams, handleDrop, open });
|
defineExpose({ acceptParams, handleDrop, open });
|
||||||
|
|||||||
Reference in New Issue
Block a user