From db1208b981dbefee15cf57775e21e17f11599936 Mon Sep 17 00:00:00 2001 From: irony Date: Sat, 6 Dec 2025 20:43:37 +0800 Subject: [PATCH] =?UTF-8?q?feat(auto):=20=E5=B7=B2=E6=94=B6=E8=97=8F?= =?UTF-8?q?=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/static/css/components.css | 134 +++++++++++++++++++ src/main/resources/static/js/app.js | 10 ++ src/main/resources/static/js/pages.js | 16 ++- 3 files changed, 156 insertions(+), 4 deletions(-) diff --git a/src/main/resources/static/css/components.css b/src/main/resources/static/css/components.css index bc8fbe4..b1f9f52 100644 --- a/src/main/resources/static/css/components.css +++ b/src/main/resources/static/css/components.css @@ -334,6 +334,7 @@ body:has(.reader-mode) #toast { overflow: hidden; flex-shrink: 0; background: var(--bg-hover); + position: relative; } .novel-cover img { @@ -342,6 +343,111 @@ body:has(.reader-mode) #toast { object-fit: cover; } +/* 书架标识徽章 - 更醒目美观的版本 */ +.shelf-badge { + position: absolute; + top: 8px; + right: 8px; + min-width: 28px; + height: 28px; + padding: 0 8px; + background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); + border-radius: 14px; + display: flex; + align-items: center; + justify-content: center; + gap: 4px; + box-shadow: + 0 2px 12px rgba(76, 175, 80, 0.4), + 0 0 0 2px rgba(255, 255, 255, 0.1), + inset 0 1px 0 rgba(255, 255, 255, 0.2); + z-index: 10; + animation: shelfBadgePulse 2.5s ease-in-out infinite; + backdrop-filter: blur(4px); + will-change: transform, box-shadow; + transform: translateZ(0); /* 启用硬件加速 */ +} + +.shelf-badge::before { + content: ''; + position: absolute; + inset: -2px; + border-radius: 16px; + background: linear-gradient(135deg, rgba(76, 175, 80, 0.3), rgba(69, 160, 73, 0.3)); + z-index: -1; + animation: shelfBadgeGlow 2.5s ease-in-out infinite; + will-change: opacity, transform; + transform: translateZ(0); +} + +.shelf-badge svg { + width: 16px; + height: 16px; + fill: #ffffff; + filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3)); + flex-shrink: 0; +} + +.shelf-badge-text { + font-size: 11px; + font-weight: 600; + color: #ffffff; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); + white-space: nowrap; + display: none; /* 默认隐藏文字,只显示图标 */ +} + +/* 列表卡片中的书架标识 - 显示文字 */ +.novel-card .shelf-badge { + min-width: auto; + padding: 0 6px; + height: 26px; +} + +.novel-card .shelf-badge-text { + display: inline; + font-size: 10px; + padding-left: 2px; +} + +@keyframes shelfBadgePulse { + 0%, 100% { + transform: scale(1) translateZ(0); + box-shadow: + 0 2px 12px rgba(76, 175, 80, 0.4), + 0 0 0 2px rgba(255, 255, 255, 0.1), + inset 0 1px 0 rgba(255, 255, 255, 0.2); + } + 50% { + transform: scale(1.05) translateZ(0); + box-shadow: + 0 4px 16px rgba(76, 175, 80, 0.5), + 0 0 0 3px rgba(255, 255, 255, 0.15), + inset 0 1px 0 rgba(255, 255, 255, 0.3); + } +} + +@keyframes shelfBadgeGlow { + 0%, 100% { + opacity: 0.5; + transform: scale(1) translateZ(0); + } + 50% { + opacity: 0.8; + transform: scale(1.1) translateZ(0); + } +} + +/* 减少动画以节省电量(可选,用户可启用) */ +@media (prefers-reduced-motion: reduce) { + .shelf-badge { + animation: none; + } + .shelf-badge::before { + animation: none; + } +} + .novel-info { flex: 1; min-width: 0; @@ -411,6 +517,34 @@ body:has(.reader-mode) #toast { height: 100%; } +/* 网格卡片中的书架标识 */ +.novel-grid-card .shelf-badge { + top: 6px; + right: 6px; + min-width: 24px; + height: 24px; + padding: 0 6px; + border-radius: 12px; +} + +.novel-grid-card .shelf-badge::before { + border-radius: 14px; +} + +.novel-grid-card .shelf-badge svg { + width: 14px; + height: 14px; +} + +/* 鼠标悬停时的增强效果(桌面端) */ +@media (hover: hover) { + .novel-card:hover .shelf-badge, + .novel-grid-card:hover .shelf-badge { + animation: shelfBadgePulse 1s ease-in-out infinite; + transform: scale(1.1); + } +} + .novel-grid-card .novel-info { padding: var(--spacing-sm); } diff --git a/src/main/resources/static/js/app.js b/src/main/resources/static/js/app.js index 3c26c81..d99988c 100644 --- a/src/main/resources/static/js/app.js +++ b/src/main/resources/static/js/app.js @@ -350,6 +350,9 @@ const App = (function() { $btn.find('svg path').attr('d', 'M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z'); $btn.contents().last()[0].textContent = '加入书架'; Toast.show('已从书架移除'); + + // 更新当前页面所有相关书籍卡片的书架标识 + $(`.novel-card[data-novel-id="${novelId}"] .shelf-badge, .novel-grid-card[data-novel-id="${novelId}"] .shelf-badge`).remove(); } else { await API.addToShelf({ userId: Store.getUserId(), novelId }); Store.addToShelfCache(novelId); @@ -357,6 +360,13 @@ const App = (function() { $btn.find('svg path').attr('d', 'M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z'); $btn.contents().last()[0].textContent = '已在书架'; Toast.show('已加入书架'); + + // 更新当前页面所有相关书籍卡片的书架标识 + // 列表视图显示文字,网格视图只显示图标 + const badgeHtmlList = '
已收藏
'; + const badgeHtmlGrid = '
'; + $(`.novel-card[data-novel-id="${novelId}"] .novel-cover`).append(badgeHtmlList); + $(`.novel-grid-card[data-novel-id="${novelId}"] .novel-cover`).append(badgeHtmlGrid); } } catch (error) { Toast.show('操作失败'); diff --git a/src/main/resources/static/js/pages.js b/src/main/resources/static/js/pages.js index 6b2682a..7d3ab1f 100644 --- a/src/main/resources/static/js/pages.js +++ b/src/main/resources/static/js/pages.js @@ -199,10 +199,12 @@ const Pages = (function() { let html = ''; novels.forEach(novel => { + const isInShelf = Store.isInShelfCache(novel.id); html += `
${novel.name} + ${isInShelf ? '
' : ''}
${novel.name}
@@ -285,7 +287,8 @@ const Pages = (function() { let html = ''; novels.forEach(novel => { - html += renderNovelCard(novel); + const isInShelf = Store.isInShelfCache(novel.id); + html += renderNovelCard(novel, isInShelf); }); if (append) { @@ -316,13 +319,16 @@ const Pages = (function() { } } - function renderNovelCard(novel) { + function renderNovelCard(novel, inShelf = false) { const synopsis = cleanContent(novel.synopsis).substring(0, 80); const tags = (novel.tags || []).slice(0, 2); + // 检查书架状态(如果没有传入,则从缓存中检查) + const isInShelf = inShelf !== undefined ? inShelf : Store.isInShelfCache(novel.id); return `
${novel.name} + ${isInShelf ? '
已收藏
' : ''}
${novel.name}
@@ -656,7 +662,8 @@ const Pages = (function() { let html = '
'; novels.forEach(novel => { - html += renderNovelCard(novel); + const isInShelf = Store.isInShelfCache(novel.id); + html += renderNovelCard(novel, isInShelf); }); html += '
'; @@ -688,7 +695,8 @@ const Pages = (function() { let html = '
'; novels.forEach(novel => { - html += renderNovelCard(novel); + const isInShelf = Store.isInShelfCache(novel.id); + html += renderNovelCard(novel, isInShelf); }); html += '
';