情侣相册
情侣相册
需求
帮我写个简单的相册:
图片按时间排序,每张图片下有日期,及文字描述。
这个相册主要记录下一对小情侣之间的美好图片回忆。
请帮我输出具体的代码。
v1-2025.9.12-豆包输出-本地测试
豆包回答
我将为您创建一个温馨浪漫的情侣回忆相册网页,按时间排序展示照片,并包含日期和文字描述。这个设计将采用柔和的色调,营造出甜蜜温馨的氛围,同时确保照片展示清晰、交互友好。
::: dtails 具体代码
couple-album.html文件:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>我们的回忆 | 爱情相册</title>
<!-- 引入Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- 引入Font Awesome -->
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<!-- 配置Tailwind自定义颜色和字体 -->
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#E94560', // 浪漫粉红色
secondary: '#1A1A2E', // 深色背景
accent: '#FFD700', // 金色点缀
light: '#F7F7F9', // 浅色背景
},
fontFamily: {
romantic: ['"Dancing Script"', 'cursive', 'sans-serif'],
sans: ['Inter', 'system-ui', 'sans-serif'],
},
}
}
}
</script>
<!-- 自定义工具类 -->
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
.photo-card-shadow {
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05), 0 0 0 1px rgba(233, 69, 96, 0.1);
}
.album-gradient {
background: linear-gradient(120deg, #fdfbfb 0%, #f7f4f7 100%);
}
}
</style>
<!-- 引入Google字体 -->
<link href="https://fonts.googleapis.com/css2?family=Dancing+Script:wght@400;700&family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
</head>
<body class="album-gradient min-h-screen">
<!-- 顶部导航栏 -->
<header class="sticky top-0 z-50 bg-white/80 backdrop-blur-md shadow-sm transition-all duration-300">
<div class="container mx-auto px-4 py-4 flex justify-between items-center">
<h1 class="text-3xl md:text-4xl font-romantic text-primary">
<i class="fa fa-heart mr-2 animate-pulse"></i>我们的故事
</h1>
<div class="flex items-center space-x-4">
<button id="sortButton" class="flex items-center text-secondary hover:text-primary transition-colors">
<i class="fa fa-sort mr-1"></i>
<span>按时间</span>
</button>
<button id="addPhotoBtn" class="bg-primary hover:bg-primary/90 text-white px-4 py-2 rounded-full shadow-md hover:shadow-lg transition-all transform hover:-translate-y-0.5">
<i class="fa fa-plus mr-1"></i>添加回忆
</button>
</div>
</div>
</header>
<!-- 主内容区 -->
<main class="container mx-auto px-4 py-8">
<!-- 相册介绍 -->
<div class="text-center mb-12 max-w-3xl mx-auto">
<h2 class="text-[clamp(1.8rem,4vw,2.5rem)] font-romantic text-secondary mb-4">记录我们的每一个美好瞬间</h2>
<p class="text-gray-600 text-lg">时光荏苒,爱意绵长。这里收藏着属于我们的点点滴滴,每一张照片都是一段珍贵的回忆。</p>
</div>
<!-- 照片筛选标签 -->
<div class="flex flex-wrap justify-center gap-2 mb-8">
<button class="filter-tag active px-4 py-2 rounded-full bg-primary text-white" data-filter="all">全部回忆</button>
<button class="filter-tag px-4 py-2 rounded-full bg-white text-gray-700 hover:bg-primary/10 transition-colors" data-filter="date">约会时光</button>
<button class="filter-tag px-4 py-2 rounded-full bg-white text-gray-700 hover:bg-primary/10 transition-colors" data-filter="travel">旅行足迹</button>
<button class="filter-tag px-4 py-2 rounded-full bg-white text-gray-700 hover:bg-primary/10 transition-colors" data-filter="festival">节日庆典</button>
<button class="filter-tag px-4 py-2 rounded-full bg-white text-gray-700 hover:bg-primary/10 transition-colors" data-filter="daily">日常点滴</button>
</div>
<!-- 照片网格 -->
<div id="photoGrid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
<!-- 照片将通过JavaScript动态加载 -->
</div>
<!-- 加载更多按钮 -->
<div class="text-center mt-12">
<button id="loadMoreBtn" class="px-6 py-3 bg-white text-primary border border-primary rounded-full hover:bg-primary hover:text-white transition-all duration-300 shadow-sm hover:shadow">
加载更多回忆 <i class="fa fa-angle-down ml-1"></i>
</button>
</div>
</main>
<!-- 照片查看模态框 -->
<div id="photoModal" class="fixed inset-0 bg-black/80 z-50 hidden flex items-center justify-center p-4">
<div class="relative max-w-4xl w-full max-h-[90vh]">
<button id="closeModal" class="absolute -top-12 right-0 text-white text-2xl hover:text-primary transition-colors">
<i class="fa fa-times"></i>
</button>
<img id="modalImage" src="" alt="照片大图" class="max-w-full max-h-[80vh] object-contain mx-auto">
<div class="bg-white/90 backdrop-blur-sm p-4 mt-4 rounded-lg">
<h3 id="modalDate" class="text-primary font-semibold text-lg mb-2"></h3>
<p id="modalDescription" class="text-gray-700"></p>
</div>
</div>
</div>
<!-- 添加照片模态框 -->
<div id="addPhotoModal" class="fixed inset-0 bg-black/60 z-50 hidden items-center justify-center p-4">
<div class="bg-white rounded-xl p-6 max-w-md w-full">
<div class="flex justify-between items-center mb-4">
<h3 class="text-xl font-romantic text-primary">添加新回忆</h3>
<button id="closeAddModal" class="text-gray-500 hover:text-primary">
<i class="fa fa-times"></i>
</button>
</div>
<form id="addPhotoForm" class="space-y-4">
<div>
<label class="block text-gray-700 mb-1" for="photoDate">日期</label>
<input type="date" id="photoDate" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/50">
</div>
<div>
<label class="block text-gray-700 mb-1" for="photoDescription">描述这段回忆</label>
<textarea id="photoDescription" rows="3" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/50" placeholder="记录下当时的心情和故事..."></textarea>
</div>
<div>
<label class="block text-gray-700 mb-1" for="photoCategory">分类</label>
<select id="photoCategory" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/50">
<option value="date">约会时光</option>
<option value="travel">旅行足迹</option>
<option value="festival">节日庆典</option>
<option value="daily">日常点滴</option>
</select>
</div>
<div>
<label class="block text-gray-700 mb-1" for="photoUrl">照片地址</label>
<input type="text" id="photoUrl" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/50" placeholder="输入照片的URL地址">
</div>
<button type="submit" class="w-full bg-primary hover:bg-primary/90 text-white py-3 rounded-lg transition-colors">
保存回忆
</button>
</form>
</div>
</div>
<!-- 页脚 -->
<footer class="bg-secondary text-white py-8 mt-16">
<div class="container mx-auto px-4 text-center">
<p class="font-romantic text-2xl mb-2">我们的故事,未完待续...</p>
<p class="text-gray-300 text-sm">© <span id="currentYear"></span> 爱与回忆 | 永远在一起</p>
</div>
</footer>
<script>
// 照片数据 - 按时间倒序排列(最新的在前)
const photos = [
{
id: 1,
date: '2023-10-15',
description: '第一次一起看海,海风很轻,你的笑容很甜。我们在沙滩上写下彼此的名字,约定要一起看遍世界的每一片海。',
url: 'https://picsum.photos/id/1039/800/600',
category: 'date'
},
{
id: 2,
date: '2023-09-21',
description: '中秋节一起做月饼,虽然形状不太完美,但这是我们一起完成的第一个节日美食。月光下的你,比月亮还要温柔。',
url: 'https://picsum.photos/id/292/800/600',
category: 'festival'
},
{
id: 3,
date: '2023-08-05',
description: '周末一起去郊外露营,晚上躺在帐篷里看星星,你说要把最亮的那颗星星命名为我们的名字。',
url: 'https://picsum.photos/id/883/800/600',
category: 'travel'
},
{
id: 4,
date: '2023-07-10',
description: '第一次一起做饭,你负责洗菜我负责炒,虽然有点手忙脚乱,但吃到嘴里的那一刻觉得无比幸福。',
url: 'https://picsum.photos/id/431/800/600',
category: 'daily'
},
{
id: 5,
date: '2023-06-01',
description: '儿童节一起去了游乐园,你像个孩子一样拉着我玩遍了所有项目,旋转木马上的笑容是我见过最美的风景。',
url: 'https://picsum.photos/id/338/800/600',
category: 'date'
},
{
id: 6,
date: '2023-05-20',
description: '520收到的惊喜,你说每天都要像今天一样爱我。其实有你的每一天,都是情人节。',
url: 'https://picsum.photos/id/240/800/600',
category: 'festival'
}
];
// 页面加载时显示的照片数量
let visibleCount = 6;
// 当前排序方式 (desc: 倒序, asc: 正序)
let sortOrder = 'desc';
// 当前筛选类别
let currentFilter = 'all';
// DOM元素
const photoGrid = document.getElementById('photoGrid');
const loadMoreBtn = document.getElementById('loadMoreBtn');
const sortButton = document.getElementById('sortButton');
const photoModal = document.getElementById('photoModal');
const modalImage = document.getElementById('modalImage');
const modalDate = document.getElementById('modalDate');
const modalDescription = document.getElementById('modalDescription');
const closeModal = document.getElementById('closeModal');
const addPhotoBtn = document.getElementById('addPhotoBtn');
const addPhotoModal = document.getElementById('addPhotoModal');
const closeAddModal = document.getElementById('closeAddModal');
const addPhotoForm = document.getElementById('addPhotoForm');
const filterTags = document.querySelectorAll('.filter-tag');
const currentYearEl = document.getElementById('currentYear');
// 设置当前年份
currentYearEl.textContent = new Date().getFullYear();
// 初始化相册
function initAlbum() {
renderPhotos();
setupEventListeners();
}
// 渲染照片
function renderPhotos() {
// 清空网格
photoGrid.innerHTML = '';
// 筛选和排序照片
let filteredPhotos = [...photos];
// 应用筛选
if (currentFilter !== 'all') {
filteredPhotos = filteredPhotos.filter(photo => photo.category === currentFilter);
}
// 应用排序
filteredPhotos.sort((a, b) => {
const dateA = new Date(a.date);
const dateB = new Date(b.date);
return sortOrder === 'desc' ? dateB - dateA : dateA - dateB;
});
// 限制显示数量
const photosToShow = filteredPhotos.slice(0, visibleCount);
// 没有照片时显示提示
if (photosToShow.length === 0) {
photoGrid.innerHTML = `
<div class="col-span-full text-center py-12">
<i class="fa fa-image text-gray-300 text-5xl mb-4"></i>
<p class="text-gray-500">没有找到符合条件的回忆</p>
</div>
`;
return;
}
// 创建照片卡片
photosToShow.forEach(photo => {
const formattedDate = formatDate(photo.date);
const photoCard = document.createElement('div');
photoCard.className = 'photo-card bg-white rounded-xl overflow-hidden photo-card-shadow transform transition-all duration-300 hover:-translate-y-2 hover:shadow-xl';
photoCard.dataset.category = photo.category;
photoCard.innerHTML = `
<div class="relative overflow-hidden group">
<img src="${photo.url}" alt="回忆照片" class="w-full h-64 object-cover transition-transform duration-500 group-hover:scale-110">
<div class="absolute inset-0 bg-primary/20 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-center justify-center">
<button class="view-photo-btn bg-white text-primary p-3 rounded-full transform translate-y-4 group-hover:translate-y-0 transition-transform duration-300">
<i class="fa fa-search-plus"></i>
</button>
</div>
</div>
<div class="p-5">
<div class="flex justify-between items-center mb-2">
<span class="text-primary font-semibold">${formattedDate}</span>
<span class="text-xs px-2 py-1 rounded-full bg-primary/10 text-primary">
${getCategoryName(photo.category)}
</span>
</div>
<p class="text-gray-600 line-clamp-3">${photo.description}</p>
</div>
`;
photoGrid.appendChild(photoCard);
// 添加查看照片事件
const viewBtn = photoCard.querySelector('.view-photo-btn');
viewBtn.addEventListener('click', () => openPhotoModal(photo));
// 点击照片也可以查看大图
const img = photoCard.querySelector('img');
img.addEventListener('click', () => openPhotoModal(photo));
});
// 控制"加载更多"按钮显示
if (visibleCount >= filteredPhotos.length) {
loadMoreBtn.classList.add('hidden');
} else {
loadMoreBtn.classList.remove('hidden');
}
}
// 格式化日期
function formatDate(dateString) {
const date = new Date(dateString);
return date.toLocaleDateString('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
}
// 获取分类名称
function getCategoryName(category) {
const categories = {
'date': '约会时光',
'travel': '旅行足迹',
'festival': '节日庆典',
'daily': '日常点滴'
};
return categories[category] || category;
}
// 打开照片模态框
function openPhotoModal(photo) {
modalImage.src = photo.url;
modalImage.alt = photo.description;
modalDate.textContent = formatDate(photo.date);
modalDescription.textContent = photo.description;
photoModal.classList.remove('hidden');
document.body.style.overflow = 'hidden'; // 防止背景滚动
}
// 关闭照片模态框
function closePhotoModal() {
photoModal.classList.add('hidden');
document.body.style.overflow = ''; // 恢复滚动
}
// 切换排序方式
function toggleSortOrder() {
sortOrder = sortOrder === 'desc' ? 'asc' : 'desc';
renderPhotos();
// 更新排序按钮文本
const sortText = sortButton.querySelector('span');
sortText.textContent = sortOrder === 'desc' ? '按时间(最新在前)' : '按时间(最早在前)';
}
// 加载更多照片
function loadMorePhotos() {
visibleCount += 3;
renderPhotos();
}
// 切换筛选类别
function setFilter(category) {
currentFilter = category;
visibleCount = 6; // 重置显示数量
// 更新筛选标签样式
filterTags.forEach(tag => {
if (tag.dataset.filter === category) {
tag.classList.add('bg-primary', 'text-white');
tag.classList.remove('bg-white', 'text-gray-700', 'hover:bg-primary/10');
} else {
tag.classList.remove('bg-primary', 'text-white');
tag.classList.add('bg-white', 'text-gray-700', 'hover:bg-primary/10');
}
});
renderPhotos();
}
// 打开添加照片模态框
function openAddPhotoModal() {
addPhotoModal.classList.remove('hidden');
addPhotoModal.classList.add('flex');
document.body.style.overflow = 'hidden';
}
// 关闭添加照片模态框
function closeAddPhotoModalFunc() {
addPhotoModal.classList.add('hidden');
addPhotoModal.classList.remove('flex');
document.body.style.overflow = '';
addPhotoForm.reset();
}
// 添加新照片
function addNewPhoto(e) {
e.preventDefault();
const date = document.getElementById('photoDate').value;
const description = document.getElementById('photoDescription').value;
const category = document.getElementById('photoCategory').value;
const url = document.getElementById('photoUrl').value;
// 简单验证
if (!date || !description || !url) {
alert('请填写所有必填字段');
return;
}
// 创建新照片对象
const newPhoto = {
id: photos.length + 1,
date,
description,
url,
category
};
// 添加到照片数组
photos.unshift(newPhoto);
// 重新渲染
renderPhotos();
// 关闭模态框
closeAddPhotoModalFunc();
// 显示成功提示
alert('回忆添加成功!');
}
// 设置事件监听器
function setupEventListeners() {
// 排序按钮
sortButton.addEventListener('click', toggleSortOrder);
// 加载更多按钮
loadMoreBtn.addEventListener('click', loadMorePhotos);
// 关闭照片模态框
closeModal.addEventListener('click', closePhotoModal);
// 点击模态框背景关闭
photoModal.addEventListener('click', (e) => {
if (e.target === photoModal) {
closePhotoModal();
}
});
// 添加照片按钮
addPhotoBtn.addEventListener('click', openAddPhotoModal);
// 关闭添加照片模态框
closeAddModal.addEventListener('click', closeAddPhotoModalFunc);
// 点击添加照片模态框背景关闭
addPhotoModal.addEventListener('click', (e) => {
if (e.target === addPhotoModal) {
closeAddPhotoModalFunc();
}
});
// 提交添加照片表单
addPhotoForm.addEventListener('submit', addNewPhoto);
// 筛选标签
filterTags.forEach(tag => {
tag.addEventListener('click', () => {
setFilter(tag.dataset.filter);
});
});
// 滚动时改变导航栏样式
window.addEventListener('scroll', () => {
const header = document.querySelector('header');
if (window.scrollY > 50) {
header.classList.add('py-2', 'shadow');
header.classList.remove('py-4');
} else {
header.classList.add('py-4');
header.classList.remove('py-2', 'shadow');
}
});
}
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', initAlbum);
</script>
</body>
</html>
:::
这个情侣回忆相册具有以下特点:
- 温馨浪漫的设计风格:采用粉红色作为主色调,搭配优雅的字体和柔和的背景,营造出甜蜜浪漫的氛围,非常适合记录情侣间的美好回忆。
- 时间排序功能:照片默认按时间倒序排列(最新的在前),用户可以点击排序按钮切换为正序排列(最早的在前)。
- 分类筛选系统:提供了四种回忆分类(约会时光、旅行足迹、节日庆典、日常点滴),方便快速筛选查看特定类型的回忆。
- 响应式布局:在手机、平板和桌面设备上都能完美展示,照片网格会根据屏幕尺寸自动调整布局。
- 交互体验:
- 照片卡片悬停时有放大和阴影效果
- 点击照片可查看大图和完整描述
- 支持添加新的回忆照片
- 滚动时导航栏会有动态变化效果
- 完整功能:包含照片展示、详情查看、排序、筛选、加载更多和添加新回忆等功能。
您可以直接使用这个代码,也可以根据需要修改照片数据、颜色方案或添加更多功能。
测试效果
还是不错的。
存在问题
问题1:在豆包里都有这个红心,为啥在我部署好的服务器上,红心消失了。。。
v2-2025.9.12-豆包输出-集成到teek里
提出问题
前面的 那个 情侣相册模块,请帮我生成一个 vue3版本的组件,不要使用Tailwind CSS,样式直接使用普通css写在组件内。
豆包回答
我将为您创建一个 Vue3 版本的情侣相册组件,不依赖 Tailwind CSS,使用原生 CSS 编写样式并整合在组件内部,保持原有的浪漫风格和功能特性。
具体代码:
<template>
<div class="couple-album">
<!-- 顶部导航 -->
<header class="album-header" :class="{ 'scrolled': isScrolled }">
<div class="container">
<h1 class="album-title">
<i class="fa fa-heart"></i> 我们的故事
</h1>
<div class="header-actions">
<button class="sort-btn" @click="toggleSortOrder">
<i class="fa fa-sort"></i>
<span>{{ sortText }}</span>
</button>
<button class="add-btn" @click="openAddModal">
<i class="fa fa-plus"></i> 添加回忆
</button>
</div>
</div>
</header>
<!-- 主内容区 -->
<main class="album-content">
<div class="container">
<!-- 相册介绍 -->
<div class="album-intro">
<h2 class="intro-title">记录我们的每一个美好瞬间</h2>
<p class="intro-text">时光荏苒,爱意绵长。这里收藏着属于我们的点点滴滴,每一张照片都是一段珍贵的回忆。</p>
</div>
<!-- 筛选标签 -->
<div class="filter-tags">
<button
class="filter-tag"
:class="{ active: currentFilter === 'all' }"
@click="setFilter('all')"
>
全部回忆
</button>
<button
class="filter-tag"
:class="{ active: currentFilter === 'date' }"
@click="setFilter('date')"
>
约会时光
</button>
<button
class="filter-tag"
:class="{ active: currentFilter === 'travel' }"
@click="setFilter('travel')"
>
旅行足迹
</button>
<button
class="filter-tag"
:class="{ active: currentFilter === 'festival' }"
@click="setFilter('festival')"
>
节日庆典
</button>
<button
class="filter-tag"
:class="{ active: currentFilter === 'daily' }"
@click="setFilter('daily')"
>
日常点滴
</button>
</div>
<!-- 照片网格 -->
<div class="photo-grid">
<PhotoCard
v-for="photo in filteredPhotos"
:key="photo.id"
:photo="photo"
:on-photo-click="openPhotoModal"
/>
</div>
<!-- 无照片提示 -->
<div class="no-photos" v-if="filteredPhotos.length === 0">
<i class="fa fa-image"></i>
<p>没有找到符合条件的回忆</p>
</div>
</div>
</main>
<!-- 页脚 -->
<footer class="album-footer">
<div class="container">
<p class="footer-quote">我们的故事,未完待续...</p>
<p class="footer-copyright">© {{ currentYear }} 爱与回忆 | 永远在一起</p>
</div>
</footer>
<!-- 照片查看模态框 -->
<div class="modal-backdrop" v-if="isPhotoModalOpen" @click="closePhotoModal">
<div class="modal-content" @click.stop>
<button class="modal-close" @click="closePhotoModal">
<i class="fa fa-times"></i>
</button>
<img
:src="currentPhoto.url"
:alt="currentPhoto.description"
class="modal-image"
>
<div class="modal-info">
<h3 class="modal-date">{{ currentPhotoFormattedDate }}</h3>
<p class="modal-description">{{ currentPhoto.description }}</p>
</div>
</div>
</div>
<!-- 添加照片模态框 -->
<div class="modal-backdrop" v-if="isAddModalOpen" @click="closeAddModal">
<div class="modal-content add-modal-content" @click.stop>
<div class="add-modal-header">
<h3 class="add-modal-title">添加新回忆</h3>
<button class="modal-close" @click="closeAddModal">
<i class="fa fa-times"></i>
</button>
</div>
<form class="add-photo-form" @submit.prevent="addNewPhoto">
<div class="form-group">
<label for="photoDate">日期</label>
<input type="date" id="photoDate" v-model="newPhoto.date" required>
</div>
<div class="form-group">
<label for="photoDescription">描述这段回忆</label>
<textarea
id="photoDescription"
rows="3"
v-model="newPhoto.description"
placeholder="记录下当时的心情和故事..."
required
></textarea>
</div>
<div class="form-group">
<label for="photoCategory">分类</label>
<select id="photoCategory" v-model="newPhoto.category" required>
<option value="date">约会时光</option>
<option value="travel">旅行足迹</option>
<option value="festival">节日庆典</option>
<option value="daily">日常点滴</option>
</select>
</div>
<div class="form-group">
<label for="photoUrl">照片地址</label>
<input
type="text"
id="photoUrl"
v-model="newPhoto.url"
placeholder="输入照片的URL地址"
required
>
</div>
<button type="submit" class="submit-btn">保存回忆</button>
</form>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, reactive } from 'vue';
import PhotoCard from './PhotoCard.vue';
// 照片数据
const photos = ref([
{
id: 1,
date: '2023-10-15',
description: '第一次一起看海,海风很轻,你的笑容很甜。我们在沙滩上写下彼此的名字,约定要一起看遍世界的每一片海。',
url: 'https://picsum.photos/id/1039/800/600',
category: 'date'
},
{
id: 2,
date: '2023-09-21',
description: '中秋节一起做月饼,虽然形状不太完美,但这是我们一起完成的第一个节日美食。月光下的你,比月亮还要温柔。',
url: 'https://picsum.photos/id/292/800/600',
category: 'festival'
},
{
id: 3,
date: '2023-08-05',
description: '周末一起去郊外露营,晚上躺在帐篷里看星星,你说要把最亮的那颗星星命名为我们的名字。',
url: 'https://picsum.photos/id/883/800/600',
category: 'travel'
},
{
id: 4,
date: '2023-07-10',
description: '第一次一起做饭,你负责洗菜我负责炒,虽然有点手忙脚乱,但吃到嘴里的那一刻觉得无比幸福。',
url: 'https://picsum.photos/id/431/800/600',
category: 'daily'
},
{
id: 5,
date: '2023-06-01',
description: '儿童节一起去了游乐园,你像个孩子一样拉着我玩遍了所有项目,旋转木马上的笑容是我见过最美的风景。',
url: 'https://picsum.photos/id/338/800/600',
category: 'date'
},
{
id: 6,
date: '2023-05-20',
description: '520收到的惊喜,你说每天都要像今天一样爱我。其实有你的每一天,都是情人节。',
url: 'https://picsum.photos/id/240/800/600',
category: 'festival'
}
]);
// 状态管理
const sortOrder = ref('desc'); // desc: 倒序, asc: 正序
const currentFilter = ref('all');
const isScrolled = ref(false);
const currentYear = ref(new Date().getFullYear());
// 模态框状态
const isPhotoModalOpen = ref(false);
const currentPhoto = ref(null);
const isAddModalOpen = ref(false);
// 新照片数据
const newPhoto = reactive({
date: '',
description: '',
category: 'date',
url: ''
});
// 处理滚动事件
onMounted(() => {
const handleScroll = () => {
isScrolled.value = window.scrollY > 50;
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
});
// 计算属性 - 排序和筛选照片
const filteredPhotos = computed(() => {
// 复制照片数组以避免修改原始数据
let result = [...photos.value];
// 筛选
if (currentFilter.value !== 'all') {
result = result.filter(photo => photo.category === currentFilter.value);
}
// 排序
result.sort((a, b) => {
const dateA = new Date(a.date).getTime();
const dateB = new Date(b.date).getTime();
return sortOrder.value === 'desc' ? dateB - dateA : dateA - dateB;
});
return result;
});
// 排序文本
const sortText = computed(() => {
return sortOrder.value === 'desc' ? '按时间(最新在前)' : '按时间(最早在前)';
});
// 格式化当前照片日期
const currentPhotoFormattedDate = computed(() => {
if (!currentPhoto.value) return '';
const date = new Date(currentPhoto.value.date);
return date.toLocaleDateString('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
});
// 切换排序方式
const toggleSortOrder = () => {
sortOrder.value = sortOrder.value === 'desc' ? 'asc' : 'desc';
};
// 设置筛选类别
const setFilter = (category) => {
currentFilter.value = category;
};
// 打开照片模态框
const openPhotoModal = (photo) => {
currentPhoto.value = photo;
isPhotoModalOpen.value = true;
document.body.style.overflow = 'hidden';
};
// 关闭照片模态框
const closePhotoModal = () => {
isPhotoModalOpen.value = false;
document.body.style.overflow = '';
};
// 打开添加照片模态框
const openAddModal = () => {
isAddModalOpen.value = true;
document.body.style.overflow = 'hidden';
// 设置默认日期为今天
newPhoto.date = new Date().toISOString().split('T')[0];
};
// 关闭添加照片模态框
const closeAddModal = () => {
isAddModalOpen.value = false;
document.body.style.overflow = '';
// 重置表单
Object.keys(newPhoto).forEach(key => {
newPhoto[key] = key === 'category' ? 'date' : '';
});
};
// 添加新照片
const addNewPhoto = () => {
const newId = photos.value.length > 0
? Math.max(...photos.value.map(p => p.id)) + 1
: 1;
photos.value.unshift({
id: newId,
...{...newPhoto}
});
closeAddModal();
// 可以添加提示信息
alert('回忆添加成功!');
};
</script>
<style scoped>
/* 基础样式 */
.couple-album {
font-family: 'Inter', system-ui, sans-serif;
background: linear-gradient(120deg, #fdfbfb 0%, #f7f4f7 100%);
min-height: 100vh;
color: #333;
}
.container {
width: 100%;
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
/* 头部样式 */
.album-header {
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(4px);
padding: 16px 0;
position: sticky;
top: 0;
z-index: 100;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
}
.album-header.scrolled {
padding: 8px 0;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
}
.album-header .container {
display: flex;
justify-content: space-between;
align-items: center;
}
.album-title {
margin: 0;
font-size: 24px;
font-family: 'Dancing Script', cursive;
color: #E94560;
display: flex;
align-items: center;
}
.album-title i {
margin-right: 8px;
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.6; }
100% { opacity: 1; }
}
.header-actions {
display: flex;
align-items: center;
gap: 15px;
}
.sort-btn {
background: none;
border: none;
color: #1A1A2E;
cursor: pointer;
display: flex;
align-items: center;
gap: 5px;
padding: 5px 10px;
border-radius: 4px;
transition: color 0.2s ease;
}
.sort-btn:hover {
color: #E94560;
}
.add-btn {
background: #E94560;
color: white;
border: none;
border-radius: 20px;
padding: 8px 16px;
display: flex;
align-items: center;
gap: 5px;
cursor: pointer;
box-shadow: 0 2px 5px rgba(233, 69, 96, 0.3);
transition: all 0.2s ease;
}
.add-btn:hover {
background: #d13b54;
box-shadow: 0 4px 8px rgba(233, 69, 96, 0.4);
transform: translateY(-1px);
}
/* 内容区样式 */
.album-content {
padding: 40px 0;
}
.album-intro {
text-align: center;
max-width: 600px;
margin: 0 auto 40px;
}
.intro-title {
font-family: 'Dancing Script', cursive;
font-size: 32px;
color: #1A1A2E;
margin: 0 0 15px;
}
.intro-text {
color: #666;
font-size: 16px;
margin: 0;
}
/* 筛选标签 */
.filter-tags {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 10px;
margin-bottom: 30px;
}
.filter-tag {
background: white;
border: none;
color: #666;
padding: 8px 16px;
border-radius: 20px;
cursor: pointer;
transition: all 0.2s ease;
}
.filter-tag.active {
background: #E94560;
color: white;
}
.filter-tag:not(.active):hover {
background: rgba(233, 69, 96, 0.1);
}
/* 照片网格 */
.photo-grid {
display: grid;
grid-template-columns: 1fr;
gap: 25px;
}
@media (min-width: 768px) {
.photo-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 1024px) {
.photo-grid {
grid-template-columns: repeat(3, 1fr);
}
}
/* 无照片提示 */
.no-photos {
text-align: center;
padding: 50px 0;
color: #aaa;
}
.no-photos i {
font-size: 60px;
margin-bottom: 15px;
display: block;
}
.no-photos p {
margin: 0;
font-size: 16px;
}
/* 页脚样式 */
.album-footer {
background: #1A1A2E;
color: white;
padding: 30px 0;
margin-top: 50px;
}
.footer-quote {
font-family: 'Dancing Script', cursive;
font-size: 24px;
text-align: center;
margin: 0 0 10px;
}
.footer-copyright {
text-align: center;
color: #aaa;
font-size: 14px;
margin: 0;
}
/* 模态框样式 */
.modal-backdrop {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
padding: 20px;
}
.modal-content {
background: white;
border-radius: 8px;
max-width: 900px;
width: 100%;
position: relative;
}
.modal-close {
position: absolute;
top: -30px;
right: 0;
background: none;
border: none;
color: white;
font-size: 24px;
cursor: pointer;
transition: color 0.2s ease;
}
.modal-close:hover {
color: #E94560;
}
.modal-image {
width: 100%;
max-height: 70vh;
object-fit: contain;
}
.modal-info {
padding: 20px;
}
.modal-date {
color: #E94560;
font-weight: 600;
margin: 0 0 10px;
}
.modal-description {
color: #666;
margin: 0;
line-height: 1.6;
}
/* 添加照片模态框 */
.add-modal-content {
max-width: 500px;
width: 100%;
}
.add-modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
.add-modal-title {
margin: 0;
font-family: 'Dancing Script', cursive;
color: #E94560;
font-size: 22px;
}
.add-photo-form {
display: flex;
flex-direction: column;
gap: 15px;
}
.form-group {
display: flex;
flex-direction: column;
gap: 5px;
}
.form-group label {
font-size: 14px;
color: #555;
}
.form-group input,
.form-group textarea,
.form-group select {
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-family: inherit;
font-size: 14px;
}
.form-group input:focus,
.form-group textarea:focus,
.form-group select:focus {
outline: none;
border-color: #E94560;
box-shadow: 0 0 0 2px rgba(233, 69, 96, 0.2);
}
.submit-btn {
background: #E94560;
color: white;
border: none;
border-radius: 4px;
padding: 12px;
font-size: 16px;
cursor: pointer;
transition: background 0.2s ease;
}
.submit-btn:hover {
background: #d13b54;
}
/* 引入Google字体 */
@import url('https://fonts.googleapis.com/css2?family=Dancing+Script:wght@400;700&family=Inter:wght@300;400;500;600&display=swap');
</style>
<template>
<div class="photo-card" :data-category="photo.category">
<div class="photo-container">
<img
:src="photo.url"
:alt="photo.description"
class="photo-image"
@click="handlePhotoClick"
>
<div class="photo-overlay">
<button class="view-photo-btn" @click="handlePhotoClick">
<i class="fa fa-search-plus"></i>
</button>
</div>
</div>
<div class="photo-info">
<div class="photo-header">
<span class="photo-date">{{ formattedDate }}</span>
<span class="photo-category">{{ getCategoryName(photo.category) }}</span>
</div>
<p class="photo-description">{{ photo.description }}</p>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
const props = defineProps({
photo: {
type: Object,
required: true,
properties: {
id: Number,
date: String,
description: String,
url: String,
category: String
}
},
onPhotoClick: {
type: Function,
required: true
}
});
// 格式化日期
const formattedDate = computed(() => {
const date = new Date(props.photo.date);
return date.toLocaleDateString('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
});
// 获取分类名称
const getCategoryName = (category) => {
const categories = {
'date': '约会时光',
'travel': '旅行足迹',
'festival': '节日庆典',
'daily': '日常点滴'
};
return categories[category] || category;
};
// 处理照片点击
const handlePhotoClick = () => {
props.onPhotoClick(props.photo);
};
</script>
<style scoped>
.photo-card {
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
}
.photo-card:hover {
transform: translateY(-5px);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
.photo-container {
position: relative;
overflow: hidden;
height: 200px;
}
.photo-image {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.5s ease;
}
.photo-card:hover .photo-image {
transform: scale(1.1);
}
.photo-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(233, 69, 96, 0.2);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.3s ease;
}
.photo-card:hover .photo-overlay {
opacity: 1;
}
.view-photo-btn {
background: white;
color: #E94560;
border: none;
border-radius: 50%;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transform: translateY(10px);
transition: transform 0.3s ease;
}
.photo-card:hover .view-photo-btn {
transform: translateY(0);
}
.photo-info {
padding: 15px;
}
.photo-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.photo-date {
color: #E94560;
font-weight: 600;
font-size: 14px;
}
.photo-category {
font-size: 12px;
padding: 3px 8px;
border-radius: 12px;
background: rgba(233, 69, 96, 0.1);
color: #E94560;
}
.photo-description {
color: #666;
font-size: 14px;
line-height: 1.5;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
</style>
这个 Vue3 版本的情侣相册组件具有以下特点:
- 组件化结构:分为
CoupleAlbum.vue
(主组件)和PhotoCard.vue
(照片卡片组件),便于维护和扩展。 - 原生 CSS 实现:完全不依赖任何 CSS 框架,所有样式使用普通 CSS 编写,方便您根据需求进行修改。
- 完整的功能:
- 照片按时间排序(可切换正序 / 倒序)
- 分类筛选系统
- 照片查看模态框
- 添加新回忆功能
- 响应式布局设计
- 精美的交互效果:
- 卡片悬停时的上浮和阴影变化
- 照片缩放效果
- 平滑的过渡动画
- 滚动时导航栏样式变化
- 数据管理:使用 Vue3 的 Composition API 管理状态和数据,逻辑清晰。
使用方法:
- 将两个组件文件放入您的 Vue3 项目中
- 在需要使用相册的页面中导入
CoupleAlbum
组件并使用 - 照片数据存储在
CoupleAlbum.vue
的photos
数组中,您可以根据需要修改或扩展 - 如需调整样式,直接修改各组件内的
<style>
部分即可
这个组件保持了原有的浪漫风格,同时使用 Vue3 的特性实现了更好的可维护性和扩展性,方便您集成到自己的项目中。
亲自测试:
(1)创建文件:在docs\.vitepress\theme\components\CoupleAlbum目录
下创建如下2个文件
具体信息见上文 豆包输出的代码:
CoupleAlbum.vue
PhotoCard.vue
(2)注册
编辑docs\.vitepress\theme\index.ts
文件,新加如下几行信息:
// 导入情侣相册组件
import CoupleAlbum from './components/CoupleAlbum/CoupleAlbum.vue'
import PhotoCard from './components/CoupleAlbum/PhotoCard.vue'
app.component('CoupleAlbum', CoupleAlbum) // 注册情侣相册组件
app.component('PhotoCard', PhotoCard)
(3)使用
创建docs\90.关于\70.情侣相册.md
文件:
---
title: 情侣相册
date: 2025-01-04 11:19:48
permalink: /about/love
layout: page
categories:
- 更多
tags:
- 关于我
sidebar: false
article: false
comment: false
articleTopTip: false
articleBottomTip: false
copyright: false
---
<CoupleAlbum />
(4)验证
运行后,验证:
结束。
v3-2025.9.13-豆包输出-解决1个告警问题
提出问题
Port 5173 is in use, trying another one...
vitepress v1.6.4
➜ Local: http://localhost:5174/
➜ Network: use --host to expose
➜ press h to show help
[vite:css] @import must precede all other statements (besides @charset or empty @layer)
346|
347| /* 引入Google字体 */
348| @import url('https://fonts.googleapis.com/css2?family=Dancing+Script:wght@400;700&family=Inter:wght@300;400;500;600&display=swap');
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
349|
最近在处理 前面 那个情侣相册 时 ,发现在运行vitepress项目时弹出了一个告警,该如何解决呢?
豆包生成
这个错误提示是由于 CSS 中 @import
语句的位置不正确导致的。在 CSS 规范中,@import
规则必须放在所有其他 CSS 语句之前(@charset
或空的 @layer
除外)。
要解决这个问题,你需要调整代码中 @import
语句的位置:
- 找到包含
@import url('https://fonts.googleapis.com/css2?family=Dancing+Script:wght@400;700&family=Inter:wght@300;400;500;600&display=swap');
这行代码的 CSS 文件 - 将这个
@import
语句移动到该 CSS 文件的最顶部,确保它在所有其他 CSS 规则之前
例如,错误的写法可能是这样的:
css
/* 一些其他样式 */
body {
margin: 0;
}
/* 引入Google字体 */
@import url('https://fonts.googleapis.com/css2?family=Dancing+Script:wght@400;700&family=Inter:wght@300;400;500;600&display=swap');
正确的写法应该是:
css
/* 引入Google字体 - 放在最顶部 */
@import url('https://fonts.googleapis.com/css2?family=Dancing+Script:wght@400;700&family=Inter:wght@300;400;500;600&display=swap');
/* 其他样式放在@import之后 */
body {
margin: 0;
}
调整后,VitePress 在编译 CSS 时就不会再报这个错误了。这是一个 CSS 语法规范要求,确保 @import
语句在样式表的最前面。
验证测试
(1)按上面所述更新自己文件 docs\.vitepress\theme\components\CoupleAlbum\CoupleAlbum.vue
<template>
<div class="couple-album">
<!-- 顶部导航 -->
<header class="album-header" :class="{ 'scrolled': isScrolled }">
<div class="container">
<h1 class="album-title">
<i class="fa fa-heart"></i> 我们的故事
</h1>
<div class="header-actions">
<button class="sort-btn" @click="toggleSortOrder">
<i class="fa fa-sort"></i>
<span>{{ sortText }}</span>
</button>
<button class="add-btn" @click="openAddModal">
<i class="fa fa-plus"></i> 添加回忆
</button>
</div>
</div>
</header>
<!-- 主内容区 -->
<main class="album-content">
<div class="container">
<!-- 相册介绍 -->
<div class="album-intro">
<h2 class="intro-title">记录我们的每一个美好瞬间</h2>
<p class="intro-text">时光荏苒,爱意绵长。这里收藏着属于我们的点点滴滴,每一张照片都是一段珍贵的回忆。</p>
</div>
<!-- 筛选标签 -->
<div class="filter-tags">
<button
class="filter-tag"
:class="{ active: currentFilter === 'all' }"
@click="setFilter('all')"
>
全部回忆
</button>
<button
class="filter-tag"
:class="{ active: currentFilter === 'date' }"
@click="setFilter('date')"
>
约会时光
</button>
<button
class="filter-tag"
:class="{ active: currentFilter === 'travel' }"
@click="setFilter('travel')"
>
旅行足迹
</button>
<button
class="filter-tag"
:class="{ active: currentFilter === 'festival' }"
@click="setFilter('festival')"
>
节日庆典
</button>
<button
class="filter-tag"
:class="{ active: currentFilter === 'daily' }"
@click="setFilter('daily')"
>
日常点滴
</button>
</div>
<!-- 照片网格 -->
<div class="photo-grid">
<PhotoCard
v-for="photo in filteredPhotos"
:key="photo.id"
:photo="photo"
:on-photo-click="openPhotoModal"
/>
</div>
<!-- 无照片提示 -->
<div class="no-photos" v-if="filteredPhotos.length === 0">
<i class="fa fa-image"></i>
<p>没有找到符合条件的回忆</p>
</div>
</div>
</main>
<!-- 页脚 -->
<footer class="album-footer">
<div class="container">
<p class="footer-quote">我们的故事,未完待续...</p>
<p class="footer-copyright">© {{ currentYear }} 爱与回忆 | 永远在一起</p>
</div>
</footer>
<!-- 照片查看模态框 -->
<div class="modal-backdrop" v-if="isPhotoModalOpen" @click="closePhotoModal">
<div class="modal-content" @click.stop>
<button class="modal-close" @click="closePhotoModal">
<i class="fa fa-times"></i>
</button>
<img
:src="currentPhoto.url"
:alt="currentPhoto.description"
class="modal-image"
>
<div class="modal-info">
<h3 class="modal-date">{{ currentPhotoFormattedDate }}</h3>
<p class="modal-description">{{ currentPhoto.description }}</p>
</div>
</div>
</div>
<!-- 添加照片模态框 -->
<div class="modal-backdrop" v-if="isAddModalOpen" @click="closeAddModal">
<div class="modal-content add-modal-content" @click.stop>
<div class="add-modal-header">
<h3 class="add-modal-title">添加新回忆</h3>
<button class="modal-close" @click="closeAddModal">
<i class="fa fa-times"></i>
</button>
</div>
<form class="add-photo-form" @submit.prevent="addNewPhoto">
<div class="form-group">
<label for="photoDate">日期</label>
<input type="date" id="photoDate" v-model="newPhoto.date" required>
</div>
<div class="form-group">
<label for="photoDescription">描述这段回忆</label>
<textarea
id="photoDescription"
rows="3"
v-model="newPhoto.description"
placeholder="记录下当时的心情和故事..."
required
></textarea>
</div>
<div class="form-group">
<label for="photoCategory">分类</label>
<select id="photoCategory" v-model="newPhoto.category" required>
<option value="date">约会时光</option>
<option value="travel">旅行足迹</option>
<option value="festival">节日庆典</option>
<option value="daily">日常点滴</option>
</select>
</div>
<div class="form-group">
<label for="photoUrl">照片地址</label>
<input
type="text"
id="photoUrl"
v-model="newPhoto.url"
placeholder="输入照片的URL地址"
required
>
</div>
<button type="submit" class="submit-btn">保存回忆</button>
</form>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, reactive } from 'vue';
import PhotoCard from './PhotoCard.vue';
// 照片数据
const photos = ref([
{
id: 1,
date: '2023-10-15',
description: '第一次一起看海,海风很轻,你的笑容很甜。我们在沙滩上写下彼此的名字,约定要一起看遍世界的每一片海。',
url: 'https://picsum.photos/id/1039/800/600',
category: 'date'
},
{
id: 2,
date: '2023-09-21',
description: '中秋节一起做月饼,虽然形状不太完美,但这是我们一起完成的第一个节日美食。月光下的你,比月亮还要温柔。',
url: 'https://picsum.photos/id/292/800/600',
category: 'festival'
},
{
id: 3,
date: '2023-08-05',
description: '周末一起去郊外露营,晚上躺在帐篷里看星星,你说要把最亮的那颗星星命名为我们的名字。',
url: 'https://picsum.photos/id/883/800/600',
category: 'travel'
},
{
id: 4,
date: '2023-07-10',
description: '第一次一起做饭,你负责洗菜我负责炒,虽然有点手忙脚乱,但吃到嘴里的那一刻觉得无比幸福。',
url: 'https://picsum.photos/id/431/800/600',
category: 'daily'
},
{
id: 5,
date: '2023-06-01',
description: '儿童节一起去了游乐园,你像个孩子一样拉着我玩遍了所有项目,旋转木马上的笑容是我见过最美的风景。',
url: 'https://picsum.photos/id/338/800/600',
category: 'date'
},
{
id: 6,
date: '2023-05-20',
description: '520收到的惊喜,你说每天都要像今天一样爱我。其实有你的每一天,都是情人节。',
url: 'https://picsum.photos/id/240/800/600',
category: 'festival'
}
]);
// 状态管理
const sortOrder = ref('desc'); // desc: 倒序, asc: 正序
const currentFilter = ref('all');
const isScrolled = ref(false);
const currentYear = ref(new Date().getFullYear());
// 模态框状态
const isPhotoModalOpen = ref(false);
const currentPhoto = ref(null);
const isAddModalOpen = ref(false);
// 新照片数据
const newPhoto = reactive({
date: '',
description: '',
category: 'date',
url: ''
});
// 处理滚动事件
onMounted(() => {
const handleScroll = () => {
isScrolled.value = window.scrollY > 50;
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
});
// 计算属性 - 排序和筛选照片
const filteredPhotos = computed(() => {
// 复制照片数组以避免修改原始数据
let result = [...photos.value];
// 筛选
if (currentFilter.value !== 'all') {
result = result.filter(photo => photo.category === currentFilter.value);
}
// 排序
result.sort((a, b) => {
const dateA = new Date(a.date).getTime();
const dateB = new Date(b.date).getTime();
return sortOrder.value === 'desc' ? dateB - dateA : dateA - dateB;
});
return result;
});
// 排序文本
const sortText = computed(() => {
return sortOrder.value === 'desc' ? '按时间(最新在前)' : '按时间(最早在前)';
});
// 格式化当前照片日期
const currentPhotoFormattedDate = computed(() => {
if (!currentPhoto.value) return '';
const date = new Date(currentPhoto.value.date);
return date.toLocaleDateString('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
});
// 切换排序方式
const toggleSortOrder = () => {
sortOrder.value = sortOrder.value === 'desc' ? 'asc' : 'desc';
};
// 设置筛选类别
const setFilter = (category) => {
currentFilter.value = category;
};
// 打开照片模态框
const openPhotoModal = (photo) => {
currentPhoto.value = photo;
isPhotoModalOpen.value = true;
document.body.style.overflow = 'hidden';
};
// 关闭照片模态框
const closePhotoModal = () => {
isPhotoModalOpen.value = false;
document.body.style.overflow = '';
};
// 打开添加照片模态框
const openAddModal = () => {
isAddModalOpen.value = true;
document.body.style.overflow = 'hidden';
// 设置默认日期为今天
newPhoto.date = new Date().toISOString().split('T')[0];
};
// 关闭添加照片模态框
const closeAddModal = () => {
isAddModalOpen.value = false;
document.body.style.overflow = '';
// 重置表单
Object.keys(newPhoto).forEach(key => {
newPhoto[key] = key === 'category' ? 'date' : '';
});
};
// 添加新照片
const addNewPhoto = () => {
const newId = photos.value.length > 0
? Math.max(...photos.value.map(p => p.id)) + 1
: 1;
photos.value.unshift({
id: newId,
...{...newPhoto}
});
closeAddModal();
// 可以添加提示信息
alert('回忆添加成功!');
};
</script>
<style scoped>
/* 引入Google字体 */
@import url('https://fonts.googleapis.com/css2?family=Dancing+Script:wght@400;700&family=Inter:wght@300;400;500;600&display=swap');
/* 基础样式 */
.couple-album {
font-family: 'Inter', system-ui, sans-serif;
background: linear-gradient(120deg, #fdfbfb 0%, #f7f4f7 100%);
min-height: 100vh;
color: #333;
}
.container {
width: 100%;
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
/* 头部样式 */
.album-header {
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(4px);
padding: 16px 0;
position: sticky;
top: 0;
z-index: 100;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
}
.album-header.scrolled {
padding: 8px 0;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
}
.album-header .container {
display: flex;
justify-content: space-between;
align-items: center;
}
.album-title {
margin: 0;
font-size: 24px;
font-family: 'Dancing Script', cursive;
color: #E94560;
display: flex;
align-items: center;
}
.album-title i {
margin-right: 8px;
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.6; }
100% { opacity: 1; }
}
.header-actions {
display: flex;
align-items: center;
gap: 15px;
}
.sort-btn {
background: none;
border: none;
color: #1A1A2E;
cursor: pointer;
display: flex;
align-items: center;
gap: 5px;
padding: 5px 10px;
border-radius: 4px;
transition: color 0.2s ease;
}
.sort-btn:hover {
color: #E94560;
}
.add-btn {
background: #E94560;
color: white;
border: none;
border-radius: 20px;
padding: 8px 16px;
display: flex;
align-items: center;
gap: 5px;
cursor: pointer;
box-shadow: 0 2px 5px rgba(233, 69, 96, 0.3);
transition: all 0.2s ease;
}
.add-btn:hover {
background: #d13b54;
box-shadow: 0 4px 8px rgba(233, 69, 96, 0.4);
transform: translateY(-1px);
}
/* 内容区样式 */
.album-content {
padding: 40px 0;
}
.album-intro {
text-align: center;
max-width: 600px;
margin: 0 auto 40px;
}
.intro-title {
font-family: 'Dancing Script', cursive;
font-size: 32px;
color: #1A1A2E;
margin: 0 0 15px;
}
.intro-text {
color: #666;
font-size: 16px;
margin: 0;
}
/* 筛选标签 */
.filter-tags {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 10px;
margin-bottom: 30px;
}
.filter-tag {
background: white;
border: none;
color: #666;
padding: 8px 16px;
border-radius: 20px;
cursor: pointer;
transition: all 0.2s ease;
}
.filter-tag.active {
background: #E94560;
color: white;
}
.filter-tag:not(.active):hover {
background: rgba(233, 69, 96, 0.1);
}
/* 照片网格 */
.photo-grid {
display: grid;
grid-template-columns: 1fr;
gap: 25px;
}
@media (min-width: 768px) {
.photo-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 1024px) {
.photo-grid {
grid-template-columns: repeat(3, 1fr);
}
}
/* 无照片提示 */
.no-photos {
text-align: center;
padding: 50px 0;
color: #aaa;
}
.no-photos i {
font-size: 60px;
margin-bottom: 15px;
display: block;
}
.no-photos p {
margin: 0;
font-size: 16px;
}
/* 页脚样式 */
.album-footer {
background: #1A1A2E;
color: white;
padding: 30px 0;
margin-top: 50px;
}
.footer-quote {
font-family: 'Dancing Script', cursive;
font-size: 24px;
text-align: center;
margin: 0 0 10px;
}
.footer-copyright {
text-align: center;
color: #aaa;
font-size: 14px;
margin: 0;
}
/* 模态框样式 */
.modal-backdrop {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
padding: 20px;
}
.modal-content {
background: white;
border-radius: 8px;
max-width: 900px;
width: 100%;
position: relative;
}
.modal-close {
position: absolute;
top: -30px;
right: 0;
background: none;
border: none;
color: white;
font-size: 24px;
cursor: pointer;
transition: color 0.2s ease;
}
.modal-close:hover {
color: #E94560;
}
.modal-image {
width: 100%;
max-height: 70vh;
object-fit: contain;
}
.modal-info {
padding: 20px;
}
.modal-date {
color: #E94560;
font-weight: 600;
margin: 0 0 10px;
}
.modal-description {
color: #666;
margin: 0;
line-height: 1.6;
}
/* 添加照片模态框 */
.add-modal-content {
max-width: 500px;
width: 100%;
}
.add-modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
.add-modal-title {
margin: 0;
font-family: 'Dancing Script', cursive;
color: #E94560;
font-size: 22px;
}
.add-photo-form {
display: flex;
flex-direction: column;
gap: 15px;
}
.form-group {
display: flex;
flex-direction: column;
gap: 5px;
}
.form-group label {
font-size: 14px;
color: #555;
}
.form-group input,
.form-group textarea,
.form-group select {
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-family: inherit;
font-size: 14px;
}
.form-group input:focus,
.form-group textarea:focus,
.form-group select:focus {
outline: none;
border-color: #E94560;
box-shadow: 0 0 0 2px rgba(233, 69, 96, 0.2);
}
.submit-btn {
background: #E94560;
color: white;
border: none;
border-radius: 4px;
padding: 12px;
font-size: 16px;
cursor: pointer;
transition: background 0.2s ease;
}
.submit-btn:hover {
background: #d13b54;
}
</style>
(2)验证
不会再有报错了:(完美)
结束。