参考地址:https://juejin.cn/post/7105933034771185701
这个参考文章的代码直接可以复制使用,样式也是给到的,但是实现的是一页一页的显示pdf内容,我的需求是要全部展示出来,页码切换时是做一个滚动定位操作。
思路:
- 通过
vue3-pdfjs
插件提供的方法createLoadingTask
获取到总页码numPages
,通过循环将所有页面渲染- 页码定位通过锚点定位方式实现。
安装插件:
npm install vue-pdf-embed@1.1.6
npm install vue3-pdfjs@0.1.6
注意:vue-pdf-embed
使用1.1.6版本,之前未固定版本出现内容不显示问题(大概1版本的可以,2版本的不行,我这边使用的是1.1.6版本)。vue3-pdfjs
安装就是0.1.6
版本的,记录加上版本好,防止后期有更新出现问题不知道原因。
完整代码:
html
<template>
<div class="pdf-preview-container">
<!-- 头部导航栏 -->
<div class="pdf-header">
<div class="page-navigation">
<div class="page-controls">
<!-- 上一页按钮 -->
<el-button link :disabled="state.pageNum <= 1" @click="prevPage">
<el-icon>
<ArrowUp />
</el-icon>
</el-button>
<!-- 页码输入框 -->
<el-input v-model.number="state.pageNum" class="page-input" @keyup.enter="goToPage" @blur="goToPage" />
<!-- 下一页按钮 :disabled="currentPage >= totalPages" -->
<el-button link @click="nextPage">
<el-icon>
<ArrowDown />
</el-icon>
</el-button>
</div>
<!-- 总页数 -->
<span class="total-pages">/ {{ state.numPages }}</span>
</div>
</div>
<!-- PDF预览区域 -->
<div class="pdf-content">
<!-- PDF内容显示区域 -->
<div class="pdf-display-area">
<div v-for="item in state.numPages" :key="item" class='pdf-item' :id="`pdf-page-${item}`">
<vue-pdf-embed :source="state.source" class="vue-pdf-embed" :page="item" />
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, watch, computed, reactive } from 'vue';
import { Close, ArrowUp, ArrowDown } from '@element-plus/icons-vue';
import VuePdfEmbed from "vue-pdf-embed";
import { createLoadingTask } from "vue3-pdfjs";
const pdfFile = 'https://pic2-cdn.trytalks.com/pic2/202507091052/files_1a1b765ec00125e0FfuRNLZz.pdf?Expires=1754621583&OSSAccessKeyId=LTAI5tQd3SECCekgpXLBnQSo&Signature=nUn9kW375rdiVNHi2wdDLDm0gAM%3D'
const state = reactive({
source: pdfFile, // 预览pdf文件地址
pageNum: 1, // 当前页面
scale: 1, // 缩放比例
numPages: 0, // 总页数
});
const loadingTask: any = ref(null)
const pdfInit = () => {
loadingTask.value = createLoadingTask(state.source);
loadingTask.value.promise.then((pdf: { numPages: number }) => {
state.numPages = pdf.numPages;
});
}
// 滚动到指定页面
let isManualNavigation = false;
const scrollToPage = (page: number) => {
// 设置手动导航标志
isManualNavigation = true;
// 先立即更新页码状态
const validPage = Math.max(1, Math.min(page, state.numPages));
state.pageNum = validPage;
// 执行滚动
const element = document.getElementById(`pdf-page-${validPage}`);
if (element) {
element.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
// 滚动结束后清除标志
setTimeout(() => {
isManualNavigation = false;
}, 1000);
};
// 上一页
const prevPage = (): void => {
if (state.pageNum > 1) {
scrollToPage(state.pageNum - 1);
}
};
// 下一页
const nextPage = (): void => {
const nextPage = state.pageNum + 1;
if (nextPage <= state.numPages) {
scrollToPage(nextPage);
}
};
// 跳转到指定页
const goToPage = (): void => {
// 确保输入是有效数字
let page = parseInt(String(state.pageNum));
if (isNaN(page) || page < 1) {
page = 1;
} else if (page > state.numPages) {
page = state.numPages;
}
// 更新输入框显示正确的页码
state.pageNum = page;
scrollToPage(page);
};
// 处理滚动事件,更新当前页码(带防抖)
let scrollTimeout: number | null = null;
const handleScroll = () => {
// 如果是手动导航触发的滚动,则不更新页码
if (isManualNavigation) return;
if (scrollTimeout) {
clearTimeout(scrollTimeout);
}
scrollTimeout = setTimeout(() => {
const container = document.querySelector('.pdf-content');
if (!container) return;
const scrollPosition = container.scrollTop;
const items = document.querySelectorAll('.pdf-item');
const containerHeight = container.clientHeight;
const threshold = containerHeight * 0.3; // 30%视口高度作为阈值
let currentPage = state.pageNum;
// 精确检测当前可见页面
items.forEach((item) => {
const pageNum = parseInt(item.id.replace('pdf-page-', ''));
const rect = item.getBoundingClientRect();
// 如果页面顶部在视口阈值范围内,则认为是当前页
if (rect.top >= -threshold && rect.top <= threshold) {
currentPage = pageNum;
}
});
// 只有当页码确实变化时才更新
if (currentPage !== state.pageNum) {
state.pageNum = currentPage;
}
}, 150) as unknown as number; // 适当增加防抖时间
};
// 组件挂载时
onMounted(() => {
// 检查浏览器PDF支持
const isPdfSupported = 'application/pdf' in navigator.mimeTypes;
console.log('浏览器原生支持PDF:', isPdfSupported);
// 添加滚动事件监听
const contentEl = document.querySelector('.pdf-content');
if (contentEl) {
contentEl.addEventListener('scroll', handleScroll);
}
pdfInit();
});
onBeforeUnmount(() => {
// 移除滚动事件监听
const contentEl = document.querySelector('.pdf-content');
if (contentEl) {
contentEl.removeEventListener('scroll', handleScroll);
}
});
</script>
<style lang="scss" scoped>
.pdf-preview-container {
display: flex;
flex-direction: column;
height: 100%;
background-color: #fff;
border-radius: 4px;
overflow: hidden;
.pdf-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 16px;
background-color: #fff;
border-bottom: 1px solid #e0e0e0;
.back-button {
cursor: pointer;
display: inline-block;
padding: 5px 5px 0px;
border-radius: 4px;
transition: background-color 0.2s;
&:hover {
background-color: #f0f0f0;
}
}
.pdf-actions {
display: flex;
gap: 10px;
}
.page-navigation {
display: flex;
align-items: center;
.page-controls {
display: flex;
align-items: center;
padding: 2px 8px;
.page-input {
width: 50px;
margin: 0 5px;
:deep(.el-input__inner) {
text-align: center;
padding: 0 5px;
}
}
}
.total-pages {
margin-left: 5px;
color: #606266;
}
}
}
.pdf-content {
flex: 1;
overflow: auto;
/* 自定义滚动条样式 */
&::-webkit-scrollbar {
width: 6px;
height: 6px;
}
&::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.05);
border-radius: 3px;
}
&::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.15);
border-radius: 3px;
transition: background 0.2s;
&:hover {
background: rgba(0, 0, 0, 0.25);
}
}
.pdf-loading,
.pdf-error {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.pdf-display-area {
.pdf-item {
margin: 16px 0px;
}
}
}
}
</style>