From c166b50e9cfef3a0e0f35dcdfaf3d7b7a347b723 Mon Sep 17 00:00:00 2001 From: chenyt Date: Thu, 7 Aug 2025 15:22:25 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=89=8D=E7=AB=AF):=20=E8=8F=9C=E5=8D=95?= =?UTF-8?q?=E6=BF=80=E6=B4=BB=E7=8A=B6=E6=80=81=E9=80=BB=E8=BE=91=E4=BF=AE?= =?UTF-8?q?=E6=94=B9+=E5=88=86=E7=89=87=E4=B8=8A=E4=BC=A0=E5=A4=A7?= =?UTF-8?q?=E5=B0=8F=E7=AD=96=E7=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web-fe/src/pages/components/Layout/index.tsx | 37 ++++++----- .../images/components/modalShow/modalShow.tsx | 62 ++++++++++++++++--- 2 files changed, 75 insertions(+), 24 deletions(-) diff --git a/web-fe/src/pages/components/Layout/index.tsx b/web-fe/src/pages/components/Layout/index.tsx index 76a409a..0d3ec8f 100644 --- a/web-fe/src/pages/components/Layout/index.tsx +++ b/web-fe/src/pages/components/Layout/index.tsx @@ -17,6 +17,7 @@ const { Header, Sider, Content } = Layout; const MainLayout: React.FC = () => { const [collapsed, setCollapsed] = useState(false); const [username, setUsername] = useState(''); + const [selectedKey, setSelectedKey] = useState('images'); // 添加选中状态 const location = useLocation(); useEffect(() => { @@ -31,12 +32,28 @@ const MainLayout: React.FC = () => { } setUsername(currentUsername || ''); + setSelectedKey(getSelectedKeyFromPath(location.pathname)); }, []); - const handleMenuClick = (key: string) => { - // 使用路由导航 - history.push(`/${key}`); - }; + // 监听路由变化,更新选中菜单 + useEffect(() => { + setSelectedKey(getSelectedKeyFromPath(location.pathname)); + }, [location.pathname]); + + const getSelectedKeyFromPath = (path: string) => { + if (path.startsWith('/userList')) return 'userList'; + if (path.startsWith('/terminal')) return 'terminal'; + if (path.startsWith('/images')) return 'images'; + if (path.startsWith('/profile')) return 'profile'; + return 'images'; // 默认选中镜像列表 + }; + + const handleMenuClick = (item: any) => { + // 更新选中状态 + setSelectedKey(item.key); + // 使用路由导航 + history.push(`/${item.key}`); + }; const handleLogout = () => { localStorage.removeItem('isLoggedIn'); @@ -61,14 +78,6 @@ const MainLayout: React.FC = () => { ); - // 根据当前路径确定选中的菜单项 - const getSelectedKey = () => { - const path = location.pathname; - if (path === '/images') return 'images'; - if (path === '/profile') return 'profile'; - return 'images'; // 默认选中镜像列表 - }; - return ( @@ -82,8 +91,8 @@ const MainLayout: React.FC = () => { handleMenuClick(key)} + selectedKeys={[selectedKey]} + onClick={handleMenuClick} > }> 用户 diff --git a/web-fe/src/pages/images/components/modalShow/modalShow.tsx b/web-fe/src/pages/images/components/modalShow/modalShow.tsx index 2b3d23c..a97fb41 100644 --- a/web-fe/src/pages/images/components/modalShow/modalShow.tsx +++ b/web-fe/src/pages/images/components/modalShow/modalShow.tsx @@ -2,7 +2,7 @@ import { IMAGES_TYPE_MAP } from '@/constants/images.constants'; import { uploadChunkAPI } from '@/services/images'; import { Alert, Button, message, Modal, Progress, Upload } from 'antd'; import { UploadProps } from 'antd/lib/upload'; -import React, { useRef, useState, useEffect } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import SparkMD5 from 'spark-md5'; import { v4 as uuidv4 } from 'uuid'; @@ -80,10 +80,23 @@ const ImportModal: React.FC = ({ 'ready' | 'uploading' | 'success' | 'error' >('ready'); const [uploadMessage, setUploadMessage] = useState(''); + /** + * 网络传输层面分片大小策略: + * ≤5GB 文件: 10MB 分片 + * 5GB-10GB 文件: 15MB 分片 + * 10GB 文件: 20MB 分片 + */ + /** + * MD5计算的内存处理层面的分块策略: + * 小文件(≤100MB): 1MB块 + * 中等文件(100MB-1GB): 2MB块 + * 大文件(1-5GB): 4MB块 + * 超大文件(>5GB): 8MB块 + */ // 分片上传相关 - const CHUNK_SIZE = 10 * 1024 * 1024; // 50MB/每片 网络传输层面的分片 - const MAX_CONCURRENT = 5; // 同时上传的分片数量 + // const CHUNK_SIZE = 10 * 1024 * 1024; // 50MB/每片 网络传输层面的分片 + const MAX_CONCURRENT = 3; // 同时上传的分片数量 const uploadQueue = useRef>([]); // 上传队列 const completedChunks = useRef(0); // 已上传的分片数量 const totalChunks = useRef(0); // 总分片数量 @@ -122,9 +135,20 @@ const ImportModal: React.FC = ({ // 5. 分块计算文件MD5 const calculateMD5InChunks = ( file: Blob, - chunkSize: number = file.size > 50 * 1024 * 1024 - ? 2 * 1024 * 1024 // 大文件用2MB块 - : 1 * 1024 * 1024, // 小文件用1MB块, // 计算MD5时的内存分块大小 内存处理层面的分块 即在计算每个10MB分片的MD5值时,为了避免占用过多内存,将10MB的分片再进一步分成2MB的小块逐步读取计算 + fileSize: number, + chunkSize: number = (() => { + if (fileSize > 5 * 1024 * 1024 * 1024) + // >5GB + return 8 * 1024 * 1024; // 8MB块 + if (fileSize > 1024 * 1024 * 1024) + // >1GB + return 4 * 1024 * 1024; // 4MB块 + if (fileSize > 100 * 1024 * 1024) + // >100MB + return 2 * 1024 * 1024; // 2MB块 + return 1 * 1024 * 1024; // 1MB块 + })(), + // 计算MD5时的内存分块大小 内存处理层面的分块 即整个文件的大小来决定 MD5 计算时的块大小 ): Promise => { return new Promise((resolve, reject) => { const spark = new SparkMD5.ArrayBuffer(); @@ -173,7 +197,7 @@ const ImportModal: React.FC = ({ return false; } // 使用 spark-md5 计算当前分片的MD5 - const chunkMD5 = await calculateMD5InChunks(chunk); + const chunkMD5 = await calculateMD5InChunks(chunk, fileSize.current); const formData = new FormData(); formData.append('file_id', fileId.current); @@ -278,6 +302,22 @@ const ImportModal: React.FC = ({ await Promise.all(promises); }; + /** + * 网络传输层面的分片 + * ≤5GB 文件: 10MB 分片 + * 5GB-10GB 文件: 15MB 分片 + * 10GB 文件: 20MB 分片 + */ + const getChunkSize = (fileSize: number): number => { + if (fileSize > 10 * 1024 * 1024 * 1024) + // >10GB + return 20 * 1024 * 1024; // 20MB + if (fileSize > 5 * 1024 * 1024 * 1024) + // >5GB + return 15 * 1024 * 1024; // 15MB + return 10 * 1024 * 1024; // 10MB (默认) + }; + // 2. 开始上传 const startUpload = async (file: File) => { try { @@ -291,7 +331,9 @@ const ImportModal: React.FC = ({ fileSize.current = file.size; fileName.current = file.name; fileId.current = uuidv4(); // 生成唯一文件ID - totalChunks.current = Math.ceil(file.size / CHUNK_SIZE); + // 使用动态分片大小 + const chunkSize = getChunkSize(file.size); + totalChunks.current = Math.ceil(file.size / chunkSize); uploadQueue.current = []; setUploadMessage(`正在分析文件... `); @@ -299,8 +341,8 @@ const ImportModal: React.FC = ({ // 创建分片并添加到队列 for (let i = 0; i < totalChunks.current; i++) { - const start = i * CHUNK_SIZE; - const end = Math.min(start + CHUNK_SIZE, file.size); + const start = i * chunkSize; + const end = Math.min(start + chunkSize, file.size); const chunk = file.slice(start, end); uploadQueue.current.push({ chunk, index: i }); }