目标:
完成动态交互留言板, 做一套留言版增删改查
- 搭建服务器基本结构
- 首页动态渲染
取数据 + 模板- 获取数据库的数据
- 配合模板引擎进渲染
- 把渲染好页面返回给浏览器
- 删除留言
- 前端点击删除按钮,传递给给后台
- 接收前端传递id, 根据id去删除对应的数据
- 先把data.json中数据读取出来,转出js数组
- 从数组中删除
- 将数组转回json字符串,写回data.json
- 页面需要重新渲染
- 添加留言
- 用户点击添加按钮,把表单数据提交给后台
- 后台获取前端提供表单数据, 添加到数据库中
- 先把data.json中数据读取出来,转出js数组
- 往数组中添加
- 将数组转回json字符串,写回data.json
- 添加完成跳转到首页, 看到添加后结果
目录结构
- assets
- css
- bootstrap.css
- fonts
- img
- js
- css
- data
- data.json
- pages
- index.html
- add.html
- index.js
html
<-- index.html -->
<style>
body {
background: url('../assets/img/bg1.png') no-repeat top left;
background-color: rgba(238, 237, 235, 1);
background-size: 100% auto;
}
.page-header {
border:none;
}
.page-header h1 {
color: #337ab7;
}
.container {
padding-top: 40px;
padding-bottom: 60px;
max-width: 970px;
}
.title {
font-size: 24px;
}
.media img {
width: 60px;
border-radius: 50%;
}
.delete,
.delete:hover {
color: #fff;
font-size: 22px;
margin-top: 5px;
text-decoration: none;
}
.send {
margin-top: 20px;
}
</style>
<div class="container">
<div class="page-header clearfix">
<h1 class="pull-left">黑马留言板</h1>
<a href="./add.html" class="btn btn-primary pull-right btn-lg send">我要留言</a>
</div>
<!-- 内容 -->
<div class="lyb-body">
{{ each list v i }}
<div class="panel panel-primary">
<!-- 标题 -->
<div class="panel-heading clearfix">
<!-- 标题 -->
<span class="title pull-left"> {{ v.title }} </span>
<!-- 删除 -->
<a href="/delete?id={{ v.id }}" class="delete pull-right glyphicon glyphicon-trash"></a>
</div>
<!-- 主体 -->
<div class="panel-body">
<div class="media">
<div class="media-left">
<a href="#">
<img class="media-object" src="../assets/img/img.jpg" >
</a>
</div>
<div class="media-body">
<h4 class="media-heading">作者:{{ v.nickname }}</h4>
<p>{{ v.content }}</p>
<p class="text-right"> {{ v.created }}</p>
</div>
</div>
</div>
</div>
{{ /each }}
</div>
</div>
<-- add.html -->
<style>
body {
background: url('../assets/img/bg1.png') no-repeat top left;
background-color: rgba(238, 237, 235, 1);
background-size: 100% auto;
}
.page-header {
border:none;
}
.container {
max-width: 970px;
}
.page-header h1 {
color:#337ab7;
}
textarea.form-control {
height: 100px;
}
</style>
div class="container">
<div class="page-header">
<h1>留言:</h1>
</div>
<form action="/submit" method="POST">
<div class="form-group">
<label for="nickname">昵称:</label>
<input type="text" class="form-control" id="nickname" placeholder="昵称" name="nickname">
</div>
<div class="form-group">
<label for="title">标题:</label>
<input type="text" class="form-control" id="title" placeholder="标题" name="title">
</div>
<div class="form-group">
<label for="content">内容:</label>
<textarea class="form-control content" name="content" id="content" placeholder="内容" ></textarea>
</div>
<button type="submit" class="btn btn-primary btn-lg pull-right">提交</button>
</form>
</div>
js
// index.js
// 加载资源
//服务器模块
const http = require('http');
// 文件操作模块
const fs = require('fs');
// 文件路径操作模块
const path = require('path');
// 模板引擎
const template = require('art-template');
// 文件性质类型判断
const mime = require('mime');
// url地址解析
const url = require('url');
// 时间格式化操作
const moment = require('moment');
// 查询字符串解析
const queryString = require('querystring');
// 1- 搭建服务器
const server = http.createServer(); // 创建服务器
server.on('request', (req, res) => { // 监听请求
// 根据不同的请求,返回不同的页面
console.log('req.url:', req.url);
if (req.url.startsWith('/index') || req.url == '/') { // 首页
// 1- 获取数据库的数据
// 2- 配合模板引擎进渲染
// 3- 把渲染好页面返回给浏览器
// 1- 获取数据库的数据
// data就是读取的数据
readData(function (data) {
console.log('data:', data); // data 是 readData()方法读取的数据
// 排序
data.list.sort((a, b) => b.id - a.id);
// 渲染
let str = template(path.join(__dirname, 'pages/index.html'), data);
res.end(str);
});
} else if (req.url.startsWith('/add')) {
// 直接读取add.html页面返回
fs.readFile(path.join(__dirname, 'pages/add.html'), (err, data) => {
if (err) {
return console.log(err);
}
res.end(data);
})
} else if (req.url.startsWith('/assets')) { //静态资源处理
// 服务器直接读取返回
fs.readFile(path.join(__dirname, req.url), (err, data) => {
if (err) {
return console.log(err);
}
// 给返回的资源设置明确mime类型
res.setHeader('content-type', mime.getType(req.url));
res.end(data); //返回读取的静态资源
})
} else if (req.url.startsWith('/delete')) { // 删除留言
// 1- 前端点击删除按钮,传递给给后台
// 2- 接收前端传递id, 根据id去删除对应的数据
// 1- 先把data.json中数据读取出来,转出js数组
// 2- 从数组中删除
// 3- 将数组转回json字符串,写回data.json
// 3- 页面需要重新渲染
// 获取前端传递id
let id = url.parse(req.url, true).query.id;
// 读取原数据
readData(function (data) {
let index = data.list.findIndex(v => v.id == id); //根据id找下下标
data.list.splice(index, 1); //删除
writeData(data, () => { // 写回去
goIndex(res); //去首页
});
})
} else if (req.url.startsWith('/submit') && req.method == 'GET') { // get 添加
// 1- 用户点击添加按钮,把表单数据提交给后台
// 2- 后台获取前端提供表单数据, 添加到数据库中
// 1- 先把data.json中数据读取出来,转出js数组
// 2- 往数组中添加
// 3- 将数组转回json字符串,写回data.json
// 3- 添加完成跳转到首页, 看到添加后结果
// 获取前端提供表单数据
let info = url.parse(req.url, true).query;
info.id = Date.now();
info.created = moment().format('YYYY年MM月DD日 HH:mm:ss');
readData((data) => {// 添加
data.list.push(info);
writeData(data, () => { // 写入
goIndex(res); //去首页
})
})
} else if (req.url.startsWith('/submit') && req.method == 'POST') { // post 添加
// 1- 用户点击添加按钮,把表单数据提交给后台
// 2- 后台获取前端提供表单数据, 添加到数据库中
// 1- 先把data.json中数据读取出来,转出js数组
// 2- 往数组中添加
// 3- 将数组转回json字符串,写回data.json
// 3- 添加完成跳转到首页, 看到添加后结果
// 前端: 需要把数据分割成片段 ,依次向后台进行传递(少量多次)
// 后台: 后台要持续接收前端传递数据片段, 还要进行拼接 , 当前前端所有的片段传递完成后,才能使用数据;
// 服务器只要监听前端以post方式传递了数据 就会触发data事件
let str = '';
req.on('data', chunk => {
str += chunk; //拼接片段
})
req.on('end', () => {
// str 查询字符串
let info = queryString.parse(str);
info.id = Date.now();
info.created = moment().format('YYYY年MM月DD日 HH:mm:ss');
readData(function (data) {
data.list.push(info); //把新数据添加到数组中
writeData(data, () => {
goIndex(res); //去首页
})
})
});
} else {
res.end('404');
}
});
// 设置服务器端口号
server.listen('9999', () => console.log('http://localhost:9999 服务器已启动')); //启动服务器
// 封装常用代码 , 提供代码复用性
// 如何封装 : 提取功能代码进行封装 , 变化部分作为参数
// 重定向到首页
function goIndex (res) {
// 跳转到首页
res.statusCode = 302;
res.setHeader('location', '/index');
res.end();
}
// 封装读取data.json文件方法
// fn: 读取数据完成后,要执行逻辑
function readData (fn) {
fs.readFile(path.join(__dirname, 'data/data.json'), 'utf8', (err, data) => {
if (err) {
return console.log(err);
}
//把数据转成json格式
data = JSON.parse(data);
// 如何处理数据?
// 将数据交给fn 回调函数进行处理
// if (fn) {
// fn(data);
// }
fn && fn(data);
})
}
// 封装写入data.json文件方法
// data: 要写入的数据
// fn: 写入完成后要执行逻辑
function writeData (data, fn) {
data = JSON.stringify(data, null, 2); //将js对象转成数据
fs.writeFile(path.join(__dirname, 'data/data.json'), data, (err) => {
if (err) {
return console.log(err);
}
//写入完成后 进行下一步操作
fn && fn();
})
}
data.json 假数据
{
"list": [
{
"id": "3",
"nickname": "二十世纪旗手",
"title": "Tuesday",
"content": "昨天上数学课的时候听歌睡着了,听到这首歌的时候我做梦了我梦到了我在泰姬陵我去了耶路撒冷我到了寺庙在那些地方尬舞,,,并且传授宗教。。。真的有毒。。",
"created": "2019年7月12日 22:35:25"
},
{
"id": "2",
"nickname": "幕后-世俗",
"title": "Tuesday",
"content": "那些表面无害内心冷血的女孩真可怕",
"created": "2019年7月12日 22:35:25"
},
{
"id": "1",
"nickname": "吃橘子的鲨鱼",
"title": "Tuesday",
"content": "说了你又不听,听了你又不懂,懂了你又不做,做了你又做错,错了你又不认,认了你又不改,改了你又不服,不服你又不说,要我怎么办?",
"created": "2019年7月12日 22:35:25"
}
]
}
涉及到的终端操作命令
- npm init
- npm i mime art-template
- node .\index.js或者nodemon .\index.js
- Ctrl+c退出