From 84fec2eadad5b5202d9a2b72ea99e674ffc63d23 Mon Sep 17 00:00:00 2001 From: mtvpls Date: Thu, 19 Feb 2026 22:03:05 +0800 Subject: [PATCH] =?UTF-8?q?=E5=89=A7=E9=9B=86=E6=9B=B4=E6=96=B0=E6=8F=90?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../003_add_new_episodes_to_play_records.sql | 8 ++ .../003_add_new_episodes_to_play_records.sql | 8 ++ src/app/api/cron/[password]/route.ts | 11 +- src/app/globals.css | 34 +++++ src/components/ContinueWatching.tsx | 129 +++++++++++++----- src/components/DetailPanel.tsx | 4 +- src/components/VirtualScrollableRow.tsx | 6 +- src/lib/d1.db.ts | 11 +- src/lib/db.client.ts | 1 + src/lib/postgres.db.ts | 11 +- src/lib/types.ts | 1 + 11 files changed, 175 insertions(+), 49 deletions(-) create mode 100644 migrations/003_add_new_episodes_to_play_records.sql create mode 100644 migrations/postgres/003_add_new_episodes_to_play_records.sql diff --git a/migrations/003_add_new_episodes_to_play_records.sql b/migrations/003_add_new_episodes_to_play_records.sql new file mode 100644 index 0000000..ee2c3b1 --- /dev/null +++ b/migrations/003_add_new_episodes_to_play_records.sql @@ -0,0 +1,8 @@ +-- ============================================ +-- 添加 new_episodes 字段到播放记录表 +-- 版本: 1.0.1 +-- 创建时间: 2026-02-19 +-- ============================================ + +-- 为播放记录表添加 new_episodes 字段(用于显示新增剧集提示) +ALTER TABLE play_records ADD COLUMN new_episodes INTEGER; diff --git a/migrations/postgres/003_add_new_episodes_to_play_records.sql b/migrations/postgres/003_add_new_episodes_to_play_records.sql new file mode 100644 index 0000000..cc40e0c --- /dev/null +++ b/migrations/postgres/003_add_new_episodes_to_play_records.sql @@ -0,0 +1,8 @@ +-- ============================================ +-- 添加 new_episodes 字段到播放记录表 +-- 版本: 1.0.1 +-- 创建时间: 2026-02-19 +-- ============================================ + +-- 为播放记录表添加 new_episodes 字段(用于显示新增剧集提示) +ALTER TABLE play_records ADD COLUMN new_episodes BIGINT; diff --git a/src/app/api/cron/[password]/route.ts b/src/app/api/cron/[password]/route.ts index c28240c..a5ddee1 100644 --- a/src/app/api/cron/[password]/route.ts +++ b/src/app/api/cron/[password]/route.ts @@ -216,6 +216,14 @@ async function refreshRecordAndFavorites() { const episodeCount = detail.episodes?.length || 0; if (episodeCount > 0 && episodeCount !== record.total_episodes) { + // 计算新增的剧集数量 + const newEpisodesCount = episodeCount > record.total_episodes + ? episodeCount - record.total_episodes + : 0; + + // 如果有新增剧集,累加到现有的 new_episodes 字段 + const updatedNewEpisodes = (record.new_episodes || 0) + newEpisodesCount; + await db.savePlayRecord(user, source, id, { title: detail.title || record.title, source_name: record.source_name, @@ -227,9 +235,10 @@ async function refreshRecordAndFavorites() { total_time: record.total_time, save_time: record.save_time, search_title: record.search_title, + new_episodes: updatedNewEpisodes > 0 ? updatedNewEpisodes : undefined, }); console.log( - `更新播放记录: ${record.title} (${record.total_episodes} -> ${episodeCount})` + `更新播放记录: ${record.title} (${record.total_episodes} -> ${episodeCount}, 新增 ${newEpisodesCount} 集)` ); } diff --git a/src/app/globals.css b/src/app/globals.css index ae2660d..2a0697a 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -211,3 +211,37 @@ div[data-media-provider] video { display: none !important; } } + +/* 水波纹缩放动画 - 第一层 (快速扩散) */ +@keyframes ping-scale { + 0% { + transform: scale(1); + opacity: 0.6; + } + 75%, 100% { + transform: scale(2); + opacity: 0; + } +} + +/* 水波纹缩放动画 - 第二层 (慢速脉冲) */ +@keyframes pulse-scale { + 0%, 100% { + transform: scale(1); + opacity: 0.4; + } + 50% { + transform: scale(1.15); + opacity: 0.2; + } +} + +/* 主体徽章缩放动画 */ +@keyframes badge-scale { + 0%, 100% { + transform: scale(1); + } + 50% { + transform: scale(1.08); + } +} diff --git a/src/components/ContinueWatching.tsx b/src/components/ContinueWatching.tsx index 969e454..556e6bc 100644 --- a/src/components/ContinueWatching.tsx +++ b/src/components/ContinueWatching.tsx @@ -124,7 +124,7 @@ export default function ContinueWatching({ className }: ContinueWatchingProps) { {loading ? ( // 加载状态显示灰色占位数据(使用原始 ScrollableRow) -
+
{Array.from({ length: 8 }).map((_, index) => (
) : ( // 使用虚拟滚动显示真实数据 - - {playRecords.map((record) => { - const { source, id } = parseKey(record.key); - return ( -
- - setPlayRecords((prev) => - prev.filter((r) => r.key !== record.key) - ) - } - type={record.total_episodes > 1 ? 'tv' : ''} - origin={record.origin} - orientation='horizontal' - playTime={record.play_time} - totalTime={record.total_time} - /> -
- ); - })} -
+
+ + {playRecords.map((record) => { + const { source, id } = parseKey(record.key); + return ( +
+ + setPlayRecords((prev) => + prev.filter((r) => r.key !== record.key) + ) + } + type={record.total_episodes > 1 ? 'tv' : ''} + origin={record.origin} + orientation='horizontal' + playTime={record.play_time} + totalTime={record.total_time} + /> + {/* 新增剧集提示 - 完全独立于 VideoCard */} + {record.new_episodes && record.new_episodes > 0 && ( +
+ {/* 水波纹动画 - 第一层 */} +
+ {/* 水波纹动画 - 第二层 */} +
+ {/* 主体徽章 */} +
+ +{record.new_episodes} +
+
+ )} +
+ ); + })} + +
)} diff --git a/src/components/DetailPanel.tsx b/src/components/DetailPanel.tsx index 723e680..2fd0915 100644 --- a/src/components/DetailPanel.tsx +++ b/src/components/DetailPanel.tsx @@ -1050,10 +1050,10 @@ const DetailPanel: React.FC = ({ {actor.profile_path ? (
handleImageClick(processImageUrl(getTMDBImageUrl(actor.profile_path, 'w185')))} + onClick={() => handleImageClick(processImageUrl(getTMDBImageUrl(actor.profile_path || null, 'w185')))} > {actor.name}(null); const [showLeftScroll, setShowLeftScroll] = useState(false); @@ -94,8 +96,8 @@ export default function VirtualScrollableRow({ {/* 滚动容器 */}
{/* 左侧占位符(用于保持滚动位置) */} {visibleRange.start > 0 && ( diff --git a/src/lib/d1.db.ts b/src/lib/d1.db.ts index 2ea644d..0bc2c60 100644 --- a/src/lib/d1.db.ts +++ b/src/lib/d1.db.ts @@ -66,9 +66,9 @@ export class D1Storage implements IStorage { INSERT INTO play_records ( username, key, title, source_name, cover, year, episode_index, total_episodes, play_time, total_time, - save_time, search_title + save_time, search_title, new_episodes ) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(username, key) DO UPDATE SET title = excluded.title, source_name = excluded.source_name, @@ -79,7 +79,8 @@ export class D1Storage implements IStorage { play_time = excluded.play_time, total_time = excluded.total_time, save_time = excluded.save_time, - search_title = excluded.search_title + search_title = excluded.search_title, + new_episodes = excluded.new_episodes `) .bind( userName, @@ -93,7 +94,8 @@ export class D1Storage implements IStorage { record.play_time, record.total_time, record.save_time, - record.search_title || '' + record.search_title || '', + record.new_episodes || null ) .run(); } catch (err) { @@ -727,6 +729,7 @@ export class D1Storage implements IStorage { total_time: row.total_time, save_time: row.save_time, search_title: row.search_title || '', + new_episodes: row.new_episodes || undefined, }; } diff --git a/src/lib/db.client.ts b/src/lib/db.client.ts index beff712..9adf9c4 100644 --- a/src/lib/db.client.ts +++ b/src/lib/db.client.ts @@ -41,6 +41,7 @@ export interface PlayRecord { save_time: number; // 记录保存时间(时间戳) search_title?: string; // 搜索时使用的标题 origin?: 'vod' | 'live'; // 来源类型 + new_episodes?: number; // 新增的剧集数量(用于显示更新提示) } // ---- 收藏类型 ---- diff --git a/src/lib/postgres.db.ts b/src/lib/postgres.db.ts index 979dd52..4279948 100644 --- a/src/lib/postgres.db.ts +++ b/src/lib/postgres.db.ts @@ -67,9 +67,9 @@ export class PostgresStorage implements IStorage { INSERT INTO play_records ( username, key, title, source_name, cover, year, episode_index, total_episodes, play_time, total_time, - save_time, search_title + save_time, search_title, new_episodes ) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) ON CONFLICT (username, key) DO UPDATE SET title = EXCLUDED.title, source_name = EXCLUDED.source_name, @@ -80,7 +80,8 @@ export class PostgresStorage implements IStorage { play_time = EXCLUDED.play_time, total_time = EXCLUDED.total_time, save_time = EXCLUDED.save_time, - search_title = EXCLUDED.search_title + search_title = EXCLUDED.search_title, + new_episodes = EXCLUDED.new_episodes `) .bind( userName, @@ -94,7 +95,8 @@ export class PostgresStorage implements IStorage { record.play_time, record.total_time, record.save_time, - record.search_title || '' + record.search_title || '', + record.new_episodes || null ) .run(); } catch (err) { @@ -301,6 +303,7 @@ export class PostgresStorage implements IStorage { total_time: row.total_time, save_time: row.save_time, search_title: row.search_title || '', + new_episodes: row.new_episodes || undefined, }; } diff --git a/src/lib/types.ts b/src/lib/types.ts index 34a723a..526bc8f 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -12,6 +12,7 @@ export interface PlayRecord { total_time: number; // 总进度(秒) save_time: number; // 记录保存时间(时间戳) search_title: string; // 搜索时使用的标题 + new_episodes?: number; // 新增的剧集数量(用于显示更新提示) } // 收藏数据结构