Files
market_page/backend/shop/admin.py
jeremygan2021 5232ab9960 sale
2026-02-11 00:19:33 +08:00

254 lines
8.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from django.contrib import admin
from django.utils.html import format_html
from django.db.models import Sum
from django import forms
from unfold.admin import ModelAdmin, TabularInline
from unfold.decorators import display
from .models import ESP32Config, Order, Salesperson, WeChatPayConfig, Service, ARService, ProductFeature, CommissionLog
import qrcode
from io import BytesIO
import base64
# 自定义后台标题
admin.site.site_header = "量迹AI硬件销售管理后台"
admin.site.site_title = "量迹AI后台"
admin.site.index_title = "欢迎使用量迹AI管理系统"
class ExternalUploadWidget(forms.URLInput):
def __init__(self, upload_url, accept='*', *args, **kwargs):
super().__init__(*args, **kwargs)
self.upload_url = upload_url
self.attrs.update({
'class': 'upload-url-input',
'data-upload-url': upload_url,
'data-accept': accept,
'readonly': 'readonly',
'placeholder': '上传文件后自动生成URL'
})
class Media:
js = ('shop/js/admin_upload.js',)
css = {
'all': ('shop/css/admin_upload.css',)
}
class ESP32ConfigAdminForm(forms.ModelForm):
class Meta:
model = ESP32Config
fields = '__all__'
widgets = {
'static_image_url': ExternalUploadWidget(
upload_url='https://data.tangledup-ai.com/upload?folder=market_page%2Fhardware_xiaozhi%2Fproduct_static_image',
accept='image/*'
),
'model_3d_url': ExternalUploadWidget(
upload_url='https://data.tangledup-ai.com/upload?folder=market_page%2Fhardware_xiaozhi%2Fproduct_3D_image',
accept='.zip'
),
}
class ProductFeatureInline(TabularInline):
model = ProductFeature
extra = 1
fields = ('title', 'description', 'icon_name', 'icon_image', 'icon_url', 'order')
@admin.register(WeChatPayConfig)
class WeChatPayConfigAdmin(ModelAdmin):
list_display = ('app_id', 'mch_id', 'is_active', 'notify_url')
list_filter = ('is_active',)
search_fields = ('app_id', 'mch_id')
fieldsets = (
('基本配置', {
'fields': ('app_id', 'mch_id', 'is_active')
}),
('微信支付 V3 安全配置 (推荐)', {
'fields': ('apiv3_key', 'mch_cert_serial_no', 'mch_private_key'),
'description': '使用 Native 支付必须配置这些项。私钥可以粘贴在这里,或者放在 backend/certs/apiclient_key.pem 文件中。'
}),
('微信支付 V2 安全配置 (旧版)', {
'fields': ('api_key', 'app_secret'),
'classes': ('collapse',),
}),
('回调配置', {
'fields': ('notify_url',)
}),
)
@admin.register(ESP32Config)
class ESP32ConfigAdmin(ModelAdmin):
form = ESP32ConfigAdminForm
list_display = ('name', 'chip_type', 'price', 'stock', 'has_camera', 'has_microphone')
list_filter = ('chip_type', 'has_camera')
search_fields = ('name', 'description')
inlines = [ProductFeatureInline]
fieldsets = (
('基本信息', {
'fields': ('name', 'price', 'stock', 'description')
}),
('硬件参数', {
'fields': ('chip_type', 'flash_size', 'ram_size', 'has_camera', 'has_microphone')
}),
('详情页图片', {
'fields': ('detail_image', 'detail_image_url'),
'description': '图片上传和URL二选一优先使用URL'
}),
('多媒体资源', {
'fields': ('static_image_url', 'model_3d_url'),
'description': '产品静态图和3D模型的外部链接'
}),
)
@admin.register(Service)
class ServiceAdmin(ModelAdmin):
list_display = ('title', 'created_at')
search_fields = ('title', 'description')
fieldsets = (
('基本信息', {
'fields': ('title', 'description', 'color')
}),
('价格与交付', {
'fields': ('price', 'unit', 'delivery_time', 'delivery_content')
}),
('图标', {
'fields': ('icon', 'icon_url'),
'description': '图标上传和URL二选一优先使用URL'
}),
('详情页图片', {
'fields': ('detail_image', 'detail_image_url'),
'description': '图片上传和URL二选一优先使用URL'
}),
('详细内容', {
'fields': ('features',)
}),
)
@admin.register(ARService)
class ARServiceAdmin(ModelAdmin):
list_display = ('title', 'created_at')
search_fields = ('title', 'description')
fieldsets = (
('基本信息', {
'fields': ('title', 'description')
}),
('封面/长图', {
'fields': ('cover_image', 'cover_image_url'),
'description': '图片上传和URL二选一优先使用URL'
}),
)
@admin.register(Salesperson)
class SalespersonAdmin(ModelAdmin):
list_display = ('name', 'code', 'total_sales', 'view_promotion_url')
search_fields = ('name', 'code')
readonly_fields = ('promotion_qr_code', 'promotion_url_display', 'total_sales_display')
def get_queryset(self, request):
queryset = super().get_queryset(request)
queryset = queryset.annotate(
_total_sales=Sum('orders__total_price', default=0)
)
return queryset
@display(description="累计销售额 (已支付)", ordering='_total_sales')
def total_sales(self, obj):
# 仅计算已支付的订单
paid_sales = obj.orders.filter(status='paid').aggregate(total=Sum('total_price'))['total']
return f"¥{paid_sales or 0:.2f}"
def total_sales_display(self, obj):
return self.total_sales(obj)
total_sales_display.short_description = "累计销售额 (已支付)"
def promotion_url(self, obj):
# 生产环境配置
base_url = "https://market.quant-speed.com"
return f"{base_url}/?ref={obj.code}"
@display(description="推广链接")
def view_promotion_url(self, obj):
url = self.promotion_url(obj)
return format_html('<a href="{}" target="_blank" class="button">打开推广链接</a>', url)
def promotion_url_display(self, obj):
return self.promotion_url(obj)
promotion_url_display.short_description = "完整推广链接"
@display(description="推广二维码")
def promotion_qr_code(self, obj):
if not obj.code:
return "请先保存以生成二维码"
url = self.promotion_url(obj)
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=10,
border=4,
)
qr.add_data(url)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
buffer = BytesIO()
img.save(buffer, format="PNG")
img_str = base64.b64encode(buffer.getvalue()).decode()
return format_html('<img src="data:image/png;base64,{}" width="200" height="200" class="qr-code" />', img_str)
fieldsets = (
('基本信息', {
'fields': ('name', 'code')
}),
('推广工具', {
'fields': ('promotion_url_display', 'promotion_qr_code')
}),
('业绩统计', {
'fields': ('total_sales_display',)
}),
('分销设置', {
'fields': ('parent', 'commission_rate', 'second_level_rate'),
'description': '设置上级分销员及各级分润比例'
}),
)
@admin.register(CommissionLog)
class CommissionLogAdmin(ModelAdmin):
list_display = ('id', 'salesperson', 'amount', 'level', 'status', 'created_at')
list_filter = ('status', 'level', 'salesperson', 'created_at')
search_fields = ('salesperson__name', 'order__id')
readonly_fields = ('amount', 'level', 'created_at')
fieldsets = (
('基本信息', {
'fields': ('salesperson', 'order', 'amount', 'level')
}),
('状态管理', {
'fields': ('status', 'created_at')
}),
)
@admin.register(Order)
class OrderAdmin(ModelAdmin):
list_display = ('id', 'customer_name', 'config', 'total_price', 'status', 'courier_name', 'tracking_number', 'salesperson', 'created_at')
list_filter = ('status', 'salesperson', 'created_at')
search_fields = ('id', 'customer_name', 'phone_number', 'wechat_trade_no')
readonly_fields = ('total_price', 'created_at', 'wechat_trade_no')
fieldsets = (
('订单信息', {
'fields': ('config', 'quantity', 'total_price', 'status', 'created_at')
}),
('客户信息', {
'fields': ('customer_name', 'phone_number', 'shipping_address')
}),
('物流信息', {
'fields': ('courier_name', 'tracking_number')
}),
('销售归属', {
'fields': ('salesperson',)
}),
('支付信息', {
'fields': ('wechat_trade_no',)
}),
)