Files
game-cards-poker-design/backend/apps/projects/views_library.py
Developer 5ca000b8ab feat: 预设素材库 - 4 套主题 × 4 张图 = 16 张 PNG
主题:classical 古典宫廷(王子/皇后/国王/小丑)
      modern    现代人物(小孩/女青年/男青年/小丑鱼)
      astronomy 天文(星星/月亮/太阳/黑洞)
      minimal   简笔符号(圆点/♀/♂/叉)

改动:
- 新增 LibraryAsset 模型(全局素材库,theme_id/role/asset_id 索引)
- 新增 seed_library 管理命令,用 Pillow 画 16 张 PNG 素材
- 新增 /api/projects/library/ 列表 API
- 新增 /api/projects/{pid}/library/{id}/apply/ 应用 API
  把预设素材复制到 projects/<pid>/joker 或 face_card 下,作为该牌位的素材
- AssetPanel 加 tab 切换:「我的素材」+「预设主题」,主题可筛选、点击套用到当前牌
- 修复 generate_card_png 的 joker asset 匹配 bug:which 应该是 card_key(含前缀)才能匹配 asset_key

设计要点:
- 预设素材只画上半身(y=0~150),下半留空,让系统的'自动对称'流水线正确工作
- 预设素材 PNG 200×300,深色描边 + 半透明浅色填充,在任意牌面背景上叠加都清晰
2026-06-02 14:39:52 +08:00

115 lines
3.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 rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
from django.conf import settings
from .models import LibraryAsset, Project, Asset
from .serializers import LibraryAssetSerializer
import os
@api_view(['GET'])
def library_list(request):
"""列出所有预设素材(按主题分组)"""
assets = LibraryAsset.objects.all()
serializer = LibraryAssetSerializer(assets, many=True, context={'request': request})
# 按 theme_id 分组
grouped = {}
for a in serializer.data:
a['file_url'] = f'{settings.MEDIA_URL}{a["file_path"]}'
grouped.setdefault(a['theme_id'], {
'theme_id': a['theme_id'],
'theme_name': a['theme_name'],
'description': a['description'],
'items': [],
})['items'].append(a)
return Response(list(grouped.values()))
@api_view(['GET'])
def library_themes(request):
"""返回所有主题的元信息(不含具体素材项,用于渲染主题筛选器)"""
themes = LibraryAsset.objects.values('theme_id', 'theme_name', 'description').distinct()
return Response(list(themes))
@api_view(['GET'])
def library_detail(request, pk):
"""单个预设素材的详情"""
try:
a = LibraryAsset.objects.get(pk=pk)
except LibraryAsset.DoesNotExist:
return Response({'error': 'Not found'}, status=status.HTTP_404_NOT_FOUND)
data = LibraryAssetSerializer(a, context={'request': request}).data
data['file_url'] = f'{settings.MEDIA_URL}{data["file_path"]}'
return Response(data)
@api_view(['POST'])
def library_apply(request, project_pk, pk):
"""把预设素材应用到项目某张牌默认spade-J / joker-big 等)
请求体: { card_key?: 'spade-J' } 可选;不传则用预设素材的 role + 默认 spade
"""
try:
project = Project.objects.get(pk=project_pk)
lib = LibraryAsset.objects.get(pk=pk)
except (Project.DoesNotExist, LibraryAsset.DoesNotExist):
return Response({'error': 'not found'}, status=status.HTTP_404_NOT_FOUND)
card_key = request.data.get('card_key')
if not card_key:
# 默认: spade-{role} 或 joker-{which}
if lib.role == 'joker':
card_key = 'joker-big' # 默认应用到 big用户可换
else:
card_key = f'spade-{lib.role}'
# 决定 asset_type: J/Q/K -> face_cardjoker -> joker
asset_type = 'face_card' if lib.role in ('J', 'Q', 'K') else 'joker'
asset_key = card_key
# 删除该项目同 (asset_type, asset_key) 的旧记录(避免重复)
Asset.objects.filter(project=project, asset_type=asset_type, asset_key=asset_key).delete()
# 复制 library 文件到 projects/<pid>/ 下,避免污染原文件
import shutil
from time import time
project_media_dir = os.path.join('projects', str(project.id), asset_type)
full_dir = os.path.join(settings.MEDIA_ROOT, project_media_dir)
os.makedirs(full_dir, exist_ok=True)
src_path = os.path.join(settings.MEDIA_ROOT, lib.file_path)
ts = int(time() * 1000)
new_file_name = f'{asset_key}_{ts}_{lib.file_name}'
dst_rel = os.path.join(project_media_dir, new_file_name)
dst_abs = os.path.join(settings.MEDIA_ROOT, dst_rel)
shutil.copy2(src_path, dst_abs)
# 读 svg 尺寸
width = height = None
try:
from PIL import Image
with Image.open(dst_abs) as im:
width, height = im.size
except Exception:
pass
asset = Asset.objects.create(
project=project,
asset_type=asset_type,
asset_key=asset_key,
file_path=dst_rel,
file_name=new_file_name,
width=width,
height=height,
)
return Response({
'ok': True,
'asset_id': asset.id,
'card_key': card_key,
'asset_type': asset_type,
'file_url': f'{settings.MEDIA_URL}{dst_rel}',
}, status=status.HTTP_201_CREATED)