This commit is contained in:
@@ -21,4 +21,5 @@ drf-spectacular-sidecar==2026.1.1
|
|||||||
gunicorn==21.2.0
|
gunicorn==21.2.0
|
||||||
requests
|
requests
|
||||||
django-filter
|
django-filter
|
||||||
|
django-admin-sortable2
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from django.db.models import Sum
|
|||||||
from django import forms
|
from django import forms
|
||||||
from unfold.admin import ModelAdmin, TabularInline
|
from unfold.admin import ModelAdmin, TabularInline
|
||||||
from unfold.decorators import display
|
from unfold.decorators import display
|
||||||
|
from adminsortable2.admin import SortableAdminMixin
|
||||||
from .models import ESP32Config, Order, Salesperson, WeChatPayConfig, Service, VCCourse, ProductFeature, CommissionLog, WeChatUser, Distributor, Withdrawal, ServiceOrder, CourseEnrollment
|
from .models import ESP32Config, Order, Salesperson, WeChatPayConfig, Service, VCCourse, ProductFeature, CommissionLog, WeChatUser, Distributor, Withdrawal, ServiceOrder, CourseEnrollment
|
||||||
import qrcode
|
import qrcode
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
@@ -83,9 +84,9 @@ class WeChatPayConfigAdmin(ModelAdmin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@admin.register(ESP32Config)
|
@admin.register(ESP32Config)
|
||||||
class ESP32ConfigAdmin(ModelAdmin):
|
class ESP32ConfigAdmin(SortableAdminMixin, ModelAdmin):
|
||||||
form = ESP32ConfigAdminForm
|
form = ESP32ConfigAdminForm
|
||||||
list_display = ('name', 'chip_type', 'price', 'stock', 'has_camera', 'has_microphone')
|
list_display = ('name', 'chip_type', 'price', 'stock', 'has_camera', 'has_microphone', 'order')
|
||||||
list_filter = ('chip_type', 'has_camera')
|
list_filter = ('chip_type', 'has_camera')
|
||||||
search_fields = ('name', 'description')
|
search_fields = ('name', 'description')
|
||||||
inlines = [ProductFeatureInline]
|
inlines = [ProductFeatureInline]
|
||||||
@@ -107,8 +108,8 @@ class ESP32ConfigAdmin(ModelAdmin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@admin.register(Service)
|
@admin.register(Service)
|
||||||
class ServiceAdmin(ModelAdmin):
|
class ServiceAdmin(SortableAdminMixin, ModelAdmin):
|
||||||
list_display = ('title', 'created_at')
|
list_display = ('title', 'created_at', 'order')
|
||||||
search_fields = ('title', 'description')
|
search_fields = ('title', 'description')
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
('基本信息', {
|
('基本信息', {
|
||||||
@@ -150,8 +151,8 @@ class ServiceOrderAdmin(ModelAdmin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@admin.register(VCCourse)
|
@admin.register(VCCourse)
|
||||||
class VCCourseAdmin(ModelAdmin):
|
class VCCourseAdmin(SortableAdminMixin, ModelAdmin):
|
||||||
list_display = ('title', 'course_type', 'price', 'tag', 'instructor', 'lesson_count', 'duration', 'created_at')
|
list_display = ('title', 'course_type', 'price', 'tag', 'instructor', 'lesson_count', 'duration', 'created_at', 'order')
|
||||||
search_fields = ('title', 'description', 'instructor', 'tag')
|
search_fields = ('title', 'description', 'instructor', 'tag')
|
||||||
list_filter = ('course_type', 'instructor', 'tag')
|
list_filter = ('course_type', 'instructor', 'tag')
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
# Generated by Django 6.0.1 on 2026-02-13 16:07
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('shop', '0029_fix_legacy_fields'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='esp32config',
|
||||||
|
options={'ordering': ['order'], 'verbose_name': '硬件配置 (小智参数)', 'verbose_name_plural': '硬件配置 (小智参数)'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='service',
|
||||||
|
options={'ordering': ['order'], 'verbose_name': 'AI服务', 'verbose_name_plural': 'AI服务管理'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='vccourse',
|
||||||
|
options={'ordering': ['order'], 'verbose_name': 'VC课程', 'verbose_name_plural': 'VC课程管理'},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='esp32config',
|
||||||
|
name='order',
|
||||||
|
field=models.IntegerField(default=0, help_text='数字越小越靠前', verbose_name='排序权重'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='service',
|
||||||
|
name='order',
|
||||||
|
field=models.IntegerField(default=0, help_text='数字越小越靠前', verbose_name='排序权重'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='vccourse',
|
||||||
|
name='order',
|
||||||
|
field=models.IntegerField(default=0, help_text='数字越小越靠前', verbose_name='排序权重'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='order',
|
||||||
|
name='config',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='orders', to='shop.esp32config', verbose_name='所选配置'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -109,6 +109,7 @@ class ESP32Config(models.Model):
|
|||||||
detail_image_url = models.URLField(blank=True, null=True, verbose_name="详情页长图 (URL)", help_text="如果填写了URL,将优先使用URL")
|
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)")
|
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)")
|
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):
|
def __str__(self):
|
||||||
return f"{self.name} - ¥{self.price}"
|
return f"{self.name} - ¥{self.price}"
|
||||||
@@ -116,6 +117,7 @@ class ESP32Config(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "硬件配置 (小智参数)"
|
verbose_name = "硬件配置 (小智参数)"
|
||||||
verbose_name_plural = "硬件配置 (小智参数)"
|
verbose_name_plural = "硬件配置 (小智参数)"
|
||||||
|
ordering = ['order']
|
||||||
|
|
||||||
|
|
||||||
class ProductFeature(models.Model):
|
class ProductFeature(models.Model):
|
||||||
@@ -226,7 +228,7 @@ class Order(models.Model):
|
|||||||
('cancelled', '已取消'),
|
('cancelled', '已取消'),
|
||||||
)
|
)
|
||||||
|
|
||||||
config = models.ForeignKey(ESP32Config, on_delete=models.CASCADE, verbose_name="所选配置", null=True, blank=True)
|
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')
|
course = models.ForeignKey('VCCourse', on_delete=models.SET_NULL, null=True, blank=True, verbose_name="所选课程", related_name='orders')
|
||||||
quantity = models.IntegerField(default=1, verbose_name="数量")
|
quantity = models.IntegerField(default=1, verbose_name="数量")
|
||||||
total_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="总价")
|
total_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="总价")
|
||||||
@@ -279,6 +281,7 @@ class Service(models.Model):
|
|||||||
detail_image = models.ImageField(upload_to='services/details/', blank=True, null=True, 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)")
|
detail_image_url = models.URLField(blank=True, null=True, verbose_name="详情页长图 (URL)")
|
||||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
|
||||||
|
order = models.IntegerField(default=0, verbose_name="排序权重", help_text="数字越小越靠前")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
@@ -286,6 +289,7 @@ class Service(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "AI服务"
|
verbose_name = "AI服务"
|
||||||
verbose_name_plural = "AI服务管理"
|
verbose_name_plural = "AI服务管理"
|
||||||
|
ordering = ['order']
|
||||||
|
|
||||||
|
|
||||||
class ServiceOrder(models.Model):
|
class ServiceOrder(models.Model):
|
||||||
@@ -354,6 +358,7 @@ class VCCourse(models.Model):
|
|||||||
detail_image_url = models.URLField(blank=True, null=True, verbose_name="详情页长图 (URL)", help_text="如果填写了URL,将优先使用URL")
|
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="创建时间")
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
|
||||||
|
order = models.IntegerField(default=0, verbose_name="排序权重", help_text="数字越小越靠前")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
@@ -361,6 +366,7 @@ class VCCourse(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "VC课程"
|
verbose_name = "VC课程"
|
||||||
verbose_name_plural = "VC课程管理"
|
verbose_name_plural = "VC课程管理"
|
||||||
|
ordering = ['order']
|
||||||
|
|
||||||
|
|
||||||
class CourseEnrollment(models.Model):
|
class CourseEnrollment(models.Model):
|
||||||
|
|||||||
@@ -604,7 +604,7 @@ class VCCourseViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
"""
|
"""
|
||||||
VC课程列表和详情
|
VC课程列表和详情
|
||||||
"""
|
"""
|
||||||
queryset = VCCourse.objects.all().order_by('-created_at')
|
queryset = VCCourse.objects.all()
|
||||||
serializer_class = VCCourseSerializer
|
serializer_class = VCCourseSerializer
|
||||||
|
|
||||||
class CourseEnrollmentViewSet(viewsets.ModelViewSet):
|
class CourseEnrollmentViewSet(viewsets.ModelViewSet):
|
||||||
@@ -628,7 +628,7 @@ class ServiceViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
"""
|
"""
|
||||||
AI服务列表和详情
|
AI服务列表和详情
|
||||||
"""
|
"""
|
||||||
queryset = Service.objects.all().order_by('-created_at')
|
queryset = Service.objects.all()
|
||||||
serializer_class = ServiceSerializer
|
serializer_class = ServiceSerializer
|
||||||
|
|
||||||
class ServiceOrderViewSet(viewsets.ModelViewSet):
|
class ServiceOrderViewSet(viewsets.ModelViewSet):
|
||||||
|
|||||||
@@ -186,7 +186,10 @@ const ServiceDetail = () => {
|
|||||||
border: `1px solid ${service.color}66`,
|
border: `1px solid ${service.color}66`,
|
||||||
borderRadius: '6px',
|
borderRadius: '6px',
|
||||||
fontSize: '14px',
|
fontSize: '14px',
|
||||||
backdropFilter: 'blur(4px)'
|
backdropFilter: 'blur(4px)',
|
||||||
|
whiteSpace: 'normal',
|
||||||
|
height: 'auto',
|
||||||
|
textAlign: 'left'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{feat}
|
{feat}
|
||||||
|
|||||||
Reference in New Issue
Block a user