2026-05-31 14:55:01 +08:00
|
|
|
|
from django.db import models
|
|
|
|
|
|
import uuid
|
|
|
|
|
|
|
2026-05-31 15:33:50 +08:00
|
|
|
|
|
2026-06-01 17:11:06 +08:00
|
|
|
|
def default_design():
|
|
|
|
|
|
"""默认设计配置(整副牌共享)"""
|
|
|
|
|
|
return {
|
|
|
|
|
|
# 全局背景色(整副牌默认用这个,个别牌可覆盖)
|
|
|
|
|
|
'background_color': '#FFFFFF',
|
|
|
|
|
|
'background_image': None, # 整副牌背景图,相对 media 的路径
|
|
|
|
|
|
# 整副牌边框
|
|
|
|
|
|
'border_color': '#333333',
|
|
|
|
|
|
'border_width': 2,
|
|
|
|
|
|
# 4 个花色符号:可以上传图片,也可保持 None(用字体符号)
|
|
|
|
|
|
'suit_symbols': {
|
|
|
|
|
|
'spade': {'type': 'text', 'value': '♠', 'asset_id': None, 'color': '#000000'},
|
|
|
|
|
|
'heart': {'type': 'text', 'value': '♥', 'asset_id': None, 'color': '#E53935'},
|
|
|
|
|
|
'club': {'type': 'text', 'value': '♣', 'asset_id': None, 'color': '#000000'},
|
|
|
|
|
|
'diamond': {'type': 'text', 'value': '♦', 'asset_id': None, 'color': '#E53935'},
|
|
|
|
|
|
},
|
|
|
|
|
|
# 数字牌角标和中心花色符号的大小(占牌面宽度比例)
|
|
|
|
|
|
'corner_size_ratio': 0.13,
|
|
|
|
|
|
'pip_size_ratio': 0.16,
|
|
|
|
|
|
# 字体
|
|
|
|
|
|
'font_family': 'Times New Roman',
|
|
|
|
|
|
'font_color': '#000000', # 角标数字颜色
|
|
|
|
|
|
# 角标布局微调(相对位置 0~1)
|
|
|
|
|
|
'corner_offset': {'x': 0, 'y': 0},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def default_card_overrides():
|
|
|
|
|
|
"""每张牌可独立覆盖的项目级设置(key=card_key, value 覆盖项)"""
|
|
|
|
|
|
return {
|
|
|
|
|
|
# 例如 'joker-big': { 'background_color': '#1B5E20' }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-06-03 21:59:28 +08:00
|
|
|
|
def default_back_design():
|
|
|
|
|
|
"""背面专用设计配置(独立于正面)"""
|
|
|
|
|
|
return {
|
|
|
|
|
|
'background_color': '#1A237E',
|
|
|
|
|
|
'border_color': '#C0A050',
|
|
|
|
|
|
'border_width': 3,
|
|
|
|
|
|
'pattern_color': None,
|
|
|
|
|
|
'image': None,
|
|
|
|
|
|
'image_dx': 0,
|
|
|
|
|
|
'image_dy': 0,
|
|
|
|
|
|
'image_scale': 1,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-31 14:55:01 +08:00
|
|
|
|
class Project(models.Model):
|
|
|
|
|
|
"""项目配置模型"""
|
|
|
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
|
|
|
|
name = models.CharField(max_length=100)
|
|
|
|
|
|
template_id = models.CharField(max_length=50, default='classic')
|
|
|
|
|
|
card_width = models.IntegerField(default=750)
|
|
|
|
|
|
card_height = models.IntegerField(default=1050)
|
2026-06-01 17:11:06 +08:00
|
|
|
|
# 项目级设计配置
|
|
|
|
|
|
design = models.JSONField(default=default_design)
|
|
|
|
|
|
# 每张牌对项目级配置的覆盖
|
|
|
|
|
|
card_overrides = models.JSONField(default=default_card_overrides)
|
2026-06-03 21:59:28 +08:00
|
|
|
|
# 背面专用设计配置(独立于正面 design)
|
|
|
|
|
|
back_design = models.JSONField(default=default_back_design)
|
2026-06-01 17:11:06 +08:00
|
|
|
|
# 数字牌花色位置微调(相对 0~1)
|
|
|
|
|
|
# { '1': [{'dx':0,'dy':0,'scale':1}, ...], '2': [...], ... }
|
|
|
|
|
|
number_layout = models.JSONField(default=dict)
|
|
|
|
|
|
# JQK 人物图的水平翻转(每张牌独立)
|
|
|
|
|
|
face_orientations = models.JSONField(default=dict)
|
2026-05-31 14:55:01 +08:00
|
|
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
|
|
|
|
|
|
|
|
# 导出设置
|
|
|
|
|
|
export_resolution = models.CharField(max_length=20, default='standard')
|
|
|
|
|
|
export_include_back = models.BooleanField(default=True)
|
|
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
|
return self.name
|
|
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
|
ordering = ['-updated_at']
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Asset(models.Model):
|
|
|
|
|
|
"""项目素材模型"""
|
2026-05-31 15:33:50 +08:00
|
|
|
|
project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='assets')
|
2026-06-01 17:11:06 +08:00
|
|
|
|
asset_type = models.CharField(max_length=20) # 'suit_symbol', 'face_card', 'joker', 'back', 'border', 'background'
|
2026-05-31 14:55:01 +08:00
|
|
|
|
asset_key = models.CharField(max_length=50) # 如 'spade', 'heart-J', 'big_joker'
|
2026-06-01 17:11:06 +08:00
|
|
|
|
file_path = models.CharField(max_length=255, blank=True) # 相对于media目录
|
|
|
|
|
|
file_name = models.CharField(max_length=100, blank=True)
|
|
|
|
|
|
width = models.IntegerField(null=True, blank=True)
|
|
|
|
|
|
height = models.IntegerField(null=True, blank=True)
|
2026-05-31 14:55:01 +08:00
|
|
|
|
uploaded_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
|
return f"{self.asset_type}:{self.asset_key}"
|
|
|
|
|
|
|
2026-05-31 15:33:50 +08:00
|
|
|
|
class Meta:
|
|
|
|
|
|
ordering = ['-uploaded_at']
|
|
|
|
|
|
|
2026-05-31 14:55:01 +08:00
|
|
|
|
|
|
|
|
|
|
class CardLayer(models.Model):
|
2026-06-01 17:11:06 +08:00
|
|
|
|
"""牌面图层配置模型(图层顺序、可见性等)"""
|
2026-05-31 15:33:50 +08:00
|
|
|
|
project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='layers')
|
2026-06-01 17:11:06 +08:00
|
|
|
|
card_type = models.CharField(max_length=20) # 'number', 'face', 'joker', 'back'
|
|
|
|
|
|
card_key = models.CharField(max_length=30) # 'spade-A', 'heart-K', 'joker-big'
|
2026-05-31 14:55:01 +08:00
|
|
|
|
layer_name = models.CharField(max_length=50)
|
2026-06-01 17:11:06 +08:00
|
|
|
|
layer_type = models.CharField(max_length=20) # 'background', 'border', 'pattern', 'image', 'text', 'symbol'
|
2026-05-31 14:55:01 +08:00
|
|
|
|
visible = models.BooleanField(default=True)
|
|
|
|
|
|
locked = models.BooleanField(default=False)
|
|
|
|
|
|
opacity = models.FloatField(default=1.0)
|
|
|
|
|
|
z_index = models.IntegerField(default=0)
|
|
|
|
|
|
|
|
|
|
|
|
# 图层属性(JSON存储)
|
|
|
|
|
|
properties = models.JSONField(default=dict)
|
|
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
|
return f"{self.card_key}-{self.layer_name}"
|
|
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
|
ordering = ['card_key', 'z_index']
|
2026-06-02 14:39:52 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LibraryAsset(models.Model):
|
|
|
|
|
|
"""预设素材库(与 Project 无关,全局共享)
|
|
|
|
|
|
主题分类:classical/modern/astronomy/minimal ...
|
|
|
|
|
|
角色:J / Q / K / joker
|
|
|
|
|
|
"""
|
|
|
|
|
|
id = models.BigAutoField(primary_key=True)
|
|
|
|
|
|
theme_id = models.CharField(max_length=50, db_index=True) # classical / modern ...
|
|
|
|
|
|
theme_name = models.CharField(max_length=100) # 古典宫廷 / 现代人物 ...
|
|
|
|
|
|
role = models.CharField(max_length=10, db_index=True) # 'J' / 'Q' / 'K' / 'joker'
|
|
|
|
|
|
role_name = models.CharField(max_length=50) # '王子' / '皇后' ...
|
|
|
|
|
|
asset_id = models.CharField(max_length=50) # 'prince' / 'queen' ...
|
|
|
|
|
|
label = models.CharField(max_length=100) # '古典·王子'
|
|
|
|
|
|
description = models.CharField(max_length=255, blank=True)
|
|
|
|
|
|
file_path = models.CharField(max_length=255) # library/classical/prince.svg
|
|
|
|
|
|
file_name = models.CharField(max_length=100)
|
|
|
|
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
|
return f'{self.theme_name}/{self.role_name}'
|
|
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
|
ordering = ['theme_id', 'role', 'asset_id']
|
|
|
|
|
|
unique_together = [['theme_id', 'asset_id']]
|