diff --git a/backend/competition/serializers.py b/backend/competition/serializers.py index 77ab292..c778af2 100644 --- a/backend/competition/serializers.py +++ b/backend/competition/serializers.py @@ -55,9 +55,19 @@ class ProjectFileSerializer(serializers.ModelSerializer): def create(self, validated_data): from django.conf import settings + from shop.utils import get_current_wechat_user + + request = self.context.get('request') + user = get_current_wechat_user(request) if request else None + + print(f"=== File Upload Debug ===") + print(f"User: {user}") + print(f"Validated data keys: {validated_data.keys()}") + file_obj = validated_data.get('file') if file_obj: + print(f"File name: {file_obj.name}, size: {file_obj.size}") ext = file_obj.name.split('.')[-1].lower() if '.' in file_obj.name else '' if ext in ['ppt', 'pptx']: validated_data['file_type'] = 'ppt' diff --git a/backend/competition/views.py b/backend/competition/views.py index adeaa15..7ee49b7 100644 --- a/backend/competition/views.py +++ b/backend/competition/views.py @@ -1,5 +1,5 @@ from rest_framework import viewsets, permissions, status, filters, serializers -from rest_framework.decorators import action, api_view, permission_classes +from rest_framework.decorators import action, api_view, permission_classes, csrf_exempt from rest_framework.response import Response from rest_framework.views import APIView from django.db.models import Q @@ -189,6 +189,7 @@ class ProjectViewSet(viewsets.ModelViewSet): return Response({"status": "submitted"}) +@csrf_exempt class ProjectFileViewSet(viewsets.ModelViewSet): """ 项目附件管理 @@ -200,13 +201,24 @@ class ProjectFileViewSet(viewsets.ModelViewSet): return ProjectFile.objects.all() def perform_create(self, serializer): - project = serializer.validated_data['project'] - user = get_current_wechat_user(self.request) - - if not user or project.contestant.user != user: - raise serializers.ValidationError("无权上传文件") + from shop.utils import get_current_wechat_user + try: + project = serializer.validated_data['project'] + user = get_current_wechat_user(self.request) - serializer.save() + print(f"=== perform_create debug ===") + print(f"User: {user}") + print(f"Project: {project}") + + if not user or project.contestant.user != user: + raise serializers.ValidationError("无权上传文件") + + serializer.save() + except Exception as e: + print(f"=== perform_create ERROR: {e} ===") + import traceback + traceback.print_exc() + raise class ScoreViewSet(viewsets.ModelViewSet): diff --git a/frontend/src/components/competition/ProjectSubmission.jsx b/frontend/src/components/competition/ProjectSubmission.jsx index 1a2c93a..cbd52fd 100644 --- a/frontend/src/components/competition/ProjectSubmission.jsx +++ b/frontend/src/components/competition/ProjectSubmission.jsx @@ -202,10 +202,39 @@ const ProjectSubmission = ({ competitionId, initialValues, onCancel, onSuccess } - } placeholder="https://example.com/image.jpg" /> + {initialValues ? ( + { + console.log('Cover image selected:', file.name); + const formData = new FormData(); + formData.append('file', file); + + uploadProjectFile(formData) + .then(res => { + const imageUrl = res.data.file_url_display || res.data.file_url; + form.setFieldsValue({ cover_image_url: imageUrl }); + message.success('封面上传成功'); + }) + .catch(err => { + message.error(`上传失败: ${err.response?.data?.detail || err.message}`); + }); + + return Upload.LIST_IGNORE; + }} + > + + + ) : null} + form.setFieldsValue({ cover_image_url: e.target.value })} + /> {initialValues?.id && ( diff --git a/miniprogram/src/api/index.ts b/miniprogram/src/api/index.ts index 7980538..6454343 100644 --- a/miniprogram/src/api/index.ts +++ b/miniprogram/src/api/index.ts @@ -94,14 +94,40 @@ export const uploadProjectFile = (filePath: string, projectId: number, fileName? 'Authorization': `Bearer ${Taro.getStorageSync('token')}` } }).then(res => { - console.log('Upload response:', res) if (res.statusCode >= 200 && res.statusCode < 300) { const data = JSON.parse(res.data) - console.log('Upload success, data:', data) + if (data.error) { + const error = new Error(data.error) as any + error.response = data + throw error + } return data } - console.error('Upload failed:', res) - throw new Error(`Upload failed: ${res.statusCode}`) + const error = new Error('Upload failed') as any + error.statusCode = res.statusCode + throw error + }) +} + +export const deleteProjectFile = (fileId: number) => { + const BASE_URL = (typeof process !== 'undefined' && process.env && process.env.TARO_APP_API_URL) || 'https://market.quant-speed.com/api' + return Taro.request({ + url: `${BASE_URL}/competition/files/${fileId}/`, + method: 'DELETE', + header: { + 'Authorization': `Bearer ${Taro.getStorageSync('token')}` + } + }).then(res => { + if (res.statusCode >= 200 && res.statusCode < 300) { + return true + } + const error = new Error('Delete failed') as any + error.statusCode = res.statusCode + try { + const data = JSON.parse(res.data) + error.response = data + } catch {} + throw error }) } @@ -120,9 +146,21 @@ export const uploadMedia = (filePath: string, type: 'image' | 'video') => { } }).then(res => { if (res.statusCode >= 200 && res.statusCode < 300) { - return JSON.parse(res.data) + const data = JSON.parse(res.data) + if (data.error) { + const error = new Error(data.error) as any + error.response = data + throw error + } + return data } - throw new Error('Upload failed') + const error = new Error('Upload failed') as any + error.statusCode = res.statusCode + try { + const data = JSON.parse(res.data) + error.response = data + } catch {} + throw error }) } diff --git a/miniprogram/src/pages/competition/project.tsx b/miniprogram/src/pages/competition/project.tsx index 80765cf..942898b 100644 --- a/miniprogram/src/pages/competition/project.tsx +++ b/miniprogram/src/pages/competition/project.tsx @@ -1,7 +1,7 @@ import { View, Text, Button, Image, Input, Textarea, Picker } from '@tarojs/components' import Taro, { useLoad, useShareAppMessage, useShareTimeline, useRouter } from '@tarojs/taro' import { useState } from 'react' -import { getProjectDetail, createProject, updateProject, uploadProjectFile, submitProject, uploadMedia, getCompetitions } from '../../api' +import { getProjectDetail, createProject, updateProject, uploadProjectFile, submitProject, uploadMedia, getCompetitions, deleteProjectFile } from '../../api' import './project.scss' export default function ProjectEdit() { @@ -89,21 +89,14 @@ export default function ProjectEdit() { Taro.showLoading({ title: '上传中...' }) - console.log('Uploading cover image:', tempFilePaths[0]) const res = await uploadMedia(tempFilePaths[0], 'image') - console.log('Cover upload result:', res) - - // res.file is the OSS URL - const coverUrl = res.file || res.file_url || res.url - console.log('Cover URL:', coverUrl) - handleInput('cover_image_url', coverUrl) + handleInput('cover_image_url', res.file) Taro.hideLoading() - Taro.showToast({ title: '上传成功', icon: 'success' }) - } catch (e) { + } catch (e: any) { Taro.hideLoading() - console.error('Cover upload error:', e) - Taro.showToast({ title: '上传失败', icon: 'none' }) + const errorMsg = e.response?.error || e.message || '上传失败' + Taro.showToast({ title: errorMsg, icon: 'none' }) } } @@ -120,52 +113,25 @@ export default function ProjectEdit() { Taro.showLoading({ title: '上传中...' }) const file = tempFiles[0] - console.log('Uploading file:', file.name, 'path:', file.path) // @ts-ignore const result = await uploadProjectFile(file.path, project.id, file.name) - console.log('Upload result:', result) - // Update file list - use file_url_display or file_url - const fileUrl = result.file_url_display || result.file_url || '' + // Update file list setProject(prev => ({ ...prev, - files: [...(prev.files || []), { - ...result, - url: fileUrl - }] + files: [...(prev.files || []), result] })) Taro.hideLoading() Taro.showToast({ title: '上传成功', icon: 'success' }) } catch (e) { Taro.hideLoading() - console.error('Upload error:', e) + console.error(e) Taro.showToast({ title: '上传失败', icon: 'none' }) } } - const handlePreviewFile = (file: any) => { - const url = file.url || file.file_url_display || file.file_url - if (!url) { - Taro.showToast({ title: '文件链接无效', icon: 'none' }) - return - } - Taro.downloadFile({ - url: url, - success: (res: any) => { - if (res.statusCode === 200) { - Taro.openDocument({ filePath: res.tempFilePath }).catch(() => { - Taro.showToast({ title: '无法打开文件', icon: 'none' }) - }) - } - }, - fail: () => { - Taro.showToast({ title: '无法打开文件', icon: 'none' }) - } - }) - } - const handleDeleteFile = (fileId) => { // API call to delete file not implemented yet? Or just remove from list? // Usually we should call delete API. For now just remove from UI. @@ -295,10 +261,10 @@ export default function ProjectEdit() { - {project.files && project.files.map((file: any, index: number) => ( - handlePreviewFile(file)}> + {project.files && project.files.map((file, index) => ( + {file.name || '未知文件'} - 点击查看 + {/* handleDeleteFile(file.id)}>删除 */} ))} {(!project.files || project.files.length === 0) && 暂无附件 (PDF/PPT/视频)} diff --git a/miniprogram/src/subpackages/forum/create/index.tsx b/miniprogram/src/subpackages/forum/create/index.tsx index e156e5e..1537b7d 100644 --- a/miniprogram/src/subpackages/forum/create/index.tsx +++ b/miniprogram/src/subpackages/forum/create/index.tsx @@ -79,12 +79,15 @@ const CreateTopic = () => { setContent(prev => prev + insertText) Taro.hideLoading() - } catch (error) { + } catch (error: any) { console.error(error) Taro.hideLoading() - // Only toast if it's an error, not cancel if (error.errMsg && error.errMsg.indexOf('cancel') === -1) { - Taro.showToast({ title: '上传失败', icon: 'none' }) + const errorMsg = error.response?.error || error.message || '上传失败' + Taro.showToast({ title: errorMsg, icon: 'none' }) + } else if (!error.errMsg) { + const errorMsg = error.response?.error || error.message || '上传失败' + Taro.showToast({ title: errorMsg, icon: 'none' }) } } } diff --git a/miniprogram/src/subpackages/forum/detail/index.tsx b/miniprogram/src/subpackages/forum/detail/index.tsx index 85453dc..8b459cb 100644 --- a/miniprogram/src/subpackages/forum/detail/index.tsx +++ b/miniprogram/src/subpackages/forum/detail/index.tsx @@ -170,10 +170,11 @@ const ForumDetail = () => { setReplyContent(prev => prev + insertText) Taro.hideLoading() - } catch (error) { + } catch (error: any) { console.error(error) Taro.hideLoading() - Taro.showToast({ title: '上传失败', icon: 'none' }) + const errorMsg = error.response?.error || error.message || '上传失败' + Taro.showToast({ title: errorMsg, icon: 'none' }) } }