1686 lines
58 KiB
JavaScript
1686 lines
58 KiB
JavaScript
// 全局变量
|
||
let allAlbums = []; // 存储所有相册数据
|
||
let displayedAlbums = []; // 当前显示的相册数据
|
||
let filteredAlbums = null; // 过滤后的相册数据(有筛选时使用,null=无筛选)
|
||
let searchTimeout = null;
|
||
let currentDisplayPage = 1; // 前端显示的分页
|
||
let albumsPerPage = 50; // 每次显示50个相册
|
||
let isLoadingMore = false;
|
||
let hasMoreAlbums = true;
|
||
let lazyLoadObserver = null;
|
||
let isAllAlbumsLoaded = false; // 标记是否已加载所有相册数据
|
||
let isInFavoritesPage = false; // 标记当前是否在收藏页
|
||
|
||
// 标签和人物筛选
|
||
let currentFilter = null; // { type: 'tag'|'person', value: string }
|
||
let collapsedSections = { people: false, tags: false };
|
||
|
||
// 侧边栏状态(持久化)
|
||
let sidebarCollapsed = localStorage.getItem('sidebarCollapsed') === 'true';
|
||
|
||
// API基础URL
|
||
const API_BASE = '/api';
|
||
|
||
// 图片加载器类
|
||
class ImageLoader {
|
||
constructor() {
|
||
this.cache = new Map();
|
||
}
|
||
|
||
// 加载图片
|
||
async loadImage(imageId, element) {
|
||
// 检查缓存
|
||
if (this.cache.has(imageId)) {
|
||
const cachedData = this.cache.get(imageId);
|
||
this.setImageSrc(element, cachedData.blobUrl, cachedData.mimeType);
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const response = await fetch(`${API_BASE}/image/${imageId}`);
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP error! status: ${response.status}`);
|
||
}
|
||
|
||
const contentType = response.headers.get('content-type');
|
||
const imageData = await response.blob();
|
||
|
||
// 创建Blob URL
|
||
const mimeType = contentType || 'image/jpeg';
|
||
const blob = new Blob([imageData], { type: mimeType });
|
||
const blobUrl = URL.createObjectURL(blob);
|
||
|
||
// 缓存结果
|
||
this.cache.set(imageId, { blobUrl, mimeType });
|
||
|
||
// 设置图片源
|
||
this.setImageSrc(element, blobUrl, mimeType);
|
||
|
||
} catch (error) {
|
||
console.error('图片加载失败:', error);
|
||
// 加载失败时使用占位图
|
||
element.src = '/assets/placeholder.svg';
|
||
}
|
||
}
|
||
|
||
// 设置图片源
|
||
setImageSrc(element, blobUrl, mimeType) {
|
||
// 创建新的Image对象进行预加载
|
||
const img = new Image();
|
||
img.onload = () => {
|
||
element.src = blobUrl;
|
||
element.classList.add('loaded');
|
||
};
|
||
img.onerror = () => {
|
||
console.error('图片解码失败');
|
||
element.src = '/assets/placeholder.svg';
|
||
};
|
||
img.src = blobUrl;
|
||
}
|
||
|
||
// 清理缓存
|
||
clearCache() {
|
||
// 清理所有Blob URL
|
||
for (const [imageId, cached] of this.cache.entries()) {
|
||
URL.revokeObjectURL(cached.blobUrl);
|
||
}
|
||
this.cache.clear();
|
||
}
|
||
}
|
||
|
||
// 创建全局图片加载器实例
|
||
const imageLoader = new ImageLoader();
|
||
|
||
// 设备检测
|
||
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
|
||
const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
||
|
||
|
||
// 页面加载完成后初始化
|
||
document.addEventListener('DOMContentLoaded', function () {
|
||
// 添加设备类到body
|
||
if (isMobile) {
|
||
document.body.classList.add('mobile-device');
|
||
}
|
||
if (isTouchDevice) {
|
||
document.body.classList.add('touch-device');
|
||
}
|
||
|
||
initSearch();
|
||
initInfiniteScroll();
|
||
initLazyLoading(); // 初始化图片懒加载
|
||
initSidebarSections(); // 初始化侧边栏区块折叠状态
|
||
|
||
// 检查URL中是否有search参数
|
||
const urlParams = new URLSearchParams(window.location.search);
|
||
const searchQuery = urlParams.get('search');
|
||
if (searchQuery) {
|
||
const searchInput = document.getElementById('search-input');
|
||
searchInput.value = searchQuery;
|
||
// 直接搜索,不先加载全部相册
|
||
searchAlbums();
|
||
} else {
|
||
loadAlbums();
|
||
}
|
||
});
|
||
|
||
// 初始化搜索功能
|
||
function initSearch() {
|
||
const searchInput = document.getElementById('search-input');
|
||
|
||
// 防抖搜索
|
||
searchInput.addEventListener('input', function () {
|
||
clearTimeout(searchTimeout);
|
||
|
||
// 优化:根据输入长度动态调整延迟
|
||
const inputLength = this.value.length;
|
||
const debounceDelay = inputLength > 3 ? 200 : 500; // 长文本快速搜索,短文本慢速搜索
|
||
|
||
searchTimeout = setTimeout(debouncedSearch, debounceDelay);
|
||
});
|
||
|
||
// 移动端优化:搜索框聚焦时自动滚动到可视区域
|
||
if (isMobile) {
|
||
searchInput.addEventListener('focus', function () {
|
||
setTimeout(() => {
|
||
searchInput.scrollIntoView({behavior: 'smooth', block: 'center'});
|
||
}, 100);
|
||
});
|
||
}
|
||
}
|
||
|
||
// 初始化无限滚动
|
||
function initInfiniteScroll() {
|
||
let scrollTimeout;
|
||
|
||
window.addEventListener('scroll', function () {
|
||
clearTimeout(scrollTimeout);
|
||
scrollTimeout = setTimeout(handleScroll, 100);
|
||
});
|
||
}
|
||
|
||
// 初始化图片懒加载
|
||
function initLazyLoading() {
|
||
// 使用Intersection Observer实现懒加载
|
||
if ('IntersectionObserver' in window) {
|
||
if (lazyLoadObserver) {
|
||
lazyLoadObserver.disconnect();
|
||
}
|
||
|
||
lazyLoadObserver = new IntersectionObserver((entries) => {
|
||
entries.forEach(entry => {
|
||
if (entry.isIntersecting) {
|
||
const img = entry.target;
|
||
const imageId = img.getAttribute('data-image-id');
|
||
|
||
if (imageId && !img.classList.contains('loaded')) {
|
||
imageLoader.loadImage(imageId, img);
|
||
}
|
||
|
||
lazyLoadObserver.unobserve(img);
|
||
}
|
||
});
|
||
}, {
|
||
rootMargin: '100px 0px', // 提前100px开始加载
|
||
threshold: 0.1
|
||
});
|
||
|
||
// 观察所有带有data-image-id的图片
|
||
document.querySelectorAll('img[data-image-id]').forEach(img => {
|
||
if (!img.classList.contains('loaded')) {
|
||
lazyLoadObserver.observe(img);
|
||
}
|
||
});
|
||
} else {
|
||
// 不支持Intersection Observer的回退方案
|
||
console.warn('浏览器不支持Intersection Observer,使用回退懒加载方案');
|
||
initFallbackLazyLoading();
|
||
}
|
||
}
|
||
|
||
// 回退懒加载方案
|
||
function initFallbackLazyLoading() {
|
||
let scrollTimeout;
|
||
|
||
function checkImagesInView() {
|
||
const images = document.querySelectorAll('img[data-image-id]:not(.loaded)');
|
||
const viewportHeight = window.innerHeight;
|
||
const scrollTop = window.scrollY;
|
||
|
||
images.forEach(img => {
|
||
const rect = img.getBoundingClientRect();
|
||
const isInView = rect.top < viewportHeight + 200 && rect.bottom > -200;
|
||
|
||
if (isInView) {
|
||
const imageId = img.getAttribute('data-image-id');
|
||
if (imageId) {
|
||
imageLoader.loadImage(imageId, img);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
window.addEventListener('scroll', () => {
|
||
clearTimeout(scrollTimeout);
|
||
scrollTimeout = setTimeout(checkImagesInView, 100);
|
||
});
|
||
|
||
// 初始检查
|
||
checkImagesInView();
|
||
}
|
||
|
||
// 处理滚动事件
|
||
function handleScroll() {
|
||
// 如果在收藏页,禁用无限滚动
|
||
if (isInFavoritesPage) return;
|
||
|
||
if (isLoadingMore || !hasMoreAlbums) return;
|
||
|
||
// 检查是否滚动到底部
|
||
const scrollTop = window.scrollY || document.documentElement.scrollTop;
|
||
const scrollHeight = document.documentElement.scrollHeight;
|
||
const clientHeight = window.innerHeight;
|
||
|
||
// 当距离底部还有200px时开始加载
|
||
if (scrollTop + clientHeight >= scrollHeight - 200) {
|
||
loadMoreAlbums();
|
||
}
|
||
}
|
||
|
||
// 加载更多相册(从前端已加载的数据中获取)
|
||
async function loadMoreAlbums() {
|
||
// 如果在收藏页,不加载更多数据
|
||
if (isInFavoritesPage) return;
|
||
|
||
if (isLoadingMore || !hasMoreAlbums) return;
|
||
// 全量模式下(无筛选)需等 isAllAlbumsLoaded;过滤模式下直接使用 filteredAlbums
|
||
if (!currentFilter && !isAllAlbumsLoaded) return;
|
||
|
||
isLoadingMore = true;
|
||
try {
|
||
// 显示加载更多指示器
|
||
showLoadingMore();
|
||
|
||
// 增加当前显示页数
|
||
currentDisplayPage++;
|
||
|
||
// 显示下一页数据
|
||
displayCurrentPage();
|
||
|
||
// 初始化图片懒加载
|
||
initLazyLoading();
|
||
|
||
// 预加载下一批图片
|
||
preloadNextBatchImages();
|
||
|
||
} catch (error) {
|
||
console.error('加载更多相册失败:', error);
|
||
showLoadMoreError();
|
||
} finally {
|
||
isLoadingMore = false;
|
||
hideLoadingMore();
|
||
}
|
||
}
|
||
|
||
// 显示加载更多错误
|
||
function showLoadMoreError() {
|
||
const container = document.getElementById('albums-container');
|
||
const errorElement = document.createElement('div');
|
||
errorElement.className = 'load-more-error';
|
||
errorElement.innerHTML = `
|
||
<div class="error-content">
|
||
<i class="fas fa-exclamation-triangle"></i>
|
||
<span>加载失败,<a href="javascript:void(0)" onclick="loadMoreAlbums()">点击重试</a></span>
|
||
</div>
|
||
`;
|
||
|
||
const existingError = container.querySelector('.load-more-error');
|
||
if (existingError) {
|
||
existingError.remove();
|
||
}
|
||
|
||
container.appendChild(errorElement);
|
||
}
|
||
|
||
// 显示骨架屏
|
||
function showSkeleton(count = 6) {
|
||
const container = document.getElementById('albums-container');
|
||
container.innerHTML = Array(count).fill(0).map(() => `
|
||
<div class="skeleton-card">
|
||
<div class="skeleton-cover skeleton"></div>
|
||
<div class="skeleton-content">
|
||
<div class="skeleton-title skeleton"></div>
|
||
<div class="skeleton-meta">
|
||
<div class="skeleton-meta-item skeleton"></div>
|
||
<div class="skeleton-meta-item skeleton"></div>
|
||
</div>
|
||
<div class="skeleton-button skeleton"></div>
|
||
</div>
|
||
</div>
|
||
`).join('');
|
||
}
|
||
|
||
// 显示详细加载状态
|
||
function showLoadingState(message = '加载中...', progress = 0) {
|
||
const container = document.getElementById('albums-container');
|
||
const progressWidth = Math.min(Math.max(progress, 0), 100);
|
||
|
||
container.innerHTML = `
|
||
<div class="loading-state">
|
||
<i class="fas fa-spinner fa-spin"></i>
|
||
<p>${message}</p>
|
||
<div class="progress-bar">
|
||
<div class="progress" style="width: ${progressWidth}%"></div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// 显示加载更多指示器
|
||
function showLoadingMore() {
|
||
const container = document.getElementById('albums-container');
|
||
const loadingIndicator = document.createElement('div');
|
||
loadingIndicator.id = 'loading-more';
|
||
loadingIndicator.className = 'loading-more';
|
||
loadingIndicator.innerHTML = `
|
||
<div class="loading-spinner">
|
||
<i class="fas fa-spinner fa-spin"></i>
|
||
<span>加载更多相册...</span>
|
||
</div>
|
||
`;
|
||
container.appendChild(loadingIndicator);
|
||
}
|
||
|
||
// 隐藏加载更多指示器
|
||
function hideLoadingMore() {
|
||
const loadingIndicator = document.getElementById('loading-more');
|
||
if (loadingIndicator) {
|
||
loadingIndicator.remove();
|
||
}
|
||
}
|
||
|
||
// 追加相册到列表
|
||
function appendAlbums(albums) {
|
||
const container = document.getElementById('albums-container');
|
||
const newAlbumsHTML = albums.map(album => {
|
||
// 处理封面图片URL - 使用懒加载
|
||
let coverImageUrl = '/assets/placeholder.svg';
|
||
let dataSrc = '';
|
||
|
||
if (album.cover_image_id) {
|
||
dataSrc = `${API_BASE}/image/${album.cover_image_id}`;
|
||
coverImageUrl = '/assets/placeholder.svg'; // 初始使用占位图
|
||
}
|
||
|
||
// 格式化日期
|
||
const createdDate = new Date(album.created_at).toLocaleDateString('zh-CN');
|
||
|
||
return `
|
||
<div class="album-card" data-album-id="${album.id}" onclick="openAlbum('${album.id}')">
|
||
<div class="album-cover">
|
||
<img
|
||
src="${coverImageUrl}"
|
||
${album.cover_image_id ? `data-image-id="${album.cover_image_id}"` : ''}
|
||
alt="${album.title}"
|
||
class="lazy"
|
||
onerror="this.src='/assets/placeholder.svg'"
|
||
>
|
||
</div>
|
||
<div class="album-info">
|
||
<h3>${album.title}</h3>
|
||
<div class="album-meta">
|
||
<span><i class="fas fa-images"></i> ${album.image_count} 张图片</span>
|
||
<span><i class="fas fa-calendar"></i> ${createdDate}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}).join('');
|
||
|
||
// 移除加载指示器并添加新相册
|
||
hideLoadingMore();
|
||
container.insertAdjacentHTML('beforeend', newAlbumsHTML);
|
||
|
||
// 重新初始化懒加载,观察新添加的图片
|
||
initLazyLoading();
|
||
}
|
||
|
||
// 加载所有相册数据(一次性加载全部)
|
||
async function loadAlbums(reset = false) {
|
||
try {
|
||
if (reset) {
|
||
currentDisplayPage = 1;
|
||
allAlbums = [];
|
||
displayedAlbums = [];
|
||
hasMoreAlbums = true;
|
||
isAllAlbumsLoaded = false;
|
||
const container = document.getElementById('albums-container');
|
||
container.innerHTML = '';
|
||
}
|
||
|
||
// 如果已经加载过所有数据,直接显示当前页
|
||
if (isAllAlbumsLoaded) {
|
||
displayCurrentPage();
|
||
return;
|
||
}
|
||
|
||
// 显示骨架屏
|
||
showSkeleton();
|
||
|
||
// 一次性加载所有相册数据
|
||
const response = await fetch(`${API_BASE}/albums`);
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP error! status: ${response.status}`);
|
||
}
|
||
|
||
allAlbums = await response.json();
|
||
isAllAlbumsLoaded = true;
|
||
|
||
// 显示第一页数据
|
||
displayCurrentPage();
|
||
|
||
updateStats(allAlbums);
|
||
|
||
// 初始化图片懒加载
|
||
initLazyLoading();
|
||
|
||
// 预加载下一批图片
|
||
preloadNextBatchImages();
|
||
|
||
// 加载热门标签和人物
|
||
loadDiscoverData();
|
||
|
||
} catch (error) {
|
||
console.error('加载相册失败:', error);
|
||
showError('加载相册数据失败,请刷新页面重试');
|
||
}
|
||
}
|
||
|
||
// 显示当前页的相册数据
|
||
function displayCurrentPage() {
|
||
// 有筛选时用 filteredAlbums,否则用 allAlbums
|
||
const source = filteredAlbums || allAlbums;
|
||
const startIndex = (currentDisplayPage - 1) * albumsPerPage;
|
||
const endIndex = startIndex + albumsPerPage;
|
||
|
||
// 获取当前页要显示的相册
|
||
displayedAlbums = source.slice(startIndex, endIndex);
|
||
|
||
// 如果是第一页,直接显示;否则追加显示
|
||
if (currentDisplayPage === 1) {
|
||
displayAlbums(displayedAlbums);
|
||
} else {
|
||
appendAlbums(displayedAlbums);
|
||
}
|
||
|
||
// 检查是否还有更多数据可以显示
|
||
hasMoreAlbums = (currentDisplayPage * albumsPerPage) < source.length;
|
||
}
|
||
|
||
// 搜索相册
|
||
// 防抖搜索函数
|
||
async function debouncedSearch() {
|
||
const searchTerm = document.getElementById('search-input').value.toLowerCase().trim();
|
||
|
||
if (searchTerm === '') {
|
||
// 搜索框为空 → 清除过滤状态,显示所有相册
|
||
currentFilter = null;
|
||
filteredAlbums = null;
|
||
displayAlbums(allAlbums);
|
||
updateStats();
|
||
return;
|
||
}
|
||
|
||
// 直接使用后端搜索API进行全站搜索
|
||
const results = await performServerSearchWithResult(searchTerm);
|
||
if (results && results.length > 0) {
|
||
filteredAlbums = results;
|
||
currentDisplayPage = 1;
|
||
hasMoreAlbums = results.length > albumsPerPage;
|
||
displayAlbums(results);
|
||
updateStats(results);
|
||
initLazyLoading();
|
||
}
|
||
}
|
||
|
||
// 在当前已加载的数据集中搜索
|
||
function searchInLoadedAlbums(searchTerm) {
|
||
if (!allAlbums.length) return [];
|
||
|
||
return allAlbums.filter(album => {
|
||
// 搜索标题和描述
|
||
const titleMatch = album.title.toLowerCase().includes(searchTerm);
|
||
const descriptionMatch = album.description && album.description.toLowerCase().includes(searchTerm);
|
||
|
||
return titleMatch || descriptionMatch;
|
||
});
|
||
}
|
||
|
||
// 显示搜索提示
|
||
function showSearchHint(searchTerm) {
|
||
const container = document.getElementById('albums-container');
|
||
container.innerHTML = `
|
||
<div class="search-hint">
|
||
<i class="fas fa-search"></i>
|
||
<h3>未在当前页面找到 "${searchTerm}"</h3>
|
||
<p>尝试:</p>
|
||
<ul>
|
||
<li>继续滚动加载更多相册</li>
|
||
<li>使用更精确的搜索词</li>
|
||
<li><button class="btn-primary" onclick="performServerSearch('${searchTerm}')">在全站搜索 "${searchTerm}"</button></li>
|
||
</ul>
|
||
</div>
|
||
`;
|
||
document.getElementById('stats').innerHTML = `在当前页面中未找到包含 "${searchTerm}" 的相册`;
|
||
}
|
||
|
||
// 执行服务器端搜索(全站搜索)
|
||
async function performServerSearch(searchTerm) {
|
||
const results = await performServerSearchWithResult(searchTerm);
|
||
if (results && results.length > 0) {
|
||
displayAlbums(results);
|
||
const oldStats = document.getElementById('stats');
|
||
if (oldStats) {
|
||
oldStats.innerHTML = `在全站找到 ${results.length} 个包含 "${searchTerm}" 的相册`;
|
||
}
|
||
updateStats(results);
|
||
initLazyLoading();
|
||
}
|
||
}
|
||
|
||
/// 执行服务器端搜索并返回结果数组(不直接渲染)
|
||
async function performServerSearchWithResult(searchTerm) {
|
||
try {
|
||
const response = await fetch(`${API_BASE}/search?q=${encodeURIComponent(searchTerm)}&page=1&limit=200`);
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP error! status: ${response.status}`);
|
||
}
|
||
|
||
const serverResults = await response.json();
|
||
|
||
if (serverResults.length === 0) {
|
||
return [];
|
||
}
|
||
|
||
return serverResults;
|
||
} catch (error) {
|
||
console.error('全站搜索失败:', error);
|
||
return [];
|
||
}
|
||
}
|
||
|
||
// 显示相册列表
|
||
function displayAlbums(albums) {
|
||
const container = document.getElementById('albums-container');
|
||
|
||
if (albums.length === 0) {
|
||
container.innerHTML = '<div class="no-results"><i class="fas fa-search"></i><br>没有找到匹配的相册</div>';
|
||
return;
|
||
}
|
||
|
||
container.innerHTML = albums.map(album => {
|
||
// 处理封面图片URL - 使用懒加载
|
||
let coverImageUrl = '/assets/placeholder.svg';
|
||
let dataSrc = '';
|
||
|
||
if (album.cover_image_id) {
|
||
dataSrc = `${API_BASE}/image/${album.cover_image_id}`;
|
||
coverImageUrl = '/assets/placeholder.svg'; // 初始使用占位图
|
||
}
|
||
|
||
// 格式化日期
|
||
const createdDate = new Date(album.created_at).toLocaleDateString('zh-CN');
|
||
|
||
return `
|
||
<div class="album-card" data-album-id="${album.id}" onclick="openAlbum('${album.id}')">
|
||
<div class="album-cover">
|
||
<img
|
||
src="${coverImageUrl}"
|
||
${album.cover_image_id ? `data-image-id="${album.cover_image_id}"` : ''}
|
||
alt="${album.title}"
|
||
class="lazy"
|
||
onerror="this.src='/assets/placeholder.svg'"
|
||
>
|
||
</div>
|
||
<div class="album-info">
|
||
<h3>${album.title}</h3>
|
||
<div class="album-meta">
|
||
<span><i class="fas fa-images"></i> ${album.image_count} 张图片</span>
|
||
<span><i class="fas fa-calendar"></i> ${createdDate}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}).join('');
|
||
}
|
||
|
||
|
||
|
||
// 预加载下一批图片
|
||
function preloadNextBatchImages() {
|
||
const imagesToPreload = [];
|
||
const visibleImages = document.querySelectorAll('img[data-image-id]:not(.loaded)');
|
||
|
||
// 预加载前5张可见图片
|
||
visibleImages.forEach((img, index) => {
|
||
if (index < 5) {
|
||
const imageId = img.getAttribute('data-image-id');
|
||
if (imageId) {
|
||
imagesToPreload.push(imageId);
|
||
}
|
||
}
|
||
});
|
||
|
||
// 使用图片加载器预加载
|
||
imagesToPreload.forEach(imageId => {
|
||
const tempImg = new Image();
|
||
imageLoader.loadImage(imageId, tempImg);
|
||
});
|
||
}
|
||
|
||
// 更新统计信息
|
||
function updateStats(albums) {
|
||
if (!albums) {
|
||
albums = allAlbums;
|
||
}
|
||
const totalImages = albums.reduce((sum, album) => sum + album.image_count, 0);
|
||
|
||
// 更新统计条
|
||
const statAlbums = document.getElementById('stat-albums');
|
||
const statImages = document.getElementById('stat-images');
|
||
if (statAlbums) statAlbums.textContent = albums.length.toLocaleString();
|
||
if (statImages) statImages.textContent = totalImages.toLocaleString();
|
||
|
||
// 兼容旧版 stats
|
||
const oldStats = document.getElementById('stats');
|
||
if (oldStats) oldStats.innerHTML = `共发现 ${albums.length} 个相册,包含 ${totalImages} 张图片`;
|
||
|
||
// 获取人物数量(用 metadata stats API)
|
||
fetch(`${API_BASE}/metadata/stats`).then(r => r.ok && r.json()).then(data => {
|
||
if (data && data.total_persons !== undefined) {
|
||
const statPeople = document.getElementById('stat-people');
|
||
if (statPeople) statPeople.textContent = data.total_persons.toLocaleString();
|
||
}
|
||
}).catch(() => {});
|
||
}
|
||
|
||
// 显示错误信息
|
||
function showError(message) {
|
||
const container = document.getElementById('albums-container');
|
||
container.innerHTML = `<div class="no-results"><i class="fas fa-exclamation-triangle"></i><br>${message}</div>`;
|
||
const oldStats = document.getElementById('stats');
|
||
if (oldStats) oldStats.innerHTML = '';
|
||
}
|
||
|
||
// 搜索相册函数
|
||
async function searchAlbums() {
|
||
await debouncedSearch();
|
||
}
|
||
|
||
|
||
// 性能监控
|
||
function logPerformance() {
|
||
if ('performance' in window) {
|
||
// 使用更可靠的方式获取页面加载时间
|
||
const calculateLoadTime = () => {
|
||
const navigationTiming = performance.getEntriesByType('navigation')[0];
|
||
if (navigationTiming && navigationTiming.loadEventEnd > 0 && navigationTiming.navigationStart > 0) {
|
||
const loadTime = navigationTiming.loadEventEnd - navigationTiming.navigationStart;
|
||
console.log('页面加载时间:', loadTime, 'ms');
|
||
} else {
|
||
// 备用方案:使用DOMContentLoaded时间
|
||
const domContentLoadedTime = performance.timing?.domContentLoadedEventEnd - performance.timing?.navigationStart;
|
||
if (domContentLoadedTime && domContentLoadedTime > 0) {
|
||
console.log('DOM加载时间:', domContentLoadedTime, 'ms');
|
||
} else {
|
||
console.log('页面加载时间: 无法计算');
|
||
}
|
||
}
|
||
};
|
||
|
||
// 如果页面已经加载完成,直接计算
|
||
if (document.readyState === 'complete') {
|
||
calculateLoadTime();
|
||
} else {
|
||
// 否则等待页面加载完成
|
||
window.addEventListener('load', calculateLoadTime);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 移动端优化:防止双击缩放
|
||
function preventDoubleTapZoom() {
|
||
if (isTouchDevice) {
|
||
let lastTouchEnd = 0;
|
||
document.addEventListener('touchend', function (event) {
|
||
const now = (new Date()).getTime();
|
||
if (now - lastTouchEnd <= 300) {
|
||
event.preventDefault();
|
||
}
|
||
lastTouchEnd = now;
|
||
}, false);
|
||
}
|
||
}
|
||
|
||
// 预加载关键资源
|
||
function preloadCriticalResources() {
|
||
// 预加载字体文件(CSS文件不应该预加载,因为已经在HTML中引用了)
|
||
const fontResources = [
|
||
'/assets/webfonts/fa-solid-900.woff2',
|
||
'/assets/webfonts/fa-brands-400.woff2',
|
||
'/assets/webfonts/fa-regular-400.woff2'
|
||
];
|
||
|
||
fontResources.forEach(resource => {
|
||
const link = document.createElement('link');
|
||
link.rel = 'preload';
|
||
link.href = resource;
|
||
link.as = 'font';
|
||
link.crossOrigin = 'anonymous';
|
||
document.head.appendChild(link);
|
||
});
|
||
|
||
// 预加载第一屏可能需要的图片
|
||
setTimeout(() => {
|
||
const firstScreenImages = [
|
||
'/assets/placeholder.svg'
|
||
];
|
||
|
||
firstScreenImages.forEach(img => {
|
||
const preload = document.createElement('link');
|
||
preload.rel = 'preload';
|
||
preload.href = img;
|
||
preload.as = 'image';
|
||
document.head.appendChild(preload);
|
||
});
|
||
}, 100);
|
||
}
|
||
|
||
// 优化滚动性能
|
||
function optimizeScrollPerformance() {
|
||
// 使用requestAnimationFrame优化滚动
|
||
let ticking = false;
|
||
|
||
function updateOnScroll() {
|
||
// 在滚动时暂停非关键任务
|
||
if (!ticking) {
|
||
requestAnimationFrame(() => {
|
||
// 延迟加载非关键图片
|
||
delayNonCriticalImages();
|
||
ticking = false;
|
||
});
|
||
ticking = true;
|
||
}
|
||
}
|
||
|
||
window.addEventListener('scroll', updateOnScroll, {passive: true});
|
||
}
|
||
|
||
// 延迟加载非关键图片
|
||
function delayNonCriticalImages() {
|
||
const nonCriticalImages = document.querySelectorAll('img[data-delayed]');
|
||
nonCriticalImages.forEach(img => {
|
||
if (isElementInViewport(img)) {
|
||
const src = img.getAttribute('data-delayed');
|
||
if (src) {
|
||
img.src = src;
|
||
img.removeAttribute('data-delayed');
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// 检查元素是否在视口中
|
||
function isElementInViewport(el) {
|
||
const rect = el.getBoundingClientRect();
|
||
return (
|
||
rect.top >= 0 &&
|
||
rect.left >= 0 &&
|
||
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
||
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
|
||
);
|
||
}
|
||
|
||
// 初始化性能监控和移动端优化
|
||
logPerformance();
|
||
preventDoubleTapZoom();
|
||
|
||
// 预加载关键资源
|
||
preloadCriticalResources();
|
||
|
||
// 优化滚动性能
|
||
optimizeScrollPerformance();
|
||
|
||
// 打开相册详情
|
||
function openAlbum(albumId) {
|
||
window.location.href = `/album?id=${albumId}`;
|
||
}
|
||
|
||
// 随机打开相册
|
||
function openRandomAlbum() {
|
||
// 添加随机动画效果 - 兼容侧边栏导航项
|
||
const navItems = document.querySelectorAll('.nav-item');
|
||
let randomButton = null;
|
||
navItems.forEach(item => {
|
||
if (item.textContent.includes('随机')) randomButton = item;
|
||
});
|
||
// 也试试顶部操作按钮
|
||
if (!randomButton) {
|
||
randomButton = document.querySelector('.action-btn[onclick*="openRandomAlbum"]');
|
||
}
|
||
|
||
if (randomButton) {
|
||
randomButton.style.transform = 'scale(0.95)';
|
||
}
|
||
|
||
// 调用新的随机相册API
|
||
fetch(`${API_BASE}/album/random`)
|
||
.then(response => {
|
||
if (!response.ok) {
|
||
if (response.status === 404) {
|
||
throw new Error('暂无相册数据,请稍后再试');
|
||
}
|
||
throw new Error('获取随机相册失败');
|
||
}
|
||
return response.json();
|
||
})
|
||
.then(randomAlbum => {
|
||
// 恢复按钮状态
|
||
if (randomButton) {
|
||
setTimeout(() => {
|
||
randomButton.style.transform = '';
|
||
}, 200);
|
||
}
|
||
|
||
// 跳转到随机相册
|
||
window.location.href = `/album?id=${randomAlbum.id}`;
|
||
})
|
||
.catch(error => {
|
||
// 恢复按钮状态
|
||
if (randomButton) {
|
||
setTimeout(() => {
|
||
randomButton.style.transform = '';
|
||
}, 200);
|
||
}
|
||
|
||
alert(error.message);
|
||
});
|
||
}
|
||
|
||
// 显示收藏夹
|
||
function showFavorites() {
|
||
// 设置当前在收藏页
|
||
isInFavoritesPage = true;
|
||
|
||
// 切换侧边栏选中状态
|
||
document.querySelectorAll('.nav-item').forEach(item => item.classList.remove('active'));
|
||
const favNav = document.querySelector('.nav-item[data-view="favorites"]');
|
||
if (favNav) favNav.classList.add('active');
|
||
|
||
// 从后端API获取收藏列表
|
||
fetch(`${API_BASE}/favorites`)
|
||
.then(response => {
|
||
if (!response.ok) {
|
||
throw new Error('获取收藏列表失败');
|
||
}
|
||
return response.json();
|
||
})
|
||
.then(favoriteImages => {
|
||
// 始终显示收藏夹视图(包括空状态)
|
||
displayFavorites(favoriteImages);
|
||
})
|
||
.catch(error => {
|
||
console.error('获取收藏列表失败:', error);
|
||
alert('获取收藏列表失败,请稍后重试');
|
||
});
|
||
}
|
||
|
||
// 显示收藏夹内容
|
||
function displayFavorites(favoriteImages) {
|
||
const container = document.getElementById('albums-container');
|
||
|
||
container.innerHTML = `
|
||
${favoriteImages.length === 0 ? `
|
||
<div class="empty-favorites">
|
||
<div class="empty-icon">
|
||
<i class="fas fa-heart-broken"></i>
|
||
</div>
|
||
<h3>收藏夹空空如也</h3>
|
||
<p>快去收藏一些喜欢的图片吧!</p>
|
||
<button class="explore-button" onclick="loadAlbums()">
|
||
<i class="fas fa-compass"></i> 探索相册
|
||
</button>
|
||
</div>
|
||
` : `${favoriteImages.map((fav, index) => `
|
||
<div class="favorite-card" onclick="openFavoriteImage('${fav.favorite.album_id}')" tabindex="0">
|
||
<div class="card-image">
|
||
<img src="/assets/placeholder.svg"
|
||
data-image-id="${fav.image.id}"
|
||
alt="${fav.image.title || '收藏的图片'}"
|
||
loading="lazy">
|
||
<div class="card-overlay">
|
||
<div class="overlay-content">
|
||
<button class="quick-view" onclick="event.stopPropagation(); previewFavorite(${index})" aria-label="预览图片">
|
||
<i class="fas fa-expand"></i>
|
||
</button>
|
||
<button class="remove-favorite" onclick="removeFavorite(event, '${fav.favorite.id}')" aria-label="移除收藏">
|
||
<i class="fas fa-trash"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="favorite-badge">
|
||
<i class="fas fa-heart"></i>
|
||
</div>
|
||
</div>
|
||
<div class="card-content">
|
||
<div class="image-meta">
|
||
<span class="meta-item">
|
||
<i class="fas fa-calendar"></i>
|
||
${formatDate(fav.favorite.created_at)}
|
||
</span>
|
||
<span class="meta-item">
|
||
<i class="fas fa-ruler-combined"></i>
|
||
${fav.image.width}×${fav.image.height}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`).join('')}
|
||
`}
|
||
`;
|
||
|
||
// 更新统计信息 - 显示收藏数量(兼容新旧版布局)
|
||
const statsEl = document.getElementById('stats');
|
||
if (statsEl) {
|
||
statsEl.innerHTML = `<div class="favorites-header">
|
||
<div class="header-content">
|
||
<div class="header-left">
|
||
<div class="favorites-icon">
|
||
<i class="fas fa-heart"></i>
|
||
</div>
|
||
<div class="header-info">
|
||
<h1>我的收藏夹</h1>
|
||
<p class="favorites-count">共收藏了 <span class="count-number">${favoriteImages.length}</span> 张图片</p>
|
||
</div>
|
||
</div>
|
||
<div class="header-right">
|
||
<div class="action-buttons">
|
||
<button class="back-button" onclick="backToHome()">
|
||
<i class="fas fa-arrow-left"></i> 返回首页
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>`;
|
||
}
|
||
|
||
// 初始化图片懒加载,确保收藏的图片被加载
|
||
initLazyLoading();
|
||
}
|
||
|
||
// 格式化日期
|
||
function formatDate(dateString) {
|
||
const date = new Date(dateString);
|
||
return date.toLocaleDateString('zh-CN', {
|
||
year: 'numeric',
|
||
month: 'short',
|
||
day: 'numeric'
|
||
});
|
||
}
|
||
|
||
// 返回首页
|
||
function backToHome() {
|
||
// 重置收藏页标记
|
||
isInFavoritesPage = false;
|
||
// 切换导航高亮到首页
|
||
document.querySelectorAll('.nav-item').forEach(item => {
|
||
item.classList.toggle('active', item.dataset.view === 'home');
|
||
});
|
||
// 清除筛选
|
||
clearFilter();
|
||
}
|
||
|
||
// 全局变量存储收藏列表和当前预览索引
|
||
let currentFavorites = [];
|
||
let currentPreviewIndex = -1;
|
||
|
||
// 预览收藏的图片
|
||
function previewFavorite(index) {
|
||
// 获取收藏列表
|
||
fetch(`${API_BASE}/favorites`)
|
||
.then(response => {
|
||
if (!response.ok) {
|
||
throw new Error('获取收藏列表失败');
|
||
}
|
||
return response.json();
|
||
})
|
||
.then(favorites => {
|
||
if (favorites && favorites.length > index) {
|
||
currentFavorites = favorites;
|
||
currentPreviewIndex = index;
|
||
showPreviewModal(index);
|
||
} else {
|
||
alert('图片不存在或已被删除');
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('预览图片失败:', error);
|
||
alert('预览图片失败,请稍后重试');
|
||
});
|
||
}
|
||
|
||
// 显示预览模态框
|
||
function showPreviewModal(index) {
|
||
const favorite = currentFavorites[index];
|
||
|
||
// 构建模态框HTML
|
||
const modalHtml = `
|
||
<div class="preview-modal" id="previewModal">
|
||
<div class="preview-overlay" onclick="closePreviewModal()"></div>
|
||
<div class="preview-container">
|
||
<div class="preview-header">
|
||
<div class="preview-title">
|
||
<span class="preview-info">来自: ${favorite.album_title}</span>
|
||
</div>
|
||
<div class="preview-actions">
|
||
<button class="preview-nav preview-prev" onclick="navigatePreview(-1)" ${index === 0 ? 'disabled' : ''}>
|
||
<i class="fas fa-chevron-left"></i>
|
||
</button>
|
||
<span class="preview-counter">${index + 1} / ${currentFavorites.length}</span>
|
||
<button class="preview-nav preview-next" onclick="navigatePreview(1)" ${index === currentFavorites.length - 1 ? 'disabled' : ''}>
|
||
<i class="fas fa-chevron-right"></i>
|
||
</button>
|
||
<button class="preview-close" onclick="closePreviewModal()">
|
||
<i class="fas fa-times"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="preview-content">
|
||
<div class="preview-image-container">
|
||
<img src="${API_BASE}/image/${favorite.image.id}"
|
||
alt="${favorite.image.title || '预览图片'}"
|
||
class="preview-image"
|
||
id="preview-image"
|
||
loading="eager">
|
||
</div>
|
||
</div>
|
||
<div class="preview-footer">
|
||
<div class="preview-meta">
|
||
<span class="meta-item">
|
||
<i class="fas fa-calendar"></i>
|
||
${formatDate(favorite.favorite.created_at)}
|
||
</span>
|
||
<span class="meta-item">
|
||
<i class="fas fa-ruler-combined"></i>
|
||
${favorite.image.width} × ${favorite.image.height}
|
||
</span>
|
||
<span class="meta-item">
|
||
<i class="fas fa-heart"></i>
|
||
已收藏
|
||
</span>
|
||
</div>
|
||
<div class="preview-controls">
|
||
<button class="preview-action" onclick="openFavoriteImage('${favorite.favorite.album_id}')">
|
||
<i class="fas fa-folder-open"></i> 打开相册
|
||
</button>
|
||
<button class="preview-action preview-remove" onclick="removeFavoriteFromPreview('${favorite.favorite.id}')">
|
||
<i class="fas fa-trash"></i> 取消收藏
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
// 添加到页面
|
||
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
||
|
||
// 显示模态框
|
||
setTimeout(() => {
|
||
const modal = document.getElementById('previewModal');
|
||
modal.classList.add('active');
|
||
}, 10);
|
||
|
||
// 监听键盘事件
|
||
document.addEventListener('keydown', handlePreviewKeydown);
|
||
}
|
||
|
||
// 关闭预览模态框
|
||
function closePreviewModal() {
|
||
const modal = document.getElementById('previewModal');
|
||
if (modal) {
|
||
modal.classList.remove('active');
|
||
setTimeout(() => {
|
||
modal.remove();
|
||
}, 300);
|
||
}
|
||
|
||
// 移除键盘事件监听
|
||
document.removeEventListener('keydown', handlePreviewKeydown);
|
||
}
|
||
|
||
// 导航预览图片
|
||
function navigatePreview(direction) {
|
||
const newIndex = currentPreviewIndex + direction;
|
||
if (newIndex >= 0 && newIndex < currentFavorites.length) {
|
||
updatePreviewContent(newIndex);
|
||
}
|
||
}
|
||
|
||
// 更新预览内容(不关闭面板)
|
||
function updatePreviewContent(newIndex) {
|
||
const favorite = currentFavorites[newIndex];
|
||
currentPreviewIndex = newIndex;
|
||
|
||
// 更新标题和相册信息
|
||
const titleElement = document.querySelector('.preview-title h3');
|
||
const infoElement = document.querySelector('.preview-info');
|
||
if (titleElement) titleElement.textContent = favorite.image.title || '未命名图片';
|
||
if (infoElement) infoElement.textContent = `来自: ${favorite.album_title}`;
|
||
|
||
// 更新计数器
|
||
const counterElement = document.querySelector('.preview-counter');
|
||
if (counterElement) counterElement.textContent = `${newIndex + 1} / ${currentFavorites.length}`;
|
||
|
||
// 更新导航按钮状态
|
||
const prevButton = document.querySelector('.preview-prev');
|
||
const nextButton = document.querySelector('.preview-next');
|
||
if (prevButton) prevButton.disabled = newIndex === 0;
|
||
if (nextButton) nextButton.disabled = newIndex === currentFavorites.length - 1;
|
||
|
||
// 更新元数据
|
||
const metaElements = document.querySelectorAll('.preview-meta .meta-item');
|
||
if (metaElements.length >= 3) {
|
||
metaElements[0].innerHTML = `<i class="fas fa-calendar"></i> ${formatDate(favorite.favorite.created_at)}`;
|
||
metaElements[1].innerHTML = `<i class="fas fa-ruler-combined"></i> ${favorite.image.width} × ${favorite.image.height}`;
|
||
}
|
||
|
||
// 更新操作按钮
|
||
const openAlbumButton = document.querySelector('.preview-action:first-child');
|
||
const removeButton = document.querySelector('.preview-remove');
|
||
if (openAlbumButton) {
|
||
openAlbumButton.onclick = () => openFavoriteImage(favorite.favorite.album_id);
|
||
}
|
||
if (removeButton) {
|
||
removeButton.onclick = () => removeFavoriteFromPreview(favorite.favorite.id);
|
||
}
|
||
|
||
// 更新图片
|
||
const imageElement = document.getElementById('preview-image');
|
||
|
||
if (imageElement) {
|
||
// 显示加载状态
|
||
imageElement.style.opacity = '0';
|
||
|
||
|
||
// 加载新图片
|
||
const newImage = new Image();
|
||
newImage.src = `${API_BASE}/image/${favorite.image.id}`;
|
||
newImage.alt = favorite.image.title || '预览图片';
|
||
newImage.className = 'preview-image';
|
||
|
||
newImage.onload = function () {
|
||
// 替换图片
|
||
if (imageElement.parentNode) {
|
||
imageElement.parentNode.replaceChild(newImage, imageElement);
|
||
// 重新设置ID
|
||
newImage.id = 'preview-image';
|
||
}
|
||
|
||
newImage.style.opacity = '1';
|
||
};
|
||
|
||
newImage.onerror = function () {
|
||
// 图片加载失败
|
||
imageElement.innerHTML = '<i class="fas fa-exclamation-triangle"></i><span>图片加载失败</span>';
|
||
};
|
||
}
|
||
}
|
||
|
||
// 处理键盘事件
|
||
function handlePreviewKeydown(event) {
|
||
if (event.key === 'Escape') {
|
||
closePreviewModal();
|
||
} else if (event.key === 'ArrowLeft') {
|
||
navigatePreview(-1);
|
||
} else if (event.key === 'ArrowRight') {
|
||
navigatePreview(1);
|
||
}
|
||
}
|
||
|
||
// 从预览中移除收藏
|
||
function removeFavoriteFromPreview(favoriteId) {
|
||
if (confirm('确定要从收藏夹移除这张图片吗?')) {
|
||
fetch(`${API_BASE}/favorites/${favoriteId}`, {
|
||
method: 'DELETE'
|
||
})
|
||
.then(response => {
|
||
if (!response.ok) {
|
||
throw new Error('取消收藏失败');
|
||
}
|
||
return response.json();
|
||
})
|
||
.then(() => {
|
||
closePreviewModal();
|
||
// 重新显示收藏夹
|
||
showFavorites();
|
||
})
|
||
.catch(error => {
|
||
console.error('取消收藏失败:', error);
|
||
alert('取消收藏失败,请稍后重试');
|
||
});
|
||
}
|
||
}
|
||
|
||
// 从收藏夹移除图片
|
||
function removeFavorite(event, favoriteId) {
|
||
event.stopPropagation(); // 阻止事件冒泡
|
||
|
||
if (confirm('确定要从收藏夹移除这张图片吗?')) {
|
||
fetch(`${API_BASE}/favorites/${favoriteId}`, {
|
||
method: 'DELETE'
|
||
})
|
||
.then(response => {
|
||
if (!response.ok) {
|
||
throw new Error('取消收藏失败');
|
||
}
|
||
return response.json();
|
||
})
|
||
.then(() => {
|
||
// 重新显示收藏夹
|
||
showFavorites();
|
||
})
|
||
.catch(error => {
|
||
console.error('取消收藏失败:', error);
|
||
alert('取消收藏失败,请稍后重试');
|
||
});
|
||
}
|
||
}
|
||
|
||
// 打开收藏的图片
|
||
function openFavoriteImage(favoriteId) {
|
||
// 这里可以跳转到对应的相册页面
|
||
// 在实际实现中,我们需要根据favoriteId获取相册信息
|
||
window.location.href = `/album?id=${favoriteId}`;
|
||
}
|
||
|
||
// ============================================================
|
||
// 标签和人物发现功能
|
||
// ============================================================
|
||
|
||
// 加载热门标签和人物数据
|
||
async function loadDiscoverData() {
|
||
try {
|
||
// 并行加载
|
||
const [tagsResp, peopleResp] = await Promise.all([
|
||
fetch(`${API_BASE}/tags/popular?limit=40`),
|
||
fetch(`${API_BASE}/recommend/popular-persons?limit=30`)
|
||
]);
|
||
|
||
if (tagsResp.ok) {
|
||
const tagsData = await tagsResp.json();
|
||
// 兼容两种格式:直接数组 或 { total, tags }
|
||
const tags = Array.isArray(tagsData) ? tagsData : (tagsData.tags || tagsData.items || []);
|
||
if (tags && tags.length > 0) {
|
||
renderTagCloud(tags);
|
||
}
|
||
}
|
||
|
||
if (peopleResp.ok) {
|
||
const people = await peopleResp.json();
|
||
if (people && people.length > 0) {
|
||
renderPeopleCloud(people);
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.warn('加载发现数据失败:', error);
|
||
}
|
||
}
|
||
|
||
// 渲染标签(侧边栏列表)
|
||
function renderTagCloud(tags) {
|
||
// 新版:侧边栏标签列表
|
||
const sidebarTags = document.getElementById('sidebar-tags');
|
||
if (sidebarTags && tags && tags.length) {
|
||
sidebarTags.innerHTML = tags.slice(0, 25).map(tag => {
|
||
const count = tag.album_cnt || tag.count || 1;
|
||
const name = tag.tag || tag.name || '';
|
||
const isActive = currentFilter && currentFilter.type === 'tag' && currentFilter.value === name;
|
||
return `<div class="sidebar-tag ${isActive ? 'active' : ''}" onclick="filterByTag('${name}')">
|
||
<span class="sidebar-tag-name">${name}</span>
|
||
<span class="sidebar-tag-count">${count}</span>
|
||
</div>`;
|
||
}).join('');
|
||
}
|
||
|
||
// 兼容旧版:标签云区域
|
||
const section = document.getElementById('tags-section');
|
||
const cloud = document.getElementById('tag-cloud');
|
||
if (section && cloud && tags && tags.length) {
|
||
section.style.display = 'block';
|
||
const maxCount = Math.max(...tags.map(t => t.album_cnt || t.count || 1));
|
||
const minCount = Math.min(...tags.map(t => t.album_cnt || t.count || 1));
|
||
cloud.innerHTML = tags.map(tag => {
|
||
const count = tag.album_cnt || tag.count || 1;
|
||
const name = tag.tag || tag.name || '';
|
||
const ratio = maxCount === minCount ? 0.5 : (count - minCount) / (maxCount - minCount);
|
||
const fontSize = 12 + ratio * 10;
|
||
const hue = 210 - ratio * 50;
|
||
const opacity = 0.6 + ratio * 0.4;
|
||
return `<span class="tag-chip"
|
||
style="font-size:${fontSize}px; color:hsl(${hue}, 70%, 45%); opacity:${opacity};"
|
||
onclick="filterByTag('${name}')"
|
||
title="${count} 个相册">
|
||
${name} <small>(${count})</small>
|
||
</span>`;
|
||
}).join('');
|
||
}
|
||
}
|
||
|
||
// 渲染热门人物(横向滚动条)
|
||
function renderPeopleCloud(people) {
|
||
// 侧边栏人物列表
|
||
const sidebarPeople = document.getElementById('sidebar-people');
|
||
if (sidebarPeople && people && people.length) {
|
||
sidebarPeople.innerHTML = people.slice(0, 25).map(p => {
|
||
const name = p.name || p.display_name || '';
|
||
const albumCount = p.total_albums || 0;
|
||
const isActive = currentFilter && currentFilter.type === 'person' && currentFilter.value === name;
|
||
return `<div class="sidebar-person ${isActive ? 'active' : ''}" onclick="filterByPerson('${name}')" title="${albumCount} 个相册">
|
||
<div class="sidebar-person-avatar">${name.charAt(0)}</div>
|
||
<div class="sidebar-person-info">
|
||
<div class="sidebar-person-name">${name}</div>
|
||
<div class="sidebar-person-count">${albumCount} 相册</div>
|
||
</div>
|
||
</div>`;
|
||
}).join('');
|
||
}
|
||
|
||
// 横向滚动条
|
||
const strip = document.getElementById('people-strip');
|
||
const scroll = document.getElementById('people-scroll');
|
||
if (strip && scroll && people && people.length) {
|
||
strip.style.display = 'flex';
|
||
scroll.innerHTML = people.slice(0, 20).map(p => {
|
||
const name = p.name || p.display_name || '';
|
||
const albumCount = p.total_albums || 0;
|
||
const isActive = currentFilter && currentFilter.type === 'person' && currentFilter.value === name;
|
||
return `<div class="person-chip-scroll ${isActive ? 'active' : ''}" onclick="filterByPerson('${name}')" title="${albumCount} 个相册">
|
||
<div class="person-chip-avatar">${name.charAt(0)}</div>
|
||
<div class="person-chip-info">
|
||
<div class="person-chip-name">${name}</div>
|
||
<div class="person-chip-count">${albumCount} 相册</div>
|
||
</div>
|
||
</div>`;
|
||
}).join('');
|
||
}
|
||
|
||
// 兼容旧版:人物卡片区域
|
||
const section = document.getElementById('people-section');
|
||
const cloud = document.getElementById('people-cloud');
|
||
if (section && cloud && people && people.length) {
|
||
section.style.display = 'block';
|
||
cloud.innerHTML = people.map(p => {
|
||
const name = p.name || p.display_name || '';
|
||
const albumCount = p.total_albums || 0;
|
||
const imageCount = p.total_images || 0;
|
||
return `<div class="person-chip" onclick="filterByPerson('${name}')" title="${albumCount} 个相册, ${imageCount} 张图片">
|
||
<div class="person-avatar">${name.charAt(0)}</div>
|
||
<div class="person-info">
|
||
<div class="person-name">${name}</div>
|
||
<div class="person-meta">${albumCount} 相册 · ${imageCount} 图</div>
|
||
</div>
|
||
</div>`;
|
||
}).join('');
|
||
}
|
||
}
|
||
|
||
// 侧边栏区块折叠/展开
|
||
function toggleSidebarSection(section) {
|
||
const group = document.querySelector(`.sidebar-section-group [data-section="${section}"]`)?.closest('.sidebar-section-group');
|
||
if (!group) return;
|
||
group.classList.toggle('collapsed');
|
||
// 保存状态到 localStorage
|
||
try {
|
||
const state = JSON.parse(localStorage.getItem('sidebarSections') || '{}');
|
||
state[section] = group.classList.contains('collapsed');
|
||
localStorage.setItem('sidebarSections', JSON.stringify(state));
|
||
} catch(e) {}
|
||
}
|
||
|
||
// 初始化侧边栏区块折叠状态
|
||
function initSidebarSections() {
|
||
try {
|
||
const state = JSON.parse(localStorage.getItem('sidebarSections') || '{}');
|
||
Object.keys(state).forEach(section => {
|
||
if (state[section]) {
|
||
const group = document.querySelector(`.sidebar-section-group [data-section="${section}"]`)?.closest('.sidebar-section-group');
|
||
if (group) group.classList.add('collapsed');
|
||
}
|
||
});
|
||
} catch(e) {}
|
||
}
|
||
|
||
// 按标签筛选
|
||
async function filterByTag(tag) {
|
||
currentFilter = { type: 'tag', value: tag };
|
||
showFilterStatus(`标签: ${tag}`);
|
||
renderTagCloud(await fetchTags().catch(() => []));
|
||
|
||
try {
|
||
showSkeleton(6);
|
||
const response = await fetch(`${API_BASE}/tags/search?tag=${encodeURIComponent(tag)}&limit=200`);
|
||
if (!response.ok) throw new Error('搜索失败');
|
||
const results = await response.json();
|
||
|
||
// 保存过滤结果到 filteredAlbums,重置分页
|
||
filteredAlbums = results;
|
||
currentDisplayPage = 1;
|
||
hasMoreAlbums = filteredAlbums.length > albumsPerPage;
|
||
|
||
displayAlbums(results);
|
||
updateStats(results);
|
||
initLazyLoading();
|
||
} catch (error) {
|
||
console.error('标签筛选失败:', error);
|
||
showError('标签筛选失败');
|
||
}
|
||
}
|
||
|
||
// 获取标签(辅助函数)
|
||
async function fetchTags() {
|
||
const resp = await fetch(`${API_BASE}/tags/popular?limit=25`);
|
||
if (!resp.ok) return [];
|
||
return resp.json();
|
||
}
|
||
|
||
// 按人物筛选
|
||
async function filterByPerson(personName) {
|
||
currentFilter = { type: 'person', value: personName };
|
||
showFilterStatus(`人物: ${personName}`);
|
||
|
||
// 刷新侧边栏人物高亮
|
||
try {
|
||
const peopleResp = await fetch(`${API_BASE}/recommend/popular-persons?limit=30`);
|
||
if (peopleResp.ok) {
|
||
const peopleData = await peopleResp.json();
|
||
renderPeopleCloud(peopleData);
|
||
}
|
||
} catch(e) {}
|
||
|
||
try {
|
||
showSkeleton(6);
|
||
const response = await fetch(`${API_BASE}/people/search?q=${encodeURIComponent(personName)}`);
|
||
if (!response.ok) throw new Error('搜索失败');
|
||
const personData = await response.json();
|
||
const persons = personData.persons || personData;
|
||
|
||
if (persons && persons.length > 0) {
|
||
try {
|
||
const detailResp = await fetch(`${API_BASE}/people/${encodeURIComponent(personName)}`);
|
||
if (detailResp.ok) {
|
||
const detail = await detailResp.json();
|
||
if (detail && detail.albums) {
|
||
// 保存过滤结果到 filteredAlbums,重置分页
|
||
filteredAlbums = detail.albums;
|
||
currentDisplayPage = 1;
|
||
hasMoreAlbums = filteredAlbums.length > albumsPerPage;
|
||
|
||
displayAlbums(detail.albums);
|
||
updateStats(detail.albums);
|
||
initLazyLoading();
|
||
return;
|
||
}
|
||
}
|
||
} catch (e) { /* 回退 */ }
|
||
}
|
||
// 回退到全站搜索
|
||
filteredAlbums = await performServerSearchWithResult(personName);
|
||
} catch (error) {
|
||
console.error('人物筛选失败:', error);
|
||
filteredAlbums = await performServerSearchWithResult(personName);
|
||
}
|
||
|
||
// 无论回退搜索是否找到结果,都要渲染页面(移除骨架屏)
|
||
if (filteredAlbums !== null) {
|
||
currentDisplayPage = 1;
|
||
hasMoreAlbums = filteredAlbums.length > albumsPerPage;
|
||
displayAlbums(filteredAlbums);
|
||
updateStats(filteredAlbums);
|
||
initLazyLoading();
|
||
}
|
||
}
|
||
|
||
// 显示筛选状态
|
||
function showFilterStatus(text) {
|
||
// 新版筛选条
|
||
const bar = document.getElementById('filter-bar');
|
||
const pill = document.getElementById('current-filter-pill');
|
||
if (bar && pill) {
|
||
pill.textContent = text;
|
||
bar.style.display = 'flex';
|
||
}
|
||
|
||
// 兼容旧版
|
||
const status = document.getElementById('filter-status');
|
||
const label = document.getElementById('current-filter');
|
||
if (status && label) {
|
||
label.textContent = text;
|
||
status.style.display = 'flex';
|
||
}
|
||
}
|
||
|
||
// 清除筛选
|
||
function clearFilter() {
|
||
currentFilter = null;
|
||
filteredAlbums = null; // 清除过滤结果缓存
|
||
currentDisplayPage = 1;
|
||
|
||
// 新版筛选条
|
||
const bar = document.getElementById('filter-bar');
|
||
if (bar) bar.style.display = 'none';
|
||
|
||
// 兼容旧版
|
||
const status = document.getElementById('filter-status');
|
||
if (status) status.style.display = 'none';
|
||
|
||
// 清空搜索框
|
||
const searchInput = document.getElementById('search-input');
|
||
if (searchInput) {
|
||
searchInput.value = '';
|
||
updateSearchClear();
|
||
}
|
||
|
||
// 重新渲染标签和人物(清除高亮)
|
||
loadDiscoverData();
|
||
|
||
// 重新加载所有相册
|
||
loadAlbums(true);
|
||
}
|
||
|
||
// ============ 侧边栏交互 ============
|
||
|
||
// 初始化侧边栏状态
|
||
function initSidebar() {
|
||
const sidebar = document.getElementById('sidebar');
|
||
if (sidebar && sidebarCollapsed) {
|
||
sidebar.classList.add('collapsed');
|
||
}
|
||
}
|
||
|
||
// 切换侧边栏折叠
|
||
function toggleSidebar() {
|
||
const sidebar = document.getElementById('sidebar');
|
||
if (!sidebar) return;
|
||
sidebarCollapsed = !sidebarCollapsed;
|
||
sidebar.classList.toggle('collapsed', sidebarCollapsed);
|
||
localStorage.setItem('sidebarCollapsed', sidebarCollapsed);
|
||
}
|
||
|
||
// 移动端切换侧边栏
|
||
function toggleMobileSidebar() {
|
||
const sidebar = document.getElementById('sidebar');
|
||
const overlay = document.getElementById('mobile-overlay');
|
||
if (!sidebar || !overlay) return;
|
||
sidebar.classList.toggle('mobile-open');
|
||
overlay.classList.toggle('active');
|
||
}
|
||
|
||
// 切换视图(首页/收藏夹)
|
||
function switchView(view) {
|
||
// 更新导航高亮
|
||
document.querySelectorAll('.nav-item').forEach(item => {
|
||
item.classList.toggle('active', item.dataset.view === view);
|
||
});
|
||
|
||
// 移动端关闭侧边栏
|
||
const sidebar = document.getElementById('sidebar');
|
||
const overlay = document.getElementById('mobile-overlay');
|
||
if (sidebar && sidebar.classList.contains('mobile-open')) {
|
||
sidebar.classList.remove('mobile-open');
|
||
overlay.classList.remove('active');
|
||
}
|
||
|
||
if (view === 'home') {
|
||
backToHome();
|
||
} else if (view === 'favorites') {
|
||
showFavorites();
|
||
}
|
||
}
|
||
|
||
// 搜索框清除按钮
|
||
function updateSearchClear() {
|
||
const input = document.getElementById('search-input');
|
||
const clear = document.getElementById('search-clear');
|
||
if (input && clear) {
|
||
clear.style.display = input.value.length > 0 ? 'flex' : 'none';
|
||
}
|
||
}
|
||
|
||
// 清除搜索
|
||
function clearSearch() {
|
||
const input = document.getElementById('search-input');
|
||
if (input) {
|
||
input.value = '';
|
||
updateSearchClear();
|
||
if (!currentFilter) {
|
||
loadAlbums(true);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 折叠/展开区域(兼容旧版)
|
||
function toggleSection(name) {
|
||
collapsedSections[name] = !collapsedSections[name];
|
||
const cloudId = name === 'people' ? 'people-cloud' : 'tag-cloud';
|
||
const cloud = document.getElementById(cloudId);
|
||
const btn = document.querySelector(`#${name}-section .section-toggle i`);
|
||
if (cloud) {
|
||
if (collapsedSections[name]) {
|
||
cloud.style.display = 'none';
|
||
btn.className = 'fas fa-chevron-down';
|
||
} else {
|
||
cloud.style.display = '';
|
||
btn.className = 'fas fa-chevron-up';
|
||
}
|
||
}
|
||
}
|
||
|
||
// ============ 初始化 ============
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
initSidebar();
|
||
|
||
// 搜索框输入事件
|
||
const searchInput = document.getElementById('search-input');
|
||
if (searchInput) {
|
||
searchInput.addEventListener('input', updateSearchClear);
|
||
}
|
||
|
||
// 键盘快捷键:ESC 关闭筛选/搜索
|
||
document.addEventListener('keydown', function(e) {
|
||
if (e.key === 'Escape') {
|
||
if (currentFilter) clearFilter();
|
||
}
|
||
});
|
||
});
|
||
|
||
// 导出函数供全局使用
|
||
window.searchAlbums = searchAlbums;
|
||
window.openRandomAlbum = openRandomAlbum;
|
||
window.showFavorites = showFavorites;
|
||
window.openAlbum = openAlbum;
|
||
window.backToHome = backToHome;
|
||
window.loadAlbums = loadAlbums;
|
||
window.removeFavorite = removeFavorite;
|
||
window.previewFavorite = previewFavorite;
|
||
window.openFavoriteImage = openFavoriteImage;
|
||
window.removeFavoriteFromPreview = removeFavoriteFromPreview;
|
||
window.filterByTag = filterByTag;
|
||
window.filterByPerson = filterByPerson;
|
||
window.clearFilter = clearFilter;
|
||
window.toggleSection = toggleSection;
|
||
window.toggleSidebar = toggleSidebar;
|
||
window.toggleMobileSidebar = toggleMobileSidebar;
|
||
window.switchView = switchView;
|
||
window.clearSearch = clearSearch; |