video curcse
All checks were successful
Deploy to Server / deploy (push) Successful in 36s

This commit is contained in:
jeremygan2021
2026-02-27 14:18:18 +08:00
parent a47be29bf1
commit 27dcbef7d5
6 changed files with 93 additions and 32 deletions

View File

@@ -176,6 +176,7 @@ class VCCourseSerializer(serializers.ModelSerializer):
display_detail_image = serializers.SerializerMethodField() display_detail_image = serializers.SerializerMethodField()
course_type_display = serializers.CharField(source='get_course_type_display', read_only=True) course_type_display = serializers.CharField(source='get_course_type_display', read_only=True)
video_url = serializers.SerializerMethodField() video_url = serializers.SerializerMethodField()
is_purchased = serializers.SerializerMethodField()
class Meta: class Meta:
model = VCCourse model = VCCourse
@@ -195,6 +196,30 @@ class VCCourseSerializer(serializers.ModelSerializer):
return obj.detail_image.url return obj.detail_image.url
return None return None
def _check_purchased(self, obj):
request = self.context.get('request')
if not request:
return False
# 尝试获取当前用户
user = get_current_wechat_user(request)
if not user:
return False
# 如果是管理员,视为已购买
if user.user and user.user.is_staff:
return True
# 检查是否已购买/报名 (通过已支付的订单)
return Order.objects.filter(
wechat_user=user,
course=obj,
status__in=['paid', 'shipped', 'completed']
).exists()
def get_is_purchased(self, obj):
return self._check_purchased(obj)
def get_video_url(self, obj): def get_video_url(self, obj):
""" """
仅当用户已付费/报名时返回视频URL 仅当用户已付费/报名时返回视频URL
@@ -202,27 +227,7 @@ class VCCourseSerializer(serializers.ModelSerializer):
if not obj.is_video_course: if not obj.is_video_course:
return None return None
request = self.context.get('request') if self._check_purchased(obj):
if not request:
return None
# 尝试获取当前用户
user = get_current_wechat_user(request)
if not user:
return None
# 如果是管理员,直接返回
if user.user and user.user.is_staff:
return obj.video_url
# 检查是否已购买/报名 (通过已支付的订单)
has_paid = Order.objects.filter(
wechat_user=user,
course=obj,
status__in=['paid', 'shipped', 'completed'] # 包含所有已完成状态
).exists()
if has_paid:
return obj.video_url return obj.video_url
return None return None

View File

@@ -425,22 +425,26 @@ const VCCourseDetail = () => {
size="large" size="large"
block block
icon={course.is_video_course ? <PlayCircleOutlined /> : <FormOutlined />} icon={course.is_video_course ? <PlayCircleOutlined /> : <FormOutlined />}
disabled={course.is_purchased}
style={{ style={{
height: 50, height: 50,
background: '#00f0ff', background: course.is_purchased ? '#333' : '#00f0ff',
borderColor: '#00f0ff', borderColor: course.is_purchased ? '#444' : '#00f0ff',
color: '#000', color: course.is_purchased ? '#888' : '#000',
fontWeight: 'bold', fontWeight: 'bold',
fontSize: '16px' fontSize: '16px',
cursor: course.is_purchased ? 'not-allowed' : 'pointer'
}} }}
onClick={() => setIsModalOpen(true)} onClick={() => !course.is_purchased && setIsModalOpen(true)}
> >
{course.is_video_course ? '购买视频课程' : '立即报名 / 咨询'} {course.is_purchased ? '已购买' : (course.is_video_course ? '购买视频课程' : '立即报名 / 咨询')}
</Button> </Button>
<p style={{ color: '#666', marginTop: 15, fontSize: 12, textAlign: 'center' }}> <p style={{ color: '#666', marginTop: 15, fontSize: 12, textAlign: 'center' }}>
{course.is_video_course {course.is_purchased
? '* 支付成功后自动解锁视频内容' ? '* 您已拥有该课程,可直接观看视频'
: '* 提交后我们的顾问将尽快与您联系确认'} : (course.is_video_course
? '* 支付成功后自动解锁视频内容'
: '* 提交后我们的顾问将尽快与您联系确认')}
</p> </p>
</div> </div>
</div> </div>

View File

@@ -352,6 +352,11 @@
border: none; border: none;
margin: 0; margin: 0;
&.disabled {
background: #333;
color: #666;
}
&::after { &::after {
border: none; border: none;
} }

View File

@@ -196,8 +196,12 @@ export default function CourseDetail() {
<Text className='label'>:</Text> <Text className='label'>:</Text>
<Text className='amount'>¥{detail.price}</Text> <Text className='amount'>¥{detail.price}</Text>
</View> </View>
<Button className='btn-buy' onClick={handleLaunch}> <Button
{detail.is_video_course ? '立即购买' : '立即报名'} className={`btn-buy ${detail.is_purchased ? 'disabled' : ''}`}
onClick={() => !detail.is_purchased && handleLaunch()}
disabled={detail.is_purchased}
>
{detail.is_purchased ? '已购买' : (detail.is_video_course ? '立即购买' : '立即报名')}
</Button> </Button>
</View> </View>
</View> </View>

View File

@@ -377,3 +377,38 @@
0% { transform: scale(1); opacity: 0.5; } 0% { transform: scale(1); opacity: 0.5; }
100% { transform: scale(1.15); opacity: 0; } 100% { transform: scale(1.15); opacity: 0; }
} }
.ai-badge {
background: rgba(0, 185, 107, 0.1);
border: 1px solid rgba(0, 185, 107, 0.3);
padding: 8px 20px;
border-radius: 30px;
margin: 15px auto;
display: inline-block;
backdrop-filter: blur(5px);
box-shadow: 0 0 10px rgba(0, 185, 107, 0.1);
text {
color: #00b96b;
font-size: 24px;
font-weight: bold;
letter-spacing: 1px;
text-shadow: 0 0 5px rgba(0, 185, 107, 0.3);
}
}
.compliance-footer {
text-align: center;
padding: 30px 20px 50px;
margin-top: 40px;
border-top: 1px solid rgba(255, 255, 255, 0.05);
background: linear-gradient(to bottom, transparent, rgba(0, 0, 0, 0.5));
.compliance-text {
color: #444;
font-size: 22px;
display: block;
margin-bottom: 5px;
letter-spacing: 1px;
}
}

View File

@@ -48,6 +48,10 @@ export default function ServicesIndex() {
<View className='page-container'> <View className='page-container'>
<View className='header'> <View className='header'>
<Text className='title'>AI <Text className='highlight'></Text></Text> <Text className='title'>AI <Text className='highlight'></Text></Text>
<View className='ai-badge'>
<Text>AI生成内容</Text>
</View>
<Text className='subtitle'> AI </Text> <Text className='subtitle'> AI </Text>
<View className='vc-promo-container'> <View className='vc-promo-container'>
@@ -130,6 +134,10 @@ export default function ServicesIndex() {
))} ))}
</View> </View>
</View> </View>
<View className='compliance-footer'>
<Text className='compliance-text'>-AI问答类目</Text>
</View>
</View> </View>
) )
} }