app
/**
* app.js 入门模块
* 职责:
* 创建服务
* 做一些服务相关配置
* 模板引擎
* body-parser 解析表单 post 请求体
* 提供静态资源服务
* 挂载路由
* 监听端口启动服务
*/
var express = require('express')
var router = require('./router')
var bodyParser = require('body-parser')
var app = express()
app.use('/node_modules/', express.static('./node_modules/'))
app.use('/public/', express.static('./public/'))
app.engine('html', require('express-art-template'))
// 配置模板引擎和 body-parser 一定要在 app.use(router) 挂载路由之前
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json
app.use(bodyParser.json())
// 把路由容器挂载到 app 服务中
app.use(router)
app.listen(3000, function () {
console.log('running 3000...')
})
module.exports = app
router
/**
* router.js 路由模块
* 职责:
* 处理路由
* 根据不同的请求方法+请求路径设置具体的请求处理函数
* 模块职责要单一,不要乱写
* 我们划分模块的目的就是为了增强项目代码的可维护性
* 提升开发效率
*/
var fs = require('fs')
var Student = require('./student')
// Express 提供了一种更好的方式
// 专门用来包装路由的
var express = require('express')
// 1. 创建一个路由容器
var router = express.Router()
// 2. 把路由都挂载到 router 路由容器中
/*
* 渲染学生列表页面
*/
router.get('/students', function (req, res) {
Student.find(function (err, students) {
if (err) {
return res.status(500).send('Server error.')
}
res.render('index.html', {
fruits: [
'苹果',
'香蕉',
'橘子'
],
students: students
})
})
})
/*
* 渲染添加学生页面
*/
router.get('/students/new', function (req, res) {
res.render('new.html')
})
/*
* 处理添加学生
*/
router.post('/students/new', function (req, res) {
// 1. 获取表单数据
// 2. 处理
// 将数据保存到 db.json 文件中用以持久化
// 3. 发送响应
Student.save(req.body, function (err) {
if (err) {
return res.status(500).send('Server error.')
}
res.redirect('/students')
})
})
/*
* 渲染编辑学生页面
*/
router.get('/students/edit', function (req, res) {
// 1. 在客户端的列表页中处理链接问题(需要有 id 参数)
// 2. 获取要编辑的学生 id
//
// 3. 渲染编辑页面
// 根据 id 把学生信息查出来
// 使用模板引擎渲染页面
Student.findById(parseInt(req.query.id), function (err, student) {
if (err) {
return res.status(500).send('Server error.')
}
res.render('edit.html', {
student: student
})
})
})
/*
* 处理编辑学生
*/
router.post('/students/edit', function (req, res) {
// 1. 获取表单数据
// req.body
// 2. 更新
// Student.updateById()
// 3. 发送响应
Student.updateById(req.body, function (err) {
if (err) {
return res.status(500).send('Server error.')
}
res.redirect('/students')
})
})
/*
* 处理删除学生
*/
router.get('/students/delete', function (req, res) {
// 1. 获取要删除的 id
// 2. 根据 id 执行删除操作
// 3. 根据操作结果发送响应数据
Student.deleteById(req.query.id, function (err) {
if (err) {
return res.status(500).send('Server error.')
}
res.redirect('/students')
})
})
// 3. 把 router 导出
module.exports = router
// 这样也不方便
// module.exports = function (app) {
// app.get('/students', function (req, res) {
// // readFile 的第二个参数是可选的,传入 utf8 就是告诉它把读取到的文件直接按照 utf8 编码转成我们能认识的字符
// // 除了这样来转换之外,也可以通过 data.toString() 的方式
// fs.readFile('./db.json', 'utf8', function (err, data) {
// if (err) {
// return res.status(500).send('Server error.')
// }
// // 从文件中读取到的数据一定是字符串
// // 所以这里一定要手动转成对象
// var students = JSON.parse(data).students
// res.render('index.html', {
// fruits: [
// '苹果',
// '香蕉',
// '橘子'
// ],
// students: students
// })
// })
// })
// app.get('/students/new', function (req, res) {
// })
// app.get('/students/new', function (req, res) {
// })
// app.get('/students/new', function (req, res) {
// })
// app.get('/students/new', function (req, res) {
// })
// app.get('/students/new', function (req, res) {
// })
// }
model层
/**
* student.js
* 数据操作文件模块
* 职责:操作文件中的数据,只处理数据,不关心业务
*
* 这里才是我们学习 Node 的精华部分:奥义之所在
* 封装异步 API
*/
var fs = require('fs')
var dbPath = './db.json'
/**
* 获取学生列表
* @param {Function} callback 回调函数
*/
exports.find = function (callback) {
fs.readFile(dbPath, 'utf8', function (err, data) {
if (err) {
return callback(err)
}
callback(null, JSON.parse(data).students)
})
}
/**
* 根据 id 获取学生信息对象
* @param {Number} id 学生 id
* @param {Function} callback 回调函数
*/
exports.findById = function (id, callback) {
fs.readFile(dbPath, 'utf8', function (err, data) {
if (err) {
return callback(err)
}
var students = JSON.parse(data).students
var ret = students.find(function (item) {
return item.id === parseInt(id)
})
callback(null, ret)
})
}
/**
* 添加保存学生
* @param {Object} student 学生对象
* @param {Function} callback 回调函数
*/
exports.save = function (student, callback) {
fs.readFile(dbPath, 'utf8', function (err, data) {
if (err) {
return callback(err)
}
var students = JSON.parse(data).students
// 添加 id ,唯一不重复
student.id = students[students.length - 1].id + 1
// 把用户传递的对象保存到数组中
students.push(student)
// 把对象数据转换为字符串
var fileData = JSON.stringify({
students: students
})
// 把字符串保存到文件中
fs.writeFile(dbPath, fileData, function (err) {
if (err) {
// 错误就是把错误对象传递给它
return callback(err)
}
// 成功就没错,所以错误对象是 null
callback(null)
})
})
}
/**
* 更新学生
*/
exports.updateById = function (student, callback) {
fs.readFile(dbPath, 'utf8', function (err, data) {
if (err) {
return callback(err)
}
var students = JSON.parse(data).students
// 注意:这里记得把 id 统一转换为数字类型
student.id = parseInt(student.id)
// 你要修改谁,就需要把谁找出来
// EcmaScript 6 中的一个数组方法:find
// 需要接收一个函数作为参数
// 当某个遍历项符合 item.id === student.id 条件的时候,find 会终止遍历,同时返回遍历项
var stu = students.find(function (item) {
return item.id === student.id
})
// 这种方式你就写死了,有 100 个难道就写 100 次吗?
// stu.name = student.name
// stu.age = student.age
// 遍历拷贝对象
for (var key in student) {
stu[key] = student[key]
}
// 把对象数据转换为字符串
var fileData = JSON.stringify({
students: students
})
// 把字符串保存到文件中
fs.writeFile(dbPath, fileData, function (err) {
if (err) {
// 错误就是把错误对象传递给它
return callback(err)
}
// 成功就没错,所以错误对象是 null
callback(null)
})
})
}
/**
* 删除学生
*/
exports.deleteById = function (id, callback) {
fs.readFile(dbPath, 'utf8', function (err, data) {
if (err) {
return callback(err)
}
var students = JSON.parse(data).students
// findIndex 方法专门用来根据条件查找元素的下标
var deleteId = students.findIndex(function (item) {
return item.id === parseInt(id)
})
// 根据下标从数组中删除对应的学生对象
students.splice(deleteId, 1)
// 把对象数据转换为字符串
var fileData = JSON.stringify({
students: students
})
// 把字符串保存到文件中
fs.writeFile(dbPath, fileData, function (err) {
if (err) {
// 错误就是把错误对象传递给它
return callback(err)
}
// 成功就没错,所以错误对象是 null
callback(null)
})
})
}
db.json
{"students":[{"id":4,"name":"张三三","gender":"0","age":"22","hobbies":"吃饭、睡觉、打豆豆"}]}
index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
<meta name="description" content="">
<meta name="author" content="">
<link rel="icon" href="../../favicon.ico">
<title>Dashboard Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<link href="/node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="/public/css/main.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Project name</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
<li><a href="#">Dashboard</a></li>
<li><a href="#">Settings</a></li>
<li><a href="#">Profile</a></li>
<li><a href="#">Help</a></li>
</ul>
<form class="navbar-form navbar-right">
<input type="text" class="form-control" placeholder="Search...">
</form>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="row">
<div class="col-sm-3 col-md-2 sidebar">
<ul class="nav nav-sidebar">
<li class="active"><a href="/">学生管理 <span class="sr-only">(current)</span></a></li>
<li><a href="#">Reports</a></li>
<li><a href="#">Analytics</a></li>
<li><a href="#">Export</a></li>
</ul>
<ul class="nav nav-sidebar">
<li><a href="">Nav item</a></li>
<li><a href="">Nav item again</a></li>
<li><a href="">One more nav</a></li>
<li><a href="">Another nav item</a></li>
<li><a href="">More navigation</a></li>
</ul>
<ul class="nav nav-sidebar">
<li><a href="">Nav item again</a></li>
<li><a href="">One more nav</a></li>
<li><a href="">Another nav item</a></li>
</ul>
</div>
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<h1 class="page-header">Dashboard</h1>
<div class="row placeholders">
{{ each fruits }}
<div class="col-xs-6 col-sm-3 placeholder">
<img src="https://img-blog.csdnimg.cn/2022010704425793220.gif" width="200" height="200" class="img-responsive" alt="Generic placeholder thumbnail">
<h4>{{ $value }}</h4>
<span class="text-muted">Something else</span>
</div>
{{ /each }}
</div>
<h2 class="sub-header">Section title</h2>
<a class="btn btn-success" href="/students/new">添加学生</a>
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>#</th>
<th>姓名</th>
<th>性别</th>
<th>年龄</th>
<th>爱好</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{{ each students }}
<tr>
<td>{{ $value.id }}</td>
<td>{{ $value.name }}</td>
<td>{{ $value.gender }}</td>
<td>{{ $value.age }}</td>
<td>{{ $value.hobbies }}</td>
<td>
<a href="/students/edit?id={{ $value.id }}">编辑</a>
<a href="/students/delete?id={{ $value.id }}">删除</a>
</td>
</tr>
{{ /each }}
</tbody>
</table>
</div>
</div>
</div>
</div>
</body>
</html>
new.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
<meta name="description" content="">
<meta name="author" content="">
<link rel="icon" href="../../favicon.ico">
<title>Dashboard Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<link href="/node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="/public/css/main.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Project name</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
<li><a href="#">Dashboard</a></li>
<li><a href="#">Settings</a></li>
<li><a href="#">Profile</a></li>
<li><a href="#">Help</a></li>
</ul>
<form class="navbar-form navbar-right">
<input type="text" class="form-control" placeholder="Search...">
</form>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="row">
<div class="col-sm-3 col-md-2 sidebar">
<ul class="nav nav-sidebar">
<li class="active"><a href="/students">学生管理 <span class="sr-only">(current)</span></a></li>
<li><a href="#">Reports</a></li>
<li><a href="#">Analytics</a></li>
<li><a href="#">Export</a></li>
</ul>
<ul class="nav nav-sidebar">
<li><a href="">Nav item</a></li>
<li><a href="">Nav item again</a></li>
<li><a href="">One more nav</a></li>
<li><a href="">Another nav item</a></li>
<li><a href="">More navigation</a></li>
</ul>
<ul class="nav nav-sidebar">
<li><a href="">Nav item again</a></li>
<li><a href="">One more nav</a></li>
<li><a href="">Another nav item</a></li>
</ul>
</div>
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<h2 class="sub-header">添加学生</h2>
<form action="/students/new" method="post">
<div class="form-group">
<label for="">姓名</label>
<input type="text" class="form-control" id="" name="name" required minlength="2" maxlength="10">
</div>
<div class="form-group">
<label for="">性别</label>
<div>
<label class="radio-inline">
<input type="radio" name="gender" id="" value="0" checked> 男
</label>
<label class="radio-inline">
<input type="radio" name="gender" id="" value="1"> 女
</label>
</div>
</div>
<div class="form-group">
<label for="">年龄</label>
<input class="form-control" type="number" id="" name="age" required min="1" max="150">
</div>
<div class="form-group">
<label for="">爱好</label>
<input class="form-control" type="text" id="" name="hobbies">
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
</div>
</div>
</div>
</body>
</html>
edit.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
<meta name="description" content="">
<meta name="author" content="">
<link rel="icon" href="../../favicon.ico">
<title>Dashboard Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<link href="/node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="/public/css/main.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Project name</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
<li><a href="#">Dashboard</a></li>
<li><a href="#">Settings</a></li>
<li><a href="#">Profile</a></li>
<li><a href="#">Help</a></li>
</ul>
<form class="navbar-form navbar-right">
<input type="text" class="form-control" placeholder="Search...">
</form>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="row">
<div class="col-sm-3 col-md-2 sidebar">
<ul class="nav nav-sidebar">
<li class="active"><a href="/students">学生管理 <span class="sr-only">(current)</span></a></li>
<li><a href="#">Reports</a></li>
<li><a href="#">Analytics</a></li>
<li><a href="#">Export</a></li>
</ul>
<ul class="nav nav-sidebar">
<li><a href="">Nav item</a></li>
<li><a href="">Nav item again</a></li>
<li><a href="">One more nav</a></li>
<li><a href="">Another nav item</a></li>
<li><a href="">More navigation</a></li>
</ul>
<ul class="nav nav-sidebar">
<li><a href="">Nav item again</a></li>
<li><a href="">One more nav</a></li>
<li><a href="">Another nav item</a></li>
</ul>
</div>
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<h2 class="sub-header">添加学生</h2>
<form action="/students/edit" method="post">
<!--
用来放一些不希望被用户看见,但是需要被提交到服务端的数据
-->
<input type="hidden" name="id" value="{{ student.id }}">
<div class="form-group">
<label for="">姓名</label>
<input type="text" class="form-control" id="" name="name" required minlength="2" maxlength="10" value="{{ student.name }}">
</div>
<div class="form-group">
<label for="">性别</label>
<div>
<label class="radio-inline">
<input type="radio" name="gender" id="" value="0" checked> 男
</label>
<label class="radio-inline">
<input type="radio" name="gender" id="" value="1"> 女
</label>
</div>
</div>
<div class="form-group">
<label for="">年龄</label>
<input class="form-control" type="number" id="" name="age" value="{{ student.age }}" required min="1" max="150">
</div>
<div class="form-group">
<label for="">爱好</label>
<input class="form-control" type="text" id="" name="hobbies" value="{{ student.hobbies }}">
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
</div>
</div>
</div>
</body>
</html>