order
All checks were successful
Deploy to Server / deploy (push) Successful in 46s

This commit is contained in:
2026-02-14 00:09:06 +08:00
parent 84e49740f3
commit f13d921625
6 changed files with 67 additions and 10 deletions

View File

@@ -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

View File

@@ -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 = (

View File

@@ -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='所选配置'),
),
]

View File

@@ -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):

View File

@@ -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):

View File

@@ -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}