feat: update card views/serializers and admin sidebar
All checks were successful
Build and Deploy LTY / build-and-deploy (push) Successful in 47m12s
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:
parent
5483c69ba9
commit
55ca2cbdaf
@ -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"
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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"""
|
||||||
|
|||||||
@ -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'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@ -27,7 +27,7 @@ from .serializers import (
|
|||||||
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)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user