SHEN YING

Syの小窝

1.ECharts 实例

一个网页可以创建多个ECharts实例,每个实例可以创建多个图表和坐标系等(且使用option来描述)。准备一个 DOM 节点作为实例的渲染容器,每个容器创建一个 ECharts 实例。每个实例独占一个 DOM 节点。

20250303211726

我们随便打开官网的一个实例代码就能发现都是类似的结构: 20250303212249

option相当于存放组件的容器,在option中的seriesxAxisyAxis都被叫做组件。

2.组件

==Echarts==中的内容都被抽象成了==组件==。比如,series是专门绘制“图”的组件。

类似的组件还有 xAxis(直角坐标系 X 轴)、yAxis(直角坐标系 Y 轴)、grid(直角坐标系底板)、angleAxis(极坐标系角度轴)、radiusAxis(极坐标系半径轴)、polar(极坐标系底板)、geo(地理坐标系)、dataZoom(数据区缩放组件)、visualMap(视觉映射组件)、tooltip(提示框组件)、toolbox(工具栏组件)、series(系列)等。

请看概念图:

20250303212645

option中申明了各个组件。

注意:因为系列是一种特殊的组件,所以有时候也会出现 “组件和系列” 这样的描述,这种语境下的 “组件” 是指:除了 “系列 series” 以外的其他组件。

那么下面就为大家介绍一下常见的组件,包括 seriesdatasetxAxisyAxisgridtooltiptitlelegend

不需要把这些组件都记下来,大致有个印象,知道它们的主要作用就好了,后续在代码练习中才不会完全懵逼。

3.series

==系列==(series)是很常见的名词。

20250303213120

它表示一组数据以及数据代表的图,所以不仅要有数据还需要用series.type表示图表类型。

其中,系列类型(series.type)至少有:line(折线图)、bar(柱状图)、pie(饼图)、scatter(散点图)、graph(关系图)、tree(树图)等等。

这里只讲讲最常用的四个: - 折线图 - 柱状图 - 饼形图 - 散点图

比如,将代码中的series.type改为pie: :::: 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
40
41
42
43
44
45
46
<!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>
<title>ECharts 快速上手</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;

option = {
xAxis: {
type: "category",
data: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
},
yAxis: {
type: "value",
},
series: [
{
data: [820, 932, 901, 934, 1290, 1330, 1320],
type: "pie",
smooth: true,
},
],
};

option && myChart.setOption(option);
</script>
<style>
* {
margin: 0;
padding: 0;
}
#main {
margin: 20px;
}
</style>
</body>
</html>

::::

效果: ::: demo-wrapper no-padding 20250303215556 :::

series中添加一个新的对象后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
series: [
{
name: 'Sy_blog',
data: [1210, 230, 120],
type: 'line',
smooth: true
},
{
name: 'Sy_blog',
data: [152, 23, 69],
type: 'line',
smooth: true
},
]

::: demo-wrapper no-padding 20250304145220 :::

常用的还有: - series.name 是系列的名字 - series.stack 是数据堆叠,后一个系列的值会在前一个系列的堆叠基础上增加

::: demo-wrapper no-padding 20250304145644 :::

4.dataset

虽然每个系列可以用series.data设置数据,将数据一条一条放在series中。但在 ECharts4 之后开始支持数据集了,可以用其来管理数据。

这是两者在写法上的区别: 20250304150715

5.用option描述图表

上面已经提到了option的概念,使用option能对图表做大部分的配置。

6.title

option中添加option.title.text:

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
var option = {
title: { // [!code focus:3]
text: "Hello World"
},
xAxis: {
type: "category",
data: ["1", "2", "3"]
},
yAxis: {
type: 'value'
},
series: [
{
name: 'Sy_blog',
data: [1210, 230, 120],
type: 'line',
smooth: true,
},
{
name: 'Hello',
data: [152, 23, 698],
type: 'line',
smooth: true,
},
]
}

更多option.title文档

7.tooltip

option中添加:

1
2
3
tooltip: {
trigger: 'axis'
},

鼠标悬浮时会产生提示信息。

::: demo-wrapper no-padding 20250304204525 :::

这里的的trigger指触发类型,包括:

  • item:数据项图形触发,主要在散点图,饼图等无类目轴的图表中使用。
  • axis:坐标轴触发,主要在柱状图,折线图等会使用类目轴的图表中使用。
  • none:什么都不触发。

8.legend 图例组件

legend.data是图例的数据数组,也就是红框框起来的值。 ::: demo-wrapper no-padding 20250304204743 :::

9.toolbox 工具栏

toolbox 工具栏,内置有导出图片(saveAsImage)、数据视图(dataView)、动态类型切换(magicType)、数据区域缩放(dataZoom)、重置(restore)等五个工具。

例如:

1
2
3
4
5
toolbox: {
feature: {
saveAsImage: {}
}
},

toolbox.feature.saveAsImage 是保存为图片,也就是这个: ::: demo-wrapper no-padding 20250304205956 :::

继续增加一个dataView

1
2
3
4
5
6
toolbox: {
feature: {
saveAsImage: {},
dataView: {}
}
},

右上角将产生一个可以预览数据的文档图表。

10.坐标轴

很多系列,例如 line(折线图)、bar(柱状图)、scatter(散点图)、heatmap(热力图)等等,需要运行在 “坐标系” 上。坐标系用于布局这些图,以及显示数据的刻度等等。例如 ECharts 中至少支持这些坐标系:直角坐标系、极坐标系、地理坐标系(GEO)、单轴坐标系、日历坐标系等。

一个坐标系,由多个组件组成,就拿最常用的直角坐标系来举个例子:

在 ECharts 的直角坐标系中,有三个重要的组件,分别为:

  • xAxis:直角坐标系 X 轴。
  • yAxis:直角坐标系 Y 轴。
  • grid:直角坐标系网格。

前两者都好理解,那么网格是干什么的呢?

网格(grid)是定义网格布局、大小和颜色的组件,用于定义直角坐标系整体的布局。

例如:

20250304211204 :::steps 1. 在绘制图表之前,我们肯定需要一个坐标区域:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
option = {
xAxis: {
type: "category",
data: ["周一", "周二", "周三", "周四", "周五", "周六", "周天"],
},
yAxis: {
type: "value",
},
series: [
{
data: [0, 0, 0, 0, 0, 0, 0],
type: "line",
},
],
grid: {
show: true,
},
};

20250304211323 2. 然后,在系列(series)中写入一些非 0 值,这样折线就绘制出来了。

1
2
3
4
5
6
series: [
{
data: [3,3,4,2,5,4,5],
type: 'line'
}
],
20250304211644 :::

10.1 grid

网格(gird)有几个常用的属性: - show 是否显示直角坐标系网格。 - leftgrid 组件离容器左侧的距离。 - topgrid 组件离容器上侧的距离。 - rightgrid 组件离容器右侧的距离。 - bottomgrid 组件离容器下侧的距离。

option中加入观察其变化:

1
2
3
4
5
6
7
 grid: {
show: true,
left: 200,
top: 100,
right: 200,
bottom: 100
}

10.2 xAxis 和 yAxis

我们来看看上面例子中的xAxisyAxis属性吧。

type是坐标轴的类型,分为:

  • value 是数值轴,适用于连续数据。
  • category 是类目轴,适用于离散的类目数据。
  • time 是时间轴,适用于连续的时序数据,与数值轴相比时间轴带有时间的格式化,在刻度计算上也有所- 不同。
  • log 是对数轴。适用于对数数据。

xAxis.data 是类目数据。

需要注意的是,它只在类目轴(type: 'category')中有效。如果没有设置 type,但是设置了 axis.data,则认为 type'category'。 如果设置了 type'category',但没有设置 axis.data,则 axis.data 的内容会自动从 series.data 中获取。

一个思维导图来总结本文内容

20250304213014

如果记不住这些配置项和属性都没关系,因为 ECharts 的使用方法就是去查官方文档,在它给出的代码上进行修改,从而定制出我们想要的图表。

ECharts是一个底层依赖于ZRender矢量图形库的JavaScript,可以用于快速构建矢量图形库。 ## 什么是 ECharts

在没有 ECharts 的年代,公司的图表业务都是用 flash 去实现的,当时的前端工程师并不负责这一块,而是由专门的图标工程师来完成。这就造成了大量的沟通成本,因为在数据接口设计上,前端工程师需要和做图表的同事进行沟通。

在这样的背景下,百度团队在 2012年8月立项,开发了一款数据可视化工具,所以 ECharts 最初诞生是为了满足公司的各种业务报表需求。

在 2013 年 6 月,ECharts 发布了 1.0 版本随着不断地迭代更新,截止本实验发布(2021 年 12 月)为止,ECharts 的最新版本是 5.2.0。

:::: demo-wrapper no-padding 20250303204659 ::::

ECharts 提供了常规的==折线图、柱状图、散点图、饼图==等,用于统计的盒形图,用于地理数据可视化的地图、热力图、线图,用于关系数据可视化的关系图、treemap、旭日图,多维数据可视化的平行坐标,还有用于 BI 的漏斗图,仪表盘,并且支持图与图之间的混搭。

除了已经内置的包含了丰富功能的图表,ECharts 还提供了自定义系列,只需要传入一个 renderItem 函数,就可以从数据映射到任何你想要的图形,更棒的是这些都还能和已有的交互组件结合使用而不需要操心其它事情。

在官网上还为大家提供了许多不同类型的可视化图表以及炫酷的数据可视化示例。

最有益于大家的地方是它提供了简单的操作、直观的结构、内置的数据源。你能够轻松的找到你想要的图表然后修改它,以此做出一个成品。

::: demo-wrapper no-padding 20250303205211 :::

简直是==炫酷==有没有。

总之,ECharts 好处多多,主要有以下六种特性:

丰富的图表类型。 专业的数据分析。 健康的开源社区。 强劲的渲染引擎。 优雅的可视化设计。 友好的无障碍访问。

获取 ECharts 的方式

学到这个阶段了,想要回答这个问题我是这个表情: ::: center 20250303205344 :::

官方文档提供了多种方式

  • 从 GitHub 获取
  • 从 npm 获取
  • 从 CDN 获取
  • 在线定制

本笔记主要贴合蓝桥杯比赛方式,使用CDN来讲解。

从 CDN 获取

可以从以下免费 CDN 中获取和引用 ECharts。

使用方法

::::steps 1. 完成后创建一个index.html文件,在文件中写入: ::: code-tabs @tab index.html

1
2
3
4
<body>
<!-- 为 ECharts 准备一个宽为 600px,高为 400px 的 DOM -->
<div id="main" style="width: 600px;height:400px;"></div>
</body>
:::

这一步为图表准备了容器。 2. 在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
40
41
42
43
44
45
46
47
<!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>
<title>ECharts 快速上手</title>
</head>
<style>
* {
margin: 0;
padding: 0;
}
#main {
margin: 20px;
background-color: rgb(228, 255, 192);
}
</style>
<body>
<!-- 为 ECharts 准备一个宽为 600px,高为 400px 的 DOM -->
<div id="main" style="width:600px;height:400px;"></div>
</body>

<script>
var chartDom = document.getElementById("main");
// 初始化实例对象 echarts.init(dom) 容器;
var myChart = echarts.init(chartDom);
// 指定配置项和数据
var option = {
xAxis: {
type: "category",
data: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
},
yAxis: {
type: "value",
},
series: [
{
data: [150, 230, 224, 218, 135, 147, 260],
type: "line",
},
],
};
// 将配置项设置给 echarts 实例对象。
myChart.setOption(option);
</script>
</html>
::: ::::

打开LiveServer可以看到效果。

接下来将用Pinia提供的全局数据共享功能来制作一个购物车项目。

项目结构: ::: file-tree - components - CardList.js - Products.js - js - store.js - products.json - lib - axios.js - pinia.min.js - vue.min.js - vueDemi.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
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="./lib/vue.min.js"></script>
<script src="./lib/vueDemi.js"></script>
<script src="./lib/axios.js"></script>
<script src="./components/ProductList.js"></script>
<script src="./lib/pinia.min.js"></script>
<script src="./js/store.js"></script>
<script src="./components/CartList.js"></script>

<title>淘购</title>
</head>
<body>
<div id="app">
<div id="app">
<h1 class="title">淘购</h1>
<div class="container">
<product></product>
<cart></cart>
</div>
</div>
</div>

<script type="module">
const { createApp} = Vue
const { createPinia } = Pinia
const app = Vue.createApp({});
app.use(createPinia())
app.component("product", ProductList)
app.component("cart", CartList)
app.mount('#app');
</script>
</body>
</html>


<style>
/* App.vue styles */
#app {
font-family: Arial, sans-serif;
text-align: center;
}

.title {
font-size: 24px;
margin-bottom: 20px;
}

.container {
display: flex;
justify-content: space-around;
}

/* ProductList styles */
.product-list {
width: 40%;
border: 1px solid #ccc;
padding: 10px;
}

.product-list ul li{
display: flex;
justify-content: space-between;
margin: 5px 0;
padding: 5px;
border: 1px solid #eee;
}

/* CartList styles */
.cart-list {
width: 40%;
border: 1px solid #ccc;
padding: 10px;
}
.cart-list ul li{
display: flex;
justify-content: space-between;
margin: 5px 0;
padding: 5px;
border: 1px solid #eee;
}
.cart-item span,.hero-item span{
width: 80px;
text-align: center;
}
.total {
font-size: 18px;
margin-top: 10px;
}

</style>

@tab CardList.js

: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
const CartList = {
template: `
<div class="cart-list">
<h2>我的购物车</h2>
<ul>
<li>
<span>商品名称</span>
<span>单价</span>
<span>数量</span>
<span>操作</span>
</li>
<li class="cart-item" v-for="cart in store.cart" :key="cart.id">
<span>{{cart.name}}</span>
<span>{{cart.price}}</span>
<span>{{cart.count}}</span>
<button @click="store.removeFromCart(cart)">移除</button>
</li>
</ul>
<p class="total">商品总价:{{store.cart.reduce((sum, item) => sum+item.count*item.price, 0)}} 元 </p>
</div>
`,
setup() {
const store = useProductStore();
return {store}
},
};
@tab store.js
: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
const { defineStore } = Pinia

const useProductStore = defineStore("product", {
state: () =>( {
products: [],
cart: []
}),
getters: {
total() {

}
},
actions: {
addToCart(param) {
let thisProduct = this.products.find(item => item.id == param.id)
thisProduct.inventory--;
let CartItem = this.cart.find(item => item.id == param.id)
if (CartItem) {
CartItem.count++;
} else {
this.cart.push({
id: param.id,
name: param.name,
price: param.price,
count: 1,
})
}
},
removeFromCart(param) {
this.cart.forEach((item, i) => {
if (item.id == param.id) {
let product = this.products.find(item => item.id == param.id)
product.inventory++;
if (item.count > 1) {
item.count--;
} else {
this.cart.splice(i, 1)
}
}
});
}
},
})

@tab ProductList.js

: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
const {onMounted} = Vue
const ProductList = {
template: `
<div class="product-list">
<h2>商品列表</h2>
<ul>
<li>
<span>商品名称</span>
<span>单价</span>
<span>库存</span>
<span>操作</span>
</li>
<li class="product-item" v-for="product in store.products" :key="product.id">
<span>{{product.name}}</span>
<span>{{product.price}}</span>
<span>{{product.inventory}}</span>
<button @click="store.addToCart(product)" :disabled="!product.inventory">
{{product.inventory?'加入购物车':'库存不足'}}
</button>
</li>
</ul>
</div>
`,
setup() {
const store = useProductStore();
onMounted(() => {
axios("./js/products.json").then(res => {
store.products = res.data;
})
})
return {store}
},
};

@tab products.json

1
2
3
4
5
6
[
{ "id": 1, "name": "书包", "price": 200, "inventory": 5 },
{ "id": 2, "name": "水杯", "price": 100, "inventory": 6 },
{ "id": 3, "name": "电脑", "price": 7000, "inventory": 2 },
{ "id": 4, "name": "鼠标", "price": 150, "inventory": 0 }
]
::::

lib中所包含的库文件CDN: :::: code-tabs @tab axios.js

1
https://ccccooh.oss-cn-hangzhou.aliyuncs.com/img/axios.js

@tab pinia.min.js

1
https://ccccooh.oss-cn-hangzhou.aliyuncs.com/img/pinia.min.js

@tab vue.min.js

1
https://ccccooh.oss-cn-hangzhou.aliyuncs.com/img/vue.min.js

@tab vueDemi.js

1
https://ccccooh.oss-cn-hangzhou.aliyuncs.com/img/vueDemi.js
::::

由于托管于我的aliyun OSS,随时可能失效。

从使用的角度来看基本和Vuex一样,但是更加简单。

有四个常用的核心概念:StateGetterMutationAction。没有了Mutation且常用的只有:StateGettersActions(同步、异步、都支持)。

1.Store

创建一个js/store.js文件:在index.html中使用: :::: code-tabs @tab js/store.js

1
2
3
4
5
6
7
8
9
10
const { defineStore } = Pinia; // 引入 defineStore 函数
// 定义一个 id 为 counter 的 Store 实例,最终返回一个调用后可获取该实例的函数,并赋值给 useCounterStore
const useCounterStore = defineStore("counter", {
state: () => ({ // [!code focus:4]
age: 10, // 声明一个状态 age,并赋初始值 10
name: "小蓝", // 声明一个状态 name,并赋初始值“小蓝”
}),
getters: {},
actions: {},
});
@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
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="./lib/vue.min.js"></script>
<script src="./lib/vueDemi.js"></script>
<script src="./lib/pinia.min.js"></script>
<!-- 引入store文件 -->
<script src="./js/store.js"></script>
</head>
<body>
<div id="app">
<h1>姓名: {{ store.name }}</h1>
<h1>年龄: {{ store.age }}</h1>
</div>
<script type="module">
const { createApp } = Vue;
const { createPinia } = Pinia;
const app = Vue.createApp({
// 创建 Vue 实例对象 app
setup() {
const store = useCounterStore(); // 调用 useCounterStore 方法获取 store 对象 counter
return {
store, // 返回 store 对象,方便 DOM 中使用
};
},
});
app.use(createPinia()); // 将 pinia 插件用于 Vue 实例对象 app 中
app.mount("#app");
</script>
</body>
</html>
::::

运行效果: :::: demo-wrapper no-padding ::: center 20250226235009 ::: ::::

2.Getters

Getters可以理解为共享的计算属性,假如需要上面的小蓝的两年后年龄就可以使用Getters

@tab js/store.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const { defineStore } = Pinia;

const useCounterStore = defineStore("counter", {
state: () => ({
age: 10,
name: "小蓝",
}),
getters: { // [!code focus:6]
twoYear: (state) => {
// 定义一个名为 twoYear 的 getter,接收 state 作为第一个参数
return state.age + 2; // 返回基于 state.age 计算后的结果
},
},
actions: {},
});

@tab index.html

1
2
3
4
5
<div id="app">
<h1>姓名: {{ store.name }}</h1>
<h1>年龄: {{ store.age }}</h1>
<h1>两年后年龄为: {{store.twoYear}}</h1> <!-- [!code focus] -->
</div>

3.Actions

如果想要修改原始数据,就需要使用Actions

@tab js/store.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const { defineStore } = Pinia;
const useCounterStore = defineStore("counter", {
state: () => ({
age: 10,
name: "小蓝",
}),
getters: {
twoYear: (state) => {
return state.age + 2;
},
},
actions: { // [!code focus:6]
changeAge() {
// 定义一个函数 changeAge 用于修改 age 的值
this.age++; // 这里的 this 相当于 store.age
},
},
});

@tab index.html

1
2
3
4
5
6
<div id="app">
<h1>姓名: {{ store.name }}</h1>
<h1>年龄: {{ store.age }}</h1>
<h1>两年后年龄为: {{store.twoYear}}</h1>
<button @click="store.changeAge()">年龄+1</button> <!-- [!code focus] -->
</div>

工程化项目中的安装

如果是通过脚手架搭建的项目可以通过NPM或者Yarn安装到项目当中:

1
npm install pinia

CDN 方式引入与使用

如果没有使用脚手架的项目也可以通过CDN来引入 Pinia的js文件来使用。

项目结构: ::: file-tree - pinia1 - js - lib - pinia.min.js # pinia 文件 - vueDemi.js # 一款开发工具。允许你为 Vue 2 和 3 编写通用 Vue 库。而无需担心用户安装的版本。 - vue.min.js # Vue3 文件 :::

接着在pinial1下创建一个index.html文件。

使用!生成模板后引入js文件:

1
2
3
<script src="./lib/vue.min.js"></script>
<script src="./lib/vueDemi.js"></script>
<script src="./lib/pinia.min.js"></script>

使用createPinia()来将其注入到Vue实例中:

1
2
3
4
5
6
7
8
9
10
11
12
...
<div id="app"></div>
...
<script type="module">
// 创建 Vue 实例
const app = Vue.createApp({
setup() {},
});
// 注入 Pinia 对象
app.use(createPinia());
app.mount("#app");
</script>

接下来创建一个store文件: ::: code-tabs @tab js/store.js

1
2
3
4
5
6
7
8
9
const { defineStore } = Pinia; // 引入 defineStore 函数
// 创建 id=counter 的 store
const useCounterStore = defineStore("counter", {
state: () => ({
count: 10, // 声明一个 state count 并初始化为 10
}),
getters: {},
actions: {},
});
:::

在上述代码中创建了一个state, count被赋值为10。

之后,在index.html中引入store.js文件:

1
2
3
4
5
<script src="./lib/vue.min.js"></script>
<script src="./lib/vueDemi.js"></script>
<script src="./lib/pinia.min.js"></script>
<!-- 引入 store 文件 -->
<script src="./js/store.js"></script>

index.html中调用store.js文件的useCounterStore方法获取store对象couter,并把该对象在Vue的setup()中返回:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script type="module">
const { createApp } = Vue
const { createPinia } = Pinia
const app = Vue.createApp({ // 创建 Vue 实例对象 app
setup() {
const store = useCounterStore(); // 调用 useCounterStore 方法获取 store 对象 counter
return {
store // 返回 store 对象,方便 DOM 中使用
}
}
});
app.use(createPinia()) // 将 pinia 插件用于 Vue 实例对象 app 中
app.mount('#app');
</script>

在DOM结构中:

1
2
3
<div id="app">
<h1>{{ store.count }}</h1>
</div>

预览并查看页面即可。

:::: demo-wrapper no-padding ::: center 20250226193628 ::: ::::

Pinia 是什么

Pinia最初是一个实验,用于取代vuex。Vuex主要服务于vue2,目前能使用vuex4来在vue3中使用,但仍然存在很大的缺陷,所以推出了Pinia

为什么叫 Pinia

Pinia(发音为 ==/piːnjʌ/==,类似于英语中的 ==peenya==)是最接近有效包名 piña(西班牙语中的 pineapple )的词,即为菠萝,其 logo 也被设计成了菠萝的模样。

:::: demo-wrapper no-padding ::: center 20250226185944 ::: ::::

菠萝实际上是一组单独的花朵,它们结合在一起形成多个水果。与状态管理器中的概念 Store 类似,每一家都是独立诞生的,但最终都是相互联系的。

Pinia vs Vuex

Pinia 试图尽可能接近 Vuex 的理念。Pinia 的作者 I(Eduardo) 是 Vue 核心团队的一员,并积极参与 Router 和 Vuex 等 API 的设计。

  • ==Vuex 核心概念==:StateGetterMutationActionModule
  • ==Pinia 核心概念==:StateGettersActionsPluginsStores outside of components

不难看出大部分的核心概念都是相似的。

不过相比于VuexPinia可以更好的支持TypeScript并且舍弃了Mutation

关于版本问题

Pinia 既支持 Vue2 也支持 Vue3,一般情况下 Vue2 习惯性使用 Vuex,而 Vue3 使用 Pinia。

Pinia 当前的最新版本是 2.x(截止 2023 年 8 月),而 Vuex 当前的最新版本是 4.x。==Pinia 可以认为就是 Vuex 的第五个版本==,因为它的作者是官方的开发人员,并且==已经被官方接管了==。

Pinia 是新版本的 Vuex,建议在你的项目中直接使用它,尤其是使用了 Vue3+TypeScript 的项目。

Tips: VueRouter 是Vue官方为单应用专门打造的。

接下来看看如何安装VueRouter

分别有使用CDN引入和使用NPM安装两种方式。

@tab CDN

1
https://unpkg.com/vue-router@2.0.0/dist/vue-router.js

@tab NPM

1
npm install vue-router@4

这里采用CDN的方式。

其基本的使用方法是: - 使用router-link组件来导航,通过to来跳转指定链接(相当于<a> </a>标签)。 - 使用router-view组件定义路由出口,路由匹配到组件将会渲染到此处。 - 使用const routes = [{ path, component }]来定义路由(路径和组件名)。 - 创建和挂载根实例,在 new Vue 中挂载上一步创建的路由实例 router

首先用命令获取库文件:

1
2
wget https://labfile.oss.aliyuncs.com/courses/1262/vue.min.js
wget https://labfile.oss.aliyuncs.com/courses/10532/vue-router.js

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
44
45
46
47
48
<!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>
<script src="vue.min.js"></script>
<script src="vue-router.js"></script>
</head>

<body>
<div id="app">
<h1>路由的使用</h1>
<p>
<!-- 使用 router-link 组件来导航 -->
<router-link to="/home">首页</router-link>
<router-link to="/hot">热门</router-link>
<router-link to="/class">分类</router-link>
</p>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
<script>
const Home = { template: "<div>首页</div>" };
const Hot = { template: "<div>热门</div>" };
const Class = { template: "<div>分类</div>" };

// 定义路由
const routes = [
{ path: "/home", component: Home },
{ path: "/hot", component: Hot },
{ path: "/class", component: Class },
];

// 创建 router 实例,然后传 routes 配置
const router = new VueRouter({
routes,
});

// 创建和挂载根实例
const app = new Vue({
router,
}).$mount("#app");
</script>
</body>
</html>

页面效果: ::: demo-wrapper no-padding 20250226184948 :::

点击不同的链接后页面只会局部刷新。

VueRouter是Vue提供的一个路由管理器,专门用于处理路由和url的映射关系,有点儿像Nginx的功能。

功能如下图所示: ::: center 20250226181117 :::

例如,当用户访问https://shenying.online/a,web服务就会收到请求。然后解析路径/a,程序就会把这个请求交给类似的路由管理器来管理。

而前端不需要像传统的路由器那样使用服务器来解析,而是通过一个本地的程序来进行hash映射或者利用H5中的history API实现。

一般使用前端路由的程序为不涉及页面跳转的单页面应用。

前端路由有如下优点: 1. ==页面刷新速度快==:由于不需要向服务器发送请求,所以这个过程不会受到网络延迟的影响,实际上只是完成部分组件间的切换,因此页面的刷新速度会比较快,用户体验也更好些。 2. ==复用性强==:由于使用前端路由的应用为单页面应用,所以代码中很多 CSS、JS 都可以共用,避免了过多的重复加载,大大提升了性能。 3. ==页面状态可记录==:如果不使用前端路由,仅通过 Ajax 在页面进行局部切换的应用,由于页面 URL 始终保持不变,因此页面的状态是无法记录的,而前端路由很好的解决了这个问题。例如,使用了前端路由的应用中访问 https://www.lanqiao.cn/a 这个链接,再打开后会直接触发 /a 匹配的路由页面中的事件。

当然,缺点也存在:使用浏览器前进和后退时浏览器不会将之前的请求结果放入缓存,导致会重新发送请求。

实际上,官方的叫法是Affix固钉

这是一个新增的导航组件,用来将元素固定到页面上。

:::: demo-wrapper no-padding ::: center 20250226175816 ::: ::::

我们可以按照在之前的views中继续加入一个Affix.vue组件,并修改App.vue中的代码: ::: code-tabs

@tab App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div id="app">
<Affix />
</div>
</template>

<script>
import Affix from "./views/Affix.vue";

export default {
name: "App",
components: {
Affix,
},
};
</script>

@tab Affix.vue

1
2
3
4
5
<template>
<el-affix :offset="200">
<el-button type="primary">距离顶部 200 个像素</el-button>
</el-affix>
</template>

:::

树形控件是类似思维导图一样的组件,并且可以折叠和展开。

实现效果如图:

::: demo-wrapper no-padding 20250226175240 :::

这是Lanqiao给出的代码实例,存在问题,建议查看官方文档;

: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
<el-tree :data="data" :props="defaultProps"></el-tree>

<script>
export default {
data() {
return {
data: [{
label: '一级 1',
children: [{
label: '二级 1-1',
children: [{
label: '三级 1-1-1'
}]
}]
}, {
label: '一级 2',
children: [{
label: '二级 2-1',
children: [{
label: '三级 2-1-1'
}]
}, {
label: '二级 2-2',
children: [{
label: '三级 2-2-1'
}]
}]
},
}],
defaultProps: {
children: 'children',
label: 'label'
}
};
}
};
</script>

0%