diff --git a/backend/community/migrations/0011_activity_auto_confirm_alter_activitysignup_status.py b/backend/community/migrations/0011_activity_auto_confirm_alter_activitysignup_status.py new file mode 100644 index 0000000..2e48ca4 --- /dev/null +++ b/backend/community/migrations/0011_activity_auto_confirm_alter_activitysignup_status.py @@ -0,0 +1,23 @@ +# Generated by Django 6.0.1 on 2026-02-23 08:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('community', '0010_activity_is_paid_activity_price_activitysignup_order'), + ] + + operations = [ + migrations.AddField( + model_name='activity', + name='auto_confirm', + field=models.BooleanField(default=False, help_text='开启后,付费活动支付成功或免费活动报名后直接显示报名成功,不需要审核', verbose_name='无需审核'), + ), + migrations.AlterField( + model_name='activitysignup', + name='status', + field=models.CharField(choices=[('unpaid', '待支付'), ('pending', '审核中'), ('confirmed', '报名成功'), ('cancelled', '已取消')], default='confirmed', max_length=20, verbose_name='状态'), + ), + ] diff --git a/backend/community/models.py b/backend/community/models.py index d810aa3..e2bcbc2 100644 --- a/backend/community/models.py +++ b/backend/community/models.py @@ -19,6 +19,7 @@ class Activity(models.Model): price = models.DecimalField(max_digits=10, decimal_places=2, default=0, verbose_name="报名费用") is_active = models.BooleanField(default=True, verbose_name="是否启用") + auto_confirm = models.BooleanField(default=False, verbose_name="无需审核", help_text="开启后,付费活动支付成功或免费活动报名后直接显示报名成功,不需要审核") # 常用报名信息开关 ask_name = models.BooleanField(default=False, verbose_name="收集姓名") @@ -72,6 +73,7 @@ class ActivitySignup(models.Model): 活动报名记录 """ STATUS_CHOICES = ( + ('unpaid', '待支付'), ('pending', '审核中'), ('confirmed', '报名成功'), ('cancelled', '已取消'), diff --git a/backend/community/views.py b/backend/community/views.py index 15b9be0..81c5b55 100644 --- a/backend/community/views.py +++ b/backend/community/views.py @@ -156,13 +156,14 @@ class ActivityViewSet(viewsets.ReadOnlyModelViewSet): if pending_signup: pending_signup.signup_info = signup_info pending_signup.order = order + pending_signup.status = 'unpaid' # Explicitly set to unpaid pending_signup.save() else: ActivitySignup.objects.create( activity=activity, user=user, signup_info=signup_info, - status='pending', + status='unpaid', order=order ) @@ -177,11 +178,14 @@ class ActivityViewSet(viewsets.ReadOnlyModelViewSet): return Response({'error': '支付接口调用失败', 'detail': result}, status=500) # Free Activity Signup + # Check auto_confirm + status_val = 'confirmed' if activity.auto_confirm else 'pending' + signup = ActivitySignup.objects.create( activity=activity, user=user, signup_info=signup_info, - status='confirmed' + status=status_val ) serializer = ActivitySignupSerializer(signup) return Response(serializer.data, status=201) diff --git a/backend/config/settings.py b/backend/config/settings.py index 24ceae8..7119072 100644 --- a/backend/config/settings.py +++ b/backend/config/settings.py @@ -100,8 +100,8 @@ DATABASES = { } # 从环境变量获取数据库配置 (Docker 环境会自动注入这些变量) -#DB_HOST = os.environ.get('DB_HOST', '121.43.104.161') -DB_HOST = os.environ.get('DB_HOST', '6.6.6.66') +DB_HOST = os.environ.get('DB_HOST', '121.43.104.161') +#DB_HOST = os.environ.get('DB_HOST', '6.6.6.66') if DB_HOST: DATABASES['default'] = { 'ENGINE': 'django.db.backends.postgresql', @@ -109,8 +109,8 @@ if DB_HOST: 'USER': os.environ.get('DB_USER', 'market'), 'PASSWORD': os.environ.get('DB_PASSWORD', '123market'), 'HOST': DB_HOST, - #'PORT': os.environ.get('DB_PORT', '6433'), - 'PORT': os.environ.get('DB_PORT', '5432'), + 'PORT': os.environ.get('DB_PORT', '6433'), + #'PORT': os.environ.get('DB_PORT', '5432'), } diff --git a/backend/shop/views.py b/backend/shop/views.py index 8e95ab9..67f8cad 100644 --- a/backend/shop/views.py +++ b/backend/shop/views.py @@ -515,9 +515,11 @@ def payment_finish(request): from community.models import ActivitySignup signup = ActivitySignup.objects.filter(order=order).first() if signup: - signup.status = 'confirmed' + # Determine status based on activity setting + new_status = 'confirmed' if signup.activity.auto_confirm else 'pending' + signup.status = new_status signup.save() - print(f"活动报名状态已更新: {signup.id}") + print(f"活动报名状态已更新: {signup.id} -> {new_status}") except Exception as e: print(f"更新活动报名状态失败: {str(e)}") diff --git a/frontend/src/pages/activity/Detail.jsx b/frontend/src/pages/activity/Detail.jsx index 8ff0691..64d2a57 100644 --- a/frontend/src/pages/activity/Detail.jsx +++ b/frontend/src/pages/activity/Detail.jsx @@ -42,33 +42,19 @@ const ActivityDetail = () => { queryFn: async () => { try { const res = await getActivityDetail(id); - return res.data; + const data = res.data; + // Map status logic + // is_signed_up is only true if status='confirmed' + // my_signup_status can be 'unpaid', 'pending', 'confirmed' + return data; } catch (err) { throw new Error(err.response?.data?.detail || 'Failed to load activity'); } }, - staleTime: 0, // Ensure fresh data - refetchOnMount: 'always', // Force refetch on mount + staleTime: 0, + refetchOnMount: 'always', }); - //// / - // Force a refresh if needed (as requested by user) - useEffect(() => { - // 1. Force React Query refetch - refetch(); - - // 2. Hard refresh logic after 1 second delay - const timer = setTimeout(() => { - const hasRefreshedKey = `has_refreshed_activity_${id}`; - if (!sessionStorage.getItem(hasRefreshedKey)) { - sessionStorage.setItem(hasRefreshedKey, 'true'); - window.location.reload(); - } - }, 0); - - return () => clearTimeout(timer); - }, [id, refetch]); - // Auto-fill form fields when the signup form becomes visible useEffect(() => { if (signupFormVisible && user && activity?.signup_form_config) { @@ -162,6 +148,37 @@ const ActivityDetail = () => { return () => clearInterval(timer); }, [paymentModalVisible, paymentInfo, id, queryClient]); + const getButtonText = () => { + if (signUpMutation.isPending) return '提交中...'; + if (activity.is_signed_up) return '已报名'; + if (activity.my_signup_status === 'pending') return '审核中'; + if (activity.my_signup_status === 'unpaid') return '去支付'; + + const isEnded = new Date(activity.end_time) < new Date(); + if (isEnded) return '活动已结束'; + + const isFull = activity.max_participants > 0 && (activity.current_signups || 0) >= activity.max_participants; + if (isFull) return '名额已满'; + + return '立即报名'; + }; + + const isButtonDisabled = () => { + if (signUpMutation.isPending) return true; + if (activity.is_signed_up) return true; + if (activity.my_signup_status === 'pending') return true; + // 'unpaid' is NOT disabled, allows payment retry + if (activity.my_signup_status === 'unpaid') return false; + + const isEnded = new Date(activity.end_time) < new Date(); + if (isEnded) return true; + + const isFull = activity.max_participants > 0 && (activity.current_signups || 0) >= activity.max_participants; + if (isFull) return true; + + return false; + }; + const handleShare = async () => { const url = window.location.href; if (navigator.share) { @@ -387,11 +404,9 @@ const ActivityDetail = () => { variants={buttonTap} whileTap="tap" onClick={handleSignUp} - disabled={signUpMutation.isPending || activity.is_signed_up || (activity.my_signup_status === 'pending' && !activity.is_paid)} + disabled={isButtonDisabled()} > - {signUpMutation.isPending ? '提交中...' : - activity.is_signed_up ? '已报名' : - (activity.my_signup_status === 'pending' ? (activity.is_paid ? '去支付' : '审核中') : '立即报名')} + {getButtonText()} diff --git a/miniprogram/src/subpackages/forum/activity/detail.tsx b/miniprogram/src/subpackages/forum/activity/detail.tsx index 18cbf62..f4a012c 100644 --- a/miniprogram/src/subpackages/forum/activity/detail.tsx +++ b/miniprogram/src/subpackages/forum/activity/detail.tsx @@ -148,12 +148,13 @@ const ActivityDetail = () => { const hasConfirmed = activity.has_signed_up const isPending = activity.my_signup_status === 'pending' + const isUnpaid = activity.my_signup_status === 'unpaid' const isPaid = activity.is_paid const canSignup = activity.is_active && !isEnded && ( - (!hasConfirmed && !isPending && !isFull) || - (isPending && isPaid) + (!hasConfirmed && !isPending && !isUnpaid && !isFull) || + (isUnpaid && isPaid) ) return ( @@ -233,9 +234,11 @@ const ActivityDetail = () => { > {submitting ? '提交中...' : ( hasConfirmed ? '您已报名' : ( - isPending ? (isPaid ? '去支付' : '审核中') : ( - isEnded ? '活动已结束' : ( - isFull ? '名额已满' : '立即报名' + isPending ? '审核中' : ( + isUnpaid ? '去支付' : ( + isEnded ? '活动已结束' : ( + isFull ? '名额已满' : '立即报名' + ) ) ) )