Vue

此文章用于记录在Vue遇到的一些问题和解决方案

Vue对于axios的并行请求处理

使用该方法, 优化性能与数据渲染效果, axios.all和axios.spread , 所有请求同时进行, 等请求全部发送后再对数据处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 官方示例
function getUserAccount() {
// 封装Promise请求
return axios.get('/user/12345');
}

function getUserPermissions() {
return axios.get('/user/12345/permissions');
}

axios.all([getUserAccount(), getUserPermissions()])
// spread将各请求结果拆分返回
.then(axios.spread(function (resp1, resp2) {
// 两个请求现在都执行完成
// 此时例如我需要对这两个请求进行处理
let permissions = resp1.data;// 可以使用此方案接收
if(permissions.code == 200){}// 进行数据处理
}));

axios.all方法接受一个数组作为参数,数组中的每个元素都是一个请求,返回一个promise对象,当数组中所有请求均已完成时,执行then方法。
在then方法中执行了 axios.spread 方法。该方法是接收一个函数作为参数,返回一个新的函数。接收的参数函数的参数是axios.all方法中每个请求返回的响应

应用场景:

并发请求: 当您需要同时发送多个请求,并在所有请求完成后处理它们的响应时,可以使用这些方法。例如,在一个页面上加载多个资源或从多个 API 端点获取数据时,您可以使用 axios.all 来同时发送这些请求,并使用 axios.spread 来处理每个请求的响应数据。

依赖关系请求: 有时,您可能需要在一个请求的结果中使用另一个请求的结果。使用 axios.all,您可以并发发送这些请求,并在它们都完成后使用 axios.spread 来处理它们的响应。这可以帮助您更高效地处理具有依赖关系的请求。

批量操作: 如果您需要执行一系列类似的操作,例如创建、更新或删除多个资源,您可以使用 axios.all 方法来同时发送这些请求,并使用 axios.spread 来处理每个请求的结果。这样可以减少请求的数量和网络延迟。

MVVM模型

什么是MVVM? MVVM 其实表示的是 View-ViewModel-Model

其中对应的就是:视图层-视图模型层-模型层, Model 是作为模型层,它是负责处理业务逻辑以及和服务器端进行交互的;ViewModel 是作为视图模型层,也就是 Vue 框架所起到的作用了,主要是作为 View 层和 Model 层之间的通信桥梁,

遇到的问题

在页面视图渲染完成之后再往已经渲染完成的data模型添加新属性, 从而后续的Vue操作在页面渲染不生效, 但是通过debug查找, 渲染模型已经有属性了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 显示
* @param jsonModel 分页数据(后端返回原始数据)
*/
displayPage(jsonModel) {
const _this = this
if(jsonModel.code === 200){
_this.pageInfo = jsonModel.data
_this.foods = jsonModel.data.data
// foods: data模型
_this.foods.forEach((item, index) => {
item.status = false // 往foods模型里面添加字段status
})
}
}

当前为问题样例:

jsonModel是后台传的原始数据对象, 当前操作是先给模型固定数据, 然后在进行对模型添加字段, 所带来的问题是: 页面模型已经渲染, 并且当前新添加的字段数据更新, 它不会触发Vue的Observe监听

解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 显示
* @param jsonModel 分页数据
*/
displayPage(jsonModel) {
const _this = this
if(jsonModel.code === 200){
// Vue 是MVVM模型 ,我们需要在渲染前修改模型数据
// 渲染模型
jsonModel.data.data.forEach((item, index) => {
item.status = false
})
// 进行页面模型对象渲染
_this.pageInfo = jsonModel.data
_this.foods = jsonModel.data.data
}
},

在页面对象模型渲染, 对模型数据进行修改

ES6异步

Promise

Promise是一个对象

Promise 状态(对象,需要实例化)

Promise操作有三种状态:

  1. pending(进行中)
  2. fulfilled(已成功)
  3. rejected(已失败)

除了异步操作的结果,任何其他操作都无法改变这个状态

Promise 对象只有:从 pending 变为 fulfilled 和从 pending 变为 rejected 的状态改变。只要处于 fulfilled 和 rejected ,状态就不会再变了即 resolved(已定型)。

缺点:1.无法取消 Promise ,一旦新建它就会立即执行,无法中途取消。 2.如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。 3.当处于 pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

.then

then 方法接收两个函数作为参数,第一个参数是 Promise 执行成功时的回调,第二个参数是 Promise 执行失败时的回调,两个函数只会有一个被调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var a = new Promise(function (resolve, reject){
// 例:
// 访问后台url
// 得到结果: jsonModel => 1
// axios.get()
var jsonModel = {name:'then',age=18}
resolve(jsonModel)
}).then(function (value){
console.log(value) // 1 处理这个value -> jsonModel
return value // 如果需要连续发请求的话,该返回值又会被包装成Promise对象
}).then(function (value){
// 当前then回调没有返回值
console.log(value.age) // 18
}).then(function (value){
// 上一个函数没有返回值给这个函数
console.log(value) // undefined
}).catch(function (value){
// 异常处理
})
  • then 方法将返回一个 resolvedrejected状态的 Promise 对象用于链式调用,且 Promise 对象的值就是这个返回值。

.catch

Promise.catch()方法是promise.then(undefined,onRejected)方法的一个别名,该方法用来注册当promise对象状态变为Rejected的回调函数。

通俗来说就是:发生异常执行的

.all(静态方法)

Promise.all可以接受一个元素为Promise对象的数组作为参数,当这个数组里面所有的promise对象都变为resolve时,该方法才会返回。Promise.all方法中会按照数组的原先顺序将结果返回;

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var promise1 = new Promise(function (resolve)[
// setInterval() 定时任务
setTimeout(function (){
resolve('请求1')
},3000)
])
var promise2 = new Promise(function (resolve)[
// setInterval() 定时任务
setTimeout(function (){
resolve('请求2')
},1000)
])
// all表示以上两个函数都执行完
Promise.all([ promise1,promise2 ]),then(function (){
console.log(value) // 则会打印 [请求1,请求2]
})

当前小结处可以查看当前文档的 #Vue对于axios的并行请求处理

.race

Promise.race的含义是只要有一个promise对象进入FulFilled或者Rejected状态的话,程序就会停止,且会继续后面的处理逻辑

可以参考ES6的promise对象研究 - 龙恩0707 - 博客园 (cnblogs.com)

Generator

构建器

可以通过 yield 关键字,把函数的执行流挂起,为改变执行流程提供了可能,从而为异步编程提供解决方案。

和一般方法不同的是: 1.在 function 后面,函数名之前有个 * 2.函数内部有 yield 表达式

1
2
3
4
function* fun(){
console.log("one");
yield '1';
}

用法:

调用 Generator 函数和调用普通函数一样,在函数名后面加上()即可,但是 Generator 函数不会像普通函数一样立即执行,而是返回一个指向内部状态对象的指针,所以要调用遍历器对象Iterator 的 next 方法,指针就会从函数头部或者上一次停下来的地方开始执行

这个就有点类似于Java的迭代器

1
2
3
4
Iterator<String> it = list.iterator();
while(it.hasNext()){
it.next();
}

单向移动, 指针后移

文件对象

在日常, 对于文件的上传操作是必要的

图片上传

页面代码案例:

需求:我需要对id=fphoto的文件对象进行处理

1
2
3
4
5
6
7
8
9
10
11
<form>
<div class="form-group">
<label for="fphoto">图片</label>
<input type="file" id="fphoto" name="fphoto">
</div>
<div class="form-group">
<label for="detail">详情</label>
<textarea v-model="detail" rows="5" cols="50" id="detail"> </textarea>
</div>
<button @click="submit" type="button" class="btn btn-default">上架 </button>
</form>

常见方案

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
submit() {
const _this = this
// 对于文件上传, 必须为post, 并且需要指定Content-Type: multipart/form-data
let fphoto = document.querySelector("#fphoto").files[0] // 这个可以取多个文件, 我们只取一个
// 创建formdata对象
let formdata = new FormData()
formdata.append("fphoto",fphoto) // 文件对象
// 其他的对象参数(append进去)
formdata.append("detail", _this.detail)
const req = {
headers:{
"Content-Type":"multipart/form-data"
}
}
// 请求参数配置
axios({
url:'/upload',
method:'post',
data:formdata,
headers: req.headers
}).then(res => {
// 处理请求 当前章节次要
let jsonModel = res.data
if(jsonModel.code === 200){
let jsonModel = res.data
if(jsonModel.code === 200){
alert("上传成功!")
}
}
})
}

该案例总结

  • 对于文件上传, 必须为post
  • 并且需要在headers指定Content-Type: multipart/form-data
  • 剩下的参数都用formdata包装即可

前端导出Excel

前端代码, 后端用EasyExcel实现, 详情见文档

导出excel, 浏览器弹窗下载关于Easyexcel | Easy Excel (alibaba.com)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
axios({
method: "post",
// 请求后台地址
url: "http://localhost:9000/upload/export/forEmployee",
responseType: "blob", //设置返回信息为二进制文件,默认为json
// 需要导出的数据
data: this.employeeList, //后台照常用@RequestBody接收即可
}).then((res) => {
// 这个地方我们应该拿到后端响应信息里面的文件信息, 此处分析其文件在res.data中
let blob = new Blob([res.data], { type: "application/xlsx" });
let url = window.URL.createObjectURL(blob);
const link = document.createElement("a"); //创建a标签
link.href = url;
link.download = "员工信息_" + new Date().getTime() + ".xlsx"; //重命名文件
link.click();
URL.revokeObjectURL(url);
});

组件通信

案例需求: 点击左边/右边元素, 然后点击左移, 实现元素移动

left operate right
A 左移 | 右移 D
B E
C F

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
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
<template>
<div id="app">
<MyCross
:left="this.left"
:right="this.right"
@moveLeft="moveLeft"
@moveRight="moveRight"
@moveAllLeft="moveLeftAll"
@moveAllRight="moveRightAll"
>
</MyCross>
</div>
</template>

<script>
import MyCross from "./components/Cross.vue";
export default {
components: {
Cross,
},
data() {
return {
left: [
{ id: 1, name: "林冲" },
{ id: 2, name: "武松" },
{ id: 3, name: "白胜" },
],
right: [
{ id: 1, name: "林冲" },
{ id: 2, name: "哈哈" },
],
};
},
methods: {
moveRight(item) {
this.right.push(item);
this.left = this.left.filter((i) => i !== item);
},
moveLeft(item) {
this.left.push(item);
this.right = this.right.filter((i) => i !== item);
},
moveLeftAll() {
this.right.forEach((item) => {
this.left.push(item);
});
this.right = [];
},
moveRightAll() {
this.left.forEach((item) => {
this.right.push(item);
});
this.left = [];
},
},
};
</script>

Cross.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
<template>
<div style="display: flex">
<ul>
<li v-for="item in left" :key="item.id" @click="moveRight(item)">
{{ item.name }}
</li>
</ul>
<div style="padding-top: 50px">
<button @click="moveAllLeft()">←←</button>
<br />
<button @click="moveAllRight()">→→</button>
</div>
<ul>
<li v-for="item in right" :key="item.id" @click="moveLeft(item)">
{{ item.name }}
</li>
</ul>
</div>
</template>

<script>
export default {
props: {
left: {
type: Array,
required: true,
},
right: {
type: Array,
required: true,
},
},
methods:{
moveRight(item){
this.$emit('moveRight',item)
},
moveLeft(item){
this.$emit('moveLeft',item)
},
moveAllLeft(){
this.$emit('moveAllLeft')
},
moveAllRight(){
this.$emit('moveAllRight')
},
}
};
</script>

说明: App.vue为父组件, Cross.vue为子组件

先注册组件

1
2
3
4
5
6
7
// 导入组件
import MyCross from "./components/Cross.vue";
export default {
components: {
// 注册组件
Cross,
},

父传子props

1
2
3
4
5
6
7
8
9
<MyCross
:left="this.left"
:right="this.right"
@moveLeft="moveLeft"
@moveRight="moveRight"
@moveAllLeft="moveLeftAll"
@moveAllRight="moveRightAll"
>
</MyCross>

父组件中引用子组件

通过语法糖(1) :left , 将父组件中的this.left数据传递给子组件, 子组件中同样需要使用该名接收

语法糖(2) @moveLeft: 子组件通知的事件名, 通知后会调用父组件中等号右侧的方法

在子组件中:

1
2
3
4
5
6
7
8
9
10
11
12
export default {
props: {
// 接收父组件中传的值(通过父组件的语法糖), 可以约束类型
left: {
type: Array,
required: true,
},
right: {
type: Array,
required: true,
},
},

此时假如父组件中传了值过来, 即可以直接使用该字段了, 在子组件中使用例如: this.left

子传父$emit

子传父, 例如此时需要通过点击事件触发

1
<button @click="moveLeft()">←←</button>

此按钮作用为: 点击了此按钮并且选中了一个元素, 则实现左移

1
2
3
4
5
6
methods:{
// 方法如下
moveLeft(item){
this.$emit('moveLeft',item) // 传输对象给父组件
},
}

通过语法 $emit通知父组件, 并且传了一个参数item过去

可以很容易的看出, 通知父组件的方法为 moveLeft, 那么父组件中调用子组件的语法中我们可以看到如下操作:

1
<MyCross @moveLeft="moveLeftFunc"></MyCross>

我们通过@moveLeft接收通知, 并且触发调用父组件中的方法moveLeftFunc, 形参接收子组件传输的对象

1
2
3
4
moveLeftFunc(item) { // item为子组件中传输的对象
this.left.push(item);
this.right = this.right.filter((i) => i !== item);
},

Vue-Admin-Template初始化

vue.config.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
module.exports = {
/**
* You will need to set publicPath if you plan to deploy your site under a sub path,
* for example GitHub Pages. If you plan to deploy your site to https://foo.github.io/bar/,
* then publicPath should be set to "/bar/".
* In most cases please use '/' !!!
* Detail: https://cli.vuejs.org/config/#publicpath
*/
publicPath: "/",
outputDir: "dist",
assetsDir: "static",
// 关闭eslint
// lintOnSave: process.env.NODE_ENV === "development",
lintOnSave: false,
productionSourceMap: false,
devServer: {
port: port,
open: true,
overlay: {
warnings: false,
errors: true,
},
proxy: {
[process.env.VUE_APP_BASE_API]: {
target: "http://127.0.0.1:9000",
changeOrigin: true,
pathRewrite: {
["^" + process.env.VUE_APP_BASE_API]: "",
},
},
},
// before: require('./mock/mock-server.js')
},

关闭本地mock模拟接口, 使用自定义后端接口

.env.development

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# just a flag
ENV = 'development'

# base api
# VUE_APP_BASE_API = 'api'
VUE_APP_BASE_API = 'http://localhost:9000'


# vue-cli uses the VUE_CLI_BABEL_TRANSPILE_MODULES environment variable,
# to control whether the babel-plugin-dynamic-import-node plugin is enabled.
# It only does one thing by converting all import() to require().
# This configuration can significantly increase the speed of hot updates,
# when you have a large number of pages.
# Detail: https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/babel-preset-app/index.js

VUE_CLI_BABEL_TRANSPILE_MODULES = true

routr/index.js

调整路由

utils/request.js

把响应返回值判断改成200