feat(前端): 联调
parent
3e61c26a6b
commit
8dc26428d7
|
@ -20,7 +20,7 @@ export default {
|
||||||
(page_num - 1) * page_size + i
|
(page_num - 1) * page_size + i
|
||||||
}`,
|
}`,
|
||||||
image_type: getRandomFormat(),
|
image_type: getRandomFormat(),
|
||||||
bt_path: `/serve/logs`,
|
bt_path: `https://releases.ubuntu.com/20.04.6/ubuntu-20.04.6-desktop-amd64.iso.torrent`,
|
||||||
image_version: '1.0.0',
|
image_version: '1.0.0',
|
||||||
os_version: 'Ubuntu 20.04',
|
os_version: 'Ubuntu 20.04',
|
||||||
image_status: Math.random() > 0.5 ? 1 : 2,
|
image_status: Math.random() > 0.5 ? 1 : 2,
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { UploadProps } from 'antd/lib/upload';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import {
|
import {
|
||||||
|
CODE,
|
||||||
IMAGE_DETAIL_FIELDS,
|
IMAGE_DETAIL_FIELDS,
|
||||||
UPLOAD_CONFIG,
|
UPLOAD_CONFIG,
|
||||||
UPLOAD_STATUS_MAP,
|
UPLOAD_STATUS_MAP,
|
||||||
|
@ -64,8 +65,7 @@ const ImportModal: React.FC<IMAGES.ImportModalProps> = ({
|
||||||
const [uploadMessage, setUploadMessage] = useState('');
|
const [uploadMessage, setUploadMessage] = useState('');
|
||||||
|
|
||||||
// 分片上传相关
|
// 分片上传相关
|
||||||
// const CHUNK_SIZE = 10 * 1024 * 1024; // 50MB/每片 网络传输层面的分片
|
const MAX_CONCURRENT = UPLOAD_CONFIG.MAX_CONCURRENT; // 同时上传的分片数量
|
||||||
const MAX_CONCURRENT = UPLOAD_CONFIG.MAX_CONCURRENT;; // 同时上传的分片数量
|
|
||||||
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); // 总分片数量
|
||||||
|
@ -77,8 +77,66 @@ const ImportModal: React.FC<IMAGES.ImportModalProps> = ({
|
||||||
const [elapsedTime, setElapsedTime] = useState<number>(0); // 上传时间
|
const [elapsedTime, setElapsedTime] = useState<number>(0); // 上传时间
|
||||||
const timerRef = useRef<NodeJS.Timeout | null>(null);
|
const timerRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
|
// const [uploadCompleted, _setUploadCompleted] = useState(false);
|
||||||
|
// const uploadCompletedRef = useRef(false);
|
||||||
|
|
||||||
|
// const setUploadCompleted = (value: boolean) => {
|
||||||
|
// uploadCompletedRef.current = value;
|
||||||
|
// _setUploadCompleted(value);
|
||||||
|
// };
|
||||||
|
|
||||||
|
// 处理页面刷新/关闭
|
||||||
|
// useEffect(() => {
|
||||||
|
// const handleBeforeUnload = (e: BeforeUnloadEvent) => {
|
||||||
|
// if (isUploading && !uploadCompletedRef.current) {
|
||||||
|
// e.preventDefault();
|
||||||
|
// e.returnValue = '镜像正在上传中,确定要离开吗?';
|
||||||
|
|
||||||
|
// // 使用 sendBeacon 发送取消请求
|
||||||
|
// const params = new URLSearchParams();
|
||||||
|
// params.append('file_id', fileId.current);
|
||||||
|
// const blob = new Blob([params.toString()], {
|
||||||
|
// type: 'application/x-www-form-urlencoded',
|
||||||
|
// });
|
||||||
|
// navigator.sendBeacon('/api/cancel-upload', blob);
|
||||||
|
|
||||||
|
// return e.returnValue;
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// window.addEventListener('beforeunload', handleBeforeUnload);
|
||||||
|
// return () => window.removeEventListener('beforeunload', handleBeforeUnload);
|
||||||
|
// }, [isUploading]);
|
||||||
|
|
||||||
|
// 计时器清理(仅组件卸载时执行)
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (timerRef.current) {
|
||||||
|
clearInterval(timerRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// // 上传取消逻辑(依赖 isUploading 和 uploadCompletedRef)
|
||||||
|
// useEffect(() => {
|
||||||
|
// return () => {
|
||||||
|
// if (isUploading && !uploadCompletedRef.current && fileId.current) {
|
||||||
|
// const params = new URLSearchParams();
|
||||||
|
// params.append('file_id', fileId.current);
|
||||||
|
// cancelUploadImagesAPI(params).then((res) => {
|
||||||
|
// if (res.code === CODE) {
|
||||||
|
// message.success('上传已取消');
|
||||||
|
// } else {
|
||||||
|
// message.error('取消上传失败');
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// }, [isUploading]); // 保留原有依赖
|
||||||
|
|
||||||
// 添加重置状态函数
|
// 添加重置状态函数
|
||||||
const resetState = () => {
|
const resetState = () => {
|
||||||
|
// setUploadCompleted(false); // 重置上传完成状态
|
||||||
setUploadProgress(0);
|
setUploadProgress(0);
|
||||||
setIsUploading(false);
|
setIsUploading(false);
|
||||||
setUploadStatus(READY);
|
setUploadStatus(READY);
|
||||||
|
@ -112,21 +170,21 @@ const ImportModal: React.FC<IMAGES.ImportModalProps> = ({
|
||||||
}, [visible]);
|
}, [visible]);
|
||||||
|
|
||||||
// 4. 上传单个分片
|
// 4. 上传单个分片
|
||||||
const uploadChunk = async (chunk: Blob, index: number): Promise<boolean> => {
|
const uploadChunk = async (
|
||||||
|
chunk: Blob,
|
||||||
|
index: number,
|
||||||
|
): Promise<IMAGES.UploadChunkResult> => {
|
||||||
try {
|
try {
|
||||||
// 检查是否已中止
|
// 检查是否已中止
|
||||||
if (abortController.current?.signal.aborted) {
|
if (abortController.current?.signal.aborted) {
|
||||||
return false;
|
return {success:false};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新状态,提示正在计算MD5
|
// 更新状态,提示正在计算MD5
|
||||||
// setUploadMessage(`正在计算第${index}个分片的MD5...`);
|
// setUploadMessage(`正在计算第${index}个分片的MD5...`);
|
||||||
|
|
||||||
// 使用 spark-md5 计算当前分片的MD5
|
// 使用 spark-md5 计算当前分片的MD5
|
||||||
const chunkMD5 = await calculateMD5InChunks(
|
const chunkMD5 = await calculateMD5InChunks(chunk, fileSize.current);
|
||||||
chunk,
|
|
||||||
fileSize.current,
|
|
||||||
);
|
|
||||||
// 更新状态,提示正在上传
|
// 更新状态,提示正在上传
|
||||||
// setUploadMessage(`正在上传第${index}个分片...`);
|
// setUploadMessage(`正在上传第${index}个分片...`);
|
||||||
|
|
||||||
|
@ -149,6 +207,7 @@ const ImportModal: React.FC<IMAGES.ImportModalProps> = ({
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
if (response.status === 'completed') {
|
if (response.status === 'completed') {
|
||||||
// 文件上传完成,设置进度为100%
|
// 文件上传完成,设置进度为100%
|
||||||
|
// setUploadCompleted(true); // 标记上传完成
|
||||||
setUploadProgress(100); // 这里已经正确设置了100%
|
setUploadProgress(100); // 这里已经正确设置了100%
|
||||||
setIsUploading(false);
|
setIsUploading(false);
|
||||||
setUploadStatus(SUCCESS);
|
setUploadStatus(SUCCESS);
|
||||||
|
@ -163,44 +222,32 @@ const ImportModal: React.FC<IMAGES.ImportModalProps> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
// 文件上传完成
|
// 文件上传完成
|
||||||
return true;
|
return response;
|
||||||
} else if (response.status === 'uploading') {
|
} else if (response.status === 'uploading') {
|
||||||
// 分片上传成功,继续上传
|
// 分片上传成功,继续上传
|
||||||
return true;
|
return response;
|
||||||
} else if (response.status === 'error') {
|
} else if (response.status === 'error') {
|
||||||
// 后端返回错误状态
|
// 后端返回错误状态
|
||||||
console.error(`上传分片 ${index} 失败:`, response.message);
|
console.error(`上传分片 ${index} 失败:`, response.message);
|
||||||
return false;
|
return response;
|
||||||
} else {
|
} else {
|
||||||
// 未知状态
|
// 未知状态
|
||||||
console.error(`上传分片 ${index} 返回未知状态:`, response.status);
|
console.error(`上传分片 ${index} 返回未知状态:`, response.status);
|
||||||
return false;
|
return response;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// success 为 false
|
// success 为 false
|
||||||
console.error(`上传分片 ${index} 失败:`, response.message);
|
console.error(`上传分片 ${index} 失败:`, response.message);
|
||||||
return false;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
// await uploadChunkAPI(formData, abortController.current?.signal);
|
|
||||||
// return true;
|
|
||||||
// 模拟上传过程
|
|
||||||
// await new Promise((resolve) =>
|
|
||||||
// setTimeout(resolve, 300 + Math.random() * 700),
|
|
||||||
// );
|
|
||||||
|
|
||||||
// 模拟8%的失败率用于测试
|
|
||||||
// if (Math.random() < 0.08) {
|
|
||||||
// throw new Error('网络错误');
|
|
||||||
// }
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 检查是否因为中止导致的错误
|
// 检查是否因为中止导致的错误
|
||||||
if (error instanceof Error && error.name === 'AbortError') {
|
if (error instanceof Error && error.name === 'AbortError') {
|
||||||
// console.log(`上传分片 ${index} 被用户取消`);
|
// console.log(`上传分片 ${index} 被用户取消`);
|
||||||
return false;
|
return {success:false};
|
||||||
}
|
}
|
||||||
// console.error(`上传分片 ${index} 失败:`, error);
|
// console.error(`上传分片 ${index} 失败:`, error);
|
||||||
return false;
|
return { success: false };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -224,9 +271,9 @@ const ImportModal: React.FC<IMAGES.ImportModalProps> = ({
|
||||||
const chunkItem = uploadQueue.current.shift();
|
const chunkItem = uploadQueue.current.shift();
|
||||||
if (chunkItem) {
|
if (chunkItem) {
|
||||||
const { chunk, index } = chunkItem;
|
const { chunk, index } = chunkItem;
|
||||||
const success = await uploadChunk(chunk, index);
|
const result = await uploadChunk(chunk, index);
|
||||||
|
|
||||||
if (success) {
|
if (result.success) {
|
||||||
// 只有在没有错误的情况下才更新进度
|
// 只有在没有错误的情况下才更新进度
|
||||||
if (!hasError) {
|
if (!hasError) {
|
||||||
completedChunks.current += 1;
|
completedChunks.current += 1;
|
||||||
|
@ -243,25 +290,6 @@ const ImportModal: React.FC<IMAGES.ImportModalProps> = ({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
setUploadProgress(progress);
|
setUploadProgress(progress);
|
||||||
|
|
||||||
// 所有分片上传完成
|
|
||||||
// if (completedChunks.current === totalChunks.current) {
|
|
||||||
// setIsUploading(false);
|
|
||||||
// setUploadStatus(SUCCESS);
|
|
||||||
// setUploadMessage(
|
|
||||||
// '文件上传成功!正在处理中,请稍后查看列表。',
|
|
||||||
// );
|
|
||||||
// message.success(
|
|
||||||
// '文件上传成功!系统正在处理,请稍后查看列表。',
|
|
||||||
// );
|
|
||||||
// onImportSuccess?.();
|
|
||||||
|
|
||||||
// // 停止计时器
|
|
||||||
// if (timerRef.current) {
|
|
||||||
// clearInterval(timerRef.current);
|
|
||||||
// timerRef.current = null;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 上传失败处理
|
// 上传失败处理
|
||||||
|
@ -269,8 +297,8 @@ const ImportModal: React.FC<IMAGES.ImportModalProps> = ({
|
||||||
hasError = true; // 设置错误标记
|
hasError = true; // 设置错误标记
|
||||||
setIsUploading(false);
|
setIsUploading(false);
|
||||||
setUploadStatus(ERROR);
|
setUploadStatus(ERROR);
|
||||||
setUploadMessage('文件上传失败,请重试');
|
setUploadMessage(result.message || '文件上传失败,请重试');
|
||||||
message.error('文件上传失败');
|
message.error(result.message ||'文件上传失败');
|
||||||
|
|
||||||
// 中止其他正在进行的上传
|
// 中止其他正在进行的上传
|
||||||
if (abortController.current) {
|
if (abortController.current) {
|
||||||
|
@ -392,16 +420,23 @@ const ImportModal: React.FC<IMAGES.ImportModalProps> = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
// 取消上传
|
// 取消上传
|
||||||
const cancelUpload = async() => {
|
const cancelUpload = async () => {
|
||||||
|
// 先更新 ref 再更新 state
|
||||||
|
// uploadCompletedRef.current = true;
|
||||||
|
// _setUploadCompleted(true);
|
||||||
if (abortController.current) {
|
if (abortController.current) {
|
||||||
abortController.current.abort();
|
abortController.current.abort();
|
||||||
|
abortController.current = null;
|
||||||
}
|
}
|
||||||
// 如果有正在上传的文件,调用后端取消上传API
|
// 如果有正在上传的文件,调用后端取消上传API
|
||||||
if (fileId.current) {
|
if (fileId.current) {
|
||||||
try {
|
try {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
params.append('file_id', fileId.current);
|
params.append('file_id', fileId.current);
|
||||||
await cancelUploadImagesAPI(params);
|
const res = await cancelUploadImagesAPI(params);
|
||||||
|
if (res.code === CODE) {
|
||||||
|
message.success('上传已取消');
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('取消上传API调用失败:', error);
|
console.error('取消上传API调用失败:', error);
|
||||||
}
|
}
|
||||||
|
@ -410,8 +445,6 @@ const ImportModal: React.FC<IMAGES.ImportModalProps> = ({
|
||||||
setUploadStatus(READY);
|
setUploadStatus(READY);
|
||||||
setUploadMessage('上传已取消');
|
setUploadMessage('上传已取消');
|
||||||
setUploadProgress(0);
|
setUploadProgress(0);
|
||||||
message.info('上传已取消');
|
|
||||||
|
|
||||||
// 停止计时器
|
// 停止计时器
|
||||||
if (timerRef.current) {
|
if (timerRef.current) {
|
||||||
clearInterval(timerRef.current);
|
clearInterval(timerRef.current);
|
||||||
|
@ -425,8 +458,8 @@ const ImportModal: React.FC<IMAGES.ImportModalProps> = ({
|
||||||
<Alert
|
<Alert
|
||||||
message="重要提示"
|
message="重要提示"
|
||||||
description={
|
description={
|
||||||
<div>
|
<div style={{ color: "rgb(237, 41, 31)" }}>
|
||||||
<div>1. 文件上传需要时间,请耐心等待。</div>
|
<div>1. 文件上传后需要组装,需要时间,请耐心等待。</div>
|
||||||
<div>
|
<div>
|
||||||
2. 文件上传中请勿刷新或者离开页面,否则会导致文件上传失败。
|
2. 文件上传中请勿刷新或者离开页面,否则会导致文件上传失败。
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -13,8 +13,6 @@ const useTableParams = (
|
||||||
useState<IMAGES.TableParams>(initialParams);
|
useState<IMAGES.TableParams>(initialParams);
|
||||||
|
|
||||||
const getApiParams = useCallback(() => {
|
const getApiParams = useCallback(() => {
|
||||||
console.log('getApiParams', tableParams);
|
|
||||||
|
|
||||||
const { pagination, filters, sort, search, ...rest } = tableParams;
|
const { pagination, filters, sort, search, ...rest } = tableParams;
|
||||||
const apiParams: Record<string, any> = {
|
const apiParams: Record<string, any> = {
|
||||||
page_size: pagination?.pageSize,
|
page_size: pagination?.pageSize,
|
||||||
|
@ -51,7 +49,7 @@ const useTableParams = (
|
||||||
newParams: Partial<IMAGES.TableParams>,
|
newParams: Partial<IMAGES.TableParams>,
|
||||||
options?: { resetPage?: boolean },
|
options?: { resetPage?: boolean },
|
||||||
) => {
|
) => {
|
||||||
console.log('updateParams', newParams);
|
// console.log('updateParams', newParams);
|
||||||
|
|
||||||
setTableParams((prev) => {
|
setTableParams((prev) => {
|
||||||
// 如果是搜索或过滤相关的更新,重置到第一页
|
// 如果是搜索或过滤相关的更新,重置到第一页
|
||||||
|
@ -86,7 +84,7 @@ const useTableParams = (
|
||||||
NonNullable<TableProps<IMAGES.ImageItem>['onChange']>
|
NonNullable<TableProps<IMAGES.ImageItem>['onChange']>
|
||||||
>(
|
>(
|
||||||
(pagination, filters, sorter, extra) => {
|
(pagination, filters, sorter, extra) => {
|
||||||
console.log('handleTableChange',pagination,filters,sorter,extra);
|
// console.log('handleTableChange',pagination,filters,sorter,extra);
|
||||||
|
|
||||||
// 过滤掉空值的filters
|
// 过滤掉空值的filters
|
||||||
const filteredFilters: Record<string, any> = {};
|
const filteredFilters: Record<string, any> = {};
|
||||||
|
@ -122,7 +120,7 @@ const useTableParams = (
|
||||||
: undefined,
|
: undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
console.log('handleTableChange', newParams);
|
// console.log('handleTableChange', newParams);
|
||||||
|
|
||||||
updateParams(newParams);
|
updateParams(newParams);
|
||||||
},
|
},
|
||||||
|
|
|
@ -37,6 +37,7 @@ type ColumnConfig = {
|
||||||
fixed?: 'left' | 'right';
|
fixed?: 'left' | 'right';
|
||||||
defaultVisible: boolean; // 默认是否显示
|
defaultVisible: boolean; // 默认是否显示
|
||||||
alwaysVisible?: boolean; // 始终显示的列
|
alwaysVisible?: boolean; // 始终显示的列
|
||||||
|
ellipsis?: boolean; // 是否启用省略号
|
||||||
filters?: { text: string; value: string }[];
|
filters?: { text: string; value: string }[];
|
||||||
filterMultiple?: boolean; // 是否多选过滤
|
filterMultiple?: boolean; // 是否多选过滤
|
||||||
filterDropdown?: (props: any) => React.ReactNode;
|
filterDropdown?: (props: any) => React.ReactNode;
|
||||||
|
@ -142,30 +143,24 @@ const ImageList: React.FC = () => {
|
||||||
width: 200,
|
width: 200,
|
||||||
defaultVisible: true,
|
defaultVisible: true,
|
||||||
alwaysVisible: true,
|
alwaysVisible: true,
|
||||||
render: (text: string) => (
|
ellipsis: true,
|
||||||
<Tooltip title={text}>
|
render: (text: string) =>
|
||||||
<span
|
text ? (
|
||||||
style={{
|
<Tooltip title={text} placement="topLeft">
|
||||||
overflow: 'hidden',
|
{text}
|
||||||
textOverflow: 'ellipsis',
|
</Tooltip>
|
||||||
whiteSpace: 'nowrap',
|
) : (
|
||||||
display: 'inline-block',
|
'--'
|
||||||
width: '100%',
|
),
|
||||||
}}
|
|
||||||
>
|
|
||||||
{text || '--'}
|
|
||||||
</span>
|
|
||||||
</Tooltip>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'image_type',
|
key: 'image_type',
|
||||||
title: '桌面类型',
|
title: '桌面类型',
|
||||||
dataIndex: 'image_type',
|
dataIndex: 'image_type',
|
||||||
width: 100,
|
width: 120,
|
||||||
render: (text: number) => {
|
render: (text: number) => {
|
||||||
const key = text as keyof typeof IMAGES_TYPE_MAP;
|
const key = text as keyof typeof IMAGES_TYPE_MAP;
|
||||||
return <Tooltip>{IMAGES_TYPE_MAP[key] || '--'}</Tooltip>;
|
return text ? IMAGES_TYPE_MAP[key] : '--';
|
||||||
},
|
},
|
||||||
defaultVisible: true,
|
defaultVisible: true,
|
||||||
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm }) => (
|
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm }) => (
|
||||||
|
@ -193,13 +188,17 @@ const ImageList: React.FC = () => {
|
||||||
dataIndex: 'storage_path',
|
dataIndex: 'storage_path',
|
||||||
width: 140,
|
width: 140,
|
||||||
defaultVisible: true,
|
defaultVisible: true,
|
||||||
|
ellipsis: true,
|
||||||
|
render: (text: string) => text ? <Tooltip title={text} placement="topLeft">{text}</Tooltip>: '--'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'bt_path',
|
key: 'bt_path',
|
||||||
title: 'BT路径',
|
title: 'BT路径',
|
||||||
dataIndex: 'bt_path',
|
dataIndex: 'bt_path',
|
||||||
width: 140,
|
width: 250,
|
||||||
defaultVisible: true,
|
defaultVisible: true,
|
||||||
|
ellipsis: true,
|
||||||
|
render: (text: string) => text ? <Tooltip title={text} placement="topLeft">{text}</Tooltip>:'--'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'image_version',
|
key: 'image_version',
|
||||||
|
@ -207,6 +206,9 @@ const ImageList: React.FC = () => {
|
||||||
dataIndex: 'image_version',
|
dataIndex: 'image_version',
|
||||||
width: 100,
|
width: 100,
|
||||||
defaultVisible: true,
|
defaultVisible: true,
|
||||||
|
ellipsis: true,
|
||||||
|
render: (text: string) =>
|
||||||
|
text ? <Tooltip title={text}>{text}</Tooltip> : '--',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'os_version',
|
key: 'os_version',
|
||||||
|
@ -214,13 +216,16 @@ const ImageList: React.FC = () => {
|
||||||
dataIndex: 'os_version',
|
dataIndex: 'os_version',
|
||||||
width: 100,
|
width: 100,
|
||||||
defaultVisible: true,
|
defaultVisible: true,
|
||||||
|
ellipsis: true,
|
||||||
|
render: (text: string) =>
|
||||||
|
text ? <Tooltip title={text}>{text}</Tooltip> : '--',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'image_status',
|
key: 'image_status',
|
||||||
title: '镜像状态',
|
title: '镜像状态',
|
||||||
dataIndex: 'image_status',
|
dataIndex: 'image_status',
|
||||||
width: 80,
|
width: 80,
|
||||||
render: (text: number) => <Tooltip>{getStatusTag(text)}</Tooltip>,
|
render: (text: number) => (text ? getStatusTag(text) : '--'),
|
||||||
defaultVisible: true,
|
defaultVisible: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -228,10 +233,16 @@ const ImageList: React.FC = () => {
|
||||||
title: '创建时间',
|
title: '创建时间',
|
||||||
dataIndex: 'create_time',
|
dataIndex: 'create_time',
|
||||||
width: 180,
|
width: 180,
|
||||||
render: (text: string) => (
|
render: (text: string) =>
|
||||||
<Tooltip>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}</Tooltip>
|
text ? (
|
||||||
),
|
<Tooltip title={dayjs(text).format('YYYY-MM-DD HH:mm:ss')}>
|
||||||
|
{text ? dayjs(text).format('YYYY-MM-DD HH:mm:ss') : '--'}
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
'--'
|
||||||
|
),
|
||||||
defaultVisible: true,
|
defaultVisible: true,
|
||||||
|
ellipsis: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'action',
|
key: 'action',
|
||||||
|
@ -288,7 +299,6 @@ const ImageList: React.FC = () => {
|
||||||
const apiParams = {
|
const apiParams = {
|
||||||
...getApiParams(),
|
...getApiParams(),
|
||||||
};
|
};
|
||||||
console.log('apiParams', apiParams);
|
|
||||||
|
|
||||||
const imagesRes = await getImagesList(apiParams);
|
const imagesRes = await getImagesList(apiParams);
|
||||||
if (imagesRes.code == CODE) {
|
if (imagesRes.code == CODE) {
|
||||||
|
@ -301,7 +311,7 @@ const ImageList: React.FC = () => {
|
||||||
...tableParams.pagination,
|
...tableParams.pagination,
|
||||||
current: imagesRes.data?.page_num || 1,
|
current: imagesRes.data?.page_num || 1,
|
||||||
total: imagesRes.data?.total || 0,
|
total: imagesRes.data?.total || 0,
|
||||||
pageSize: tableParams.pagination.pageSize || 10,
|
pageSize: tableParams.pagination?.pageSize || 10,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -319,7 +329,7 @@ const ImageList: React.FC = () => {
|
||||||
2: { color: 'red', text: '失败' },
|
2: { color: 'red', text: '失败' },
|
||||||
};
|
};
|
||||||
const config = statusMap[status as keyof typeof statusMap];
|
const config = statusMap[status as keyof typeof statusMap];
|
||||||
return <Tag color={config.color}>{config.text}</Tag>;
|
return <Tag color={config?.color}>{config.text}</Tag>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleViewDetail = (record: IMAGES.ImageItem) => {
|
const handleViewDetail = (record: IMAGES.ImageItem) => {
|
||||||
|
@ -328,8 +338,6 @@ const ImageList: React.FC = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (record: IMAGES.ImageItem) => {
|
const handleDelete = (record: IMAGES.ImageItem) => {
|
||||||
console.log(record);
|
|
||||||
|
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: '确认删除',
|
title: '确认删除',
|
||||||
content: `确定要删除镜像 "${record.image_name}" 吗?`,
|
content: `确定要删除镜像 "${record.image_name}" 吗?`,
|
||||||
|
@ -424,12 +432,6 @@ const ImageList: React.FC = () => {
|
||||||
|
|
||||||
const handleSearch = useCallback(
|
const handleSearch = useCallback(
|
||||||
(searchValue: string) => {
|
(searchValue: string) => {
|
||||||
// 只有当搜索值变化时才发送请求
|
|
||||||
// if (searchInputRef.current === searchValue) return;
|
|
||||||
|
|
||||||
console.log('实际触发搜索:', searchValue);
|
|
||||||
// searchInputRef.current = searchValue;
|
|
||||||
|
|
||||||
const currentTableParams = tableParamsRef.current;
|
const currentTableParams = tableParamsRef.current;
|
||||||
updateParams({
|
updateParams({
|
||||||
search: {
|
search: {
|
||||||
|
@ -455,7 +457,6 @@ const ImageList: React.FC = () => {
|
||||||
// 取消所有未执行的防抖请求
|
// 取消所有未执行的防抖请求
|
||||||
debouncedSearch.cancel();
|
debouncedSearch.cancel();
|
||||||
immediateSearch.cancel();
|
immediateSearch.cancel();
|
||||||
console.log('输入变化:', value);
|
|
||||||
|
|
||||||
// 清空时立即触发搜索
|
// 清空时立即触发搜索
|
||||||
if (value === '') {
|
if (value === '') {
|
||||||
|
@ -469,8 +470,6 @@ const ImageList: React.FC = () => {
|
||||||
|
|
||||||
// 修改回车搜索处理
|
// 修改回车搜索处理
|
||||||
const handleEnterSearch = (value: string) => {
|
const handleEnterSearch = (value: string) => {
|
||||||
console.log('回车搜索:', value);
|
|
||||||
|
|
||||||
// 回车搜索时取消未执行的防抖
|
// 回车搜索时取消未执行的防抖
|
||||||
debouncedSearch.cancel();
|
debouncedSearch.cancel();
|
||||||
immediateSearch.cancel();
|
immediateSearch.cancel();
|
||||||
|
|
|
@ -94,6 +94,7 @@ export const calculateMD5InChunks = (
|
||||||
* @returns 格式化后的时间字符串
|
* @returns 格式化后的时间字符串
|
||||||
*/
|
*/
|
||||||
export const formatTime = (seconds: number): string => {
|
export const formatTime = (seconds: number): string => {
|
||||||
|
|
||||||
const { HOUR_LABEL, MINUTE_LABEL, SECOND_LABEL } = TIME_FORMAT_CONFIG;
|
const { HOUR_LABEL, MINUTE_LABEL, SECOND_LABEL } = TIME_FORMAT_CONFIG;
|
||||||
|
|
||||||
const h = Math.floor(seconds / 3600);
|
const h = Math.floor(seconds / 3600);
|
||||||
|
|
|
@ -59,6 +59,12 @@ declare namespace IMAGES {
|
||||||
render: (value: any) => React.ReactNode;
|
render: (value: any) => React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type UploadChunkResult = {
|
||||||
|
success: boolean;
|
||||||
|
message?: string;
|
||||||
|
status?: boolean; // 是否应该停止上传
|
||||||
|
};
|
||||||
|
|
||||||
export type ImageDetailField =
|
export type ImageDetailField =
|
||||||
| ImageDetailFieldWithoutRender
|
| ImageDetailFieldWithoutRender
|
||||||
| ImageDetailFieldWithRender;
|
| ImageDetailFieldWithRender;
|
||||||
|
|
Loading…
Reference in New Issue