From e78e8feea985fcae8fc9e206b54c082942fe4a4e Mon Sep 17 00:00:00 2001 From: taiyi Date: Wed, 15 Apr 2026 22:33:30 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A7=86=E9=A2=91=E8=83=8C=E6=99=AF=E4=B8=8A?= =?UTF-8?q?=E4=BC=A0=E5=A2=9E=E5=8A=A0=E5=B0=81=E9=9D=A2=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/api/background_router.py | 7 + .../migrations/add_background_cover_url.py | 51 ++ backend/models/database.py | 1 + .../src/components/BackgroundSelector.vue | 15 + .../components/admin/BackgroundManager.vue | 435 ++++++++++++++++-- 5 files changed, 458 insertions(+), 51 deletions(-) create mode 100644 backend/migrations/add_background_cover_url.py diff --git a/backend/api/background_router.py b/backend/api/background_router.py index 2eaa74c..7b8867e 100644 --- a/backend/api/background_router.py +++ b/backend/api/background_router.py @@ -76,6 +76,7 @@ async def list_backgrounds( "price_points": bg.price_points, "category": getattr(bg, 'category', 'default'), "thumbnail": bg.thumbnail, + "cover_url": getattr(bg, 'cover_url', None), "created_at": bg.created_at.isoformat() if bg.created_at else None, "updated_at": bg.updated_at.isoformat() if bg.updated_at else None, "device_type": getattr(bg, 'device_type', 1), @@ -133,6 +134,7 @@ async def get_background_detail( "price_points": bg.price_points, "category": getattr(bg, 'category', 'default'), "thumbnail": bg.thumbnail, + "cover_url": getattr(bg, 'cover_url', None), "created_at": bg.created_at.isoformat() if bg.created_at else None, "updated_at": bg.updated_at.isoformat() if bg.updated_at else None, "device_type": getattr(bg, 'device_type', 1), # 1: 电脑背景, 2: 手机背景 @@ -166,6 +168,7 @@ async def create_background( price_points=background_data.get("price_points", 0), category=background_data.get("category", "default"), thumbnail=background_data.get("thumbnail", background_data.get("resource_url")), + cover_url=background_data.get("cover_url", background_data.get("thumbnail")), device_type=background_data.get("device_type", 1) # 1: 电脑背景, 2: 手机背景 ) @@ -188,6 +191,7 @@ async def create_background( "price_points": new_background.price_points, "category": getattr(new_background, 'category', 'default'), "thumbnail": new_background.thumbnail, + "cover_url": getattr(new_background, 'cover_url', None), "created_at": new_background.created_at.isoformat() if new_background.created_at else None, "updated_at": new_background.updated_at.isoformat() if new_background.updated_at else None, "pet_ids": [] @@ -238,6 +242,8 @@ async def update_background( setattr(bg, 'category', background_data["category"]) if "thumbnail" in background_data: bg.thumbnail = background_data["thumbnail"] + if "cover_url" in background_data: + setattr(bg, 'cover_url', background_data["cover_url"]) if "device_type" in background_data: bg.device_type = background_data["device_type"] @@ -261,6 +267,7 @@ async def update_background( "price_points": bg.price_points, "category": getattr(bg, 'category', 'default'), "thumbnail": bg.thumbnail, + "cover_url": getattr(bg, 'cover_url', None), "created_at": bg.created_at.isoformat() if bg.created_at else None, "updated_at": bg.updated_at.isoformat() if bg.updated_at else None, "device_type": getattr(bg, 'device_type', 1), # 1: 电脑背景, 2: 手机背景 diff --git a/backend/migrations/add_background_cover_url.py b/backend/migrations/add_background_cover_url.py new file mode 100644 index 0000000..cb3cb55 --- /dev/null +++ b/backend/migrations/add_background_cover_url.py @@ -0,0 +1,51 @@ +""" +数据库迁移脚本 - 为背景表添加封面字段 +运行方式: cd backend; $env:PYTHONPATH="."; python migrations/add_background_cover_url.py +""" + +import asyncio +from sqlalchemy import text +from sqlalchemy.ext.asyncio import create_async_engine +from config.settings import settings + + +async def migrate(): + """执行迁移""" + db_url = settings.DATABASE_URL + + if 'mysql+pymysql' in db_url: + async_db_url = db_url.replace('mysql+pymysql', 'mysql+asyncmy') + elif 'mysql' in db_url: + async_db_url = db_url.replace('mysql', 'mysql+asyncmy') + else: + async_db_url = db_url + + print(f"数据库URL: {async_db_url[:50]}...") + + engine = create_async_engine(async_db_url, echo=False) + + async with engine.begin() as conn: + try: + result = await conn.execute(text("SHOW COLUMNS FROM t_background LIKE 'cover_url'")) + row = result.fetchone() + if row: + print("[OK] column cover_url already exists") + else: + print("[INFO] column cover_url not found, adding it now") + await conn.execute( + text("ALTER TABLE t_background ADD COLUMN cover_url VARCHAR(512) DEFAULT NULL COMMENT '视频背景封面地址'") + ) + print("[OK] added column cover_url") + except Exception as e: + error_msg = str(e) + if "Duplicate column" in error_msg or "already exists" in error_msg.lower(): + print("[OK] column cover_url already exists, skipped") + else: + raise + + await engine.dispose() + print("migration completed") + + +if __name__ == "__main__": + asyncio.run(migrate()) diff --git a/backend/models/database.py b/backend/models/database.py index 5e4894a..eac911e 100644 --- a/backend/models/database.py +++ b/backend/models/database.py @@ -71,6 +71,7 @@ class Background(Base): category = Column(String(50), default='default') description = Column(String(255), nullable=True) thumbnail = Column(String(512), nullable=True) + cover_url = Column(String(512), nullable=True) device_type = Column(Integer, nullable=True, default=1) # 1: 电脑背景, 2: 手机背景 class ChatLog(Base): diff --git a/frontend/src/components/BackgroundSelector.vue b/frontend/src/components/BackgroundSelector.vue index 6fd4cda..22b0464 100644 --- a/frontend/src/components/BackgroundSelector.vue +++ b/frontend/src/components/BackgroundSelector.vue @@ -15,6 +15,7 @@ :alt="background.name" @error="handlePreviewError" /> +
视频背景
{{ background.price_points }} 积分 @@ -80,6 +81,9 @@ export default { } return background?.thumbnail || background?.resource_url || this.defaultThumbnail }, + getBackgroundPoster(background) { + return background?.thumbnail || this.defaultThumbnail + }, handlePreviewError(event) { if (event?.target && event.target.src !== this.defaultThumbnail) { event.target.src = this.defaultThumbnail @@ -135,6 +139,17 @@ export default { overflow: hidden; } +.video-badge { + position: absolute; + left: 8px; + top: 8px; + padding: 2px 8px; + border-radius: 999px; + background: rgba(0, 0, 0, 0.55); + color: #fff; + font-size: 12px; +} + .background-image img { width: 100%; height: 100%; diff --git a/frontend/src/views/components/admin/BackgroundManager.vue b/frontend/src/views/components/admin/BackgroundManager.vue index 0de57a7..9ef9d66 100644 --- a/frontend/src/views/components/admin/BackgroundManager.vue +++ b/frontend/src/views/components/admin/BackgroundManager.vue @@ -64,7 +64,7 @@