This commit is contained in:
@@ -176,6 +176,7 @@ class VCCourseSerializer(serializers.ModelSerializer):
|
||||
display_detail_image = serializers.SerializerMethodField()
|
||||
course_type_display = serializers.CharField(source='get_course_type_display', read_only=True)
|
||||
video_url = serializers.SerializerMethodField()
|
||||
is_purchased = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = VCCourse
|
||||
@@ -195,6 +196,30 @@ class VCCourseSerializer(serializers.ModelSerializer):
|
||||
return obj.detail_image.url
|
||||
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):
|
||||
"""
|
||||
仅当用户已付费/报名时返回视频URL
|
||||
@@ -202,27 +227,7 @@ class VCCourseSerializer(serializers.ModelSerializer):
|
||||
if not obj.is_video_course:
|
||||
return None
|
||||
|
||||
request = self.context.get('request')
|
||||
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:
|
||||
if self._check_purchased(obj):
|
||||
return obj.video_url
|
||||
|
||||
return None
|
||||
|
||||
@@ -425,22 +425,26 @@ const VCCourseDetail = () => {
|
||||
size="large"
|
||||
block
|
||||
icon={course.is_video_course ? <PlayCircleOutlined /> : <FormOutlined />}
|
||||
disabled={course.is_purchased}
|
||||
style={{
|
||||
height: 50,
|
||||
background: '#00f0ff',
|
||||
borderColor: '#00f0ff',
|
||||
color: '#000',
|
||||
background: course.is_purchased ? '#333' : '#00f0ff',
|
||||
borderColor: course.is_purchased ? '#444' : '#00f0ff',
|
||||
color: course.is_purchased ? '#888' : '#000',
|
||||
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>
|
||||
<p style={{ color: '#666', marginTop: 15, fontSize: 12, textAlign: 'center' }}>
|
||||
{course.is_video_course
|
||||
{course.is_purchased
|
||||
? '* 您已拥有该课程,可直接观看视频'
|
||||
: (course.is_video_course
|
||||
? '* 支付成功后自动解锁视频内容'
|
||||
: '* 提交后我们的顾问将尽快与您联系确认'}
|
||||
: '* 提交后我们的顾问将尽快与您联系确认')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -352,6 +352,11 @@
|
||||
border: none;
|
||||
margin: 0;
|
||||
|
||||
&.disabled {
|
||||
background: #333;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
@@ -196,8 +196,12 @@ export default function CourseDetail() {
|
||||
<Text className='label'>总价:</Text>
|
||||
<Text className='amount'>¥{detail.price}</Text>
|
||||
</View>
|
||||
<Button className='btn-buy' onClick={handleLaunch}>
|
||||
{detail.is_video_course ? '立即购买' : '立即报名'}
|
||||
<Button
|
||||
className={`btn-buy ${detail.is_purchased ? 'disabled' : ''}`}
|
||||
onClick={() => !detail.is_purchased && handleLaunch()}
|
||||
disabled={detail.is_purchased}
|
||||
>
|
||||
{detail.is_purchased ? '已购买' : (detail.is_video_course ? '立即购买' : '立即报名')}
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -377,3 +377,38 @@
|
||||
0% { transform: scale(1); opacity: 0.5; }
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,10 @@ export default function ServicesIndex() {
|
||||
<View className='page-container'>
|
||||
<View className='header'>
|
||||
<Text className='title'>AI 全栈<Text className='highlight'>解决方案</Text></Text>
|
||||
<View className='ai-badge'>
|
||||
<Text>生成式AI生成内容</Text>
|
||||
</View>
|
||||
|
||||
<Text className='subtitle'>从数据处理到模型部署,我们为您提供一站式 AI 基础设施服务。</Text>
|
||||
|
||||
<View className='vc-promo-container'>
|
||||
@@ -130,6 +134,10 @@ export default function ServicesIndex() {
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className='compliance-footer'>
|
||||
<Text className='compliance-text'>深度合成-AI问答类目</Text>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user