Skip to content
0

情侣相册

情侣相册

image-20250912212724371

需求

bash
帮我写个简单的相册:

图片按时间排序,每张图片下有日期,及文字描述。

这个相册主要记录下一对小情侣之间的美好图片回忆。

请帮我输出具体的代码。

v1-2025.9.12-豆包输出-本地测试

豆包回答

我将为您创建一个温馨浪漫的情侣回忆相册网页,按时间排序展示照片,并包含日期和文字描述。这个设计将采用柔和的色调,营造出甜蜜温馨的氛围,同时确保照片展示清晰、交互友好。

::: dtails 具体代码

couple-album.html文件:

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. 温馨浪漫的设计风格:采用粉红色作为主色调,搭配优雅的字体和柔和的背景,营造出甜蜜浪漫的氛围,非常适合记录情侣间的美好回忆。
  2. 时间排序功能:照片默认按时间倒序排列(最新的在前),用户可以点击排序按钮切换为正序排列(最早的在前)。
  3. 分类筛选系统:提供了四种回忆分类(约会时光、旅行足迹、节日庆典、日常点滴),方便快速筛选查看特定类型的回忆。
  4. 响应式布局:在手机、平板和桌面设备上都能完美展示,照片网格会根据屏幕尺寸自动调整布局。
  5. 交互体验
    • 照片卡片悬停时有放大和阴影效果
    • 点击照片可查看大图和完整描述
    • 支持添加新的回忆照片
    • 滚动时导航栏会有动态变化效果
  6. 完整功能:包含照片展示、详情查看、排序、筛选、加载更多和添加新回忆等功能。

您可以直接使用这个代码,也可以根据需要修改照片数据、颜色方案或添加更多功能。

测试效果

还是不错的。

存在问题

问题1:在豆包里都有这个红心,为啥在我部署好的服务器上,红心消失了。。。

image-20250912211420362

v2-2025.9.12-豆包输出-集成到teek里

提出问题

bash
前面的 那个 情侣相册模块,请帮我生成一个 vue3版本的组件,不要使用Tailwind CSS,样式直接使用普通css写在组件内。

豆包回答

我将为您创建一个 Vue3 版本的情侣相册组件,不依赖 Tailwind CSS,使用原生 CSS 编写样式并整合在组件内部,保持原有的浪漫风格和功能特性。

具体代码:
CoupleAlbum.vue
html
<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>
PhotoCard.vue
html
<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 版本的情侣相册组件具有以下特点:

  1. 组件化结构:分为CoupleAlbum.vue(主组件)和PhotoCard.vue(照片卡片组件),便于维护和扩展。
  2. 原生 CSS 实现:完全不依赖任何 CSS 框架,所有样式使用普通 CSS 编写,方便您根据需求进行修改。
  3. 完整的功能:
    • 照片按时间排序(可切换正序 / 倒序)
    • 分类筛选系统
    • 照片查看模态框
    • 添加新回忆功能
    • 响应式布局设计
  4. 精美的交互效果:
    • 卡片悬停时的上浮和阴影变化
    • 照片缩放效果
    • 平滑的过渡动画
    • 滚动时导航栏样式变化
  5. 数据管理:使用 Vue3 的 Composition API 管理状态和数据,逻辑清晰。

使用方法:

  1. 将两个组件文件放入您的 Vue3 项目中
  2. 在需要使用相册的页面中导入CoupleAlbum组件并使用
  3. 照片数据存储在CoupleAlbum.vuephotos数组中,您可以根据需要修改或扩展
  4. 如需调整样式,直接修改各组件内的<style>部分即可

这个组件保持了原有的浪漫风格,同时使用 Vue3 的特性实现了更好的可维护性和扩展性,方便您集成到自己的项目中。


亲自测试:

(1)创建文件:在docs\.vitepress\theme\components\CoupleAlbum目录下创建如下2个文件

具体信息见上文 豆包输出的代码:

bash
CoupleAlbum.vue  
PhotoCard.vue

image-20250912212151862

(2)注册

编辑docs\.vitepress\theme\index.ts文件,新加如下几行信息:

ts
// 导入情侣相册组件
import CoupleAlbum from './components/CoupleAlbum/CoupleAlbum.vue'
import PhotoCard from './components/CoupleAlbum/PhotoCard.vue'



    app.component('CoupleAlbum', CoupleAlbum) // 注册情侣相册组件
    app.component('PhotoCard', PhotoCard)

image-20250912212517691

(3)使用

创建docs\90.关于\70.情侣相册.md文件:

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)验证

运行后,验证:

image-20250912212724371

结束。

v3-2025.9.13-豆包输出-解决1个告警问题

提出问题

bash
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项目时弹出了一个告警,该如何解决呢?

image-20250913070303204

豆包生成

这个错误提示是由于 CSS 中 @import 语句的位置不正确导致的。在 CSS 规范中,@import 规则必须放在所有其他 CSS 语句之前(@charset 或空的 @layer 除外)。

要解决这个问题,你需要调整代码中 @import 语句的位置:

  1. 找到包含 @import url('https://fonts.googleapis.com/css2?family=Dancing+Script:wght@400;700&family=Inter:wght@300;400;500;600&display=swap'); 这行代码的 CSS 文件
  2. 将这个 @import 语句移动到该 CSS 文件的最顶部,确保它在所有其他 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

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

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>

image-20250913070954419

(2)验证

不会再有报错了:(完美)

image-20250913071141678

结束。

最近更新