一、问题背景描述
使用vue开源的框架,环境搭建、运行都很正常,在页面加入了导出excel文件按钮
下载后打开文件报错,如下图所示,无法开发文件。
二、排查过程
1. 对比其他项目中vue写法,查看是否缺少类型配置
// 导出按钮
handleExport() {
this.download('iot/task/pumpExport', {
...this.queryParams
}, `pump_${new Date().getTime()}.xlsx`)
}
查看request.js中的download方法
// 通用下载方法
export function download(url, params, filename) {
downloadLoadingInstance = Loading.service({ text: "正在下载数据,请稍候", spinner: "el-icon-loading", background: "rgba(0, 0, 0, 0.7)", })
return service.post(url, params, {
transformRequest: [(params) => { return tansParams(params) }],
// headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
responseType: 'blob'
}).then( async (data) => {
const isLogin = await blobValidate(data);
console.log("download:",data)
if (isLogin) {
const blob = new Blob([data],{type: "application/vnd.ms-excel"})
// saveAs(blob, filename)
let link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.setAttribute('download', filename);
link.click();
link = null;
} else {
Message.error('无效的会话,或者会话已过期,请重新登录。');
}
downloadLoadingInstance.close();
}).catch((r) => {
console.error(r)
Message.error('下载文件出现错误,请联系管理员!')
downloadLoadingInstance.close();
})
}
对比其他能正常下载文件的源码,使用的下载方式通过创建a标签download属性,进行下载,更改vue代码后,下载的文件还是不能正常打开。
2. 查看后台服务端接口,文件导出接口是否正确。使用接口测试工具(postman、apifox)调用文件导出接口,是否正确返回,导出的文件可以正常打开,说明问题不在后台接口上。
3. 继续排查vue代码,vue代码中添加打印返回结果,发现设置的responseType没有起作用,返回的结果中responseType为空
网上搜索相关问题,看到有文章(参考了文章:Vue-element-ui responseType: ‘blob‘ 设置无效_elementui responsetype: "blob-CSDN博客)上说与mockjs有关,查找了代码,并未发现文中提到的设置
import Mock from 'mockjs'
//延时200-600毫秒请求到数据
Mock.setup({
timeout: '200-600'
})
const Random = Mock.Random;
// 用户总览
function countUserNum() {
const a = Mock.mock({
success: true,
data: {
offlineNum:'@integer(1, 100)',
lockNum: '@integer(1, 10)',
totalNum:218
}
})
a.data.onlineNum=a.data.totalNum-a.data.offlineNum-a.data.lockNum
return a
}
// 接口,第一个参数url,第二个参数请求类型,第三个参数响应回调
Mock.mock(new RegExp('countUserNum'), 'get', countUserNum)
// /设备总览
function countDeviceNum() {
const a = Mock.mock({
success: true,
data: {
alarmNum: '@integer(100, 1000)',
offlineNum: '@integer(0, 50)',
totalNum:698
}
})
a.data.onlineNum=a.data.totalNum-a.data.offlineNum
return a
}
Mock.mock(new RegExp('countDeviceNum'), 'get', countDeviceNum)
// /设备总览
function sbtx() {
const a = Mock.mock({
success: true,
data: {
"list|20": [
{
provinceName: "@province()",
cityName: '@city()',
countyName: "@county()",
createTime: "@datetime('yyyy-MM-dd HH:mm:ss')",
deviceId: "6c512d754bbcd6d7cd86abce0e0cac58",
"gatewayno|+1": 10000,
"onlineState|1": [0, 1],
}
]
}
})
return a
}
Mock.mock(new RegExp('sbtx'), 'get', sbtx)
//中间地图
function centermap(options) {
let params = parameteUrl(options.url)
if (params.regionCode && params.regionCode != -1) {
const a = Mock.mock({
success: true,
data: {
"dataList|30": [
{
name: "@city()",
value: '@integer(1, 1000)'
}
],
regionCode: params.regionCode,//-代表中国
}
})
return a
} else {
const a = Mock.mock({
success: true,
data: {
"dataList|8": [
{
name: "@province()",
value: '@integer(1, 1000)'
}
],
regionCode: -1,//-代表中国
}
})
return a
}
}
Mock.mock(new RegExp('centermap'), 'get', centermap)
// 报警次数
function alarmNum() {
const a = Mock.mock({
success: true,
data: {
dateList:['2021-11', '2021-12', '2022-01', '2022-02', '2022-03',"2022-04"],
"numList|6":[
'@integer(0, 1000)'
],
"numList2|6":[
'@integer(0, 1000)'
]
}
})
return a
}
Mock.mock(new RegExp('alarmNum'), 'get', alarmNum)
// 实时预警
function ssyj() {
const a = Mock.mock({
success: true,
data: {
"list|40":[{
alertdetail: "@csentence(5,10)",
"alertname|1": ["水浸告警","各种报警"],
alertvalue: "@float(60, 200)",
createtime: "2022-04-19 08:38:33",
deviceid: null,
"gatewayno|+1": 10000,
phase: "A1",
sbInfo: "@csentence(10,18)",
"terminalno|+1": 100,
provinceName: "@province()",
cityName: '@city()',
countyName: "@county()",
}],
}
})
return a
}
Mock.mock(new RegExp('ssyj'), 'get', ssyj)
//安装计划
function installationPlan() {
let num= RandomNumBoth(26,32);
const a = Mock.mock({
["category|"+num]:["@city()"],
["barData|"+num]:["@integer(10, 100)"],
})
let lineData=[],rateData=[];
for (let index = 0; index < num; index++) {
let lineNum = Mock.mock('@integer(0, 100)')+a.barData[index]
lineData.push(lineNum)
let rate = a.barData[index] / lineNum;
rateData.push((rate*100).toFixed(0))
}
a.lineData=lineData
a.rateData=rateData
return {
success: true,
data:a
}
}
Mock.mock(new RegExp('installationPlan'), 'get', installationPlan)
//报警排名
function ranking() {
//多生成几个避免重复 重复会报错
let num =Mock.mock({"list|48":[{ value:"@integer(50,1000)",name:"@city()"}]}).list
// console.log(num);
let newNum =[],numObj={}
num.map(item=>{
if(!numObj[item.name] && newNum.length<8){
numObj[item.name] =true
newNum.push(item)
}
})
let arr = newNum.sort((a,b)=>{
return b.value-a.value
})
let a ={
success:true,
data:arr
}
return a
}
Mock.mock(new RegExp('ranking'), 'get', ranking)
/**
* @description: min ≤ r ≤ max 随机数
* @param {*} Min
* @param {*} Max
* @return {*}
*/
function RandomNumBoth(Min,Max){
var Range = Max - Min;
var Rand = Math.random();
var num = Min + Math.round(Rand * Range); //四舍五入
return num;
}
/**
* @description: 获取路径参数
* @param {*} url
* @return {*}
*/
function parameteUrl(url) {
var json = {}
if (/\?/.test(url)) {
var urlString = url.substring(url.indexOf("?") + 1);
var urlArray = urlString.split("&");
for (var i = 0; i < urlArray.length; i++) {
var urlItem = urlArray[i];
var item = urlItem.split("=");
console.log(item);
json[item[0]] = item[1];
}
return json;
}
return {};
}
三、解决
最后尝试了下,删除mockjs(把代码中模拟数据接口的代码全删除),再运行vue,结果能正常下载并打开文件
import Mock from 'mockjs'
//延时200-600毫秒请求到数据
Mock.setup({
timeout: '200-600'
})
const Random = Mock.Random;
// 删除了其他模拟数据接口代码
四、总结
这个问题困扰了我两夜一天,前后台代码修改、调整、调试了很多次。
其实很早就能排除后台接口问题,但当时没有发现问题时,想着不放弃任何可能性,调整接口代码,甚至尝试了多个导出Excel文件的三方库,但结果还是一样,徒劳
最后在mockjs问题上也犹豫了很长时间,因为实在是没看到怎么影响的,最后也是出于尝试下的心里,没想到问题解决了。