这篇笔记咱们记录: 后端返回文件流数据,前端根据对应的文件流类型渲染成PDF和DOCX文件。渲染PDF可以不用安装包(比如pdfjs-dist),利用浏览器内置的PDF阅读器,实现渲染。
- 利用iframe 渲染PDF,src给Blob:url, 不用安装第三方包
- fetch和axios请求,获取流数据,为什么fetch不用指定返回类型,axios要制定responseType
- 将PDF转为文档流对应的后端代码(Express)
- 利用docx-preview 渲染docx文件
利用iframe 渲染PDF
利用iframe, 给src一个流URL(blob:url), 然后后端设置返回的数据类型是application/pdf, iframe就可以渲染
我们用来渲染PDF的iframe
vue
<template>
<div>
<iframe
:src="pdfViewerUrl"
width="100%"
height="800px"
frameborder="0"
></iframe>
</div>
</template>
获取PDF流的方法, 这里我们用了fetch, 这样就能渲染了
vue
<script setup lang="ts">
import { onMounted, onUnmounted } from 'vue'
import { ref } from 'vue'
const baseUrl = import.meta.env.VITE_API_BASE || ''
const pdfViewerUrl = ref('')
const getPdf = async () => {
try {
// 使用 fetch 获取 PDF 流
const response = await fetch(`${baseUrl}/pdf`, { method: 'GET' })
if (!response.ok) {
throw new Error(`HTTP 错误: ${response.status}`)
}
// 直接获取Blob(无需手动转换ArrayBuffer)
const blob = await response.blob()
// 创建可访问的 URL
const pdfUrl = URL.createObjectURL(blob)
pdfViewerUrl.value = pdfUrl
} catch (error) {
console.error('获取 PDF 失败:', error)
}
}
onMounted(() => {
getPdf()
})
onUnmounted(() => {
if (pdfViewerUrl.value.includes('blob:')) {
const url = new URL(pdfViewerUrl.value)
const fileParam = new URLSearchParams(url.search).get('file')
if (fileParam && fileParam.startsWith('blob:')) {
URL.revokeObjectURL(fileParam)
}
}
})
Chrome正常渲染《小王子》的截图
用fetch获取blob可以有2种方式,
js
// 方式一:直接使用response.blob()(更简洁)
const blob = await response.blob();
// 方式二:先获取ArrayBuffer再创建Blob
const arrayBuffer = await response.arrayBuffer();
const blob = new Blob([arrayBuffer], { type: 'application/pdf' });
方式二的优点
- 兼容性更好:
arrayBuffer()
是 Fetch API 最基础的二进制数据获取方式 - 可控性更强:手动创建 Blob 时可以精确指定 MIME 类型
- 更清晰的数据流:明确展示了从二进制数据到 Blob 的转换过程
方式一更简洁,但是要注意,这样后端返回流的时候一定要设置成application/pdf
fetch 和 axios请求
如果用axios请求接口,会有一点点不一样,我们要指定responseType是arrayBuffer, 因为 Axios 的默认响应类型是 json
,若不指定,二进制数据会被错误解析导致文件损坏。
js
import axios from 'axios';
const getPdf = async () => {
try {
// 关键:必须设置 responseType: 'arraybuffer'
const response = await axios.get(`${baseUrl}/pdf`, {
responseType: 'arraybuffer', // 指定响应类型为二进制缓冲区
headers: {
'Content-Type': 'application/pdf'
}
});
// 创建 Blob 对象
const blob = new Blob([response.data], { type: 'application/pdf' });
// 生成 URL 并加载到 PDF 查看器
const pdfUrl = URL.createObjectURL(blob);
pdfViewerUrl.value = pdfUrl
} catch (error) {
console.error('获取 PDF 失败:', error);
// 处理错误(如显示错误信息)
}
};
为什么fetch不需要指定返回类型呢, 因为fetch返回的响应数据是可读流 ,通过不同的方法将其转换为不同格式,response.arrayBuffer()、response.blob()、response.text()、response.json(), 具体可看MDN文档

将PDF转为文档流对应的后端代码(Express)
知道后端的话,方便我们理解整个渲染逻辑
js
const express = require('express')
const fs = require('fs')
const path = require('path')
const app = express()
const port = 3000
const cors = require('cors')
app.use(express.static('public'))
// 允许本地和线上发布的地址访问
app.use(
cors({
origin: ['https://pdf-min.netlify.app', 'http://localhost:5173'],
})
)
// 提供 PDF 文件流
app.get('/pdf', (req, res) => {
// 小王子pdf和public同一级目录
const filePath = path.join(__dirname, 'prince.pdf')
const stat = fs.statSync(filePath)
res.setHeader('Content-Type', 'application/pdf')
res.setHeader('Content-Length', stat.size)
const readStream = fs.createReadStream(filePath)
readStream.pipe(res)
})
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`)
})
前面我们用了const blob = await response.blob()
, 没有用arrayBuffer, 然后设置类型,因为后端这里设置了res.setHeader('Content-Type', 'application/pdf')
, 如果前后端都不设置的话,返回的是这样

利用docx-preview 渲染docx文件
vue
<template>
<div>
<div id="docx-container"></div>
</div>
</template>
<script setup lang="ts">
import { renderAsync } from 'docx-preview'
const getDocx = async () => {
try {
// 使用 fetch 获取 PDF 流
const response = await fetch(`${baseUrl}/docx`, { method: 'GET' })
if (!response.ok) {
throw new Error(`请求异常: ${response.status}`)
}
// 获取 ArrayBuffer
const arrayBuffer = await response.arrayBuffer()
const docxContainer = document.getElementById('docx-container')
if (docxContainer) {
// 渲染 DOCX 到指定容器
await renderAsync(arrayBuffer, docxContainer, undefined, {
className: 'docx-render', // 自定义样式类
breakPages: true, // 启用分页
ignoreWidth: false, // 保留页面宽度
})
}
} catch (error) {
console.error('获取 PDF 失败:', error)
}
}
<script>
我们用了readerAsync方法,我们传了文档流,容器ID,样式容器undefined, 以及一些可选配置
- 第一个参数是文档流,
- 第二个参数是DOM容器,
- 第三个参数是样式容器
- 可选配置。
看下我们渲染出的效果
具体配置, 参考文档
typescript
// renders document into specified element
renderAsync(
document: Blob | ArrayBuffer | Uint8Array, // could be any type that supported by JSZip.loadAsync
bodyContainer: HTMLElement, //element to render document content,
styleContainer: HTMLElement, //element to render document styles, numbeings, fonts. If null, bodyContainer will be used.
options: {
className: string = "docx", //class name/prefix for default and document style classes
inWrapper: boolean = true, //enables rendering of wrapper around document content
hideWrapperOnPrint: boolean = false, //disable wrapper styles on print
ignoreWidth: boolean = false, //disables rendering width of page
ignoreHeight: boolean = false, //disables rendering height of page
ignoreFonts: boolean = false, //disables fonts rendering
breakPages: boolean = true, //enables page breaking on page breaks
ignoreLastRenderedPageBreak: boolean = true, //disables page breaking on lastRenderedPageBreak elements
experimental: boolean = false, //enables experimental features (tab stops calculation)
trimXmlDeclaration: boolean = true, //if true, xml declaration will be removed from xml documents before parsing
useBase64URL: boolean = false, //if true, images, fonts, etc. will be converted to base 64 URL, otherwise URL.createObjectURL is used
renderChanges: false, //enables experimental rendering of document changes (inserions/deletions)
renderHeaders: true, //enables headers rendering
renderFooters: true, //enables footers rendering
renderFootnotes: true, //enables footnotes rendering
renderEndnotes: true, //enables endnotes rendering
renderComments: false, //enables experimental comments rendering
renderAltChunks: true, //enables altChunks (html parts) rendering
debug: boolean = false, //enables additional logging
}): Promise<WordDocument>
对应的后端代码
js
app.get('/docx', (req, res) => {
const filePath = path.join(__dirname, '健身房小程序安装使用手册.docx')
const stat = fs.statSync(filePath)
res.setHeader(
'Content-Type',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
)
res.setHeader('Content-Length', stat.size)
const readStream = fs.createReadStream(filePath)
readStream.pipe(res)
})
后端返回的流形式
