vue - 实现文件的上传-字节流下载详细过程
  9m65el8SCpbP 2023年11月24日 29 0

前言
这个需求一般是在有合同的地方用,首先不止一个地方用我们应该把他封装成一个简易的全局组件,

上传的时候封装成全局组件,传2个值,一个合同id,一个开关,下载的时候调用接口获取文档流下载

注意看下面的文件上传下载-细节有详细的过程和遇到的问题和解决方案

效果图
接口配置

import request from '@/utils/request'

// 上传合同附件
export function uploadPDF (data) {
  return request({
    url: '/contractMsg/updatePDF',
    method: 'put',
    headers: { 'Content-Type': 'multipart/form-data' },
    data
  })
}

// 下载合同附件
export function download (id) {
  return request({
    url: `/contractMsg/getPDF/${id}`,
    method: 'get',
    //参数序列化 可加可不加
    paramsSerializer: params => {
      return qs.stringify(params)
    },
    //将二进制流转换成blob对象
    reponseType: 'blob',
    // 可加可不加
    headers: {
      'Content-Type': 'application/json;charset=UTF-8;'
    }
  })
}

文件上传下载-细节
文件上传时

1.全局注册组件-主页文章有

2.单向数据流的问题,要把开关值通知父组件关闭使用.sync语法糖,不然会报错。

3.跟Excel文件上传是一样道理,传递formdata类型给后端,记得在api二次封装时候设置formdata类型

4.最好在formdata传2个值,一个上传文件,一个文件id。方便下载。记得回显数据到父组件输入框

5.formdatad对象直接打印是空的,要这样打印 console.log("formdata", formdata.get("file"));

文件下载时

在网上看来很多文章一头雾水,自己试一下,在 这里记录一下。

首先第一点这个文件下载主要是后端写,我们只要传入id即可。但是我们要排查问题。

如果后端把这个接口写好了,在测试工具测试可以直接下载,并且返回结果返回文档流。那剩下就是前端问题。

代码是不能直接下载我们需要blob对象转一下,通过a标签来下载。

网上很多文章是说使用windows.URL就可以实现下载,但是会报undefined,查阅资料好像是说api废弃了,我们可以使用webkitURL是一样效果

还有就是如果我们有全局响应拦截的话,可能会过不去。因为后端可能没有放回状态码,只有文档流。那我们就要在request 响应拦截加上res != ' '(我们是判断code通过),axios会默认加一层data

如下例子

service.interceptors.response.use(
  res => {
    // axios默认加了一层data
    // 这个res包括这个请求响应回来的所有信息
    // 所有的接口请求都会回到这里
    // 获取到本次请求得到的数据
    const data = res.data
    // 会帮所有的请求打印
    // console.log(data);
    // 判断本次请求是否成功
    if (data.code === 200 || data != '') {
      // 如果响应成功,则正常给他返回数据
      return data
    } else {
      // 证明失败,我们需要让外面的catch被调用
      // 要让catch被调用,就要手动抛出一个错误,并把服务器返回的消息抛回去
      // Message.warning(data.message)
      return Promise.reject(data.message)
    }
  },
}

全局上传组件-全局components下

我的命名UploadAnnex/index.vue

<template>
  <el-dialog
    title="合同附件上传"
    :visible.sync="dialogVisible"
    width="40%"
    :before-close="handleClose"
  >
    <el-form ref="form" :model="form" size="small" label-width="80px">
      <el-form-item label="文件名称:">
        <el-input v-model="form.contitle"></el-input>
      </el-form-item>
      <el-form-item label="文件上传:">
        <div class="uppicture">
          <input type="file" class="upinput" ref="file" @change="showimg" />
          <i class="el-icon-plus" id="changes" @click="changeimg"></i>
          <p>上传合同文件附件</p>
          <el-button type="primary" class="uploadbutton" @click="addupload"
            >上传附件{{ id }}</el-button
          >
        </div>
      </el-form-item>
    </el-form>

    <span slot="footer" class="dialog-footer">
      <el-button @click="handleClose" style="background: #f7f7f7" size="small"
        >取 消</el-button
      >
      <!-- <el-button type="primary" @click="upload">确 定</el-button> -->
    </span>
  </el-dialog>
</template>

<script>
import { uploadPDF } from "@/api/client/administrator";
export default {
  name: "UploadAnnex",
  data() {
    return {
      form: {
        // 合同名称
        contitle: "",
      },
      formdata: {},
    };
  },
  props: {
    // 显示隐藏
    dialogVisible: {
      type: Boolean,
      //   必传
      required: true,
    },
    // 合同id
    id: {
      type: Number,
      //   必传
      required: true,
    },
  },
  methods: {
    // 关闭之前
    handleClose() {
      console.log("关闭之前");
      // .sync语法糖,单向数据流问题,
      // 父组件传递给子组件的数据,子组件直接修改会报错,需要传递给父组件修改
      this.$emit("update:dialogVisible", false);
    },
    // 输入款获取事件
    showimg() {
      const that = this;
      console.log(that.$refs.file);
      console.log(that.$refs.file.files[0]);
      // 文件名称复制
      that.form.contitle = that.$refs.file.files[0].name;
      // 声明一个formdata对象
      this.formdata = new FormData();
      // 赋值需要传递的文件-添加到formdata对象中
      this.formdata.append("file", that.$refs.file.files[0]);
      // 赋值需要传递文件id
      this.formdata.append("id", this.id);
      // 打印formdata对象
       console.log("formdata", formdata.get("file"));
    },
    // 图标触发输入框事件
    changeimg() {
      // 点击图标时候,触发input选择文件按钮
      this.$refs.file.dispatchEvent(new MouseEvent("click"));
    },
    // 上传附件
    async addupload() {
      // 上传文文件提示,未选择文件提示用户
      if (!this.form.contitle) {
        return this.$message.warning("请先在左侧上传文件");
      }
      const res = await uploadPDF(this.formdata);
      console.log("合同上传", res);
      // 回显文件名称给父组件的form表单
      this.$emit("updata", this.form.contitle);
      //  清空表单
      this.form.contitle = "";
      this.formdata = {};
      // 关闭弹框
      this.handleClose();
    },
  },
};
</script>

<style lang="scss" scoped>
::v-deep .el-dialog {
  border-radius: 10px;
  .el-dialog__header {
    border-radius: 9px 9px 0 0;
    background-color: #1488c6;
    padding: 8px 20px;
    .el-dialog__title {
      color: white;
      font-size: 16px;
    }

    .el-dialog__headerbtn {
      top: 12px;
      i {
        color: white;
      }
    }
  }
  .el-dialog__footer {
    text-align: center;
  }
  .el-dialog__body {
    padding: 12px;
  }
  .uppicture {
    width: 120px;
    height: 120px;
    border: 1px solid #717376;
    position: relative;
    cursor: pointer;
    input {
      width: 100%;
      height: 100%;
      vertical-align: middle;
      opacity: 0;
    }
    i {
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      font-size: 30px;
      // background-color: skyblue;
    }
    p {
      position: absolute;
      bottom: -2px;
      left: 50%;
      word-break: keep-all;
      transform: translate(-50%);
    }
    .uploadbutton {
      position: absolute;
      bottom: 0;
      margin-left: 20px;
    }
    &:hover {
      color: #2da9fa;
      border: 1px solid #2da9fa;
      p {
        color: #2da9fa;
      }
    }
  }
}
</style>

使用组件-父组件

<!-- 上传组件使用 -->
    <UploadAnnex
      :dialogVisible.sync="dialogannex"
      :id="uploadid"
      @updata="updata = $event"
    ></UploadAnnex>


父组件数据

// 上传组件开关
      dialogannex: false,
      // 合同id
      uploadid: 2,
      updata: "",
父组件回显文件名称和使用结构<el-form-item label="合同扫描件:" >
              <el-input
                v-model="updata"
                style="width: 350px; margin-right: 10px"
              ></el-input>
              <el-button @click="download">下载</el-button>
              <el-button @click="addupload">上载</el-button>
            </el-form-item>


父组件事件

link.download 是下载文件的名字,可能会因为编码格式原因中文名字会乱码。所以可以在这条数据文件上传时把名字在数据库存一次,查单条的时候放回来,下载时候重新赋值名字

import { download } from "@/api/client/administrator";

 // 上传组件按钮
    addupload() {
      this.dialogannex = true;
    },
    // 下载文件
    async download() {
      await download(this.uploadid)
        .then((res) => {
          console.log("res", res);
          let blob = new Blob([res.data], {
            type: "application/pdf;",
          });
          console.log("blob", blob);
          let filename = "css.doc";
          if (window.navigator.msSaveOrOpenBlob) {
            navigator.msSaveBlob(blob, filename);
          } else {
            var link = document.createElement("a");
            link.href = webkitURL.createObjectURL(blob);
            link.download = filename;
            link.click();
            webkitURL.revokeObjectURL(link.href);
          }
        })
        .catch((err) => {
          console.log("错误信息", err);
        });
    },

总结:

经过这一趟流程下来相信你也对 vue - 实现文件的上传-文档流下载详细过程 有了初步的深刻印象,但在实际开发中我 们遇到的情况肯定是不一样的,所以我们要理解它的原理,万变不离其宗。加油,打工人!

 

参考文章:http://blog.ncmem.com/wordpress/2023/10/26/vue-实现文件的上传-字节流下载详细过程/


 

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月24日 0

暂无评论

推荐阅读
9m65el8SCpbP