This commit is contained in:
@@ -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 = (
|
||||
|
||||
Reference in New Issue
Block a user