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