云文件系统初始化
This commit is contained in:
@@ -19,6 +19,9 @@ import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -242,8 +245,12 @@ public class FileController {
|
||||
public ResponseEntity<?> moveFile(
|
||||
@AuthenticationPrincipal UserPrincipal principal,
|
||||
@PathVariable Long id,
|
||||
@RequestBody Map<String, Long> request) {
|
||||
Long folderId = request.get("folderId");
|
||||
@RequestBody Map<String, Object> request) {
|
||||
Object folderIdObj = request.get("folderId");
|
||||
Long folderId = null;
|
||||
if (folderIdObj != null && !folderIdObj.toString().isEmpty() && !"null".equals(folderIdObj.toString()) && !"undefined".equals(folderIdObj.toString())) {
|
||||
folderId = Long.parseLong(folderIdObj.toString());
|
||||
}
|
||||
try {
|
||||
fileService.moveFile(id, principal.getUserId(), folderId);
|
||||
return ResponseEntity.ok(Map.of("message", "移动成功"));
|
||||
@@ -255,10 +262,25 @@ public class FileController {
|
||||
@PostMapping("/batchDownload")
|
||||
public ResponseEntity<?> batchDownload(
|
||||
@AuthenticationPrincipal UserPrincipal principal,
|
||||
@RequestBody Map<String, List<Long>> request,
|
||||
@RequestBody Map<String, Object> request,
|
||||
HttpServletResponse response) throws IOException {
|
||||
List<Long> ids = request.get("ids");
|
||||
if (ids == null || ids.isEmpty()) {
|
||||
Object idsObj = request.get("ids");
|
||||
if (idsObj == null) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "请选择要下载的文件"));
|
||||
}
|
||||
|
||||
List<Long> ids = new ArrayList<>();
|
||||
if (idsObj instanceof List) {
|
||||
for (Object id : (List<?>) idsObj) {
|
||||
if (id instanceof Number) {
|
||||
ids.add(((Number) id).longValue());
|
||||
} else if (id instanceof String) {
|
||||
ids.add(Long.parseLong((String) id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ids.isEmpty()) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "请选择要下载的文件"));
|
||||
}
|
||||
|
||||
@@ -280,8 +302,9 @@ public class FileController {
|
||||
@GetMapping("/movableFolders")
|
||||
public ResponseEntity<?> getMovableFolders(
|
||||
@AuthenticationPrincipal UserPrincipal principal,
|
||||
@RequestParam(required = false) List<Long> excludeIds) {
|
||||
List<FileEntity> folders = fileService.getMovableFolders(principal.getUserId(), excludeIds);
|
||||
@RequestParam(required = false) List<Long> excludeIds,
|
||||
@RequestParam(required = false) Long currentFolderId) {
|
||||
List<FileEntity> folders = fileService.getMovableFolders(principal.getUserId(), excludeIds, currentFolderId);
|
||||
return ResponseEntity.ok(Map.of("data", folders));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -497,7 +497,12 @@ public class FileService {
|
||||
}
|
||||
|
||||
file.setFolderId(targetFolderId);
|
||||
fileMapper.updateById(file);
|
||||
// 使用直接更新确保 null 值也能被设置
|
||||
fileMapper.update(null,
|
||||
new LambdaUpdateWrapper<FileEntity>()
|
||||
.eq(FileEntity::getId, fileId)
|
||||
.set(FileEntity::getFolderId, targetFolderId)
|
||||
);
|
||||
}
|
||||
|
||||
public byte[] createZipArchive(List<Long> fileIds, Long userId) throws IOException {
|
||||
@@ -558,12 +563,19 @@ public class FileService {
|
||||
zos.closeEntry();
|
||||
}
|
||||
|
||||
public List<FileEntity> getMovableFolders(Long userId, List<Long> excludeIds) {
|
||||
public List<FileEntity> getMovableFolders(Long userId, List<Long> excludeIds, Long currentFolderId) {
|
||||
LambdaQueryWrapper<FileEntity> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(FileEntity::getUserId, userId)
|
||||
.eq(FileEntity::getIsDeleted, 0)
|
||||
.eq(FileEntity::getIsFolder, 1);
|
||||
|
||||
// 只查询当前目录下的文件夹(同级)
|
||||
if (currentFolderId == null) {
|
||||
wrapper.isNull(FileEntity::getFolderId);
|
||||
} else {
|
||||
wrapper.eq(FileEntity::getFolderId, currentFolderId);
|
||||
}
|
||||
|
||||
if (excludeIds != null && !excludeIds.isEmpty()) {
|
||||
wrapper.notIn(FileEntity::getId, excludeIds);
|
||||
}
|
||||
|
||||
@@ -26,5 +26,5 @@ export const cancelShare = (id) => request.delete(`/files/${id}/cancelShare`)
|
||||
export const renameFile = (id, name) => request.put(`/files/${id}/rename`, { name })
|
||||
export const moveFile = (id, folderId) => request.put(`/files/${id}/move`, { folderId })
|
||||
export const batchDownload = (ids) => request.post('/files/batchDownload', { ids }, { responseType: 'blob' })
|
||||
export const getMovableFolders = (excludeIds) => request.get('/files/movableFolders', { params: { excludeIds } })
|
||||
export const getMovableFolders = (excludeIds, currentFolderId) => request.get('/files/movableFolders', { params: { excludeIds, currentFolderId } })
|
||||
export const getFilePreview = (id) => request.get(`/files/${id}/preview`, { responseType: 'blob' })
|
||||
|
||||
67
web-vue/src/components/BatchMoveDialog.vue
Normal file
67
web-vue/src/components/BatchMoveDialog.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
title="批量移动"
|
||||
width="400px"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form>
|
||||
<el-form-item label="目标文件夹">
|
||||
<el-select v-model="targetFolderId" placeholder="请选择目标文件夹" style="width: 100%" clearable>
|
||||
<el-option :label="parentFolderName" :value="'parent'" v-if="canGoParent" />
|
||||
<el-option label="根目录" :value="'root'" />
|
||||
<el-option
|
||||
v-for="folder in folders"
|
||||
:key="folder.id"
|
||||
:label="folder.name"
|
||||
:value="folder.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">
|
||||
<el-icon><Close /></el-icon>
|
||||
<span style="margin-left: 4px">取消</span>
|
||||
</el-button>
|
||||
<el-button type="primary" @click="handleConfirm">
|
||||
<el-icon><FolderOpened /></el-icon>
|
||||
<span style="margin-left: 4px">移动</span>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { Close, FolderOpened } from '@element-plus/icons-vue'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: Boolean, default: false },
|
||||
folders: { type: Array, default: () => [] },
|
||||
canGoParent: { type: Boolean, default: false },
|
||||
parentFolderName: { type: String, default: '返回上一级' }
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'confirm'])
|
||||
|
||||
const visible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emit('update:modelValue', val)
|
||||
})
|
||||
|
||||
const targetFolderId = ref('')
|
||||
|
||||
const handleConfirm = () => {
|
||||
emit('confirm', targetFolderId.value)
|
||||
visible.value = false
|
||||
targetFolderId.value = ''
|
||||
}
|
||||
|
||||
const open = () => {
|
||||
targetFolderId.value = ''
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
@@ -30,7 +30,7 @@
|
||||
<!-- 批量操作工具栏 -->
|
||||
<div v-if="activeMenu === 'my-files'" class="batch-toolbar">
|
||||
<el-button-group>
|
||||
<el-button @click="handleBatchDownload" :disabled="selectedFiles.length === 0 || !hasSelectedFiles">
|
||||
<el-button @click="handleBatchDownload" :disabled="selectedFiles.length === 0">
|
||||
<el-icon><Download /></el-icon>
|
||||
<span style="margin-left: 4px">批量下载</span>
|
||||
</el-button>
|
||||
@@ -141,6 +141,13 @@
|
||||
:file="currentRenameFile"
|
||||
@rename="handleConfirmRename"
|
||||
/>
|
||||
<BatchMoveDialog
|
||||
v-model="batchMoveVisible"
|
||||
:folders="movableFolders"
|
||||
:can-go-parent="folderStack.length > 0"
|
||||
:parent-folder-name="folderStack.length > 0 ? folderStack[folderStack.length - 1].name : '返回上一级'"
|
||||
@confirm="handleConfirmBatchMove"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -156,6 +163,7 @@ import FolderDialog from '@/components/FolderDialog.vue'
|
||||
import ShareDialog from '@/components/ShareDialog.vue'
|
||||
import PreviewDialog from '@/components/PreviewDialog.vue'
|
||||
import RenameDialog from '@/components/RenameDialog.vue'
|
||||
import BatchMoveDialog from '@/components/BatchMoveDialog.vue'
|
||||
import {
|
||||
getFiles, getTrashFiles, getSharedByMe, getSharedByMeFolderFiles, getSharedToMe, getSharedFolderFiles,
|
||||
uploadFiles, downloadFile, deleteFile, restoreFile,
|
||||
@@ -191,7 +199,8 @@ const previewContent = ref('')
|
||||
const renameVisible = ref(false)
|
||||
const currentRenameFile = ref(null)
|
||||
const selectedFiles = ref([])
|
||||
const hasSelectedFiles = computed(() => selectedFiles.value.length > 0)
|
||||
const batchMoveVisible = ref(false)
|
||||
const movableFolders = ref([])
|
||||
|
||||
// 存储 —— 真实数据
|
||||
const storagePercent = computed(() => {
|
||||
@@ -337,7 +346,7 @@ const handleUpload = async (fileList) => {
|
||||
if (totalSize > remaining) {
|
||||
const needGb = (totalSize / (1024 * 1024 * 1024)).toFixed(2)
|
||||
const remainMb = (remaining / (1024 * 1024)).toFixed(2)
|
||||
ElMessage.warning(`存储空间不足!需要 ${needGb} GB,剩余仅 ${remainMb} MB`)
|
||||
ElMessage.warning(`存储空间不足,需要 ${needGb} GB,剩余仅 ${remainMb} MB`)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -413,7 +422,7 @@ const handleDownload = async (file) => {
|
||||
}
|
||||
|
||||
const handleDelete = async (file) => {
|
||||
await ElMessageBox.confirm("确定删除此文件吗?", '提示', { type: 'warning' })
|
||||
await ElMessageBox.confirm("确定删除此文件或目录吗?", '提示', { type: 'warning' })
|
||||
try {
|
||||
await deleteFile(file.id)
|
||||
ElMessage.success('已移至回收站')
|
||||
@@ -434,7 +443,7 @@ const handleRestore = async (file) => {
|
||||
}
|
||||
|
||||
const handleDeletePermanently = async (file) => {
|
||||
await ElMessageBox.confirm("确定彻底删除此文件吗?", '警告', { type: 'error' })
|
||||
await ElMessageBox.confirm("确定彻底删除此文件或目录吗?", '警告', { type: 'error' })
|
||||
try {
|
||||
await deletePermanently(file.id)
|
||||
ElMessage.success('已彻底删除')
|
||||
@@ -490,14 +499,10 @@ const handleSelectionChange = (selection) => {
|
||||
selectedFiles.value = selection
|
||||
}
|
||||
|
||||
const clearSelection = () => {
|
||||
selectedFiles.value = []
|
||||
}
|
||||
|
||||
const handleBatchDownload = async () => {
|
||||
const filesToDownload = selectedFiles.value.filter(f => f.type !== 'folder')
|
||||
if (filesToDownload.length === 0 && selectedFiles.value.length > 0) {
|
||||
ElMessage.warning('请选择要下载的文件(文件夹不支持批量下载)')
|
||||
ElMessage.warning('请选择要下载的文件')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -517,8 +522,6 @@ const handleBatchDownload = async () => {
|
||||
a.download = `download_${new Date().getTime()}.zip`
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
|
||||
ElMessage.success('下载完成')
|
||||
selectedFiles.value = []
|
||||
} catch (e) {
|
||||
ElMessage.error('下载失败')
|
||||
@@ -531,54 +534,47 @@ const handleBatchMove = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取可移动的文件夹列表
|
||||
try {
|
||||
const selectedIds = selectedFiles.value.map(f => f.id)
|
||||
const res = await getMovableFolders(selectedIds)
|
||||
const folders = res.data || []
|
||||
|
||||
// 构建选项
|
||||
const options = [
|
||||
{ label: '根目录', value: null },
|
||||
...folders.map(f => ({ label: f.name, value: f.id }))
|
||||
]
|
||||
|
||||
// 使用 ElMessageBox.prompt 的自定义方式
|
||||
const { value: targetFolderId } = await ElMessageBox.confirm(
|
||||
'请选择目标文件夹',
|
||||
'批量移动',
|
||||
{
|
||||
confirmButtonText: '移动',
|
||||
cancelButtonText: '取消',
|
||||
type: 'info',
|
||||
distinguishCancelAndClose: true,
|
||||
message: h => h('div', [
|
||||
h('p', '请选择目标文件夹:'),
|
||||
h('select', {
|
||||
style: 'width: 100%; padding: 8px; border: 1px solid #dcdfe6; border-radius: 4px;',
|
||||
onChange: (e) => { window.selectedTargetFolder = e.target.value === 'null' ? null : Number(e.target.value) }
|
||||
}, options.map(opt => h('option', { value: opt.value === null ? 'null' : opt.value }, opt.label)))
|
||||
])
|
||||
const res = await getMovableFolders(selectedIds, currentFolderId.value)
|
||||
movableFolders.value = res.data || []
|
||||
batchMoveVisible.value = true
|
||||
} catch (e) {
|
||||
ElMessage.error('获取文件夹列表失败')
|
||||
}
|
||||
).then(() => window.selectedTargetFolder)
|
||||
}
|
||||
|
||||
const handleConfirmBatchMove = async (targetFolderId) => {
|
||||
// 处理各种选项
|
||||
let finalFolderId = null
|
||||
if (targetFolderId === 'parent') {
|
||||
if (folderStack.value.length > 0) {
|
||||
const parentFolder = folderStack.value[folderStack.value.length - 1]
|
||||
finalFolderId = parentFolder.id
|
||||
} else {
|
||||
finalFolderId = null
|
||||
}
|
||||
} else if (targetFolderId === 'root' || targetFolderId === '' || targetFolderId === null || targetFolderId === undefined) {
|
||||
// 根目录
|
||||
finalFolderId = null
|
||||
} else {
|
||||
// 具体文件夹
|
||||
finalFolderId = targetFolderId
|
||||
}
|
||||
|
||||
console.log('targetFolderId:', targetFolderId, 'finalFolderId:', finalFolderId)
|
||||
|
||||
// 执行移动
|
||||
for (const file of selectedFiles.value) {
|
||||
try {
|
||||
await moveFile(file.id, targetFolderId)
|
||||
await moveFile(file.id, finalFolderId)
|
||||
} catch (e) {
|
||||
ElMessage.error(`移动 ${file.name} 失败`)
|
||||
ElMessage.error("移动文件或目录失败")
|
||||
}
|
||||
}
|
||||
|
||||
ElMessage.success('批量移动完成')
|
||||
selectedFiles.value = []
|
||||
loadFiles()
|
||||
} catch (e) {
|
||||
if (e !== 'cancel') {
|
||||
ElMessage.error('移动失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleRowDblClick = (row) => {
|
||||
|
||||
Reference in New Issue
Block a user