基于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

相关推荐
freesky11 分钟前
Ubuntu基础命令
服务器·数据库·ubuntu
黑客呀28 分钟前
常见漏洞—SQL注入
数据库·sql·安全·web安全·系统安全
MeyrlNotFound1 小时前
SQL语句
数据库·sql
奥顺2 小时前
黄历宜忌算法 API:黄道吉日 PHP 计算方法
大数据·mysql·开源·php
奈葵2 小时前
MySQL表的增删改查(2)
数据库·mysql
장숙혜2 小时前
npm下载依赖相关命令
前端·npm·node.js
小陈phd2 小时前
QT从入门到精通(二) ——信号与槽机制
开发语言·数据库·qt
2201_759154882 小时前
Linux应用开发————mysql数据库
数据库·mysql
m0_716499062 小时前
达梦8-达梦数据的示例用户和表
数据库
A尘埃2 小时前
项目中常见的Mapper文件和接口
数据库·mybatis