This commit is contained in:
@@ -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='状态'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -19,6 +19,7 @@ class Activity(models.Model):
|
|||||||
price = models.DecimalField(max_digits=10, decimal_places=2, default=0, verbose_name="报名费用")
|
price = models.DecimalField(max_digits=10, decimal_places=2, default=0, verbose_name="报名费用")
|
||||||
|
|
||||||
is_active = models.BooleanField(default=True, 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="收集姓名")
|
ask_name = models.BooleanField(default=False, verbose_name="收集姓名")
|
||||||
@@ -72,6 +73,7 @@ class ActivitySignup(models.Model):
|
|||||||
活动报名记录
|
活动报名记录
|
||||||
"""
|
"""
|
||||||
STATUS_CHOICES = (
|
STATUS_CHOICES = (
|
||||||
|
('unpaid', '待支付'),
|
||||||
('pending', '审核中'),
|
('pending', '审核中'),
|
||||||
('confirmed', '报名成功'),
|
('confirmed', '报名成功'),
|
||||||
('cancelled', '已取消'),
|
('cancelled', '已取消'),
|
||||||
|
|||||||
@@ -156,13 +156,14 @@ class ActivityViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
if pending_signup:
|
if pending_signup:
|
||||||
pending_signup.signup_info = signup_info
|
pending_signup.signup_info = signup_info
|
||||||
pending_signup.order = order
|
pending_signup.order = order
|
||||||
|
pending_signup.status = 'unpaid' # Explicitly set to unpaid
|
||||||
pending_signup.save()
|
pending_signup.save()
|
||||||
else:
|
else:
|
||||||
ActivitySignup.objects.create(
|
ActivitySignup.objects.create(
|
||||||
activity=activity,
|
activity=activity,
|
||||||
user=user,
|
user=user,
|
||||||
signup_info=signup_info,
|
signup_info=signup_info,
|
||||||
status='pending',
|
status='unpaid',
|
||||||
order=order
|
order=order
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -177,11 +178,14 @@ class ActivityViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
return Response({'error': '支付接口调用失败', 'detail': result}, status=500)
|
return Response({'error': '支付接口调用失败', 'detail': result}, status=500)
|
||||||
|
|
||||||
# Free Activity Signup
|
# Free Activity Signup
|
||||||
|
# Check auto_confirm
|
||||||
|
status_val = 'confirmed' if activity.auto_confirm else 'pending'
|
||||||
|
|
||||||
signup = ActivitySignup.objects.create(
|
signup = ActivitySignup.objects.create(
|
||||||
activity=activity,
|
activity=activity,
|
||||||
user=user,
|
user=user,
|
||||||
signup_info=signup_info,
|
signup_info=signup_info,
|
||||||
status='confirmed'
|
status=status_val
|
||||||
)
|
)
|
||||||
serializer = ActivitySignupSerializer(signup)
|
serializer = ActivitySignupSerializer(signup)
|
||||||
return Response(serializer.data, status=201)
|
return Response(serializer.data, status=201)
|
||||||
|
|||||||
@@ -100,8 +100,8 @@ DATABASES = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# 从环境变量获取数据库配置 (Docker 环境会自动注入这些变量)
|
# 从环境变量获取数据库配置 (Docker 环境会自动注入这些变量)
|
||||||
#DB_HOST = os.environ.get('DB_HOST', '121.43.104.161')
|
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', '6.6.6.66')
|
||||||
if DB_HOST:
|
if DB_HOST:
|
||||||
DATABASES['default'] = {
|
DATABASES['default'] = {
|
||||||
'ENGINE': 'django.db.backends.postgresql',
|
'ENGINE': 'django.db.backends.postgresql',
|
||||||
@@ -109,8 +109,8 @@ if DB_HOST:
|
|||||||
'USER': os.environ.get('DB_USER', 'market'),
|
'USER': os.environ.get('DB_USER', 'market'),
|
||||||
'PASSWORD': os.environ.get('DB_PASSWORD', '123market'),
|
'PASSWORD': os.environ.get('DB_PASSWORD', '123market'),
|
||||||
'HOST': DB_HOST,
|
'HOST': DB_HOST,
|
||||||
#'PORT': os.environ.get('DB_PORT', '6433'),
|
'PORT': os.environ.get('DB_PORT', '6433'),
|
||||||
'PORT': os.environ.get('DB_PORT', '5432'),
|
#'PORT': os.environ.get('DB_PORT', '5432'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -515,9 +515,11 @@ def payment_finish(request):
|
|||||||
from community.models import ActivitySignup
|
from community.models import ActivitySignup
|
||||||
signup = ActivitySignup.objects.filter(order=order).first()
|
signup = ActivitySignup.objects.filter(order=order).first()
|
||||||
if signup:
|
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()
|
signup.save()
|
||||||
print(f"活动报名状态已更新: {signup.id}")
|
print(f"活动报名状态已更新: {signup.id} -> {new_status}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"更新活动报名状态失败: {str(e)}")
|
print(f"更新活动报名状态失败: {str(e)}")
|
||||||
|
|
||||||
|
|||||||
@@ -42,33 +42,19 @@ const ActivityDetail = () => {
|
|||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
try {
|
try {
|
||||||
const res = await getActivityDetail(id);
|
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) {
|
} catch (err) {
|
||||||
throw new Error(err.response?.data?.detail || 'Failed to load activity');
|
throw new Error(err.response?.data?.detail || 'Failed to load activity');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
staleTime: 0, // Ensure fresh data
|
staleTime: 0,
|
||||||
refetchOnMount: 'always', // Force refetch on mount
|
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
|
// Auto-fill form fields when the signup form becomes visible
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (signupFormVisible && user && activity?.signup_form_config) {
|
if (signupFormVisible && user && activity?.signup_form_config) {
|
||||||
@@ -162,6 +148,37 @@ const ActivityDetail = () => {
|
|||||||
return () => clearInterval(timer);
|
return () => clearInterval(timer);
|
||||||
}, [paymentModalVisible, paymentInfo, id, queryClient]);
|
}, [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 handleShare = async () => {
|
||||||
const url = window.location.href;
|
const url = window.location.href;
|
||||||
if (navigator.share) {
|
if (navigator.share) {
|
||||||
@@ -387,11 +404,9 @@ const ActivityDetail = () => {
|
|||||||
variants={buttonTap}
|
variants={buttonTap}
|
||||||
whileTap="tap"
|
whileTap="tap"
|
||||||
onClick={handleSignUp}
|
onClick={handleSignUp}
|
||||||
disabled={signUpMutation.isPending || activity.is_signed_up || (activity.my_signup_status === 'pending' && !activity.is_paid)}
|
disabled={isButtonDisabled()}
|
||||||
>
|
>
|
||||||
{signUpMutation.isPending ? '提交中...' :
|
{getButtonText()}
|
||||||
activity.is_signed_up ? '已报名' :
|
|
||||||
(activity.my_signup_status === 'pending' ? (activity.is_paid ? '去支付' : '审核中') : '立即报名')}
|
|
||||||
</motion.button>
|
</motion.button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -148,12 +148,13 @@ const ActivityDetail = () => {
|
|||||||
|
|
||||||
const hasConfirmed = activity.has_signed_up
|
const hasConfirmed = activity.has_signed_up
|
||||||
const isPending = activity.my_signup_status === 'pending'
|
const isPending = activity.my_signup_status === 'pending'
|
||||||
|
const isUnpaid = activity.my_signup_status === 'unpaid'
|
||||||
const isPaid = activity.is_paid
|
const isPaid = activity.is_paid
|
||||||
|
|
||||||
const canSignup = activity.is_active && !isEnded &&
|
const canSignup = activity.is_active && !isEnded &&
|
||||||
(
|
(
|
||||||
(!hasConfirmed && !isPending && !isFull) ||
|
(!hasConfirmed && !isPending && !isUnpaid && !isFull) ||
|
||||||
(isPending && isPaid)
|
(isUnpaid && isPaid)
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -233,9 +234,11 @@ const ActivityDetail = () => {
|
|||||||
>
|
>
|
||||||
{submitting ? '提交中...' : (
|
{submitting ? '提交中...' : (
|
||||||
hasConfirmed ? '您已报名' : (
|
hasConfirmed ? '您已报名' : (
|
||||||
isPending ? (isPaid ? '去支付' : '审核中') : (
|
isPending ? '审核中' : (
|
||||||
isEnded ? '活动已结束' : (
|
isUnpaid ? '去支付' : (
|
||||||
isFull ? '名额已满' : '立即报名'
|
isEnded ? '活动已结束' : (
|
||||||
|
isFull ? '名额已满' : '立即报名'
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user