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