diff --git a/internal/service/file.go b/internal/service/file.go index 9779807d..66735f4a 100644 --- a/internal/service/file.go +++ b/internal/service/file.go @@ -175,7 +175,7 @@ func (s *FileService) Move(w http.ResponseWriter, r *http.Request) { } if io.Exists(req.Target) && !req.Force { - Error(w, http.StatusForbidden, "target path %s already exists", req.Target) + Error(w, http.StatusForbidden, "target path already exists") // no translate, frontend will use it to determine whether to continue return } @@ -200,7 +200,7 @@ func (s *FileService) Copy(w http.ResponseWriter, r *http.Request) { } if io.Exists(req.Target) && !req.Force { - Error(w, http.StatusForbidden, "target path %s already exists", req.Target) + Error(w, http.StatusForbidden, "target path already exists") // no translate, frontend will use it to determine whether to continue return } diff --git a/web/src/api/panel/file/index.ts b/web/src/api/panel/file/index.ts index 7a059630..0849d908 100644 --- a/web/src/api/panel/file/index.ts +++ b/web/src/api/panel/file/index.ts @@ -1,6 +1,7 @@ import type { AxiosResponse } from 'axios' import { request } from '@/utils' +import type { RequestConfig } from '~/types/axios' export default { // 创建文件/文件夹 @@ -25,11 +26,11 @@ export default { }) }, // 移动文件 - move: (source: string, target: string): Promise> => - request.post('/file/move', { source, target }), + move: (source: string, target: string, force: boolean): Promise> => + request.post('/file/move', { source, target, force }, { noNeedTip: true } as RequestConfig), // 复制文件 - copy: (source: string, target: string): Promise> => - request.post('/file/copy', { source, target }), + copy: (source: string, target: string, force: boolean): Promise> => + request.post('/file/copy', { source, target, force }, { noNeedTip: true } as RequestConfig), // 远程下载 remoteDownload: (path: string, url: string): Promise> => request.post('/file/remoteDownload', { path, url }), diff --git a/web/src/views/file/ListTable.vue b/web/src/views/file/ListTable.vue index ef8602e4..404424d5 100644 --- a/web/src/views/file/ListTable.vue +++ b/web/src/views/file/ListTable.vue @@ -38,7 +38,7 @@ const unCompressModel = ref({ const options = computed(() => { if (selectedRow.value == null) return [] - return [ + let options = [ { label: selectedRow.value.dir ? '打开' : '编辑', key: selectedRow.value.dir ? 'open' : 'edit' @@ -59,6 +59,14 @@ const options = computed(() => { { label: '重命名', key: 'rename' }, { label: () => h('span', { style: { color: 'red' } }, '删除'), key: 'delete' } ] + if (marked.value.length) { + options.unshift({ + label: '粘贴', + key: 'paste' + }) + } + + return options }) const columns: DataTableColumns = [ @@ -385,11 +393,43 @@ const handleRename = () => { return } - file.move(source, target).then(() => { - window.$message.success('重命名成功') - renameModal.value = false - window.$bus.emit('file:refresh') - }) + file + .move(source, target, false) + .then(() => { + window.$message.success( + `重命名 ${renameModel.value.source} 为 ${renameModel.value.target} 成功` + ) + renameModal.value = false + window.$bus.emit('file:refresh') + }) + .catch((err) => { + if (err.message == 'target path already exists') { + window.$dialog.warning({ + title: '警告', + content: `存在同名项,是否强制覆盖?`, + positiveText: '覆盖', + negativeText: '取消', + onPositiveClick: () => { + file + .move(source, target, true) + .then(() => { + window.$message.success( + `重命名 ${renameModel.value.source} 为 ${renameModel.value.target} 成功` + ) + renameModal.value = false + window.$bus.emit('file:refresh') + }) + .catch((err) => { + window.$message.error(err.message) + }) + }, + onNegativeClick: () => { + renameModal.value = false + window.$bus.emit('file:refresh') + } + }) + } + }) } const handleUnCompress = () => { @@ -422,8 +462,89 @@ const onChecked = (rowKeys: any) => { selected.value = rowKeys } +const handlePaste = async () => { + if (!marked.value.length) { + window.$message.error('请先标记需要复制或移动的文件/文件夹') + return + } + + for (const { name, source, type } of marked.value) { + const target = path.value + '/' + name + if (type === 'copy') { + file + .copy(source, target, false) + .then(() => { + window.$message.success(`复制 ${source} 到 ${target} 成功`) + }) + .catch((err) => { + if (err.message == 'target path already exists') { + window.$dialog.warning({ + title: '警告', + content: `目标 ${target} 已存在,是否覆盖?`, + positiveText: '覆盖', + negativeText: '取消', + onPositiveClick: () => { + file + .copy(source, target, true) + .then(() => { + window.$message.success(`复制 ${source} 到 ${target} 成功`) + }) + .catch((err) => { + window.$message.error(err.message) + }) + }, + onNegativeClick: () => { + marked.value = [] + } + }) + } + }) + .finally(() => { + window.$bus.emit('file:refresh') + }) + } else { + file + .move(source, target, false) + .then(() => { + window.$message.success(`移动 ${source} 到 ${target} 成功`) + }) + .catch((err) => { + if (err.message == 'target path already exists') { + window.$dialog.warning({ + title: '警告', + content: `目标 ${target} 已存在,是否覆盖?`, + positiveText: '覆盖', + negativeText: '取消', + onPositiveClick: () => { + file + .move(source, target, true) + .then(() => { + window.$message.success(`移动 ${source} 到 ${target} 成功`) + }) + .catch((err) => { + window.$message.error(err.message) + }) + }, + onNegativeClick: () => { + marked.value = [] + } + }) + } + }) + .finally(() => { + window.$bus.emit('file:refresh') + }) + } + } + + marked.value = [] +} + const handleSelect = (key: string) => { switch (key) { + case 'paste': + handlePaste() + break case 'open': path.value = selectedRow.value.full break diff --git a/web/src/views/file/ToolBar.vue b/web/src/views/file/ToolBar.vue index a86d1b03..10ef0bba 100644 --- a/web/src/views/file/ToolBar.vue +++ b/web/src/views/file/ToolBar.vue @@ -83,6 +83,10 @@ const handleMove = () => { window.$message.success('标记成功,请前往目标路径粘贴') } +const handleCancel = () => { + marked.value = [] +} + const handlePaste = async () => { if (!marked.value.length) { window.$message.error('请先标记需要复制或移动的文件/文件夹') @@ -92,25 +96,75 @@ const handlePaste = async () => { for (const { name, source, type } of marked.value) { const target = path.value + '/' + name if (type === 'copy') { - await file.copy(source, target).then(() => { - window.$message.success(`复制 ${source} 到 ${target} 成功`) - window.$bus.emit('file:refresh') - }) + file + .copy(source, target, false) + .then(() => { + window.$message.success(`复制 ${source} 到 ${target} 成功`) + }) + .catch((err) => { + if (err.message == 'target path already exists') { + window.$dialog.warning({ + title: '警告', + content: `目标 ${target} 已存在,是否覆盖?`, + positiveText: '覆盖', + negativeText: '取消', + onPositiveClick: () => { + file + .copy(source, target, true) + .then(() => { + window.$message.success(`复制 ${source} 到 ${target} 成功`) + }) + .catch((err) => { + window.$message.error(err.message) + }) + }, + onNegativeClick: () => { + handleCancel() + } + }) + } + }) + .finally(() => { + window.$bus.emit('file:refresh') + }) } else { - await file.move(source, target).then(() => { - window.$message.success(`移动 ${source} 到 ${target} 成功`) - window.$bus.emit('file:refresh') - }) + file + .move(source, target, false) + .then(() => { + window.$message.success(`移动 ${source} 到 ${target} 成功`) + }) + .catch((err) => { + if (err.message == 'target path already exists') { + window.$dialog.warning({ + title: '警告', + content: `目标 ${target} 已存在,是否覆盖?`, + positiveText: '覆盖', + negativeText: '取消', + onPositiveClick: () => { + file + .move(source, target, true) + .then(() => { + window.$message.success(`移动 ${source} 到 ${target} 成功`) + }) + .catch((err) => { + window.$message.error(err.message) + }) + }, + onNegativeClick: () => { + handleCancel() + } + }) + } + }) + .finally(() => { + window.$bus.emit('file:refresh') + }) } } marked.value = [] } -const handleCancel = () => { - marked.value = [] -} - const bulkDelete = () => { if (!selected.value.length) { window.$message.error('请选择要删除的文件/文件夹')