feat(auto): 已收藏显示

This commit is contained in:
irony 2025-12-06 20:43:37 +08:00
parent dc23a4abd7
commit db1208b981
3 changed files with 156 additions and 4 deletions

View File

@ -334,6 +334,7 @@ body:has(.reader-mode) #toast {
overflow: hidden; overflow: hidden;
flex-shrink: 0; flex-shrink: 0;
background: var(--bg-hover); background: var(--bg-hover);
position: relative;
} }
.novel-cover img { .novel-cover img {
@ -342,6 +343,111 @@ body:has(.reader-mode) #toast {
object-fit: cover; 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 { .novel-info {
flex: 1; flex: 1;
min-width: 0; min-width: 0;
@ -411,6 +517,34 @@ body:has(.reader-mode) #toast {
height: 100%; 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 { .novel-grid-card .novel-info {
padding: var(--spacing-sm); padding: var(--spacing-sm);
} }

View File

@ -350,6 +350,9 @@ const App = (function() {
$btn.find('svg path').attr('d', 'M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z'); $btn.find('svg path').attr('d', 'M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z');
$btn.contents().last()[0].textContent = '加入书架'; $btn.contents().last()[0].textContent = '加入书架';
Toast.show('已从书架移除'); Toast.show('已从书架移除');
// 更新当前页面所有相关书籍卡片的书架标识
$(`.novel-card[data-novel-id="${novelId}"] .shelf-badge, .novel-grid-card[data-novel-id="${novelId}"] .shelf-badge`).remove();
} else { } else {
await API.addToShelf({ userId: Store.getUserId(), novelId }); await API.addToShelf({ userId: Store.getUserId(), novelId });
Store.addToShelfCache(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.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 = '已在书架'; $btn.contents().last()[0].textContent = '已在书架';
Toast.show('已加入书架'); Toast.show('已加入书架');
// 更新当前页面所有相关书籍卡片的书架标识
// 列表视图显示文字,网格视图只显示图标
const badgeHtmlList = '<div class="shelf-badge" title="已在书架"><svg viewBox="0 0 24 24"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg><span class="shelf-badge-text">已收藏</span></div>';
const badgeHtmlGrid = '<div class="shelf-badge" title="已在书架"><svg viewBox="0 0 24 24"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg></div>';
$(`.novel-card[data-novel-id="${novelId}"] .novel-cover`).append(badgeHtmlList);
$(`.novel-grid-card[data-novel-id="${novelId}"] .novel-cover`).append(badgeHtmlGrid);
} }
} catch (error) { } catch (error) {
Toast.show('操作失败'); Toast.show('操作失败');

View File

@ -199,10 +199,12 @@ const Pages = (function() {
let html = ''; let html = '';
novels.forEach(novel => { novels.forEach(novel => {
const isInShelf = Store.isInShelfCache(novel.id);
html += ` html += `
<div class="novel-grid-card" data-novel-id="${novel.id}"> <div class="novel-grid-card" data-novel-id="${novel.id}">
<div class="novel-cover"> <div class="novel-cover">
<img src="${novel.cover || ''}" alt="${novel.name}" loading="lazy"> <img src="${novel.cover || ''}" alt="${novel.name}" loading="lazy">
${isInShelf ? '<div class="shelf-badge" title="已在书架"><svg viewBox="0 0 24 24"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg></div>' : ''}
</div> </div>
<div class="novel-info"> <div class="novel-info">
<div class="novel-title text-ellipsis">${novel.name}</div> <div class="novel-title text-ellipsis">${novel.name}</div>
@ -285,7 +287,8 @@ const Pages = (function() {
let html = ''; let html = '';
novels.forEach(novel => { novels.forEach(novel => {
html += renderNovelCard(novel); const isInShelf = Store.isInShelfCache(novel.id);
html += renderNovelCard(novel, isInShelf);
}); });
if (append) { 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 synopsis = cleanContent(novel.synopsis).substring(0, 80);
const tags = (novel.tags || []).slice(0, 2); const tags = (novel.tags || []).slice(0, 2);
// 检查书架状态(如果没有传入,则从缓存中检查)
const isInShelf = inShelf !== undefined ? inShelf : Store.isInShelfCache(novel.id);
return ` return `
<div class="novel-card" data-novel-id="${novel.id}"> <div class="novel-card" data-novel-id="${novel.id}">
<div class="novel-cover"> <div class="novel-cover">
<img src="${novel.cover || ''}" alt="${novel.name}" loading="lazy"> <img src="${novel.cover || ''}" alt="${novel.name}" loading="lazy">
${isInShelf ? '<div class="shelf-badge" title="已在书架"><svg viewBox="0 0 24 24"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg><span class="shelf-badge-text">已收藏</span></div>' : ''}
</div> </div>
<div class="novel-info"> <div class="novel-info">
<div class="novel-title text-ellipsis">${novel.name}</div> <div class="novel-title text-ellipsis">${novel.name}</div>
@ -656,7 +662,8 @@ const Pages = (function() {
let html = '<div class="novel-list">'; let html = '<div class="novel-list">';
novels.forEach(novel => { novels.forEach(novel => {
html += renderNovelCard(novel); const isInShelf = Store.isInShelfCache(novel.id);
html += renderNovelCard(novel, isInShelf);
}); });
html += '</div>'; html += '</div>';
@ -688,7 +695,8 @@ const Pages = (function() {
let html = '<div class="novel-list">'; let html = '<div class="novel-list">';
novels.forEach(novel => { novels.forEach(novel => {
html += renderNovelCard(novel); const isInShelf = Store.isInShelfCache(novel.id);
html += renderNovelCard(novel, isInShelf);
}); });
html += '</div>'; html += '</div>';