一、起步
-
初始化
npm init -y
生成package.json文件 -
模板处理
cnpm install express --save cnpm install bootstrap --savenpm 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 anyoneelse.</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 + 1students.push(student)// 再将对象转成字符串写入文件中var fileData = JSON.stringify({students: students})fs.writeFile(dbPath, fileData, function(err) {if (err) {// 错误就 把错误对象传递给callbackreturn callback(err)}// 成功就是没有错误, 所以错误对象是nullcallback(null)})})
}/* 更新学生 */
exports.updateById = function(student, callback) {fs.readFile(dbPath, 'utf8', function(err, data) {if (err) {return callback(err)}// 注意:这里记得把 id 统一转换为数字类型var students = JSON.parse(data).studentsstudent.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)}// 成功就没错,所以错误对象是 nullcallback(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).studentsvar 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)}// 成功就没错,所以错误对象是 nullcallback(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.bodyStudent.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处理数据
- 根据操作结果给客户端发送请求
-
业务功能顺序
- 列表
- 添加
- 编辑
- 删除