from django.db import models from django.utils.html import format_html import qrcode from io import BytesIO import base64 from django.contrib.auth.models import User class WeChatUser(models.Model): """ 微信小程序用户模型 """ user = models.OneToOneField(User, on_delete=models.CASCADE, null=True, blank=True, related_name='wechat_profile', verbose_name="关联系统用户") openid = models.CharField(max_length=64, unique=True, verbose_name="OpenID") unionid = models.CharField(max_length=64, blank=True, null=True, verbose_name="UnionID", db_index=True) session_key = models.CharField(max_length=64, verbose_name="SessionKey", blank=True) nickname = models.CharField(max_length=64, verbose_name="昵称", blank=True) phone_number = models.CharField(max_length=20, unique=True, null=True, blank=True, verbose_name="手机号") avatar_url = models.URLField(verbose_name="头像URL", blank=True) gender = models.IntegerField(default=0, verbose_name="性别", help_text="0:未知, 1:男, 2:女") country = models.CharField(max_length=64, verbose_name="国家", blank=True) province = models.CharField(max_length=64, verbose_name="省份", blank=True) city = models.CharField(max_length=64, verbose_name="城市", blank=True) # 明星技术用户/专家标识 is_star = models.BooleanField(default=False, verbose_name="是否明星技术用户") title = models.CharField(max_length=50, default="技术专家", verbose_name="专家头衔", blank=True) skills = models.JSONField(default=list, verbose_name="专家技能", blank=True, help_text="格式: [{'icon': 'url', 'text': 'React'}, ...]") order = models.IntegerField(default=0, verbose_name="排序权重", help_text="数字越小越靠前") # 徽章标识 has_web_badge = models.BooleanField(default=False, verbose_name="是否拥有Web徽章") created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间") def save(self, *args, **kwargs): is_new = self.pk is None super().save(*args, **kwargs) if is_new and self.order == 0: WeChatUser.objects.filter(pk=self.pk).update(order=self.pk) self.order = self.pk def __str__(self): return self.phone_number or self.nickname or self.openid class Meta: verbose_name = "微信用户" verbose_name_plural = "微信用户管理" ordering = ['order', '-created_at'] class IdentityTag(models.Model): """ 身份标签模型 - 用于给用户打身份标签 """ name = models.CharField(max_length=50, verbose_name="标签名称", unique=True) description = models.TextField(blank=True, verbose_name="标签描述") color = models.CharField(max_length=7, default="#3B82F6", verbose_name="标签颜色", help_text="十六进制颜色代码,如 #3B82F6") icon = models.CharField(max_length=50, blank=True, verbose_name="图标名称", help_text="Material图标名称") sort_order = models.IntegerField(default=0, verbose_name="排序") is_active = models.BooleanField(default=True, verbose_name="是否启用") created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间") class Meta: verbose_name = "身份标签" verbose_name_plural = "身份标签管理" ordering = ['sort_order', '-created_at'] def __str__(self): return self.name class UserIdentity(models.Model): """ 用户身份关联模型 - 记录用户拥有的身份标签 """ user = models.ForeignKey(WeChatUser, on_delete=models.CASCADE, related_name='identities', verbose_name="微信用户") tag = models.ForeignKey(IdentityTag, on_delete=models.CASCADE, related_name='users', verbose_name="身份标签") assigned_at = models.DateTimeField(auto_now_add=True, verbose_name="分配时间") assigned_by = models.CharField(max_length=100, blank=True, verbose_name="分配人") notes = models.TextField(blank=True, verbose_name="备注") class Meta: verbose_name = "用户身份" verbose_name_plural = "用户身份管理" ordering = ['-assigned_at'] unique_together = ['user', 'tag'] def __str__(self): return f"{self.user.nickname or self.user.phone_number} - {self.tag.name}" class Distributor(models.Model): """ 分销员模型 (替代原 Salesperson 或与其并存,此处为新系统) """ STATUS_CHOICES = ( ('pending', '审核中'), ('active', '正常'), ('disabled', '已禁用'), ) user = models.OneToOneField(WeChatUser, on_delete=models.CASCADE, related_name='distributor', verbose_name="关联微信用户") parent = models.ForeignKey('self', on_delete=models.SET_NULL, null=True, blank=True, related_name='children', verbose_name="上级分销员") level = models.IntegerField(default=1, verbose_name="分销等级") commission_rate = models.DecimalField(max_digits=5, decimal_places=4, default=0.10, verbose_name="分佣比例", help_text="例如 0.10 表示 10%") total_earnings = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name="累计收益") withdrawable_balance = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name="可提现余额") status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending', verbose_name="状态") invite_code = models.CharField(max_length=20, unique=True, blank=True, verbose_name="邀请码") qr_code_url = models.URLField(blank=True, verbose_name="推广二维码URL") created_at = models.DateTimeField(auto_now_add=True, verbose_name="申请时间") updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间") def __str__(self): return f"{self.user.nickname} - {self.get_status_display()}" class Meta: verbose_name = "分销员" verbose_name_plural = "分销员管理" class Withdrawal(models.Model): """ 提现记录 """ STATUS_CHOICES = ( ('pending', '审核中'), ('approved', '已打款'), ('rejected', '已拒绝'), ) distributor = models.ForeignKey(Distributor, on_delete=models.CASCADE, related_name='withdrawals', verbose_name="分销员") amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="提现金额") status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending', verbose_name="状态") remark = models.TextField(blank=True, verbose_name="备注") created_at = models.DateTimeField(auto_now_add=True, verbose_name="申请时间") updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间") def __str__(self): return f"{self.distributor.user.nickname} - ¥{self.amount}" class Meta: verbose_name = "提现记录" verbose_name_plural = "提现管理" class ESP32Config(models.Model): """ ESP32 硬件配置选项模型 用于定义可售卖的硬件参数 """ name = models.CharField(max_length=100, verbose_name="配置名称") chip_type = models.CharField(max_length=50, verbose_name="芯片型号", help_text="例如: ESP32-S3, ESP32-C3") flash_size = models.IntegerField(verbose_name="Flash大小(MB)", default=4) ram_size = models.IntegerField(verbose_name="PSRAM大小(MB)", default=2) has_camera = models.BooleanField(default=False, verbose_name="是否包含摄像头") has_microphone = models.BooleanField(default=False, verbose_name="是否包含麦克风") stock = models.IntegerField(default=0, verbose_name="库存数量") price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="价格") commission_rate = models.DecimalField(max_digits=5, decimal_places=4, default=0.00, verbose_name="产品分润比例", help_text="例如 0.10 表示 10%,优先级高于销售员默认比例") description = models.TextField(verbose_name="描述", blank=True) detail_image = models.ImageField(upload_to='products/details/', blank=True, null=True, verbose_name="详情页长图 (上传)") detail_image_url = models.URLField(blank=True, null=True, verbose_name="详情页长图 (URL)", help_text="如果填写了URL,将优先使用URL") static_image_url = models.URLField(blank=True, null=True, verbose_name="产品静态图 (URL)") model_3d_url = models.URLField(blank=True, null=True, verbose_name="产品3D模型 (URL)") order = models.IntegerField(default=0, verbose_name="排序权重", help_text="数字越小越靠前") def __str__(self): return f"{self.name} - ¥{self.price}" class Meta: verbose_name = "硬件配置 (小智参数)" verbose_name_plural = "硬件配置 (小智参数)" ordering = ['order'] class ProductFeature(models.Model): """ 产品特性模型 (关联到具体硬件配置) """ product = models.ForeignKey(ESP32Config, on_delete=models.CASCADE, related_name='features', verbose_name="所属产品") title = models.CharField(max_length=50, verbose_name="特性标题") description = models.TextField(verbose_name="特性描述") icon_name = models.CharField(max_length=50, blank=True, null=True, verbose_name="Antd图标名称", help_text="例如: SafetyCertificate, Eye, Thunderbolt") icon_image = models.ImageField(upload_to='products/features/', blank=True, null=True, verbose_name="特性图标 (上传)") icon_url = models.URLField(blank=True, null=True, verbose_name="特性图标 (URL)") order = models.IntegerField(default=0, verbose_name="排序权重", help_text="数字越小越靠前") def __str__(self): return f"{self.product.name} - {self.title}" class Meta: verbose_name = "产品特性" verbose_name_plural = "产品特性" ordering = ['order'] class Salesperson(models.Model): """ 销售人员模型 """ name = models.CharField(max_length=50, verbose_name="销售员姓名") code = models.CharField(max_length=20, unique=True, verbose_name="推广码", help_text="唯一的推广标识码,如: zhangsan01") parent = models.ForeignKey('self', on_delete=models.SET_NULL, null=True, blank=True, related_name='children', verbose_name="上级分销员") commission_rate = models.DecimalField(max_digits=5, decimal_places=4, default=0.10, verbose_name="默认分润比例", help_text="例如 0.10 表示 10%") second_level_rate = models.DecimalField(max_digits=5, decimal_places=4, default=0.02, verbose_name="二级分销比例", help_text="作为上级时可获得的分润比例,例如 0.02 表示 2%") created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") def __str__(self): return f"{self.name} ({self.code})" class Meta: verbose_name = "销售员" verbose_name_plural = "销售员管理" class CommissionLog(models.Model): """ 佣金结算记录 """ STATUS_CHOICES = ( ('pending', '待结算'), ('settled', '已结算'), ('cancelled', '已取消'), ) order = models.ForeignKey('Order', on_delete=models.CASCADE, verbose_name="关联订单", related_name='commissions') salesperson = models.ForeignKey(Salesperson, on_delete=models.CASCADE, verbose_name="获佣销售员", related_name='commissions', null=True, blank=True) distributor = models.ForeignKey(Distributor, on_delete=models.CASCADE, verbose_name="获佣分销员", related_name='commissions', null=True, blank=True) amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="佣金金额") level = models.IntegerField(default=1, verbose_name="分销层级", help_text="1: 直接销售, 2: 二级分销") status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending', verbose_name="状态") created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") class Meta: verbose_name = "佣金记录" verbose_name_plural = "佣金结算" def __str__(self): return f"{self.salesperson.name} - ¥{self.amount} ({self.get_status_display()})" class WeChatPayConfig(models.Model): """ 微信支付配置模型 """ app_id = models.CharField(max_length=50, verbose_name="AppID") mch_id = models.CharField(max_length=50, verbose_name="商户号(MchID)") api_key = models.CharField(max_length=100, verbose_name="API密钥(V2 Key)", blank=True, null=True) apiv3_key = models.CharField(max_length=100, verbose_name="API V3密钥", blank=True, null=True) mch_cert_serial_no = models.CharField(max_length=100, verbose_name="商户证书序列号", blank=True, null=True) mch_private_key = models.TextField(verbose_name="商户私钥内容", blank=True, null=True, help_text="apiclient_key.pem 的内容") app_secret = models.CharField(max_length=100, verbose_name="AppSecret", blank=True, null=True) notify_url = models.URLField(verbose_name="回调通知地址") is_active = models.BooleanField(default=True, verbose_name="是否启用") class Meta: verbose_name = "微信支付配置" verbose_name_plural = "微信支付配置" def __str__(self): return f"微信支付配置 ({'启用' if self.is_active else '禁用'})" def save(self, *args, **kwargs): # 确保只有一个启用的配置 if self.is_active: WeChatPayConfig.objects.filter(is_active=True).exclude(id=self.id).update(is_active=False) super().save(*args, **kwargs) class Order(models.Model): """ 订单模型 记录用户的购买请求和支付状态 """ STATUS_CHOICES = ( ('pending', '待支付'), ('paid', '已支付'), ('shipped', '已发货'), ('cancelled', '已取消'), ) config = models.ForeignKey(ESP32Config, on_delete=models.CASCADE, verbose_name="所选配置", null=True, blank=True, related_name='orders') course = models.ForeignKey('VCCourse', on_delete=models.SET_NULL, null=True, blank=True, verbose_name="所选课程", related_name='orders') activity = models.ForeignKey('community.Activity', on_delete=models.SET_NULL, null=True, blank=True, verbose_name="所选活动", related_name='orders') quantity = models.IntegerField(default=1, verbose_name="数量") total_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="总价") status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending', verbose_name="订单状态") # 销售归属 salesperson = models.ForeignKey(Salesperson, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="所属销售员", related_name='orders') distributor = models.ForeignKey(Distributor, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="所属分销员", related_name='orders') # 关联微信用户 wechat_user = models.ForeignKey(WeChatUser, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="下单微信用户", related_name='orders') # 用户信息 customer_name = models.CharField(max_length=100, verbose_name="收货人姓名", default="") phone_number = models.CharField(max_length=20, verbose_name="联系电话", default="") shipping_address = models.TextField(verbose_name="发货地址", default="") # 物流信息 courier_name = models.CharField(max_length=50, blank=True, null=True, verbose_name="快递公司") tracking_number = models.CharField(max_length=100, blank=True, null=True, verbose_name="快递单号") # 微信支付相关字段 out_trade_no = models.CharField(max_length=100, blank=True, null=True, verbose_name="商户订单号") wechat_trade_no = models.CharField(max_length=100, blank=True, null=True, verbose_name="微信支付单号") created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间") def __str__(self): return f"Order #{self.id} - {self.customer_name} - {self.status}" class Meta: verbose_name = "订单" verbose_name_plural = "订单列表" class Service(models.Model): """ AI服务项目模型 """ title = models.CharField(max_length=100, verbose_name="服务名称") icon = models.ImageField(upload_to='services/icons/', blank=True, null=True, verbose_name="图标 (上传)") icon_url = models.URLField(blank=True, null=True, verbose_name="图标 (URL)") description = models.TextField(verbose_name="简介") features = models.TextField(verbose_name="特性列表", help_text="每行一个特性") price = models.DecimalField(max_digits=10, decimal_places=2, default=0, verbose_name="起步价格") unit = models.CharField(max_length=20, default="次", verbose_name="计费单位", help_text="例如:次、小时、月、个") delivery_time = models.CharField(max_length=50, blank=True, verbose_name="预计交付周期", help_text="例如:3-5个工作日") delivery_content = models.TextField(blank=True, verbose_name="交付内容", help_text="描述将交付给客户的具体成果") color = models.CharField(max_length=20, default="#00f0ff", verbose_name="主题色") detail_image = models.ImageField(upload_to='services/details/', blank=True, null=True, verbose_name="详情页长图 (上传)") detail_image_url = models.URLField(blank=True, null=True, verbose_name="详情页长图 (URL)") created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") order = models.IntegerField(default=0, verbose_name="排序权重", help_text="数字越小越靠前") def __str__(self): return self.title class Meta: verbose_name = "AI服务" verbose_name_plural = "AI服务管理" ordering = ['order'] class ServiceOrder(models.Model): """ AI服务订单模型 """ STATUS_CHOICES = ( ('pending', '待沟通/待支付'), ('processing', '服务进行中'), ('completed', '已完成'), ('cancelled', '已取消'), ) service = models.ForeignKey(Service, on_delete=models.CASCADE, verbose_name="所选服务") customer_name = models.CharField(max_length=100, verbose_name="客户姓名") company_name = models.CharField(max_length=100, blank=True, verbose_name="公司名称") phone_number = models.CharField(max_length=20, verbose_name="联系电话") email = models.EmailField(blank=True, verbose_name="电子邮箱") requirements = models.TextField(verbose_name="具体需求描述", blank=True) total_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="预估总价", default=0) status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending', verbose_name="订单状态") salesperson = models.ForeignKey(Salesperson, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="所属销售员") created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间") def __str__(self): return f"{self.customer_name} - {self.service.title}" class Meta: verbose_name = "服务订单" verbose_name_plural = "服务订单列表" class VCCourse(models.Model): """ VC (VB Coding) 课程模型 """ COURSE_TYPE_CHOICES = ( ('software', '软件课程'), ('hardware', '硬件课程'), ('incubation', '产品商业孵化'), ) title = models.CharField(max_length=100, verbose_name="课程名称") description = models.TextField(verbose_name="课程简介") course_type = models.CharField(max_length=20, choices=COURSE_TYPE_CHOICES, default='software', verbose_name="课程类型") duration = models.CharField(max_length=50, verbose_name="课程时长", help_text="例如: 30分钟", default="30分钟") lesson_count = models.IntegerField(default=1, verbose_name="课时数量") instructor = models.CharField(max_length=50, verbose_name="讲师", default="VC讲师") instructor_title = models.CharField(max_length=50, verbose_name="讲师头衔", default="资深讲师") instructor_avatar = models.ImageField(upload_to='instructors/avatars/', blank=True, null=True, verbose_name="讲师头像 (上传)") instructor_avatar_url = models.URLField(blank=True, null=True, verbose_name="讲师头像 (URL)") instructor_desc = models.TextField(blank=True, verbose_name="讲师简介", default="拥有多年开发经验,擅长...") tag = models.CharField(max_length=20, blank=True, verbose_name="标签", help_text="例如: 热门, 推荐, 进阶") # 视频课程相关 is_video_course = models.BooleanField(default=False, verbose_name="是否视频课程") video_url = models.URLField(blank=True, null=True, verbose_name="视频课程URL", help_text="仅当用户付费或报名后可见") video_embed_code = models.TextField(blank=True, null=True, verbose_name="视频嵌入代码", help_text="支持iframe嵌入代码,优先级高于视频URL") # 课程时间安排 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") cover_image = models.ImageField(upload_to='courses/covers/', blank=True, null=True, verbose_name="封面图 (上传)") cover_image_url = models.URLField(blank=True, null=True, verbose_name="封面图 (URL)") detail_image = models.ImageField(upload_to='courses/details/', blank=True, null=True, verbose_name="详情页长图 (上传)") detail_image_url = models.URLField(blank=True, null=True, verbose_name="详情页长图 (URL)", help_text="如果填写了URL,将优先使用URL") created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") order = models.IntegerField(default=0, verbose_name="排序权重", help_text="数字越小越靠前") def save(self, *args, **kwargs): is_new = self.pk is None super().save(*args, **kwargs) if is_new and self.order == 0: VCCourse.objects.filter(pk=self.pk).update(order=self.pk) self.order = self.pk def __str__(self): return self.title class Meta: verbose_name = "课程" verbose_name_plural = "课程管理" ordering = ['order'] class CourseEnrollment(models.Model): """ 课程报名/咨询记录 """ STATUS_CHOICES = ( ('pending', '待联系'), ('contacted', '已联系'), ('completed', '已完成'), ('cancelled', '已取消'), ) course = models.ForeignKey(VCCourse, on_delete=models.CASCADE, verbose_name="咨询课程", related_name='enrollments') customer_name = models.CharField(max_length=100, verbose_name="姓名") phone_number = models.CharField(max_length=20, verbose_name="联系电话") email = models.EmailField(blank=True, verbose_name="电子邮箱") wechat_id = models.CharField(max_length=50, blank=True, verbose_name="微信号") message = models.TextField(blank=True, verbose_name="留言/备注") status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending', verbose_name="状态") # 销售归属 salesperson = models.ForeignKey(Salesperson, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="所属销售员") distributor = models.ForeignKey(Distributor, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="所属分销员") created_at = models.DateTimeField(auto_now_add=True, verbose_name="提交时间") updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间") def __str__(self): return f"{self.customer_name} - {self.course.title}" class Meta: verbose_name = "课程报名" verbose_name_plural = "课程报名" class AdminPhoneNumber(models.Model): """ 管理员通知手机号配置 用于接收订单支付成功等重要通知 """ name = models.CharField(max_length=50, verbose_name="管理员姓名") phone_number = models.CharField(max_length=20, verbose_name="手机号") is_active = models.BooleanField(default=True, verbose_name="是否接收通知") created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") def __str__(self): return f"{self.name} - {self.phone_number}" class Meta: verbose_name = "管理员通知手机号" verbose_name_plural = "管理员通知手机号"