Compare commits
3 Commits
5e33d53ea3
...
3f5ddde664
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f5ddde664 | ||
|
|
be004854e2 | ||
|
|
f0605dca4c |
40
index.html
@@ -96,7 +96,7 @@
|
|||||||
|
|
||||||
<!-- 完整博客文章弹窗(点击后显示) -->
|
<!-- 完整博客文章弹窗(点击后显示) -->
|
||||||
<div id="blog-posts-full-modal" class="guestbook-full-modal">
|
<div id="blog-posts-full-modal" class="guestbook-full-modal">
|
||||||
<div class="full-modal-content">
|
<div class="full-modal-content solid-modal">
|
||||||
<div class="guestbook-header">
|
<div class="guestbook-header">
|
||||||
<h2>博客文章</h2>
|
<h2>博客文章</h2>
|
||||||
<button id="blog-posts-close" class="guestbook-close" title="关闭">×</button>
|
<button id="blog-posts-close" class="guestbook-close" title="关闭">×</button>
|
||||||
@@ -107,9 +107,44 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 旅途剪影按钮 -->
|
||||||
|
<div class="photo-album-btn-container">
|
||||||
|
<button id="photo-album-btn" class="photo-album-btn" title="旅途剪影">
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="3" y="3" width="18" height="18" rx="2" ry="2" stroke="#666" stroke-width="2"/>
|
||||||
|
<circle cx="8.5" cy="8.5" r="1.5" fill="#666"/>
|
||||||
|
<path d="M21 15L16 10L5 21" stroke="#666" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
<span class="btn-label" data-i18n="btn_photo_album">旅途剪影</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Hover 浮动预览面板 -->
|
||||||
|
<div id="photo-album-preview" class="photo-album-preview">
|
||||||
|
<div class="preview-header">
|
||||||
|
<h3 data-i18n="photo_latest_photos">最新照片</h3>
|
||||||
|
</div>
|
||||||
|
<div class="preview-body photo-album-list">
|
||||||
|
<!-- 最新照片预览将在这里动态加载 -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 完整旅途剪影弹窗(点击后显示) -->
|
||||||
|
<div id="photo-album-full-modal" class="guestbook-full-modal">
|
||||||
|
<div class="full-modal-content">
|
||||||
|
<div class="guestbook-header">
|
||||||
|
<h2 data-i18n="photo_album_title">旅途剪影</h2>
|
||||||
|
<button id="photo-album-close" class="guestbook-close" title="关闭">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="guestbook-body photo-album-body">
|
||||||
|
<!-- 照片网格将在这里动态加载 -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 完整留言板弹窗(点击后显示) -->
|
<!-- 完整留言板弹窗(点击后显示) -->
|
||||||
<div id="guestbook-full-modal" class="guestbook-full-modal">
|
<div id="guestbook-full-modal" class="guestbook-full-modal">
|
||||||
<div class="full-modal-content">
|
<div class="full-modal-content solid-modal">
|
||||||
<div class="guestbook-header">
|
<div class="guestbook-header">
|
||||||
<h2 data-i18n="guestbook_title">留言板</h2>
|
<h2 data-i18n="guestbook_title">留言板</h2>
|
||||||
<button id="guestbook-close" class="guestbook-close" title="关闭">×</button>
|
<button id="guestbook-close" class="guestbook-close" title="关闭">×</button>
|
||||||
@@ -328,6 +363,7 @@
|
|||||||
<script src="./static/js/map-footprint.js"></script>
|
<script src="./static/js/map-footprint.js"></script>
|
||||||
<script src="./static/js/guestbook.js"></script>
|
<script src="./static/js/guestbook.js"></script>
|
||||||
<script src="./static/js/blog_posts.js"></script>
|
<script src="./static/js/blog_posts.js"></script>
|
||||||
|
<script src="./static/js/photo_album.js"></script>
|
||||||
<script src="./static/js/tabchange.js"></script>
|
<script src="./static/js/tabchange.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
14
scripts/build-photos.bat
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
@echo off
|
||||||
|
chcp 65001 >nul
|
||||||
|
echo ========================================
|
||||||
|
echo 人生相册构建脚本
|
||||||
|
echo ========================================
|
||||||
|
echo.
|
||||||
|
|
||||||
|
cd /d "%~dp0.."
|
||||||
|
|
||||||
|
python scripts\generate-photos-json.py
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ========================================
|
||||||
|
pause
|
||||||
93
scripts/generate-photos-json.py
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# ==================== 配置区域 ====================
|
||||||
|
# 请在此处指定你的照片目录和输出文件路径
|
||||||
|
# DEFAULT_PHOTOS_DIR = Path("/www/wwwroot/www.iletter.top/static/img/photos")
|
||||||
|
# DEFAULT_OUTPUT_FILE = Path("/www/wwwroot/www.iletter.top/static/img/photos/photos.json")
|
||||||
|
DEFAULT_PHOTOS_DIR = Path("D:\\UserData\\Desktop\\my_proj\\www.iletter.top\\static\\img\\photos")
|
||||||
|
DEFAULT_OUTPUT_FILE = Path("D:\\UserData\\Desktop\\my_proj\\www.iletter.top\\static\\img\\photos\\photos.json")
|
||||||
|
# ================================================
|
||||||
|
|
||||||
|
# 支持的图片格式
|
||||||
|
IMAGE_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.svg'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def natural_sort_key(filename):
|
||||||
|
"""自然排序键函数,支持数字正确排序"""
|
||||||
|
return [int(text) if text.isdigit() else text.lower()
|
||||||
|
for text in re.split(r'(\d+)', filename)]
|
||||||
|
|
||||||
|
|
||||||
|
def scan_photos(photos_dir):
|
||||||
|
"""扫描照片目录,返回按修改时间排序的文件名列表(最新的在最前面)"""
|
||||||
|
if not photos_dir.exists():
|
||||||
|
print(f"❌ 目录不存在: {photos_dir}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
photos = []
|
||||||
|
for file in photos_dir.iterdir():
|
||||||
|
if file.is_file() and file.suffix.lower() in IMAGE_EXTENSIONS:
|
||||||
|
# 排除 JSON 文件本身
|
||||||
|
if file.name == "photos.json":
|
||||||
|
continue
|
||||||
|
# 存储文件名和修改时间戳
|
||||||
|
photos.append((file.name, file.stat().st_mtime))
|
||||||
|
|
||||||
|
# 按修改时间降序排序(最新的在最前面)
|
||||||
|
photos.sort(key=lambda x: x[1], reverse=True)
|
||||||
|
|
||||||
|
# 只返回文件名列表
|
||||||
|
return [p[0] for p in photos]
|
||||||
|
|
||||||
|
|
||||||
|
def generate_json(photos, output_file):
|
||||||
|
"""生成 photos.json 文件"""
|
||||||
|
data = {
|
||||||
|
"photos": photos,
|
||||||
|
"total": len(photos),
|
||||||
|
"lastUpdated": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
}
|
||||||
|
|
||||||
|
# 写入文件
|
||||||
|
with open(output_file, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
return len(photos)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# 确定照片目录和输出文件路径
|
||||||
|
photos_dir = DEFAULT_PHOTOS_DIR.resolve()
|
||||||
|
output_file = DEFAULT_OUTPUT_FILE.resolve()
|
||||||
|
|
||||||
|
print("🚀 开始扫描照片目录...")
|
||||||
|
print(f"📁 照片目录: {photos_dir}")
|
||||||
|
print(f"📝 输出文件: {output_file}")
|
||||||
|
|
||||||
|
photos = scan_photos(photos_dir)
|
||||||
|
|
||||||
|
if not photos:
|
||||||
|
print("⚠️ 未找到任何照片文件")
|
||||||
|
# 仍然生成空的 JSON
|
||||||
|
generate_json([], output_file)
|
||||||
|
print(f"✅ 已生成空的 {output_file}")
|
||||||
|
return
|
||||||
|
|
||||||
|
count = generate_json(photos, output_file)
|
||||||
|
|
||||||
|
print(f"✅ 成功生成 {output_file}")
|
||||||
|
print(f"📊 共扫描到 {count} 张照片:")
|
||||||
|
for i, photo in enumerate(photos, 1):
|
||||||
|
print(f" {i}. {photo}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -14,6 +14,28 @@
|
|||||||
z-index: 999;
|
z-index: 999;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 旅途剪影按钮容器 */
|
||||||
|
.photo-album-btn-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 210px;
|
||||||
|
right: 80px;
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 透明桥梁:向左延伸覆盖预览面板区域,确保鼠标滑入时 hover 不中断 */
|
||||||
|
.guestbook-btn-container::after,
|
||||||
|
.blog-posts-btn-container::after,
|
||||||
|
.photo-album-btn-container::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
right: 80px; /* 从容器的左边缘开始 */
|
||||||
|
top: 0;
|
||||||
|
width: 200px; /* 15px 间距 + 380px 预览面板宽度 */
|
||||||
|
height: 80px; /* 与按钮高度一致 */
|
||||||
|
background: transparent;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.guestbook-btn {
|
.guestbook-btn {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
@@ -47,6 +69,22 @@
|
|||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.photo-album-btn {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e8e8e8;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 4px;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
.btn-label {
|
.btn-label {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #666;
|
color: #666;
|
||||||
@@ -78,7 +116,8 @@
|
|||||||
|
|
||||||
/* Hover 浮动预览面板 */
|
/* Hover 浮动预览面板 */
|
||||||
.guestbook-preview,
|
.guestbook-preview,
|
||||||
.blog-posts-preview {
|
.blog-posts-preview,
|
||||||
|
.photo-album-preview {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -103,7 +142,8 @@
|
|||||||
|
|
||||||
/* 当鼠标悬停在容器上时显示预览面板 */
|
/* 当鼠标悬停在容器上时显示预览面板 */
|
||||||
.guestbook-btn-container:hover .guestbook-preview,
|
.guestbook-btn-container:hover .guestbook-preview,
|
||||||
.blog-posts-btn-container:hover .blog-posts-preview {
|
.blog-posts-btn-container:hover .blog-posts-preview,
|
||||||
|
.photo-album-btn-container:hover .photo-album-preview {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: translateX(0) scale(1);
|
transform: translateX(0) scale(1);
|
||||||
@@ -186,7 +226,7 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
background-color: rgba(0, 0, 0, 0.15);
|
||||||
z-index: 10000;
|
z-index: 10000;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -206,15 +246,106 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 旅途剪影弹窗样式 - iOS 26 风格极致玻璃拟态 */
|
||||||
.full-modal-content {
|
.full-modal-content {
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
rgba(255, 255, 255, 0.12) 0%,
|
||||||
|
rgba(255, 255, 255, 0.06) 50%,
|
||||||
|
rgba(255, 255, 255, 0.1) 100%
|
||||||
|
);
|
||||||
|
backdrop-filter: blur(80px) saturate(250%);
|
||||||
|
-webkit-backdrop-filter: blur(80px) saturate(250%);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.45);
|
||||||
|
border-radius: 28px;
|
||||||
|
width: 95%;
|
||||||
|
max-width: 1400px;
|
||||||
|
max-height: 95vh;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow:
|
||||||
|
0 32px 100px rgba(0, 0, 0, 0.25),
|
||||||
|
0 0 0 1px rgba(255, 255, 255, 0.3) inset,
|
||||||
|
inset 0 1px 1px rgba(255, 255, 255, 0.6),
|
||||||
|
inset 0 -1px 1px rgba(255, 255, 255, 0.15);
|
||||||
|
animation: slideUp 0.4s cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
|
position: relative;
|
||||||
|
isolation: isolate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 顶部高光反射 - 增强版 */
|
||||||
|
.full-modal-content::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 50%;
|
||||||
|
background: linear-gradient(180deg,
|
||||||
|
rgba(255, 255, 255, 0.5) 0%,
|
||||||
|
rgba(255, 255, 255, 0.2) 30%,
|
||||||
|
rgba(255, 255, 255, 0.05) 70%,
|
||||||
|
transparent 100%);
|
||||||
|
border-radius: 28px 28px 0 0;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 底部环境光反射 */
|
||||||
|
.full-modal-content::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 10%;
|
||||||
|
right: 10%;
|
||||||
|
height: 80px;
|
||||||
|
background: radial-gradient(ellipse at center bottom,
|
||||||
|
rgba(255, 255, 255, 0.2) 0%,
|
||||||
|
transparent 60%);
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 侧边边缘反光 */
|
||||||
|
.full-modal-content .guestbook-header::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 20%;
|
||||||
|
left: 0;
|
||||||
|
bottom: 20%;
|
||||||
|
width: 2px;
|
||||||
|
background: linear-gradient(180deg,
|
||||||
|
transparent 0%,
|
||||||
|
rgba(255, 255, 255, 0.3) 30%,
|
||||||
|
rgba(255, 255, 255, 0.5) 50%,
|
||||||
|
rgba(255, 255, 255, 0.3) 70%,
|
||||||
|
transparent 100%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 实心背景弹窗样式(留言板、博客文章专用) */
|
||||||
|
.full-modal-content.solid-modal {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
|
backdrop-filter: none;
|
||||||
|
-webkit-backdrop-filter: none;
|
||||||
|
border: none;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
width: 92%;
|
width: 92%;
|
||||||
max-width: 850px;
|
max-width: 850px;
|
||||||
max-height: 92vh;
|
max-height: 92vh;
|
||||||
overflow: hidden;
|
|
||||||
box-shadow: 0 12px 48px rgba(0, 0, 0, 0.18);
|
box-shadow: 0 12px 48px rgba(0, 0, 0, 0.18);
|
||||||
animation: slideUp 0.3s ease;
|
}
|
||||||
|
|
||||||
|
/* 隐藏弹窗内部滚动条 */
|
||||||
|
.guestbook-body {
|
||||||
|
padding: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: calc(95vh - 70px);
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
|
-ms-overflow-style: none; /* IE and Edge */
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-body::-webkit-scrollbar {
|
||||||
|
display: none; /* Chrome, Safari, Opera */
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes slideUp {
|
@keyframes slideUp {
|
||||||
@@ -316,41 +447,55 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.guestbook-header {
|
.guestbook-header {
|
||||||
padding: 16px 20px;
|
padding: 22px 30px;
|
||||||
border-bottom: 1px solid #f0f0f0;
|
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: rgba(255, 255, 255, 0.8);
|
background: transparent;
|
||||||
backdrop-filter: blur(10px);
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.guestbook-header h2 {
|
.guestbook-header h2 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 16px;
|
font-size: 24px;
|
||||||
color: #333;
|
color: #1a1a1a;
|
||||||
font-weight: 500;
|
font-weight: 700;
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 1.5px;
|
||||||
|
text-shadow: 0 1px 3px rgba(255, 255, 255, 0.9), 0 0 20px rgba(255, 255, 255, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.guestbook-close {
|
.guestbook-close {
|
||||||
background: none;
|
background: rgba(255, 255, 255, 0.25);
|
||||||
border: none;
|
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||||
font-size: 24px;
|
font-size: 28px;
|
||||||
color: #999;
|
color: #444;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
width: 24px;
|
width: 42px;
|
||||||
height: 24px;
|
height: 42px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
transition: color 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
border-radius: 50%;
|
||||||
|
backdrop-filter: blur(20px) saturate(150%);
|
||||||
|
-webkit-backdrop-filter: blur(20px) saturate(150%);
|
||||||
|
box-shadow:
|
||||||
|
0 2px 8px rgba(0, 0, 0, 0.1),
|
||||||
|
inset 0 1px 1px rgba(255, 255, 255, 0.5);
|
||||||
|
z-index: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.guestbook-close:hover {
|
.guestbook-close:hover {
|
||||||
color: #333;
|
background: rgba(255, 255, 255, 0.4);
|
||||||
|
color: #222;
|
||||||
|
transform: rotate(90deg) scale(1.05);
|
||||||
|
box-shadow:
|
||||||
|
0 4px 16px rgba(0, 0, 0, 0.15),
|
||||||
|
inset 0 1px 1px rgba(255, 255, 255, 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
.guestbook-body {
|
.guestbook-body {
|
||||||
@@ -691,10 +836,95 @@
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 加载更多按钮 */
|
/* 分页导航样式 - 极致玻璃拟态 */
|
||||||
|
.pagination-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 30px 0;
|
||||||
|
background: transparent;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-btn {
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.4);
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
backdrop-filter: blur(20px) saturate(150%);
|
||||||
|
-webkit-backdrop-filter: blur(20px) saturate(150%);
|
||||||
|
border-radius: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #333;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow:
|
||||||
|
0 2px 8px rgba(0, 0, 0, 0.06),
|
||||||
|
inset 0 1px 1px rgba(255, 255, 255, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.35);
|
||||||
|
transform: translateY(-3px) scale(1.05);
|
||||||
|
box-shadow:
|
||||||
|
0 6px 20px rgba(0, 0, 0, 0.1),
|
||||||
|
inset 0 1px 1px rgba(255, 255, 255, 0.5);
|
||||||
|
border-color: rgba(255, 255, 255, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-num {
|
||||||
|
min-width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.35);
|
||||||
|
padding: 0 12px;
|
||||||
|
backdrop-filter: blur(10px) saturate(150%);
|
||||||
|
-webkit-backdrop-filter: blur(10px) saturate(150%);
|
||||||
|
box-shadow: inset 0 1px 1px rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-num:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.35);
|
||||||
|
border-color: rgba(255, 255, 255, 0.55);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-num.active {
|
||||||
|
background: rgba(255, 255, 255, 0.45);
|
||||||
|
color: #1a1a1a;
|
||||||
|
cursor: default;
|
||||||
|
backdrop-filter: blur(15px) saturate(180%);
|
||||||
|
-webkit-backdrop-filter: blur(15px) saturate(180%);
|
||||||
|
box-shadow:
|
||||||
|
0 4px 16px rgba(0, 0, 0, 0.1),
|
||||||
|
inset 0 1px 1px rgba(255, 255, 255, 0.5);
|
||||||
|
border-color: rgba(255, 255, 255, 0.7);
|
||||||
|
font-weight: 700;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-dots {
|
||||||
|
color: #777;
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 加载更多按钮样式(用于留言板等实心弹窗) */
|
||||||
.load-more-btn {
|
.load-more-btn {
|
||||||
display: block;
|
display: block;
|
||||||
margin: 20px auto 20px auto;
|
margin: 20px auto;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border: 1px solid #e8e8e8;
|
border: 1px solid #e8e8e8;
|
||||||
color: #666;
|
color: #666;
|
||||||
@@ -712,12 +942,6 @@
|
|||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.load-more-btn:disabled {
|
|
||||||
color: #ccc;
|
|
||||||
border-color: #eee;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 加载完成提示 */
|
/* 加载完成提示 */
|
||||||
.load-complete {
|
.load-complete {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -768,6 +992,194 @@
|
|||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 旅途剪影预览面板样式 - 列表模式 */
|
||||||
|
.photo-album-container {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-preview-item {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-preview-item:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-preview-item img {
|
||||||
|
width: 100%;
|
||||||
|
height: 120px;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 旅途剪影瀑布流布局 - 紧密贴住 */
|
||||||
|
.photo-album-grid {
|
||||||
|
column-count: 3;
|
||||||
|
column-gap: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
.photo-album-grid {
|
||||||
|
column-count: 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.photo-album-grid {
|
||||||
|
column-count: 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-album-item {
|
||||||
|
break-inside: avoid;
|
||||||
|
margin-bottom: 0;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
box-shadow: none;
|
||||||
|
background-color: transparent;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-album-item:hover {
|
||||||
|
transform: scale(1.02);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-album-item img {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
display: block;
|
||||||
|
opacity: 0; /* 初始隐藏,加载完再显示 */
|
||||||
|
transition: opacity 0.5s ease, transform 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-album-item:hover img {
|
||||||
|
transform: scale(1.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 照片放大弹窗 */
|
||||||
|
.photo-lightbox {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0, 0, 0, 0.95);
|
||||||
|
z-index: 10000;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
animation: fadeIn 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-lightbox.active {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-lightbox img {
|
||||||
|
max-width: 85%;
|
||||||
|
max-height: 85%;
|
||||||
|
object-fit: contain;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
|
||||||
|
animation: zoomIn 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-lightbox-close {
|
||||||
|
position: absolute;
|
||||||
|
top: 30px;
|
||||||
|
right: 40px;
|
||||||
|
font-size: 40px;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
z-index: 10001;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-lightbox-close:hover {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 左右切换按钮 */
|
||||||
|
.photo-lightbox-nav {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
font-size: 50px;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
z-index: 10001;
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 50%;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-lightbox-nav:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.25);
|
||||||
|
transform: translateY(-50%) scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-lightbox-prev {
|
||||||
|
left: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-lightbox-next {
|
||||||
|
right: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 图片计数器 */
|
||||||
|
.photo-lightbox-counter {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 30px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 16px;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
padding: 8px 20px;
|
||||||
|
border-radius: 20px;
|
||||||
|
z-index: 10001;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes zoomIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.8);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* 响应式设计 */
|
/* 响应式设计 */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.guestbook-btn-container {
|
.guestbook-btn-container {
|
||||||
@@ -780,9 +1192,15 @@
|
|||||||
top: 90px;
|
top: 90px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.photo-album-btn-container {
|
||||||
|
right: 15px;
|
||||||
|
top: 165px;
|
||||||
|
}
|
||||||
|
|
||||||
/* 移动端预览面板调整位置,避免超出屏幕 */
|
/* 移动端预览面板调整位置,避免超出屏幕 */
|
||||||
.guestbook-preview,
|
.guestbook-preview,
|
||||||
.blog-posts-preview {
|
.blog-posts-preview,
|
||||||
|
.photo-album-preview {
|
||||||
right: auto;
|
right: auto;
|
||||||
left: calc(100% + 10px);
|
left: calc(100% + 10px);
|
||||||
transform-origin: center left;
|
transform-origin: center left;
|
||||||
@@ -790,7 +1208,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.guestbook-btn-container:hover .guestbook-preview,
|
.guestbook-btn-container:hover .guestbook-preview,
|
||||||
.blog-posts-btn-container:hover .blog-posts-preview {
|
.blog-posts-btn-container:hover .blog-posts-preview,
|
||||||
|
.photo-album-btn-container:hover .photo-album-preview {
|
||||||
transform: translateX(0) scale(1);
|
transform: translateX(0) scale(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
BIN
static/img/photos/0c49fd33f4e83dcedde820d858ec44e9.jpg
Normal file
|
After Width: | Height: | Size: 467 KiB |
BIN
static/img/photos/1 (1).jpg
Normal file
|
After Width: | Height: | Size: 140 KiB |
BIN
static/img/photos/1 (10).jpg
Normal file
|
After Width: | Height: | Size: 490 KiB |
BIN
static/img/photos/1 (11).jpg
Normal file
|
After Width: | Height: | Size: 248 KiB |
BIN
static/img/photos/1 (12).jpg
Normal file
|
After Width: | Height: | Size: 722 KiB |
BIN
static/img/photos/1 (13).jpg
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
static/img/photos/1 (2).jpg
Normal file
|
After Width: | Height: | Size: 304 KiB |
BIN
static/img/photos/1 (3).jpg
Normal file
|
After Width: | Height: | Size: 442 KiB |
BIN
static/img/photos/1 (4).jpg
Normal file
|
After Width: | Height: | Size: 473 KiB |
BIN
static/img/photos/1 (5).jpg
Normal file
|
After Width: | Height: | Size: 345 KiB |
BIN
static/img/photos/1 (6).jpg
Normal file
|
After Width: | Height: | Size: 352 KiB |
BIN
static/img/photos/1 (7).jpg
Normal file
|
After Width: | Height: | Size: 156 KiB |
BIN
static/img/photos/1 (8).jpg
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
static/img/photos/1 (9).jpg
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
static/img/photos/1b72bd604e55942f6a7d7c28bc5ed5be.jpg
Normal file
|
After Width: | Height: | Size: 453 KiB |
BIN
static/img/photos/20955b34b4cbc661f3ec7ff2eda0b815.jpg
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
static/img/photos/2799e89f4632184d151941741b59257f.jpg
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
static/img/photos/3e0ad66c001c9894c26eac4b98601c73.jpg
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
static/img/photos/4c5a2a39e6ae9e01bf2ea9860ae8e41f.jpg
Normal file
|
After Width: | Height: | Size: 423 KiB |
BIN
static/img/photos/522915fe4632f9591faca7c46a068ea7.jpg
Normal file
|
After Width: | Height: | Size: 141 KiB |
BIN
static/img/photos/64b539e20378d3e6ac3324e9dcc616ce.jpg
Normal file
|
After Width: | Height: | Size: 815 KiB |
BIN
static/img/photos/6b736db3eeaa315422e98e5e550f23ce.jpg
Normal file
|
After Width: | Height: | Size: 125 KiB |
BIN
static/img/photos/74641a449ac2d8f9d113d27ca7a7254c.jpg
Normal file
|
After Width: | Height: | Size: 126 KiB |
BIN
static/img/photos/7480c6e1755218592bd4881338750d8c.jpg
Normal file
|
After Width: | Height: | Size: 236 KiB |
BIN
static/img/photos/7b9532126cb8cd0752f8bdcd2d683cb6.jpg
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
static/img/photos/7fe76931d5653349a7876166db59d25e.jpg
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
static/img/photos/8498ee3191ba50dc27b85b7fbfd1e0fc.jpg
Normal file
|
After Width: | Height: | Size: 134 KiB |
BIN
static/img/photos/85306709e27843201604a7b0efc88565.jpg
Normal file
|
After Width: | Height: | Size: 456 KiB |
BIN
static/img/photos/9f647be6532ea2acf9778eb302a19742.jpg
Normal file
|
After Width: | Height: | Size: 547 KiB |
BIN
static/img/photos/a09ebc8f61856503211b42c975fe5cd5.jpg
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
static/img/photos/a62e6036f35722b1f0dd131a929d3a17.jpg
Normal file
|
After Width: | Height: | Size: 490 KiB |
BIN
static/img/photos/c0ca11cc8eaaa16564c75263357e19ad.jpg
Normal file
|
After Width: | Height: | Size: 170 KiB |
BIN
static/img/photos/c53486080dd63ac735f1c5d27203b1c4.jpg
Normal file
|
After Width: | Height: | Size: 401 KiB |
BIN
static/img/photos/c8be53d1cb4d3689420aa56dea0b16f5.jpg
Normal file
|
After Width: | Height: | Size: 126 KiB |
BIN
static/img/photos/dae46343c972876a77f6b765ce1e57ea.jpg
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
static/img/photos/e1349403bdf5692c209d809ddb1e026d.jpg
Normal file
|
After Width: | Height: | Size: 275 KiB |
BIN
static/img/photos/f3aece19d8e1fdcd0bb140f5ef3ff882.jpg
Normal file
|
After Width: | Height: | Size: 242 KiB |
43
static/img/photos/photos.json
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"photos": [
|
||||||
|
"74641a449ac2d8f9d113d27ca7a7254c.jpg",
|
||||||
|
"9f647be6532ea2acf9778eb302a19742.jpg",
|
||||||
|
"a09ebc8f61856503211b42c975fe5cd5.jpg",
|
||||||
|
"a62e6036f35722b1f0dd131a929d3a17.jpg",
|
||||||
|
"1b72bd604e55942f6a7d7c28bc5ed5be.jpg",
|
||||||
|
"0c49fd33f4e83dcedde820d858ec44e9.jpg",
|
||||||
|
"64b539e20378d3e6ac3324e9dcc616ce.jpg",
|
||||||
|
"c0ca11cc8eaaa16564c75263357e19ad.jpg",
|
||||||
|
"7480c6e1755218592bd4881338750d8c.jpg",
|
||||||
|
"8498ee3191ba50dc27b85b7fbfd1e0fc.jpg",
|
||||||
|
"e1349403bdf5692c209d809ddb1e026d.jpg",
|
||||||
|
"85306709e27843201604a7b0efc88565.jpg",
|
||||||
|
"c53486080dd63ac735f1c5d27203b1c4.jpg",
|
||||||
|
"f3aece19d8e1fdcd0bb140f5ef3ff882.jpg",
|
||||||
|
"dae46343c972876a77f6b765ce1e57ea.jpg",
|
||||||
|
"6b736db3eeaa315422e98e5e550f23ce.jpg",
|
||||||
|
"522915fe4632f9591faca7c46a068ea7.jpg",
|
||||||
|
"7fe76931d5653349a7876166db59d25e.jpg",
|
||||||
|
"7b9532126cb8cd0752f8bdcd2d683cb6.jpg",
|
||||||
|
"c8be53d1cb4d3689420aa56dea0b16f5.jpg",
|
||||||
|
"2799e89f4632184d151941741b59257f.jpg",
|
||||||
|
"4c5a2a39e6ae9e01bf2ea9860ae8e41f.jpg",
|
||||||
|
"3e0ad66c001c9894c26eac4b98601c73.jpg",
|
||||||
|
"20955b34b4cbc661f3ec7ff2eda0b815.jpg",
|
||||||
|
"1 (7).jpg",
|
||||||
|
"1 (11).jpg",
|
||||||
|
"1 (9).jpg",
|
||||||
|
"1 (6).jpg",
|
||||||
|
"1 (5).jpg",
|
||||||
|
"1 (4).jpg",
|
||||||
|
"1 (3).jpg",
|
||||||
|
"1 (2).jpg",
|
||||||
|
"1 (1).jpg",
|
||||||
|
"1 (8).jpg",
|
||||||
|
"1 (13).jpg",
|
||||||
|
"1 (10).jpg",
|
||||||
|
"1 (12).jpg"
|
||||||
|
],
|
||||||
|
"total": 37,
|
||||||
|
"lastUpdated": "2026-05-18 02:36:56"
|
||||||
|
}
|
||||||
@@ -30,20 +30,6 @@
|
|||||||
// 关闭按钮
|
// 关闭按钮
|
||||||
blogPostsClose.addEventListener('click', closeFullModal);
|
blogPostsClose.addEventListener('click', closeFullModal);
|
||||||
|
|
||||||
// 点击背景关闭
|
|
||||||
blogPostsFullModal.addEventListener('click', function(e) {
|
|
||||||
if (e.target === blogPostsFullModal) {
|
|
||||||
closeFullModal();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// ESC 键关闭
|
|
||||||
document.addEventListener('keydown', function(e) {
|
|
||||||
if (e.key === 'Escape' && blogPostsFullModal.classList.contains('active')) {
|
|
||||||
closeFullModal();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 页面加载时自动加载数据(用于 Hover 预览)
|
// 页面加载时自动加载数据(用于 Hover 预览)
|
||||||
loadBlogPosts(true);
|
loadBlogPosts(true);
|
||||||
}
|
}
|
||||||
@@ -94,12 +80,13 @@
|
|||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
const posts = result.data.dataSet || [];
|
const posts = result.data.dataSet || [];
|
||||||
const totalPages = result.data.pages || 0;
|
const totalPages = result.data.pages || 0;
|
||||||
|
const totalCount = result.data.count || 0; // 获取总数
|
||||||
|
|
||||||
// 判断是否还有更多数据
|
// 判断是否还有更多数据
|
||||||
hasMore = currentPage < totalPages;
|
hasMore = currentPage < totalPages;
|
||||||
|
|
||||||
if (reset) {
|
if (reset) {
|
||||||
renderPosts(posts, true);
|
renderPosts(posts, true, totalCount);
|
||||||
} else {
|
} else {
|
||||||
appendPosts(posts);
|
appendPosts(posts);
|
||||||
}
|
}
|
||||||
@@ -117,7 +104,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 渲染文章列表
|
// 渲染文章列表
|
||||||
function renderPosts(posts, isReset = false) {
|
function renderPosts(posts, isReset = false, totalCount = 0) {
|
||||||
let html = '';
|
let html = '';
|
||||||
|
|
||||||
if (!posts || posts.length === 0) {
|
if (!posts || posts.length === 0) {
|
||||||
@@ -138,6 +125,18 @@
|
|||||||
|
|
||||||
if (blogPostsList) blogPostsList.innerHTML = html;
|
if (blogPostsList) blogPostsList.innerHTML = html;
|
||||||
if (blogPostsFullBody) blogPostsFullBody.innerHTML = html;
|
if (blogPostsFullBody) blogPostsFullBody.innerHTML = html;
|
||||||
|
|
||||||
|
// 更新预览面板标题显示总数
|
||||||
|
const previewHeader = document.querySelector('#blog-posts-preview .preview-header h3');
|
||||||
|
if (previewHeader && totalCount > 0) {
|
||||||
|
const lang = localStorage.getItem('preferred_language') || 'zh';
|
||||||
|
const translations = {
|
||||||
|
'zh': typeof translationsZH !== 'undefined' ? translationsZH : {},
|
||||||
|
'en': typeof translationsEN !== 'undefined' ? translationsEN : {}
|
||||||
|
};
|
||||||
|
const titleText = translations[lang]['blog_latest_posts'] || '最新文章';
|
||||||
|
previewHeader.textContent = `${titleText} (${totalCount})`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 追加文章
|
// 追加文章
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// Flask API 地址
|
// Flask API 地址
|
||||||
const API_BASE_URL = 'http://localhost:8360';
|
const API_BASE_URL = 'https://comments.iletter.top';
|
||||||
const PAGE_SIZE = 10;
|
const PAGE_SIZE = 10;
|
||||||
|
|
||||||
let currentPage = 1;
|
let currentPage = 1;
|
||||||
@@ -63,20 +63,6 @@
|
|||||||
// 关闭按钮
|
// 关闭按钮
|
||||||
guestbookClose.addEventListener('click', closeFullModal);
|
guestbookClose.addEventListener('click', closeFullModal);
|
||||||
|
|
||||||
// 点击背景关闭
|
|
||||||
guestbookFullModal.addEventListener('click', function(e) {
|
|
||||||
if (e.target === guestbookFullModal) {
|
|
||||||
closeFullModal();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// ESC 键关闭
|
|
||||||
document.addEventListener('keydown', function(e) {
|
|
||||||
if (e.key === 'Escape' && guestbookFullModal.classList.contains('active')) {
|
|
||||||
closeFullModal();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 预览面板触底加载
|
// 预览面板触底加载
|
||||||
if (previewBody) {
|
if (previewBody) {
|
||||||
previewBody.addEventListener('scroll', handlePreviewScroll);
|
previewBody.addEventListener('scroll', handlePreviewScroll);
|
||||||
@@ -217,12 +203,29 @@
|
|||||||
guestbookBody.innerHTML = html;
|
guestbookBody.innerHTML = html;
|
||||||
|
|
||||||
// 同时更新 Hover 预览面板(使用累积的所有留言)
|
// 同时更新 Hover 预览面板(使用累积的所有留言)
|
||||||
renderPreview(allComments);
|
renderPreview(allComments, totalCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 渲染 Hover 预览面板
|
// 渲染 Hover 预览面板
|
||||||
function renderPreview(comments) {
|
function renderPreview(comments, totalCount = 0) {
|
||||||
if (!previewBody || !comments || comments.length === 0) return;
|
if (!previewBody) return;
|
||||||
|
|
||||||
|
if (!comments || comments.length === 0) {
|
||||||
|
previewBody.innerHTML = '<div class="guestbook-empty">暂无留言,快来抢沙发吧!</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新标题显示总数
|
||||||
|
const previewHeader = document.querySelector('#guestbook-preview .preview-header h3');
|
||||||
|
if (previewHeader && totalCount > 0) {
|
||||||
|
const lang = localStorage.getItem('preferred_language') || 'zh';
|
||||||
|
const translations = {
|
||||||
|
'zh': typeof translationsZH !== 'undefined' ? translationsZH : {},
|
||||||
|
'en': typeof translationsEN !== 'undefined' ? translationsEN : {}
|
||||||
|
};
|
||||||
|
const titleText = translations[lang]['guestbook_title'] || '留言板';
|
||||||
|
previewHeader.textContent = `${titleText} (${totalCount})`;
|
||||||
|
}
|
||||||
|
|
||||||
let html = '';
|
let html = '';
|
||||||
comments.forEach(comment => {
|
comments.forEach(comment => {
|
||||||
@@ -305,7 +308,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 更新预览面板
|
// 更新预览面板
|
||||||
renderPreview(allComments);
|
renderPreview(allComments, allComments.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 渲染单个留言项
|
// 渲染单个留言项
|
||||||
|
|||||||
@@ -73,5 +73,8 @@ const translationsEN = {
|
|||||||
// Top-right buttons
|
// Top-right buttons
|
||||||
btn_guestbook: "Guestbook",
|
btn_guestbook: "Guestbook",
|
||||||
btn_blog_posts: "Blog Posts",
|
btn_blog_posts: "Blog Posts",
|
||||||
|
btn_photo_album: "Travel Shadows",
|
||||||
blog_latest_posts: "Latest Posts",
|
blog_latest_posts: "Latest Posts",
|
||||||
|
photo_latest_photos: "Latest Photos",
|
||||||
|
photo_album_title: "Travel Shadows",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -73,5 +73,8 @@ const translationsZH = {
|
|||||||
// 右上角按钮
|
// 右上角按钮
|
||||||
btn_guestbook: "留言板",
|
btn_guestbook: "留言板",
|
||||||
btn_blog_posts: "博客文章",
|
btn_blog_posts: "博客文章",
|
||||||
|
btn_photo_album: "旅途剪影",
|
||||||
blog_latest_posts: "最新文章",
|
blog_latest_posts: "最新文章",
|
||||||
|
photo_latest_photos: "最新照片",
|
||||||
|
photo_album_title: "旅途剪影",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
|
|
||||||
let html = "";
|
let html = "";
|
||||||
memos.forEach((memo) => {
|
memos.forEach((memo) => {
|
||||||
const formattedDate = formatDate(memo.displayTime);
|
const formattedDate = formatDate(memo.createTime);
|
||||||
const tagsHtml =
|
const tagsHtml =
|
||||||
memo.tags && memo.tags.length > 0
|
memo.tags && memo.tags.length > 0
|
||||||
? `<div class="memo-tags">${memo.tags
|
? `<div class="memo-tags">${memo.tags
|
||||||
|
|||||||
320
static/js/photo_album.js
Normal file
@@ -0,0 +1,320 @@
|
|||||||
|
// ==================== 旅途剪影功能 ====================
|
||||||
|
(function() {
|
||||||
|
// DOM 元素
|
||||||
|
const photoAlbumBtn = document.getElementById('photo-album-btn');
|
||||||
|
const photoAlbumPreview = document.getElementById('photo-album-preview');
|
||||||
|
const photoAlbumFullModal = document.getElementById('photo-album-full-modal');
|
||||||
|
const photoAlbumClose = document.getElementById('photo-album-close');
|
||||||
|
const photoAlbumList = document.querySelector('.photo-album-list');
|
||||||
|
const photoAlbumBody = document.querySelector('#photo-album-full-modal .photo-album-body');
|
||||||
|
|
||||||
|
// 照片数据
|
||||||
|
let allPhotos = [];
|
||||||
|
let currentPhotoIndex = 0; // 当前查看的照片索引
|
||||||
|
const PHOTO_BASE_PATH = './static/img/photos/';
|
||||||
|
|
||||||
|
// 分页加载配置
|
||||||
|
const PAGE_SIZE = 20; // 每页加载数量
|
||||||
|
let currentPage = 1; // 当前页码
|
||||||
|
let hasMore = true; // 是否还有更多数据
|
||||||
|
let isLoading = false; // 是否正在加载中
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
function init() {
|
||||||
|
if (!photoAlbumBtn || !photoAlbumFullModal) return;
|
||||||
|
|
||||||
|
// 点击按钮打开完整弹窗
|
||||||
|
photoAlbumBtn.addEventListener('click', openFullModal);
|
||||||
|
|
||||||
|
// 关闭按钮
|
||||||
|
photoAlbumClose.addEventListener('click', closeFullModal);
|
||||||
|
|
||||||
|
// 页面加载时自动加载照片(用于 Hover 预览)
|
||||||
|
loadPhotos(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载照片
|
||||||
|
async function loadPhotos(reset = false) {
|
||||||
|
if (reset) {
|
||||||
|
if (photoAlbumList) photoAlbumList.innerHTML = '<div class="guestbook-loading">正在加载照片...</div>';
|
||||||
|
if (photoAlbumBody) photoAlbumBody.innerHTML = '<div class="guestbook-loading">正在加载照片...</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 从 JSON 文件获取照片列表
|
||||||
|
const response = await fetch(`${PHOTO_BASE_PATH}photos.json`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to load photos.json');
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
const photoFiles = data.photos || [];
|
||||||
|
|
||||||
|
allPhotos = photoFiles.map((filename, index) => ({
|
||||||
|
id: index + 1,
|
||||||
|
src: `${PHOTO_BASE_PATH}${encodeURIComponent(filename)}`,
|
||||||
|
filename: filename
|
||||||
|
}));
|
||||||
|
|
||||||
|
renderPhotos(allPhotos, reset);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载照片失败:', error);
|
||||||
|
if (reset) {
|
||||||
|
const errorMsg = '<div class="guestbook-empty">加载照片失败,请稍后重试</div>';
|
||||||
|
if (photoAlbumList) photoAlbumList.innerHTML = errorMsg;
|
||||||
|
if (photoAlbumBody) photoAlbumBody.innerHTML = errorMsg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染照片列表
|
||||||
|
function renderPhotos(photos, isReset = false) {
|
||||||
|
let html = '';
|
||||||
|
|
||||||
|
if (!photos || photos.length === 0) {
|
||||||
|
html = '<div class="guestbook-empty">暂无照片</div>';
|
||||||
|
} else {
|
||||||
|
// 预览面板:显示最新的几张照片
|
||||||
|
if (isReset && photoAlbumList) {
|
||||||
|
const previewPhotos = photos.slice(0, 5); // 只显示前5张
|
||||||
|
let previewHtml = '<div class="photo-album-container">';
|
||||||
|
previewPhotos.forEach((photo, index) => {
|
||||||
|
previewHtml += `
|
||||||
|
<div class="photo-preview-item" onclick="window.openPhotoLightbox(${index})">
|
||||||
|
<img src="${photo.src}" alt="照片 ${photo.id}" loading="lazy">
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
previewHtml += '</div>';
|
||||||
|
photoAlbumList.innerHTML = previewHtml;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 完整弹窗:瀑布流布局显示当前页的照片
|
||||||
|
const startIndex = (currentPage - 1) * PAGE_SIZE;
|
||||||
|
const endIndex = Math.min(currentPage * PAGE_SIZE, photos.length);
|
||||||
|
const currentPhotos = photos.slice(startIndex, endIndex);
|
||||||
|
|
||||||
|
// 首次渲染:创建完整的 HTML
|
||||||
|
html = '<div class="photo-album-grid">';
|
||||||
|
currentPhotos.forEach((photo, index) => {
|
||||||
|
const globalIndex = startIndex + index;
|
||||||
|
html += `
|
||||||
|
<div class="photo-album-item" onclick="window.openPhotoLightbox(${globalIndex})">
|
||||||
|
<img src="${photo.src}" alt="照片 ${photo.id}" loading="lazy" onload="this.style.opacity=1">
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
// 分页导航
|
||||||
|
const totalPages = Math.ceil(photos.length / PAGE_SIZE);
|
||||||
|
if (totalPages > 1) {
|
||||||
|
html += '<div class="pagination-container">';
|
||||||
|
|
||||||
|
// 上一页
|
||||||
|
if (currentPage > 1) {
|
||||||
|
html += `<button class="page-btn" onclick="window.goToPhotoPage(${currentPage - 1})"><</button>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页码
|
||||||
|
for (let i = 1; i <= totalPages; i++) {
|
||||||
|
if (i === currentPage) {
|
||||||
|
html += `<span class="page-num active">${i}</span>`;
|
||||||
|
} else {
|
||||||
|
// 只显示当前页附近的页码,避免太多
|
||||||
|
if (i === 1 || i === totalPages || (i >= currentPage - 2 && i <= currentPage + 2)) {
|
||||||
|
html += `<span class="page-num" onclick="window.goToPhotoPage(${i})">${i}</span>`;
|
||||||
|
} else if (i === currentPage - 3 || i === currentPage + 3) {
|
||||||
|
html += `<span class="page-dots">...</span>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下一页
|
||||||
|
if (currentPage < totalPages) {
|
||||||
|
html += `<button class="page-btn" onclick="window.goToPhotoPage(${currentPage + 1})">></button>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
html += '</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (photoAlbumBody) photoAlbumBody.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开完整弹窗
|
||||||
|
function openFullModal() {
|
||||||
|
photoAlbumFullModal.classList.add('active');
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
|
||||||
|
// 重置分页状态
|
||||||
|
currentPage = 1;
|
||||||
|
hasMore = allPhotos.length > PAGE_SIZE;
|
||||||
|
|
||||||
|
// 清空弹窗内容,避免重复渲染
|
||||||
|
if (photoAlbumBody) photoAlbumBody.innerHTML = '';
|
||||||
|
|
||||||
|
renderPhotos(allPhotos, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭完整弹窗
|
||||||
|
function closeFullModal() {
|
||||||
|
photoAlbumFullModal.classList.remove('active');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开照片放大查看器
|
||||||
|
window.openPhotoLightbox = function(photoIndex) {
|
||||||
|
currentPhotoIndex = photoIndex;
|
||||||
|
|
||||||
|
// 检查是否已经存在 lightbox
|
||||||
|
let lightbox = document.querySelector('.photo-lightbox');
|
||||||
|
if (!lightbox) {
|
||||||
|
lightbox = document.createElement('div');
|
||||||
|
lightbox.className = 'photo-lightbox';
|
||||||
|
lightbox.innerHTML = `
|
||||||
|
<span class="photo-lightbox-close">×</span>
|
||||||
|
<span class="photo-lightbox-nav photo-lightbox-prev">❮</span>
|
||||||
|
<img src="" alt="放大照片">
|
||||||
|
<span class="photo-lightbox-nav photo-lightbox-next">❯</span>
|
||||||
|
<div class="photo-lightbox-counter"></div>
|
||||||
|
`;
|
||||||
|
document.body.appendChild(lightbox);
|
||||||
|
|
||||||
|
// 点击关闭按钮
|
||||||
|
lightbox.querySelector('.photo-lightbox-close').addEventListener('click', closePhotoLightbox);
|
||||||
|
|
||||||
|
// 点击背景关闭
|
||||||
|
lightbox.addEventListener('click', function(e) {
|
||||||
|
if (e.target === lightbox) {
|
||||||
|
closePhotoLightbox();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 上一张按钮
|
||||||
|
lightbox.querySelector('.photo-lightbox-prev').addEventListener('click', function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
showPreviousPhoto();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 下一张按钮
|
||||||
|
lightbox.querySelector('.photo-lightbox-next').addEventListener('click', function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
showNextPhoto();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 键盘事件
|
||||||
|
document.addEventListener('keydown', handleLightboxKeydown);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新图片显示
|
||||||
|
updateLightboxImage();
|
||||||
|
lightbox.classList.add('active');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更新 Lightbox 图片
|
||||||
|
function updateLightboxImage() {
|
||||||
|
const lightbox = document.querySelector('.photo-lightbox');
|
||||||
|
if (!lightbox || !allPhotos[currentPhotoIndex]) return;
|
||||||
|
|
||||||
|
const img = lightbox.querySelector('img');
|
||||||
|
const counter = lightbox.querySelector('.photo-lightbox-counter');
|
||||||
|
|
||||||
|
// 淡出效果
|
||||||
|
img.style.opacity = '0';
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
img.src = allPhotos[currentPhotoIndex].src;
|
||||||
|
img.alt = `照片 ${currentPhotoIndex + 1}`;
|
||||||
|
|
||||||
|
// 更新计数器
|
||||||
|
if (counter) {
|
||||||
|
counter.textContent = `${currentPhotoIndex + 1} / ${allPhotos.length}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 淡入效果
|
||||||
|
img.onload = () => {
|
||||||
|
img.style.opacity = '1';
|
||||||
|
};
|
||||||
|
}, 150);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示上一张照片
|
||||||
|
function showPreviousPhoto() {
|
||||||
|
if (currentPhotoIndex > 0) {
|
||||||
|
currentPhotoIndex--;
|
||||||
|
} else {
|
||||||
|
currentPhotoIndex = allPhotos.length - 1; // 循环到最后一张
|
||||||
|
}
|
||||||
|
updateLightboxImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示下一张照片
|
||||||
|
function showNextPhoto() {
|
||||||
|
if (currentPhotoIndex < allPhotos.length - 1) {
|
||||||
|
currentPhotoIndex++;
|
||||||
|
} else {
|
||||||
|
currentPhotoIndex = 0; // 循环到第一张
|
||||||
|
}
|
||||||
|
updateLightboxImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理键盘事件
|
||||||
|
function handleLightboxKeydown(e) {
|
||||||
|
if (e.key === 'ArrowLeft') {
|
||||||
|
showPreviousPhoto();
|
||||||
|
} else if (e.key === 'ArrowRight') {
|
||||||
|
showNextPhoto();
|
||||||
|
} else if (e.key === 'Escape') {
|
||||||
|
closePhotoLightbox();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭照片放大查看器
|
||||||
|
function closePhotoLightbox() {
|
||||||
|
const lightbox = document.querySelector('.photo-lightbox');
|
||||||
|
if (lightbox) {
|
||||||
|
lightbox.classList.remove('active');
|
||||||
|
|
||||||
|
// 移除键盘事件监听器
|
||||||
|
document.removeEventListener('keydown', handleLightboxKeydown);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
lightbox.remove();
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载更多照片
|
||||||
|
window.loadMorePhotos = function() {
|
||||||
|
if (isLoading || !hasMore) return;
|
||||||
|
|
||||||
|
isLoading = true;
|
||||||
|
currentPage++;
|
||||||
|
|
||||||
|
// 判断是否还有更多数据
|
||||||
|
hasMore = currentPage * PAGE_SIZE < allPhotos.length;
|
||||||
|
|
||||||
|
renderPhotos(allPhotos, false);
|
||||||
|
isLoading = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 跳转到指定页码
|
||||||
|
window.goToPhotoPage = function(page) {
|
||||||
|
if (page < 1 || page > Math.ceil(allPhotos.length / PAGE_SIZE)) return;
|
||||||
|
currentPage = page;
|
||||||
|
renderPhotos(allPhotos, false);
|
||||||
|
// 滚动到顶部
|
||||||
|
if (photoAlbumBody) photoAlbumBody.scrollTop = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// DOM 加载完成后初始化
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', init);
|
||||||
|
} else {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
})();
|
||||||