创赢未来评分系统 - 初始化提交(移除大文件)

This commit is contained in:
爽哒哒
2026-03-18 22:28:45 +08:00
commit f26d35da66
315 changed files with 36043 additions and 0 deletions

368
backend/shop/serializers.py Normal file
View File

@@ -0,0 +1,368 @@
from rest_framework import serializers
from .models import ESP32Config, Order, Salesperson, Service, VCCourse, ProductFeature, ServiceOrder, WeChatUser, Distributor, Withdrawal, CommissionLog, CourseEnrollment
from .utils import get_current_wechat_user
class CommissionLogSerializer(serializers.ModelSerializer):
"""
佣金记录序列化器
"""
order_info = serializers.SerializerMethodField()
class Meta:
model = CommissionLog
fields = ['id', 'amount', 'level', 'status', 'created_at', 'order_info']
read_only_fields = ['id', 'amount', 'level', 'status', 'created_at', 'order_info']
def get_order_info(self, obj):
return {
'order_id': obj.order.id,
'total_price': obj.order.total_price,
'customer_name': obj.order.customer_name
}
class WeChatUserSerializer(serializers.ModelSerializer):
is_admin = serializers.SerializerMethodField()
has_web_account = serializers.SerializerMethodField()
class Meta:
model = WeChatUser
fields = ['id', 'openid', 'nickname', 'avatar_url', 'gender', 'country', 'province', 'city', 'phone_number', 'is_star', 'title', 'skills', 'is_admin', 'has_web_account', 'has_web_badge']
read_only_fields = ['id', 'openid', 'phone_number', 'is_star', 'title', 'skills', 'is_admin', 'has_web_account', 'has_web_badge']
def get_is_admin(self, obj):
# 检查是否关联了系统用户且具有管理员权限
return bool(obj.user and obj.user.is_staff)
def get_has_web_account(self, obj):
# 检查是否关联了系统用户(即网页账号)
return obj.user is not None
class DistributorSerializer(serializers.ModelSerializer):
user_info = WeChatUserSerializer(source='user', read_only=True)
class Meta:
model = Distributor
fields = ['id', 'user_info', 'level', 'commission_rate', 'total_earnings', 'withdrawable_balance', 'status', 'invite_code', 'qr_code_url']
read_only_fields = ['level', 'commission_rate', 'total_earnings', 'withdrawable_balance', 'status', 'invite_code', 'qr_code_url']
class WithdrawalSerializer(serializers.ModelSerializer):
class Meta:
model = Withdrawal
fields = ['id', 'amount', 'status', 'remark', 'created_at']
read_only_fields = ['status', 'created_at', 'remark']
class ProductFeatureSerializer(serializers.ModelSerializer):
"""
产品特性序列化器
"""
display_icon = serializers.SerializerMethodField()
class Meta:
model = ProductFeature
fields = ['title', 'description', 'icon_name', 'display_icon', 'order']
def get_display_icon(self, obj):
if obj.icon_url:
return obj.icon_url
if obj.icon_image:
return obj.icon_image.url
return None
class ServiceSerializer(serializers.ModelSerializer):
"""
AI服务序列化器
"""
features_list = serializers.SerializerMethodField()
display_icon = serializers.SerializerMethodField()
display_detail_image = serializers.SerializerMethodField()
class Meta:
model = Service
fields = '__all__'
def get_features_list(self, obj):
if obj.features:
return [line.strip() for line in obj.features.split('\n') if line.strip()]
return []
def get_display_icon(self, obj):
if obj.icon_url:
return obj.icon_url
if obj.icon:
return obj.icon.url
return None
def get_display_detail_image(self, obj):
if obj.detail_image_url:
return obj.detail_image_url
if obj.detail_image:
return obj.detail_image.url
return None
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)
class Meta:
model = CourseEnrollment
fields = ['id', 'course', 'course_title', 'customer_name', 'phone_number', 'email', 'wechat_id', 'message', 'status', 'created_at', 'ref_code']
read_only_fields = ['status', 'created_at']
def create(self, validated_data):
ref_code = validated_data.pop('ref_code', None)
# 尝试关联销售员或分销员
if ref_code:
try:
salesperson = Salesperson.objects.get(code=ref_code)
validated_data['salesperson'] = salesperson
except Salesperson.DoesNotExist:
pass
try:
distributor = Distributor.objects.get(invite_code=ref_code)
validated_data['distributor'] = distributor
except Distributor.DoesNotExist:
pass
return super().create(validated_data)
class ServiceOrderSerializer(serializers.ModelSerializer):
"""
AI服务订单序列化器
"""
service_name = serializers.CharField(source='service.title', read_only=True)
# 接收前端传来的 ref_code
ref_code = serializers.CharField(write_only=True, required=False, allow_blank=True)
class Meta:
model = ServiceOrder
fields = ['id', 'service', 'service_name', 'customer_name', 'company_name',
'phone_number', 'email', 'requirements', 'total_price', 'status', 'created_at', 'ref_code']
read_only_fields = ['total_price', 'status', 'created_at']
def create(self, validated_data):
ref_code = validated_data.pop('ref_code', None)
service = validated_data.get('service')
# 默认设置预估总价为服务起步价
if service:
validated_data['total_price'] = service.price
# 尝试关联销售员
if ref_code:
try:
salesperson = Salesperson.objects.get(code=ref_code)
validated_data['salesperson'] = salesperson
except Salesperson.DoesNotExist:
pass
try:
distributor = Distributor.objects.get(invite_code=ref_code)
validated_data['distributor'] = distributor
except Distributor.DoesNotExist:
pass
return super().create(validated_data)
class VCCourseSerializer(serializers.ModelSerializer):
"""
VC课程序列化器
"""
display_cover_image = serializers.SerializerMethodField()
display_detail_image = serializers.SerializerMethodField()
course_type_display = serializers.CharField(source='get_course_type_display', read_only=True)
video_url = serializers.SerializerMethodField()
video_embed_code = serializers.SerializerMethodField()
is_purchased = serializers.SerializerMethodField()
class Meta:
model = VCCourse
fields = '__all__'
def get_display_cover_image(self, obj):
if obj.cover_image_url:
return obj.cover_image_url
if obj.cover_image:
return obj.cover_image.url
return None
def get_display_detail_image(self, obj):
if obj.detail_image_url:
return obj.detail_image_url
if obj.detail_image:
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
# 检查是否已购买/报名 (通过已支付的订单)
has_order = Order.objects.filter(
wechat_user=user,
course=obj,
status__in=['paid', 'shipped', 'completed']
).exists()
return has_order
def get_is_purchased(self, obj):
return self._check_purchased(obj)
def get_video_url(self, obj):
"""
仅当用户已付费/报名时返回视频URL
"""
if not obj.is_video_course:
return None
if self._check_purchased(obj):
return obj.video_url
return None
def get_video_embed_code(self, obj):
"""
仅当用户已付费/报名时返回视频嵌入代码
"""
if not obj.is_video_course:
return None
if self._check_purchased(obj):
return obj.video_embed_code
return None
class ESP32ConfigSerializer(serializers.ModelSerializer):
"""
ESP32配置序列化器
"""
display_detail_image = serializers.SerializerMethodField()
features = ProductFeatureSerializer(many=True, read_only=True)
class Meta:
model = ESP32Config
fields = '__all__'
def get_display_detail_image(self, obj):
if obj.detail_image_url:
return obj.detail_image_url
if obj.detail_image:
return obj.detail_image.url
return None
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)
# 接收前端传来的 ref_code用于查找 Salesperson
ref_code = serializers.CharField(write_only=True, required=False, allow_blank=True)
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',
'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 = {
'customer_name': {'required': True},
'phone_number': {'required': True},
}
def validate(self, data):
# 如果是部分更新 (PATCH),可能不需要校验所有字段,但这里主要用于创建
if self.instance:
return data
config = data.get('config')
course = data.get('course')
activity = data.get('activity')
if not config and not course and not activity:
raise serializers.ValidationError("必须选择一种商品(硬件配置、课程或活动)")
# Count how many types are selected
selected_types = sum([bool(config), bool(course), bool(activity)])
if selected_types > 1:
raise serializers.ValidationError("一次只能购买一种类型的商品")
if config and not data.get('shipping_address'):
raise serializers.ValidationError({"shipping_address": "购买硬件产品需要填写收货地址"})
return data
def get_config_image(self, obj):
if obj.config:
if obj.config.static_image_url:
return obj.config.static_image_url
if obj.config.detail_image_url:
return obj.config.detail_image_url
if obj.config.detail_image:
return obj.config.detail_image.url
elif obj.course:
if obj.course.cover_image_url:
return obj.course.cover_image_url
if obj.course.cover_image:
return obj.course.cover_image.url
elif obj.activity:
# Use activity.display_banner_url logic
if obj.activity.banner:
return obj.activity.banner.url
if obj.activity.banner_url:
return obj.activity.banner_url
return None
def create(self, validated_data):
"""
重写创建方法,自动计算总价并关联销售员/分销员
"""
config = validated_data.get('config')
course = validated_data.get('course')
activity = validated_data.get('activity')
quantity = validated_data.get('quantity', 1)
ref_code = validated_data.pop('ref_code', None)
if config:
validated_data['total_price'] = config.price * quantity
elif course:
validated_data['total_price'] = course.price * quantity
elif activity:
validated_data['total_price'] = activity.price * quantity
# 尝试关联销售员或分销员
if ref_code:
# 1. 尝试查找旧版销售员
try:
salesperson = Salesperson.objects.get(code=ref_code)
validated_data['salesperson'] = salesperson
except Salesperson.DoesNotExist:
pass
# 2. 尝试查找新版分销员
try:
distributor = Distributor.objects.get(invite_code=ref_code)
validated_data['distributor'] = distributor
except Distributor.DoesNotExist:
pass
return super().create(validated_data)