mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-02-14 20:40:06 +08:00
parent
ab932ae154
commit
8bb225d272
@ -417,6 +417,23 @@ func (b *BaseApi) CheckFile(c *gin.Context) {
|
||||
helper.SuccessWithData(c, true)
|
||||
}
|
||||
|
||||
// @Tags File
|
||||
// @Summary Batch check file exist
|
||||
// @Accept json
|
||||
// @Param request body request.FilePathsCheck true "request"
|
||||
// @Success 200 {array} response.ExistFileInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /files/batch/check [post]
|
||||
func (b *BaseApi) BatchCheckFiles(c *gin.Context) {
|
||||
var req request.FilePathsCheck
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
fileList := fileService.BatchCheckFiles(req)
|
||||
helper.SuccessWithData(c, fileList)
|
||||
}
|
||||
|
||||
// @Tags File
|
||||
// @Summary Change file name
|
||||
// @Accept json
|
||||
|
@ -79,6 +79,10 @@ type FilePathCheck struct {
|
||||
Path string `json:"path" validate:"required"`
|
||||
}
|
||||
|
||||
type FilePathsCheck struct {
|
||||
Paths []string `json:"paths" validate:"required"`
|
||||
}
|
||||
|
||||
type FileWget struct {
|
||||
Url string `json:"url" validate:"required"`
|
||||
Path string `json:"path" validate:"required"`
|
||||
|
@ -2,6 +2,7 @@ package response
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"time"
|
||||
)
|
||||
|
||||
type FileInfo struct {
|
||||
@ -46,3 +47,10 @@ type FileLineContent struct {
|
||||
type FileExist struct {
|
||||
Exist bool `json:"exist"`
|
||||
}
|
||||
|
||||
type ExistFileInfo struct {
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
Size float64 `json:"size"`
|
||||
ModTime time.Time `json:"modTime"`
|
||||
}
|
||||
|
@ -50,6 +50,7 @@ type IFileService interface {
|
||||
ChangeMode(op request.FileCreate) error
|
||||
BatchChangeModeAndOwner(op request.FileRoleReq) error
|
||||
ReadLogByLine(req request.FileReadByLineReq) (*response.FileLineContent, error)
|
||||
BatchCheckFiles(req request.FilePathsCheck) []response.ExistFileInfo
|
||||
}
|
||||
|
||||
var filteredPaths = []string{
|
||||
@ -501,3 +502,18 @@ func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.Fi
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (f *FileService) BatchCheckFiles(req request.FilePathsCheck) []response.ExistFileInfo {
|
||||
fileList := make([]response.ExistFileInfo, 0, len(req.Paths))
|
||||
for _, filePath := range req.Paths {
|
||||
if info, err := os.Stat(filePath); err == nil {
|
||||
fileList = append(fileList, response.ExistFileInfo{
|
||||
Size: float64(info.Size()),
|
||||
Name: info.Name(),
|
||||
Path: filePath,
|
||||
ModTime: info.ModTime(),
|
||||
})
|
||||
}
|
||||
}
|
||||
return fileList
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ func (f *FileRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
fileRouter.POST("/content", baseApi.GetContent)
|
||||
fileRouter.POST("/save", baseApi.SaveContent)
|
||||
fileRouter.POST("/check", baseApi.CheckFile)
|
||||
fileRouter.POST("/batch/check", baseApi.BatchCheckFiles)
|
||||
fileRouter.POST("/upload", baseApi.UploadFiles)
|
||||
fileRouter.POST("/chunkupload", baseApi.UploadChunkFiles)
|
||||
fileRouter.POST("/rename", baseApi.ChangeFileName)
|
||||
|
@ -152,6 +152,14 @@ export namespace File {
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface ExistFileInfo {
|
||||
name: string;
|
||||
path: string;
|
||||
size: number;
|
||||
uploadSize: number;
|
||||
modTime: string;
|
||||
}
|
||||
|
||||
export interface RecycleBin {
|
||||
sourcePath: string;
|
||||
name: string;
|
||||
|
@ -53,6 +53,10 @@ export const CheckFile = (path: string) => {
|
||||
return http.post<boolean>('files/check', { path: path });
|
||||
};
|
||||
|
||||
export const BatchCheckFiles = (paths: string[]) => {
|
||||
return http.post<File.ExistFileInfo[]>('files/batch/check', { paths: paths }, TimeoutEnum.T_5M);
|
||||
};
|
||||
|
||||
export const UploadFileData = (params: FormData, config: AxiosRequestConfig) => {
|
||||
return http.upload<File.File>('files/upload', params, config);
|
||||
};
|
||||
|
78
frontend/src/components/exist-file/index.vue
Normal file
78
frontend/src/components/exist-file/index.vue
Normal file
@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="$t('file.existFileTitle')"
|
||||
width="35%"
|
||||
:close-on-click-modal="false"
|
||||
:destroy-on-close="true"
|
||||
>
|
||||
<el-alert :show-icon="true" type="warning" :closable="false">
|
||||
<div class="whitespace-break-spaces">
|
||||
<span>{{ $t('file.existFileHelper') }}</span>
|
||||
</div>
|
||||
</el-alert>
|
||||
<div>
|
||||
<el-table :data="existFiles" max-height="350">
|
||||
<el-table-column type="index" :label="$t('commons.table.serialNumber')" width="55" />
|
||||
<el-table-column prop="path" :label="$t('commons.table.name')" :min-width="200" />
|
||||
<el-table-column :label="$t('file.existFileSize')" width="230">
|
||||
<template #default="{ row }">
|
||||
{{ getFileSize(row.uploadSize) }} -> {{ getFileSize(row.size) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="handleSkip">{{ $t('commons.button.skip') }}</el-button>
|
||||
<el-button type="primary" @click="handleOverwrite()">
|
||||
{{ $t('commons.button.cover') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { computeSize } from '@/utils/util';
|
||||
|
||||
const dialogVisible = ref();
|
||||
const existFiles = ref<DialogProps[]>([]);
|
||||
|
||||
interface DialogProps {
|
||||
name: string;
|
||||
path: string;
|
||||
size: number;
|
||||
uploadSize: number;
|
||||
modTime: string;
|
||||
}
|
||||
let onConfirmCallback = null;
|
||||
const getFileSize = (size: number) => {
|
||||
return computeSize(size);
|
||||
};
|
||||
|
||||
const handleSkip = () => {
|
||||
dialogVisible.value = false;
|
||||
if (onConfirmCallback) {
|
||||
onConfirmCallback(
|
||||
'skip',
|
||||
existFiles.value.map((file) => file.path),
|
||||
);
|
||||
}
|
||||
};
|
||||
const handleOverwrite = () => {
|
||||
dialogVisible.value = false;
|
||||
if (onConfirmCallback) {
|
||||
onConfirmCallback('overwrite');
|
||||
}
|
||||
};
|
||||
const acceptParams = async ({ paths, onConfirm }): Promise<void> => {
|
||||
existFiles.value = paths;
|
||||
onConfirmCallback = onConfirm;
|
||||
dialogVisible.value = true;
|
||||
};
|
||||
|
||||
defineExpose({ acceptParams });
|
||||
</script>
|
@ -74,6 +74,8 @@ const message = {
|
||||
createNewFile: 'Create new file',
|
||||
helpDoc: 'Help Document',
|
||||
unbind: 'Unbind',
|
||||
cover: 'cover',
|
||||
skip: 'skip',
|
||||
},
|
||||
search: {
|
||||
timeStart: 'Time start',
|
||||
@ -110,6 +112,7 @@ const message = {
|
||||
refreshRate: 'Refresh rate',
|
||||
refreshRateUnit: 'No refresh | {n} second/time | {n} seconds/time',
|
||||
selectColumn: 'Select column',
|
||||
serialNumber: 'Serial number',
|
||||
},
|
||||
loadingText: {
|
||||
Upgrading: 'System upgrade, please wait...',
|
||||
@ -1320,6 +1323,9 @@ const message = {
|
||||
minimap: 'Code mini map',
|
||||
fileCanNotRead: 'File can not read',
|
||||
panelInstallDir: `1Panel installation directory can't be deleted`,
|
||||
existFileTitle: 'Same name file prompt',
|
||||
existFileHelper: 'The uploaded file contains a file with the same name, do you want to overwrite it?',
|
||||
existFileSize: 'File size (new -> old)',
|
||||
},
|
||||
ssh: {
|
||||
setting: 'Setting',
|
||||
|
@ -73,6 +73,8 @@ const message = {
|
||||
createNewFile: '新しいファイルを作成します',
|
||||
helpDoc: '文書をヘルプします',
|
||||
unbind: 'バインド',
|
||||
cover: '上書き',
|
||||
skip: 'スキップ',
|
||||
},
|
||||
search: {
|
||||
timeStart: '時間開始',
|
||||
@ -109,6 +111,7 @@ const message = {
|
||||
refreshRate: 'リフレッシュレート',
|
||||
refreshRateUnit: '更新なし|{n}秒/時間 |{n}秒/時間',
|
||||
selectColumn: '列を選択します',
|
||||
serialNumber: 'シリアル番号',
|
||||
},
|
||||
loadingText: {
|
||||
Upgrading: 'システムのアップグレード、待ってください...',
|
||||
@ -1298,6 +1301,9 @@ const message = {
|
||||
minimap: 'コードミニマップ',
|
||||
fileCanNotRead: 'ファイルは読み取れません',
|
||||
panelInstallDir: `1Panelインストールディレクトリは削除できません`,
|
||||
existFileTitle: '同名ファイルの警告',
|
||||
existFileHelper: 'アップロードしたファイルに同じ名前のファイルが含まれています。上書きしますか?',
|
||||
existFileSize: 'ファイルサイズ(新しい -> 古い)',
|
||||
},
|
||||
ssh: {
|
||||
setting: '設定',
|
||||
|
@ -74,6 +74,8 @@ const message = {
|
||||
createNewFile: '새 파일 생성',
|
||||
helpDoc: '도움말 문서',
|
||||
unbind: '연결 해제',
|
||||
cover: '덮어쓰기',
|
||||
skip: '건너뛰기',
|
||||
},
|
||||
search: {
|
||||
timeStart: '시작 시간',
|
||||
@ -110,6 +112,7 @@ const message = {
|
||||
refreshRate: '새로 고침 속도',
|
||||
refreshRateUnit: '새로 고침 안 함 | {n} 초/회 | {n} 초/회',
|
||||
selectColumn: '열 선택',
|
||||
serialNumber: '일련 번호',
|
||||
},
|
||||
loadingText: {
|
||||
Upgrading: '시스템 업그레이드 중입니다. 잠시만 기다려 주십시오...',
|
||||
@ -1285,6 +1288,9 @@ const message = {
|
||||
minimap: '코드 미니맵',
|
||||
fileCanNotRead: '파일을 읽을 수 없습니다.',
|
||||
panelInstallDir: `1Panel 설치 디렉터리는 삭제할 수 없습니다.`,
|
||||
existFileTitle: '동일한 이름의 파일 경고',
|
||||
existFileHelper: '업로드한 파일에 동일한 이름의 파일이 포함되어 있습니다. 덮어쓰시겠습니까?',
|
||||
existFileSize: '파일 크기 (새로운 -> 오래된)',
|
||||
},
|
||||
ssh: {
|
||||
setting: '설정',
|
||||
|
@ -74,6 +74,8 @@ const message = {
|
||||
createNewFile: 'Cipta fail baru',
|
||||
helpDoc: 'Dokumen Bantuan',
|
||||
unbind: 'Nyahkaitkan',
|
||||
cover: 'Tindih',
|
||||
skip: 'Langkau',
|
||||
},
|
||||
search: {
|
||||
timeStart: 'Masa mula',
|
||||
@ -110,6 +112,7 @@ const message = {
|
||||
refreshRate: 'Kadar penyegaran',
|
||||
refreshRateUnit: 'Tiada penyegaran | {n} saat/masa | {n} saat/masa',
|
||||
selectColumn: 'Pilih lajur',
|
||||
serialNumber: 'Nombor siri',
|
||||
},
|
||||
loadingText: {
|
||||
Upgrading: 'Peningkatan sistem, sila tunggu...',
|
||||
@ -1340,6 +1343,9 @@ const message = {
|
||||
minimap: 'Peta mini kod',
|
||||
fileCanNotRead: 'Fail tidak dapat dibaca',
|
||||
panelInstallDir: 'Direktori pemasangan 1Panel tidak boleh dipadamkan',
|
||||
existFileTitle: 'Amaran fail dengan nama yang sama',
|
||||
existFileHelper: 'Fail yang dimuat naik mengandungi fail dengan nama yang sama. Adakah anda mahu menimpanya?',
|
||||
existFileSize: 'Saiz fail (baru -> lama)',
|
||||
},
|
||||
ssh: {
|
||||
setting: 'tetapan',
|
||||
|
@ -74,6 +74,8 @@ const message = {
|
||||
createNewFile: 'Criar novo arquivo',
|
||||
helpDoc: 'Documento de ajuda',
|
||||
unbind: 'Desvincular',
|
||||
cover: 'Substituir',
|
||||
skip: 'Pular',
|
||||
},
|
||||
search: {
|
||||
timeStart: 'Hora inicial',
|
||||
@ -110,6 +112,7 @@ const message = {
|
||||
refreshRate: 'Taxa de atualização',
|
||||
refreshRateUnit: 'Sem atualização | {n} segundo/atualização | {n} segundos/atualização',
|
||||
selectColumn: 'Selecionar coluna',
|
||||
serialNumber: 'Número de série',
|
||||
},
|
||||
loadingText: {
|
||||
Upgrading: 'Atualizando o sistema, por favor, aguarde...',
|
||||
@ -1326,6 +1329,9 @@ const message = {
|
||||
minimap: 'Mini mapa de código',
|
||||
fileCanNotRead: 'O arquivo não pode ser lido',
|
||||
panelInstallDir: 'O diretório de instalação do 1Panel não pode ser excluído',
|
||||
existFileTitle: 'Aviso de arquivo com o mesmo nome',
|
||||
existFileHelper: 'O arquivo enviado contém um arquivo com o mesmo nome. Deseja substituí-lo?',
|
||||
existFileSize: 'Tamanho do arquivo (novo -> antigo)',
|
||||
},
|
||||
ssh: {
|
||||
setting: 'configuração',
|
||||
|
@ -74,6 +74,8 @@ const message = {
|
||||
createNewFile: 'Создать новый файл',
|
||||
helpDoc: 'Справка',
|
||||
unbind: 'Отвязать',
|
||||
cover: 'Заменить',
|
||||
skip: 'Пропустить',
|
||||
},
|
||||
search: {
|
||||
timeStart: 'Время начала',
|
||||
@ -110,6 +112,8 @@ const message = {
|
||||
refreshRate: 'Частота обновления',
|
||||
refreshRateUnit: 'Без обновления | {n} секунда/раз | {n} секунд/раз',
|
||||
selectColumn: 'Выбрать столбец',
|
||||
cover: 'Заменить',
|
||||
skip: 'Пропустить',
|
||||
},
|
||||
loadingText: {
|
||||
Upgrading: 'Обновление системы, пожалуйста, подождите...',
|
||||
@ -1328,6 +1332,9 @@ const message = {
|
||||
minimap: 'Мини-карта кода',
|
||||
fileCanNotRead: 'Файл не может быть прочитан',
|
||||
panelInstallDir: 'Директорию установки 1Panel нельзя удалить',
|
||||
existFileTitle: 'Предупреждение о файле с тем же именем',
|
||||
existFileHelper: 'Загруженный файл содержит файл с таким же именем. Заменить его?',
|
||||
existFileSize: 'Размер файла (новый -> старый)',
|
||||
},
|
||||
ssh: {
|
||||
setting: 'настройка',
|
||||
|
@ -73,6 +73,8 @@ const message = {
|
||||
createNewFile: '新增檔案',
|
||||
helpDoc: '說明文件',
|
||||
unbind: '解綁',
|
||||
cover: '覆蓋',
|
||||
skip: '跳過',
|
||||
},
|
||||
search: {
|
||||
timeStart: '開始時間',
|
||||
@ -110,6 +112,7 @@ const message = {
|
||||
noRefresh: '不更新',
|
||||
refreshRateUnit: '不更新 | {0} 秒/次 | {0} 秒/次',
|
||||
selectColumn: '選擇列',
|
||||
serialNumber: '序號',
|
||||
},
|
||||
loadingText: {
|
||||
Upgrading: '系統升級中,請稍候...',
|
||||
@ -1257,6 +1260,9 @@ const message = {
|
||||
minimap: '縮圖',
|
||||
fileCanNotRead: '此檔案不支援預覽',
|
||||
panelInstallDir: '1Panel 安裝目錄不能刪除',
|
||||
existFileTitle: '同名檔案提示',
|
||||
existFileHelper: '上傳的檔案存在同名檔案,是否覆蓋?',
|
||||
existFileSize: '文件大小(新->舊)',
|
||||
},
|
||||
ssh: {
|
||||
setting: '設定',
|
||||
|
@ -73,6 +73,8 @@ const message = {
|
||||
createNewFile: '新建文件',
|
||||
helpDoc: '帮助文档',
|
||||
unbind: '解绑',
|
||||
cover: '覆盖',
|
||||
skip: '跳过',
|
||||
},
|
||||
search: {
|
||||
timeStart: '开始时间',
|
||||
@ -109,6 +111,7 @@ const message = {
|
||||
refreshRate: '刷新频率',
|
||||
refreshRateUnit: '不刷新 | {n} 秒/次 | {n} 秒/次',
|
||||
selectColumn: '选择列',
|
||||
serialNumber: '序号',
|
||||
},
|
||||
loadingText: {
|
||||
Upgrading: '系统升级中,请稍候...',
|
||||
@ -1258,6 +1261,9 @@ const message = {
|
||||
minimap: '缩略图',
|
||||
fileCanNotRead: '此文件不支持预览',
|
||||
panelInstallDir: '1Panel 安装目录不能删除',
|
||||
existFileTitle: '同名文件提示',
|
||||
existFileHelper: '上传的文件存在同名文件,是否覆盖?',
|
||||
existFileSize: '文件大小 (新 -> 旧)',
|
||||
},
|
||||
ssh: {
|
||||
setting: '配置',
|
||||
|
@ -85,18 +85,20 @@
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
<ExistFileDialog ref="dialogExistFileRef" />
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { nextTick, reactive, ref } from 'vue';
|
||||
import { UploadFile, UploadFiles, UploadInstance, UploadProps, UploadRawFile } from 'element-plus';
|
||||
import { ChunkUploadFileData, UploadFileData } from '@/api/modules/files';
|
||||
import { BatchCheckFiles, ChunkUploadFileData, UploadFileData } from '@/api/modules/files';
|
||||
import i18n from '@/lang';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { MsgError, MsgSuccess, MsgWarning } from '@/utils/message';
|
||||
import { Close, Document, UploadFilled } from '@element-plus/icons-vue';
|
||||
import { TimeoutEnum } from '@/enums/http-enum';
|
||||
import ExistFileDialog from '@/components/exist-file/index.vue';
|
||||
|
||||
interface UploadFileProps {
|
||||
path: string;
|
||||
@ -108,6 +110,7 @@ let uploadPercent = ref(0);
|
||||
const open = ref(false);
|
||||
const path = ref();
|
||||
let uploadHelper = ref('');
|
||||
const dialogExistFileRef = ref();
|
||||
|
||||
const em = defineEmits(['close']);
|
||||
const handleClose = () => {
|
||||
@ -122,6 +125,8 @@ const uploaderFiles = ref<UploadFiles>([]);
|
||||
const hoverIndex = ref<number | null>(null);
|
||||
const tmpFiles = ref<UploadFiles>([]);
|
||||
const breakFlag = ref(false);
|
||||
const CHUNK_SIZE = 1024 * 1024 * 5;
|
||||
const MAX_SINGLE_FILE_SIZE = 1024 * 1024 * 10;
|
||||
|
||||
const upload = (command: string) => {
|
||||
state.uploadEle.webkitdirectory = command == 'dir';
|
||||
@ -257,87 +262,125 @@ const handleSuccess: UploadProps['onSuccess'] = (res, file) => {
|
||||
};
|
||||
|
||||
const submit = async () => {
|
||||
loading.value = true;
|
||||
let success = 0;
|
||||
const files = uploaderFiles.value.slice();
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
const fileSize = file.size;
|
||||
|
||||
uploadHelper.value = i18n.global.t('file.fileUploadStart', [file.name]);
|
||||
if (fileSize <= 1024 * 1024 * 10) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file.raw);
|
||||
if (file.raw.webkitRelativePath != '') {
|
||||
formData.append('path', path.value + '/' + getPathWithoutFilename(file.raw.webkitRelativePath));
|
||||
} else {
|
||||
formData.append('path', path.value + '/' + getPathWithoutFilename(file.name));
|
||||
const fileNamesWithPath = Array.from(
|
||||
new Set(files.map((file) => `${path.value}/${file.raw.webkitRelativePath || file.name}`)),
|
||||
);
|
||||
const existFiles = await BatchCheckFiles(fileNamesWithPath);
|
||||
if (existFiles.data.length > 0) {
|
||||
const fileSizeMap = new Map(
|
||||
files.map((file) => [`${path.value}/${file.raw.webkitRelativePath || file.name}`, file.size]),
|
||||
);
|
||||
existFiles.data.forEach((file) => {
|
||||
if (fileSizeMap.has(file.path)) {
|
||||
file.uploadSize = fileSizeMap.get(file.path);
|
||||
}
|
||||
formData.append('overwrite', 'True');
|
||||
uploadPercent.value = 0;
|
||||
await UploadFileData(formData, {
|
||||
onUploadProgress: (progressEvent) => {
|
||||
const progress = Math.round((progressEvent.loaded / progressEvent.total) * 100);
|
||||
uploadPercent.value = progress;
|
||||
},
|
||||
timeout: 40000,
|
||||
});
|
||||
success++;
|
||||
uploaderFiles.value[i].status = 'success';
|
||||
} else {
|
||||
const CHUNK_SIZE = 1024 * 1024 * 5;
|
||||
const chunkCount = Math.ceil(fileSize / CHUNK_SIZE);
|
||||
let uploadedChunkCount = 0;
|
||||
for (let c = 0; c < chunkCount; c++) {
|
||||
const start = c * CHUNK_SIZE;
|
||||
const end = Math.min(start + CHUNK_SIZE, fileSize);
|
||||
const chunk = file.raw.slice(start, end);
|
||||
const formData = new FormData();
|
||||
});
|
||||
dialogExistFileRef.value.acceptParams({
|
||||
paths: existFiles.data,
|
||||
onConfirm: handleFileUpload,
|
||||
});
|
||||
} else {
|
||||
await uploadFile(files);
|
||||
}
|
||||
};
|
||||
|
||||
formData.append('filename', getFilenameFromPath(file.name));
|
||||
if (file.raw.webkitRelativePath != '') {
|
||||
formData.append('path', path.value + '/' + getPathWithoutFilename(file.raw.webkitRelativePath));
|
||||
} else {
|
||||
formData.append('path', path.value + '/' + getPathWithoutFilename(file.name));
|
||||
}
|
||||
formData.append('chunk', chunk);
|
||||
formData.append('chunkIndex', c.toString());
|
||||
formData.append('chunkCount', chunkCount.toString());
|
||||
const handleFileUpload = (action: 'skip' | 'overwrite', skippedPaths: string[] = []) => {
|
||||
const files = uploaderFiles.value.slice();
|
||||
if (action === 'skip') {
|
||||
const filteredFiles = files.filter(
|
||||
(file) => !skippedPaths.includes(`${path.value}/${file.raw.webkitRelativePath || file.name}`),
|
||||
);
|
||||
uploaderFiles.value = filteredFiles;
|
||||
uploadFile(filteredFiles);
|
||||
} else if (action === 'overwrite') {
|
||||
uploadFile(files);
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
await ChunkUploadFileData(formData, {
|
||||
onUploadProgress: (progressEvent) => {
|
||||
const progress = Math.round(
|
||||
((uploadedChunkCount + progressEvent.loaded / progressEvent.total) * 100) / chunkCount,
|
||||
);
|
||||
uploadPercent.value = progress;
|
||||
},
|
||||
timeout: TimeoutEnum.T_60S,
|
||||
});
|
||||
uploadedChunkCount++;
|
||||
} catch (error) {
|
||||
uploaderFiles.value[i].status = 'fail';
|
||||
break;
|
||||
}
|
||||
if (uploadedChunkCount == chunkCount) {
|
||||
success++;
|
||||
uploaderFiles.value[i].status = 'success';
|
||||
break;
|
||||
}
|
||||
const uploadFile = async (files: any[]) => {
|
||||
if (files.length == 0) {
|
||||
clearFiles();
|
||||
} else {
|
||||
loading.value = true;
|
||||
let successCount = 0;
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
uploadHelper.value = i18n.global.t('file.fileUploadStart', [file.name]);
|
||||
|
||||
let isSuccess =
|
||||
file.size <= MAX_SINGLE_FILE_SIZE ? await uploadSingleFile(file) : await uploadLargeFile(file);
|
||||
|
||||
if (isSuccess) {
|
||||
successCount++;
|
||||
uploaderFiles.value[i].status = 'success';
|
||||
} else {
|
||||
uploaderFiles.value[i].status = 'fail';
|
||||
}
|
||||
}
|
||||
|
||||
if (i == files.length - 1) {
|
||||
loading.value = false;
|
||||
uploadHelper.value = '';
|
||||
if (success == files.length) {
|
||||
clearFiles();
|
||||
MsgSuccess(i18n.global.t('file.uploadSuccess'));
|
||||
}
|
||||
loading.value = false;
|
||||
uploadHelper.value = '';
|
||||
|
||||
if (successCount === files.length) {
|
||||
clearFiles();
|
||||
MsgSuccess(i18n.global.t('file.uploadSuccess'));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const uploadSingleFile = async (file: { raw: string | Blob }) => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file.raw);
|
||||
formData.append('path', getUploadPath(file));
|
||||
formData.append('overwrite', 'True');
|
||||
uploadPercent.value = 0;
|
||||
await UploadFileData(formData, {
|
||||
onUploadProgress: (progressEvent) => {
|
||||
uploadPercent.value = Math.round((progressEvent.loaded / progressEvent.total) * 100);
|
||||
},
|
||||
timeout: 40000,
|
||||
});
|
||||
return true;
|
||||
};
|
||||
|
||||
const uploadLargeFile = async (file: { size: any; raw: string | Blob; name: string }) => {
|
||||
const fileSize = file.size;
|
||||
const chunkCount = Math.ceil(fileSize / CHUNK_SIZE);
|
||||
let uploadedChunkCount = 0;
|
||||
for (let c = 0; c < chunkCount; c++) {
|
||||
const start = c * CHUNK_SIZE;
|
||||
const end = Math.min(start + CHUNK_SIZE, fileSize);
|
||||
const chunk = file.raw.slice(start, end);
|
||||
const formData = new FormData();
|
||||
formData.append('filename', getFilenameFromPath(file.name));
|
||||
formData.append('path', getUploadPath(file));
|
||||
formData.append('chunk', chunk);
|
||||
formData.append('chunkIndex', c.toString());
|
||||
formData.append('chunkCount', chunkCount.toString());
|
||||
|
||||
try {
|
||||
await ChunkUploadFileData(formData, {
|
||||
onUploadProgress: (progressEvent) => {
|
||||
uploadPercent.value = Math.round(
|
||||
((uploadedChunkCount + progressEvent.loaded / progressEvent.total) * 100) / chunkCount,
|
||||
);
|
||||
},
|
||||
timeout: TimeoutEnum.T_60S,
|
||||
});
|
||||
uploadedChunkCount++;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return uploadedChunkCount === chunkCount;
|
||||
};
|
||||
|
||||
const getUploadPath = (file) => {
|
||||
return `${path.value}/${getPathWithoutFilename(file.raw.webkitRelativePath || file.name)}`;
|
||||
};
|
||||
|
||||
const getPathWithoutFilename = (path: string) => {
|
||||
return path ? path.split('/').slice(0, -1).join('/') : path;
|
||||
};
|
||||
@ -353,8 +396,7 @@ const acceptParams = (props: UploadFileProps) => {
|
||||
uploadHelper.value = '';
|
||||
|
||||
nextTick(() => {
|
||||
const uploadEle = document.querySelector('.el-upload__input');
|
||||
state.uploadEle = uploadEle;
|
||||
state.uploadEle = document.querySelector('.el-upload__input');
|
||||
});
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user