需求: 要求批量导入 spuid 的表格后导出对应的商品图片,且以对应的 spuid 为命名的文件夹下存放图片,导出是压缩包的形式。
实现效果:
主页面:
导入的文件:
解压展示如下图:
用 fetch 请求图片资源
接口返回的图片资源有:
请求的图片资源时有:
这里实现的难点在于: 请求图片资源时用 fetch而非 axios,因为用 axios 拿到的是二进制,这时要学会用 fetch
const response = await fetch(url);
const blob = await response.blob();
用 axios 返回的文件是二进制的,如下所示,不推荐
const imageResponse = await axios.get(imageUrl, { responseType: 'blob' });
const imageBlob = imageResponse.data;
所有代码
<template>
<basic-container>
<div class="tips">
<p>操作指引:</p>
<p>1、上传需要下载图片的spuid;</p>
<p>2、点击下载则开始执行,文件以spuid进行命名</p>
</div>
<div style="padding: 22px 0">
<el-upload action="" :show-file-list="false" :accept="'.xlsx'" style="display: inline-block"
:http-request="importData">
<el-button type="primary" icon="el-icon-upload" size="mini">上传文件
</el-button>
</el-upload>
<el-button type="primary" icon="el-icon-download" size="mini" style="margin-left: 22px;"
@click="downloadTemplate">下载模板
</el-button>
</div>
<el-dialog title="导出图片" :visible.sync="progressExportVisible" width="30%" center>
<strong style="margin-bottom: 10px;">当前进度:</strong>
<el-progress text-color="#434343" :stroke-width="20" :percentage="progressExportPageSize"></el-progress>
<span slot="footer" class="dialog-footer">
<el-button @click="progressExportVisible = false" size="mini">取 消</el-button>
<el-button type="primary" @click="onConfirmProgressData" size="mini">下载压缩包</el-button>
</span>
</el-dialog>
</basic-container>
</template>
<script>
import ProgressExport from '@/components/progress-export/index.vue';
import { mapGetters } from 'vuex';
import axiosHttp from '@/util/http';
import JSZip from 'jszip';
import { saveAs } from 'file-saver';
export default {
name: 'index',
components: { ProgressExport },
computed: {
...mapGetters(['permissions']),
},
data() {
return {
jsonData: [],
zipBlob: null,
progressExportVisible: false,
progressExportPageSize: 0,
};
},
methods: {
startProgress() {
// 重置进度条
this.progressExportPageSize = 0;
// 总时间(毫秒)
const totalTime = 1000;
// 更新间隔(毫秒)
const intervalTime = 100;
// 计算每次增加的百分比
const increment = (100 * intervalTime) / totalTime;
// 设置定时器
this.intervalId = setInterval(() => {
if (this.progressExportPageSize < 100) {
this.progressExportPageSize = Math.min(
this.progressExportPageSize + increment,
100
);
} else {
// 当进度达到 100 时,清除定时器
clearInterval(this.intervalId);
}
}, intervalTime);
},
downloadTemplate() {
const link = document.createElement('a');
link.href = '/admin/static/excel/spuIds-shopImage.xlsx';
link.download = '商品id导入模板';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
},
importData({ file }) {
this.progressExportPageSize = 0;
this.progressExportVisible = true;
const formData = new FormData();
const fileName = file.fileName;
formData.append('file', file);
axiosHttp.post('/mall/goodsspu/batch/downloadSpuPic', formData).then((res) => {
if (res.code == 0) {
// this.progressExportPageSize = 100;
this.jsonData = res.data;
this.downloadImagesAsZip()
}
});
},
onConfirmProgressData() {
if (this.progressExportPageSize == 100) {
this.progressExportVisible = false;
saveAs(this.zipBlob, '商品图压缩包.zip');
} else {
this.$message.warning('压缩包未生成!')
}
},
// 定义一个函数来控制请求速率
rateLimit(maxRequestsPerSecond) {
let requestsInProgress = 0;
const requestQueue = [];
// 每秒重置请求计数
setInterval(() => {
requestsInProgress = 0;
// 从队列中取出请求并执行
while (requestsInProgress < maxRequestsPerSecond && requestQueue.length > 0) {
const nextRequest = requestQueue.shift();
nextRequest();
requestsInProgress++;
}
}, 1000);
return async (url) => {
return new Promise((resolve, reject) => {
const executeRequest = async () => {
try {
const response = await fetch(url);
const blob = await response.blob();
resolve(blob);
} catch (error) {
reject(error);
} finally {
requestsInProgress--;
// 如果队列中有请求,继续执行
if (requestQueue.length > 0 && requestsInProgress < maxRequestsPerSecond) {
const nextRequest = requestQueue.shift();
nextRequest();
requestsInProgress++;
}
}
};
if (requestsInProgress < maxRequestsPerSecond) {
executeRequest();
requestsInProgress++;
} else {
requestQueue.push(executeRequest);
}
});
};
},
// 下载压缩图片
async downloadImagesAsZip() {
const limitedFetch = this.rateLimit(30);
try {
// 模拟请求接口获取 JSON 数据,实际使用时需替换为真实接口地址
// 创建一个 JSZip 实例
const zip = new JSZip();
// 遍历 JSON 数据中的每个对象
for (const item of this.jsonData) {
const spuId = item.spuId;
const picUrls = item.picUrls;
const descUrls = item.descUrls;
// 为每个 spuId 创建一个文件夹
const folder = zip.folder(spuId);
const coverImg = folder.folder(`封面图`);
const detailsImg = folder.folder(`详情图`);
// 遍历该 spuId 对应的图片链接
for (let i = 0; i < picUrls.length; i++) {
const imageUrl = picUrls[i];
// const data1 = await fetch(imageUrl);
const imageBlob1 = await limitedFetch(imageUrl);
// const imageBlob1 = await data1.blob();
// 为图片命名并添加到对应的文件夹中
coverImg.file(`image${i + 1}.jpg`, imageBlob1);
}
for (let i = 0; i < descUrls.length; i++) {
const descUrl = descUrls[i];
// const data2 = await fetch(descUrl);
// const imageBlob2 = await data2.blob();
const imageBlob2 = await limitedFetch(descUrl);
// 为图片命名并添加到对应的文件夹中
detailsImg.file(`image${i + 1}.jpg`, imageBlob2);
}
}
// 生成压缩包
this.zipBlob = await zip.generateAsync({ type: 'blob' });
this.startProgress();
// 使用 FileSaver 保存压缩包
// saveAs(zipBlob, 'images.zip');
} catch (error) {
console.error('下载图片压缩包时出错:', error);
}
},
},
};
</script>
<style scoped lang="scss">
::v-deep .el-table__header .el-table__cell {
color: rgba(0, 0, 0, .85) !important;
background-color: #fafafa !important;
}
.tips {
width: 100%;
// color: #bf0000;
font-size: 14px;
}
</style>