基于NodeJs+Express+MySQL 实现的个人博客项目

目录

  • 项目简介:
  • 项目目标:
  • 效果展示:
  • [一. 创建项目并初始化](#一. 创建项目并初始化)
  • [二. 项目初始化](#二. 项目初始化)
  • [三. 安装项目所需要的包](#三. 安装项目所需要的包)
  • [四. 创建需要的数据库](#四. 创建需要的数据库)
  • [五. 编写app.js](#五. 编写app.js)
  • [六. 创建前端页面](#六. 创建前端页面)

项目简介:

这个项目是一个基于Node.js和Express框架构建的简易博客系统,用户可以在该系统中进行注册、登录、发布文章、查看文章、删除文章以及留言等操作。项目利用MySQL数据库存储用户和文章数据,并通过前端页面提供用户友好的交互界面。以下是项目的主要功能简介:

主要功能

  1. 用户注册与登录

    • 用户可以通过提供用户名和密码进行注册,注册后凭借用户名和密码登录系统。 * 系统会对用户密码进行加密储存,提高安全性。
  2. 文章管理

    • 登录用户可以发布新的文章,包括输入标题和内容。
    • 用户可以查看所有已发布的文章列表,并可以通过文章ID查找具体文章的详细内容。
    • 登录用户可以删除自己发布的文章。
  3. 留言板功能

    • 用户可以在留言板上发布留言和查看其他用户的留言。
    • 留言也可以被删除。
  4. 文章与留言分页

    • 在文章列表和留言板上支持分页功能,每页显示固定数量的内容,用户可翻页查看更多内容。
  5. 用户会话管理

    • 系统通过Session管理用户登录状态,确保用户的安全性与隐私。
  6. 前端页面

    • 采用HTML、CSS和jQuery构建响应式用户界面,前端页面包括登录页、注册页、文章发布页、文章详情页、留言板等。

技术栈

  • 后端:Node.js, Express, MySQL
  • 前端:HTML, CSS, JavaScript (jQuery)
  • 其他:bcrypt用于密码安全,Axios用于进行API请求。

项目目标:

该项目的目标是提供一个简易的博客系统,使得用户能够方便地进行文章管理和互动留言,增强用户之间的联系与交流。适合于学习Node.js、Express和前后端交互的初学者。

效果展示:

登录页

注册页

首页

文章详情页

添加文章页

留言页

修改和删除文章页

修改文章页

一. 创建项目并初始化

项目结构

二. 项目初始化

bash 复制代码
//进入项目文件夹执行命令初始化
npm init -y

三. 安装项目所需要的包

bash 复制代码
npm i bcrypt body-parser cors express express-session mysql

四. 创建需要的数据库

创建数据库和表用户表、文章表、留言表

sql 复制代码
/*
 Navicat Premium Dump SQL

 Source Server         : weblog2
 Source Server Type    : MySQL
 Source Server Version : 80037 (8.0.37)
 Source Host           : localhost:3306
 Source Schema         : notebook

 Target Server Type    : MySQL
 Target Server Version : 80037 (8.0.37)
 File Encoding         : 65001

 Date: 17/12/2024 19:15:06
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for article
-- ----------------------------
DROP TABLE IF EXISTS `article`;
CREATE TABLE `article`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `username` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin NULL DEFAULT NULL,
  `title` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin NULL DEFAULT NULL,
  `content` varchar(9999) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin NULL DEFAULT NULL,
  `time` varchar(99) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 84 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_bin ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for leaving
-- ----------------------------
DROP TABLE IF EXISTS `leaving`;
CREATE TABLE `leaving`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `username` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin NULL DEFAULT NULL,
  `content` varchar(9999) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin NULL DEFAULT NULL,
  `time` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 54 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_bin ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `username` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin NULL DEFAULT NULL,
  `password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_bin ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

五. 编写app.js

连接数据库和所有后端接口代码都在这里,都写在app.js

bash 复制代码
//导入express模块
const express = require('express')
const bodyParser = require('body-parser')
const session = require('express-session')
//导入 mysql 模块
const mysql = require('mysql')
const bcrypt = require('bcrypt')
const cors = require('cors')
const fs = require('fs')

//建立与 MySQL 数据库的连接关系
const db = mysql.createPool({
    host: 'localhost',
    user: 'root',
    password: 'root',
    database: 'notebook',
})

//创建app应用
const app = express()

app.use(cors(
    {
        origin: 'http://localhost',
        credentials: true
    }
))

//使用session中间件
app.use(session({
    secret: 'keyboard cat',
    resave: false,
    saveUninitialized: true,
    cookie: { maxAge: 1000 * 60 * 60 * 24 }
}))

//定义post传递的格式
app.use(express.static('./pages'))
// 静态资源目录
app.use('/public', express.static('./public'))
//使用body-parser中间件
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())

//登录接口
app.post('/api/login', (req, res) => {
    const sqlStr = 'SELECT * FROM user WHERE username = ?'
    const params = [req.body.username]

    db.query(sqlStr, params, (err, results) => {
        if (err) {
            console.error(err.message)
            return res.status(500).send({ message: '服务器内部错误' })
        }

        if (results.length === 0) {
            return res.status(400).send({ message: '该用户不存在' })
        }

        // 验证密码
        bcrypt.compare(req.body.password, results[0].password, (err, validPassword) => {
            if (err) {
                console.error(err.message)
                return res.status(500).send({ message: '服务器内部错误' })
            }

            if (!validPassword) {
                return res.status(401).send({ message: '密码错误' })
            }

            req.session.user = req.body.username;
            req.session.islogin = true;
            res.status(200).send({ message: '登录成功' })
        })
    })
})

//注册接口
app.post('/api/register', (req, res) => {
    const sqlStr = 'SELECT * FROM user WHERE username = ?'
    const params = [req.body.username]
    db.query(sqlStr, params, (err, results) => {
        if (err) {
            console.error(err.message)
            return res.status(500).send({ message: '服务器内部错误' })
        }

        if (results.length > 0) {
            return res.status(400).send({ message: '该用户已存在' })
        }

        // 对密码进行哈希处理
        bcrypt.hash(req.body.password, 10, (err, hashedPassword) => {
            if (err) {
                console.error(err.message)
                return res.status(500).send({ message: '服务器内部错误' })
            }

            const sqlInsert = 'INSERT INTO user (username, password) VALUES (?, ?)'
            db.query(sqlInsert, [req.body.username, hashedPassword], (err, results) => {
                if (err) {
                    console.error(err.message);
                    return res.status(500).send({ message: '服务器内部错误' })
                }

                res.status(201).send({ message: '注册成功' })
            })
        })
    })
})

// 获取用户姓名接口
app.get('/api/username', (req, res) => {
    //从 Session 中获取用户的名称,响应给客户端
    if (req.session.islogin && req.session.user) {
        return res.send({
            status: 200,
            message: '获取用户名成功',
            username: req.session.user // 返回用户名
        })
    }
})

// 退出登录接口
app.post('/api/logout', (req, res) => {
    // 清除session
    req.session.destroy()
    res.send({ status: 200, message: '退出登录成功' })
})

// 获取文章列表接口
app.get('/api/getArticle', (req, res) => {
    const sqlStr = 'SELECT * FROM article';
    db.query(sqlStr, (err, results) => {
        if (err) {
            return res.status(500).send({
                status: 500,
                message: '服务器内部错误'
            });
        }
        res.send({
            status: 200,
            message: '获取文章成功',
            data: results // 将所有文章数据返回
        });
    });
});

// 新增文章接口
app.post('/api/addArticle', (req, res) => {
    // 获取当前时间
    let time = new Date().toLocaleString()

    // 检查请求体中是否包含必要字段
    if (!req.body.title || !req.body.content) {
        return res.status(400).send({
            status: 400,
            message: '标题和内容不能为空',
        })
    }

    const sqlStr = 'INSERT INTO article (username, title, content, time) VALUES (?,?,?,?)'
    const params = [req.body.username, req.body.title, req.body.content, time]
    db.query(sqlStr, params, (err, results) => {
        if (err) {
            console.log(err.message);
            return res.status(500).send({
                status: 500,
                message: '服务器内部错误',
            })
        }

        res.send({
            status: 200,
            message: '新增文章成功',
            data: {
                id: results.insertId,  // 返回新文章的ID
                username: req.session.user,
                title: req.body.title,
                content: req.body.content,
                time: time
            }
        })
    })
})

// 查找文章接口
app.post('/api/search', (req, res) => {
    let time = new Date().toLocaleString()
    const sqlStr = 'SELECT * FROM article WHERE id = ?'
    const params = [req.body.id];
    db.query(sqlStr, params, (err, results) => {
        if (err) {
            console.log(err.message);
            return res.status(500).send({
                status: 500,
                message: '服务器内部错误',
            })
        }

        if (results.length === 0) {
            return res.send({
                status: 404,
                message: '未找到文章',
            })
        }

        res.send({
            status: 200,
            message: '查找文章成功',
            data: results[0]  // 如果按ID查找,返回单个结果
        })
    })
})

// 删除文章接口
app.post('/api/delete', (req, res) => {
    const sqlStr = 'DELETE FROM article WHERE id = ?'
    const params = [req.body.id]
    db.query(sqlStr, params, (err, results) => {
        if (err) {
            console.error(err.message)
            return res.status(500).send({
                status: 500,
                message: '服务器内部错误'
            })
        }

        if (results.affectedRows === 0) {
            return res.status(404).send({
                status: 404,
                message: '未找到该文章'
            })
        }

        res.send({
            status: 200,
            message: '删除文章成功'
        })
    })
})

// 动态获取文章内容接口
app.get('/api/article/:_id', (req, res) => {
    const id = parseInt(req.params._id) // 获取并转为整数
    const sqlStr = 'SELECT * FROM article WHERE id = ?'
    const params = [id]

    db.query(sqlStr, params, (err, results) => {
        if (err) {
            console.error(err.message)
            return res.status(500).send({
                status: 500,
                message: '服务器内部错误'
            })
        }

        if (results.length === 0) {
            return res.status(404).send({
                status: 404,
                message: '未找到该文章'
            })
        }

        // 发送 JSON 格式的响应
        res.send({
            status: 200,
            message: '获取文章成功',
            data: {
                username: results[0].username,
                title: results[0].title,
                content: results[0].content,
                time: results[0].time
            }
        })
    })
})

//获取留言列表接口
app.get('/api/getlist', (req, res) => {
    const sqlStr = 'SELECT * FROM leaving'
    db.query(sqlStr, (err, results) => {
        if (err) {
            console.error(err.message)
            return res.status(500).send({
                status: 500,
                message: '服务器内部错误'
            })
        }

        // 获取总留言数
        const totalCount = results.length;
        res.send({
            status: 200,
            message: '获取评论列表成功',
            data: results,
            totalCount: totalCount, // 返回总条数
            username: req.session.user // 返回当前登录的用户名
        })
    })
})

//新增留言接口
app.post('/api/addlist', (req, res) => {
    // 获取当前时间
    let time = new Date().toLocaleString()
    const sqlStr = 'INSERT INTO leaving (username, content, time) VALUES (?,?,?)'
    const params = [req.body.username, req.body.content, time]
    db.query(sqlStr, params, (err, results) => {
        if (err) {
            console.error(err.message)
            return res.status(500).send({
                status: 500,
                message: '服务器内部错误'
            })
        }

        res.send({
            status: 200,
            message: '新增评论成功',
            data: {
                id: results.insertId,  // 返回新留言的ID
                username: req.session.user,
                content: req.body.content,
                time: time
            }
        })
    })
})

// 删除留言接口
app.post('/api/deleteList', (req, res) => {
    const sqlStr = 'DELETE FROM leaving WHERE id = ?'
    const params = [req.body.id]
    db.query(sqlStr, params, (err, results) => {
        if (err) {
            console.error(err.message)
            return res.status(500).send({
                status: 500,
                message: '服务器内部错误'
            })
        }

        if (results.affectedRows === 0) {
            return res.status(404).send({
                status: 404,
                message: '未找到该评论'
            })
        }

        res.send({
            status: 200,
            message: '删除评论成功'
        })
    })
})

// 留言分页查询接口
app.post('/api/limitList', (req, res) => {
    const start = parseInt(req.body.num, 10) || 0; // 默认从第0条开始
    const count = 10; // 每页显示10条

    const sqlStr = 'SELECT * FROM leaving LIMIT ?, ?';
    const params = [start, count];

    db.query(sqlStr, params, (err, results) => {
        if (err) {
            return res.status(500).send({
                status: 500,
                message: '服务器内部错误'
            });
        }
        res.send({
            status: 200,
            data: results
        });
    });
});

// 分页查询接口
app.post('/api/limit', (req, res) => {
    const start = parseInt(req.body.num, 10) || 0; // 默认从第0条开始
    const count = 10; // 每页显示10条

    const sqlStr = 'SELECT * FROM article LIMIT ?, ?';
    const params = [start, count];

    db.query(sqlStr, params, (err, results) => {
        if (err) {
            return res.status(500).send({
                status: 500,
                message: '服务器内部错误'
            });
        }
        res.send({
            status: 200,
            data: results
        });
    });
});

// 获取文章总数接口
app.get('/api/getArticleCount', (req, res) => {
    const sqlStr = 'SELECT COUNT(*) AS count FROM article';
    db.query(sqlStr, (err, results) => {
        if (err) {
            return res.status(500).send({
                status: 500,
                ge: '服务器内部错误'
            });
        }
        res.send({
            status: 200,
            totalCount: results[0].count
        });
    });
});




app.listen(80, () => {
    console.log('server is running at http://localhost:80')
})

六. 创建前端页面

登录页 login.html

bash 复制代码
<!DOCTYPE html>
<html lang="zh">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>登录页</title>
    <script src="../public/jQuery.js"></script>
    <script src="../public/axios.js"></script>

    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        html,
        body {
            height: 100%;
            font-family: Arial, sans-serif;
        }

        .container {
            height: 100%;
            display: flex;
            align-items: center;
            justify-content: center;
            background: linear-gradient(to right, #4facfe, #00f2fe);
        }

        .login-wrapper {
            background-color: #ffffff;
            width: 360px;
            border-radius: 15px;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
            padding: 40px 30px;
        }

        .header {
            font-size: 36px;
            font-weight: bold;
            text-align: center;
            margin-bottom: 20px;
        }

        .input-item {
            width: 100%;
            padding: 10px;
            margin-bottom: 20px;
            border: 1px solid #ccc;
            border-radius: 5px;
            font-size: 14px;
            outline: none;
            transition: border-color 0.3s;
        }

        .input-item:focus {
            border-color: #4facfe;
        }

        .btn {
            display: block;
            width: 100%;
            padding: 12px;
            margin-top: 20px;
            background: linear-gradient(to right, #4facfe, #00f2fe);
            color: #fff;
            text-align: center;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            transition: transform 0.2s;
        }

        .btn:hover {
            background-image: linear-gradient(to right, #00f2fe, #4facfe);
            color: #000;
            transform: translateY(-3px);
            box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
        }

        .msg {
            text-align: center;
            margin-top: 20px;
        }

        .tankuang {
            display: none;
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: #ee3b3b;
            color: white;
            border-radius: 5px;
            padding: 15px 20px;
            z-index: 1000;
        }
    </style>
</head>

<body>
    <div class="tankuang" id="error_message"></div>

    <div class="container">
        <div class="login-wrapper">
            <div class="header">登录</div>
            <form id="form_login">
                <input type="text" name="username" placeholder="账号" class="input-item" id="user" required>
                <input type="password" name="password" placeholder="密码" class="input-item" id="pwd" required>
                <button type="submit" class="btn" id="login_btn">登录</button>
                <button type="button" class="btn" id="register_btn">去注册</button>
            </form>
            <div class="msg">
                有问题? <b>请联系QQ: 2655372128</b>
            </div>
        </div>
    </div>

    <script>
        $(function () {
            const apiUrl = 'http://localhost/api/login';

            // 显示错误消息的通用函数
            function showError(message) {
                $('#error_message').text(message).fadeIn().delay(2000).fadeOut();
            }

            // 发送 API 请求的通用函数
            function apiRequest(method, url, data = null) {
                if (data === null) {
                    return $.ajax({
                        method: method,
                        url: url,
                    });
                } else {
                    return $.ajax({
                        method: method,
                        url: url,
                        data: data,
                    });
                }
            }

            // 登录处理函数
            function handleLogin(event) {
                event.preventDefault();
                const data = $('#form_login').serialize();

                apiRequest('POST', apiUrl, data)
                    .then(function (res) {
                        if (res.status === 400) {
                            showError('账号不存在,请先注册');
                        } else if (res.status === 401) {
                            showError('密码错误,请重新登录');
                        } else {
                            window.location.href = './index.html';
                        }
                    })
                    .catch(function () {
                        showError('服务器出现问题,请稍后再试。');
                    });
            }

            // 事件绑定
            $('#form_login').on('submit', handleLogin);
            $('#register_btn').click(function () {
                window.location.href = './register.html';
            });
        });
    </script>

</body>

</html>

注册页 register.html

bash 复制代码
<!DOCTYPE html>
<html lang="zh">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>注册页</title>
    <script src="../public/jQuery.js"></script>
    <script src="../public/axios.js"></script>

    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        html,
        body {
            height: 100%;
            font-family: Arial, sans-serif;
        }

        .container {
            height: 100%;
            display: flex;
            align-items: center;
            justify-content: center;
            background: linear-gradient(to right, #4facfe, #00f2fe);
        }

        .register-wrapper {
            background-color: #ffffff;
            width: 360px;
            border-radius: 15px;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
            padding: 40px 30px;
        }

        .header {
            font-size: 36px;
            font-weight: bold;
            text-align: center;
            margin-bottom: 20px;
        }

        .input-item {
            width: 100%;
            padding: 10px;
            margin-bottom: 20px;
            border: 1px solid #ccc;
            border-radius: 5px;
            font-size: 14px;
            outline: none;
            transition: border-color 0.3s;
        }

        .input-item:focus {
            border-color: #4facfe;
        }

        .btn {
            display: block;
            width: 100%;
            padding: 12px;
            margin-top: 20px;
            background: linear-gradient(to right, #4facfe, #00f2fe);
            color: #fff;
            text-align: center;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            transition: transform 0.2s;
        }

        .btn:hover {
            background-image: linear-gradient(to right, #00f2fe, #4facfe);
            color: #000;
            transform: translateY(-3px);
            box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
        }

        .msg {
            text-align: center;
            margin-top: 20px;
        }

        .notification {
            display: none;
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: #ee3b3b;
            color: white;
            border-radius: 5px;
            padding: 15px 20px;
            z-index: 1000;
        }
    </style>
</head>

<body>
    <div class="notification" id="error_message"></div>

    <div class="container">
        <div class="register-wrapper">
            <div class="header">注册</div>
            <form id="form_register">
                <input type="text" name="username" placeholder="账号" class="input-item" id="user" required>
                <input type="password" name="password" placeholder="密码" class="input-item" id="pwd" required>
                <button type="submit" class="btn">注册</button>
                <button type="submit" class="btn" id="back_btn">返回登录</button>
            </form>
            <div class="msg">
                有问题? <b>请联系QQ: 2655372128</b>
            </div>
        </div>
    </div>

    <script>
        $(function () {
            const apiUrl = 'http://localhost/api/register'; // 注册 API 地址

            // 显示错误消息的通用函数
            function showError(message, redirectUrl = null) {
                $('#error_message').text(message).fadeIn().delay(2000).fadeOut();
                if (redirectUrl) {
                    setTimeout(function () {
                        location.href = redirectUrl; // 跳转至指定页面
                    }, 2000);
                }
            }

            // 发送 API 请求的通用函数
            function apiRequest(method, url, data = null) {
                return data ? $.ajax({ method, url, data }) : $.ajax({ method, url });
            }

            // 注册表单提交处理
            $('#form_register').on('submit', function (e) {
                e.preventDefault();
                const data = $(this).serialize();

                apiRequest('POST', apiUrl, data)
                    .then(function (res) {
                        if (res.message === '注册成功') { // 使用message而不是status判断
                            showError('注册成功,跳转到登录页面...', './login.html');
                        } else {
                            showError('账号已存在,请重新注册');
                        }
                    })
                    .catch(function () {
                        showError('服务器错误,请稍后再试');
                    });
            });

            // 返回登录按钮点击处理
            $('#back_btn').on('click', function () {
                location.href = './login.html'; // 跳转到登录页
            });
        });
    </script>


</body>

</html>

首页 index.html

bash 复制代码
<!DOCTYPE html>
<html lang="zh">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>首页</title>
    <script src="../public/jQuery.js"></script>
    <link rel="stylesheet" href="../public/bootstrap.min.css">
    <script src="../public/axios.js"></script>

    <style>
        body {
            background-color: #f8f9fa;
            font-family: Arial, sans-serif;
        }

        .navbar {
            background-color: #007bff;
            color: white;
        }

        .navbar-brand,
        .navbar-nav li a {
            color: white !important;
            cursor: pointer;
        }

        .navbar-brand:hover,
        .navbar-nav li a:hover {
            color: #000 !important;
        }

        .container {
            margin-top: 20px;
        }

        .header {
            font-size: 32px;
            font-weight: bold;
            text-align: center;
            margin-bottom: 20px;
        }

        .article-list {
            background-color: #ffffff;
            border-radius: 10px;
            padding: 20px;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
        }

        .article-title-bg {
            background-color: #007bff;
            /* 设置背景色 */
            padding: 10px;
            border-radius: 5px;
            /* 添加圆角 */
            text-align: center;
            margin-bottom: 20px;
            color: white;
        }

        .article-item {
            display: flex;
            justify-content: space-between;
            padding: 10px;
            border-bottom: 1px solid #e0e0e0;
        }

        .article-title {
            flex-grow: 1;
            margin-right: 20px;
        }

        .pagination {
            display: flex;
            justify-content: center;
            margin-top: 20px;
        }

        .pagination li {
            list-style: none;
            margin: 0 5px;
            display: flex;
            align-items: center;
        }

        .pagination .page-link {
            text-decoration: none;
            padding: 8px 12px;
            border: none;
            border-radius: 5px;
            color: white;
            background-image: linear-gradient(to right, #4facfe, #00f2fe);
            transition: background-color 0.3s, color 0.3s;
        }

        .pagination .page-link:hover {
            background-image: linear-gradient(to right, #00f2fe, #4facfe);
            color: #000;
            transform: translateY(-3px);
            box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
        }

        .pagination .page-link:disabled {
            background-color: #e0e0e0;
            color: #a0a0a0;
            cursor: not-allowed;
        }

        .pagination .page-number {
            display: flex;
            align-items: center;
            height: 100%;
        }
    </style>
</head>

<body>

    <nav class="navbar navbar-expand-lg">
        <div class="container-fluid">
            <a class="navbar-brand" href="./index.html">首页</a>
            <div class="collapse navbar-collapse" id="navbarNav">
                <ul class="navbar-nav ms-auto ml-auto">
                    <li class="nav-item">
                        <a class="nav-link" href="./add_article.html">添加文章</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="./edit_delete_article.html">修改和删除文章</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="./leaving.html">留言板</a>
                    </li>
                    <li class="nav-item">
                        <span class="navbar-text" id="username"></span>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" id="btnLogout">退出登录</a>
                    </li>
                </ul>
            </div>
        </div>
    </nav>

    <div class="container">
        <div class="header">欢迎来到乐风刂(快乐自由)的博客</div>
        <div class="article-list">
            <h2 class="article-title-bg">文章列表</h2>
            <div id="articleList"></div>
            <nav aria-label="Page navigation" class="limit">
                <ul class="pagination" id="pagination">
                    <li class="page-item"><a class="page-link" id="prevPage" href="#">上一页</a></li>
                    <li class="page-item"><span id="currentPage">1</span> / <span id="totalPages">1</span></li>
                    <li class="page-item"><a class="page-link" id="nextPage" href="#">下一页</a></li>
                </ul>
            </nav>
        </div>
    </div>

    <script>
        $(function () {
            const apiUrl = 'http://localhost/api';
            let currentPage = 1; // 当前页面
            const pageSize = 10; // 每页显示10条

            // 获取用户名
            function fetchUsername() {
                axios.get(`${apiUrl}/username`, { withCredentials: true })
                    .then(function (res) {
                        if (res.data.status === 200) {
                            $('#username').text(`用户: ${res.data.username}`);
                        } else {
                            $('#username').text('未登录');
                        }
                    })
                    .catch(function (error) {
                        console.error('获取用户名失败:', error);
                    });
            }

            // 获取文章列表
            function fetchArticles(page) {
                axios.post(`${apiUrl}/limit`, { num: (page - 1) * pageSize })
                    .then(function (res) {
                        const articles = res.data.data;
                        let articleListHtml = '';

                        articles.forEach(article => {
                            articleListHtml += `
                        <div class="article-item">
                            <div class="article-title">
                                <a href="./detail_page.html?id=${article.id}">${article.title}</a>
                            </div>
                            <div>用户: ${article.username} | 发布时间: ${article.time}</div>
                        </div>
                    `;
                        });

                        $('#articleList').html(articleListHtml);
                        updatePagination();
                    })
                    .catch(function (error) {
                        console.error('获取文章列表失败:', error);
                    });
            }

            // 更新分页
            function updatePagination() {
                axios.get(`${apiUrl}/getArticleCount`) // 假设有接口可以返回文章总数
                    .then(function (res) {
                        const totalArticles = res.data.totalCount;
                        const totalPages = Math.ceil(totalArticles / pageSize);

                        $('#totalPages').text(totalPages);
                        $('#currentPage').text(currentPage);

                        // 控制前后按钮显示
                        $('#prevPage').prop('disabled', currentPage === 1);
                        $('#nextPage').prop('disabled', currentPage === totalPages);
                    });
            }

            $('#prevPage').click(function () {
                if (currentPage > 1) {
                    currentPage--;
                    fetchArticles(currentPage);
                }
            });

            $('#nextPage').click(function () {
                const totalPages = parseInt($('#totalPages').text());
                if (currentPage < totalPages) {
                    currentPage++;
                    fetchArticles(currentPage);
                }
            });

            // 退出登录
            $('#btnLogout').click(function () {
                axios.post(`${apiUrl}/logout`)
                    .then(function (res) {
                        if (res.status === 200) {
                            location.href = './login.html';
                        }
                    })
                    .catch(function () {
                        alert('退出登录失败,请重试');
                    });
            });

            // 页面加载时获取用户名和文章
            fetchUsername();
            fetchArticles(currentPage);
        });
    </script>

</body>

</html>

添加文章页 add_article.html

bash 复制代码
<!DOCTYPE html>
<html lang="zh">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>添加文章</title>
    <script src="../public/jQuery.js"></script>
    <link rel="stylesheet" href="../public/bootstrap.min.css">
    <script src="../public/axios.js"></script>

    <style>
        * {
            margin: 0;
            padding: 0;
        }

        body {
            background-color: #f8f9fa;
            /* 背景色 */
            font-family: Arial, sans-serif;
        }

        .navbar {
            background-color: #007bff;
            /* 导航栏颜色 */
            color: white;
        }

        .navbar-brand,
        .navbar-nav li a {
            color: white !important;
            /* 导航文字颜色 */
            cursor: pointer;
            /* 使得导航链接显示手形光标 */
        }

        .navbar-brand:hover,
        .navbar-nav li a:hover {
            color: #000 !important;
        }



        .container {
            margin-top: 20px;
        }

        .form-wrapper {
            background-color: #fff;
            border-radius: 15px;
            padding: 40px;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
            max-width: 800px;
            margin: auto;
        }

        .header {
            font-size: 32px;
            font-weight: bold;
            text-align: center;
            margin-bottom: 20px;
        }

        .input-item {
            width: 100%;
            padding: 10px;
            margin-bottom: 20px;
            border: 1px solid #ccc;
            border-radius: 5px;
            font-size: 16px;
            outline: none;
        }

        textarea {
            height: 400px;
        }


        .btn {
            width: 100%;
            padding: 12px;
            background-image: linear-gradient(to right, #4facfe, #00f2fe);
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            transition: transform 0.2s;
        }

        .btn:hover {
            background-image: linear-gradient(to right, #00f2fe, #4facfe);
            transform: translateY(-3px);
            box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
        }

        .tankuang {
            display: none;
            position: fixed;
            background: linear-gradient(to right, #4facfe, #00f2fe);
            z-index: 99;
            width: 200px;
            height: 100px;
            text-align: center;
            line-height: 100px;
            border-radius: 5px;
            top: 30%;
            left: 50%;
            transform: translate(-50%, -50%);
        }

        #layer_msg {
            color: #ffffff;
            font-size: 20px;
        }
    </style>
</head>

<body>

    <div class="tankuang">
        <div id="header">
            <span id="layer_msg">提交成功...</span>
        </div>
    </div>
    <nav class="navbar navbar-expand-lg">
        <div class="container-fluid">
            <a class="navbar-brand" href="./index.html">返回首页</a>
            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
                aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarNav">
                <ul class="navbar-nav ms-auto ml-auto">
                    <li class="nav-item">
                        <a class="nav-link" id="btnLogout">退出登录</a>
                    </li>
                </ul>
            </div>
        </div>
    </nav>

    <div class="container">
        <div class="form-wrapper">
            <div class="header">写文章</div>
            <form id="form_add">
                <input type="text" class="input-item" name="title" placeholder="文章标题" required>
                <h5>文章内容</h5>
                <textarea class="input-item" rows="10" name="content" placeholder="请输入文章内容" required></textarea>
                <button type="submit" class="btn">发布文章</button>
            </form>
            <div class="msg text-center mt-3">
                有问题? <b>请联系QQ: 2655372128</b>
            </div>
        </div>
    </div>

    <script>
        $(function () {
            const apiUrl = 'http://localhost/api';

            // 显示消息的通用函数
            function showMessage(message, redirect = false) {
                document.querySelector('.tankuang').style.display = 'block';
                $('#layer_msg').text(message);
                if (redirect) {
                    setTimeout(function () {
                        location.href = './index.html';
                    }, 800);
                } else {
                    setTimeout(function () {
                        document.querySelector('.tankuang').style.display = 'none';
                    }, 800);
                }
            }

            // 发送 API 请求的通用函数
            function apiRequest(method, url, data = {}) {
                return axios({
                    method: method,
                    url: url,
                    data: data,
                    withCredentials: true // 确保可以携带凭证
                });
            }

            // 发布文章
            $('#form_add').on('submit', function (e) {
                e.preventDefault();
                const data = $(this).serialize();

                // 检查文章内容,自动在开头添加两个空格
                const content = $('#form_add textarea[name="content"]').val();
                const formattedContent = '  ' + content; // 添加两个空格
                const formattedData = data.replace(content, formattedContent); // 替换内容字符串

                apiRequest('POST', `${apiUrl}/addArticle`, data)
                    .then(function (res) {
                        if (res.data.status === 200) {
                            showMessage('文章发布成功', true);
                        } else {
                            showMessage('文章发布失败');
                        }
                    })
                    .catch(function () {
                        showMessage('请求失败,请稍后再试');
                    });
            });

            // 获取用户信息判断是否登录
            apiRequest('GET', `${apiUrl}/username`)
                .then(function (res) {
                    if (res.status !== 200) {
                        showMessage('请先完成登录', true);
                    }
                })
                .catch(function () {
                    showMessage('获取用户信息失败,请重试', true);
                });

            // 退出登录
            $('#btnLogout').click(function () {
                apiRequest('POST', `${apiUrl}/logout`)
                    .then(function (res) {
                        if (res.status === 200) {
                            location.href = './login.html';
                        }
                    })
                    .catch(function () {
                        showMessage('退出登录失败,请重试');
                    });
            });
        });
    </script>


</body>

</html>

文章详情页 detail_page.html

bash 复制代码
<!DOCTYPE html>
<html lang="zh">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>文章详情页</title>
    <script src="../public/jQuery.js"></script>
    <link rel="stylesheet" href="../public/bootstrap.min.css">
    <script src="../public/axios.js"></script>

    <style>
        * {
            margin: 0;
            padding: 0;
        }

        body {
            background-color: #f8f9fa;
            /* 背景色 */
            font-family: Arial, sans-serif;
        }

        .navbar {
            background-color: #007bff;
            /* 导航栏颜色 */
            color: white;
        }

        .navbar-brand,
        .navbar-nav li a {
            color: white !important;
            /* 导航文字颜色 */
            cursor: pointer;
            /* 使得导航链接显示手形光标 */
        }

        .container {
            margin-top: 20px;

        }

        .article-wrapper {
            background-color: #ffffff;
            border-radius: 15px;
            padding: 40px;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
            max-width: 800px;
            margin: auto;
        }

        .header {
            font-size: 32px;
            font-weight: bold;
            margin-bottom: 20px;
            text-align: center;
        }

        .content {
            font-size: 18px;
            line-height: 1.6;
            margin-bottom: 20px;
            white-space: pre-wrap;
            /* 保留换行符和空格,同时文本在容器内自动换行 */
            word-wrap: break-word;
            /* 长单词自动换行 */
        }

        .article-meta {
            font-size: 14px;
            color: #888;
            margin-bottom: 20px;
            text-align: center;
        }

        #btnComment {
            background-image: linear-gradient(to right, #4facfe, #00f2fe);
            color: white;
            border: none;
            padding: 10px 20px;
            border-radius: 5px;
            transition: background-color 0.3s;
            /* 添加过渡效果 */
        }

        #btnComment:hover {
            background-image: linear-gradient(to right, #00f2fe, #4facfe);
            /* 鼠标悬停时颜色反转 */
        }

        .tankuang {
            display: none;
            position: fixed;
            background-image: linear-gradient(to right, #4facfe, #00f2fe);
            z-index: 99;
            width: 200px;
            height: 100px;
            text-align: center;
            line-height: 100px;
            border-radius: 5px;
            top: 30%;
            left: 50%;
            transform: translate(-50%, -50%);
        }

        #layer_msg {
            color: #ffffff;
            font-size: 20px;
        }


        .separator {
            height: 1px;
            background-color: #e0e0e0;
            /* 分割线颜色 */
            margin: 20px 0;
            /* 分割线的上下间距 */
        }
    </style>
</head>

<body>

    <div class="tankuang">
        <div id="header">
            <span id="layer_msg">加载中...</span>
        </div>
    </div>

    <nav class="navbar navbar-expand-lg">
        <div class="container-fluid">
            <a class="navbar-brand" href="./index.html">返回首页</a>
            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
                aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarNav">
                <ul class="navbar-nav ms-auto ml-auto">
                    <li class="nav-item">
                        <a class="nav-link user"></a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" id="btnLogout">退出登录</a>
                    </li>
                </ul>
            </div>
        </div>
    </nav>

    <div class="container">
        <div class="article-wrapper">
            <div class="header" id="articleTitle">文章标题</div>
            <div class="article-meta" id="articleMeta">
                <span class="username">用户: </span>
                <span class="time">发布于: </span>
            </div>
            <div class="separator"></div>
            <pre class="content" id="articleContent">文章内容将显示在这里...</pre>
            <div class="text-center">
                <button class="btn btn-primary" id="btnComment">查看留言</button>
            </div>
        </div>
    </div>

    <script>
        $(function () {
            const apiUrl = 'http://localhost/api';
            const urlParams = new URLSearchParams(window.location.search);
            const articleId = urlParams.get('id'); // 从 URL 中获取文章 ID

            // 显示消息的通用函数
            function showMessage(message, redirectUrl = null) {
                $('#error_message').text(message).fadeIn().delay(2000).fadeOut();
                if (redirectUrl) {
                    setTimeout(function () {
                        location.href = redirectUrl; // 页面重定向
                    }, 2000);
                }
            }

            // 发送 API 请求的通用函数
            function apiRequest(method, url, data = null) {
                return data ? $.ajax({ method, url, data }) : $.ajax({ method, url });
            }

            // 获取文章详情
            apiRequest('POST', `${apiUrl}/search`, { id: articleId })
                .then(function (res) {
                    if (res.status === 200) {
                        $('#articleTitle').text(res.data.title);
                        $('#articleContent').html(res.data.content); // 使用 .html() 以支持 HTML 内容
                        $('.username').text('用户: ' + res.data.username);
                        $('.time').text('发布于: ' + res.data.time);
                    } else {
                        showMessage('未找到文章');
                    }
                })
                .catch(function () {
                    showMessage('请求失败,请稍后再试');
                });

            // 获取用户名判断是否登录
            apiRequest('GET', `${apiUrl}/username`)
                .then(function (res) {
                    if (res.status === 200) {
                        $('.user').text(res.username);
                    } else {
                        showMessage('请先完成登录', './login.html'); // 重定向到登录页面
                    }
                })
                .catch(function () {
                    showMessage('获取用户信息失败,请重试', './login.html'); // 重定向到登录页面
                });

            // 退出登录
            $('#btnLogout').click(function () {
                apiRequest('POST', `${apiUrl}/logout`)
                    .then(function (res) {
                        if (res.status === 200) {
                            location.href = './login.html'; // 登录成功后重定向
                        }
                    })
                    .catch(function () {
                        showMessage('退出登录失败,请重试');
                    });
            });

            // 查看留言按钮
            $('#btnComment').click(function () {
                location.href = './leaving.html'; // 跳转到留言板
            });
        });
    </script>


</body>

</html>

留言板 leaving.html

bash 复制代码
<!DOCTYPE html>
<html lang="zh">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>留言板</title>
    <script src="../public/jQuery.js"></script>
    <link rel="stylesheet" href="../public/bootstrap.min.css">
    <script src="../public/axios.js"></script>
    <style>
        body {
            background-color: #f8f9fa;
            font-family: Arial, sans-serif;
        }

        .navbar {
            background-color: #007bff;
            color: white;
        }

        .navbar-brand,
        .navbar-nav li a {
            color: white !important;
            cursor: pointer;
        }

        .navbar-brand:hover,
        .navbar-nav li a:hover {
            color: #000 !important;
        }

        .container {
            margin-top: 20px;
        }

        .form-wrapper {
            background-color: #fff;
            border-radius: 15px;
            padding: 20px;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
            max-width: 800px;
            margin: auto;
            border: none;
        }

        .header {
            font-size: 32px;
            font-weight: bold;
            text-align: center;
            margin-bottom: 20px;
        }

        table {
            width: 100%;
            border-collapse: collapse;
            margin-top: 20px;
        }

        th,
        td {
            border: 1px solid #ccc;
            padding: 10px;
            text-align: left;
            max-width: 400px;
            overflow-wrap: break-word;
            word-wrap: break-word;
            overflow: hidden;
        }

        th {
            background-color: #f2f2f2;
        }

        .btn-delete {
            background: linear-gradient(to right, #4facfe, #00f2fe);
            color: white;
            border: none;
            border-radius: 5px;
            padding: 10px 15px;
            cursor: pointer;
            display: block;
            margin: 0 auto;
        }

        .btn-delete:hover {
            background-image: linear-gradient(to right, #00f2fe, #4facfe);
            color: #000;
            transform: translateY(-3px);
            box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
        }

        .btn-submit {
            width: 100%;
            padding: 12px;
            background-image: linear-gradient(to right, #4facfe, #00f2fe);
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            transition: transform 0.2s;
        }

        .btn-pagination {
            background: linear-gradient(to right, #4facfe, #00f2fe);
            color: white;
            border: none;
            border-radius: 5px;
            padding: 10px 15px;
            cursor: pointer;
            display: inline-block;
            margin: 10px auto;
            width: fit-content;
        }

        .btn-pagination:hover {
            background-image: linear-gradient(to right, #00f2fe, #4facfe);
            color: #000;
            transform: translateY(-3px);
            box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
        }

        .btn {
            width: 100%;
            padding: 12px;
            background-image: linear-gradient(to right, #4facfe, #00f2fe);
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            transition: transform 0.2s;
        }

        .btn:hover {
            background-image: linear-gradient(to right, #00f2fe, #4facfe);
            transform: translateY(-3px);
            box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
        }

        textarea {
            width: 100%;
            padding: 10px;
            border-radius: 5px;
            border: 1px solid #ccc;
            outline: none;
        }

        .pagination-wrapper {
            text-align: center;
            margin-top: 20px;
        }

        .tankuang {
            display: none;
            position: fixed;
            background: linear-gradient(to right, #4facfe, #00f2fe);
            z-index: 99;
            width: 300px;
            height: auto;
            text-align: center;
            line-height: 100px;
            border-radius: 5px;
            top: 30%;
            left: 50%;
            transform: translate(-50%, -50%);
            color: #ffffff;
            font-size: 20px;
            white-space: nowrap;
            /* 防止换行 */
            overflow: hidden;
            /* 超出部分隐藏 */
            text-overflow: ellipsis;
            /* 超出部分用省略号表示 */
        }
    </style>
</head>

<body>

    <div class="tankuang" id="successMessage">删除成功...</div>
    <div class="tankuang" id="publishMessage">发布成功...</div>
    <div class="tankuang" id="errorMessage">留言内容不能为空...</div>
    <div class="tankuang" id="deleteErrorMessage">删除留言失败...</div>

    <div class="tankuang" id="confirmDeleteMessage" style="height: auto; padding: 20px;">
        <div style="margin-bottom: 10px; font-size: 18px;">您确定要删除这条留言吗?</div>
        <div style="display: flex; justify-content: center; gap: 10px;">
            <button class="btn" id="confirmDelete">确认</button>
            <button class="btn" id="cancelDelete">取消</button>
        </div>
    </div>





    <nav class="navbar navbar-expand-lg">
        <div class="container-fluid">
            <a class="navbar-brand" href="./index.html">返回首页</a>
            <div class="collapse navbar-collapse" id="navbarNav">
                <ul class="navbar-nav ms-auto ml-auto">
                    <li class="nav-item">
                        <a class="nav-link" id="btnLogout">退出登录</a>
                    </li>
                </ul>
            </div>
        </div>
    </nav>

    <div class="container">
        <div class="form-wrapper">
            <div class="header">留言板</div>
            <textarea id="messageInput" rows="5" placeholder="请输入留言..."></textarea>
            <button class="btn" id="btnSubmit">发布留言</button>
            <table>
                <thead>
                    <tr>
                        <th>留言内容</th>
                        <th>时间</th>
                        <th>操作</th>
                    </tr>
                </thead>
                <tbody id="messageList"></tbody>
            </table>
            <div class="pagination-wrapper" id="pagination">
                <button class="btn-pagination" id="prevPage">上一页</button>
                <span id="currentPage">1</span> / <span id="totalPages">1</span>
                <button class="btn-pagination" id="nextPage">下一页</button>
            </div>
        </div>
    </div>

    <script>
        $(function () {
            const messages = [];
            const apiUrl = 'http://localhost/api';
            let currentPage = 1;
            const itemsPerPage = 10; // 每页显示的留言数量
            let totalMessages = 0;

            fetchMessages();

            function fetchMessages() {
                axios.get(`${apiUrl}/getlist`)
                    .then(function (res) {
                        if (res.data.status === 200) {
                            totalMessages = res.data.totalCount;
                            const totalPages = Math.ceil(totalMessages / itemsPerPage);

                            if (totalMessages > itemsPerPage) {
                                $('#pagination').show(); // 显示分页按钮
                                $('#totalPages').text(totalPages); // 更新总页数
                            } else {
                                $('#pagination').hide(); // 隐藏分页按钮
                            }

                            loadPage(currentPage);
                        }
                    })
                    .catch(function (error) {
                        console.error('获取留言列表失败:', error);
                    });
            }

            function loadPage(page) {
                axios.post(`${apiUrl}/limitList`, { num: (page - 1) * itemsPerPage })
                    .then(function (res) {
                        if (res.data.status === 200) {
                            messages.length = 0;
                            res.data.data.forEach(msg => {
                                messages.push({
                                    id: msg.id,
                                    message: msg.content,
                                    timestamp: msg.time
                                });
                            });
                            $('#currentPage').text(page);
                            displayMessages();
                        }
                    })
                    .catch(function (error) {
                        console.error('分页获取留言失败:', error);
                    });
            }

            $('#btnSubmit').click(function () {
                const message = $('#messageInput').val().trim();

                if (message) {
                    axios.post(`${apiUrl}/addlist`, { content: message })
                        .then(function (res) {
                            if (res.data.status === 200) {
                                $('#publishMessage').text('发布成功...').show();
                                setTimeout(function () {
                                    $('#publishMessage').fadeOut();
                                }, 2000);

                                // 直接推入新消息对象
                                messages.unshift({
                                    id: res.data.newId,
                                    message: message,
                                    timestamp: new Date().toLocaleString() // 当前时间
                                });

                                displayMessages(); // 更新显示
                                $('#messageInput').val('');
                                fetchMessages(); // 刷新留言列表
                            }
                        })
                        .catch(function (error) {
                            console.error('发布留言失败:', error);
                        });
                } else {
                    $('#errorMessage').text('留言内容不能为空...').show();
                    setTimeout(function () {
                        $('#errorMessage').fadeOut();
                    }, 2000);
                }
            });

            function displayMessages() {
                $('#messageList').empty();
                messages.forEach((msg) => {
                    $('#messageList').append(`
            <tr>
                <td style="white-space: pre-wrap; word-wrap: break-word;">${msg.message}</td>
                <td>${msg.timestamp}</td>
                <td><button class="btn-delete" data-id="${msg.id}">删除</button></td>
            </tr>
        `);
                });
            }


            $(document).on('click', '.btn-delete', function () {
                const id = $(this).data('id');
                // 显示确认删除弹窗
                $('#confirmDeleteMessage').show();

                // 处理确认删除
                $('#confirmDelete').off('click').on('click', function () {
                    axios.post(`${apiUrl}/deleteList`, { id })
                        .then(function (res) {
                            if (res.data.status === 200) {
                                $('#successMessage').text('删除成功...').show();
                                setTimeout(function () {
                                    $('#successMessage').fadeOut();
                                }, 2000);
                                fetchMessages();
                            }
                        })
                        .catch(function (error) {
                            console.error('删除留言失败:', error);
                            $('#deleteErrorMessage').text('删除留言失败,请重试...').show();
                            setTimeout(function () {
                                $('#deleteErrorMessage').fadeOut();
                            }, 2000);
                        });
                    $('#confirmDeleteMessage').hide(); // 隐藏确认弹窗
                });

                // 处理取消删除
                $('#cancelDelete').off('click').on('click', function () {
                    $('#confirmDeleteMessage').hide(); // 隐藏确认弹窗
                });
            });



            $('#prevPage').click(function () {
                if (currentPage > 1) {
                    currentPage--;
                    loadPage(currentPage);
                }
            });

            $('#nextPage').click(function () {
                const totalPages = Math.ceil(totalMessages / itemsPerPage);
                if (currentPage < totalPages) {
                    currentPage++;
                    loadPage(currentPage);
                }
            });

            $('#btnLogout').click(function () {
                axios.post(`${apiUrl}/logout`)
                    .then(function (res) {
                        if (res.status === 200) {
                            location.href = './login.html';
                        }
                    })
                    .catch(function () {
                        alert('退出登录失败,请重试');
                    });
            });
        });

    </script>

</body>

</html>

修改文章 edit_article.html

bash 复制代码
<!DOCTYPE html>
<html lang="zh">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>修改文章</title>
    <script src="../public/jQuery.js"></script>
    <link rel="stylesheet" href="../public/bootstrap.min.css">
    <script src="../public/axios.js"></script>

    <style>
        * {
            margin: 0;
            padding: 0;
        }

        body {
            background-color: #f8f9fa;
            font-family: Arial, sans-serif;
        }

        .navbar {
            background-color: #007bff;
            color: white;
        }

        .navbar-brand,
        .navbar-nav li a {
            color: white !important;
            cursor: pointer;
        }

        .navbar-brand:hover,
        .navbar-nav li a:hover {
            color: #000 !important;
        }

        .container {
            margin-top: 20px;
        }

        .form-wrapper {
            background-color: #fff;
            border-radius: 15px;
            padding: 40px;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
            max-width: 800px;
            margin: auto;
        }

        .header {
            font-size: 32px;
            font-weight: bold;
            text-align: center;
            margin-bottom: 20px;
        }

        .input-item {
            width: 100%;
            padding: 10px;
            margin-bottom: 20px;
            border: 1px solid #ccc;
            border-radius: 5px;
            font-size: 16px;
            outline: none;
        }

        textarea {
            height: 500px;
        }

        .btn-save {
            width: 100%;
            padding: 12px;
            background-image: linear-gradient(to right, #4facfe, #00f2fe);
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            transition: transform 0.2s;
        }

        .btn-save:hover {
            background-image: linear-gradient(to right, #00f2fe, #4facfe);
            transform: translateY(-3px);
            box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
        }

        .tankuang {
            display: none;
            position: fixed;
            background: linear-gradient(to right, #4facfe, #00f2fe);
            z-index: 99;
            width: 200px;
            height: 100px;
            text-align: center;
            line-height: 100px;
            border-radius: 5px;
            top: 30%;
            left: 50%;
            transform: translate(-50%, -50%);
            color: #ffffff;
            font-size: 20px;
        }
    </style>
</head>

<body>

    <div class="tankuang" id="successMessage">修改成功...</div>

    <nav class="navbar navbar-expand-lg">
        <div class="container-fluid">
            <a class="navbar-brand" href="./index.html">返回首页</a>
            <div class="collapse navbar-collapse" id="navbarNav">
                <ul class="navbar-nav ms-auto ml-auto">
                    <li class="nav-item">
                        <a class="nav-link" id="btnLogout">退出登录</a>
                    </li>
                </ul>
            </div>
        </div>
    </nav>

    <div class="container">
        <div class="form-wrapper">
            <div class="header">修改文章</div>
            <form id="editArticleForm">
                <input type="hidden" id="articleId" name="articleId">
                <div class="mb-3">
                    <label for="title" class="form-label">标题</label>
                    <input type="text" class="input-item" id="title" name="title" placeholder="请输入文章标题" required>
                </div>
                <div class="mb-3">
                    <label for="content" class="form-label">内容</label>
                    <textarea class="input-item" id="content" name="content" rows="5" placeholder="请输入文章内容"
                        required></textarea>
                </div>
                <button type="submit" class="btn-save">保存修改</button>
            </form>
        </div>
    </div>

    <script>
        $(function () {
            const apiUrl = 'http://localhost/api';
            const urlParams = new URLSearchParams(window.location.search);
            const articleId = urlParams.get('id');

            // 获取文章详情
            function fetchArticle(id) {
                axios.post(`${apiUrl}/search`, { id: id })
                    .then(function (res) {
                        if (res.status === 200) {
                            $('#articleId').val(res.data.data.id);
                            $('#title').val(res.data.data.title);
                            $('#content').val(res.data.data.content);
                        } else {
                            alert('未找到文章');
                        }
                    })
                    .catch(function () {
                        alert('请求失败,请稍后再试');
                    });
            }

            // 提交修改文章表单
            $('#editArticleForm').on('submit', function (e) {
                e.preventDefault();
                const id = $('#articleId').val();
                const title = $('#title').val();
                const content = $('#content').val();

                axios.post(`${apiUrl}/updateArticle`, { id, title, content })
                    .then(function (res) {
                        $('#successMessage').text('修改成功...').show();
                        setTimeout(function () {
                            $('#successMessage').fadeOut();
                            window.location.href = `./detail_page.html?id=${id}`;
                        }, 2000);
                    })
                    .catch(function (error) {
                        const response = error.response.data;
                        alert(response.message);
                    });
            });

            // 退出登录
            $('#btnLogout').click(function () {
                axios.post(`${apiUrl}/logout`)
                    .then(function (res) {
                        if (res.status === 200) {
                            location.href = './login.html';
                        }
                    })
                    .catch(function () {
                        alert('退出登录失败,请重试');
                    });
            });

            // 页面加载时获取文章信息
            if (articleId) {
                fetchArticle(articleId);
            }
        });
    </script>


</body>

</html>

修改和删除文章 edit_delete_article.html

bash 复制代码
<!DOCTYPE html>
<html lang="zh">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>修改和删除文章</title>
    <script src="../public/jQuery.js"></script>
    <link rel="stylesheet" href="../public/bootstrap.min.css">
    <script src="../public/axios.js"></script>

    <style>
        body {
            background-color: #f8f9fa;
            font-family: Arial, sans-serif;
        }

        .navbar {
            background-color: #007bff;
            color: white;
        }

        .navbar-brand,
        .navbar-nav li a {
            color: white !important;
            cursor: pointer;
        }

        .navbar-brand:hover,
        .navbar-nav li a:hover {
            color: #000 !important;
        }

        .container {
            margin-top: 20px;
        }

        .form-wrapper {
            background-color: #fff;
            border-radius: 15px;
            padding: 20px;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
            max-width: 800px;
            margin: auto;
        }

        .header {
            font-size: 32px;
            font-weight: bold;
            text-align: center;
            margin-bottom: 20px;
        }

        .article-item {
            display: flex;
            justify-content: space-between;
            /* 使按钮右对齐 */
            align-items: center;
            padding: 10px;
            border-bottom: 1px solid #ccc;
        }

        .article-item div {
            flex-grow: 1;
            /* 让标题部分占用剩余空间 */
        }

        .btn-delete {
            background: linear-gradient(to right, #4facfe, #00f2fe);
            color: white;
            border: none;
            border-radius: 5px;
            padding: 5px 10px;
            cursor: pointer;
        }

        .btn-delete:hover {
            background-image: linear-gradient(to right, #00f2fe, #4facfe);
            color: #000;
            transform: translateY(-3px);
            box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
        }

        .btn-edit {
            background: linear-gradient(to right, #4facfe, #00f2fe);
            color: white;
            border: none;
            border-radius: 5px;
            padding: 5px 10px;
            cursor: pointer;
            margin-right: 5px;
            /* 增加右边距以便于分隔 */
        }

        .btn-edit:hover {
            background-image: linear-gradient(to right, #00f2fe, #4facfe);
            color: #000;
            transform: translateY(-3px);
            box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
        }


        .tankuang {
            display: none;
            position: fixed;
            background: linear-gradient(to right, #4facfe, #00f2fe);
            z-index: 99;
            width: 200px;
            height: 100px;
            text-align: center;
            line-height: 100px;
            border-radius: 5px;
            top: 30%;
            left: 50%;
            transform: translate(-50%, -50%);
            color: #ffffff;
            font-size: 20px;
        }

        .pagination {
            display: flex;
            justify-content: center;
            margin-top: 20px;
        }

        .pagination li {
            list-style: none;
            margin: 0 5px;
            display: flex;
            align-items: center;
        }

        .pagination .page-link {
            text-decoration: none;
            padding: 8px 12px;
            border: none;
            border-radius: 5px;
            color: white;
            background-image: linear-gradient(to right, #4facfe, #00f2fe);
            transition: background-color 0.3s, color 0.3s;
        }

        .pagination .page-link:hover {
            background-image: linear-gradient(to right, #00f2fe, #4facfe);
            color: #000;
            transform: translateY(-3px);
            box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
        }

        .pagination .page-link:disabled {
            background-color: #e0e0e0;
            color: #a0a0a0;
            cursor: not-allowed;
        }

        .pagination .page-number {
            display: flex;
            align-items: center;
            height: 100%;
        }
    </style>
</head>

<body>

    <div class="tankuang" id="successMessage">删除成功...</div>

    <nav class="navbar navbar-expand-lg">
        <div class="container-fluid">
            <a class="navbar-brand" href="./index.html">返回首页</a>
            <div class="collapse navbar-collapse" id="navbarNav">
                <ul class="navbar-nav ms-auto ml-auto">
                    <li class="nav-item">
                        <a class="nav-link" id="btnLogout">退出登录</a>
                    </li>
                </ul>
            </div>
        </div>
    </nav>

    <div class="container">
        <div class="form-wrapper">
            <div class="header">文章列表</div>
            <div id="articleList"></div>

            <!-- 新的分页结构 -->
            <nav aria-label="Page navigation" class="limit">
                <ul class="pagination" id="pagination">
                    <li class="page-item"><a class="page-link" id="prevPage" href="#">上一页</a></li>
                    <li class="page-item"><span id="currentPage">1</span> / <span id="totalPages">1</span></li>
                    <li class="page-item"><a class="page-link" id="nextPage" href="#">下一页</a></li>
                </ul>
            </nav>
        </div>
    </div>

    <script>
        $(function () {
            const apiUrl = 'http://localhost/api';
            let currentPage = 1; // 当前页
            const pageSize = 10; // 每页条数

            // 获取文章列表
            function fetchArticles(page) {
                axios.post(`${apiUrl}/limit`, { num: (page - 1) * pageSize })
                    .then(function (res) {
                        const articles = res.data.data;
                        let articleListHtml = '';

                        articles.forEach(article => {
                            articleListHtml += `
                                <div class="article-item">
                                    <div>${article.title}</div>
                                    <button class="btn-edit" data-id="${article.id}">修改</button> <!-- 修改按钮 -->
                                    <button class="btn-delete" data-id="${article.id}">删除</button>
                                </div>
                            `;
                        });

                        $('#articleList').html(articleListHtml);
                        updatePagination(); // 更新分页
                    })
                    .catch(function (error) {
                        console.error('获取文章列表失败:', error);
                    });
            }

            // 更新分页
            function updatePagination() {
                axios.get(`${apiUrl}/getArticleCount`)
                    .then(function (res) {
                        const totalArticles = res.data.totalCount;
                        const totalPages = Math.ceil(totalArticles / pageSize);

                        $('#totalPages').text(totalPages);
                        $('#currentPage').text(currentPage);

                        // 控制前后按钮显示
                        $('#prevPage').prop('disabled', currentPage === 1);
                        $('#nextPage').prop('disabled', currentPage === totalPages);
                    });
            }

            // 删除文章
            $(document).on('click', '.btn-delete', function () {
                const articleId = $(this).data('id');
                axios.post(`${apiUrl}/delete`, { id: articleId })
                    .then(function (res) {
                        $('#successMessage').text('删除成功...').show();
                        setTimeout(function () {
                            $('#successMessage').fadeOut();
                        }, 2000);
                        fetchArticles(currentPage); // 重新获取文章列表
                    })
                    .catch(function (error) {
                        const response = error.response.data;
                        alert(response.message);
                    });
            });

            // 修改按钮点击事件
            $(document).on('click', '.btn-edit', function () {
                const articleId = $(this).data('id');
                location.href = `./edit_article.html?id=${articleId}`;
            });

            // 分页点击事件
            $('#prevPage').click(function () {
                if (currentPage > 1) {
                    currentPage--;
                    fetchArticles(currentPage);
                }
            });

            $('#nextPage').click(function () {
                // 假设有个接口提供总页数
                const totalPages = parseInt($('#totalPages').text());
                if (currentPage < totalPages) {
                    currentPage++;
                    fetchArticles(currentPage);
                }
            });

            // 退出登录
            $('#btnLogout').click(function () {
                axios.post(`${apiUrl}/logout`)
                    .then(function (res) {
                        if (res.status === 200) {
                            location.href = './login.html';
                        }
                    })
                    .catch(function () {
                        alert('退出登录失败,请重试');
                    });
            });

            // 页面加载时获取文章
            fetchArticles(currentPage);
        });
    </script>

</body>

</html>

至此完成项目,欢迎各位大佬指点错误
我做这个项目时参考了这个博主的也利用AI生成代码参考https://blog.csdn.net/weixin_45932821/article/details/127854901?spm=1001.2101.3001.6650.7\&utm_medium=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromBaidu~Rate-7-127854901-blog-117518358.235^v43^pc_blog_bottom_relevance_base5\&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromBaidu~Rate-7-127854901-blog-117518358.235^v43^pc_blog_bottom_relevance_base5\&utm_relevant_index=13

相关推荐
月光水岸New1 小时前
Ubuntu 中建的mysql数据库使用Navicat for MySQL连接不上
数据库·mysql·ubuntu
狄加山6751 小时前
数据库基础1
数据库
我爱松子鱼1 小时前
mysql之规则优化器RBO
数据库·mysql
chengooooooo1 小时前
苍穹外卖day8 地址上传 用户下单 订单支付
java·服务器·数据库
Rverdoser2 小时前
【SQL】多表查询案例
数据库·sql
Galeoto2 小时前
how to export a table in sqlite, and import into another
数据库·sqlite
人间打气筒(Ada)3 小时前
MySQL主从架构
服务器·数据库·mysql
leegong231113 小时前
学习PostgreSQL专家认证
数据库·学习·postgresql
喝醉酒的小白3 小时前
PostgreSQL:更新字段慢
数据库·postgresql
敲敲敲-敲代码3 小时前
【SQL实验】触发器
数据库·笔记·sql