商务模块的文件夹结构优化成树形层级结构
This commit is contained in:
parent
0401a28037
commit
40ae745dfb
|
@ -37,7 +37,7 @@
|
||||||
|
|
||||||
<div class="folder-content">
|
<div class="folder-content">
|
||||||
<!-- 加载状态 -->
|
<!-- 加载状态 -->
|
||||||
<a-skeleton :loading="loading && folderList.length === 0" :rows="5" v-if="loading" animation="pulse">
|
<a-skeleton :loading="loading && folderList.length === 0" :rows="5" v-if="loading" :animation="true">
|
||||||
<template #skeleton>
|
<template #skeleton>
|
||||||
<div class="skeleton-item flex items-center px-4 py-3" v-for="i in 5" :key="i">
|
<div class="skeleton-item flex items-center px-4 py-3" v-for="i in 5" :key="i">
|
||||||
<div class="w-6 h-6 rounded bg-gray-200 mr-3"></div>
|
<div class="w-6 h-6 rounded bg-gray-200 mr-3"></div>
|
||||||
|
@ -53,51 +53,53 @@
|
||||||
</a-typography-text>
|
</a-typography-text>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a-empty v-if="!loading && folderList.length === 0" :description="searchKeyword ? '未找到匹配的文件夹' : '暂无文件夹'" />
|
<a-empty v-if="!loading && folderList.length === 0" :description="searchKeyword ? '未找到匹配的文件夹' : '暂无文件夹'" />
|
||||||
|
|
||||||
<a-list v-if="!loading && folderList.length > 0" class="folder-list">
|
<!-- 树形文件夹结构 -->
|
||||||
<!-- 文件夹列表 -->
|
<div v-if="!loading && folderList.length > 0" class="folder-tree-container">
|
||||||
<a-list-item
|
|
||||||
v-for="folder in folderList"
|
|
||||||
:key="folder.id"
|
|
||||||
:class="['folder-list-item', { 'active': currentFolderId === folder.id }]"
|
|
||||||
@click="handleFolderClick(folder.id)"
|
|
||||||
:tooltip="sidebarCollapsed ? folder.name : ''"
|
|
||||||
>
|
|
||||||
<!-- 第一行:文件夹图标和名称 -->
|
|
||||||
<div class="folder-main-info">
|
|
||||||
<div class="folder-icon-wrapper">
|
|
||||||
<IconFolder class="folder-icon" :style="{ color: folderColor }" />
|
|
||||||
</div>
|
|
||||||
<span v-if="!sidebarCollapsed" class="folder-name">{{ folder.name }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 第二行:文件夹操作按钮 -->
|
<a-tree
|
||||||
<div v-if="!sidebarCollapsed" class="folder-actions-row">
|
:data="folderTreeData"
|
||||||
|
:selected-keys="currentFolderId ? [currentFolderId] : []"
|
||||||
|
:field-names="{ key: 'key', title: 'title', children: 'children' }"
|
||||||
|
:show-line="!sidebarCollapsed"
|
||||||
|
:block-node="true"
|
||||||
|
:default-expand-all="true"
|
||||||
|
@select="handleFolderSelect"
|
||||||
|
@dblclick="handleFolderDoubleClick"
|
||||||
|
class="folder-tree"
|
||||||
|
:class="{ 'collapsed': sidebarCollapsed }"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 文件夹操作按钮 -->
|
||||||
|
<div v-if="currentFolderId && currentFolderId !== '0'" class="folder-actions-bar" style="padding: 8px; border-top: 1px solid #e5e6eb; margin-top: 8px;">
|
||||||
|
<a-space>
|
||||||
<a-button
|
<a-button
|
||||||
type="text"
|
type="text"
|
||||||
shape="circle"
|
|
||||||
size="small"
|
size="small"
|
||||||
@click.stop="handleRenameFolder(folder, folder.id, folder.name)"
|
@click="handleRenameCurrentFolder"
|
||||||
tooltip="重命名"
|
tooltip="重命名"
|
||||||
class="action-btn"
|
|
||||||
>
|
>
|
||||||
<icon-edit />
|
<template #icon><icon-edit /></template>
|
||||||
|
重命名
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button
|
<a-button
|
||||||
type="text"
|
type="text"
|
||||||
shape="circle"
|
|
||||||
size="small"
|
size="small"
|
||||||
@click.stop="handleDeleteFolder(folder)"
|
@click="handleDeleteCurrentFolder"
|
||||||
tooltip="删除"
|
tooltip="删除"
|
||||||
status="danger"
|
status="danger"
|
||||||
class="action-btn"
|
|
||||||
>
|
>
|
||||||
<icon-delete />
|
<template #icon><icon-delete /></template>
|
||||||
|
删除
|
||||||
</a-button>
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</a-list-item>
|
|
||||||
</a-list>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 侧边栏底部分页控件 -->
|
<!-- 侧边栏底部分页控件 -->
|
||||||
|
@ -108,7 +110,8 @@
|
||||||
</a-typography-text>
|
</a-typography-text>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pagination-controls">
|
<!-- 隐藏分页控件,因为现在获取所有文件夹 -->
|
||||||
|
<!-- <div class="pagination-controls">
|
||||||
<a-pagination
|
<a-pagination
|
||||||
:current="currentPage"
|
:current="currentPage"
|
||||||
:page-size="pageSize"
|
:page-size="pageSize"
|
||||||
|
@ -120,7 +123,7 @@
|
||||||
size="small"
|
size="small"
|
||||||
show-total
|
show-total
|
||||||
/>
|
/>
|
||||||
</div>
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
</a-layout-sider>
|
</a-layout-sider>
|
||||||
|
|
||||||
|
@ -128,8 +131,14 @@
|
||||||
<a-layout-header class="file-header">
|
<a-layout-header class="file-header">
|
||||||
<div class="breadcrumbs">
|
<div class="breadcrumbs">
|
||||||
<a-breadcrumb>
|
<a-breadcrumb>
|
||||||
<a-breadcrumb-item>知识库</a-breadcrumb-item>
|
<a-breadcrumb-item
|
||||||
<a-breadcrumb-item>{{ currentFolderName }}</a-breadcrumb-item>
|
v-for="(item, index) in breadcrumbPath"
|
||||||
|
:key="index"
|
||||||
|
:class="{ 'clickable': index < breadcrumbPath.length - 1 }"
|
||||||
|
@click="handleBreadcrumbClick(index)"
|
||||||
|
>
|
||||||
|
{{ item }}
|
||||||
|
</a-breadcrumb-item>
|
||||||
</a-breadcrumb>
|
</a-breadcrumb>
|
||||||
<a-button
|
<a-button
|
||||||
type="text"
|
type="text"
|
||||||
|
@ -369,20 +378,18 @@
|
||||||
<a-input v-model="folderForm.name" placeholder="输入文件夹名称" max-length="50" />
|
<a-input v-model="folderForm.name" placeholder="输入文件夹名称" max-length="50" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="父级目录" field="parentId" :rules="folderRules.parentId">
|
<a-form-item label="父级目录" field="parentId" :rules="folderRules.parentId">
|
||||||
<a-select
|
<a-tree-select
|
||||||
v-model="folderForm.parentId"
|
v-model="folderForm.parentId"
|
||||||
placeholder="请选择父级目录"
|
placeholder="请选择父级目录"
|
||||||
|
:data="folderTreeSelectData"
|
||||||
|
:field-names="{ key: 'id', title: 'name', children: 'children' }"
|
||||||
|
allow-clear
|
||||||
|
:tree-props="{ showLine: true }"
|
||||||
>
|
>
|
||||||
<a-option value="0">根目录</a-option>
|
<template #title="{ node }">
|
||||||
<a-option
|
<span>{{ node?.title || node?.name }}</span>
|
||||||
v-for="folder in folderList"
|
</template>
|
||||||
:key="folder.id"
|
</a-tree-select>
|
||||||
:value="folder.id"
|
|
||||||
v-if="!folderForm.id || folder.id !== folderForm.id"
|
|
||||||
>
|
|
||||||
{{ folder.name }}
|
|
||||||
</a-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
@ -592,7 +599,8 @@ import {
|
||||||
const folderList = ref([]);
|
const folderList = ref([]);
|
||||||
const fileList = ref([]);
|
const fileList = ref([]);
|
||||||
const currentFolderId = ref('');
|
const currentFolderId = ref('');
|
||||||
const currentFolderName = ref('');
|
// 移除currentFolderName,现在使用面包屑导航
|
||||||
|
// const currentFolderName = ref('');
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const folderDialogVisible = ref(false);
|
const folderDialogVisible = ref(false);
|
||||||
const uploadDialogVisible = ref(false);
|
const uploadDialogVisible = ref(false);
|
||||||
|
@ -664,6 +672,111 @@ const canUpload = computed(() => {
|
||||||
return hasFiles.value && !uploading.value && uploadForm.folderId;
|
return hasFiles.value && !uploading.value && uploadForm.folderId;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 计算属性:将平铺的文件夹数据转换为树形结构
|
||||||
|
const folderTreeData = computed(() => {
|
||||||
|
console.log('=== folderTreeData计算属性执行 ===');
|
||||||
|
console.log('folderList.value:', folderList.value);
|
||||||
|
console.log('folderList.value.length:', folderList.value?.length);
|
||||||
|
|
||||||
|
if (!folderList.value || folderList.value.length === 0) {
|
||||||
|
console.log('folderList为空,返回空数组');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建文件夹映射表
|
||||||
|
const folderMap = new Map();
|
||||||
|
const rootFolders = [];
|
||||||
|
|
||||||
|
console.log('=== 开始创建文件夹映射 ===');
|
||||||
|
// 首先创建所有文件夹的映射
|
||||||
|
folderList.value.forEach((folder, index) => {
|
||||||
|
console.log(`处理第${index + 1}个文件夹:`, folder);
|
||||||
|
// 确保文件夹数据完整
|
||||||
|
if (folder && folder.id && folder.name) {
|
||||||
|
const node = {
|
||||||
|
key: folder.id, // Tree组件需要的key字段
|
||||||
|
title: folder.name, // Tree组件需要的title字段
|
||||||
|
children: [], // Tree组件需要的children字段
|
||||||
|
// 保留原始字段用于其他功能
|
||||||
|
id: folder.id,
|
||||||
|
name: folder.name,
|
||||||
|
parentId: folder.parentId
|
||||||
|
};
|
||||||
|
folderMap.set(folder.id, node);
|
||||||
|
console.log(`✅ 成功添加文件夹到映射: ${folder.name} (ID: ${folder.id})`);
|
||||||
|
} else {
|
||||||
|
console.warn('❌ 跳过不完整的文件夹数据:', folder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('=== 开始构建树形结构 ===');
|
||||||
|
console.log('文件夹映射表大小:', folderMap.size);
|
||||||
|
|
||||||
|
// 构建树形结构
|
||||||
|
folderList.value.forEach((folder, index) => {
|
||||||
|
console.log(`构建第${index + 1}个文件夹的树形结构:`, folder);
|
||||||
|
// 确保文件夹数据完整
|
||||||
|
if (!folder || !folder.id || !folder.name) {
|
||||||
|
console.warn('❌ 跳过不完整的文件夹数据:', folder);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const node = folderMap.get(folder.id);
|
||||||
|
if (!node) {
|
||||||
|
console.warn('❌ 找不到文件夹节点:', folder.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`处理文件夹: ${folder.name} (ID: ${folder.id}, ParentID: ${folder.parentId})`);
|
||||||
|
|
||||||
|
if (folder.parentId === '0' || folder.parentId === 0) {
|
||||||
|
// 根文件夹
|
||||||
|
rootFolders.push(node);
|
||||||
|
console.log(`✅ 添加为根文件夹: ${folder.name}`);
|
||||||
|
} else {
|
||||||
|
// 子文件夹
|
||||||
|
const parent = folderMap.get(folder.parentId);
|
||||||
|
if (parent) {
|
||||||
|
parent.children.push(node);
|
||||||
|
console.log(`✅ 添加为子文件夹: ${folder.name} -> ${parent.name}`);
|
||||||
|
} else {
|
||||||
|
// 如果找不到父文件夹,当作根文件夹处理
|
||||||
|
console.warn('⚠️ 找不到父文件夹,将文件夹作为根文件夹:', folder.name, folder.parentId);
|
||||||
|
rootFolders.push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('=== 树形结构构建完成 ===');
|
||||||
|
console.log('根文件夹数量:', rootFolders.length);
|
||||||
|
console.log('构建的树形结构:', rootFolders);
|
||||||
|
|
||||||
|
// 验证树形结构中的节点数据
|
||||||
|
rootFolders.forEach((root, index) => {
|
||||||
|
console.log(`根文件夹${index + 1}:`, {
|
||||||
|
id: root.id,
|
||||||
|
name: root.name,
|
||||||
|
childrenCount: root.children?.length || 0
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return rootFolders;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 计算属性:树形选择器数据(包含根目录选项)
|
||||||
|
const folderTreeSelectData = computed(() => {
|
||||||
|
const rootOption = {
|
||||||
|
key: '0', // Tree组件需要的key字段
|
||||||
|
title: '根目录', // Tree组件需要的title字段
|
||||||
|
children: [], // Tree组件需要的children字段
|
||||||
|
// 保留原始字段用于其他功能
|
||||||
|
id: '0',
|
||||||
|
name: '根目录'
|
||||||
|
};
|
||||||
|
|
||||||
|
return [rootOption, ...folderTreeData.value];
|
||||||
|
});
|
||||||
|
|
||||||
// 搜索相关
|
// 搜索相关
|
||||||
const searchKeyword = ref(''); // 文件夹搜索关键词
|
const searchKeyword = ref(''); // 文件夹搜索关键词
|
||||||
const fileSearchKeyword = ref(''); // 文件搜索关键词
|
const fileSearchKeyword = ref(''); // 文件搜索关键词
|
||||||
|
@ -682,7 +795,7 @@ const initData = async () => {
|
||||||
|
|
||||||
const apiParams = {
|
const apiParams = {
|
||||||
page: currentPage.value,
|
page: currentPage.value,
|
||||||
pageSize: pageSize.value,
|
pageSize: 1000, // 修改为足够大的值,确保获取所有文件夹
|
||||||
folderName: searchKeyword.value.trim() || undefined
|
folderName: searchKeyword.value.trim() || undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -701,11 +814,43 @@ const initData = async () => {
|
||||||
|
|
||||||
// 根据后端返回的数据结构处理
|
// 根据后端返回的数据结构处理
|
||||||
if (folderRes.code === 200 && folderRes.data) {
|
if (folderRes.code === 200 && folderRes.data) {
|
||||||
const processedFolders = folderRes.data.rows.map(folder => ({
|
console.log('=== 开始处理数据 ===');
|
||||||
|
console.log('folderRes.data:', folderRes.data);
|
||||||
|
console.log('folderRes.data.rows:', folderRes.data.rows);
|
||||||
|
console.log('folderRes.data.rows类型:', typeof folderRes.data.rows);
|
||||||
|
console.log('folderRes.data.rows长度:', folderRes.data.rows?.length);
|
||||||
|
|
||||||
|
// 检查数据结构
|
||||||
|
if (!folderRes.data.rows || !Array.isArray(folderRes.data.rows)) {
|
||||||
|
console.error('API返回的数据结构不正确,rows字段不存在或不是数组');
|
||||||
|
console.log('可用的字段:', Object.keys(folderRes.data));
|
||||||
|
folderList.value = [];
|
||||||
|
totalFolders.value = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const processedFolders = folderRes.data.rows.map((folder, index) => {
|
||||||
|
console.log(`处理第${index + 1}个原始文件夹数据:`, folder);
|
||||||
|
console.log(`原始数据字段:`, Object.keys(folder));
|
||||||
|
console.log(`folderId:`, folder.folderId);
|
||||||
|
console.log(`folderName:`, folder.folderName);
|
||||||
|
console.log(`parentId:`, folder.parentId);
|
||||||
|
|
||||||
|
// 确保所有必需字段都存在
|
||||||
|
if (!folder.folderId || !folder.folderName) {
|
||||||
|
console.warn('❌ 跳过不完整的文件夹数据:', folder);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const processedFolder = {
|
||||||
id: String(folder.folderId),
|
id: String(folder.folderId),
|
||||||
name: folder.folderName,
|
name: String(folder.folderName),
|
||||||
parentId: String(folder.parentId || 0)
|
parentId: String(folder.parentId || 0)
|
||||||
}));
|
};
|
||||||
|
|
||||||
|
console.log(`✅ 处理后的文件夹数据:`, processedFolder);
|
||||||
|
return processedFolder;
|
||||||
|
}).filter(Boolean); // 过滤掉null值
|
||||||
|
|
||||||
folderList.value = processedFolders;
|
folderList.value = processedFolders;
|
||||||
totalFolders.value = Number(folderRes.data.total) || 0;
|
totalFolders.value = Number(folderRes.data.total) || 0;
|
||||||
|
@ -720,6 +865,8 @@ const initData = async () => {
|
||||||
totalFolders.value = 0;
|
totalFolders.value = 0;
|
||||||
console.log('API响应异常,清空列表');
|
console.log('API响应异常,清空列表');
|
||||||
console.log('响应码不是200或数据为空');
|
console.log('响应码不是200或数据为空');
|
||||||
|
console.log('folderRes.code:', folderRes.code);
|
||||||
|
console.log('folderRes.data:', folderRes.data);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('初始化文件夹数据失败:', error);
|
console.error('初始化文件夹数据失败:', error);
|
||||||
|
@ -784,6 +931,7 @@ const handleSearchClear = () => {
|
||||||
}
|
}
|
||||||
currentPage.value = 1;
|
currentPage.value = 1;
|
||||||
console.log('重置页码为:', currentPage.value);
|
console.log('重置页码为:', currentPage.value);
|
||||||
|
// 清除搜索后立即刷新数据,显示所有文件夹
|
||||||
initData();
|
initData();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -854,13 +1002,6 @@ const loadFiles = async (folderId) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
currentFolderId.value = folderId;
|
currentFolderId.value = folderId;
|
||||||
|
|
||||||
if (folderId === '0') {
|
|
||||||
currentFolderName.value = '根目录';
|
|
||||||
} else {
|
|
||||||
const folder = folderList.value.find(f => f.id === folderId);
|
|
||||||
currentFolderName.value = folder ? folder.name : '未知文件夹';
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载文件失败:', error);
|
console.error('加载文件失败:', error);
|
||||||
Message.error('服务开小差,请稍后再试');
|
Message.error('服务开小差,请稍后再试');
|
||||||
|
@ -873,14 +1014,141 @@ const loadFiles = async (folderId) => {
|
||||||
|
|
||||||
|
|
||||||
// 文件夹点击事件
|
// 文件夹点击事件
|
||||||
const handleFolderClick = (folderId) => {
|
// const handleFolderClick = (folderId) => {
|
||||||
const id = String(folderId);
|
// const id = String(folderId);
|
||||||
if (currentFolderId.value !== id) {
|
// if (currentFolderId.value !== id) {
|
||||||
|
// fileCurrentPage.value = 1;
|
||||||
|
// // 切换文件夹时清空文件搜索关键词
|
||||||
|
// fileSearchKeyword.value = '';
|
||||||
|
// }
|
||||||
|
// currentFolderId.value = id;
|
||||||
|
// };
|
||||||
|
|
||||||
|
// 树形文件夹选择事件
|
||||||
|
const handleFolderSelect = (selectedKeys, info) => {
|
||||||
|
if (selectedKeys.length > 0) {
|
||||||
|
const folderId = selectedKeys[0];
|
||||||
|
if (currentFolderId.value !== folderId) {
|
||||||
fileCurrentPage.value = 1;
|
fileCurrentPage.value = 1;
|
||||||
// 切换文件夹时清空文件搜索关键词
|
// 切换文件夹时清空文件搜索关键词
|
||||||
fileSearchKeyword.value = '';
|
fileSearchKeyword.value = '';
|
||||||
}
|
}
|
||||||
currentFolderId.value = id;
|
currentFolderId.value = folderId;
|
||||||
|
loadFiles(folderId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 文件夹双击处理
|
||||||
|
const handleFolderDoubleClick = (info) => {
|
||||||
|
console.log('文件夹双击:', info);
|
||||||
|
|
||||||
|
const { node } = info;
|
||||||
|
if (!node) return;
|
||||||
|
|
||||||
|
// 显示操作选项
|
||||||
|
Modal.confirm({
|
||||||
|
title: '文件夹操作',
|
||||||
|
content: `请选择对文件夹"${node.title}"的操作`,
|
||||||
|
okText: '重命名',
|
||||||
|
cancelText: '删除',
|
||||||
|
onOk: () => handleRenameFolder(node),
|
||||||
|
onCancel: () => handleDeleteFolder(node)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 重命名当前选中的文件夹
|
||||||
|
const handleRenameCurrentFolder = () => {
|
||||||
|
if (!currentFolderId.value || currentFolderId.value === '0') {
|
||||||
|
Message.warning('请先选择一个文件夹');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从folderList中找到当前选中的文件夹
|
||||||
|
const currentFolder = folderList.value.find(folder => folder.id === currentFolderId.value);
|
||||||
|
if (!currentFolder) {
|
||||||
|
Message.error('找不到当前文件夹信息');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构造node对象
|
||||||
|
const node = {
|
||||||
|
key: currentFolder.id,
|
||||||
|
title: currentFolder.name,
|
||||||
|
id: currentFolder.id,
|
||||||
|
name: currentFolder.name
|
||||||
|
};
|
||||||
|
|
||||||
|
handleRenameFolder(node);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除当前选中的文件夹
|
||||||
|
const handleDeleteCurrentFolder = () => {
|
||||||
|
if (!currentFolderId.value || currentFolderId.value === '0') {
|
||||||
|
Message.warning('请先选择一个文件夹');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从folderList中找到当前选中的文件夹
|
||||||
|
const currentFolder = folderList.value.find(folder => folder.id === currentFolderId.value);
|
||||||
|
if (!currentFolder) {
|
||||||
|
Message.error('找不到当前文件夹信息');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构造node对象
|
||||||
|
const node = {
|
||||||
|
key: currentFolder.id,
|
||||||
|
title: currentFolder.name,
|
||||||
|
id: currentFolder.id,
|
||||||
|
name: currentFolder.name
|
||||||
|
};
|
||||||
|
|
||||||
|
handleDeleteFolder(node);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 计算属性:面包屑导航路径
|
||||||
|
const breadcrumbPath = computed(() => {
|
||||||
|
if (!currentFolderId.value || currentFolderId.value === '0') {
|
||||||
|
return ['知识库', '根目录'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = ['知识库'];
|
||||||
|
let currentId = currentFolderId.value;
|
||||||
|
|
||||||
|
// 从当前文件夹向上查找父级路径
|
||||||
|
while (currentId && currentId !== '0') {
|
||||||
|
const folder = folderList.value.find(f => f.id === currentId);
|
||||||
|
if (folder) {
|
||||||
|
path.unshift(folder.name);
|
||||||
|
currentId = folder.parentId;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 面包屑点击事件处理
|
||||||
|
const handleBreadcrumbClick = (index) => {
|
||||||
|
if (index === 0) {
|
||||||
|
// 点击"知识库",回到根目录
|
||||||
|
currentFolderId.value = '0';
|
||||||
|
loadFiles('0');
|
||||||
|
} else {
|
||||||
|
// 点击其他路径项,需要找到对应的文件夹ID
|
||||||
|
const targetPath = breadcrumbPath.value.slice(0, index + 1);
|
||||||
|
const targetFolderName = targetPath[targetPath.length - 1];
|
||||||
|
|
||||||
|
// 查找对应的文件夹
|
||||||
|
const targetFolder = folderList.value.find(folder => folder.name === targetFolderName);
|
||||||
|
if (targetFolder) {
|
||||||
|
currentFolderId.value = targetFolder.id;
|
||||||
|
loadFiles(targetFolder.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 重命名对话框状态
|
// 重命名对话框状态
|
||||||
|
@ -901,20 +1169,40 @@ const renameFileForm = reactive({
|
||||||
});
|
});
|
||||||
|
|
||||||
// 文件夹重命名处理函数
|
// 文件夹重命名处理函数
|
||||||
const handleRenameFolder = (folder, folderId, currentName) => {
|
const handleRenameFolder = (folder) => {
|
||||||
console.log('handleRenameFolder 被调用:', { folder, folderId, currentName });
|
console.log('重命名文件夹:', folder);
|
||||||
|
|
||||||
|
if (!folder) {
|
||||||
|
Message.error('文件夹信息不能为空');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const folderId = folder.key || folder.id;
|
||||||
|
const currentName = folder.title || folder.name;
|
||||||
|
|
||||||
// 验证参数
|
|
||||||
if (!folderId) {
|
if (!folderId) {
|
||||||
console.error('folderId 为空');
|
|
||||||
Message.error('文件夹ID不能为空');
|
Message.error('文件夹ID不能为空');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!currentName) {
|
if (!currentName) {
|
||||||
console.error('currentName 为空');
|
Message.error('文件夹名称不能为空');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currentName) {
|
||||||
|
console.error('❌ currentName 为空');
|
||||||
|
console.error('尝试从folder对象获取名称...');
|
||||||
|
const fallbackName = folder?.title || folder?.name;
|
||||||
|
console.error('fallbackName:', fallbackName);
|
||||||
|
|
||||||
|
if (!fallbackName) {
|
||||||
Message.error('当前文件夹名称不能为空');
|
Message.error('当前文件夹名称不能为空');
|
||||||
return;
|
return;
|
||||||
|
} else {
|
||||||
|
console.log('✅ 使用fallbackName:', fallbackName);
|
||||||
|
currentName = fallbackName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 先显示一个简单的提示,确认函数被调用
|
// 先显示一个简单的提示,确认函数被调用
|
||||||
|
@ -961,13 +1249,14 @@ const confirmRename = async () => {
|
||||||
if (result && result.code === 200) {
|
if (result && result.code === 200) {
|
||||||
if (isRoot) {
|
if (isRoot) {
|
||||||
Message.success('根目录重命名成功');
|
Message.success('根目录重命名成功');
|
||||||
currentFolderName.value = newName.trim();
|
// 移除对currentFolderName的设置,现在使用面包屑导航
|
||||||
|
// currentFolderName.value = newName.trim();
|
||||||
} else {
|
} else {
|
||||||
Message.success('文件夹重命名成功');
|
Message.success('文件夹重命名成功');
|
||||||
// 如果重命名的是当前选中的文件夹,更新显示名称
|
// 移除对currentFolderName的设置,现在使用面包屑导航
|
||||||
if (currentFolderId.value === folderId) {
|
// if (currentFolderId.value === folderId) {
|
||||||
currentFolderName.value = newName.trim();
|
// currentFolderName.value = newName.trim();
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
initData(); // 刷新文件夹列表
|
initData(); // 刷新文件夹列表
|
||||||
|
@ -1015,6 +1304,10 @@ const handleFilePageSizeChange = (current, size) => {
|
||||||
const refreshData = async () => {
|
const refreshData = async () => {
|
||||||
refreshing.value = true;
|
refreshing.value = true;
|
||||||
try {
|
try {
|
||||||
|
// 强制清空搜索关键词,确保显示所有文件夹
|
||||||
|
searchKeyword.value = '';
|
||||||
|
currentPage.value = 1;
|
||||||
|
|
||||||
await initData();
|
await initData();
|
||||||
if (currentFolderId.value) {
|
if (currentFolderId.value) {
|
||||||
await loadFiles(currentFolderId.value);
|
await loadFiles(currentFolderId.value);
|
||||||
|
@ -1035,14 +1328,22 @@ const submitFolderForm = async () => {
|
||||||
await updateFolderApi(folderForm.id, folderForm.name);
|
await updateFolderApi(folderForm.id, folderForm.name);
|
||||||
Message.success('文件夹重命名成功');
|
Message.success('文件夹重命名成功');
|
||||||
} else {
|
} else {
|
||||||
await createFolderApi({
|
const result = await createFolderApi({
|
||||||
name: folderForm.name,
|
name: folderForm.name,
|
||||||
parentId: folderForm.parentId
|
parentId: folderForm.parentId
|
||||||
});
|
});
|
||||||
Message.success('文件夹创建成功');
|
Message.success('文件夹创建成功');
|
||||||
|
|
||||||
|
// 新建文件夹后,刷新数据并自动选中新建的文件夹
|
||||||
|
await initData();
|
||||||
|
|
||||||
|
// 如果有返回新建文件夹的ID,自动选中它
|
||||||
|
if (result.data && result.data.folderId) {
|
||||||
|
currentFolderId.value = String(result.data.folderId);
|
||||||
|
loadFiles(currentFolderId.value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
folderDialogVisible.value = false;
|
folderDialogVisible.value = false;
|
||||||
initData();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('文件夹操作失败:', error);
|
console.error('文件夹操作失败:', error);
|
||||||
Message.error(folderForm.id ? '重命名失败' : '创建失败');
|
Message.error(folderForm.id ? '重命名失败' : '创建失败');
|
||||||
|
@ -1669,7 +1970,7 @@ const showAudioPreview = (url, fileName) => {
|
||||||
marginBottom: '20px',
|
marginBottom: '20px',
|
||||||
color: '#165DFF'
|
color: '#165DFF'
|
||||||
}
|
}
|
||||||
}, '🎵'),
|
}, '<EFBFBD><EFBFBD>'),
|
||||||
|
|
||||||
// 音频播放器
|
// 音频播放器
|
||||||
h('audio', {
|
h('audio', {
|
||||||
|
@ -1883,16 +2184,17 @@ const confirmRenameFile = async () => {
|
||||||
const handleDeleteFolder = (folder) => {
|
const handleDeleteFolder = (folder) => {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: '确认删除',
|
title: '确认删除',
|
||||||
content: `确定要删除文件夹「${folder.name}」吗?删除后无法恢复,文件夹内的所有文件也将被删除。`,
|
content: `确定要删除文件夹「${folder.title || folder.name}」吗?删除后无法恢复,文件夹内的所有文件也将被删除。`,
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
try {
|
try {
|
||||||
const result = await deleteFolderApi(folder.id);
|
const result = await deleteFolderApi(folder.key || folder.id);
|
||||||
if (result.code === 200) {
|
if (result.code === 200) {
|
||||||
Message.success('文件夹删除成功');
|
Message.success('文件夹删除成功');
|
||||||
// 如果删除的是当前选中的文件夹,切换到根目录
|
// 如果删除的是当前选中的文件夹,切换到根目录
|
||||||
if (currentFolderId.value === folder.id) {
|
if (currentFolderId.value === (folder.key || folder.id)) {
|
||||||
currentFolderId.value = '0';
|
currentFolderId.value = '0';
|
||||||
currentFolderName.value = '根目录';
|
// 移除对currentFolderName的设置,现在使用面包屑导航
|
||||||
|
// currentFolderName.value = '根目录';
|
||||||
fileList.value = [];
|
fileList.value = [];
|
||||||
totalFiles.value = 0;
|
totalFiles.value = 0;
|
||||||
}
|
}
|
||||||
|
@ -2067,6 +2369,8 @@ onMounted(() => {
|
||||||
background-color: var(--color-bg-2);
|
background-color: var(--color-bg-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* 侧边栏样式 */
|
/* 侧边栏样式 */
|
||||||
.folder-sidebar {
|
.folder-sidebar {
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||||
|
@ -3208,4 +3512,110 @@ onMounted(() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 树形文件夹结构 */
|
||||||
|
.folder-tree-container {
|
||||||
|
padding: 8px;
|
||||||
|
background: var(--color-bg-2);
|
||||||
|
border-radius: 6px;
|
||||||
|
margin: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-node-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-name {
|
||||||
|
flex: 1;
|
||||||
|
margin-left: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--color-text-1);
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-node-content:hover .folder-actions {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-actions .action-btn {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
color: var(--color-text-3);
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--color-primary);
|
||||||
|
background: var(--color-fill-3);
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-tree {
|
||||||
|
:deep(.arco-tree-node-content) {
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-fill-2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.arco-tree-node-selected .arco-tree-node-content) {
|
||||||
|
background-color: var(--color-primary-light-1);
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.arco-tree-node-indent) {
|
||||||
|
padding-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.collapsed {
|
||||||
|
:deep(.arco-tree-node-content) {
|
||||||
|
padding: 8px 4px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.arco-tree-node-title) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.arco-tree-node-switcher) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.arco-tree-node-indent) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 面包屑导航样式 */
|
||||||
|
.breadcrumbs {
|
||||||
|
.clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--color-primary);
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--color-primary-light-1);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
Loading…
Reference in New Issue