diff --git a/backend/community/views.py b/backend/community/views.py
index aea131f..17d4f7b 100644
--- a/backend/community/views.py
+++ b/backend/community/views.py
@@ -38,14 +38,19 @@ class ActivityViewSet(viewsets.ReadOnlyModelViewSet):
activity = self.get_object()
- # Check if already signed up (and not cancelled)
- existing_signup = ActivitySignup.objects.filter(activity=activity, user=user).exclude(status='cancelled').first()
- if existing_signup:
+ # 1. Check confirmed signup
+ if ActivitySignup.objects.filter(activity=activity, user=user, status='confirmed').exists():
return Response({'error': '您已报名该活动'}, status=400)
+
+ # 2. Get pending signup (for retry)
+ pending_signup = ActivitySignup.objects.filter(activity=activity, user=user, status='pending').first()
- # Check limit (exclude cancelled)
- current_count = activity.signups.exclude(status='cancelled').count()
- if current_count >= activity.max_participants:
+ # 3. Check limit (exclude cancelled, exclude current pending)
+ query = activity.signups.exclude(status='cancelled')
+ if pending_signup:
+ query = query.exclude(id=pending_signup.id)
+
+ if query.count() >= activity.max_participants:
return Response({'error': '活动名额已满'}, status=400)
# Get signup info
@@ -77,32 +82,43 @@ class ActivityViewSet(viewsets.ReadOnlyModelViewSet):
import time
from wechatpayv3 import WeChatPayType
- # Create Order
- # Check if there is a pending order
- pending_order = Order.objects.filter(
- wechat_user=user,
- activity=activity,
- status='pending'
- ).first()
+ # Create or Get Order
+ order = None
+ if pending_signup and pending_signup.order:
+ # Reuse existing order if it's pending
+ if pending_signup.order.status == 'pending':
+ order = pending_signup.order
+ # Update contact info if needed
+ contact_name = signup_info.get('name') or signup_info.get('participant_name') or user.nickname or 'Activity User'
+ contact_phone = signup_info.get('phone') or user.phone_number or ''
+ if contact_name: order.customer_name = contact_name
+ if contact_phone: order.phone_number = contact_phone
+ order.save()
- if pending_order:
- order = pending_order
- # Update info if needed? Maybe not.
- else:
- # 优先从报名信息获取联系方式
- contact_name = signup_info.get('name') or signup_info.get('participant_name') or user.nickname or 'Activity User'
- contact_phone = signup_info.get('phone') or user.phone_number or ''
+ if not order:
+ # Check independent pending order
+ pending_order = Order.objects.filter(
+ wechat_user=user,
+ activity=activity,
+ status='pending'
+ ).first()
- order = Order.objects.create(
- wechat_user=user,
- activity=activity,
- total_price=activity.price,
- status='pending',
- quantity=1,
- customer_name=contact_name,
- phone_number=contact_phone,
- shipping_address=activity.location or '线下活动', # 使用活动地点作为发货地址
- )
+ if pending_order:
+ order = pending_order
+ else:
+ contact_name = signup_info.get('name') or signup_info.get('participant_name') or user.nickname or 'Activity User'
+ contact_phone = signup_info.get('phone') or user.phone_number or ''
+
+ order = Order.objects.create(
+ wechat_user=user,
+ activity=activity,
+ total_price=activity.price,
+ status='pending',
+ quantity=1,
+ customer_name=contact_name,
+ phone_number=contact_phone,
+ shipping_address=activity.location or '线下活动',
+ )
# Generate Pay Code
out_trade_no = f"ACT{activity.id}U{user.id}T{int(time.time())}"
@@ -128,14 +144,18 @@ class ActivityViewSet(viewsets.ReadOnlyModelViewSet):
if code in range(200, 300):
code_url = result.get('code_url')
- # Create a pending signup record so we can update it later
- ActivitySignup.objects.create(
- activity=activity,
- user=user,
- signup_info=signup_info,
- status='pending',
- order=order
- )
+ if pending_signup:
+ pending_signup.signup_info = signup_info
+ pending_signup.order = order
+ pending_signup.save()
+ else:
+ ActivitySignup.objects.create(
+ activity=activity,
+ user=user,
+ signup_info=signup_info,
+ status='pending',
+ order=order
+ )
return Response({
'payment_required': True,
diff --git a/frontend/src/pages/activity/Detail.jsx b/frontend/src/pages/activity/Detail.jsx
index 3493fdf..495c888 100644
--- a/frontend/src/pages/activity/Detail.jsx
+++ b/frontend/src/pages/activity/Detail.jsx
@@ -3,7 +3,7 @@ import React, { useState, useEffect } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { motion, useScroll, useTransform } from 'framer-motion';
-import { ArrowLeftOutlined, ShareAltOutlined, CalendarOutlined, ClockCircleOutlined, EnvironmentOutlined, UserOutlined, UploadOutlined } from '@ant-design/icons';
+import { ArrowLeftOutlined, ShareAltOutlined, CalendarOutlined, ClockCircleOutlined, EnvironmentOutlined, UserOutlined, UploadOutlined, PayCircleOutlined } from '@ant-design/icons';
import confetti from 'canvas-confetti';
import { message, Spin, Button, Result, Modal, Form, Input, Select, Radio, Checkbox, Upload } from 'antd';
import { getActivityDetail, signUpActivity, queryOrderStatus } from '../../api';
@@ -316,6 +316,10 @@ const ActivityDetail = () => {