年前赶项目真累啊~
系统需要实现文件下载、预览功能,文件流调试费了我好大功夫。这篇记录遇到的坑。
文件格式
后端读取文件后以流的形式传给我,从前端的角度理解就是Blob形式。
打开浏览器调试工具,resonpse需要携带这样的头:
注意在请求接口里面需要添加一个responseType
1 2 3 4 5 6 7 8 9 10
| export function downloadFile(url: string, json: object) { return axios({ url, method: "post", data: json, responseType: "blob", }) .then((res) => res) .catch((error) => error); }
|
下载
前端使用js-file-download
读取Blob数据后可以直接实现浏览器下载。
1 2 3 4 5 6
| import fileDownload from "js-file-download"; export default defineComponent({ setup() { fileDownload(res.data, file.name); } })
|
预览
经过调试后,我觉得txt文件、图片、word文档比较适合直接预览,pdf适合打开新页面,使用Chrome浏览器的自带预览功能看起来更爽。
其中,word文档使用docx-preview
包来实现预览。
文件格式对应type如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const fileTypeMap = { xls: "application/vnd.ms-excel", xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", doc: "application/msword", docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", pdf: "application/pdf", ppt: "application/pdf", pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation", png: "image/png", gif: "image/gif", jpeg: "image/jpeg", jpg: "image/jpeg", txt: "text/plain", };
|
预览文件具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| const handleqaPreviewFileRaw = (fileRaw, name) => { let suffix = name.substring(name.indexOf(".") + 1); const type = fileTypeMap[suffix]; if (suffix == "pdf") { const url = URL.createObjectURL(new Blob([fileRaw], { type })); window.open(url); } else if (suffix == "txt") { const url = URL.createObjectURL(new Blob([fileRaw], { type })); state.txtUrl = url; } else if (suffix == "docx" || suffix == "doc") { state.docFile = true; let docx = require("docx-preview"); nextTick(() => { docx .renderAsync(fileRaw, docContainer.value) .then((x) => console.log("docx: finished", x)); }); } else if (suffix == "png" || suffix == "jpg" || suffix == "jpeg") { const img = new Image(); const url = URL.createObjectURL(fileRaw); state.imgUrl = url; } else { ElMessage.info("本文件暂不支持预览,请直接下载后查看,谢谢!"); } };
|
拆成组件
父组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| <template> <file-preview ref="fileRef" :fileObj="fileObj"></file-preview> </template> <script lang="ts"> export default defineComponent({ components: { FilePreview, }, setup() { const fileRef = ref<any>(); const state = reactive({ fileObj: { fileRaw: null, name: "", }, }); const handleqaPreviewFileRaw = (fileRaw, name) => { fileRef.value.clearAll(); state.fileObj.fileRaw = fileRaw; state.fileObj.name = name; fileRef.value.handleqaPreviewFileRaw(); };
return { fileRef, ...toRefs(state), }; }, }); </script>
|
子组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| <template> <div class="previewList"> <iframe v-if="txtUrl" :src="txtUrl" frameborder="0" style="width: 100%; height: 100%; min-height: 500px" ></iframe> <div v-if="imgUrl" class="imgPrew"> <img :src="imgUrl" alt="" /> </div> <div v-if="docFile"> <div ref="docContainer"></div> </div> </div> </template>
<script lang="ts"> export default defineComponent({ name: "FilePreview", props: { fileObj: { type: Object, } }, setup(props, context) { const state = reactive({ imgUrl: "", txtUrl: "", docFile: false, }); const fileTypeMap = {}; const docContainer = ref(null);
const handleqaPreviewFileRaw = () => {};
return { ...toRefs(state), docContainer, handleqaPreviewFileRaw, }; }, }); </script>
|