From 77b8376878b2ebf5696a5a5f4d9095649aa8ca3f Mon Sep 17 00:00:00 2001 From: jeremygan2021 Date: Sun, 22 Mar 2026 22:43:59 +0800 Subject: [PATCH] upload 1 --- backend/community/views.py | 5 +- .../competition/ProjectSubmission.jsx | 182 +++++++++++++----- 2 files changed, 141 insertions(+), 46 deletions(-) diff --git a/backend/community/views.py b/backend/community/views.py index 5511cc4..6361dcd 100644 --- a/backend/community/views.py +++ b/backend/community/views.py @@ -343,6 +343,8 @@ class ReplyViewSet(viewsets.ModelViewSet): return Response({'liked': liked, 'count': obj.likes.count()}) import requests +import warnings +warnings.filterwarnings('ignore', message='Unverified HTTPS request') class TopicMediaViewSet(viewsets.ViewSet): """ @@ -367,7 +369,8 @@ class TopicMediaViewSet(viewsets.ViewSet): try: # 这里的 headers 不需要 Content-Type,requests 会自动设置 multipart/form-data - response = requests.post(upload_url, files=files, timeout=30) + # 注意: verify=False 跳过SSL证书验证(data.tangledup-ai.com 证书已过期) + response = requests.post(upload_url, files=files, timeout=30, verify=False) if response.status_code == 200: data = response.json() diff --git a/frontend/src/components/competition/ProjectSubmission.jsx b/frontend/src/components/competition/ProjectSubmission.jsx index cbd52fd..101f8d1 100644 --- a/frontend/src/components/competition/ProjectSubmission.jsx +++ b/frontend/src/components/competition/ProjectSubmission.jsx @@ -31,6 +31,9 @@ const ProjectSubmission = ({ competitionId, initialValues, onCancel, onSuccess } const [form] = Form.useForm(); const [uploadedFiles, setUploadedFiles] = useState([]); const [uploadingFiles, setUploadingFiles] = useState({}); + const [pendingCoverImage, setPendingCoverImage] = useState(null); + const [pendingAttachments, setPendingAttachments] = useState([]); + const [isCreatingProject, setIsCreatingProject] = useState(false); const queryClient = useQueryClient(); useEffect(() => { @@ -66,30 +69,6 @@ const ProjectSubmission = ({ competitionId, initialValues, onCancel, onSuccess } } }, [initialValues, form]); - const createMutation = useMutation({ - mutationFn: createProject, - onSuccess: () => { - message.success('项目创建成功'); - queryClient.invalidateQueries(['projects']); - onSuccess(); - }, - onError: (error) => { - message.error(`创建失败: ${error.response?.data?.detail || error.message}`); - } - }); - - const updateMutation = useMutation({ - mutationFn: (data) => updateProject(initialValues.id, data), - onSuccess: () => { - message.success('项目更新成功'); - queryClient.invalidateQueries(['projects']); - onSuccess(); - }, - onError: (error) => { - message.error(`更新失败: ${error.response?.data?.detail || error.message}`); - } - }); - const handleUpload = ({ file, onSuccess, onError }) => { console.log('handleUpload called', file.name); @@ -144,17 +123,80 @@ const ProjectSubmission = ({ competitionId, initialValues, onCancel, onSuccess } }); }; - const onFinish = (values) => { + const uploadPendingFiles = async (projectId) => { + const uploadedFilesList = []; + + if (pendingCoverImage) { + try { + const formData = new FormData(); + formData.append('file', pendingCoverImage); + const res = await uploadProjectFile(formData); + const imageUrl = res.data.file_url_display || res.data.file_url; + await updateProject(projectId, { cover_image_url: imageUrl }); + setPendingCoverImage(null); + } catch (err) { + console.error('封面上传失败:', err); + } + } + + for (const file of pendingAttachments) { + try { + const formData = new FormData(); + formData.append('file', file); + formData.append('project', projectId); + const res = await uploadProjectFile(formData); + uploadedFilesList.push({ + uid: res.data.id, + id: res.data.id, + name: res.data.name || file.name, + url: res.data.file_url_display || res.data.file_url, + fileType: res.data.file_type, + status: 'done' + }); + } catch (err) { + console.error('文件上传失败:', err); + message.error(`文件 ${file.name} 上传失败`); + } + } + + if (uploadedFilesList.length > 0) { + setUploadedFiles(prev => [...prev, ...uploadedFilesList]); + } + setPendingAttachments([]); + }; + + const onFinish = async (values) => { + setIsCreatingProject(true); const data = { ...values, competition: competitionId, }; if (initialValues?.id) { - updateMutation.mutate(data); + await updateProject(initialValues.id, data); + if (pendingAttachments.length > 0) { + await uploadPendingFiles(initialValues.id); + } + message.success('项目更新成功'); + queryClient.invalidateQueries(['projects']); + onSuccess(); } else { - createMutation.mutate(data); + try { + const res = await createProject(data); + const projectId = res.data.id; + + if (pendingAttachments.length > 0 || pendingCoverImage) { + await uploadPendingFiles(projectId); + } + + message.success('项目创建成功'); + queryClient.invalidateQueries(['projects']); + onSuccess(); + } catch (error) { + message.error(`创建失败: ${error.response?.data?.detail || error.message}`); + } } + setIsCreatingProject(false); }; return ( @@ -205,12 +247,13 @@ const ProjectSubmission = ({ competitionId, initialValues, onCancel, onSuccess } label="封面图片" extra="支持上传本地图片,自动转换为URL" > - {initialValues ? ( - { - console.log('Cover image selected:', file.name); + { + console.log('Cover image selected:', file.name); + + if (initialValues?.id) { const formData = new FormData(); formData.append('file', file); @@ -223,13 +266,21 @@ const ProjectSubmission = ({ competitionId, initialValues, onCancel, onSuccess } .catch(err => { message.error(`上传失败: ${err.response?.data?.detail || err.message}`); }); - - return Upload.LIST_IGNORE; - }} - > - - - ) : null} + } else { + setPendingCoverImage(file); + const reader = new FileReader(); + reader.onload = () => { + form.setFieldsValue({ cover_image_url: reader.result }); + }; + reader.readAsDataURL(file); + message.info('封面已选择,提交时将自动上传'); + } + + return Upload.LIST_IGNORE; + }} + > + + - {initialValues?.id && ( + {( + {!initialValues?.id && pendingAttachments.length > 0 && ( +
+
待上传文件:
+ + {pendingAttachments.map((file, index) => ( +
+ + {getFileIcon(file.name.split('.').pop()?.toLowerCase())} + + {file.name} + +
+ ))} +
+
+ )}
- {uploadedFiles.length === 0 ? ( + {uploadedFiles.length === 0 && initialValues?.id ? (
暂无上传文件
) : ( @@ -307,8 +389,18 @@ const ProjectSubmission = ({ competitionId, initialValues, onCancel, onSuccess } message.error('文件大小不能超过 50MB'); return Upload.LIST_IGNORE; } - console.log('beforeUpload passed, manually calling handleUpload'); - handleUpload({ file, onSuccess: () => {}, onError: () => {} }); + + if (initialValues?.id) { + console.log('beforeUpload passed, manually calling handleUpload'); + handleUpload({ file, onSuccess: () => {}, onError: () => {} }); + } else { + if (pendingAttachments.length >= 5) { + message.warning('最多只能上传5个文件'); + return Upload.LIST_IGNORE; + } + setPendingAttachments(prev => [...prev, file]); + message.info('文件已添加到待上传列表,提交时将自动上传'); + } return Upload.LIST_IGNORE; }} > @@ -320,7 +412,7 @@ const ProjectSubmission = ({ competitionId, initialValues, onCancel, onSuccess }
- {initialValues?.id && (