diff --git a/backend/community/models.py b/backend/community/models.py
index 71fa95f..d810aa3 100644
--- a/backend/community/models.py
+++ b/backend/community/models.py
@@ -55,9 +55,9 @@ class Activity(models.Model):
@property
def current_signups(self):
"""
- 当前有效报名人数
+ 当前有效报名人数(仅统计已确认/已支付的报名)
"""
- return self.signups.exclude(status='cancelled').count()
+ return self.signups.filter(status='confirmed').count()
def __str__(self):
return self.title
diff --git a/backend/community/serializers.py b/backend/community/serializers.py
index ef3abfd..0fb9498 100644
--- a/backend/community/serializers.py
+++ b/backend/community/serializers.py
@@ -9,6 +9,7 @@ class ActivitySerializer(serializers.ModelSerializer):
current_signups = serializers.IntegerField(read_only=True)
has_signed_up = serializers.SerializerMethodField()
is_signed_up = serializers.SerializerMethodField()
+ my_signup_status = serializers.SerializerMethodField()
class Meta:
model = Activity
@@ -17,14 +18,25 @@ class ActivitySerializer(serializers.ModelSerializer):
def get_has_signed_up(self, obj):
return self.get_is_signed_up(obj)
+ def get_my_signup_status(self, obj):
+ request = self.context.get('request')
+ if not request:
+ return None
+ user = get_current_wechat_user(request)
+ if user:
+ # Return the status of the non-cancelled signup
+ signup = obj.signups.filter(user=user).exclude(status='cancelled').first()
+ return signup.status if signup else None
+ return None
+
def get_is_signed_up(self, obj):
request = self.context.get('request')
if not request:
return False
user = get_current_wechat_user(request)
if user:
- # Check if there is a valid signup (not cancelled)
- return obj.signups.filter(user=user).exclude(status='cancelled').exists()
+ # Check if there is a valid signup (only confirmed counts)
+ return obj.signups.filter(user=user, status='confirmed').exists()
return False
def get_signup_form_config(self, obj):
diff --git a/backend/community/views.py b/backend/community/views.py
index 17d4f7b..15b9be0 100644
--- a/backend/community/views.py
+++ b/backend/community/views.py
@@ -93,6 +93,11 @@ class ActivityViewSet(viewsets.ReadOnlyModelViewSet):
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
+
+ # Ensure activity is linked
+ if not order.activity:
+ order.activity = activity
+
order.save()
if not order:
@@ -105,6 +110,9 @@ class ActivityViewSet(viewsets.ReadOnlyModelViewSet):
if pending_order:
order = pending_order
+ # Ensure shipping address is up-to-date
+ order.shipping_address = activity.location or '线下活动'
+ order.save()
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 ''
@@ -136,7 +144,8 @@ class ActivityViewSet(viewsets.ReadOnlyModelViewSet):
'total': int(activity.price * 100),
'currency': 'CNY'
},
- notify_url=wxpay._notify_url
+ notify_url=wxpay._notify_url,
+ attach=f'{{"type":"activity","activity_id":{activity.id}}}'
)
import json
diff --git a/backend/config/settings.py b/backend/config/settings.py
index 1bb69af..24ceae8 100644
--- a/backend/config/settings.py
+++ b/backend/config/settings.py
@@ -100,7 +100,7 @@ DATABASES = {
}
# 从环境变量获取数据库配置 (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')
if DB_HOST:
DATABASES['default'] = {
diff --git a/backend/shop/admin.py b/backend/shop/admin.py
index 7817c08..04e8ccf 100644
--- a/backend/shop/admin.py
+++ b/backend/shop/admin.py
@@ -373,12 +373,14 @@ class OrderAdmin(ModelAdmin):
return f"[硬件] {obj.config.name}"
if obj.course:
return f"[课程] {obj.course.title}"
+ if obj.activity:
+ return f"[活动] {obj.activity.title}"
return "未知商品"
get_item_name.short_description = "购买商品"
fieldsets = (
('订单信息', {
- 'fields': ('config', 'course', 'quantity', 'total_price', 'status', 'created_at')
+ 'fields': ('config', 'course', 'activity', 'quantity', 'total_price', 'status', 'created_at')
}),
('客户信息', {
'fields': ('customer_name', 'phone_number', 'shipping_address', 'wechat_user')
diff --git a/backend/shop/serializers.py b/backend/shop/serializers.py
index 7afbebb..173a07b 100644
--- a/backend/shop/serializers.py
+++ b/backend/shop/serializers.py
@@ -207,6 +207,7 @@ class OrderSerializer(serializers.ModelSerializer):
"""
config_name = serializers.CharField(source='config.name', read_only=True)
course_title = serializers.CharField(source='course.title', read_only=True)
+ activity_title = serializers.CharField(source='activity.title', read_only=True)
config_image = serializers.SerializerMethodField()
salesperson_name = serializers.CharField(source='salesperson.name', read_only=True)
salesperson_code = serializers.CharField(source='salesperson.code', read_only=True)
@@ -215,7 +216,7 @@ class OrderSerializer(serializers.ModelSerializer):
class Meta:
model = Order
- fields = ['id', 'config', 'config_name', 'config_image', 'course', 'course_title', 'quantity', 'total_price', 'status', 'created_at', 'updated_at', 'wechat_trade_no',
+ fields = ['id', 'config', 'config_name', 'config_image', 'course', 'course_title', 'activity', 'activity_title', 'quantity', 'total_price', 'status', 'created_at', 'updated_at', 'wechat_trade_no',
'customer_name', 'phone_number', 'shipping_address', 'ref_code', 'salesperson_name', 'salesperson_code', 'courier_name', 'tracking_number']
read_only_fields = ['total_price', 'status', 'wechat_trade_no', 'created_at', 'updated_at']
extra_kwargs = {
@@ -230,11 +231,14 @@ class OrderSerializer(serializers.ModelSerializer):
config = data.get('config')
course = data.get('course')
+ activity = data.get('activity')
- if not config and not course:
- raise serializers.ValidationError("必须选择一种商品(硬件配置或课程)")
+ if not config and not course and not activity:
+ raise serializers.ValidationError("必须选择一种商品(硬件配置、课程或活动)")
- if config and course:
+ # Count how many types are selected
+ selected_types = sum([bool(config), bool(course), bool(activity)])
+ if selected_types > 1:
raise serializers.ValidationError("一次只能购买一种类型的商品")
if config and not data.get('shipping_address'):
@@ -255,6 +259,12 @@ class OrderSerializer(serializers.ModelSerializer):
return obj.course.cover_image_url
if obj.course.cover_image:
return obj.course.cover_image.url
+ elif obj.activity:
+ # Use activity.display_banner_url logic
+ if obj.activity.banner:
+ return obj.activity.banner.url
+ if obj.activity.banner_url:
+ return obj.activity.banner_url
return None
def create(self, validated_data):
@@ -263,6 +273,7 @@ class OrderSerializer(serializers.ModelSerializer):
"""
config = validated_data.get('config')
course = validated_data.get('course')
+ activity = validated_data.get('activity')
quantity = validated_data.get('quantity', 1)
ref_code = validated_data.pop('ref_code', None)
@@ -270,6 +281,8 @@ class OrderSerializer(serializers.ModelSerializer):
validated_data['total_price'] = config.price * quantity
elif course:
validated_data['total_price'] = course.price * quantity
+ elif activity:
+ validated_data['total_price'] = activity.price * quantity
# 尝试关联销售员或分销员
if ref_code:
diff --git a/frontend/src/pages/activity/Detail.jsx b/frontend/src/pages/activity/Detail.jsx
index 119cb95..8ff0691 100644
--- a/frontend/src/pages/activity/Detail.jsx
+++ b/frontend/src/pages/activity/Detail.jsx
@@ -387,9 +387,11 @@ const ActivityDetail = () => {
variants={buttonTap}
whileTap="tap"
onClick={handleSignUp}
- disabled={signUpMutation.isPending || activity.is_signed_up}
+ disabled={signUpMutation.isPending || activity.is_signed_up || (activity.my_signup_status === 'pending' && !activity.is_paid)}
>
- {signUpMutation.isPending ? '提交中...' : activity.is_signed_up ? '已报名' : '立即报名'}
+ {signUpMutation.isPending ? '提交中...' :
+ activity.is_signed_up ? '已报名' :
+ (activity.my_signup_status === 'pending' ? (activity.is_paid ? '去支付' : '审核中') : '立即报名')}
diff --git a/miniprogram/src/pages/order/detail.tsx b/miniprogram/src/pages/order/detail.tsx
index 109fbd3..fd21d3c 100644
--- a/miniprogram/src/pages/order/detail.tsx
+++ b/miniprogram/src/pages/order/detail.tsx
@@ -74,7 +74,7 @@ export default function OrderDetail() {
商品信息
商品名称
- {order.config_name || order.course_title}
+ {order.config_name || order.course_title || (order.activity_title ? `报名活动:${order.activity_title}` : '未知商品')}
数量
diff --git a/miniprogram/src/pages/order/list.tsx b/miniprogram/src/pages/order/list.tsx
index 165f4e4..c42d40b 100644
--- a/miniprogram/src/pages/order/list.tsx
+++ b/miniprogram/src/pages/order/list.tsx
@@ -40,7 +40,9 @@ export default function OrderList() {
- {order.config_name}
+
+ {order.config_name || order.course_title || (order.activity_title ? `报名活动:${order.activity_title}` : '未知商品')}
+
x {order.quantity}
diff --git a/miniprogram/src/subpackages/forum/activity/detail.tsx b/miniprogram/src/subpackages/forum/activity/detail.tsx
index 0804ea2..18cbf62 100644
--- a/miniprogram/src/subpackages/forum/activity/detail.tsx
+++ b/miniprogram/src/subpackages/forum/activity/detail.tsx
@@ -145,7 +145,16 @@ const ActivityDetail = () => {
const isFull = activity.max_participants > 0 && (activity.current_signups || 0) >= activity.max_participants
const isEnded = new Date(activity.end_time) < new Date()
- const canSignup = activity.is_active && !isFull && !isEnded && !activity.has_signed_up
+
+ const hasConfirmed = activity.has_signed_up
+ const isPending = activity.my_signup_status === 'pending'
+ const isPaid = activity.is_paid
+
+ const canSignup = activity.is_active && !isEnded &&
+ (
+ (!hasConfirmed && !isPending && !isFull) ||
+ (isPending && isPaid)
+ )
return (
@@ -223,9 +232,11 @@ const ActivityDetail = () => {
onClick={handleSignup}
>
{submitting ? '提交中...' : (
- activity.has_signed_up ? '您已报名' : (
- isEnded ? '活动已结束' : (
- isFull ? '名额已满' : '立即报名'
+ hasConfirmed ? '您已报名' : (
+ isPending ? (isPaid ? '去支付' : '审核中') : (
+ isEnded ? '活动已结束' : (
+ isFull ? '名额已满' : '立即报名'
+ )
)
)
)}