feat(前端): 菜单激活状态逻辑修改+分片上传大小策略

master
chenyt 2025-08-07 15:22:25 +08:00
parent 4348cfffea
commit c166b50e9c
2 changed files with 75 additions and 24 deletions

View File

@ -17,6 +17,7 @@ const { Header, Sider, Content } = Layout;
const MainLayout: React.FC = () => { const MainLayout: React.FC = () => {
const [collapsed, setCollapsed] = useState(false); const [collapsed, setCollapsed] = useState(false);
const [username, setUsername] = useState(''); const [username, setUsername] = useState('');
const [selectedKey, setSelectedKey] = useState('images'); // 添加选中状态
const location = useLocation(); const location = useLocation();
useEffect(() => { useEffect(() => {
@ -31,12 +32,28 @@ const MainLayout: React.FC = () => {
} }
setUsername(currentUsername || ''); setUsername(currentUsername || '');
setSelectedKey(getSelectedKeyFromPath(location.pathname));
}, []); }, []);
const handleMenuClick = (key: string) => { // 监听路由变化,更新选中菜单
// 使用路由导航 useEffect(() => {
history.push(`/${key}`); 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 = () => { const handleLogout = () => {
localStorage.removeItem('isLoggedIn'); localStorage.removeItem('isLoggedIn');
@ -61,14 +78,6 @@ const MainLayout: React.FC = () => {
</Menu> </Menu>
); );
// 根据当前路径确定选中的菜单项
const getSelectedKey = () => {
const path = location.pathname;
if (path === '/images') return 'images';
if (path === '/profile') return 'profile';
return 'images'; // 默认选中镜像列表
};
return ( return (
<ConfigProvider locale={zhCN}> <ConfigProvider locale={zhCN}>
<Layout className="main-layout"> <Layout className="main-layout">
@ -82,8 +91,8 @@ const MainLayout: React.FC = () => {
<Menu <Menu
theme="dark" theme="dark"
mode="inline" mode="inline"
selectedKeys={[getSelectedKey()]} selectedKeys={[selectedKey]}
onClick={({ key }) => handleMenuClick(key)} onClick={handleMenuClick}
> >
<Menu.Item key="userList" icon={<AppstoreOutlined />}> <Menu.Item key="userList" icon={<AppstoreOutlined />}>

View File

@ -2,7 +2,7 @@ import { IMAGES_TYPE_MAP } from '@/constants/images.constants';
import { uploadChunkAPI } from '@/services/images'; import { uploadChunkAPI } from '@/services/images';
import { Alert, Button, message, Modal, Progress, Upload } from 'antd'; import { Alert, Button, message, Modal, Progress, Upload } from 'antd';
import { UploadProps } from 'antd/lib/upload'; 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 SparkMD5 from 'spark-md5';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
@ -80,10 +80,23 @@ const ImportModal: React.FC<ImportModalProps> = ({
'ready' | 'uploading' | 'success' | 'error' 'ready' | 'uploading' | 'success' | 'error'
>('ready'); >('ready');
const [uploadMessage, setUploadMessage] = useState(''); 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 CHUNK_SIZE = 10 * 1024 * 1024; // 50MB/每片 网络传输层面的分片
const MAX_CONCURRENT = 5; // 同时上传的分片数量 const MAX_CONCURRENT = 3; // 同时上传的分片数量
const uploadQueue = useRef<Array<{ chunk: Blob; index: number }>>([]); // 上传队列 const uploadQueue = useRef<Array<{ chunk: Blob; index: number }>>([]); // 上传队列
const completedChunks = useRef<number>(0); // 已上传的分片数量 const completedChunks = useRef<number>(0); // 已上传的分片数量
const totalChunks = useRef<number>(0); // 总分片数量 const totalChunks = useRef<number>(0); // 总分片数量
@ -122,9 +135,20 @@ const ImportModal: React.FC<ImportModalProps> = ({
// 5. 分块计算文件MD5 // 5. 分块计算文件MD5
const calculateMD5InChunks = ( const calculateMD5InChunks = (
file: Blob, file: Blob,
chunkSize: number = file.size > 50 * 1024 * 1024 fileSize: number,
? 2 * 1024 * 1024 // 大文件用2MB块 chunkSize: number = (() => {
: 1 * 1024 * 1024, // 小文件用1MB块, // 计算MD5时的内存分块大小 内存处理层面的分块 即在计算每个10MB分片的MD5值时为了避免占用过多内存将10MB的分片再进一步分成2MB的小块逐步读取计算 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<string> => { ): Promise<string> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const spark = new SparkMD5.ArrayBuffer(); const spark = new SparkMD5.ArrayBuffer();
@ -173,7 +197,7 @@ const ImportModal: React.FC<ImportModalProps> = ({
return false; return false;
} }
// 使用 spark-md5 计算当前分片的MD5 // 使用 spark-md5 计算当前分片的MD5
const chunkMD5 = await calculateMD5InChunks(chunk); const chunkMD5 = await calculateMD5InChunks(chunk, fileSize.current);
const formData = new FormData(); const formData = new FormData();
formData.append('file_id', fileId.current); formData.append('file_id', fileId.current);
@ -278,6 +302,22 @@ const ImportModal: React.FC<ImportModalProps> = ({
await Promise.all(promises); 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. 开始上传 // 2. 开始上传
const startUpload = async (file: File) => { const startUpload = async (file: File) => {
try { try {
@ -291,7 +331,9 @@ const ImportModal: React.FC<ImportModalProps> = ({
fileSize.current = file.size; fileSize.current = file.size;
fileName.current = file.name; fileName.current = file.name;
fileId.current = uuidv4(); // 生成唯一文件ID 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 = []; uploadQueue.current = [];
setUploadMessage(`正在分析文件... `); setUploadMessage(`正在分析文件... `);
@ -299,8 +341,8 @@ const ImportModal: React.FC<ImportModalProps> = ({
// 创建分片并添加到队列 // 创建分片并添加到队列
for (let i = 0; i < totalChunks.current; i++) { for (let i = 0; i < totalChunks.current; i++) {
const start = i * CHUNK_SIZE; const start = i * chunkSize;
const end = Math.min(start + CHUNK_SIZE, file.size); const end = Math.min(start + chunkSize, file.size);
const chunk = file.slice(start, end); const chunk = file.slice(start, end);
uploadQueue.current.push({ chunk, index: i }); uploadQueue.current.push({ chunk, index: i });
} }