个人博客系统设计与实现

57 阅读8分钟

个人博客系统,包含用户注册登录、文章发布、图片上传和头像设置等功能。

设计思路

  1. 使用PHP处理后端逻辑和数据库操作
  2. 使用MySQL存储用户信息和博客内容
  3. 使用HTML5/CSS3构建响应式前端界面
  4. 使用JavaScript处理前端交互和表单验证
  5. 使用AJAX实现无刷新操作

完整代码实现

1. 数据库设计 (blog.sql)

CREATE DATABASE blog;
USE blog;

CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    password VARCHAR(255) NOT NULL,
    avatar VARCHAR(255) DEFAULT 'default.png',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE posts (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT NOT NULL,
    title VARCHAR(255) NOT NULL,
    content TEXT NOT NULL,
    image VARCHAR(255),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

CREATE TABLE sessions (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT NOT NULL,
    session_id VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

2. 配置文件 (config.php)

<?php
// 数据库配置
define('DB_HOST', 'localhost');
define('DB_USER', 'root');
define('DB_PASS', '');
define('DB_NAME', 'blog');

// 文件上传配置
define('UPLOAD_PATH', 'uploads/');
define('MAX_FILE_SIZE', 5 * 1024 * 1024); // 5MB
define('ALLOWED_IMAGE_TYPES', ['jpg', 'jpeg', 'png', 'gif']);

// 会话配置
session_start();

// 创建数据库连接
function getDBConnection() {
    $conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
    if ($conn->connect_error) {
        die("数据库连接失败: " . $conn->connect_error);
    }
    return $conn;
}
?>

3. 用户注册 (register.php)

<?php
require_once 'config.php';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $username = trim($_POST['username']);
    $email = trim($_POST['email']);
    $password = $_POST['password'];
    $confirm_password = $_POST['confirm_password'];
    
    $errors = [];
    
    // 验证输入
    if (empty($username) || strlen($username) < 3) {
        $errors[] = "用户名至少需要3个字符";
    }
    
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $errors[] = "请输入有效的邮箱地址";
    }
    
    if (strlen($password) < 6) {
        $errors[] = "密码至少需要6个字符";
    }
    
    if ($password !== $confirm_password) {
        $errors[] = "两次输入的密码不一致";
    }
    
    // 检查用户名和邮箱是否已存在
    $conn = getDBConnection();
    
    $stmt = $conn->prepare("SELECT id FROM users WHERE username = ? OR email = ?");
    $stmt->bind_param("ss", $username, $email);
    $stmt->execute();
    $result = $stmt->get_result();
    
    if ($result->num_rows > 0) {
        $errors[] = "用户名或邮箱已被使用";
    }
    
    if (empty($errors)) {
        // 创建用户
        $hashed_password = password_hash($password, PASSWORD_DEFAULT);
        $stmt = $conn->prepare("INSERT INTO users (username, email, password) VALUES (?, ?, ?)");
        $stmt->bind_param("sss", $username, $email, $hashed_password);
        
        if ($stmt->execute()) {
            echo json_encode(['success' => true, 'message' => '注册成功,请登录']);
        } else {
            echo json_encode(['success' => false, 'message' => '注册失败,请稍后重试']);
        }
    } else {
        echo json_encode(['success' => false, 'message' => implode('<br>', $errors)]);
    }
    
    $conn->close();
}
?>

4. 用户登录 (login.php)

<?php
require_once 'config.php';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $username = trim($_POST['username']);
    $password = $_POST['password'];
    
    $conn = getDBConnection();
    
    $stmt = $conn->prepare("SELECT id, username, password FROM users WHERE username = ? OR email = ?");
    $stmt->bind_param("ss", $username, $username);
    $stmt->execute();
    $result = $stmt->get_result();
    
    if ($result->num_rows === 1) {
        $user = $result->fetch_assoc();
        
        if (password_verify($password, $user['password'])) {
            $_SESSION['user_id'] = $user['id'];
            $_SESSION['username'] = $user['username'];
            
            echo json_encode(['success' => true, 'message' => '登录成功']);
        } else {
            echo json_encode(['success' => false, 'message' => '密码错误']);
        }
    } else {
        echo json_encode(['success' => false, 'message' => '用户不存在']);
    }
    
    $conn->close();
}
?>

5. 头像上传 (upload_avatar.php)

<?php
require_once 'config.php';

if (!isset($_SESSION['user_id'])) {
    echo json_encode(['success' => false, 'message' => '请先登录']);
    exit;
}

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['avatar'])) {
    $user_id = $_SESSION['user_id'];
    $file = $_FILES['avatar'];
    
    // 检查文件错误
    if ($file['error'] !== UPLOAD_ERR_OK) {
        echo json_encode(['success' => false, 'message' => '文件上传错误']);
        exit;
    }
    
    // 检查文件大小
    if ($file['size'] > MAX_FILE_SIZE) {
        echo json_encode(['success' => false, 'message' => '文件大小不能超过5MB']);
        exit;
    }
    
    // 检查文件类型
    $file_extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
    if (!in_array($file_extension, ALLOWED_IMAGE_TYPES)) {
        echo json_encode(['success' => false, 'message' => '只允许上传JPG、PNG和GIF格式的图片']);
        exit;
    }
    
    // 生成唯一文件名
    $filename = uniqid() . '_' . $user_id . '.' . $file_extension;
    $upload_path = UPLOAD_PATH . 'avatars/';
    
    // 确保上传目录存在
    if (!is_dir($upload_path)) {
        mkdir($upload_path, 0777, true);
    }
    
    // 移动文件
    if (move_uploaded_file($file['tmp_name'], $upload_path . $filename)) {
        // 更新数据库
        $conn = getDBConnection();
        $stmt = $conn->prepare("UPDATE users SET avatar = ? WHERE id = ?");
        $stmt->bind_param("si", $filename, $user_id);
        
        if ($stmt->execute()) {
            echo json_encode(['success' => true, 'message' => '头像上传成功', 'filename' => $filename]);
        } else {
            echo json_encode(['success' => false, 'message' => '头像更新失败']);
        }
        
        $conn->close();
    } else {
        echo json_encode(['success' => false, 'message' => '文件保存失败']);
    }
}
?>

6. 发布文章 (create_post.php)

<?php
require_once 'config.php';

if (!isset($_SESSION['user_id'])) {
    echo json_encode(['success' => false, 'message' => '请先登录']);
    exit;
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $user_id = $_SESSION['user_id'];
    $title = trim($_POST['title']);
    $content = trim($_POST['content']);
    $image = null;
    
    $errors = [];
    
    if (empty($title)) {
        $errors[] = "标题不能为空";
    }
    
    if (empty($content)) {
        $errors[] = "内容不能为空";
    }
    
    // 处理图片上传
    if (isset($_FILES['image']) && $_FILES['image']['error'] === UPLOAD_ERR_OK) {
        $file = $_FILES['image'];
        
        // 检查文件大小
        if ($file['size'] > MAX_FILE_SIZE) {
            $errors[] = "图片大小不能超过5MB";
        }
        
        // 检查文件类型
        $file_extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
        if (!in_array($file_extension, ALLOWED_IMAGE_TYPES)) {
            $errors[] = "只允许上传JPG、PNG和GIF格式的图片";
        }
        
        if (empty($errors)) {
            // 生成唯一文件名
            $filename = uniqid() . '_' . $user_id . '.' . $file_extension;
            $upload_path = UPLOAD_PATH . 'posts/';
            
            // 确保上传目录存在
            if (!is_dir($upload_path)) {
                mkdir($upload_path, 0777, true);
            }
            
            // 移动文件
            if (move_uploaded_file($file['tmp_name'], $upload_path . $filename)) {
                $image = $filename;
            } else {
                $errors[] = "图片上传失败";
            }
        }
    }
    
    if (empty($errors)) {
        $conn = getDBConnection();
        $stmt = $conn->prepare("INSERT INTO posts (user_id, title, content, image) VALUES (?, ?, ?, ?)");
        $stmt->bind_param("isss", $user_id, $title, $content, $image);
        
        if ($stmt->execute()) {
            echo json_encode(['success' => true, 'message' => '文章发布成功']);
        } else {
            echo json_encode(['success' => false, 'message' => '文章发布失败']);
        }
        
        $conn->close();
    } else {
        echo json_encode(['success' => false, 'message' => implode('<br>', $errors)]);
    }
}
?>

7. 获取文章列表 (get_posts.php)

<?php
require_once 'config.php';

$conn = getDBConnection();

$page = isset($_GET['page']) ? intval($_GET['page']) : 1;
$limit = 10;
$offset = ($page - 1) * $limit;

// 获取文章总数
$total_result = $conn->query("SELECT COUNT(*) as total FROM posts");
$total_row = $total_result->fetch_assoc();
$total_posts = $total_row['total'];
$total_pages = ceil($total_posts / $limit);

// 获取文章列表
$stmt = $conn->prepare("
    SELECT p.*, u.username, u.avatar 
    FROM posts p 
    JOIN users u ON p.user_id = u.id 
    ORDER BY p.created_at DESC 
    LIMIT ? OFFSET ?
");
$stmt->bind_param("ii", $limit, $offset);
$stmt->execute();
$result = $stmt->get_result();

$posts = [];
while ($row = $result->fetch_assoc()) {
    $posts[] = $row;
}

echo json_encode([
    'success' => true,
    'posts' => $posts,
    'total_pages' => $total_pages,
    'current_page' => $page
]);

$conn->close();
?>

8. 前端界面 (index.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>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }
        
        body {
            background-color: #f5f5f5;
            color: #333;
            line-height: 1.6;
        }
        
        .container {
            max-width: 1200px;
            margin: 0 auto;
            padding: 0 20px;
        }
        
        header {
            background-color: #2c3e50;
            color: white;
            padding: 1rem 0;
            box-shadow: 0 2px 5px rgba(0,0,0,0.1);
        }
        
        .header-content {
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
        
        .logo {
            font-size: 1.8rem;
            font-weight: bold;
        }
        
        nav ul {
            display: flex;
            list-style: none;
        }
        
        nav ul li {
            margin-left: 20px;
        }
        
        nav ul li a {
            color: white;
            text-decoration: none;
            padding: 5px 10px;
            border-radius: 3px;
            transition: background-color 0.3s;
        }
        
        nav ul li a:hover {
            background-color: rgba(255,255,255,0.1);
        }
        
        .auth-buttons button {
            background-color: #3498db;
            color: white;
            border: none;
            padding: 8px 15px;
            border-radius: 3px;
            cursor: pointer;
            margin-left: 10px;
            transition: background-color 0.3s;
        }
        
        .auth-buttons button:hover {
            background-color: #2980b9;
        }
        
        .user-info {
            display: flex;
            align-items: center;
        }
        
        .user-avatar {
            width: 40px;
            height: 40px;
            border-radius: 50%;
            margin-right: 10px;
            object-fit: cover;
        }
        
        .main-content {
            display: flex;
            margin-top: 30px;
        }
        
        .posts-container {
            flex: 3;
            margin-right: 30px;
        }
        
        .sidebar {
            flex: 1;
            background-color: white;
            padding: 20px;
            border-radius: 5px;
            box-shadow: 0 2px 5px rgba(0,0,0,0.1);
        }
        
        .post {
            background-color: white;
            padding: 20px;
            margin-bottom: 20px;
            border-radius: 5px;
            box-shadow: 0 2px 5px rgba(0,0,0,0.1);
        }
        
        .post-header {
            display: flex;
            align-items: center;
            margin-bottom: 15px;
        }
        
        .post-avatar {
            width: 40px;
            height: 40px;
            border-radius: 50%;
            margin-right: 10px;
            object-fit: cover;
        }
        
        .post-author {
            font-weight: bold;
        }
        
        .post-date {
            color: #777;
            font-size: 0.9rem;
        }
        
        .post-title {
            font-size: 1.5rem;
            margin-bottom: 10px;
            color: #2c3e50;
        }
        
        .post-content {
            margin-bottom: 15px;
        }
        
        .post-image {
            max-width: 100%;
            border-radius: 5px;
            margin-bottom: 15px;
        }
        
        .modal {
            display: none;
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0,0,0,0.5);
            z-index: 1000;
            align-items: center;
            justify-content: center;
        }
        
        .modal-content {
            background-color: white;
            padding: 30px;
            border-radius: 5px;
            width: 90%;
            max-width: 500px;
            box-shadow: 0 5px 15px rgba(0,0,0,0.3);
        }
        
        .modal h2 {
            margin-bottom: 20px;
            color: #2c3e50;
        }
        
        .form-group {
            margin-bottom: 15px;
        }
        
        .form-group label {
            display: block;
            margin-bottom: 5px;
            font-weight: bold;
        }
        
        .form-group input, .form-group textarea {
            width: 100%;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 3px;
            font-size: 1rem;
        }
        
        .form-group textarea {
            min-height: 150px;
            resize: vertical;
        }
        
        .btn {
            background-color: #3498db;
            color: white;
            border: none;
            padding: 10px 15px;
            border-radius: 3px;
            cursor: pointer;
            font-size: 1rem;
            transition: background-color 0.3s;
        }
        
        .btn:hover {
            background-color: #2980b9;
        }
        
        .btn-block {
            display: block;
            width: 100%;
        }
        
        .error {
            color: #e74c3c;
            margin-top: 5px;
            font-size: 0.9rem;
        }
        
        .success {
            color: #27ae60;
            margin-top: 5px;
            font-size: 0.9rem;
        }
        
        .pagination {
            display: flex;
            justify-content: center;
            margin-top: 30px;
        }
        
        .pagination button {
            background-color: white;
            border: 1px solid #ddd;
            padding: 8px 12px;
            margin: 0 5px;
            cursor: pointer;
            border-radius: 3px;
        }
        
        .pagination button.active {
            background-color: #3498db;
            color: white;
            border-color: #3498db;
        }
        
        .pagination button:hover:not(.active) {
            background-color: #f5f5f5;
        }
        
        .avatar-preview {
            width: 100px;
            height: 100px;
            border-radius: 50%;
            object-fit: cover;
            margin-bottom: 10px;
            display: none;
        }
        
        .image-preview {
            max-width: 100%;
            max-height: 200px;
            margin-bottom: 10px;
            display: none;
            border-radius: 5px;
        }
        
        @media (max-width: 768px) {
            .main-content {
                flex-direction: column;
            }
            
            .posts-container {
                margin-right: 0;
                margin-bottom: 30px;
            }
            
            .header-content {
                flex-direction: column;
                text-align: center;
            }
            
            nav ul {
                margin-top: 15px;
                justify-content: center;
            }
            
            .auth-buttons {
                margin-top: 15px;
            }
        }
    </style>
</head>
<body>
    <header>
        <div class="container">
            <div class="header-content">
                <div class="logo">我的博客</div>
                <nav>
                    <ul>
                        <li><a href="#" id="home-link">首页</a></li>
                        <li><a href="#" id="create-post-link">写文章</a></li>
                    </ul>
                </nav>
                <div class="auth-buttons" id="auth-buttons">
                    <button id="login-btn">登录</button>
                    <button id="register-btn">注册</button>
                </div>
                <div class="user-info" id="user-info" style="display: none;">
                    <img src="" alt="头像" class="user-avatar" id="user-avatar">
                    <span id="username-display"></span>
                    <button id="logout-btn" style="margin-left: 15px;">退出</button>
                </div>
            </div>
        </div>
    </header>
    
    <div class="container">
        <div class="main-content">
            <div class="posts-container" id="posts-container">
                <!-- 文章列表将通过JavaScript动态加载 -->
            </div>
            
            <div class="sidebar">
                <h3>关于</h3>
                <p>欢迎来到我的个人博客!这里分享我的想法、经验和学习笔记。</p>
                
                <div id="user-actions" style="display: none; margin-top: 20px;">
                    <button id="change-avatar-btn" class="btn">更改头像</button>
                </div>
            </div>
        </div>
        
        <div class="pagination" id="pagination">
            <!-- 分页按钮将通过JavaScript动态生成 -->
        </div>
    </div>
    
    <!-- 登录模态框 -->
    <div class="modal" id="login-modal">
        <div class="modal-content">
            <h2>登录</h2>
            <form id="login-form">
                <div class="form-group">
                    <label for="login-username">用户名或邮箱</label>
                    <input type="text" id="login-username" name="username" required>
                </div>
                <div class="form-group">
                    <label for="login-password">密码</label>
                    <input type="password" id="login-password" name="password" required>
                </div>
                <button type="submit" class="btn btn-block">登录</button>
                <div id="login-message" class="error"></div>
            </form>
        </div>
    </div>
    
    <!-- 注册模态框 -->
    <div class="modal" id="register-modal">
        <div class="modal-content">
            <h2>注册</h2>
            <form id="register-form">
                <div class="form-group">
                    <label for="register-username">用户名</label>
                    <input type="text" id="register-username" name="username" required>
                </div>
                <div class="form-group">
                    <label for="register-email">邮箱</label>
                    <input type="email" id="register-email" name="email" required>
                </div>
                <div class="form-group">
                    <label for="register-password">密码</label>
                    <input type="password" id="register-password" name="password" required>
                </div>
                <div class="form-group">
                    <label for="register-confirm-password">确认密码</label>
                    <input type="password" id="register-confirm-password" name="confirm_password" required>
                </div>
                <button type="submit" class="btn btn-block">注册</button>
                <div id="register-message" class="error"></div>
            </form>
        </div>
    </div>
    
    <!-- 创建文章模态框 -->
    <div class="modal" id="create-post-modal">
        <div class="modal-content">
            <h2>发布新文章</h2>
            <form id="create-post-form" enctype="multipart/form-data">
                <div class="form-group">
                    <label for="post-title">标题</label>
                    <input type="text" id="post-title" name="title" required>
                </div>
                <div class="form-group">
                    <label for="post-content">内容</label>
                    <textarea id="post-content" name="content" required></textarea>
                </div>
                <div class="form-group">
                    <label for="post-image">上传图片 (可选)</label>
                    <input type="file" id="post-image" name="image" accept="image/*">
                    <img id="post-image-preview" class="image-preview">
                </div>
                <button type="submit" class="btn btn-block">发布</button>
                <div id="create-post-message" class="error"></div>
            </form>
        </div>
    </div>
    
    <!-- 更改头像模态框 -->
    <div class="modal" id="change-avatar-modal">
        <div class="modal-content">
            <h2>更改头像</h2>
            <form id="change-avatar-form" enctype="multipart/form-data">
                <div class="form-group">
                    <label for="avatar">选择头像</label>
                    <input type="file" id="avatar" name="avatar" accept="image/*" required>
                    <img id="avatar-preview" class="avatar-preview">
                </div>
                <button type="submit" class="btn btn-block">上传</button>
                <div id="change-avatar-message" class="error"></div>
            </form>
        </div>
    </div>
    
    <script>
        // 全局变量
        let currentPage = 1;
        let totalPages = 1;
        let isLoggedIn = false;
        
        // DOM元素
        const authButtons = document.getElementById('auth-buttons');
        const userInfo = document.getElementById('user-info');
        const usernameDisplay = document.getElementById('username-display');
        const userAvatar = document.getElementById('user-avatar');
        const postsContainer = document.getElementById('posts-container');
        const pagination = document.getElementById('pagination');
        const userActions = document.getElementById('user-actions');
        
        // 模态框
        const loginModal = document.getElementById('login-modal');
        const registerModal = document.getElementById('register-modal');
        const createPostModal = document.getElementById('create-post-modal');
        const changeAvatarModal = document.getElementById('change-avatar-modal');
        
        // 表单
        const loginForm = document.getElementById('login-form');
        const registerForm = document.getElementById('register-form');
        const createPostForm = document.getElementById('create-post-form');
        const changeAvatarForm = document.getElementById('change-avatar-form');
        
        // 按钮
        const loginBtn = document.getElementById('login-btn');
        const registerBtn = document.getElementById('register-btn');
        const logoutBtn = document.getElementById('logout-btn');
        const createPostLink = document.getElementById('create-post-link');
        const homeLink = document.getElementById('home-link');
        const changeAvatarBtn = document.getElementById('change-avatar-btn');
        
        // 图片预览
        const avatarInput = document.getElementById('avatar');
        const avatarPreview = document.getElementById('avatar-preview');
        const postImageInput = document.getElementById('post-image');
        const postImagePreview = document.getElementById('post-image-preview');
        
        // 初始化
        document.addEventListener('DOMContentLoaded', function() {
            checkLoginStatus();
            loadPosts(1);
            
            // 事件监听
            loginBtn.addEventListener('click', () => showModal(loginModal));
            registerBtn.addEventListener('click', () => showModal(registerModal));
            logoutBtn.addEventListener('click', logout);
            createPostLink.addEventListener('click', () => {
                if (isLoggedIn) {
                    showModal(createPostModal);
                } else {
                    showModal(loginModal);
                }
            });
            homeLink.addEventListener('click', () => loadPosts(1));
            changeAvatarBtn.addEventListener('click', () => showModal(changeAvatarModal));
            
            // 表单提交
            loginForm.addEventListener('submit', handleLogin);
            registerForm.addEventListener('submit', handleRegister);
            createPostForm.addEventListener('submit', handleCreatePost);
            changeAvatarForm.addEventListener('submit', handleChangeAvatar);
            
            // 图片预览
            avatarInput.addEventListener('change', function() {
                previewImage(this, avatarPreview);
            });
            
            postImageInput.addEventListener('change', function() {
                previewImage(this, postImagePreview);
            });
            
            // 点击模态框外部关闭
            window.addEventListener('click', function(event) {
                if (event.target.classList.contains('modal')) {
                    hideAllModals();
                }
            });
        });
        
        // 检查登录状态
        function checkLoginStatus() {
            // 在实际应用中,这里应该检查session或token
            // 这里简化处理,假设用户已登录
            // 在实际应用中,你需要通过AJAX请求检查登录状态
            // 这里我们假设用户未登录
            updateUIForLoginStatus(false);
        }
        
        // 更新UI根据登录状态
        function updateUIForLoginStatus(loggedIn) {
            isLoggedIn = loggedIn;
            if (loggedIn) {
                authButtons.style.display = 'none';
                userInfo.style.display = 'flex';
                userActions.style.display = 'block';
                // 在实际应用中,这里应该从服务器获取用户信息
                usernameDisplay.textContent = '用户';
            } else {
                authButtons.style.display = 'block';
                userInfo.style.display = 'none';
                userActions.style.display = 'none';
            }
        }
        
        // 显示模态框
        function showModal(modal) {
            hideAllModals();
            modal.style.display = 'flex';
        }
        
        // 隐藏所有模态框
        function hideAllModals() {
            loginModal.style.display = 'none';
            registerModal.style.display = 'none';
            createPostModal.style.display = 'none';
            changeAvatarModal.style.display = 'none';
        }
        
        // 图片预览
        function previewImage(input, previewElement) {
            const file = input.files[0];
            if (file) {
                const reader = new FileReader();
                reader.onload = function(e) {
                    previewElement.src = e.target.result;
                    previewElement.style.display = 'block';
                }
                reader.readAsDataURL(file);
            }
        }
        
        // 加载文章
        function loadPosts(page) {
            currentPage = page;
            
            fetch(`get_posts.php?page=${page}`)
                .then(response => response.json())
                .then(data => {
                    if (data.success) {
                        displayPosts(data.posts);
                        displayPagination(data.total_pages, data.current_page);
                    } else {
                        postsContainer.innerHTML = '<p>加载文章失败</p>';
                    }
                })
                .catch(error => {
                    console.error('Error:', error);
                    postsContainer.innerHTML = '<p>加载文章失败</p>';
                });
        }
        
        // 显示文章
        function displayPosts(posts) {
            if (posts.length === 0) {
                postsContainer.innerHTML = '<p>暂无文章</p>';
                return;
            }
            
            let postsHTML = '';
            posts.forEach(post => {
                postsHTML += `
                    <div class="post">
                        <div class="post-header">
                            <img src="uploads/avatars/${post.avatar}" alt="${post.username}" class="post-avatar">
                            <div>
                                <div class="post-author">${post.username}</div>
                                <div class="post-date">${new Date(post.created_at).toLocaleString()}</div>
                            </div>
                        </div>
                        <h2 class="post-title">${post.title}</h2>
                        <div class="post-content">${post.content}</div>
                        ${post.image ? `<img src="uploads/posts/${post.image}" alt="文章图片" class="post-image">` : ''}
                    </div>
                `;
            });
            
            postsContainer.innerHTML = postsHTML;
        }
        
        // 显示分页
        function displayPagination(totalPages, currentPage) {
            if (totalPages <= 1) {
                pagination.innerHTML = '';
                return;
            }
            
            let paginationHTML = '';
            
            // 上一页按钮
            if (currentPage > 1) {
                paginationHTML += `<button onclick="loadPosts(${currentPage - 1})">上一页</button>`;
            }
            
            // 页码按钮
            for (let i = 1; i <= totalPages; i++) {
                if (i === currentPage) {
                    paginationHTML += `<button class="active">${i}</button>`;
                } else {
                    paginationHTML += `<button onclick="loadPosts(${i})">${i}</button>`;
                }
            }
            
            // 下一页按钮
            if (currentPage < totalPages) {
                paginationHTML += `<button onclick="loadPosts(${currentPage + 1})">下一页</button>`;
            }
            
            pagination.innerHTML = paginationHTML;
        }
        
        // 处理登录
        function handleLogin(e) {
            e.preventDefault();
            
            const formData = new FormData(loginForm);
            const messageElement = document.getElementById('login-message');
            
            fetch('login.php', {
                method: 'POST',
                body: formData
            })
            .then(response => response.json())
            .then(data => {
                if (data.success) {
                    messageElement.textContent = data.message;
                    messageElement.className = 'success';
                    updateUIForLoginStatus(true);
                    setTimeout(() => {
                        hideAllModals();
                        loadPosts(1);
                    }, 1000);
                } else {
                    messageElement.textContent = data.message;
                    messageElement.className = 'error';
                }
            })
            .catch(error => {
                console.error('Error:', error);
                messageElement.textContent = '登录失败,请稍后重试';
                messageElement.className = 'error';
            });
        }
        
        // 处理注册
        function handleRegister(e) {
            e.preventDefault();
            
            const formData = new FormData(registerForm);
            const messageElement = document.getElementById('register-message');
            
            fetch('register.php', {
                method: 'POST',
                body: formData
            })
            .then(response => response.json())
            .then(data => {
                if (data.success) {
                    messageElement.textContent = data.message;
                    messageElement.className = 'success';
                    setTimeout(() => {
                        hideAllModals();
                        showModal(loginModal);
                    }, 1000);
                } else {
                    messageElement.textContent = data.message;
                    messageElement.className = 'error';
                }
            })
            .catch(error => {
                console.error('Error:', error);
                messageElement.textContent = '注册失败,请稍后重试';
                messageElement.className = 'error';
            });
        }
        
        // 处理创建文章
        function handleCreatePost(e) {
            e.preventDefault();
            
            const formData = new FormData(createPostForm);
            const messageElement = document.getElementById('create-post-message');
            
            fetch('create_post.php', {
                method: 'POST',
                body: formData
            })
            .then(response => response.json())
            .then(data => {
                if (data.success) {
                    messageElement.textContent = data.message;
                    messageElement.className = 'success';
                    createPostForm.reset();
                    postImagePreview.style.display = 'none';
                    setTimeout(() => {
                        hideAllModals();
                        loadPosts(1);
                    }, 1000);
                } else {
                    messageElement.textContent = data.message;
                    messageElement.className = 'error';
                }
            })
            .catch(error => {
                console.error('Error:', error);
                messageElement.textContent = '发布失败,请稍后重试';
                messageElement.className = 'error';
            });
        }
        
        // 处理更改头像
        function handleChangeAvatar(e) {
            e.preventDefault();
            
            const formData = new FormData(changeAvatarForm);
            const messageElement = document.getElementById('change-avatar-message');
            
            fetch('upload_avatar.php', {
                method: 'POST',
                body: formData
            })
            .then(response => response.json())
            .then(data => {
                if (data.success) {
                    messageElement.textContent = data.message;
                    messageElement.className = 'success';
                    userAvatar.src = 'uploads/avatars/' + data.filename;
                    setTimeout(() => {
                        hideAllModals();
                    }, 1000);
                } else {
                    messageElement.textContent = data.message;
                    messageElement.className = 'error';
                }
            })
            .catch(error => {
                console.error('Error:', error);
                messageElement.textContent = '上传失败,请稍后重试';
                messageElement.className = 'error';
            });
        }
        
        // 退出登录
        function logout() {
            // 在实际应用中,这里应该发送请求到服务器清除session
            updateUIForLoginStatus(false);
            loadPosts(1);
        }
    </script>
</body>
</html>

使用说明

  1. 首先创建数据库并导入SQL文件
  2. 修改config.php中的数据库配置
  3. 确保uploads目录及其子目录(avatars, posts)有写权限
  4. 访问index.html开始使用博客系统

功能特点

  • 用户注册和登录
  • 发布博客文章
  • 上传文章图片
  • 设置和更改用户头像
  • 响应式设计,适配各种设备
  • 分页显示文章列表

这个博客系统包含了所有要求的功能,并且代码结构清晰,易于理解和扩展。