npm install pdfjs-dist@5.2.133
项目结构:
<!--
* @creater: geovindu
* @since: 2025-05-09 21:56:20
* @LastAuthor: geovindu
* @lastTime: 2025-05-09 22:12:17
* @文件相对于项目的路径: \jsstudy\vuepdfpreview\comonents\pdfjs.vue
* @message: geovindu
* @IDE: vscode
* @Development: node.js 20, vuejs3.0
* @package:
* @ISO: windows10
* @database: mysql 8.0 sql server 2019 postgresSQL 16
* Copyright (c) 2025 by geovindu email:geovindu@163.com, All Rights Reserved.
-->
<template>
<div class="pdf-container">
<div v-if="loading" class="text-center py-8">加载中...</div>
<div v-else-if="error" class="text-center py-8 text-red-500">{{ error }}</div>
<div v-else>
<!-- 控制工具栏 -->
<div class="flex justify-between items-center mb-4">
<div class="flex space-x-2">
<button @click="zoomIn" class="px-3 py-1 bg-blue-500 text-white rounded hover:bg-blue-600">
<i class="fa fa-search-plus mr-1"></i> 放大
</button>
<button @click="zoomOut" class="px-3 py-1 bg-blue-500 text-white rounded hover:bg-blue-600">
<i class="fa fa-search-minus mr-1"></i> 缩小
</button>
<button @click="downloadPDF" class="px-3 py-1 bg-green-500 text-white rounded hover:bg-green-600">
<i class="fa fa-download mr-1"></i> 下载文档
</button>
</div>
<div class="text-center">
缩放比例: {{ Math.round(scale * 100) }}%
</div>
</div>
<!-- PDF 容器 -->
<div id="pdf-container" class="w-full h-[600px] border border-gray-300 overflow-auto">
<canvas ref="pdfCanvas"></canvas>
</div>
<!-- 页码控制 -->
<div class="mt-4 text-center">
<button @click="firstPage" :disabled="currentPage <= 1" class="px-3 py-1 bg-gray-200 rounded hover:bg-gray-300 mx-1">
第一页
</button>
<button @click="prevPage" :disabled="currentPage <= 1" class="px-3 py-1 bg-gray-200 rounded hover:bg-gray-300 mx-1">
上一页
</button>
<span class="mx-3">第 {{ currentPage }} / {{ totalPages }} 页</span>
<button @click="nextPage" :disabled="currentPage >= totalPages" class="px-3 py-1 bg-gray-200 rounded hover:bg-gray-300 mx-1">
下一页
</button>
<button @click="lastPage" :disabled="currentPage >= totalPages" class="px-3 py-1 bg-gray-200 rounded hover:bg-gray-300 mx-1">
最后一页
</button>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, reactive } from 'vue';
import * as pdfjsLib from 'pdfjs-dist';
// 设置 worker 路径
//pdfjsLib.GlobalWorkerOptions.workerSrc = '/pdfjs/pdf.worker.mjs';
// 动态解析worker路径,确保使用.mjs文件
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.mjs',
import.meta.url
).toString();
const props = defineProps({
pdfUrl: { type: String, required: true }
});
const pdfCanvas = ref(null);
const loading = ref(true);
const error = ref('');
const currentPage = ref(1);
const totalPages = ref(0);
const scale = ref(1.0);
let pdfDoc = null;
const renderPage = async (num) => {
try {
const page = await pdfDoc.getPage(num);
const viewport = page.getViewport({ scale: scale.value });
// 设置 canvas 尺寸
pdfCanvas.value.width = viewport.width;
pdfCanvas.value.height = viewport.height;
// 渲染页面
const renderContext = {
canvasContext: pdfCanvas.value.getContext('2d'),
viewport: viewport
};
await page.render(renderContext).promise;
currentPage.value = num;
} catch (err) {
console.error('渲染页面失败:', err);
error.value = `渲染失败: ${err.message}`;
}
};
const prevPage = () => {
if (currentPage.value > 1) {
renderPage(currentPage.value - 1);
}
};
const nextPage = () => {
if (currentPage.value < totalPages.value) {
renderPage(currentPage.value + 1);
}
};
const firstPage = () => {
if (currentPage.value !== 1) {
renderPage(1);
}
};
const lastPage = () => {
if (currentPage.value !== totalPages.value) {
renderPage(totalPages.value);
}
};
const zoomIn = () => {
scale.value = Math.min(scale.value + 0.1, 3.0); // 最大缩放 300%
renderPage(currentPage.value);
};
const zoomOut = () => {
scale.value = Math.max(scale.value - 0.1, 0.5); // 最小缩放 50%
renderPage(currentPage.value);
};
const downloadPDF = () => {
try {
const link = document.createElement('a');
link.href = props.pdfUrl;
link.download = props.pdfUrl.split('/').pop() || 'document.pdf';
link.click();
} catch (e) {
console.error('下载失败:', e);
error.value = '下载失败,请尝试右键另存为';
window.open(props.pdfUrl, '_blank');
}
};
onMounted(async () => {
try {
// 加载 PDF
const loadingTask = pdfjsLib.getDocument(props.pdfUrl);
pdfDoc = await loadingTask.promise;
totalPages.value = pdfDoc.numPages;
// 渲染第一页
renderPage(1);
loading.value = false;
} catch (err) {
console.error('加载 PDF 失败:', err);
error.value = `加载失败: ${err.message}`;
loading.value = false;
}
});
</script>
<style scoped>
.pdf-container {
max-width: 1000px;
margin: 0 auto;
}
#pdf-container canvas {
max-width: 100%;
display: block;
margin: 0 auto;
}
</style>
<!--
* @creater: geovindu
* @since: 2025-05-09 21:56:20
* @LastAuthor: geovindu
* @lastTime: 2025-05-09 22:12:17
* @文件相对于项目的路径: \jsstudy\vuepdfpreview\src\App.vue
* @message: geovindu
* @IDE: vscode
* @Development: node.js 20, vuejs3.0
* @package:
* @ISO: windows10
* @database: mysql 8.0 sql server 2019 postgresSQL 16
* Copyright (c) 2025 by geovindu email:geovindu@163.com, All Rights Reserved.
-->
<template>
<div class="pdf-container">
<PDFView :pdfUrl="pdfUrl" v-if="pdfUrl" />
<div v-else >加载中...</div>
</div>
<div>
<a href="https://vite.dev" target="_blank">
<img src="/vite.svg" class="logo" alt="Vite logo" />
</a>
<a href="https://vuejs.org/" target="_blank">
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
</a>
</div>
<HelloWorld msg="Vite + Vue" />
</template>
<script setup lang="ts">
import HelloWorld from './components/HelloWorld.vue'
//import PDFView from './components/vuepdfjs.vue' // 可以
//import PDFView from './components/pdfPreview.vue' // 可以
import PDFView from "./components/pdfjs.vue" //可以
//import pdfUrl from "./pdfs/01.pdf"
const pdfUrl = "./pdfs/09.pdf"
</script>
<style scoped>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
}
</style>
输出: