feat(前端): 菜单激活状态逻辑修改+分片上传大小策略
parent
4348cfffea
commit
c166b50e9c
|
@ -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 />}>
|
||||
用户
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue