mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-31 14:08:06 +08:00
feat: 增加上传文件夹功能 (#2661)
Refs https://github.com/1Panel-dev/1Panel/issues/1364 Refs https://github.com/1Panel-dev/1Panel/issues/2053
This commit is contained in:
parent
f30f561723
commit
b045261f16
@ -568,12 +568,13 @@ func (b *BaseApi) Size(c *gin.Context) {
|
||||
}
|
||||
|
||||
func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int) error {
|
||||
if _, err := os.Stat(path.Dir(dstDir)); err != nil && os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(path.Dir(dstDir), os.ModePerm); err != nil {
|
||||
op := files.NewFileOp()
|
||||
dstDir = strings.TrimSpace(dstDir)
|
||||
if _, err := os.Stat(dstDir); err != nil && os.IsNotExist(err) {
|
||||
if err = op.CreateDir(dstDir, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
targetFile, err := os.Create(filepath.Join(dstDir, fileName))
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -975,6 +975,7 @@ const message = {
|
||||
favorite: 'favorites',
|
||||
removeFavorite: 'Remove from favorites?',
|
||||
addFavorite: 'Add to favorites',
|
||||
clearList: 'Clear list',
|
||||
},
|
||||
ssh: {
|
||||
autoStart: 'Auto Start',
|
||||
|
@ -936,6 +936,7 @@ const message = {
|
||||
favorite: '收藏夾',
|
||||
removeFavorite: '是否從收藏夾移出?',
|
||||
addFavorite: '加入收藏夾',
|
||||
clearList: '清空列表',
|
||||
},
|
||||
ssh: {
|
||||
autoStart: '開機自啟',
|
||||
|
@ -937,6 +937,7 @@ const message = {
|
||||
favorite: '收藏夹',
|
||||
removeFavorite: '是否从收藏夹移出?',
|
||||
addFavorite: '加入收藏夹子',
|
||||
clearList: '清空列表',
|
||||
},
|
||||
ssh: {
|
||||
autoStart: '开机自启',
|
||||
|
@ -53,6 +53,7 @@ const forceDelete = ref(false);
|
||||
const acceptParams = (props: File.File[]) => {
|
||||
files.value = props;
|
||||
open.value = true;
|
||||
forceDelete.value = false;
|
||||
};
|
||||
|
||||
const onConfirm = () => {
|
||||
|
@ -9,6 +9,26 @@
|
||||
<template #header>
|
||||
<DrawerHeader :header="$t('file.upload')" :back="handleClose" />
|
||||
</template>
|
||||
<div class="button-container">
|
||||
<el-dropdown @command="upload">
|
||||
<el-button type="primary">
|
||||
{{ $t('file.upload') }}
|
||||
<el-icon><arrow-down /></el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="file">
|
||||
{{ $t('file.file') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="dir">
|
||||
{{ $t('file.dir') }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-button @click="clearFiles">{{ $t('file.clearList') }}</el-button>
|
||||
</div>
|
||||
|
||||
<el-upload
|
||||
action="#"
|
||||
drag
|
||||
@ -17,8 +37,9 @@
|
||||
:on-change="fileOnChange"
|
||||
:on-exceed="handleExceed"
|
||||
:on-success="hadleSuccess"
|
||||
show-file-list
|
||||
:show-file-list="false"
|
||||
multiple
|
||||
v-model:file-list="uploaderFiles"
|
||||
>
|
||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<div class="el-upload__text">
|
||||
@ -30,6 +51,31 @@
|
||||
<el-progress v-if="loading" text-inside :stroke-width="20" :percentage="uploadPrecent"></el-progress>
|
||||
</template>
|
||||
</el-upload>
|
||||
<div>
|
||||
<p
|
||||
v-for="(item, index) in uploaderFiles"
|
||||
:key="index"
|
||||
class="file-item"
|
||||
@mouseover="hoverIndex = index"
|
||||
@mouseout="hoverIndex = null"
|
||||
>
|
||||
<el-icon class="file-icon"><Document /></el-icon>
|
||||
<span v-if="item.raw.webkitRelativePath != ''">{{ item.raw.webkitRelativePath }}</span>
|
||||
<span v-else>{{ item.name }}</span>
|
||||
<span v-if="item.status === 'success'" class="success-icon">
|
||||
<el-icon><Select /></el-icon>
|
||||
</span>
|
||||
<span v-else>
|
||||
<el-button
|
||||
class="delete-button"
|
||||
type="primary"
|
||||
link
|
||||
@click="removeFile(index)"
|
||||
:icon="Close"
|
||||
></el-button>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button>
|
||||
@ -42,12 +88,13 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { nextTick, reactive, ref } from 'vue';
|
||||
import { UploadFile, UploadFiles, UploadInstance, UploadProps, UploadRawFile } from 'element-plus';
|
||||
import { ChunkUploadFileData, UploadFileData } from '@/api/modules/files';
|
||||
import i18n from '@/lang';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { Close } from '@element-plus/icons-vue';
|
||||
|
||||
interface UploadFileProps {
|
||||
path: string;
|
||||
@ -66,13 +113,35 @@ const handleClose = () => {
|
||||
uploadRef.value!.clearFiles();
|
||||
em('close', false);
|
||||
};
|
||||
|
||||
const state = reactive({
|
||||
uploadEle: null,
|
||||
});
|
||||
const uploaderFiles = ref<UploadFiles>([]);
|
||||
const isUploadFolder = ref(false);
|
||||
const hoverIndex = ref(null);
|
||||
|
||||
const upload = (commnad: string) => {
|
||||
if (commnad == 'dir') {
|
||||
state.uploadEle.webkitdirectory = true;
|
||||
} else {
|
||||
state.uploadEle.webkitdirectory = false;
|
||||
}
|
||||
isUploadFolder.value = true;
|
||||
uploadRef.value.$el.querySelector('input').click();
|
||||
};
|
||||
|
||||
const removeFile = (index: number) => {
|
||||
uploaderFiles.value.splice(index, 1);
|
||||
};
|
||||
|
||||
const fileOnChange = (_uploadFile: UploadFile, uploadFiles: UploadFiles) => {
|
||||
uploaderFiles.value = uploadFiles;
|
||||
};
|
||||
|
||||
const clearFiles = () => {
|
||||
uploadRef.value!.clearFiles();
|
||||
};
|
||||
|
||||
const handleExceed: UploadProps['onExceed'] = (files) => {
|
||||
uploadRef.value!.clearFiles();
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
@ -111,7 +180,11 @@ const submit = async () => {
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append('filename', file.name);
|
||||
formData.append('path', path.value);
|
||||
if (file.raw.webkitRelativePath != '') {
|
||||
formData.append('path', path.value + '/' + getPathWithoutFilename(file.raw.webkitRelativePath));
|
||||
} else {
|
||||
formData.append('path', path.value);
|
||||
}
|
||||
formData.append('chunk', chunk);
|
||||
formData.append('chunkIndex', c.toString());
|
||||
formData.append('chunkCount', chunkCount.toString());
|
||||
@ -149,12 +222,58 @@ const submit = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const getPathWithoutFilename = (path: string) => {
|
||||
return path ? path.split('/').slice(0, -1).join('/') : path;
|
||||
};
|
||||
|
||||
const acceptParams = (props: UploadFileProps) => {
|
||||
path.value = props.path;
|
||||
open.value = true;
|
||||
uploadPrecent.value = 0;
|
||||
uploadHelper.value = '';
|
||||
|
||||
nextTick(() => {
|
||||
const uploadEle = document.querySelector('.el-upload__input');
|
||||
state.uploadEle = uploadEle;
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({ acceptParams });
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.button-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.file-item {
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.file-item:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.file-icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.delete-button {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.success-icon {
|
||||
color: green;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
</style>
|
||||
|
Loading…
x
Reference in New Issue
Block a user