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 [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 = () => {
</Menu>
);
// 根据当前路径确定选中的菜单项
const getSelectedKey = () => {
const path = location.pathname;
if (path === '/images') return 'images';
if (path === '/profile') return 'profile';
return 'images'; // 默认选中镜像列表
};
return (
<ConfigProvider locale={zhCN}>
<Layout className="main-layout">
@ -82,8 +91,8 @@ const MainLayout: React.FC = () => {
<Menu
theme="dark"
mode="inline"
selectedKeys={[getSelectedKey()]}
onClick={({ key }) => handleMenuClick(key)}
selectedKeys={[selectedKey]}
onClick={handleMenuClick}
>
<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 { 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<ImportModalProps> = ({
'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<Array<{ chunk: Blob; index: number }>>([]); // 上传队列
const completedChunks = useRef<number>(0); // 已上传的分片数量
const totalChunks = useRef<number>(0); // 总分片数量
@ -122,9 +135,20 @@ const ImportModal: React.FC<ImportModalProps> = ({
// 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<string> => {
return new Promise((resolve, reject) => {
const spark = new SparkMD5.ArrayBuffer();
@ -173,7 +197,7 @@ const ImportModal: React.FC<ImportModalProps> = ({
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<ImportModalProps> = ({
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<ImportModalProps> = ({
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<ImportModalProps> = ({
// 创建分片并添加到队列
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 });
}