SHEN YING

Syの小窝

一、介绍

在上一篇笔记中简单介绍了MongoDb的使用方法,想必任何一个第一次学这个数据库的前端开发者早已无比火热了,现在让我们来直接开🦌一个 Restful API规范的应用吧。

二、安装驱动

这里说的所谓的安装驱动,就是在node.js中安装这个模块,这里假设你还没有安装express模块。

打开一个文件夹,

1
2
3
npm init
# 回车跳过项目信息配置
npm i express mongodb

三、配置连接

在根目录新建一个配置信息的文件夹:config,并创建文件config/mongoDbConnection.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
const {
MongoClient
} = require("mongodb");

// Replace the uri string with your connection string.
const uri = "mongodb://localhost:27017/";


const dbName = "myBlog"

const _db = null;

async function connectDb() {
if (!_db) {
try {
const client = new MongoClient(url, {
useUnifiedTopology: true
});
await client.connect();
_db = await client.db(dbName);
} catch (err) {
throw "连接到数据库出错"
}
return _db;
}
}

exports.getCollection = collection => {
let _col = null;
return async () => {
try {
if (!_col) {
const db = await connectDb();
_col = await db.collection(collection);
}
} catch (err) {
throw "连接 connection 出错";
}
return _col;
}
}

这里说一个我在官方文档看到的小技巧,url可以在Mongo Compass中通过粘贴获得:

image-20250325214747615

这段代码很长,看起来非常劝退,但是稍微扒一下就能发现一点儿也不难。

因为其中用到了很多防御性编程,所以看起来较为臃肿,我来理一理核心逻辑:

  • 通过解构赋值导入mongodb模块下的MongoClient类后面用于创建服务链接。
  • url存放数据库的地址。
  • 定义了一个connectDb()方法链接数据库返回并存到_db中以便复用。连接失败报错(防御性编程)。
  • _db用于存放连接到的数据库,便于全局多次复用。

3.1 connectDb() 方法

这里用到了几个我比较陌生的API,也来扒一下:

1
2
3
4
5
6
7
8
const client = new MongoClient(url, {
useUnifiedTopology: true
});
// 可以看出,MongoClient功能类似于连接数据库客户端
await client.connect();
// 一个Promise 返回客户端连接到的结果
await client.db(dbName);
// 一个Promise 返回客户端中的一个数据库

差点就去查资料了,还好我突然理解了~

对照这我们的MongoDB Compass其实就很好理解了:

image-20250325220135877
  • 我们用导入的类MongoClient创建了一个客户端的对象,并传入客户端的url参数作为客户端寻址。
  • 创建完client后并不会直接连接客户端,需要异步地调用client.connect()方法来连接。
  • 连接后我们需要选择一个数据库进行操作,所以异步地调用了client.db(dbName)并传入我们需要访问的数据库名。

完成后,我们就可以像之前那样,用db.xxx来进行一系列集合与文档的操作了,语法甚至都完全一样。

3.2 getCollection() 方法

我们在这个文件中还创建了一个用于获取集合的方法,并将其导出为模块。

这个方法代码更加简单,通过getCollection方法来得到了db数据库,并获取collection集合返回,将函数闭包并将获取的集合存入_col

一、介绍

MongoDb是文档存储结构的数据库,与传统的SQL数据库有所不同。这是一开始学习Mongodb给我的印象。

传统的Sql数据库中有数据库、表、记录这三个层次的概念,映射到MongoDb中:数据库就是一个Connection连接,表则是一个Collection集合,集合或者说表中的每一个记录在Mongodb中叫做一个个的文档。

初步学习下来,Mongodb的集合像是一个存储Json格式的容器,似乎非常贴合web请求的Json格式,让前端开发者旋转。

二、开始Mongo

2.1 添加文档/创建集合

下载部署完成 Mongodb 后,可以通过如下命令来创建一个集合:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
db.postCollection.insertOne({
title: "mongodb 入门教程",
author: {
name: "Sy",
avatar: "https://shenying.online"
},
createdAt: "2025-3-25",
content: "Mongodb 是文档存储结构的数据库",
comments: [
{
user: "小明",
comment: "真不错"
},
{
user: "toby",
comment: "wow!"
}
]
})

这里的postCollection是我们当前创建的集合的名字,相当于在sql中创建了一张名为postCollection的表。

实际上,insertOne是用来在集合中添加文档数据的,但是因为没有该集合,所以mongoDb会自动创建这个名字的集合并插入数据,完成API的功能。

你也可以用同样的方式创建一张学生表(在mongodb中尽量叫他集合,下面也是如此):

1
db.Students.insertOne({内容省略...})

2.2 查询集合数据

  1. 查询集合的全部文档

要查询集合的所有元素可以使用db.集合名.find({})方法,不传递空对象则默认传递空对象,效果为查询集合的所有文档。

  1. 带条件的集合文档查询

以一张学生表为例,假设每一个学生文档有如下属性:nameagesex,如果要查询满足姓名为sy的学生,可以这样查:

1
db.Students.find({name: 'sy'})

注意这里的Students是学生集合的名字。

  1. 查询嵌套的文档结构:

假如文档的结构是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
title: "mongodb 入门教程",
author: {
name: "Sy",
avatar: "https://shenying.online"
},
createdAt: "2025-3-25",
content: "Mongodb 是文档存储结构的数据库",
comments: [
{
user: "小明",
comment: "真不错"
},
{
user: "toby",
comment: "wow!"
}
]
}

想要按照这里author.name这个二级属性来进行条件查询就需要这样写:

1
db.postCollection.find({"author.name": "Sy"})

注意这里的传递键名需要用双引号包裹,有所不同。

2.3 更新集合数据

更新数据和查询数据语法类似,存在两中方法:

  • updateOne() 仅修改一条数据
  • updateMany() 修改所有满足查询条件数据

他们都接受两个参数,依次是:1. 查询修改的条件 2. 要修改的属性

直接用之前的修改方法会出现报错:

1
db.user.updateOne({name: "Sy"}, {name: "谌y"})
image-20250325212710634

从这里可以得知,MongoDb中修改数据需要专门的操作符语法。

  1. $set

在修改的属性参数最外层再包一层大括号,并将$set键所对应的值设置为{name: "Syyyy", age: 19...}表示要修改$set所对应的所有属性。

1
db.user.updateMany({}, {$set: {name: 'kkk'}})

如果不这样做,Mongodb 就无法保证唯一性,请问你是只修改name还是要将这个文档修改为{ name: xxx }并舍去其他的键呢?

2.3 删除集合数据

与更新类似,删除数据根据数量也有两种写法:

  • deleteOne() 删除一条数据
  • deleteMany() 删除所有满足查询条件数据

语法很简单:

1
db.user.delete({ _id: ObjectId('67e2ae7c7da85b2443b61a2a') })

这里的_idMongoDb在创建文档时自动为我们生成的唯一标识,帮我们省去了自己后端使用雪花算法、uuid生成标识的时间。

三、总结

经过一番使用,可以看出MongoDb有一些比较特别的地方。

首先是性能方面,MongoDb适用于性能要求很高但是数据一致性要求不高的应用中。因为创建集合、数据分片过于方便,这样会导致很多数据不会保持相同的文档结构。

因为其操作语法和JavaScript非常接近,所以用 JS 创建的应用可以首选 MongoDb 作为数据库存储引擎。

一、前言

本文将讲述Mongodb的部署过程,以MacOS为例。

由于最近node项目一直装不上sqlite,让我发现了Mongodb这个宝藏。

二、下载文件

进入Mongodb的官网,最上方导航栏选择产品》社区版

mongo

点击这个绿色的下载按钮: mongo2

下拉到这个部分下载对应的文件: 20250325024051

完成后跟着这个博客来部署:CSDN博客链接

这里说一下,如果因为某些特殊原因在启动时报了这样的错误:

20250325024240

不要害怕,重启电脑或用mac的活动监视器强制关闭上一次的mongod进程即可。

对于所有的文件操作,首先要引入模块:

1
const fs = require("fs");

1.读取操作

语法如下:

1
fs.readFile(path[, options], callback)

第一个参数是路径,通常为相对路径。第二个参数为可选项,可以设置编码格式和文件打开行为。

flag 名称 描述
a 打开文件进行追加。 如果文件不存在,则创建该文件。
ax 类似于 a 但如果路径存在则失败。
a+ 打开文件进行读取和追加。 如果文件不存在,则创建该文件。
ax+ 类似于 a+ 但如果路径存在则失败。
as 以同步模式打开文件进行追加。 如果文件不存在,则创建该文件。
as+ 以同步模式打开文件进行读取和追加。 如果文件不存在,则创建该文件。
r 打开文件进行读取。 如果文件不存在,则会发生异常。
r+ 打开文件进行读写。 如果文件不存在,则会发生异常。
rs+ 以同步模式打开文件进行读写。 指示操作系统绕过本地文件系统缓存。
w 打开文件进行写入。 如果它不存在则创建,如果它存在则截断该文件。
wx 类似于 w 但如果路径存在则失败。
w+ 打开文件进行读写。 如果它不存在则创建,如果它存在则截断该文件。
wx+ 类似于 w+ 但如果路径存在则失败。

第三个参数是一个回调函数,传入两个参数,error表示读取失败时的错误信息,data表示正确读取时文件的内容。

代码示例:

1
2
3
4
5
6
7
8
// 导入 fs 模块
const fs = require("fs");

// 调用模块中异步读取文件的方法
fs.readFile("./public/msg.txt", "utf-8", (error, data) => {
if (error) throw error;
console.log(data);
});

2.文件写入操作

语法:

1
fs.writeFile(file, data[, options], callback)

第一个参数为文件路径,第二个参数表示写入的内容和可选的配置项,第三个参数为一个回调,传入参数为error

基本的用法:

1
2
3
4
5
6
7
8
// 导入 fs 模块
const fs = require("fs");

// 调用模块中异步写入文件的方法
fs.writeFile("./public/msg.txt", "今天是一个好日子", (error) => {
if (error) throw error;
console.log("写入成功!");
});

可选的参数用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 导入 fs 模块
const fs = require("fs");

// 调用模块中异步写入文件的方法
fs.writeFile(
"./public/msg.txt",
",明天又是一个大晴天。",
{ flag: "a", encoding: "utf-8" },
(error) => {
if (error) throw error;
console.log("写入成功!");
}
);

3.删除文件操作

语法:

1
fs.unlink(path, callback);

基本的用法:

1
2
3
4
5
6
7
8
// 导入 fs 模块
const fs = require("fs");

// 调用模块中异步删除文件的方法
fs.unlink("./public/temp.txt", (error) => {
if (error) throw error;
console.log("删除成功!");
});

1.认识路由

路由,顾名思义,它是指路径的指向或由来,即访问一个地址后它的指向,确定地址指向后,将会根据不同的路由地址,编写相应的代码,如下图所示。

20250309001102

一次地址的指向,实质上是一次数据请求的过程,在这种请求的过程中,还可以携带请求的方式,如 POST 或 GET,同时,也可以携带请求的参数,根据这些请求携带的方式和参数,即使是同一个地址,也可以执行不同的代码,如下图所示。

20250309001032

借助路由的这些特性,被广泛地应用于项目中各页面的切换,数据接口的请求,因此,路由是项目开发中必须要掌握的内容。在理解了它的重要性之后,如何去配置一个路由呢?带着这个问题,下面来说路由的配置方法。

2.配置路由

node 中配置路由十分简单,首先,使用以下命令下载搭建好的开发环境项目包,地址如下所示:

1
wget https://labfile.oss.aliyuncs.com/courses/4380/router.zip && unzip router.zip

然后,解压该项目包至 project 文件夹中,并打开项目包 router 文件夹,找到 bin 文件夹,在该文件夹下,创建一个名称为 reqRouters 的 js 文件,加入如下所示的代码:

1
2
3
4
5
6
7
8
9
10
11
12
const reqRouters = (req, res) => {
if (req.path === "/aa") {
return "首页";
}
if (req.path === "/bb") {
return "列表页";
}
if (req.path === "/cc") {
return "详细页";
}
};
module.exports = reqRouters;

上述代码中定义了一个 reqRouters 函数,在函数中,参数req表示请求携带的对象,用于创建服务器请求时回调函数使用,在这个req对象中,通过path来获取请求到时的路由地址,根据不同的地址,返回不同的内容,最后输出这个名称为 reqRouters 函数。

接下来,再次找到 bin 文件夹,打开名称为 app 的 js 文件,删除原有的内容,加入如下所示的代码。

@tab app.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const reqRouters = require("./reqRouters");
const serverHandle = (req, res) => {
res.setHeader("Content-Type", "application/json");
req.path = req.url.split("?")[0];
const result = reqRouters(req, res);
if (result) {
res.write(result);
res.end();
return;
}
res.writeHead(404, { "Content-Type": "text/plain" });
res.write("404 Not Found\n");
res.end();
};
module.exports = serverHandle;

在上述代码中,首先,使用 require 输入 reqRouters 模块,用于获取请求输出的内容,其次,定义一个名称为 serverHandle 的函数,用于服务创建时的回调,在该函数中,向 reqRouters 函数传入请求的路由地址,获取输出的内容并保存至变量 result 中,最后,判断变量 result 中是否有内容,如果有,则直接输出在页面中,否则,在页面中输出 “404 Not Found” 的信息。

最后,找到 bin 文件夹,打开名称为 index 的 js 文件,删除原有的内容,加入如下所示的代码。

@tab index.js

1
2
3
4
5
6
7
const http = require("http");
const serverHandle = require("./app");
const port = 8080;
const server = http.createServer(serverHandle);
server.listen(port, () => {
console.log("服务器运行在 8080 端口...");
});

在上述代码中,首先,分别导入 httpserverHandle 模块,前者用于创建一个新的服务,是 node 自带模块, 后者用于创建服务后的回调函数,当服务创建成功后,使用 listen 方法,在指定的 8080 端口下侦听,当服务启动后,就可以在浏览器的地址栏中,根据启动的地址和端口访问这个服务了,由于配置的路由在服务中,这时,就可以按配置的路由访问页面了 🤪

3.访问路由

要访问路由,需要先启动服务,因此,首先,在项目文件夹router下打开终端界面,并输入如下指令:

1
npm run dev

服务启动后的界面效果如下图所示:

20250309003725

服务器启动成功后就可以在浏览器中,根据指定的地址和端口,访问路由对应的的页面了。

当在浏览器地址栏中输入 localhost:8080/aa 时,它的 url.path 值为 ‘/a’ ,传给 reqRouters 函数后,则返回 “首页” 字符,因此,页面中输出 “首页” 内容,其他输入地址依此类推,根据不同的 url.path 值,向页面输出不同的内容,最终实现路由的功能。

4.路由开发

4.1 获取请求方式

在发送一次请求时,不同的请求方式,将会返回不同的数据,目前常用的请求方式分为 GETPOST 两种,前者常用于查询请求,后者用于增加、修改和删除的请求,那么,在 node 中,如何获取路由中的请求方式呢?

首先,使用以下命令获取搭建好的路由初始化项目包,地址如下所示:

1
wget https://labfile.oss.aliyuncs.com/courses/4380/router-param.zip && unzip router-param.zip

打开router-param/bin/reqRouters.js文件,将代码删除并修改为:

@tab reqRouters.js

1
2
3
4
5
const reqRouters = (req, res) => {
if (req.method === "GET") return "这是一次 GET 方式请求";
if (req.method === "POST") return "这是一次 POST 方式请求";
};
module.exports = reqRouters;

上面通过reqmethod属性获取到了服务器请求的方式,并返回不同的文字。

修改完成后在router-param文件夹下,打开终端输入指令启动 Node 服务:

1
npm run dev

如果需要测试POST请求,需要借助到之前的postman工具,更多内容见官网

20250309145521

通过上述例子知道,我们可以获取路由的方式。不仅如此,我们还能获取到路由的参数,接下来以 GET 方式为例,介绍路由的参数。

4.2 获取 GET 方式传参

我们知道路由传参是通过在url后面加上?并让参数以&分隔的方式来组合。

获取GET方式传参的过程实际上是根据传参名称来获取变量值的过程。操作起来就是,先实例化一个 URL对象 ,然后通过该对象获取 网址查询参数的 searchParams 对象,根据参数的名称获取对应的值。

打开reqRouters.js,将代码修改为:

@tab reqRouters.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 引入 url 模块
var url = require("url");
const reqRouters = (req, res) => {
if (req.method === "GET") {
const myURL = new URL(req.url, req.headers.host);
let params = myURL.searchParams;
if (params.get("id") && params.get("name")) {
return params.get("id") + "," + params.get("name");
} else {
return "没有传入相应参数!";
}
}
};
module.exports = reqRouters;

上述代码中,首先实例化了一个 URL 对象,在实例化的过程中传入了两个参数,第一个参数是 url 的请求地址,第二个是base 表示要解析的基本网址,包括地址的域名和端口。如果 url 地址是相对的,则要添加第二个参数,如果是绝对地址就可以省略第二个参数。

然后判断网址查询参数 params = myURL.searchParams 是否存在对应的 URL 参数,处理和返回它。

在项目文件夹router-params下输入指令:

1
npm run dev

4.3 获取 POST 方式传参

相比 GET 方式来说,POST要复杂很多,但也更加安全。使用 POST 传参时,携带的请求数据并不在路由中,而是在请求对象中,因此,需要绑定请求过程中的两个事件,一个data事件,一个end事件。

data事件中获取并累加每次请求传入的参数值,end事件会在1请求结束后触发,在事件中处理累加结束后的请求参数,输出至页面中。

修改router-param中的bin下的index.js文件,删除原有代码并替换成: :::: code-tabs @tab 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
const http = require("http");
const serverHandle = (req, res) => {
if (req.method === "POST") {
var strPOST = "";
// 绑定数据请求事件,每当接受到请求体的数据,就累加到 strPOST 变量中
req.on("data", function (chunk) {
strPOST += chunk;
});
// 绑定数据请求结束事件,向页面输出指定的参数值
req.on("end", function () {
res.setHeader("Content-Type", "application/json");
let objPOST = new URLSearchParams(strPOST);
if (objPOST.get("id") && objPOST.get("name")) {
const result = objPOST.get("id") + "," + objPOST.get("name");
res.write(result);
} else {
res.write("没有传入相应参数!");
}
res.end();
return;
});
}
};
const port = 8080;
const server = http.createServer(serverHandle);
server.listen(port, () => {
console.log("服务器运行在 8080 端口...");
});
::::

在事件data中每次累加参数,在end中查询参数是否包含idname,然后写入页面。

修改完成后,在 router-param 下,打开终端界面:

1
npm run dev

1.介绍

HTTP 协议(Hyper Text Transfer Protocol)是超文本传输协议,它是基于请求-响应的一个协议,即客户端浏览器给服务器发送一个 HTTP 请求,然后服务器对客户端的请求作出响应。我们开发的基于浏览器的 B/S 架构的程序都是基于 HTTP 协议(也有 HTTPS)的,例如登录、注册、查询商品等。

当用户在 URL 中输入一个网址到看到页面,大体经过如下几步:

  • DNS 解析,建立 TCP 连接,发送 HTTP 请求。
  • Server 端接收 HTTP 请求,处理,返回结果。
  • 客户端接收到返回数据,进行页面渲染显示内容。
  • 本节介绍的就是发送请求最常用的两种方式,GET 和 POST。

2.GET 请求

通过浏览器向服务器发送请求的常用方式分为两种,一种是 GET 方式,另一种是 POST 方式,前者请求的安全性不高,常用于查询的操作,如查询用户信息、查询博客信息等。

GET 请求传递参数是在 URL 后面加入一个 “?” ,然后在 “?” 后面加入想要传递的参数,多个参数之间用 “&” 隔开。格式如下:

1
http://localhost:8000/index?id=1001&name=abc

首先在右侧控制台中输入以下命令来初始化 npm 环境,初始化完成后会生成一个 package.json 文件。

1
npm init -y

在 project 文件夹下,新建 index.js 文件,并加入下列代码:

@tab index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const http = require("http");
const server = http.createServer((req, res) => {
res.writeHead(200, { "Content-Type": "text/html;charset=utf-8" });
// 通过请求对象获取完整的请求地址并保存在变量 url 中
let url = req.headers["x-scheme"] + "://" + req.headers.host + req.url;
// 将变量传入实例化方法中,并实例化一个 URL 对象
const myURL = new URL(url);
let params = myURL.searchParams.toString();
res.write(params);
res.end();
});
// 服务侦听 8080 端口
server.listen(8080, () => {
console.log("服务器运行在 8080 端口...");
});

解释如下:

  • 定义一个变量 url 用于保存 req 对象请求的完整地址,其中 req.headers['x-scheme'] 返回请求协议名称,如 httphttps ,而 req.headers.host 返回请求的域名和端口,req.url 返回请求的详细路径,包含查询字符串。
  • 获取并保存完整的请求地址后,实例化一个 URL 对象,在实例化对象的方法中,可以传递两个参数,第一个参数是必选项,表示要解析的绝对或相对的网址,第二个参数是可选项,表示要解析的基础网址,如果第一个参数是绝对地址,则第二个可以省略,如本示例中的代码;如果第一个对数是相对地址,则第二个参数必须添加请求地址的协议名称、域名和端口。
  • 如果将示例中绝对的请求地址修改成相对地址,那么在实例化 URL 对象时,代码修改如下所示:
1
2
3
4
5
6
7
// 通过请求对象获取相对请求路径并保存在变量 url 中
let url = req.url;
// 在实例化过程中,通过第二个参数添加请求协议的名称、域名和端口
const myURL = new URL(
url,
req.headers["x-scheme"] + "://" + req.headers.host
);

这两种代码实现的方式在使用时,结果都是一样的,但在实际运用时,传入完整的请求地址,使用会更多些。

在控制台中输入以下命令运行该程序:

1
node index.js

然后点击右侧的 “Web 服务”,并在 simplelab.cn 地址后加入 ?id=1001&name=abc,刷新页面,效果如下:

::: demo-wrapper no-padding 20250308234403 :::

页面中显示的内容就是以字符串形式输出的 GET 请求参数,如果需要使用,也可以通过指定参数名称获取对应的值。

3.处理 POST 请求

相比于 GET 方式,POST 请求在传输数据时要安全的多,因此常用与数据的添加、删除、修改操作,例如新建博客、删除博客、修改博客等就会使用到 POST 请求。

URL 中输入地址直接访问属于 GET 请求,而 POST 请求则常用于表单数据的提交,先在 form 中设置 method=post,当用户点击提交表单按钮时就会发送一个 POST 请求,并把表单中用户填入的数据传递给服务器。但是手工编写表单的代码显然比较麻烦,有没有一种更加简单的方式发送 POST 请求呢?

答案是有的。可以使用 Postman 这个工具来帮助我们发送 POST 请求。

3.Postman 下载和安装

首先在右侧桌面环境打开 Firefox 浏览器,然后在 URL 地址栏中输入 http://postman.com/downloads 进入到下载页面。系统自动识别下载 linux 版本的 postman

::: demo-wrapper no-padding 20250308235008 :::

然后点击Download the App进行下载,在弹出的界面选择 Linux 64-bit 下载。

20250308234920

单击“保存文件”按钮进行保存,如下图:

20250308235049

点击“打开所在文件夹”,然后右键点击 Postman-linux-x86_64-8.11.1.tar.gz 文件重命名为 postman.tar.gz,然后使用鼠标把该文件拖拽到左侧 shiyanlou 目录下。然后在实验楼路径中点击右键,选择“在此打开终端”(如下图)。

终端中进行解压:

1
sudo tar -zxvf postman.tar.gz

启动 postman:

1
./Postman/Postman

启动后点击左上角的 “File”—“new” 新建一个窗口,效果如下:

::: demo-wrapper no-padding 20250308235315 :::

然后申请一个 Postman 账号并点击 Sign in 登录(此过程不再截图,同学们自行完成)。登录成功后如下图,然后点击 Create new

::: demo-wrapper no-padding 20250308235416 :::

在弹出的界面中点击 HTTP Request,如下图:

::: demo-wrapper no-padding 20250308235439 :::

如果想发送 HTTP 请求还需要装一个 Postman 客户端才能正常发送请求。使用下列命令下载 Postman 客户端。

1
2
cd /home/shiyanlou
wget https://labfile.oss.aliyuncs.com/courses/4380/postmanAgent.tar.gz

注意:如果想复制如上命令到云课桌面环境进行粘贴的话,可以借助右侧“剪切板”功能。 先复制好想要粘贴的命令,然后点击右侧“剪切板”,把复制的内容粘贴到剪切板中,然后点击保存即可(如下图), 然后就可以在云课桌面环境中进行粘贴了。

下载完毕后截图如下:

::: demo-wrapper no-padding 20250308235606 :::

然后使用如下命令进行解压安装:

1
2
cd /home/shiyanlou
sudo tar -zxvf postmanAgent.tar.gz

4.编写代码处理 POST 请求

然后在 /home/shiyanlou/ 路径下新建 index.js 文件,然后编写代码如下:

@tab 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
const http = require("http");

const server = http.createServer((req, res) => {
if (req.method === "POST") {
console.log("content-type:", req.headers["content-type"]); // 获取请求类型 application/json

//读post数据
let postData = ""; // postData 用来存储传递给服务器的全部数据
// 分段循环传输数据,每次传递数据都会执行后面的回调函数
req.on("data", (chunk) => {
postData += chunk.toString(); // chunk是二进制数据 所以要把它转换成字符串
});

// 当数据传输完毕会执行 end 事件后的回调函数
req.on("end", () => {
console.log("postData:", postData);
res.end("Hello"); //在这里返回因为是异步
});
console.log("test"); //这里先被打印 因为上面的代码是异步的
}
});

server.listen(8080, () => {
console.log("服务器运行在 8080 端口...");
});

在终端中运行以下命令运行程序:

1
2
cd /home/shiyanlou
node index.js

5.使用 Postman 发送 POST 请求

然后打开刚才的 Postman 界面,根据下图依次选择 POST 请求,输入请求地址 http://localhost:8080。 然后点击 Body,选择 rawJSON,输入发送的内容后点击 Send 按钮。

20250309000722

当看到上图中的返回内容 Hello 时,说明程序已经调试成功了。

1.介绍

Node.js 是一门服务器编程语言,它也遵循了 ECMAScript 语法规范,在此规范的基础上加入了 Node.js API,包含处理 http 请求、处理文件、socket 编程等。Node.js API 和 ECMAScript 两者结合组成了 Node.js,完成 Server 端的任何操作,为客户端浏览器进行服务。

Node.js 是一门服务器编程语言。它发布于 2009 年 5 月,由 Ryan Dahl 开发,也是一个基于 Chrome V8 引擎的 JavaScript 运行环境,使用了一个事件驱动、非阻塞式 I/O 模型,让 JavaScript 运行在服务端的开发平台,它让 JavaScript 成为与 PHP、Python、Perl、Ruby 等服务端语言平起平坐的脚本语言。

Node.js 有如下特点和优势:

  • 它是一个 JavaScript 运行环境
  • 依赖于 Chrome V8 引擎进行代码解析
  • 事件驱动(event-driven)
  • 非阻塞 I/O(non-blocking I/O)
  • 轻量、可伸缩,适于实时数据交互应用
  • 单进程,单线程

学到这里有的同学可能有些疑惑,这么多特点都是什么意思呢?Node.js 相对于其他服务器语言,例如 Ruby、Java 又有什么区别和优势呢?别着急,马上就会提到。

传统的服务器开发语言(例如 java)是多线程的,例如我们在淘宝商城购物,如果只有一个顾客发送请求,那显然是没问题的。但是当有成百上千万的用户同时访问的时候,我们肯定不希望别人买完以后才能轮到自己购买,这显然要等待很长时间。但是如果 CPU 只有一个核心的时候或者 CPU 只有一个线程的时候,确实要等待别人购买完毕以后才能购买。这就像在饭馆吃饭,如果只有一个厨师的话,同一时间只能给一个顾客做菜,这样必须要等待这一个厨师为别人做完菜以后才能为自己做菜。

为了解决这个问题,我们可以多增加几个厨师来同时为这些顾客做菜,这就是 java 语言的处理方式:使用多线程并发执行,但是这种方式势必会增加聘请厨师的成本和消耗更大的厨房空间,这无疑是一个资源的浪费,而且这种多线程模型,CPU(厨师) 在为客户服务的时候不能做其他的事,例如使用 I/O 读取文件的时候,只能等待文件系统读取文件完毕以后,才能继续做其他事。

为了解决上述问题 Node.js 应运而生。Node.js 是单线程模型、非阻塞 I/O、采用事件循环机制的原理进行处理。如下图:

20250308225452

非阻塞 I/O 的意思就是,文件系统在进行 I/O 操作的时候,Node.js 这个线程还可以做其他的事,当文件系统读取文件完毕时,通过事件的回调函数告诉 Node.js 线程,然后 Node.js 把读取的内容响应给用户。

Node.js 中的 I/O 操作可以理解成生活中的一个幕后工作者,就像在餐馆点餐这个场景中,饭馆可以直接去把点餐的任务派发给外部擅长做菜、煮饭、酿酒的厂家,这些厂家都是非常擅长这些工作的,Node 中也有各种擅长做某件事的模块。这样当这些幕后的厂家完成这些做菜、饭、酒的事件以后,把做完的饭菜酒等食品交给厨师,然后由厨师统一的让服务员把饭菜交给顾客。这样就大大提高了整个餐馆的运营速度。

20250308225621

如果还不明白的话,我们再看下一个例子,Node.js 就像国王,国王每天都在写任务清单,然后派发给大臣。大臣把任务清单交给下面的官员去做,而这个时候国王还是可以继续写任务清单的。当官员完成任务这个事件后,统一的任务结果交给国王(以事件回调函数的形式通知 Node.js)。此事件反复进行,这便是事件循环。所以说除了国王(Node.js)线程以外,每件事都是并行发生的,这便是 Node.js 单线程和事件循环能同时处理多个请求的原理。

2.在win和mac上使用Node.js

Node 官网下载。

20250308230006

官网会根据系统自动推荐对应系统的下载版本。 推荐下载 LTS 长期支持版。

傻瓜式安装完成后,通过下面的指令检查是否成功:

1
2
node - v; // 查看 node 版本
npm - v; // 查看 npm 版本

如果有版本信息则为成功。

需要说明的是:笔记是基于 node v14+ 的版本,低于该版本,请自行更新至对应版本中。

安装 Node 的时候会同时帮我们安装 npm (Node Package Manager)Node 包管理工具,用于下载依赖的 node 包。

3.Linux 版本的 Node 下载和安装

一般我们的Node程序都是部署到Linux服务器上的,所以需要Linux的版本。

一样进入Node的官网,点击下方的Downloads

20250308230857

在弹出的界面中显示的 Node 的全部下载版本,这里我们下载 Linux Binaries x64 版。见下图:

20250308230928

也可以直接用下面的指令下载蓝桥杯云服务器提供的 Linux Node 安装文件。

1
wget https://labfile.oss.aliyuncs.com/courses/4380/node-v14.17.3-linux-x64.tar.xz

然后使用解压命令:

1
tar -xvf node-v14.17.3-linux-x64.tar.xz

解压完毕后就可以把 node-v14.17.3-linux-x64.tar.xz 文件删除了。

用命令对解压后的 node 文件夹进行改名:

1
mv node-v14.17.3-linux-x64 node

改名后文件夹📂名为node

这时还没有安装完成,如果想要安装最新版本,需要使用如下命令:

1
2
cd node/bin
./node -v

注意这里是使用./node 来使用当前路径下的 node 命令。

发现版本为 14.17.3 ,说明我们已经使用了自己下载的最新版本的 Node,但是每次都需要进入到 Node 安装路径的 bin 路径下才能使用 Node 命令,比较麻烦。如何能让我们在系统的任何地方都能使用最新安装的 Node 呢?这里需要修改我们的环境变量 PATH,node/bin 这个路径添加到 PATH 下。

接下来介绍怎么在 Linux 环境修改环境变量 PATH

为了在全局使用最新安装的 Node,我们还需要修改一个配置文件 ~/.zshrc

PATH 作用:当我们在控制台输入命令的时候(例如 node -v),系统是去 PATH 环境变量下配置的路径中寻找这个命令是否存在,查找的路径顺序为从左到右依次查找(linux 路径分隔符为 :)。如果发现对应的 PATH 路径下有 node 命令就会使用,否则就会报错。

首先使用下列命令查看我们的环境变量:

1
env | grep node

VScode终端的效果如下:

20250308231539

发现 PATH 中有 /usr/sbin/nodejs/bin,这个路径就是我们(Lanqiao 云课)实验环境默认安装的 14.15.1 的 Node 安装路径,如果想让我们新安装的 Node 优先执行的话,只需要把新版本 Node 的安装路径下的 bin 路径放到 /usr/sbin/nodejs/bin 路径前即可。

使用下列命令修改 ~/.zshrc

1
vim ~/.zshrc

使用 i 进入插入模式,在文件最后加入如下内容:

1
export PATH=/home/project/node/bin:$PATH

然后输入 ESC 回到普通模式,输入 : 进入命令模式,然后 输入 wq 进行保存。修改完成的文件并不会马上生效,需要使用如下命令让刚才配置的环境变量生效。

1
source ~/.zshrc

最后输入 node -v 重新查看 node 版本就会发现全局的 Node 已经生效。

Mac 用户可能已经发现了,配置环境变量的过程基本和 Mac 上一致,因为 Mac 就是 Linux 系统。

1.什么是事件处理

在 ECharts 的图表中用户的操作将会触发相应的事件,比如点击事件。我们可以使用 on 方法来监听用户触发的事件,通过回调函数做出相应的处理,比如弹出对话框、跳转到另一个地址等操作。

语法格式

1
myChart.on("事件名称", 回调函数);

在 ECharts 中,事件分为两种类型: - 鼠标事件,或者悬浮(hover)图表的图形时触发的事件。 - 交互的组件后触发的行为事件,例如数据区域缩放时触发的 datazoom 事件。

2.鼠标事件

在 ECharts 中,支持的常见鼠标事件有以下几种: - click:点击鼠标时触发。 - dblclick:在同一个元素上双击鼠标时触发。 - mouseup:释放按下的鼠标键时触发。 - mousedown:按下鼠标键时触发。 - mousemove:当鼠标在一个节点内部移动时触发。 - mouseover:鼠标进入一个节点时触发。 - mouseout:鼠标离开一个节点时触发。 - globalout:鼠标移出坐标系触发。 - contextmenu:打开上下文菜单时被触发。

打开蓝桥实验环境病下载文件:

1
2
wget https://labfile.oss.aliyuncs.com/courses/5788/echarts.js
wget https://labfile.oss.aliyuncs.com/courses/3774/jquery-3.6.0.min.js

index.html中写入内容: :::: code-tabs @tab index.html

:collapsed-lines
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="echarts.js"></script>
<script src="jquery-3.6.0.min.js"></script>
<title>事件触发</title>
</head>

<body>
<div id="main" style="width:600px; height:400px;"></div>
<script>
var chartDom = document.getElementById("main");
var myChart = echarts.init(chartDom);
var option = {
series: [
{
name: "人气",
type: "pie",
radius: "50%",
data: [
{ value: 35, name: "数据库" },
{ value: 48, name: "后端开发" },
{ value: 24, name: "信息安全" },
{ value: 30, name: "人工智能" },
],
},
],
};
myChart.setOption(option);
// 处理点击事件并且跳转到相应的课程页面
myChart.on("click", function (params) {
console.log(params.name);
window.open("https://www.lanqiao.cn/courses/?category=" + params.name);
});
</script>
</body>
</html>
::::

点击图表后会发现打开了响应的浏览器窗口。

在上面的代码中,我们用params.name得到了点击的区域名,用window.open来打开了对应的课程页面。

其他的事件也是类似的用法,可以参考官方文档

在之前的例子中,我们一直将数据直接填入setOption配置项中,那么当我们的数据项很大的时候,这样写就不太完美了。

所以我们可以使用jQuery等工具来异步获取数据,并填入setOption中。

1.数据加载

本节我们来基于 jQuery 获取数据。

  • 数据要单独放入 .json 文件中
  • 使用 jQuery 中的 get 方法来获取数据

首先,使用以下命令获取实验需要的 .json 文件。

1
wget https://labfile.oss.aliyuncs.com/courses/10532/data.json

使用以下命令获取 ECharts 和 jQuery 文件。

1
2
wget https://labfile.oss.aliyuncs.com/courses/5788/echarts.js
wget https://labfile.oss.aliyuncs.com/courses/3774/jquery-3.6.0.min.js

获取了图表需要的数据后,我们使用 jQuery 中的 get 方法来获取 data.json 文件中的数据。回忆一下,语法格式如下:

1
$.get(url, data, callback(data, status, xhr), dataType);
然后,新建一个 index.html 文件,在文件中写入以下内容:

:collapsed-lines
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="echarts.js"></script>
<script src="jquery-3.6.0.min.js"></script>
<title>异步加载数据</title>
</head>

<body>
<div id="main" style="width:600px; height:400px;"></div>
<script type="text/javascript">
var myChart = echarts.init(document.getElementById("main"));
$.get(
"data.json",
function (data) {
myChart.setOption({
title: {
text: "电影类型",
left: "center",
},
tooltip: {
trigger: "item",
},
legend: {
orient: "vertical",
left: "left",
},
series: [
{
type: "pie",
radius: "55%",
data: data.data_pie, // 数据
},
],
});
},
"json"
);
</script>
</body>
</html>

效果如下:

::: center 20250306193655 :::

尽管已经很方便了,但是还存在一个问题。那就是日常生活中的数据都是动态变化的,如何让图标实时更新呢?

2.数据更新

从上面的学习中我们知道了,数据的加载都是在 setOption 配置项中完成的。

其实除了加载数据,设置图表实例的配置项、数据、万能接口、所有参数和数据的修改都可以通过 setOption 来完成,ECharts 会合并新的参数和数据,然后刷新图表。如果开启动画配置项的话,ECharts 会找到两组数据之间的差异,然后通过合适的动画去表现数据的变化。

比如我们来看个例子:

:collapsed-lines
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="echarts.js"></script>
<script src="jquery-3.6.0.min.js"></script>
<title>更新数据</title>
</head>

<body>
<div id="main" style="width:600px; height:400px;"></div>
<!--绑定点击事件 updateData-->
<button onclick="updateData()">更新数据</button>
<script type="text/javascript">
var nums = [25, 5, 4, 35, 12, 30];
var myChart = echarts.init(document.getElementById("main"));
option = {
xAxis: {
type: "category",
data: ["喜剧片", "恐怖片", "爱情片", "科幻片", "纪录片", "动画片"],
},
yAxis: {
type: "value",
},
series: [
{
data: nums,
type: "bar",
},
],
};
myChart.setOption(option);
function updateData() {
// 生成随机数
var num = Math.floor(Math.random() * 10);
for (var i in nums) {
nums[i] = nums[i] + num;
}
myChart.setOption(option); // 绘制新图表
}
</script>
</body>
</html>

这段代码采用函数updateDate()来更新图表📊,其实我们可以封装一个setInterval(),更加直观。

1.柱状图

ECharts 的优点之一就是:我们不用从 0 开始写代码,只需要在官方文档中找到相似度高的示例,然后在示例代码的基础上进行修改即可。

比如我们在官网找到带背景色的柱状图: ::: center 20250304213540 :::

这里就不扯那么多了,直接来讲讲代码中的陌生配置项目:

color是绘制图表的调色盘的颜色列表,如果系列没有设置颜色,则会依次循环从该列表中取颜色作为系列颜色。 默认为:

1
['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc']

也就是多个系列时依次显示的颜色: ::: demo-wrapper no-padding 20250304214726 :::

  • backgroundStyle 用于设置每一个柱条的背景样式,需要将 showBackground 设置为 true 时才会生效。

1.1 多列柱状图

有时候我们需要在同一个轴点上进行多列数据的对比,比如下图不同国家男女人口的数量统计。

20250304215255

我们可以试着在官网中找找对应的模板:发现可以用折柱混合图为模版。

20250306115651

将代码修改为:

:collapsed-lines
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
option = {
tooltip: {
trigger: "axis",
axisPointer: {
// 坐标轴指示器配置项
type: "cross",
crossStyle: {
color: "red",
},
},
},
toolbox: {
feature: {
dataView: { show: true, readOnly: false },
saveAsImage: { show: true }, // 保存图片
},
},
legend: {
data: ["男", "女"], // 图例的数据数组
},
xAxis: [
{
type: "category",
data: ["中国", "美国", "古巴", "印度", "巴西", "不丹", "德国"],
axisPointer: {
type: "shadow",
},
},
],
yAxis: [
{
type: "value",
name: "人口数量",
min: 0,
max: 8000,
interval: 1000,
axisLabel: {
formatter: "{value} 万",
},
},
],
series: [
{
name: "男",
type: "bar",
data: [7113, 1619, 2340, 6987, 1046, 3783, 4145],
},
{
name: "女",
type: "bar",
data: [6787, 1660, 2049, 6545, 1080, 4260, 3989],
},
],
};

修改的配置项说明如下: - tooltip.axisPointer是配置坐标轴指示器的全局共用设置,也就是说里面包含的属性都是和坐标轴相关的设置。 - tooltip.axisPointer.type是指示器类型,包含 line(直线指示器)、shadow(阴影指示器)、none(无指示器)、cross(十字准星指示器)这四种类型。 - tooltip.axisPointer.crossStyle.color用于设置线的颜色 - legend.data是图例的数据数组 - xAxis.axisPointer.type是指示器类型,包含 line(直线指示器)、shadow(阴影指示器)、none(无指示器)这三种类型。 - yAxis.min 用于设置 y 轴的最小值。 - yAxis.max 用于设置 y 轴的最大值。 - yAxis.axisLabel.formatter 是刻度标签的内容格式器。

1.2 堆积条形图

有时候,我们想要在某些大类中将它们各自包含的小类也突出显示出来,这时候就可以使用堆积柱状图来表现。

堆积柱状图就是一个系列的数值“堆积”在另一个系列上,而从表达总量的变化。

分析好效果图的结构,我们在官网上,找到堆叠条形图,拷贝代码到 index2.html 文件中,对代码进行修改。

20250306130144

修改代码

:collapsed-lines
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
option = {
title: {
text: "男女就业比",
},
tooltip: {
trigger: "axis",
axisPointer: {
type: "shadow",
},
},
legend: {},
grid: {
left: "3%",
right: "4%",
bottom: "3%",
containLabel: true, // grid 区域是否包含坐标轴的刻度标签
},
xAxis: {
type: "value",
min: 0,
max: 100,
splitLine: {
show: false, // 是否显示分隔线
},
axisLabel: {
formatter: "{value} %",
show: false, // 不显示刻度标签
},
},
yAxis: {
type: "category",
axisLine: false, // 是否显示刻度线
inverse: true, // 是否反向坐标轴
data: ["前端", "后端", "运维", "测试", "UI", "AI"],
},
series: [
{
barWidth: 30, // 设置柱子的宽度
name: "男",
type: "bar",
stack: "total",
label: {
show: true,
},
emphasis: {
focus: "series", // 高亮的图表样式和标签样式
},
itemStyle: {
color: "#a2d2ff", // 设置柱子的颜色
borderRadius: [6, 0, 0, 6], // 设置圆角边框
},
data: [30, 65, 60, 25, 20, 35],
},
{
name: "女",
type: "bar",
stack: "total",
label: {
show: true,
},
emphasis: {
focus: "series",
},
itemStyle: {
color: "#b1e693",
borderRadius: [0, 6, 6, 0],
},
data: [60, 25, 20, 75, 70, 15],
},
],
};

  • grid.containLabel 设置 grid 区域是否包含坐标轴的刻度标签,默认为 false。
  • xAxis.splitLine.show 设置是否显示分隔线。
  • xAxis.axisLabel.show 设置是否显示刻度。
  • yAxis.axisLine.show 设置是否显示坐标轴线。
  • yAxis.inverse 设置是否反向坐标轴。
  • series-bar.barWidth 设置条柱的宽度。
  • series-bar.emphasis.focus 设置高亮的图表样式和标签样式,在高亮图表时,它支持三种配置:none 不- 淡出其它图表,默认使用该配置;self 只聚焦(不淡出)当前高亮的数据的图表;series 聚焦当前高亮的- 数据所在的系列的所有图表。
  • series-bar.itemStyle.color 设置图表的颜色,如果不设置颜色,就默认从调色盘依次取色。
  • series-bar.itemStyle.borderRadius 设置圆角半径

2.折线图

::: demo-wrapper no-padding 20250306131933 :::

我们先分析一下效果图的样式和结构,到官网示例中找一个类似的线图,通过比较,发现堆叠面积图比较接近。

修改代码:

:collapsed-lines
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
option = {
tooltip: {
trigger: "axis",
axisPointer: {
type: "cross",
label: {
backgroundColor: "#6a7985",
},
},
},
legend: {
data: ["播放量", "转发量"],
},
grid: {
left: "3%",
right: "4%",
bottom: "3%",
containLabel: true,
},
xAxis: [
{
type: "category",
boundaryGap: false,
data: [
"01",
"02",
"03",
"04",
"05",
"06",
"07",
"08",
"09",
"10",
"11",
"12",
"13",
"14",
"15",
],
},
],
yAxis: [
{
type: "value",
},
],
series: [
{
name: "播放量",
type: "line",
stack: "Total",
areaStyle: {},
emphasis: {
focus: "series",
},
data: [30, 40, 30, 40, 30, 40, 10, 60, 35, 24, 55, 40, 30, 40, 50],
},
{
name: "转发量",
type: "line",
stack: "Total",
areaStyle: {},
emphasis: {
focus: "series",
},
data: [50, 30, 55, 40, 15, 60, 30, 70, 20, 10, 50, 60, 50, 30, 20],
},
],
};

2.1 定制线图

将上面代码的文字颜色进行更改:

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
var option = {
// 这不是完整代码,只给出了代码的修改部分
tooltip: {
trigger: 'axis',
axisPointer: {
lineStyle: {
color: '#dddc6b',
},
},
},
legend: {
top: '0%',
textStyle: {
color: 'rgba(255,255,255,.5)',
fontSize: '12',
},
},
grid: {
left: '10',
top: '30',
right: '10',
bottom: '10',
containLabel: true,
},
};

2.2 X轴相关设置

修改代码:

:collapsed-lines
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
<script>
var option = {
// 只给出修改部分的代码
xAxis: [
{
type: 'category',
boundaryGap: false, // 去除轴内间距
axisLabel: {
textStyle: {
color: 'rgba(255,255,255,.6)', // 文本颜色
fontSize: 12,
},
},
axisLine: {
lineStyle: {
color: 'rgba(255,255,255,.2)', // X 轴线颜色
},
},
data: [
'01', '02', '03',
'04', '05', '06',
'07', '08', '09',
'10', '11', '12',
'13', '14', '15',
]
},
],
};
</script>

在上面代码中,xAxis.boundaryGap 是坐标轴两边留白策略,在类目轴和非类目轴的设置和表现是不一样的。

  • 在类目轴中,boundaryGap 配置为布尔类型,当设置为 true 时,坐标轴上的刻度只会作为分隔线,标签和数据会处于两个刻度之间的中间。
  • 在非类目轴中,boundaryGap 配置为具有两个值的数组,这两个值代表数据的最大值和最小值的延伸范围。

2.3 Y 轴相关设置

:collapsed-lines
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
<script>
var option = {
// 只给出修改部分代码
yAxis: [
{
type: 'value',
axisTick: { show: false }, // 去除刻度
axisLine: {
lineStyle: {
color: 'rgba(255,255,255,.1)', // Y 轴线颜色
},
},
axisLabel: {
textStyle: {
color: 'rgba(255,255,255,.6)', // 文字颜色
fontSize: 12, // 文字大小
},
},

splitLine: {
lineStyle: {
color: 'rgba(255,255,255,.1)', // 分割线颜色
},
},
},
],
};
</script>

修改后效果如下: ::: demo-wrapper no-padding 20250306140225 :::

2.4 修改两个线模块配置

最终效果: ::: demo-wrapper no-padding 20250306143415 :::

根据需求我们去官方文档查找相应的配置。

  • series-line.smooth 设置是否平滑曲线显示。

  • series-line.areaStyle 设置区域填充样式。

  • series-line.areaStyle.color 设置填充的颜色,包括不同的渐变。

  • series-line.showSymbol 是否显示 symbol,也就是图上的数据点,默认为 true。

修改后代码如下:

:collapsed-lines
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
<script>
var option = {
// 你给出修改部分代码
series: [
{
name: '播放量',
type: 'line',
smooth: true, // 线是圆滑
symbol: 'circle', // 设置拐点为小圆点
symbolSize: 5, // 拐点大小
showSymbol: false, // 开始不显示拐点, 鼠标经过显示
// 单独修改线的样式
lineStyle: {
normal: {
color: '#0184d5',
width: 2,
},
},
// 填充区域
areaStyle: {
normal: {
// 渐变色,只需要复制即可
color: new echarts.graphic.LinearGradient(
0,
0,
0,
1,
[
{
offset: 0,
// 渐变色的起始颜色
color: 'rgba(1, 132, 213, 0.4)',
},
{
offset: 0.8,
// 渐变线的结束颜色
color: 'rgba(1, 132, 213, 0.1)',
},
],
false
),
shadowColor: 'rgba(0, 0, 0, 0.1)',
},
},
// 设置拐点颜色以及边框
itemStyle: {
normal: {
color: '#0184d5',
borderColor: 'rgba(221, 220, 107, .1)',
borderWidth: 12,
},
},
data: [120, 132, 101, 134, 90, 230, 210],
},
{
name: '转发量',
type: 'line',
smooth: true,
symbol: 'circle',
symbolSize: 5,
showSymbol: false,
lineStyle: {
normal: {
color: '#00d887',
width: 2,
},
},
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(
0,
0,
0,
1,
[
{
offset: 0,
color: 'rgba(0, 216, 135, 0.4)',
},
{
offset: 0.8,
color: 'rgba(0, 216, 135, 0.1)',
},
],
false
),
shadowColor: 'rgba(0, 0, 0, 0.1)',
},
},
itemStyle: {
normal: {
color: '#00d887',
borderColor: 'rgba(221, 220, 107, .1)',
borderWidth: 12,
},
},
data: [220, 182, 191, 234, 290, 330, 310],
},
],
};
</script>

实现效果如下: ::: demo-wrapper no-padding 20250306143631 :::

3.饼形图

3.1 南丁格尔图

我们先分析一下效果图的图样式和结构,到官网示例中找一个类似的线图,通过比较,选择了基础南丁格尔玫瑰图

20250306143821

南丁格尔图又称玫瑰图,通常用弧度相同但是半径不同的扇形表示各个类目。

修改代码如下:

:collapsed-lines
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
option = {
color: [
"#7DCEA0",
"#A9DFBF",
"#F9E79F",
"#F4D03F",
"#B7950B",
"#F8C471",
"#E59866",
"#E67E22",
"#D68910",
"#F1948A",
"#E74C3C",
],
title: {
text: "全球新冠疫情部分数据",
left: "center",
textStyle: {
color: "#fff",
},
},
legend: {
top: "bottom",
},
series: [
{
name: "面积模式",
type: "pie",
radius: [50, 250], // 饼图的半径。
center: ["50%", "50%"], // 饼图中心坐标
roseType: "area", // 设置是否展示成南丁格尔图
itemStyle: {
borderRadius: 8, //
},
data: [
{ value: 109, name: "巴林" },
{ value: 117, name: "马来西亚" },
{ value: 160, name: "新加坡" },
{ value: 176, name: "挪威" },
{ value: 239, name: "比利时" },
{ value: 248, name: "瑞典" },
{ value: 319, name: "英国" },
{ value: 321, name: "荷兰" },
{ value: 337, name: "瑞士" },
{ value: 522, name: "日本" },
{ value: 572, name: "美国" },
],
},
],
};

陌生配置说明如下: - series.radius 设置饼形的半径。 - series.center 设置饼图的中心(圆心)坐标,数组的第一项是横坐标,第二项是纵坐标。 - series.roseType 设置是否展示成南丁格尔图,通过半径区分数据大小,有两种模式: - radius 区圆心角展现数据的百分比,半径展现数据的大小。 - area 所有扇区圆心角相同,仅通过半径展现数据大小。 - series.itemStyle.borderRadius 用于指定饼图扇形区块的内外圆角半径。

最终效果图: ::: demo-wrapper no-padding 20250306144750 :::

3.2 定制饼状图

我们来看一下当前效果(左图)与最终效果(右图)的对比图。 ::: demo-wrapper no-padding 20250306145037 :::

先来讲一下相关配置: - legend.itemWidthlegend.itemHeight 分别是图例标记的图表宽度和高度,也就是最下方小图标的宽高。 - series-pie.labelLine.length 是视觉引导线第一段的长度。 - series-pie.labelLine.length2 是视觉引导项第二段的长度。

4.散点图

我们先分析一下效果图的图样式和结构,到官网示例中找一个类似的线图,通过比较,选择了 AQI 气泡图

20250306145935
  • tooltip.backgroundColor 是提示框浮层的背景颜色。 tooltip.formatter 是用来设置提示浮层内容显示的格式,它支持字符串模板和回调函数两- 种形式。
  • xAxis.nameGap 用于设置坐标轴名称与轴线之间的距离,默认值为 15。
  • xAxis.nameTextStyle.fontsize 是用来设置坐标轴名称文字的字体大小,默认值为 12。
  • xAxis.nameLocation 是用来设置坐标轴名称显示位置,可选值有 start(开头)、- middlecenter(居中)、end(末尾)。
  • visualMap 是视觉映射组件。
  • visualMap.leftvisualMap.top 设置 visualMap 组件离容器左侧和顶部的距离。
  • visualMap.dimension 用来指定数据的哪个维度映射到视觉元素上。
  • visualMap.minvisualMap.max 用来设置 visualMap 组件允许的最小值和最大值。

visualMap.itemWidthvisualMap.itemHeight 设置 visualMap 组件图表的宽度和- 度。 - visualMap.calculable 设置是否显示拖拽用的手柄(手柄能拖拽调整选中范围)。 - visualMap.precision 是设置数据展示的小数精度,默认为 0,无小数点。

  • visualMap.textvisualMap.textGap 用来设置 visualMap 组件图表上两端的文字- 设置文字与图表之间的距离。
  • visualMap.inRange.symbolSize 设置选中范围内散点的大小。
  • visualMap.outOfRange.symbolSize 设置选中范围外散点的大小。
  • visualMap.outOfRange.color 设置选中范围外散点的颜色。
  • visualMap.controller 是 visualMap 组件中,控制器的 inRangeoutOfRange 设置。

详见官方文档

4.1 定制散点图

左边是最终效果,右边是当前效果: ::: demo-wrapper no-padding 20250306152404 :::

修改代码;

:collapsed-lines
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
var option = {
// 只给出修改部分的代码
backgroundColor: "#2e4c6d",
color: ["#ffC4E1", "#71DFE7"],
legend: {
data: ["晴天", "雨天"],
textStyle: {
color: "#fff",
fontSize: 16,
},
},
xAxis: {
nameTextStyle: {
color: "#fff",
fontSize: 14,
},
axisLine: {
lineStyle: {
color: "#777",
},
},
axisTick: {
lineStyle: {
color: "#777", // 刻度线的颜色
},
},
axisLabel: {
formatter: "{value}",
textStyle: {
color: "#fff",
},
},
},
yAxis: {
name: "心情指数",
nameTextStyle: {
color: "#fff",
fontSize: 16,
},
axisLine: {
lineStyle: {
color: "#777",
},
},
axisTick: {
lineStyle: {
color: "#777",
},
},
splitLine: {
show: false,
},
axisLabel: {
textStyle: {
color: "#fff",
},
},
},
visualMap: [
{
left: "right", // 组件在图的右侧显示
top: "40%", // 组件与顶部的距离
dimension: 2, // 映射数据的维度
itemWidth: 30, // 组件的宽度
itemHeight: 120, // 组件的高度
calculable: true, // 是否显示拖拽用的手柄
precision: 0.1, // 数据展示的小数精度
text: ["指数范围"], // 组件的文本内容
textGap: 30, // 两端文字主体之间的距离
textStyle: {
color: "#fff",
},
controller: {
inRange: {
color: ["#77e4d4"],
},
},
},
],
series: [
{
name: "晴天",
},
{
name: "雨天",
},
],
};

效果:

::: demo-wrapper no-padding 20250306152511 :::

0%