初始化项目与安装插件
本次设计的CRUD项目开发选用 express 框架;模板引擎选择 art-template;网页 css 样式选择 bootstrap; 获取表单 POST 请求体的API:body-parser。
1.初始化package.json 2.安装express 3.安装art-template 4.安装bootstrap 5.安装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
<!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/cssdashboard.css" rel="stylesheet">
</head>
<body>
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<h2 class="sub-header">学生名单</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>
</tr>
</thead>
<tbody>
{{ each students }}
<tr>
<td>{{ $value.id }}</td>
<td>{{ $value.name }}</td>
<td>{{ $value.gender }}</td>
<td>{{ $value.age }}</td>
<td>{{ $value.major }}</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>
</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/cssdashboard.css" rel="stylesheet">
</head>
<body>
<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 class="form-control" type="text" id="" name="name" required minlength="2" maxlength="10">
</div>
<div class="form-group">
<label for="">性别</label>
<div class="radio">
<label>
<input type="radio" name="gender" id="optionsRadios1" value="0" checked> 男
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="gender" id="optionsRadios2" 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="major">
</div>
<button type="submit" class="btn btn-success">Submit</button>
</form>
</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/cssdashboard.css" rel="stylesheet">
</head>
<body>
<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 class="form-control" type="text" id="" name="name" required minlength="2" maxlength="10" value="{{ student.name }}">
</div>
<div class="form-group">
<label for="">性别</label>
<div class="radio">
<label>
<input type="radio" name="gender" id="optionsRadios1" value="0" checked="true"> 男
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="gender" id="optionsRadios2" 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" value="{{ student.age }}">
</div>
<div class="form-group">
<label for="">专业</label>
<input class="form-control" type="text" id="" name="major" value="{{ student.major }}">
</div>
<button type="submit" class="btn btn-success">Submit</button>
</form>
</div>
</body>
</html>
总结
- 处理模板
- 配置开放静态资源
- 配置模板引擎
- 简单路由: /students 渲染静态页面
- 路由设计
- 提取路由模块
- 由于接下来一些业务操作都需要处理文件数据,所以我们需要异步封装 API crud.js
- crud.js 文件结构
- 查询所有学生列表数据: find
- 通过学生id查询学生列表数据:findById
- 保存学生列表数据:save
- 通过学生id更新学生列表数据:updateById
- 通过学生id删除学生列表数据:deleteById
- 实现具体功能
- 通过路由收到请求
- 接收请求中的数据 (get、post)
- req.query
- req.body
- 调用数据操作 API 处理数据
- 根据操作结果给客户端发送响应
- 渲染页面
- 重定向
- 业务功能顺序:
- 列表
- 添加
- 编辑
- 删除
网友评论