Files
rust-imager/assets/js/album.js
2026-07-21 02:45:14 +08:00

958 lines
32 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 全局变量
let currentAlbum = null;
let currentImageIndex = 0;
let images = [];
// 设备检测
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
// API基础URL
const API_BASE = '/api';
// 图片加载器类(与app.js中的保持一致)
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 contentEncoding = response.headers.get('content-encoding');
const originalContentType = response.headers.get('x-original-content-type');
let imageData;
if (contentEncoding === 'br') {
// Brotli压缩数据,需要解压
const compressedData = await response.arrayBuffer();
imageData = await this.decompressBrotli(compressedData);
} else {
// 未压缩数据
imageData = await response.blob();
}
// 创建Blob URL
const mimeType = originalContentType || 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;
}
// Brotli解压
async decompressBrotli(compressedData) {
// 检查浏览器是否支持Brotli解压
if (window.TextDecoder && window.TextEncoder) {
try {
// 使用浏览器的Brotli解压(如果支持)
if (window.DecompressionStream && 'br' in window) {
const decompressionStream = new DecompressionStream('br');
const decompressedStream = new Response(compressedData).body.pipeThrough(decompressionStream);
return new Response(decompressedStream).arrayBuffer();
}
} catch (error) {
console.warn('浏览器Brotli解压失败,使用JavaScript解压:', error);
}
}
// 使用JavaScript实现的Brotli解压(备用方案)
return await this.decompressBrotliJS(compressedData);
}
// JavaScript实现的Brotli解压(备用方案)
async decompressBrotliJS(compressedData) {
// 这里可以集成一个轻量级的Brotli解压库
// 由于Brotli解压比较复杂,这里先返回原始数据
// 在实际项目中,可以使用第三方库如brotli-wasm
console.warn('Brotli解压未实现,返回原始数据');
return compressedData;
}
// 清理缓存
clearCache() {
// 清理所有Blob URL
for (const [imageId, cached] of this.cache.entries()) {
URL.revokeObjectURL(cached.blobUrl);
}
this.cache.clear();
}
}
// 创建全局图片加载器实例
const imageLoader = new ImageLoader();
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
// 添加设备类到body
if (isMobile) {
document.body.classList.add('mobile-device');
}
if (isTouchDevice) {
document.body.classList.add('touch-device');
}
const urlParams = new URLSearchParams(window.location.search);
const albumId = urlParams.get('id');
if (albumId) {
loadAlbum(albumId);
} else {
showError('缺少相册ID参数');
}
// 初始化键盘导航和触摸手势
initLightboxControls();
// 初始化图片懒加载
initAlbumLazyLoading();
});
// 初始化相册图片懒加载
function initAlbumLazyLoading() {
// 使用Intersection Observer实现懒加载
if ('IntersectionObserver' in window) {
const observer = 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);
}
observer.unobserve(img);
}
});
}, {
rootMargin: '200px 0px', // 提前200px开始加载
threshold: 0.1
});
// 观察所有带有data-image-id的图片
document.querySelectorAll('img[data-image-id]').forEach(img => {
if (!img.classList.contains('loaded')) {
observer.observe(img);
}
});
} else {
// 不支持Intersection Observer的回退方案
console.warn('浏览器不支持Intersection Observer,使用回退懒加载方案');
initAlbumFallbackLazyLoading();
}
}
// 相册回退懒加载方案
function initAlbumFallbackLazyLoading() {
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 + 300 && rect.bottom > -300;
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 showImageSkeleton(count = 8) {
const container = document.getElementById('images-container');
container.innerHTML = Array(count).fill(0).map(() => `
<div class="image-item">
<div class="image-loading">
<i class="fas fa-spinner"></i>
</div>
<div class="image-caption">
<small>加载中...</small>
</div>
</div>
`).join('');
}
// 加载相册详情
async function loadAlbum(albumId) {
try {
// 显示骨架屏
showImageSkeleton();
const response = await fetch(`${API_BASE}/album/${albumId}`);
if (!response.ok) {
if (response.status === 404) {
throw new Error('相册不存在');
}
throw new Error(`HTTP error! status: ${response.status}`);
}
const albumData = await response.json();
currentAlbum = albumData.album;
// tags/persons 在 albumData 顶层(AlbumWithImages),存到 currentAlbum 方便渲染
currentAlbum.tags = albumData.tags || [];
currentAlbum.persons = albumData.persons || [];
images = albumData.images;
updateAlbumInfo();
displayImages();
// 初始化图片懒加载
initLazyLoading();
} catch (error) {
console.error('加载相册失败:', error);
showError('加载相册失败: ' + error.message);
}
}
// 更新相册信息
function updateAlbumInfo() {
document.getElementById('album-title').textContent = currentAlbum.title;
let infoHtml = `${images.length} 张图片`;
document.getElementById('album-info').innerHTML = infoHtml;
// 显示相册元数据(人名 + 标签)
updateAlbumMeta();
document.title = `${currentAlbum.title} - 图片展示服务`;
}
// 标签分类映射(与后端 tag_extractor.rs 保持一致)
const TAG_CATEGORIES = {
style: ['日系', '韩系', '森系', '复古', '甜美', '性感', '清新', '冷艳',
'优雅', '知性', '可爱', '俏皮', '温柔', '妩媚', '野性', '清纯',
'时尚', '潮流', '街头', '学院', '文艺', '古典', '民族', '异域',
'japanese', 'korean', 'retro', 'vintage', 'cute', 'sexy',
'elegant', 'fresh', 'sweet', 'wild', 'pure', 'fashion'],
outfit: ['比基尼', '泳装', '泳衣', '旗袍', '制服', '丝袜', '内衣', '文胸',
'包臀裙', '紧身裙', '超短裙', '连衣裙', '半身裙', '短裙', '长裙',
'牛仔裤', '热裤', '短裤', '休闲裤', '西装裤',
'T恤', '衬衫', '毛衣', '针织衫', '外套', '风衣', '大衣', '西装',
'吊带', '抹胸', '露背', '透视', '蕾丝', '镂空', '开叉',
'高跟鞋', '运动鞋', '凉鞋', '拖鞋', '靴子',
'bikini', 'swimsuit', 'stockings', 'lingerie', 'dress',
'skirt', 'jeans', 'shirt', 'sweater', 'coat', 'heels'],
scene: ['室内', '户外', '街拍', '浴室', '浴缸', '床上', '床照', '沙发',
'厨房', '阳台', '窗边', '楼梯', '走廊', '电梯', '车内', '驾驶座',
'海边', '沙滩', '泳池', '温泉', '森林', '草地', '花园', '公园',
'街道', '巷弄', '建筑', '天台', '屋顶', '咖啡馆', '餐厅', '酒店',
'indoor', 'outdoor', 'street', 'beach', 'pool', 'bathroom',
'bedroom', 'kitchen', 'balcony', 'car', 'cafe', 'hotel'],
body: ['美腿', '美胸', '翘臀', '玉足', '美背', '香肩', '蛮腰', '细腰',
'长腿', '酥胸', '丰臀', '白皙', '肌肤', '纹身',
'legs', 'breasts', 'ass', 'feet', 'back', 'shoulders', 'waist',
'skin', 'tattoo'],
mood: ['清纯', '诱惑', '唯美', '清新', '冷艳', '妩媚', '娇羞', '俏皮',
'温柔', '野性', '冷傲', '热情', '慵懒', '迷离', '甜美', '知性',
'pure', 'seductive', 'beautiful', 'fresh', 'cool', 'charming',
'shy', 'cute', 'gentle', 'wild', 'lazy', 'sweet'],
topic: ['写真', '私房', '街拍', '旅拍', '婚纱照', '艺术照', '生活照',
'自拍', '跟拍', '约拍', '商拍',
'photoshoot', 'portrait', 'selfie', 'travel', 'wedding'],
};
// 获取标签的分类
function getTagCategory(tag) {
const tagLower = tag.toLowerCase();
for (const [category, tags] of Object.entries(TAG_CATEGORIES)) {
if (tags.some(t => t.toLowerCase() === tagLower)) {
return category;
}
}
return 'default';
}
// 更新相册元数据(人名 + 标签)简洁版:只用颜色区分,不写分类名
function updateAlbumMeta() {
const metaContainer = document.getElementById('album-meta-tags');
if (!metaContainer) return;
let chips = [];
// 添加人名标签(从 search.db 返回的 persons 数组)
if (currentAlbum.persons && Array.isArray(currentAlbum.persons)) {
currentAlbum.persons.forEach(name => {
chips.push({
text: name,
type: 'person',
category: 'person',
query: name
});
});
}
// 添加标签(从 search.db 返回的 tags 数组)
if (currentAlbum.tags && Array.isArray(currentAlbum.tags)) {
currentAlbum.tags.forEach(tag => {
chips.push({
text: tag,
type: 'tag',
category: getTagCategory(tag),
query: tag
});
});
}
if (chips.length === 0) {
metaContainer.style.display = 'none';
return;
}
metaContainer.style.display = 'flex';
metaContainer.innerHTML = chips.map(chip => {
const icon = chip.type === 'person' ? 'fa-user' : 'fa-tag';
const cssClass = chip.type === 'person' ? 'meta-chip-person' : `meta-chip-tag-${chip.category || 'default'}`;
return `<span class="meta-chip ${cssClass}" data-meta-type="${chip.type}" data-meta-query="${encodeURIComponent(chip.query)}" title="点击搜索: ${chip.text}">
<i class="fas ${icon}"></i> ${chip.text}
</span>`;
}).join('');
}
// 事件委托:点击元数据标签
document.addEventListener('click', function(e) {
const chip = e.target.closest('.meta-chip');
if (!chip) return;
const query = chip.dataset.metaQuery;
if (query) {
searchByMeta(query);
}
});
// 点击元数据标签搜索
function searchByMeta(encodedQuery) {
window.location.href = `/?search=${encodedQuery}`;
}
// 智能判断图片方向 - 更精确的判断逻辑
function getImageOrientation(width, height) {
if (!width || !height) return 'square';
const ratio = width / height;
// 更精确的阈值判断
if (ratio > 1.5) return 'landscape'; // 宽幅图片
if (ratio < 0.67) return 'portrait'; // 竖幅图片
if (ratio > 1.2) return 'wide'; // 较宽图片
if (ratio < 0.83) return 'tall'; // 较高图片
return 'square'; // 方形或接近方形的图片
}
// 显示图片列表
function displayImages() {
const container = document.getElementById('images-container');
if (images.length === 0) {
container.innerHTML = '<div class="no-results"><i class="fas fa-images"></i><br>此相册暂无图片</div>';
return;
}
container.innerHTML = images.map((image, index) => {
// 智能判断图片方向
const orientation = getImageOrientation(image.width, image.height);
// 使用懒加载技术
let imageUrl = '/assets/placeholder.svg';
let dataSrc = '';
if (image.id) {
dataSrc = `${API_BASE}/image/${image.id}`;
imageUrl = '/assets/placeholder.svg'; // 初始使用占位图
} else if (image.original_url) {
dataSrc = image.original_url;
imageUrl = '/assets/placeholder.svg';
}
const title = image.title || `图片 ${index + 1}`;
// 检查图片是否已被收藏
isImageFavorited(image.id || image.original_url);
return `
<div class="image-item ${orientation}">
<img
src="${imageUrl}"
${image.id ? `data-image-id="${image.id}"` : ''}
alt="${title}"
class="lazy"
loading="lazy"
onerror="this.src='/assets/placeholder.svg'"
onclick="openLightbox(${index})"
>
<div class="image-caption">
${image.width ? `<small>${image.width}×${image.height}</small>` : ''}
${image.file_size ? `<small>${(image.file_size / 1024).toFixed(2)} KB</small>` : ''}
</div>
</div>
`;
}).join('');
}
// 打开图片查看器 - 使用previewModal相同逻辑
function openLightbox(index) {
currentImageIndex = index;
const lightbox = document.getElementById('lightbox');
// 更新lightbox内容
updateLightboxContent(index);
// 显示lightbox
lightbox.classList.add('active');
document.body.style.overflow = 'hidden';
}
// 关闭图片查看器
function closeLightbox() {
const lightbox = document.getElementById('lightbox');
lightbox.classList.remove('active');
document.body.style.overflow = 'auto';
}
// 下一张图片
function nextImage() {
const newIndex = (currentImageIndex + 1) % images.length;
updateLightboxContent(newIndex);
}
// 上一张图片
function prevImage() {
const newIndex = (currentImageIndex - 1 + images.length) % images.length;
updateLightboxContent(newIndex);
}
// 更新lightbox内容(不关闭面板)
function updateLightboxContent(newIndex) {
currentImageIndex = newIndex;
const image = images[newIndex];
// 更新标题和相册信息
const titleElement = document.getElementById('lightbox-title');
const albumElement = document.getElementById('lightbox-album');
if (titleElement) titleElement.textContent = image.title || `图片 ${newIndex + 1}`;
if (albumElement) albumElement.textContent = `来自: ${currentAlbum.title}`;
// 更新计数器
const counterElement = document.getElementById('lightbox-counter');
if (counterElement) counterElement.textContent = `${newIndex + 1} / ${images.length}`;
// 更新导航按钮状态
const prevButton = document.getElementById('lightbox-prev');
const nextButton = document.getElementById('lightbox-next');
if (prevButton) prevButton.disabled = newIndex === 0;
if (nextButton) nextButton.disabled = newIndex === images.length - 1;
// 检查收藏状态
checkLightboxFavoriteStatus();
// 更新元数据
const dimensionsElement = document.getElementById('lightbox-dimensions');
const sizeElement = document.getElementById('lightbox-size');
if (dimensionsElement) {
dimensionsElement.innerHTML = `<i class="fas fa-ruler-combined"></i> ${image.width || 0} × ${image.height || 0}`;
}
if (sizeElement) {
const fileSizeKB = image.file_size ? (image.file_size / 1024).toFixed(2) : '0';
sizeElement.innerHTML = `<i class="fas fa-weight-hanging"></i> ${fileSizeKB} KB`;
}
// 更新图片
const imageElement = document.getElementById('lightbox-img');
const loadingElement = document.querySelector('#lightbox .preview-loading');
if (imageElement && loadingElement) {
// 显示加载状态
imageElement.style.opacity = '0';
loadingElement.style.display = 'flex';
// 加载新图片
const newImage = new Image();
// 在lightbox中优先使用原始图片以获得更好的显示效果
let imageUrl = '/assets/placeholder.svg';
if (image.id) {
imageUrl = `${API_BASE}/image/${image.id}`;
} else if (image.original_url) {
imageUrl = image.original_url;
}
newImage.src = imageUrl;
newImage.alt = image.title || `图片 ${newIndex + 1}`;
newImage.className = 'preview-image';
newImage.onload = function() {
// 替换图片
if (imageElement.parentNode) {
imageElement.parentNode.replaceChild(newImage, imageElement);
// 重新设置ID
newImage.id = 'lightbox-img';
}
// 隐藏加载状态
loadingElement.style.display = 'none';
newImage.style.opacity = '1';
};
newImage.onerror = function() {
// 图片加载失败
loadingElement.innerHTML = '<i class="fas fa-exclamation-triangle"></i><span>图片加载失败</span>';
};
}
}
// 初始化图片懒加载
function initLazyLoading() {
// 使用Intersection Observer实现懒加载
if ('IntersectionObserver' in window) {
const observer = 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);
}
observer.unobserve(img);
}
});
}, {
rootMargin: '200px 0px', // 提前200px开始加载
threshold: 0.1
});
// 观察所有带有data-image-id的图片
document.querySelectorAll('img[data-image-id]').forEach(img => {
if (!img.classList.contains('loaded')) {
observer.observe(img);
}
});
} else {
// 不支持Intersection Observer的回退方案
console.warn('浏览器不支持Intersection Observer,使用回退懒加载方案');
initAlbumFallbackLazyLoading();
}
}
// 初始化lightbox控制
function initLightboxControls() {
// 键盘导航
document.addEventListener('keydown', function(event) {
const lightbox = document.getElementById('lightbox');
if (lightbox.classList.contains('active')) {
switch(event.key) {
case 'Escape':
closeLightbox();
break;
case 'ArrowLeft':
prevImage();
break;
case 'ArrowRight':
nextImage();
break;
}
}
});
// 点击背景关闭
document.getElementById('lightbox').addEventListener('click', function(event) {
if (event.target === this) closeLightbox();
});
// 触摸手势支持
if (isTouchDevice) {
let touchStartX = 0;
let touchStartY = 0;
document.addEventListener('touchstart', function(event) {
touchStartX = event.touches[0].clientX;
touchStartY = event.touches[0].clientY;
});
document.addEventListener('touchend', function(event) {
const lightbox = document.getElementById('lightbox');
if (!lightbox.classList.contains('active')) return;
// 检查点击目标是否是导航按钮或关闭按钮
const target = event.target;
const isNavButton = target.closest('.preview-nav') || target.closest('.preview-close');
if (isNavButton) {
// 如果是按钮点击,不处理轻点关闭逻辑
return;
}
const touchEndX = event.changedTouches[0].clientX;
const touchEndY = event.changedTouches[0].clientY;
const deltaX = touchEndX - touchStartX;
const deltaY = touchEndY - touchStartY;
// 水平滑动距离大于垂直滑动距离,且水平滑动距离足够大
if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > 50) {
if (deltaX > 0) {
prevImage();
} else {
nextImage();
}
// 滑动操作后阻止轻点关闭判断
return;
}
// 轻点关闭(如果移动距离很小)
if (Math.abs(deltaX) < 10 && Math.abs(deltaY) < 10) {
closeLightbox();
}
});
}
}
// 从lightbox添加收藏
function addToFavoritesFromLightbox() {
const image = images[currentImageIndex];
const imageId = image.id;
if (!imageId) {
alert('无法收藏此图片:缺少图片ID');
return;
}
// 检查是否已经收藏
const favoriteBtn = document.getElementById('lightbox-favorite-btn');
if (favoriteBtn.classList.contains('favorited')) {
// 取消收藏
fetch(`${API_BASE}/favorites/image/${imageId}`, {
method: 'DELETE'
})
.then(response => {
if (!response.ok) {
throw new Error('取消收藏失败');
}
return response.json();
})
.then(() => {
favoriteBtn.classList.remove('favorited');
favoriteBtn.innerHTML = '<i class="fas fa-heart"></i> 加入收藏';
showFavoriteToast('已取消收藏', false);
})
.catch(error => {
console.error('取消收藏失败:', error);
alert('取消收藏失败,请稍后重试');
});
} else {
// 添加收藏
const favoriteData = {
image_id: imageId,
album_id: currentAlbum.id
};
fetch(`${API_BASE}/favorites`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(favoriteData)
})
.then(response => {
if (!response.ok) {
throw new Error('添加收藏失败');
}
return response.json();
})
.then(() => {
favoriteBtn.classList.add('favorited');
favoriteBtn.innerHTML = '<i class="fas fa-heart"></i> 已收藏';
showFavoriteToast('已添加到收藏夹', true);
})
.catch(error => {
console.error('添加收藏失败:', error);
alert('添加收藏失败,请稍后重试');
});
}
// 添加动画效果
const heartIcon = favoriteBtn.querySelector('i');
if (heartIcon) {
heartIcon.style.transform = 'scale(1.3)';
setTimeout(() => {
heartIcon.style.transform = 'scale(1)';
}, 300);
}
}
// 检查图片收藏状态并更新lightbox按钮
function checkLightboxFavoriteStatus() {
const image = images[currentImageIndex];
const imageId = image.id;
const favoriteBtn = document.getElementById('lightbox-favorite-btn');
if (!imageId || !favoriteBtn) return;
// 检查收藏状态
fetch(`${API_BASE}/favorites/status/${imageId}`)
.then(response => {
if (!response.ok) {
throw new Error('检查收藏状态失败');
}
return response.json();
})
.then(status => {
if (status.is_favorited) {
favoriteBtn.classList.add('favorited');
favoriteBtn.innerHTML = '<i class="fas fa-heart"></i> 已收藏';
} else {
favoriteBtn.classList.remove('favorited');
favoriteBtn.innerHTML = '<i class="fas fa-heart"></i> 加入收藏';
}
})
.catch(error => {
console.error('检查收藏状态失败:', error);
// 失败时默认显示"加入收藏"
favoriteBtn.classList.remove('favorited');
favoriteBtn.innerHTML = '<i class="fas fa-heart"></i> 加入收藏';
});
}
// 显示错误信息
function showError(message) {
const container = document.getElementById('images-container');
container.innerHTML = `<div class="no-results"><i class="fas fa-exclamation-triangle"></i><br>${message}</div>`;
document.getElementById('album-title').textContent = '加载失败';
document.getElementById('album-info').innerHTML = '';
}
// 移动端优化:防止双击缩放
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 logPerformance() {
if ('performance' in window) {
window.addEventListener('load', () => {
setTimeout(() => {
const navigationTiming = performance.getEntriesByType('navigation')[0];
console.log('相册页面加载时间:', navigationTiming.loadEventEnd - navigationTiming.navigationStart, 'ms');
}, 0);
});
}
}
// 检查图片是否已收藏
function isImageFavorited(imageId) {
// 这里需要从后端API获取收藏状态
// 暂时返回false,实际实现中需要调用API
return false;
}
// 切换收藏状态
function toggleFavorite(event, imageIndex) {
event.stopPropagation(); // 阻止事件冒泡,避免打开lightbox
const image = images[imageIndex];
const imageId = image.id;
if (!imageId) {
alert('无法收藏此图片:缺少图片ID');
return;
}
// 找到按钮元素(event.target 可能是 i 图标)
const btn = event.target.closest('.favorite-btn');
if (!btn) return;
// 检查当前收藏状态
const isFavorited = btn.classList.contains('favorited');
if (isFavorited) {
// 取消收藏
fetch(`${API_BASE}/favorites/image/${imageId}`, {
method: 'DELETE'
})
.then(response => {
if (!response.ok) {
throw new Error('取消收藏失败');
}
return response.json();
})
.then(() => {
btn.classList.remove('favorited');
showFavoriteToast('已取消收藏', false);
})
.catch(error => {
console.error('取消收藏失败:', error);
alert('取消收藏失败,请稍后重试');
});
} else {
// 添加收藏
const favoriteData = {
image_id: imageId,
album_id: currentAlbum.id
};
fetch(`${API_BASE}/favorites`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(favoriteData)
})
.then(response => {
if (!response.ok) {
throw new Error('添加收藏失败');
}
return response.json();
})
.then(() => {
btn.classList.add('favorited');
showFavoriteToast('已添加到收藏夹', true);
})
.catch(error => {
console.error('添加收藏失败:', error);
alert('添加收藏失败,请稍后重试');
});
}
// 添加动画效果
let heartIcon;
// 确保找到正确的图标元素
if (event.target.tagName === 'I') {
heartIcon = event.target;
} else if (event.target.tagName === 'BUTTON') {
heartIcon = event.target.querySelector('i');
} else {
heartIcon = event.target.closest('.favorite-btn')?.querySelector('i');
}
if (heartIcon) {
heartIcon.style.transform = 'scale(1.3)';
setTimeout(() => {
heartIcon.style.transform = 'scale(1)';
}, 300);
}
}
// 显示收藏提示
function showFavoriteToast(message, isAdd) {
// 创建提示元素
let toast = document.getElementById('favorite-toast');
if (!toast) {
toast = document.createElement('div');
toast.id = 'favorite-toast';
toast.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: ${isAdd ? '#10b981' : '#ef4444'};
color: white;
padding: 12px 20px;
border-radius: 25px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 1000;
opacity: 0;
transform: translateX(100px);
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 8px;
`;
document.body.appendChild(toast);
}
toast.innerHTML = `<i class="fas ${isAdd ? 'fa-heart' : 'fa-heart-broken'}"></i> ${message}`;
toast.style.background = isAdd ? '#10b981' : '#ef4444';
// 显示动画
setTimeout(() => {
toast.style.opacity = '1';
toast.style.transform = 'translateX(0)';
}, 10);
// 隐藏动画
setTimeout(() => {
toast.style.opacity = '0';
toast.style.transform = 'translateX(100px)';
setTimeout(() => {
if (toast.parentNode) {
toast.parentNode.removeChild(toast);
}
}, 300);
}, 2000);
}
// 初始化性能监控和移动端优化
logPerformance();
preventDoubleTapZoom();
// 导出函数供全局使用
window.openLightbox = openLightbox;
window.closeLightbox = closeLightbox;
window.prevImage = prevImage;
window.nextImage = nextImage;
window.toggleFavorite = toggleFavorite;
window.addToFavoritesFromLightbox = addToFavoritesFromLightbox;