From 4348cfffea363fdcfc9652237880e656ac025bef Mon Sep 17 00:00:00 2001 From: chenyt Date: Thu, 7 Aug 2025 11:28:17 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E9=95=9C=E5=83=8F=E7=AE=A1=E7=90=86):=20?= =?UTF-8?q?=E5=9B=BD=E9=99=85=E5=8C=96=E9=85=8D=E7=BD=AE+layout=E5=B8=83?= =?UTF-8?q?=E5=B1=80=E9=80=82=E5=BA=94+=E4=BF=AE=E6=94=B9=E9=80=BB?= =?UTF-8?q?=E8=BE=91+=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web-fe/.umirc.ts | 11 +- web-fe/package.json | 1 + web-fe/src/pages/components/Layout/index.tsx | 5 +- .../images/components/modalShow/modalShow.tsx | 118 ++++++--- web-fe/src/pages/images/index.less | 223 ++++++++++++------ web-fe/src/pages/images/index.tsx | 71 +++--- web-fe/yarn.lock | 33 +-- 7 files changed, 289 insertions(+), 173 deletions(-) diff --git a/web-fe/.umirc.ts b/web-fe/.umirc.ts index 3805e34..bed3df5 100644 --- a/web-fe/.umirc.ts +++ b/web-fe/.umirc.ts @@ -8,6 +8,11 @@ export default defineConfig({ request: {}, layout: false, outputPath: 'serve/dist', + locale: { + default: 'zh-CN', + antd: true, // 启用 antd 国际化 + baseNavigator: true, + }, // 路由配置 routes: [ { @@ -31,9 +36,9 @@ export default defineConfig({ component: '@/pages/userList', }, { - path:"/terminal", - component: '@/pages/terminal', - } + path: '/terminal', + component: '@/pages/terminal', + }, ], }, ], diff --git a/web-fe/package.json b/web-fe/package.json index 6829372..f15808e 100644 --- a/web-fe/package.json +++ b/web-fe/package.json @@ -14,6 +14,7 @@ "@ant-design/pro-components": "^2.4.4", "@umijs/max": "^4.4.11", "antd": "^5.4.0", + "dayjs": "^1.11.13", "spark-md5": "^3.0.2", "uuid": "^11.1.0" }, diff --git a/web-fe/src/pages/components/Layout/index.tsx b/web-fe/src/pages/components/Layout/index.tsx index 9ff7238..76a409a 100644 --- a/web-fe/src/pages/components/Layout/index.tsx +++ b/web-fe/src/pages/components/Layout/index.tsx @@ -117,7 +117,10 @@ const MainLayout: React.FC = () => { - + diff --git a/web-fe/src/pages/images/components/modalShow/modalShow.tsx b/web-fe/src/pages/images/components/modalShow/modalShow.tsx index 0ddbb3f..2b3d23c 100644 --- a/web-fe/src/pages/images/components/modalShow/modalShow.tsx +++ b/web-fe/src/pages/images/components/modalShow/modalShow.tsx @@ -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 } from 'react'; +import React, { useRef, useState, useEffect } from 'react'; import SparkMD5 from 'spark-md5'; import { v4 as uuidv4 } from 'uuid'; @@ -92,6 +92,33 @@ const ImportModal: React.FC = ({ const fileSize = useRef(0); // 文件大小 const abortController = useRef(null); // 用于取消上传 + // 添加重置状态函数 + const resetState = () => { + setUploadProgress(0); + setIsUploading(false); + setUploadStatus('ready'); + setUploadMessage(''); + completedChunks.current = 0; + totalChunks.current = 0; + fileId.current = ''; + fileName.current = ''; + fileSize.current = 0; + uploadQueue.current = []; + + // 如果有正在进行的上传,先中止 + if (abortController.current) { + abortController.current.abort(); + abortController.current = null; + } + }; + + // 当弹窗关闭时重置状态 + useEffect(() => { + if (!visible) { + resetState(); + } + }, [visible]); + // 5. 分块计算文件MD5 const calculateMD5InChunks = ( file: Blob, @@ -158,8 +185,6 @@ const ImportModal: React.FC = ({ formData.append('shard_index', index.toString()); formData.append('shard_total', totalChunks.current.toString()); - // 这里应该调用实际的上传API - // 示例: await uploadChunkAPI(formData); await uploadChunkAPI(formData, abortController.current?.signal); // 模拟上传过程 // await new Promise((resolve) => @@ -186,6 +211,7 @@ const ImportModal: React.FC = ({ // 3. 处理上传队列:每次处理最多 MAX_CONCURRENT 个分片 const processUploadQueue = async () => { const promises: Promise[] = []; + let hasError = false; // 添加错误标记 // 同时处理最多 MAX_CONCURRENT 个分片 for ( @@ -196,34 +222,49 @@ const ImportModal: React.FC = ({ const processChunk = async () => { while ( uploadQueue.current.length > 0 && - !abortController.current?.signal.aborted + !abortController.current?.signal.aborted && + !hasError // 添加错误检查,出错时停止处理新分片 ) { const chunkItem = uploadQueue.current.shift(); if (chunkItem) { const { chunk, index } = chunkItem; const success = await uploadChunk(chunk, index); - if (success) { - completedChunks.current += 1; - const progress = Math.round( - (completedChunks.current / totalChunks.current) * 100, - ); - setUploadProgress(progress); - // 所有分片上传完成 - if (completedChunks.current === totalChunks.current) { - setIsUploading(false); - setUploadStatus('success'); - setUploadMessage('文件上传成功!正在处理中,请稍后查看列表。'); - message.success('文件上传成功!系统正在处理,请稍后查看列表。'); - onImportSuccess?.(); + if (success) { + // 只有在没有错误的情况下才更新进度 + if (!hasError) { + completedChunks.current += 1; + const progress = Math.round( + (completedChunks.current / totalChunks.current) * 100, + ); + setUploadProgress(progress); + + // 所有分片上传完成 + if (completedChunks.current === totalChunks.current) { + setIsUploading(false); + setUploadStatus('success'); + setUploadMessage( + '文件上传成功!正在处理中,请稍后查看列表。', + ); + message.success( + '文件上传成功!系统正在处理,请稍后查看列表。', + ); + onImportSuccess?.(); + } } } else { // 上传失败处理 - if (!abortController.current?.signal.aborted) { + if (!abortController.current?.signal.aborted && !hasError) { + hasError = true; // 设置错误标记 setIsUploading(false); setUploadStatus('error'); setUploadMessage('文件上传失败,请重试'); message.error('文件上传失败'); + + // 中止其他正在进行的上传 + if (abortController.current) { + abortController.current.abort(); + } } return; } @@ -253,7 +294,8 @@ const ImportModal: React.FC = ({ totalChunks.current = Math.ceil(file.size / CHUNK_SIZE); uploadQueue.current = []; - setUploadMessage(`正在分析文件... 共 ${totalChunks.current} 个分片`); + setUploadMessage(`正在分析文件... `); + // setUploadMessage(`正在分析文件... 共 ${totalChunks.current} 个分片`); // 创建分片并添加到队列 for (let i = 0; i < totalChunks.current; i++) { @@ -263,7 +305,8 @@ const ImportModal: React.FC = ({ uploadQueue.current.push({ chunk, index: i }); } - setUploadMessage(`开始上传文件,共 ${totalChunks.current} 个分片`); + setUploadMessage(`开始上传文件... `); + // setUploadMessage(`开始上传文件,共 ${totalChunks.current} 个分片`); // 创建新的AbortController abortController.current = new AbortController(); @@ -328,10 +371,7 @@ const ImportModal: React.FC = ({ message="重要提示" description={
-
- 1. - 文件上传需要时间,请耐心等待。 -
+
1. 文件上传需要时间,请耐心等待。
2. 文件上传中请勿刷新或者离开页面,否则会导致文件上传失败。
@@ -368,23 +408,32 @@ const ImportModal: React.FC = ({ marginBottom: 8, }} > - 上传进度 - {uploadProgress}% + 上传进度 +
+ `${percent}%`} + /> +
- + {uploadMessage && (
{uploadMessage}
)} - {/* {isUploading && ( + {isUploading && (
- )} */} + )} )} @@ -397,6 +446,9 @@ const ImportModal: React.FC = ({ onCancel={() => { if (isUploading) { cancelUpload(); + } else { + // 如果不是上传状态,直接重置状态 + resetState(); } onCancel(); }} @@ -406,6 +458,8 @@ const ImportModal: React.FC = ({ onClick={() => { if (isUploading) { cancelUpload(); + } else { + resetState(); } onCancel(); }} diff --git a/web-fe/src/pages/images/index.less b/web-fe/src/pages/images/index.less index 54cdc1b..9194601 100644 --- a/web-fe/src/pages/images/index.less +++ b/web-fe/src/pages/images/index.less @@ -1,94 +1,163 @@ // 页面头部样式 .page-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 24px; + + h2 { + margin: 0; + font-size: 24px; + font-weight: 600; + color: #333; + } +} + +// 镜像列表样式 +.image-list { + height: 100%; + display: flex; + flex-direction: column; + padding: 16px; + box-sizing: border-box; + .search-box { + margin-bottom: 16px; display: flex; justify-content: space-between; - align-items: center; - margin-bottom: 24px; - - h2 { - margin: 0; - font-size: 24px; - font-weight: 600; - color: #333; + .search-input { + display: flex; + gap: 8px; + align-items: center; } } - - // 镜像列表样式 - .image-list { - padding: 10px; - .image-detail { - .detail-item { - margin-bottom: 16px; - - label { + .images-list-container { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + .images-list-table { + display: flex; + flex: 1; + overflow: hidden; + } + } + // 表格适应样式 + .ant-table-wrapper { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + + .ant-spin-nested-loading { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + + .ant-spin-container { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + + .ant-table { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + + .ant-table-container { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + + .ant-table-body { + flex: 1; + overflow-y: auto !important; + + table { + height: 100%; + } + } + } + } + } + } + } + .image-detail { + .detail-item { + margin-bottom: 16px; + + label { + font-weight: 600; + color: #333; + display: inline-block; + width: 100px; + } + + span { + color: #666; + } + + p { + margin: 8px 0 0 100px; + color: #666; + line-height: 1.6; + } + } + } +} + +// 个人资料样式 +.profile-page { + .profile-content { + .profile-header { + display: flex; + align-items: center; + gap: 16px; + margin-bottom: 16px; + + .profile-info { + h3 { + margin: 0 0 4px 0; + font-size: 20px; font-weight: 600; color: #333; - display: inline-block; - width: 100px; } - - span { - color: #666; - } - + p { - margin: 8px 0 0 100px; + margin: 0; color: #666; - line-height: 1.6; + font-size: 14px; } } } - } - - // 个人资料样式 - .profile-page { - .profile-content { - .profile-header { - display: flex; - align-items: center; - gap: 16px; - margin-bottom: 16px; - - .profile-info { - h3 { - margin: 0 0 4px 0; - font-size: 20px; - font-weight: 600; - color: #333; - } - - p { - margin: 0; - color: #666; - font-size: 14px; - } - } - } - - .quick-actions { - display: flex; - gap: 12px; - flex-wrap: wrap; - } - } - } - - // 响应式设计 - @media (max-width: 768px) { - .page-header { - flex-direction: column; - align-items: flex-start; + + .quick-actions { + display: flex; gap: 12px; + flex-wrap: wrap; } - - .profile-content { - .profile-header { - flex-direction: column; - text-align: center; - } - - .quick-actions { - justify-content: center; - } + } +} + +// 响应式设计 +@media (max-width: 768px) { + .page-header { + flex-direction: column; + align-items: flex-start; + gap: 12px; + } + + .profile-content { + .profile-header { + flex-direction: column; + text-align: center; } - } \ No newline at end of file + + .quick-actions { + justify-content: center; + } + } +} diff --git a/web-fe/src/pages/images/index.tsx b/web-fe/src/pages/images/index.tsx index 0c81cba..151d2d7 100644 --- a/web-fe/src/pages/images/index.tsx +++ b/web-fe/src/pages/images/index.tsx @@ -6,6 +6,7 @@ import { SettingOutlined, } from '@ant-design/icons'; import type { TableProps } from 'antd'; +import dayjs from 'dayjs'; import { Button, Checkbox, @@ -19,7 +20,7 @@ import { Tooltip, } from 'antd'; import React, { useEffect, useState } from 'react'; -import { ModalDetailShow, ImportModal } from './components/modalShow/modalShow'; +import { ImportModal, ModalDetailShow } from './components/modalShow/modalShow'; import './index.less'; import { ReactComponent as RefreshIcon } from '@/assets/icons/refresh.svg'; @@ -62,6 +63,12 @@ const ImageList: React.FC = () => { JSON.stringify(tableParams.filters), JSON.stringify(tableParams.keywords), ]); + + /** + * 表格参数转换 + * @param params - 表格参数对象,包含分页、过滤、排序等信息 + * @returns 转换后的接口参数对象 + */ const getRandomuserParams = (params: IMAGES.TableParams) => { const { pagination, @@ -111,11 +118,14 @@ const ImageList: React.FC = () => { if (imagesRes.error_code === ERROR_CODE) { setImages(imagesRes.data.data || []); setLoading(false); + // 直接使用后端返回的分页信息 setTableParams((prev) => ({ ...prev, pagination: { ...prev.pagination, + current: imagesRes.data.paging.page_num || 1, total: imagesRes.data.paging.total || 0, + pageSize: imagesRes.data.paging.page_size || 10, }, })); } else { @@ -268,6 +278,7 @@ const ImageList: React.FC = () => { dataIndex: 'create_time', key: 'create_time', width: 180, + render:(text:string)=>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}, ...(visibleColumns['create_time'] ? {} : { hidden: true }), }, { @@ -297,6 +308,7 @@ const ImageList: React.FC = () => { }, ].filter((column) => !column.hidden); + // 处理表格分页、过滤和排序变化 const handleTableChange: TableProps['onChange'] = ( pagination, filters, @@ -323,28 +335,27 @@ const ImageList: React.FC = () => { loadImages(); }; - // 导入成功后的回调 + // 导入镜像成功后的回调 const handleImportSuccess = () => { - // 可以在这里添加导入成功后的处理逻辑 - // 例如:刷新列表或显示提示信息 setTimeout(() => { - loadImages(); // 一段时间后刷新列表查看是否有新导入的镜像 + loadImages(); }, 5000); }; + // 自定义分页配置 + const paginationConfig = { + ...tableParams.pagination, + showTotal: (total: number) => `共 ${total} 条记录`, + showSizeChanger: true, + showQuickJumper: true, + pageSizeOptions: ['10', '20', '50', '100'], + }; + return (
-
-
- -
-
+
+ +
{
- `共 ${total} 条记录`, - // }} - /> +
+
+
+ + {detailVisible ? (