diff --git a/backend/community/serializers.py b/backend/community/serializers.py
index 2e6cb5c..ef3abfd 100644
--- a/backend/community/serializers.py
+++ b/backend/community/serializers.py
@@ -1,6 +1,6 @@
from rest_framework import serializers
from .models import Activity, ActivitySignup, Topic, Reply, TopicMedia, Announcement
-from shop.serializers import WeChatUserSerializer, ESP32ConfigSerializer, ServiceSerializer, VCCourseSerializer, OrderSerializer
+from shop.serializers import WeChatUserSerializer, ESP32ConfigSerializer, ServiceSerializer, VCCourseSerializer
from .utils import get_current_wechat_user
class ActivitySerializer(serializers.ModelSerializer):
@@ -47,11 +47,10 @@ class ActivitySerializer(serializers.ModelSerializer):
class ActivitySignupSerializer(serializers.ModelSerializer):
activity_info = serializers.SerializerMethodField()
- order = OrderSerializer(read_only=True)
class Meta:
model = ActivitySignup
- fields = ['id', 'activity', 'activity_info', 'user', 'signup_time', 'status', 'signup_info', 'order']
+ fields = ['id', 'activity', 'activity_info', 'user', 'signup_time', 'status', 'signup_info']
read_only_fields = ['signup_time', 'status', 'user']
def get_activity_info(self, obj):
diff --git a/backend/shop/serializers.py b/backend/shop/serializers.py
index 0e9d24a..7afbebb 100644
--- a/backend/shop/serializers.py
+++ b/backend/shop/serializers.py
@@ -207,7 +207,6 @@ 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)
@@ -216,7 +215,7 @@ class OrderSerializer(serializers.ModelSerializer):
class Meta:
model = Order
- fields = ['id', 'config', 'config_name', 'config_image', 'course', 'course_title', 'activity', 'activity_title', 'quantity', 'total_price', 'status', 'created_at', 'updated_at', 'wechat_trade_no',
+ fields = ['id', 'config', 'config_name', 'config_image', 'course', 'course_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 = {
diff --git a/frontend/src/pages/MyOrders.jsx b/frontend/src/pages/MyOrders.jsx
index 8613255..90722a4 100644
--- a/frontend/src/pages/MyOrders.jsx
+++ b/frontend/src/pages/MyOrders.jsx
@@ -1,7 +1,8 @@
import React, { useState, useEffect } from 'react';
import { Form, Input, Button, Card, List, Tag, Typography, message, Space, Statistic, Divider, Modal, Descriptions, Tabs } from 'antd';
import { MobileOutlined, LockOutlined, SearchOutlined, CarOutlined, InboxOutlined, SafetyCertificateOutlined, CheckCircleOutlined, ClockCircleOutlined, CloseCircleOutlined, UserOutlined, EnvironmentOutlined, PhoneOutlined, CalendarOutlined } from '@ant-design/icons';
-import api, { getMySignups } from '../api';
+import { queryMyOrders, getMySignups } from '../api';
+import { motion } from 'framer-motion';
import LoginModal from '../components/LoginModal';
import { useAuth } from '../context/AuthContext';
import { useNavigate } from 'react-router-dom';
@@ -40,6 +41,8 @@ const MyOrders = () => {
const handleQueryData = async () => {
setLoading(true);
try {
+ const { default: api } = await import('../api');
+
// Parallel fetch
const [ordersRes, activitiesRes] = await Promise.allSettled([
api.get('/orders/'),
@@ -341,7 +344,7 @@ const MyOrders = () => {
onLoginSuccess={(userData) => {
login(userData);
if (userData.phone_number) {
- handleQueryData();
+ handleQueryOrders(userData.phone_number);
}
}}
/>
diff --git a/frontend/src/pages/activity/Detail.jsx b/frontend/src/pages/activity/Detail.jsx
index 08959a1..119cb95 100644
--- a/frontend/src/pages/activity/Detail.jsx
+++ b/frontend/src/pages/activity/Detail.jsx
@@ -28,7 +28,7 @@ const ActivityDetail = () => {
const [signupFormVisible, setSignupFormVisible] = useState(false);
const [paymentModalVisible, setPaymentModalVisible] = useState(false);
const [paymentInfo, setPaymentInfo] = useState(null);
- const [isPaidSuccess, setIsPaidSuccess] = useState(false);
+ const [paymentStatus, setPaymentStatus] = useState('unpaid'); // 'unpaid' | 'paying' | 'paid'
const [form] = Form.useForm();
// Header animation: transparent to white with shadow
@@ -93,13 +93,17 @@ const ActivityDetail = () => {
const signUpMutation = useMutation({
mutationFn: (values) => signUpActivity(id, { signup_info: values || {} }),
- onSuccess: (data) => {
+ onSuccess: (response) => {
+ // API returns axios response object, so we need to access .data
+ const data = response.data || response; // Fallback in case it was already unwrapped
+ console.log('Signup response:', data);
+
// 检查是否需要支付
if (data.payment_required) {
setPaymentInfo(data);
- setIsPaidSuccess(false);
- // 先关闭报名表单,确保层级正确
- setSignupFormVisible(false);
+ setPaymentStatus('paying');
+
+ // 不关闭报名表单,直接打开支付弹窗
// 延迟一点点时间打开支付弹窗,避免状态更新冲突
setTimeout(() => {
setPaymentModalVisible(true);
@@ -110,6 +114,7 @@ const ActivityDetail = () => {
message.success('报名成功!');
setSignupFormVisible(false);
+ setPaymentStatus('paid'); // In case it was free but we track it
confetti({
particleCount: 150,
spread: 70,
@@ -127,13 +132,15 @@ const ActivityDetail = () => {
// Polling for payment status
useEffect(() => {
let timer;
- if (paymentModalVisible && paymentInfo?.order_id && !isPaidSuccess) {
+ if (paymentModalVisible && paymentInfo?.order_id) {
timer = setInterval(async () => {
try {
const response = await queryOrderStatus(paymentInfo.order_id);
if (response.data.status === 'paid') {
- setIsPaidSuccess(true);
- message.success('支付成功,报名已确认!');
+ message.success('支付成功,请点击“完成报名”!');
+ setPaymentModalVisible(false);
+ setPaymentInfo(null);
+ setPaymentStatus('paid');
// Trigger success effects
confetti({
@@ -153,7 +160,7 @@ const ActivityDetail = () => {
}, 3000);
}
return () => clearInterval(timer);
- }, [paymentModalVisible, paymentInfo, id, queryClient, isPaidSuccess]);
+ }, [paymentModalVisible, paymentInfo, id, queryClient]);
const handleShare = async () => {
const url = window.location.href;
@@ -180,11 +187,15 @@ const ActivityDetail = () => {
return;
}
- // Check if we need to collect info
- if (activity.signup_form_config && activity.signup_form_config.length > 0) {
+ setPaymentStatus('unpaid');
+ setPaymentInfo(null);
+
+ // Check if we need to collect info OR if it's a paid activity
+ // We want to use the modal for payment flow as well
+ if ((activity.signup_form_config && activity.signup_form_config.length > 0) || (activity.is_paid && activity.price > 0)) {
setSignupFormVisible(true);
} else {
- // Direct signup if no info needed
+ // Direct signup if no info needed and free
signUpMutation.mutate({});
}
};
@@ -395,9 +406,36 @@ const ActivityDetail = () => {
title="填写报名信息"
open={signupFormVisible}
onCancel={() => setSignupFormVisible(false)}
- onOk={form.submit}
- okText={activity?.is_paid && activity?.price > 0 ? `支付 ¥${activity.price}` : '提交报名'}
- confirmLoading={signUpMutation.isPending}
+ footer={[
+ ,
+ (activity?.is_paid && activity?.price > 0) ? (
+
¥{paymentInfo.price}
-请使用微信扫一扫支付
- > - ) : ( -支付成功
-¥{paymentInfo.price}
+请使用微信扫一扫支付
> ) : (