From c3fab398bb909f3134075be44702ca0c033dab76 Mon Sep 17 00:00:00 2001 From: jeremygan2021 Date: Mon, 23 Feb 2026 15:28:41 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8A=A5=E5=90=8D=E8=A1=A8=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/community/views.py | 96 ++++++++++++++++---------- frontend/src/pages/activity/Detail.jsx | 6 +- 2 files changed, 63 insertions(+), 39 deletions(-) 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 = () => { {activity.current_signups || 0} / {activity.max_participants} 已报名 +
+ + {activity.is_paid ? `¥${activity.price}` : '免费'} +