Industrial-image-management.../src/views/bussiness-data/bussiness.vue

2477 lines
64 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<a-layout class="knowledge-container">
<!-- 侧边栏 -->
<a-layout-sider
:width="sidebarWidth"
:collapsed-width="80"
theme="dark"
class="folder-sidebar"
:collapsed="sidebarCollapsed"
@collapse="handleSidebarCollapse"
@expand="handleSidebarExpand"
>
<div class="sidebar-header">
<a-space direction="vertical" size="medium" v-if="!sidebarCollapsed">
<a-button
type="primary"
size="large"
long
@click="handleCreateFolder"
class="create-folder-btn"
>
<template #icon><icon-plus /></template>
新建文件夹
</a-button>
<a-input-search
placeholder="搜索文件夹..."
allow-clear
v-model="searchKeyword"
@search="handleFolderSearch"
@input="handleSearchInput"
@clear="handleSearchClear"
class="search-input"
/>
</a-space>
</div>
<div class="folder-content">
<!-- 加载状态 -->
<a-skeleton :loading="loading && folderList.length === 0" :rows="5" v-if="loading" :animation="true">
<template #skeleton>
<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="flex-1 h-4 bg-gray-200 rounded"></div>
</div>
</template>
</a-skeleton>
<!-- 搜索结果提示 -->
<div v-if="searchKeyword && !loading" class="search-result-tip">
<a-typography-text type="secondary">
搜索 "{{ searchKeyword }}" 的结果:共 {{ totalFolders }} 个文件夹
</a-typography-text>
</div>
<a-empty v-if="!loading && folderList.length === 0" :description="searchKeyword ? '未找到匹配的文件夹' : '暂无文件夹'" />
<!-- 树形文件夹结构 -->
<div v-if="!loading && folderList.length > 0" class="folder-tree-container">
<a-tree
: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
type="text"
size="small"
@click="handleRenameCurrentFolder"
tooltip="重命名"
>
<template #icon><icon-edit /></template>
重命名
</a-button>
<a-button
type="text"
size="small"
@click="handleDeleteCurrentFolder"
tooltip="删除"
status="danger"
>
<template #icon><icon-delete /></template>
删除
</a-button>
</a-space>
</div>
</div>
</div>
<!-- 拖拽分隔线 -->
<div
v-if="!sidebarCollapsed"
class="sidebar-resizer"
@mousedown="startResize"
@touchstart="startResize"
:title="`当前宽度: ${sidebarWidth}px (拖拽调整)`"
>
<div class="resizer-handle"></div>
</div>
</a-layout-sider>
<a-layout>
<a-layout-header>
<FileHeader
:breadcrumb-path="breadcrumbPath"
:refreshing="refreshing"
@breadcrumb-click="handleBreadcrumbClick"
@refresh="refreshData"
@upload="handleUploadFile"
@create-folder="handleCreateFolder"
/>
</a-layout-header>
<a-layout-content class="file-content">
<a-card :bordered="false" class="file-card">
<FileList
:files="fileList"
:loading="loading"
:current-folder-id="currentFolderId"
:sort-field="sortField"
:sort-order="sortOrder"
@file-search="handleFileSearch"
@file-search-input="handleFileSearchInput"
@file-search-clear="handleFileSearchClear"
@sort-change="handleSortChange"
@file-preview="handlePreview"
@file-download="handleDownload"
@file-edit="handleEditFile"
@file-delete="handleDelete"
@upload="handleUploadFile"
/>
<!-- 文件分页 -->
<FilePagination
:total="totalFiles"
:current="fileCurrentPage"
:page-size="filePageSize"
:visible="currentFolderId && !loading && totalFiles > 0"
@page-change="handleFilePageChange"
@page-size-change="handleFilePageSizeChange"
/>
</a-card>
</a-layout-content>
</a-layout>
<!-- 新建/编辑文件夹对话框 -->
<a-modal
v-model:visible="folderDialogVisible"
:title="folderForm.id ? '编辑文件夹' : '新建文件夹'"
width="520px"
@ok="submitFolderForm"
@cancel="folderDialogVisible = false"
:confirm-loading="folderSubmitting"
>
<a-form
:model="folderForm"
ref="folderFormRef"
layout="vertical"
:validate-trigger="['change', 'blur']"
>
<a-form-item label="文件夹名称" field="name" :rules="folderRules.name">
<a-input v-model="folderForm.name" placeholder="输入文件夹名称" max-length="50" />
</a-form-item>
<a-form-item label="父级目录" field="parentId" :rules="folderRules.parentId">
<a-tree-select
v-model="folderForm.parentId"
placeholder="请选择父级目录"
:data="folderTreeSelectData"
:field-names="{ key: 'id', title: 'name', children: 'children' }"
allow-clear
:tree-props="{ showLine: true }"
>
<template #title="{ node }">
<span>{{ node?.title || node?.name }}</span>
</template>
</a-tree-select>
</a-form-item>
</a-form>
</a-modal>
<!-- 文件上传组件 -->
<FileUpload
v-model:visible="uploadDialogVisible"
:folder-list="folderList"
:current-folder-id="currentFolderId"
@upload-success="handleUploadSuccess"
/>
<!-- 重命名文件夹对话框 -->
<a-modal
v-model:visible="renameModalVisible"
:title="renameForm.isRoot ? '重命名根目录' : '重命名文件夹'"
width="520px"
@ok="confirmRename"
@cancel="renameModalVisible = false"
>
<a-form layout="vertical">
<a-form-item label="文件夹名称">
<a-input
v-model="renameForm.newName"
placeholder="请输入新的文件夹名称"
max-length="50"
@keyup.enter="confirmRename"
/>
</a-form-item>
</a-form>
</a-modal>
<!-- 重命名文件对话框 -->
<a-modal
v-model:visible="renameFileModalVisible"
title="重命名文件"
width="520px"
@ok="confirmRenameFile"
@cancel="renameFileModalVisible = false"
>
<a-form layout="vertical">
<a-form-item label="新文件名">
<a-input
v-model="renameFileForm.newName"
placeholder="请输入新的文件名称(不含扩展名)"
max-length="100"
@keyup.enter="confirmRenameFile"
/>
</a-form-item>
</a-form>
</a-modal>
</a-layout>
</template>
<script setup>
// 导入核心依赖
import { ref, reactive, onMounted, computed, watch, nextTick, h } from 'vue';
// 导入子组件
import FilePagination from './components/FilePagination.vue';
import FileHeader from './components/FileHeader.vue';
import FileList from './components/FileList.vue';
import FileUpload from './components/FileUpload.vue';
import {
IconFolder,
IconFile,
IconPlus,
IconMenuFold,
IconMenuUnfold,
IconEye,
IconDownload,
IconDelete,
IconRefresh,
IconEdit,
IconFolderAdd,
} from '@arco-design/web-vue/es/icon';
import { Message, Modal } from '@arco-design/web-vue';
// 导入API
import {
getFolderListApi,
getFilesApi,
createFolderApi,
updateFolderApi,
deleteFolderApi,
deleteFileApi,
downloadFileApi,
updateFileNameApi,
renameFileApi,
previewFileApi
} from '@/apis/bussiness'
// 状态管理
const folderList = ref([]);
const fileList = ref([]);
const currentFolderId = ref('');
// 移除currentFolderName现在使用面包屑导航
// const currentFolderName = ref('');
const loading = ref(false);
const folderDialogVisible = ref(false);
const uploadDialogVisible = ref(false);
// 分页
const currentPage = ref(1);
const pageSize = ref(10);
const totalFolders = ref(0);
const fileCurrentPage = ref(1);
const filePageSize = ref(10);
const totalFiles = ref(0);
// 排序状态
const sortField = ref('');
const sortOrder = ref('');
// 排序字段映射(前端显示名 -> 后端字段名)
const sortFieldMap = {
'fileName': 'file_name',
'fileType': 'file_type',
'fileSize': 'file_size',
'uploadTime': 'upload_time'
};
// 表单数据
const folderForm = reactive({
id: '',
name: '',
parentId: '0'
});
const folderRules = {
name: [
{ required: true, message: '请输入文件夹名称' },
{ maxLength: 50, message: '文件夹名称不能超过50个字符' }
],
parentId: [
{ required: true, message: '请选择父级目录' }
]
};
const folderFormRef = ref(null);
const folderColor = 'var(--color-primary)';
const refreshing = ref(false);
const folderSubmitting = ref(false);
// 计算属性:将平铺的文件夹数据转换为树形结构
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 fileSearchKeyword = ref(''); // 文件搜索关键词
const searchTimeout = ref(null);
// 初始化文件夹数据
const initData = async () => {
try {
loading.value = true;
console.log('=== 开始初始化数据 ===');
console.log('搜索关键词:', searchKeyword.value);
console.log('搜索关键词类型:', typeof searchKeyword.value);
console.log('搜索关键词长度:', searchKeyword.value?.length);
console.log('当前页码:', currentPage.value);
console.log('页面大小:', pageSize.value);
const apiParams = {
page: currentPage.value,
pageSize: 1000, // 修改为足够大的值,确保获取所有文件夹
folderName: searchKeyword.value.trim() || undefined
};
console.log('API参数:', apiParams);
const folderRes = await getFolderListApi(apiParams);
console.log('=== API响应详情 ===');
console.log('完整响应:', folderRes);
console.log('响应状态码:', folderRes.code);
console.log('响应数据:', folderRes.data);
console.log('rows数据:', folderRes.data?.rows);
console.log('rows数据类型:', typeof folderRes.data?.rows);
console.log('rows数据长度:', folderRes.data?.rows?.length);
console.log('total数据:', folderRes.data?.total);
// 根据后端返回的数据结构处理
if (folderRes.code === 200 && folderRes.data) {
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),
name: String(folder.folderName),
parentId: String(folder.parentId || 0)
};
console.log(`✅ 处理后的文件夹数据:`, processedFolder);
return processedFolder;
}).filter(Boolean); // 过滤掉null值
folderList.value = processedFolders;
totalFolders.value = Number(folderRes.data.total) || 0;
console.log('=== 处理后的数据 ===');
console.log('处理后的文件夹列表:', folderList.value);
console.log('文件夹列表长度:', folderList.value.length);
console.log('总文件夹数:', totalFolders.value);
console.log('当前folderList.value:', folderList.value);
} else {
folderList.value = [];
totalFolders.value = 0;
console.log('API响应异常清空列表');
console.log('响应码不是200或数据为空');
console.log('folderRes.code:', folderRes.code);
console.log('folderRes.data:', folderRes.data);
}
} catch (error) {
console.error('初始化文件夹数据失败:', error);
console.error('错误详情:', error.response?.data);
Message.error('加载文件夹失败,请重试');
folderList.value = [];
totalFolders.value = 0;
} finally {
loading.value = false;
console.log('=== 初始化完成 ===');
console.log('最终folderList.value:', folderList.value);
console.log('最终loading.value:', loading.value);
}
};
// 分页事件处理
const handlePageChange = (page) => {
currentPage.value = page;
initData();
};
const handlePageSizeChange = (current, size) => {
pageSize.value = size;
currentPage.value = 1;
initData();
};
const handleFolderSearch = () => {
console.log('=== 执行搜索 ===');
console.log('搜索关键词:', searchKeyword.value);
// 重置到第一页并搜索
currentPage.value = 1;
console.log('重置页码为:', currentPage.value);
initData();
};
const handleSearchInput = (value) => {
console.log('=== 搜索输入 ===');
console.log('输入值:', value);
searchKeyword.value = value;
console.log('设置搜索关键词为:', searchKeyword.value);
// 防抖搜索300ms后自动搜索
if (searchTimeout.value) {
clearTimeout(searchTimeout.value);
console.log('清除之前的搜索定时器');
}
searchTimeout.value = setTimeout(() => {
console.log('=== 防抖搜索执行 ===');
currentPage.value = 1;
console.log('重置页码为:', currentPage.value);
initData();
}, 300);
};
const handleSearchClear = () => {
console.log('=== 清除搜索 ===');
searchKeyword.value = '';
console.log('清空搜索关键词');
if (searchTimeout.value) {
clearTimeout(searchTimeout.value);
console.log('清除搜索定时器');
}
currentPage.value = 1;
console.log('重置页码为:', currentPage.value);
// 清除搜索后立即刷新数据,显示所有文件夹
initData();
};
// 文件搜索相关函数
const handleFileSearch = () => {
console.log('=== 执行文件搜索 ===');
console.log('文件搜索关键词:', fileSearchKeyword.value);
// 重置到第一页并搜索
fileCurrentPage.value = 1;
// 搜索时重置排序状态
sortField.value = '';
sortOrder.value = '';
console.log('重置文件页码为:', fileCurrentPage.value);
if (currentFolderId.value) {
loadFiles(currentFolderId.value);
}
};
const handleFileSearchInput = (value) => {
console.log('=== 文件搜索输入 ===');
console.log('输入值:', value);
fileSearchKeyword.value = value;
console.log('设置文件搜索关键词为:', fileSearchKeyword.value);
// 防抖搜索300ms后自动搜索
if (searchTimeout.value) {
clearTimeout(searchTimeout.value);
console.log('清除之前的文件搜索定时器');
}
searchTimeout.value = setTimeout(() => {
console.log('=== 防抖文件搜索执行 ===');
fileCurrentPage.value = 1;
// 搜索时重置排序状态
sortField.value = '';
sortOrder.value = '';
console.log('重置文件页码为:', fileCurrentPage.value);
if (currentFolderId.value) {
loadFiles(currentFolderId.value);
}
}, 300);
};
const handleFileSearchClear = () => {
console.log('=== 清除文件搜索 ===');
fileSearchKeyword.value = '';
console.log('清空文件搜索关键词');
if (searchTimeout.value) {
clearTimeout(searchTimeout.value);
console.log('清除文件搜索定时器');
}
fileCurrentPage.value = 1;
// 清除搜索时重置排序状态
sortField.value = '';
sortOrder.value = '';
console.log('重置文件页码为:', fileCurrentPage.value);
if (currentFolderId.value) {
loadFiles(currentFolderId.value);
}
};
const loadFiles = async (folderId) => {
try {
loading.value = true;
const apiParams = {
folderId: folderId,
page: fileCurrentPage.value,
pageSize: filePageSize.value,
fileName: fileSearchKeyword.value || undefined
};
// 添加排序参数
if (sortField.value && sortOrder.value) {
apiParams.sortField = sortField.value;
apiParams.sortOrder = sortOrder.value;
console.log('📤 发送排序参数:', apiParams.sortField, apiParams.sortOrder);
} else {
console.log('📤 未发送排序参数');
}
console.log('📤 发送API参数:', apiParams);
const res = await getFilesApi(apiParams);
// 根据后端返回的数据结构处理
if (res.code === 200 && res.data) {
fileList.value = res.data.rows || [];
totalFiles.value = res.data.total || 0;
} else {
fileList.value = [];
totalFiles.value = 0;
}
currentFolderId.value = folderId;
} catch (error) {
console.error('加载文件失败:', error);
Message.error('服务开小差,请稍后再试');
fileList.value = [];
totalFiles.value = 0;
} finally {
loading.value = false;
}
};
// 排序处理函数
const handleSortChange = (field) => {
console.log('=== 排序处理函数被调用 ===');
console.log('传入的字段:', field);
const backendField = sortFieldMap[field];
console.log('映射后的后端字段:', backendField);
if (!backendField) {
console.log('❌ 找不到对应的后端字段,退出');
return;
}
console.log('当前排序字段:', sortField.value);
console.log('当前排序方向:', sortOrder.value);
// 切换排序方向
if (sortField.value === backendField) {
sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc';
console.log('✅ 切换排序方向为:', sortOrder.value);
} else {
// 新字段,默认降序
sortField.value = backendField;
sortOrder.value = 'desc';
console.log('✅ 设置新排序字段:', sortField.value, '排序方向:', sortOrder.value);
}
// 重新加载文件列表
if (currentFolderId.value) {
console.log('🔄 重新加载文件列表文件夹ID:', currentFolderId.value);
loadFiles(currentFolderId.value);
}
};
// 文件夹点击事件
// const handleFolderClick = (folderId) => {
// const id = String(folderId);
// 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;
// 切换文件夹时清空文件搜索关键词和排序状态
fileSearchKeyword.value = '';
sortField.value = '';
sortOrder.value = '';
}
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';
// 重置排序状态
sortField.value = '';
sortOrder.value = '';
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;
// 重置排序状态
sortField.value = '';
sortOrder.value = '';
loadFiles(targetFolder.id);
}
}
};
// 重命名对话框状态
const renameModalVisible = ref(false);
const renameForm = reactive({
folderId: '',
currentName: '',
newName: '',
isRoot: false
});
// 重命名文件对话框状态
const renameFileModalVisible = ref(false);
const renameFileForm = reactive({
fileId: '',
newName: '',
fileExtension: ''
});
// 文件夹重命名处理函数
const handleRenameFolder = (folder) => {
console.log('重命名文件夹:', folder);
if (!folder) {
Message.error('文件夹信息不能为空');
return;
}
const folderId = folder.key || folder.id;
let currentName = folder.title || folder.name;
if (!folderId) {
Message.error('文件夹ID不能为空');
return;
}
if (!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('当前文件夹名称不能为空');
return;
} else {
console.log('✅ 使用fallbackName:', fallbackName);
currentName = fallbackName;
}
}
// 先显示一个简单的提示,确认函数被调用
Message.info('重命名功能被触发');
// 设置重命名对话框数据
renameForm.folderId = folderId;
renameForm.currentName = currentName;
renameForm.newName = currentName;
renameForm.isRoot = folderId === '0';
console.log('重命名表单数据已设置:', renameForm);
// 显示重命名对话框
renameModalVisible.value = true;
console.log('重命名对话框已显示');
};
// 确认重命名
const confirmRename = async () => {
const { folderId, newName, currentName, isRoot } = renameForm;
console.log('确认重命名:', { folderId, newName, currentName, isRoot });
if (!newName || newName.trim() === '') {
Message.warning('文件夹名称不能为空');
return;
}
if (newName.trim() === currentName) {
renameModalVisible.value = false;
return;
}
try {
console.log('开始调用重命名API...');
console.log('API参数:', { folderId, newName: newName.trim() });
// 调用重命名API
const result = await updateFolderApi(folderId, newName.trim());
console.log('重命名API响应:', result);
// 检查API响应
if (result && result.code === 200) {
if (isRoot) {
Message.success('根目录重命名成功');
// 移除对currentFolderName的设置现在使用面包屑导航
// currentFolderName.value = newName.trim();
} else {
Message.success('文件夹重命名成功');
// 移除对currentFolderName的设置现在使用面包屑导航
// if (currentFolderId.value === folderId) {
// currentFolderName.value = newName.trim();
// }
}
initData(); // 刷新文件夹列表
renameModalVisible.value = false;
} else {
// API返回错误
const errorMsg = result?.msg || '重命名失败,请检查网络连接';
console.error('重命名API返回错误:', result);
Message.error(errorMsg);
}
} catch (error) {
console.error('重命名失败 - 详细错误信息:', error);
console.error('错误响应数据:', error.response?.data);
console.error('错误状态码:', error.response?.status);
// 显示更详细的错误信息
let errorMessage = '重命名失败';
if (error.response?.data?.msg) {
errorMessage = error.response.data.msg;
} else if (error.message) {
errorMessage = `重命名失败: ${error.message}`;
}
Message.error(errorMessage);
}
};
// 文件分页事件
const handleFilePageChange = (page) => {
fileCurrentPage.value = page;
if (currentFolderId.value) {
loadFiles(currentFolderId.value);
}
};
const handleFilePageSizeChange = (current, size) => {
filePageSize.value = size;
fileCurrentPage.value = 1;
if (currentFolderId.value) {
loadFiles(currentFolderId.value);
}
};
// 刷新数据
const refreshData = async () => {
refreshing.value = true;
try {
// 强制清空搜索关键词,确保显示所有文件夹
searchKeyword.value = '';
currentPage.value = 1;
// 刷新时重置排序状态
sortField.value = '';
sortOrder.value = '';
await initData();
if (currentFolderId.value) {
await loadFiles(currentFolderId.value);
}
Message.success('数据已刷新');
} catch (error) {
Message.error('刷新失败');
} finally {
refreshing.value = false;
}
};
// 新建/编辑文件夹提交
const submitFolderForm = async () => {
folderSubmitting.value = true;
try {
if (folderForm.id) {
await updateFolderApi(folderForm.id, folderForm.name);
Message.success('文件夹重命名成功');
} else {
const result = await createFolderApi({
name: folderForm.name,
parentId: folderForm.parentId
});
Message.success('文件夹创建成功');
// 新建文件夹后,刷新数据并自动选中新建的文件夹
await initData();
// 如果有返回新建文件夹的ID自动选中它
if (result.data && result.data.folderId) {
currentFolderId.value = String(result.data.folderId);
loadFiles(currentFolderId.value);
}
}
folderDialogVisible.value = false;
} catch (error) {
console.error('文件夹操作失败:', error);
Message.error(folderForm.id ? '重命名失败' : '创建失败');
} finally {
folderSubmitting.value = false;
}
};
// 获取文件扩展名
const getFileExtension = (fileName) => {
const lastDotIndex = fileName.lastIndexOf('.');
return lastDotIndex > 0 ? fileName.slice(lastDotIndex + 1) : '';
};
// 预览文件
const handlePreview = async (file) => {
try {
console.log('开始预览文件:', file);
Message.loading('正在加载预览...', 0); // 显示加载提示
const blob = await previewFileApi(file.fileId);
Message.clear(); // 清除加载提示
if (!blob) {
Message.error('无法获取文件数据');
return;
}
const url = URL.createObjectURL(blob);
const fileName = file.fileName || file.name;
const ext = getFileExtension(fileName).toLowerCase();
console.log('文件扩展名:', ext);
// 根据文件类型决定预览方式
if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(ext)) {
// 图片预览 - 使用更好的图片预览组件
showImagePreview(url, fileName);
} else if (ext === 'pdf') {
// PDF预览在新窗口打开
window.open(url, '_blank');
Message.success('PDF文件已在新窗口打开');
} else if (['txt', 'md', 'json', 'xml', 'csv', 'log'].includes(ext)) {
// 文本文件预览
showTextPreview(blob, fileName);
} else if (['mp4', 'avi', 'mov', 'wmv', 'flv'].includes(ext)) {
// 视频预览
showVideoPreview(url, fileName);
} else if (['mp3', 'wav', 'flac', 'aac'].includes(ext)) {
// 音频预览
showAudioPreview(url, fileName);
} else if (['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'].includes(ext)) {
// Office文档预览提示
Modal.confirm({
title: '文件预览',
content: `${fileName} 是Office文档格式您可以选择`,
okText: '下载查看',
cancelText: '取消',
onOk: () => {
handleDownload(file);
}
});
} else {
// 其他类型询问是否下载
Modal.confirm({
title: '文件预览',
content: `文件类型 ${ext.toUpperCase()} 暂不支持在线预览,是否下载查看?`,
okText: '下载',
cancelText: '取消',
onOk: () => {
handleDownload(file);
}
});
}
// 延迟释放URL确保预览组件有足够时间加载
setTimeout(() => {
URL.revokeObjectURL(url);
}, 10000);
} catch (error) {
Message.clear(); // 清除加载提示
console.error('预览失败:', error);
// 更详细的错误处理
if (error.response?.status === 404) {
Message.error('文件不存在或已被删除');
} else if (error.response?.status === 403) {
Message.error('没有权限访问该文件');
} else if (error.response?.status === 500) {
Message.error('服务器内部错误,请稍后重试');
} else {
Message.error('预览文件失败,请检查网络连接');
}
}
};
// 显示图片预览
const showImagePreview = (url, fileName) => {
Modal.info({
title: '图片预览',
content: h('div', {
style: {
textAlign: 'center',
padding: '20px'
}
}, [
h('img', {
src: url,
style: {
maxWidth: '100%',
maxHeight: '70vh',
objectFit: 'contain'
}
})
]),
width: '80%',
footer: null,
closable: true
});
};
// 显示文本预览
const showTextPreview = async (blob, fileName) => {
try {
const text = await blob.text();
Modal.info({
title: '文本预览',
content: h('div', {
style: {
textAlign: 'center',
padding: '20px'
}
}, [
h('div', {
style: {
maxWidth: '100%',
maxHeight: '70vh',
overflow: 'auto',
backgroundColor: 'var(--color-fill-1)',
border: '1px solid var(--color-border)',
borderRadius: '8px',
padding: '20px',
fontFamily: "'Consolas', 'Monaco', 'Courier New', monospace",
fontSize: '14px',
lineHeight: '1.6',
whiteSpace: 'pre-wrap',
wordBreak: 'break-word',
color: 'var(--color-text-1)',
textAlign: 'left'
}
}, text)
]),
width: '80%',
footer: null,
closable: true
});
} catch (error) {
console.error('文本预览失败:', error);
Message.error('无法读取文本内容');
}
};
// 显示视频预览
const showVideoPreview = (url, fileName) => {
const container = h('div', {
class: 'preview-container',
style: {
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
padding: '20px',
boxSizing: 'border-box',
background: '#f8fafc !important'
}
}, [
// 视频容器
h('div', {
style: {
width: '100%',
maxWidth: '1000px',
backgroundColor: '#ffffff',
borderRadius: '8px',
padding: '20px',
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
border: '1px solid #e2e8f0',
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
}
}, [
h('video', {
src: url,
controls: true,
style: {
width: '100%',
maxHeight: '70vh',
borderRadius: '8px',
background: '#000',
boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
border: '1px solid #e2e8f0'
}
})
]),
// 文件信息栏
h('div', {
style: {
marginTop: '16px',
padding: '12px 16px',
backgroundColor: '#ffffff',
borderRadius: '8px',
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
border: '1px solid #e2e8f0',
width: '100%',
maxWidth: '1000px',
display: 'flex',
alignItems: 'center',
gap: '16px',
fontSize: '14px',
color: '#2c3e50'
}
}, [
h('span', { style: { display: 'flex', alignItems: 'center', gap: '6px' } }, [
h('span', { style: { fontSize: '16px' } }, '🎬'),
fileName
]),
h('span', { style: { color: '#666', fontSize: '13px' } }, '|'),
h('span', { style: { color: '#666', fontSize: '13px' } }, '视频文件'),
h('span', { style: { color: '#666', fontSize: '13px' } }, '|'),
h('span', { style: { color: '#666', fontSize: '13px' } }, new Date().toLocaleString('zh-CN'))
])
]);
Modal.info({
title: h('div', {
style: {
display: 'flex',
alignItems: 'center',
gap: '8px',
fontSize: '16px',
fontWeight: '600',
color: '#2c3e50'
}
}, [
h('span', { style: { fontSize: '18px' } }, '🎬'),
'视频预览'
]),
content: container,
width: '90%',
style: {
maxWidth: '1200px',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
borderRadius: '8px',
overflow: 'hidden',
boxShadow: '0 8px 24px rgba(0,0,0,0.15)',
border: '1px solid #e2e8f0',
zIndex: 10000
},
mask: true,
maskClosable: true,
maskStyle: {
backgroundColor: 'rgba(0, 0, 0, 0.6)',
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
width: '100vw',
height: '100vh',
zIndex: 9999
},
footer: null,
closable: true,
okText: null,
cancelText: null
});
};
// 显示音频预览
const showAudioPreview = (url, fileName) => {
const container = h('div', {
class: 'preview-container',
style: {
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
padding: '20px',
boxSizing: 'border-box',
background: '#f8fafc !important'
}
}, [
// 音频容器
h('div', {
style: {
width: '100%',
maxWidth: '500px',
backgroundColor: '#ffffff',
borderRadius: '8px',
padding: '32px',
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
border: '1px solid #e2e8f0',
textAlign: 'center'
}
}, [
// 音频图标
h('div', {
style: {
fontSize: '48px',
marginBottom: '20px',
color: '#165DFF'
}
}, '<27><>'),
// 音频播放器
h('audio', {
src: url,
controls: true,
style: {
width: '100%',
height: '40px',
borderRadius: '6px',
marginBottom: '16px'
}
}),
// 文件名
h('div', {
style: {
fontSize: '14px',
color: '#2c3e50',
wordBreak: 'break-all',
textAlign: 'center',
padding: '8px 12px',
backgroundColor: '#f8fafc',
borderRadius: '6px',
fontWeight: '500',
border: '1px solid #e2e8f0'
}
}, fileName)
]),
// 文件信息栏
h('div', {
style: {
marginTop: '16px',
padding: '12px 16px',
backgroundColor: '#ffffff',
borderRadius: '8px',
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
border: '1px solid #e2e8f0',
width: '100%',
maxWidth: '500px',
display: 'flex',
alignItems: 'center',
gap: '16px',
fontSize: '14px',
color: '#2c3e50'
}
}, [
h('span', { style: { display: 'flex', alignItems: 'center', gap: '6px' } }, [
h('span', { style: { fontSize: '16px' } }, '🎵'),
fileName
]),
h('span', { style: { color: '#666', fontSize: '13px' } }, '|'),
h('span', { style: { color: '#666', fontSize: '13px' } }, '音频文件'),
h('span', { style: { color: '#666', fontSize: '13px' } }, '|'),
h('span', { style: { color: '#666', fontSize: '13px' } }, new Date().toLocaleString('zh-CN'))
])
]);
Modal.info({
title: h('div', {
style: {
display: 'flex',
alignItems: 'center',
gap: '8px',
fontSize: '16px',
fontWeight: '600',
color: '#2c3e50'
}
}, [
h('span', { style: { fontSize: '18px' } }, '🎵'),
'音频预览'
]),
content: container,
width: '70%',
style: {
maxWidth: '800px',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
borderRadius: '8px',
overflow: 'hidden',
boxShadow: '0 8px 24px rgba(0,0,0,0.15)',
border: '1px solid #e2e8f0',
zIndex: 10000
},
mask: true,
maskClosable: true,
maskStyle: {
backgroundColor: 'rgba(0, 0, 0, 0.6)',
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
width: '100vw',
height: '100vh',
zIndex: 9999
},
footer: null,
closable: true,
okText: null,
cancelText: null
});
};
// 下载文件
const handleDownload = async (file) => {
try {
const blob = await downloadFileApi(file.fileId);
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = file.fileName || file.name;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
Message.success('开始下载');
} catch (error) {
console.error('下载失败:', error);
Message.error('下载文件失败');
}
};
// 重命名文件
const handleEditFile = (file) => {
console.log('=== 重命名文件函数被调用 ===');
console.log('重命名文件 - 文件对象:', file);
console.log('文件对象的所有属性:', Object.keys(file));
console.log('文件属性详情:', {
fileId: file.fileId,
fileName: file.fileName,
name: file.name,
originalName: file.originalName,
displayName: file.displayName,
title: file.title
});
// 尝试多种可能的文件名字段
let fileName = '';
if (file.fileName) {
fileName = file.fileName;
console.log('使用 fileName 字段:', fileName);
} else if (file.name) {
fileName = file.name;
console.log('使用 name 字段:', fileName);
} else if (file.originalName) {
fileName = file.originalName;
console.log('使用 originalName 字段:', fileName);
} else if (file.displayName) {
fileName = file.displayName;
console.log('使用 displayName 字段:', fileName);
} else if (file.title) {
fileName = file.title;
console.log('使用 title 字段:', fileName);
}
console.log('最终获取到的文件名:', fileName);
if (!fileName) {
console.error('无法获取文件名,文件对象:', file);
Message.error('无法获取文件名,请检查文件数据');
return;
}
const fileExtension = getFileExtension(fileName);
const fileNameWithoutExtension = fileExtension ? fileName.substring(0, fileName.lastIndexOf('.')) : fileName;
console.log('处理后的文件名信息:', {
originalName: fileName,
extension: fileExtension,
nameWithoutExtension: fileNameWithoutExtension
});
// 设置重命名表单数据
renameFileForm.fileId = file.fileId;
renameFileForm.newName = fileNameWithoutExtension;
renameFileForm.fileExtension = fileExtension;
console.log('设置的重命名表单数据:', renameFileForm);
// 显示重命名弹窗
renameFileModalVisible.value = true;
};
// 确认重命名文件
const confirmRenameFile = async () => {
if (!renameFileForm.newName.trim()) {
Message.warning('请输入文件名称');
return;
}
// 使用用户输入的文件名,不添加任何扩展名
const newFileName = renameFileForm.newName.trim();
try {
await renameFileApi(renameFileForm.fileId, newFileName);
Message.success('文件重命名成功');
renameFileModalVisible.value = false;
// 刷新当前文件夹的文件列表
if (currentFolderId.value) {
loadFiles(currentFolderId.value);
}
} catch (error) {
console.error('重命名文件失败:', error);
Message.error('重命名失败');
}
};
// 删除文件夹
const handleDeleteFolder = (folder) => {
Modal.confirm({
title: '确认删除',
content: `确定要删除文件夹「${folder.title || folder.name}」吗?删除后无法恢复,文件夹内的所有文件也将被删除。`,
onOk: async () => {
try {
const result = await deleteFolderApi(folder.key || folder.id);
if (result.code === 200) {
Message.success('文件夹删除成功');
// 如果删除的是当前选中的文件夹,切换到根目录
if (currentFolderId.value === (folder.key || folder.id)) {
currentFolderId.value = '0';
// 移除对currentFolderName的设置现在使用面包屑导航
// currentFolderName.value = '根目录';
fileList.value = [];
totalFiles.value = 0;
}
// 刷新文件夹列表
initData();
} else {
Message.error(result.msg || '删除失败');
}
} catch (error) {
console.error('删除文件夹失败:', error);
Message.error('删除失败');
}
}
});
};
// 删除文件
const handleDelete = (file) => {
Modal.confirm({
title: '确认删除',
content: `确定要删除 ${file.fileName || file.name} 吗?`,
onOk: async () => {
try {
const result = await deleteFileApi(file.fileId);
if (result.code === 200) {
Message.success('删除成功');
loadFiles(currentFolderId.value);
} else {
Message.error(result.msg || '删除失败');
}
} catch (error) {
console.error('删除失败:', error);
Message.error('删除失败');
}
}
});
};
// 侧边栏控制
const sidebarCollapsed = ref(false);
// 侧边栏宽度控制
const sidebarWidth = ref(260); // 默认宽度
const isResizing = ref(false);
const startX = ref(0);
const startWidth = ref(0);
// 从localStorage加载保存的宽度
const loadSavedWidth = () => {
try {
const savedWidth = localStorage.getItem('bussiness-sidebar-width');
if (savedWidth) {
const width = parseInt(savedWidth);
if (width >= 200 && width <= 500) {
sidebarWidth.value = width;
}
}
} catch (error) {
console.warn('加载侧边栏宽度失败:', error);
}
};
// 保存宽度到localStorage
const saveWidth = (width) => {
try {
localStorage.setItem('bussiness-sidebar-width', width.toString());
} catch (error) {
console.warn('保存侧边栏宽度失败:', error);
}
};
// 开始拖拽调整大小
const startResize = (event) => {
event.preventDefault();
isResizing.value = true;
startX.value = event.type === 'mousedown' ? event.clientX : event.touches[0].clientX;
startWidth.value = sidebarWidth.value;
// 添加事件监听器
if (event.type === 'mousedown') {
document.addEventListener('mousemove', handleResize);
document.addEventListener('mouseup', stopResize);
} else {
document.addEventListener('touchmove', handleResize);
document.addEventListener('touchend', stopResize);
}
// 添加样式
document.body.classList.add('resizing');
};
// 处理拖拽调整
const handleResize = (event) => {
if (!isResizing.value) return;
const currentX = event.type === 'mousemove' ? event.clientX : event.touches[0].clientX;
const deltaX = currentX - startX.value;
const newWidth = Math.max(200, Math.min(500, startWidth.value + deltaX));
sidebarWidth.value = newWidth;
};
// 停止拖拽调整
const stopResize = () => {
isResizing.value = false;
// 移除事件监听器
document.removeEventListener('mousemove', handleResize);
document.removeEventListener('mouseup', stopResize);
document.removeEventListener('touchmove', handleResize);
document.removeEventListener('touchend', stopResize);
// 移除样式
document.body.classList.remove('resizing');
// 保存宽度
saveWidth(sidebarWidth.value);
};
// 打开新建文件夹对话框
const handleCreateFolder = () => {
folderForm.id = '';
folderForm.name = '';
folderForm.parentId = currentFolderId.value || '0';
folderDialogVisible.value = true;
};
// 打开上传文件对话框
const handleUploadFile = () => {
uploadDialogVisible.value = true;
};
// 上传成功回调
const handleUploadSuccess = () => {
// 刷新当前文件夹文件列表
if (currentFolderId.value) {
loadFiles(currentFolderId.value);
}
};
// 侧边栏控制函数
const handleSidebarCollapse = (collapsed) => {
sidebarCollapsed.value = collapsed;
};
const handleSidebarExpand = (collapsed) => {
sidebarCollapsed.value = collapsed;
};
// 监听文件夹ID变化自动加载文件
watch(currentFolderId, (newId) => {
if (newId) {
loadFiles(newId);
}
});
// 初始化加载
onMounted(() => {
loadSavedWidth(); // 加载保存的侧边栏宽度
initData();
});
</script>
<style scoped>
.knowledge-container {
height: 100vh;
background-color: var(--color-bg-1);
}
/* 侧边栏样式 */
.folder-sidebar {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
border-right: 1px solid var(--color-border);
transition: all 0.3s ease;
overflow: hidden;
position: relative;
background: var(--color-bg-1);
display: flex;
flex-direction: column;
}
.sidebar-header {
padding: 20px 16px;
border-bottom: 1px solid var(--color-border);
background: var(--color-bg-1);
position: relative;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 1px;
background: linear-gradient(90deg, transparent 0%, var(--color-border) 50%, transparent 100%);
}
}
.folder-content {
padding: 16px 0;
flex: 1;
overflow-y: auto;
scrollbar-width: thin;
background: var(--color-bg-1);
display: flex;
flex-direction: column;
min-height: 0;
max-height: calc(100vh - 200px);
}
.folder-content::-webkit-scrollbar {
width: 8px;
}
.folder-content::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.05);
border-radius: 4px;
}
.folder-content::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, var(--color-primary-light-2) 0%, var(--color-primary) 100%);
border-radius: 4px;
border: 1px solid rgba(255, 255, 255, 0.2);
&:hover {
background: linear-gradient(180deg, var(--color-primary) 0%, var(--color-primary-dark-1) 100%);
}
}
.folder-list {
border: none;
background: transparent;
padding: 0 16px;
}
/* 文件夹列表项样式 */
.folder-list-item {
padding: 12px 16px;
margin-bottom: 6px;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
display: flex;
flex-direction: column;
align-items: flex-start;
border: 1px solid transparent;
&:hover {
background: linear-gradient(135deg, var(--color-fill-2) 0%, var(--color-fill-3) 100%);
border-color: var(--color-primary-light-2);
transform: translateX(2px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
&.active {
background: linear-gradient(135deg, var(--color-primary-light-1) 0%, var(--color-primary-light-2) 100%);
color: var(--color-primary);
font-weight: 500;
border-color: var(--color-primary);
box-shadow: 0 2px 12px rgba(var(--color-primary-6), 0.2);
}
}
/* 文件夹主要信息样式 */
.folder-main-info {
display: flex;
align-items: center;
width: 100%;
margin-bottom: 8px;
}
.folder-name {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-left: 12px;
font-size: 14px;
font-weight: 500;
}
/* 文件夹操作按钮样式 */
.folder-actions-row {
display: flex;
gap: 6px;
width: 100%;
justify-content: flex-end;
opacity: 0;
transition: opacity 0.3s ease;
}
.folder-list-item:hover .folder-actions-row {
opacity: 1;
}
.folder-actions .action-btn {
width: 28px;
height: 28px;
color: var(--color-text-3);
border-radius: 6px;
transition: all 0.2s ease;
&:hover {
color: var(--color-primary);
background: var(--color-fill-3);
transform: scale(1.1);
}
}
/* 删除旧的span样式因为现在使用.folder-name */
/* 文件内容区域样式 */
.file-content {
flex: 1;
display: flex;
flex-direction: column;
padding: 24px;
overflow: hidden;
border-radius: 8px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
background: var(--color-bg-1);
min-height: 0;
max-height: calc(100vh - 120px);
position: relative;
}
.file-card {
border-radius: 8px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
min-height: 300px;
display: flex;
flex-direction: column;
position: relative;
height: 100%;
overflow: hidden;
padding-bottom: 80px; /* 为分页器留出空间 */
}
/* 上传区域样式 */
.upload-area {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
min-height: 140px;
border: 1px dashed var(--color-border);
border-radius: 4px;
background-color: var(--color-fill-1);
transition: all 0.2s;
cursor: pointer;
&:hover {
border-color: rgb(var(--primary-6));
background-color: var(--color-primary-light-1);
}
}
.upload-icon {
text-align: center;
}
.upload-text {
margin-top: 8px;
color: var(--color-text-3);
}
/* 动画效果 */
:deep(.arco-icon-refresh.spin) {
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* 预览弹窗动画效果 */
@keyframes float {
0%, 100% { transform: translateY(0px) rotate(0deg); }
25% { transform: translateY(-10px) rotate(1deg); }
50% { transform: translateY(-5px) rotate(-1deg); }
75% { transform: translateY(-15px) rotate(0.5deg); }
}
@keyframes pulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.05); opacity: 0.8; }
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(-30px) scale(0.9);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
/* 预览弹窗样式增强 */
:deep(.arco-modal) {
animation: slideIn 0.2s ease-out;
z-index: 10000 !important;
}
/* 确保遮罩覆盖整个屏幕 */
:deep(.arco-modal-mask) {
animation: fadeIn 0.2s ease;
background-color: rgba(0, 0, 0, 0.6) !important;
position: fixed !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
width: 100vw !important;
height: 100vh !important;
z-index: 9999 !important;
margin: 0 !important;
padding: 0 !important;
border: none !important;
outline: none !important;
box-sizing: border-box !important;
}
:deep(.arco-modal-content) {
border-radius: 8px !important;
overflow: hidden;
box-shadow: 0 8px 24px rgba(0,0,0,0.15) !important;
border: 1px solid #e2e8f0 !important;
background: #ffffff !important;
}
:deep(.arco-modal-body) {
background: #f8fafc !important;
padding: 0 !important;
}
/* 强制覆盖所有可能的背景样式 */
:deep(.arco-modal-body > div) {
background: #f8fafc !important;
}
:deep(.arco-modal-body > div > div) {
background: #f8fafc !important;
}
/* 确保预览弹窗内容区域使用正确的背景 */
:deep(.arco-modal-body .preview-container) {
background: #f8fafc !important;
}
/* 强制覆盖所有预览弹窗的背景 */
:deep(.arco-modal-body) {
background: #f8fafc !important;
}
:deep(.arco-modal-body > *) {
background: #f8fafc !important;
}
/* 确保没有渐变背景 */
:deep(.arco-modal-body *[style*="linear-gradient"]) {
background: #f8fafc !important;
}
:deep(.arco-modal-header) {
background: #ffffff;
border-bottom: 1px solid #e2e8f0;
padding: 16px 20px;
}
:deep(.arco-modal-title) {
font-weight: 600;
color: #2c3e50;
font-size: 16px;
}
:deep(.arco-modal-close) {
background: #f8fafc;
border-radius: 50%;
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
border: 1px solid #e2e8f0;
&:hover {
background: var(--color-fill-2);
color: var(--color-primary);
}
}
/* 按钮悬停效果增强 */
:deep(.arco-btn) {
transition: all 0.2s ease;
&:hover {
border-color: var(--color-primary);
color: var(--color-primary);
}
&:active {
transform: translateY(0);
}
}
/* 骨架屏样式 */
.skeleton-item {
height: 36px;
margin: 4px 16px;
border-radius: 4px;
background-color: var(--color-fill-2);
}
.file-skeleton-item {
height: 60px;
margin: 8px 0;
border-radius: 4px;
background-color: var(--color-fill-2);
}
/** 文件夹样式 **/
.folder-icon-wrapper {
margin-right: 12px;
display: flex;
align-items: center;
width: 24px;
height: 24px;
border-radius: 6px;
background: linear-gradient(135deg, var(--color-primary-light-1) 0%, var(--color-primary-light-2) 100%);
justify-content: center;
transition: all 0.3s ease;
}
.folder-icon {
font-size: 14px;
color: var(--color-primary);
transition: all 0.3s ease;
}
.folder-list-item:hover .folder-icon-wrapper {
transform: scale(1.1);
box-shadow: 0 2px 8px rgba(var(--color-primary-6), 0.2);
}
.folder-list-item.active .folder-icon-wrapper {
background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-dark-1) 100%);
box-shadow: 0 2px 8px rgba(var(--color-primary-6), 0.3);
}
.folder-list-item.active .folder-icon {
color: white;
}
/* 文件夹操作按钮样式 */
.folder-actions {
display: flex;
gap: 4px;
opacity: 0;
transition: opacity 0.2s;
}
.folder-list-item:hover .folder-actions {
opacity: 1;
}
.action-btn {
width: 24px;
height: 24px;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
}
.action-btn:hover {
background-color: var(--color-fill-3);
}
/* 确保在折叠状态下不显示操作按钮 */
:deep(.folder-sidebar.collapsed) .folder-actions-row {
display: none;
}
/* 新建文件夹按钮美化 */
.create-folder-btn {
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 100%);
border: none;
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);
transition: all 0.3s ease;
&:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(24, 144, 255, 0.4);
}
&:active {
transform: translateY(0);
}
}
/* 搜索输入框美化 */
.search-input {
:deep(.arco-input-wrapper) {
border-radius: 8px;
border: 1px solid var(--color-border);
transition: all 0.3s ease;
background: rgba(var(--color-bg-1-rgb), 0.9);
&:hover {
border-color: var(--color-primary-light-2);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
&:focus-within {
border-color: var(--color-primary);
box-shadow: 0 0 0 3px rgba(var(--color-primary-6), 0.1);
}
}
}
/* 搜索结果提示样式 */
.search-result-tip {
padding: 12px 16px;
margin: 12px 16px;
background: linear-gradient(135deg, var(--color-primary-light-1) 0%, var(--color-primary-light-2) 100%);
border-radius: 8px;
border-left: 4px solid var(--color-primary);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(45deg, transparent 30%, rgba(var(--color-bg-1-rgb), 0.1) 50%, transparent 70%);
animation: shimmer 2s infinite;
}
}
@keyframes shimmer {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
/* 树形文件夹结构 */
.folder-tree-container {
padding: 8px;
background: var(--color-bg-1);
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;
}
}
}
/* 拖拽分隔线样式 */
.sidebar-resizer {
position: absolute;
top: 0;
right: 0;
width: 6px;
height: 100%;
cursor: col-resize;
background: transparent;
transition: background-color 0.2s ease;
z-index: 10;
&:hover {
background: rgba(var(--color-primary-6), 0.1);
}
&:active {
background: rgba(var(--color-primary-6), 0.2);
}
}
.resizer-handle {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 2px;
height: 40px;
background: var(--color-primary);
border-radius: 1px;
opacity: 0.6;
transition: opacity 0.2s ease;
}
.sidebar-resizer:hover .resizer-handle {
opacity: 1;
}
/* 拖拽时的全局样式 */
body.resizing {
cursor: col-resize !important;
user-select: none !important;
}
body.resizing * {
cursor: col-resize !important;
}
</style>