feat: add certificate management module with image upload

- Add Certificate + CertificateCategory models with full CRUD API
- Image upload via base64 data URL stored in Text column
- Certificate fields: title, issuer, issue_date, expiry_date, image, description
- Frontend: card grid with category sidebar filter, create/edit dialog
- Include certificates in data backup/export
- Fix hasPhaseParent optimization in GoalDetailPage

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
祀梦
2026-05-18 00:25:58 +08:00
parent 5048de4fa1
commit 4ee1e39454
14 changed files with 1027 additions and 7 deletions

View File

@@ -0,0 +1,43 @@
import uuid as _uuid
from sqlalchemy import Column, Integer, String, Text, Date, Boolean, DateTime, ForeignKey
from sqlalchemy.orm import relationship
from app.database import Base
from app.utils.datetime import utcnow
class CertificateCategory(Base):
"""证书分类模型"""
__tablename__ = "certificate_categories"
id = Column(Integer, primary_key=True, index=True)
uuid = Column(String(36), default=lambda: str(_uuid.uuid4()), unique=True, index=True)
name = Column(String(50), nullable=False)
icon = Column(String(50), default="medal")
color = Column(String(20), default="#FFB7C5")
sort_order = Column(Integer, default=0)
is_deleted = Column(Boolean, default=False)
sync_version = Column(Integer, default=1)
certificates = relationship("Certificate", back_populates="category")
class Certificate(Base):
"""证书模型"""
__tablename__ = "certificates"
id = Column(Integer, primary_key=True, index=True)
uuid = Column(String(36), default=lambda: str(_uuid.uuid4()), unique=True, index=True)
title = Column(String(200), nullable=False)
category_id = Column(Integer, ForeignKey("certificate_categories.id"), nullable=True)
image = Column(Text, nullable=True) # base64 data URL
issuer = Column(String(200), nullable=True) # 来源/颁发机构
issue_date = Column(Date, nullable=True)
expiry_date = Column(Date, nullable=True) # null = 永久有效
description = Column(Text, nullable=True)
sort_order = Column(Integer, default=0)
is_deleted = Column(Boolean, default=False)
sync_version = Column(Integer, default=1)
created_at = Column(DateTime, default=utcnow)
updated_at = Column(DateTime, default=utcnow, onupdate=utcnow)
category = relationship("CertificateCategory", back_populates="certificates")