11
10
index.html
@@ -107,15 +107,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 人生相册按钮 -->
|
||||
<!-- 旅途剪影按钮 -->
|
||||
<div class="photo-album-btn-container">
|
||||
<button id="photo-album-btn" class="photo-album-btn" title="人生相册">
|
||||
<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>
|
||||
<span class="btn-label" data-i18n="btn_photo_album">旅途剪影</span>
|
||||
</button>
|
||||
|
||||
<!-- Hover 浮动预览面板 -->
|
||||
@@ -129,11 +129,11 @@
|
||||
</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>
|
||||
<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">
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
# 人生相册构建脚本
|
||||
|
||||
## 📖 功能说明
|
||||
|
||||
自动扫描 `/static/img/photos/` 目录下的所有图片文件,生成 `photos.json` 索引文件。
|
||||
|
||||
## 🚀 使用方法
|
||||
|
||||
### 方法一:双击运行(推荐)
|
||||
|
||||
直接双击 `build-photos.bat` 文件即可。
|
||||
|
||||
### 方法二:命令行运行
|
||||
|
||||
```bash
|
||||
# 在项目根目录下执行
|
||||
python scripts/generate-photos-json.py
|
||||
```
|
||||
|
||||
## 📝 工作流程
|
||||
|
||||
1. 将新照片放入 `/static/img/photos/` 目录
|
||||
2. 运行构建脚本(双击 `build-photos.bat`)
|
||||
3. 脚本会自动:
|
||||
- 扫描目录下所有图片文件(支持 jpg, png, gif, webp, bmp, svg)
|
||||
- 按文件名排序
|
||||
- 生成/更新 `photos.json` 文件
|
||||
4. 刷新网页,新照片自动显示
|
||||
|
||||
## 📂 生成的 JSON 格式
|
||||
|
||||
```json
|
||||
{
|
||||
"photos": [
|
||||
"1 (1).jpg",
|
||||
"1 (2).jpg",
|
||||
"..."
|
||||
],
|
||||
"total": 13,
|
||||
"lastUpdated": "2026-03-03 15:30:00"
|
||||
}
|
||||
```
|
||||
|
||||
## ⚙️ 配置说明
|
||||
|
||||
支持的图片格式:
|
||||
- `.jpg` / `.jpeg`
|
||||
- `.png`
|
||||
- `.gif`
|
||||
- `.webp`
|
||||
- `.bmp`
|
||||
- `.svg`
|
||||
|
||||
如需添加其他格式,编辑 `generate-photos-json.py` 中的 `IMAGE_EXTENSIONS` 集合。
|
||||
|
||||
## 💡 提示
|
||||
|
||||
- 照片会按文件名**字母顺序**排序
|
||||
- 建议使用数字前缀控制顺序,如:`001.jpg`, `002.jpg`
|
||||
- 脚本会自动排除 `photos.json` 文件本身
|
||||
- 每次运行都会重新扫描并覆盖旧的 JSON 文件
|
||||
@@ -9,8 +9,10 @@ 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("/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")
|
||||
# ================================================
|
||||
|
||||
# 支持的图片格式
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
/* 人生相册按钮容器 */
|
||||
/* 旅途剪影按钮容器 */
|
||||
.photo-album-btn-container {
|
||||
position: fixed;
|
||||
top: 210px;
|
||||
@@ -808,7 +808,7 @@
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
/* 人生相册预览面板样式 - 列表模式 */
|
||||
/* 旅途剪影预览面板样式 - 列表模式 */
|
||||
.photo-album-container {
|
||||
padding: 10px;
|
||||
}
|
||||
@@ -832,7 +832,7 @@
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* 人生相册瀑布流布局 */
|
||||
/* 旅途剪影瀑布流布局 */
|
||||
.photo-album-grid {
|
||||
column-count: 3;
|
||||
column-gap: 15px;
|
||||
@@ -863,6 +863,8 @@
|
||||
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
background-color: #f0f0f0; /* 占位背景色 */
|
||||
min-height: 200px; /* 最小高度,防止塌陷 */
|
||||
}
|
||||
|
||||
.photo-album-item:hover {
|
||||
@@ -874,7 +876,8 @@
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
opacity: 0; /* 初始隐藏,加载完再显示 */
|
||||
transition: opacity 0.5s ease, transform 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.photo-album-item:hover img {
|
||||
|
||||
BIN
static/img/photos/0c49fd33f4e83dcedde820d858ec44e9.jpg
Normal file
|
After Width: | Height: | Size: 467 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 |
@@ -1,19 +1,43 @@
|
||||
{
|
||||
"photos": [
|
||||
"1 (1).jpg",
|
||||
"1 (2).jpg",
|
||||
"1 (3).jpg",
|
||||
"1 (4).jpg",
|
||||
"1 (5).jpg",
|
||||
"1 (6).jpg",
|
||||
"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 (8).jpg",
|
||||
"1 (9).jpg",
|
||||
"1 (10).jpg",
|
||||
"1 (11).jpg",
|
||||
"1 (12).jpg",
|
||||
"1 (13).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": 13,
|
||||
"lastUpdated": "2026-05-18 01:57:45"
|
||||
"total": 37,
|
||||
"lastUpdated": "2026-05-18 02:36:56"
|
||||
}
|
||||
@@ -73,8 +73,8 @@ const translationsEN = {
|
||||
// Top-right buttons
|
||||
btn_guestbook: "Guestbook",
|
||||
btn_blog_posts: "Blog Posts",
|
||||
btn_photo_album: "Photo Album",
|
||||
btn_photo_album: "Travel Shadows",
|
||||
blog_latest_posts: "Latest Posts",
|
||||
photo_latest_photos: "Latest Photos",
|
||||
photo_album_title: "Photo Album",
|
||||
photo_album_title: "Travel Shadows",
|
||||
};
|
||||
|
||||
@@ -73,8 +73,8 @@ const translationsZH = {
|
||||
// 右上角按钮
|
||||
btn_guestbook: "留言板",
|
||||
btn_blog_posts: "博客文章",
|
||||
btn_photo_album: "人生相册",
|
||||
btn_photo_album: "旅途剪影",
|
||||
blog_latest_posts: "最新文章",
|
||||
photo_latest_photos: "最新照片",
|
||||
photo_album_title: "人生相册",
|
||||
photo_album_title: "旅途剪影",
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// ==================== 人生相册功能 ====================
|
||||
// ==================== 旅途剪影功能 ====================
|
||||
(function() {
|
||||
// DOM 元素
|
||||
const photoAlbumBtn = document.getElementById('photo-album-btn');
|
||||
@@ -13,6 +13,12 @@
|
||||
let currentPhotoIndex = 0; // 当前查看的照片索引
|
||||
const PHOTO_BASE_PATH = './static/img/photos/';
|
||||
|
||||
// 分页加载配置
|
||||
const PAGE_SIZE = 10; // 每页加载数量
|
||||
let currentPage = 1; // 当前页码
|
||||
let hasMore = true; // 是否还有更多数据
|
||||
let isLoading = false; // 是否正在加载中
|
||||
|
||||
// 初始化
|
||||
function init() {
|
||||
if (!photoAlbumBtn || !photoAlbumFullModal) return;
|
||||
@@ -84,16 +90,65 @@
|
||||
photoAlbumList.innerHTML = previewHtml;
|
||||
}
|
||||
|
||||
// 完整弹窗:瀑布流布局显示所有照片
|
||||
// 完整弹窗:瀑布流布局显示当前页的照片
|
||||
const startIndex = (currentPage - 1) * PAGE_SIZE;
|
||||
const endIndex = Math.min(currentPage * PAGE_SIZE, photos.length);
|
||||
const currentPhotos = photos.slice(startIndex, endIndex);
|
||||
|
||||
// 如果不是重置,先获取已有的网格容器
|
||||
const existingGrid = !isReset ? document.querySelector('.photo-album-grid') : null;
|
||||
|
||||
if (existingGrid && !isReset) {
|
||||
// 1. 彻底清理旧的按钮和提示
|
||||
const oldControls = document.querySelectorAll('.load-more-btn, .load-complete');
|
||||
oldControls.forEach(el => el.remove());
|
||||
|
||||
// 2. 追加新照片到现有网格
|
||||
currentPhotos.forEach((photo, index) => {
|
||||
const globalIndex = startIndex + index;
|
||||
const div = document.createElement('div');
|
||||
div.className = 'photo-album-item';
|
||||
div.onclick = () => window.openPhotoLightbox(globalIndex);
|
||||
div.innerHTML = `
|
||||
<img src="${photo.src}" alt="照片 ${photo.id}" loading="lazy" onload="this.style.opacity=1">
|
||||
`;
|
||||
existingGrid.appendChild(div);
|
||||
});
|
||||
|
||||
// 3. 添加新的按钮/提示
|
||||
if (hasMore) {
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'load-more-btn';
|
||||
btn.textContent = '点击加载更多';
|
||||
btn.onclick = window.loadMorePhotos;
|
||||
existingGrid.after(btn);
|
||||
} else {
|
||||
const tip = document.createElement('div');
|
||||
tip.className = 'load-complete';
|
||||
tip.textContent = '已显示全部';
|
||||
existingGrid.after(tip);
|
||||
}
|
||||
return; // 提前返回
|
||||
}
|
||||
|
||||
// 首次渲染:创建完整的 HTML
|
||||
html = '<div class="photo-album-grid">';
|
||||
photos.forEach((photo, index) => {
|
||||
currentPhotos.forEach((photo, index) => {
|
||||
const globalIndex = startIndex + index;
|
||||
html += `
|
||||
<div class="photo-album-item" onclick="window.openPhotoLightbox(${index})">
|
||||
<img src="${photo.src}" alt="照片 ${photo.id}" loading="lazy">
|
||||
<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>';
|
||||
|
||||
// 添加加载更多按钮或提示
|
||||
if (hasMore) {
|
||||
html += `<button class="load-more-btn" onclick="window.loadMorePhotos()">点击加载更多</button>`;
|
||||
} else {
|
||||
html += '<div class="load-complete">已显示全部</div>';
|
||||
}
|
||||
}
|
||||
|
||||
if (photoAlbumBody) photoAlbumBody.innerHTML = html;
|
||||
@@ -103,6 +158,15 @@
|
||||
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);
|
||||
}
|
||||
|
||||
// 关闭完整弹窗
|
||||
@@ -233,6 +297,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 加载更多照片
|
||||
window.loadMorePhotos = function() {
|
||||
if (isLoading || !hasMore) return;
|
||||
|
||||
isLoading = true;
|
||||
currentPage++;
|
||||
|
||||
// 判断是否还有更多数据
|
||||
hasMore = currentPage * PAGE_SIZE < allPhotos.length;
|
||||
|
||||
renderPhotos(allPhotos, false);
|
||||
isLoading = false;
|
||||
};
|
||||
|
||||
|
||||
|
||||
// DOM 加载完成后初始化
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
|
||||