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 @@