958 lines
32 KiB
JavaScript
958 lines
32 KiB
JavaScript
// 全局变量
|
||
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; |