mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-18 22:22:59 +08:00
feat: 增加文件移动功能
This commit is contained in:
parent
16b9adfefe
commit
f4eead9cc2
@ -180,3 +180,16 @@ func (b *BaseApi) Download(c *gin.Context) {
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
||||
func (b *BaseApi) Move(c *gin.Context) {
|
||||
var req dto.FileMove
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
if err := fileService.MvFile(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, nil)
|
||||
}
|
||||
|
@ -63,3 +63,9 @@ type FileDownload struct {
|
||||
Path string `json:"path" validate:"required"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
}
|
||||
|
||||
type FileMove struct {
|
||||
Type string `json:"type" validate:"required"`
|
||||
OldPaths []string `json:"oldPaths" validate:"required"`
|
||||
NewPath string `json:"newPath" validate:"required"`
|
||||
}
|
||||
|
@ -125,6 +125,14 @@ func (f FileService) Download(c dto.FileDownload) error {
|
||||
return fo.DownloadFile(c.Url, filepath.Join(c.Path, c.Name))
|
||||
}
|
||||
|
||||
func (f FileService) MvFile(c dto.FileMove) error {
|
||||
fo := files.NewFileOp()
|
||||
if c.Type == "cut" {
|
||||
return fo.Cut(c.OldPaths, c.NewPath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getUuid() string {
|
||||
b := make([]byte, 16)
|
||||
io.ReadFull(rand.Reader, b)
|
||||
|
@ -27,6 +27,7 @@ func (f *FileRouter) InitFileRouter(Router *gin.RouterGroup) {
|
||||
fileRouter.POST("/upload", baseApi.UploadFiles)
|
||||
fileRouter.POST("/rename", baseApi.ChangeName)
|
||||
fileRouter.POST("/download", baseApi.Download)
|
||||
fileRouter.POST("/move", baseApi.Move)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -105,6 +105,17 @@ func (f FileOp) DownloadFile(url, dst string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f FileOp) Cut(oldPaths []string, dst string) error {
|
||||
for _, p := range oldPaths {
|
||||
base := filepath.Base(p)
|
||||
dstPath := filepath.Join(dst, base)
|
||||
if err := f.Fs.Rename(p, dstPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type CompressType string
|
||||
|
||||
const (
|
||||
|
@ -36,6 +36,7 @@ type FileOption struct {
|
||||
Path string `json:"path"`
|
||||
Search string `json:"search"`
|
||||
Expand bool `json:"expand"`
|
||||
Dir bool `json:"dir"`
|
||||
}
|
||||
|
||||
func NewFileInfo(op FileOption) (*FileInfo, error) {
|
||||
@ -69,7 +70,7 @@ func NewFileInfo(op FileOption) (*FileInfo, error) {
|
||||
}
|
||||
if op.Expand {
|
||||
if file.IsDir {
|
||||
if err := file.listChildren(); err != nil {
|
||||
if err := file.listChildren(op.Dir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return file, nil
|
||||
@ -82,14 +83,18 @@ func NewFileInfo(op FileOption) (*FileInfo, error) {
|
||||
return file, nil
|
||||
}
|
||||
|
||||
func (f *FileInfo) listChildren() error {
|
||||
func (f *FileInfo) listChildren(dir bool) error {
|
||||
afs := &afero.Afero{Fs: f.Fs}
|
||||
dir, err := afs.ReadDir(f.Path)
|
||||
files, err := afs.ReadDir(f.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var items []*FileInfo
|
||||
for _, df := range dir {
|
||||
for _, df := range files {
|
||||
if dir && !df.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
name := df.Name()
|
||||
fPath := path.Join(f.Path, df.Name())
|
||||
|
||||
|
@ -22,6 +22,7 @@ export namespace File {
|
||||
path: string;
|
||||
search?: string;
|
||||
expand: boolean;
|
||||
dir?: boolean;
|
||||
}
|
||||
|
||||
export interface FileTree {
|
||||
@ -75,4 +76,10 @@ export namespace File {
|
||||
name: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface FileMove {
|
||||
oldPaths: string[];
|
||||
newPath: string;
|
||||
type: string;
|
||||
}
|
||||
}
|
||||
|
@ -48,3 +48,7 @@ export const RenameRile = (params: File.FileRename) => {
|
||||
export const DownloadFile = (params: File.FileDownload) => {
|
||||
return http.post<File.File>('files/download', params);
|
||||
};
|
||||
|
||||
export const MoveFile = (params: File.FileMove) => {
|
||||
return http.post<File.File>('files/move', params);
|
||||
};
|
||||
|
@ -54,6 +54,10 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: '/',
|
||||
},
|
||||
dir: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const em = defineEmits(['choose']);
|
||||
@ -89,6 +93,7 @@ const jump = async (index: number) => {
|
||||
};
|
||||
|
||||
const search = async (req: File.ReqFile) => {
|
||||
req.dir = props.dir;
|
||||
loading.value = true;
|
||||
await GetFilesList(req)
|
||||
.then((res) => {
|
||||
|
@ -200,5 +200,8 @@ export default {
|
||||
downloadSuccess: '下载成功',
|
||||
downloadUrl: '下载地址',
|
||||
downloadStart: '下载开始!',
|
||||
moveStart: '移动成功',
|
||||
move: '移动',
|
||||
copy: '复制',
|
||||
},
|
||||
};
|
||||
|
@ -60,10 +60,17 @@
|
||||
<el-button type="primary" plain @click="openUpload"> {{ $t('file.upload') }}</el-button>
|
||||
<!-- <el-button type="primary" plain> {{ $t('file.search') }}</el-button> -->
|
||||
<el-button type="primary" plain @click="openDownload"> {{ $t('file.remoteFile') }}</el-button>
|
||||
<el-button type="primary" plain @click="openMove('copy')" :disabled="selects.length === 0">
|
||||
{{ $t('file.copy') }}</el-button
|
||||
>
|
||||
<el-button type="primary" plain @click="openMove('cut')" :disabled="selects.length === 0">
|
||||
{{ $t('file.move') }}</el-button
|
||||
>
|
||||
<!-- <el-button type="primary" plain> {{ $t('file.sync') }}</el-button>
|
||||
<el-button type="primary" plain> {{ $t('file.terminal') }}</el-button>
|
||||
<el-button type="primary" plain> {{ $t('file.shareList') }}</el-button> -->
|
||||
</template>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column :label="$t('commons.table.name')" min-width="250" fix show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<svg-icon v-if="row.isDir" className="table-icon" iconName="p-file-folder"></svg-icon>
|
||||
@ -132,6 +139,7 @@
|
||||
></FileRename>
|
||||
<Upload :open="uploadPage.open" :path="uploadPage.path" @close="closeUpload"></Upload>
|
||||
<FileDown :open="downloadPage.open" :path="downloadPage.path" @close="closeDownload"></FileDown>
|
||||
<Move :open="movePage.open" :oldPaths="movePage.oldPaths" :type="movePage.type" @close="clodeMove"></Move>
|
||||
</el-row>
|
||||
</LayoutContent>
|
||||
</template>
|
||||
@ -151,11 +159,12 @@ import ChangeRole from './change-role/index.vue';
|
||||
import Compress from './compress/index.vue';
|
||||
import Decompress from './decompress/index.vue';
|
||||
import Upload from './upload/index.vue';
|
||||
import FileRename from './file-rename/index.vue';
|
||||
import FileRename from './rename/index.vue';
|
||||
import { useDeleteData } from '@/hooks/use-delete-data';
|
||||
import CodeEditor from './code-editor/index.vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import FileDown from './file-down/index.vue';
|
||||
import FileDown from './download/index.vue';
|
||||
import Move from './move/index.vue';
|
||||
|
||||
let data = ref();
|
||||
let selects = ref<any>([]);
|
||||
@ -175,6 +184,7 @@ const codeReq = reactive({ path: '', expand: false });
|
||||
const uploadPage = reactive({ open: false, path: '' });
|
||||
const renamePage = reactive({ open: false, path: '', oldName: '' });
|
||||
const downloadPage = reactive({ open: false, path: '' });
|
||||
const movePage = reactive({ open: false, oldPaths: [''], type: '' });
|
||||
|
||||
const defaultProps = {
|
||||
children: 'children',
|
||||
@ -370,6 +380,21 @@ const closeRename = () => {
|
||||
search(req);
|
||||
};
|
||||
|
||||
const openMove = (type: string) => {
|
||||
movePage.type = type;
|
||||
movePage.open = true;
|
||||
const oldpaths = [];
|
||||
for (const s of selects.value) {
|
||||
oldpaths.push(s['path']);
|
||||
}
|
||||
movePage.oldPaths = oldpaths;
|
||||
};
|
||||
|
||||
const clodeMove = () => {
|
||||
movePage.open = false;
|
||||
search(req);
|
||||
};
|
||||
|
||||
const saveContent = (content: string) => {
|
||||
editorPage.loading = true;
|
||||
SaveFileContent({ path: codeReq.path, content: content }).finally(() => {
|
||||
|
107
frontend/src/views/file-management/move/index.vue
Normal file
107
frontend/src/views/file-management/move/index.vue
Normal file
@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<el-dialog v-model="open" :title="title" :before-close="handleClose" width="30%" @open="onOpen">
|
||||
<el-form
|
||||
ref="fileForm"
|
||||
label-position="left"
|
||||
:model="addForm"
|
||||
label-width="100px"
|
||||
:rules="rules"
|
||||
v-loading="loading"
|
||||
>
|
||||
<el-form-item :label="$t('file.path')" prop="newPath">
|
||||
<el-input v-model="addForm.newPath">
|
||||
<template #append> <FileList @choose="getPath" :dir="true"></FileList> </template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="submit(fileForm)" :disabled="loading">{{
|
||||
$t('commons.button.confirm')
|
||||
}}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { MoveFile } from '@/api/modules/files';
|
||||
import { Rules } from '@/global/form-rues';
|
||||
import i18n from '@/lang';
|
||||
import { ElMessage, FormInstance, FormRules } from 'element-plus';
|
||||
import { toRefs, ref, reactive, PropType, computed } from 'vue';
|
||||
import FileList from '@/components/file-list/index.vue';
|
||||
|
||||
const props = defineProps({
|
||||
open: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
oldPaths: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
const { open } = toRefs(props);
|
||||
const fileForm = ref<FormInstance>();
|
||||
const loading = ref(false);
|
||||
|
||||
const title = computed(() => {
|
||||
if (props.type === 'cut') {
|
||||
return i18n.global.t('file.move');
|
||||
} else {
|
||||
return i18n.global.t('file.copy');
|
||||
}
|
||||
});
|
||||
|
||||
const addForm = reactive({
|
||||
oldPaths: [] as string[],
|
||||
newPath: '',
|
||||
type: '',
|
||||
});
|
||||
|
||||
const rules = reactive<FormRules>({
|
||||
newPath: [Rules.requiredInput],
|
||||
});
|
||||
|
||||
const em = defineEmits(['close']);
|
||||
|
||||
const handleClose = () => {
|
||||
em('close', open);
|
||||
};
|
||||
|
||||
const getPath = (path: string) => {
|
||||
addForm.newPath = path;
|
||||
};
|
||||
|
||||
const submit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
await formEl.validate((valid) => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
MoveFile(addForm)
|
||||
.then(() => {
|
||||
ElMessage.success(i18n.global.t('file.moveStart'));
|
||||
handleClose();
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const onOpen = () => {
|
||||
addForm.oldPaths = props.oldPaths;
|
||||
addForm.type = props.type;
|
||||
};
|
||||
</script>
|
Loading…
Reference in New Issue
Block a user