要求批量导入 spuid 的表格后导出对应的商品图片,且以对应的 spuid 为命名的文件夹下存放图片

77 阅读2分钟

需求: 要求批量导入 spuid 的表格后导出对应的商品图片,且以对应的 spuid 为命名的文件夹下存放图片,导出是压缩包的形式。

实现效果: 主页面: image.png

导入的文件:

image.png

image.png

image.png

image.png

解压展示如下图:

image.png

用 fetch 请求图片资源

image.png

接口返回的图片资源有:

image.png

请求的图片资源时有:

image.png

image.png

这里实现的难点在于: 请求图片资源时用 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>