mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-19 06:32: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)
|
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"`
|
Path string `json:"path" validate:"required"`
|
||||||
Name string `json:"name" 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))
|
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 {
|
func getUuid() string {
|
||||||
b := make([]byte, 16)
|
b := make([]byte, 16)
|
||||||
io.ReadFull(rand.Reader, b)
|
io.ReadFull(rand.Reader, b)
|
||||||
|
@ -27,6 +27,7 @@ func (f *FileRouter) InitFileRouter(Router *gin.RouterGroup) {
|
|||||||
fileRouter.POST("/upload", baseApi.UploadFiles)
|
fileRouter.POST("/upload", baseApi.UploadFiles)
|
||||||
fileRouter.POST("/rename", baseApi.ChangeName)
|
fileRouter.POST("/rename", baseApi.ChangeName)
|
||||||
fileRouter.POST("/download", baseApi.Download)
|
fileRouter.POST("/download", baseApi.Download)
|
||||||
|
fileRouter.POST("/move", baseApi.Move)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -105,6 +105,17 @@ func (f FileOp) DownloadFile(url, dst string) error {
|
|||||||
return nil
|
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
|
type CompressType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -36,6 +36,7 @@ type FileOption struct {
|
|||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
Search string `json:"search"`
|
Search string `json:"search"`
|
||||||
Expand bool `json:"expand"`
|
Expand bool `json:"expand"`
|
||||||
|
Dir bool `json:"dir"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFileInfo(op FileOption) (*FileInfo, error) {
|
func NewFileInfo(op FileOption) (*FileInfo, error) {
|
||||||
@ -69,7 +70,7 @@ func NewFileInfo(op FileOption) (*FileInfo, error) {
|
|||||||
}
|
}
|
||||||
if op.Expand {
|
if op.Expand {
|
||||||
if file.IsDir {
|
if file.IsDir {
|
||||||
if err := file.listChildren(); err != nil {
|
if err := file.listChildren(op.Dir); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return file, nil
|
return file, nil
|
||||||
@ -82,14 +83,18 @@ func NewFileInfo(op FileOption) (*FileInfo, error) {
|
|||||||
return file, nil
|
return file, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FileInfo) listChildren() error {
|
func (f *FileInfo) listChildren(dir bool) error {
|
||||||
afs := &afero.Afero{Fs: f.Fs}
|
afs := &afero.Afero{Fs: f.Fs}
|
||||||
dir, err := afs.ReadDir(f.Path)
|
files, err := afs.ReadDir(f.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var items []*FileInfo
|
var items []*FileInfo
|
||||||
for _, df := range dir {
|
for _, df := range files {
|
||||||
|
if dir && !df.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
name := df.Name()
|
name := df.Name()
|
||||||
fPath := path.Join(f.Path, df.Name())
|
fPath := path.Join(f.Path, df.Name())
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ export namespace File {
|
|||||||
path: string;
|
path: string;
|
||||||
search?: string;
|
search?: string;
|
||||||
expand: boolean;
|
expand: boolean;
|
||||||
|
dir?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FileTree {
|
export interface FileTree {
|
||||||
@ -75,4 +76,10 @@ export namespace File {
|
|||||||
name: string;
|
name: string;
|
||||||
url: 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) => {
|
export const DownloadFile = (params: File.FileDownload) => {
|
||||||
return http.post<File.File>('files/download', params);
|
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,
|
type: String,
|
||||||
default: '/',
|
default: '/',
|
||||||
},
|
},
|
||||||
|
dir: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const em = defineEmits(['choose']);
|
const em = defineEmits(['choose']);
|
||||||
@ -89,6 +93,7 @@ const jump = async (index: number) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const search = async (req: File.ReqFile) => {
|
const search = async (req: File.ReqFile) => {
|
||||||
|
req.dir = props.dir;
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
await GetFilesList(req)
|
await GetFilesList(req)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
|
@ -200,5 +200,8 @@ export default {
|
|||||||
downloadSuccess: '下载成功',
|
downloadSuccess: '下载成功',
|
||||||
downloadUrl: '下载地址',
|
downloadUrl: '下载地址',
|
||||||
downloadStart: '下载开始!',
|
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 @click="openUpload"> {{ $t('file.upload') }}</el-button>
|
||||||
<!-- <el-button type="primary" plain> {{ $t('file.search') }}</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="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.sync') }}</el-button>
|
||||||
<el-button type="primary" plain> {{ $t('file.terminal') }}</el-button>
|
<el-button type="primary" plain> {{ $t('file.terminal') }}</el-button>
|
||||||
<el-button type="primary" plain> {{ $t('file.shareList') }}</el-button> -->
|
<el-button type="primary" plain> {{ $t('file.shareList') }}</el-button> -->
|
||||||
</template>
|
</template>
|
||||||
|
<el-table-column type="selection" width="55" />
|
||||||
<el-table-column :label="$t('commons.table.name')" min-width="250" fix show-overflow-tooltip>
|
<el-table-column :label="$t('commons.table.name')" min-width="250" fix show-overflow-tooltip>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<svg-icon v-if="row.isDir" className="table-icon" iconName="p-file-folder"></svg-icon>
|
<svg-icon v-if="row.isDir" className="table-icon" iconName="p-file-folder"></svg-icon>
|
||||||
@ -132,6 +139,7 @@
|
|||||||
></FileRename>
|
></FileRename>
|
||||||
<Upload :open="uploadPage.open" :path="uploadPage.path" @close="closeUpload"></Upload>
|
<Upload :open="uploadPage.open" :path="uploadPage.path" @close="closeUpload"></Upload>
|
||||||
<FileDown :open="downloadPage.open" :path="downloadPage.path" @close="closeDownload"></FileDown>
|
<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>
|
</el-row>
|
||||||
</LayoutContent>
|
</LayoutContent>
|
||||||
</template>
|
</template>
|
||||||
@ -151,11 +159,12 @@ import ChangeRole from './change-role/index.vue';
|
|||||||
import Compress from './compress/index.vue';
|
import Compress from './compress/index.vue';
|
||||||
import Decompress from './decompress/index.vue';
|
import Decompress from './decompress/index.vue';
|
||||||
import Upload from './upload/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 { useDeleteData } from '@/hooks/use-delete-data';
|
||||||
import CodeEditor from './code-editor/index.vue';
|
import CodeEditor from './code-editor/index.vue';
|
||||||
import { ElMessage } from 'element-plus';
|
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 data = ref();
|
||||||
let selects = ref<any>([]);
|
let selects = ref<any>([]);
|
||||||
@ -175,6 +184,7 @@ const codeReq = reactive({ path: '', expand: false });
|
|||||||
const uploadPage = reactive({ open: false, path: '' });
|
const uploadPage = reactive({ open: false, path: '' });
|
||||||
const renamePage = reactive({ open: false, path: '', oldName: '' });
|
const renamePage = reactive({ open: false, path: '', oldName: '' });
|
||||||
const downloadPage = reactive({ open: false, path: '' });
|
const downloadPage = reactive({ open: false, path: '' });
|
||||||
|
const movePage = reactive({ open: false, oldPaths: [''], type: '' });
|
||||||
|
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
children: 'children',
|
children: 'children',
|
||||||
@ -370,6 +380,21 @@ const closeRename = () => {
|
|||||||
search(req);
|
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) => {
|
const saveContent = (content: string) => {
|
||||||
editorPage.loading = true;
|
editorPage.loading = true;
|
||||||
SaveFileContent({ path: codeReq.path, content: content }).finally(() => {
|
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