Vue2基础知识

一. Vue2基础

1. 环境准备

安装脚手架

1
npm install -g @vue/cli
  • g 参数表示全局安装,这样在任意目录都可以使用 vue 脚本创建项目

创建项目

使用在需要创建项目的文件夹下打开命令行操作,输入指令回车后会跳转到vue创建项目的网站中去

1
vue ui

在这里可以进行vue项目的创建


安装 devtools


运行vue

在项目文件夹里打开终端输入指令

1
npm run serve

修改端口号

1
2
3
devServer: {
port: 7070
}

添加代理

为了避免前后端服务器联调时, fetch、xhr 请求产生跨域问题,需要配置代理

  • 文档地址:DevServer | webpack

  • 打开 vue.config.js 添加

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
      
    devServer: {
    port: 7070,
    proxy: {
    '/api': {
    target: 'http://localhost:8080',
    changeOrigin: true
    }
    }
    }
    })

Vue项目结构

  • assets - 静态资源
  • components - 可重用组件
  • router - 路由
  • store - 数据共享
  • views - 视图组件

以后还会添加

  • api - 跟后台交互,发送 fetch、xhr 请求,接收响应
  • plugins - 插件


2. Vue组件

Vue 的组件文件以 .vue 结尾,每个组件由三部分组成

1
2
3
4
5
<template></template>

<script></script>

<style></style>
  • template 模板部分,由它生成 html 代码
  • script 代码部分,控制模板的数据来源和行为
  • style 样式部分,一般不咋关心

先删除App.vue原有代码,来个 Hello, World 例子

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<h1>{{msg}}</h1>
</template>

<script>
export default {
data() {
return {
msg: "Hello, Vue!"
}
}
}
</script>

解释

  • export default 导出组件对象,供 main.js 导入使用
  • 这个对象有一个 data 方法,返回一个对象,给 template 提供数据
  • {{}} 在 Vue 里称之为插值表达式,用来绑定 data 方法返回的对象属性,绑定的含义是数据发生变化时,页面显示会同步变化

2.1 文本插值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div>
<h1>{{ name }}</h1>
<h1>{{ age > 60 ? '老年' : '青年' }}</h1>
</div>
</template>
<script>
const options = {
data: function () {
return { name: '张三', age: 70 };
}
};
export default options;
</script>
  • {{}} 里只能绑定一个属性,绑定多个属性需要用多个 {{}} 分别绑定
  • template 内只能有一个根元素
  • 插值内可以进行简单的表达式计算

2.2 属性绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div>
<div><input type="text" v-bind:value="name"></div>
<div><input type="date" v-bind:value="birthday"></div>
<div><input type="text" :value="age"></div>
</div>
</template>
<script>
const options = {
data: function () {
return { name: '王五', birthday: '1995-05-01', age: 20 };
}
};
export default options;
</script>
  • 简写方式:可以省略 v-bind 只保留冒号

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
24
25
26
<!-- 事件绑定 -->
<template>
<div>
<div><input type="button" value="点我执行m1" v-on:click="m1"></div>
<div><input type="button" value="点我执行m2" @click="m2"></div>
<div>{{count}}</div>
</div>
</template>
<script>
const options = {
data: function () {
return { count: 0 };
},
methods: {
m1() {
this.count ++;
console.log("m1")
},
m2() {
this.count --;
console.log("m2")
}
}
};
export default options;
</script>
  • 简写方式:可以把 v-on: 替换为 @
  • 在 methods 方法中的 this 代表的是 data 函数返回的数据对象


2.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
<template>
<div>
<div>
<label for="">请输入姓名</label>
<input type="text" v-model="name">
</div>
<div>
<label for="">请输入年龄</label>
<input type="text" v-model="age">
</div>
<div>
<label for="">请选择性别</label>
男 <input type="radio" value="男" v-model="sex">
女 <input type="radio" value="女" v-model="sex">
</div>
<div>
<label for="">请选择爱好</label>
游泳 <input type="checkbox" value="游泳" v-model="fav">
打球 <input type="checkbox" value="打球" v-model="fav">
健身 <input type="checkbox" value="健身" v-model="fav">
</div>
</div>
</template>
<script>
const options = {
data: function () {
return { name: '', age: null, sex:'男' , fav:['打球']};
},
methods: {
}
};
export default options;
</script>
  • 用 v-model 实现双向绑定,即
    • JavaScript 数据可以同步到表单标签
    • 反过来用户在表单标签输入的新值也会同步到 JavaScript 这边
  • 双向绑定只适用于表单这种带【输入】功能的标签,其它标签的数据绑定,单向就足够了
  • 复选框这种标签,双向绑定的 JavaScript 数据类型一般用数组

2.5 计算属性

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
<!-- 计算属性 -->
<template>
<div>
// <h2>{{fullName()}}</h2>
<h2>{{fullName}}</h2>
<h2>{{fullName}}</h2>
<h2>{{fullName}}</h2>
</div>
</template>
<script>
const options = {
data: function () {
return { firstName: '哥', lastName: '正' };
},
/* methods: {
fullName() {
console.log('进入了 fullName')
return this.lastName + this.firstName;
}
},*/
computed: {
fullName() {
console.log('进入了 fullName')
return this.lastName + this.firstName;
}
}
};
export default options;
  • 普通方法调用必须加 (),没有缓存功能
  • 计算属性使用时就把它当属性来用,不加 (),有缓存功能:
    • 一次计算后,会将结果缓存,下次再计算时,只要数据没有变化,不会重新计算,直接返回缓存结果


3. axios

axios 它的底层是用了 XMLHttpRequest(xhr)方式发送请求和接收响应,xhr 相对于之前讲过的 fetch api 来说,功能更强大,但由于是比较老的 api,不支持 Promise,axios 对 xhr 进行了封装,使之支持 Promise,并提供了对请求、响应的统一拦截功能

安装

1
npm install axios -S

导入

1
import axios from 'axios'
  • axios 默认导出一个对象,这里的 import 导入的就是它默认导出的对象

方法

请求 备注
axios.get(url[, config])
axios.delete(url[, config])
axios.head(url[, config])
axios.options(url[, config])
axios.post(url[, data[, config]])
axios.put(url[, data[, config]])
axios.patch(url[, data[, config]])
  • config - 选项对象、例如查询参数、请求头…
  • data - 请求体数据、最常见的是 json 格式数据
  • get、head 请求无法携带请求体,这应当是浏览器的限制所致(xhr、fetch api 均有限制)
  • options、delete 请求可以通过 config 中的 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
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
<template>
<div>
<input type="button" value="获取远程数据" @click="sendReq()">
</div>
</template>
<script>
import axios from 'axios'
const options = {
methods: {
async sendReq() {
// 1. 演示 get, post
const resp1 = await axios.get('/api/a2');
const resp2 = await axios.post('/api/a2');

// 2. 发送请求头
const resp3 = await axios.post('/api/a3',{},{
headers:{
Authorization:'abc'
}
});

// 3. 发送请求时携带查询参数 ?name=xxx&age=xxx
const name = encodeURIComponent('&&&');
const age = 18;
const resp4 = await axios.post(`/api/a4?name=${name}&age=${age}`);

// 不想自己拼串、处理特殊字符、就用下面的办法
const resp5 = await axios.post('/api/a4', {}, {
params: {
name:'&&&&',
age: 20
}
});

// 4. 用请求体发数据,格式为 urlencoded
const params1 = new URLSearchParams();
params1.append("name", "张三");
params1.append("age", 24)

const resp6 = await axios.post('/api/a4', params);

// 5. 用请求体发数据,格式为 multipart
const params2 = new FormData();
params2.append("name", "李四");
params2.append("age", 30);
const resp7 = await axios.post('/api/a5', params);

// 6. 用请求体发数据,格式为 json
const resp8 = await axios.post('/api/a5json', {
name: '王五',
age: 50
});

console.log(resp);
}
}
};
export default options;
</script>

创建实例

1
const _axios = axios.create(config);
  • axios 对象可以直接使用,但使用的是默认的设置
  • 用 axios.create 创建的对象,可以覆盖默认设置,config 见下面说明

常见的 config 项有

名称 含义
baseURL 将自动加在 url 前面
headers 请求头,类型为简单对象
params 跟在 URL 后的请求参数,类型为简单对象或 URLSearchParams
data 请求体,类型有简单对象、FormData、URLSearchParams、File 等
withCredentials 跨域时是否携带 Cookie 等凭证,默认为 false
responseType 响应类型,默认为 json

1
2
3
4
5
6
const _axios = axios.create({
baseURL: 'http://localhost:8080',
withCredentials: true
});
await _axios.post('/api/a6set')
await _axios.post('/api/a6get')
  • 生产环境希望 xhr 请求不走代理,可以用 baseURL 统一修改
  • 希望跨域请求携带 cookie,需要配置 withCredentials: true,服务器也要配置 allowCredentials = true,否则浏览器获取跨域返回的 cookie 时会报错

响应格式

名称 含义
data 响应体数据
status 状态码
headers 响应头
  • 200 表示响应成功
  • 400 请求数据不正确 age=abc
  • 401 身份验证没通过
  • 403 没有权限
  • 404 资源不存在
  • 405 不支持请求方式 post
  • 500 服务器内部错误

请求拦截器

1
2
3
4
5
6
7
8
9
10
11
12
_axios.interceptors.request.use(
function(config) {
// 比如在这里添加统一的 headers
config.headers = {
Authorization: 'aaa.bbb.ccc'
}
return config;
},
function(error) {
return Promise.reject(error);
}
);

响应拦截器

1
2
3
4
5
6
7
8
9
10
_axios.interceptors.response.use(
function(response) {
// 2xx 范围内走这里
return response;
},
function(error) {
// 超出 2xx, 比如 4xx, 5xx 走这里
return Promise.reject(error);
}
);
  • 这里的 _axios 是自己创建的axios对象

  • 请求拦截器中第一个方法是请求正常的情况下要执行的拦截动作,第二个方法是请求拦截异常的情况下要执行的拦截动作

  • 响应拦截器中第一个方法是响应正常的情况下要执行的拦截动作,第二个方法是响应拦截异常的情况下要执行的拦截动作

  • 响应时出现的异常可以放在响应拦截器中的第二个方法中去,如下所示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    _axios.interceptors.response.use(
    function(response) {
    return response;
    },
    function(error) {
    if (error.response.status === 400) {
    console.log('请求参数不正确');
    return Promise.resolve(400);
    } else if (error.response.status === 401) {
    console.log('跳转至登录页面');
    return Promise.resolve(401);
    } else if (error.response.status === 404) {
    console.log('资源未找到');
    return Promise.resolve(404);
    }
    return Promise.reject(error);
    }
    );

把上面的代码抽取到一个js文件中去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import axios from 'axios'
const _axios = axios.create({
// baseURL: 'http://localhost:8080',
withCredentials: true
});
_axios.interceptors.request.use(
function(config) {
// 比如在这里添加统一的 headers
config.headers = {
Authorization: 'aaa.bbb.ccc'
}
return config;
},
function(error) {
return Promise.reject(error);
}
);
_axios.interceptors.response.use(
function(response) {
return response;
},
function(error) {
if (error.response.status === 400) {
console.log('请求参数不正确');
return Promise.resolve(400);
} else if (error.response.status === 401) {
console.log('跳转至登录页面');
return Promise.resolve(401);
} else if (error.response.status === 404) {
console.log('资源未找到');
return Promise.resolve(404);
}
return Promise.reject(error);
}
);
export default _axios;

二. Vue2进阶

1. ElementUI

安装:在vue项目文件下打开命令行输入以下安装代码

1
npm install element-ui -S

安装完后在package.json文件中会引入element-ui的依赖


引入组件

把下面的代码放入main.js中,Vue.use(Element)要放在new Vue前面

1
2
3
4
import Element from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'

Vue.use(Element)

1.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
<template>
<div>
<el-table :data="students">
<el-table-column label="编号" prop="id"></el-table-column>
<el-table-column label="姓名" prop="name"></el-table-column>
<el-table-column label="性别" prop="sex"></el-table-column>
<el-table-column label="年龄" prop="age"></el-table-column>
</el-table>
</div>
</template>
<script>
import axios from '../utils/myaxios'
const options = {
async mounted() {
const resp = await axios.get('/api/students');
this.students = resp.data.data
},
data() {
return {
students: []
}
}
}
export default options;
</script>
  • el-table:表格
    • data:要展示的数据对象
  • el-table-column:表格中的列
    • label:命名
    • prop:数据对象对应的属性

1.2 分页组件

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
<template>
<div>
<el-table :data="students">
<el-table-column label="编号" prop="id"></el-table-column>
<el-table-column label="姓名" prop="name"></el-table-column>
<el-table-column label="性别" prop="sex"></el-table-column>
<el-table-column label="年龄" prop="age"></el-table-column>
</el-table>
<el-pagination
:total="total"
:page-size="queryDto.size"
:current-page="queryDto.page"
layout="prev,pager,next,sizes,->,total"
:page-sizes="[5,10,15,20]"
@current-change="currentChange"
@size-change="sizeChange"
></el-pagination>
</div>
</template>

<script>
import axios from '../utils/myaxios'
const options = {
mounted() {
this.query();
},
methods: {
currentChange(page) {
this.queryDto.page = page;
this.query();
},
sizeChange(size) {
this.queryDto.size = size;
this.query();
},
async query() {
const resp = await axios.get('/api/students/q', {
params: this.queryDto
});
this.students = resp.data.data.list;
this.total = resp.data.data.total;
}
},
data() {
return {
students: [],
queryDto: {
page: 1,
size: 5
},
total: 0
}
}
}
export default options;
</script>
  • el-pagination:element-ui分页组件

    • 有 : 和 没有 : 的区别,有:表示该属性的值会去data中去查找,没有则是给定固定值
    • total: 总数量
    • page-size:每页条目个数,可以自定义
      • :page-sizes=”[5,10,15,20]” :自定义每页条目数
    • layout:组件布局,子组件名用逗号分隔(决定页面显示哪些组件)sizes, prev, pager, next, jumper, ->, total, slot
    • @current-change:current- page改变时会触发,这里通过调用自定义函数currentChange来改变data中的page,来实现数据的双向绑定
    • @size-change:page-size改变时会触发,这里通过调用自定义函数sizeChange来改变data中的size,来实现数据的双向绑定
  • query()函数:把向前端的请求封装成方法,方便调用。刚开始加载页面时会调用(钩子函数中),页数改变时会调用(currentChange函数中),每页条目个数改变时会调用(sizeChange函数中)

  • this.students = resp.data.data.list和this.total = resp.data.data.total 因为后端返回的数据中的data是一个map集合


1.3 分页搜索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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
<template>
<div>
<!-- 输入框,用于输入姓名 -->
<el-input placeholder="请输入姓名" size="mini" v-model="queryDto.name"></el-input>

<!-- 下拉选择框,用于选择性别 -->
<el-select placeholder="请选择性别" size="mini" v-model="queryDto.sex">
<el-option value="" label="无限制"></el-option>
<el-option value="男"></el-option>
<el-option value="女"></el-option>
</el-select>

<!-- 下拉选择框,用于选择年龄范围 -->
<el-select placeholder="请选择年龄" size="mini" v-model="queryDto.age">
<el-option value="" label="无限制"></el-option>
<el-option value="0,20" label="0到20"></el-option>
<el-option value="21,40" label="21到40"></el-option>
<el-option value="41,60" label="41到60"></el-option>
<el-option value="61,200" label="61岁以上"></el-option>
</el-select>

<!-- 按钮,点击后进行搜索 -->
<el-button type="primary" size="mini" @click="search()">搜索</el-button>

<!-- 表格,用于显示学生数据 -->
<el-table :data="students">
<el-table-column label="编号" prop="id"></el-table-column>
<el-table-column label="姓名" prop="name"></el-table-column>
<el-table-column label="性别" prop="sex"></el-table-column>
<el-table-column label="年龄" prop="age"></el-table-column>
</el-table>

<!-- 分页器,用于切换页面和调整每页显示的数量 -->
<el-pagination
:total="total"
:page-size="queryDto.size"
:current-page="queryDto.page"
layout="prev,pager,next,sizes,->,total"
:page-sizes="[5,10,15,20]"
@current-change="currentChange"
@size-change="sizeChange"
></el-pagination>
</div>
</template>

<script>
import axios from '../utils/myaxios'
const options = {
// 组件挂载后立即执行查询
mounted() {
this.query();
},
methods: {
// 搜索方法,执行查询
search() {
this.query();
},
// 当前页改变时的处理方法
currentChange(page) {
this.queryDto.page = page;
this.query();
},
// 每页显示数量改变时的处理方法
sizeChange(size) {
this.queryDto.size = size;
this.query();
},
// 查询方法,从后端获取数据
async query() {
const resp = await axios.get('/api/students/q', {
params: this.queryDto
});
this.students = resp.data.data.list;
this.total = resp.data.data.total;
}
},
// 组件的初始数据
data() {
return {
students: [], // 学生数据
queryDto: { // 查询参数
name: '',
sex: '',
age: '',
page: 1,
size: 5
},
total: 0 // 总数
}
}
}
export default options;
</script>

<style scoped>
/* 设置输入框和选择框的宽度和边距 */
.el-input--mini,
.el-select--mini {
width: 193px;
margin: 10px 10px 0 0;
}
</style>
  • el-option中的value是传给后端的值,label是显示在前端页面的值,如果没有添加label属性则把value显示在前端页面
  • 输入框和选择框都可以通过v-model实现数据的双向绑定


1.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
62
63
64
65
66
<template>
<!-- el-cascader 组件用于显示级联选择器 -->
<el-cascader :options="ops" clearable></el-cascader>
</template>
<script>
const options = {
data() {
return {
// ops 是级联选择器的选项数据
ops:[
// 第一级选项:菜品
{value:100 ,label:'菜品' ,children:[
// 第二级选项:湘菜
{value:110, label:'湘菜', children:[
// 第三级选项:剁椒鱼头
{value:111, label:'剁椒鱼头'},
// 第三级选项:辣椒炒肉
{value:112, label:'辣椒炒肉'}
]},
// 第二级选项:川菜
{value:120, label:'川菜', children:[
// 第三级选项:红烧兔头
{value:121, label:'红烧兔头'},
// 第三级选项:宫保鸡丁
{value:122, label:'宫保鸡丁'}
]},
// 第二级选项:粤菜
{value:130, label:'粤菜', children:[
// 第三级选项:白切鸡
{value:131, label:'白切鸡'},
// 第三级选项:蜜汁叉烧
{value:132, label:'蜜汁叉烧'}
]}
]},
// 第一级选项:饮品
{value:200 ,label:'饮品' ,children:[
// 第二级选项:饮料
{value:210, label:'饮料', children:[
// 第三级选项:王老吉
{value:211, label:'王老吉'},
// 第三级选项:可乐
{value:212, label:'可乐'},
// 第三级选项:雪碧
{value:213, label:'雪碧'}
]},
// 第二级选项:啤酒
{value:220, label:'啤酒', children:[
// 第三级选项:雪花
{value:221, label:'雪花'},
// 第三级选项:百威
{value:222, label:'百威'}
]},
// 第二级选项:白酒
{value:230, label:'白酒', children:[
// 第三级选项:茅台
{value:231, label:'茅台'},
// 第三级选项:江小白
{value:232, label:'江小白'}
]}
]},
]
}
}
};
export default options;
</script>



Vue2基础知识
https://lzhengjy.github.io/2023/11/04/Vue2基础知识/
作者
Zheng
发布于
2023年11月4日
许可协议