报名表单
All checks were successful
Deploy to Server / deploy (push) Successful in 37s

This commit is contained in:
jeremygan2021
2026-02-23 15:39:47 +08:00
parent c3fab398bb
commit 0bf5f94483
5 changed files with 52 additions and 29 deletions

View File

@@ -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
from shop.serializers import WeChatUserSerializer, ESP32ConfigSerializer, ServiceSerializer, VCCourseSerializer, OrderSerializer
from .utils import get_current_wechat_user
class ActivitySerializer(serializers.ModelSerializer):
@@ -47,10 +47,11 @@ 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']
fields = ['id', 'activity', 'activity_info', 'user', 'signup_time', 'status', 'signup_info', 'order']
read_only_fields = ['signup_time', 'status', 'user']
def get_activity_info(self, obj):

View File

@@ -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'),
}

View File

@@ -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 = {

View File

@@ -1,8 +1,7 @@
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 { queryMyOrders, getMySignups } from '../api';
import { motion } from 'framer-motion';
import api, { getMySignups } from '../api';
import LoginModal from '../components/LoginModal';
import { useAuth } from '../context/AuthContext';
import { useNavigate } from 'react-router-dom';
@@ -41,8 +40,6 @@ 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/'),
@@ -344,7 +341,7 @@ const MyOrders = () => {
onLoginSuccess={(userData) => {
login(userData);
if (userData.phone_number) {
handleQueryOrders(userData.phone_number);
handleQueryData();
}
}}
/>

View File

@@ -28,6 +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 [form] = Form.useForm();
// Header animation: transparent to white with shadow
@@ -96,6 +97,7 @@ const ActivityDetail = () => {
// 检查是否需要支付
if (data.payment_required) {
setPaymentInfo(data);
setIsPaidSuccess(false);
// 先关闭报名表单,确保层级正确
setSignupFormVisible(false);
// 延迟一点点时间打开支付弹窗,避免状态更新冲突
@@ -125,14 +127,13 @@ const ActivityDetail = () => {
// Polling for payment status
useEffect(() => {
let timer;
if (paymentModalVisible && paymentInfo?.order_id) {
if (paymentModalVisible && paymentInfo?.order_id && !isPaidSuccess) {
timer = setInterval(async () => {
try {
const response = await queryOrderStatus(paymentInfo.order_id);
if (response.data.status === 'paid') {
setIsPaidSuccess(true);
message.success('支付成功,报名已确认!');
setPaymentModalVisible(false);
setPaymentInfo(null);
// Trigger success effects
confetti({
@@ -152,7 +153,7 @@ const ActivityDetail = () => {
}, 3000);
}
return () => clearInterval(timer);
}, [paymentModalVisible, paymentInfo, id, queryClient]);
}, [paymentModalVisible, paymentInfo, id, queryClient, isPaidSuccess]);
const handleShare = async () => {
const url = window.location.href;
@@ -475,10 +476,27 @@ const ActivityDetail = () => {
</Form>
</Modal>
<Modal
title="微信支付"
title={isPaidSuccess ? "支付成功" : "微信支付"}
open={paymentModalVisible}
onCancel={() => setPaymentModalVisible(false)}
footer={null}
onCancel={() => {
setPaymentModalVisible(false);
if (isPaidSuccess) {
setPaymentInfo(null);
}
}}
footer={[
<Button
key="ok"
type="primary"
disabled={!isPaidSuccess}
onClick={() => {
setPaymentModalVisible(false);
setPaymentInfo(null);
}}
>
{isPaidSuccess ? '完成' : '等待支付...'}
</Button>
]}
destroyOnHidden
width={360}
zIndex={1001} // 确保层级高于其他弹窗
@@ -486,20 +504,26 @@ const ActivityDetail = () => {
<div style={{ textAlign: 'center', padding: '20px 0' }}>
{paymentInfo?.code_url ? (
<>
<div style={{ background: '#fff', padding: 10, display: 'inline-block' }}>
<QRCodeSVG value={paymentInfo.code_url} size={200} />
</div>
<p style={{ marginTop: 20, fontSize: 18, fontWeight: 'bold' }}>¥{paymentInfo.price}</p>
<p style={{ color: '#666' }}>请使用微信扫一扫支付</p>
{!isPaidSuccess ? (
<>
<div style={{ background: '#fff', padding: 10, display: 'inline-block' }}>
<QRCodeSVG value={paymentInfo.code_url} size={200} />
</div>
<p style={{ marginTop: 20, fontSize: 18, fontWeight: 'bold' }}>¥{paymentInfo.price}</p>
<p style={{ color: '#666' }}>请使用微信扫一扫支付</p>
</>
) : (
<div style={{ padding: '20px 0' }}>
<div style={{ fontSize: 48, color: '#52c41a', marginBottom: 16 }}>
<i className="anticon anticon-check-circle"><svg viewBox="64 64 896 896" focusable="false" data-icon="check-circle" width="1em" height="1em" fill="currentColor" aria-hidden="true"><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm193.5 301.7l-210.6 292a31.8 31.8 0 01-51.7 0L318.5 484.9c-3.8-5.3 0-12.7 6.5-12.7h46.9c10.2 0 19.9 4.9 25.9 13.3l71.2 98.8 157.2-218c6-8.3 15.6-13.3 25.9-13.3H699c6.5 0 10.3 7.4 6.5 12.7z"></path></svg></i>
</div>
<p style={{ fontSize: 16, fontWeight: 'bold' }}>支付成功</p>
</div>
)}
</>
) : (
<Spin tip="正在生成二维码..." />
)}
<div style={{ marginTop: 20 }}>
<Button type="primary" onClick={() => window.location.reload()}>
我已支付
</Button>
</div>
</div>
</Modal>