在 Vue 项目中实现 "左侧目录 + 右侧 PDF 文档预览" 的功能,可通过 pdf.js(Mozilla 官方 PDF 解析库) 结合分栏布局来完成。以下是详细的实现步骤和代码示例:
一、技术选型与依赖安装
核心依赖是 pdfjs-dist(pdf.js 的 npm 包),用于解析 PDF 文档并提取目录、渲染页面。
bash
javascript
# 安装依赖
npm install pdfjs-dist
二、组件实现(以 Vue3 为例)
创建 PDFViewer.vue 组件,分为左侧目录栏 和右侧 PDF 预览区,并实现目录跳转功能。
vue
javascript
<template>
<div class="pdf-viewer-container">
<!-- 左侧目录 -->
<div class="toc-column">
<h3>文档目录</h3>
<ul class="toc-list">
<li
v-for="item in flattenedToc"
:key="item.id"
class="toc-item"
@click="scrollToPage(item.dest)"
>
{{ item.title }}
</li>
</ul>
</div>
<!-- 右侧PDF预览 -->
<div class="pdf-viewer">
<canvas
v-for="page in pages"
:key="page.num"
:id="`page-${page.num}`"
class="pdf-page-canvas"
></canvas>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import pdfjs from 'pdfjs-dist';
import 'pdfjs-dist/build/pdf.worker.entry'; // 引入worker
const props = defineProps({
src: {
type: String,
required: true
}
});
const pages = ref([]); // 存储PDF所有页的信息
const flattenedToc = ref([]); // 扁平化的目录列表
// 初始化PDF查看器
const initPDFViewer = async () => {
try {
// 加载PDF文档
const loadingTask = pdfjs.getDocument(props.src);
const pdf = await loadingTask.promise;
// 提取目录(大纲)
const outline = await pdf.getOutline();
flattenedToc.value = flattenOutline(outline);
// 加载所有页并渲染
const totalPages = pdf.numPages;
for (let i = 1; i <= totalPages; i++) {
const page = await pdf.getPage(i);
renderPage(page, i);
}
} catch (error) {
console.error('PDF加载失败:', error);
}
};
// 扁平化目录结构(处理嵌套目录)
const flattenOutline = (outline, parentTitle = '', level = 0) => {
const result = [];
outline.forEach((item, index) => {
const title = `${' '.repeat(level)}${item.title}`;
result.push({
id: `${parentTitle}-${index}`,
title,
dest: item.dest
});
if (item.items) {
result.push(...flattenOutline(item.items, item.title, level + 1));
}
});
return result;
};
// 渲染单页PDF到Canvas
const renderPage = (page, pageNum) => {
const canvas = document.getElementById(`page-${pageNum}`);
const context = canvas.getContext('2d');
const viewport = page.getViewport({ scale: 1.5 }); // 缩放比例,可调整
canvas.width = viewport.width;
canvas.height = viewport.height;
const renderContext = {
canvasContext: context,
viewport
};
page.render(renderContext);
};
// 目录跳转(平滑滚动到对应页面)
const scrollToPage = (dest) => {
const pageNum = Array.isArray(dest) ? dest[0].num : dest;
const pageElement = document.getElementById(`page-${pageNum}`);
pageElement?.scrollIntoView({ behavior: 'smooth' });
};
onMounted(() => {
initPDFViewer();
});
</script>
<style scoped>
.pdf-viewer-container {
display: flex;
height: 100vh;
}
.toc-column {
width: 300px;
border-right: 1px solid #ccc;
overflow-y: auto;
padding: 20px;
}
.toc-list {
list-style: none;
padding: 0;
}
.toc-item {
padding: 8px 12px;
cursor: pointer;
transition: background 0.3s;
}
.toc-item:hover {
background: #f5f5f5;
}
.pdf-viewer {
flex: 1;
overflow-y: auto;
padding: 20px;
}
.pdf-page-canvas {
display: block;
margin: 0 auto 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
</style>
三、使用示例
在父组件中引入 PDFViewer 并传入 PDF 地址:
javascript
<template>
<div>
<PDFViewer src="/static/report.pdf" />
</div>
</template>
<script setup>
import PDFViewer from './components/PDFViewer.vue';
</script>
四、功能说明
- 目录提取与渲染 :通过
pdf.getOutline()提取 PDF 内置的目录(大纲),并递归处理嵌套结构,生成扁平化的目录列表。 - PDF 页面渲染 :使用
canvas逐页渲染 PDF 内容,支持缩放和自适应布局。 - 目录跳转 :点击目录项时,通过
scrollIntoView平滑滚动到对应页面。
五、Vue2 兼容方案
若使用 Vue2,可将上述代码调整为选项式 API 风格,并确保 pdfjs-dist 版本兼容(如 pdfjs-dist@2.16.105)。核心逻辑与 Vue3 一致,仅语法略有差异。
通过以上步骤,即可在 Vue 项目中实现 "左侧目录 + 右侧 PDF 预览" 的完整功能,满足文档查阅、目录导航的需求。