feat: update card views/serializers and admin sidebar
All checks were successful
Build and Deploy LTY / build-and-deploy (push) Successful in 47m12s

- Add new card API endpoints and serializers
- Update sidebar navigation
- Update claude settings permissions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
pmc 2026-03-25 11:35:11 +08:00
parent 5483c69ba9
commit 55ca2cbdaf
5 changed files with 131 additions and 7 deletions

View File

@ -136,7 +136,24 @@
"Bash(curl -s \"http://127.0.0.1:8000/api/device/devices/bind_status/?mac_address=FF:FF:FF:FF:FF:FF\")", "Bash(curl -s \"http://127.0.0.1:8000/api/device/devices/bind_status/?mac_address=FF:FF:FF:FF:FF:FF\")",
"Bash(curl -s -X POST http://127.0.0.1:8000/api/device/user-devices/bind/ -H \"Content-Type: application/json\" -H \"Authorization: Bearer 026e2157-404a-45e9-ad97-3f79e8a31a09\" -d \"{\"\"mac_address\"\": \"\"AA:BB:CC:DD:EE:01\"\"}\")", "Bash(curl -s -X POST http://127.0.0.1:8000/api/device/user-devices/bind/ -H \"Content-Type: application/json\" -H \"Authorization: Bearer 026e2157-404a-45e9-ad97-3f79e8a31a09\" -d \"{\"\"mac_address\"\": \"\"AA:BB:CC:DD:EE:01\"\"}\")",
"Bash(curl -s http://127.0.0.1:8000/api/device/user-devices/ -H \"Authorization: Bearer 026e2157-404a-45e9-ad97-3f79e8a31a09\")", "Bash(curl -s http://127.0.0.1:8000/api/device/user-devices/ -H \"Authorization: Bearer 026e2157-404a-45e9-ad97-3f79e8a31a09\")",
"Bash(curl -s -X DELETE http://127.0.0.1:8000/api/device/user-devices/7/ -H \"Authorization: Bearer 026e2157-404a-45e9-ad97-3f79e8a31a09\" -w \"\\\\nHTTP_CODE: %{http_code}\")" "Bash(curl -s -X DELETE http://127.0.0.1:8000/api/device/user-devices/7/ -H \"Authorization: Bearer 026e2157-404a-45e9-ad97-3f79e8a31a09\" -w \"\\\\nHTTP_CODE: %{http_code}\")",
"Bash(curl -s -v \"https://qy-lty.airlabs.art/api/device/devices/bind_status/?mac_address=test\")",
"Bash(curl -s \"https://qy-lty.airlabs.art/api/device/devices/bind_status/?mac_address=test\")",
"Bash(curl -s \"https://qy-lty.airlabs.art/api/device/\")",
"Bash(curl -s -o /dev/null -w \"%{http_code}\" \"https://qy-lty.airlabs.art/api/device/devices/register/\")",
"Bash(curl -s -X POST \"https://qy-lty.airlabs.art/api/device/devices/register/\" -H \"Content-Type: application/json\" -d '{\"\"mac_address\"\":\"\"test\"\"}')",
"Bash(curl -s -v \"https://qy-lty.airlabs.art/api/device/devices/nonexistent_action_xyz/\")",
"Bash(curl -s \"https://qy-lty.airlabs.art/swagger/?format=openapi\")",
"Bash(curl -s --connect-timeout 5 \"http://localhost:8000/api/device/devices/bind_status/?mac_address=test\")",
"Bash(curl -s \"http://localhost:8000/api/device/devices/bind_status/\")",
"Bash(curl -s -X POST \"http://localhost:8000/api/device/devices/register/\" -H \"Content-Type: application/json\" -d '{\"\"mac_address\"\":\"\"test\"\"}')",
"Bash(curl -s \"http://localhost:8000/api/device/devices/bind_status/?mac_address=test\")",
"Bash(grep -E \"\\(layout|page\\\\.tsx$\\)\")",
"Bash(grep -E \"\\\\.\\(tsx|ts\\)$\")",
"Bash(python -c \"from card.serializers import MobileClothingTemplateSerializer; print\\(''Serializer OK''\\)\")",
"Bash(python -c \"from card.views import MobileClothingListView; print\\(''View OK''\\)\")",
"Bash(python -c \"from card.urls import urlpatterns; print\\(f''URLs OK, {len\\(urlpatterns\\)} routes''\\)\")",
"Bash(DJANGO_SETTINGS_MODULE=qy_lty.settings python -c \"import django; django.setup\\(\\); from card.serializers import MobileClothingTemplateSerializer; print\\(''Serializer OK''\\); from card.views import MobileClothingListView; print\\(''View OK''\\); from card.urls import urlpatterns; print\\(f''URLs OK, {len\\(urlpatterns\\)} routes''\\)\")"
], ],
"additionalDirectories": [ "additionalDirectories": [
"C:\\Users\\admin\\.claude" "C:\\Users\\admin\\.claude"

View File

@ -107,8 +107,8 @@ export function Sidebar() {
return ( return (
<div className="flex h-screen border-r bg-gradient-to-b from-white to-gray-50 shadow-md"> <div className="flex h-screen border-r bg-gradient-to-b from-white to-gray-50 shadow-md">
<div className="flex w-full flex-col space-y-4 p-4"> <div className="flex w-full flex-col p-4">
<div className="flex h-16 items-center px-4 py-2"> <div className="flex h-16 items-center px-4 py-2 shrink-0">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="h-8 w-8 rounded-full bg-gradient-to-br from-pink-500 to-purple-600 flex items-center justify-center"> <div className="h-8 w-8 rounded-full bg-gradient-to-br from-pink-500 to-purple-600 flex items-center justify-center">
<Sparkles className="h-4 w-4 text-white" /> <Sparkles className="h-4 w-4 text-white" />
@ -122,7 +122,7 @@ export function Sidebar() {
</div> </div>
</div> </div>
<div className="space-y-1.5"> <div className="space-y-1.5 flex-1 overflow-y-auto mt-4">
{/* 仪表盘 - 所有角色都可见 */} {/* 仪表盘 - 所有角色都可见 */}
<Button <Button
variant={pathname === "/" ? "default" : "ghost"} variant={pathname === "/" ? "default" : "ghost"}
@ -177,7 +177,7 @@ export function Sidebar() {
)} )}
</div> </div>
<div className="mt-auto pt-4"> <div className="shrink-0 pt-4">
<Button <Button
variant="ghost" variant="ghost"
className="w-full justify-start hover:bg-red-50 hover:text-red-600 transition-colors" className="w-full justify-start hover:bg-red-50 hover:text-red-600 transition-colors"

View File

@ -402,6 +402,49 @@ class CategoryTemplateSerializer(CardTemplateDetailSerializer):
return representation return representation
class MobileClothingTemplateSerializer(serializers.ModelSerializer):
"""手机端服装模板序列化器 - 只返回已发布服装的必要字段"""
card_type_display = serializers.SerializerMethodField()
rarity_display = serializers.SerializerMethodField()
status_display = serializers.SerializerMethodField()
image_url = serializers.URLField(source='image', read_only=True)
active_cards_count = serializers.SerializerMethodField()
attributes = serializers.SerializerMethodField()
class Meta:
model = CardTemplate
fields = [
'id', 'name', 'description',
'card_type', 'card_type_display',
'rarity', 'rarity_display',
'image_url',
'status', 'status_display',
'published_at',
'active_cards_count',
'attributes',
]
def get_card_type_display(self, obj):
return obj.get_card_type_display()
def get_rarity_display(self, obj):
return obj.get_rarity_display()
def get_status_display(self, obj):
return obj.get_status_display()
def get_active_cards_count(self, obj):
return obj.cards.filter(status='active').count()
def get_attributes(self, obj):
try:
if hasattr(obj, 'clothing_attrs'):
return ClothingAttributesSerializer(obj.clothing_attrs).data
except Exception:
pass
return None
# 带有专有属性的卡牌详情序列化器 # 带有专有属性的卡牌详情序列化器
class CategoryCardDetailSerializer(CardDetailSerializer): class CategoryCardDetailSerializer(CardDetailSerializer):
"""Card detail serializer with category-specific attributes""" """Card detail serializer with category-specific attributes"""

View File

@ -11,5 +11,6 @@ urlpatterns = [
path('', include(router.urls)), path('', include(router.urls)),
path('user/cards/', views.UserCardListView.as_view(), name='user-cards'), path('user/cards/', views.UserCardListView.as_view(), name='user-cards'),
path('category/<str:category>/', views.CategoryCardTemplateListView.as_view(), name='category-templates'), path('category/<str:category>/', views.CategoryCardTemplateListView.as_view(), name='category-templates'),
path('mobile/clothing/', views.MobileClothingListView.as_view(), name='mobile-clothing-list'),
] ]

View File

@ -24,10 +24,10 @@ import csv
from .models import CardTemplate, Card, CardBatch, CardUsageLog from .models import CardTemplate, Card, CardBatch, CardUsageLog
from .serializers import ( from .serializers import (
CardTemplateSerializer, CardTemplateDetailSerializer, CardTemplateSerializer, CardTemplateDetailSerializer,
CardSerializer, CardDetailSerializer, CardBatchSerializer, CardUsageLogSerializer, CardSerializer, CardDetailSerializer, CardBatchSerializer, CardUsageLogSerializer,
CardScanSerializer, CardUseSerializer, CardBatchGenerateSerializer, CardScanSerializer, CardUseSerializer, CardBatchGenerateSerializer,
CardTemplatePublishSerializer, CardBatchPublishSerializer, CardBatchManufactureSerializer, CardTemplatePublishSerializer, CardBatchPublishSerializer, CardBatchManufactureSerializer,
CategoryTemplateSerializer, CategoryCardDetailSerializer, CategoryTemplateSerializer, CategoryCardDetailSerializer, MobileClothingTemplateSerializer,
ClothingAttributesSerializer, PropAttributesSerializer, SongAttributesSerializer, ClothingAttributesSerializer, PropAttributesSerializer, SongAttributesSerializer,
DanceAttributesSerializer, FurnitureAttributesSerializer, DecorationAttributesSerializer DanceAttributesSerializer, FurnitureAttributesSerializer, DecorationAttributesSerializer
) )
@ -1020,3 +1020,66 @@ class CategoryCardTemplateListView(generics.ListAPIView):
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
"""实现分页的列表查询""" """实现分页的列表查询"""
return super().list(request, *args, **kwargs) return super().list(request, *args, **kwargs)
class MobileClothingListView(generics.ListAPIView):
"""
手机端服装列表接口
仅返回已发布的服装模板供手机端展示
支持按稀有度类型筛选和关键词搜索
"""
serializer_class = MobileClothingTemplateSerializer
permission_classes = [IsAuthenticated]
authentication_classes = [RedisTokenAuthentication]
tags = ['手机端']
def get_queryset(self):
queryset = CardTemplate.objects.filter(
category='clothing',
status='published',
).order_by('-published_at')
# 按稀有度筛选
rarity = self.request.query_params.get('rarity')
if rarity:
queryset = queryset.filter(rarity=rarity)
# 按类型筛选
card_type = self.request.query_params.get('card_type')
if card_type:
queryset = queryset.filter(card_type=card_type)
# 关键词搜索
search = self.request.query_params.get('search')
if search:
queryset = queryset.filter(name__icontains=search)
return queryset
@swagger_schema(
responses={
200: openapi.Response('查询成功', schema=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'count': openapi.Schema(type=openapi.TYPE_INTEGER, description='总数'),
'next': openapi.Schema(type=openapi.TYPE_STRING, description='下一页URL', nullable=True),
'previous': openapi.Schema(type=openapi.TYPE_STRING, description='上一页URL', nullable=True),
'results': openapi.Schema(
type=openapi.TYPE_ARRAY,
items=openapi.Schema(type=openapi.TYPE_OBJECT, description='服装模板'),
),
},
)),
},
operation_description="获取已发布的服装列表(手机端专用)",
tags=['手机端'],
manual_parameters=[
openapi.Parameter('rarity', openapi.IN_QUERY, description="按稀有度筛选 (common/uncommon/rare/epic/legendary/limited)", type=openapi.TYPE_STRING, required=False),
openapi.Parameter('card_type', openapi.IN_QUERY, description="按类型筛选 (regular/seasonal/event)", type=openapi.TYPE_STRING, required=False),
openapi.Parameter('search', openapi.IN_QUERY, description="按名称搜索", type=openapi.TYPE_STRING, required=False),
],
security=[{'Bearer': []}]
)
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)