📙 简单图书管理系统


🍀 开发环境


🌏 展示效果

网页效果


数据库


文件目录


🖥️ 模板引擎搭建系统雏形(后端渲染)


模板文件

<!-- index.art -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>图书管理系统</title>
    <link rel="stylesheet" href="/www/style.css">  <!-- 静态资源服务 -->
</head>
<body>
    <div class="title">图书管理系统<a href="/toAddBook">添加图书</a></div>
    <div class="content">
        <table cellpadding="0" cellspacing="0"> <!-- 边框紧凑 -->
            <thead>
                <tr>
                    <th>编号</th>
                    <th>名称</th>
                    <th>作者</th>
                    <th>分类</th>
                    <th>描述</th>
                    <th>操作</th>
                </tr>
            </thead> 
            <tbody>
            {{each list}}
                <tr>
                    <td>{{$value.id}}</td>
                    <td>{{$value.name}}</td>
                    <td>{{$value.author}}</td>
                    <td>{{$value.category}}</td>
                    <td>{{$value.description}}</td>
                    <td><a href="/toEditBook?id={{$value.id}}">修改</a>|<a href="/toDeleteBook?id={{$value.id}}">删除</a></td>
                </tr>
            {{/each}}
            </tbody>
        </table>
    </div>
</body>
</html>
<!-- addBook.art -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>添加图书</title>
</head>
<body>
    <div>添加图书</div>
    <form action="/addBook" method="post">
        名称:<input type="text" name="name"><br>
        作者:<input type="text" name="author"><br>
        分类:<input type="text" name="category"><br>
        描述:<input type="text" name="description"><br>
        <input type="submit" value="提交">
    </form>
</body>
</html>
<!-- editBook.art -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>添加图书</title>
</head>
<body>
    <div>修改图书</div>
    <form action="/editBook" method="post">
        <input type="hidden" name="id" value="{{id}}"> <!-- 很重要 --> 
        名称:<input type="text" name="name" value="{{name}}"><br>
        作者:<input type="text" name="author" value="{{author}}"><br>
        分类:<input type="text" name="category" value="{{category}}"><br>
        描述:<input type="text" name="description" value="{{description}}"><br>
        <input type="submit" value="提交">
    </form>
</body>
</html>

业务模块

/* 
    业务模块
*/
let data = require('./data.json');
const path = require('path');
const fs = require('fs');

// @ 自动生成图书编号
let maxBookCode = () => {
    let arr = [];
    data.forEach((item) => {
        arr.push(item.id);
    });
    return Math.max.apply(null, arr); // !
};

// @ 渲染主页面
exports.showIndex = (req, res) => {
    res.render('index', {
        list: data
    }); // * 给数组起了个名字叫data
};

// @ 跳转到添加图书的页面
exports.toAddBook = (req, res) => {
    res.render('addBook', {});
};

// @ 添加图书保存数据
exports.addBook = (req, res) => {
    // ! 获取表单数据
    let info = req.body;
    let book = {};
    for (let key in info) {
        book[key] = info[key];
    }
    book.id = maxBookCode() + 1;
    data.push(book);
    // ! 需要把内存中的数据写入到文件
    fs.writeFile(path.join(__dirname, 'data.json'), JSON.stringify(data), (err) => {
        if (err) {
            res.send('server error');
        }
        // ! 文件写入成功之后重新跳转到主页面
        res.redirect('/');
    });
}

// @ 跳转到编辑图书页面
exports.toEditBook = (req, res) => {
    let id = req.query.id; // tip
    let book = {};
    data.forEach((item) => {
        if (item.id == id) {
            book = item;
            return; //break;
        }
    });
    res.render('editBook', book);
};

// @ 编辑图书更新数据
exports.editBook = (req, res) => {
    let info = req.body;
    data.forEach((item) => {
        if (info.id == item.id) {
            for (let key in info) {
                item[key] = info[key];
            }
        }
    });

    // ! 需要把内存中的数据写入到文件
    fs.writeFile(path.join(__dirname, 'data.json'), JSON.stringify(data), (err) => {
        if (err) {
            res.send('server error');
        }
        // ! 文件写入成功之后重新跳转到主页面
        res.redirect('/');
    });
}

路由模块

/*
    路由模块
*/

const express = require('express');
const router = express.Router();
const service = require('./service.js');

// 路由处理

// @ 渲染主页
router.get('/',service.showIndex);

// @ 添加图书(跳转到添加图书的页面)
router.get('/toAddBook',service.toAddBook);

// @ 添加图书(提交表单)
router.post('/addBook',service.addBook);

// @ 跳转到编辑图书信息页面
router.get('/toEditBook',service.toEditBook);

// @ 编辑图书提交表单
router.post('/editBook',service.editBook);


module.exports = router; // @ 导出

入口文件

/*
    图书管理系统 - 入口文件
*/
const express = require('express');
const template = require('art-template');
const path = require('path');
const router = require('./router.js')
const app = express();

// ! 设置模板引擎
// 使express兼容art-template模板引擎
app.engine('art', require('express-art-template'));
// 设置模板路径
app.set('views',path.join(__dirname,'views'));
// 设置模板引擎
app.set('view engine','art');

// ! 处理请求参数
// 挂载参数处理中间件(post)
app.use(express.urlencoded({ extended: false }));
// 处理json格式的参数
app.use(express.json());

// ! 启动静态资源服务
app.use('/www',express.static(path.join(__dirname,'public')));  // @ 第一个参数虚拟路径

// ! 启动服务器功能
// 配置路由
app.use(router);
// 监听端口
app.listen(3000,()=>{
    console.log('running...');
});

json 假数据

[{"id":"1","name":"三国演义","author":"罗贯中","category":"文学","desc":"一个杀伐纷争的年代"},{"id":"2","name":"水浒传","author":"施耐庵","category":"文学","desc":"108条好汉的故事"},{"id":"3","name":"西游记","author":"吴承恩","category":"文学","desc":"佛教与道教的斗争"},{"id":"4","name":"红楼梦","author":"曹雪芹","category":"文学","desc":"一个封建王朝的缩影"},{"name":"陈思艳123","author":"其味无穷二213123","category":"请问请问","desc":"主线程","id":"5","_locals":{}}]


🖥️ Mysql 数据库模块


生成SQL语句

/*
    @ 把data.json文件中的数据拼接成insert语句
*/
const path = require('path');
const fs = require('fs');

fs.readFile(path.join(__dirname,'../','data.json'),'utf8',(err,data)=>{
    if(err) return;
    let list = JSON.parse(data);
    let arr = [];
    list.forEach(item => {
        let sql = `\ninsert into book(name,author,category,description) 
        values('${item.name}','${item.author}','${item.category}','${item.desc}');`;
        arr.push(sql);
    });
    fs.writeFile(path.join(__dirname,'data.sql'),arr.join('').replace(/^\s+/gm, ""),'utf8',(err)=>{
        if(err)console.log("write error");
    });
});

增删改查

连接数据库

/* 
    操作数据库基本步骤
*/
// ! 加载数据库驱动
const mysql = require('mysql');
// ! 创建数据库连接
var connection = mysql.createConnection({
    host: 'localhost',
    user: 'root',
    password: 'sql2008',
    database: 'book'
});

connection.connect();

connection.query('SELECT count(*) as total from book', function (error, results, fields) {
    if (error) throw error;
    console.log('The solution is: ', results[0].total);
});

connection.end();

插入数据

/* 
    插入数据
*/
// ! 加载数据库驱动
const mysql = require('mysql');
// ! 创建数据库连接
var connection = mysql.createConnection({
    host: 'localhost',
    user: 'root',
    password: 'sql2008',
    database: 'book'
});

connection.connect();

let sql = 'insert into book set ?';
let data = {
    name : '明朝那些事',
    author : '当年明月',
    category : '文学',
    description : '明朝的历史'
};

connection.query(sql,data, function (error, results, fields) {
    if (error) throw error;
    // console.log(results);
    if(results.affectedRows==1)console.log('数据插入成功');
});

connection.end();


更新数据

/* 
     更新数据
*/
// ! 加载数据库驱动
const mysql = require('mysql');
// ! 创建数据库连接
var connection = mysql.createConnection({
    host: 'localhost',
    user: 'root',
    password: 'sql2008',
    database: 'book'
});

connection.connect();

let sql = 'update book set name=?,author=?,category=?,description=? where id=?';
let data = ['浪潮之巅','吴军','计算机','IT巨头的兴衰史',8];

connection.query(sql,data, function (error, results, fields) {
    if (error) throw error;
    // console.log(results);
    if(results.affectedRows==1)console.log('数据更新成功');
});

connection.end();

删除数据

/* 
    删除数据
*/
// ! 加载数据库驱动
const mysql = require('mysql');
// ! 创建数据库连接
var connection = mysql.createConnection({
    host: 'localhost',
    user: 'root',
    password: 'sql2008',
    database: 'book'
});

connection.connect();

let sql = 'delete from book where id = ?';
let data = [7]; // @ 格式上的要求

connection.query(sql,data, function (error, results, fields) {
    if (error) throw error;
    // console.log(results);
    if(results.affectedRows==1)console.log('数据删除成功');
});

connection.end();

查询数据

/* 
    查询数据
*/
// ! 加载数据库驱动
const mysql = require('mysql');
// ! 创建数据库连接
var connection = mysql.createConnection({
    host: 'localhost',
    user: 'root',
    password: 'sql2008',
    database: 'book'
});

connection.connect();

let sql = 'select * from book where id = ?';
let data = [6]; 

connection.query(sql,data, function (error, results, fields) {
    if (error) throw error;
    //console.log(results[1]);
    console.log(results[0].name);
});

connection.end();

封装API

封装脚本

/*
    @ 封装数据库通用API
*/
const mysql = require('mysql');

exports.base = (sql,data,callback)=>{
// ! 加载数据库驱动
const connection = mysql.createConnection({
    host: 'localhost',
    user: 'root',
    password: 'sql2008',
    database: 'book'
});

connection.connect();

// info 数据库操作也是异步的
connection.query(sql,data, function (error, results, fields) {
    if (error) throw error;
    callback(results);
});

connection.end();
}

测试脚本

/*
    测试通用api
*/
const db = require('./db.js');

let sql,data;

// test 插入操作
sql = 'insert into book set ?';
data = {
    name : '笑傲江湖',
    author : '金庸',
    category: '文学',
    description : '武侠小说'
};

db.base(sql,data,res=>{
    console.log(res);
});

// test 更新操作
sql = 'update book set name=?,author=?,category=?,description=? where id = ?';
data = ['天龙八部','金庸','文学','武侠小说',1];

db.base(sql,data,res=>{
    console.log(res);
});

// test 删除操作
sql = 'delete from book where id = ?';
data = [12];
db.base(sql,data,res=>{
    console.log(res);
});

// test 查询操作
sql = 'select * from book where id = ?';
data = [2];
db.base(sql,data,res=>{
    console.log(res);
});

登录验证

/*
    登录验证(前端+后端+数据库)
*/

const express = require('express');
const app = express();
const path = require('path');
const db = require('./db');

app.use(express.urlencoded({ extended: false }));

app.use(express.static(path.join(__dirname,'public')));

app.post('/check' , (req , res)=>{
    let {username,password} = req.body;

    let sql = 'select count(*) as total from users where username = ? and password = ?';
    let data = [username,password];
    db.base(sql,data,result=>{
        if(result[0].total==1){
            res.send('success');
        }
        else{
            res.send('failure');
        }
    });
});

app.listen(3000,()=>{
    console.log('running...');
});


业务模块与Mysql结合

/* 
    业务模块
*/
let data = require('./data.json');
const path = require('path');
const fs = require('fs');
const db = require('./db');

// @ 自动生成图书编号
let maxBookCode = () => {
    let arr = [];
    data.forEach((item) => {
        arr.push(item.id);
    });
    return Math.max.apply(null, arr); // !
};

// @ 渲染主页面
exports.showIndex = (req, res) => {
    let sql = 'select * from book';
    db.base(sql, null, data => {
        res.render('index', {
            list: data
        });
    });
};

// @ 跳转到添加图书的页面
exports.toAddBook = (req, res) => {
    res.render('addBook', {});
};

// @ 添加图书保存数据
exports.addBook = (req, res) => {
    // ! 获取表单数据
    let info = req.body;
    // let book = {};
    // for (let key in info) {
    //     book[key] = info[key];
    // }
    // book.id = maxBookCode() + 1;

    let sql = 'insert into book set ?';
    db.base(sql,info,result=>{
        if(result.affectedRows == 1 ){
            res.redirect('/');
        }
    });
    return;
    data.push(book);
    // ! 需要把内存中的数据写入到文件
    fs.writeFile(path.join(__dirname, 'data.json'), JSON.stringify(data), (err) => {
        if (err) {
            res.send('server error');
        }
        // ! 文件写入成功之后重新跳转到主页面
        res.redirect('/');
    });
}

// @ 跳转到编辑图书页面
exports.toEditBook = (req, res) => {
    let id = req.query.id; // tip
    // let book = {};
    // data.forEach((item) => {
    //     if (item.id == id) {
    //         book = item;
    //         return; //break;
    //     }
    // });
    let sql = 'select * from book where id=?';
    let data = [id];
    db.base(sql,data,result=>{
        if(result.length>0)   
            res.render('editBook', result[0]);
        else
            res.send('查找不到');
    });
};

// @ 编辑图书更新数据
exports.editBook = (req, res) => {
    let info = req.body;

    let sql = 'update book set name=?,author=?,category=?,description=? where id=?';
    let data = [info.name,info.author,info.category,info.description,info.id];
    db.base(sql,data,result=>{
        if(result.affectedRows==1){
            res.redirect('/');
        }
    });

    // data.forEach((item) => {
    //     if (info.id == item.id) {
    //         for (let key in info) {
    //             item[key] = info[key];
    //         }
    //     }
    // });
    // // ! 需要把内存中的数据写入到文件
    // fs.writeFile(path.join(__dirname, 'data.json'), JSON.stringify(data), (err) => {
    //     if (err) {
    //         res.send('server error');
    //     }
    //     // ! 文件写入成功之后重新跳转到主页面
    //     res.redirect('/');
    // });
}

// @ 删除图书信息
exports.toDeleteBook = (req,res) =>{
    let id = req.query.id;
    let sql = 'delete from book where id=?';
    let data = [id];
    db.base(sql,data,result=>{
        console.log(result);
        if(result.affectedRows==1){ // @ affectedRows
            res.redirect('/');
        }
    });
}

🖥️ API 接口开发

后台接口开发

/*
  !  后台接口开发
*/
const express = require('express');
const app = express();
const db = require('./db');

// @ 指定api路径 allBooks (JSON)
app.get('/allBooks',(req,res)=>{
    let sql = 'select * from book';
    db.base(sql,null,result=>{
        res.json(result);
    });
});

// @ 指定api路径 allBooks (JSONP)
app.set('jsonp callback name','call');

app.get('/allBooks2',(req,res)=>{
    let sql = 'select * from book';
    db.base(sql,null,result=>{
        res.jsonp(result);
    });
});

app.listen(3000,()=>{
    console.log('running...');
});

restful api

/*
    ! restful api 是从URL的格式来表述的
    get    http://localhost:3000/books
    get    http://localhost:3000/books/book
    post   http://localhost:3000/books/book
    get    http://localhost:3000/books/book/1
    put    http://localhost:3000/books/book    // @ 更新数据
    delete http://localhost:3000/books/book/1

    @ 传统URL风格
    http://localhost:3000/
    http://localhost:3000/toAddBook
    http://localhost:3000/addBook
    http://localhost:3000/toEditBook?id=1
    http://localhost:3000/editBook
    http://localhost:3000/toDeleteBook?id=2
*/
const express = require('express');
const app = express();
const db = require('./db');

app.get('/books' , (req , res)=>{
    let sql = 'select * from book';
    db.base(sql,null,result=>{
        res.json(result);
    });  
});

// http://localhost:3000/books/book/1
app.get('/books/book/:id' , (req , res)=>{
    let id = req.params.id;
    let sql = 'select * from book where id = ?';
    let data = [id];
    db.base(sql,data,result=>{
        res.json(result[0]);
    });  
})

app.listen(3000,()=>{
    console.log('running...');
});

⭐ 前端渲染开发(前端渲染)


Restful API


路由模块

const db = require('./db');
const {base:query} = db;

exports.allBooks = (req,res)=>{
    let sql = 'select * from book';
    query(sql,null,result=>{
        res.json(result);
    });
};

exports.addBook = (req,res)=>{
    let info = req.body;
    // test
    delete info.id;
    let sql = 'insert into book set ?';  // @ 要一个对象!
    query(sql,info,(result)=>{
        if(result.affectedRows==1){
            res.json({flag:1});
        }
        else{
            res.json({flag:2});
        }
    })
};

exports.getBookById = (req,res)=>{
    let id = req.params.id;
    let sql = 'select * from book where id=?';
    let data = [id];
    query(sql,data,result=>{
        res.json(result[0]);
    });
};

exports.editBook = (req,res)=>{
    let info = req.body;
    let sql = 'update book set name=?,author=?,category=?,description=? where id = ?';
    let data = [info.name,info.author,info.category,info.description,info.id];
    query(sql,data,(result)=>{
        if(result.affectedRows==1){
            res.json({flag:1});
        }
        else{
            res.json({flag:2});
        }
    });
};

exports.deleteBook = (req,res)=>{
    let id = req.params.id;
    let sql = 'delete from book where id = ?';
    let data = [id];
    query(sql,data,(result)=>{
        if(result.affectedRows==1){
            res.json({flag:1});
        }
        else{
            res.json({flag:2});
        }
    });
};

业务处理模块

const db = require('./db');
const {base:query} = db;


exports.allBooks = (req,res)=>{
    let sql = 'select * from book';
    query(sql,null,result=>{
        res.json(result);
    });
};

exports.addBook = (req,res)=>{
    let info = req.body;
    // test
    delete info.id;
    let sql = 'insert into book set ?';  // @ 要一个对象!
    query(sql,info,(result)=>{
        if(result.affectedRows==1){
            res.json({flag:1});
        }
        else{
            res.json({flag:2});
        }
    })
};

exports.getBookById = (req,res)=>{
    let id = req.params.id;
    let sql = 'select * from book where id=?';
    let data = [id];
    query(sql,data,result=>{
        res.json(result[0]);
    });
};

exports.editBook = (req,res)=>{
    let info = req.body;
    let sql = 'update book set name=?,author=?,category=?,description=? where id = ?';
    let data = [info.name,info.author,info.category,info.description,info.id];
    query(sql,data,(result)=>{
        if(result.affectedRows==1){
            res.json({flag:1});
        }
        else{
            res.json({flag:2});
        }
    });
};

exports.deleteBook = (req,res)=>{
    let id = req.params.id;
    let sql = 'delete from book where id = ?';
    let data = [id];
    query(sql,data,(result)=>{
        if(result.affectedRows==1){
            res.json({flag:1});
        }
        else{
            res.json({flag:2});
        }
    });
};

Mysql API

/*
    @ 封装数据库通用API
*/
const mysql = require('mysql');

exports.base = (sql,data,callback)=>{
// ! 加载数据库驱动
const connection = mysql.createConnection({
    host: 'localhost',
    user: 'root',
    password: 'sql2008',
    database: 'book'
});

connection.connect();

// info 数据库操作也是异步的
connection.query(sql,data, function (error, results, fields) {
    if (error) {
        console.log(error);
        return;
    }
    callback(results);
});

connection.end();
}

入口文件

/*
  ! 实现图书管理系统所有的后台接口
*/

const express = require('express');
const router = require('./router.js');
const app = express();
const path = require('path');

app.use('/www',express.static(path.join(__dirname,'public')));
app.use(express.urlencoded({extended:false}));
app.use(router);

app.listen(3000,()=>{
    console.log('running...');
});

💡 前端开发( IMPORTANT )


Common CSS

/* style.css */

.title {
    text-align: center;
    background-color: lightgreen;
    height: 50px;
    line-height: 50px;
    font-size: 18px;
}

.content {
    background-color: lightblue;
}

.content table {
    width: 100%;
    text-align: center;
    border-right: 1px solid orange;
    border-bottom: 1px solid orange;
}

.content td,
th {
    border-left: 1px solid orange;
    border-top: 1px solid orange;
    height: 40px;
    line-height: 40px;
}

.form{
    display: none;
    position: absolute;
    left: 50%;
    margin-left: -110px;
    top: 100px;
}

Common HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>图书管理系统</title>
    <link rel="stylesheet" href="/www/css/style.css">  <!-- 静态资源服务 -->
    <script type="text/javascript" src="/www/js/dialog.js"></script>
    <script type="text/javascript" src="/www/js/jquery.js"></script>
    <script type="text/javascript" src="/www/js/template-web.js"></script>
    <script type="text/javascript" src="/www/js/index_ajax.js"></script>
    <script type="text/template" id="indexTpl">
            {{each list}}
                <tr>
                    <td>{{$value.id}}</td>
                    <td>{{$value.name}}</td>
                    <td>{{$value.author}}</td>
                    <td>{{$value.category}}</td>
                    <td>{{$value.description}}</td>
                    <td><a href="javascript:;">修改</a>|<a href="javascript:;">删除</a></td>
                </tr>
            {{/each}}
    </script>
</head>
<body>
    <div class="title">图书管理系统<a id="addBookId" href="javascript:void(0);">添加图书</a></div>
    <div class="content">
        <table cellpadding="0" cellspacing="0"> <!-- 边框紧凑 -->
            <thead>
                <tr>
                    <th>编号</th>
                    <th>名称</th>
                    <th>作者</th>
                    <th>分类</th>
                    <th>描述</th>
                    <th>操作</th>
                </tr>
            </thead> 
            <tbody id="dataList">


            </tbody>
        </table>
    </div>

    <form class="form" id="addBookForm">
        <!-- @ 需要一个隐藏域 -->
        <input type="hidden" name="id"> 
        名称:<input type="text" name="name"><br>
        作者:<input type="text" name="author"><br>
        分类:<input type="text" name="category"><br>
        描述:<input type="text" name="description"><br>
        <input type="button" value="提交">
    </form>
</body>
</html>

HTML + JQUERY + AJAX 开发

$(function () {
    // @ 渲染列表数据
    function initList() {
        $.ajax({
            type: 'get',
            url: '/books',
            dataType: 'json',
            success: function (data) {
                // 渲染数据列表
                var html = template('indexTpl', {
                    list: data
                });
                $('#dataList').html(html);
                // @ 必须在渲染完成内容之后才可以操作DOM标签
                $('#dataList').find('tr').each((index, elem) => {
                    var td = $(elem).find('td:eq(5)');

                    var id = $(elem).find('td:eq(0)').text();
                    // @ 绑定编辑图书的单击事件
                    td.find('a:eq(0)').click(() => {
                        editBook(id);
                    });
                    // @ 绑定删除图书的单击事件
                    td.find('a:eq(1)').click(() => {
                        deleteBook(id);
                    });

                    // @ 绑定添加图书信息的单击事件
                    addBook(); // test ?

                    // @ 操作完成之后重置表单
                    var form = $('#addBookForm');
                    form.get(0).reset(); // ! 重置表单(但是不会对隐藏域进行处理)
                    form.find('input[type=hidden]').val('');
                });
            }
        });
    };
    initList();

    // @ 编辑图书信息
    function editBook(id) {
        var form = $('#addBookForm');
        // 先根据数据id查询最新的数据
        $.ajax({
            type: 'get',
            url: '/books/book/' + id,
            dataType: 'json',
            success: data => {
                // 生成弹出窗口
                var mark = new MarkBox(600, 400, '编辑图书', form.get(0));
                mark.init();
                // 填充表单数据
                form.find('input[name=id]').val(data.id);
                form.find('input[name=name]').val(data.name);
                form.find('input[name=author]').val(data.author);
                form.find('input[name=category]').val(data.category);
                form.find('input[name=description]').val(data.description);
                // 对表单提交按钮重新绑定单机事件
                form.find('input[type=button]').unbind('click').click(() => {
                    // 编辑完成数据之后重新提交表单
                    $.ajax({
                        type : 'put',
                        url : '/books/book',
                        data : form.serialize(),
                        dataType : 'json',
                        success : data=>{
                            if(data.flag=='1'){
                                // 隐藏弹窗
                                mark.close();
                                // 重新渲染数据列表
                                initList();
                            }
                        }
                    });
                });
            }
        });
    }

    // fixme 表格序列化过程中,会带入id值的问题
    // @ 添加图书信息
    function addBook() {
        $('#addBookId').click(() => {
            var form = $('#addBookForm');
            // 实例化弹窗对象
            var mark = new MarkBox(600, 400, '添加图书', form.get(0));
            mark.init();

            form.find('input[type=button]').unbind('click').click(() => {
                $.ajax({
                    type: 'post',
                    url: '/books/book',
                    data: form.serialize(),
                    dataType: 'json',
                    success: data => {
                        if (data.flag == '1') {
                            // @ 关闭弹窗
                            mark.close();
                            // @ 添加图书之后重新渲染数据列表
                            initList();
                        }
                    }
                });
            });
        });
    }

    // @ 删除图书信息
    function deleteBook(id){
        $.ajax({
            type : 'delete',
            url : '/books/book/'+id,
            dataType : 'json',
            success : data=>{
                // 删除图书信息之后重新渲染数据列表
                if(data.flag == '1'){
                    initList();
                }
            }
        });
    }
});

HTML + JQUERY + AXIOS 开发

// @ 使用 Axios 进行网络请求接收处理
$(function () {
    // @ 渲染列表数据
    function initList() {
        axios.get('/books')
            .then(res => {
                // @ 处理获取的数据
                // ^ 将数据填充到表格
                var html = template('indexTpl', {
                    list: res.data
                });
                $('#dataList').html(html);
                // ^ 为每行tr的a标签写入事件
                $('#dataList').find('tr').each((index, elem) => {
                    var td = $(elem).find('td:eq(5)');

                    var id = $(elem).find('td:eq(0)').text();
                    // @ 绑定编辑图书的单击事件
                    td.find('a:eq(0)').click(() => {
                        editBook(id);
                    });
                    // @ 绑定删除图书的单击事件
                    td.find('a:eq(1)').click(() => {
                        deleteBook(id);
                    });

                    // @ 绑定添加图书信息的单击事件
                    addBook(); // test ?

                    // @ 操作完成之后重置表单
                    var form = $('#addBookForm');
                    form.get(0).reset(); // ! 重置表单(但是不会对隐藏域进行处理)
                    form.find('input[type=hidden]').val('');
                });

            })
            .catch(err => {
                console.error(err);
            });
    }

    initList();

    // @ 编辑图书信息
    function editBook(id) {
        var form = $('#addBookForm');
        // 先根据数据id查询最新的数据
        axios.get('/books/book/' + id)
            .then(res => {
                let data = res.data;
                // 生成弹出窗口
                var mark = new MarkBox(600, 400, '编辑图书', form.get(0));
                mark.init();
                // 填充表单数据
                form.find('input[name=id]').val(data.id);
                form.find('input[name=name]').val(data.name);
                form.find('input[name=author]').val(data.author);
                form.find('input[name=category]').val(data.category);
                form.find('input[name=description]').val(data.description);
                // 对表单提交按钮重新绑定单机事件
                form.find('input[type=button]').unbind('click').click(() => {
                    // 编辑完成数据之后重新提交表单
                    axios.put('/books/book', form.serialize())
                        .then(res => {
                            let data = res.data;
                            if (data.flag == '1') {
                                // 隐藏弹窗
                                mark.close();
                                // 重新渲染数据列表
                                initList();
                            }
                        })
                        .catch(err => {
                            console.error(err);
                        })
                })
            })
            .catch(function (error) {
                console.log(error);
            });
    }

    // fixme 表格序列化过程中,会带入id值的问题
    // @ 添加图书信息
    function addBook() {
        $('#addBookId').click(() => {
            var form = $('#addBookForm');
            // 实例化弹窗对象
            var mark = new MarkBox(600, 400, '添加图书', form.get(0));
            mark.init();

            form.find('input[type=button]').unbind('click').click(() => {

                axios.post('/books/book', form.serialize())
                    .then(res => {
                        if (res.data.flag == '1') {
                            // @ 关闭弹窗
                            mark.close();
                            // @ 添加图书之后重新渲染数据列表
                            initList();
                        }
                    })
                    .catch(err => {
                        console.error(err);
                    });
            });
        });
    }

    // @ 删除图书信息
    function deleteBook(id) {
        axios.delete('/books/book/' + id)
            .then(res => {
                // 删除图书信息之后重新渲染数据列表
                if (res.data.flag == '1') {
                    initList();
                }
            })
            .catch(err => {
                console.error(err);
            })
    }
});

🖥️ 服务器主动发送请求

Select

/*
    ! 从服务器主动发送请求调用接口-查询数据
*/

const http = require('http');
const path = require('path');
const fs = require('fs');

const options = {
    hostname : 'localhost',
    port : 3000,
    path : '/books'
};


var req = http.request(options,(res)=>{
    let info = '';
    res.on('data',(chunk)=>{
        info+=chunk;
    });
    res.on('end',()=>{
        console.log(info);
    });
});

req.end();

Insert

/*
    ! 从服务器主动发送请求调用接口-添加数据
*/

const http = require('http');
const path = require('path');
const fs = require('fs');

const options = {
    hostname : 'localhost',
    port : 3000,
    path : '/books/book',
    method : 'post',
    headers :{
        'Content-Type': 'application/x-www-form-urlencoded',
    }
};

let data = new URLSearchParams({
    name : 'adafa',
    author : 'xxxx',
    category : 'xasdasd',
    description : 'hahaha'
}).toString(); // @ 字符串化

var req = http.request(options,(res)=>{
    let info = '';
    res.on('data',(chunk)=>{
        info+=chunk;
    });
    res.on('end',()=>{
        console.log(info);
    });
});

// @ req写入Body
req.write(data); 
req.end();

Select by id

/*
    ! 从服务器主动发送请求调用接口-选择数据
*/

const http = require('http');
const path = require('path');
const fs = require('fs');


const options = {
    hostname : 'localhost',
    port : 3000,
    path : '/books/book/24',
    method : 'get'
};

var req = http.request(options,(res)=>{
    let info = '';
    res.on('data',(chunk)=>{
        info+=chunk;
    });
    res.on('end',()=>{
        console.log('\x1B[1m %s \x1B[1m',info);
    });
});

req.end();

Update

/*
    ! 从服务器主动发送请求调用接口-更新数据
*/

const http = require('http');
const path = require('path');
const fs = require('fs');


const options = {
    hostname : 'localhost',
    port : 3000,
    path : '/books/book',
    method : 'put', 
    headers :{
        'Content-Type': 'application/x-www-form-urlencoded',
    }
};

let data = new URLSearchParams({
    id : '21',
    name : 'adafa',
    author : 'xxxx',
    category : 'xasdasd',
    description : 'hahaha'
}).toString(); // @ 字符串化

var req = http.request(options,(res)=>{
    let info = '';
    res.on('data',(chunk)=>{
        info+=chunk;
    });
    res.on('end',()=>{
        console.log('\x1B[1m %s \x1B[1m',info);
    });
});

req.write(data);
req.end();

Delete

/*
    ! 从服务器主动发送请求调用接口-删除数据
*/

const http = require('http');
const path = require('path');
const fs = require('fs');


const options = {
    hostname : 'localhost',
    port : 3000,
    path : '/books/book/22',
    method : 'delete', 
    headers :{
        'Content-Type': 'application/x-www-form-urlencoded',
    }
};

var req = http.request(options,(res)=>{
    let info = '';
    res.on('data',(chunk)=>{
        info+=chunk;
    });
    res.on('end',()=>{
        console.log('\x1B[1m %s \x1B[1m',info);
    });
});

req.end();

⭐调用第三方接口

直接访问

/*
    ! 从服务器主动发送请求调用第三方接口
*/

const http = require('http');
const path = require('path');
const fs = require('fs');


let citycode = '101010100'
const options = {
    hostname : 'www.weather.com.cn',
    port : 80,
    path : `/data/sk/${citycode}.html`,
    method : 'get' 
};

var req = http.request(options,(res)=>{
    let info = '';
    res.on('data',(chunk)=>{
        info+=chunk;
    });
    res.on('end',()=>{
        console.log('\x1B[1m %s \x1B[1m',info);
    });
});

req.end();


封装访问方法

/*
    ! 从服务器主动发送请求调用第三方接口
*/

const http = require('http');
const path = require('path');
const fs = require('fs');

exports.queryWether = (cityCode,callback)=>{
    //let cityCode = '101010100'
    const options = {
        hostname : 'www.weather.com.cn',
        port : 80,
        path : `/data/sk/${cityCode}.html`,
        method : 'get' 
    };

    var req = http.request(options,(res)=>{
        let info = '';
        res.on('data',(chunk)=>{
            info+=chunk;
        });
        res.on('end',()=>{
            //console.log('\x1B[1m %s \x1B[1m',info);
            callback(JSON.parse(info)); // @ 转换成对象
        });
    });

    req.end();
};
const weather = require('./1-7 third serverapi.js');

weather.queryWether('101020100',data=>{
    let {weatherinfo:info} = data;
    console.log(`${info.city} 今天风向是 ${info.WD}`);
});