在Web应用中,PDF预览是一个常见需求。今天我将分享如何使用vue-pdf实现一个支持多页同时预览的PDF查看器组件。
功能需求
我们需要实现一个PDF查看器,具有以下功能:
- 支持同时显示多页(本文以3页为例)
- 支持前后翻页浏览
- 显示加载状态
技术选型
我们使用以下技术:
- Vue.js:前端框架
- vue-pdf:pdf.js的Vue封装
- lodash,throttle:用于函数节流
核心代码实现
组件结构
> <div class="multi-pdf-viewer">
<div v-if="loading" class="pdf-loading-mask">
<div class="loading-spinner"></div>
PDF加载中...
</div>
<button
class="left-btn"
@click="prevSet"
:disabled="currentSet === 1 || loading"
>
<i class="iconfont icon-zuojiantou"></i>
</button>
<div class="pdf-pages">
<pdf
:src="pdfSrc"
:page="startPage"
class="pdf-page"
@num-pages="numPages = $event"
@page-loaded="handlePageLoaded(startPage)"
></pdf>
<pdf
:src="pdfSrc"
:page="startPage + 1"
class="pdf-page"
@page-loaded="handlePageLoaded(startPage + 1)"
></pdf>
<pdf
:src="pdfSrc"
:page="startPage + 2"
class="pdf-page"
@page-loaded="handlePageLoaded(startPage + 2)"
></pdf>
</div>
<button
class="right-btn"
@click="nextSet"
:disabled="currentSet === totalSets || loading"
>
<i class="iconfont icon-youjiantou"></i>
</button>
</div>
</template>
JavaScript逻辑
> import { throttle } from 'lodash'
import pdf from 'vue-pdf'
import { GlobalWorkerOptions } from 'pdfjs-dist'
export default {
name: 'VuePdfViewer',
components: { pdf },
props: {
fileUrl: {
type: String,
default: '',
},
},
data() {
return {
loading: false,
currentComp: 'pdf',
pdfSrc: null,
numPages: 0,
currentSet: 1, //当前组
pagesPerSet: 3, //每组页数
forceUpdateKey: 0 loadedPages: new Set(), // 记录已加载页面
loadTimeout: null,
}
},
//切换PDF文件
watch: {
fileUrl() {
this.loadPdf()
},
},
created() {
GlobalWorkerOptions.workerSrc =
'https://cdn.jsdelivr.net/npm/pdfjs-dist@2.10.377/build/pdf.worker.min.js'
this.loadPdf()
},
computed: {
// 计算总组数
totalSets() {
return Math.ceil(this.numPages / this.pagesPerSet)
},
// 当前组的起始页
startPage() {
return (this.currentSet - 1) * this.pagesPerSet + 1
},
// 当前组的结束页
endPage() {
return Math.min(this.currentSet * this.pagesPerSet, this.numPages)
},
},
methods: {
// 加载PDF文件
loadPdf() {
this.currentSet = 1
this.loadedPages = new Set()
this.pdfSrc = pdf.createLoadingTask({
url: this.fileUrl,
cMapPacked: true,
})
this.pdfSrc.promise
.then((pdf) => {
this.numPages = pdf.numPages
console.log(' this.numPages:', this.numPages)
})
.catch((error) => {
console.error('PDF加载失败:', error)
this.$baseMessage('PDF加载失败', 'error', 'vab-hey-message2-error')
})
}, // 使用 throttle 控制 1s 内只能触发一次翻页
// 上一组
prevSet: throttle(function () {
if (this.currentSet > 1 && !this.loading) {
this.loading = true
this.loadedPages = new Set() // 重置加载状态
this.currentSet--
}
}, 1000), // 节流间隔-1s
// 下一组
nextSet: throttle(function () {
if (this.currentSet < this.totalSets && !this.loading) {
this.loading = true
this.loadedPages = new Set()
this.currentSet++
}
}, 1000), //加载页面的回调
handlePageLoaded(page) {
clearTimeout(this.loadTimeout)
if (page >= this.startPage && page <= this.endPage) {
this.loadedPages.add(page)
const expectedPageCount = Math.min(
this.pagesPerSet,
this.numPages - (this.currentSet - 1) * this.pagesPerSet
)
if (this.loadedPages.size >= expectedPageCount) {
this.$nextTick(() => {
this.loading = false
})
} else {
// 设置超时,防止某些页面永远不触发loaded事件
this.loadTimeout = setTimeout(() => {
if (this.loading) {
console.warn(
`加载超时,已加载 ${this.loadedPages.size}/${expectedPageCount} 页`
)
this.loading = false
}
}, 5000) // 5秒超时
}
}
}, },
}
</script>