大家好,我是Counterrr,生命不息学习不止。
Talk is cheap, Show me the bug.
本文目录
- 项目效果预览;
- 了解
crud
,了解路由设计模式; - 实现学生系统的增删改查功能;
1. 项目效果预览:
2、了解crud
,了解路由设计模式:
crud
是什么呢?其实写过后端的话对这个就不陌生,其实crud
就是英文增删改查的首字母缩写。create read update delete
。
我们需要开发一套系统就要先设计好路由。
路由设计:
请求方法 | 请求路径 | get参数 | post参数 | 描述 |
---|---|---|---|---|
GET | / | 渲染首页 | ||
GET | /students/create | 渲染添加学生页面 | ||
POST | /students/create | name(string)、age(number)、sex(string) | 处理添加学生请求 | |
GET | /students/edit | id(number) | 渲染编辑学生页面 | |
POST | /students/edit | name(string)、age(number)、sex(string) | 修改编辑学生页面 | |
GET | /students/delete | id(number) | 删除学生信息 |
好的我们设计好路由后,后面的开发将会非常的清晰。
3、实现学生系统的增删改查功能:
在桌面新建文件夹express-crud
,目录结构如下:
express-crud
├── database
│ └── db.json
├── public
│ └── dashbord.css
├── views
│ ├── index.html
│ ├── studentCreate.html
│ └── studentEdit.html
├── app.js
├── router.js
├── Student.js
database
文件夹下的db.json
当做是我们的数据存放位置(暂时充当数据库的角色。)public
文件夹是我们存放公共资源的位置,其中dashbord.css
为页面部分样式。views
文件夹我们存放服务端需要渲染的html
模板,其中index.html
为首页展示学生信息的列表。studentCreate.html
为新增一个学生的页面,studentEdit.html
为编辑一个学生的页面。app.js
为入口文件。router.js
为路由处理文件。Student.js
为专门处理数据的增删改查业务的文件。
在真实开发中,都是以模块化开发的。
好的构建完目录后,我们打开vscode
命令行,输入命令 npm init -y
快速生成项目说明书,接着来安装下开发依赖包,输入命令:
npm install express art-template express-art-template --save
安装完后,我们在文件中键入代码:
app.js
代码如下:
const express = require('express')
const router = require('./router')
const bodyParser = require('body-parser')
// 创建serve服务
const app = express()
app.use(bodyParser.urlencoded({extended:false}))
app.use(bodyParser.json())
// 开放静态资源
app.use('/public/', express.static('./public/'))
// 服务端模板引擎
app.engine('html', require('express-art-template'))
app.use(router)
// 监听端口
app.listen(3000, () => {
console.log('serve is running on 3000 port')
})
- 代码解读:
1、其实这里面的代码我们之前都写过了,熟悉不过,唯一新增的一行之前为接触的代码app.use(router)
,这行代码其实就是去使用了router.js
这个文件,其实就是使用express
为我们封装好的路由容器,我们在这里面去写各个路由的状态。
router.js
代码如下:
const express = require('express')
const Student = require('./Student.js')
const router = express.Router()
router.get('/', (req, res) => {
// 获取学生信息列表
Student.read((err, array) => {
if (err) {
res
.sendStatus(500)
.send('SERVE ERROR')
}
else {
// console.log(array)
res.render('index.html', {array})
}
})
})
// 渲染学生添加页面
router.get('/students/create', (req, res) => {
res.render('studentCreate.html')
})
// 添加学生信息
router.post('/students/create', (req,res) => {
// 添加保存学生信息
Student.save(req.body, (err) => {
if (err) {
res
.sendStatus(500)
.send('SERVE ERROR')
}
else {
res.redirect('/')
}
})
})
// 渲染编辑学生页面
router.get('/students/edit', (req, res) => {
// 根据学生id进行查找
Student.editById(parseInt(req.query.id), (err, student) => {
if (err) {
res
.sendStatus(500)
.send('SERVE ERROR')
}
else {
res.render('studentEdit.html', {
student
})
}
})
})
// 提交编辑学生页面
router.post('/students/edit', (req, res) => {
// console.log(req.body)
Student.updateById(req.body, (err) => {
if (err) {
res
.sendStatus(500)
.send('SERVE ERROR')
}
else {
res.redirect('/')
}
})
})
// 删除学生信息
router.get('/students/delete', (req, res) => {
Student.deleteById(req.query.id, (err) => {
if (err) {
res.sendStatus(500).send('SERVER ERROR')
}
else {
res.redirect('/')
}
})
})
module.exports = router
这块的话代码就多了,主要就是根据我们路由的设计去完成相对应的增删改查的功能。
- 代码解读:
1、我们这里引用了express
,并且调用了它上面的Router( )
方法,那这个方法就是express
为我们封装好的路由容器,那我们在这上面去进行GET、POST。。。
请求方法更好。
2、引入了Student.js
那涉及到读取文件然后学生数据的增删改查的逻辑都放在这个js
文件里去写,那路由的文件,我们专注于写路由,这样条理更清晰。代码也更好维护。
3、那剩下的语法我们之前也熟悉了,res.render( )
去渲染模板并且返回给客户端。res.redirect('/')
为临时重定向到首页。res.sendStatus(500).send('SERVER ERROR')
发生错误给客户端返回状态码,并且提示错误。
4、那剩下的Student.read( ) 、Student.save( ) 、 Student.editById( )、Student.updateById( )、 Student.deleteById( )
我们在Student.js
这个文件中去解读。
Student.js
的代码如下:
const fs = require('fs')
const path = './database/db.json'
//查询所有学生信息
exports.read = (callback) => {
fs.readFile(path, 'utf8', (err, data) => {
if (err) {
return callback(err)
}
else {
return callback(null, JSON.parse(data).students)
}
})
}
// 保存学生信息
exports.save = (stuObj, callback) => {
fs.readFile(path, 'utf8', (err, data) => {
if (err) {
return callback(err)
}
else {
let students = JSON.parse(data).students
stuObj.id = parseInt(students[students.length - 1].id) + 1
stuObj.sex = parseInt(stuObj.sex)
students.push(stuObj)
newData = {
students: students
}
fs.writeFile(path, JSON.stringify(newData), (err) => {
if (err) {
return callback(err)
}
else {
return callback(null)
}
})
}
})
}
// 更改学生信息
exports.updateById = (stuObj, callback) => {
fs.readFile(path, 'utf8', (err, data) => {
if (err) {
return callback(err)
}
else {
let students = JSON.parse(data).students
let newStuObj = students.find((item) => {
return item.id == stuObj.id
})
for(let i in stuObj) {
newStuObj[i] = stuObj[i]
}
students = {
"students": students
}
fs.writeFile(path, JSON.stringify(students), (err) => {
if (err) {
return callback(err)
}
else {
return callback(null)
}
})
}
})
}
// 编辑学生信息界面
exports.editById = (id, callback) => {
fs.readFile(path, 'utf8', (err, data) => {
if (err) {
return callback(err)
}
else {
let students = JSON.parse(data).students
let result = students.find((i) => i.id == id)
callback(null, result)
}
})
}
// 删除学生信息
exports.deleteById = (id, callback) => {
fs.readFile(path, 'utf8', (err, data) => {
if (err) {
return callback(err)
}
else {
let students = JSON.parse(data).students
let index = students.findIndex((item) => {
return item.id == id
})
students.splice(index, 1)
students = {
students
}
fs.writeFile(path, JSON.stringify(students), (err) => {
callback(err)
})
}
})
}
- 代码解读:
1、可以看到我们这边主要引入了读写文件操作的核心包fs
,这个在之前的小项目中也操作了。那可以看到我们这边每个方法都接收一个函数作为参数,回调函数,因为对./database/db.json
这个文件的增删改查都是异步的,所以这边得用回调函数处理的方式,在es6
中有promise
解决的方法和async await
的方法,但是都是基于回调函数,所以我们有必要自己了解下这个,等到后面就可以用es6
的方式去解决,好的剩下的都是基于数组的操作,这个应该不难,等到后期接触了数据,那更简单了,不用我们再去操作文件了,直接一个api
的事情,这边简单了解底层实现的原理。
db.json
代码如下:
{
"students": [
{
"id": "1",
"name": "张三",
"age": "22",
"sex": "1"
},
{
"id": 2,
"name": "小花",
"age": 21,
"sex": 1
},
{
"name": "小芳",
"age": "29",
"sex": 0,
"id": 3
},
{
"name": "王五",
"age": "18",
"sex": 0,
"id": 4
}
]
}
简单的写死几个数据。
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标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
<title>Dashboard Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<link href="/public/dashbord.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="#">学生管理系统</a>
</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>
</ul>
</div>
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<h1 class="page-header">控制台</h1>
<a href="/students/create" class="btn btn-primary" role="button">添加学生信息</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 array}}
<tr>
<td>{{$value.id}}</td>
<td>{{$value.name}}</td>
<td>{{$value.age}}</td>
<td>{{$value.sex ? '女' : '男'}}</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>
主要用了bootstrap
的样式和模板。
studentCreate.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标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
<title>Dashboard Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<link href="/public/dashbord.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="#">学生管理系统</a>
</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>
</ul>
</div>
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<h1 class="page-header">添加学生信息</h1>
<form action="/students/create" method="POST">
<div class="form-group">
<label for="exampleInputPassword1">姓名</label>
<input type="text" name="name" class="form-control" placeholder="请输入姓名">
</div>
<div class="form-group">
<label for="exampleInputPassword1">年龄</label>
<input type="number" name="age" class="form-control" placeholder="请输入年龄">
</div>
<div class="radio">
<label>
<input type="radio" name="sex" value="0">
男
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="sex" value="1">
女
</label>
</div>
<button type="submit" class="btn btn-default">确定</button>
</form>
</div>
</div>
</div>
</body>
</html>
studentEdit.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标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
<title>Dashboard Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<link href="/public/dashbord.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="#">学生管理系统</a>
</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>
</ul>
</div>
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<h1 class="page-header">编辑学生信息</h1>
<form action="/students/edit" method="POST">
<input type="hidden" value="{{student.id}}" name="id">
<div class="form-group">
<label for="exampleInputPassword1">姓名</label>
<input required type="text" name="name" class="form-control" placeholder="请输入姓名" value="{{student.name}}">
</div>
<div class="form-group">
<label for="exampleInputPassword1">年龄</label>
<input required type="number" name="age" class="form-control" placeholder="请输入年龄" value="{{student.age}}">
</div>
<div class="radio">
<label>
<input type="radio" name="sex" {{ student.sex ? '' : 'checked' }} value="0">
男
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="sex" {{ student.sex ? 'checked' : '' }} value="1">
女
</label>
</div>
<button type="submit" class="btn btn-default">确定</button>
</form>
</div>
</div>
</div>
</body>
</html>
dashbord.css
代码如下:
/*
* Base structure
*/
/* Move down content because we have a fixed navbar that is 50px tall */
body {
padding-top: 50px;
}
/*
* Global add-ons
*/
.sub-header {
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
/*
* Top navigation
* Hide default border to remove 1px line.
*/
.navbar-fixed-top {
border: 0;
}
/*
* Sidebar
*/
/* Hide for mobile, show later */
.sidebar {
display: none;
}
@media (min-width: 768px) {
.sidebar {
position: fixed;
top: 51px;
bottom: 0;
left: 0;
z-index: 1000;
display: block;
padding: 20px;
overflow-x: hidden;
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
background-color: #f5f5f5;
border-right: 1px solid #eee;
}
}
/* Sidebar navigation */
.nav-sidebar {
margin-right: -21px; /* 20px padding + 1px border */
margin-bottom: 20px;
margin-left: -20px;
}
.nav-sidebar > li > a {
padding-right: 20px;
padding-left: 20px;
}
.nav-sidebar > .active > a,
.nav-sidebar > .active > a:hover,
.nav-sidebar > .active > a:focus {
color: #fff;
background-color: #428bca;
}
/*
* Main content
*/
.main {
padding: 20px;
}
@media (min-width: 768px) {
.main {
padding-right: 40px;
padding-left: 40px;
}
}
.main .page-header {
margin-top: 0;
}
/*
* Placeholder dashboard ideas
*/
.placeholders {
margin-bottom: 30px;
text-align: center;
}
.placeholders h4 {
margin-bottom: 0;
}
.placeholder {
margin-bottom: 20px;
}
.placeholder img {
display: inline-block;
border-radius: 50%;
}
转载:https://blog.csdn.net/weixin_44103733/article/details/105949334
查看评论