vue - 实现文件的上传-字节流下载详细过程
前言
这个需求一般是在有合同的地方用,首先不止一个地方用我们应该把他封装成一个简易的全局组件,
上传的时候封装成全局组件,传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-%e5%ae%9e%e7%8e%b0%e6%96%87%e4%bb%b6%e7%9a%84%e4%b8%8a%e4%bc%a0-%e5%ad%97%e8%8a%82%e6%b5%81%e4%b8%8b%e8%bd%bd%e8%af%a6%e7%bb%86%e8%bf%87%e7%a8%8b/
欢迎入群一起讨论