一、起步
-
初始化
npm init -y
生成package.json文件 -
模板处理
cnpm install express --save cnpm install bootstrap --save npm install --save art-template npm install --save express-art-template //两个一起安装 npm i --save art-template express-art-template
-
文件目录如下:
db.json文件:
{
"students": [{
"id": 1,
"name": "张三三",
"gender": "0",
"age": "22",
"hobbies": "吃饭、睡觉、打豆豆"
},
{
"id": 2,
"name": "张三",
"gender": "1",
"age": "22",
"hobbies": "吃饭、看书、打豆豆"
},
{
"id": 3,
"name": "小娜",
"gender": "0",
"age": "22",
"hobbies": "吃饭、睡觉、打豆豆"
},
{
"id": 4,
"name": "大头儿子",
"gender": "0",
"age": "22",
"hobbies": "吃饭、敲代码、打豆豆"
},
{
"id": 5,
"name": "天猫",
"gender": "0",
"age": "27",
"hobbies": "吃饭、睡觉、玩游戏"
},
{
"id": 6,
"name": "Rick",
"gender": "0",
"age": "25",
"hobbies": "打篮球、睡觉、打豆豆"
}
]
}
二、路由设计
三、提取路由模块
router.js:
/*
router.js 路由模块
职责:
处理路由
根据不同的请求方法+请求路径设置具体的请求处理函数
模块值则要单一,不要乱写
*/
var fs = require('fs')
// express提供了一种更好的方式,专门用来包装路由
var express = require('express')
// 1.创建一个路由容器
var router = express.Router()
// 2.把路由都挂载到router路由容器上
router.get('/students', function(req, res) {
// res.send('hello world!')
// readFile() 的第二个参数是可选的,传入utf8 就是告诉它把读取道德文件直接按照utf8进行编码转成我们能认识的字符
// 除了这样来转换之外,也可通过data.toString()的方式
fs.readFile('./db.json', 'utf8', function(err, data) {
if (err) {
return res.status(500).send('Server error!')
}
console.log(data) // 这里的data 是 string 类型的
res.render('index.html', {
fruits: [
'苹果',
'香蕉',
'橘子',
'桃子'
],
students: JSON.parse(data).students //将string类型的data通过JSON.parse(data)转成对象类型
})
})
})
router.get('/students/new', function(req, res) {
res.send('new new new')
})
// 3.把router导出
module.exports = router
// 这样也不方便
/* module.exports = function(app) {
app.get('/students', function(req, res) {
// res.send('hello world!')
// readFile() 的第二个参数是可选的,传入utf8 就是告诉它把读取道德文件直接按照utf8进行编码转成我们能认识的字符
// 除了这样来转换之外,也可通过data.toString()的方式
fs.readFile('./db.json', 'utf8', function(err, data) {
if (err) {
return res.status(500).send('Server error!')
}
console.log(data) // 这里的data 是 string 类型的
res.render('index.html', {
fruits: [
'苹果',
'香蕉',
'橘子',
'桃子'
],
students: JSON.parse(data).students //将string类型的data通过JSON.parse(data)转成对象类型
})
})
})
app.get('/students/new', function(req, res) {
})
/* app.get('/students', function(req, res) {
})
app.get('/students', function(req, res) {
})
app.get('/students', function(req, res) {
})
app.get('/students', function(req, res) {
})
app.get('/students', function(req, res) {
})
}
*/
app.js:
/*
app.js入口模块
职责:
创建服务
做一些服务相关的配置
模板引擎
body-parse 用来解析表单post请求体
提供静态资源服务
挂载路由
监听端口启动服务
*/
var express = require('express')
var router =require('./router')
var app = express()
// 开放node_modules 和 public文件夹
app.use('/node_modules/', express.static('./node_modules/'))
app.use('/public/', express.static('./public/'))
// 配置模板引擎
app.engine('html', require('express-art-template'))
/* app.get('/', function(req, res) {
}) */
// 把路由容器挂载到app服务中
app.use(router)
app.listen(3000, function() {
console.log("Server running... 3000")
})
四、页面准备(来自bootstrap官网提供的模板)
模板:view-source:https://v3.bootcss.com/examples/dashboard/
<!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"> -->
<link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="../public/css/dashboard.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="#">Overview <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="data:image/gif;base64,R0lGODlhAQABAIAAAHd3dwAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw=="
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 href="#" class="btn btn-success">添加学生</a>
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<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>
</tr>
{
{ /each }}
</tbody>
</table>
</div>
</div>
</div>
</div>
</body>
</html>
增加添加学生页面new.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"> -->
<link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="../public/css/dashboard.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="#">Overview <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="/student/new" method="post">
<div class="form-group">
<label for="name">姓名</label>
<input type="text" class="form-control" id="name" name="name">
<small id="emailHelp" class="form-text text-muted">We'll never share your email with anyone
else.</small>
</div>
<div class="form-group">
<label for="sex">性别</label>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" id="inlineRadio1"
value="0">
<label class="form-check-label" for="inlineRadio1">男</label>
<input class="form-check-input" type="radio" name="gender" id="inlineRadio2"
value="1">
<label class="form-check-label" for="inlineRadio2">女</label>
</div>
</div>
<div class="form-group">
<label for="age">年龄</label>
<input type="text" class="form-control" id="age" name="age">
</div>
<div class="form-group">
<label for="hobbies">爱好</label>
<input type="text" class="form-control" id="hobbies" name="hobbies">
</div>
<button type="submit" class="btn btn-primary">提交</button>
</form>
</div>
</div>
</div>
</body>
</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"> -->
<link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="../public/css/dashboard.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="#">Overview <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="name">姓名</label>
<input value="{
{ student.name }}" type="text" class="form-control" id="name" name="name">
</div>
<div class="form-group">
<label for="sex">性别</label>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" id="inlineRadio1"
value="0">
<label class="form-check-label" for="inlineRadio1">男</label>
<input class="form-check-input" type="radio" name="gender" id="inlineRadio2"
value="1">
<label class="form-check-label" for="inlineRadio2">女</label>
</div>
</div>
<div class="form-group">
<label for="age">年龄</label>
<input value="{
{ student.age }}" type="text" class="form-control" id="age" name="age">
</div>
<div class="form-group">
<label for="hobbies">爱好</label>
<input value="{
{ student.hobbies }}" type="text" class="form-control" id="hobbies" name="hobbies">
</div>
<button type="submit" class="btn btn-primary">提交</button>
</form>
</div>
</div>
</div>
</body>
</html>
编写router.js文件
因为需要拿到post请求体中的数据,所以需要安装第三方插件来帮助我们获取请求体数据
在该项目文件中,cmd命令行输入命令cnpm install --save body-parser
由于接下来的一系列业务操作都需要处理文件数据,所以我们需要封装Student.js
student.js:
/*
student.js
数据操作文件模块
职责: 操作文件中的数据,只处理数据,不关心业务
*/
var fs = require('fs')
var dbPath = './db.json'
/* 获取所有学生列表 */
/*
callback中的参数
第一个参数是 err
成功是 null
错误是 错误对象
第二个参数是 结果
成功是数组
错误是 undefined
*/
exports.find = function(callback) {
fs.readFile(dbPath, 'utf8', function(err, data) {
if (err) {
return callback(err)
}
callback(null, JSON.parse(data).students)
})
}
/* 添加保存学生 */
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) {
// 错误就 把错误对象传递给callback
return callback(err)
}
// 成功就是没有错误, 所以错误对象是null
callback(null)
})
})
}
/* 更新学生 */
exports.updateById = function(student, callback) {
fs.readFile(dbPath, 'utf8', function(err, data) {
if (err) {
return callback(err)
}
// 注意:这里记得把 id 统一转换为数字类型
var students = JSON.parse(data).students
student.id = parseInt(student.id)
// 你要修改谁,就需要把谁找出来
// EcmaScript 6 中的一个数组方法:find
// 需要接收一个函数作为参数
// 当某个遍历项符合 item.id === student.id 条件的时候,find 会终止遍历,同时返回遍历项
var stu = students.find(function(item) {
return item.id === student.id
})
// 遍历拷贝对象
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)
})
})
}
/* 调用格式 */
/* updateById({
id: 1,
name: 'xx',
age: 15
},function(err) {
}) */
/**
* 根据 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)
})
}
/* findById()调用方式 */
/* Student.findById(id, function(err, student) {
}) */
/* 删除学生 */
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)
})
})
}
router.js:
/*
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) {
// res.send('hello world!')
// readFile() 的第二个参数是可选的,传入utf8 就是告诉它把读取到的文件直接按照utf8进行编码转成我们能认识的字符
// 除了这样来转换之外,也可通过data.toString()的方式
/* fs.readFile('./db.json', 'utf8', function(err, data) {
if (err) {
return res.status(500).send('Server error!')
}
console.log(data) // 这里的data 是 string 类型的
res.render('index.html', {
fruits: [
'苹果',
'香蕉',
'橘子',
'桃子'
],
students: JSON.parse(data).students //将string类型的data通过JSON.parse(data)转成对象类型
})
}) */
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文件中用以持久化保存
// 先读取文件数据,转成对象
// 然后往对象中push数据
// 然后把对象转为字符串
// 然后把字符串再次写入文件
// 3.发送响应
console.log(req.body)
var student = req.body
Student.save(student, 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把学生信息查出来
// 使用模板引擎渲染页面
console.log(req.query.id)
Student.findById(parseInt(req.query.id), function(err, student) {
if (err) {
return res.status(500).send('Server error!')
}
console.log(student)
res.render('edit.html', {
student: student
})
})
})
router.post('/students/edit', function(req, res) {
// 1.获取表单数据 req.body
// 2.更新 Student.updateById()
// 2.发送响应
console.log(req.body)
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.根据操作结果发送响应数据
console.log(req.query.id)
Student.deleteById(req.query.id, function(err) {
if(err) {
return res.status(500).send('Server error!')
}
console.log('删除成功')
res.redirect('/students')
})
})
// 3.把router导出
module.exports = router
// 这样也不方便
/* module.exports = function(app) {
app.get('/students', function(req, res) {
// res.send('hello world!')
// readFile() 的第二个参数是可选的,传入utf8 就是告诉它把读取道德文件直接按照utf8进行编码转成我们能认识的字符
// 除了这样来转换之外,也可通过data.toString()的方式
fs.readFile('./db.json', 'utf8', function(err, data) {
if (err) {
return res.status(500).send('Server error!')
}
console.log(data) // 这里的data 是 string 类型的
res.render('index.html', {
fruits: [
'苹果',
'香蕉',
'橘子',
'桃子'
],
students: JSON.parse(data).students //将string类型的data通过JSON.parse(data)转成对象类型
})
})
})
app.get('/students/new', function(req, res) {
})
/* app.get('/students', function(req, res) {
})
app.get('/students', function(req, res) {
})
app.get('/students', function(req, res) {
})
app.get('/students', function(req, res) {
})
app.get('/students', function(req, res) {
})
}
*/
总结
步骤
-
处理模板
-
配置静态开放资源
-
配置模板引擎
-
简单的路由,/studens渲染静态页出来
-
路由设计
-
提取路由模块
-
由于接下来的一系列业务操作都需要处理文件数据,所以我们需要封装Student.js’
-
先写好student.js文件结构
- 查询所有学生列别哦的API
- findById
- save
- updateById
- deleteById
-
实现具体功能
- 通过路由收到请求
- 接受请求中的参数(get,post)
- req.query
- req.body
- 调用数据操作API处理数据
- 根据操作结果给客户端发送请求
-
业务功能顺序
- 列表
- 添加
- 编辑
- 删除