云文件系统初始化

This commit is contained in:
2026-04-01 23:50:49 +08:00
parent 32fc36cae1
commit 3107b11bc4
5 changed files with 163 additions and 65 deletions

View File

@@ -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' })

View 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>

View File

@@ -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)))
])
}
).then(() => window.selectedTargetFolder)
// 执行移动
for (const file of selectedFiles.value) {
try {
await moveFile(file.id, targetFolderId)
} catch (e) {
ElMessage.error(`移动 ${file.name} 失败`)
}
}
ElMessage.success('批量移动完成')
selectedFiles.value = []
loadFiles()
const res = await getMovableFolders(selectedIds, currentFolderId.value)
movableFolders.value = res.data || []
batchMoveVisible.value = true
} catch (e) {
if (e !== 'cancel') {
ElMessage.error('移动失败')
ElMessage.error('获取文件夹列表失败')
}
}
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, finalFolderId)
} catch (e) {
ElMessage.error("移动文件或目录失败")
}
}
ElMessage.success('批量移动完成')
selectedFiles.value = []
loadFiles()
}
const handleRowDblClick = (row) => {