封面来源:本文封面来源于网络,如有侵权,请联系删除。

参考视频:适用于后端编程人员的Vue实战教程,整合SpringBoot项目案例、已完结!

1. Vue 引言

Vue 是一款渐进式 JavaScript 框架。

所谓渐进式:

1、易用:熟悉 HTML、CSS、JavaScript 可以快速上手

2、高效:Vue 用于开发前端页面非常高效

3、灵活:开发灵活,多样性

对于后端服务端开发人员来说:

Vue 是一款渐进式 JavaScript 框架,可以让我们通过操作很少的 DOM,甚至不需要操作页面中任何 DOM 元素就能很容易完成数据和视图绑定(双向绑定 MVVM)。

注意: 日后在使用 Vue 的过程中,不要在页面使用 jQuery。

Vue 的作者

尤雨溪,国人。曾就职于 Google Creative Labs 和 Meteor Development Group,目前加入了阿里作为技术顾问,但现居于美国新泽西,全职开发和维护 Vue.js。

PS:可以关注下尤雨溪的知乎,很有意思。

2. Vue 基本语法

2.1 下载 Vuejs

根据官网:

1
2
3
4
5
<!-- development version, includes helpful console warnings -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<!-- OR -->
<!-- production version, optimized for size and speed -->
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>

2.2 Hello World

以后编写代码请严格按照以下格式进行编码:

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue 的第一个入门案例</title>

</head>
<body>
<div id="app">
{{ username }}
{{ msg }}
<br>
<span>
<h1>{{ username }}</h1>
{{ msg }}
</span>
</div>

<!-- 引入 Vue.js, 我自己下载了一份保存到本地 -->
<script src="../dependency/vue.js"></script>
<!-- DOM 树上有 Vue 的作用域,才能渲染成功 -->
<script>
const app = new Vue({ // Vue 的 V 要大写
el:"#app", // element 用来给 Vue 实例定义一个作用范围
data:{ // 用来给 Vue 实例定义一些相关数据
msg:"https://mofan212.github.io",
username:"默烦"
},
});
</script>
</body>
</html>

通过上面的 Hello World 案例,我们可以得出:

1、Vue 实例(对象)中的 el 属性:代表 Vue 的作用范围,日后在 Vue 作用范围内 都可以使用 Vue 的语法。所谓作用范围内,比如上述实例中 <div></div> 内就是作用范围内,在这之外就属于作用范围外。

2、Vue 实例(对象)中的 data 属性:用来给 Vue 实例绑定一些相关数据,绑定的数据可以通过 {{变量名}} 的形式在 Vue 的作用范围内取出。

取值形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<body>

<div id="app">
<h3>{{msg}}</h3>
<h3>用户名:{{ user.name }} 年龄:{{ user.age }}</h3>
<h3>身高:{{ height }}</h3>
<h3>兴趣:{{ hobby[0] }} --- {{ hobby[1] }} --- {{ hobby[2] }}</h3>
<h3>朋友:{{ friend[0].name }} --- {{ friend[1].name }}</h3>
</div>

<script src="../dependency/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
msg:"Hello Vue",
user:{name:"默烦", age:18},
height:179,
hobby:["写博客","学习","玩游戏"],
friend:[{name:"小明", age:18}, {name:"小红", age:19}]
}
})
</script>
</body>

取值形式其实有点类似于模板引擎 Thymeleaf。

3、在使用 {{}} 获取 data 中的数据时,可以在 {{}} 中书写表达式、运算符,调用相关的方法以及逻辑运算等。比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<body>
<div id="app">
<span>{{ msg.toUpperCase() + '!'}}</span>
</div>

<script src="../dependency/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
msg:"Hello Vue"
}
})
</script>
</body>

4、el 属性中可以书写任意的 CSS 选择器(或者说 jQuery 选择器),但是在使用 Vue 开发时,建议使用 id 选择器。

页面加载顺序与 <script> 标签的位置

如果我们在书写代码时,将自己编写的 Vue 代码放在了 <head> 标签内,页面是不会渲染成功的,页面中仍然是未渲染的插值表达式。

这是因为:页面的加载顺序是从上至下的,当加载到:

1
2
3
4
5
6
7
8
9
const app = new Vue({
el:"#app",
data:{

},
methods:{

},
})

中的 el 属性时,此时 DOM 树中并没有 id 为 app 的标签,等到后续加载到 id 为 app 的标签时,Vue 将不会渲染页面。

因此,一定要将自己编写的 Vue 代码放在 <body> 标签内的末尾。

一般来说,我们也习惯将引入的 Vue.js 也放在 <body> 标签内的末尾,与自己编写的 Vue 代码放在一起。

拓展:

<body> 中的 JS 会在页面加载的时候被执行。在 <head> 部分中的 JS 会在被调用的时候才执行。

<head> 中的脚本:需调用才执行的脚本或事件触发执行的脚本放在 <head> 中。当你把脚本放在 <head> 中时,可以保证脚本在任何调用之前被加载。

<body> 中的脚本:当页面被加载时执行的脚本放在 <body> 中。放在 <body> 中的脚本通常被用来生成页面的内容。

还需注意:

浏览器加载页面是按从上到下顺序加载的。加载 JS 并执行的时候,会阻塞其他资源的加载。这是因为 JS 可能会有 DOM 、样式等操作,所以浏览器要先加载这段 JS 并执行,再加载放在它后面的 HTML、CSS。

因此,加入一段巨大的 JS 放在最上面,浏览器首先要下载并执行,这段时间里面,页面是空白的。相比于加载了部分 HTML 和 样式,但是没有 JS 交互功能,显然是后者对于浏览者体验要好。

然而,并不是所有 JS 都要放在最下面。 总的来说,还是要根据团队和业务来决定 JS 放置的位置。

就针对 jQuery 而言,没运行 jQuery 之前,先运行了调用 jQuery 函数的代码,就会报错。我们可以控制自己写的 JS 放在页面最下面,放在 jQuery 下面,但是我们无法保证页面上其他地方会不会有人图省事直接在 HTML 代码里面输出 JS。如果有,那么就会报错。在这种情况下,我们一般选择将 jQuery 放在最上面。

但是 jQuery 放在后面可以提高首屏加载时间,load 时间提前接近 1 秒。为了提高加载时间,我们也可以选择将 jQuery 放在后面。

参考链接:网站为什么 JS 调用尽量放到网页底部?

2.3 v-text 和 v-html

v-text 的使用

v-text 可以用来获取 data 中的数据,将数据以文本的形式渲染到指定标签内部,类似于 JavaScript 中的 innerText。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<body>
<div id="app">
<span>{{ msg.toUpperCase() + '!'}}</span>
<br>
<!-- 这样也可以获取数据 -->
<span v-text="msg + '!'"></span>
</div>

<script src="../dependency/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
msg:"Hello Vue"
}
})
</script>
</body>

小总结:

在 Vue 中 {{}} 叫做插值表达值(回忆:FreeMarker 中的 ${} 也叫插值)。那 {{}}v-text 获取数据的区别是什么呢?

  • 使用 v-text 取值会将标签中原有的数据覆盖,使用插值表达式的形式不会覆盖标签中原有的数据。至于具体用哪个可以自行选择。
  • 使用 v-text 可以避免由于网络问题造成的插值闪烁。

v-html 的使用

v-html 用来获取 data 中的数据,将数据中含有 HTML 标签先解析然后再渲染到指定标签的内部,类似于 JavaScript 中的 innerHTML。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<body>

<div id="app">
<span>{{ msg }}</span>
<br>

<span v-html="msg"></span>
</div>

<script src="../dependency/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
msg:"<a href='https://mofan212.github.io'>默烦的博客</a> "
}
})
</script>
</body>

v-text 一样,使用 v-html 取值也会将标签中原有的数据覆盖,也能避免由于网络问题造成的插值闪烁。

2.4 Vue 中的事件绑定(v-on)

绑定事件语法

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
<body>

<div id="app">
<h2>{{ message }}</h2>
<h2>年龄:{{ age }}</h2>
<br>

<input type="button" value="点我改变年龄" v-on:click="changeAge">
</div>

<script src="../dependency/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
message:"点一下,年龄就增长一次",
age:18
},
methods:{ // methods 用来定义 Vue 的事件
changeAge:function(){
// 在函数中获取 Vue 实例中 data 的数据,在事件函数中 this 就是 Vue 实例
this.age++;
}
}
})
</script>
</body>

小总结:

1、在 Vue 中绑定事件是通过 v-on 指令来完成的,格式是:v-on:事件名,比如:v-on:click

2、在 v-on:事件名 的赋值语句中是当前事件触发调用的函数名

回忆 JS 事件的三要素:事件源(发生事件的 DOM 元素)、事件(发生特定的动作)、监听器(发生特定动作之后的事件处理程序,通常是 JS 中的函数)

3、在 Vue 中,事件的函数统一定义在 Vue 实例的 methods 属性中。

4、在 Vue 定义的事件中,this 指的就是当前的 Vue 实例,日后可以在事件中通过使用 this 来获取 Vue 实例中的相关数据

Vue 中常用的几个事件:

事件名
v-on:mouseover
v-on:mouseout
v-on:mousedown
v-on:dblclick

Vue 中事件的简化语法

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>

<div id="app">
<h2>{{ age }}</h2>

<input type="button" value="通过 v-on 事件修改年龄每次 +1" v-on:click="changeAge">

<input type="button" value="通过 @ 绑定事件修改年龄每次 -1" @click="editAge">
</div>

<script src="../dependency/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
age:18
},
methods:{
changeAge:function(){
this.age++;
},
editAge:function(){
this.age--;
}
}
})
</script>
</body>

小总结:

1、日后在 Vue 中绑定事件可以通过 @ 符号的形式简化 v-on 的事件绑定

Vue 事件函数的两种写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<body>

<div id="app">
<span>{{ count }}</span>
<input type="button" value="改变 count 的值" @click="changeCount">
</div>

<script src="../dependency/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
count:1
},
methods:{
changeCount(){
this.count++;
}
}
})
</script>
</body>

小总结:

1、在 Vue 中,事件定义存在两种写法:

  • 函数名:function()
  • 函数名(){}

Vue 事件参数传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<body>

<div id="app">
<span>{{ count }}</span>
<input type="button" value="改变 count 为指定的值" @click="changeCount(20, 'mofan')">
</div>

<script src="../dependency/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
count:18
},
methods:{
changeCount(count, name){
this.count = count;
alert(name);
}
}
})
</script>
</body>

小总结:

1、在使用事件时,可以直接在事件调用处给事件进行参数传递,在事件定义处通过定义对应变量接受传递的参数

2.5 v-show v-if v-bind

v-show

v-show 可以用来控制页面中某个标签元素是否展示,底层控制的是标签的 display 属性。

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
<body>
<div id="app">
<!-- v-show:用来控制标签展示还是隐藏 -->
<h2 v-show="false">知者不博</h2>
<h2 v-show="show">知者不博,博者不知</h2>

<input type="button" value="展示隐藏标签" @click="showMsg">

<h2 v-show="age>=20">默烦</h2>
<input type="button" value="通过修改age值控制标签展示" @click="changeAge">
</div>

<script src="../dependency/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
show: false,
age: 18
},
methods:{
showMsg() {
this.show = !this.show;
},
changeAge() {
this.age++;
}
}
})
</script>
</body>

小总结:

1、在使用 v-show 时,可以直接书写 boolean 值控制标签展示,也可以通过变量控制标签的展示和隐藏。

2、在 v-show 中可以通过 boolean 表达式控制标签的展示和隐藏。

v-if

v-if 用来控制页面元素是否展示,底层控制的是 DOM 元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<body>

<div id="app">
<h2 v-if="false"> 信言不美 </h2>
<h2 v-if="show"> 信言不美,美言不信 </h2>

<input type="button" value="点击显示隐藏元素" @click="showMsg">
</div>

<script src="../dependency/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
show: false
},
methods:{
showMsg() {
this.show = !this.show;
}
}
})
</script>
</body>

使用方式和 v-show 一样,在此就不赘述了。

v-ifv-show 的区别,假设页面元素都没显示。

针对 v-show 可以在开发者工具中看到:

1
2
3
<div id="app">
<h2 style="display: none;">知者不博</h2>
</div>

针对 v-if 可以在开发者工具中看到:

1
2
3
<div id="app">
<!---->
</div>

v-bind

v-bind 用来绑定标签的属性从而通过 Vue 动态修改标签的属性。

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
<head>
<meta charset="UTF-8">
<title>Vue 中 v-bind 使用</title>
<style>
.aa{
border: 5px solid #ddd;
padding: 5px;
background: #fff;
}
</style>
</head>
<body>

<div id="app">
<img width="300" v-bind:title="msg" v-bind:class="{aa:showCss}" v-bind:src="src" alt="vue">
<br>
<input type="button" value="点击修改图片边框" @click="showBorder">
<input type="button" value="点击更改图片" @click="changeSrc">
</div>

<script src="../dependency/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
msg:"Vue",
showCss:false,
src:"https://pic4.zhimg.com/v2-0cd1f1c469f59713d397864275f9349e_1440w.jpg?source=172ae18b"
},
methods:{
showBorder(){
this.showCss = !this.showCss;
},
changeSrc() {
this.src = "https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3580813152,896575090&fm=26&gp=0.jpg";
}
}
})
</script>
</body>

Vue 为了方便我们日后绑定标签的属性,提供了对属性绑定的简化写法,如 v-bind:属性名 可以简化为 :属性名

v-bind:属性名 的简化写法就是不写 v-bind。😂

2.6 v-for 与 v-model

v-for

v-for:用来对对象进行遍历。数组也是对象的一种。

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
<body>
<div id="app">
<span> {{user.name}} {{user.age}} </span>
<br>
<!-- 通过 v-for 遍历对象 -->
<span v-for="(key, value, index) in user">
{{index}} -- {{key}} -- {{value}}
</span>

<!-- 通过 v-for 遍历数组 -->
<ul>
<li v-for="a, index in hobby">
{{index}} -- {{a}}
</li>
</ul>

<!--
使用 v-for 遍历数组中的对象
:key 可以便于 Vue 内部做重用和排序
-->
<ul>
<li v-for="user, index in users" :key="user.id">
{{index}} == {{user.name}} == {{user.age}}
</li>
</ul>
</div>

<script src="../dependency/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
user:{name:"默烦", age:18},
hobby:[
"吃饭",
"睡觉",
"打豆豆"
],
users:[
{id:"1", name:"张三", age:20},
{id:"2", name:"李四", age:19},
{id:"3", name:"王五", age:21}
]
},
methods:{

}
})
</script>
</body>

小总结:

1、在使用 v-for 的使用一定要注意加入 :key 用来给 Vue 内部提供重用和排序的唯一 key。

v-model 双向绑定

v-model 可以用来绑定标签元素的值与 Vue 实例对象中 data 数据保持一致,从而实现双向的数据绑定机制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<body>
<div id="app">
<input type="text" v-model="message">
<span> {{message}} </span>
<hr>
<input type="button" value="改变 Data 中的值" @click="changeValue">
</div>

<script src="../dependency/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
message:""
},
methods:{
changeValue() {
this.message = "xxxxxxx";
}
}
})
</script>
</body>

小总结:

1、使用 v-model 可以实现数据的双向绑定

2、所谓双向绑定,就是表单中的数据变化会导致 Vue 实例 data 数据的变化,而 Vue 实例中 data 数据的变化也会导致表单中数据的变化。

MVVM 架构

M:Model,意为数据,就是 Vue 实例中绑定的数据

VM:ViewModel,意为监听器,会监听 M 和 V 中数据的变化。如果 View 变化了,会将 View 的变化传递给 Model;如果 Model 变化了,也会将 Model 的变化传递给 View。

V:View,意为页面,指页面中展示的数据

2.7 一个综合小案例

案例介绍

运行结果图:

记事本案例运行结果图

在输入框中输入数据后,点击“添加到记事本”,会在下方的无序列表中增加一条数据,数据的内容就是输入框中输入的数据。

选中无序列表中某一项,点击该项的“删除”,即可删除该项。

总数量会随着无序列表中项数的变化而变化。

点击“删除所有”按钮后,会清空无序列表中的所有项,同时总数量也会变为 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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<body>
<div id="app">
<input type="text" v-model="msg">
<input type="button" value="添加到记事本" @click="save">

<br>

<ul>
<li v-for="item, index in lists">
{{index + 1}} {{item}} <a href="javascript:;" @click="delRow(index)">删除</a>
</li>
</ul>

<br>
<span> 总数量:{{ lists.length }} 条 </span> <input type="button" v-show="lists.length != 0" value="删除所有" @click="deleteAll">
</div>

<!--
1. 完成记事本的查询所有思路:1). 将所有数据绑定为 Vue 实例 2). 遍历 Vue 实例中的数据到页面
2. 添加 1) 添加按钮绑定事件中 2). 在事件中获取输入框中的数据 3). 将获取到的数据放入 lists 中
3. 删除 删除所有 修改总数量
-->

<script src="../dependency/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
lists: ["信言不美,美言不信", "知者不博,博者不知"],
msg: ""
},
methods: {
save() { // 数据添加到记事本中
this.lists.push(this.msg);
this.msg = '';
},
delRow(index) { // 删除一条记录
// 根据下标删除对应的数据
this.lists.splice(index, 1); // 从 index 下标开始删除,删除 1 个元素
},
deleteAll() {
this.lists = [];
}
}
})
</script>
</body>

2.8 事件修饰符

修饰符是用来和事件连用,用来决定事件的触发条件或者阻止事件的触发机制。

常用的事件修饰符:

  • .stop
  • .prevent
  • .capture
  • .self
  • .once
  • .passive

官方文档参考:Vue 事件修饰符

stop 事件修饰符

.stop 事件修饰符可以用来阻止事件冒泡。

事件冒泡:当一个元素接收到事件的时候,会把他接收到的事件传给自己的父级,一直到 window。

假设现在有个 div,这个 div 里还有一个按钮,div 和按钮都绑定了一个点击事件。这时候点击按钮,会先触发按钮的点击事件,再触发 div 的点击事件,这就是事件冒泡。

但现在的需求是,我只想触发按钮的点击事件,不想触发 div 的点击事件,那么就需要阻止事件冒泡,使用事件修饰符 .stop

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
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue 的事件修饰符</title>
<style>
.aa {
background: red;
width: 300px;
height: 300px;
}
</style>
</head>

<body>

<div id="app">

<div class="aa" @click="divClick ">
<!-- 用来阻止事件冒泡 -->
<input type="button" value="按钮" @click.stop="btnClick">
</div>

</div>

<script src="../dependency/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {

},
methods: {
btnClick() {
alert("按钮被点击了");
},
divClick() {
alert("div被点击了");
}
}
})
</script>
</body>

prevent 时间修饰符

.prevent 可以用来阻止标签的默认行为。

比如 <a> 超链接标签,默认行为就是点击这个超链接可以跳转至指定的网页(假设指定了正确的网页地址),如果我们想不让其跳转至指定的网页,就可以使用 .prevent 事件修饰符。比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<body>

<div id="app">

<!-- 用来阻止事件的默认行为 -->
<a href="https://mofan212.github.io/" @click.prevent="aClick">默烦的博客</a>

</div>

<script src="../dependency/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {

},
methods: {
aClick() {
alert("<a> 被点击了");
}
}
})
</script>
</body>

self 事件修饰符

.self 用来针对于当前标签的事件触发,或者说只触发自己标签上的特定动作的事件。

再简单来说,就是只关心自己标签上触发的事件,不监听事件冒泡。

比方说,针对 .stop 修饰符中的案例,我在 div 里多加几个按钮,然后这些按钮都绑定一些点击事件(点击事件未使用 .stop 修饰符),由于事件冒泡,我点击这些按钮会触发 div 的点击事件,解决方式也很简单,给按钮的点击事件使用 .stop 修饰符即可,但是每个按钮都使用就会很冗余,那么可以给 div 的点击事件使用 .self 修饰符,让其专注于自己的事件,不关心事件冒泡。

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
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>stop 事件修饰符</title>
<style>
.aa {
background: red;
width: 300px;
height: 300px;
}
</style>
</head>

<body>

<div id="app">

<div class="aa" @click.self="divClick ">
<!-- 用来阻止事件冒泡 -->
<input type="button" value="按钮" @click.stop="btnClick">
<input type="button" value="按钮1" @click="btnClick1">
</div>

</div>

<script src="../dependency/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {

},
methods: {
btnClick() {
alert("按钮被点击了");
},
divClick() {
alert("div被点击了");
},
btnClick1() {
alert("btn1 被点击了");
}
}
})
</script>
</body>

once 事件修饰符

.once 可以让指定的事件只触发一次。

.prevent 时间修饰符的案例中给超链接的点击事件添加 .once 修饰符,比如:

1
<a href="https://mofan212.github.io/" @click.prevent.once="aClick">默烦的博客</a>

那么结果会怎么样呢?

.prevent 可以让阻止标签的默认行为,.once 让事件只触发一次,两个一结合,就只会阻止一次默认行为。即:点击第一次 不会跳转 至对应的链接,但是第二次点击时 会跳转 至对应的链接。

2.9 按键修饰符

按键修饰符可以用来与键盘中按键事件绑定在一起,用来修饰特定的按键事件的修饰符。

官方文档参考:Vue 按键修饰符

常用的按键修饰符:

  • .enter
  • .tab
  • .delete (捕获“删除”和“退格”键)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right

使用示例

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
<body>
<div id="app">
<!-- enter 键抬起时触发 -->
<input type="text" v-model="msg" @keyup.enter="keyUps">
<!-- tab 键抬起时触发 -->
<input type="text" @keyup.tab="keyupTab">
<!-- tab 键按下时触发 -->
<input type="text" @keydown.tab="keydownTab">
</div>

<script src="../dependency/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
msg:""
},
methods: {
keyUps() {
alert("输入的内容是:" + this.msg);
},
keyupTab() {
console.log("抬起了 tab 键");
},
keydownTab() {
console.log("按下了 tab 键");
}
}
})
</script>
</body>

.enter 可以用来触发在按下回车按键之后的事件。

对于 .tab 来说,需要区分触发事件。如果是 keyup,会在使用 Tab 键后,将焦点聚焦到当前组件时就会触发;如果是 keydown,在当前组件上按下 Tab 键就会触发。

3. Axios 的使用

3.1 引言

Vue 不再提倡我们操作 DOM 元素,也不建议使用 jQuery,那么在使用 Vue 做前后端分离的网站时,需要在前端页面获取数据,那么在获取数据时就需要用到异步的请求技术,比如:Ajax,但在 Vue 中更加推荐我们使用 Axios。

Axios 是一个异步请求技术,核心作用是用来在页面中发送异步请求,并获取对应数据在页面中渲染。这就是一种页面局部更新技术,和 Ajax 类似,虽然也可以使用 Ajax,但不建议使用 jQuery 后,编写原生的 Ajax 多难受啊。

3.2 Axios 的基本使用

Axios 中文文档:Axios

我们先使用 CDN 的方式使用 Axios:

1
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

SpringBoot Demo 的准备

为了能够更好地展示 Axios 的使用,需要先使用 IDEA 创建一个 SpringBoot 项目,在这个项目中提供一些数据和接口,以便我们可以通过 Axios 获取到数据。

编写一个简单的实体类:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* @author mofan 2020/12/11
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String id;
private String username;
private String email;
private int age;
private String phone;
}

然后根据这个实体类,编写一个简单的控制器,用于模拟请求:

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
/**
* @author mofan 2020/12/11
*/
@RestController
@RequestMapping("user")
public class UserController {

/**
* 模拟删除用户
* 注解 @CrossOrigin 用于处理跨域
* @param id 用户 id
*/
@CrossOrigin
@DeleteMapping("delete")
public Map<String, Object> delete(String id) {
HashMap<String, Object> map = new HashMap<>(8);
System.out.println("id = " + id);
map.put("success", true);
return map;
}

/**
* 保存数据
*/
@CrossOrigin
@PostMapping("save")
public Map<String, Object> save(@RequestBody User user) {
Map<String, Object> map = new HashMap<>(8);
System.out.println("User = " + user);
map.put("success", true);
return map;
}

/**
* 展示索引
*/
@CrossOrigin
@GetMapping("findAll")
public List<User> findAll(String name) {
System.out.println("name = " + name);
List<User> users = new ArrayList<>();
users.add(new User("21", "张三", "zhangsan233@gamil.com", 18, "13323336666"));
users.add(new User("22", "李四", "lisi666@gamil.com", 19, "18514725836"));
users.add(new User("23", "王五", "wangwu888@gamil.com", 20, "15196385274"));
return users;
}
}

application.properties 配置文件信息:

1
server.port=9090

然后 运行项目,以便后续在 VSCode 中编写的页面能够获取到请求。

GET 方式的请求

1
2
3
4
5
6
7
8
9
10
<body>
<!-- 引入 Axios, 我将其下到了本地 -->
<script src="../dependency/axios.min.js"></script>
<script>
// 发送 GET 方式请求
axios.get("http://localhost:9090/user/findAll?name=mofan").then(function(response){
console.log(response.data);
})
</script>
</body>

然后运行这个页面,可以在开发者工具(F12 打开)的 Console 栏中看到打印出的数据:

0: {id: "21", username: "张三", email: "zhangsan233@gamil.com", age: 18, phone: "13323336666"}
1: {id: "22", username: "李四", email: "lisi666@gamil.com", age: 19, phone: "18514725836"}
2: {id: "23", username: "王五", email: "wangwu888@gamil.com", age: 20, phone: "15196385274"}

在 Network 栏中可以看到 findAll?name=mofan 请求,在请求的 Response 中也能看到获取到的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[
{
"id": "21",
"username": "张三",
"email": "zhangsan233@gamil.com",
"age": 18,
"phone": "13323336666"
},
{
"id": "22",
"username": "李四",
"email": "lisi666@gamil.com",
"age": 19,
"phone": "18514725836"
},
{
"id": "23",
"username": "王五",
"email": "wangwu888@gamil.com",
"age": 20,
"phone": "15196385274"
}
]

最后在 IDEA 的控制台可以看到:

name = mofan

POST 方式的请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<body>
<script src="../dependency/axios.min.js"></script>
<script>
// 发送 POST 方式请求
axios.post("http://localhost:9090/user/save", {
username: "默烦",
age:18,
email:"cy.mofan@qq.com",
phone:"152xxxxxxxx"
}).then(function(response) {
console.log(response.data);
}).catch(function(err){
console.log(err);
})
</script>
</body>

然后运行这个页面,可以在开发者工具(F12 打开)的 Console 栏中看到打印出的数据:

success: true

在 Network 栏中可以看到 save 请求,在请求的 Response 中也能看到返回的数据:

1
{"success":true}

最后在 IDEA 的控制台可以看到:

User = User(id=null, username=默烦, email=cy.mofan@qq.com, age=18, phone=152xxxxxxxx)

Axios 的并发请求

所谓并发请求,就是将多个请求在同一时刻发送到后端服务接口,最后在集中处理每个请求的响应结果。

假设我们需要并发执行 savefindAll,那么我们可以这么做:

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
<body>
<script src="../dependency/axios.min.js"></script>
<script>

// 1. 创建一个查询所有的请求
function findAll() {
return axios.get("http://localhost:9090/user/findAll?name=张三");
}
// 2. 创建一个保存的请求
function save() {
return axios.post("http://localhost:9090/user/save", {
username: "默烦",
age: 18,
email: "cy.mofan@qq.com",
phone: "152xxxxxxxx"
});
}

// 3. 并发执行
axios.all([findAll(), save()]).then(
axios.spread(function (res1, res2) {
console.log(res1.data);
console.log(res2.data);
})
); // 用来发送一组并发请求
</script>
</body>

然后运行这个页面,可以在开发者工具(F12 打开)的 Console 栏中看到打印出的数据是同时执行两个请求的结果。

在其余地方也能看到结果是两个请求同时执行的结果。

3.3 Vue 、Axios 综合案例一

功能说明

界面图片:

Axios和Vue的综合案例

前端界面如上图所示。

当我们在输入框中输入城市并按下 ENTER 键或旁边的“搜索”按钮,可以在天气信息显示处显示输入的城市和对应的天气信息。

当我们在输入框删除内容时,清空天气信息显示处的数据。

当我们点击输入框下的超链接时,也会在天气信息显示处显示对应的城市和天气。

SpringBoot Demo 的准备

同样,为了 Axios 能够正常使用,需要在 IDEA 中编写控制器的方法,然后模拟天气信息。

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
/**
* @author mofan 2020/12/11
*/
@RestController
@RequestMapping("city")
public class CityController {

@CrossOrigin
@GetMapping("find")
public Map<String, String> findWeatherByCity(String name) {
Map<String, String> map = new HashMap<>(8);
String weather = getWeather(name);
map.put("message", weather);
return map;
}

/**
* 模拟天气信息
*/
public String getWeather(String name) {
Map<String, String> map = new HashMap<>(8);
map.put("北京","晴,空气质量良好");
map.put("上海","多云转晴,空气质量良好");
map.put("青藏","晴,空气质量优");
map.put("成都","小雨,空气质量及格");
map.put("深圳","多云,空气质量良好");
return map.get(name);
}
}

别忘记我们的端口号是:9090

前端页面代码

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
<body>
<div id="app">
<input type="text" v-model="name" @keyup.delete="shows" @keyup.enter="searchCity">
<input type="button" value="搜索" @click="searchCity"> <br>

<span v-for="city in hostCity">
<a href="" v-text="city" @click.prevent="searchCitys(city)">
</a>&nbsp;
</span>

<hr>

<span v-show="isShow">{{ name }},今天的天气是: {{ msg }} </span>
</div>

<script src="../dependency/vue.js"></script>
<script src="../dependency/axios.min.js"></script>
<script>

const app = new Vue({
el: "#app",
data: {
hostCity: ["北京", "上海", "青藏", "成都", "深圳"],
name: "",
msg:"",
isShow: false,
},
methods: {
searchCity() {
// 获取输入的城市信息
console.log(this.name);
let _this = this;
// 发送 Axios 请求
axios.get("http://localhost:9090/city/find?name=" + this.name).then(
function (response) {
console.log(response.data.message);
_this.msg = response.data.message;
_this.isShow = true;
}).catch(function (err) {
console.log(err);
})
},
shows() {
this.isShow = false;
},
searchCitys(city) {
this.name = city;
this.searchCity(); // 函数中调用函数
}
}
})
</script>
</body>

当然,上述代码只是实现了一些最主要的功能,还有一些细节需要完善,可以自行修改。

3.4 Vue 的生命周期

Vue 的官方术语是:Vue 的生命周期钩子 → 生命周期函数

Vue 官方文档:实例生命周期钩子

在官方文档中,还有这样一张图:

Vue生命周期

Vue 生命周期小总结:

1、初始化阶段

2、运行阶段

3、销毁阶段

生命周期函数讲解

初始化阶段:

beforeCreate() 生命周期中第一个函数,该函数在执行时 Vue 实例仅仅完成了自身事件的绑定和生命周期函数的初始化工作,Vue 实例中还有没有 data、el、methods 等相关属性。

created() 生命周期中第二个函数,该函数在执行时 Vue 实例已经初始化了 data 属性和 methods 中的相关方法。

beforeMount() 生命周期中第三个函数,该函数在执行时 Vue 将 el 中指定作用范围作为模板 编译

mounted() 生命周期中第四个函数,该函数在执行过程中,已经将数据渲染到界面中,并且已经更新界面。

运行阶段(数据发生变化时调用):

beforeUpdate() 生命周期中第五个函数,该函数是 data 中数据发生变化时执行。这个函数执行时仅仅是 Vue 实例中 data 数据变化,页面中显示的依然是原数据。

updated() 生命周期中第六个函数,该函数执行时,data 中的数据发生了变化,页面中的数据也发生了变化,页面中的数据已经和 data 中的数据一致了。

销毁阶段:

beforeDestroy() 生命周期中第七个函数,该函数执行时,Vue 中所有数据、methods、component 等都没被销毁。

destroyed() 生命周期中最后一个函数,该函数执行时, Vue 实例被彻底销毁。

代码讲解

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
<body>

<div id="app">
<!-- Vue 模板 -->
<span id="sp"> {{ name }} </span>
<input type="button" value="数据改变" @click="changeData">
</div>

<script src="../dependency/vue.js"></script>
<script>
const app = new Vue({
el:"#app",
data:{
name: "mofan",
},
methods:{
changeData() {
this.name = "默烦"
}
},
// 初始化阶段
beforeCreate() {
console.log("beforeCreate: " + this.name);
},
created() {
console.log("created: " + this.name);
},
beforeMount() {
console.log("beforeMount: " + document.getElementById("sp").innerText);
},
mounted() {
console.log("mounted: " + document.getElementById("sp").innerText);
},
beforeUpdate() {
// data 中的数据
console.log("beforeUpdate → " + this.name);
// 页面中的数据
console.log("beforeUpdate: " + document.getElementById("sp").innerText);
},
updated() {
// data 中的数据
console.log("updated → " + this.name);
// 页面中的数据
console.log("updated: " + document.getElementById("sp").innerText);
},
beforeDestroy() {

},
destroyed() {

}
})
</script>
</body>

运行后浏览器显示:

Vue生命周期代码运行结果

开发者工具运行结果:

Vue生命周期控制台输出结果

3.5 Vue 与 Bootstrap 小案例

使用 IDEA 创建一个 SpringBoot 项目,在这个项目中导入如下依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>

数据库表信息如下:

1
2
3
4
5
6
7
8
CREATE TABLE `t_user` (
`id` varchar(40) NOT NULL,
`name` varchar(40) DEFAULT NULL,
`age` int(3) DEFAULT NULL,
`salary` double(7,2) DEFAULT NULL,
`phone_code` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

application.properties 配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
server:
port: 9090
servlet:
context-path: /users

spring:
application:
name: users
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql:///vue_bootstrap?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=GMT%2B8
username: root
password: 123456

mybatis:
mapper-locations: classpath:indi/mofan/mapper/*.xml
type-aliases-package: indi.mofan.entity
configuration:
map-underscore-to-camel-case: true

后台代码编写

User 实体类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @author mofan 2020/12/13
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class User {
private String id;
private String name;
private Integer age;
private Double salary;
private String phoneCode;
}

数据访问层 UserDao:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* @author mofan 2020/12/13
*/
@Repository
@Mapper
public interface UserDao {

List<User> findAll();

void save(User user);

void delete(String id);

User findOneById(String id);

void update(User user);

List<User> findUsersByNameOrPhoneCode(@Param("name") String name,
@Param("code") String phoneCode);
}

对应的 MyBatis mapper 文件:

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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="indi.mofan.dao.UserDao">
<!--保存用户-->
<insert id="save" parameterType="indi.mofan.entity.User">
INSERT INTO t_user VALUES (#{id}, #{name}, #{age}, #{salary}, #{phoneCode})
</insert>

<!--根据 id 修改用户信息-->
<update id="update">
UPDATE t_user SET name = #{name}, age = #{age}, salary = #{salary}, phone_code = #{phoneCode}
WHERE id = #{id}
</update>

<!--根据 id 删除用户信息-->
<delete id="delete">
DELETE FROM t_user WHERE id = #{id}
</delete>

<!--查询所有用户-->
<select id="findAll" resultType="indi.mofan.entity.User">
SELECT id, name, age, salary, phone_code FROM t_user
</select>

<select id="findOneById" resultType="indi.mofan.entity.User">
SELECT id, name, age, salary, phone_code
FROM t_user
WHERE id = #{id}
</select>

<!--根据姓名或电话号码查询用户-->
<select id="findUsersByNameOrPhoneCode" resultType="indi.mofan.entity.User">
SELECT id, name, age, salary, phone_code FROM t_user
<where>
<if test="name!=''">
name like concat('%', #{name}, '%')
</if>
<if test="code!=''">
or phone_code like concat('%', #{code}, '%')
</if>
</where>
</select>
<select id="findById" resultType="indi.mofan.entity.User"></select>
</mapper>

业务层 UserService 接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* @author cheny 2020/12/13
*/
public interface UserService {

List<User> findAll();

void save(User user);

void delete(String id);

User findOneById(String id);

void update(User user);

List<User> findUsersByNameOrPhoneCode(String name, String code);
}

业务层接口实现类:

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
/**
* @author cheny 2020/12/13
*/
@Service
@Transactional(rollbackFor = Exception.class)
public class UserServiceImpl implements UserService{

@Autowired
private UserDao userDao;

@Override
@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public List<User> findAll() {
return userDao.findAll();
}

@Override
public void save(User user) {
user.setId(UUID.randomUUID().toString());
userDao.save(user);
}

@Override
public void delete(String id) {
userDao.delete(id);
}

@Override
public User findOneById(String id) {
return userDao.findOneById(id);
}

@Override
public void update(User user) {
userDao.update(user);
}

@Override
public List<User> findUsersByNameOrPhoneCode(String name, String code) {
return userDao.findUsersByNameOrPhoneCode(name, code);
}
}

最后的控制层 UserController:

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
/**
* @author cheny 2020/12/13
*/
@RestController
@RequestMapping("user")
@CrossOrigin
public class UserController {

@Autowired
private UserService userService;

@GetMapping("findAll")
public List<User> findAll() {
List<User> users = userService.findAll();
return users;
}

@PostMapping("save")
public Map<String, Object> save(@RequestBody User user) {
HashMap<String, Object> map = new HashMap<>(8);
try {
if (StringUtils.hasText(user.getId())) {
userService.update(user);
} else {
userService.save(user);
}
map.put("success", true);
} catch (Exception e) {
map.put("success", false);
map.put("msg", "用户保存或更新失败!");
}
return map;
}

@GetMapping("delete")
public Map<String, Object> delete(String id) {
Map<String, Object> map = new HashMap<>(8);
try {
userService.delete(id);
map.put("success", true);
} catch (Exception e) {
map.put("success", false);
map.put("msg", "删除用户信息失败,请稍后再试!");
}
return map;
}

@GetMapping("findOne")
public User findOneById(String id) {
return userService.findOneById(id);
}

@GetMapping("searchLike")
public List<User> findUsersByNameOrPhoneCode(String name, String code) {
return userService.findUsersByNameOrPhoneCode(name, code);
}
}

前端页面编写

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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>用户管理界面</title>
<!-- 引入 bootstrap 的 CSS -->
<link rel="stylesheet" href="../dependency/bootstrap/css/bootstrap.min.css">
</head>

<body>

<!-- 导航 -->
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="#">用户管理系统</a>
</div>

<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav navbar-right">
<li><a href="#">欢迎:xxx</a></li>
</ul>
</div>
</div>
</nav>

<div id="app">
<!-- 中心布局 -->
<div class="container-fluid">
<!-- 搜索框 -->
<div class="row">
<div class="col-md-8 col-md-offset-1">
<form class="form-inline">
<div class="form-group">
<label for="searchName">姓名</label>
<input type="text" class="form-control" id="searchName" v-model="searchName">
</div>
<div class="form-group">
<label for="searchPhoneCode">电话号码</label>
<input type="text" class="form-control" id="searchPhoneCode" v-model="searchPhoneCode">
</div>
<button type="submit" class="btn btn-info" @click.prevent="searchLike">搜索</button>
</form>
</div>
</div>
<div class="row" style="margin-top: 20px;">
<div class="col-md-8">
<!-- 用户信息的展示 -->
<table class="table table-striped table-bordered table-hover">
<tr>
<th>ID</th>
<th>姓名</th>
<th>年龄</th>
<th>薪资</th>
<th>电话</th>
<th>操作</th>
</tr>
<tbody>
<tr v-for="user, index in users" :key="user.id">
<td> {{user.id}} </td>
<td> {{user.name}} </td>
<td> {{user.age}} </td>
<td> {{user.salary}} </td>
<td> {{user.phoneCode}} </td>
<td><button class="btn btn-danger"
@click="deleteUserInfo(user.id)">删除</button>&nbsp;&nbsp;
<button class="btn btn-primary" @click="findOneUserInfo(user.id)">修改</button>
</td>
</tr>
</tbody>
</table>
</div>
<div class="col-md-4">
<form>
<div class="form-group">
<label for="name">姓名:</label>
<input type="text" class="form-control" id="name" v-model="user.name" placeholder="请输入姓名">
</div>
<div class="form-group">
<label for="age">年龄:</label>
<input type="text" class="form-control" id="age" v-model="user.age" placeholder="请输入年龄">
</div>
<div class="form-group">
<label for="salary">薪资:</label>
<input type="text" class="form-control" id="salary" v-model="user.salary"
placeholder="请输入薪资">
</div>
<div class="form-group">
<label for="phoneCode">电话号码:</label>
<input type="text" class="form-control" id="phoneCode" v-model="user.phoneCode"
placeholder="请输入电话号码">
</div>
<button type="button" class="btn btn-default" @click="saveUserInfo">提交</button>
<button type="button" class="btn btn-danger" @click="restUserInfo">重置</button>
</form>
</div>
</div>
</div>
</div>


<!-- 引入 Vue.js -->
<script src="../dependency/vue.js"></script>
<!-- 引入 axios.js -->
<script src="../dependency/axios.min.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
users: [], // 数据的赋值来源于后端服务接口,在页面加载完成之前完成赋值
user: {}, // 用来完成数据的双向绑定
searchName:"", // 用来处理搜索数据绑定
searchPhoneCode:"", // 用来处理搜索数据绑定
},
methods: {
saveUserInfo() {
let _this = this;
axios.post("http://localhost:9090/users/user/save", this.user).then(function (response) {
console.log(response.data);
if (response.data.success) {
// 更新页面列表
_this.findAll();
// 清空上次保存的信息
_this.user = {};
} else {
alert(response.data.msg);
}
}).catch(function (err) {
console.log(err);
})
},
// 查询所有用户信息的方法
findAll() {
let _this = this;
// 发送查询所有用户信息的操作
axios.get("http://localhost:9090/users/user/findAll").then(function (response) {
_this.users = response.data;
}).catch(function (err) {
console.log(err);
})
},
// 重置表单数据
restUserInfo() {
this.user = {};
},
// 根据 User 的编号删除用户信息
deleteUserInfo(id) {
let _this = this;
console.log(id);
if (window.confirm("确定要删除这条信息吗?")) {
axios.get("http://localhost:9090/users/user/delete?id=" + id)
.then(res => {
console.log(res.data);
if (res.data.success) {
// 更新页面数据
_this.findAll();
} else {
alert(res.data.msg);
}
})
.catch(err => {
console.error(err);
})
}
},
findOneUserInfo(id) {
let _this = this;
// 根据 id 查询用户的信息
axios.get("http://localhost:9090/users/user/findOne?id=" + id)
.then(res => {
console.log(res.data);
_this.user = res.data;
})
.catch(err => {
console.error(err);
})
},
// 处理模糊搜索
searchLike() {
let _this = this;
axios.get("http://localhost:9090/users/user/searchLike?name="+ this.searchName +"&code="+this.searchPhoneCode)
.then(res => {
console.log(res.data);
_this.users = res.data;
})
.catch(err => {
console.error(err);
})
}
},
created() {
this.findAll();
}
})
</script>
</body>

</html>

页面显示结果

启动 SpringBoot 项目后,运行前端页面 HTML 文件,可在浏览器中看到以下界面:

Vue与Bootstrap案例界面

4. Vue 组件

4.1 说明与基本使用

组件的说明

直接上官方文档:Vue组件基础

组件的作用:用来减少 Vue 实例对象中代码量,日后在使用 Vue 的开发过程中,可以根据不同业务功能将页面中划分成不同的组件,然后由多个组件去完成整个页面的布局,便于后续使用 Vue 进行开发与页面管理,方便开发人员维护。

全局组件的注册

说明:全局组件注册给 Vue 实例,日后可以在任意 Vue 实例的范围内使用该组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<body>
<div id="app">
<!-- 全局组件的使用 -->
<login></login>
</div>

<script src="../dependency/vue.js"></script>
<script>
// 全局组件注册 参数1:组件名称 参数2:组件配置对象
// template:用来书写组件的HTML代码(注意:在template中必须存在一个容器)
Vue.component('login', {
template: '<div><h1>用户登录</h2></div>'
})
const app = new Vue({
el:"#app",
data:{

},
methods:{

},
})
</script>
</body>

注意:

1、 Vue.component() 用来开发全局组件,参数 1:组件的名称,参数 2:组件配置对象。

2、template:用来书写组件的 HTML 代码,template 中必须有且仅有一个 root 元素。

3、使用全局组件时,需要在 Vue 的作用范围内根据组件名使用全局组件。

4、如果在注册组件过程中使用驼峰命名法来命名组件,那么在使用组件时,必须将驼峰的所有字母小写并加入 - 进行使用。比如在注册时:

1
2
3
Vue.component('userLogin', {
template: '<div><h1>用户登录</h2></div>'
})

那么使用时,就要这样用:

1
<user-login></user-login>

局部组件的注册

在官方文档中,有这样一段话:

全局注册往往是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。

文档链接:局部注册

因此,还是更建议使用局部组件。

说明:通过将组件注册给对应 Vue 实例中一个 Component 属性来完成组件的注册,这种方式不会对 Vue 实例造成累加。

第一种使用方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<body>
<div id="app">
<!-- 局部组件的使用 在 Vue 的实例范围内使用 -->
<login></login>
</div>

<script src="../dependency/vue.js"></script>
<script>
// 局部组件登录模板
let login = { // 具体局部组件名称
template: '<div><h2>用户登录</h2></div>'
};

const app = new Vue({
el:"#app",
data:{ },
methods:{ },
components:{ // 用来注册局部组件
login: login // 注册局部组件
}
})
</script>
</body>

第二种使用方式:

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>
<div id="app">
<!-- 局部组件的使用 在 Vue 的实例范围内使用 -->
<login></login>
</div>

<!-- 通过模板标签形式注册局部组件 -->
<template id="loginTemplate">
<h1>用户登录</h1>
</template>

<script src="../dependency/vue.js"></script>
<script>
// 局部组件登录模板
let login = { // 具体局部组件名称
template: '#loginTemplate' // 使用自定义 template 标签选择器
};

const app = new Vue({
el:"#app",
data:{ },
methods:{ },
components:{ // 用来注册局部组件
login: login // 注册局部组件
}
})
</script>
</body>

在使用第二种方式时,使用了 <template> 标签,这个标签需要在 Vue 实例作用范围外使用。

如果注册的组件名和变量名一样,可以只写一个,比如:

1
2
3
4
5
6
7
8
const app = new Vue({
el:"#app",
data:{ },
methods:{ },
components:{ // 用来注册局部组件
login
}
})

4.2 prop 属性

作用:props 属性可以用来给组件传递相应静态数据或动态数据。

通过在组件上声明静态数据传递给组件内部

1、声明组件的模板对象

2、注册组件

3、通过组件完成数据传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<body>
<div id="app">
<!-- 使用组件 -->
<login user-name="默烦" age="18"></login>
</div>

<script src="../dependency/vue.js"></script>
<script>
// 声明一个组件配置对象
let login = {
template: '<div><h2>欢迎: {{ userName }} , 年龄: {{ age }} </h2></div>',
props: ['userName', 'age']
}
const app = new Vue({
el:"#app",
data:{ },
methods:{ },
components:{
login // 组件注册
}
})
</script>
</body>

总结:

1、使用组件时可以在组件上定义多个属性以及对应数据

2、在组件内部可以使用 props 属性声明多个定义在组件上的属性名,日后可以在组件上通过插值表达式的方式获取组件中的属性值

通过在组件上声明动态数据传递给组件内部

1、声明组件模板对象

2、注册局部组件

3、使用组件(使用 v-bind 的形式将数据绑定 Vue 实例中的 data 属性,日后 data 属性发生变化,组件内部数据也跟着变化)

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
<body>
<div id="app">
<!-- 使用组件 -->
<login :name="username"></login>
</div>

<script src="../dependency/vue.js"></script>
<script>
// 声明一个组件配置对象
let login = {
template: '<div><h2>欢迎: {{ name }} </h2></div>',
props: ['name']
}
const app = new Vue({
el:"#app",
data:{
username: "mofan"
},
methods:{ },
components:{
login // 组件注册
}
})
</script>
</body>

组件中定义组件自己的数据

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>
<div id="app">
<login :username="username"></login>
</div>

<script src="../dependency/vue.js"></script>
<script>
const login = {
template: '<div><h2>欢迎: {{ name }} == {{ username }} </h2></div>',
data(){ // 组件中自己 data 的数据
return {
name: '默烦'
};
},
props: ['username']
}
const app = new Vue({
el:"#app",
data:{
username: 'mofan'
},
methods:{ },
components:{
login // 组件注册
}
})
</script>
</body>

prop 的单向数据流

在官方文档中,有这样一段话:

所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。

额外的,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。

文档链接:单向数据流

比如:Vue 实例中的数据就是父级组件,而自定义组件就是子组件,父组件可以更改子组件的值,但是反过来就不行。

4.3 定义数据和使用事件

组件中定义属于组件的数据

使用 data 函数方式定义组件的数据,在 template 的 HTML 代码中通过插值表达式直接获取。

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
<body>
<div id="app">
<!-- 组件的使用 -->
<login></login>
</div>

<script src="../dependency/vue.js"></script>
<script>
const login = {
template: '<div><h2> {{ msg }} 默烦</h2><ul><li v-for="item in lists"> {{item}} </li></ul><div>',
data(){
return {
msg: "hello",
lists:['Java', "Spring", 'JS']
} // 组件中定义自己的数据
}
}
const app = new Vue({
el: "#app",
data: {},
methods: {},
components: {
login // 组件注册
}
})
</script>
</body>

组件中使用事件

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
<body>
<div id="app">
<!-- 组件的使用 -->
<login></login>
</div>

<script src="../dependency/vue.js"></script>
<script>
const login = {
template: '<div><input type="button" value="click" @click="change"></div>',
data() {
return {
name: "默烦"
}
},
methods: {
change() {
alert(this.name);
alert("触发事件");
}
}
}
const app = new Vue({
el: "#app",
data: {},
methods: {},
components: {
login // 组件注册
}
})
</script>
</body>

总结:

1、组件中定义事件和直接在 Vue 中定义事件基本一致,直接在组件内部 template 中的 HTML 代码上加上 @事件名=函数名 即可。

2、在组件内部使用 methods 属性用来定义对应的事件函数即可,事件函数中 this 指向的是当前组件的实例。

4.4 父组件传递事件

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
<body>
<div id="app">
<!-- 组件的使用 -->
<login :name="username" @find="findAll"></login>
</div>

<script src="../dependency/vue.js"></script>
<script>
// 1. 声明组件
const login = {
template: '<div><h2> {{ name }} </h2><input type="button" value="click" @click="change"></div>',
data() {
return {
name: this.name
}
},
props: ['name'],
methods: {
change() {
// 调用 Vue 实例中函数
this.$emit('find');
}
}
}
// 2. 注册组件
const app = new Vue({
el: "#app",
data: {
username: "默烦"
},
methods: {
findAll() { // 事件函数,将这个函数传递给子组件
alert("Vue 实例中定义函数");
}
},
components: {
login // 组件注册
}
})
</script>
</body>

在子组件中调用传递过来的相关事件,必须使用 this.$emit('函数名') 的方式调用。

5. Vue 路由

5.1 路由

路由(Router):根据请求的路径按照一定的路由规则进行请求的转发从而帮助我们实现统一请求的管理。

4. Vue 组件 中,我们介绍了组件相关的内容,但是通过介绍我们发现组件的意义似乎不大。其实在实际开发过程中,我们一般会将 Vue 中的组件和路由相结合使用,通过路由,实现在某些规则下显示特定的组件。

因此,路由的作用就是:实现在 Vue 中组件之间的动态切换。

路由的安装

我们需要在 Vue 后面加载 vue-router,可以使用在线 CDN 的形式:

1
2
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router@3.4.9/dist/vue-router.js"></script>

也可以将其下载到本地,然后引入:

1
2
<script src="../dependency/vue.js"></script>
<script src="../dependency/vue-router.js"></script>

路由的使用

1、引入路由

2、创建组件对象

3、定义路由对象的规则

4、将路由对象注册到 Vue 实例

5、在页面中显示不同路径路由

6、根据链接切换路由

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
<body>
<div id="app">
<a href="#/login">点我登录</a>
<a href="#/register">点我注册</a>

<!-- 显示路由的组件 -->
<router-view></router-view>
</div>

<script src="../dependency/vue.js"></script>
<script src="../dependency/vue-router.js"></script>

<script>
// 声明组件模板 创建组件对象
const login = {
template: '<div><h2>登录</h2></div>'
}
const register = {
template: '<div><h2>注册</h2></div>'
}

// 创建路由对象 定义路由对象的规则
const router = new VueRouter({
routes: [
// path: 路由的路径 component: 路径对应的组件
{path:'/login', component: login},
{path:'/register', component: register}
]
});

// 将路由注册到 Vue 实例
const app = new Vue({
el: "#app",
data: {

},
methods: {

},
router: router // 设置路由对象
})
</script>
</body>

<router-link> 的作用:用来替换使用 <a> 标签来切换路由。

<router-link> 的好处:可以自动给路由路径加上 #,而不需要手动加入。

1
2
3
4
5
6
7
<!-- router-link 好处: 书写路由路径不需要 # , to: 用来书写路由路径 -->
<router-link to="/login">我要登录</router-link>
<router-link to="/register">我要注册</router-link>

<!-- 如果不使用这个标签,我们需要这样写 -->
<a href="#/login">我要登录</a>
<a href="#/register">我要注册</a>

这里的 <router-link> 标签的默认样式是超链接,那我想将其样式设置成按钮可以吗?

那当然是可以的,只需要使用 tag 属性即可,甚至还可以设置成 span

1
2
<router-link to="/login" tag="button">我要登录</router-link>
<router-link to="/register" tag="span">我要注册</router-link>

总结

1、<router-link> 用来替换使用 <a> 标签实现路由切换,而不需要书写 # 号,以直接书写路由路径。

2、<router-link> 中的 to 属性用来书写路由路径,tag 属性用来将 <router-link> 渲染成其他样式。

5.3 默认路由

在上述使用路由时,我们可以发现一个问题:刚进入页面时,组件是没有被渲染出来的,这显然不符合实际使用环境。

对此,我们就需要使用默认路由,用来在第一次进入页面时显示一个默认的界面。

1
2
3
4
5
6
7
8
// 创建路由对象
const router = new VueRouter({
routes: [
{ path: '/', component: login},
{ path: '/login', component: login },
{ path: '/register', component: register }
]
})

这样写虽然实现了我们的需求,但是当我们点击 我要登录 时,地址栏发生变化,界面并不会发生变化。

我们可以这样做:

1
2
3
4
5
6
7
const router = new VueRouter({
routes: [
{ path: '/', redirect: '/login'},
{ path: '/login', component: login },
{ path: '/register', component: register }
]
})

使用 redirect 可以实现当访问的是默认路由 / 时,跳转到指定的路由展示(重定向嘛,很好理解)。注意 redirect 的属性值是路径,而非组件模板对象。

5.4 路由中参数传递

使用问号的形式传递参数

1、通过 ? 拼接参数

2、在组件中获取参数,使用 this.$route.query

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
<body>
<div id="app">

<!-- router-link 好处: 书写路由路径不需要 # , to: 用来书写路由路径 -->
<router-link to="/login?id=212&name=mofan">我要登录</router-link>
<router-link to="/register">我要注册</router-link>

<!-- 显示路由组件 -->
<router-view></router-view>
</div>

<script src="../dependency/vue.js"></script>
<script src="../dependency/vue-router.js"></script>

<script>
// 声明组件模板
const login = {
template: '<div><h2>用户登录</h2><span> {{ $route.query.id }} {{ $route.query.name }} </span></div>',
created() {
console.log("==> "+this.$route.query.id);
console.log("==> "+this.$route.query.name);
}
}

const register = {
template: '<div><h2>用户注册</h2></div>'
}

// 创建路由对象
const router = new VueRouter({
routes: [
{ path: '/', redirect: '/login'},
{ path: '/login', component: login },
{ path: '/register', component: register }
]
})

// 将路由注册到 Vue 实例
const app = new Vue({
el: "#app",
data: {

},
methods: {

},
router: router
})
</script>
</body>

使用 RESTful 的形式传递参数

1、通过路径的方式传递参数

2、组件中获取参数,使用 this.$route.params

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
<body>
<div id="app">

<!-- router-link 好处: 书写路由路径不需要 # , to: 用来书写路由路径 -->
<router-link to="/login?id=212&name=mofan">我要登录</router-link>
<router-link to="/register/18/默烦">我要注册</router-link>

<!-- 显示路由组件 -->
<router-view></router-view>
</div>

<script src="../dependency/vue.js"></script>
<script src="../dependency/vue-router.js"></script>

<script>
// 声明组件模板
const login = {
template: '<div><h2>用户登录</h2><span> {{ $route.query.id }} {{ $route.query.name }} </span></div>',
created() {
console.log("==> "+this.$route.query.id);
console.log("==> "+this.$route.query.name);
}
}

const register = {
template: '<div><h2>用户注册</h2><span> {{ $route.params.id }} {{ $route.params.name }} </span></div>',
created() {
console.log("==> " + this.$route.params.id);
console.log("==> " + this.$route.params.name);
}
}

// 创建路由对象
const router = new VueRouter({
routes: [
{ path: '/', redirect: '/login'},
{ path: '/login', component: login },
{ path: '/register/:id/:name', component: register }
]
})

// 将路由注册到 Vue 实例
const app = new Vue({
el: "#app",
data: {

},
methods: {

},
router: router
})
</script>
</body>

5.5 嵌套路由

官方文档:嵌套路由

实际生活中的应用界面,通常由多层嵌套的组件组合而成。同样地,URL 中各段动态路径也按某种结构对应嵌套的各层组件,例如:

/user/foo/profile                     /user/foo/posts
+------------------+                  +-----------------+
| User             |                  | User            |
| +--------------+ |                  | +-------------+ |
| | Profile      | |  +------------>  | | Posts       | |
| |              | |                  | |             | |
| +--------------+ |                  | +-------------+ |
+------------------+                  +-----------------+

借助 <vue-router>,使用嵌套路由配置,就可以很简单地表达这种关系。

嵌套路由使用方式

1、声明最外层和内层路由

2、创建路由对象(含有嵌套路由)

3、注册路由对象

4、测试路由

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
<body>
<div id="app">

<router-link to="/product">商品管理</router-link>

<!-- 显示路由组件 -->
<router-view></router-view>
</div>

<template id="product">
<div>
<h1>商品管理</h1>
<router-link to="/product/add">商品添加</router-link>
<router-link to="/product/edit">商品编辑</router-link>

<router-view></router-view>
</div>
</template>


<script src="../dependency/vue.js"></script>
<script src="../dependency/vue-router.js"></script>

<script>
// 声明组件模板
const product = {
template: '#product',
}

const add = {
template: '<div><h3>商品添加</h3></div>'
}

const edit = {
template: '<div><h3>商品编辑</h3></div>'
}


// 创建路由对象
const router = new VueRouter({
routes: [
{
path: '/product',
component: product,
children: [
{ path: 'add', component: add},
{ path: 'edit', component: edit},
]
}
]
})

// 将路由注册到 Vue 实例
const app = new Vue({
el: "#app",
data: { },
methods: { },
router, // 定义路由对象
})
</script>
</body>

需要注意的是: 当我们创建含有嵌套路由的路由对象时,使用了 children 属性,而不是直接创建路由。

5.6 路由的小案例

代码的编写

在 IDEA 中创建一个 SpringBoot 项目,导入以下依赖:

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>

编写一个 User 实体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @author mofan 2021/1/7
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class User {
private String id;
private String name;
private Integer age;
@JsonFormat(pattern = "yyyy-MM-dd")
private Date birth;
}

编写一个简单的控制器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* @author mofan 2021/1/7
*/
@RestController
@RequestMapping("user")
public class UserController {

@GetMapping("findAll")
@CrossOrigin
public List<User> findAll() {
System.out.println("查询所有...");
List<User> list = Arrays.asList(
new User("1", "张三", 18, new Date()),
new User("2", "李四", 20, new Date()),
new User("3", "默烦", 18, new Date())
);
return list;
}
}

在 VSCode 中编写我们需要的页面:

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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>使用Vue开发简单页面</title>
<!-- 引入 bootstrap 的 CSS -->
<link rel="stylesheet" href="../dependency/bootstrap/css/bootstrap.min.css">
</head>

<body>
<div id="app">

<div class="container">
<div class="row" style="margin-top: 70px;">
<div class="col-md-10 col-md-offset-1">
<ul class="nav nav-pills nav-justified">
<li role="presentation" :class="showActive == 'home'?'active':''"><a href="#/home" @click="changeActive('home')">主页</a></li>
<li role="presentation" :class="showActive == 'user'?'active':''"><a href="#/user" @click="changeActive('user')">用户管理</a></li>
<li role="presentation" :class="showActive == 'student'?'active':''"><a href="#/student" @click="changeActive('student')">学生管理</a></li>
</ul>
</div>
</div>
<div class="row">
<div class="col-md-10 col-md-offset-1">
<!-- 显示路由组件内容 -->
<router-view></router-view>
</div>
</div>
</div>

</div>

<template id="home">
<div>
<div class="jumbotron" style="margin-top: 100px;">
<h1>Hello, world!</h1>
<p>This is a simple hero unit, a simple jumbotron-style component for calling extra attention to featured content or information.</p>
<p><a class="btn btn-primary btn-lg" href="#" role="button">Learn more</a></p>
</div>
</div>
</template>

<template id="user">
<div>
<table class="table table-striped" style="margin-top: 100px;">
<tr>
<th>id</th>
<th>姓名</th>
<th>年龄</th>
<th>生日</th>
<th>操作</th>
</tr>
<tr v-for="user in users">
<td> {{user.id}} </td>
<td> {{user.name}} </td>
<td> {{user.age}} </td>
<td> {{user.birth}} </td>
<td>
<a href="" class="btn btn-default">修改</a>
<a href="" class="btn btn-danger">删除</a>
</td>
</tr>
</table>
</div>
</template>

<template id="student">
<div>
<table class="table table-striped" style="margin-top: 100px;">
<tr>
<th>id</th>
<th>学生姓名</th>
<th>学历</th>
<th>邮箱</th>
<th>操作</th>
</tr>
<tr>
<td>1</td>
<td>李四</td>
<td>本科</td>
<td>xxxx@gmail.com</td>
<td>
<a href="" class="btn btn-default">修改</a>
<a href="" class="btn btn-danger">删除</a>
</td>
</tr>
</table>
</div>
</template>

<!-- 引入 Vue.js -->
<script src="../dependency/vue.js"></script>
<!-- 引入 axios.js -->
<script src="../dependency/axios.min.js"></script>
<!-- 引入 vue-router -->
<script src="../dependency/vue-router.js"></script>
<script>
// 1. 主页组件配置对象
const home = {
template: '#home'
}
// 2. 用户管理组件配置对象
const user = {
template: '#user',
data() {
return {
users:[],
}
},
methods:{

},
created() {
// 发送查询所有用户信息
let _this = this;
axios.get("http://localhost:8080/user/findAll")
.then(res => {
console.log(res.data);
_this.users = res.data;
})
.catch(err => {
console.error(err);
})
}
}
// 3. 学生管理组件配置对象
const student = {
template: '#student'
}

// 使用路由
const router = new VueRouter({
routes: [
{ path: "/", redirect: "/home" },
{ path: "/home", component: home },
{ path: "/user", component: user },
{ path: "/student", component: student },
]
})

const app = new Vue({
el: "#app",
data: {
showActive: 'home',
},
methods: {
changeActive(value) {
this.showActive = value;
}
},
router // 注册路由
})
</script>
</body>

</html>

运行结果

首页的显示:

Vue路由案例运行结果-1

点击“用户管理”:

Vue路由案例运行结果-2

点击“学生管理”:

Vue路由案例运行结果-3

我们可以成功通过 Axios 获得后台提供的数据!🎉

但是在这个小案例中,我们也能发现其中的问题:就这样一个简单的功能,居然用了这么多的代码,如果后续再功能再复杂一点,那岂不是完蛋?🥚

6. Vue CLI 脚手架

6.1 Vue 脚手架引言

什么是 CLI

摘自百度百科:

命令行界面(英语:command-line interface,缩写:CLI)是在图形用户界面得到普及之前使用最为广泛的用户界面,它通常不支持鼠标,用户通过键盘输入指令,计算机接收到指令后,予以执行。也有人称之为字符用户界面(CUI)。

什么是 Vue CLI

官方文档 是这么写的:

Vue CLI 是一个基于 Vue.js 进行快速开发的完整系统,提供:

  • 通过 @vue/cli 实现的交互式的项目脚手架。
  • 通过 @vue/cli + @vue/cli-service-global 实现的零配置原型开发。
  • 一个运行时依赖 (@vue/cli-service),该依赖:
    • 可升级;
    • 基于 webpack 构建,并带有合理的默认配置;
    • 可以通过项目内的配置文件进行配置;
    • 可以通过插件进行扩展。
  • 一个丰富的官方插件集合,集成了前端生态中最好的工具。
  • 一套完全图形化的创建和管理 Vue.js 项目的用户界面。

Vue CLI 致力于将 Vue 生态中的工具基础标准化。它确保了各种构建工具能够基于智能的默认配置即可平稳衔接,这样你可以专注在撰写应用上,而不必花好几天去纠结配置的问题。与此同时,它也为每个工具提供了调整配置的灵活性,无需 eject。

使用 Vue 脚手架后,我们开发的页面将是一个完整系统(项目)。

Vue CLI 的优势

  • 通过执行命令的方式下载相关依赖
  • 对项目进行快速初始化
  • 提供了一个运行时依赖
  • 集成了前端生态中最好的工具
  • 提供了一套完全图形化界面

6.2 Node.js 的安装

在本站搜索【Node.js 的安装与 Hexo 的升级】进行查看即可。

6.3 Vue CLI 的安装

在安装之前,请确保已安装 Node.js、npm 和 Git。

进入 Vue CLI 的官网,查看如何安装,由于本文是基于 Vue 2.0 的,因此我们需要安装老版本的 Vue CLI,只需要执行以下命令即可:

1
npm install -g vue-cli

如果我们要卸载,只需要执行以下命令:

1
npm uninstall vue-cli -g

6.4 初始化项目

初始化项目

接下来,我们可以使用 Vue CLI 来创建一个项目,我们只需要在我们想创建项目的地方打开 Git,执行:

1
vue init <template-name> <project-name>

比如:

1
vue init webpack hello-vue-cli

其中 webpack 是打包工具,而 hello-vue-cli 是项目名,一般来说,我们只需要修改最后一个参数即可。

执行命令后,会让我们进行选择,按照如下方式进行选择即可:

? Project name (hello-vue-cli)
? Project name hello-vue-cli
? Project description (A Vue.js project)
? Project description A Vue.js project
? Author (mofan )
? Author mofan 
? Vue build (Use arrow keys)
> Runtime + Compiler: recommended for most users
  Runtime-only: about 6KB lighter min+gzip, but templates (or any Vue-specific H
? Vue build standalone
? Install vue-router? (Y/n) y
? Install vue-router? Yes
? Use ESLint to lint your code? (Y/n) n
? Use ESLint to lint your code? No
? Set up unit tests (Y/n) n
? Set up unit tests No
? Setup e2e tests with Nightwatch? (Y/n) n
? Setup e2e tests with Nightwatch? No
? Should we run `npm install` for you after the project has been created? (recom
? Should we run `npm install` for you after the project has been created? (recom
mended) yarn

然后静静等待,这个过程可能需要一些时间,慢慢等就行了。

如果初始化过程中没有出现错误,且出现类似如下的字样:

added 1270 packages from 675 contributors in 387.856s
42 packages are looking for funding run `npm fund` for details
# Project initialization finished! # ========================

就表示我们的项目初始化成功了!


在安装过程中可能会出现以下错误:

错误: 找不到或无法加载主类 install

这是因为我们更改了 Node.js 的全局模块的存放路径,我们更改了环境变量,但是没有修改系统变量,只需要在修改环境变量的位置给 Path 环境变量添加修改的的环境变量即可。

比如我修改的环境变量是:

NODE_PATH环境变量

那么需要给 Path 环境变量添加 %NODE_PATH% 即可,如:

添加%NODE_PATH%给Path

项目的启动

创建好项目后,可以看到这样的目录结构:

使用Vue脚手架创建的第一个项目结构

然后我们在创建好的项目的根目录中使用 Git 执行 npm start 即可启动前端项目(就是在包含了 package.json 文件的目录中执行命令)。

启动成功后,最后会出现如下信息:

 I  Your application is running here: http://localhost:8080

在浏览器的地址栏中输入 http://localhost:8080 进行访问:

第一次成功启动项目出现的界面

页面出现上述画面后,证明我们项目启动成功!

项目文件解析

使用 Vue 脚手架生成项目并成功启动后,我们来介绍一下项目中的文件都是干嘛的:

hello-vue-cli 			---> 项目名
 - build 				---> 用来使用 webpack 打包使用 build 依赖
 - config 				---> 用来做整个项目配置目录
 - node_modules 		---> 用来管理项目中使用的依赖
 - src  				---> 用来书写 Vue 的源代码 【重点】
   + assets 			---> 用来存放静态资源 【重点】
   + components 		---> 用来书写 Vue 组件 【重点】
   + router 			---> 用来配置项目中的路由 【重点】
     ++ index.js  		---> 路由规则设置文件 【重点】
   + App.vue 			---> 项目中根组件【重点】
   + main.js 			---> 项目中主入口 【重点】
 - static 				---> 其他静态资源
 - .babelrc 			---> 将 ES6 语法转换为 ES5 运行
 - .editorconfig 		---> 项目编辑配置
 - .gitignore 			---> git 版本控制忽略文件
 - .postcssrc.js 		---> 源码相关 js
 - index.html 			---> 项目主页
 - package.json 		---> 类似 pom.xml 用于依赖管理 不建议手动修改
 - package-lock.json 	---> 对 package.json 加锁
 - README.md 			---> 项目说明文件

Vue CLI 中项目开发方式

需要注意的是:一切皆组件。

1、Vue CLI 的开发方式是在项目中开发一个个组件,它们对应着一个个的业务功能模块,日后可以将多个组件组合到一起形成一个前端系统。

2、日后在使用 Vue CLI 进行开发时,不在书写 HTML,编写的是一个个组件(这些组件是以 .vue 结尾的文件),日后打包时 Vue CLI 会将组件编译成运行的 HTML 文件。

6.5 脚手架文件内容认识

main.js

在 【6.4 初始化项目】中的 项目文件解析 可知,src 文件夹及其内部的内容是相当重要的,我们在使用脚手架时,也通常就是在这个文件夹里“玩”。现在一下 main.js 文件的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
import Vue from 'vue' // 在页面中引入 vue.js
import App from './App' // 引入自定义组件
import router from './router' // 引入 vue router js

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({ // Vue 全局实例
el: '#app', // 绑定 Vue 实例全局作用范围
router, // 注册路由对象
components: { App }, // 注册 App 组件, 主应用组件
template: '<App/>' // 将全局实例也设置成一个组件, 指向 index.html
})

这个文件是项目的主入口,一般来说,我们不会修改这个文件。

针对 import Vue from 'vue' 一句来说,就是引入了 vue.js 并设置一个变量名为 Vue,方便后续使用。在引入文件时,可以省略文件拓展名,也可以引入文件夹。

在内容的第三行,引入了 router 路由,指的是项目中 router 文件夹下所有内容。以后修改路由的配置就在这里面修改。

初始化项目时,在 router 文件夹下 默认 创建了一个名为 index.js 的路由规则设置文件,以后修改路由规则可以直接修改这个文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'

Vue.use(Router)

export default new Router({
routes: [
{
path: '/',
name: 'HelloWorld', // 路由的名字
component: HelloWorld
}
]
})

name 属性表示路由的名字,可以通过这个属性值来访问组件。比如:

1
<router-view  name="HelloWorld"></router-view>

只不过在前文中都是使用 path 来访问组件的。

App.vue

再来看看 App.vue 文件,这个文件是主应用组件,或者说根组件。这个文件中有这样几行代码:

1
2
3
4
5
6
<template>
<div id="app">
<img src="./assets/logo.png">
<router-view/>
</div>
</template>

其中的 <router-view/> 表示哪个组件呢?

main.js 文件中,为 Vue 全局实例注册了 App 组件,表示当项目其中时就会加载名为 App 的组件。

同时在 index.js 文件中,配置了路由规则,当访问路径是 / 时,将显示名为 HelloWorld 的组件。

此时的访问路径是 http://localhost:8080/#/,因此会显示 HelloWorld 组件,而 <router-view/> 此时指的就是 HelloWorld 组件。

验证一下,先在 App.vue 文件中加一行字:

1
2
3
4
5
6
7
<template>
<div id="app">
<img src="./assets/logo.png">
<router-view/>
App Content
</div>
</template>

页面将显示:

修改App.vue后界面显示结果

然后注释 index.js 文件中配置的路由规则:

1
2
3
4
5
export default new Router({
routes: [
// { path: '/', component: HelloWorld }
]
})

页面将显示:

注释路由规则后界面显示结果

如果注释 App.vue 中的 <router-view/>, 也会显示上述页面:

1
2
3
4
5
6
7
<template>
<div id="app">
<img src="./assets/logo.png">
<!-- <router-view/> -->
App Content
</div>
</template>

如果更改浏览器地址栏的路径,比如更改为 http://localhost:8080/#/233,也会显示上述界面。

暴露当前组件对象

App.vue 文件中,还有这样几行代码:

1
2
3
4
5
<script>
export default { // ES6 暴露当前组件对象
name: 'App' // 暴露组件对象的名字
}
</script>

这又是干嘛的?

在 Vue CLI 中有个要求:Vue 实例对象不是想使用组件嘛,那就必须将这些组件暴露出去,这几行代码就是用来暴露当前组件对象的。

其中的 name 属性表示暴露组件对象的名字,除此之外,还拥有其他属性和方法。

App.vue 文件进行改写:

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
<template>
<div id="app">
<h1 class="aa">欢迎: {{ name }}</h1>
<router-view />
</div>
</template>

<script>
export default {
// ES6 暴露当前组件对象
name: "App", // 暴露组件对象的名字
data() {
return {
name: "默烦",
};
},
methods: {},
comments: {},
created() {},
};
</script>

<style>
.aa{
background-color: aqua;
}
</style>

最终页面显示:

改写App.vue文件后界面显示结果

6.6 脚手架的基本使用

通常,我们习惯将组件文件名首字母大写。

主应用组件 App.vue

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
<template>
<div id="app">

<router-link to="/home">主页</router-link>
<router-link to="/user">用户模块</router-link>

<!-- 用来展示路由组件 -->
<router-view />

<!-- 使用页脚组件 -->
<Footer></Footer>
</div>
</template>

<script>
import Footer from './components/Footer'
export default {
// ES6 暴露当前组件对象
name: "App", // 暴露组件对象的名字
components: {
Footer
}
};
</script>

<style></style>

页脚组件 Footer.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div>
<h4>我也页脚 @Mofan 我是默烦</h4>
</div>
</template>

<script>
export default {
name: 'Footer'
}
</script>

<style></style>

主页组件 Home.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<div>
<h1>主页组件</h1>

<div style="width: 100%; height: 300px; background: red;">
我是内容
</div>
</div>
</template>

<script>
export default {
name: 'Home'
}
</script>

<style></style>

用户组件 User.vue

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
<template>
<div>
<h1>用户模块</h1>
<table border="1">
<tr>
<td>编号</td>
<td>姓名</td>
<td>年龄</td>
<td>操作</td>
</tr>
<tr v-for="user in users" :key="user.id">
<td> {{user.id}} </td>
<td> {{user.name}} </td>
<td> {{user.age}} </td>
<td> <a href="#">删除</a> <a href="#">修改</a></td>
</tr>
</table>
</div>
</template>

<script>
export default {
name: 'User',
data() {
return {
users: [
{id: "1", name: "mofan", age: 18},
{id: "2", name: "默烦", age: 20},
]
}
},
methods: {},
components: {},
created() {},
}
</script>

<style></style>

路由规则文件 index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import Vue from 'vue'
import Router from 'vue-router'
import Home from '../components/Home.vue'
import User from '../components/User.vue'

Vue.use(Router)

export default new Router({
routes: [
{ path: '/', redirect: "/home" },
{ path: '/home', component: Home },
{ path: '/user', component: User }
]
})

最后在浏览器地址栏输入 http://localhost:8080/#/ 进行访问,界面效果如图所示:

Vue脚手架的基本使用

7. 案例实战

7.1 项目准备

在存放项目文件夹内,打开 Git,执行以下语句,创建名为 users 的项目:

1
vue init webpack users

等待项目创建完毕后,进入项目根目录,执行命令:

1
npm start

然后根据提示,在浏览器地址栏键入项目的访问地址,查看能否成功访问。

除此之外,在本项目中,我们还会用到 Axios 和 Rap2。 👇

使用 Axios

在项目的根目录中,执行以下命令,安装 Axios:

1
npm install axios --save-dev

等待安装完毕后,前往 main.js 文件,添加以下代码:

1
2
3
4
5
// 引入 Axios
import axios from 'axios'

// 修改内部的 $http 为 axios
Vue.prototype.$http = axios

那怎么使用 Axios 呢?还是和以前一样吗?

get 请求为例,只需要在发送异步请求的位置使用:

1
this.$http.get("url").then((res) => ()).catch((e) => {})

现在的使用方式和以前不同了,应该引起注意。

Rap2 的使用

在前端开发过程中,我们需要实时与后端进行数据交互。然而大多数时候,前端开发都是在没有后端数据提供的情况下进行的,这时我们就需要用到假数据模拟。

Rap2 就是一款在线模拟数据生成器,可以拦截 Ajax 请求,其作用在于帮助前端工程师独立于后端进行开发, 实现前后端分离

百度搜索 Rap2,找到 Rap 接口管理平台,点击进入,然后注册账号并登录进入主界面:

Rap接口管理平台界面

点击页面中央的 新建仓库,输入仓库基本信息,创建仓库:

Rap中新建Users仓库

常见好仓库后,点击进入仓库,点击新建接口,输入接口信息:

Users仓库中新建第一个接口

PS:进入仓库后,我设置了偏高设置,所以界面颜色发生了变化。

新建好接口后,还需要设置 请求参数响应内容,对这两项进行如下设置:

为第一个接口设置请求参数和响应内容

最后点击保存,我们的接口就创建好了。

然后就可以使用创建的接口了。

7.2 前端用户列表

项目创建成功后,创建一些需要的组件。

主页组件

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div>
<h1>欢迎进入我们的网站</h1>
</div>
</template>

<script>
export default {
name: "Home"
}
</script>

<style></style>

学生组件

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div>
<h1>学生列表</h1>
</div>
</template>

<script>
export default {
name: "Student"
}
</script>

<style></style>

用户组件

根据 7.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
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
<template>
<div>
<h1>用户列表</h1>
<table border="1">
<tr>
<td>编号</td>
<td>姓名</td>
<td>年龄</td>
<td>生日</td>
<td>操作</td>
</tr>
<tr v-for="user in users" :key="user.id">
<td>{{ user.id }}</td>
<td>{{ user.name }}</td>
<td>{{ user.age }}</td>
<td>{{ user.birth }}</td>
<td><a href="">删除</a><a href="">修改</a></td>
</tr>
</table>

<a href="">添加</a>
</div>
</template>

<script>
export default {
name: "User",
data() {
return {
users: [],
};
},
methods: {
findAll() {
// 使用 Rap2 创建的接口
this.$http
.get(
"http://rap2api.taobao.org/app/mock/275110/user/findAll?page=1&rows=4"
)
.then((res) => {
// 接口中的 results 就是用户数据
this.users = res.data.results;
});
},
},
components: {},
created() {
this.findAll();
},
};
</script>

<style>
</style>

定义路由对象规则

修改 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
import Vue from 'vue'
import Router from 'vue-router'
import Home from '../components/Home.vue'
import User from '../components/User.vue'
import Student from '../components/Student.vue'

Vue.use(Router)

export default new Router({
routes: [{
path: '/',
redirect: '/home'
},
{
path: '/home',
component: Home
},
{
path: '/user',
component: User
},
{
path: '/student',
component: Student
}
]
})

实现界面跳转

在项目根组件 App.vue 中添加页面跳转超链接:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<div id="app">
<a href="#/home">主页</a>
<a href="#/user">用户管理</a>
<a href="#/student">学生管理</a>
<router-view/>
</div>
</template>

<script>
export default {
name: 'App'
}
</script>

<style>
</style>

界面显示结果

最终,用户列表显示界面如下图所示:

用户列表显示界面

7.3 前端用户添加

现在 Rap2 中新建一个接口:

Rap2新建用户添加接口

这个接口的 请求参数响应内容 如下图所示:

设置用户添加接口的请求参数和响应内容

因为需要新增用户,因此也需要创建一个新的组件。创建 UserAdd 组件,用于新增用户:

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
<template>
<div>
<h2>用户添加信息</h2>
<form action="">
用户名: <input v-model="user.name" type="text" /><br />
年龄: <input v-model="user.age" type="text" /><br />
生日: <input v-model="user.birth" type="text" /><br />
<input type="button" @click="saveUserInfo" value="添加用户信息" />
</form>
</div>
</template>

<script>
export default {
name: "UserAdd",
data() {
return {
user:{},
};
},
methods: {
saveUserInfo() {
this.$http.post("http://rap2api.taobao.org/app/mock/275110/user/add", this.user)
.then(res => {
if(res.data.success) {
// 切换路由
this.$router.push("/user");
}
});
}
},
};
</script>

<style>
</style>

需要注意的是,当我们成功添加了数据后还需要 切换路由

编写好用户添加组件后,添加路由对象规则,将用户添加组件设置为用户组件的子组件:

1
2
3
4
5
6
7
{
path: '/user',
component: User,
children: [
{ path: 'add', component: UserAdd }
]
},

修改 User.vue 用户组件,设置监听、添加跳转链接:

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
<template>
<div>
<!-- 省略其他代码 -->
<a href="#/user/add">添加</a>
<router-view></router-view>
</div>
</template>

<script>
export default {
// 省略其他代码

watch: {
// 用来监听
$route: {
handler: function (val, oldVal) {
if(val.path == "/user") {
this.findAll();
}
},
// 深度观测监听
deep: true,
},
},
};
</script>

<style>
</style>

最后来测试一下,由于我们使用的是 Rap2 模拟的接口,因此无法在界面上看到添加的数据显示,但我们可以通过开发者调试器来查看请求。

7.4 前端用户删除

同样,我们在 Rap2 中创建一个删除用户的接口,接口地址为 /user/delete,接口的 请求参数响应内容 如下图所示:

设置删除用户接口的请求参数和响应内容

然后在 User.vue 用户组件中为 删除 的链接绑定点击事件,并编写触发事件函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<a href="javascript:;" @click="delRow(user.id)">删除</a>

<script>
export default {
// 省略其他代码
methods: {
// 省略其他代码
delRow(id) {
this.$http.get("http://rap2api.taobao.org/app/mock/275110/user/delete?id=" + id).then(res => {
console.log(res);
if(res.data.success == true) {
this.findAll(); // 查询所有用户
}
})
},
},
// 省略其他代码
};
</script>

点击删除链接后,在浏览器的开发者调试工具的 Network 中查看发送的请求:

点击删除后发送的请求

7.5 前端用户修改

在 Rap2 中创建一个获取某个用户信息的接口,接口地址为 /user/findOne,接口的 请求参数响应内容 如下图所示:

设置获取某个用户信息接口的请求参数和响应内容

在 Rap2 中创建一个修改用户信息的接口,接口地址为 /user/update,接口的 请求参数响应内容 如下图所示:

设置修改用户信息接口的请求参数和响应内容

然后编写一个用户信息修改组件 UserEdit.vue

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
<template>
<div>
<h2>修改用户信息</h2>
<form action="">
用户名: <input v-model="user.name" type="text" /><br />
年龄: <input v-model="user.age" type="text" /><br />
生日: <input v-model="user.birth" type="text" /><br />
<input type="button" @click="editUserInfo" value="添加" />
</form>
</div>
</template>

<script>
export default {
name: "UserEdit",
data() {
return {
user:{
id:"",
}
}
},
methods: {
findOne() {
this.$http.get("http://rap2api.taobao.org/app/mock/275110/user/findOne?id=" + this.user.id).then(res => {
console.log(res.data);
this.user = res.data;
})
},
editUserInfo() {
this.$http.post("http://rap2api.taobao.org/app/mock/275110/user/update",this.user)
.then(res => {
console.log(res);
if(res.data.success) {
this.$router.push("/user");
}
})
}
},
created() {
console.log(this.$route.query.id);
this.user.id = this.$route.query.id;
this.findOne();
}
};
</script>

<style>
</style>

修改 index.js 文件,设置路由对象规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import UserEdit from '../components/UserEdit.vue'

export default new Router({
// 省略其他代码
routes: [
{
path: '/user',
component: User,
children: [
{ path: 'add', component: UserAdd },
{ path: 'edit', component: UserEdit },
]
},
// 省略其他代码
]
})

User.vue 用户组件中添加修改信息的超链接:

1
<a :href="'#/user/edit?id=' + user.id">修改</a>

7.6 真实后台搭建

前面所使用的接口都是 Rap2 接口管理平台生成的,不是真实的接口,接下来搭建真实后台。

使用 IDEA 创建一个 SpringBoot 项目,在这个项目中导入如下依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>

项目基本目录结构如下:

├─src
  ├─main
  │  ├─java
  │  │  └─indi
  │  │      └─mofan
  │  │          ├─controller
  │  │          ├─dao
  │  │          ├─entity
  │  │          └─service
  │  └─resources
  │      ├─indi
  │      │  └─mofan
  │      │      └─mapper
  │      ├─static
  │      └─templates
  └─test
      └─java
          └─indi
              └─mofan

数据库表信息如下:

1
2
3
4
5
6
7
CREATE TABLE `t_user_info` (
`id` int(6) NOT NULL AUTO_INCREMENT,
`name` varchar(40) NOT NULL,
`age` int(3) NOT NULL,
`birth` timestamp NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

application.properties 配置文件:

1
2
3
4
5
6
7
8
9
10
server.servlet.context-path=/vue
server.port=8989

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql:///vue_bootstrap?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123456

mybatis.mapper-locations=classpath:indi/mofan/mapper/*.xml

后端代码编写

User 实体类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @author mofan 2021/1/9
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class User {
private String id;
private String name;
private Integer age;
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GTM+8")
private Date birth;
}

数据访问层 UserDao:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* @author mofan 2021/1/9
*/
@Mapper
@Repository
public interface UserDao {
void save(User user);

void update(User user);

void delete(String id);

List<User> findAll();

User findById(String id);
}

对应的 MyBatis mapper 文件:

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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="indi.mofan.dao.UserDao">
<insert id="save" useGeneratedKeys="true" keyProperty="id">
insert into t_user_info values(#{id}, #{name}, #{age}, #{birth})
</insert>

<select id="findAll" resultType="indi.mofan.entity.User">
select * from t_user_info;
</select>

<update id="update">
update t_user_info
set name = #{name}, age=#{age}, birth=#{birth}
where id = #{id}
</update>

<delete id="delete">
delete from t_user_info where id = #{id}
</delete>

<select id="findById" resultType="indi.mofan.entity.User">
select id, name, age, birth from t_user_info where id = #{id}
</select>

</mapper>

业务层 UserService 接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @author mofan 2021/1/9
*/
public interface UserService {
void save(User user);

void update(User user);

void delete(String id);

List<User> findAll();

User findById(String 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
27
28
29
30
31
32
33
34
35
/**
* @author mofan 2021/1/9
*/
@Service
@Transactional(rollbackFor = Exception.class)
public class UserServiceImpl implements UserService {

@Autowired
private UserDao userDao;

@Override
public void save(User user) {
userDao.save(user);
}

@Override
public void update(User user) {
userDao.update(user);
}

@Override
public void delete(String id) {
userDao.delete(id);
}

@Override
public List<User> findAll() {
return userDao.findAll();
}

@Override
public User findById(String id) {
return userDao.findById(id);
}
}

最后的控制层 UserController:

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
/**
* @author mofan 2021/1/9
*/
@RestController
@CrossOrigin
@RequestMapping("user")
public class UserController {
@Autowired
private UserService userService;


@GetMapping("findAll")
public Map<String, Object> findAll(Integer page, Integer rows) {
Map<String, Object> map = new HashMap<>(8);
List<User> results = userService.findAll();

map.put("total", 10);
map.put("totalPage", 1);
map.put("page", page);
map.put("results", results);

return map;
}

@PostMapping("add")
public Map<String, Object> add(@RequestBody User user) {
Map<String, Object> map = new HashMap<>(8);
try {
userService.save(user);
map.put("success", true);
map.put("msg", "添加用户信息成功");
} catch (Exception e) {
map.put("success", false);
map.put("msg", "添加用户信息失败" + e.getMessage());
}
return map;
}

@GetMapping("delete")
public Map<String, Object> delete(String id) {
Map<String, Object> map = new HashMap<>(8);
try {
userService.delete(id);
map.put("success", true);
map.put("msg", "删除用户信息成功");
} catch (Exception e) {
map.put("success", false);
map.put("msg", "删除用户信息失败" + e.getMessage());
}
return map;
}

@GetMapping("findOne")
public User findOne(String id) {
return userService.findById(id);
}

@PostMapping("update")
public Map<String, Object> update(@RequestBody User user) {
Map<String, Object> map = new HashMap<>(8);
try {
userService.update(user);
map.put("success", true);
map.put("msg", "修改用户信息成功");
} catch (Exception e) {
map.put("success", false);
map.put("msg", "修改用户信息失败" + e.getMessage());
}
return map;
}
}

前端路径修改

根据控制层的路径,需要对前端 Axios 的请求路径进行修改,将请求路径修改成如下路径:

1
2
3
4
5
6
7
8
9
this.$http.get("http://localhost:8989/vue/user/findAll?page=1&rows=4")

this.$http.get("http://localhost:8989/vue/user/delete?id=" + id)

this.$http.post("http://localhost:8989/vue/user/add", this.user)

this.$http.get("http://localhost:8989/vue/user/findOne?id=" + this.user.id)

this.$http.post("http://localhost:8989/vue/user/update",this.user)

这样,前后端就编写完了,功能测试也可以通过,只剩最后的打包部署了。 😎

7.7 打包部署

在项目的根目录中执行以下命令:

1
npm run build

注意: Vue 脚手架打包的项目必须在服务器上运行,不能直接双击运行。

等待打包成功后,可以看到在项目文件夹中出现 dist 目录,dist 目录就是 Vue 脚手架项目生产目录或者说是直接部署目录。

那怎么使用呢?

可以直接将 dist 文件夹放到 SpringBoot 项目或者 Tomcat 或者 Nginx 中,咱们以 SpringBoot 项目而言,可以直接将 dist 目录拷贝到 resources 目录下的 static 文件夹中。此时 resources 的目录结构如下:

├─indi
│  └─mofan
│      └─mapper
├─static
│  └─dist
│      └─static
│          ├─css
│          └─js
└─templates

还没完,还需要修改 dist 目录下的 index.html 文件,因为此时这个文件的路径是不对的,启动项目后无法访问的。

修改前:

1
2
3
4
5
6
7
8
9
10
11
12
13
<head>
<meta charset=utf-8>
<meta name=viewport content="width=device-width,initial-scale=1">
<title>users</title>
<link href=/static/css/app.ef585a51c06fc65944dc9d07c172ce4c.css rel=stylesheet>
</head>

<body>
<div id=app></div>
<script type=text/javascript src=/static/js/manifest.2ae2e69a05c33dfc65f8.js></script>
<script type=text/javascript src=/static/js/vendor.d6b702d7d3b945faef84.js></script>
<script type=text/javascript src=/static/js/app.333b75cc2b1add4904b4.js></script>
</body>

根据上述代码中的路径,修改后为:

1
2
3
4
5
6
7
8
9
10
11
12
13
<head>
<meta charset=utf-8>
<meta name=viewport content="width=device-width,initial-scale=1">
<title>users</title>
<link href=/vue/dist/static/css/app.ef585a51c06fc65944dc9d07c172ce4c.css rel=stylesheet>
</head>

<body>
<div id=app></div>
<script type=text/javascript src=/vue/dist/static/js/manifest.2ae2e69a05c33dfc65f8.js></script>
<script type=text/javascript src=/vue/dist/static/js/vendor.d6b702d7d3b945faef84.js></script>
<script type=text/javascript src=/vue/dist/static/js/app.333b75cc2b1add4904b4.js></script>
</body>

其中的 vue 路径是在 application.properties 配置文件中进行的配置:

1
server.servlet.context-path=/vue

如果没配置,就不写。

dist 指的就是 dist 文件夹。

最后,根据前面的配置和路径,启动项目后,在浏览器地址栏键入以下地址就可进入主界面:

http://localhost:8989/vue/dist/index.html

Vue脚手架案例实战主界面

搞定,收工!🔨

伊莉雅推眼镜

Vue2.0 快速入门完