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' })
}
}