diff --git a/web-fe/mock/images.ts b/web-fe/mock/images.ts index 727f6f5..f80a9a0 100644 --- a/web-fe/mock/images.ts +++ b/web-fe/mock/images.ts @@ -19,13 +19,15 @@ export default { image_name: `Win版 PR 2024 【支持win10、win11】.zip${ (page_num - 1) * page_size + i }`, - image_type: getRandomFormat(), + image_file_name: `Win版 PR 2024 【支持win10、win11】.zip`, + // image_type: getRandomFormat(), bt_path: `https://releases.ubuntu.com/20.04.6/ubuntu-20.04.6-desktop-amd64.iso.torrent`, image_version: '1.0.0', os_version: 'Ubuntu 20.04', image_status: Math.random() > 0.5 ? 1 : 2, storage_path: '/mock/images', create_time: +new Date(), + description: `这是一个测试镜像文件,ID: ${i}`, }); } const result = { diff --git a/web-fe/src/constants/images.constants.ts b/web-fe/src/constants/images.constants.ts index c5e99e6..ae38e3b 100644 --- a/web-fe/src/constants/images.constants.ts +++ b/web-fe/src/constants/images.constants.ts @@ -1,4 +1,4 @@ -export const CODE = "200"; +export const CODE = '200'; export const IMAGES_TYPE_MAP = { 1: 'VHD', @@ -38,6 +38,51 @@ export const MD5_CHUNK_SIZE_CONFIG = [ { maxSize: Infinity, chunkSize: 8 * 1024 * 1024 }, // >30GB : 8MB ]; +// 表单字段配置 +export const FORM_FIELDS_CONFIG: IMAGES.FormFieldConfig[] = [ + { + name: 'image_name', + label: '镜像名称', + type: 'input', + rules: [{ required: true, message: '请输入镜像名称' }], + props: { + placeholder: '请输入镜像名称', + maxLength: 50, + }, + }, + { + name: 'image_version', + label: '版本', + type: 'input', + rules: [{ required: true, message: '请输入版本号' }], + props: { + placeholder: '请输入版本号', + maxLength: 100, + }, + }, + { + name: 'os_version', + label: '操作系统', + type: 'select', + rules: [{ required: true, message: '请选择操作系统' }], + props: { + placeholder: '请选择操作系统', + options: [ + { value: 'windows', label: 'Windows' }, + { value: 'linux', label: 'Linux' }, + ], + }, + }, + { + name: 'description', + label: '描述', + type: 'textarea', + props: { + placeholder: '请输入描述', + }, + }, +]; + /** * 上传配置 */ @@ -57,6 +102,14 @@ export const UPLOAD_STATUS_MAP = { ERROR: 'error', } as const; +// 上传状态message信息显示样式映射 +export const MESSAGE_STYLE_MAP = { + ready: { color: '#666' }, + uploading: { color: '#1890ff' }, + success: { color: '#52c41a' }, + error: { color: '#f5222d' }, +}; + /** * 时间格式化配置 */ @@ -66,18 +119,29 @@ export const TIME_FORMAT_CONFIG = { SECOND_LABEL: '秒', }; +// 镜像状态映射 +export const STATUS_MAP = { + 1: { color: 'green', text: '成功' }, + 2: { color: 'red', text: '失败' }, +}; + /** * 镜像详情字段配置 */ export const IMAGE_DETAIL_FIELDS: IMAGES.ImageDetailField[] = [ { label: '镜像名称:', key: 'image_name' }, - { - label: '桌面类型:', - key: 'image_type', - render: (value: number) => - IMAGES_TYPE_MAP[value as keyof typeof IMAGES_TYPE_MAP] || '--', - }, + { label: '镜像文件:', key: 'image_file_name' }, + // { + // label: '桌面类型:', + // key: 'image_type', + // render: (value: number) => + // IMAGES_TYPE_MAP[value as keyof typeof IMAGES_TYPE_MAP] || '--', + // }, + { label: '镜像版本:', key: 'image_version' }, + { label: '操作系统:', key: 'os_version' }, + { label: '镜像状态:', key: 'image_status' }, + { label: '上传时间:', key: 'create_time' }, { label: '模板存放路径:', key: 'storage_path' }, { label: 'BT路径:', key: 'bt_path' }, - { label: '创建时间:', key: 'create_time' }, -] as const; \ No newline at end of file + { label: '描述:', key: 'description' }, +] as const; diff --git a/web-fe/src/pages/images/components/modalShow/modalShow.tsx b/web-fe/src/pages/images/components/modalShow/modalShow.tsx index 0372e91..bb677b6 100644 --- a/web-fe/src/pages/images/components/modalShow/modalShow.tsx +++ b/web-fe/src/pages/images/components/modalShow/modalShow.tsx @@ -1,19 +1,20 @@ -import { uploadChunkAPI, cancelUploadImagesAPI } from '@/services/images'; -import { Alert, Button, message, Modal, Progress, Upload } from 'antd'; -import { UploadProps } from 'antd/lib/upload'; -import React, { useEffect, useRef, useState } from 'react'; -import { v4 as uuidv4 } from 'uuid'; import { CODE, IMAGE_DETAIL_FIELDS, + STATUS_MAP, UPLOAD_CONFIG, UPLOAD_STATUS_MAP, } from '@/constants/images.constants'; import { - getChunkSize, calculateMD5InChunks, formatTime, + getChunkSize, } from '@/pages/images/utils/images'; +import { cancelUploadImagesAPI, uploadChunkAPI } from '@/services/images'; +import { Alert, Button, message, Modal, Progress, Tag, Upload } from 'antd'; +import { UploadProps } from 'antd/lib/upload'; +import React, { useEffect, useRef, useState } from 'react'; +import { v4 as uuidv4 } from 'uuid'; const { READY, UPLOADING, SUCCESS, ERROR } = UPLOAD_STATUS_MAP; @@ -21,37 +22,78 @@ const { Dragger } = Upload; /**详情弹窗 */ const ModalDetailShow = (props: any) => { const { detailVisible, setDetailVisible, selectedImage, title } = props; - return ( - setDetailVisible(false)} - footer={[ - setDetailVisible(false)}> - 关闭 - , - ]} - width={600} - > - {selectedImage && ( - - {IMAGE_DETAIL_FIELDS.map((field) => ( - - {field.label} - - {'render' in field && field.render - ? field.render(selectedImage[field.key]) - : selectedImage[field.key] || '--'} - - - ))} - - )} - - ); + const longTextFields = ['bt_path', 'description', 'storage_path']; + const getStatusTag = (status: number) => { + const config = STATUS_MAP[status as keyof typeof STATUS_MAP]; + return {config.text}; + }; + return ( + setDetailVisible(false)} + footer={[ + setDetailVisible(false)}> + 关闭 + , + ]} + width={800} // 增加宽度以容纳两列布局 + > + {selectedImage && ( + + {IMAGE_DETAIL_FIELDS.map((field) => ( + + + {field.label} + + + {'render' in field && field.render + ? field.render(selectedImage[field.key]) + : field.key === 'image_status' + ? getStatusTag(selectedImage[field.key]) + : selectedImage[field.key] || '--'} + + + ))} + + )} + + ); }; - const ImportModal: React.FC = ({ visible, onCancel, @@ -86,9 +128,8 @@ const ImportModal: React.FC = ({ const handleBeforeUnload = (e: BeforeUnloadEvent) => { if (isUploading && !uploadCompletedRef.current) { e.preventDefault(); - e.returnValue = '镜像正在上传中,确定要离开吗?'; + e.returnValue = '镜像正在上传中,确定要离开吗?'; return e.returnValue; - } }; @@ -142,7 +183,7 @@ const ImportModal: React.FC = ({ try { // 检查是否已中止 if (abortController.current?.signal.aborted) { - return {success:false}; + return { success: false }; } // 更新状态,提示正在计算MD5 @@ -210,7 +251,7 @@ const ImportModal: React.FC = ({ // 检查是否因为中止导致的错误 if (error instanceof Error && error.name === 'AbortError') { // console.log(`上传分片 ${index} 被用户取消`); - return {success:false}; + return { success: false }; } // console.error(`上传分片 ${index} 失败:`, error); return { success: false }; @@ -295,7 +336,7 @@ const ImportModal: React.FC = ({ // 2. 开始上传 const startUpload = async (file: File) => { try { - isManualCancel.current=false; + isManualCancel.current = false; uploadCompletedRef.current = false; setIsUploading(true); setUploadStatus(UPLOADING); @@ -313,7 +354,7 @@ const ImportModal: React.FC = ({ } timerRef.current = setInterval(() => { // 重新计算开始时间,基于当前时间和已用时间 - setElapsedTime((prev) => prev + 1); + setElapsedTime((prev) => prev + 1); }, 1000); // 初始化状态 diff --git a/web-fe/src/pages/images/components/uploadFileModal/uploadFileModal.less b/web-fe/src/pages/images/components/uploadFileModal/uploadFileModal.less new file mode 100644 index 0000000..40aebcb --- /dev/null +++ b/web-fe/src/pages/images/components/uploadFileModal/uploadFileModal.less @@ -0,0 +1,9 @@ +.ant-modal { + .ant-modal-header { + margin-bottom: 20px !important; + .ant-modal-title { + font-weight: 500 !important; + color: #17233d !important; + } + } +} diff --git a/web-fe/src/pages/images/components/uploadFileModal/uploadFileModal.tsx b/web-fe/src/pages/images/components/uploadFileModal/uploadFileModal.tsx new file mode 100644 index 0000000..af6ff9f --- /dev/null +++ b/web-fe/src/pages/images/components/uploadFileModal/uploadFileModal.tsx @@ -0,0 +1,748 @@ +import { + FORM_FIELDS_CONFIG, + MESSAGE_STYLE_MAP, + UPLOAD_CONFIG, + UPLOAD_STATUS_MAP, +} from '@/constants/images.constants'; +import { + calculateMD5InChunks, + formatTime, + getChunkSize, +} from '@/pages/images/utils/images'; +import { cancelUploadImagesAPI, uploadChunkAPI } from '@/services/images'; +import { + ArrowLeftOutlined, + CloseOutlined, + FileOutlined, + UploadOutlined, +} from '@ant-design/icons'; +import { + Button, + Form, + Input, + message, + Modal, + Progress, + Select, + Space, + Steps, + Upload, +} from 'antd'; +import { UploadProps } from 'antd/lib/upload'; +import React, { useEffect, useRef, useState } from 'react'; +import { v4 as uuidv4 } from 'uuid'; +import './uploadFileModal.less'; + +const { Step } = Steps; +const { READY, UPLOADING, SUCCESS, ERROR } = UPLOAD_STATUS_MAP; + +const { Dragger } = Upload; + +const ImportModal: React.FC = ({ + visible, + onCancel, + onImportSuccess, +}) => { + const [form] = Form.useForm(); + const [currentStep, setCurrentStep] = useState(0); + // 上传相关状态 + const [uploadProgress, setUploadProgress] = useState(0); + const [isUploading, setIsUploading] = useState(false); + const [uploadStatus, setUploadStatus] = useState< + 'ready' | 'uploading' | 'success' | 'error' + >('ready'); + const [uploadMessage, setUploadMessage] = useState(''); + + // 分片上传相关 + const MAX_CONCURRENT = UPLOAD_CONFIG.MAX_CONCURRENT; // 同时上传的分片数量 + const uploadQueue = useRef>([]); // 上传队列 + const completedChunks = useRef(0); // 已上传的分片数量 + const totalChunks = useRef(0); // 总分片数量 + const fileId = useRef(''); // 文件id + const fileName = useRef(''); // 文件名 + const fileSize = useRef(0); // 文件大小 + const abortController = useRef(null); // 用于取消上传 + // 上传镜像时间相关 + const [currentFile, setCurrentFile] = useState(null); + const [elapsedTime, setElapsedTime] = useState(0); // 上传时间 + const timerRef = useRef(null); + // 上传完成状态 + const uploadCompletedRef = useRef(false); + // 手动取消状态 + const isManualCancel = useRef(false); // 是否手动取消上传 + // 是否已开始上传 + const [hasStartedUpload, setHasStartedUpload] = useState(false); + + // 处理页面刷新/关闭 + useEffect(() => { + const handleBeforeUnload = (e: BeforeUnloadEvent) => { + if (isUploading && !uploadCompletedRef.current) { + e.preventDefault(); + e.returnValue = '镜像正在上传中,确定要离开吗?'; + return e.returnValue; + } + }; + + window.addEventListener('beforeunload', handleBeforeUnload); + return () => window.removeEventListener('beforeunload', handleBeforeUnload); + }, [isUploading]); + + // 计时器清理(仅组件卸载时执行) + useEffect(() => { + return () => { + if (timerRef.current) { + clearInterval(timerRef.current); + } + }; + }, []); + + useEffect(() => { + if (visible) { + resetOtherStates(); // 每次打开弹窗时重置状态 + } + }, [visible]); + + const resetOtherStates = () => { + setCurrentFile(null); + setCurrentStep(0); + form.resetFields(); + } + + // 添加重置状态函数 + const resetState = () => { + setUploadProgress(0); + setIsUploading(false); + setUploadStatus(READY); + setUploadMessage(''); + setElapsedTime(0); + setHasStartedUpload(false); + + uploadCompletedRef.current = false; + isManualCancel.current = false; + completedChunks.current = 0; + totalChunks.current = 0; + fileId.current = ''; + fileName.current = ''; + fileSize.current = 0; + uploadQueue.current = []; + + if (timerRef.current) { + clearInterval(timerRef.current); + timerRef.current = null; + } + + if (abortController.current) { + abortController.current.abort(); + abortController.current = null; + } + }; + + const validateStepOne = async () => { + try { + const values = await form.validateFields(); + return FORM_FIELDS_CONFIG.every((field) => { + if (field.rules?.some((rule) => rule.required)) { + return values[field.name]; + } + return true; + }); + } catch { + return false; + } + }; + + // 修改下一步按钮处理 + const handleNext = async () => { + const isValid = await validateStepOne(); + if (isValid) { + setCurrentStep(1); + } else { + message.error('请填写完整信息'); + } + }; + + // 修改上一步按钮处理 + const handlePrev = () => { + if (!isUploading) { + setCurrentStep(0); + } + }; + // 根据配置渲染表单字段 + const renderFormFields = () => { + return FORM_FIELDS_CONFIG.map((field) => { + switch (field.type) { + case 'input': + return ( + + + + ); + case 'select': + return ( + + + + ); + case 'textarea': + return ( + + + + ); + default: + return null; + } + }); + }; + + // 检查表单是否完整 + const isFormValid = () => { + const values = form.getFieldsValue(); + return ( + FORM_FIELDS_CONFIG.every((field) => { + // 检查必填字段 + if (field.rules?.some((rule) => rule.required)) { + return values[field.name]; + } + return true; + }) && currentFile + ); + }; + + const handleSubmit = () => { + if (!hasStartedUpload && currentFile) { + // 如果还没开始上传且有文件,则开始上传 + startUpload(currentFile); + setHasStartedUpload(true); + } else if (uploadStatus === 'success') { + // 如果上传已完成,则提交表单 + form.validateFields().then((values) => { + const formData = { + ...values, + file: { + name: fileName.current, + size: fileSize.current, + id: fileId.current, + }, + }; + console.log('提交数据:', formData); + message.success('镜像提交成功'); + onCancel(); + }); + } else if (uploadStatus === 'error') { + // 如果是上传失败状态,点击"重新上传" + if (currentFile) { + resetState(); // 重置上传状态但不重置文件 + startUpload(currentFile); + setHasStartedUpload(true); + } + } + }; + + // 上传单个分片 + const uploadChunk = async ( + chunk: Blob, + index: number, + ): Promise => { + try { + if (abortController.current?.signal.aborted) { + return { success: false }; + } + // 获取表单中的镜像信息 + const formValues = form.getFieldsValue([ + 'image_name', + 'image_version', + 'os_version', + ]); + + const chunkMD5 = await calculateMD5InChunks(chunk, fileSize.current); + + const formData = new FormData(); + formData.append('file_id', fileId.current); + formData.append('file_name', fileName.current); + formData.append('file_size', fileSize.current.toString()); + formData.append('chunk', chunk); + formData.append('chunk_md5', chunkMD5); + formData.append('chunk_size', chunk.size.toString()); + formData.append('shard_index', index.toString()); + formData.append('shard_total', totalChunks.current.toString()); + + // 添加镜像信息参数 + formData.append('image_name', formValues.image_name); + formData.append('image_version', formValues.image_version); + formData.append('os_version', formValues.os_version); + + const response = await uploadChunkAPI( + formData, + abortController.current?.signal, + ); + + if (response.success) { + if (response.status === 'completed') { + uploadCompletedRef.current = true; // 设置上传完成状态 + isManualCancel.current = false; // 重置手动取消状态 + // 文件上传完成,设置进度为100% + setUploadProgress(100); // 这里已经正确设置了100% + setIsUploading(false); + setUploadStatus(SUCCESS); + setUploadMessage('文件上传成功!请稍后查看列表。'); + message.success('文件上传成功!系统正在处理,请稍后查看列表。'); + onImportSuccess?.(); + + // 停止计时器 + if (timerRef.current) { + clearInterval(timerRef.current); + timerRef.current = null; + } + } + + return response; + } else { + console.error(`上传分片 ${index} 失败:`, response.message); + return response; + } + } catch (error) { + if (error instanceof Error && error.name === 'AbortError') { + return { success: false }; + } + return { success: false }; + } + }; + + // 3. 处理上传队列:每次处理最多 MAX_CONCURRENT 个分片 + const processUploadQueue = async () => { + const promises: Promise[] = []; + let hasError = false; + + for ( + let i = 0; + i < Math.min(MAX_CONCURRENT, uploadQueue.current.length); + i++ + ) { + const processChunk = async () => { + while ( + uploadQueue.current.length > 0 && + !abortController.current?.signal.aborted && + !hasError + ) { + const chunkItem = uploadQueue.current.shift(); + if (chunkItem) { + const { chunk, index } = chunkItem; + const result = await uploadChunk(chunk, index); + + if (result.success) { + // 只有在没有错误的情况下才更新进度 + if (!hasError) { + completedChunks.current += 1; + // 修改进度计算方式,只有在真正完成时才显示100% + let progress; + if (completedChunks.current === totalChunks.current) { + progress = 100; // 当所有分片都完成时,显示100% + } else { + progress = Math.min( + Math.floor( + (completedChunks.current / totalChunks.current) * 100, + ), + 99, // 最大显示99%,防止在最后一步完成前就显示100% + ); + } + setUploadProgress(progress); + } + } else { + if (!abortController.current?.signal.aborted && !hasError) { + hasError = true; + setIsUploading(false); + setUploadStatus(ERROR); + if (!isManualCancel.current) { + setUploadMessage(result.message || '文件上传失败'); + message.error(result.message || '文件上传失败'); + } + if (abortController.current) { + abortController.current.abort(); + } + // 停止计时器 + if (timerRef.current) { + clearInterval(timerRef.current); + timerRef.current = null; + } + } + return; + } + } + } + }; + promises.push(processChunk()); + } + await Promise.all(promises); + }; + + // 2. 开始上传 + const startUpload = async (file: File) => { + try { + isManualCancel.current = false; + uploadCompletedRef.current = false; + setIsUploading(true); + setUploadStatus(UPLOADING); + setUploadProgress(0); + setUploadMessage('正在准备上传...'); + setCurrentFile(file); + + // 启动计时器 + // 启动计时器 + setElapsedTime(0); + + // 立即更新一次时间 + setElapsedTime(Math.floor((Date.now() - Date.now()) / 1000)); + + if (timerRef.current) { + clearInterval(timerRef.current); + } + timerRef.current = setInterval(() => { + // 重新计算开始时间,基于当前时间和已用时间 + setElapsedTime((prev) => prev + 1); + }, 1000); + + // 初始化分片状态 + completedChunks.current = 0; + fileSize.current = file.size; + fileName.current = file.name; + fileId.current = uuidv4(); + const chunkSize = getChunkSize(file.size); + totalChunks.current = Math.ceil(file.size / chunkSize); + uploadQueue.current = []; + setUploadMessage(`正在分析文件... `); + + // 切割文件为分片 + for (let i = 0; i < totalChunks.current; i++) { + const start = i * chunkSize; + const end = Math.min(start + chunkSize, file.size); + const chunk = file.slice(start, end); + uploadQueue.current.push({ chunk, index: i + 1 }); + } + + setUploadMessage(`开始上传文件... `); + // 创建新的AbortController + abortController.current = new AbortController(); + + // 开始处理上传队列 + await processUploadQueue(); + } catch (error) { + console.error('上传准备失败:', error); + setIsUploading(false); + setUploadStatus(ERROR); + setUploadMessage('上传准备失败,请重试'); + message.error('上传准备失败'); + if (timerRef.current) { + clearInterval(timerRef.current); + timerRef.current = null; + } + } + }; + // 1. 处理文件上传 + const handleFileUpload: UploadProps['beforeUpload'] = (file) => { + // 检查文件扩展名 + // const allowedExtensions = ['.tar.gz', '.iso', '.qcow2']; + // const fileExtension = file.name + // .substring(file.name.lastIndexOf('.')) + // .toLowerCase(); + // const isAllowedExtension = allowedExtensions.some((ext) => + // file.name.toLowerCase().endsWith(ext), + // ); + + // if (!isAllowedExtension) { + // message.error( + // '不支持的文件类型,请上传 .tar.gz, .iso, .qcow2 格式的文件', + // ); + // return false; + // } + + // 检查文件大小 + if (file.size > UPLOAD_CONFIG.MAX_FILE_SIZE) { + // 50GB + message.error('文件大小不能超过50GB'); + return false; + } + // 设置文件但不立即上传 + setCurrentFile(file); + setHasStartedUpload(false); + return false; + // // 开始上传流程 + // startUpload(file); + // return false; // 阻止默认上传行为 + }; + + // 取消上传 + const cancelUpload = async () => { + isManualCancel.current = true; + uploadCompletedRef.current = true; + uploadQueue.current = []; + + const oldController = abortController.current; + abortController.current = new AbortController(); + if (oldController) { + oldController.abort(); + } + + if (fileId.current) { + try { + const params = new URLSearchParams(); + params.append('file_id', fileId.current); + await cancelUploadImagesAPI(params); + message.success('上传已取消'); + } catch (error) { + console.error('取消上传API调用失败:', error); + } + } + + setIsUploading(false); + setUploadStatus(READY); + setUploadProgress(0); + setHasStartedUpload(false); + + if (timerRef.current) { + clearInterval(timerRef.current); + timerRef.current = null; + } + }; + + const renderFileSize = (file: File | null) => { + if (!file) return '0 Bytes'; + + const bytes = file.size; + if (bytes === 0) return '0 Bytes'; + + const k = 1024; + const sizes = ['Bytes', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; + }; + + const renderUploadInfo = () => ( + + + + + {currentFile?.name} + + {renderFileSize(currentFile)} + + + + + {hasStartedUpload ? ( + + + {uploadMessage} + + {/* 修改Progress组件,添加format显示百分比 */} + `${percent}%`} + /> + + {formatTime(elapsedTime)} + + {isUploading ? ( + + 取消上传 + + ) : uploadStatus === 'success' ? null : ( + } + onClick={() => { + setCurrentFile(null); + setHasStartedUpload(false); + resetState(); + }} + > + 移除文件 + + )} + {/* } + title="删除文件" + // onClick={cancelUpload} + disabled={isUploading && uploadStatus !== ERROR} + /> */} + + ) : ( + } + onClick={() => { + setCurrentFile(null); + setHasStartedUpload(false); + }} + > + 移除文件 + + )} + + ); + + // 导入弹窗内容 + const renderStepContent = () => { + switch (currentStep) { + case 0: + return ( + + {renderFormFields()} + + ); + case 1: + return ( + + {!currentFile ? ( + + + + + 点击上传或拖拽文件到此处 + + 支持单个文件上传,大小限制为50G + + + ) : ( + renderUploadInfo() + )} + + ); + default: + return null; + } + }; + + const renderFooter = () => { + if (currentStep === 0) { + return [ + // + // 关闭 + // , + + 下一步 + , + ]; + } else { + return [ + } + > + 上一步 + , + 0))) || + uploadStatus === 'success' // 上传失败且已有进度时不禁用 + } + loading={isUploading} + > + {hasStartedUpload + ? uploadStatus === 'success' + ? '上传成功' // 上传成功时显示"上传成功" + : uploadStatus === 'error' + ? '重新上传' // 上传失败时显示"重新上传" + : '上传中...' + : '开始上传'} + , + + 关闭 + , + ]; + } + }; + + const handleModalCancel = () => { + if (isUploading) { + cancelUpload().finally(() => { + resetState(); // 确保取消完成后再重置 + onCancel(); + }); + } else { + resetState(); + onCancel(); + } + }; + + return ( + + + + + + + + {renderStepContent()} + + ); +}; + +export { ImportModal }; diff --git a/web-fe/src/pages/images/index.tsx b/web-fe/src/pages/images/index.tsx index 8555ab3..ae1dad3 100644 --- a/web-fe/src/pages/images/index.tsx +++ b/web-fe/src/pages/images/index.tsx @@ -1,4 +1,8 @@ -import { CODE, IMAGES_TYPE_MAP } from '@/constants/images.constants'; +import { + CODE, + IMAGES_TYPE_MAP, + STATUS_MAP, +} from '@/constants/images.constants'; import { delImagesAPI, getImagesList } from '@/services/images'; import { DeleteOutlined, @@ -21,7 +25,8 @@ import { } from 'antd'; import dayjs from 'dayjs'; import React, { useCallback, useEffect, useRef, useState } from 'react'; -import { ImportModal, ModalDetailShow } from './components/modalShow/modalShow'; +import { ModalDetailShow } from './components/modalShow/modalShow'; +import { ImportModal } from './components/uploadFileModal/uploadFileModal'; import useTableParams from './hook/hook'; import './index.less'; @@ -140,7 +145,7 @@ const ImageList: React.FC = () => { key: 'image_name', title: '镜像名称', dataIndex: 'image_name', - width: 200, + width: 150, defaultVisible: true, alwaysVisible: true, ellipsis: true, @@ -154,34 +159,51 @@ const ImageList: React.FC = () => { ), }, { - key: 'image_type', - title: '桌面类型', - dataIndex: 'image_type', - width: 120, - render: (text: number) => { - const key = text as keyof typeof IMAGES_TYPE_MAP; - return text ? IMAGES_TYPE_MAP[key] : '--'; - }, + key: 'image_file_name', + title: '镜像文件', + dataIndex: 'image_file_name', + width: 150, defaultVisible: true, - filterDropdown: ({ setSelectedKeys, selectedKeys, confirm }) => ( - 0 ? selectedKeys : ['全部']} - onClick={({ key }) => { - setSelectedKeys(key === '全部' ? [] : [key]); - confirm({ closeDropdown: true }); // 立即触发筛选并关闭下拉菜单 - }} - items={[ - { key: '全部', label: '全部' }, - ...Object.entries(IMAGES_TYPE_MAP).map(([key, value]) => ({ - key, - label: value, - })), - ]} - /> - ), - filterMultiple: false, - defaultFilteredValue: ['全部'], + alwaysVisible: true, + ellipsis: true, + render: (text: string) => + text ? ( + + {text} + + ) : ( + '--' + ), }, + // { + // key: 'image_type', + // title: '桌面类型', + // dataIndex: 'image_type', + // width: 120, + // render: (text: number) => { + // const key = text as keyof typeof IMAGES_TYPE_MAP; + // return text ? IMAGES_TYPE_MAP[key] : '--'; + // }, + // defaultVisible: true, + // filterDropdown: ({ setSelectedKeys, selectedKeys, confirm }) => ( + // 0 ? selectedKeys : ['全部']} + // onClick={({ key }) => { + // setSelectedKeys(key === '全部' ? [] : [key]); + // confirm({ closeDropdown: true }); // 立即触发筛选并关闭下拉菜单 + // }} + // items={[ + // { key: '全部', label: '全部' }, + // ...Object.entries(IMAGES_TYPE_MAP).map(([key, value]) => ({ + // key, + // label: value, + // })), + // ]} + // /> + // ), + // filterMultiple: false, + // defaultFilteredValue: ['全部'], + // }, { key: 'storage_path', title: '模板存放路径', @@ -189,16 +211,30 @@ const ImageList: React.FC = () => { width: 140, defaultVisible: true, ellipsis: true, - render: (text: string) => text ? {text}: '--' + render: (text: string) => + text ? ( + + {text} + + ) : ( + '--' + ), }, { key: 'bt_path', title: 'BT路径', dataIndex: 'bt_path', - width: 180, + width: 140, defaultVisible: true, ellipsis: true, - render: (text: string) => text ? {text}:'--' + render: (text: string) => + text ? ( + + {text} + + ) : ( + '--' + ), }, { key: 'image_version', @@ -230,7 +266,7 @@ const ImageList: React.FC = () => { }, { key: 'create_time', - title: '创建时间', + title: '上传时间', dataIndex: 'create_time', width: 160, render: (text: string) => @@ -324,11 +360,7 @@ const ImageList: React.FC = () => { } }; const getStatusTag = (status: number) => { - const statusMap = { - 1: { color: 'green', text: '成功' }, - 2: { color: 'red', text: '失败' }, - }; - const config = statusMap[status as keyof typeof statusMap]; + const config = STATUS_MAP[status as keyof typeof STATUS_MAP]; return {config.text}; }; @@ -481,7 +513,7 @@ const ImageList: React.FC = () => { return ( - setImportModalVisible(true)}>导入 + setImportModalVisible(true)}>新建
+ +
点击上传或拖拽文件到此处
+ 支持单个文件上传,大小限制为50G +