new
All checks were successful
Deploy to Server / deploy (push) Successful in 24s

This commit is contained in:
2026-02-14 00:20:44 +08:00
parent 4a96a0c2e4
commit c92633279d

View File

@@ -2,9 +2,10 @@ from django.contrib import admin
from django.utils.html import format_html
from django.db.models import Sum
from django import forms
from django.urls import path, reverse
from django.shortcuts import redirect
from unfold.admin import ModelAdmin, TabularInline
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
import qrcode
from io import BytesIO
@@ -15,6 +16,64 @@ admin.site.site_header = "量迹AI科技硬件/服务商场后台"
admin.site.site_title = "量迹AI后台"
admin.site.index_title = "欢迎使用量迹AI管理系统"
class OrderableAdminMixin:
"""
为 Admin 添加排序功能的 Mixin
提供上移、下移按钮,直接交换 order 值
"""
def get_urls(self):
urls = super().get_urls()
custom_urls = [
path('<path:object_id>/move-up/', self.admin_site.admin_view(self.move_up_view), name=f'{self.model._meta.app_label}_{self.model._meta.model_name}_move_up'),
path('<path:object_id>/move-down/', self.admin_site.admin_view(self.move_down_view), name=f'{self.model._meta.app_label}_{self.model._meta.model_name}_move_down'),
]
return custom_urls + urls
def move_up_view(self, request, object_id):
obj = self.get_object(request, object_id)
if obj:
# 找到排在它前面的一个 (order 小于它的最大值)
prev_obj = self.model.objects.filter(order__lt=obj.order).order_by('-order').first()
if prev_obj:
# 交换
obj.order, prev_obj.order = prev_obj.order, obj.order
obj.save()
prev_obj.save()
self.message_user(request, f"成功将 {obj} 上移")
else:
# 已经是第一个,或者前面没有更小的 order
# 尝试查找 order 等于它的其他对象(理论上不应发生,但为了稳健)
pass
return redirect(request.META.get('HTTP_REFERER', '..'))
def move_down_view(self, request, object_id):
obj = self.get_object(request, object_id)
if obj:
# 找到排在它后面的一个 (order 大于它的最小值)
next_obj = self.model.objects.filter(order__gt=obj.order).order_by('order').first()
if next_obj:
# 交换
obj.order, next_obj.order = next_obj.order, obj.order
obj.save()
next_obj.save()
self.message_user(request, f"成功将 {obj} 下移")
return redirect(request.META.get('HTTP_REFERER', '..'))
def order_actions(self, obj):
return format_html(
'<div style="display: flex; align-items: center; gap: 8px;">'
'<a href="{}" class="button" style="padding: 4px 8px; background-color: #f3f4f6; border-radius: 4px; text-decoration: none; color: #374151; font-size: 12px; border: 1px solid #d1d5db; transition: all 0.2s;" onmouseover="this.style.backgroundColor=\'#e5e7eb\'" onmouseout="this.style.backgroundColor=\'#f3f4f6\'" title="上移">⬆️ 上移</a>'
'<span style="font-weight: bold; font-family: monospace; min-width: 24px; text-align: center; color: #4b5563;">{}</span>'
'<a href="{}" class="button" style="padding: 4px 8px; background-color: #f3f4f6; border-radius: 4px; text-decoration: none; color: #374151; font-size: 12px; border: 1px solid #d1d5db; transition: all 0.2s;" onmouseover="this.style.backgroundColor=\'#e5e7eb\'" onmouseout="this.style.backgroundColor=\'#f3f4f6\'" title="下移">⬇️ 下移</a>'
'</div>',
reverse(f'admin:{self.model._meta.app_label}_{self.model._meta.model_name}_move_up', args=[obj.pk]),
obj.order,
reverse(f'admin:{self.model._meta.app_label}_{self.model._meta.model_name}_move_down', args=[obj.pk]),
)
order_actions.short_description = "排序调节"
order_actions.allow_tags = True
class ExternalUploadWidget(forms.URLInput):
def __init__(self, upload_url, accept='*', *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -84,10 +143,9 @@ class WeChatPayConfigAdmin(ModelAdmin):
)
@admin.register(ESP32Config)
class ESP32ConfigAdmin(ModelAdmin):
class ESP32ConfigAdmin(OrderableAdminMixin, ModelAdmin):
form = ESP32ConfigAdminForm
list_display = ('name', 'chip_type', 'price', 'stock', 'has_camera', 'has_microphone', 'order')
list_editable = ('order',)
list_display = ('name', 'chip_type', 'price', 'stock', 'has_camera', 'has_microphone', 'order_actions')
list_filter = ('chip_type', 'has_camera')
search_fields = ('name', 'description')
inlines = [ProductFeatureInline]
@@ -109,9 +167,8 @@ class ESP32ConfigAdmin(ModelAdmin):
)
@admin.register(Service)
class ServiceAdmin(ModelAdmin):
list_display = ('title', 'created_at', 'order')
list_editable = ('order',)
class ServiceAdmin(OrderableAdminMixin, ModelAdmin):
list_display = ('title', 'created_at', 'order_actions')
search_fields = ('title', 'description')
fieldsets = (
('基本信息', {
@@ -153,9 +210,8 @@ class ServiceOrderAdmin(ModelAdmin):
)
@admin.register(VCCourse)
class VCCourseAdmin(ModelAdmin):
list_display = ('title', 'course_type', 'price', 'tag', 'instructor', 'lesson_count', 'duration', 'created_at', 'order')
list_editable = ('order',)
class VCCourseAdmin(OrderableAdminMixin, ModelAdmin):
list_display = ('title', 'course_type', 'price', 'tag', 'instructor', 'lesson_count', 'duration', 'created_at', 'order_actions')
search_fields = ('title', 'description', 'instructor', 'tag')
list_filter = ('course_type', 'instructor', 'tag')
fieldsets = (