初始化项目与安装插件
本次设计的CRUD项目开发选用 express 框架;模板引擎选择 art-template;网页 css 样式选择 bootstrap; 获取表单 POST 请求体的API:body-parser。
路由设计
请求方法 | 请求路径 | get参数 | post参数 | 作用 |
---|---|---|---|---|
GET | /student | 渲染首页 | ||
GET | /student/new | 渲染添加学生页面 | ||
POST | /student/new | name、age、gender、major | 处理添加学生请求 | |
GET | /student/edit | id | 渲染编辑页面 | |
POST | /students/edit | id、name、age、gender、major | 处理编辑请求 | |
GET | /student/delete | id | 处理删除请求 |
提取路由模块
路由模块
- 职责:
- 处理路由
- 根据不同的请求方法和请求路径设置具体的请求处理函数
- 模块职责要单一,不要乱写
- 划分模块的目的就是为了增强项目代码的可维护性,提升开发效率
router.js
var express = require('express')
// 1. 创建一个路由容器
var router = express.router()
// 2. 把路由都挂载到 router 路由容器中
router.get('/students', function (req, res) {
})
router.get('/students/new', function (req, res) {
})
router.post('/students/new', function (req, res) {
})
router.get('/student/edit', function (req, res) {
})
router.post('/student/edit', function (req, res) {
})
router.get('/students/delete', function (req, res) {
})
// 3. 把 router 导出
module.exports = router
app.js
var router = require('./router')
// 挂载路由
app.use(router)
设计 html 页面
在 bootstrap 官网找个css模板仿照着写:
bootstrap官网dashboard模板
bootstrap表单
从文件中读取数据
为了将数据持久化,我们设计将数据存到 json 文件中,通过读取 json 文件得到学生对象,进而渲染整个学生列表页面。简单的代码示例如下:
db.json
{
"students":[
{"id":1, "name": "张三", "gender": 0, "age": 20, "major": "英语"},
{"id":2, "name": "李四", "gender": 0, "age": 20, "major": "英语"},
{"id":3, "name": "小红", "gender": 1, "age": 20, "major": "数学"},
{"id":4, "name": "哈哈", "gender": 1, "age": 20, "major": "英语"},
{"id":5, "name": "嘻嘻", "gender": 0, "age": 20, "major": "数学"},
{"id":6, "name": "小明", "gender": 0, "age": 20, "major": "英语"}
]
}
app.js
var express = require('express')
var fs = require('fs')
var app = express()
app.use('/node_modules', express.static('node_modules'))
app.use('/public', express.static('public'))
app.engine('html',require('express-art-template'))
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('new.html', {
students: students
})
})
})
app.listen(3000, function () {
console.log('running 3000...')
})
设计操作数据的 API 文件模块
- 异步封装 API
由于我们的业务操作涉及到增删改查,需要处理文件数据,因此考虑把这部分操作封装成通用方法。而我们操作文件数据的时候调用的方法都是异步的方法,因此封装 API 的时候要考虑异步封装。在这里举个简单的异步封装例子:
// 封装API,函数功能为延时1s,函数参数为回调函数
function delay(callback) {
setTimeout(function () {
var data = 'hello'
callback(data)
}, 1000)
}
// 调用API,传入的参数(回调函数)是用户自定义的函数,比如这里打印log
delay(function (data) {
console.log(data)
})
代码
思路大体是这样,详细内容见Code:
app.js
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'))
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
app.use(router)
app.listen(3000, function () {
console.log('running 3000...')
})
router.js
var fs = require('fs')
var crud = require('./crud')
var express = require('express')
var router = express.Router()
router.get('/students', function (req, res) {
crud.find(function (err, students) {
if (err) {
return res.status(500).send('Server error.')
}
res.render('index.html', {
students: students
})
})
})
router.get('/students/new', function (req, res) {
res.render('new.html')
})
router.post('/students/new', function (req, res) {
crud.save(req.body, function (err) {
if(err) {
return res.status(500).send('Server error.')
}
res.redirect('/students')
})
})
router.get('/students/edit', function (req, res) {
crud.findById(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) {
crud.updateById(req.body, function (err) {
if(err) {
return res.status(500).send('Server error.')
}
res.redirect('/students')
})
})
router.get('/students/delete', function (req, res) {
crud.deleteById(req.query.id, function (err) {
if(err) {
return res.status(500).send('Server error.')
}
res.redirect('/students')
})
})
module.exports = router
crud.js
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)
})
}
/**
* 根据学号获取学生信息对象
* @param {Number} 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
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)
}
callback(null)
})
})
}
/**
* 更新学生
* @param {Object} student 学生对象
* @param {Function} callback 回调函数
*/
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
})
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)
}
callback(null)
})
})
}
/**
* 删除学生
* @param {Number} id 学生学号
* @param {Function} callback 回调函数
*/
exports.deleteById = function (id, callback) {
fs.readFile(dbPath, 'utf8', function (err, data) {
if(err) {
return callback(err)
}
var students = JSON.parse(data).students
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)
}
callback(null)
})
})
}
index.html
Dashboard Template for Bootstrap
new.html
Dashboard Template for Bootstrap
添加学生
edit.html
Dashboard Template for Bootstrap
编辑学生
总结
- 处理模板
- 配置开放静态资源
- 配置模板引擎
- 简单路由: /students 渲染静态页面
- 路由设计
- 提取路由模块
- 由于接下来一些业务操作都需要处理文件数据,所以我们需要异步封装 API crud.js
- crud.js 文件结构
- 查询所有学生列表数据: find
- 通过学生id查询学生列表数据:findById
- 保存学生列表数据:save
- 通过学生id更新学生列表数据:updateById
- 通过学生id删除学生列表数据:deleteById
- 实现具体功能
- 通过路由收到请求
- 接收请求中的数据 (get、post)
- req.query
- req.body
- 调用数据操作 API 处理数据
- 根据操作结果给客户端发送响应
- 渲染页面
- 重定向
- 业务功能顺序:
- 列表
- 添加
- 编辑
- 删除