vb
This commit is contained in:
@@ -12,7 +12,7 @@ links = [
|
||||
"admin:shop_distributor_changelist",
|
||||
"admin:shop_esp32config_changelist",
|
||||
"admin:shop_service_changelist",
|
||||
"admin:shop_arservice_changelist",
|
||||
"admin:shop_vbcourse_changelist",
|
||||
"admin:shop_order_changelist",
|
||||
"admin:shop_serviceorder_changelist",
|
||||
"admin:shop_withdrawal_changelist",
|
||||
|
||||
Binary file not shown.
@@ -232,9 +232,9 @@ UNFOLD = {
|
||||
"link": reverse_lazy("admin:shop_service_changelist"),
|
||||
},
|
||||
{
|
||||
"title": "AR体验",
|
||||
"icon": "view_in_ar",
|
||||
"link": reverse_lazy("admin:shop_arservice_changelist"),
|
||||
"title": "VB课程",
|
||||
"icon": "school",
|
||||
"link": reverse_lazy("admin:shop_vbcourse_changelist"),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -4,7 +4,7 @@ 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, WeChatUser, Distributor, Withdrawal, ServiceOrder
|
||||
from .models import ESP32Config, Order, Salesperson, WeChatPayConfig, Service, VBCourse, ProductFeature, CommissionLog, WeChatUser, Distributor, Withdrawal, ServiceOrder
|
||||
import qrcode
|
||||
from io import BytesIO
|
||||
import base64
|
||||
@@ -19,11 +19,11 @@ class ExternalUploadWidget(forms.URLInput):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.upload_url = upload_url
|
||||
self.attrs.update({
|
||||
'class': 'upload-url-input',
|
||||
'class': 'upload-url-input vTextField',
|
||||
'data-upload-url': upload_url,
|
||||
'data-accept': accept,
|
||||
'readonly': 'readonly',
|
||||
'placeholder': '上传文件后自动生成URL'
|
||||
'placeholder': '上传文件后自动生成URL',
|
||||
'style': 'width: 100%;'
|
||||
})
|
||||
|
||||
class Media:
|
||||
@@ -141,18 +141,26 @@ class ServiceOrderAdmin(ModelAdmin):
|
||||
}),
|
||||
)
|
||||
|
||||
@admin.register(ARService)
|
||||
class ARServiceAdmin(ModelAdmin):
|
||||
list_display = ('title', 'created_at')
|
||||
search_fields = ('title', 'description')
|
||||
@admin.register(VBCourse)
|
||||
class VBCourseAdmin(ModelAdmin):
|
||||
list_display = ('title', 'course_type', 'tag', 'instructor', 'lesson_count', 'duration', 'created_at')
|
||||
search_fields = ('title', 'description', 'instructor', 'tag')
|
||||
list_filter = ('course_type', 'instructor', 'tag')
|
||||
fieldsets = (
|
||||
('基本信息', {
|
||||
'fields': ('title', 'description')
|
||||
'fields': ('title', 'description', 'course_type', 'tag')
|
||||
}),
|
||||
('封面/长图', {
|
||||
('课程详情', {
|
||||
'fields': ('instructor', 'duration', 'lesson_count')
|
||||
}),
|
||||
('封面', {
|
||||
'fields': ('cover_image', 'cover_image_url'),
|
||||
'description': '图片上传和URL二选一,优先使用URL'
|
||||
}),
|
||||
('详情页长图', {
|
||||
'fields': ('detail_image', 'detail_image_url'),
|
||||
'description': '图片上传和URL二选一,优先使用URL'
|
||||
}),
|
||||
)
|
||||
|
||||
@admin.register(Salesperson)
|
||||
|
||||
35
backend/shop/migrations/0018_vbcourse_delete_arservice.py
Normal file
35
backend/shop/migrations/0018_vbcourse_delete_arservice.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# Generated by Django 6.0.1 on 2026-02-10 18:48
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('shop', '0017_withdrawal'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='VBCourse',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=100, verbose_name='课程名称')),
|
||||
('description', models.TextField(verbose_name='课程简介')),
|
||||
('course_type', models.CharField(choices=[('software', '软件课程'), ('hardware', '硬件课程')], default='software', max_length=20, verbose_name='课程类型')),
|
||||
('duration', models.CharField(default='30分钟', help_text='例如: 30分钟', max_length=50, verbose_name='课程时长')),
|
||||
('lesson_count', models.IntegerField(default=1, verbose_name='课时数量')),
|
||||
('instructor', models.CharField(default='VB讲师', max_length=50, verbose_name='讲师')),
|
||||
('cover_image', models.ImageField(blank=True, null=True, upload_to='courses/covers/', verbose_name='封面图 (上传)')),
|
||||
('cover_image_url', models.URLField(blank=True, null=True, verbose_name='封面图 (URL)')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'VB课程',
|
||||
'verbose_name_plural': 'VB课程管理',
|
||||
},
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='ARService',
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,28 @@
|
||||
# Generated by Django 6.0.1 on 2026-02-10 18:55
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('shop', '0018_vbcourse_delete_arservice'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='vbcourse',
|
||||
name='detail_image',
|
||||
field=models.ImageField(blank=True, null=True, upload_to='courses/details/', verbose_name='详情页长图 (上传)'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='vbcourse',
|
||||
name='detail_image_url',
|
||||
field=models.URLField(blank=True, help_text='如果填写了URL,将优先使用URL', null=True, verbose_name='详情页长图 (URL)'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='vbcourse',
|
||||
name='tag',
|
||||
field=models.CharField(blank=True, help_text='例如: 热门, 推荐, 进阶', max_length=20, verbose_name='标签'),
|
||||
),
|
||||
]
|
||||
18
backend/shop/migrations/0020_alter_vbcourse_course_type.py
Normal file
18
backend/shop/migrations/0020_alter_vbcourse_course_type.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 6.0.1 on 2026-02-10 18:58
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('shop', '0019_vbcourse_detail_image_vbcourse_detail_image_url_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='vbcourse',
|
||||
name='course_type',
|
||||
field=models.CharField(choices=[('software', '软件课程'), ('hardware', '硬件课程'), ('incubation', '产品商业孵化')], default='software', max_length=20, verbose_name='课程类型'),
|
||||
),
|
||||
]
|
||||
@@ -312,19 +312,36 @@ class ServiceOrder(models.Model):
|
||||
verbose_name_plural = "服务订单列表"
|
||||
|
||||
|
||||
class ARService(models.Model):
|
||||
class VBCourse(models.Model):
|
||||
"""
|
||||
AR体验服务模型
|
||||
VB Coding 课程模型
|
||||
"""
|
||||
title = models.CharField(max_length=100, verbose_name="体验名称")
|
||||
description = models.TextField(verbose_name="简介")
|
||||
cover_image = models.ImageField(upload_to='ar/covers/', blank=True, null=True, verbose_name="封面/长图 (上传)")
|
||||
cover_image_url = models.URLField(blank=True, null=True, verbose_name="封面/长图 (URL)")
|
||||
COURSE_TYPE_CHOICES = (
|
||||
('software', '软件课程'),
|
||||
('hardware', '硬件课程'),
|
||||
('incubation', '产品商业孵化'),
|
||||
)
|
||||
|
||||
title = models.CharField(max_length=100, verbose_name="课程名称")
|
||||
description = models.TextField(verbose_name="课程简介")
|
||||
course_type = models.CharField(max_length=20, choices=COURSE_TYPE_CHOICES, default='software', verbose_name="课程类型")
|
||||
duration = models.CharField(max_length=50, verbose_name="课程时长", help_text="例如: 30分钟", default="30分钟")
|
||||
lesson_count = models.IntegerField(default=1, verbose_name="课时数量")
|
||||
instructor = models.CharField(max_length=50, verbose_name="讲师", default="VB讲师")
|
||||
|
||||
tag = models.CharField(max_length=20, blank=True, verbose_name="标签", help_text="例如: 热门, 推荐, 进阶")
|
||||
|
||||
cover_image = models.ImageField(upload_to='courses/covers/', blank=True, null=True, verbose_name="封面图 (上传)")
|
||||
cover_image_url = models.URLField(blank=True, null=True, verbose_name="封面图 (URL)")
|
||||
|
||||
detail_image = models.ImageField(upload_to='courses/details/', blank=True, null=True, verbose_name="详情页长图 (上传)")
|
||||
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="创建时间")
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
class Meta:
|
||||
verbose_name = "AR体验"
|
||||
verbose_name_plural = "AR体验管理"
|
||||
verbose_name = "VB课程"
|
||||
verbose_name_plural = "VB课程管理"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from rest_framework import serializers
|
||||
from .models import ESP32Config, Order, Salesperson, Service, ARService, ProductFeature, ServiceOrder, WeChatUser, Distributor, Withdrawal
|
||||
from .models import ESP32Config, Order, Salesperson, Service, VBCourse, ProductFeature, ServiceOrder, WeChatUser, Distributor, Withdrawal
|
||||
|
||||
class WeChatUserSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
@@ -101,14 +101,16 @@ class ServiceOrderSerializer(serializers.ModelSerializer):
|
||||
|
||||
return super().create(validated_data)
|
||||
|
||||
class ARServiceSerializer(serializers.ModelSerializer):
|
||||
class VBCourseSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
AR服务序列化器
|
||||
VB课程序列化器
|
||||
"""
|
||||
display_cover_image = serializers.SerializerMethodField()
|
||||
display_detail_image = serializers.SerializerMethodField()
|
||||
course_type_display = serializers.CharField(source='get_course_type_display', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = ARService
|
||||
model = VBCourse
|
||||
fields = '__all__'
|
||||
|
||||
def get_display_cover_image(self, obj):
|
||||
@@ -118,6 +120,13 @@ class ARServiceSerializer(serializers.ModelSerializer):
|
||||
return obj.cover_image.url
|
||||
return None
|
||||
|
||||
def get_display_detail_image(self, obj):
|
||||
if obj.detail_image_url:
|
||||
return obj.detail_image_url
|
||||
if obj.detail_image:
|
||||
return obj.detail_image.url
|
||||
return None
|
||||
|
||||
class ESP32ConfigSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
ESP32配置序列化器
|
||||
|
||||
@@ -2,7 +2,7 @@ from django.urls import path, include, re_path
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from .views import (
|
||||
ESP32ConfigViewSet, OrderViewSet, order_check_view,
|
||||
ServiceViewSet, ARServiceViewSet, ServiceOrderViewSet,
|
||||
ServiceViewSet, VBCourseViewSet, ServiceOrderViewSet,
|
||||
payment_finish, pay, send_sms_code, wechat_login, update_user_info, DistributorViewSet
|
||||
)
|
||||
|
||||
@@ -10,7 +10,7 @@ router = DefaultRouter()
|
||||
router.register(r'configs', ESP32ConfigViewSet)
|
||||
router.register(r'orders', OrderViewSet)
|
||||
router.register(r'services', ServiceViewSet)
|
||||
router.register(r'ar', ARServiceViewSet)
|
||||
router.register(r'courses', VBCourseViewSet)
|
||||
router.register(r'service-orders', ServiceOrderViewSet)
|
||||
router.register(r'distributor', DistributorViewSet, basename='distributor')
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ from django.shortcuts import render
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.http import HttpResponse
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter, OpenApiExample
|
||||
from .models import ESP32Config, Order, WeChatPayConfig, Service, ARService, ServiceOrder, Salesperson, CommissionLog, WeChatUser, Distributor, Withdrawal
|
||||
from .serializers import ESP32ConfigSerializer, OrderSerializer, ServiceSerializer, ARServiceSerializer, ServiceOrderSerializer, WeChatUserSerializer, DistributorSerializer, WithdrawalSerializer
|
||||
from .models import ESP32Config, Order, WeChatPayConfig, Service, VBCourse, ServiceOrder, Salesperson, CommissionLog, WeChatUser, Distributor, Withdrawal
|
||||
from .serializers import ESP32ConfigSerializer, OrderSerializer, ServiceSerializer, VBCourseSerializer, ServiceOrderSerializer, WeChatUserSerializer, DistributorSerializer, WithdrawalSerializer
|
||||
from django.core.signing import TimestampSigner, BadSignature, SignatureExpired
|
||||
from django.contrib.auth.models import User
|
||||
from wechatpayv3 import WeChatPay, WeChatPayType
|
||||
@@ -508,15 +508,15 @@ def payment_finish(request):
|
||||
return HttpResponse(str(e), status=500)
|
||||
|
||||
@extend_schema_view(
|
||||
list=extend_schema(summary="获取AR服务列表", description="获取所有可用的AR服务"),
|
||||
retrieve=extend_schema(summary="获取AR服务详情", description="获取指定AR服务的详细信息")
|
||||
list=extend_schema(summary="获取VB课程列表", description="获取所有可用的VB课程"),
|
||||
retrieve=extend_schema(summary="获取VB课程详情", description="获取指定VB课程的详细信息")
|
||||
)
|
||||
class ARServiceViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
class VBCourseViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
"""
|
||||
AR服务列表和详情
|
||||
VB课程列表和详情
|
||||
"""
|
||||
queryset = ARService.objects.all().order_by('-created_at')
|
||||
serializer_class = ARServiceSerializer
|
||||
queryset = VBCourse.objects.all().order_by('-created_at')
|
||||
serializer_class = VBCourseSerializer
|
||||
|
||||
def order_check_view(request):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user