报名表单
All checks were successful
Deploy to Server / deploy (push) Successful in 36s

This commit is contained in:
jeremygan2021
2026-02-23 15:28:41 +08:00
parent c7e75de8be
commit c3fab398bb
2 changed files with 63 additions and 39 deletions

View File

@@ -38,14 +38,19 @@ class ActivityViewSet(viewsets.ReadOnlyModelViewSet):
activity = self.get_object() activity = self.get_object()
# Check if already signed up (and not cancelled) # 1. Check confirmed signup
existing_signup = ActivitySignup.objects.filter(activity=activity, user=user).exclude(status='cancelled').first() if ActivitySignup.objects.filter(activity=activity, user=user, status='confirmed').exists():
if existing_signup:
return Response({'error': '您已报名该活动'}, status=400) return Response({'error': '您已报名该活动'}, status=400)
# Check limit (exclude cancelled) # 2. Get pending signup (for retry)
current_count = activity.signups.exclude(status='cancelled').count() pending_signup = ActivitySignup.objects.filter(activity=activity, user=user, status='pending').first()
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) return Response({'error': '活动名额已满'}, status=400)
# Get signup info # Get signup info
@@ -77,32 +82,43 @@ class ActivityViewSet(viewsets.ReadOnlyModelViewSet):
import time import time
from wechatpayv3 import WeChatPayType from wechatpayv3 import WeChatPayType
# Create Order # Create or Get Order
# Check if there is a pending order order = None
pending_order = Order.objects.filter( if pending_signup and pending_signup.order:
wechat_user=user, # Reuse existing order if it's pending
activity=activity, if pending_signup.order.status == 'pending':
status='pending' order = pending_signup.order
).first() # 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: if not order:
order = pending_order # Check independent pending order
# Update info if needed? Maybe not. pending_order = Order.objects.filter(
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, wechat_user=user,
activity=activity, activity=activity,
total_price=activity.price, status='pending'
status='pending', ).first()
quantity=1,
customer_name=contact_name, if pending_order:
phone_number=contact_phone, order = pending_order
shipping_address=activity.location or '线下活动', # 使用活动地点作为发货地址 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 # Generate Pay Code
out_trade_no = f"ACT{activity.id}U{user.id}T{int(time.time())}" 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): if code in range(200, 300):
code_url = result.get('code_url') code_url = result.get('code_url')
# Create a pending signup record so we can update it later if pending_signup:
ActivitySignup.objects.create( pending_signup.signup_info = signup_info
activity=activity, pending_signup.order = order
user=user, pending_signup.save()
signup_info=signup_info, else:
status='pending', ActivitySignup.objects.create(
order=order activity=activity,
) user=user,
signup_info=signup_info,
status='pending',
order=order
)
return Response({ return Response({
'payment_required': True, 'payment_required': True,

View File

@@ -3,7 +3,7 @@ import React, { useState, useEffect } from 'react';
import { useParams, useNavigate } from 'react-router-dom'; import { useParams, useNavigate } from 'react-router-dom';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { motion, useScroll, useTransform } from 'framer-motion'; 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 confetti from 'canvas-confetti';
import { message, Spin, Button, Result, Modal, Form, Input, Select, Radio, Checkbox, Upload } from 'antd'; import { message, Spin, Button, Result, Modal, Form, Input, Select, Radio, Checkbox, Upload } from 'antd';
import { getActivityDetail, signUpActivity, queryOrderStatus } from '../../api'; import { getActivityDetail, signUpActivity, queryOrderStatus } from '../../api';
@@ -316,6 +316,10 @@ const ActivityDetail = () => {
<UserOutlined /> <UserOutlined />
<span>{activity.current_signups || 0} / {activity.max_participants} 已报名</span> <span>{activity.current_signups || 0} / {activity.max_participants} 已报名</span>
</div> </div>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<PayCircleOutlined />
<span>{activity.is_paid ? `¥${activity.price}` : '免费'}</span>
</div>
</div> </div>
<div style={{ display: 'flex', gap: 10 }}> <div style={{ display: 'flex', gap: 10 }}>