[Asm] 纯文本查看 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>❤️ 我们的爱情故事相册 ❤❤️</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Pacifico&family=Dancing+Script:wght@400;700&display=swap" rel="stylesheet">
<style>
:root {
--primary-pink: #ff6b8b;
--secondary-pink: #ffa5b5;
--light-pink: #fff5f7;
--gold: #ffd700;
--purple: #9c88ff;
--deep-pink: #ff1493;
--rose-red: #e91e63;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
min-height: 100vh;
font-family: 'Microsoft YaHei', sans-serif;
overflow-x: hidden;
}
/* 粒子背景效果 */
#particles-js {
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 0;
}
.main-container {
position: relative;
z-index: 1;
}
/* 浪漫标题样式 */
.romantic-header {
background: linear-gradient(45deg, var(--primary-pink), var(--purple), var(--deep-pink));
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 0 4px 15px rgba(0,0,0,0.1);
margin: 2rem 0;
font-weight: 700;
}
.romantic-title {
font-family: 'Pacifico', cursive;
font-size: 3.5rem;
margin-bottom: 0.5rem;
}
.romantic-subtitle {
font-family: 'Dancing Script', cursive;
font-size: 2rem;
color: var(--primary-pink);
margin-bottom: 2rem;
}
/* 音乐播放器 */
.music-player {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 25px;
padding: 10px 15px;
box-shadow: 0 5px 20px rgba(0,0,0,0.1);
display: flex;
align-items: center;
gap: 10px;
}
.music-player button {
background: linear-gradient(45deg, var(--primary-pink), var(--purple));
border: none;
border-radius: 20px;
padding: 8px 20px;
color: white;
font-weight: 600;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(255,107,139,0.4);
}
.music-player button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(255,107,139,0.6);
}
/* 相册卡片样式 */
.album-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border: none;
border-radius: 20px;
box-shadow: 0 15px 35px rgba(0,0,0,0.1);
transition: all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
overflow: hidden;
height: 100%;
margin-bottom: 2rem;
}
.album-card:hover {
transform: translateY(-15px) scale(1.02);
box-shadow: 0 25px 50px rgba(0,0,0,0.2);
}
/* 3D样式相册 */
.album-style-3d {
transform-style: preserve-3d;
perspective: 1000px;
}
.album-style-3d:hover {
transform: rotateY(10deg) translateY(-10px);
}
/* 杂志样式相册 */
.album-style-magazine {
border-top: 5px solid var(--primary-pink);
border-left: 3px solid var(--gold);
}
.album-style-magazine .album-header {
background: linear-gradient(45deg, #667eea, #764ba2);
color: white;
padding: 15px;
}
/* 网格样式相册 */
.album-style-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 5px;
padding: 10px;
}
.album-style-grid .album-thumbnail {
border-radius: 8px;
overflow: hidden;
}
/* 轮播样式相册 */
.album-style-carousel {
position: relative;
overflow: hidden;
}
.album-style-carousel .album-images {
display: flex;
transition: transform 0.5s ease;
}
/* 瀑布流样式 */
.album-style-masonry {
columns: 2;
column-gap: 10px;
}
.album-style-masonry .album-thumbnail {
break-inside: avoid;
margin-bottom: 10px;
}
/* 拍立得样式 */
.album-style-polaroid {
background: white;
padding: 15px 15px 40px 15px;
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}
.album-style-polaroid .album-cover {
border: 1px solid #ddd;
box-shadow: inset 0 0 10px rgba(0,0,0,0.1);
}
/* 相册封面 */
.album-cover {
position: relative;
width: 100%;
height: 200px;
overflow: hidden;
border-radius: 15px 15px 0 0;
}
.album-cover img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.5s ease;
}
.album-card:hover .album-cover img {
transform: scale(1.1);
}
/* 相册信息 */
.album-info {
padding: 20px;
}
.album-title {
font-family: 'Dancing Script', cursive;
font-size: 1.8rem;
color: var(--deep-pink);
margin-bottom: 10px;
font-weight: 700;
}
.album-description {
color: #666;
font-size: 0.95rem;
line-height: 1.6;
margin-bottom: 15px;
}
.album-meta {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 15px;
padding-top: 15px;
border-top: 1px solid #eee;
}
.album-date {
color: #999;
font-size: 0.85rem;
}
.album-photo-count {
background: var(--light-pink);
color: var(--primary-pink);
padding: 4px 12px;
border-radius: 20px;
font-size: 0.85rem;
}
/* 双击提示样式 */
.double-click-hint {
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid #eee;
text-align: center;
font-size: 0.8rem;
color: #999;
}
/* 故事卡片 */
.story-card {
background: linear-gradient(135deg, #ffeef2, #ffffff);
border: none;
border-radius: 15px;
padding: 25px;
margin-bottom: 2rem;
box-shadow: 0 5px 20px rgba(0,0,0,0.08);
transition: all 0.3s ease;
height: 100%;
}
.story-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 30px rgba(0,0,0,0.15);
}
.story-title {
font-family: 'Dancing Script', cursive;
font-size: 1.8rem;
color: var(--primary-pink);
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 10px;
}
.story-content {
color: #555;
line-height: 1.8;
font-size: 1.05rem;
}
.story-date {
color: #999;
font-size: 0.85rem;
margin-top: 15px;
}
/* 操作按钮 */
.action-buttons {
margin-top: 3rem;
text-align: center;
}
.romantic-btn {
background: linear-gradient(45deg, var(--primary-pink), var(--purple));
border: none;
border-radius: 25px;
padding: 12px 30px;
color: white;
font-weight: 600;
transition: all 0.3s ease;
box-shadow: 0 5px 20px rgba(255,107,139,0.4);
margin: 0 10px 10px 10px;
}
.romantic-btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(255,107,139,0.6);
color: white;
}
.romantic-btn-outline {
background: transparent;
border: 2px solid var(--primary-pink);
color: var(--primary-pink);
}
.romantic-btn-outline:hover {
background: linear-gradient(45deg, var(--primary-pink), var(--purple));
color: white;
}
/* 标签样式 */
.tag-cloud {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin: 2rem 0;
justify-content: center;
}
.romantic-tag {
background: linear-gradient(45deg, var(--primary-pink), var(--purple));
color: white;
padding: 8px 20px;
border-radius: 20px;
font-size: 0.9rem;
box-shadow: 0 3px 10px rgba(255,107,139,0.3);
transition: all 0.3s ease;
}
.romantic-tag:hover {
transform: translateY(-2px);
}
/* 动画效果 */
@keyframes heartbeat {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.1); }
}
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
.heart-animation {
animation: heartbeat 1.5s ease-in-out infinite;
}
.float-animation {
animation: float 3s ease-in-out infinite;
}
/* 全屏浏览样式 */
.fullscreen-viewer {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.95);
z-index: 9999;
display: flex;
flex-direction: column;
animation: fadeIn 0.3s ease;
}
.fullscreen-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
background: rgba(0, 0, 0, 0.7);
color: white;
z-index: 10;
}
.header-content {
flex: 1;
}
.album-title-fullscreen {
font-family: 'Dancing Script', cursive;
font-size: 2.5rem;
color: var(--primary-pink);
margin-bottom: 5px;
}
.album-description-fullscreen {
color: #ccc;
margin-bottom: 10px;
}
.photo-counter {
font-size: 1.1rem;
color: var(--gold);
}
.btn-close-fullscreen {
background: none;
border: none;
color: white;
font-size: 1.5rem;
cursor: pointer;
padding: 10px;
border-radius: 50%;
transition: background 0.3s;
}
.btn-close-fullscreen:hover {
background: rgba(255, 255, 255, 0.1);
}
.fullscreen-content {
display: flex;
align-items: center;
justify-content: center;
flex: 1;
position: relative;
padding: 20px;
}
.nav-btn {
background: rgba(255, 255, 255, 0.2);
border: none;
color: white;
width: 60px;
height: 60px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
cursor: pointer;
transition: all 0.3s;
backdrop-filter: blur(10px);
margin: 0 20px;
}
.nav-btn:hover {
background: rgba(255, 107, 139, 0.8);
transform: scale(1.1);
}
.photo-viewer {
max-width: 80%;
max-height: 80%;
display: flex;
flex-direction: column;
align-items: center;
}
.photo-container {
max-width: 100%;
max-height: 70vh;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20px;
}
.photo-container img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
}
.photo-info {
text-align: center;
color: white;
max-width: 600px;
}
.photo-info h4 {
font-family: 'Dancing Script', cursive;
font-size: 1.8rem;
color: var(--primary-pink);
margin-bottom: 10px;
}
.photo-info p {
color: #ccc;
font-size: 1rem;
}
.photo-thumbnails {
display: flex;
justify-content: center;
padding: 20px;
background: rgba(0, 0, 0, 0.7);
overflow-x: auto;
gap: 10px;
}
.thumbnail {
width: 80px;
height: 80px;
border-radius: 5px;
overflow: hidden;
cursor: pointer;
opacity: 0.6;
transition: all 0.3s;
border: 2px solid transparent;
}
.thumbnail:hover, .thumbnail.active {
opacity: 1;
transform: scale(1.1);
border-color: var(--primary-pink);
}
.thumbnail img {
width: 100%;
height: 100%;
object-fit: cover;
}
.fullscreen-controls {
display: flex;
justify-content: center;
padding: 20px;
background: rgba(0, 0, 0, 0.7);
gap: 15px;
}
.control-btn {
background: linear-gradient(45deg, var(--primary-pink), var(--purple));
border: none;
border-radius: 25px;
padding: 10px 20px;
color: white;
cursor: pointer;
transition: all 0.3s;
display: flex;
align-items: center;
gap: 8px;
}
.control-btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(255, 107, 139, 0.4);
}
/* 动画效果 */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
/* 响应式调整 */
[url=home.php?mod=space&uid=945662]@media[/url] (max-width: 768px) {
.romantic-title {
font-size: 2.5rem;
}
.romantic-subtitle {
font-size: 1.5rem;
}
.music-player {
top: 10px;
right: 10px;
flex-direction: column;
align-items: center;
}
.album-card {
margin-bottom: 1.5rem;
}
.romantic-btn {
display: block;
width: 100%;
margin: 10px 0;
}
/* 全屏浏览响应式 */
.fullscreen-content {
flex-direction: column;
padding: 10px;
}
.nav-btn {
width: 50px;
height: 50px;
margin: 10px 0;
}
.photo-viewer {
max-width: 95%;
}
.album-title-fullscreen {
font-size: 1.8rem;
}
.thumbnail {
width: 60px;
height: 60px;
}
.fullscreen-controls {
flex-wrap: wrap;
}
.control-btn {
padding: 8px 15px;
font-size: 0.9rem;
}
}
</style>
</head>
<body>
<!-- 粒子背景 -->
<div id="particles-js"></div>
<!-- 音乐播放器 -->
<div class="music-player" id="musicPlayer">
<button onclick="toggleMusic()">
<i class="fas fa-music"></i>
<span id="musicText">播放</span>
</button>
<small id="musicTitle" class="text-muted"></small>
</div>
<div class="main-container">
<div class="container py-5">
<!-- 浪漫标题区 -->
<div class="text-center mb-5">
<h1 class="romantic-title romantic-header">
<i class="fas fa-heart heart-animation"></i>
我们的爱情故事
<i class="fas fa-heart heart-animation"></i>
</h1>
<h2 class="romantic-subtitle">记录每一个心动瞬间,珍藏每一份美好回忆</h2>
<!-- 标签云 -->
<div class="tag-cloud">
<span class="romantic-tag float-animation">相遇</span>
<span class="romantic-tag float-animation" style="animation-delay: 0.2s;">相爱</span>
<span class="romantic-tag float-animation" style="animation-delay: 0.4s;">相守</span>
<span class="romantic-tag float-animation" style="animation-delay: 0.6s;">永恒</span>
<span class="romantic-tag float-animation" style="animation-delay: 0.8s;">浪漫</span>
<span class="romantic-tag float-animation" style="animation-delay: 1s;">甜蜜</span>
</div>
</div>
<!-- 相册展示区 -->
<div class="row" id="albumsContainer">
<!-- 相册将通过JavaScript动态加载 -->
<div class="col-12 text-center py-5">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">加载中...</span>
</div>
<p class="mt-3">正在加载相册...</p>
</div>
</div>
<!-- 爱情故事区 -->
<div class="row mt-5">
<div class="col-12">
<h2 class="romantic-header text-center mb-4">
<i class="fas fa-book-heart me-2"></i>我们的爱情故事
</h2>
<div class="row" id="storiesContainer">
<!-- 故事将通过JavaScript动态加载 -->
</div>
</div>
</div>
<!-- 轮播预览区 -->
<div class="row mt-5">
<div class="col-12">
<div class="slideshow-preview bg-white rounded-3 p-4 text-center">
<h3 class="romantic-header mb-3">浪漫轮播展示</h3>
<p class="text-muted mb-4">点击下方按钮,全屏欣赏我们的爱情故事</p>
<div class="slideshow-preview-images mb-4">
<div id="previewCarousel" class="carousel slide" data-bs-ride="carousel">
<div class="carousel-inner rounded-3">
<!-- 轮播预览图片将通过JavaScript动态加载 -->
<div class="carousel-item active">
<div class="d-flex align-items-center justify-content-center bg-light rounded-3" style="height: 300px;">
<i class="fas fa-heart fa-5x text-muted"></i>
</div>
</div>
</div>
<button class="carousel-control-prev" type="button" data-bs-target="#previewCarousel" data-bs-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="visually-hidden">上一张</span>
</button>
<button class="carousel-control-next" type="button" data-bs-target="#previewCarousel" data-bs-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="visually-hidden">下一张</span>
</button>
</div>
</div>
<a href="/slideshow" class="btn romantic-btn btn-lg">
<i class="fas fa-play-circle me-2"></i>开始浪漫轮播
</a>
</div>
</div>
</div>
<!-- 行动按钮区 -->
<div class="text-center mt-5">
<a href="/admin" class="btn romantic-btn me-3">
<i class="fas fa-cog me-2"></i>管理后台
</a>
<button class="btn romantic-btn-outline" onclick="randomizeAlbumStyles()">
<i class="fas fa-random me-2"></i>随机样式
</button>
</div>
</div>
</div>
<!-- 相册详情模态框 -->
<div class="modal fade" id="albumModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header" style="background: linear-gradient(45deg, var(--primary-pink), var(--purple)); color: white;">
<h5 class="modal-title" id="albumModalTitle">相册详情</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="albumModalBody">
<!-- 相册内容将通过JavaScript动态加载 -->
</div>
</div>
</div>
</div>
<!-- 故事详情模态框 -->
<div class="modal fade" id="storyModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header" style="background: linear-gradient(45deg, var(--primary-pink), var(--purple)); color: white;">
<h5 class="modal-title" id="storyModalTitle">爱情故事</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="storyModalContent">
<!-- 故事内容将通过JavaScript动态加载 -->
</div>
</div>
</div>
</div>
<!-- 大图查看模态框 -->
<div class="modal fade" id="imageModal" tabindex="-1">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-body text-center p-0">
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/particles.js/2.0.0/particles.min.js"></script>
<script>
// 全局变量
let currentAudio = null;
let albums = [];
let stories = [];
let albumStyles = [
'standard', '3d', 'magazine', 'grid', 'carousel', 'masonry',
'polaroid', 'filmstrip', 'collage', 'timeline', 'gallery',
'slideshow', 'stack', 'flipbook', 'panorama', 'vintage',
'modern', 'minimal', 'elegant'
];
// 全屏浏览相关变量
let currentAlbum = null;
let currentPhotoIndex = 0;
let autoPlayInterval = null;
let clickTimer = null; // 用于处理单击/双击冲突
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
initParticles();
loadAlbums();
loadStories();
loadMusic();
});
// 初始化粒子背景
function initParticles() {
if (typeof particlesJS !== 'undefined') {
particlesJS('particles-js', {
particles: {
number: { value: 80, density: { enable: true, value_area: 800 } },
color: { value: ["#ff6b8b", "#9c88ff", "#ffd700", "#e91e63"] },
shape: { type: ["circle", "heart"] },
opacity: { value: 0.5, random: true },
size: { value: 3, random: true },
line_linked: {
enable: true,
distance: 150,
color: "#ff6b8b",
opacity: 0.2,
width: 1
},
move: {
enable: true,
speed: 2,
direction: "none",
random: true,
out_mode: "out"
}
}
});
}
}
// 加载相册数据
async function loadAlbums() {
try {
const response = await fetch('/api/albums');
if (!response.ok) throw new Error('加载相册失败');
albums = await response.json();
renderAlbums();
} catch (error) {
console.error('加载相册失败:', error);
showError('相册加载失败,请稍后重试');
}
}
// 渲染相册
function renderAlbums() {
const container = document.getElementById('albumsContainer');
if (!albums || albums.length === 0) {
container.innerHTML = `
<div class="col-12 text-center py-5">
<div class="album-card p-5">
<i class="fas fa-images fa-4x text-muted mb-3"></i>
<h3>还没有相册哦</h3>
<p class="text-muted">快去创建你的第一个相册吧!</p>
<a href="/admin" class="btn romantic-btn">创建相册</a>
</div>
</div>
`;
return;
}
let html = '';
albums.forEach((album, index) => {
// 随机选择一个样式
const style = getRandomAlbumStyle(index);
const photoCount = album.photos ? album.photos.length : 0;
let coverImage = '';
if (album.photos && album.photos.length > 0) {
const firstPhoto = album.photos[0];
coverImage = `/static/uploads/images/${firstPhoto.filename}`; // 修改点1:修复照片路径
} else {
coverImage = 'https://via.placeholder.com/400x300/ff6b8b/ffffff?text=暂无照片';
}
// 修改点2:添加单击和双击事件处理
html += `
<div class="col-md-4 mb-4">
<div class="album-card album-style-${style}"
onclick="handleAlbumClick(${album.id})"
ondblclick="handleAlbumDoubleClick(${album.id})"
title="单击查看详情,双击全屏浏览">
<div class="album-cover">
<img src="${coverImage}" alt="${album.title}">
</div>
<div class="album-info">
<h3 class="album-title">${album.title}</h3>
<p class="album-description">${album.description || '记录我们的美好回忆'}</p>
<div class="album-meta">
<span class="album-date">
<i class="far fa-calendar me-1"></i>
${formatDate(album.create_time)}
</span>
<span class="album-photo-count">
<i class="fas fa-image me-1"></i>
${photoCount} 张照片
</span>
</div>
<!-- 添加双击提示 -->
<div class="double-click-hint">
<small class="text-muted">
<i class="fas fa-expand-alt me-1"></i>双击全屏浏览
</small>
</div>
</div>
</div>
</div>
`;
});
container.innerHTML = html;
}
// 修改点3:处理单击事件(延迟执行,避免与双击冲突)
function handleAlbumClick(albumId) {
// 清除之前的定时器
clearTimeout(clickTimer);
// 设置新的定时器,延迟执行单击操作
clickTimer = setTimeout(() => {
viewAlbumDetail(albumId);
}, 300); // 300毫秒延迟,确保双击不会触发单击
}
// 修改点4:处理双击事件
function handleAlbumDoubleClick(albumId) {
// 清除单击事件的定时器
clearTimeout(clickTimer);
// 直接执行双击操作
viewAlbumPhotosFullscreen(albumId);
const container = document.getElementById('fullscreenAlbumViewer');
if (!document.fullscreenElement) {
container.requestFullscreen().catch(err => {
console.error('全屏请求失败:', err);
});
} else {
document.exitFullscreen();
}
}
// 获取随机相册样式
function getRandomAlbumStyle(seed) {
return albumStyles[seed % albumStyles.length];
}
// 随机化所有样式
function randomizeAlbumStyles() {
renderAlbums();
showToast('样式已随机更新!');
}
// 查看相册详情
async function viewAlbumDetail(albumId) {
try {
const album = albums.find(a => a.id === albumId);
if (!album) {
showToast('相册不存在');
return;
}
document.getElementById('albumModalTitle').textContent = album.title;
let photosHtml = '';
if (album.photos && album.photos.length > 0) {
photosHtml = '<div class="row g-3">';
album.photos.forEach(photo => {
photosHtml += `
<div class="col-md-4 col-6">
<div class="photo-item" onclick="viewFullImage('${photo.filename}')">
<img src="/static/uploads/images/${photo.filename}" alt="${photo.description || '照片'}" class="img-fluid rounded-3">
</div>
</div>
`;
});
photosHtml += '</div>';
} else {
photosHtml = `
<div class="text-center py-5">
<i class="fas fa-images fa-4x text-muted mb-3"></i>
<h5>这个相册还没有照片</h5>
<p class="text-muted">快去上传照片吧!</p>
</div>
`;
}
document.getElementById('albumModalBody').innerHTML = `
<div class="mb-4">
<h6>相册描述:</h6>
<p class="lead">${album.description || '暂无描述'}</p>
</div>
<div>
<h6>照片 (${album.photos ? album.photos.length : 0}张):</h6>
${photosHtml}
</div>
`;
new bootstrap.Modal(document.getElementById('albumModal')).show();
} catch (error) {
console.error('加载相册详情失败:', error);
showToast('加载相册详情失败: ' + error.message);
}
}
// 修改点5:添加全屏浏览相册照片功能
function viewAlbumPhotosFullscreen(albumId) {
const album = albums.find(a => a.id === albumId);
if (!album || !album.photos || album.photos.length === 0) {
showToast('这个相册还没有照片');
return;
}
// 创建全屏浏览容器
createFullscreenViewer(album);
}
// 修改点6:创建全屏浏览界面
function createFullscreenViewer(album) {
// 创建全屏容器
const fullscreenContainer = document.createElement('div');
fullscreenContainer.id = 'fullscreenAlbumViewer';
fullscreenContainer.className = 'fullscreen-viewer';
fullscreenContainer.innerHTML = `
<div class="fullscreen-header">
<div class="header-content">
<h2 class="album-title-fullscreen">${album.title}</h2>
<p class="album-description-fullscreen">${album.description || ''}</p>
<div class="photo-counter">
<span id="currentPhotoIndex">1</span> / <span>${album.photos.length}</span>
</div>
</div>
<button class="btn-close-fullscreen" onclick="closeFullscreenViewer()">
<i class="fas fa-times"></i>
</button>
</div>
<div class="fullscreen-content">
<button class="nav-btn nav-prev" onclick="navigatePhoto(-1)">
<i class="fas fa-chevron-left"></i>
</button>
<div class="photo-viewer">
<div class="photo-container">
<img id="fullscreenPhoto" src="" alt="" onerror="this.onerror=null; this.src='https://via.placeholder.com/800x600/ff6b8b/ffffff?text=图片加载失败';">
</div>
<div class="photo-info">
<h4 id="photoTitle"></h4>
<p id="photoDescription"></p>
</div>
</div>
<button class="nav-btn nav-next" onclick="navigatePhoto(1)">
<i class="fas fa-chevron-right"></i>
</button>
</div>
<div class="photo-thumbnails">
${album.photos.map((photo, index) => `
<div class="thumbnail ${index === 0 ? 'active' : ''}"
onclick="showPhoto(${index})">
<img src="/static/uploads/images/${photo.filename}" alt="${photo.description || '缩略图'}">
</div>
`).join('')}
</div>
<div class="fullscreen-controls">
<button class="control-btn" onclick="toggleAutoPlay()" id="autoPlayBtn">
<i class="fas fa-play"></i> 自动播放
</button>
<button class="control-btn" onclick="downloadCurrentPhoto()">
<i class="fas fa-download"></i> 下载
</button>
<button class="control-btn" onclick="toggleFullscreen()">
<i class="fas fa-expand"></i> 全屏
</button>
</div>
`;
document.body.appendChild(fullscreenContainer);
// 显示第一张照片
currentAlbum = album;
currentPhotoIndex = 0;
showPhoto(0);
// 添加键盘事件监听
document.addEventListener('keydown', handleFullscreenKeydown);
// 阻止背景滚动
document.body.style.overflow = 'hidden';
}
// 修改点7:显示指定索引的照片
function showPhoto(index) {
if (!currentAlbum || !currentAlbum.photos) return;
// 确保索引在有效范围内
if (index < 0) index = currentAlbum.photos.length - 1;
if (index >= currentAlbum.photos.length) index = 0;
currentPhotoIndex = index;
const photo = currentAlbum.photos[index];
// 更新照片显示
const photoImg = document.getElementById('fullscreenPhoto');
const photoTitle = document.getElementById('photoTitle');
const photoDescription = document.getElementById('photoDescription');
const currentIndex = document.getElementById('currentPhotoIndex');
if (photoImg) {
photoImg.src = `/static/uploads/images/${photo.filename}`; // 修改点8:修复照片路径
photoImg.alt = photo.description || '美好的回忆';
}
if (photoTitle) {
photoTitle.textContent = photo.description || '美好的回忆';
}
if (photoDescription) {
photoDescription.textContent = `拍摄时间: ${formatDate(photo.upload_time)}`;
}
if (currentIndex) {
currentIndex.textContent = index + 1;
}
// 更新缩略图激活状态
document.querySelectorAll('.thumbnail').forEach((thumb, i) => {
thumb.classList.toggle('active', i === index);
});
}
// 修改点9:导航照片
function navigatePhoto(direction) {
showPhoto(currentPhotoIndex + direction);
}
// 修改点10:关闭全屏浏览
function closeFullscreenViewer() {
const viewer = document.getElementById('fullscreenAlbumViewer');
if (viewer) {
viewer.remove();
}
// 移除键盘事件监听
document.removeEventListener('keydown', handleFullscreenKeydown);
// 恢复背景滚动
document.body.style.overflow = '';
// 停止自动播放
if (autoPlayInterval) {
clearInterval(autoPlayInterval);
autoPlayInterval = null;
}
}
// 修改点11:键盘导航
function handleFullscreenKeydown(e) {
switch(e.key) {
case 'ArrowLeft':
case 'a':
case 'A':
e.preventDefault();
navigatePhoto(-1);
break;
case 'ArrowRight':
case 'd':
case 'D':
e.preventDefault();
navigatePhoto(1);
break;
case 'Escape':
e.preventDefault();
closeFullscreenViewer();
break;
case ' ':
e.preventDefault();
toggleAutoPlay();
break;
}
}
// 修改点12:自动播放功能
function toggleAutoPlay() {
const btn = document.getElementById('autoPlayBtn');
if (autoPlayInterval) {
clearInterval(autoPlayInterval);
autoPlayInterval = null;
btn.innerHTML = '<i class="fas fa-play"></i> 自动播放';
} else {
autoPlayInterval = setInterval(() => {
navigatePhoto(1);
}, 3000); // 3秒自动切换
btn.innerHTML = '<i class="fas fa-pause"></i> 停止播放';
}
}
// 修改点13:下载当前照片
function downloadCurrentPhoto() {
if (!currentAlbum || !currentAlbum.photos[currentPhotoIndex]) return;
const photo = currentAlbum.photos[currentPhotoIndex];
const link = document.createElement('a');
link.href = `/static/uploads/images/${photo.filename}`; // 修改点14:修复下载路径
link.download = photo.filename;
link.click();
}
// 修改点15:切换全屏模式
function toggleFullscreen() {
const container = document.getElementById('fullscreenAlbumViewer');
if (!document.fullscreenElement) {
container.requestFullscreen().catch(err => {
console.error('全屏请求失败:', err);
});
} else {
document.exitFullscreen();
}
}
// 修改点16:查看大图
function viewFullImage(filename) {
const modalImage = document.getElementById('modalImage');
modalImage.src = `/static/uploads/images/${filename}`; // 修改点17:修复大图查看路径
new bootstrap.Modal(document.getElementById('imageModal')).show();
}
// 加载故事数据
async function loadStories() {
try {
const response = await fetch('/api/stories');
if (!response.ok) throw new Error('加载故事失败');
stories = await response.json();
renderStories();
} catch (error) {
console.error('加载故事失败:', error);
document.getElementById('storiesContainer').innerHTML = `
<div class="col-12 text-center py-5">
<i class="fas fa-book-heart fa-4x text-muted mb-3"></i>
<h5>加载故事失败</h5>
<p class="text-muted">请检查网络连接或稍后重试</p>
<button class="btn romantic-btn mt-2" onclick="loadStories()">重试</button>
</div>
`;
}
}
// 渲染故事
function renderStories() {
const container = document.getElementById('storiesContainer');
if (!stories || stories.length === 0) {
container.innerHTML = `
<div class="col-12 text-center py-5">
<i class="fas fa-book-heart fa-4x text-muted mb-3"></i>
<h4>还没有爱情故事</h4>
<p class="text-muted">添加你们的第一篇故事,记录美好回忆</p>
<button class="btn romantic-btn" onclick="showCreateStoryModal()">
<i class="fas fa-plus me-2"></i>添加故事
</button>
</div>
`;
return;
}
let html = '';
stories.forEach((story, index) => {
const content = story.content.length > 150 ?
story.content.substring(0, 150) + '...' : story.content;
html += `
<div class="col-md-6 mb-4">
<div class="story-card" onclick="viewStoryDetail(${index})">
<h3 class="story-title">
<i class="fas fa-star" style="color: var(--gold);"></i>
${story.title}
</h3>
<p class="story-content">${content}</p>
<div class="story-date">
<i class="far fa-clock me-1"></i>
${formatDate(story.create_time)}
</div>
</div>
</div>
`;
});
container.innerHTML = html;
}
// 查看故事详情
function viewStoryDetail(index) {
const story = stories[index];
if (!story) return;
document.getElementById('storyModalTitle').textContent = story.title;
document.getElementById('storyModalContent').innerHTML = `
<div class="story-content" style="font-size: 1.1rem; line-height: 1.8;">
${story.content.replace(/\n/g, '<br>')}
</div>
<div class="text-center mt-4">
<small class="text-muted">
<i class="far fa-clock me-1"></i>
${formatDate(story.create_time)}
</small>
</div>
`;
new bootstrap.Modal(document.getElementById('storyModal')).show();
}
// 显示创建故事模态框
function showCreateStoryModal() {
// 这里可以打开一个模态框来创建新故事
// 由于我们没有实现后端,暂时使用简单提示
alert('创建故事功能需要在管理后台中使用');
}
// 加载音乐
async function loadMusic() {
try {
const response = await fetch('/api/music');
const data = await response.json();
setupMusicPlayer(data.currentMusic);
} catch (error) {
console.error('加载音乐失败:', error);
// 设置默认音乐信息
setupMusicPlayer({filename: '', title: '暂无音乐'});
}
}
// 设置音乐播放器
function setupMusicPlayer(music) {
const title = document.getElementById('musicTitle');
const text = document.getElementById('musicText');
if (music && music.filename) {
title.textContent = music.title || '背景音乐';
text.textContent = '播放';
// 创建音频元素
currentAudio = new Audio(`/uploads/music/${music.filename}`);
currentAudio.loop = true;
currentAudio.volume = 0.3;
// 尝试自动播放
setTimeout(() => {
if (currentAudio) {
currentAudio.play().then(() => {
text.textContent = '暂停';
}).catch(() => {
// 自动播放被阻止
});
}
}, 1000);
} else {
title.textContent = '暂无音乐';
text.textContent = '播放';
}
}
// 切换音乐播放状态
function toggleMusic() {
if (!currentAudio) {
showToast('没有可播放的音乐');
return;
}
const text = document.getElementById('musicText');
if (currentAudio.paused) {
currentAudio.play();
text.textContent = '暂停';
} else {
currentAudio.pause();
text.textContent = '播放';
}
}
// 开始轮播
function startSlideshow() {
window.open('/slideshow', '_blank');
}
// 显示错误消息
function showError(message) {
const container = document.getElementById('albumsContainer');
if (container) {
container.innerHTML = `
<div class="col-12">
<div class="alert alert-danger" role="alert">
<i class="fas fa-exclamation-triangle me-2"></i>
${message}
<button class="btn btn-sm btn-danger ms-2" onclick="loadAlbums()">重试</button>
</div>
</div>
`;
}
}
// 显示提示消息
function showToast(message) {
// 创建toast元素
const toast = document.createElement('div');
toast.className = 'position-fixed bottom-0 end-0 p-3';
toast.style.zIndex = '1000';
toast.innerHTML = `
<div class="toast show" role="alert">
<div class="toast-header" style="background: linear-gradient(45deg, var(--primary-pink), var(--purple)); color: white;">
<strong class="me-auto">提示</strong>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="toast"></button>
</div>
<div class="toast-body">
${message}
</div>
</div>
`;
document.body.appendChild(toast);
// 3秒后移除
setTimeout(() => {
toast.remove();
}, 3000);
}
// 格式化日期
function formatDate(dateString) {
if (!dateString) return '未知日期';
try {
const date = new Date(dateString);
return date.toLocaleDateString('zh-CN') + ' ' + date.toLocaleTimeString('zh-CN', {
hour: '2-digit',
minute: '2-digit'
});
} catch (error) {
return dateString;
}
}
// 模拟API数据(实际项目中应从后端API获取)
function loadMockData() {
// 模拟相册数据
albums = [
{
id: 1,
title: "旅行回忆",
description: "记录我们美好的旅行时光",
style: "standard",
create_time: "2026-01-15 10:30:00",
photos: [
{ id: 1, filename: "travel1.jpg", description: "美丽的风景", upload_time: "2026-01-15 10:30:00" },
{ id: 2, filename: "travel2.jpg", description: "我们的合影", upload_time: "2026-01-15 11:20:00" },
{ id: 3, filename: "travel3.jpg", description: "美食探索", upload_time: "2026-01-15 12:15:00" }
]
},
{
id: 2,
title: "家庭聚会",
description: "温馨的家庭聚会记录",
style: "magazine",
create_time: "2026-01-20 14:20:00",
photos: [
{ id: 4, filename: "family1.jpg", description: "团圆时刻", upload_time: "2026-01-20 14:20:00" },
{ id: 5, filename: "family2.jpg", description: "欢乐时光", upload_time: "2026-01-20 15:30:00" }
]
},
{
id: 3,
title: "浪漫时刻",
description: "我们最珍贵的浪漫回忆",
style: "3d",
create_time: "2026-02-01 18:45:00",
photos: [
{ id: 6, filename: "romantic1.jpg", description: "烛光晚餐", upload_time: "2026-02-01 18:45:00" },
{ id: 7, filename: "romantic2.jpg", description: "海边漫步", upload_time: "2026-02-01 19:20:00" },
{ id: 8, filename: "romantic3.jpg", description: "星空下的约定", upload_time: "2026-02-01 20:10:00" }
]
}
];
// 模拟故事数据
stories = [
{
id: 1,
title: "我们的相遇",
content: "那是一个阳光明媚的下午,我们在咖啡馆偶然相遇。你的笑容如阳光般温暖,那一刻我就知道,你就是我一直在寻找的那个人。从那天起,我们的生活开始交织在一起,每一刻都充满了甜蜜和惊喜。",
create_time: "2026-01-10 09:15:00"
},
{
id: 2,
title: "第一次约会",
content: "还记得我们第一次约会去看电影,你紧张得一直搓手,我却觉得特别可爱。电影结束后,我们在江边散步,聊了很久很久... 那天晚上,我知道我已经深深地爱上了你。",
create_time: "2026-01-25 20:30:00"
}
];
// 渲染数据
renderAlbums();
renderStories();
}
// 如果API不可用,使用模拟数据
setTimeout(() => {
if (albums.length === 0) {
loadMockData();
showToast('使用示例数据演示,实际使用请连接后端API');
}
}, 2000);
</script>
</body>
</html>
[mw_shl_code=asm,true]<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>浪漫相册轮播</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Pacifico&family=Dancing+Script:wght@400;700&display=swap" rel="stylesheet">
<style>
:root {
--primary-pink: #ff6b8b;
--secondary-pink: #ffa5b5;
--light-pink: #fff5f7;
--gold: #ffd700;
--purple: #9c88ff;
--deep-pink: #ff1493;
--rose-red: #e91e63;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Microsoft YaHei', sans-serif;
background: #000;
color: white;
overflow: hidden;
height: 100vh;
}
/* 全屏轮播容器 */
.slideshow-container {
position: relative;
width: 100%;
height: 100vh;
overflow: hidden;
}
/* 控制面板 */
.control-panel {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(10px);
border-radius: 15px;
padding: 15px;
color: white;
min-width: 200px;
box-shadow: 0 5px 20px rgba(0,0,0,0.3);
}
.control-panel h4 {
font-family: 'Pacifico', cursive;
color: var(--primary-pink);
margin-bottom: 15px;
text-align: center;
}
.control-item {
margin-bottom: 10px;
display: flex;
align-items: center;
justify-content: space-between;
}
.control-label {
font-size: 0.9rem;
}
.control-value {
font-weight: bold;
color: var(--gold);
}
.btn-control {
background: linear-gradient(45deg, var(--primary-pink), var(--purple));
border: none;
border-radius: 20px;
padding: 8px 15px;
color: white;
font-size: 0.9rem;
transition: all 0.3s ease;
width: 100%;
margin-top: 10px;
}
.btn-control:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(255,107,139,0.6);
}
/* 音乐控制 */
.music-control {
position: fixed;
top: 20px;
left: 20px;
z-index: 1000;
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(10px);
border-radius: 50%;
width: 50px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
}
.music-control i {
font-size: 1.5rem;
color: var(--primary-pink);
}
/* 样式指示器 */
.style-indicator {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
z-index: 1000;
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 10px 20px;
color: white;
text-align: center;
min-width: 200px;
}
.style-name {
font-family: 'Dancing Script', cursive;
font-size: 1.5rem;
color: var(--gold);
margin-bottom: 5px;
}
.style-timer {
font-size: 0.9rem;
color: #ccc;
}
.progress-bar {
height: 5px;
background: rgba(255,255,255,0.2);
border-radius: 5px;
margin-top: 5px;
overflow: hidden;
}
.progress {
height: 100%;
background: linear-gradient(90deg, var(--primary-pink), var(--purple));
width: 0%;
transition: width 1s linear;
}
/* 退出按钮 */
.exit-btn {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(10px);
border-radius: 50%;
width: 50px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
color: white;
text-decoration: none;
}
.exit-btn:hover {
background: rgba(255, 107, 139, 0.8);
color: white;
}
/* 轮播样式 */
/* 基础样式 */
.slideshow-style {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
transition: opacity 1.5s ease-in-out;
}
.slideshow-style.active {
opacity: 1;
}
/* 1. 标准网格样式 */
.style-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: repeat(3, 1fr);
gap: 10px;
padding: 20px;
}
.style-grid .photo-item {
border-radius: 10px;
overflow: hidden;
position: relative;
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
transition: transform 0.5s ease;
}
.style-grid .photo-item:hover {
transform: scale(1.05);
z-index: 10;
}
.style-grid .photo-item img {
width: 100%;
height: 100%;
object-fit: cover;
}
/* 2. 3D立方体样式 */
.style-3d {
perspective: 1000px;
display: flex;
align-items: center;
justify-content: center;
}
.cube-container {
width: 300px;
height: 300px;
position: relative;
transform-style: preserve-3d;
animation: rotateCube 20s infinite linear;
}
.cube-face {
position: absolute;
width: 300px;
height: 300px;
border: 2px solid rgba(255,255,255,0.1);
border-radius: 10px;
overflow: hidden;
backface-visibility: hidden;
}
.cube-face img {
width: 100%;
height: 100%;
object-fit: cover;
}
.cube-face.front { transform: translateZ(150px); }
.cube-face.back { transform: rotateY(180deg) translateZ(150px); }
.cube-face.right { transform: rotateY(90deg) translateZ(150px); }
.cube-face.left { transform: rotateY(-90deg) translateZ(150px); }
.cube-face.top { transform: rotateX(90deg) translateZ(150px); }
.cube-face.bottom { transform: rotateX(-90deg) translateZ(150px); }
@keyframes rotateCube {
0% { transform: rotateY(0) rotateX(0); }
100% { transform: rotateY(360deg) rotateX(360deg); }
}
/* 3. 杂志样式 */
.style-magazine {
display: flex;
align-items: center;
justify-content: center;
padding: 40px;
}
.magazine-spread {
display: flex;
width: 80%;
height: 80%;
background: white;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
}
.magazine-page {
flex: 1;
position: relative;
overflow: hidden;
}
.magazine-page img {
width: 100%;
height: 100%;
object-fit: cover;
}
.magazine-page::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(45deg, rgba(255,107,139,0.1), rgba(156,136,255,0.1));
}
/* 4. 瀑布流样式 */
.style-masonry {
columns: 4;
column-gap: 15px;
padding: 20px;
}
.style-masonry .photo-item {
break-inside: avoid;
margin-bottom: 15px;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
}
.style-masonry .photo-item img {
width: 100%;
height: auto;
display: block;
}
/* 5. 拍立得样式 */
.style-polaroid {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
padding: 40px;
gap: 30px;
}
.polaroid-item {
background: white;
padding: 15px 15px 40px 15px;
border-radius: 5px;
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
transform: rotate(var(--rotation));
transition: transform 0.5s ease;
}
.polaroid-item:hover {
transform: rotate(0) scale(1.1);
z-index: 10;
}
.polaroid-item img {
width: 200px;
height: 200px;
object-fit: cover;
display: block;
}
/* 6. 胶片样式 */
.style-filmstrip {
display: flex;
height: 100%;
align-items: center;
overflow: hidden;
position: relative;
}
.filmstrip {
display: flex;
animation: scrollFilm 30s linear infinite;
}
.film-frame {
height: 80vh;
width: auto;
margin: 0 10px;
border: 10px solid #000;
border-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" fill="black"><rect width="100" height="100" fill="black"/></svg>') 10 round;
box-shadow: 0 0 20px rgba(255,255,255,0.1);
}
.film-frame img {
height: 100%;
width: auto;
object-fit: cover;
}
@keyframes scrollFilm {
0% { transform: translateX(0); }
100% { transform: translateX(calc(-100% + 100vw)); }
}
/* 7. 拼贴画样式 */
.style-collage {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(3, 1fr);
gap: 10px;
padding: 20px;
height: 100%;
}
.collage-item {
border-radius: 10px;
overflow: hidden;
position: relative;
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
}
.collage-item:nth-child(1) { grid-column: 1 / 3; grid-row: 1 / 3; }
.collage-item:nth-child(2) { grid-column: 3 / 4; grid-row: 1 / 2; }
.collage-item:nth-child(3) { grid-column: 3 / 4; grid-row: 2 / 3; }
.collage-item:nth-child(4) { grid-column: 1 / 2; grid-row: 3 / 4; }
.collage-item:nth-child(5) { grid-column: 2 / 4; grid-row: 3 / 4; }
.collage-item img {
width: 100%;
height: 100%;
object-fit: cover;
}
/* 8. 时间线样式 */
.style-timeline {
display: flex;
height: 100%;
align-items: center;
padding: 0 100px;
position: relative;
}
.timeline {
position: absolute;
top: 50%;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, var(--primary-pink), var(--purple));
}
.timeline-item {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 200px;
text-align: center;
}
.timeline-item img {
width: 150px;
height: 150px;
object-fit: cover;
border-radius: 50%;
border: 5px solid white;
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
margin-bottom: 10px;
}
/* 9. 画廊样式 */
.style-gallery {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
.gallery-wall {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: repeat(3, 1fr);
gap: 15px;
width: 90%;
height: 90%;
}
.gallery-item {
border: 10px solid white;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
overflow: hidden;
position: relative;
}
.gallery-item img {
width: 100%;
height: 100%;
object-fit: cover;
}
/* 10. 翻转书样式 */
.style-flipbook {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
perspective: 1000px;
}
.flipbook {
width: 60%;
height: 80%;
position: relative;
transform-style: preserve-3d;
}
.flip-page {
position: absolute;
width: 100%;
height: 100%;
background: white;
border-radius: 5px;
overflow: hidden;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
transform-origin: left center;
transition: transform 1.5s ease-in-out;
}
.flip-page img {
width: 100%;
height: 100%;
object-fit: cover;
}
/* 响应式调整 */
@media (max-width: 768px) {
.control-panel {
top: 10px;
right: 10px;
padding: 10px;
min-width: 150px;
}
.style-indicator {
bottom: 10px;
padding: 8px 15px;
min-width: 150px;
}
.style-name {
font-size: 1.2rem;
}
.music-control, .exit-btn {
top: 10px;
width: 40px;
height: 40px;
}
.style-grid {
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(4, 1fr);
gap: 5px;
padding: 10px;
}
.style-masonry {
columns: 2;
column-gap: 10px;
padding: 10px;
}
}
</style>
</head>
<body>
<!-- 退出按钮 -->
<a href="/" class="exit-btn" title="返回首页">
<i class="fas fa-home"></i>
</a>
<!-- 音乐控制 -->
<div class="music-control" id="musicControl" onclick="toggleMusic()">
<i class="fas fa-music"></i>
</div>
<!-- 控制面板 -->
<div class="control-panel">
<h4>浪漫轮播</h4>
<div class="control-item">
<span class="control-label">当前样式:</span>
<span class="control-value" id="currentStyle">标准网格</span>
</div>
<div class="control-item">
<span class="control-label">剩余时间:</span>
<span class="control-value" id="timeRemaining">30秒</span>
</div>
<div class="control-item">
<span class="control-label">照片数量:</span>
<span class="control-value" id="photoCount">0张</span>
</div>
<button class="btn-control" onclick="togglePlayback()">
<i class="fas fa-pause" id="playPauseIcon"></i> <span id="playPauseText">暂停</span>
</button>
<button class="btn-control" onclick="skipStyle()">
<i class="fas fa-forward"></i> 跳过样式
</button>
</div>
<!-- 样式指示器 -->
<div class="style-indicator">
<div class="style-name" id="styleName">标准网格</div>
<div class="style-timer" id="styleTimer">00:30</div>
<div class="progress-bar">
<div class="progress" id="styleProgress"></div>
</div>
</div>
<!-- 轮播容器 -->
<div class="slideshow-container" id="slideshowContainer">
<!-- 各种样式将通过JavaScript动态加载 -->
</div>
<!-- 音频元素 -->
<audio id="backgroundMusic" loop>
<source src="" type="audio/mp3">
</audio>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
// 全局变量
let currentStyleIndex = 0;
let isPlaying = true;
let styleTimer = null;
let timeRemaining = 30; // 30秒
let currentPhotos = [];
let allPhotos = [];
let backgroundMusic = null;
let isMusicPlaying = false;
// 可用的轮播样式
const slideStyles = [
{ name: '标准网格', class: 'style-grid', description: '整齐的网格布局' },
{ name: '3D立方体', class: 'style-3d', description: '3D旋转立方体效果' },
{ name: '杂志风格', class: 'style-magazine', description: '杂志翻页效果' },
{ name: '瀑布流', class: 'style-masonry', description: '瀑布流布局' },
{ name: '拍立得', class: 'style-polaroid', description: '拍立得照片墙' },
{ name: '胶片风格', class: 'style-filmstrip', description: '电影胶片效果' },
{ name: '拼贴画', class: 'style-collage', description: '创意拼贴布局' },
{ name: '时间线', class: 'style-timeline', description: '时间线展示' },
{ name: '艺术画廊', class: 'style-gallery', description: '画廊展示效果' },
{ name: '翻转书', class: 'style-flipbook', description: '书本翻页效果' }
];
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
loadPhotos();
loadMusic();
startSlideshow();
});
// 加载照片数据
async function loadPhotos() {
try {
// 模拟API调用,实际项目中替换为真实API
const response = await fetch('/api/slideshow-photos');
allPhotos = await response.json();
// 如果没有照片,使用示例数据
if (!allPhotos || allPhotos.length === 0) {
allPhotos = generateSamplePhotos(20);
}
document.getElementById('photoCount').textContent = allPhotos.length + '张';
selectRandomPhotos();
} catch (error) {
console.error('加载照片失败:', error);
// 使用示例数据
allPhotos = generateSamplePhotos(20);
document.getElementById('photoCount').textContent = allPhotos.length + '张';
selectRandomPhotos();
}
}
// 生成示例照片数据
function generateSamplePhotos(count) {
const samplePhotos = [];
for (let i = 1; i <= count; i++) {
samplePhotos.push({
id: i,
filename: `photo${i}.jpg`,
description: `示例照片 ${i}`,
album_title: '示例相册'
});
}
return samplePhotos;
}
// 随机选择照片
function selectRandomPhotos() {
// 随机选择5-10张照片
const count = Math.floor(Math.random() * 6) + 5;
currentPhotos = [...allPhotos]
.sort(() => 0.5 - Math.random())
.slice(0, count);
}
// 加载音乐
async function loadMusic() {
try {
const response = await fetch('/api/current-music');
const music = await response.json();
if (music && music.filename) {
backgroundMusic = document.getElementById('backgroundMusic');
backgroundMusic.src = `/uploads/music/${music.filename}`;
backgroundMusic.volume = 0.3;
// 尝试自动播放(需要用户交互)
setTimeout(() => {
backgroundMusic.play().then(() => {
isMusicPlaying = true;
}).catch(() => {
// 自动播放被阻止
});
}, 1000);
}
} catch (error) {
console.error('加载音乐失败:', error);
}
}
// 切换音乐播放状态
function toggleMusic() {
if (!backgroundMusic) return;
if (isMusicPlaying) {
backgroundMusic.pause();
isMusicPlaying = false;
} else {
backgroundMusic.play();
isMusicPlaying = true;
}
}
// 开始轮播
function startSlideshow() {
applyRandomStyle();
startStyleTimer();
}
// 应用随机样式
function applyRandomStyle() {
// 移除当前活动样式
document.querySelectorAll('.slideshow-style').forEach(style => {
style.classList.remove('active');
});
// 随机选择新样式(确保不与当前样式相同)
let newIndex;
do {
newIndex = Math.floor(Math.random() * slideStyles.length);
} while (newIndex === currentStyleIndex && slideStyles.length > 1);
currentStyleIndex = newIndex;
const style = slideStyles[currentStyleIndex];
// 更新界面显示
document.getElementById('currentStyle').textContent = style.name;
document.getElementById('styleName').textContent = style.name;
// 重新随机选择照片
selectRandomPhotos();
// 创建样式容器
const container = document.getElementById('slideshowContainer');
let styleHTML = '';
// 根据样式生成对应的HTML结构
switch(style.class) {
case 'style-grid':
styleHTML = createGridStyle();
break;
case 'style-3d':
styleHTML = create3DStyle();
break;
case 'style-magazine':
styleHTML = createMagazineStyle();
break;
case 'style-masonry':
styleHTML = createMasonryStyle();
break;
case 'style-polaroid':
styleHTML = createPolaroidStyle();
break;
case 'style-filmstrip':
styleHTML = createFilmstripStyle();
break;
case 'style-collage':
styleHTML = createCollageStyle();
break;
case 'style-timeline':
styleHTML = createTimelineStyle();
break;
case 'style-gallery':
styleHTML = createGalleryStyle();
break;
case 'style-flipbook':
styleHTML = createFlipbookStyle();
break;
default:
styleHTML = createGridStyle();
}
container.innerHTML = `
<div class="slideshow-style ${style.class} active" id="currentStyle">
${styleHTML}
</div>
`;
// 重置计时器
timeRemaining = 30;
updateTimerDisplay();
}
// 创建网格样式HTML
// 修复所有样式创建函数中的照片路径
function createGridStyle() {
let html = '';
currentPhotos.forEach(photo => {
html += `
<div class="photo-item">
<img src="/uploads/images/${photo.filename}" alt="${photo.title}">
</div>
`;
});
return html;
}
// 添加错误处理函数
function handleImageError(img, filename) {
console.error('图片加载失败:', filename);
img.src = 'https://via.placeholder.com/800x600?text=图片加载失败';
img.alt = '图片加载失败';
}
// 创建3D样式HTML
function create3DStyle() {
let html = '<div class="cube-container">';
const faces = ['front', 'back', 'right', 'left', 'top', 'bottom'];
faces.forEach((face, index) => {
const photo = currentPhotos[index % currentPhotos.length];
html += `
<div class="cube-face ${face}">
<img src="/uploads/images/${photo.filename}" alt="${photo.title}">
</div>
`;
});
html += '</div>';
return html;
}
// 创建杂志样式HTML
function createMagazineStyle() {
let html = '<div class="magazine-spread">';
// 创建杂志封面和内页
const spreads = [
{ type: 'cover', photo: currentPhotos[0] },
{ type: 'spread', photos: [currentPhotos[1], currentPhotos[2]] },
{ type: 'spread', photos: [currentPhotos[3], currentPhotos[4]] },
{ type: 'back', photo: currentPhotos[5] }
];
spreads.forEach((spread, index) => {
if (spread.type === 'cover' && spread.photo) {
const photo = spread.photo;
html += `
<div class="magazine-cover" data-page="${index}">
<div class="cover-image">
</div>
<div class="cover-content">
<img src="/uploads/images/${photo.filename}" alt="${photo.title}">
<h2>${photo.album_title || '浪漫相册'}</h2>
<h1>${photo.description || '我们的爱情故事'}</h1>
<div class="cover-decoration">
<i class="fas fa-heart"></i>
<span>永恒的记忆</span>
<i class="fas fa-heart"></i>
</div>
</div>
</div>
`;
} else if (spread.type === 'spread' && spread.photos) {
html += `
<div class="magazine-spread-page" data-page="${index}">
<div class="spread-left">
${spread.photos[0] ? `
<div class="spread-photo">
<div class="photo-caption">
<img src="/uploads/images/${photo.filename}" alt="${photo.title}">
<h3>${spread.photos[0].album_title || '回忆'}</h3>
<p>${spread.photos[0].description || '珍贵的瞬间'}</p>
</div>
</div>
` : '<div class="placeholder-text">更多美好回忆...</div>'}
</div>
<div class="spread-right">
${spread.photos[1] ? `
<div class="spread-photo">
<div class="photo-caption">
<img src="/uploads/images/${photo.filename}" alt="${photo.title}">
<h3>${spread.photos[1].album_title || '时光'}</h3>
<p>${spread.photos[1].description || '永恒的瞬间'}</p>
</div>
</div>
` : '<div class="placeholder-text">期待更多故事...</div>'}
</div>
</div>
`;
} else if (spread.type === 'back' && spread.photo) {
const photo = spread.photo;
html += `
<div class="magazine-back-cover" data-page="${index}">
<div class="back-content">
<div class="final-message">
<i class="fas fa-heart fa-2x"></i>
<h2>我们的故事还在继续...</h2>
<p>每一张照片都是我们爱情的见证</p>
<div class="signature">
<span>永远爱你</span>
<div class="heart-line"></div>
</div>
</div>
<div class="back-photo">
</div>
</div>
</div>
`;
}
});
html += '</div>';
return html;
}
// 创建瀑布流样式HTML
function createMasonryStyle() {
let html = '';
currentPhotos.forEach(photo => {
// 随机高度,模拟瀑布流效果
const heightClass = ['', 'tall', 'short'][Math.floor(Math.random() * 3)];
html += `
<div class="photo-item ${heightClass}">
<img src="/uploads/images/${photo.filename}" alt="${photo.title}">
</div>
`;
});
return html;
}
// 创建拍立得样式HTML
function createPolaroidStyle() {
let html = '';
currentPhotos.forEach((photo, index) => {
// 随机旋转角度
const rotation = (Math.random() * 20) - 10; // -10到10度之间
html += `
<div class="polaroid-item" style="--rotation: ${rotation}deg">
<img src="/uploads/images/${photo.filename}" alt="${photo.title}">
</div>
`;
});
return html;
}
// 创建胶片样式HTML
function createFilmstripStyle() {
let html = '<div class="filmstrip">';
// 创建足够多的胶片帧
for (let i = 0; i < 10; i++) {
const photo = currentPhotos[i % currentPhotos.length];
html += `
<div class="film-frame">
<img src="/uploads/images/${photo.filename}" alt="${photo.title}">
</div>
`;
}
html += '</div>';
return html;
}
// 创建拼贴画样式HTML
function createCollageStyle() {
let html = '';
// 创建5个拼贴项目
for (let i = 0; i < 5; i++) {
const photo = currentPhotos[i % currentPhotos.length];
html += `
<div class="collage-item">
<img src="/uploads/images/${photo.filename}" alt="${photo.title}">
</div>
`;
}
return html;
}
// 创建时间线样式HTML
function createTimelineStyle() {
let html = '<div class="timeline"></div>';
// 在时间线上均匀分布照片
currentPhotos.forEach((photo, index) => {
const position = (index / (currentPhotos.length - 1)) * 80 + 10; // 10% 到 90%
html += `
<div class="timeline-item" style="left: ${position}%">
<img src="/uploads/images/${photo.filename}" alt="${photo.title}">
<div>${photo.description || '美好回忆'}</div>
</div>
`;
});
return html;
}
// 创建画廊样式HTML
function createGalleryStyle() {
let html = '<div class="gallery-wall">';
// 创建12个画廊项目
for (let i = 0; i < 12; i++) {
const photo = currentPhotos[i % currentPhotos.length];
html += `
<div class="gallery-item">
<img src="/uploads/images/${photo.filename}" alt="${photo.title}">
</div>
`;
}
html += '</div>';
return html;
}
function createFlipbookStyle() {
let html = '<div class="flipbook">';
// 创建3-5页翻转书,确保不超过照片数量
const pageCount = Math.min(5, currentPhotos.length);
for (let i = 0; i < pageCount; i++) {
const photo = currentPhotos[i];
if (!photo) continue;
const delay = i * 0.8; // 每页延迟0.8秒,更自然的翻页效果
const rotation = i * 10; // 每页稍微旋转,模拟真实书本
html += `
<div class="flip-page" style="animation-delay: ${delay}s; transform: rotate(${rotation}deg);">
<div class="page-content">
<div class="page-image">
</div>
<div class="page-info">
<img src="/uploads/images/${photo.filename}" alt="${photo.title}">
<h4>${photo.album_title || '我们的回忆'}</h4>
<p>${photo.description || '美好的瞬间'}</p>
<small>第${i + 1}页</small>
</div>
</div>
</div>
`;
}
html += '</div>';
return html;
}
// 开始样式计时器
function startStyleTimer() {
if (styleTimer) {
clearInterval(styleTimer);
}
timeRemaining = 30;
updateTimerDisplay();
styleTimer = setInterval(() => {
if (isPlaying) {
timeRemaining--;
updateTimerDisplay();
if (timeRemaining <= 0) {
// 切换到下一个样式
applyRandomStyle();
}
}
}, 1000);
}
// 更新计时器显示
function updateTimerDisplay() {
document.getElementById('timeRemaining').textContent = timeRemaining + '秒';
document.getElementById('styleTimer').textContent = formatTime(timeRemaining);
// 更新进度条
const progress = ((30 - timeRemaining) / 30) * 100;
document.getElementById('styleProgress').style.width = progress + '%';
}
// 格式化时间显示
function formatTime(seconds) {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
// 切换播放状态
function togglePlayback() {
isPlaying = !isPlaying;
const icon = document.getElementById('playPauseIcon');
const text = document.getElementById('playPauseText');
if (isPlaying) {
icon.className = 'fas fa-pause';
text.textContent = '暂停';
} else {
icon.className = 'fas fa-play';
text.textContent = '播放';
}
// 控制背景音乐
if (backgroundMusic) {
if (isPlaying) {
backgroundMusic.play();
} else {
backgroundMusic.pause();
}
}
}
// 跳过当前样式
function skipStyle() {
applyRandomStyle();
}
// 全屏显示
function toggleFullscreen() {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen().catch(err => {
console.error('全屏请求失败:', err);
});
} else {
if (document.exitFullscreen) {
document.exitFullscreen();
}
}
}
// 键盘快捷键支持
document.addEventListener('keydown', function(e) {
switch(e.key) {
case ' ':
// 空格键切换播放状态
e.preventDefault();
togglePlayback();
break;
case 'ArrowRight':
// 右箭头跳过当前样式
e.preventDefault();
skipStyle();
break;
case 'f':
case 'F':
// F键切换全屏
e.preventDefault();
toggleFullscreen();
break;
case 'm':
case 'M':
// M键切换音乐
e.preventDefault();
toggleMusic();
break;
case 'Escape':
// ESC键退出全屏
if (document.fullscreenElement) {
document.exitFullscreen();
}
break;
}
});
// 处理全屏变化
document.addEventListener('fullscreenchange', function() {
const isFullscreen = !!document.fullscreenElement;
// 可以在这里添加全屏状态变化的处理逻辑
});
// 页面可见性变化处理(标签页切换时暂停)
document.addEventListener('visibilitychange', function() {
if (document.hidden) {
// 页面隐藏时暂停
if (isPlaying) {
togglePlayback();
}
}
});
// 页面加载完成后初始化
window.addEventListener('load', function() {
// 延迟加载,确保所有资源已加载
setTimeout(() => {
loadPhotos();
loadMusic();
startSlideshow();
}, 500);
});
</script>
</body>
</html>