diff --git a/web-vue/packages/core/layouts/views/desktop/workbench/components/myfiles/index.vue b/web-vue/packages/core/layouts/views/desktop/workbench/components/myfiles/index.vue index e7af802e..5335abe7 100644 --- a/web-vue/packages/core/layouts/views/desktop/workbench/components/myfiles/index.vue +++ b/web-vue/packages/core/layouts/views/desktop/workbench/components/myfiles/index.vue @@ -23,7 +23,18 @@ :class="{ active: folder.expanded }" :style="{ paddingLeft: '0px' }" > +
+ + + + + + + + - {{ folder.id === '0' ? folder.name : (folder.folderName || folder.name) }} + + {{ folder.id === '0' ? folder.name : (folder.folderName || folder.name) }} + ({{ folder.totalCount || 0 }}) + +
+ + +
+ + + + + + + + +
- - -
@@ -66,6 +96,15 @@ :style="{ paddingLeft: '16px' }" >
+ + + + + + + + - {{ second.folderName || second.name }} + + {{ second.folderName || second.name }} + ({{ second.totalCount || 0 }}) + +
+ + +
+ + + + + + + + +
- - -
@@ -108,6 +166,15 @@ :style="{ paddingLeft: '32px' }" >
+ + + + + + + + - {{ third.folderName || third.name }} + + {{ third.folderName || third.name }} + ({{ third.totalCount || 0 }}) + +
+ + +
+ + + + + + + + +
- - -
@@ -150,6 +236,15 @@ :style="{ paddingLeft: '48px' }" >
+ + + + + + + + - {{ fourth.folderName || fourth.name }} + + {{ fourth.folderName || fourth.name }} + ({{ fourth.totalCount || 0 }}) + +
+ + +
+ + + + + + + + +
- - -
@@ -191,12 +305,23 @@ :style="{ paddingLeft: '64px' }" >
+ + + + + + + + - {{ fifth.folderName || fifth.name }} + + {{ fifth.folderName || fifth.name }} + ({{ fifth.totalCount || 0 }}) + +
+ + +
+ + + +
@@ -280,7 +420,7 @@ import { downloadByUrl } from '@jeesite/core/utils/file/download'; import UploadModal from './upload.vue'; import FolderModal from './folder.vue'; -import { bizFoldersSave, bizFoldersAllData, bizFolderItemAll } from '@jeesite/biz/api/biz/folders'; +import { bizFoldersSave, bizFoldersAllData, bizFolderItemAll, bizFoldersDelete } from '@jeesite/biz/api/biz/folders'; import { BizMyfiles, bizMyfilesListAll, bizMyfilesDelete } from '@jeesite/biz/api/biz/myfiles'; import { useUserStore } from '@jeesite/core/store/modules/user'; @@ -305,6 +445,11 @@ interface FolderNode { parentName?: string; userName?: string; loginCode?: string; + fileCount?: number; // 当前文件夹直接文件数量 + folderCount?: number; // 当前文件夹直接子文件夹数量 + totalFileCount?: number; // 递归统计的所有下级文件总数 + totalFolderCount?: number; // 递归统计的所有下级文件夹总数 + totalCount?: number; // 总数量 = 所有下级文件数 + 所有下级文件夹数 } interface EditingInfo { @@ -318,6 +463,7 @@ interface EditingInfo { const expandedState = ref>({}); const currentEditingNode = ref(null); +const showDeleteBtn = ref(null); // 新增:控制删除按钮显示 const editingInfo = ref({ id: null, @@ -337,6 +483,103 @@ const folderSearchText = ref(''); const fileSearchText = ref(''); const folderList = ref([]); const fileList = ref([]); +const folderFileCountMap = ref>({}); // 文件夹直接文件数量映射 +const folderChildCountMap = ref>({}); // 文件夹直接子文件夹数量映射 + +// 获取文件夹直接文件数量 +const getFolderFileCount = async (folderId: string) => { + try { + const reqParams = { + folderId, + loginCode: userinfo.value.loginCode, + }; + const result = await bizMyfilesListAll(reqParams); + return result ? result.length : 0; + } catch (error) { + console.error(`获取文件夹 ${folderId} 直接文件数量失败:`, error); + return 0; + } +}; + +// 批量获取所有文件夹的直接文件数量 +const getAllFolderFileCounts = async (folders: FolderNode[]) => { + const countMap: Record = {}; + + // 递归遍历所有文件夹 + const traverseFolders = async (folderNodes: FolderNode[]) => { + for (const folder of folderNodes) { + if (folder.id !== '0') { // 根目录跳过 + countMap[folder.id] = await getFolderFileCount(folder.id); + } + if (folder.children && folder.children.length) { + await traverseFolders(folder.children); + } + } + }; + + await traverseFolders(folders); + return countMap; +}; + +// 获取文件夹直接子文件夹数量 +const getFolderChildCount = async (folderId: string) => { + try { + const reqParams = { + parentId: folderId, + loginCode: userinfo.value.loginCode, + }; + const result = await bizFoldersAllData(reqParams); + return Array.isArray(result) ? result.length : 0; + } catch (error) { + console.error(`获取文件夹 ${folderId} 直接子文件夹数量失败:`, error); + return 0; + } +}; + +// 批量获取所有文件夹的直接子文件夹数量 +const getAllFolderChildCounts = async (folders: FolderNode[]) => { + const countMap: Record = {}; + + // 递归遍历所有文件夹 + const traverseFolders = async (folderNodes: FolderNode[]) => { + for (const folder of folderNodes) { + if (folder.id !== '0') { // 根目录也统计 + countMap[folder.id] = await getFolderChildCount(folder.id); + } + if (folder.children && folder.children.length) { + await traverseFolders(folder.children); + } + } + }; + + await traverseFolders(folders); + return countMap; +}; + +// 递归计算文件夹的所有下级文件和文件夹总数 - 修复核心逻辑 +const calculateRecursiveCounts = (folder: FolderNode): { totalFiles: number, totalFolders: number } => { + // 初始值:仅当前文件夹的直接文件数(文件夹数初始为0,因为子文件夹数要从子节点累加) + let totalFiles = folder.fileCount || 0; + let totalFolders = 0; // 修复:初始化为0,不再包含当前文件夹的直接子文件夹数 + + // 递归遍历所有子文件夹 + if (folder.children && folder.children.length) { + folder.children.forEach(child => { + const childCounts = calculateRecursiveCounts(child); + // 累加子文件夹的所有文件数 + totalFiles += childCounts.totalFiles; + // 累加子文件夹的数量(子文件夹自身 + 其子文件夹数) + totalFolders += 1 + childCounts.totalFolders; // 修复:+1 表示当前子文件夹本身,再累加其子文件夹数 + }); + } + + // 保存到当前文件夹节点 + folder.totalFileCount = totalFiles; + folder.totalFolderCount = totalFolders; + folder.totalCount = totalFiles + totalFolders; + + return { totalFiles, totalFolders }; +}; const bindParentReferences = (folders: FolderNode[], _parent: FolderNode | null = null) => { folders.forEach(folder => { @@ -344,9 +587,16 @@ const bindParentReferences = (folders: FolderNode[], _parent: FolderNode | null if (expandedState.value[folder.id]) { folder.expanded = true; } + // 设置直接文件数量和直接子文件夹数量(仅用于单独展示,不参与递归统计) + folder.fileCount = folderFileCountMap.value[folder.id] || 0; + folder.folderCount = folderChildCountMap.value[folder.id] || 0; + if (folder.children && folder.children.length) { bindParentReferences(folder.children, folder); } + + // 递归计算所有下级的总数 + calculateRecursiveCounts(folder); }); }; @@ -512,9 +762,16 @@ const getDataList = async (params: {}, keepExpanded = true) => { }); } + // 获取所有文件夹的直接文件数量 + folderFileCountMap.value = await getAllFolderFileCounts(folders); + // 获取所有文件夹的直接子文件夹数量 + folderChildCountMap.value = await getAllFolderChildCounts(folders); + + // 绑定父引用并递归计算总数 bindParentReferences(folders); folderList.value = folders; getFileList({}); + if (currentEditingNode.value) { nextTick(() => { const nodeElement = document.querySelector(`[data-node-id="${currentEditingNode.value?.id}"]`); @@ -559,9 +816,14 @@ const toggleFolder = async (targetFolder: FolderNode) => { if (targetFolder.id !== '0'){ UploadFile = false; } + + // 点击文件夹时关闭删除按钮 + showDeleteBtn.value = null; + if (editingInfo.value.id === targetFolder.id && editingInfo.value.type === 'parent' && targetFolder.id !== '0') return; expandedState.value[targetFolder.id] = !targetFolder.expanded; targetFolder.expanded = !targetFolder.expanded; + if (targetFolder.expanded) { try { if ('loading' in targetFolder) targetFolder.loading = true; @@ -601,6 +863,9 @@ const toggleFolder = async (targetFolder: FolderNode) => { }; const handleChildClick = (child: FolderNode) => { + // 点击子文件夹时关闭删除按钮 + showDeleteBtn.value = null; + if (editingInfo.value.id === child.id && editingInfo.value.type === 'child') return; ParamsFolders.value = { folderId: child.id, @@ -611,6 +876,41 @@ const handleChildClick = (child: FolderNode) => { getFileList(ParamsFolders.value); }; +// 切换文件夹菜单显示/隐藏 +const toggleFolderMenu = (folderId: string) => { + showDeleteBtn.value = showDeleteBtn.value === folderId ? null : folderId; +}; + +const handleDeleteFolder = async (folder: FolderNode) => { + if(folder.totalCount != 0 ){ + message.error("该文件夹非空,无法删除!"); + showDeleteBtn.value = null; + return; + } + + Modal.confirm({ + title: '删除确认', + content: '您确定要删除当前文件夹吗?', + okText: '确认删除', + cancelText: '取消', + okType: 'danger', + onOk: async () => { + try { + const res = await bizFoldersDelete({ folderId: folder.id }); + message.success(res.message); + showDeleteBtn.value = null; + await getDataList({}, true); + if (ParamsFolders.value.folderId === folder.id) { + ParamsFolders.value = {}; + fileList.value = []; + } + } catch (error) { + console.error('删除文件夹失败:', error); + } + } + }); +}; + const handleFolderDblClick = ( item: FolderNode, type: 'parent' | 'child', @@ -618,6 +918,9 @@ const handleFolderDblClick = ( ) => { if (item.id === '0') return; + // 双击编辑时关闭删除按钮 + showDeleteBtn.value = null; + currentEditingNode.value = item; const itemName = item.id === '0' ? item.name : item.folderName || item.name; @@ -845,7 +1148,7 @@ onMounted(() => { .folder-node { display: flex; align-items: center; - justify-content: space-between; + justify-content: space-between; /* 左右分布:名称区域在左,操作区域在右 */ padding: 8px 6px; cursor: pointer; border-radius: 6px; @@ -853,6 +1156,7 @@ onMounted(() => { flex-wrap: wrap; border-bottom: 1px solid rgba(207, 226, 239, 0.3); transition: all 0.2s ease; + position: relative; } .folder-node.active, @@ -879,27 +1183,32 @@ onMounted(() => { align-items: center; flex: 1; overflow: hidden; + gap: 6px; /* 元素之间的间距 */ } -.folder-node:last-child { - border-bottom: none; -} - -.tree-toggle { - display: inline-block; - width: 18px; - height: 18px; - text-align: center; +/* 省略号按钮样式 */ +.folder-more-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + border-radius: 50%; + cursor: pointer; + color: #8c8c8c; + transition: all 0.2s ease; font-size: 12px; - color: #1890ff; - font-weight: bold; - user-select: none; flex-shrink: 0; - transition: transform 0.2s ease; } +.folder-more-btn:hover { + background-color: rgba(0, 0, 0, 0.05); + color: #1890ff; +} + +/* 文件夹图标样式 */ .folder-icon { - margin: 0 6px; + margin: 0; font-size: 14px; flex-shrink: 0; color: #1890ff; @@ -911,6 +1220,7 @@ onMounted(() => { color: #096dd9; } +/* 文件夹名称样式 */ .folder-name { font-size: 13px; color: #333; @@ -920,6 +1230,15 @@ onMounted(() => { flex: 1; } +/* 文件数量样式 */ +.file-count { + font-size: 11px; + color: #8c8c8c; + margin-left: 4px; + font-weight: normal; +} + +/* 编辑输入框样式 */ .folder-edit-input { width: 100%; font-size: 13px; @@ -927,6 +1246,54 @@ onMounted(() => { border: 1px solid #1890ff; border-radius: 4px; background: rgba(255, 255, 255, 0.95); + flex: 1; +} + +/* 右侧操作区域样式 */ +.folder-actions { + display: flex; + align-items: center; + gap: 2px; + flex-shrink: 0; + margin-left: 8px; +} + +/* 删除按钮样式 */ +.folder-delete-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + border-radius: 50%; + cursor: pointer; + transition: all 0.2s ease; + font-size: 12px; + flex-shrink: 0; +} + +.folder-delete-btn:hover { + background-color: rgba(255, 77, 79, 0.1); +} + +/* 折叠/展开箭头样式 */ +.tree-toggle { + display: inline-flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + text-align: center; + font-size: 10px; + color: #1890ff; + font-weight: bold; + user-select: none; + flex-shrink: 0; + transition: transform 0.2s ease; +} + +.folder-node:last-child { + border-bottom: none; } .tree-children { @@ -1142,19 +1509,19 @@ onMounted(() => { border-radius: 4px; } +/* 响应式样式 */ @media (max-width: 1400px) { .file-list { grid-template-columns: repeat(3, 1fr); } } -@media (max-width: 1200px) { +@media (max-width: 1024px) { .file-list { grid-template-columns: repeat(2, 1fr); } - - .file-card { - height: 90px; + .sidebar { + width: 220px; } } @@ -1162,16 +1529,34 @@ onMounted(() => { .file-list { grid-template-columns: 1fr; } - - .file-card { - height: 100px; - } .sidebar { - width: 220px; + width: 200px; + } + .header-bar { + flex-direction: column; + align-items: stretch; + gap: 8px; + } + .header-btn-group { + justify-content: flex-end; } } -* { - box-sizing: border-box; +@media (max-width: 480px) { + .file-manager-container { + flex-direction: column; + height: auto; + min-height: calc(100vh - 425px); + } + .sidebar { + width: 100%; + height: 200px; + border-radius: 8px 8px 0 0; + border-right: none; + border-bottom: 1px solid rgba(207, 226, 239, 0.6); + } + .main-content { + border-radius: 0 0 8px 8px; + } } \ No newline at end of file