diff --git a/backend/config/settings.py b/backend/config/settings.py
index 954dc43..664f04c 100644
--- a/backend/config/settings.py
+++ b/backend/config/settings.py
@@ -101,19 +101,7 @@ DATABASES = {
#从环境变量获取数据库配置 (Docker 环境会自动注入这些变量。
-DB_HOST = os.environ.get('DB_HOST', '6.6.6.66')
-if DB_HOST:
- DATABASES['default'] = {
- 'ENGINE': 'django.db.backends.postgresql',
- 'NAME': os.environ.get('DB_NAME', 'market'),
- 'USER': os.environ.get('DB_USER', 'market'),
- 'PASSWORD': os.environ.get('DB_PASSWORD', '123market'),
- 'HOST': DB_HOST,
- 'PORT': os.environ.get('DB_PORT', '5432'),
- }
-
-
-# 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',
@@ -121,10 +109,22 @@ 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'),
# }
+DB_HOST = os.environ.get('DB_HOST', '121.43.104.161')
+if DB_HOST:
+ DATABASES['default'] = {
+ 'ENGINE': 'django.db.backends.postgresql',
+ 'NAME': os.environ.get('DB_NAME', 'market'),
+ 'USER': os.environ.get('DB_USER', 'market'),
+ 'PASSWORD': os.environ.get('DB_PASSWORD', '123market'),
+ 'HOST': DB_HOST,
+ 'PORT': os.environ.get('DB_PORT', '6433'),
+ }
+
+
# Password validation
# https://docs.djangoproject.com/en/6.0/ref/settings/#auth-password-validators
diff --git a/backend/shop/admin.py b/backend/shop/admin.py
index 04e8ccf..f822092 100644
--- a/backend/shop/admin.py
+++ b/backend/shop/admin.py
@@ -235,6 +235,10 @@ class VCCourseAdmin(OrderableAdminMixin, ModelAdmin):
('基本信息', {
'fields': ('title', 'description', 'course_type', 'tag', 'price')
}),
+ ('课程安排', {
+ 'fields': ('is_fixed_schedule', 'start_time', 'end_time'),
+ 'description': '勾选“是否固定时间课程”后,请设置开始和结束时间'
+ }),
('讲师信息', {
'fields': ('instructor', 'instructor_title', 'instructor_desc', 'instructor_avatar', 'instructor_avatar_url'),
'description': '讲师头像上传和URL二选一,优先使用URL'
diff --git a/backend/shop/migrations/0033_vccourse_is_fixed_schedule_vccourse_schedule_time.py b/backend/shop/migrations/0033_vccourse_is_fixed_schedule_vccourse_schedule_time.py
new file mode 100644
index 0000000..e529428
--- /dev/null
+++ b/backend/shop/migrations/0033_vccourse_is_fixed_schedule_vccourse_schedule_time.py
@@ -0,0 +1,23 @@
+# Generated by Django 6.0.1 on 2026-02-23 15:58
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('shop', '0032_order_activity'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='vccourse',
+ name='is_fixed_schedule',
+ field=models.BooleanField(default=False, help_text='勾选后,前端将显示具体的开课时间', verbose_name='是否固定时间课程'),
+ ),
+ migrations.AddField(
+ model_name='vccourse',
+ name='schedule_time',
+ field=models.CharField(blank=True, help_text='例如:每周六晚 20:00', max_length=100, null=True, verbose_name='课程具体时间'),
+ ),
+ ]
diff --git a/backend/shop/migrations/0034_remove_vccourse_schedule_time_vccourse_end_time_and_more.py b/backend/shop/migrations/0034_remove_vccourse_schedule_time_vccourse_end_time_and_more.py
new file mode 100644
index 0000000..c5a9d49
--- /dev/null
+++ b/backend/shop/migrations/0034_remove_vccourse_schedule_time_vccourse_end_time_and_more.py
@@ -0,0 +1,27 @@
+# Generated by Django 6.0.1 on 2026-02-23 16:02
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('shop', '0033_vccourse_is_fixed_schedule_vccourse_schedule_time'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='vccourse',
+ name='schedule_time',
+ ),
+ migrations.AddField(
+ model_name='vccourse',
+ name='end_time',
+ field=models.DateTimeField(blank=True, null=True, verbose_name='结束时间'),
+ ),
+ migrations.AddField(
+ model_name='vccourse',
+ name='start_time',
+ field=models.DateTimeField(blank=True, null=True, verbose_name='开始时间'),
+ ),
+ ]
diff --git a/backend/shop/models.py b/backend/shop/models.py
index 3447f8c..361e771 100644
--- a/backend/shop/models.py
+++ b/backend/shop/models.py
@@ -348,6 +348,11 @@ class VCCourse(models.Model):
instructor_desc = models.TextField(blank=True, verbose_name="讲师简介", default="拥有多年开发经验,擅长...")
tag = models.CharField(max_length=20, blank=True, verbose_name="标签", help_text="例如: 热门, 推荐, 进阶")
+
+ # 课程时间安排
+ is_fixed_schedule = models.BooleanField(default=False, verbose_name="是否固定时间课程", help_text="勾选后,前端将显示具体的开课时间")
+ start_time = models.DateTimeField(blank=True, null=True, verbose_name="开始时间")
+ end_time = models.DateTimeField(blank=True, null=True, verbose_name="结束时间")
price = models.DecimalField(max_digits=10, decimal_places=2, default=0, verbose_name="价格", help_text="0表示免费")
content = models.TextField(blank=True, verbose_name="详细内容", help_text="支持Markdown或HTML")
diff --git a/backend/shop/serializers.py b/backend/shop/serializers.py
index 173a07b..e25b3d7 100644
--- a/backend/shop/serializers.py
+++ b/backend/shop/serializers.py
@@ -92,7 +92,7 @@ class CourseEnrollmentSerializer(serializers.ModelSerializer):
课程报名序列化器
"""
course_title = serializers.CharField(source='course.title', read_only=True)
- ref_code = serializers.CharField(write_only=True, required=False, allow_blank=True)
+ ref_code = serializers.CharField(write_only=True, required=False, allow_blank=True, allow_null=True)
class Meta:
model = CourseEnrollment
@@ -124,7 +124,7 @@ class ServiceOrderSerializer(serializers.ModelSerializer):
"""
service_name = serializers.CharField(source='service.title', read_only=True)
# 接收前端传来的 ref_code
- ref_code = serializers.CharField(write_only=True, required=False, allow_blank=True)
+ ref_code = serializers.CharField(write_only=True, required=False, allow_blank=True, allow_null=True)
class Meta:
model = ServiceOrder
@@ -212,7 +212,7 @@ class OrderSerializer(serializers.ModelSerializer):
salesperson_name = serializers.CharField(source='salesperson.name', read_only=True)
salesperson_code = serializers.CharField(source='salesperson.code', read_only=True)
# 接收前端传来的 ref_code,用于查找 Salesperson
- ref_code = serializers.CharField(write_only=True, required=False, allow_blank=True)
+ ref_code = serializers.CharField(write_only=True, required=False, allow_blank=True, allow_null=True)
class Meta:
model = Order
diff --git a/backend/shop/views.py b/backend/shop/views.py
index 12efe9c..bf4b02d 100644
--- a/backend/shop/views.py
+++ b/backend/shop/views.py
@@ -268,8 +268,8 @@ def pay(request):
product = None
if order_type == 'course':
try:
- product = VBCourse.objects.get(id=good_id)
- except VBCourse.DoesNotExist:
+ product = VCCourse.objects.get(id=good_id)
+ except VCCourse.DoesNotExist:
print(f"课程不存在: {good_id}")
return Response({'error': f'找不到 ID 为 {good_id} 的课程'}, status=status.HTTP_404_NOT_FOUND)
else:
@@ -355,7 +355,8 @@ def pay(request):
print(f"微信支付 V3 Native 下单成功!")
print(f"订单 ID: {order.id}")
print(f"商户订单号: {out_trade_no}")
- print(f"商品: {product.name} x {quantity}")
+ product_name = getattr(product, 'name', getattr(product, 'title', 'Unknown Product'))
+ print(f"商品: {product_name} x {quantity}")
print(f"总额: {total_price} 元")
print(f"code_url: {code_url}")
print(f"========================================")
@@ -596,6 +597,16 @@ class OrderViewSet(viewsets.ModelViewSet):
return queryset.filter(wechat_user=user).order_by('-created_at')
return queryset.order_by('-created_at')
+ def create(self, request, *args, **kwargs):
+ print(f"Creating order with data: {request.data}")
+ serializer = self.get_serializer(data=request.data)
+ if not serializer.is_valid():
+ print(f"Order validation failed: {serializer.errors}")
+ return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+ self.perform_create(serializer)
+ headers = self.get_success_headers(serializer.data)
+ return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
+
def perform_create(self, serializer):
"""
创建订单时自动关联当前微信用户
diff --git a/frontend/src/pages/MyOrders.jsx b/frontend/src/pages/MyOrders.jsx
index 90722a4..0ee9250 100644
--- a/frontend/src/pages/MyOrders.jsx
+++ b/frontend/src/pages/MyOrders.jsx
@@ -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 { getMySignups } from '../api';
import LoginModal from '../components/LoginModal';
import { useAuth } from '../context/AuthContext';
import { useNavigate } from 'react-router-dom';
@@ -75,6 +74,24 @@ const MyOrders = () => {
}
};
+ const getOrderTypeTag = (order) => {
+ if (order.config) return 硬件;
+ if (order.course) return VC课程;
+ if (order.activity) return 活动;
+ return 其他;
+ };
+
+ const getOrderTitle = (order) => {
+ if (order.config_name) return order.config_name;
+ if (order.course_title) return order.course_title;
+ if (order.activity_title) return order.activity_title;
+ // Fallback to ID if no name/title
+ if (order.config) return `硬件 ID: ${order.config}`;
+ if (order.course) return `课程 ID: ${order.course}`;
+ if (order.activity) return `活动 ID: ${order.activity}`;
+ return '未知商品';
+ };
+
return (
{
) : (
-
+
当前登录用户:
{user.nickname}
)}
-
{order.config_name || `商品 ID: ${order.config}`}
+
{getOrderTitle(order)}
数量: x{order.quantity}
@@ -277,7 +294,7 @@ const MyOrders = () => {
)
}
]} />
-
+
)}
{
{currentOrder.id}
- {currentOrder.config_name}
+ {getOrderTypeTag(currentOrder)}
+ {getOrderTitle(currentOrder)}
{new Date(currentOrder.created_at).toLocaleString()}
{new Date(currentOrder.updated_at).toLocaleString()}
{getStatusTag(currentOrder.status)}
@@ -344,7 +362,7 @@ const MyOrders = () => {
onLoginSuccess={(userData) => {
login(userData);
if (userData.phone_number) {
- handleQueryOrders(userData.phone_number);
+ handleQueryData();
}
}}
/>
diff --git a/frontend/src/pages/VCCourseDetail.jsx b/frontend/src/pages/VCCourseDetail.jsx
index 341d4cb..16529d4 100644
--- a/frontend/src/pages/VCCourseDetail.jsx
+++ b/frontend/src/pages/VCCourseDetail.jsx
@@ -1,10 +1,10 @@
import React, { useEffect, useState } from 'react';
import { useParams, useNavigate, useSearchParams } from 'react-router-dom';
import { Typography, Button, Spin, Empty, Descriptions, Tag, Row, Col, Modal, Form, Input, message } from 'antd';
-import { ArrowLeftOutlined, ClockCircleOutlined, UserOutlined, BookOutlined, FormOutlined } from '@ant-design/icons';
-import { getVCCourseDetail, createOrder } from '../api';
-import { motion } from 'framer-motion';
+import { ArrowLeftOutlined, ClockCircleOutlined, UserOutlined, BookOutlined, FormOutlined, CalendarOutlined } from '@ant-design/icons';
+import { getVCCourseDetail, createOrder, nativePay, queryOrderStatus } from '../api';
import { useAuth } from '../context/AuthContext';
+import { QRCodeSVG } from 'qrcode.react';
const { Title, Paragraph } = Typography;
@@ -19,6 +19,12 @@ const VCCourseDetail = () => {
const [submitting, setSubmitting] = useState(false);
const [form] = Form.useForm();
+ // Payment states
+ const [payMode, setPayMode] = useState(false);
+ const [qrCodeUrl, setQrCodeUrl] = useState(null);
+ const [currentOrderId, setCurrentOrderId] = useState(null);
+ const [paySuccess, setPaySuccess] = useState(false);
+
// 优先从 URL 获取,如果没有则从 localStorage 获取
const refCode = searchParams.get('ref') || localStorage.getItem('ref_code');
@@ -37,33 +43,85 @@ const VCCourseDetail = () => {
}, [id]);
useEffect(() => {
- if (isModalOpen && user && user.phone_number) {
- form.setFieldsValue({
- phone_number: user.phone_number
- });
+ if (isModalOpen) {
+ // Reset payment state when modal opens
+ setPayMode(false);
+ setQrCodeUrl(null);
+ setCurrentOrderId(null);
+ setPaySuccess(false);
+
+ if (user && user.phone_number) {
+ form.setFieldsValue({
+ phone_number: user.phone_number
+ });
+ }
}
}, [isModalOpen, user, form]);
+ // Polling for payment status
+ useEffect(() => {
+ let timer;
+ if (payMode && !paySuccess && currentOrderId) {
+ timer = setInterval(async () => {
+ try {
+ const response = await queryOrderStatus(currentOrderId);
+ if (response.data.status === 'paid') {
+ setPaySuccess(true);
+ message.success('支付成功!报名已完成。');
+ setTimeout(() => {
+ setIsModalOpen(false);
+ }, 2000); // Wait 2 seconds before closing
+ clearInterval(timer);
+ }
+ } catch (error) {
+ console.error('Check payment status failed:', error);
+ }
+ }, 3000);
+ }
+ return () => clearInterval(timer);
+ }, [payMode, paySuccess, currentOrderId]);
+
const handleEnroll = async (values) => {
setSubmitting(true);
try {
- const orderData = {
- course: course.id,
- customer_name: values.customer_name,
- phone_number: values.phone_number,
- ref_code: refCode,
- quantity: 1,
- // 将其他信息放入收货地址字段中
- shipping_address: `[课程报名] 微信号: ${values.wechat_id || '无'}, 邮箱: ${values.email || '无'}, 备注: ${values.message || '无'}`
- };
+ const isFree = course.price === 0 || parseFloat(course.price) === 0;
- await createOrder(orderData);
- if (course.price === 0 || parseFloat(course.price) === 0) {
+ if (isFree) {
+ const orderData = {
+ course: course.id,
+ customer_name: values.customer_name,
+ phone_number: values.phone_number,
+ ref_code: refCode || "",
+ quantity: 1,
+ // 将其他信息放入收货地址字段中
+ shipping_address: `[课程报名] 微信号: ${values.wechat_id || '无'}, 邮箱: ${values.email || '无'}, 备注: ${values.message || '无'}`
+ };
+
+ await createOrder(orderData);
message.success('报名成功!您已成功加入课程。');
+ setIsModalOpen(false);
} else {
- message.success('报名咨询已提交,我们的课程顾问将尽快与您联系!');
+ // Paid course - use nativePay to generate QR code
+ const orderData = {
+ goodid: course.id,
+ type: 'course',
+ quantity: 1,
+ customer_name: values.customer_name,
+ phone_number: values.phone_number,
+ ref_code: refCode || "",
+ shipping_address: `[课程报名] 微信号: ${values.wechat_id || '无'}, 邮箱: ${values.email || '无'}, 备注: ${values.message || '无'}`
+ };
+
+ const response = await nativePay(orderData);
+ if (response.data && response.data.code_url) {
+ setQrCodeUrl(response.data.code_url);
+ setCurrentOrderId(response.data.order_id);
+ setPayMode(true);
+ message.success('订单创建成功,请扫码支付');
+ } else {
+ throw new Error('Failed to generate payment QR code');
+ }
}
- setIsModalOpen(false);
} catch (error) {
console.error(error);
message.error('提交失败,请重试');
@@ -103,11 +161,7 @@ const VCCourseDetail = () => {
返回课程列表
-
+
@@ -168,6 +222,14 @@ const VCCourseDetail = () => {
课时}>
{course.lesson_count} 课时
+ {course.is_fixed_schedule && (course.start_time || course.end_time) && (
+
开课时间}>
+
+ {course.start_time &&
开始:{new Date(course.start_time).toLocaleString('zh-CN', {year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit'})}
}
+ {course.end_time &&
结束:{new Date(course.end_time).toLocaleString('zh-CN', {year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit'})}
}
+
+
+ )}
{/* 讲师简介 */}
@@ -255,43 +317,75 @@ const VCCourseDetail = () => {
-
+
{/* Enroll Modal */}
setIsModalOpen(false)}
footer={null}
destroyOnHidden
+ width={payMode ? 400 : 520}
>
- 请填写您的联系方式,我们将为您安排课程顾问。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ {payMode ? (
+
+ {paySuccess ? (
+
+ ) : (
+ <>
+
+ {qrCodeUrl ? (
+
+ ) : (
+
+ )}
+
+
¥{course.price}
+
请使用微信扫一扫支付
+
+ 支付完成后将自动完成报名
+
+ >
+ )}
+
+ ) : (
+ <>
+ 请填写您的联系方式,我们将为您安排课程顾问。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )}
);
diff --git a/miniprogram/src/pages/courses/detail.scss b/miniprogram/src/pages/courses/detail.scss
index fb0e3e9..6ccdb86 100644
--- a/miniprogram/src/pages/courses/detail.scss
+++ b/miniprogram/src/pages/courses/detail.scss
@@ -185,6 +185,36 @@
}
}
+.schedule-section {
+ .schedule-box {
+ background: #111;
+ padding: 30px;
+ border-radius: 16px;
+ border: 1px solid rgba(0, 240, 255, 0.2);
+
+ .time-row {
+ display: flex;
+ margin-bottom: 16px;
+ font-size: 28px;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+
+ .label {
+ color: #888;
+ width: 160px;
+ }
+
+ .value {
+ color: #00f0ff;
+ flex: 1;
+ font-weight: bold;
+ }
+ }
+ }
+}
+
.desc-text {
color: #aaa;
font-size: 28px;
diff --git a/miniprogram/src/pages/courses/detail.tsx b/miniprogram/src/pages/courses/detail.tsx
index 85bc403..2d35f16 100644
--- a/miniprogram/src/pages/courses/detail.tsx
+++ b/miniprogram/src/pages/courses/detail.tsx
@@ -56,6 +56,17 @@ export default function CourseDetail() {
if (loading) return Loading...
if (!detail) return Not Found
+ const formatDateTime = (dateStr: string) => {
+ if (!dateStr) return ''
+ const date = new Date(dateStr)
+ const year = date.getFullYear()
+ const month = (date.getMonth() + 1).toString().padStart(2, '0')
+ const day = date.getDate().toString().padStart(2, '0')
+ const hour = date.getHours().toString().padStart(2, '0')
+ const minute = date.getMinutes().toString().padStart(2, '0')
+ return `${year}/${month}/${day} ${hour}:${minute}`
+ }
+
return (
@@ -109,6 +120,27 @@ export default function CourseDetail() {
+ {/* 开课时间 */}
+ {detail.is_fixed_schedule && (detail.start_time || detail.end_time) && (
+
+ 开课时间
+
+ {detail.start_time && (
+
+ 开始时间:
+ {formatDateTime(detail.start_time)}
+
+ )}
+ {detail.end_time && (
+
+ 结束时间:
+ {formatDateTime(detail.end_time)}
+
+ )}
+
+
+ )}
+
{/* 课程简介 */}
课程简介
diff --git a/miniprogram/src/pages/order/checkout.tsx b/miniprogram/src/pages/order/checkout.tsx
index 63bfe2a..32dee6d 100644
--- a/miniprogram/src/pages/order/checkout.tsx
+++ b/miniprogram/src/pages/order/checkout.tsx
@@ -92,9 +92,31 @@ export default function Checkout() {
}, [items])
const submitOrder = async () => {
- if (!address) {
- Taro.showToast({ title: '请选择收货地址', icon: 'none' })
- return
+ // 免费课程不需要地址
+ const isFreeCourse = params.type === 'course' && items.length > 0 && Number(items[0].price) === 0
+
+ if (!address && !isFreeCourse) {
+ // 尝试调用 chooseAddress
+ try {
+ await chooseAddress()
+ if (!address) {
+ Taro.showToast({ title: '请选择收货地址', icon: 'none' })
+ return
+ }
+ } catch (e) {
+ Taro.showToast({ title: '请选择收货地址', icon: 'none' })
+ return
+ }
+ }
+
+ // 如果是免费课程且没有地址,使用默认值
+ const orderAddress = address || {
+ userName: '免费课程学员',
+ telNumber: '13800000000',
+ provinceName: '',
+ cityName: '',
+ countyName: '',
+ detailInfo: '线上课程'
}
Taro.showLoading({ title: '提交中...' })
@@ -102,11 +124,13 @@ export default function Checkout() {
try {
const orderPromises = items.map(item => {
const type = params.type || 'config'
+
+ // 构造订单数据
const orderData: any = {
quantity: item.quantity,
- customer_name: address.userName,
- phone_number: address.telNumber,
- shipping_address: `${address.provinceName}${address.cityName}${address.countyName}${address.detailInfo}`,
+ customer_name: orderAddress.userName,
+ phone_number: orderAddress.telNumber,
+ shipping_address: `${orderAddress.provinceName}${orderAddress.cityName}${orderAddress.countyName}${orderAddress.detailInfo}`,
ref_code: Taro.getStorageSync('ref_code') || ''
}
diff --git a/miniprogram/src/pages/order/payment.tsx b/miniprogram/src/pages/order/payment.tsx
index f5777bf..2083ed7 100644
--- a/miniprogram/src/pages/order/payment.tsx
+++ b/miniprogram/src/pages/order/payment.tsx
@@ -23,7 +23,19 @@ export default function Payment() {
const handlePay = async () => {
if (!order) return
+
setLoading(true)
+
+ // 如果是免费订单,直接显示成功并跳转
+ if (parseFloat(order.total_price) <= 0) {
+ Taro.showToast({ title: '报名成功', icon: 'success' })
+ setTimeout(() => {
+ Taro.redirectTo({ url: '/pages/order/list' })
+ }, 1500)
+ setLoading(false)
+ return
+ }
+
try {
const params = await prepayMiniprogram(order.id)
@@ -81,7 +93,9 @@ export default function Payment() {
-
+
)
diff --git a/miniprogram/src/subpackages/forum/activity/detail.scss b/miniprogram/src/subpackages/forum/activity/detail.scss
index 5e52af7..ddf2cf2 100644
--- a/miniprogram/src/subpackages/forum/activity/detail.scss
+++ b/miniprogram/src/subpackages/forum/activity/detail.scss
@@ -373,3 +373,49 @@
}
}
}
+
+/* Signup Form Styles */
+.signup-form {
+ .form-field-wrapper {
+
+ &.custom-field {
+ padding: 12px 24px;
+ position: relative;
+ background-color: #fff; /* Ensure white background inside modal */
+
+ &::after {
+ content: '';
+ position: absolute;
+ bottom: 0;
+ left: 24px;
+ right: 0;
+ height: 1px;
+ background-color: #f0f0f0;
+ transform: scaleY(0.5);
+ }
+
+ .field-label {
+ font-size: 28px;
+ color: #333;
+ margin-bottom: 16px;
+
+ .required {
+ color: #ff4949;
+ margin-right: 4px;
+ }
+ }
+
+ .at-radio::before, .at-radio::after,
+ .at-checkbox::before, .at-checkbox::after,
+ .at-textarea::after {
+ display: none;
+ }
+
+ .at-textarea {
+ padding: 0;
+ background: transparent;
+ border: none;
+ }
+ }
+ }
+}
diff --git a/miniprogram/src/subpackages/forum/activity/detail.tsx b/miniprogram/src/subpackages/forum/activity/detail.tsx
index f4a012c..8f9eed0 100644
--- a/miniprogram/src/subpackages/forum/activity/detail.tsx
+++ b/miniprogram/src/subpackages/forum/activity/detail.tsx
@@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react'
import Taro, { useRouter, useShareAppMessage, useShareTimeline, useDidShow } from '@tarojs/taro'
-import { View, Text, Image, Button, RichText } from '@tarojs/components'
-import { AtIcon, AtProgress, AtModal, AtModalHeader, AtModalContent, AtModalAction, AtInput } from 'taro-ui'
+import { View, Text, Image, Button, RichText, Picker } from '@tarojs/components'
+import { AtIcon, AtProgress, AtModal, AtModalHeader, AtModalContent, AtModalAction, AtInput, AtTextarea, AtRadio, AtCheckbox } from 'taro-ui'
import { getActivityDetail, signupActivity } from '../../../api'
import { marked } from 'marked'
import './detail.scss'
@@ -79,6 +79,12 @@ const ActivityDetail = () => {
setShowSignupModal(true)
return
}
+
+ // Check if already unpaid (resume payment)
+ if (activity.my_signup_status === 'unpaid' && activity.my_order_id) {
+ Taro.navigateTo({ url: `/pages/order/payment?id=${activity.my_order_id}` })
+ return
+ }
// Direct signup if no config
submitSignup({})
@@ -87,7 +93,18 @@ const ActivityDetail = () => {
const submitSignup = async (data: any) => {
setSubmitting(true)
try {
- await signupActivity(Number(id), { signup_info: data })
+ const res = await signupActivity(Number(id), { signup_info: data })
+
+ // Handle payment if order_id is returned
+ if (res.order_id) {
+ Taro.showToast({ title: '即将跳转支付', icon: 'none' })
+ setShowSignupModal(false)
+ setTimeout(() => {
+ Taro.navigateTo({ url: `/pages/order/payment?id=${res.order_id}` })
+ }, 1500)
+ return
+ }
+
Taro.showToast({ title: '报名成功', icon: 'success' })
setShowSignupModal(false)
fetchDetail() // Refresh status
@@ -224,7 +241,9 @@ const ActivityDetail = () => {
{/* Footer Action Bar */}
- 免费
+
+ {Number(activity.price) > 0 ? `¥${activity.price}` : '免费'}
+
限时活动