This commit is contained in:
@@ -343,6 +343,8 @@ class ReplyViewSet(viewsets.ModelViewSet):
|
|||||||
return Response({'liked': liked, 'count': obj.likes.count()})
|
return Response({'liked': liked, 'count': obj.likes.count()})
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
import warnings
|
||||||
|
warnings.filterwarnings('ignore', message='Unverified HTTPS request')
|
||||||
|
|
||||||
class TopicMediaViewSet(viewsets.ViewSet):
|
class TopicMediaViewSet(viewsets.ViewSet):
|
||||||
"""
|
"""
|
||||||
@@ -367,7 +369,8 @@ class TopicMediaViewSet(viewsets.ViewSet):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# 这里的 headers 不需要 Content-Type,requests 会自动设置 multipart/form-data
|
# 这里的 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:
|
if response.status_code == 200:
|
||||||
data = response.json()
|
data = response.json()
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ const ProjectSubmission = ({ competitionId, initialValues, onCancel, onSuccess }
|
|||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [uploadedFiles, setUploadedFiles] = useState([]);
|
const [uploadedFiles, setUploadedFiles] = useState([]);
|
||||||
const [uploadingFiles, setUploadingFiles] = useState({});
|
const [uploadingFiles, setUploadingFiles] = useState({});
|
||||||
|
const [pendingCoverImage, setPendingCoverImage] = useState(null);
|
||||||
|
const [pendingAttachments, setPendingAttachments] = useState([]);
|
||||||
|
const [isCreatingProject, setIsCreatingProject] = useState(false);
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -66,30 +69,6 @@ const ProjectSubmission = ({ competitionId, initialValues, onCancel, onSuccess }
|
|||||||
}
|
}
|
||||||
}, [initialValues, form]);
|
}, [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 }) => {
|
const handleUpload = ({ file, onSuccess, onError }) => {
|
||||||
console.log('handleUpload called', file.name);
|
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 = {
|
const data = {
|
||||||
...values,
|
...values,
|
||||||
competition: competitionId,
|
competition: competitionId,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (initialValues?.id) {
|
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 {
|
} 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 (
|
return (
|
||||||
@@ -205,12 +247,13 @@ const ProjectSubmission = ({ competitionId, initialValues, onCancel, onSuccess }
|
|||||||
label="封面图片"
|
label="封面图片"
|
||||||
extra="支持上传本地图片,自动转换为URL"
|
extra="支持上传本地图片,自动转换为URL"
|
||||||
>
|
>
|
||||||
{initialValues ? (
|
<Upload
|
||||||
<Upload
|
showUploadList={false}
|
||||||
showUploadList={false}
|
accept="image/*"
|
||||||
accept="image/*"
|
beforeUpload={(file) => {
|
||||||
beforeUpload={(file) => {
|
console.log('Cover image selected:', file.name);
|
||||||
console.log('Cover image selected:', file.name);
|
|
||||||
|
if (initialValues?.id) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', file);
|
formData.append('file', file);
|
||||||
|
|
||||||
@@ -223,13 +266,21 @@ const ProjectSubmission = ({ competitionId, initialValues, onCancel, onSuccess }
|
|||||||
.catch(err => {
|
.catch(err => {
|
||||||
message.error(`上传失败: ${err.response?.data?.detail || err.message}`);
|
message.error(`上传失败: ${err.response?.data?.detail || err.message}`);
|
||||||
});
|
});
|
||||||
|
} 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;
|
return Upload.LIST_IGNORE;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button icon={<CloudUploadOutlined />}>选择图片</Button>
|
<Button icon={<CloudUploadOutlined />}>选择图片</Button>
|
||||||
</Upload>
|
</Upload>
|
||||||
) : null}
|
|
||||||
<Input
|
<Input
|
||||||
placeholder="上传图片或输入URL"
|
placeholder="上传图片或输入URL"
|
||||||
style={{ marginTop: 8 }}
|
style={{ marginTop: 8 }}
|
||||||
@@ -237,10 +288,41 @@ const ProjectSubmission = ({ competitionId, initialValues, onCancel, onSuccess }
|
|||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
{initialValues?.id && (
|
{(
|
||||||
<Form.Item label="项目附件 (PPT/PDF/视频/图片)">
|
<Form.Item label="项目附件 (PPT/PDF/视频/图片)">
|
||||||
|
{!initialValues?.id && pendingAttachments.length > 0 && (
|
||||||
|
<div style={{ marginBottom: 16 }}>
|
||||||
|
<div style={{ color: '#999', fontStyle: 'italic', marginBottom: 8 }}>待上传文件:</div>
|
||||||
|
<Space orientation="vertical" style={{ width: '100%' }} size="middle">
|
||||||
|
{pendingAttachments.map((file, index) => (
|
||||||
|
<div key={index} style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: '12px 16px',
|
||||||
|
background: '#fff7e6',
|
||||||
|
borderRadius: 8,
|
||||||
|
border: '1px solid #ffd591'
|
||||||
|
}}>
|
||||||
|
<span style={{ fontSize: 20, marginRight: 12 }}>
|
||||||
|
{getFileIcon(file.name.split('.').pop()?.toLowerCase())}
|
||||||
|
</span>
|
||||||
|
<span style={{ flex: 1, fontWeight: 500 }}>{file.name}</span>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
danger
|
||||||
|
onClick={() => {
|
||||||
|
setPendingAttachments(prev => prev.filter((_, i) => i !== index));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div style={{ marginBottom: 16 }}>
|
<div style={{ marginBottom: 16 }}>
|
||||||
{uploadedFiles.length === 0 ? (
|
{uploadedFiles.length === 0 && initialValues?.id ? (
|
||||||
<div style={{ color: '#999', fontStyle: 'italic' }}>暂无上传文件</div>
|
<div style={{ color: '#999', fontStyle: 'italic' }}>暂无上传文件</div>
|
||||||
) : (
|
) : (
|
||||||
<Space orientation="vertical" style={{ width: '100%' }} size="middle">
|
<Space orientation="vertical" style={{ width: '100%' }} size="middle">
|
||||||
@@ -307,8 +389,18 @@ const ProjectSubmission = ({ competitionId, initialValues, onCancel, onSuccess }
|
|||||||
message.error('文件大小不能超过 50MB');
|
message.error('文件大小不能超过 50MB');
|
||||||
return Upload.LIST_IGNORE;
|
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;
|
return Upload.LIST_IGNORE;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -320,7 +412,7 @@ const ProjectSubmission = ({ competitionId, initialValues, onCancel, onSuccess }
|
|||||||
<Form.Item>
|
<Form.Item>
|
||||||
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 10 }}>
|
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 10 }}>
|
||||||
<Button onClick={onCancel}>取消</Button>
|
<Button onClick={onCancel}>取消</Button>
|
||||||
<Button type="primary" htmlType="submit" loading={createMutation.isLoading || updateMutation.isLoading}>
|
<Button type="primary" htmlType="submit" loading={isCreatingProject}>
|
||||||
{initialValues?.id ? '保存修改' : '保存草稿'}
|
{initialValues?.id ? '保存修改' : '保存草稿'}
|
||||||
</Button>
|
</Button>
|
||||||
{initialValues?.id && (
|
{initialValues?.id && (
|
||||||
|
|||||||
Reference in New Issue
Block a user