吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 46|回复: 3
上一主题 下一主题
收起左侧

[原创工具] 从零到一:我用原生PHP写了一个高颜值儿童成长相册

[复制链接]
跳转到指定楼层
楼主
天之牙2012 发表于 2026-2-3 14:28 回帖奖励
本帖最后由 天之牙2012 于 2026-2-3 14:35 编辑

【开篇叙事】为什么我要写这个相册系统?

大家好,我是一个普通开发者,也是一个刚当上爸爸的程序员。自从孩子出生后,手机里的照片越来越多,从满月照到第一次翻身、第一次走路……每一个瞬间都想好好保存。市面上的云相册要么收费,要么担心隐私,自己搭又觉得麻烦。于是我决定自己写一个——要简单、要安全、要好看,还要能随时随地访问。不想用数据库,不想装一堆依赖,最好就是一个文件扔到服务器就能用。于是,这个 「儿童成长相册 - 单文件版」 诞生了。它不仅仅是一个相册,更是一个 「时间胶囊」。每次打开,都能按年份、月份自动归类,还能计算孩子的年龄,记录他/她成长的每一步。


【技术特色】这个相册有什么不一样?


纯单文件,无数据库依赖整个系统就一个 photo.php,扔到支持PHP的服务器就能跑。照片按 YYYY-MM-DD.jpg 格式命名,自动按年份归类,无需数据库,全靠文件系统+智能命名。
响应式高颜值UI用了CSS Grid + Flex布局,搭配柔和的橙色调,支持从手机到PC的全端适配。背景有浮动动画、卡片有悬停效果,兼顾视觉美感与交互体验。
照片旋转功能(支持键盘/触摸)在查看大图时,可左右旋转照片(快捷键 [ ]),支持重置,角度实时显示。移动端还支持双指旋转,体验接近原生相册。
智能时间线与年龄计算自动从文件名提取拍摄时间,按年份分组展示,并计算“拍摄时孩子的年龄”,比如“约1岁3个月”。
安全防护到位
  • 使用 realpath 防目录遍历攻击
  • 文件名格式严格校验(YYYY-MM-DD.扩展名)
  • 路径合法性检查,杜绝非法访问

更多实用功能
  • 按年份/关键词搜索
  • 缩略图自动生成(GD库支持)
  • 支持打印样式
  • 键盘导航(左右箭头切换、ESC返回)
  • 响应式图片懒加载

【使用场景】适合哪些人?
  • 想为孩子搭建私人成长相册的家长
  • 想学习PHP文件操作与安全编程的初学者
  • 需要一个小型、美观、可定制相册系统的个人用户
  • 希望脱离云服务、自建隐私相册的技术爱好者


【部署教程】三步上手
  • 在网站根目录创建 /photo 文件夹
  • 将 photo.php 上传至根目录
  • 将照片按 YYYY-MM-DD.jpg 格式命名,放入 /photo 即可

访问 http://你的域名/photo.php,相册自动呈现。【结尾】写在最后这个项目是我作为程序员父亲的一份礼物,也是我对“简单技术实现优雅需求”的一次尝试。它不完美,但足够用;不复杂,但足够美。如果你也需要一个轻量、私密、好看的成长相册,不妨试试它。如果你有更好的建议或想要二次开发,也欢迎一起交流(代码是AI写的,文案是AI出的,只有宝宝是真的)


[PHP] 纯文本查看 复制代码
<?php
// 儿童成长相册 - 单文件版
// 部署说明: 将此文件放在网站根目录,确保/photo目录存在并包含照片
// 配置设置
define('PHOTO_DIR', __DIR__ . '/photo');  // 照片目录路径
define('ALLOWED_EXTENSIONS', ['jpg', 'jpeg', 'png', 'gif', 'webp']);
define('MAX_FILE_SIZE', 20 * 1024 * 1024); // 20MB
define('SITE_NAME', '宝贝成长相册');
define('CHILD_NAME', '来来');  // 修改为孩子的名字
define('TIMEZONE', 'Asia/Shanghai');
date_default_timezone_set(TIMEZONE);

// 安全函数:防止目录遍历攻击
function sanitize_path($path) {
    $path = realpath($path);
    $base = realpath(PHOTO_DIR);
    
    // 确保路径在照片目录内
    if ($path === false || $base === false || strpos($path, $base) !== 0) {
        return false;
    }
    
    return $path;
}

// 获取请求参数
$action = $_GET['action'] ?? 'index';
$photo_name = $_GET['photo'] ?? '';
$size = $_GET['size'] ?? 'medium';
$search = $_GET['search'] ?? '';
$year_filter = $_GET['year'] ?? '';

// 路由处理
switch ($action) {
    case 'view':
        display_photo($photo_name);
        break;
    case 'thumbnail':
        generate_thumbnail($photo_name, $size);
        break;
    case 'full':
        display_full_image($photo_name);
        break;
    default:
        display_gallery($search, $year_filter);
        break;
}

// 显示相册主页
function display_gallery($search = '', $year_filter = '') {
    // 获取所有照片
    $all_photos = get_all_photos();
    
    // 如果有搜索关键词,过滤照片
    $photos = $all_photos;
    if (!empty($search)) {
        $photos = filter_photos_by_search($all_photos, $search);
    }
    
    // 如果有年份筛选,只显示该年份的照片
    if (!empty($year_filter) && isset($photos[$year_filter])) {
        $filtered_photos = [$year_filter => $photos[$year_filter]];
        $photos = $filtered_photos;
    }
    
    // 计算统计数据
    $total_photos = 0;
    $earliest_date = null;
    $latest_date = null;
    
    foreach ($photos as $year => $year_photos) {
        $total_photos += count($year_photos);
        
        foreach ($year_photos as $photo) {
            if (!$earliest_date || $photo['timestamp'] < $earliest_date) {
                $earliest_date = $photo['timestamp'];
            }
            if (!$latest_date || $photo['timestamp'] > $latest_date) {
                $latest_date = $photo['timestamp'];
            }
        }
    }
    
    // 按年份排序(最新的年份在前面)
    krsort($photos);
    
    // 获取所有可用年份
    $all_years = array_keys($all_photos);
    rsort($all_years);
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><?php echo SITE_NAME; ?> - <?php echo CHILD_NAME; ?>的成长记录</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        :root {
            --primary-orange: #FF9800;
            --light-orange: #FFB74D;
            --dark-orange: #F57C00;
            --background: #FFF5E6;
            --card-bg: #FFFFFF;
            --text-dark: #5A4A42;
            --text-light: #7A6A62;
            --success: #41B3A3;
            --warning: #FFA726;
            --error: #F44336;
            --shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
            --shadow-hover: 0 15px 40px rgba(255, 152, 0, 0.2);
        }
        
        body {
            font-family: 'Noto Sans SC', sans-serif;
            background: linear-gradient(135deg, #fdfcfb 0%, #f5e8d0 100%);
            color: var(--text-dark);
            min-height: 100vh;
            line-height: 1.6;
            overflow-x: hidden;
        }
        
        /* 背景装饰 */
        .bg-decoration {
            position: fixed;
            z-index: 0;
            opacity: 0.08;
            pointer-events: none;
            font-size: 80px;
        }
        
        .decoration-1 {
            top: 10%;
            left: 5%;
            animation: float 25s infinite linear;
            color: var(--primary-orange);
        }
        
        .decoration-2 {
            bottom: 15%;
            right: 8%;
            animation: float 30s infinite linear reverse;
            color: var(--light-orange);
            transform: rotate(30deg);
        }
        
        .decoration-3 {
            top: 20%;
            right: 12%;
            animation: float 35s infinite linear;
            color: var(--success);
            transform: rotate(-15deg);
        }
        
        .decoration-4 {
            bottom: 25%;
            left: 10%;
            animation: float 20s infinite linear reverse;
            color: var(--warning);
        }
        
        /* 顶部导航 */
        .top-nav {
            background: rgba(255, 255, 255, 0.95);
            backdrop-filter: blur(10px);
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
            position: sticky;
            top: 0;
            z-index: 100;
            padding: 15px 0;
            border-bottom: 2px solid rgba(255, 152, 0, 0.2);
        }
        
        .nav-container {
            max-width: 1400px;
            margin: 0 auto;
            padding: 0 20px;
            display: flex;
            justify-content: space-between;
            align-items: center;
            flex-wrap: wrap;
            gap: 15px;
        }
        
        .logo {
            display: flex;
            align-items: center;
            gap: 12px;
            text-decoration: none;
        }
        
        .logo-icon {
            width: 48px;
            height: 48px;
            background: linear-gradient(135deg, var(--primary-orange), var(--light-orange));
            border-radius: 12px;
            display: flex;
            align-items: center;
            justify-content: center;
            color: white;
            font-size: 24px;
            box-shadow: 0 5px 15px rgba(255, 152, 0, 0.3);
        }
        
        .logo-text {
            display: flex;
            flex-direction: column;
        }
        
        .logo-title {
            font-size: 1.8rem;
            font-weight: 700;
            color: var(--primary-orange);
            line-height: 1;
            text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.1);
        }
        
        .logo-subtitle {
            font-size: 0.9rem;
            color: var(--text-light);
            margin-top: 2px;
        }
        
        /* 搜索框 */
        .search-box {
            flex: 1;
            max-width: 400px;
            position: relative;
        }
        
        .search-input {
            width: 100%;
            padding: 14px 20px 14px 50px;
            border: 2px solid rgba(255, 152, 0, 0.3);
            border-radius: 50px;
            font-size: 1rem;
            background: white;
            color: var(--text-dark);
            transition: all 0.3s ease;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
        }
        
        .search-input:focus {
            outline: none;
            border-color: var(--primary-orange);
            box-shadow: 0 0 0 3px rgba(255, 152, 0, 0.1);
        }
        
        .search-icon {
            position: absolute;
            left: 20px;
            top: 50%;
            transform: translateY(-50%);
            color: var(--light-orange);
            font-size: 1.1rem;
        }
        
        .search-btn {
            position: absolute;
            right: 6px;
            top: 50%;
            transform: translateY(-50%);
            background: linear-gradient(135deg, var(--primary-orange), var(--light-orange));
            color: white;
            border: none;
            border-radius: 50px;
            padding: 8px 20px;
            cursor: pointer;
            font-weight: 500;
            transition: all 0.3s ease;
        }
        
        .search-btn:hover {
            transform: translateY(-50%) scale(1.05);
            box-shadow: 0 5px 15px rgba(255, 152, 0, 0.4);
        }
        
        /* 主容器 */
        .container {
            max-width: 1400px;
            margin: 0 auto;
            padding: 30px 20px;
            position: relative;
            z-index: 1;
        }
        
        /* 页面标题 */
        .page-header {
            text-align: center;
            margin-bottom: 40px;
            padding: 30px;
            background: rgba(255, 255, 255, 0.9);
            border-radius: 20px;
            box-shadow: var(--shadow);
            border: 2px solid rgba(255, 152, 0, 0.15);
            position: relative;
            overflow: hidden;
        }
        
        .page-header::before {
            content: '';
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 8px;
            background: linear-gradient(90deg, var(--primary-orange), var(--light-orange));
        }
        
        .page-title {
            font-size: 2.8rem;
            color: var(--primary-orange);
            margin-bottom: 15px;
            text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 15px;
        }
        
        .baby-emoji {
            font-size: 3rem;
            animation: bounce 2s infinite;
        }
        
        .page-description {
            font-size: 1.2rem;
            color: var(--text-light);
            max-width: 600px;
            margin: 0 auto;
            line-height: 1.8;
        }
        
        /* 控制面板 */
        .control-panel {
            background: rgba(255, 255, 255, 0.95);
            border-radius: 15px;
            padding: 25px;
            margin-bottom: 40px;
            box-shadow: var(--shadow);
            border: 2px solid rgba(255, 152, 0, 0.2);
            display: flex;
            justify-content: space-between;
            align-items: center;
            flex-wrap: wrap;
            gap: 20px;
        }
        
        .stats-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
            gap: 20px;
            flex: 1;
        }
        
        .stat-card {
            background: linear-gradient(135deg, rgba(255, 152, 0, 0.1), rgba(255, 183, 77, 0.1));
            border-radius: 12px;
            padding: 20px;
            text-align: center;
            border: 1px solid rgba(255, 152, 0, 0.2);
            transition: all 0.3s ease;
        }
        
        .stat-card:hover {
            transform: translateY(-5px);
            box-shadow: 0 10px 25px rgba(255, 152, 0, 0.2);
        }
        
        .stat-number {
            font-size: 2.5rem;
            font-weight: 800;
            color: var(--primary-orange);
            margin-bottom: 8px;
            line-height: 1;
        }
        
        .stat-label {
            color: var(--text-light);
            font-size: 0.95rem;
            font-weight: 500;
        }
        
        .filter-group {
            display: flex;
            gap: 15px;
            align-items: center;
            flex-wrap: wrap;
        }
        
        .year-select {
            padding: 12px 25px 12px 15px;
            border: 2px solid rgba(255, 152, 0, 0.3);
            border-radius: 50px;
            background: white;
            color: var(--text-dark);
            font-size: 1rem;
            cursor: pointer;
            appearance: none;
            background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23FF9800' viewBox='0 0 16 16'%3E%3Cpath d='M7.247 11.14L2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z'/%3E%3C/svg%3E");
            background-repeat: no-repeat;
            background-position: right 15px center;
            background-size: 12px;
            min-width: 180px;
        }
        
        .year-select:focus {
            outline: none;
            border-color: var(--primary-orange);
            box-shadow: 0 0 0 3px rgba(255, 152, 0, 0.1);
        }
        
        .reset-btn {
            padding: 12px 25px;
            background: linear-gradient(135deg, var(--success), #5cd6c6);
            color: white;
            border: none;
            border-radius: 50px;
            font-size: 1rem;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s ease;
            display: flex;
            align-items: center;
            gap: 8px;
        }
        
        .reset-btn:hover {
            transform: translateY(-3px);
            box-shadow: 0 10px 20px rgba(65, 179, 163, 0.3);
        }
        
        /* 年份分组 */
        .year-section {
            background: rgba(255, 255, 255, 0.95);
            border-radius: 15px;
            margin-bottom: 30px;
            overflow: hidden;
            box-shadow: var(--shadow);
            border: 2px solid rgba(255, 152, 0, 0.15);
            animation: fadeIn 0.5s ease-out;
        }
        
        .year-header {
            background: linear-gradient(135deg, var(--primary-orange), var(--light-orange));
            color: white;
            padding: 25px 30px;
            display: flex;
            justify-content: space-between;
            align-items: center;
            cursor: pointer;
            transition: all 0.3s ease;
            position: relative;
            overflow: hidden;
        }
        
        .year-header::before {
            content: '';
            position: absolute;
            top: 0;
            left: -100%;
            width: 100%;
            height: 100%;
            background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
            transition: 0.5s;
        }
        
        .year-header:hover::before {
            left: 100%;
        }
        
        .year-title {
            font-size: 1.8rem;
            font-weight: 700;
            display: flex;
            align-items: center;
            gap: 12px;
            margin: 0;
        }
        
        .year-icon {
            font-size: 1.5rem;
        }
        
        .year-count {
            background: rgba(255, 255, 255, 0.25);
            padding: 8px 20px;
            border-radius: 50px;
            font-weight: 600;
            font-size: 1.1rem;
            display: flex;
            align-items: center;
            gap: 8px;
        }
        
        .year-content {
            padding: 30px;
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
            gap: 25px;
            transition: all 0.3s ease;
        }
        
        .year-content.hidden {
            display: none;
        }
        
        /* 照片卡片 */
        .photo-card {
            background: white;
            border-radius: 12px;
            overflow: hidden;
            box-shadow: var(--shadow);
            transition: all 0.3s ease;
            position: relative;
            border: 1px solid rgba(255, 152, 0, 0.1);
        }
        
        .photo-card:hover {
            transform: translateY(-10px) scale(1.02);
            box-shadow: var(--shadow-hover);
            border-color: var(--primary-orange);
        }
        
        .photo-link {
            display: block;
            text-decoration: none;
            color: inherit;
            position: relative;
        }
        
        .photo-image {
            width: 100%;
            height: 250px;
            object-fit: cover;
            display: block;
            transition: transform 0.5s;
        }
        
        .photo-card:hover .photo-image {
            transform: scale(1.08);
        }
        
        .photo-overlay {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: linear-gradient(to top, rgba(0, 0, 0, 0.6), transparent 50%);
            opacity: 0;
            transition: opacity 0.3s ease;
            display: flex;
            align-items: flex-end;
            padding: 20px;
        }
        
        .photo-card:hover .photo-overlay {
            opacity: 1;
        }
        
        .view-button {
            background: linear-gradient(135deg, var(--primary-orange), var(--light-orange));
            color: white;
            border: none;
            border-radius: 50px;
            padding: 10px 25px;
            font-weight: 600;
            cursor: pointer;
            display: flex;
            align-items: center;
            gap: 8px;
            opacity: 0;
            transform: translateY(20px);
            transition: all 0.3s ease;
        }
        
        .photo-card:hover .view-button {
            opacity: 1;
            transform: translateY(0);
        }
        
        .photo-info {
            padding: 20px;
            background: white;
        }
        
        .photo-date {
            font-weight: 700;
            color: var(--text-dark);
            margin-bottom: 8px;
            font-size: 1.1rem;
            display: flex;
            align-items: center;
            gap: 8px;
        }
        
        .photo-age {
            font-size: 0.95rem;
            color: var(--text-light);
            display: flex;
            align-items: center;
            gap: 5px;
        }
        
        .age-icon {
            color: var(--primary-orange);
        }
        
        /* 空状态 */
        .empty-state {
            text-align: center;
            padding: 80px 20px;
            background: rgba(255, 255, 255, 0.9);
            border-radius: 20px;
            box-shadow: var(--shadow);
            border: 2px dashed rgba(255, 152, 0, 0.3);
            margin: 40px 0;
        }
        
        .empty-icon {
            font-size: 80px;
            color: var(--light-orange);
            margin-bottom: 25px;
            display: block;
        }
        
        .empty-title {
            font-size: 2rem;
            color: var(--text-dark);
            margin-bottom: 15px;
        }
        
        .empty-description {
            max-width: 500px;
            margin: 0 auto 30px;
            line-height: 1.8;
            color: var(--text-light);
        }
        
        /* 提示卡片 */
        .tips-card {
            background: linear-gradient(135deg, rgba(255, 152, 0, 0.1), rgba(255, 183, 77, 0.1));
            border-radius: 15px;
            padding: 25px;
            margin-top: 40px;
            border-left: 5px solid var(--primary-orange);
            box-shadow: var(--shadow);
        }
        
        .tips-title {
            color: var(--primary-orange);
            margin-bottom: 15px;
            font-size: 1.3rem;
            display: flex;
            align-items: center;
            gap: 10px;
        }
        
        .tips-list {
            list-style: none;
            padding: 0;
        }
        
        .tips-list li {
            margin-bottom: 10px;
            padding-left: 30px;
            position: relative;
            line-height: 1.6;
        }
        
        .tips-list li:before {
            content: '&#10003;';
            position: absolute;
            left: 0;
            color: var(--success);
            font-weight: bold;
        }
        
        /* 页脚 */
        .footer {
            text-align: center;
            padding: 40px 20px;
            color: var(--text-light);
            font-size: 0.95rem;
            border-top: 1px solid rgba(255, 152, 0, 0.2);
            margin-top: 60px;
            background: rgba(255, 255, 255, 0.9);
        }
        
        .footer-links {
            display: flex;
            justify-content: center;
            gap: 30px;
            margin-bottom: 20px;
            flex-wrap: wrap;
        }
        
        .footer-link {
            color: var(--primary-orange);
            text-decoration: none;
            display: flex;
            align-items: center;
            gap: 8px;
            transition: all 0.3s ease;
        }
        
        .footer-link:hover {
            color: var(--dark-orange);
            transform: translateY(-2px);
        }
        
        /* 动画 */
        @keyframes float {
            0% { transform: translate(0, 0) rotate(0deg); }
            33% { transform: translate(30px, 30px) rotate(120deg); }
            66% { transform: translate(-20px, 40px) rotate(240deg); }
            100% { transform: translate(0, 0) rotate(360deg); }
        }
        
        @keyframes fadeIn {
            from { opacity: 0; transform: translateY(20px); }
            to { opacity: 1; transform: translateY(0); }
        }
        
        @keyframes bounce {
            0%, 100% { transform: translateY(0); }
            50% { transform: translateY(-15px); }
        }
        
        @keyframes pulse {
            0% { transform: scale(1); }
            50% { transform: scale(1.05); }
            100% { transform: scale(1); }
        }
        
        /* 响应式设计 */
        @media (max-width: 1200px) {
            .container {
                max-width: 95%;
            }
            
            .year-content {
                grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
            }
        }
        
        @media (max-width: 992px) {
            .nav-container {
                flex-direction: column;
                align-items: stretch;
            }
            
            .search-box {
                max-width: 100%;
            }
            
            .control-panel {
                flex-direction: column;
                align-items: stretch;
            }
            
            .filter-group {
                justify-content: center;
            }
            
            .page-title {
                font-size: 2.2rem;
            }
            
            .stats-grid {
                grid-template-columns: repeat(2, 1fr);
            }
        }
        
        @media (max-width: 768px) {
            .container {
                padding: 20px 15px;
            }
            
            .page-header {
                padding: 20px;
            }
            
            .page-title {
                font-size: 1.8rem;
                flex-direction: column;
                gap: 10px;
            }
            
            .baby-emoji {
                font-size: 2.3rem;
            }
            
            .stats-grid {
                grid-template-columns: 1fr;
            }
            
            .year-content {
                grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
                padding: 20px;
                gap: 20px;
            }
            
            .year-header {
                padding: 20px;
                flex-direction: column;
                gap: 15px;
                align-items: flex-start;
            }
            
            .year-count {
                align-self: flex-start;
            }
            
            .filter-group {
                flex-direction: column;
                align-items: stretch;
            }
            
            .year-select, .reset-btn {
                width: 100%;
            }
        }
        
        @media (max-width: 576px) {
            .year-content {
                grid-template-columns: 1fr;
            }
            
            .photo-image {
                height: 200px;
            }
            
            .page-title {
                font-size: 1.6rem;
            }
            
            .page-description {
                font-size: 1rem;
            }
            
            .stat-number {
                font-size: 2rem;
            }
            
            .bg-decoration {
                display: none;
            }
        }
        
        /* 打印样式 */
        @media print {
            .top-nav, .control-panel, .footer, .view-button {
                display: none;
            }
            
            .year-content {
                grid-template-columns: repeat(3, 1fr);
                gap: 10px;
                padding: 10px;
            }
            
            .photo-card {
                break-inside: avoid;
                box-shadow: none;
                border: 1px solid #ddd;
            }
            
            .photo-image {
                height: 150px;
            }
            
            body {
                background: white;
            }
        }
    </style>
</head>
<body>
    <!-- 背景装饰 -->
    <div class="bg-decoration decoration-1">
        <i class="fas fa-heart"></i>
    </div>
    <div class="bg-decoration decoration-2">
        <i class="fas fa-baby"></i>
    </div>
    <div class="bg-decoration decoration-3">
        <i class="fas fa-camera"></i>
    </div>
    <div class="bg-decoration decoration-4">
        <i class="fas fa-star"></i>
    </div>
    
    <!-- 顶部导航 -->
    <div class="top-nav">
        <div class="nav-container">
            <a href="?" class="logo">
                <div class="logo-icon">
                    <i class="fas fa-baby-carriage"></i>
                </div>
                <div class="logo-text">
                    <span class="logo-title"><?php echo SITE_NAME; ?></span>
                    <span class="logo-subtitle"><?php echo CHILD_NAME; ?>的成长记录</span>
                </div>
            </a>
            
            <div class="search-box">
                <i class="fas fa-search search-icon"></i>
                <form method="GET" action="" style="display: inline;">
                    <input type="text" name="search" class="search-input" placeholder="搜索照片..." value="<?php echo htmlspecialchars($search); ?>">
                    <button type="submit" class="search-btn">搜索</button>
                </form>
            </div>
            
            <div style="display: flex; gap: 10px;">
                <a href="?" class="btn" style="padding: 10px 20px; background: linear-gradient(135deg, var(--primary-orange), var(--light-orange)); color: white; border-radius: 50px; text-decoration: none; font-weight: 600;">
                    <i class="fas fa-home"></i> 首页
                </a>
            </div>
        </div>
    </div>
    
    <!-- 主容器 -->
    <div class="container">
        <!-- 页面标题 -->
        <div class="page-header">
            <h1 class="page-title">
                <span class="baby-emoji">&#128118;
                <?php echo CHILD_NAME; ?>的相册
                &#127775;</span>
            </h1>
            <p class="page-description">
                记录<?php echo CHILD_NAME; ?>成长的美好瞬间,每一张照片都是珍贵的记忆
            </p>
        </div>
        
        <?php if (empty($photos)): ?>
            <!-- 空状态 -->
            <div class="empty-state">
                <span class="empty-icon">&#128247;</span>
                <h2 class="empty-title">相册空空如也</h2>
                <p class="empty-description">
                    还没有<?php echo CHILD_NAME; ?>的照片哦,请将照片按"YYYY-MM-DD.jpg"格式命名并放入photo目录
                </p>
                
                <div class="tips-card">
                    <h3 class="tips-title"><i class="fas fa-lightbulb"></i> 使用提示:</h3>
                    <ul class="tips-list">
                        <li>照片文件名格式:<strong>YYYY-MM-DD.jpg</strong>(例如:2026-01-01.jpg)</li>
                        <li>支持的图片格式:JPG、PNG、GIF、WebP</li>
                        <li>将照片放入网站目录下的 <strong>/photo</strong> 文件夹中</li>
                        <li>系统会自动按时间线整理照片</li>
                        <li>您可以使用搜索框按日期查找照片</li>
                    </ul>
                </div>
            </div>
        <?php else: ?>
            <!-- 控制面板 -->
            <div class="control-panel">
                <div class="stats-grid">
                    <div class="stat-card">
                        <div class="stat-number"><?php echo $total_photos; ?></div>
                        <div class="stat-label">成长瞬间</div>
                    </div>
                    <div class="stat-card">
                        <div class="stat-number"><?php echo count($photos); ?></div>
                        <div class="stat-label">成长年份</div>
                    </div>
                    <div class="stat-card">
                        <div class="stat-number">
                            <?php 
                            if ($earliest_date) {
                                echo date('Y', $earliest_date);
                            }
                            ?>
                        </div>
                        <div class="stat-label">记录开始</div>
                    </div>
                    <div class="stat-card">
                        <div class="stat-number">
                            <?php 
                            if ($latest_date) {
                                echo date('Y', $latest_date);
                            }
                            ?>
                        </div>
                        <div class="stat-label">最新记录</div>
                    </div>
                </div>
                
                <div class="filter-group">
                    <select class="year-select">
                        <option value="">所有年份</option>
                        <?php foreach ($all_years as $year): ?>
                            <option value="<?php echo $year; ?>" <?php echo ($year_filter == $year) ? 'selected' : ''; ?>>
                                <?php echo $year; ?>年
                            </option>
                        <?php endforeach; ?>
                    </select>
                    
                    <?php if ($search || $year_filter): ?>
                        <a href="?" class="reset-btn">
                            <i class="fas fa-redo"></i> 重置筛选
                        </a>
                    <?php endif; ?>
                </div>
            </div>
            
            <!-- 年份分组 -->
            <?php foreach ($photos as $year => $year_photos): ?>
                <?php 
                // 计算该年份的照片数量
                $year_count = count($year_photos);
                
                // 计算该年份的平均照片数(按月份)
                $months_with_photos = [];
                foreach ($year_photos as $photo) {
                    $month = date('Y-m', $photo['timestamp']);
                    if (!in_array($month, $months_with_photos)) {
                        $months_with_photos[] = $month;
                    }
                }
                $months_count = count($months_with_photos);
                ?>
                
                <div class="year-section" id="year-<?php echo $year; ?>">
                    <div class="year-header">
                        <h2 class="year-title">
                            <span class="year-icon">&#128197;</span>
                            <?php echo $year; ?>年 · <?php echo CHILD_NAME; ?>的成长记录
                        </h2>
                        <div class="year-count">
                            <i class="fas fa-camera"></i>
                            <?php echo $year_count; ?> 张照片 · <?php echo $months_count; ?> 个月份
                        </div>
                    </div>
                    <div class="year-content" id="content-<?php echo $year; ?>">
                        <?php foreach ($year_photos as $photo): ?>
                            <div class="photo-card">
                                <a href="?action=view&photo=<?php echo urlencode($photo['filename']); ?>" class="photo-link">
                                    <img src="?action=thumbnail&photo=<?php echo urlencode($photo['filename']); ?>&size=medium" 
                                         alt="<?php echo CHILD_NAME; ?>在<?php echo $photo['date_display']; ?>的照片" 
                                         class="photo-image"
                                         loading="lazy">
                                    <div class="photo-overlay">
                                        <button class="view-button">
                                            <i class="fas fa-eye"></i> 查看详情
                                        </button>
                                    </div>
                                </a>
                                <div class="photo-info">
                                    <div class="photo-date">
                                        <i class="far fa-calendar-alt"></i> <?php echo $photo['date_display']; ?>
                                    </div>
                                    <div class="photo-age">
                                        <span class="age-icon">&#128118;</span>
                                        <?php
                                        // 计算孩子的年龄(假设生日为最早的照片日期)
                                        if ($earliest_date) {
                                            $diff = $photo['timestamp'] - $earliest_date;
                                            $years = floor($diff / (365 * 24 * 60 * 60));
                                            $months = floor(($diff % (365 * 24 * 60 * 60)) / (30 * 24 * 60 * 60));
                                            $days = floor(($diff % (30 * 24 * 60 * 60)) / (24 * 60 * 60));
                                            
                                            if ($years > 0) {
                                                echo "约 {$years}岁{$months}个月";
                                            } elseif ($months > 0) {
                                                echo "约 {$months}个月{$days}天";
                                            } else {
                                                echo "约 {$days}天";
                                            }
                                        }
                                        ?>
                                    </div>
                                </div>
                            </div>
                        <?php endforeach; ?>
                    </div>
                </div>
            <?php endforeach; ?>
            
            <!-- 提示卡片 -->
            <div class="tips-card">
                <h3 class="tips-title"><i class="fas fa-info-circle"></i> 相册使用小贴士</h3>
                <ul class="tips-list">
                    <li>点击照片可以查看高清大图</li>
                    <li>使用年份筛选可以快速定位到特定年份的照片</li>
                    <li>在搜索框中输入日期(如"2026-01")可以查找特定月份的照片</li>
                    <li>鼠标悬停在照片上可以预览查看按钮</li>
                    <li>支持键盘导航:在查看页面可以使用左右箭头键切换照片</li>
                </ul>
            </div>
        <?php endif; ?>
    </div>
    
    <!-- 页脚 -->
    <div class="footer">
        <div class="footer-links">
            <a href="#" class="footer-link">
                <i class="fas fa-share-alt"></i> 分享相册
            </a>
            <a href="#" class="footer-link">
                <i class="fas fa-print"></i> 打印相册
            </a>
            <a href="#" class="footer-link">
                <i class="fas fa-download"></i> 备份相册
            </a>
        </div>
        <p><?php echo SITE_NAME; ?> · 记录<?php echo CHILD_NAME; ?>的成长点滴</p>
        <p>&#169; <?php echo date('Y'); ?> 家庭相册 · 由爱而生</p>
        <p style="margin-top: 10px; font-size: 0.85rem; color: #aaa;">
            <i class="fas fa-heart" style="color: #ff6b6b;"></i> 已记录 <?php echo $total_photos ?? 0; ?> 个美好瞬间
        </p>
    </div>
    
    <script>
        // 切换年份显示/隐藏
        function toggleYear(year) {
            const yearContent = document.getElementById('content-' + year);
            const yearHeader = document.querySelector('#year-' + year + ' .year-header');
            
            if (yearContent.classList.contains('hidden')) {
                yearContent.classList.remove('hidden');
                yearHeader.style.borderBottom = 'none';
            } else {
                yearContent.classList.add('hidden');
                yearHeader.style.borderBottom = '2px solid rgba(255, 152, 0, 0.1)';
            }
        }
        
        // 页面加载后初始化
        document.addEventListener('DOMContentLoaded', function() {
            // 默认展开最新年份
            const yearSections = document.querySelectorAll('.year-section');
            if (yearSections.length > 0) {
                // 展开第一个年份(最新的)
                const firstYear = yearSections[0];
                const yearId = firstYear.id.replace('year-', '');
                const yearContent = document.getElementById('content-' + yearId);
                if (yearContent) {
                    yearContent.classList.remove('hidden');
                }
                
                // 折叠其他年份
                for (let i = 1; i < yearSections.length; i++) {
                    const yearId = yearSections[i].id.replace('year-', '');
                    const yearContent = document.getElementById('content-' + yearId);
                    if (yearContent) {
                        yearContent.classList.add('hidden');
                        const yearHeader = yearSections[i].querySelector('.year-header');
                        yearHeader.style.borderBottom = '2px solid rgba(255, 152, 0, 0.1)';
                    }
                }
            }
            
            // 添加滚动动画
            const observerOptions = {
                threshold: 0.1,
                rootMargin: '0px 0px -50px 0px'
            };
            
            const observer = new IntersectionObserver((entries) => {
                entries.forEach(entry => {
                    if (entry.isIntersecting) {
                        entry.target.style.opacity = 1;
                        entry.target.style.transform = 'translateY(0)';
                    }
                });
            }, observerOptions);
            
            // 观察所有年份区域
            document.querySelectorAll('.year-section').forEach(section => {
                section.style.opacity = 0;
                section.style.transform = 'translateY(30px)';
                section.style.transition = 'opacity 0.6s, transform 0.6s';
                observer.observe(section);
            });
            
            // 观察所有照片卡片
            document.querySelectorAll('.photo-card').forEach((card, index) => {
                card.style.opacity = 0;
                card.style.transform = 'translateY(20px)';
                card.style.transition = `opacity 0.5s, transform 0.5s ${index * 0.05}s`;
                
                const cardObserver = new IntersectionObserver((entries) => {
                    entries.forEach(entry => {
                        if (entry.isIntersecting) {
                            entry.target.style.opacity = 1;
                            entry.target.style.transform = 'translateY(0)';
                        }
                    });
                }, observerOptions);
                
                cardObserver.observe(card);
            });
            
            // 更新当前时间
            function updateCurrentTime() {
                const now = new Date();
                const timeString = now.toLocaleDateString('zh-CN', {
                    year: 'numeric',
                    month: 'long',
                    day: 'numeric',
                    weekday: 'long'
                }) + ' ' + now.toLocaleTimeString('zh-CN', {
                    hour: '2-digit',
                    minute: '2-digit'
                });
                
                const timeElement = document.getElementById('current-time');
                if (timeElement) {
                    timeElement.textContent = timeString;
                }
            }
            
            // 添加当前时间显示
            const footer = document.querySelector('.footer p:nth-child(2)');
            if (footer) {
                const timeSpan = document.createElement('span');
                timeSpan.id = 'current-time';
                timeSpan.style.display = 'block';
                timeSpan.style.marginTop = '5px';
                timeSpan.style.fontSize = '0.9rem';
                footer.appendChild(document.createElement('br'));
                footer.appendChild(timeSpan);
                updateCurrentTime();
                setInterval(updateCurrentTime, 60000);
            }
            
            // 添加相册统计
            const totalPhotos = <?php echo $total_photos ?? 0; ?>;
            if (totalPhotos > 0) {
                const earliestDate = <?php echo $earliest_date ?? 0; ?>;
                if (earliestDate) {
                    const earliest = new Date(earliestDate * 1000);
                    const daysDiff = Math.floor((new Date() - earliest) / (1000 * 60 * 60 * 24));
                    
                    const statsElement = document.createElement('div');
                    statsElement.style.marginTop = '10px';
                    statsElement.style.fontSize = '0.85rem';
                    statsElement.style.color = '#888';
                    statsElement.innerHTML = `
                        <i class="fas fa-history"></i> 已记录 ${daysDiff} 天 · 
                        <i class="fas fa-images"></i> 平均 ${(totalPhotos / daysDiff).toFixed(2)} 张/天
                    `;
                    
                    const footerParagraphs = document.querySelectorAll('.footer p');
                    if (footerParagraphs.length > 2) {
                        footerParagraphs[2].appendChild(document.createElement('br'));
                        footerParagraphs[2].appendChild(statsElement);
                    }
                }
            }
        });
        
        // 键盘快捷键
        document.addEventListener('keydown', function(e) {
            // 如果当前在图片查看页面,使用左右箭头导航
            if (window.location.search.includes('action=view')) {
                const prevBtn = document.querySelector('.nav-button.prev');
                const nextBtn = document.querySelector('.nav-button.next');
                
                if (e.key === 'ArrowLeft' && prevBtn) {
                    prevBtn.click();
                } else if (e.key === 'ArrowRight' && nextBtn) {
                    nextBtn.click();
                } else if (e.key === 'Escape') {
                    window.location.href = '?';
                }
            }
        });
        
        // 搜索功能增强
        const searchInput = document.querySelector('.search-input');
        if (searchInput) {
            searchInput.addEventListener('focus', function() {
                this.parentElement.style.transform = 'scale(1.02)';
            });
            
            searchInput.addEventListener('blur', function() {
                this.parentElement.style.transform = 'scale(1)';
            });
            
            // 添加搜索提示
            searchInput.addEventListener('input', function() {
                const searchBtn = this.nextElementSibling;
                if (this.value.trim()) {
                    searchBtn.style.background = 'linear-gradient(135deg, var(--dark-orange), var(--primary-orange))';
                } else {
                    searchBtn.style.background = 'linear-gradient(135deg, var(--primary-orange), var(--light-orange))';
                }
            });
        }
        
        // 添加返回顶部按钮
        const backToTopBtn = document.createElement('button');
        backToTopBtn.innerHTML = '<i class="fas fa-chevron-up"></i>';
        backToTopBtn.style.position = 'fixed';
        backToTopBtn.style.bottom = '30px';
        backToTopBtn.style.right = '30px';
        backToTopBtn.style.width = '50px';
        backToTopBtn.style.height = '50px';
        backToTopBtn.style.background = 'linear-gradient(135deg, var(--primary-orange), var(--light-orange))';
        backToTopBtn.style.color = 'white';
        backToTopBtn.style.border = 'none';
        backToTopBtn.style.borderRadius = '50%';
        backToTopBtn.style.fontSize = '1.2rem';
        backToTopBtn.style.cursor = 'pointer';
        backToTopBtn.style.boxShadow = '0 5px 20px rgba(255, 152, 0, 0.3)';
        backToTopBtn.style.zIndex = '99';
        backToTopBtn.style.opacity = '0';
        backToTopBtn.style.transition = 'all 0.3s ease';
        backToTopBtn.onclick = () => window.scrollTo({ top: 0, behavior: 'smooth' });
        
        document.body.appendChild(backToTopBtn);
        
        // 显示/隐藏返回顶部按钮
        window.addEventListener('scroll', function() {
            if (window.scrollY > 300) {
                backToTopBtn.style.opacity = '1';
                backToTopBtn.style.transform = 'translateY(0)';
            } else {
                backToTopBtn.style.opacity = '0';
                backToTopBtn.style.transform = 'translateY(20px)';
            }
        });
        
        // 添加加载动画
        document.querySelectorAll('img').forEach(img => {
            img.addEventListener('load', function() {
                this.style.animation = 'pulse 0.5s ease';
                setTimeout(() => {
                    this.style.animation = '';
                }, 500);
            });
        });
    </script>
</body>
</html>
<?php
}

// 搜索照片
function filter_photos_by_search($photos, $search) {
    $filtered = [];
    
    foreach ($photos as $year => $year_photos) {
        foreach ($year_photos as $photo) {
            if (stripos($photo['date'], $search) !== false || 
                stripos($photo['date_display'], $search) !== false) {
                $filtered[$year][] = $photo;
            }
        }
    }
    
    return $filtered;
}

// 显示单张照片 - 添加旋转功能
function display_photo($photo_name) {
    // 验证照片文件名
    if (!validate_photo_name($photo_name)) {
        header('HTTP/1.0 400 Bad Request');
        die('无效的照片文件名');
    }
    
    // 获取照片路径
    $photo_path = PHOTO_DIR . '/' . $photo_name;
    $photo_path = sanitize_path($photo_path);
    
    if (!$photo_path || !file_exists($photo_path)) {
        header('HTTP/1.0 404 Not Found');
        die('照片不存在');
    }
    
    // 从文件名提取日期
    $date_str = pathinfo($photo_name, PATHINFO_FILENAME);
    $timestamp = strtotime($date_str);
    $date_display = date('Y年m月d日', $timestamp);
    
    // 获取相邻照片
    $adjacent_photos = get_adjacent_photos($photo_name);
    
    // 计算年龄信息
    $photos = get_all_photos();
    $earliest_date = null;
    foreach ($photos as $year_photos) {
        foreach ($year_photos as $photo) {
            if (!$earliest_date || $photo['timestamp'] < $earliest_date) {
                $earliest_date = $photo['timestamp'];
            }
        }
    }
    
    $age_info = '';
    if ($earliest_date) {
        $diff = $timestamp - $earliest_date;
        $years = floor($diff / (365 * 24 * 60 * 60));
        $months = floor(($diff % (365 * 24 * 60 * 60)) / (30 * 24 * 60 * 60));
        $days = floor(($diff % (30 * 24 * 60 * 60)) / (24 * 60 * 60));
        
        if ($years > 0) {
            $age_info = "约 {$years}岁{$months}个月";
        } elseif ($months > 0) {
            $age_info = "约 {$months}个月{$days}天";
        } else {
            $age_info = "约 {$days}天";
        }
    }
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><?php echo $date_display; ?> - <?php echo CHILD_NAME; ?>的照片 - <?php echo SITE_NAME; ?></title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        :root {
            --primary-orange: #FF9800;
            --light-orange: #FFB74D;
            --dark-orange: #F57C00;
            --background: #FFF5E6;
            --card-bg: #FFFFFF;
            --text-dark: #5A4A42;
            --text-light: #7A6A62;
            --success: #41B3A3;
            --warning: #FFA726;
            --error: #F44336;
        }
        
        body {
            font-family: 'Noto Sans SC', sans-serif;
            background: linear-gradient(135deg, #111 0%, #222 100%);
            color: white;
            line-height: 1.6;
            height: 100vh;
            overflow: hidden;
            position: relative;
        }
        
        /* 背景装饰 */
        .bg-decoration {
            position: absolute;
            z-index: 0;
            opacity: 0.05;
            pointer-events: none;
            font-size: 80px;
        }
        
        .decoration-1 {
            top: 10%;
            left: 5%;
            animation: float 25s infinite linear;
            color: var(--primary-orange);
        }
        
        .decoration-2 {
            bottom: 15%;
            right: 8%;
            animation: float 30s infinite linear reverse;
            color: var(--light-orange);
            transform: rotate(30deg);
        }
        
        /* 查看器容器 */
        .viewer-container {
            display: flex;
            flex-direction: column;
            height: 100vh;
            position: relative;
            z-index: 1;
        }
        
        /* 顶部导航 */
        .viewer-header {
            background: rgba(0, 0, 0, 0.85);
            backdrop-filter: blur(10px);
            padding: 20px 30px;
            display: flex;
            justify-content: space-between;
            align-items: center;
            z-index: 100;
            box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3);
            border-bottom: 2px solid rgba(255, 152, 0, 0.3);
            flex-wrap: wrap;
            gap: 15px;
        }
        
        .viewer-header h1 {
            font-size: 1.4rem;
            color: white;
            display: flex;
            align-items: center;
            gap: 10px;
        }
        
        .viewer-header .child-name {
            color: var(--light-orange);
            font-weight: 700;
        }
        
        .breadcrumb {
            display: flex;
            align-items: center;
            gap: 10px;
            font-size: 0.95rem;
            color: #aaa;
        }
        
        .breadcrumb a {
            color: var(--light-orange);
            text-decoration: none;
            transition: color 0.3s;
        }
        
        .breadcrumb a:hover {
            color: white;
            text-decoration: underline;
        }
        
        .nav-links {
            display: flex;
            gap: 15px;
            align-items: center;
        }
        
        .nav-links a {
            color: white;
            text-decoration: none;
            padding: 10px 20px;
            border-radius: 50px;
            background: rgba(255, 152, 0, 0.2);
            transition: all 0.3s;
            font-size: 0.95rem;
            display: flex;
            align-items: center;
            gap: 8px;
            border: 1px solid rgba(255, 152, 0, 0.3);
        }
        
        .nav-links a:hover {
            background: rgba(255, 152, 0, 0.4);
            transform: translateY(-3px);
            box-shadow: 0 5px 15px rgba(255, 152, 0, 0.2);
        }
        
        /* 照片查看区域 */
        .photo-viewer {
            flex: 1;
            display: flex;
            justify-content: center;
            align-items: center;
            overflow: hidden;
            position: relative;
            background: #000;
            padding: 20px;
        }
        
        .photo-container {
            max-width: 95%;
            max-height: 80vh;
            display: flex;
            justify-content: center;
            align-items: center;
            position: relative;
        }
        
        .photo-image-full {
            max-width: 100%;
            max-height: 80vh;
            object-fit: contain;
            border-radius: 10px;
            box-shadow: 0 20px 50px rgba(0, 0, 0, 0.7);
            animation: fadeIn 0.8s ease;
            transition: transform 0.5s ease;
        }
        
        /* 旋转控制栏 */
        .rotate-controls {
            position: absolute;
            bottom: 20px;
            left: 50%;
            transform: translateX(-50%);
            display: flex;
            gap: 15px;
            background: rgba(0, 0, 0, 0.7);
            padding: 12px 20px;
            border-radius: 50px;
            backdrop-filter: blur(10px);
            border: 1px solid rgba(255, 152, 0, 0.3);
            z-index: 10;
            box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3);
            opacity: 0.7;
            transition: opacity 0.3s;
        }
        
        .rotate-controls:hover {
            opacity: 1;
        }
        
        .rotate-btn {
            background: rgba(255, 152, 0, 0.3);
            color: white;
            border: none;
            width: 45px;
            height: 45px;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 1.2rem;
            cursor: pointer;
            transition: all 0.3s;
            border: 1px solid rgba(255, 152, 0, 0.5);
        }
        
        .rotate-btn:hover {
            background: var(--primary-orange);
            transform: scale(1.1);
            box-shadow: 0 5px 15px rgba(255, 152, 0, 0.3);
        }
        
        .rotate-value {
            color: white;
            font-size: 1rem;
            display: flex;
            align-items: center;
            justify-content: center;
            min-width: 60px;
            font-weight: 600;
        }
        
        .reset-rotate-btn {
            background: rgba(65, 179, 163, 0.3);
            border-color: rgba(65, 179, 163, 0.5);
        }
        
        .reset-rotate-btn:hover {
            background: var(--success);
        }
        
        /* 旋转角度指示器 */
        .rotation-indicator {
            position: absolute;
            top: 20px;
            right: 20px;
            background: rgba(0, 0, 0, 0.7);
            color: white;
            padding: 8px 15px;
            border-radius: 20px;
            font-size: 0.9rem;
            display: none;
            align-items: center;
            gap: 8px;
            backdrop-filter: blur(10px);
            border: 1px solid rgba(255, 152, 0, 0.3);
            z-index: 10;
        }
        
        /* 导航箭头 */
        .arrow-nav {
            position: absolute;
            top: 50%;
            transform: translateY(-50%);
            background: rgba(255, 152, 0, 0.8);
            color: white;
            width: 60px;
            height: 60px;
            border-radius: 50%;
            display: flex;
            justify-content: center;
            align-items: center;
            font-size: 24px;
            cursor: pointer;
            transition: all 0.3s;
            z-index: 10;
            border: 2px solid rgba(255, 255, 255, 0.2);
            box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3);
        }
        
        .arrow-nav:hover {
            background: var(--primary-orange);
            transform: translateY(-50%) scale(1.1);
            box-shadow: 0 8px 25px rgba(255, 152, 0, 0.4);
        }
        
        .arrow-left {
            left: 30px;
        }
        
        .arrow-right {
            right: 30px;
        }
        
        /* 信息面板 */
        .photo-info-panel {
            background: rgba(0, 0, 0, 0.85);
            backdrop-filter: blur(10px);
            padding: 25px 30px;
            text-align: center;
            z-index: 100;
            box-shadow: 0 -5px 20px rgba(0, 0, 0, 0.3);
            border-top: 2px solid rgba(255, 152, 0, 0.3);
        }
        
        .photo-info-content {
            max-width: 800px;
            margin: 0 auto;
        }
        
        .photo-date-large {
            font-size: 1.8rem;
            font-weight: 700;
            margin-bottom: 10px;
            color: white;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 15px;
        }
        
        .photo-age-info {
            color: var(--light-orange);
            font-size: 1.2rem;
            margin-bottom: 15px;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 10px;
        }
        
        .photo-filename {
            color: #aaa;
            font-size: 0.95rem;
            margin-bottom: 20px;
            background: rgba(255, 255, 255, 0.1);
            padding: 10px 20px;
            border-radius: 10px;
            display: inline-block;
        }
        
        .navigation-buttons {
            display: flex;
            justify-content: center;
            gap: 20px;
            margin-top: 20px;
            flex-wrap: wrap;
        }
        
        .nav-button {
            padding: 14px 30px;
            background: linear-gradient(135deg, var(--primary-orange), var(--light-orange));
            color: white;
            text-decoration: none;
            border-radius: 50px;
            font-weight: 600;
            transition: all 0.3s;
            min-width: 150px;
            text-align: center;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 10px;
            border: none;
            cursor: pointer;
            font-size: 1rem;
            box-shadow: 0 5px 15px rgba(255, 152, 0, 0.3);
        }
        
        .nav-button:hover {
            transform: translateY(-5px);
            box-shadow: 0 10px 25px rgba(255, 152, 0, 0.4);
        }
        
        .nav-button.disabled {
            background: rgba(100, 100, 100, 0.5);
            cursor: not-allowed;
            pointer-events: none;
            opacity: 0.6;
        }
        
        .back-button {
            background: linear-gradient(135deg, var(--success), #5cd6c6);
        }
        
        .back-button:hover {
            box-shadow: 0 10px 25px rgba(65, 179, 163, 0.4);
        }
        
        .download-button {
            background: linear-gradient(135deg, #2196F3, #64B5F6);
        }
        
        .download-button:hover {
            box-shadow: 0 10px 25px rgba(33, 150, 243, 0.4);
        }
        
        /* 照片元数据 */
        .photo-meta {
            display: flex;
            justify-content: center;
            gap: 30px;
            margin-top: 20px;
            flex-wrap: wrap;
        }
        
        .meta-item {
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 5px;
        }
        
        .meta-icon {
            font-size: 1.5rem;
            color: var(--light-orange);
        }
        
        .meta-label {
            font-size: 0.9rem;
            color: #aaa;
        }
        
        .meta-value {
            font-size: 1rem;
            font-weight: 600;
            color: white;
        }
        
        /* 动画 */
        @keyframes fadeIn {
            from { opacity: 0; transform: scale(0.95); }
            to { opacity: 1; transform: scale(1); }
        }
        
        @keyframes float {
            0% { transform: translate(0, 0) rotate(0deg); }
            33% { transform: translate(30px, 30px) rotate(120deg); }
            66% { transform: translate(-20px, 40px) rotate(240deg); }
            100% { transform: translate(0, 0) rotate(360deg); }
        }
        
        /* 响应式调整 */
        @media (max-width: 992px) {
            .viewer-header {
                flex-direction: column;
                text-align: center;
                padding: 15px;
            }
            
            .breadcrumb {
                justify-content: center;
            }
            
            .nav-links {
                justify-content: center;
            }
            
            .arrow-nav {
                width: 50px;
                height: 50px;
                font-size: 20px;
            }
            
            .arrow-left {
                left: 15px;
            }
            
            .arrow-right {
                right: 15px;
            }
            
            .photo-image-full {
                max-height: 70vh;
            }
            
            .rotate-controls {
                bottom: 10px;
                padding: 10px 15px;
            }
            
            .rotate-btn {
                width: 40px;
                height: 40px;
                font-size: 1rem;
            }
        }
        
        @media (max-width: 768px) {
            .photo-date-large {
                font-size: 1.4rem;
            }
            
            .photo-age-info {
                font-size: 1rem;
            }
            
            .nav-button {
                min-width: 120px;
                padding: 12px 20px;
                font-size: 0.9rem;
            }
            
            .navigation-buttons {
                gap: 10px;
            }
            
            .photo-meta {
                gap: 15px;
            }
            
            .rotate-controls {
                gap: 10px;
                padding: 8px 12px;
            }
            
            .rotate-value {
                min-width: 50px;
                font-size: 0.9rem;
            }
        }
        
        @media (max-width: 576px) {
            .photo-image-full {
                max-height: 60vh;
            }
            
            .arrow-nav {
                width: 40px;
                height: 40px;
                font-size: 18px;
            }
            
            .arrow-left {
                left: 10px;
            }
            
            .arrow-right {
                right: 10px;
            }
            
            .viewer-header h1 {
                font-size: 1.1rem;
            }
            
            .nav-links a {
                padding: 8px 15px;
                font-size: 0.85rem;
            }
            
            .photo-info-panel {
                padding: 15px;
            }
            
            .photo-date-large {
                flex-direction: column;
                gap: 5px;
            }
            
            .rotate-controls {
                flex-wrap: wrap;
                justify-content: center;
                width: 90%;
                border-radius: 25px;
            }
        }
    </style>
</head>
<body>
    <!-- 背景装饰 -->
    <div class="bg-decoration decoration-1">
        <i class="fas fa-heart"></i>
    </div>
    <div class="bg-decoration decoration-2">
        <i class="fas fa-star"></i>
    </div>
    
    <div class="viewer-container">
        <div class="viewer-header">
            <h1>
                <span class="child-name"><?php echo CHILD_NAME; ?></span>的成长记录 · 
                <span class="photo-date"><?php echo $date_display; ?></span>
            </h1>
            
            <div class="breadcrumb">
                <a href="?"><i class="fas fa-home"></i> 首页</a>
                <span><i class="fas fa-chevron-right"></i></span>
                <a href="?year=<?php echo date('Y', $timestamp); ?>"><?php echo date('Y', $timestamp); ?>年</a>
                <span><i class="fas fa-chevron-right"></i></span>
                <span><?php echo $date_display; ?></span>
            </div>
            
            <div class="nav-links">
                <a href="?"><i class="fas fa-th"></i> 返回相册</a>
                <a href="#"><i class="fas fa-print"></i> 打印照片</a>
            </div>
        </div>
        
        <div class="photo-viewer">
            <?php if ($adjacent_photos['prev']): ?>
                <a href="?action=view&photo=<?php echo urlencode($adjacent_photos['prev']); ?>" class="arrow-nav arrow-left">
                    <i class="fas fa-chevron-left"></i>
                </a>
            <?php endif; ?>
            
            <div class="photo-container">
                <img src="?action=full&photo=<?php echo urlencode($photo_name); ?>" 
                     alt="<?php echo CHILD_NAME; ?>在<?php echo $date_display; ?>的照片" 
                     class="photo-image-full"
                     id="main-photo">
                
                <!-- 旋转角度指示器 -->
                <div class="rotation-indicator" id="rotation-indicator">
                    <i class="fas fa-sync-alt"></i>
                    <span id="rotation-angle">0°</span>
                </div>
                
                <!-- 旋转控制栏 -->
                <div class="rotate-controls">
                    <button class="rotate-btn" id="rotate-left" title="向左旋转90° (快捷键: [)">
                        <i class="fas fa-undo"></i>
                    </button>
                    
                    <div class="rotate-value" id="rotate-value">0°</div>
                    
                    <button class="rotate-btn" id="rotate-right" title="向右旋转90° (快捷键: ])">
                        <i class="fas fa-redo"></i>
                    </button>
                    
                    <button class="rotate-btn reset-rotate-btn" id="reset-rotate" title="重置旋转 (快捷键: 0)">
                        <i class="fas fa-history"></i>
                    </button>
                </div>
            </div>
            
            <?php if ($adjacent_photos['next']): ?>
                <a href="?action=view&photo=<?php echo urlencode($adjacent_photos['next']); ?>" class="arrow-nav arrow-right">
                    <i class="fas fa-chevron-right"></i>
                </a>
            <?php endif; ?>
        </div>
        
        <div class="photo-info-panel">
            <div class="photo-info-content">
              
                
                <div class="photo-age-info">
                    <i class="fas fa-baby"></i>
                    <?php echo $age_info; ?>
                </div>
                
          
                
                <div class="navigation-buttons">
                    <?php if ($adjacent_photos['prev']): ?>
                        <a href="?action=view&photo=<?php echo urlencode($adjacent_photos['prev']); ?>" class="nav-button prev">
                            <i class="fas fa-arrow-left"></i> 上一张
                        </a>
                    <?php else: ?>
                        <span class="nav-button disabled">
                            <i class="fas fa-arrow-left"></i> 上一张
                        </span>
                    <?php endif; ?>
                    
                    <a href="?" class="nav-button back-button">
                        <i class="fas fa-th"></i> 返回相册
                    </a>
                    
                    <a href="?action=full&photo=<?php echo urlencode($photo_name); ?>" 
                       download="<?php echo $photo_name; ?>" 
                       class="nav-button download-button">
                        <i class="fas fa-download"></i> 下载原图
                    </a>
                    
                    <?php if ($adjacent_photos['next']): ?>
                        <a href="?action=view&photo=<?php echo urlencode($adjacent_photos['next']); ?>" class="nav-button next">
                            下一张 <i class="fas fa-arrow-right"></i>
                        </a>
                    <?php else: ?>
                        <span class="nav-button disabled">
                            下一张 <i class="fas fa-arrow-right"></i>
                        </span>
                    <?php endif; ?>
                </div>
            </div>
        </div>
    </div>
    
    <script>
        // 旋转功能
        let rotationAngle = 0;
        const mainPhoto = document.getElementById('main-photo');
        const rotateValue = document.getElementById('rotate-value');
        const rotationIndicator = document.getElementById('rotation-indicator');
        const rotationAngleText = document.getElementById('rotation-angle');
        
        // 初始化旋转状态
        function initRotation() {
            // 从sessionStorage中读取旋转角度(如果有)
            const photoId = '<?php echo $photo_name; ?>';
            const savedAngle = sessionStorage.getItem(`rotation_${photoId}`);
            
            if (savedAngle !== null) {
                rotationAngle = parseInt(savedAngle);
                applyRotation();
            }
        }
        
        // 应用旋转
        function applyRotation() {
            // 限制角度在0-360度之间
            rotationAngle = ((rotationAngle % 360) + 360) % 360;
            
            // 应用CSS变换
            mainPhoto.style.transform = `rotate(${rotationAngle}deg)`;
            rotateValue.textContent = `${rotationAngle}°`;
            
            // 显示或隐藏旋转指示器
            if (rotationAngle !== 0) {
                rotationIndicator.style.display = 'flex';
                rotationAngleText.textContent = `${rotationAngle}°`;
            } else {
                rotationIndicator.style.display = 'none';
            }
            
            // 保存到sessionStorage
            const photoId = '<?php echo $photo_name; ?>';
            sessionStorage.setItem(`rotation_${photoId}`, rotationAngle.toString());
        }
        
        // 旋转按钮事件
        document.getElementById('rotate-left').addEventListener('click', function() {
            rotationAngle -= 90;
            applyRotation();
            showRotationNotification('向左旋转90°');
        });
        
        document.getElementById('rotate-right').addEventListener('click', function() {
            rotationAngle += 90;
            applyRotation();
            showRotationNotification('向右旋转90°');
        });
        
        document.getElementById('reset-rotate').addEventListener('click', function() {
            rotationAngle = 0;
            applyRotation();
            showRotationNotification('重置旋转');
        });
        
        // 显示旋转通知
        function showRotationNotification(message) {
            // 创建通知元素
            const notification = document.createElement('div');
            notification.className = 'rotation-notification';
            notification.innerHTML = `
                <i class="fas fa-sync-alt"></i>
                <span>${message}</span>
            `;
            
            // 添加到页面
            document.body.appendChild(notification);
            
            // 添加样式
            notification.style.cssText = `
                position: fixed;
                top: 100px;
                left: 50%;
                transform: translateX(-50%);
                background: rgba(0, 0, 0, 0.8);
                color: white;
                padding: 12px 25px;
                border-radius: 50px;
                display: flex;
                align-items: center;
                gap: 12px;
                z-index: 10000;
                font-weight: 500;
                font-size: 1rem;
                backdrop-filter: blur(10px);
                border: 1px solid rgba(255, 152, 0, 0.5);
                animation: slideInDown 0.3s ease, fadeOut 0.5s ease 2s forwards;
                box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3);
            `;
            
            // 2秒后移除
            setTimeout(() => {
                notification.remove();
            }, 2000);
        }
        
        // 键盘导航
        document.addEventListener('keydown', function(e) {
            const prevBtn = document.querySelector('.nav-button.prev');
            const nextBtn = document.querySelector('.nav-button.next');
            const backBtn = document.querySelector('.back-button');
            
            // 照片导航
            if (e.key === 'ArrowLeft' && prevBtn) {
                prevBtn.click();
            } else if (e.key === 'ArrowRight' && nextBtn) {
                nextBtn.click();
            } else if (e.key === 'Escape' && backBtn) {
                backBtn.click();
            }
            // 旋转控制
            else if (e.key === '[' || e.key === '【') { // 向左旋转
                document.getElementById('rotate-left').click();
                e.preventDefault();
            } else if (e.key === ']' || e.key === '】') { // 向右旋转
                document.getElementById('rotate-right').click();
                e.preventDefault();
            } else if (e.key === '0' || e.key === '零') { // 重置旋转
                document.getElementById('reset-rotate').click();
                e.preventDefault();
            } else if (e.key === 'f' || e.key === 'F') {
                // 全屏切换
                if (!document.fullscreenElement) {
                    mainPhoto.requestFullscreen().catch(err => {
                        console.log(`全屏错误: ${err.message}`);
                    });
                } else {
                    document.exitFullscreen();
                }
            }
        });
        
        // 图片加载错误处理
        mainPhoto.addEventListener('error', function() {
            this.src = 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="400" height="300" viewBox="0 0 400 300"><rect width="400" height="300" fill="%23333"/><text x="200" y="150" font-family="Arial" font-size="20" fill="white" text-anchor="middle">图片加载失败</text></svg>';
        });
        
        // 双击全屏
        mainPhoto.addEventListener('dblclick', function() {
            if (!document.fullscreenElement) {
                this.requestFullscreen().catch(err => {
                    console.log(`全屏错误: ${err.message}`);
                });
            } else {
                document.exitFullscreen();
            }
        });
        
        // 鼠标滚轮切换图片
        document.addEventListener('wheel', function(e) {
            const prevBtn = document.querySelector('.nav-button.prev');
            const nextBtn = document.querySelector('.nav-button.next');
            
            if (e.deltaY > 0 && nextBtn) {
                nextBtn.click();
            } else if (e.deltaY < 0 && prevBtn) {
                prevBtn.click();
            }
        });
        
        // 触摸滑动切换(移动端)
        let touchStartX = 0;
        let touchEndX = 0;
        let touchStartY = 0;
        let touchEndY = 0;
        
        document.addEventListener('touchstart', function(e) {
            touchStartX = e.changedTouches[0].screenX;
            touchStartY = e.changedTouches[0].screenY;
        });
        
        document.addEventListener('touchend', function(e) {
            touchEndX = e.changedTouches[0].screenX;
            touchEndY = e.changedTouches[0].screenY;
            handleSwipe();
        });
        
        function handleSwipe() {
            const swipeThreshold = 50;
            const prevBtn = document.querySelector('.nav-button.prev');
            const nextBtn = document.querySelector('.nav-button.next');
            
            // 水平滑动
            if (Math.abs(touchEndX - touchStartX) > Math.abs(touchEndY - touchStartY)) {
                if (touchEndX < touchStartX - swipeThreshold && nextBtn) {
                    nextBtn.click();
                } else if (touchEndX > touchStartX + swipeThreshold && prevBtn) {
                    prevBtn.click();
                }
            }
        }
        
        // 触摸旋转(双指旋转)
        let initialAngle = 0;
        let initialDistance = 0;
        let isRotating = false;
        
        document.addEventListener('touchstart', function(e) {
            if (e.touches.length === 2) {
                isRotating = true;
                initialAngle = rotationAngle;
                
                // 计算初始距离和角度
                const touch1 = e.touches[0];
                const touch2 = e.touches[1];
                initialDistance = Math.hypot(
                    touch2.clientX - touch1.clientX,
                    touch2.clientY - touch1.clientY
                );
            }
        });
        
        document.addEventListener('touchmove', function(e) {
            if (e.touches.length === 2 && isRotating) {
                e.preventDefault();
                
                const touch1 = e.touches[0];
                const touch2 = e.touches[1];
                
                // 计算当前角度
                const currentAngle = Math.atan2(
                    touch2.clientY - touch1.clientY,
                    touch2.clientX - touch1.clientX
                ) * 180 / Math.PI;
                
                // 计算旋转变化
                if (e.target === mainPhoto || e.target.closest('.photo-container')) {
                    const angleDiff = currentAngle - initialAngle;
                    rotationAngle = Math.round(initialAngle + angleDiff / 5) * 5;
                    applyRotation();
                }
            }
        });
        
        document.addEventListener('touchend', function(e) {
            if (e.touches.length < 2) {
                isRotating = false;
            }
        });
        
        // 添加CSS动画
        const style = document.createElement('style');
        style.textContent = `
            @keyframes slideInDown {
                from { transform: translate(-50%, -20px); opacity: 0; }
                to { transform: translate(-50%, 0); opacity: 1; }
            }
            
            @keyframes fadeOut {
                from { opacity: 1; }
                to { opacity: 0; }
            }
            
            @keyframes pulse {
                0% { transform: scale(1) rotate(var(--rotation-angle)); }
                50% { transform: scale(1.05) rotate(var(--rotation-angle)); }
                100% { transform: scale(1) rotate(var(--rotation-angle)); }
            }
        `;
        document.head.appendChild(style);
        
        // 页面加载完成后初始化旋转功能
        document.addEventListener('DOMContentLoaded', function() {
            initRotation();
            
            // 添加旋转说明
            const photoMeta = document.querySelector('.photo-meta');
            if (photoMeta) {
                const rotationTip = document.createElement('div');
                rotationTip.className = 'meta-item';
                rotationTip.innerHTML = `
                    <div class="meta-icon"><i class="fas fa-sync-alt"></i></div>
                    <div class="meta-label">旋转控制</div>
                    <div class="meta-value">[ ← ] → 0</div>
                `;
                photoMeta.appendChild(rotationTip);
            }
        });
        
        // 切换照片时清除旋转状态(如果需要的话)
        const navButtons = document.querySelectorAll('.nav-button:not(.disabled)');
        navButtons.forEach(btn => {
            btn.addEventListener('click', function() {
                // 如果切换到另一张照片,清除当前照片的旋转状态
                const currentPhotoId = '<?php echo $photo_name; ?>';
                sessionStorage.removeItem(`rotation_${currentPhotoId}`);
            });
        });
    </script>
</body>
</html>
<?php
}

// 文件大小格式化函数
function format_file_size($bytes) {
    if ($bytes >= 1073741824) {
        return number_format($bytes / 1073741824, 2) . ' GB';
    } elseif ($bytes >= 1048576) {
        return number_format($bytes / 1048576, 2) . ' MB';
    } elseif ($bytes >= 1024) {
        return number_format($bytes / 1024, 2) . ' KB';
    } else {
        return $bytes . ' 字节';
    }
}

// 生成缩略图
function generate_thumbnail($photo_name, $size) {
    // 验证照片文件名
    if (!validate_photo_name($photo_name)) {
        header('HTTP/1.0 400 Bad Request');
        die('无效的照片文件名');
    }
    
    // 获取照片路径
    $photo_path = PHOTO_DIR . '/' . $photo_name;
    $photo_path = sanitize_path($photo_path);
    
    if (!$photo_path || !file_exists($photo_path)) {
        header('HTTP/1.0 404 Not Found');
        die('照片不存在');
    }
    
    // 定义缩略图尺寸
    $sizes = [
        'small' => [200, 200],
        'medium' => [400, 400],
        'large' => [800, 800]
    ];
    
    if (!isset($sizes[$size])) {
        $size = 'medium';
    }
    
    list($width, $height) = $sizes[$size];
    
    // 获取文件扩展名
    $ext = strtolower(pathinfo($photo_path, PATHINFO_EXTENSION));
    
    // 检查GD库是否可用
    if (!extension_loaded('gd')) {
        // 如果GD库不可用,直接输出原图
        header('Content-Type: ' . mime_content_type($photo_path));
        readfile($photo_path);
        exit;
    }
    
    // 根据扩展名创建图像资源
    switch ($ext) {
        case 'jpg':
        case 'jpeg':
            $source = @imagecreatefromjpeg($photo_path);
            break;
        case 'png':
            $source = @imagecreatefrompng($photo_path);
            break;
        case 'gif':
            $source = @imagecreatefromgif($photo_path);
            break;
        case 'webp':
            $source = @imagecreatefromwebp($photo_path);
            break;
        default:
            // 不支持的文件类型
            header('Content-Type: image/svg+xml');
            echo '<svg xmlns="http://www.w3.org/2000/svg" width="' . $width . '" height="' . $height . '" viewBox="0 0 ' . $width . ' ' . $height . '"><rect width="' . $width . '" height="' . $height . '" fill="#f5e8d0"/><text x="' . ($width/2) . '" y="' . ($height/2) . '" font-family="Arial" font-size="14" fill="#FF9800" text-anchor="middle">不支持的文件格式</text></svg>';
            exit;
    }
    
    if (!$source) {
        // 如果无法创建图像资源,返回占位符
        header('Content-Type: image/svg+xml');
        echo '<svg xmlns="http://www.w3.org/2000/svg" width="' . $width . '" height="' . $height . '" viewBox="0 0 ' . $width . ' ' . $height . '"><rect width="' . $width . '" height="' . $height . '" fill="#f5e8d0"/><text x="' . ($width/2) . '" y="' . ($height/2) . '" font-family="Arial" font-size="14" fill="#FF9800" text-anchor="middle">无法加载图片</text></svg>';
        exit;
    }
    
    // 获取原始图片尺寸
    $orig_width = imagesx($source);
    $orig_height = imagesy($source);
    
    // 计算缩略图尺寸(保持比例)
    $ratio = $orig_width / $orig_height;
    if ($width / $height > $ratio) {
        $width = $height * $ratio;
    } else {
        $height = $width / $ratio;
    }
    
    // 创建缩略图
    $thumb = imagecreatetruecolor($width, $height);
    
    // 对于PNG和GIF,保留透明度
    if ($ext === 'png' || $ext === 'gif' || $ext === 'webp') {
        imagealphablending($thumb, false);
        imagesavealpha($thumb, true);
        $transparent = imagecolorallocatealpha($thumb, 255, 255, 255, 127);
        imagefilledrectangle($thumb, 0, 0, $width, $height, $transparent);
    }
    
    // 重新采样图像
    imagecopyresampled($thumb, $source, 0, 0, 0, 0, $width, $height, $orig_width, $orig_height);
    
    // 设置缓存头(缓存1小时)
    header('Cache-Control: public, max-age=3600');
    header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 3600) . ' GMT');
    
    // 输出图像
    switch ($ext) {
        case 'jpg':
        case 'jpeg':
            header('Content-Type: image/jpeg');
            imagejpeg($thumb, null, 85);
            break;
        case 'png':
            header('Content-Type: image/png');
            imagepng($thumb);
            break;
        case 'gif':
            header('Content-Type: image/gif');
            imagegif($thumb);
            break;
        case 'webp':
            header('Content-Type: image/webp');
            imagewebp($thumb);
            break;
    }
    
    // 释放内存
    imagedestroy($source);
    imagedestroy($thumb);
    exit;
}

// 显示完整图片
function display_full_image($photo_name) {
    // 验证照片文件名
    if (!validate_photo_name($photo_name)) {
        header('HTTP/1.0 400 Bad Request');
        die('无效的照片文件名');
    }
    
    // 获取照片路径
    $photo_path = PHOTO_DIR . '/' . $photo_name;
    $photo_path = sanitize_path($photo_path);
    
    if (!$photo_path || !file_exists($photo_path)) {
        header('HTTP/1.0 404 Not Found');
        die('照片不存在');
    }
    
    // 获取文件信息
    $file_info = finfo_open(FILEINFO_MIME_TYPE);
    $mime_type = finfo_file($file_info, $photo_path);
    finfo_close($file_info);
    
    // 设置缓存头(缓存1天)
    header('Cache-Control: public, max-age=86400');
    header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 86400) . ' GMT');
    
    // 输出原始图片
    header('Content-Type: ' . $mime_type);
    header('Content-Length: ' . filesize($photo_path));
    header('Content-Disposition: inline; filename="' . basename($photo_path) . '"');
    readfile($photo_path);
    exit;
}

// 辅助函数:验证照片文件名
function validate_photo_name($filename) {
    if (empty($filename)) return false;
    
    // 验证文件名格式:YYYY-MM-DD.扩展名
    $pattern = '/^(\d{4})-(\d{2})-(\d{2})\.(jpg|jpeg|png|gif|webp)$/i';
    
    if (!preg_match($pattern, $filename, $matches)) {
        return false;
    }
    
    // 验证日期是否有效
    $year = intval($matches[1]);
    $month = intval($matches[2]);
    $day = intval($matches[3]);
    
    if (!checkdate($month, $day, $year)) {
        return false;
    }
    
    return true;
}

// 辅助函数:获取所有照片
function get_all_photos() {
    $photos = [];
    
    // 检查目录是否存在
    if (!is_dir(PHOTO_DIR)) {
        // 尝试创建目录
        if (!mkdir(PHOTO_DIR, 0755, true)) {
            return $photos;
        }
    }
    
    // 扫描目录获取照片
    $files = @scandir(PHOTO_DIR);
    if ($files === false) {
        return $photos;
    }
    
    foreach ($files as $file) {
        if ($file === '.' || $file === '..') continue;
        
        $file_path = PHOTO_DIR . '/' . $file;
        
        // 检查是否为文件
        if (!is_file($file_path)) continue;
        
        // 验证文件名格式
        if (!validate_photo_name($file)) continue;
        
        // 从文件名提取日期
        $date_str = pathinfo($file, PATHINFO_FILENAME);
        $timestamp = strtotime($date_str);
        $date_display = date('Y年m月d日', $timestamp);
        $year = date('Y', $timestamp);
        $year_month = date('Y年m月', $timestamp);
        
        // 添加到照片数组
        $photos[$year][] = [
            'filename' => $file,
            'date' => $date_str,
            'date_display' => $date_display,
            'timestamp' => $timestamp,
            'month_group' => $year_month,
            'url' => '?action=view&photo=' . urlencode($file)
        ];
    }
    
    // 在每个年份内按日期排序(最新的在前面)
    foreach ($photos as $year => &$year_photos) {
        usort($year_photos, function($a, $b) {
            return $b['timestamp'] - $a['timestamp'];
        });
    }
    
    return $photos;
}

// 辅助函数:获取相邻照片
function get_adjacent_photos($current_photo) {
    $files = @scandir(PHOTO_DIR);
    if ($files === false) {
        return ['prev' => null, 'next' => null];
    }
    
    $photo_files = [];
    
    foreach ($files as $file) {
        if ($file === '.' || $file === '..') continue;
        
        $file_path = PHOTO_DIR . '/' . $file;
        
        if (!is_file($file_path)) continue;
        
        if (!validate_photo_name($file)) continue;
        
        $photo_files[] = $file;
    }
    
    // 按日期排序
    sort($photo_files);
    
    // 查找当前照片的位置
    $current_index = array_search($current_photo, $photo_files);
    $prev_photo = ($current_index > 0) ? $photo_files[$current_index - 1] : null;
    $next_photo = ($current_index < count($photo_files) - 1) ? $photo_files[$current_index + 1] : null;
    
    return [
        'prev' => $prev_photo,
        'next' => $next_photo
    ];
}
?>

20260203140739.png (767.55 KB, 下载次数: 0)

20260203140739.png

20260203141409.png (382 KB, 下载次数: 0)

20260203141409.png

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

沙发
Mzhang2008 发表于 2026-2-3 14:32
非常用心,点赞
3#
halo灬 发表于 2026-2-3 14:34
4#
醉生梦死. 发表于 2026-2-3 14:35
世人皆知母爱伟大,殊不知父爱同样伟大!向您致敬!
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-2-3 14:36

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表