NodeJS笔记-系列
初识NodeJS
Express框架
NodeJS-MongoDB
NodeJS接口、会话控制
Node-回眸[一]
Node-回眸[二]
Node-回眸[三]
QX-AI
GPT-4
QX-AI初始化中...
暂无预设简介,请点击下方生成AI简介按钮。
介绍自己
生成预设简介
推荐相关文章
生成AI简介

express框架

express 是一个web框架,也是npm的一个工具包,功能和http模块类似,但功能更加强大,开发服务端效率更高

基本使用步骤:

  1. 导入express框架
  2. 创建服务器实例对象
  3. 绑定事件
  4. 监听端口,启动服务器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//导入express
const express = require('express');
// 创建对象
const app = express();
//创建路由,req请求报文的封装对象,res响应报文
app.get('/home', (req, res) => {
// 如果请求的方法是get,并且url路径是/home,则执行
res.end('Welcome');
})
// 监听端口启动服务
app.listen(3000, () => {
console.log('服务启动');
});

路由

路由:确定如何响应客户端对特定端点的请求

使用方法,由请求方法、路径、回调函数组成
1
app.<method>(path,(req, res) => {})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//创建路由,req请求报文的封装对象,res响应报文
//从上往下匹配,匹配到则不再往后匹配
app.get('/home', (req, res) => {
// 如果请求的方法是get,并且url路径是/home,则执行
res.end('Welcome /home');
})

app.get('/', (req, res) => {
res.end('Welcome /');
});

app.post('/login', (req, res) => {
res.end('Welcome /login');
});

app.all('/test', (req, res)=>{
res.end('Welcome /test');
});
// 匹配所有路径和方法,可以放在最后进行404响应
app.all('*', (req, res)=>{
res.end('404');
});

获取请求报文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
app.get('/', (req, res) => {
//express兼容原生操作
console.log(req.method);//请求方法
console.log(req.url);//请求url
console.log(req.httpVersion);//http版本
console.log(req.headers);//请求头
//express封装方法
console.log(req.path);//请求路径
console.log(req.query);//查询字符串
console.log(req.ip);//用户ip
console.log(req.get('host'));//获取特定请求头的属性值

res.send('Welcome /');
});

路由参数

路由参数指的是 URL 路径中的参数(数据),如多篇文章按1.html、2.html往后排,匹配路由参数从而无需写多个路由

获取路由参数:req.params.id

1
2
3
app.get('/:id.html',(req, res) => {
res.send('id为' + req.params.id);
});

路由参数练习:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
"singers": [
{
"singer_name": "周杰伦",
"singer_pic": "http://y.gtimg.cn/music/photo_new/T001R150x150M0000025NhlN2yWrP4.webp",
"other_name": "Jay Chou",
"singer_id": 4558,
"id": 1
},
{
"singer_name": "林俊杰",
"singer_pic": "http://y.gtimg.cn/music/photo_new/T001R150x150M000001BLpXF2DyJe2.webp",
"other_name": "JJ Lin",
"singer_id": 4286,
"id": 2
},
{
"singer_name": "G.E.M. 邓紫棋",
"singer_pic": "http://y.gtimg.cn/music/photo_new/T001R150x150M000001fNHEf1SFEFN.webp",
"other_name": "Gloria Tang",
"singer_id": 13948,
"id": 3
}
]
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
const express = require('express');
// 创建对象
const app = express();
//导入json文件
const {singers} = require('./JSON/singers.json');

app.get('/singer/:id.html',(req, res) => {
let {id} = req.params;
let result = singers.find((item)=>{
if(item.id === Number(id)){
return true;
}
});
// console.log(result);
if(!result){
res.send("404");
return;
}
res.send(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>${result.singer_name}</h2>
<img src=${result.singer_pic}}></img>
</body>
</html>
`);
});

app.listen(3000, () => {
console.log('服务启动');
})

响应设置

一般响应

1
2
3
4
5
6
7
8
9
10
11
12
13
app.get('/', (req, res)=>{
// 原生响应
res.statusCode = 200;
res.statusMessage = 'OK';
res.setHeader('Content-Type', 'text/html;charset=UTF-8');
// express
res.status(200);
res.set('Content-Type', 'text/html');
res.send('你好');//自动添加字符集,不乱码
// 连贯操作
res.status(200).set('xxx','yyy').send('你好');
});

其它响应,一般不能多个同时成功响应

1
2
3
4
5
6
7
8
9
10
11
12
13
app.get('/', (req, res)=>{
// 302重定向
res.redirect('https://www.baidu.com/');
// 下载响应
res.download('../3.mp4');
// 响应JSON
res.json({
a:1
});
// 响应文件内容
res.sendFile(__dirname + '/package.json');
});

中间件

中间件(Middleware)本质是一个回调函数

中间件函数可以像路由回调一样访问请求对象(request),响应对象(response)

作用:使用函数封装公共操作,简化代码

类型:全局中间件、路由中间件

每一个请求到达服务端后都会执行全局中间件函数
满足某个路由规则的请求会触发指定路由中间件函数

声明中间件函数
1
2
3
4
5
6
function middleware(req,res,next){
//实现功能代码
//执行next函数
//调用next()执行完中间件函数后,继续执行路由中的回调函数
next();
}

全局中间件

每一个请求到达服务端后都会执行全局中间件函数

app.use是专门解析中间件函数的方法,使用之后express会将http请求响应对象交给中间件函数,中间件函数处理完通过next()方法传递给下一中间件函数,直到返回响应数据

可以使用 app.use() 定义多个全局中间件

案例:

利用中间件对所有路由记录访问路径和用户ip
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 声明中间件
function middleware1(req, res, next) {
let {url, ip} = req;
fs.appendFileSync(path.resolve(__dirname + '/1.log'), `${url} ${ip}\r\n`);
// 中间件函数处理完请求后,通过next方法将http请求对象交给下一个中间件或路由,直到返回响应
next();
}
function middleware2(req, res, next) {
let {url, ip} = req;
console.log(url, ip);
next();
}
// 使用app.use定义全局中间件,express将http请求对象交给中间件
app.use(middleware1);
app.use(middleware2);

app.get('/home', (req, res)=>{
res.send('首页');
});
app.get('/admin', (req, res)=>{
res.send('后台');
});

路由中间件

满足某个路由规则的请求会触发指定路由中间件函数

只需要对某一些路由进行功能封装,则使用路由中间件,可以设置多个中间件函数

格式
1
app.get('/路径',`中间件函数1`,`中间件函数2`,(req,res)=>{});

案例:

访问后台和设置页的请求必须携带code为123的参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function checkCode(req, res, next){
if (req.query.code === '123'){
next();//继续执行路由回调
}else{
res.send('code参数错误');
}
}
app.get('/home', (req, res)=>{
res.send('首页');
});
// 放到需要的路由上
app.get('/admin', checkCode, (req, res)=>{
res.send('后台');
});
app.get('/setting', checkCode, (req, res)=>{
res.send('设置');
});

静态资源中间件

express内置处理静态资源的中间件

1
app.use(express.static('<静态资源目录>'));

设置后,所有静态资源都会根据静态资源目录去寻找,index.html、index.css等为默认打开的资源

果静态资源与路由规则同时匹配,从上而下谁先匹配谁就响应

一般用路由响应动态资源,如搜索结果等,用静态资源中间件响应静态资源,如html、css等

1
2
// 设置静态资源目录,会自动添加Mime类型
app.use(express.static('../public-test'));

获取请求体

express 可以使用 body-parser 包处理请求体,该包内置许多中间件来处理请求体,需要先npm安装npm i body-parser

包内常用中间件,当中间件解析完请求后会向 req 对象上添加 body 属性,里面包含了请求体对象

1
2
3
4
// 解析 json 格式的中间件
var jsonParser = bodyParser.json()
// 解析 querystring 格式的中间件
var urlencodedParser = bodyParser.urlencoded({ extended: false })

案例:

获取表单post提交的用户名和密码,get请求则显示登录页面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const bodyParser = require('body-parser');
// 获取表单post提交的用户名和密码,get请求则显示登录页面
app.get('/login', (req, res)=>{
// 响应登录页面
res.sendFile(__dirname + '/login.html');
});
// 获取 bodyParser 包中的中间件函数
// 解析 json 格式的中间件
// 自带的express.json()也能解析json格式
var jsonParser = bodyParser.json() // express.json()
// 解析 querystring 格式的中间件
var urlencodedParser = bodyParser.urlencoded({ extended: false })
app.post('/login', urlencodedParser, (req, res) => {
// 当urlencodedParser中间件执行完毕,会向req对象上添加一个body属性
console.log(req.body);//{ username: '543', password: '345' }
// 成功获取包含用户名和密码的对象
res.send('登录成功!');
});

防盗链

请求报文中的 referer 会携带当前访问资源的域名和端口,限制 referer 的值即可实现防盗链

全局中间件实现防盗链
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
app.use((req, res, next) => {
// 获取请求头中的referer
let referer = req.get('referer');
// referer不为空再去执行
if (referer) {
// 解析referer
let url = new URL(referer);
// url.hostname 获取域名,判断是否是目标域名
if (url.hostname !== '127.0.0.1') {
res.status(404).send('404 Not Found');
return;
}
}
next();
});

路由模块化Router

Router 是一个完整的中间件和路由系统,相当于一个小型的 app 对象。

声明Router和app
1
2
const router = express.Router();
const app = express();
home.js一个完整的路由
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const express = require('express');

const router = express.Router();// 相当于一个小型的app对象

router.get('/home', (req, res)=>{
res.send('首页');
});

router.get('/search', (req, res)=>{
res.send('搜索');
});

// 将路由暴露出去
module.exports = router;

在主文件需要导入路由并使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const express = require('express');
// 导入路由
const homeRouter = require('./home.js');
const adminRuter = require('./admin.js');

const app = express();
// 使用路由
app.use(homeRouter);
app.use(adminRuter);

app.all('*', (req, res) => {
res.send('404 Not Found');
});

app.listen(3000, ()=>{
console.log('服务启动');
});

app.use可以添加一个路径参数,来给路由添加前缀

1
2
app.use('/', indexRouter);
app.use('/users', usersRouter);// 路由中的路径都会自动添加这个前缀

模板引擎ejs

模板引擎是分离用户界面业务数据的一种技术,在许多语言都有的一种通用技术

EJS 是一个高效的 Javascript 的模板引擎,用于分离html和服务端js

1
2
3
4
5
6
7
8
9
10
11
const ejs = require('ejs');
// 字符串
let str1 = 'qx';
let str2 = `${str1}chuckle`;
console.log(str2);// qxchuckle

// 使用ejs
let str = '<%= str %>chuckle';
let result = ejs.render(str, {str:str1});
console.log(result);// qxchuckle

将 ejs 模板写在单独的文件再由fs读入

1
2
3
4
let str = fs.readFileSync('./01.html').toString();
let [name,age] = ['chuckle',19]
let result = ejs.render(str, {name,age});
console.log(result);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2><%= name %></h2>
<h3><%= age %></h3>
</body>
</html>

ejs列表渲染:

原生js, js和html耦合在一起
1
2
3
4
5
6
7
8
9
const arr = [1,2,3,4];
let html = '<ul>';
arr.forEach(item=>{
html += `<li>${item}</li>`;
})
html += '</ul>';
console.log(html);
//<ul><li>1</li><li>2</li><li>3</li><li>4</li></ul>

使用ejs, 分离html和服务端js
1
2
3
4
5
6
7
8
9
10
const arr = [1,2,3,4];
let html = `<ul>
<% arr.forEach(item => { %>
<li><%= item %></li>
<% }) %>
</ul>`;
let result = ejs.render(html, {arr});
console.log(result);
//<ul><li>1</li><li>2</li><li>3</li><li>4</li></ul>

ejs条件渲染

1
2
3
4
5
6
7
8
9
10
let isLogin = true;
let html = `<% if(isLogin) { %>
<span>登陆</span>
<%}else{ %>
<span>注册</span>
<%} %>`;
let result = ejs.render(html, {isLogin});
console.log(result);
// <span>登陆</span>

express使用ejs

使用 app.set() 设置express使用的模板引擎、设置模板文件存放位置

1
2
3
4
// 设置express使用的模板引擎
app.set('view engine', 'ejs');
// 设置模板文件存放位置
app.set('views', path.resolve(__dirname +'/ejs'));

然后就可以用res调用render方法响应渲染好的页面

1
2
3
4
// res.render('<模板文件名>','<数据>');
let name = 'chuckle';
// 在模板文件夹中创建home.ejs
res.render('home', {name});

完整案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const express = require('express');
const path = require('path');

const app = express();
// 设置express使用的模板引擎
app.set('view engine', 'ejs');
// 设置模板文件存放位置
app.set('views', path.resolve(__dirname +'/ejs'));
// 然后就可以用res调用render方法
app.get('/', (req, res) => {
// res.render('<模板文件名>','<数据>');
let name = 'chuckle';
// 在模板文件夹中创建home.ejs
res.render('home', {name});
});

app.listen(3000, ()=>{
console.log('服务启动');
});

express-generator

express-generator是一个node的自动化创建项目工具,类似于vue-cli,能快速构建express项目标准骨架

安装后会暴露一个全局命令:express

安装
1
2
npm install -g express-generator
express -v //查看版本和帮助

构建项目:

1
express -e <文件夹名称>// -e参数是添加ejs支持
  1. app.js是项目主文件;
  2. views目录用于存放页面文件;
  3. routes目录用于存放路由文件;
  4. public用于存放静态文件;
  5. bin中的www是项目的启动文件;

文件上传

在index.js中添加两个路由,get显示表单,post接收上传的数据

1
2
3
4
5
6
7
8
9
10
// 显示表单
router.get('/portrait', (req,res) => {
res.render('portrait');
});

// 处理文件上传
router.post('/portrait', (req, res)=>{
res.send('上传成功');
});

新建 portrait.ejs 创建表单页面,对于文件上传,form表单必须添加 enctype=”multipart/form-data” 属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h3>文件上传</h3>
<form action="/portrait" method="post" enctype="multipart/form-data">
用户名:<input type="text" name="username"><br />
头像:<input type="file" name="portrait"><br />
<button>提交</button>
</form>
</body>
</html>

处理文件上传需要使用 formidable 包,它是用于解析表单数据的 Node.js 模块,尤其是文件上传。

完整的index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
var express = require('express');
var router = express.Router();
// 导入formidable
const formidable = require('formidable');

/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});

// 显示表单
router.get('/portrait', (req,res) => {
res.render('portrait');
});

// 处理文件上传
router.post('/portrait', (req, res)=>{
// 创建表单对象
const form = formidable({
multiples: true,
// 设置上传文件的保存目录
uploadDir: __dirname + '/../public/images',
// 保持文件后缀
keepExtensions: true
});
// 解析表单对象
form.parse(req, (err, fields, files) => {
if (err) {
next(err);
return;
}
// fields存放除文件上传的一般表单提交
console.log(fields);
// files保存文件上传
console.log(files);
// res.json({ fields, files });
// 保存上传文件的存放路径,以后将此数据保存在数据库中
let url = '/images/' + files.portrait.newFilename;
res.send(url);// 给用户返回url路径
});
});

module.exports = router;

lowdb包

在保存一些简单数据时可以使用 lowdb 包,在json中进行增删改查数据,推荐使用1.0.0版本

案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const low = require('lowdb')
const FileSync = require('lowdb/adapters/FileSync')
// 数据存储的josn
const adapter = new FileSync('db.json')
// 获取db对象
const db = low(adapter)
// 初始化数据,也可以手动自定义初始化
db.defaults({ posts: [], user: {} }).write()

// 增删改都需要最后调用write()
// 往posts这个数组尾中添加元素
db.get('posts')
// push从尾部插入元素
.push({ id: 1, title: 'lowdb is awesome'})
.write()
db.get('posts')
// unshift从头插入元素
.unshift({ id: 2, title: 'lowdb is awesome'})
.write()

// 获取数据
let value = db.get('posts').value();
console.log(value);
// 获取单条数据
let single = db.get('posts').find({id: 1}).value();
console.log(single);

// 删除所有匹配的数据,返回被删除的数据
db.get('posts').remove({id: 1}).write();

// 更新数据,先获取再修改
db.get('posts').find({id: 2}).assign({title: 'chuckle'}).write();

案例-记账本

主要的一些文件

主要路由:

/routes/index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
var express = require('express');
var router = express.Router();

// 使用lowdb存储数据
const low = require('lowdb')
const FileSync = require('lowdb/adapters/FileSync')
// 数据存储的josn
const adapter = new FileSync(__dirname + '/../data/db.json')
// 获取db对象
const db = low(adapter)

// 导入shortid为数据生成id
const shortid = require('shortid');

// 记账本页面路由
router.get('/', function(req, res, next) {
// 获取所有账单记录数据
let content = db.get('content').value();
// 将数据数组传递过去遍历渲染
res.render('index', {content});
});

// 添加记录页面路由
router.get('/add', function(req, res, next) {
res.render('add');
});

// 添加记录post接口路由
router.post('/add', function(req, res, next) {
// 生成唯一id
let id = shortid.generate();
// 往db.josn写入数据
db.get('content').unshift({id:id, ...req.body}).write();
// 跳转到提示页
res.render('tip',{msg: '添加成功!', url: '/'});
});

// 根据id删除数据,使用路由参数
router.get('/delete/:id',(req, res) => {
let id = req.params.id;// 获取路由参数
db.get('content').remove({id}).write();
res.render('tip',{msg: '删除成功!', url: '/'});
});


module.exports = router;

页面ejs:

/views/index.ejs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>记账本</title>
<link rel="stylesheet" href="/css/index.css" />
</head>

<body>
<div class="content">
<div class="content-box">
<div class="title">
<h2>记账本</h2>
</div>
<div class="add">
<a href="/add">添加</a>
</div>
<div class="record-box">
<% content.forEach(item=>{%>
<div class="record-item">
<div class="date"><%= item.date %></div>
<div class="type <%= item.type==='支出'?'expenditure':'income'%>">
<%= item.type %>
</div>
<div class="record-body">
<div class="record-content">
<div class="matter"><%= item.matter %></div>
<div class="account"><%= item.account %> 元</div>
</div>
<a href="/delete/<%= item.id %>" class="delete">删除</a>
</div>
</div>
<%})%>
</div>
</div>
</div>
</body>
</html>

/views/add.ejs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>添加记录</title>
<link rel="stylesheet" href="/css/add.css" />
</head>

<body>
<div class="content">
<div class="content-box">
<div class="title">
<h2>添加记录</h2>
</div>
<div class="home">
<a href="/">账单列表</a>
</div>
<form action="/add" method="post">
<div class="form-item">
<label for="matter">事项</label>
<input class="control" type="text" name="matter" id="matter" />
</div>
<div class="form-item">
<label for="date">时间</label>
<input class="control" type="date" name="date" id="date" />
</div>
<div class="form-item">
<label for="type">类型</label>
<select class="control" name="type" id="type">
<option selected="">支出</option>
<option>收入</option>
</select>
</div>
<div class="form-item">
<label for="account">金额</label>
<input class="control" type="text" name="account" id="account" />
</div>
<div class="form-item">
<label for="remark">备注</label>
<textarea class="control" name="remark" id="remark"></textarea>
</div>
<hr />
<button>添加</button>
</form>
</div>
</div>
</body>
</html>

/views/tip.ejs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" href="/css/tip.css" />
</head>
<body>
<div class="content">
<h3><%= msg %></h3>
<a href="<%= url %>">返回</a>
</div>
</body>
</html>

css:

/public/css/index.css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
body{
font-family:"Microsoft YaHei",微软雅黑;
color: #363636;
}
*{
padding: 0;
margin: 0;
box-sizing: border-box;
text-decoration: none;
}
.content{
max-width: 600px;
margin: 0 auto;
}
.content-box{
display: flex;
flex-direction: column;
flex-wrap: wrap;
align-content: center;
margin: 0 10px;
}
.content-box>*{
width: 100%;
}
.title{
padding: 20px 0;
border-bottom: 1px solid rgb(220, 220, 220);
}
.title h2{
font-size: 30px;
font-weight: 500;
}
.add{
margin-top: 10px;
padding-left: 20px;
}
.add a{
text-decoration: none;
}
.record-box{
margin-top: 10px;
border-bottom: 1px solid rgb(220, 220, 220);
}
.record-item{
margin-bottom: 15px;
border: 1px solid rgb(226, 226, 226);
border-radius: 6px;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
-ms-border-radius: 6px;
-o-border-radius: 6px;
position: relative;
}
.record-item>.date{
height: 34px;
background: rgb(174, 209, 237);
padding: 6px 12px;
font-size: 14px;
line-height: 22px;
}
.record-body{
display: flex;
flex-direction: row;
flex-wrap: wrap;
padding: 10px 20px;
justify-content: space-between;
align-content: center;
}
.record-content{
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
flex: 1;
}
.record-body>.delete{
padding-left: 10px;
margin-left: 10px;
border-left: 1px solid rgb(201, 201, 201);
color: rgb(33, 70, 181);
}
.record-item>.type{
position: absolute;
top: 5px;
right: 10px;
font-size: 14px;
padding: 0 6px;
height: 22px;
line-height: 22px;
color: #fff;
border-radius: 4px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
-ms-border-radius: 4px;
-o-border-radius: 4px;
}
.type.expenditure{
background: rgb(241, 141, 158);
}
.type.income{
background: rgb(85, 194, 134);
}

/public/css/add.css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
body{
font-family:"Microsoft YaHei",微软雅黑;
color: #363636;
}
*{
padding: 0;
margin: 0;
box-sizing: border-box;
text-decoration: none;
}
hr{
margin: 20px auto;
border: 0;
border-top: 1px solid rgb(220, 220, 220);
}
.content{
max-width: 600px;
margin: 0 auto;
}
.content-box{
display: flex;
flex-direction: column;
flex-wrap: wrap;
align-content: center;
margin: 0 10px;
}
.content-box>*{
width: 100%;
}
.home{
margin-top: 10px;
padding-left: 10px;
}
.title{
padding: 20px 0;
border-bottom: 1px solid rgb(220, 220, 220);
}
.title h2{
font-size: 30px;
font-weight: 500;
}
.content-box form{
margin-top: 10px;
}
.form-item{
margin-bottom: 15px;
}
.form-item label{
display: block;
height: 32px;
line-height: 32px;
font-size: 18px;
}
.form-item>*{
width: 100%;
}
.form-item>.control{
padding: 6px 12px;
height: 36px;
font-size: 16px;
line-height: 36px;
color: #363636;
border: 1px solid #ccc;
border-radius: 4px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
-ms-border-radius: 4px;
-o-border-radius: 4px;
transition: all 0.3s;
-webkit-transition: all 0.3s;
-moz-transition: all 0.3s;
-ms-transition: all 0.3s;
-o-transition: all 0.3s;
outline: none;
font-family:"Microsoft YaHei",微软雅黑;
}
.form-item>.control:focus{
border-color: #66afe9;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6);
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6);
}
.form-item>textarea.control{
padding: 8px 12px;
height: 100px;
line-height: 1;
resize: none;
}
.content-box form>button{
width: 100%;
padding: 6px 12px;
margin-bottom: 15px;
border: 1px solid rgb(57, 162, 204);
background: rgb(37, 173, 204);
border-radius: 4px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
-ms-border-radius: 4px;
-o-border-radius: 4px;
font-size: 17px;
color: #fff;
transition: all 0.3s;
-webkit-transition: all 0.3s;
-moz-transition: all 0.3s;
-ms-transition: all 0.3s;
-o-transition: all 0.3s;
}
.content-box form>button:hover{
background: rgb(32, 153, 190);
}

/public/css/tip.css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
body{
font-family:"Microsoft YaHei",微软雅黑;
color: #363636;
}
*{
padding: 0;
margin: 0;
box-sizing: border-box;
text-decoration: none;
}
.content{
margin: 0 auto;
max-width: 300px;
padding: 20px 30px;
background: rgba(77, 190, 215, 0.6);
margin-top: 20px;
display: flex;
align-items: center;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
border-radius: 4px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
-ms-border-radius: 4px;
-o-border-radius: 4px;
}