// ==UserScript==
// @name 【云】福币记收藏功能增强
// @namespace http://tampermonkey.net/
// @version 2.2
// @description 为福币记网站添加收藏功能,支持列表页和详情页收藏,可导出JSON,支持图片预加载,田字格布局,搜索排序,浏览次数统计,清空未加载浏览记录,黑名单功能,优化图片加载性能,支持分页功能
// @author You
// @match https://fubiji.site/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// 添加样式
GM_addStyle(`
.favorite-btn {
background: #9e9e9e;
color: white;
border: none;
padding: 0px 10px 5px 10px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
margin-right: 5px;
transition: all 0.3s;
min-width: 50px;
text-align: center;
}
.favorite-btn:hover {
background: #757575;
transform: scale(1.05);
}
.favorite-btn.favorited {
background: #4caf50;
}
.favorite-btn.favorited:hover {
background: #45a049;
}
.super-like-btn {
background: #9e9e9e;
color: white;
border: none;
padding: 0px 10px 5px 10px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
margin-right: 5px;
transition: all 0.3s;
min-width: 50px;
text-align: center;
}
.super-like-btn:hover {
background: #757575;
transform: scale(1.05);
}
.super-like-btn.super-liked {
background: #ff9800;
}
.super-like-btn.super-liked:hover {
background: #f57c00;
}
.view-count {
background: #2196f3;
color: white;
border: none;
padding: 0px 10px 5px 10px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
margin-left: 5px;
transition: all 0.3s;
display: inline-flex;
align-items: center;
gap: 4px;
}
.view-count:hover {
background: #1976d2;
transform: scale(1.05);
}
.view-count-icon {
font-size: 10px;
}
.favorites-panel {
position: fixed;
top: 20px;
right: 20px;
width: 850px;
max-height: 700px;
background: white;
border: 1px solid #ddd;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 10000;
font-family: Arial, sans-serif;
overflow: hidden;
}
.favorites-header {
background: #f8f9fa;
padding: 15px 20px;
border-bottom: 1px solid #ddd;
display: flex;
justify-content: space-between;
align-items: center;
}
.favorites-title {
font-weight: bold;
color: #333;
font-size: 16px;
}
.favorites-close {
background: none;
border: none;
font-size: 20px;
cursor: pointer;
color: #666;
}
.favorites-close:hover {
color: #333;
}
.favorites-content {
max-height: 350px;
overflow-y: auto;
padding: 12px;
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 6px;
}
.favorites-search {
padding: 15px 20px;
border-bottom: 1px solid #ddd;
background: #f8f9fa;
}
.search-input {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 12px;
box-sizing: border-box;
}
.search-input:focus {
outline: none;
border-color: #007bff;
}
.favorites-sort {
padding: 10px 20px;
border-bottom: 1px solid #ddd;
background: #f8f9fa;
display: flex;
gap: 10px;
align-items: center;
}
.sort-select {
padding: 5px 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 12px;
background: white;
}
.sort-select:focus {
outline: none;
border-color: #007bff;
}
.favorite-item {
padding: 4px;
border: 1px solid #eee;
cursor: pointer;
transition: all 0.2s;
border-radius: 4px;
background: white;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
width: 100%;
max-width: 130px;
min-width: 110px;
}
.list-item-images {
margin-top: 8px;
height: 200px;
overflow: hidden;
border-radius: 4px;
position: relative;
background: #f5f5f5;
z-index: 1;
display: block !important;
}
.list-item-images-container {
display: flex;
height: 100%;
transition: transform 0.3s ease;
}
.list-item-image {
min-width: 33.333%;
height: 200px;
object-fit: cover;
border: none;
opacity: 0;
transition: opacity 0.3s ease;
display: block !important;
visibility: visible !important;
}
.list-item-image.loaded {
opacity: 1;
}
.list-item-image-nav {
position: absolute;
top: 50%;
transform: translateY(-50%);
background: rgba(0,0,0,0.7);
color: white;
border: none;
width: 32px;
height: 32px;
border-radius: 50%;
cursor: pointer;
font-size: 16px;
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
transition: all 0.3s;
}
.list-item-image-nav:hover {
background: rgba(0,0,0,0.9);
transform: translateY(-50%) scale(1.1);
}
.list-item-image-nav.prev {
left: 4px;
}
.list-item-image-nav.next {
right: 4px;
}
.list-item-image-nav.hidden {
display: none;
}
.list-item-image-indicator {
position: absolute;
bottom: 4px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 4px;
z-index: 10;
}
.list-item-image-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: rgba(255,255,255,0.5);
cursor: pointer;
transition: background 0.3s;
}
.list-item-image-dot.active {
background: rgba(255,255,255,0.9);
}
.favorite-item:hover {
background: #f8f9fa;
box-shadow: 0 2px 6px rgba(0,0,0,0.15);
transform: translateY(-1px);
}
.favorite-item.super-liked {
border: 2px solid #ff9800;
background: linear-gradient(135deg, #fff3e0 0%, #ffffff 100%);
}
.favorite-item.super-liked:hover {
background: linear-gradient(135deg, #ffe0b2 0%, #f8f9fa 100%);
}
.favorite-item.no-images {
border: 2px solid #f44336;
background: linear-gradient(135deg, #ffebee 0%, #ffffff 100%);
}
.favorite-item.no-images:hover {
background: linear-gradient(135deg, #ffcdd2 0%, #f8f9fa 100%);
}
.favorite-item.super-liked.no-images {
border: 2px solid #f44336;
background: linear-gradient(135deg, #ffebee 0%, #fff3e0 100%);
}
.favorite-item.super-liked.no-images:hover {
background: linear-gradient(135deg, #ffcdd2 0%, #ffe0b2 100%);
}
.super-like-badge {
position: absolute;
top: -2px;
right: -2px;
background: #ff9800;
color: white;
border-radius: 50%;
width: 16px;
height: 16px;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
font-weight: bold;
z-index: 5;
}
/* 列表项背景色样式 */
.format-standard.viewed-not-favorited {
background: linear-gradient(135deg, #ffebee 0%, #ffffff 100%);
border-left: 4px solid #f44336;
}
.format-standard.viewed-not-favorited:hover {
background: linear-gradient(135deg, #ffcdd2 0%, #f8f9fa 100%);
}
.format-standard.favorited {
background: linear-gradient(135deg, #e8f5e8 0%, #ffffff 100%);
border-left: 4px solid #4caf50;
}
.format-standard.favorited:hover {
background: linear-gradient(135deg, #c8e6c9 0%, #f8f9fa 100%);
}
.format-standard.viewed-and-favorited {
background: linear-gradient(135deg, #e8f5e8 0%, #ffffff 100%);
border-left: 4px solid #4caf50;
}
.format-standard.viewed-and-favorited:hover {
background: linear-gradient(135deg, #c8e6c9 0%, #f8f9fa 100%);
}
/* 列表页左侧浮动按钮容器样式 */
.list-page .list-item-buttons {
position: absolute;
left: -350px;
top: 50%;
transform: translateY(-50%);
display: flex;
align-items: center;
gap: 5px;
min-width: 200px;
z-index: 10;
}
/* 列表页列表项相对定位,为绝对定位的按钮提供参考 */
.list-page .format-standard {
position: relative;
}
/* 详情页按钮容器样式(保持原有样式) */
.detail-page .list-item-buttons {
display: flex;
align-items: center;
gap: 5px;
margin-left: 10px;
}
.favorite-item:last-child {
margin-bottom: 0;
}
.favorite-title {
font-size: 10px;
color: #333;
margin-top: 4px;
font-weight: 500;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 100%;
line-height: 1.2;
}
.favorite-info {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 4px;
font-size: 9px;
color: #666;
}
.favorite-views {
display: flex;
align-items: center;
gap: 2px;
}
.favorite-views-icon {
font-size: 8px;
}
.favorite-date {
font-size: 8px;
color: #999;
}
.favorite-images {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1px;
width: 100%;
aspect-ratio: 1;
border: 1px solid #ddd;
border-radius: 4px;
overflow: hidden;
max-width: 100%;
}
.favorite-image {
width: 100%;
height: 100%;
object-fit: cover;
border: none;
transition: transform 0.2s;
background: #f5f5f5;
opacity: 0;
transition: opacity 0.3s ease;
}
.favorite-image.loaded {
opacity: 1;
}
.favorite-image:hover {
transform: scale(1.05);
}
.favorite-image.placeholder {
background: #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
color: #999;
font-size: 8px;
border: none;
cursor: pointer;
transition: all 0.3s;
}
.favorite-image.placeholder:hover {
background: #e0e0e0;
color: #666;
transform: scale(1.05);
}
.favorites-actions {
padding: 15px 20px;
border-top: 1px solid #ddd;
display: flex;
gap: 10px;
}
.favorites-action-btn {
background: #007bff;
color: white;
border: none;
padding: 0px 12px 8px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
flex: 1;
}
.favorites-action-btn:hover {
background: #0056b3;
}
.favorites-action-btn.danger {
background: #dc3545;
}
.favorites-action-btn.danger:hover {
background: #c82333;
}
.favorites-toggle {
position: fixed;
top: 20px;
right: 20px;
background: #007bff;
color: white;
border: none;
padding: 0px 15px 10px 15px;
border-radius: 4px;
cursor: pointer;
z-index: 9999;
font-size: 13px;
}
.favorites-toggle:hover {
background: #0056b3;
}
.preload-all-btn {
position: fixed;
top: 20px;
right: 120px;
background: #9c27b0;
color: white;
border: none;
padding: 0px 15px 10px 15px;
border-radius: 4px;
cursor: pointer;
z-index: 9999;
font-size: 13px;
transition: all 0.3s;
}
.preload-all-btn:hover {
background: #7b1fa2;
transform: scale(1.05);
}
.preload-all-btn.loading {
background: #ff9800;
pointer-events: none;
}
.preload-all-btn.loading::after {
content: '';
display: inline-block;
width: 12px;
height: 12px;
border: 2px solid transparent;
border-top: 2px solid white;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-left: 5px;
}
.clear-unloaded-views-btn {
position: fixed;
top: 20px;
right: 220px;
background: #e91e63;
color: white;
border: none;
padding: 0px 15px 10px 15px;
border-radius: 4px;
cursor: pointer;
z-index: 9999;
font-size: 13px;
transition: all 0.3s;
}
.clear-unloaded-views-btn:hover {
background: #c2185b;
transform: scale(1.05);
}
.clear-unloaded-views-btn.loading {
background: #ff9800;
pointer-events: none;
}
.clear-unloaded-views-btn.loading::after {
content: '';
display: inline-block;
width: 12px;
height: 12px;
border: 2px solid transparent;
border-top: 2px solid white;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-left: 5px;
}
/* 黑名单功能样式 */
.blacklist-toggle {
position: fixed;
top: 20px;
right: 370px;
background: #333;
color: white;
border: none;
padding: 0px 15px 10px 15px;
border-radius: 4px;
cursor: pointer;
z-index: 9999;
font-size: 13px;
transition: all 0.3s;
}
.blacklist-toggle:hover {
background: #555;
transform: scale(1.05);
}
.blacklist-panel {
position: fixed;
top: 20px;
right: 20px;
width: 400px;
max-height: 600px;
background: white;
border: 1px solid #ddd;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 10000;
font-family: Arial, sans-serif;
overflow: hidden;
display: none;
}
.blacklist-header {
background: #333;
color: white;
padding: 15px 20px;
border-bottom: 1px solid #ddd;
display: flex;
justify-content: space-between;
align-items: center;
}
.blacklist-title {
font-weight: bold;
font-size: 16px;
}
.blacklist-close {
background: none;
border: none;
font-size: 20px;
cursor: pointer;
color: white;
}
.blacklist-close:hover {
color: #ccc;
}
.blacklist-content {
padding: 20px;
max-height: 400px;
overflow-y: auto;
}
.blacklist-search {
margin-bottom: 15px;
}
.blacklist-search-input {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
box-sizing: border-box;
margin-bottom: 10px;
}
.blacklist-search-input:focus {
outline: none;
border-color: #333;
}
.blacklist-search-info {
font-size: 12px;
color: #666;
margin-bottom: 10px;
}
.blacklist-input-section {
margin-bottom: 20px;
}
.blacklist-input {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
box-sizing: border-box;
margin-bottom: 10px;
}
.blacklist-input:focus {
outline: none;
border-color: #333;
}
.blacklist-add-btn {
background: #333;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s;
}
.blacklist-add-btn:hover {
background: #555;
}
.blacklist-add-btn:disabled {
background: #ccc;
cursor: not-allowed;
}
.blacklist-items {
margin-top: 15px;
}
.blacklist-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
background: #f8f9fa;
border: 1px solid #ddd;
border-radius: 4px;
margin-bottom: 8px;
transition: all 0.3s;
}
.blacklist-item:hover {
background: #e9ecef;
}
.blacklist-item-text {
flex: 1;
font-size: 14px;
color: #333;
word-break: break-all;
}
.blacklist-item-remove {
background: #dc3545;
color: white;
border: none;
padding: 4px 8px;
border-radius: 3px;
cursor: pointer;
font-size: 12px;
margin-left: 10px;
transition: all 0.3s;
}
.blacklist-item-remove:hover {
background: #c82333;
}
.blacklist-actions {
padding: 15px 20px;
border-top: 1px solid #ddd;
display: flex;
gap: 10px;
}
.blacklist-action-btn {
background: #333;
color: white;
border: none;
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
flex: 1;
transition: all 0.3s;
}
.blacklist-action-btn:hover {
background: #555;
}
.blacklist-action-btn.danger {
background: #dc3545;
}
.blacklist-action-btn.danger:hover {
background: #c82333;
}
.blacklist-count {
background: #dc3545;
color: white;
border-radius: 50%;
width: 22px;
height: 22px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 11px;
margin-left: 6px;
}
/* 黑名单列表项样式 */
.format-standard.blacklisted {
background: #000 !important;
color: #333 !important;
border-left: 4px solid #333 !important;
opacity: 0.7;
position: relative;
}
.format-standard.blacklisted:hover {
background: #000 !important;
opacity: 0.8;
}
.format-standard.blacklisted a {
color: #333 !important;
text-decoration: none;
}
.format-standard.blacklisted a:hover {
color: #333 !important;
text-decoration: none;
}
.format-standard.blacklisted .list-item-buttons {
opacity: 0.3;
pointer-events: none;
}
.format-standard.blacklisted .list-item-buttons button {
background: #666 !important;
color: #999 !important;
cursor: not-allowed !important;
}
.format-standard.blacklisted .list-item-buttons button:hover {
background: #666 !important;
transform: none !important;
}
/* 黑名单提示样式 */
.blacklist-tooltip {
position: absolute;
top: -30px;
left: 50%;
transform: translateX(-50%);
background: #333;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 11px;
white-space: nowrap;
z-index: 1000;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s;
max-width: 300px;
overflow: hidden;
text-overflow: ellipsis;
}
.format-standard.blacklisted:hover .blacklist-tooltip {
opacity: 1;
}
.blacklist-tooltip::after {
content: '';
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
border: 4px solid transparent;
border-top-color: #333;
}
.favorites-count {
background: #ff6b6b;
color: white;
border-radius: 50%;
width: 22px;
height: 22px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 11px;
margin-left: 6px;
}
.favorites-settings {
padding: 15px 20px;
border-top: 1px solid #ddd;
background: #f8f9fa;
}
.auto-update-toggle {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
color: #666;
cursor: pointer;
}
.auto-update-toggle input[type="checkbox"] {
margin: 0;
}
.auto-update-toggle span {
user-select: none;
}
.storage-info {
margin-top: 8px;
font-size: 11px;
color: #666;
display: flex;
justify-content: space-between;
align-items: center;
}
.storage-size {
font-weight: bold;
color: #007bff;
}
.storage-warning {
color: #ff9800;
font-weight: bold;
}
.storage-danger {
color: #f44336;
font-weight: bold;
}
.favorites-message {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 15px 20px;
border-radius: 8px;
color: white;
font-size: 14px;
font-weight: bold;
z-index: 10001;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
animation: messageSlideIn 0.3s ease-out;
}
.favorites-message-success {
background: #4caf50;
}
.favorites-message-error {
background: #f44336;
}
.favorites-message-info {
background: #2196f3;
}
@keyframes messageSlideIn {
from {
opacity: 0;
transform: translate(-50%, -50%) scale(0.8);
}
to {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
}
}
.preload-btn {
background: #9c27b0;
color: white;
border: none;
padding: 0px 10px 5px 10px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
margin-left: 5px;
transition: all 0.3s;
}
.preload-btn:hover {
background: #7b1fa2;
transform: scale(1.05);
}
.preload-btn.preloaded {
background: #4caf50;
}
.preload-btn.preloaded:hover {
background: #45a049;
}
.preload-btn.loading {
background: #ff9800;
pointer-events: none;
}
.preload-btn.loading::after {
content: '';
display: inline-block;
width: 12px;
height: 12px;
border: 2px solid transparent;
border-top: 2px solid white;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-left: 5px;
}
.blacklist-btn {
background: #333;
color: white;
border: none;
padding: 0px 10px 5px 10px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
margin-left: 5px;
transition: all 0.3s;
}
.blacklist-btn:hover {
background: #555;
transform: scale(1.05);
}
.blacklist-btn.blacklisted {
background: #dc3545;
}
.blacklist-btn.blacklisted:hover {
background: #c82333;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 图片查看器样式 */
.image-viewer {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.9);
z-index: 10003;
display: none;
align-items: center;
justify-content: center;
overflow: hidden;
}
.image-viewer-content {
position: relative;
max-width: 90%;
max-height: 90vh;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
}
.image-viewer img {
max-width: 100%;
max-height: 90vh;
object-fit: contain;
border-radius: 8px;
box-shadow: 0 8px 32px rgba(0,0,0,0.5);
width: auto;
height: auto;
}
.image-viewer-close {
position: absolute;
top: -40px;
right: 0;
background: rgba(255,255,255,0.2);
color: white;
border: none;
width: 32px;
height: 32px;
border-radius: 50%;
cursor: pointer;
font-size: 18px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s;
}
.image-viewer-close:hover {
background: rgba(255,255,255,0.3);
transform: scale(1.1);
}
.image-viewer-nav {
position: absolute;
top: 50%;
transform: translateY(-50%);
background: rgba(255,255,255,0.2);
color: white;
border: none;
width: 48px;
height: 48px;
border-radius: 50%;
cursor: pointer;
font-size: 24px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s;
}
.image-viewer-nav:hover {
background: rgba(255,255,255,0.3);
transform: translateY(-50%) scale(1.1);
}
.image-viewer-nav.prev {
left: -60px;
}
.image-viewer-nav.next {
right: -60px;
}
.image-viewer-info {
position: absolute;
bottom: -40px;
left: 50%;
transform: translateX(-50%);
color: white;
font-size: 14px;
background: rgba(0,0,0,0.5);
padding: 8px 16px;
border-radius: 20px;
}
.image-viewer-counter {
position: absolute;
top: -40px;
left: 50%;
transform: translateX(-50%);
color: white;
font-size: 14px;
background: rgba(0,0,0,0.5);
padding: 8px 16px;
border-radius: 20px;
}
/* 图片点击效果 */
.list-item-image {
cursor: pointer;
transition: transform 0.2s;
}
.list-item-image:hover {
transform: scale(1.05);
}
.favorite-image {
cursor: pointer;
transition: transform 0.2s;
}
.favorite-image:hover {
transform: scale(1.05);
}
/* 分页控件样式 */
.pagination-container {
display: flex;
justify-content: center;
align-items: center;
margin: 15px 0;
padding: 5px 0;
border-top: 1px solid #eee;
}
.pagination-btn {
background: #f8f9fa;
border: 1px solid #ddd;
color: #333;
padding: 4px 8px;
margin: 0 3px;
border-radius: 3px;
cursor: pointer;
font-size: 12px;
transition: all 0.2s;
}
.pagination-btn:hover {
background: #e9ecef;
border-color: #adb5bd;
}
.pagination-btn.active {
background: #007bff;
color: white;
border-color: #007bff;
}
.pagination-btn.disabled {
background: #f8f9fa;
color: #adb5bd;
cursor: not-allowed;
border-color: #eee;
}
.page-size-container {
display: flex;
align-items: center;
margin-left: 15px;
font-size: 12px;
color: #666;
}
.page-size-select {
padding: 3px 5px;
border: 1px solid #ddd;
border-radius: 3px;
margin-left: 5px;
font-size: 12px;
}
.pagination-info {
margin-right: 15px;
font-size: 12px;
color: #666;
}
`);
// 图片查看器管理
class ImageViewer {
constructor() {
this.currentImages = [];
this.currentIndex = 0;
this.viewer = null;
this.init();
}
init() {
this.createViewer();
this.bindEvents();
}
createViewer() {
this.viewer = document.createElement('div');
this.viewer.className = 'image-viewer';
this.viewer.innerHTML = `
<div class="image-viewer-content">
<button class="image-viewer-close">×</button>
<button class="image-viewer-nav prev">‹</button>
<button class="image-viewer-nav next">›</button>
<div class="image-viewer-counter"></div>
<div class="image-viewer-info"></div>
<img src="" alt="查看图片" style="max-height: 90vh; max-width: 90%;">
</div>
`;
document.body.appendChild(this.viewer);
// 绑定关闭按钮
this.viewer.querySelector('.image-viewer-close').onclick = () => {
this.hide();
};
// 绑定导航按钮
this.viewer.querySelector('.image-viewer-nav.prev').onclick = () => {
this.prev();
};
this.viewer.querySelector('.image-viewer-nav.next').onclick = () => {
this.next();
};
// 点击背景关闭
this.viewer.onclick = (e) => {
if (e.target === this.viewer) {
this.hide();
}
};
}
bindEvents() {
// 键盘事件
document.addEventListener('keydown', (e) => {
if (this.viewer.style.display === 'flex') {
switch (e.key) {
case 'Escape':
this.hide();
break;
case 'ArrowLeft':
this.prev();
break;
case 'ArrowRight':
this.next();
break;
}
}
});
}
show(images, startIndex = 0) {
if (!images || images.length === 0) return;
this.currentImages = images;
this.currentIndex = startIndex;
this.updateDisplay();
this.viewer.style.display = 'flex';
}
hide() {
this.viewer.style.display = 'none';
this.currentImages = [];
this.currentIndex = 0;
}
prev() {
if (this.currentImages.length === 0) return;
this.currentIndex = (this.currentIndex - 1 + this.currentImages.length) % this.currentImages.length;
this.updateDisplay();
}
next() {
if (this.currentImages.length === 0) return;
this.currentIndex = (this.currentIndex + 1) % this.currentImages.length;
this.updateDisplay();
}
updateDisplay() {
const img = this.viewer.querySelector('img');
const counter = this.viewer.querySelector('.image-viewer-counter');
const info = this.viewer.querySelector('.image-viewer-info');
const prevBtn = this.viewer.querySelector('.image-viewer-nav.prev');
const nextBtn = this.viewer.querySelector('.image-viewer-nav.next');
if (this.currentImages.length > 0) {
// 先清除旧图片的src,避免切换时出现旧图片
img.src = '';
// 设置新图片加载事件
img.onload = () => {
// 图片加载完成后,根据图片比例调整显示
const imgRatio = img.naturalWidth / img.naturalHeight;
const viewerContent = this.viewer.querySelector('.image-viewer-content');
if (imgRatio > 1) {
// 横向图片,以宽度为主
img.style.width = 'auto';
img.style.height = 'auto';
img.style.maxWidth = '90%';
img.style.maxHeight = '90vh';
} else {
// 纵向图片,以高度为主
img.style.width = 'auto';
img.style.height = 'auto';
img.style.maxHeight = '90vh';
img.style.maxWidth = '90%';
}
};
// 设置新图片
img.src = this.currentImages[this.currentIndex];
counter.textContent = `${this.currentIndex + 1} / ${this.currentImages.length}`;
// 显示图片信息(如果有的话)
const imageUrl = this.currentImages[this.currentIndex];
const fileName = imageUrl.split('/').pop().split('?')[0];
info.textContent = fileName;
// 更新导航按钮状态
prevBtn.style.display = this.currentImages.length > 1 ? 'flex' : 'none';
nextBtn.style.display = this.currentImages.length > 1 ? 'flex' : 'none';
}
}
}
// 预加载管理
class PreloadManager {
constructor() {
this.preloadedUrls = new Map(); // 存储预加载的URL和对应的数据
this.preloadQueue = []; // 预加载队列
this.maxPreloads = 100; // 增加最大预加载数量,避免频繁清理
}
// 预加载URL - 使用fetch方式优化
async preloadUrl(url, title, retryCount = 0) {
if (this.preloadedUrls.has(url)) {
return Promise.resolve(this.preloadedUrls.get(url));
}
// 如果预加载数量超过限制,移除最旧的(但保留当前页面的预加载内容)
if (this.preloadedUrls.size >= this.maxPreloads) {
this.cleanupOldPreloads();
}
const maxRetries = 2; // 最大重试次数
try {
console.log(`开始预加载: ${url} (尝试 ${retryCount + 1}/${maxRetries + 1})`);
let htmlText = '';
// 使用fetch获取HTML内容
try {
const response = await fetch(url, {
method: 'GET',
headers: {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Cache-Control': 'no-cache',
'Pragma': 'no-cache'
},
mode: 'cors',
credentials: 'omit' // 不发送cookies,避免跨域问题
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
htmlText = await response.text();
} catch (fetchError) {
console.log('fetch请求失败,尝试使用GM_xmlhttpRequest:', fetchError);
// 如果fetch失败,尝试使用GM_xmlhttpRequest(如果可用)
if (typeof GM_xmlhttpRequest !== 'undefined') {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'GET',
url: url,
headers: {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8'
},
onload: function(response) {
if (response.status === 200) {
const images = this.extractImagesFromHTML(response.responseText);
const preloadData = {
title: title,
loaded: true,
timestamp: Date.now(),
images: images,
html: response.responseText
};
this.preloadedUrls.set(url, preloadData);
resolve(preloadData);
} else {
reject(new Error(`HTTP ${response.status}: ${response.statusText}`));
}
}.bind(this),
onerror: function(error) {
reject(new Error('GM_xmlhttpRequest请求失败'));
}
});
});
} else {
throw fetchError;
}
}
// 解析HTML获取图片
const images = this.extractImagesFromHTML(htmlText);
console.log(`预加载完成: ${url}, 获取到 ${images.length} 张图片`);
const preloadData = {
title: title,
loaded: true,
timestamp: Date.now(),
images: images,
html: htmlText // 保存HTML以备后用
};
this.preloadedUrls.set(url, preloadData);
return preloadData;
} catch (error) {
console.error(`预加载失败 (尝试 ${retryCount + 1}/${maxRetries + 1}):`, url, error);
// 如果还有重试次数,则重试
if (retryCount < maxRetries) {
console.log(`等待1秒后重试: ${url}`);
await new Promise(resolve => setTimeout(resolve, 1000));
return this.preloadUrl(url, title, retryCount + 1);
}
throw error;
}
}
// 从HTML中提取图片URL
extractImagesFromHTML(htmlText) {
const images = [];
try {
// 创建临时DOM解析HTML
const parser = new DOMParser();
const doc = parser.parseFromString(htmlText, 'text/html');
// 查找.entry-content中的图片
const entryContent = doc.querySelector('.entry-content');
if (entryContent) {
const imgElements = entryContent.querySelectorAll('img');
imgElements.forEach(img => {
const src = img.src || img.getAttribute('data-src') || img.getAttribute('data-lazy-src') || img.getAttribute('data-original');
if (src && src.trim()) {
const lowerSrc = src.toLowerCase();
// 过滤掉不需要的图片
if (!lowerSrc.includes('emoji') &&
!lowerSrc.includes('icon') &&
!lowerSrc.includes('avatar') &&
!lowerSrc.includes('logo') &&
!lowerSrc.includes('banner') &&
!lowerSrc.includes('ad') &&
!lowerSrc.includes('loading') &&
!lowerSrc.includes('placeholder') &&
(img.width > 50 || img.width === 0) &&
(img.height > 50 || img.height === 0)) {
images.push(src.trim());
}
}
});
}
// 如果没有找到.entry-content,尝试查找所有图片
if (images.length === 0) {
const allImages = doc.querySelectorAll('img');
allImages.forEach(img => {
const src = img.src || img.getAttribute('data-src') || img.getAttribute('data-lazy-src') || img.getAttribute('data-original');
if (src && src.trim()) {
const lowerSrc = src.toLowerCase();
if (!lowerSrc.includes('emoji') &&
!lowerSrc.includes('icon') &&
!lowerSrc.includes('avatar') &&
!lowerSrc.includes('logo') &&
!lowerSrc.includes('banner') &&
!lowerSrc.includes('ad') &&
!lowerSrc.includes('loading') &&
!lowerSrc.includes('placeholder') &&
(img.width > 50 || img.width === 0) &&
(img.height > 50 || img.height === 0)) {
images.push(src.trim());
}
}
});
}
// 去重并限制数量
const uniqueImages = [...new Set(images)];
const limitedImages = uniqueImages.slice(0, 10); // 最多取10张图片
console.log(`从HTML中提取到 ${images.length} 张图片,去重后 ${uniqueImages.length} 张,限制后 ${limitedImages.length} 张`);
return limitedImages;
} catch (error) {
console.error('解析HTML提取图片失败:', error);
}
return images;
}
// 获取预加载的内容
getPreloaded(url) {
return this.preloadedUrls.get(url);
}
// 检查是否已预加载
isPreloaded(url) {
return this.preloadedUrls.has(url);
}
// 移除预加载
removePreload(url) {
const preload = this.preloadedUrls.get(url);
this.preloadedUrls.delete(url);
// 清理对应的图片显示
if (window.pageHandler && window.pageHandler.ui) {
const items = document.querySelectorAll('.format-standard');
items.forEach(item => {
const link = item.querySelector('a');
if (link && link.href === url) {
const imagesContainer = item.querySelector('.list-item-images');
if (imagesContainer) {
imagesContainer.remove();
}
// 重置预加载按钮状态
const preloadBtn = item.querySelector('.preload-btn');
if (preloadBtn) {
preloadBtn.classList.remove('preloaded');
preloadBtn.textContent = '预加载';
}
}
});
}
}
// 智能清理旧预加载内容
cleanupOldPreloads() {
// 获取当前页面的所有URL
const currentPageUrls = new Set();
const items = document.querySelectorAll('.format-standard');
items.forEach(item => {
const link = item.querySelector('a');
if (link) {
currentPageUrls.add(link.href);
}
});
// 按时间戳排序,保留最新的和当前页面的
const sortedUrls = Array.from(this.preloadedUrls.entries())
.sort((a, b) => a[1].timestamp - b[1].timestamp);
// 移除最旧的,但保留当前页面的内容
let removedCount = 0;
for (const [url, preload] of sortedUrls) {
// 如果是当前页面的URL,跳过
if (currentPageUrls.has(url)) {
continue;
}
// 移除最旧的预加载内容
this.removePreload(url);
removedCount++;
// 如果移除了一些内容,检查是否还需要继续移除
if (this.preloadedUrls.size < this.maxPreloads * 0.8) {
break;
}
}
console.log(`清理了 ${removedCount} 个旧预加载内容,当前剩余: ${this.preloadedUrls.size}`);
}
// 清空所有预加载
clearAll() {
this.preloadedUrls.clear();
// 清理所有图片显示
if (window.pageHandler && window.pageHandler.ui) {
const imagesContainers = document.querySelectorAll('.list-item-images');
imagesContainers.forEach(container => {
container.remove();
});
// 重置所有预加载按钮状态
const preloadBtns = document.querySelectorAll('.preload-btn');
preloadBtns.forEach(btn => {
btn.classList.remove('preloaded', 'loading');
btn.textContent = '预加载';
});
}
}
// 清空非当前页面的预加载内容
clearNonCurrentPagePreloads() {
// 获取当前页面的所有URL
const currentPageUrls = new Set();
const items = document.querySelectorAll('.format-standard');
items.forEach(item => {
const link = item.querySelector('a');
if (link) {
currentPageUrls.add(link.href);
}
});
// 只移除非当前页面的预加载内容
const urlsToRemove = [];
this.preloadedUrls.forEach((preload, url) => {
if (!currentPageUrls.has(url)) {
urlsToRemove.push(url);
}
});
urlsToRemove.forEach(url => {
this.removePreload(url);
});
console.log(`清空了 ${urlsToRemove.length} 个非当前页面的预加载内容`);
}
}
// 黑名单管理
class BlacklistManager {
constructor() {
this.blacklist = this.loadBlacklist();
this.initCrossTabSync();
}
initCrossTabSync() {
// 监听其他标签页的数据更新
window.addEventListener('storage', (e) => {
if (e.key === 'fubiji_blacklist' && e.newValue) {
try {
this.blacklist = JSON.parse(e.newValue);
console.log('收到其他标签页的黑名单数据更新');
// 触发UI更新
this.notifyDataChanged();
} catch (error) {
console.error('解析其他标签页黑名单数据失败:', error);
}
}
});
// 监听自定义事件(同一标签页内的更新)
window.addEventListener('blacklistUpdated', (e) => {
if (e.detail && e.detail.blacklist) {
this.blacklist = e.detail.blacklist;
console.log('收到同标签页的黑名单数据更新');
this.notifyDataChanged();
}
});
}
notifyDataChanged() {
// 通知UI更新
if (window.pageHandler && window.pageHandler.ui) {
window.pageHandler.ui.updateBlacklistToggleButton();
window.pageHandler.ui.updateBlacklistPanel();
window.pageHandler.updateAllBlacklistStates();
}
}
loadBlacklist() {
const saved = localStorage.getItem('fubiji_blacklist') || '[]';
try {
return JSON.parse(saved);
} catch (e) {
console.error('加载黑名单数据失败:', e);
return [];
}
}
saveBlacklist() {
try {
localStorage.setItem('fubiji_blacklist', JSON.stringify(this.blacklist));
// 触发自定义事件,通知其他标签页数据已更新
window.dispatchEvent(new CustomEvent('blacklistUpdated', {
detail: { blacklist: this.blacklist }
}));
} catch (e) {
console.error('保存黑名单数据失败:', e);
}
}
addBlacklistItem(text) {
const trimmedText = text.trim();
if (!trimmedText) return false;
if (!this.blacklist.includes(trimmedText)) {
this.blacklist.push(trimmedText);
this.saveBlacklist();
console.log(`添加黑名单项: ${trimmedText}`);
// 标记收藏管理器有数据变化
if (window.pageHandler && window.pageHandler.manager) {
window.pageHandler.manager.hasChanges = true;
}
return true;
}
return false;
}
removeBlacklistItem(text) {
const index = this.blacklist.indexOf(text);
if (index !== -1) {
this.blacklist.splice(index, 1);
this.saveBlacklist();
console.log(`移除黑名单项: ${text}`);
// 标记收藏管理器有数据变化
if (window.pageHandler && window.pageHandler.manager) {
window.pageHandler.manager.hasChanges = true;
}
return true;
}
return false;
}
isBlacklisted(title) {
if (!title) return false;
const lowerTitle = title.toLowerCase();
return this.blacklist.some(item =>
lowerTitle.includes(item.toLowerCase())
);
}
// 获取匹配的黑名单关键字
getMatchingBlacklistKeywords(title) {
if (!title) return [];
const lowerTitle = title.toLowerCase();
return this.blacklist.filter(item =>
lowerTitle.includes(item.toLowerCase())
);
}
getBlacklist() {
return this.blacklist;
}
clearAll() {
this.blacklist = [];
this.saveBlacklist();
// 标记收藏管理器有数据变化
if (window.pageHandler && window.pageHandler.manager) {
window.pageHandler.manager.hasChanges = true;
}
this.notifyDataChanged();
}
exportJSON() {
const exportData = {
blacklist: this.blacklist,
exportTime: Date.now(),
version: '1.0'
};
return JSON.stringify(exportData, null, 2);
}
importJSON(jsonString) {
try {
const data = JSON.parse(jsonString);
let importBlacklist = [];
// 兼容旧版本数据(只有黑名单数组)
if (Array.isArray(data)) {
importBlacklist = data;
}
// 新版本数据(包含黑名单和其他信息)
else if (data.blacklist && Array.isArray(data.blacklist)) {
importBlacklist = data.blacklist;
} else {
return false;
}
// 获取当前黑名单
const currentBlacklist = [...this.blacklist];
// 合并黑名单,去重
const mergedBlacklist = [...new Set([...currentBlacklist, ...importBlacklist])];
// 计算新增的数量
const addedCount = mergedBlacklist.length - currentBlacklist.length;
// 更新黑名单
this.blacklist = mergedBlacklist;
this.saveBlacklist();
this.notifyDataChanged();
// 返回导入结果信息
return {
success: true,
totalCount: mergedBlacklist.length,
addedCount: addedCount,
existingCount: currentBlacklist.length,
importCount: importBlacklist.length
};
} catch (e) {
console.error('导入黑名单失败:', e);
return false;
}
}
}
// 浏览记录管理
class ViewHistoryManager {
constructor() {
this.viewHistory = this.loadViewHistory();
this.maxHistorySize = 1000; // 最大历史记录数量
this.cleanupThreshold = 800; // 清理阈值
}
loadViewHistory() {
const saved = localStorage.getItem('fubiji_view_history') || '{}';
try {
const compressedData = JSON.parse(saved);
// 检查是否为压缩格式数据
if (compressedData && typeof compressedData === 'object') {
const firstKey = Object.keys(compressedData)[0];
if (firstKey && compressedData[firstKey] && typeof compressedData[firstKey] === 'object') {
const firstRecord = compressedData[firstKey];
// 如果是压缩格式(包含 c, l, f 字段),则解压缩
if ('c' in firstRecord || 'l' in firstRecord || 'f' in firstRecord) {
const decompressedData = {};
Object.keys(compressedData).forEach(id => {
const record = compressedData[id];
decompressedData[id] = {
count: record.c || 0,
lastView: record.l || 0,
firstView: record.f || 0
};
});
return decompressedData;
}
}
}
// 如果不是压缩格式,直接返回
return compressedData;
} catch (e) {
console.error('加载浏览历史失败:', e);
return {};
}
}
saveViewHistory() {
try {
// 压缩数据,只保留关键信息
const compressedData = {};
Object.keys(this.viewHistory).forEach(id => {
const record = this.viewHistory[id];
compressedData[id] = {
c: record.count || 0, // count
l: record.lastView || 0, // lastView
f: record.firstView || 0 // firstView
};
});
localStorage.setItem('fubiji_view_history', JSON.stringify(compressedData));
// 触发自定义事件,通知其他标签页数据已更新
window.dispatchEvent(new CustomEvent('viewHistoryUpdated', {
detail: { viewHistory: this.viewHistory }
}));
} catch (e) {
console.error('保存浏览历史失败:', e);
}
}
// 增加浏览次数
incrementView(id) {
const now = Date.now();
if (!this.viewHistory[id]) {
this.viewHistory[id] = {
count: 0,
firstView: now,
lastView: now
};
console.log(`创建新的浏览记录 ID: ${id}`);
}
this.viewHistory[id].count++;
this.viewHistory[id].lastView = now;
console.log(`增加浏览次数 ID: ${id}, 新次数: ${this.viewHistory[id].count}`);
// 检查是否需要清理旧数据
if (Object.keys(this.viewHistory).length > this.cleanupThreshold) {
this.cleanupOldRecords();
}
this.saveViewHistory();
}
// 获取浏览次数
getViewCount(id) {
const record = this.viewHistory[id];
if (record) {
const count = record.count || 0;
console.log(`获取浏览次数 ID: ${id}, 次数: ${count}`);
return count;
} else {
console.log(`获取浏览次数 ID: ${id}, 无记录,返回 0`);
return 0;
}
}
// 获取最后浏览时间
getLastView(id) {
return this.viewHistory[id] ? this.viewHistory[id].lastView : 0;
}
// 清理旧记录
cleanupOldRecords() {
const records = Object.entries(this.viewHistory);
// 按最后浏览时间排序,保留最新的记录
records.sort((a, b) => b[1].lastView - a[1].lastView);
// 只保留最新的记录
const newHistory = {};
records.slice(0, this.maxHistorySize).forEach(([id, record]) => {
newHistory[id] = record;
});
this.viewHistory = newHistory;
console.log(`清理浏览历史,保留 ${Object.keys(this.viewHistory).length} 条记录`);
}
// 清空所有记录
clearAll() {
this.viewHistory = {};
this.saveViewHistory();
}
// 导出数据(包含完整信息)
exportData() {
const exportData = {};
Object.keys(this.viewHistory).forEach(id => {
const record = this.viewHistory[id];
exportData[id] = {
count: record.count,
firstView: record.firstView,
lastView: record.lastView
};
});
return exportData;
}
// 导入数据
importData(data) {
try {
if (typeof data === 'object' && data !== null) {
this.viewHistory = data;
this.saveViewHistory();
return true;
}
} catch (e) {
console.error('导入浏览历史失败:', e);
}
return false;
}
// 获取所有浏览记录
getAllRecords() {
return this.viewHistory;
}
}
// 收藏数据管理
class FavoritesManager {
constructor() {
this.favorites = this.loadFavorites();
this.viewHistoryManager = new ViewHistoryManager();
this.blacklistManager = new BlacklistManager();
this.hasChanges = false; // 标记是否有数据变化
this.initCrossTabSync();
}
initCrossTabSync() {
// 监听其他标签页的数据更新
window.addEventListener('storage', (e) => {
if (e.key === 'fubiji_favorites' && e.newValue) {
try {
this.favorites = JSON.parse(e.newValue);
console.log('收到其他标签页的收藏数据更新');
// 触发UI更新
this.notifyDataChanged();
} catch (error) {
console.error('解析其他标签页数据失败:', error);
}
}
if (e.key === 'fubiji_view_history' && e.newValue) {
try {
const compressedData = JSON.parse(e.newValue);
// 解压缩数据
this.viewHistoryManager.viewHistory = {};
Object.keys(compressedData).forEach(id => {
const record = compressedData[id];
this.viewHistoryManager.viewHistory[id] = {
count: record.c || 0,
lastView: record.l || 0,
firstView: record.f || 0
};
});
console.log('收到其他标签页的浏览历史更新');
this.notifyDataChanged();
} catch (error) {
console.error('解析其他标签页浏览历史失败:', error);
}
}
});
// 监听自定义事件(同一标签页内的更新)
window.addEventListener('favoritesUpdated', (e) => {
if (e.detail && e.detail.favorites) {
this.favorites = e.detail.favorites;
console.log('收到同标签页的收藏数据更新');
this.notifyDataChanged();
}
});
window.addEventListener('viewHistoryUpdated', (e) => {
if (e.detail && e.detail.viewHistory) {
this.viewHistoryManager.viewHistory = e.detail.viewHistory;
console.log('收到同标签页的浏览历史更新');
this.notifyDataChanged();
}
});
}
notifyDataChanged() {
// 通知UI更新
if (window.pageHandler && window.pageHandler.ui) {
window.pageHandler.ui.updateToggleButton();
window.pageHandler.ui.updatePanel();
window.pageHandler.updateAllFavoriteStates();
window.pageHandler.updateAllViewCounts();
}
}
loadFavorites() {
const saved = localStorage.getItem('fubiji_favorites') || '[]';
try {
return JSON.parse(saved);
} catch (e) {
console.error('加载收藏数据失败:', e);
return [];
}
}
saveFavorites() {
try {
localStorage.setItem('fubiji_favorites', JSON.stringify(this.favorites));
// 触发自定义事件,通知其他标签页数据已更新
window.dispatchEvent(new CustomEvent('favoritesUpdated', {
detail: { favorites: this.favorites }
}));
} catch (e) {
console.error('保存收藏数据失败:', e);
}
}
addFavorite(id, title, url, images = []) {
if (!this.favorites.find(f => f.id === id)) {
const hasImages = images && images.length > 0;
this.favorites.push({
id,
title,
url,
images,
timestamp: Date.now(),
superLiked: false,
hasImages: hasImages
});
this.hasChanges = true; // 标记有变化
this.saveFavorites();
console.log(`添加收藏: ${id}, 标题: ${title}, 有图片: ${hasImages}, 图片数量: ${images.length}`);
return true;
}
return false;
}
removeFavorite(id) {
const index = this.favorites.findIndex(f => f.id === id);
if (index !== -1) {
this.favorites.splice(index, 1);
this.hasChanges = true; // 标记有变化
this.saveFavorites();
return true;
}
return false;
}
isFavorited(id) {
return this.favorites.some(f => f.id === id);
}
toggleSuperLike(id) {
const favorite = this.favorites.find(f => f.id === id);
if (favorite) {
favorite.superLiked = !favorite.superLiked;
this.hasChanges = true; // 标记有变化
console.log('切换特别喜欢状态:', id, favorite.superLiked);
this.saveFavorites();
return true;
}
return false;
}
isSuperLiked(id) {
const favorite = this.favorites.find(f => f.id === id);
return favorite ? favorite.superLiked : false;
}
getFavorites() {
// 确保所有收藏项都有hasImages字段(兼容旧数据)
this.favorites.forEach(fav => {
if (fav.hasImages === undefined) {
fav.hasImages = fav.images && fav.images.length > 0;
}
});
return this.favorites;
}
clearAll() {
this.favorites = [];
this.hasChanges = true; // 标记有变化
this.saveFavorites();
// 通知UI更新
this.notifyDataChanged();
}
exportJSON() {
// 导出包含收藏、浏览历史和黑名单的数据
const exportData = {
favorites: this.favorites,
viewHistory: this.viewHistoryManager.exportData(),
blacklist: this.blacklistManager.getBlacklist(),
exportTime: Date.now(),
version: '1.5'
};
return JSON.stringify(exportData, null, 2);
}
// 重置变化标记(在导出成功后调用)
resetChangesFlag() {
this.hasChanges = false;
console.log('重置数据变化标记');
}
importJSON(jsonString) {
try {
const data = JSON.parse(jsonString);
// 兼容旧版本数据(只有收藏数组)
if (Array.isArray(data)) {
// 为旧数据添加hasImages字段
this.favorites = data.map(fav => ({
...fav,
hasImages: fav.images && fav.images.length > 0
}));
this.hasChanges = true; // 标记有变化
this.saveFavorites();
this.notifyDataChanged();
return true;
}
// 新版本数据(包含收藏、浏览历史和黑名单)
if (data.favorites && Array.isArray(data.favorites)) {
// 确保所有收藏项都有hasImages字段
this.favorites = data.favorites.map(fav => ({
...fav,
hasImages: fav.hasImages !== undefined ? fav.hasImages : (fav.images && fav.images.length > 0)
}));
this.hasChanges = true; // 标记有变化
this.saveFavorites();
}
if (data.viewHistory && typeof data.viewHistory === 'object') {
this.viewHistoryManager.importData(data.viewHistory);
}
if (data.blacklist && Array.isArray(data.blacklist)) {
// 使用追加方式导入黑名单,而不是覆盖
const currentBlacklist = [...this.blacklistManager.blacklist];
const mergedBlacklist = [...new Set([...currentBlacklist, ...data.blacklist])];
this.blacklistManager.blacklist = mergedBlacklist;
this.blacklistManager.saveBlacklist();
// 标记有数据变化
this.hasChanges = true;
}
this.notifyDataChanged();
return true;
} catch (e) {
console.error('导入失败:', e);
}
return false;
}
// 增加浏览次数
incrementView(id) {
this.viewHistoryManager.incrementView(id);
this.hasChanges = true; // 标记有变化
}
// 获取浏览次数
getViewCount(id) {
return this.viewHistoryManager.getViewCount(id);
}
// 获取最后浏览时间
getLastView(id) {
return this.viewHistoryManager.getLastView(id);
}
// 从页面中获取图片
getImagesFromPage() {
const entryContent = document.querySelector('.entry-content');
if (!entryContent) {
console.log('未找到.entry-content元素');
return [];
}
const images = entryContent.querySelectorAll('img');
const imageUrls = [];
images.forEach(img => {
const src = img.src || img.getAttribute('data-src');
if (src && src.trim()) {
// 过滤掉一些不需要的图片(如表情、图标等)
const lowerSrc = src.toLowerCase();
if (!lowerSrc.includes('emoji') &&
!lowerSrc.includes('icon') &&
!lowerSrc.includes('avatar') &&
!lowerSrc.includes('logo') &&
img.width > 50 && img.height > 50) { // 过滤掉太小的图片
imageUrls.push(src.trim());
}
}
});
// 如果筛选后的图片不足4张,使用所有图片
const maxImages = Math.min(imageUrls.length, 4);
if (maxImages === 0) {
console.log('未找到合适的图片');
return [];
}
// 避免选中第一张图,除非图片不够4张
let selectedImages = [];
if (imageUrls.length <= 4) {
// 图片不够4张,使用所有图片
selectedImages = imageUrls;
} else {
// 图片超过4张,从第2张开始选择,避免第一张
const shuffled = imageUrls.slice(1).sort(() => 0.5 - Math.random());
selectedImages = shuffled.slice(0, maxImages);
}
console.log(`从页面获取到 ${selectedImages.length} 张图片`);
return selectedImages;
}
}
// UI管理
class FavoritesUI {
constructor(manager) {
this.manager = manager;
this.preloadManager = new PreloadManager();
this.imageViewer = new ImageViewer();
this.panel = null;
this.toggleBtn = null;
this.currentPage = 1;
this.pageSize = 5;
this.blacklistCurrentPage = 1;
this.blacklistPageSize = 5;
this.init();
}
init() {
this.createToggleButton();
this.createPreloadAllButton();
this.createBlacklistToggleButton();
this.createPanel();
this.createBlacklistPanel();
this.updateToggleButton();
this.updateBlacklistToggleButton();
this.updatePreloadCount();
this.updateStorageSize();
}
createToggleButton() {
this.toggleBtn = document.createElement('button');
this.toggleBtn.className = 'favorites-toggle';
this.toggleBtn.innerHTML = '收藏夹 <span class="favorites-count">0</span>';
this.toggleBtn.onclick = () => this.togglePanel();
document.body.appendChild(this.toggleBtn);
}
createPreloadAllButton() {
this.preloadAllBtn = document.createElement('button');
this.preloadAllBtn.className = 'preload-all-btn';
this.preloadAllBtn.textContent = '全部预加载';
this.preloadAllBtn.title = '点击预加载当前页面所有列表项(忽略黑名单项目)';
this.preloadAllBtn.onclick = () => this.preloadAllItems();
document.body.appendChild(this.preloadAllBtn);
// 创建清空未加载浏览记录按钮
this.clearUnloadedViewsBtn = document.createElement('button');
this.clearUnloadedViewsBtn.className = 'clear-unloaded-views-btn';
this.clearUnloadedViewsBtn.textContent = '清空未加载浏览记录';
this.clearUnloadedViewsBtn.title = '点击清空当前页面所有未预加载列表项的浏览记录';
this.clearUnloadedViewsBtn.onclick = () => this.clearUnloadedViews();
document.body.appendChild(this.clearUnloadedViewsBtn);
}
createBlacklistToggleButton() {
this.blacklistToggleBtn = document.createElement('button');
this.blacklistToggleBtn.className = 'blacklist-toggle';
this.blacklistToggleBtn.innerHTML = '黑名单 <span class="blacklist-count">0</span>';
this.blacklistToggleBtn.onclick = () => this.toggleBlacklistPanel();
document.body.appendChild(this.blacklistToggleBtn);
}
createPanel() {
this.panel = document.createElement('div');
this.panel.className = 'favorites-panel';
this.panel.style.display = 'none';
this.panel.innerHTML = `
<div class="favorites-header">
<span class="favorites-title">我的收藏</span>
<button class="favorites-close">×</button>
</div>
<div class="favorites-search">
<input type="text" class="search-input" placeholder="搜索收藏标题或浏览次数..." id="search-input">
</div>
<div class="favorites-sort">
<span style="font-size: 12px; color: #666;">筛选:</span>
<select class="sort-select" id="filter-select" style="margin-right: 10px;">
<option value="all">全部收藏</option>
<option value="super-liked">特别喜欢</option>
<option value="no-images">无图片</option>
</select>
<span style="font-size: 12px; color: #666;">排序:</span>
<select class="sort-select" id="sort-select" style="margin-right: 10px;">
<option value="time-desc">收藏时间 (最新)</option>
<option value="time-asc">收藏时间 (最早)</option>
<option value="name-asc">标题 (A-Z)</option>
<option value="name-desc">标题 (Z-A)</option>
<option value="views-desc">浏览次数 (最多)</option>
<option value="views-asc">浏览次数 (最少)</option>
</select>
<button class="show-images-btn" id="show-images-btn" style="background: #28a745; color: white; border: none; padding: 4px 8px; border-radius: 4px; font-size: 12px; cursor: pointer; transition: all 0.3s;">显示图片</button>
</div>
<div class="favorites-content"></div>
<div class="favorites-actions">
<button class="favorites-action-btn" id="export-btn">导出JSON</button>
<button class="favorites-action-btn" id="import-btn">导入JSON</button>
<button class="favorites-action-btn danger" id="clear-btn">清空收藏</button>
</div>
<div class="favorites-settings">
<label class="auto-update-toggle">
<input type="checkbox" id="auto-update-checkbox" checked>
<span>自动更新状态 (5秒)</span>
</label>
<div style="margin-top: 8px; font-size: 11px; color: #666;">
预加载: <span id="preload-count">0</span> 个页面
<button id="clear-preloads" style="margin-left: 8px; background: #dc3545; color: white; border: none; padding: 0px 6px 2px 6px; border-radius: 3px; font-size: 10px; cursor: pointer;">清空</button>
</div>
<div class="storage-info">
<span>存储数据大小:</span>
<span id="storage-size" class="storage-size">计算中...</span>
</div>
</div>
`;
document.body.appendChild(this.panel);
// 绑定事件
this.panel.querySelector('.favorites-close').onclick = () => this.hidePanel();
this.panel.querySelector('#export-btn').onclick = () => this.exportFavorites();
this.panel.querySelector('#import-btn').onclick = () => this.importFavorites();
this.panel.querySelector('#clear-btn').onclick = () => this.clearFavorites();
// 绑定自动更新开关
const autoUpdateCheckbox = this.panel.querySelector('#auto-update-checkbox');
autoUpdateCheckbox.onchange = (e) => {
if (e.target.checked) {
window.pageHandler.startAutoUpdate();
} else {
window.pageHandler.stopAutoUpdate();
}
};
// 绑定清空预加载按钮
const clearPreloadsBtn = this.panel.querySelector('#clear-preloads');
clearPreloadsBtn.onclick = () => {
const choice = confirm('选择清空方式:\n\n确定 - 清空所有预加载内容\n取消 - 只清空非当前页面的预加载内容');
if (choice) {
// 清空所有预加载内容
this.preloadManager.clearAll();
this.updatePreloadCount();
// 更新所有预加载按钮状态
document.querySelectorAll('.preload-btn').forEach(btn => {
btn.classList.remove('preloaded', 'loading');
btn.textContent = '预加载';
});
} else {
// 只清空非当前页面的预加载内容
this.preloadManager.clearNonCurrentPagePreloads();
this.updatePreloadCount();
}
};
// 绑定搜索功能
const searchInput = this.panel.querySelector('#search-input');
if (searchInput) {
searchInput.addEventListener('input', () => {
this.updatePanel();
});
}
// 绑定筛选功能
const filterSelect = this.panel.querySelector('#filter-select');
if (filterSelect) {
filterSelect.addEventListener('change', () => {
this.updatePanel();
});
}
// 绑定排序功能
const sortSelect = this.panel.querySelector('#sort-select');
if (sortSelect) {
sortSelect.addEventListener('change', () => {
this.updatePanel();
});
}
// 绑定显示图片按钮
const showImagesBtn = this.panel.querySelector('#show-images-btn');
if (showImagesBtn) {
console.log('找到显示图片按钮,绑定点击事件');
showImagesBtn.addEventListener('click', (e) => {
console.log('显示图片按钮被点击');
e.preventDefault();
e.stopPropagation();
this.toggleImagesDisplay();
});
} else {
console.error('未找到显示图片按钮');
}
}
togglePanel() {
if (this.panel.style.display === 'none') {
this.showPanel();
} else {
this.hidePanel();
}
}
toggleBlacklistPanel() {
if (this.blacklistPanel.style.display === 'none') {
this.showBlacklistPanel();
} else {
this.hideBlacklistPanel();
}
}
showBlacklistPanel() {
this.blacklistPanel.style.display = 'block';
this.updateBlacklistPanel();
}
hideBlacklistPanel() {
this.blacklistPanel.style.display = 'none';
}
showPanel() {
console.log('显示收藏夹面板...');
this.panel.style.display = 'block';
// 显示收藏列表,但不加载图片
this.updatePanel();
this.updateToggleButton();
this.updateStorageSize();
console.log('收藏夹面板显示完成');
}
hidePanel() {
this.panel.style.display = 'none';
}
updatePanel() {
console.log('开始更新收藏夹面板...');
const content = this.panel.querySelector('.favorites-content');
const searchInput = this.panel.querySelector('#search-input');
const filterSelect = this.panel.querySelector('#filter-select');
const sortSelect = this.panel.querySelector('#sort-select');
let favorites = this.manager.getFavorites();
console.log('获取到收藏数量:', favorites.length);
// 更新存储大小
this.updateStorageSize();
// 筛选过滤
const filterValue = filterSelect ? filterSelect.value : 'all';
if (filterValue === 'super-liked') {
favorites = favorites.filter(fav => fav.superLiked);
} else if (filterValue === 'no-images') {
favorites = favorites.filter(fav => !fav.hasImages);
}
// 搜索过滤
const searchTerm = searchInput ? searchInput.value.toLowerCase() : '';
if (searchTerm) {
favorites = favorites.filter(fav => {
const titleMatch = fav.title.toLowerCase().includes(searchTerm);
const viewCountMatch = this.manager.viewHistoryManager.getViewCount(fav.id).toString().includes(searchTerm);
return titleMatch || viewCountMatch;
});
}
// 排序
const sortValue = sortSelect ? sortSelect.value : 'time-desc';
favorites.sort((a, b) => {
switch (sortValue) {
case 'time-desc':
return b.timestamp - a.timestamp;
case 'time-asc':
return a.timestamp - b.timestamp;
case 'name-asc':
return a.title.localeCompare(b.title);
case 'name-desc':
return b.title.localeCompare(a.title);
case 'views-desc':
return this.manager.viewHistoryManager.getViewCount(b.id) - this.manager.viewHistoryManager.getViewCount(a.id);
case 'views-asc':
return this.manager.viewHistoryManager.getViewCount(a.id) - this.manager.viewHistoryManager.getViewCount(b.id);
default:
return 0;
}
});
if (favorites.length === 0) {
const message = searchTerm ? '没有找到匹配的收藏' : '暂无收藏';
content.innerHTML = `<div style="text-align: center; color: #666; padding: 20px;">${message}</div>`;
// 清除分页控件
this.updateFavoritesPagination(favorites, 0);
console.log('无收藏数据,显示空状态');
return;
}
// 分页处理
const totalItems = favorites.length;
const totalPages = Math.ceil(totalItems / this.pageSize);
// 确保当前页在有效范围内
if (this.currentPage > totalPages) {
this.currentPage = totalPages;
}
if (this.currentPage < 1) {
this.currentPage = 1;
}
// 计算当前页的数据
const startIndex = (this.currentPage - 1) * this.pageSize;
const endIndex = Math.min(startIndex + this.pageSize, totalItems);
const currentPageItems = favorites.slice(startIndex, endIndex);
console.log(`分页信息: 总数=${totalItems}, 每页=${this.pageSize}, 总页数=${totalPages}, 当前页=${this.currentPage}, 显示=${startIndex}-${endIndex-1}`);
console.log('开始创建收藏列表HTML...');
// 创建田字格布局,不加载图片
content.innerHTML = currentPageItems.map((fav, index) => {
const images = fav.images || [];
const imageElements = [];
// 创建4个图片位置(田字格),但不加载图片
for (let i = 0; i < 4; i++) {
if (i < images.length) {
// 使用占位符,不加载实际图片
imageElements.push(`<div class="favorite-image placeholder" data-src="${images[i]}" data-image-index="${i}">点击显示图片</div>`);
} else {
imageElements.push(`<div class="favorite-image placeholder">无图</div>`);
}
}
// 确定CSS类
const cssClasses = ['favorite-item'];
if (fav.superLiked) cssClasses.push('super-liked');
if (!fav.hasImages) cssClasses.push('no-images');
return `
<div class="${cssClasses.join(' ')}" onclick="window.open('${fav.url}', '_blank')" title="${fav.title}" data-id="${fav.id}">
${fav.superLiked ? '<div class="super-like-badge">❤️</div>' : ''}
<div class="favorite-images">
${imageElements.join('')}
</div>
<div class="favorite-title">${fav.title}</div>
<div class="favorite-info">
<div class="favorite-views">
<span class="view-count-icon">👁️</span>
<span>${this.manager.viewHistoryManager.getViewCount(fav.id)}</span>
</div>
<span class="favorite-date">${new Date(fav.timestamp).toLocaleDateString()}</span>
</div>
</div>
`;
}).join('');
// 更新分页控件
this.updateFavoritesPagination(favorites, totalPages);
// 为收藏夹中的图片占位符添加点击事件
this.setupFavoriteImageClickEvents();
console.log('收藏夹面板更新完成');
}
// 更新收藏夹分页控件
updateFavoritesPagination(favorites, totalPages) {
// 先移除旧的分页控件
const oldPagination = this.panel.querySelector('.pagination-container');
if (oldPagination) {
oldPagination.remove();
}
// 如果没有数据或只有一页,不显示分页
if (favorites.length === 0 || totalPages <= 1) {
return;
}
// 创建分页容器
const paginationContainer = document.createElement('div');
paginationContainer.className = 'pagination-container';
// 添加分页信息
const paginationInfo = document.createElement('div');
paginationInfo.className = 'pagination-info';
const startIndex = (this.currentPage - 1) * this.pageSize + 1;
const endIndex = Math.min(startIndex + this.pageSize - 1, favorites.length);
paginationInfo.textContent = `显示 ${startIndex}-${endIndex},共 ${favorites.length} 项`;
paginationContainer.appendChild(paginationInfo);
// 添加分页按钮
// 首页按钮
const firstPageBtn = document.createElement('button');
firstPageBtn.className = `pagination-btn ${this.currentPage === 1 ? 'disabled' : ''}`;
firstPageBtn.textContent = '首页';
firstPageBtn.disabled = this.currentPage === 1;
firstPageBtn.onclick = () => {
if (this.currentPage !== 1) {
this.currentPage = 1;
this.updatePanel();
}
};
paginationContainer.appendChild(firstPageBtn);
// 上一页按钮
const prevPageBtn = document.createElement('button');
prevPageBtn.className = `pagination-btn ${this.currentPage === 1 ? 'disabled' : ''}`;
prevPageBtn.textContent = '上一页';
prevPageBtn.disabled = this.currentPage === 1;
prevPageBtn.onclick = () => {
if (this.currentPage > 1) {
this.currentPage--;
this.updatePanel();
}
};
paginationContainer.appendChild(prevPageBtn);
// 页码按钮
const maxPageButtons = 5; // 最多显示5个页码按钮
let startPage = Math.max(1, this.currentPage - Math.floor(maxPageButtons / 2));
let endPage = Math.min(totalPages, startPage + maxPageButtons - 1);
// 调整startPage,确保显示maxPageButtons个按钮
if (endPage - startPage + 1 < maxPageButtons) {
startPage = Math.max(1, endPage - maxPageButtons + 1);
}
for (let i = startPage; i <= endPage; i++) {
const pageBtn = document.createElement('button');
pageBtn.className = `pagination-btn ${i === this.currentPage ? 'active' : ''}`;
pageBtn.textContent = i.toString();
pageBtn.onclick = () => {
if (this.currentPage !== i) {
this.currentPage = i;
this.updatePanel();
}
};
paginationContainer.appendChild(pageBtn);
}
// 下一页按钮
const nextPageBtn = document.createElement('button');
nextPageBtn.className = `pagination-btn ${this.currentPage === totalPages ? 'disabled' : ''}`;
nextPageBtn.textContent = '下一页';
nextPageBtn.disabled = this.currentPage === totalPages;
nextPageBtn.onclick = () => {
if (this.currentPage < totalPages) {
this.currentPage++;
this.updatePanel();
}
};
paginationContainer.appendChild(nextPageBtn);
// 末页按钮
const lastPageBtn = document.createElement('button');
lastPageBtn.className = `pagination-btn ${this.currentPage === totalPages ? 'disabled' : ''}`;
lastPageBtn.textContent = '末页';
lastPageBtn.disabled = this.currentPage === totalPages;
lastPageBtn.onclick = () => {
if (this.currentPage !== totalPages) {
this.currentPage = totalPages;
this.updatePanel();
}
};
paginationContainer.appendChild(lastPageBtn);
// 添加每页显示数量选择
const pageSizeContainer = document.createElement('div');
pageSizeContainer.className = 'page-size-container';
pageSizeContainer.innerHTML = '每页显示: ';
const pageSizeSelect = document.createElement('select');
pageSizeSelect.className = 'page-size-select';
[5, 10, 50, 100, 200].forEach(size => {
const option = document.createElement('option');
option.value = size;
option.textContent = size.toString();
option.selected = this.pageSize === size;
pageSizeSelect.appendChild(option);
});
pageSizeSelect.onchange = (e) => {
const newPageSize = parseInt(e.target.value);
// 计算新的当前页,尽量保持显示的是相同的内容
const firstItemIndex = (this.currentPage - 1) * this.pageSize;
this.pageSize = newPageSize;
this.currentPage = Math.floor(firstItemIndex / newPageSize) + 1;
this.updatePanel();
};
pageSizeContainer.appendChild(pageSizeSelect);
paginationContainer.appendChild(pageSizeContainer);
// 将分页控件添加到面板
const favoritesContent = this.panel.querySelector('.favorites-content');
favoritesContent.parentNode.insertBefore(paginationContainer, favoritesContent.nextSibling);
}
// 切换图片显示状态
toggleImagesDisplay() {
const showImagesBtn = this.panel.querySelector('#show-images-btn');
if (!showImagesBtn) {
console.error('显示图片按钮未找到');
return;
}
const isShowingImages = showImagesBtn.textContent === '隐藏图片';
console.log('切换图片显示状态,当前状态:', isShowingImages ? '显示图片' : '隐藏图片');
if (isShowingImages) {
// 隐藏图片,恢复占位符
this.hideImages();
showImagesBtn.textContent = '显示图片';
showImagesBtn.style.background = '#28a745';
console.log('已隐藏图片,恢复占位符');
} else {
// 显示图片,只加载当前可见区域的图片
this.showImages();
showImagesBtn.textContent = '隐藏图片';
showImagesBtn.style.background = '#dc3545';
console.log('已显示图片,加载当前可见区域图片');
}
}
// 显示图片(只加载当前可见区域的图片)
showImages() {
const content = this.panel.querySelector('.favorites-content');
if (!content) {
console.error('收藏内容容器未找到');
return;
}
console.log('开始显示图片,查找当前页面的收藏项...');
// 获取当前页面的收藏项(不再使用可见性判断,而是使用当前页的所有项)
const currentPageItems = content.querySelectorAll('.favorite-item');
console.log('找到当前页面收藏项数量:', currentPageItems.length);
currentPageItems.forEach(item => {
const imagesContainer = item.querySelector('.favorite-images');
if (!imagesContainer) {
console.log('收藏项图片容器未找到,跳过');
return;
}
// 获取该收藏项的数据
const id = item.getAttribute('data-id');
const favorite = this.manager.getFavorites().find(f => f.id === id);
if (!favorite || !favorite.images || favorite.images.length === 0) {
console.log('收藏项数据或图片为空,跳过 ID:', id);
return;
}
console.log('处理收藏项图片,ID:', id, '图片数量:', favorite.images.length);
// 替换占位符为实际图片
const placeholders = imagesContainer.querySelectorAll('.favorite-image.placeholder[data-src]');
console.log('找到占位符数量:', placeholders.length);
placeholders.forEach((placeholder, index) => {
if (index < favorite.images.length) {
console.log('替换占位符为图片,索引:', index, '图片URL:', favorite.images[index]);
const img = document.createElement('img');
img.className = 'favorite-image';
img.src = favorite.images[index];
img.alt = `图片 ${index + 1}`;
img.setAttribute('data-image-index', index);
// 添加点击事件
img.onclick = (e) => {
e.stopPropagation();
if (this.imageViewer) {
this.imageViewer.show(favorite.images, index);
}
};
// 图片加载事件
img.onload = () => {
img.classList.add('loaded');
console.log('图片加载成功,索引:', index);
};
img.onerror = () => {
console.log('图片加载失败,索引:', index, 'URL:', favorite.images[index]);
img.classList.add('placeholder');
img.style.display = 'flex';
img.style.alignItems = 'center';
img.style.justifyContent = 'center';
img.innerHTML = '图片加载失败';
img.style.color = '#999';
img.style.fontSize = '8px';
};
// 替换占位符
placeholder.parentNode.replaceChild(img, placeholder);
console.log('占位符替换完成,索引:', index);
}
});
});
// 设置滚动监听,动态加载新进入可视区域的图片
this.setupScrollListener();
console.log('图片显示完成,已设置滚动监听');
// 重新设置图片点击事件(因为新加载的图片需要点击事件)
this.setupFavoriteImageClickEvents();
}
// 隐藏图片,恢复占位符
hideImages() {
const content = this.panel.querySelector('.favorites-content');
if (!content) {
console.error('收藏内容容器未找到');
return;
}
console.log('开始隐藏图片,恢复占位符...');
// 移除滚动监听
this.removeScrollListener();
// 将所有图片替换回占位符
const images = content.querySelectorAll('.favorite-image:not(.placeholder)');
console.log('找到需要隐藏的图片数量:', images.length);
images.forEach((img, index) => {
const dataSrc = img.src;
const dataIndex = img.getAttribute('data-image-index');
console.log(`隐藏图片 ${index},URL: ${dataSrc},索引: ${dataIndex}`);
const placeholder = document.createElement('div');
placeholder.className = 'favorite-image placeholder';
placeholder.setAttribute('data-src', dataSrc);
placeholder.setAttribute('data-image-index', dataIndex);
placeholder.textContent = '点击显示图片';
img.parentNode.replaceChild(placeholder, img);
});
console.log('图片隐藏完成,已恢复占位符');
// 重新设置占位符点击事件(因为占位符被重新创建)
this.setupFavoriteImageClickEvents();
}
// 此方法已不再使用,保留方法签名以兼容旧代码
getVisibleFavoriteItems() {
const content = this.panel.querySelector('.favorites-content');
if (!content) {
console.error('收藏内容容器未找到');
return [];
}
// 直接返回当前页面的所有收藏项
const items = content.querySelectorAll('.favorite-item');
console.log('当前页面收藏项数量:', items.length);
return Array.from(items);
}
// 设置滚动监听
setupScrollListener() {
const content = this.panel.querySelector('.favorites-content');
if (!content) return;
// 移除可能存在的旧监听器
this.removeScrollListener();
// 创建新的滚动监听器
this.scrollListener = () => {
// 获取当前页面的所有收藏项
const currentPageItems = content.querySelectorAll('.favorite-item');
currentPageItems.forEach(item => {
const imagesContainer = item.querySelector('.favorite-images');
if (!imagesContainer) return;
// 检查是否已经有图片加载了
const hasLoadedImages = imagesContainer.querySelectorAll('.favorite-image:not(.placeholder)').length > 0;
if (hasLoadedImages) return;
// 获取该收藏项的数据
const id = item.getAttribute('data-id');
const favorite = this.manager.getFavorites().find(f => f.id === id);
if (!favorite || !favorite.images || favorite.images.length === 0) return;
// 加载图片
const placeholders = imagesContainer.querySelectorAll('.favorite-image.placeholder[data-src]');
placeholders.forEach((placeholder, index) => {
if (index < favorite.images.length) {
const img = document.createElement('img');
img.className = 'favorite-image';
img.src = favorite.images[index];
img.alt = `图片 ${index + 1}`;
img.setAttribute('data-image-index', index);
// 添加点击事件
img.onclick = (e) => {
e.stopPropagation();
if (this.imageViewer) {
this.imageViewer.show(favorite.images, index);
}
};
// 图片加载事件
img.onload = () => {
img.classList.add('loaded');
};
img.onerror = () => {
img.classList.add('placeholder');
img.style.display = 'flex';
img.style.alignItems = 'center';
img.style.justifyContent = 'center';
img.innerHTML = '图片加载失败';
img.style.color = '#999';
img.style.fontSize = '8px';
};
// 替换占位符
placeholder.parentNode.replaceChild(img, placeholder);
}
});
});
};
// 添加滚动监听器
content.addEventListener('scroll', this.scrollListener);
}
// 移除滚动监听
removeScrollListener() {
const content = this.panel.querySelector('.favorites-content');
if (!content || !this.scrollListener) return;
content.removeEventListener('scroll', this.scrollListener);
this.scrollListener = null;
}
updateBlacklistPanel() {
const itemsContainer = this.blacklistPanel.querySelector('#blacklist-items');
const searchInput = this.blacklistPanel.querySelector('#blacklist-search-input');
const searchInfo = this.blacklistPanel.querySelector('#blacklist-search-info');
let blacklist = this.manager.blacklistManager.getBlacklist();
const searchTerm = searchInput ? searchInput.value.toLowerCase().trim() : '';
// 搜索过滤
if (searchTerm) {
const filteredBlacklist = blacklist.filter(item =>
item.toLowerCase().includes(searchTerm)
);
// 更新搜索信息
if (searchInfo) {
searchInfo.textContent = `搜索 "${searchTerm}" 找到 ${filteredBlacklist.length}/${blacklist.length} 个黑名单项`;
}
blacklist = filteredBlacklist;
} else {
// 清除搜索信息
if (searchInfo) {
searchInfo.textContent = `共 ${blacklist.length} 个黑名单项`;
}
}
if (blacklist.length === 0) {
const message = searchTerm ? '没有找到匹配的黑名单项' : '暂无黑名单项';
itemsContainer.innerHTML = `<div style="text-align: center; color: #666; padding: 20px;">${message}</div>`;
// 清除分页控件
this.updateBlacklistPagination(blacklist, 0);
return;
}
// 分页处理
const totalItems = blacklist.length;
const totalPages = Math.ceil(totalItems / this.blacklistPageSize);
// 确保当前页在有效范围内
if (this.blacklistCurrentPage > totalPages) {
this.blacklistCurrentPage = totalPages;
}
if (this.blacklistCurrentPage < 1) {
this.blacklistCurrentPage = 1;
}
// 计算当前页的数据
const startIndex = (this.blacklistCurrentPage - 1) * this.blacklistPageSize;
const endIndex = Math.min(startIndex + this.blacklistPageSize, totalItems);
const currentPageItems = blacklist.slice(startIndex, endIndex);
console.log(`黑名单分页信息: 总数=${totalItems}, 每页=${this.blacklistPageSize}, 总页数=${totalPages}, 当前页=${this.blacklistCurrentPage}, 显示=${startIndex}-${endIndex-1}`);
itemsContainer.innerHTML = currentPageItems.map(item => `
<div class="blacklist-item" data-keyword="${item}">
<span class="blacklist-item-text">${item}</span>
<button class="blacklist-item-remove" data-keyword="${item}">删除</button>
</div>
`).join('');
// 更新分页控件
this.updateBlacklistPagination(blacklist, totalPages);
// 重新绑定删除按钮事件
this.bindBlacklistRemoveEvents();
}
// 更新黑名单分页控件
updateBlacklistPagination(blacklist, totalPages) {
// 先移除旧的分页控件
const oldPagination = this.blacklistPanel.querySelector('.pagination-container');
if (oldPagination) {
oldPagination.remove();
}
// 如果没有数据或只有一页,不显示分页
if (blacklist.length === 0 || totalPages <= 1) {
return;
}
// 创建分页容器
const paginationContainer = document.createElement('div');
paginationContainer.className = 'pagination-container';
// 添加分页信息
const paginationInfo = document.createElement('div');
paginationInfo.className = 'pagination-info';
const startIndex = (this.blacklistCurrentPage - 1) * this.blacklistPageSize + 1;
const endIndex = Math.min(startIndex + this.blacklistPageSize - 1, blacklist.length);
paginationInfo.textContent = `显示 ${startIndex}-${endIndex},共 ${blacklist.length} 项`;
paginationContainer.appendChild(paginationInfo);
// 添加分页按钮
// 首页按钮
const firstPageBtn = document.createElement('button');
firstPageBtn.className = `pagination-btn ${this.blacklistCurrentPage === 1 ? 'disabled' : ''}`;
firstPageBtn.textContent = '首页';
firstPageBtn.disabled = this.blacklistCurrentPage === 1;
firstPageBtn.onclick = () => {
if (this.blacklistCurrentPage !== 1) {
this.blacklistCurrentPage = 1;
this.updateBlacklistPanel();
}
};
paginationContainer.appendChild(firstPageBtn);
// 上一页按钮
const prevPageBtn = document.createElement('button');
prevPageBtn.className = `pagination-btn ${this.blacklistCurrentPage === 1 ? 'disabled' : ''}`;
prevPageBtn.textContent = '上一页';
prevPageBtn.disabled = this.blacklistCurrentPage === 1;
prevPageBtn.onclick = () => {
if (this.blacklistCurrentPage > 1) {
this.blacklistCurrentPage--;
this.updateBlacklistPanel();
}
};
paginationContainer.appendChild(prevPageBtn);
// 页码按钮
const maxPageButtons = 5; // 最多显示5个页码按钮
let startPage = Math.max(1, this.blacklistCurrentPage - Math.floor(maxPageButtons / 2));
let endPage = Math.min(totalPages, startPage + maxPageButtons - 1);
// 调整startPage,确保显示maxPageButtons个按钮
if (endPage - startPage + 1 < maxPageButtons) {
startPage = Math.max(1, endPage - maxPageButtons + 1);
}
for (let i = startPage; i <= endPage; i++) {
const pageBtn = document.createElement('button');
pageBtn.className = `pagination-btn ${i === this.blacklistCurrentPage ? 'active' : ''}`;
pageBtn.textContent = i.toString();
pageBtn.onclick = () => {
if (this.blacklistCurrentPage !== i) {
this.blacklistCurrentPage = i;
this.updateBlacklistPanel();
}
};
paginationContainer.appendChild(pageBtn);
}
// 下一页按钮
const nextPageBtn = document.createElement('button');
nextPageBtn.className = `pagination-btn ${this.blacklistCurrentPage === totalPages ? 'disabled' : ''}`;
nextPageBtn.textContent = '下一页';
nextPageBtn.disabled = this.blacklistCurrentPage === totalPages;
nextPageBtn.onclick = () => {
if (this.blacklistCurrentPage < totalPages) {
this.blacklistCurrentPage++;
this.updateBlacklistPanel();
}
};
paginationContainer.appendChild(nextPageBtn);
// 末页按钮
const lastPageBtn = document.createElement('button');
lastPageBtn.className = `pagination-btn ${this.blacklistCurrentPage === totalPages ? 'disabled' : ''}`;
lastPageBtn.textContent = '末页';
lastPageBtn.disabled = this.blacklistCurrentPage === totalPages;
lastPageBtn.onclick = () => {
if (this.blacklistCurrentPage !== totalPages) {
this.blacklistCurrentPage = totalPages;
this.updateBlacklistPanel();
}
};
paginationContainer.appendChild(lastPageBtn);
// 添加每页显示数量选择
const pageSizeContainer = document.createElement('div');
pageSizeContainer.className = 'page-size-container';
pageSizeContainer.innerHTML = '每页显示: ';
const pageSizeSelect = document.createElement('select');
pageSizeSelect.className = 'page-size-select';
[5, 10, 50, 100, 200].forEach(size => {
const option = document.createElement('option');
option.value = size;
option.textContent = size.toString();
option.selected = this.blacklistPageSize === size;
pageSizeSelect.appendChild(option);
});
pageSizeSelect.onchange = (e) => {
const newPageSize = parseInt(e.target.value);
// 计算新的当前页,尽量保持显示的是相同的内容
const firstItemIndex = (this.blacklistCurrentPage - 1) * this.blacklistPageSize;
this.blacklistPageSize = newPageSize;
this.blacklistCurrentPage = Math.floor(firstItemIndex / newPageSize) + 1;
this.updateBlacklistPanel();
};
pageSizeContainer.appendChild(pageSizeSelect);
paginationContainer.appendChild(pageSizeContainer);
// 将分页控件添加到面板
const blacklistItems = this.blacklistPanel.querySelector('#blacklist-items');
blacklistItems.parentNode.insertBefore(paginationContainer, blacklistItems.nextSibling);
}
addBlacklistItem() {
const input = this.blacklistPanel.querySelector('#blacklist-input');
const text = input.value.trim();
if (!text) {
this.showMessage('请输入要屏蔽的关键词', 'error');
return;
}
const success = this.manager.blacklistManager.addBlacklistItem(text);
if (success) {
input.value = '';
this.updateBlacklistPanel();
this.updateBlacklistToggleButton();
this.showMessage(`已添加黑名单项: ${text}`, 'success');
// 标记收藏管理器有数据变化
if (window.pageHandler && window.pageHandler.manager) {
window.pageHandler.manager.hasChanges = true;
}
// 更新所有列表项的黑名单状态
if (window.pageHandler) {
window.pageHandler.updateAllBlacklistStates();
}
// 重置变化标记(添加后)
if (window.pageHandler && window.pageHandler.manager) {
window.pageHandler.manager.resetChangesFlag();
}
} else {
this.showMessage('该关键词已存在', 'error');
}
}
removeBlacklistItem(text) {
const success = this.manager.blacklistManager.removeBlacklistItem(text);
if (success) {
this.updateBlacklistPanel();
this.updateBlacklistToggleButton();
this.showMessage(`已移除黑名单项: ${text}`, 'success');
// 标记收藏管理器有数据变化
if (window.pageHandler && window.pageHandler.manager) {
window.pageHandler.manager.hasChanges = true;
}
// 更新所有列表项的黑名单状态
if (window.pageHandler) {
window.pageHandler.updateAllBlacklistStates();
}
// 重置变化标记(删除后)
if (window.pageHandler && window.pageHandler.manager) {
window.pageHandler.manager.resetChangesFlag();
}
}
}
// 绑定黑名单删除按钮事件
bindBlacklistRemoveEvents() {
const removeButtons = this.blacklistPanel.querySelectorAll('.blacklist-item-remove');
removeButtons.forEach(btn => {
btn.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
const keyword = btn.getAttribute('data-keyword');
if (keyword) {
this.removeBlacklistItem(keyword);
}
};
});
}
exportBlacklist() {
const json = this.manager.blacklistManager.exportJSON();
const blob = new Blob([json], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
const blacklistCount = this.manager.blacklistManager.getBlacklist().length;
const fileName = `blacklist_${blacklistCount}_${new Date().toISOString().split('T')[0]}.json`;
a.download = fileName;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
// 重置变化标记(导出成功后)
if (window.pageHandler && window.pageHandler.manager) {
window.pageHandler.manager.resetChangesFlag();
}
this.showMessage(`导出成功!共导出 ${blacklistCount} 个黑名单项`, 'success');
}
importBlacklist() {
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = '.json';
fileInput.style.display = 'none';
fileInput.onchange = (e) => {
const file = e.target.files[0];
if (!file) return;
if (file.size > 1024 * 1024) {
this.showMessage('文件过大,请选择小于1MB的文件', 'error');
return;
}
const reader = new FileReader();
reader.onload = (event) => {
try {
const jsonData = event.target.result;
const result = this.manager.blacklistManager.importJSON(jsonData);
if (result && result.success) {
this.updateBlacklistPanel();
this.updateBlacklistToggleButton();
// 显示详细的导入结果信息
let message = `导入成功!`;
if (result.addedCount > 0) {
message += `新增 ${result.addedCount} 个黑名单项`;
if (result.importCount > result.addedCount) {
message += `,跳过 ${result.importCount - result.addedCount} 个重复项`;
}
} else {
message += `所有 ${result.importCount} 个黑名单项都已存在,无新增`;
}
message += `,当前共 ${result.totalCount} 个黑名单项`;
this.showMessage(message, 'success');
// 标记收藏管理器有数据变化
if (window.pageHandler && window.pageHandler.manager) {
window.pageHandler.manager.hasChanges = true;
}
// 更新所有列表项的黑名单状态
if (window.pageHandler) {
window.pageHandler.updateAllBlacklistStates();
}
// 重置变化标记(导入后)
if (window.pageHandler && window.pageHandler.manager) {
window.pageHandler.manager.resetChangesFlag();
}
} else {
this.showMessage('导入失败:数据格式错误', 'error');
}
} catch (error) {
console.error('导入黑名单错误:', error);
this.showMessage('导入失败:文件格式错误或读取失败', 'error');
}
};
reader.onerror = () => {
this.showMessage('导入失败:文件读取失败', 'error');
};
reader.readAsText(file);
};
document.body.appendChild(fileInput);
fileInput.click();
document.body.removeChild(fileInput);
}
clearBlacklist() {
const blacklistCount = this.manager.blacklistManager.getBlacklist().length;
if (blacklistCount === 0) {
this.showMessage('黑名单已为空', 'info');
return;
}
if (confirm(`确定要清空所有黑名单项吗?\n\n当前有 ${blacklistCount} 个黑名单项将被清空`)) {
this.manager.blacklistManager.clearAll();
this.updateBlacklistPanel();
this.updateBlacklistToggleButton();
this.showMessage('已清空所有黑名单项', 'success');
// 标记收藏管理器有数据变化
if (window.pageHandler && window.pageHandler.manager) {
window.pageHandler.manager.hasChanges = true;
}
// 更新所有列表项的黑名单状态
if (window.pageHandler) {
window.pageHandler.updateAllBlacklistStates();
}
// 重置变化标记(清空后)
if (window.pageHandler && window.pageHandler.manager) {
window.pageHandler.manager.resetChangesFlag();
}
}
}
createBlacklistPanel() {
this.blacklistPanel = document.createElement('div');
this.blacklistPanel.className = 'blacklist-panel';
this.blacklistPanel.style.display = 'none';
this.blacklistPanel.innerHTML = `
<div class="blacklist-header">
<span class="blacklist-title">黑名单管理</span>
<button class="blacklist-close">×</button>
</div>
<div class="blacklist-content">
<div class="blacklist-search">
<input type="text" class="blacklist-search-input" placeholder="搜索黑名单关键词..." id="blacklist-search-input">
<div class="blacklist-search-info" id="blacklist-search-info">共 0 个黑名单项</div>
</div>
<div class="blacklist-input-section">
<input type="text" class="blacklist-input" placeholder="输入要屏蔽的关键词..." id="blacklist-input">
<button class="blacklist-add-btn" id="blacklist-add-btn">添加</button>
</div>
<div class="blacklist-items" id="blacklist-items">
<!-- 黑名单项将在这里动态生成 -->
</div>
</div>
<div class="blacklist-actions">
<button class="blacklist-action-btn" id="blacklist-export-btn">导出JSON</button>
<button class="blacklist-action-btn" id="blacklist-import-btn">导入JSON</button>
<button class="blacklist-action-btn danger" id="blacklist-clear-btn">清空黑名单</button>
</div>
`;
document.body.appendChild(this.blacklistPanel);
// 绑定事件
this.blacklistPanel.querySelector('.blacklist-close').onclick = () => this.hideBlacklistPanel();
this.blacklistPanel.querySelector('#blacklist-add-btn').onclick = () => this.addBlacklistItem();
this.blacklistPanel.querySelector('#blacklist-export-btn').onclick = () => this.exportBlacklist();
this.blacklistPanel.querySelector('#blacklist-import-btn').onclick = () => this.importBlacklist();
this.blacklistPanel.querySelector('#blacklist-clear-btn').onclick = () => this.clearBlacklist();
// 绑定输入框回车事件
const input = this.blacklistPanel.querySelector('#blacklist-input');
input.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
this.addBlacklistItem();
}
});
// 绑定搜索框事件
const searchInput = this.blacklistPanel.querySelector('#blacklist-search-input');
searchInput.addEventListener('input', () => {
this.updateBlacklistPanel();
});
// 初始化黑名单项显示
this.updateBlacklistPanel();
}
updateToggleButton() {
const favoritesCount = this.manager.getFavorites().length;
const superLikedCount = this.manager.getFavorites().filter(f => f.superLiked).length;
const noImagesCount = this.manager.getFavorites().filter(f => !f.hasImages).length;
const viewHistoryCount = Object.keys(this.manager.viewHistoryManager.getAllRecords()).length;
const blacklistCount = this.manager.blacklistManager.getBlacklist().length;
this.toggleBtn.innerHTML = `收藏夹 <span class="favorites-count">${favoritesCount}</span>`;
// 更新面板标题
const titleElement = this.panel.querySelector('.favorites-title');
if (titleElement) {
let title = `我的收藏 (${favoritesCount})`;
if (superLikedCount > 0) {
title += ` | 特别喜欢 (${superLikedCount})`;
}
if (noImagesCount > 0) {
title += ` | 无图片 (${noImagesCount})`;
}
if (viewHistoryCount > 0) {
title += ` | 浏览记录 (${viewHistoryCount})`;
}
if (blacklistCount > 0) {
title += ` | 黑名单 (${blacklistCount})`;
}
titleElement.textContent = title;
}
// 更新存储大小
this.updateStorageSize();
}
updateBlacklistToggleButton() {
const blacklistCount = this.manager.blacklistManager.getBlacklist().length;
this.blacklistToggleBtn.innerHTML = `黑名单 <span class="blacklist-count">${blacklistCount}</span>`;
}
updatePreloadCount() {
const countElement = this.panel.querySelector('#preload-count');
if (countElement) {
countElement.textContent = this.preloadManager.preloadedUrls.size;
}
}
// 计算并更新存储数据大小
updateStorageSize() {
const sizeElement = this.panel.querySelector('#storage-size');
if (!sizeElement) return;
try {
// 计算收藏数据大小
const favoritesData = JSON.stringify(this.manager.getFavorites());
const favoritesSize = new Blob([favoritesData]).size;
// 计算浏览历史数据大小
const viewHistoryData = JSON.stringify(this.manager.viewHistoryManager.getAllRecords());
const viewHistorySize = new Blob([viewHistoryData]).size;
// 计算黑名单数据大小
const blacklistData = JSON.stringify(this.manager.blacklistManager.getBlacklist());
const blacklistSize = new Blob([blacklistData]).size;
// 计算预加载数据大小(估算)
let preloadSize = 0;
this.preloadManager.preloadedUrls.forEach((preload, url) => {
// 估算每个预加载项的大小(包含iframe、图片URL等)
const preloadData = JSON.stringify({
url: url,
title: preload.title || '',
images: preload.images || [],
timestamp: preload.timestamp || 0
});
preloadSize += new Blob([preloadData]).size;
});
// 总大小
const totalSize = favoritesSize + viewHistorySize + blacklistSize + preloadSize;
// 格式化显示
const formatSize = (bytes) => {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
};
const formattedSize = formatSize(totalSize);
// 根据大小设置颜色
let sizeClass = 'storage-size';
if (totalSize > 50 * 1024 * 1024) { // 超过50MB
sizeClass = 'storage-danger';
} else if (totalSize > 10 * 1024 * 1024) { // 超过10MB
sizeClass = 'storage-warning';
}
sizeElement.textContent = formattedSize;
sizeElement.className = sizeClass;
// 添加详细信息的tooltip
const details = `收藏数据: ${formatSize(favoritesSize)}\n浏览历史: ${formatSize(viewHistorySize)}\n黑名单数据: ${formatSize(blacklistSize)}\n预加载数据: ${formatSize(preloadSize)}`;
sizeElement.title = details;
} catch (error) {
console.error('计算存储大小失败:', error);
sizeElement.textContent = '计算失败';
sizeElement.className = 'storage-danger';
}
}
// 全部预加载功能
async preloadAllItems() {
if (this.preloadAllBtn.classList.contains('loading')) {
return;
}
// 获取当前页面的所有列表项
const items = document.querySelectorAll('.format-standard');
if (items.length === 0) {
this.showMessage('当前页面没有找到列表项', 'info');
return;
}
this.preloadAllBtn.classList.add('loading');
this.preloadAllBtn.textContent = '预加载中...';
let successCount = 0;
let totalCount = 0;
let skippedCount = 0;
let blacklistedCount = 0;
for (const item of items) {
const link = item.querySelector('a');
if (!link) continue;
const url = link.href;
const title = link.textContent.trim();
const id = window.pageHandler.extractIdFromUrl(url);
// 跳过黑名单项目
if (this.manager.blacklistManager.isBlacklisted(title)) {
blacklistedCount++;
totalCount++;
continue;
}
// 跳过已经预加载的
if (this.preloadManager.isPreloaded(url)) {
successCount++;
totalCount++;
continue;
}
// 跳过已收藏的
if (id && this.manager.isFavorited(id)) {
skippedCount++;
totalCount++;
continue;
}
try {
await this.preloadManager.preloadUrl(url, title);
successCount++;
// 显示图片内容(在增加浏览次数之前)
this.showItemImages(item, url);
// 增加浏览次数(图片元素插入DOM后)
if (id) {
this.manager.viewHistoryManager.incrementView(id);
console.log(`预加载成功,增加浏览次数 ID: ${id}`);
// 更新对应项的浏览次数按钮
const viewCountBtn = item.querySelector('.view-count');
if (viewCountBtn) {
const newViewCount = this.manager.viewHistoryManager.getViewCount(id);
viewCountBtn.innerHTML = `<span class="view-count-icon">👁️</span><span>${newViewCount}</span>`;
viewCountBtn.title = `浏览次数: ${newViewCount}`;
}
// 更新列表项背景色
this.updateListItemBackground(item, id);
}
// 更新对应项的预加载按钮状态
const preloadBtn = item.querySelector('.preload-btn');
if (preloadBtn) {
preloadBtn.classList.remove('loading');
preloadBtn.classList.add('preloaded');
preloadBtn.textContent = '已预加载';
}
} catch (error) {
console.error('预加载失败:', url, error);
}
totalCount++;
// 更新按钮文本显示进度
this.preloadAllBtn.textContent = `预加载中... (${successCount}/${totalCount})`;
// 添加小延迟避免过于频繁的请求
await new Promise(resolve => setTimeout(resolve, 100));
}
this.preloadAllBtn.classList.remove('loading');
this.preloadAllBtn.textContent = '全部预加载';
this.updatePreloadCount();
let message = `预加载完成!成功 ${successCount}/${totalCount} 项`;
if (blacklistedCount > 0) {
message += `,跳过 ${blacklistedCount} 项(黑名单)`;
}
if (skippedCount > 0) {
message += `,跳过 ${skippedCount} 项(已收藏)`;
}
this.showMessage(message, 'success');
}
// 显示列表项的图片内容
showItemImages(item, url) {
// 检查是否已经显示过图片
if (item.querySelector('.list-item-images')) {
console.log('图片已显示,跳过:', url);
return;
}
const preloaded = this.preloadManager.getPreloaded(url);
if (!preloaded || !preloaded.images || preloaded.images.length === 0) {
console.log('没有预加载数据或图片为空:', url);
return;
}
const images = preloaded.images;
console.log(`开始显示图片,URL: ${url}, 图片数量: ${images.length}, 总组数: ${Math.ceil(images.length / 3)}`);
// 创建图片容器
const imagesContainer = document.createElement('div');
imagesContainer.className = 'list-item-images';
imagesContainer.style.display = 'block';
imagesContainer.style.visibility = 'visible';
// 创建图片滑动容器
const imagesWrapper = document.createElement('div');
imagesWrapper.className = 'list-item-images-container';
imagesWrapper.style.display = 'flex';
imagesWrapper.style.visibility = 'visible';
// 添加图片 - 只加载前3张,其余使用懒加载
images.forEach((imgSrc, index) => {
const img = document.createElement('img');
img.className = 'list-item-image';
img.alt = `图片 ${index + 1}`;
// 添加点击事件,打开图片查看器
img.onclick = (e) => {
e.stopPropagation();
if (window.pageHandler && window.pageHandler.ui && window.pageHandler.ui.imageViewer) {
window.pageHandler.ui.imageViewer.show(images, index);
}
};
// 设置图片加载事件
img.onload = () => {
img.classList.add('loaded');
img.style.opacity = '1'; // 确保图片可见
console.log(`图片加载成功: ${imgSrc}, 索引: ${index}`);
// 确保图片可见
img.style.display = 'block';
};
img.onerror = () => {
console.log(`图片加载失败: ${imgSrc}, 索引: ${index}`);
img.style.display = 'none';
img.style.opacity = '0'; // 确保图片不可见
img.classList.remove('loaded'); // 确保失败时移除loaded类
// 移除src属性,避免重复尝试加载
img.removeAttribute('src');
};
// 只加载前3张图片,其余使用懒加载
if (index < 3) {
img.src = imgSrc;
img.style.opacity = '0'; // 初始设置为不可见,加载成功后显示
img.style.display = 'block'; // 确保图片容器可见
console.log(`直接加载图片 ${index}: ${imgSrc}`);
} else {
// 使用懒加载,设置data-src属性
img.setAttribute('data-src', imgSrc);
img.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'; // 透明占位图
// 确保懒加载的图片不会意外被标记为已加载
img.classList.remove('loaded');
// 设置懒加载的图片为不可见,直到真正加载
img.style.opacity = '0';
img.style.display = 'block'; // 确保图片容器可见
console.log(`设置懒加载图片 ${index}: ${imgSrc}, data-src: ${imgSrc}`);
}
imagesWrapper.appendChild(img);
});
imagesContainer.appendChild(imagesWrapper);
// 如果有多张图片,添加导航按钮和指示器
if (images.length > 3) {
// 计算总组数
const totalGroups = Math.ceil(images.length / 3);
console.log(`添加导航按钮和指示器,总组数: ${totalGroups}`);
// 添加导航按钮
const prevBtn = document.createElement('button');
prevBtn.className = 'list-item-image-nav prev';
prevBtn.innerHTML = '‹';
prevBtn.onclick = (e) => {
e.stopPropagation();
console.log('点击上一组按钮');
this.navigateImages(imagesContainer, -1);
};
const nextBtn = document.createElement('button');
nextBtn.className = 'list-item-image-nav next';
nextBtn.innerHTML = '›';
nextBtn.onclick = (e) => {
e.stopPropagation();
console.log('点击下一组按钮');
this.navigateImages(imagesContainer, 1);
};
imagesContainer.appendChild(prevBtn);
imagesContainer.appendChild(nextBtn);
// 添加指示器
const indicator = document.createElement('div');
indicator.className = 'list-item-image-indicator';
for (let i = 0; i < totalGroups; i++) {
const dot = document.createElement('div');
dot.className = `list-item-image-dot ${i === 0 ? 'active' : ''}`;
dot.onclick = (e) => {
e.stopPropagation();
console.log(`点击指示器切换到图片组 ${i}`);
this.showImageGroup(imagesContainer, i);
};
indicator.appendChild(dot);
}
imagesContainer.appendChild(indicator);
}
// 设置初始状态(只加载第一组的图片)
if (images.length > 3) {
// 如果有多张图片,调用showImageGroup来懒加载第一组
// 注意:前3张图片已经在上面直接加载了,这里只需要处理导航状态
console.log(`图片数量>3,设置初始导航状态,总图片数: ${images.length}, 总组数: ${Math.ceil(images.length / 3)}`);
this.showImageGroup(imagesContainer, 0);
} else {
// 如果图片数量<=3,所有图片都已经直接加载了,确保它们都可见
console.log(`图片数量<=3,所有图片已直接加载,确保可见性,总图片数: ${images.length}`);
let loadedCount = 0;
images.forEach((img, index) => {
if (index < images.length) {
img.style.display = 'block';
// 如果图片已加载,确保它可见
if (img.classList.contains('loaded')) {
loadedCount++;
img.style.opacity = '1';
img.style.display = 'block';
} else {
// 如果图片未加载,设置为不可见
img.style.opacity = '0';
}
}
});
console.log(`图片数量<=3处理完成,已加载: ${loadedCount}/${images.length}`);
}
// 将图片容器添加到列表项
item.appendChild(imagesContainer);
console.log(`图片容器已添加到DOM,URL: ${url}, 图片数量: ${images.length}, 总组数: ${Math.ceil(images.length / 3)}`);
// 强制触发重绘,确保图片容器可见
setTimeout(() => {
if (imagesContainer.style.display === 'none') {
imagesContainer.style.display = 'block';
console.log('强制显示图片容器:', url);
}
// 检查图片容器是否真的在DOM中
const checkContainer = item.querySelector('.list-item-images');
if (checkContainer) {
console.log('图片容器确认在DOM中:', url);
} else {
console.log('图片容器未在DOM中找到:', url);
}
// 检查图片加载状态
const loadedImages = checkContainer ? checkContainer.querySelectorAll('.list-item-image.loaded').length : 0;
const totalImages = checkContainer ? checkContainer.querySelectorAll('.list-item-image').length : 0;
console.log(`图片加载状态检查: 已加载=${loadedImages}/${totalImages}, URL: ${url}`);
}, 100);
}
// 图片导航
navigateImages(container, direction) {
const wrapper = container.querySelector('.list-item-images-container');
const images = container.querySelectorAll('.list-item-image');
const dots = container.querySelectorAll('.list-item-image-dot');
const prevBtn = container.querySelector('.prev');
const nextBtn = container.querySelector('.next');
// 获取当前组索引
let currentGroup = 0;
for (let i = 0; i < dots.length; i++) {
if (dots[i].classList.contains('active')) {
currentGroup = i;
break;
}
}
let newGroup = currentGroup + direction;
const totalGroups = Math.ceil(images.length / 3);
if (newGroup < 0) newGroup = totalGroups - 1;
if (newGroup >= totalGroups) newGroup = 0;
console.log(`导航图片组: 从组 ${currentGroup} 到组 ${newGroup}, 方向: ${direction > 0 ? '下一组' : '上一组'}`);
this.showImageGroup(container, newGroup);
}
// 显示指定组的图片
showImageGroup(container, groupIndex) {
const wrapper = container.querySelector('.list-item-images-container');
const images = container.querySelectorAll('.list-item-image');
const dots = container.querySelectorAll('.list-item-image-dot');
const prevBtn = container.querySelector('.prev');
const nextBtn = container.querySelector('.next');
const totalGroups = Math.ceil(images.length / 3);
if (groupIndex < 0 || groupIndex >= totalGroups) return;
// 检查是否已经处理过这个组(防止重复调用)
const currentActiveDot = container.querySelector('.list-item-image-dot.active');
const currentActiveIndex = Array.from(dots).indexOf(currentActiveDot);
if (currentActiveIndex === groupIndex) {
console.log(`图片组 ${groupIndex} 已经是当前激活状态,跳过重复处理`);
return;
}
console.log(`切换到图片组 ${groupIndex},之前激活组: ${currentActiveIndex},总组数: ${totalGroups}, 图片总数: ${images.length}`);
// 移动图片容器(每组3张图片,每张占33.333%宽度)
wrapper.style.transform = `translateX(-${groupIndex * 100}%)`;
console.log(`移动图片容器: 组 ${groupIndex}, 位移: -${groupIndex * 100}%`);
// 确保当前组的图片可见
let currentGroupCount = 0;
let loadedCount = 0;
let lazyLoadCount = 0;
images.forEach((img, index) => {
const isInCurrentGroup = index >= groupIndex * 3 && index < (groupIndex + 1) * 3;
if (isInCurrentGroup) {
currentGroupCount++;
img.style.display = 'block';
// 如果当前组的图片还没有加载,确保它们会被懒加载
if (img.getAttribute('data-src') && !img.classList.contains('loaded')) {
lazyLoadCount++;
console.log(`准备懒加载当前组图片 ${index}, data-src: ${img.getAttribute('data-src')}`);
}
// 如果图片已加载,确保它可见
if (img.classList.contains('loaded')) {
loadedCount++;
img.style.opacity = '1';
img.style.display = 'block';
} else {
// 如果图片未加载,设置为不可见
img.style.opacity = '0';
}
} else {
// 非当前组的图片可以隐藏以节省资源
img.style.display = 'none';
img.style.opacity = '0'; // 确保非当前组的图片不可见
}
});
console.log(`当前组状态: 组 ${groupIndex}, 当前组图片数: ${currentGroupCount}, 已加载: ${loadedCount}, 待懒加载: ${lazyLoadCount}`);
// 更新指示器
dots.forEach((dot, i) => {
dot.classList.toggle('active', i === groupIndex);
});
console.log(`更新指示器: 激活组 ${groupIndex}, 总指示器数: ${dots.length}`);
// 更新导航按钮状态
if (prevBtn) {
prevBtn.classList.toggle('hidden', totalGroups <= 1);
}
if (nextBtn) {
nextBtn.classList.toggle('hidden', totalGroups <= 1);
}
console.log(`导航按钮状态: 总组数=${totalGroups}, 上一组按钮=${totalGroups <= 1 ? '隐藏' : '显示'}, 下一组按钮=${totalGroups <= 1 ? '隐藏' : '显示'}`);
// 懒加载当前组的图片(只加载当前显示的3张)
const startIndex = groupIndex * 3;
const endIndex = Math.min(startIndex + 3, images.length); // 只加载当前组的3张图片
console.log(`懒加载图片组 ${groupIndex}, 图片范围: ${startIndex}-${endIndex-1}, 总图片数: ${images.length}, 当前组图片数量: ${endIndex - startIndex}, 总组数: ${Math.ceil(images.length / 3)}, 当前组状态: 已加载=${loadedCount}, 待懒加载=${lazyLoadCount}`);
for (let i = startIndex; i < endIndex; i++) {
const img = images[i];
const dataSrc = img.getAttribute('data-src');
// 检查图片是否真的已经加载(不仅检查loaded类,还要检查src是否不是占位图)
const isActuallyLoaded = img.classList.contains('loaded') &&
img.src &&
!img.src.includes('data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7') &&
img.style.display !== 'none';
if (dataSrc && !isActuallyLoaded) {
console.log(`开始懒加载图片 ${i}: ${dataSrc}`);
img.src = dataSrc;
img.removeAttribute('data-src');
img.onload = () => {
img.classList.add('loaded');
img.style.opacity = '1'; // 显示图片
console.log(`懒加载图片成功: ${dataSrc}, 索引: ${i}`);
// 确保图片可见
img.style.display = 'block';
};
img.onerror = () => {
console.log(`懒加载图片失败: ${dataSrc}, 索引: ${i}`);
img.style.display = 'none';
img.style.opacity = '0'; // 确保图片不可见
img.classList.remove('loaded'); // 确保失败时移除loaded类
// 移除data-src属性,避免重复尝试加载
img.removeAttribute('data-src');
// 重置src为占位图,避免重复尝试加载
img.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
};
} else if (dataSrc && isActuallyLoaded) {
console.log(`图片 ${i} 已经有data-src但已真正加载,跳过`);
} else if (!dataSrc && isActuallyLoaded) {
console.log(`图片 ${i} 没有data-src,已直接加载`);
} else if (!dataSrc && !isActuallyLoaded) {
console.log(`图片 ${i} 没有data-src且未加载,可能是占位图或已加载失败`);
} else {
console.log(`图片 ${i} 状态未知: dataSrc=${!!dataSrc}, isActuallyLoaded=${isActuallyLoaded}, src=${img.src ? '有src' : '无src'}, display=${img.style.display}, opacity=${img.style.opacity}, loaded类=${img.classList.contains('loaded')}, data-src=${img.getAttribute('data-src') || '无'}, 当前组=${groupIndex}, 图片范围=${startIndex}-${endIndex-1}, 总图片数=${images.length}, 总组数=${Math.ceil(images.length / 3)}, 当前组状态: 已加载=${loadedCount}, 待懒加载=${lazyLoadCount}, 图片索引=${i}, 图片总数=${images.length}, 当前组图片数=${endIndex - startIndex}, 当前组索引范围=${groupIndex * 3}-${(groupIndex + 1) * 3 - 1}, 是否在当前组=${i >= groupIndex * 3 && i < (groupIndex + 1) * 3}, 图片元素存在=${!!img}, 图片元素类型=${img ? img.tagName : 'null'}, 图片元素类名=${img ? img.className : 'null'}, 图片元素父元素=${img ? img.parentElement ? img.parentElement.className : '无父元素' : 'null'}, 图片元素祖父元素=${img ? img.parentElement ? img.parentElement.parentElement ? img.parentElement.parentElement.className : '无祖父元素' : '无父元素' : 'null'}, 图片元素祖父元素存在=${img ? img.parentElement ? img.parentElement.parentElement ? '存在' : '不存在' : '无父元素' : 'null'}, 图片元素祖父元素类型=${img ? img.parentElement ? img.parentElement.parentElement ? img.parentElement.parentElement.tagName : '无祖父元素' : '无父元素' : 'null'}, 图片元素祖父元素类名=${img ? img.parentElement ? img.parentElement.parentElement ? img.parentElement.parentElement.className : '无祖父元素' : '无父元素' : 'null'}, 图片元素祖父元素子元素数量=${img ? img.parentElement ? img.parentElement.parentElement ? img.parentElement.parentElement.children.length : '无祖父元素' : '无父元素' : 'null'}, 图片元素祖父元素子元素类型=${img ? img.parentElement ? img.parentElement.parentElement ? Array.from(img.parentElement.parentElement.children).map(child => child.tagName).join(',') : '无祖父元素' : '无父元素' : 'null'}`);
}
}
// 如果是初始状态(groupIndex=0),记录已处理的图片
if (groupIndex === 0) {
console.log(`初始状态处理完成,图片0-${endIndex-1}已处理,总图片数: ${images.length}, 当前组状态: 已加载=${loadedCount}, 待懒加载=${lazyLoadCount}`);
}
}
exportFavorites() {
const json = this.manager.exportJSON();
const blob = new Blob([json], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
// 获取收藏、浏览记录和黑名单数量
const favoritesCount = this.manager.getFavorites().length;
const viewHistoryCount = Object.keys(this.manager.viewHistoryManager.getAllRecords()).length;
const blacklistCount = this.manager.blacklistManager.getBlacklist().length;
const fileName = `favorites_${favoritesCount}_views_${viewHistoryCount}_blacklist_${blacklistCount}_${new Date().toISOString().split('T')[0]}.json`;
a.download = fileName;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
// 重置变化标记(导出成功后)
this.manager.resetChangesFlag();
// 显示导出成功消息
let message = `导出成功!共导出 ${favoritesCount} 个收藏`;
if (viewHistoryCount > 0) {
message += `,${viewHistoryCount} 条浏览记录`;
}
if (blacklistCount > 0) {
message += `,${blacklistCount} 个黑名单项`;
}
this.showMessage(message, 'success');
}
importFavorites() {
// 创建文件输入元素
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = '.json';
fileInput.style.display = 'none';
fileInput.onchange = (e) => {
const file = e.target.files[0];
if (!file) return;
// 检查文件大小(限制为2MB,因为包含浏览历史)
if (file.size > 2 * 1024 * 1024) {
this.showMessage('文件过大,请选择小于2MB的文件', 'error');
return;
}
const reader = new FileReader();
reader.onload = (event) => {
try {
const jsonData = event.target.result;
const data = JSON.parse(jsonData);
// 验证数据格式(兼容旧版本和新版本)
let isValidData = false;
let importCount = 0;
let viewHistoryCount = 0;
let blacklistCount = 0;
if (Array.isArray(data)) {
// 旧版本数据(只有收藏数组)
isValidData = data.every(item =>
item && typeof item === 'object' &&
item.id && item.title && item.url
);
importCount = data.length;
} else if (data.favorites && Array.isArray(data.favorites)) {
// 新版本数据(包含收藏、浏览历史和黑名单)
isValidData = data.favorites.every(item =>
item && typeof item === 'object' &&
item.id && item.title && item.url
);
importCount = data.favorites.length;
viewHistoryCount = data.viewHistory ? Object.keys(data.viewHistory).length : 0;
blacklistCount = data.blacklist ? data.blacklist.length : 0;
}
if (!isValidData) {
this.showMessage('导入失败:数据格式不正确,缺少必要字段', 'error');
return;
}
// 询问用户是否导入数据
const currentCount = this.manager.getFavorites().length;
const currentViewCount = Object.keys(this.manager.viewHistoryManager.getAllRecords()).length;
const currentBlacklistCount = this.manager.blacklistManager.getBlacklist().length;
let confirmMessage = `将导入 ${importCount} 个收藏`;
if (viewHistoryCount > 0) {
confirmMessage += `,${viewHistoryCount} 条浏览记录`;
}
if (blacklistCount > 0) {
confirmMessage += `,${blacklistCount} 个黑名单项(将追加到现有的 ${currentBlacklistCount} 个中,重复项会自动去重)`;
}
if (currentCount > 0) {
confirmMessage += `,当前有 ${currentCount} 个收藏将被覆盖`;
}
if (currentViewCount > 0) {
confirmMessage += `,${currentViewCount} 条浏览记录将被覆盖`;
}
confirmMessage += ',确定继续吗?';
if (confirm(confirmMessage)) {
const success = this.manager.importJSON(jsonData);
if (success) {
// 更新UI
this.updateToggleButton();
this.updateBlacklistToggleButton();
this.updateBlacklistPanel();
this.updateStorageSize();
this.updateAllButtons();
// 更新所有列表项的黑名单状态
if (window.pageHandler) {
window.pageHandler.updateAllBlacklistStates();
}
// 显示成功消息
let successMessage = `导入成功!共导入 ${importCount} 个收藏`;
if (viewHistoryCount > 0) {
successMessage += `,${viewHistoryCount} 条浏览记录`;
}
if (blacklistCount > 0) {
const finalBlacklistCount = this.manager.blacklistManager.getBlacklist().length;
const addedBlacklistCount = finalBlacklistCount - currentBlacklistCount;
if (addedBlacklistCount > 0) {
successMessage += `,新增 ${addedBlacklistCount} 个黑名单项`;
if (blacklistCount > addedBlacklistCount) {
successMessage += `(跳过 ${blacklistCount - addedBlacklistCount} 个重复项)`;
}
} else {
successMessage += `,所有 ${blacklistCount} 个黑名单项都已存在`;
}
successMessage += `,当前共 ${finalBlacklistCount} 个黑名单项`;
}
this.showMessage(successMessage, 'success');
} else {
this.showMessage('导入失败:数据保存错误', 'error');
}
}
} catch (error) {
console.error('导入错误:', error);
this.showMessage('导入失败:文件格式错误或读取失败', 'error');
}
};
reader.onerror = () => {
this.showMessage('导入失败:文件读取失败', 'error');
};
reader.readAsText(file);
};
// 触发文件选择
document.body.appendChild(fileInput);
fileInput.click();
document.body.removeChild(fileInput);
}
showMessage(message, type = 'info') {
// 创建消息提示元素
const messageDiv = document.createElement('div');
messageDiv.className = `favorites-message favorites-message-${type}`;
messageDiv.textContent = message;
// 添加到页面
document.body.appendChild(messageDiv);
// 3秒后自动移除
setTimeout(() => {
if (messageDiv.parentNode) {
messageDiv.parentNode.removeChild(messageDiv);
}
}, 3000);
}
// 清空未加载浏览记录功能
clearUnloadedViews() {
if (this.clearUnloadedViewsBtn.classList.contains('loading')) {
return;
}
// 获取当前页面的所有列表项
const items = document.querySelectorAll('.format-standard');
if (items.length === 0) {
this.showMessage('当前页面没有找到列表项', 'info');
return;
}
this.clearUnloadedViewsBtn.classList.add('loading');
this.clearUnloadedViewsBtn.textContent = '清空中...';
let clearedCount = 0;
let totalCount = 0;
let skippedCount = 0;
for (const item of items) {
const link = item.querySelector('a');
if (!link) continue;
const url = link.href;
const id = window.pageHandler.extractIdFromUrl(url);
if (!id) {
skippedCount++;
totalCount++;
continue;
}
// 检查是否已经预加载
if (this.preloadManager.isPreloaded(url)) {
skippedCount++;
totalCount++;
continue;
}
// 检查是否有浏览记录
const viewCount = this.manager.viewHistoryManager.getViewCount(id);
if (viewCount === 0) {
skippedCount++;
totalCount++;
continue;
}
// 清空浏览记录
try {
// 从浏览历史中删除该记录
delete this.manager.viewHistoryManager.viewHistory[id];
clearedCount++;
// 更新对应的浏览次数按钮
const viewCountBtn = item.querySelector('.view-count');
if (viewCountBtn) {
viewCountBtn.innerHTML = `<span class="view-count-icon">👁️</span><span>0</span>`;
viewCountBtn.title = '浏览次数: 0';
}
// 更新列表项背景色
if (window.pageHandler) {
window.pageHandler.updateListItemBackground(item, id);
}
} catch (error) {
console.error('清空浏览记录失败:', id, error);
}
totalCount++;
// 更新按钮文本显示进度
this.clearUnloadedViewsBtn.textContent = `清空中... (${clearedCount}/${totalCount})`;
}
// 保存更新后的浏览历史
this.manager.viewHistoryManager.saveViewHistory();
this.clearUnloadedViewsBtn.classList.remove('loading');
this.clearUnloadedViewsBtn.textContent = '清空未加载浏览记录';
// 更新收藏夹按钮
if (window.pageHandler && window.pageHandler.ui) {
window.pageHandler.ui.updateToggleButton();
window.pageHandler.ui.updateStorageSize();
}
let message = `清空完成!成功清空 ${clearedCount} 个未预加载项的浏览记录`;
if (skippedCount > 0) {
message += `,跳过 ${skippedCount} 项(已预加载或无浏览记录)`;
}
this.showMessage(message, 'success');
}
clearFavorites() {
const favoritesCount = this.manager.getFavorites().length;
const viewHistoryCount = Object.keys(this.manager.viewHistoryManager.getAllRecords()).length;
const blacklistCount = this.manager.blacklistManager.getBlacklist().length;
let confirmMessage = '确定要清空所有收藏吗?';
if (viewHistoryCount > 0) {
confirmMessage += `\n\n同时将清空 ${viewHistoryCount} 条浏览记录`;
}
if (blacklistCount > 0) {
confirmMessage += `\n\n同时将清空 ${blacklistCount} 个黑名单项`;
}
if (confirm(confirmMessage)) {
this.manager.clearAll();
this.manager.viewHistoryManager.clearAll();
this.manager.blacklistManager.clearAll();
// 清空后重置变化标记
this.manager.resetChangesFlag();
this.updateToggleButton();
this.updateBlacklistToggleButton();
this.updateBlacklistPanel();
this.updateStorageSize();
this.updateAllButtons();
// 更新所有列表项的黑名单状态
if (window.pageHandler) {
window.pageHandler.updateAllBlacklistStates();
}
this.showMessage('已清空所有收藏、浏览记录和黑名单', 'success');
}
}
updateAllButtons() {
document.querySelectorAll('.favorite-btn').forEach(btn => {
const id = btn.getAttribute('data-id');
if (id) {
if (this.manager.isFavorited(id)) {
btn.classList.add('favorited');
btn.textContent = '已收藏';
} else {
btn.classList.remove('favorited');
btn.textContent = '收藏';
}
}
});
}
// 更新特别喜欢徽章(不重新渲染整个面板)
updateSuperLikeBadge(id) {
const content = this.panel.querySelector('.favorites-content');
if (!content) return;
// 找到对应的收藏项
const favoriteItem = content.querySelector(`.favorite-item[data-id="${id}"]`);
if (!favoriteItem) return;
const favorite = this.manager.getFavorites().find(f => f.id === id);
if (!favorite) return;
console.log('更新特别喜欢徽章:', id, favorite.superLiked);
// 更新特别喜欢状态
if (favorite.superLiked) {
favoriteItem.classList.add('super-liked');
// 如果还没有徽章,添加徽章
if (!favoriteItem.querySelector('.super-like-badge')) {
const badge = document.createElement('div');
badge.className = 'super-like-badge';
badge.innerHTML = '❤️';
favoriteItem.appendChild(badge);
console.log('添加特别喜欢徽章');
}
} else {
favoriteItem.classList.remove('super-liked');
// 移除徽章
const badge = favoriteItem.querySelector('.super-like-badge');
if (badge) {
badge.remove();
console.log('移除特别喜欢徽章');
}
}
// 确保no-images类保持正确
if (!favorite.hasImages) {
favoriteItem.classList.add('no-images');
} else {
favoriteItem.classList.remove('no-images');
}
}
// 为收藏夹中的图片添加点击事件
setupFavoriteImageClickEvents() {
const content = this.panel.querySelector('.favorites-content');
if (!content) {
console.error('收藏内容容器未找到,无法设置图片点击事件');
return;
}
console.log('开始设置收藏夹图片点击事件...');
// 为所有收藏项中的图片和占位符添加点击事件
const favoriteItems = content.querySelectorAll('.favorite-item');
console.log('找到收藏项数量:', favoriteItems.length);
favoriteItems.forEach((item, itemIndex) => {
const id = item.getAttribute('data-id');
const favorite = this.manager.getFavorites().find(f => f.id === id);
if (favorite && favorite.images && favorite.images.length > 0) {
console.log(`设置收藏项 ${itemIndex} 的图片点击事件,ID: ${id},图片数量: ${favorite.images.length}`);
// 为已加载的图片添加点击事件
const images = item.querySelectorAll('.favorite-image:not(.placeholder)');
console.log(`收藏项 ${itemIndex} 已加载图片数量:`, images.length);
images.forEach((img, imgIndex) => {
img.onclick = (e) => {
e.stopPropagation();
console.log(`点击已加载图片,收藏项: ${itemIndex},图片索引: ${imgIndex}`);
if (this.imageViewer) {
this.imageViewer.show(favorite.images, imgIndex);
}
};
});
// 为占位符添加点击事件(加载单个图片)
const placeholders = item.querySelectorAll('.favorite-image.placeholder[data-src]');
console.log(`收藏项 ${itemIndex} 占位符数量:`, placeholders.length);
placeholders.forEach((placeholder, placeholderIndex) => {
placeholder.onclick = (e) => {
e.stopPropagation();
console.log(`点击占位符,收藏项: ${itemIndex},占位符索引: ${placeholderIndex}`);
// 检查是否已经显示图片
const showImagesBtn = this.panel.querySelector('#show-images-btn');
const isShowingImages = showImagesBtn && showImagesBtn.textContent === '隐藏图片';
if (!isShowingImages) {
// 如果还没有显示图片,先显示图片
console.log('当前未显示图片,先显示图片');
this.showImages();
showImagesBtn.textContent = '隐藏图片';
showImagesBtn.style.background = '#dc3545';
}
// 加载这个特定的图片
const dataSrc = placeholder.getAttribute('data-src');
const dataIndex = placeholder.getAttribute('data-image-index');
if (dataSrc) {
console.log(`加载单个图片,URL: ${dataSrc},索引: ${dataIndex}`);
const img = document.createElement('img');
img.className = 'favorite-image';
img.src = dataSrc;
img.alt = `图片 ${parseInt(dataIndex) + 1}`;
img.setAttribute('data-image-index', dataIndex);
// 添加点击事件
img.onclick = (e) => {
e.stopPropagation();
console.log(`点击单个加载的图片,收藏项: ${itemIndex},图片索引: ${dataIndex}`);
if (this.imageViewer) {
this.imageViewer.show(favorite.images, parseInt(dataIndex));
}
};
// 图片加载事件
img.onload = () => {
img.classList.add('loaded');
console.log(`单个图片加载成功,收藏项: ${itemIndex},图片索引: ${dataIndex}`);
};
img.onerror = () => {
console.log(`单个图片加载失败,收藏项: ${itemIndex},图片索引: ${dataIndex},URL: ${dataSrc}`);
img.classList.add('placeholder');
img.style.display = 'flex';
img.style.alignItems = 'center';
img.style.justifyContent = 'center';
img.innerHTML = '图片加载失败';
img.style.color = '#999';
img.style.fontSize = '8px';
};
// 替换占位符
placeholder.parentNode.replaceChild(img, placeholder);
console.log(`占位符替换完成,收藏项: ${itemIndex},图片索引: ${dataIndex}`);
}
};
});
} else {
console.log(`收藏项 ${itemIndex} 无图片数据,跳过设置点击事件,ID: ${id}`);
}
});
console.log('收藏夹图片点击事件设置完成');
}
// 设置懒加载功能
setupLazyLoading() {
const content = this.panel.querySelector('.favorites-content');
if (!content) return;
// 创建Intersection Observer来监听滚动
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
const dataSrc = img.getAttribute('data-src');
if (dataSrc) {
// 加载图片
img.src = dataSrc;
img.removeAttribute('data-src');
// 图片加载完成后显示
img.onload = () => {
img.classList.add('loaded');
// 为新加载的图片添加点击事件
const item = img.closest('.favorite-item');
if (item) {
const id = item.getAttribute('data-id');
const favorite = this.manager.getFavorites().find(f => f.id === id);
if (favorite && favorite.images && favorite.images.length > 0) {
const imgIndex = parseInt(img.getAttribute('data-image-index') || '0');
img.onclick = (e) => {
e.stopPropagation();
if (window.pageHandler && window.pageHandler.ui && window.pageHandler.ui.imageViewer) {
window.pageHandler.ui.imageViewer.show(favorite.images, imgIndex);
}
};
}
}
};
// 停止观察这个元素
observer.unobserve(img);
}
}
});
}, {
root: content,
rootMargin: '50px', // 提前50px开始加载
threshold: 0.1
});
// 观察所有懒加载的图片
const lazyImages = content.querySelectorAll('img[data-src]');
lazyImages.forEach(img => {
imageObserver.observe(img);
});
// 监听滚动事件,手动触发检查(备用方案)
let scrollTimeout;
content.addEventListener('scroll', () => {
if (scrollTimeout) {
clearTimeout(scrollTimeout);
}
scrollTimeout = setTimeout(() => {
// 手动检查可见的懒加载图片
const visibleLazyImages = content.querySelectorAll('img[data-src]');
visibleLazyImages.forEach(img => {
const rect = img.getBoundingClientRect();
const contentRect = content.getBoundingClientRect();
// 检查图片是否在可视区域内
if (rect.top < contentRect.bottom && rect.bottom > contentRect.top) {
const dataSrc = img.getAttribute('data-src');
if (dataSrc) {
img.src = dataSrc;
img.removeAttribute('data-src');
img.onload = () => {
img.classList.add('loaded');
// 为新加载的图片添加点击事件
const item = img.closest('.favorite-item');
if (item) {
const id = item.getAttribute('data-id');
const favorite = this.manager.getFavorites().find(f => f.id === id);
if (favorite && favorite.images && favorite.images.length > 0) {
const imgIndex = parseInt(img.getAttribute('data-image-index') || '0');
img.onclick = (e) => {
e.stopPropagation();
if (window.pageHandler && window.pageHandler.ui && window.pageHandler.ui.imageViewer) {
window.pageHandler.ui.imageViewer.show(favorite.images, imgIndex);
}
};
}
}
};
}
}
});
}, 100);
});
}
}
// 页面处理
class PageHandler {
constructor(manager, ui) {
this.manager = manager;
this.ui = ui;
this.updateTimer = null;
this.init();
}
init() {
// 等待页面加载完成
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => this.processPage());
} else {
this.processPage();
}
// 监听动态内容变化
const observer = new MutationObserver(() => {
this.processPage();
});
observer.observe(document.body, { childList: true, subtree: true });
// 启动定时更新收藏状态
this.startAutoUpdate();
// 页面卸载时清理定时器
window.addEventListener('beforeunload', (e) => {
this.stopAutoUpdate();
// 不清理预加载的iframe,让它们保持到页面刷新
// 这样可以保持预览效果
// 如果是列表页且有收藏数据且有变化,提示导出
const currentUrl = window.location.href;
if (this.isListPage(currentUrl)) {
const favorites = this.manager.getFavorites();
const blacklist = this.manager.blacklistManager.getBlacklist();
if ((favorites.length > 0 || blacklist.length > 0) && this.manager.hasChanges) {
let message = '';
if (favorites.length > 0) {
message += `您有 ${favorites.length} 个收藏`;
}
if (blacklist.length > 0) {
if (message) message += ',';
message += `${blacklist.length} 个黑名单项`;
}
message += '且有数据变化,建议导出备份!\n\n确定要离开吗?';
e.preventDefault();
e.returnValue = message;
return message;
}
}
});
}
startAutoUpdate() {
// 清除可能存在的旧定时器
if (this.updateTimer) {
clearInterval(this.updateTimer);
}
// 每5秒更新一次所有收藏按钮状态和黑名单状态
this.updateTimer = setInterval(() => {
console.log('执行自动更新...');
this.updateAllFavoriteStates();
this.updateAllBlacklistStates();
}, 5000);
console.log('收藏状态自动更新已启动,间隔5秒,定时器ID:', this.updateTimer);
}
stopAutoUpdate() {
if (this.updateTimer) {
clearInterval(this.updateTimer);
this.updateTimer = null;
console.log('收藏状态自动更新已停止');
}
}
updateAllFavoriteStates() {
// 重新从localStorage加载最新数据
this.manager.favorites = this.manager.loadFavorites();
// 更新所有收藏按钮的状态
document.querySelectorAll('.favorite-btn').forEach(btn => {
const id = btn.getAttribute('data-id');
if (id) {
if (this.manager.isFavorited(id)) {
btn.classList.add('favorited');
btn.textContent = '已收藏';
} else {
btn.classList.remove('favorited');
btn.textContent = '收藏';
}
}
});
// 更新所有特别喜欢按钮的状态
document.querySelectorAll('.super-like-btn').forEach(btn => {
const id = btn.getAttribute('data-id');
if (id) {
if (this.manager.isSuperLiked(id)) {
btn.classList.add('super-liked');
btn.textContent = '特别喜欢';
} else {
btn.classList.remove('super-liked');
btn.textContent = '特别喜欢';
}
}
});
// 更新所有浏览次数按钮的状态
document.querySelectorAll('.view-count').forEach(btn => {
const id = btn.getAttribute('data-id');
if (id) {
const viewCount = this.manager.viewHistoryManager.getViewCount(id);
btn.innerHTML = `<span class="view-count-icon">👁️</span><span>${viewCount}</span>`;
btn.title = `浏览次数: ${viewCount}`;
}
});
// 更新所有黑名单按钮的状态
document.querySelectorAll('.blacklist-btn').forEach(btn => {
const item = btn.closest('.format-standard');
if (item) {
const link = item.querySelector('a');
if (link) {
const title = link.textContent.trim();
const isBlacklisted = this.manager.blacklistManager.isBlacklisted(title);
if (isBlacklisted) {
btn.classList.add('blacklisted');
btn.textContent = '已屏蔽';
} else {
btn.classList.remove('blacklisted');
btn.textContent = '黑名单';
}
}
}
});
// 更新所有列表项的背景色
this.updateAllListItemBackgrounds();
// 更新收藏夹按钮计数
if (window.pageHandler && window.pageHandler.ui) {
window.pageHandler.ui.updateToggleButton();
window.pageHandler.ui.updateStorageSize();
}
// 更新导出提醒按钮
this.updateExportReminder();
// 添加调试日志
console.log('自动更新收藏状态完成,当前收藏数量:', this.manager.getFavorites().length);
}
updateAllBlacklistStates() {
// 更新所有列表项的黑名单状态
document.querySelectorAll('.format-standard').forEach(item => {
const link = item.querySelector('a');
if (link) {
const title = link.textContent.trim();
this.updateListItemBlacklistState(item, title);
}
});
// 更新黑名单按钮计数
if (window.pageHandler && window.pageHandler.ui) {
window.pageHandler.ui.updateBlacklistToggleButton();
}
console.log('自动更新黑名单状态完成,当前黑名单数量:', this.manager.blacklistManager.getBlacklist().length);
}
updateAllViewCounts() {
// 重新从localStorage加载最新数据
this.manager.viewHistoryManager.viewHistory = this.manager.viewHistoryManager.loadViewHistory();
// 更新所有收藏项的浏览次数显示
document.querySelectorAll('.favorite-item').forEach(item => {
const id = item.getAttribute('data-id');
if (id) {
const viewCountSpan = item.querySelector('.view-count span');
if (viewCountSpan) {
viewCountSpan.textContent = this.manager.viewHistoryManager.getViewCount(id);
}
}
});
// 更新所有列表项的背景色
this.updateAllListItemBackgrounds();
// 更新收藏夹按钮计数
if (window.pageHandler && window.pageHandler.ui) {
window.pageHandler.ui.updateStorageSize();
}
// 添加调试日志
console.log('自动更新浏览次数完成,当前浏览记录数量:', this.manager.viewHistoryManager.getAllRecords().length);
}
updateExportReminder() {
const reminderBtn = document.querySelector('.export-reminder-btn');
const favorites = this.manager.getFavorites();
const blacklist = this.manager.blacklistManager.getBlacklist();
if (reminderBtn) {
if (favorites.length > 0 || blacklist.length > 0) {
let text = '💾 导出数据';
if (favorites.length > 0) {
text += ` (收藏:${favorites.length}`;
if (blacklist.length > 0) {
text += `, 黑名单:${blacklist.length}`;
}
text += ')';
} else if (blacklist.length > 0) {
text += ` (黑名单:${blacklist.length})`;
}
reminderBtn.innerHTML = text;
reminderBtn.style.display = 'block';
} else {
reminderBtn.style.display = 'none';
}
} else if ((favorites.length > 0 || blacklist.length > 0) && this.isListPage(window.location.href)) {
// 如果按钮不存在但有数据,重新添加按钮
this.addExportReminder();
}
}
processPage() {
// 判断当前页面类型
const currentUrl = window.location.href;
console.log('当前页面URL:', currentUrl);
// 为页面添加类型标识
if (this.isListPage(currentUrl)) {
document.body.classList.add('list-page');
document.body.classList.remove('detail-page');
console.log('检测到列表页,处理列表页收藏按钮');
this.processListPage();
} else if (this.isDetailPage(currentUrl)) {
document.body.classList.add('detail-page');
document.body.classList.remove('list-page');
console.log('检测到详情页,处理详情页收藏按钮');
this.processDetailPage();
} else {
console.log('未检测到支持的页面类型');
}
}
isListPage(url) {
// 检查是否为列表页:包含 ?s= 参数
const isList = url.includes('?s=');
console.log('列表页检查:', url, '结果:', isList);
return isList;
}
isDetailPage(url) {
// 检查是否为详情页:格式为 https://fubiji.site/数字/标题
const urlObj = new URL(url);
const pathParts = urlObj.pathname.split('/').filter(part => part);
const isDetail = pathParts.length >= 2 && /^\d+$/.test(pathParts[0]);
console.log('详情页检查:', url, '路径部分:', pathParts, '结果:', isDetail);
return isDetail;
}
processListPage() {
const items = document.querySelectorAll('.format-standard');
items.forEach(item => {
const link = item.querySelector('a');
if (!link) return;
const title = link.textContent.trim();
const url = link.href;
// 从链接URL中提取ID,与详情页保持一致
const id = this.extractIdFromUrl(url);
if (!id) {
console.log('无法从链接URL中提取ID:', url);
return;
}
// 检查是否已经添加过按钮
if (item.querySelector('.favorite-btn')) return;
// 设置列表项的背景色样式
this.updateListItemBackground(item, id);
// 设置列表项的黑名单状态
this.updateListItemBlacklistState(item, title);
// 创建收藏按钮
const favoriteBtn = document.createElement('button');
favoriteBtn.className = 'favorite-btn';
favoriteBtn.setAttribute('data-id', id);
favoriteBtn.textContent = this.manager.isFavorited(id) ? '已收藏' : '收藏';
if (this.manager.isFavorited(id)) {
favoriteBtn.classList.add('favorited');
}
favoriteBtn.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
if (window.pageHandler) {
window.pageHandler.toggleFavorite(id, title, url, favoriteBtn);
}
};
// 创建特别喜欢按钮
const superLikeBtn = document.createElement('button');
superLikeBtn.className = 'super-like-btn';
superLikeBtn.setAttribute('data-id', id);
superLikeBtn.textContent = this.manager.isSuperLiked(id) ? '特别喜欢' : '特别喜欢';
if (this.manager.isSuperLiked(id)) {
superLikeBtn.classList.add('super-liked');
}
superLikeBtn.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
if (window.pageHandler) {
window.pageHandler.toggleSuperLikeInList(id, title, url, superLikeBtn);
}
};
// 创建浏览次数按钮
const viewCountBtn = document.createElement('button');
viewCountBtn.className = 'view-count';
viewCountBtn.setAttribute('data-id', id);
const viewCount = this.manager.viewHistoryManager.getViewCount(id);
viewCountBtn.innerHTML = `<span class="view-count-icon">👁️</span><span>${viewCount}</span>`;
viewCountBtn.title = `浏览次数: ${viewCount}`;
// 创建预加载按钮
const preloadBtn = document.createElement('button');
preloadBtn.className = 'preload-btn';
preloadBtn.textContent = '预加载';
preloadBtn.title = '点击预加载页面,获取图片内容';
// 预加载按钮的点击逻辑
preloadBtn.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
// 如果已经预加载,显示提示但不移除
if (window.pageHandler && window.pageHandler.ui && window.pageHandler.ui.preloadManager.isPreloaded(url)) {
if (window.pageHandler && window.pageHandler.ui) {
window.pageHandler.ui.showMessage('已预加载,图片内容已显示', 'info');
}
return;
}
// 开始预加载
preloadBtn.classList.add('loading');
preloadBtn.textContent = '加载中';
window.pageHandler.ui.preloadManager.preloadUrl(url, title)
.then(() => {
preloadBtn.classList.remove('loading');
preloadBtn.classList.add('preloaded');
preloadBtn.textContent = '已预加载';
window.pageHandler.ui.updatePreloadCount();
// 显示图片内容(在增加浏览次数之前)
if (window.pageHandler && window.pageHandler.ui) {
window.pageHandler.ui.showItemImages(item, url);
}
// 增加浏览次数(图片元素插入DOM后)
if (id) {
window.pageHandler.manager.viewHistoryManager.incrementView(id);
console.log(`预加载成功,增加浏览次数 ID: ${id}`);
// 更新对应项的浏览次数按钮
const viewCountBtn = item.querySelector('.view-count');
if (viewCountBtn) {
const newViewCount = window.pageHandler.manager.viewHistoryManager.getViewCount(id);
viewCountBtn.innerHTML = `<span class="view-count-icon">👁️</span><span>${newViewCount}</span>`;
viewCountBtn.title = `浏览次数: ${newViewCount}`;
}
// 更新列表项背景色
window.pageHandler.updateListItemBackground(item, id);
}
})
.catch((error) => {
console.error('预加载失败:', error);
preloadBtn.classList.remove('loading');
preloadBtn.textContent = '预加载失败';
setTimeout(() => {
preloadBtn.textContent = '预加载';
}, 2000);
});
};
// 检查是否已经预加载
if (window.pageHandler && window.pageHandler.ui && window.pageHandler.ui.preloadManager.isPreloaded(url)) {
preloadBtn.classList.add('preloaded');
preloadBtn.textContent = '已预加载';
}
// 创建黑名单按钮
const blacklistBtn = document.createElement('button');
blacklistBtn.className = 'blacklist-btn';
blacklistBtn.textContent = '黑名单';
blacklistBtn.title = '点击将词条标题添加到黑名单';
// 检查是否已经在黑名单中
if (window.pageHandler && window.pageHandler.manager && window.pageHandler.manager.blacklistManager.isBlacklisted(title)) {
blacklistBtn.classList.add('blacklisted');
blacklistBtn.textContent = '已屏蔽';
}
// 黑名单按钮的点击逻辑
blacklistBtn.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
if (window.pageHandler && window.pageHandler.manager && window.pageHandler.manager.blacklistManager) {
const isBlacklisted = window.pageHandler.manager.blacklistManager.isBlacklisted(title);
if (isBlacklisted) {
// 如果已经在黑名单中,询问是否移除
if (confirm(`"${title}" 已在黑名单中,是否要移除?`)) {
const success = window.pageHandler.manager.blacklistManager.removeBlacklistItem(title);
if (success) {
blacklistBtn.classList.remove('blacklisted');
blacklistBtn.textContent = '黑名单';
// 更新列表项的黑名单状态
window.pageHandler.updateListItemBlacklistState(item, title);
// 更新黑名单按钮计数
if (window.pageHandler.ui) {
window.pageHandler.ui.updateBlacklistToggleButton();
window.pageHandler.ui.updateBlacklistPanel();
}
window.pageHandler.ui.showMessage(`已从黑名单中移除: ${title}`, 'success');
}
}
} else {
// 直接添加到黑名单,不需要确认
const success = window.pageHandler.manager.blacklistManager.addBlacklistItem(title);
if (success) {
blacklistBtn.classList.add('blacklisted');
blacklistBtn.textContent = '已屏蔽';
// 更新列表项的黑名单状态
window.pageHandler.updateListItemBlacklistState(item, title);
// 更新黑名单按钮计数
if (window.pageHandler.ui) {
window.pageHandler.ui.updateBlacklistToggleButton();
window.pageHandler.ui.updateBlacklistPanel();
}
window.pageHandler.ui.showMessage(`已添加到黑名单: ${title}`, 'success');
} else {
window.pageHandler.ui.showMessage('该词条已在黑名单中', 'info');
}
}
}
};
// 创建左侧浮动按钮容器
const buttonContainer = document.createElement('div');
buttonContainer.className = 'list-item-buttons';
// 将按钮添加到容器中
buttonContainer.appendChild(favoriteBtn);
buttonContainer.appendChild(superLikeBtn);
buttonContainer.appendChild(viewCountBtn);
buttonContainer.appendChild(preloadBtn);
buttonContainer.appendChild(blacklistBtn);
// 将按钮容器添加到列表项中
item.appendChild(buttonContainer);
});
// 在列表页添加导出提醒按钮
this.addExportReminder();
}
// 更新列表项背景色
updateListItemBackground(item, id) {
if (!item || !id) return;
// 移除所有可能的状态类
item.classList.remove('viewed-not-favorited', 'favorited', 'viewed-and-favorited');
const isFavorited = this.manager.isFavorited(id);
const viewCount = this.manager.viewHistoryManager.getViewCount(id);
const isViewed = viewCount > 0;
if (isViewed && !isFavorited) {
// 已浏览但未收藏 - 浅红色
item.classList.add('viewed-not-favorited');
} else if (isFavorited) {
// 已收藏 - 浅绿色
item.classList.add('favorited');
}
// 如果既未浏览也未收藏,保持默认样式
}
addExportReminder() {
// 检查是否已经添加过导出提醒按钮
if (document.querySelector('.export-reminder-btn')) return;
const favorites = this.manager.getFavorites();
const blacklist = this.manager.blacklistManager.getBlacklist();
if (favorites.length === 0 && blacklist.length === 0) return; // 没有数据时不显示
const reminderBtn = document.createElement('button');
reminderBtn.className = 'export-reminder-btn';
let text = '💾 导出数据';
if (favorites.length > 0) {
text += ` (收藏:${favorites.length}`;
if (blacklist.length > 0) {
text += `, 黑名单:${blacklist.length}`;
}
text += ')';
} else if (blacklist.length > 0) {
text += ` (黑名单:${blacklist.length})`;
}
reminderBtn.innerHTML = text;
reminderBtn.title = '点击导出收藏和黑名单数据备份';
reminderBtn.onclick = () => {
if (window.pageHandler && window.pageHandler.ui) {
window.pageHandler.ui.exportFavorites();
}
};
// 添加到页面顶部
reminderBtn.style.position = 'fixed';
reminderBtn.style.top = '20px';
reminderBtn.style.left = '20px';
reminderBtn.style.zIndex = '9997';
reminderBtn.style.background = '#ff9800';
reminderBtn.style.color = 'white';
reminderBtn.style.border = 'none';
reminderBtn.style.padding = '0px 12px 8px 12px';
reminderBtn.style.borderRadius = '4px';
reminderBtn.style.cursor = 'pointer';
reminderBtn.style.fontSize = '12px';
reminderBtn.style.fontWeight = 'bold';
reminderBtn.style.boxShadow = '0 2px 8px rgba(0,0,0,0.2)';
reminderBtn.style.transition = 'all 0.3s';
reminderBtn.onmouseover = () => {
reminderBtn.style.background = '#f57c00';
reminderBtn.style.transform = 'scale(1.05)';
};
reminderBtn.onmouseout = () => {
reminderBtn.style.background = '#ff9800';
reminderBtn.style.transform = 'scale(1)';
};
document.body.appendChild(reminderBtn);
}
processDetailPage() {
// 检查是否已经在右上角添加了收藏按钮
if (document.querySelector('.detail-favorite-btn')) return;
// 获取页面标题和URL
const title = document.title || '未知标题';
const url = window.location.href;
// 从URL中提取ID(福币记详情页格式:/数字/标题)
const id = this.extractIdFromUrl(url);
if (!id) {
console.log('无法从URL中提取ID');
return;
}
// 增加浏览次数(详情页访问时)
this.manager.viewHistoryManager.incrementView(id);
const btn = document.createElement('button');
btn.className = 'favorite-btn detail-favorite-btn';
btn.setAttribute('data-id', id);
btn.textContent = this.manager.isFavorited(id) ? '已收藏' : '收藏';
if (this.manager.isFavorited(id)) {
btn.classList.add('favorited');
}
btn.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
if (window.pageHandler) {
window.pageHandler.toggleFavorite(id, title, url, btn);
}
};
// 创建特别喜欢按钮
const superLikeBtn = document.createElement('button');
superLikeBtn.className = 'super-like-btn detail-super-like-btn';
superLikeBtn.setAttribute('data-id', id);
superLikeBtn.textContent = this.manager.isSuperLiked(id) ? '特别喜欢' : '特别喜欢';
if (this.manager.isSuperLiked(id)) {
superLikeBtn.classList.add('super-liked');
}
superLikeBtn.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
if (window.pageHandler) {
window.pageHandler.toggleSuperLike(id, superLikeBtn);
}
};
// 创建浏览次数按钮
const viewCountBtn = document.createElement('button');
viewCountBtn.className = 'view-count detail-view-count';
viewCountBtn.setAttribute('data-id', id);
const viewCount = this.manager.viewHistoryManager.getViewCount(id);
viewCountBtn.innerHTML = `<span class="view-count-icon">👁️</span><span>${viewCount}</span>`;
viewCountBtn.title = `浏览次数: ${viewCount}`;
// 添加到右上角
btn.style.position = 'fixed';
btn.style.top = '20px';
btn.style.right = '340px'; // 在收藏夹面板左侧
btn.style.zIndex = '9998';
document.body.appendChild(btn);
superLikeBtn.style.position = 'fixed';
superLikeBtn.style.top = '20px';
superLikeBtn.style.right = '500px'; // 在收藏按钮左侧
superLikeBtn.style.zIndex = '9998';
document.body.appendChild(superLikeBtn);
viewCountBtn.style.position = 'fixed';
viewCountBtn.style.top = '20px';
viewCountBtn.style.right = '580px'; // 在特别喜欢按钮左侧
viewCountBtn.style.zIndex = '9998';
document.body.appendChild(viewCountBtn);
}
extractIdFromUrl(url) {
// 针对福币记网站的URL格式提取ID
const urlObj = new URL(url);
const pathParts = urlObj.pathname.split('/').filter(part => part);
// 福币记详情页格式:/数字/标题
if (pathParts.length >= 1 && /^\d+$/.test(pathParts[0])) {
return pathParts[0]; // 返回数字ID
}
// 如果URL是相对路径,尝试从完整URL中提取
if (url.includes('fubiji.site/')) {
const match = url.match(/fubiji\.site\/(\d+)/);
if (match) {
return match[1]; // 返回数字ID
}
}
return null;
}
toggleFavorite(id, title, url, btn) {
if (this.manager.isFavorited(id)) {
this.manager.removeFavorite(id);
btn.classList.remove('favorited');
btn.textContent = '收藏';
} else {
// 获取图片:优先从预加载内容获取,其次从详情页获取
let images = [];
// 首先尝试从预加载内容获取图片
if (window.pageHandler && window.pageHandler.ui && window.pageHandler.ui.preloadManager) {
const preloaded = window.pageHandler.ui.preloadManager.getPreloaded(url);
if (preloaded && preloaded.images && preloaded.images.length > 0) {
images = preloaded.images;
console.log('从预加载内容获取到图片:', images);
}
}
// 如果预加载没有图片,且当前在详情页,则从页面获取
if (images.length === 0 && this.isDetailPage(window.location.href)) {
images = this.manager.getImagesFromPage();
console.log('从详情页获取到图片:', images);
}
// 无论是否有图片,都允许收藏
const success = this.manager.addFavorite(id, title, url, images);
if (success) {
btn.classList.add('favorited');
btn.textContent = '已收藏';
// 如果没有图片,显示提示信息
if (images.length === 0) {
console.log('收藏成功,但未获取到图片');
if (window.pageHandler && window.pageHandler.ui) {
window.pageHandler.ui.showMessage('收藏成功,但未获取到图片', 'info');
}
}
}
}
if (window.pageHandler && window.pageHandler.ui) {
window.pageHandler.ui.updateToggleButton();
window.pageHandler.ui.updateStorageSize();
}
// 更新所有相同ID的按钮状态,确保列表页和详情页同步
this.updateAllButtonsWithId(id);
// 更新导出提醒按钮
this.updateExportReminder();
}
updateAllButtonsWithId(id) {
// 更新所有具有相同ID的按钮状态
document.querySelectorAll(`.favorite-btn[data-id="${id}"]`).forEach(btn => {
if (this.manager.isFavorited(id)) {
btn.classList.add('favorited');
btn.textContent = '已收藏';
} else {
btn.classList.remove('favorited');
btn.textContent = '收藏';
}
});
// 更新所有具有相同ID的特别喜欢按钮状态
document.querySelectorAll(`.super-like-btn[data-id="${id}"]`).forEach(btn => {
if (this.manager.isSuperLiked(id)) {
btn.classList.add('super-liked');
btn.textContent = '特别喜欢';
} else {
btn.classList.remove('super-liked');
btn.textContent = '特别喜欢';
}
});
// 更新对应列表项的背景色
document.querySelectorAll('.format-standard').forEach(item => {
const link = item.querySelector('a');
if (link) {
const itemId = this.extractIdFromUrl(link.href);
if (itemId === id) {
this.updateListItemBackground(item, id);
}
}
});
}
updateViewCountButton(id) {
// 更新所有具有相同ID的浏览次数按钮
const viewCount = this.manager.viewHistoryManager.getViewCount(id);
document.querySelectorAll(`.view-count[data-id="${id}"]`).forEach(btn => {
btn.innerHTML = `<span class="view-count-icon">👁️</span><span>${viewCount}</span>`;
btn.title = `浏览次数: ${viewCount}`;
});
}
toggleSuperLike(id, btn) {
// 如果还没有收藏,先收藏
if (!this.manager.isFavorited(id)) {
const title = document.title || '未知标题';
const url = window.location.href;
let images = [];
// 获取图片:优先从预加载内容获取,其次从详情页获取
if (window.pageHandler && window.pageHandler.ui && window.pageHandler.ui.preloadManager) {
const preloaded = window.pageHandler.ui.preloadManager.getPreloaded(url);
if (preloaded && preloaded.images && preloaded.images.length > 0) {
images = preloaded.images;
console.log('从预加载内容获取到图片:', images);
}
}
// 如果预加载没有图片,且当前在详情页,则从页面获取
if (images.length === 0 && this.isDetailPage(window.location.href)) {
images = this.manager.getImagesFromPage();
console.log('从详情页获取到图片:', images);
}
// 无论是否有图片,都允许收藏
const success = this.manager.addFavorite(id, title, url, images);
if (success && images.length === 0) {
console.log('特别喜欢收藏成功,但未获取到图片');
if (window.pageHandler && window.pageHandler.ui) {
window.pageHandler.ui.showMessage('特别喜欢收藏成功,但未获取到图片', 'info');
}
}
}
// 切换特别喜欢状态
this.manager.toggleSuperLike(id);
// 更新按钮状态
if (this.manager.isSuperLiked(id)) {
btn.classList.add('super-liked');
btn.textContent = '特别喜欢';
} else {
btn.classList.remove('super-liked');
btn.textContent = '特别喜欢';
}
// 更新所有相同ID的按钮状态
this.updateAllSuperLikeButtonsWithId(id);
// 更新收藏夹面板(只更新标题,不重新渲染整个面板)
if (window.pageHandler && window.pageHandler.ui) {
window.pageHandler.ui.updateToggleButton();
window.pageHandler.ui.updateSuperLikeBadge(id);
window.pageHandler.ui.updateStorageSize();
}
}
toggleSuperLikeInList(id, title, url, btn) {
// 如果还没有收藏,先收藏
if (!this.manager.isFavorited(id)) {
let images = [];
// 获取图片:优先从预加载内容获取
if (window.pageHandler && window.pageHandler.ui && window.pageHandler.ui.preloadManager) {
const preloaded = window.pageHandler.ui.preloadManager.getPreloaded(url);
if (preloaded && preloaded.images && preloaded.images.length > 0) {
images = preloaded.images;
console.log('从预加载内容获取到图片:', images);
}
}
// 无论是否有图片,都允许收藏
const success = this.manager.addFavorite(id, title, url, images);
if (success && images.length === 0) {
console.log('特别喜欢收藏成功,但未获取到图片');
if (window.pageHandler && window.pageHandler.ui) {
window.pageHandler.ui.showMessage('特别喜欢收藏成功,但未获取到图片', 'info');
}
}
}
// 切换特别喜欢状态
this.manager.toggleSuperLike(id);
// 更新按钮状态
if (this.manager.isSuperLiked(id)) {
btn.classList.add('super-liked');
btn.textContent = '特别喜欢';
} else {
btn.classList.remove('super-liked');
btn.textContent = '特别喜欢';
}
// 更新所有相同ID的按钮状态
this.updateAllSuperLikeButtonsWithId(id);
// 更新收藏夹面板(只更新标题,不重新渲染整个面板)
if (window.pageHandler && window.pageHandler.ui) {
window.pageHandler.ui.updateToggleButton();
window.pageHandler.ui.updateSuperLikeBadge(id);
window.pageHandler.ui.updateStorageSize();
}
}
updateAllSuperLikeButtonsWithId(id) {
// 更新所有具有相同ID的特别喜欢按钮状态
document.querySelectorAll(`.super-like-btn[data-id="${id}"]`).forEach(btn => {
if (this.manager.isSuperLiked(id)) {
btn.classList.add('super-liked');
btn.textContent = '特别喜欢';
} else {
btn.classList.remove('super-liked');
btn.textContent = '特别喜欢';
}
});
}
// 更新所有列表项的背景色
updateAllListItemBackgrounds() {
document.querySelectorAll('.format-standard').forEach(item => {
const link = item.querySelector('a');
if (link) {
const id = this.extractIdFromUrl(link.href);
if (id) {
this.updateListItemBackground(item, id);
}
}
});
}
// 更新列表项黑名单状态
updateListItemBlacklistState(item, title) {
if (!item || !title) return;
const isBlacklisted = this.manager.blacklistManager.isBlacklisted(title);
if (isBlacklisted) {
item.classList.add('blacklisted');
// 获取匹配的黑名单关键字
const matchingKeywords = this.manager.blacklistManager.getMatchingBlacklistKeywords(title);
// 移除已存在的提示
const existingTooltip = item.querySelector('.blacklist-tooltip');
if (existingTooltip) {
existingTooltip.remove();
}
// 创建新的提示
if (matchingKeywords.length > 0) {
const tooltip = document.createElement('div');
tooltip.className = 'blacklist-tooltip';
tooltip.textContent = `被屏蔽: ${matchingKeywords.join(', ')}`;
tooltip.title = `被以下关键词屏蔽: ${matchingKeywords.join(', ')}`;
item.appendChild(tooltip);
}
} else {
item.classList.remove('blacklisted');
// 移除提示
const existingTooltip = item.querySelector('.blacklist-tooltip');
if (existingTooltip) {
existingTooltip.remove();
}
}
}
}
// 初始化
const manager = new FavoritesManager();
const ui = new FavoritesUI(manager);
const handler = new PageHandler(manager, ui);
// 将UI更新方法暴露到全局,供按钮更新使用
window.updateAllFavoriteButtons = () => {
ui.updateAllButtons();
};
// 将存储大小更新方法暴露到全局
window.updateStorageSize = () => {
if (window.pageHandler && window.pageHandler.ui) {
window.pageHandler.ui.updateStorageSize();
}
};
// 将重置变化标记方法暴露到全局
window.resetChangesFlag = () => {
if (window.pageHandler && window.pageHandler.manager) {
window.pageHandler.manager.resetChangesFlag();
console.log('手动重置数据变化标记');
}
};
// 将pageHandler暴露到全局,供收藏夹面板使用
window.pageHandler = handler;
// 将manager暴露到全局,供跨标签页同步使用
window.favoritesManager = manager;
// 添加测试图片查看器函数到全局
window.testImageViewer = (images) => {
if (window.pageHandler && window.pageHandler.ui && window.pageHandler.ui.imageViewer) {
console.log('手动测试图片查看器:', images);
window.pageHandler.ui.imageViewer.show(images, 0);
} else {
console.error('图片查看器未正确初始化');
}
};
// 添加测试无图片收藏函数到全局
window.testNoImagesFavorite = (id, title, url) => {
if (window.pageHandler && window.pageHandler.manager) {
console.log('手动测试无图片收藏:', id, title, url);
const success = window.pageHandler.manager.addFavorite(id, title, url, []);
if (success) {
console.log('无图片收藏测试成功');
if (window.pageHandler.ui) {
window.pageHandler.ui.updateToggleButton();
window.pageHandler.ui.updatePanel();
window.pageHandler.ui.updateStorageSize();
}
}
} else {
console.error('收藏管理器未正确初始化');
}
};
// 添加测试黑名单功能函数到全局
window.testBlacklist = (keyword) => {
if (window.pageHandler && window.pageHandler.manager && window.pageHandler.manager.blacklistManager) {
console.log('手动测试黑名单功能:', keyword);
const success = window.pageHandler.manager.blacklistManager.addBlacklistItem(keyword);
if (success) {
console.log('黑名单测试成功');
if (window.pageHandler.ui) {
window.pageHandler.ui.updateBlacklistToggleButton();
window.pageHandler.ui.updateBlacklistPanel();
window.pageHandler.updateAllBlacklistStates();
}
} else {
console.log('黑名单项已存在或为空');
}
} else {
console.error('黑名单管理器未正确初始化');
}
};
console.log('福币记收藏功能脚本已加载 v2.2,使用localStorage存储,支持跨标签页同步,添加分页功能');
console.log('可以通过 window.testImageViewer(images) 手动测试图片查看器功能');
console.log('可以通过 window.testNoImagesFavorite(id, title, url) 手动测试无图片收藏功能');
console.log('可以通过 window.testBlacklist(keyword) 手动测试黑名单功能');
console.log('可以通过 window.updateStorageSize() 手动更新存储大小显示');
console.log('可以通过 window.resetChangesFlag() 手动重置数据变化标记');
console.log('浏览次数功能:详情页访问时自动增加次数,预加载成功后也会增加次数');
console.log('图片查看功能:点击列表页和收藏夹中的图片可以放大查看,支持左右切换和键盘操作');
console.log('无图片收藏功能:无法获取图片的收藏会标记为红色边框,但仍可成功收藏');
console.log('黑名单功能:输入关键词屏蔽匹配的列表项,被屏蔽的项显示为黑色且禁用所有功能,悬停显示被哪些关键词屏蔽');
console.log('黑名单按钮功能:在列表页每个词条旁添加黑名单按钮,点击可将词条标题添加到黑名单,已屏蔽的显示"已屏蔽"状态');
console.log('黑名单搜索功能:在黑名单管理面板中可以搜索关键词,实时过滤显示匹配的黑名单项');
console.log('黑名单导入优化:导入黑名单时采用追加方式,自动去重,不会覆盖现有黑名单项');
console.log('存储大小功能:实时显示收藏数据、浏览历史和预加载数据的总大小,超过10MB显示警告,超过50MB显示危险');
console.log('页面关闭提示功能:只有在收藏夹有数据变化时才会提示导出备份,导出或清空后自动重置变化标记');
console.log('清空未加载浏览记录功能:点击按钮清空当前页面所有未预加载列表项的浏览记录,已预加载的项会被跳过');
console.log('预加载功能:使用fetch请求获取HTML并解析图片,支持重试机制,比iframe方式更高效');
console.log('收藏夹图片优化:进入页面时不自动加载收藏夹内容,打开收藏夹面板时不加载图片,点击"显示图片"按钮后才开始加载,只加载当前可见区域的图片');
console.log('图片加载优化:使用占位符显示,点击占位符可加载单个图片,点击"显示图片"按钮可加载当前可见区域的所有图片,支持滚动时动态加载');
// 测试功能是否正常工作
setTimeout(() => {
console.log('测试图片查看器功能:', {
imageViewer: window.pageHandler?.ui?.imageViewer,
viewer: window.pageHandler?.ui?.imageViewer?.viewer,
viewerDisplay: window.pageHandler?.ui?.imageViewer?.viewer?.style?.display
});
// 测试图片查看器的显示状态
if (window.pageHandler?.ui?.imageViewer?.viewer) {
const viewer = window.pageHandler.ui.imageViewer.viewer;
console.log('图片查看器状态:', {
display: viewer.style.display,
visibility: viewer.style.visibility,
opacity: viewer.style.opacity,
zIndex: viewer.style.zIndex
});
}
console.log('预加载管理器状态:', {
preloadManager: window.pageHandler?.ui?.preloadManager,
preloadedCount: window.pageHandler?.ui?.preloadManager?.preloadedUrls?.size || 0
});
}, 2000);
})();
Wrap
Beautify