npm install pdfjs-dist@3.4.120
项目结构:
pdfjsViewer.vue
<template>
<div>
<div v-if="loading" class="flex justify-center items-center py-8">
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500"></div>
</div>
<div v-else>
<div class="flex flex-col md:flex-row gap-4 mb-4">
<button @click="loadPreviousPage" :disabled="currentPage === 1" class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
<i class="fa fa-arrow-left mr-1"></i> 上一页
</button>
<button @click="loadFirstPage" :disabled="currentPage === 1" class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
<i class="fa fa-fast-backward mr-1"></i> 第一页
</button>
<span class="text-lg self-center">
第 {{ currentPage }} 页,共 {{ totalPages }} 页
</span>
<button @click="loadNextPage" :disabled="currentPage === totalPages" class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
下一页 <i class="fa fa-arrow-right ml-1"></i>
</button>
<button @click="loadLastPage" :disabled="currentPage === totalPages" class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
最后一页 <i class="fa fa-fast-forward ml-1"></i>
</button>
</div>
</div>
<div class="flex items-center mb-4">
<span class="mr-2">缩放:</span>
<button @click="zoomOut" class="px-2 py-1 bg-gray-200 rounded hover:bg-gray-300">
<i class="fa fa-search-minus"></i>
</button>
<span class="mx-2">{{ Math.round(scale * 100) }}%</span>
<button @click="zoomIn" class="px-2 py-1 bg-gray-200 rounded hover:bg-gray-300">
<i class="fa fa-search-plus"></i>
</button>
<select v-model="scaleValue" @change="changeScale" class="ml-4 px-2 py-1 border rounded">
<option value="0.5">50%</option>
<option value="0.75">75%</option>
<option value="1">100%</option>
<option value="1.25">125%</option>
<option value="1.5">150%</option>
<option value="2">200%</option>
</select>
</div>
<div class="relative w-full bg-gray-100" ref="pdfContainer">
<canvas ref="pdfCanvas" class="border-2 border-gray-300 rounded shadow-lg w-full"></canvas>
</div>
</div>
</template>
<script>
// 调整导入路径以适应3.4.120版本
import * as pdfjsLib from 'pdfjs-dist/build/pdf';
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker';
// 设置worker路径
pdfjsLib.GlobalWorkerOptions.workerSrc = '/node_modules/pdfjs-dist/build/pdf.worker.js';
export default {
name: 'PdfViewer',
props: {
pdfPath: {
type: [String, Uint8Array],
required: true
}
},
data() {
return {
//pdfDoc: null,
currentPage: 1,
totalPages: 0,
scale: 1,
scaleValue: '1',
canvas: null,
ctx: null,
renderTask: null,
loading: true,
error: null
};
},
watch: {
pdfPath: {
immediate: true,
handler(newVal) {
this.loadPDF(newVal);
}
}
},
mounted() {
this.$nextTick(() => {
this.canvas = this.$refs.pdfCanvas;
this.ctx = this.canvas.getContext('2d');
});
},
methods: {
async loadPDF(path) {
this.loading = true;
this.error = null;
this.pdfDoc= null;
try {
await this.$nextTick();
if (!this.$refs.pdfCanvas) {
console.error('Canvas元素未找到');
this.error = 'Canvas元素未找到';
return;
}
this.canvas = this.$refs.pdfCanvas;
this.ctx = this.canvas.getContext('2d');
if (this.renderTask) {
//this.renderTask.cancel();
}
this.currentPage = 1;
const loadingTask = pdfjsLib.getDocument({
url: path,
disableFontFace: false,
cMapUrl: '/node_modules/pdfjs-dist/cmaps/',
cMapPacked: true
});
this.pdfDoc = await loadingTask.promise;
this.totalPages = this.pdfDoc.numPages;
console.log('PDF加载成功,总页数:', this.totalPages);
await this.renderPage(this.currentPage);
this.$emit('pageChanged', this.currentPage, this.totalPages);
} catch (error) {
console.error('加载PDF时出错:', error);
this.error = error.message;
alert('加载PDF失败: ' + error.message);
} finally {
this.loading = false;
}
},
async renderPage(num) {
if (!this.pdfDoc || !this.canvas || !this.ctx) {
console.error('PDF文档或Canvas未初始化');
return;
}
try {
await this.$nextTick();
console.log("renderPage:");
console.log(this.pdfDoc);
console.log('渲染第', num, '页,缩放比例:', this.scale);
if (this.renderTask) {
//this.renderTask.cancel();
}
const page = await this.pdfDoc.getPage(num);
console.log("pp",page);
const viewport = page.getViewport({ scale: this.scale });
// 设置canvas尺寸
this.canvas.height = viewport.height;
this.canvas.width = viewport.width;
// 清除canvas
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
console.log('Canvas尺寸设置为:', this.canvas.width, 'x', this.canvas.height);
const renderContext = {
canvasContext: this.ctx,
viewport: viewport
};
console.log('开始渲染页面...');
this.renderTask = page.render(renderContext);
await this.renderTask.promise;
console.log('页面渲染成功');
this.$emit('pageChanged', num, this.totalPages);
} catch (error) {
if (error.name !== 'RenderingCancelledException') {
console.error('渲染页面时出错:', error);
this.error = error.message;
}
}
},
async loadFirstPage() {
if (this.currentPage !== 1) {
this.currentPage = 1;
console.log("Page:");
console.log(this.pdfDoc);
await this.renderPage(this.currentPage);
}
},
async loadPreviousPage() {
if (this.currentPage > 1) {
this.currentPage--;
console.log("Page:");
console.log(this.pdfDoc);
await this.renderPage(this.currentPage);
}
},
async loadNextPage() {
if (this.currentPage < this.totalPages) {
this.currentPage++;
console.log("Page:");
console.log(this.pdfDoc);
await this.renderPage(this.currentPage);
}
},
async loadLastPage() {
if (this.currentPage !== this.totalPages) {
this.currentPage = this.totalPages;
console.log("Page:");
console.log(this.pdfDoc);
await this.renderPage(this.currentPage);
}
},
zoomIn() {
this.scale += 0.25;
this.scaleValue = this.scale.toString();
this.renderPage(this.currentPage);
},
zoomOut() {
if (this.scale > 0.5) {
this.scale -= 0.25;
this.scaleValue = this.scale.toString();
this.renderPage(this.currentPage);
}
},
changeScale() {
this.scale = parseFloat(this.scaleValue);
this.renderPage(this.currentPage);
}
}
};
</script>
<style scoped>
.pdf-container {
overflow: auto;
min-height: 500px;
}
canvas {
max-width: 100%;
display: block;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>
dupdf.vue
<!--
* ......................................&&.........................
* ....................................&&&..........................
* .................................&&&&............................
* ...............................&&&&..............................
* .............................&&&&&&..............................
* ...........................&&&&&&....&&&..&&&&&&&&&&&&&&&........
* ..................&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&..............
* ................&...&&&&&&&&&&&&&&&&&&&&&&&&&&&&.................
* .......................&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&.........
* ...................&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&...............
* ..................&&& &&&&&&&&&&&&&&&&&&&&&&&&&&&&&............
* ...............&&&&&@ &&&&&&&&&&..&&&&&&&&&&&&&&&&&&&...........
* ..............&&&&&&&&&&&&&&&.&&....&&&&&&&&&&&&&..&&&&&.........
* ..........&&&&&&&&&&&&&&&&&&...&.....&&&&&&&&&&&&&...&&&&........
* ........&&&&&&&&&&&&&&&&&&&.........&&&&&&&&&&&&&&&....&&&.......
* .......&&&&&&&&.....................&&&&&&&&&&&&&&&&.....&&......
* ........&&&&&.....................&&&&&&&&&&&&&&&&&&.............
* ..........&...................&&&&&&&&&&&&&&&&&&&&&&&............
* ................&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&............
* ..................&&&&&&&&&&&&&&&&&&&&&&&&&&&&..&&&&&............
* ..............&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&....&&&&&............
* ...........&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&......&&&&............
* .........&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&.........&&&&............
* .......&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&...........&&&&............
* ......&&&&&&&&&&&&&&&&&&&...&&&&&&...............&&&.............
* .....&&&&&&&&&&&&&&&&............................&&..............
* ....&&&&&&&&&&&&&&&.................&&...........................
* ...&&&&&&&&&&&&&&&.....................&&&&......................
* ...&&&&&&&&&&.&&&........................&&&&&...................
* ..&&&&&&&&&&&..&&..........................&&&&&&&...............
* ..&&&&&&&&&&&&...&............&&&.....&&&&...&&&&&&&.............
* ..&&&&&&&&&&&&&.................&&&.....&&&&&&&&&&&&&&...........
* ..&&&&&&&&&&&&&&&&..............&&&&&&&&&&&&&&&&&&&&&&&&.........
* ..&&.&&&&&&&&&&&&&&&&&.........&&&&&&&&&&&&&&&&&&&&&&&&&&&.......
* ...&&..&&&&&&&&&&&&.........&&&&&&&&&&&&&&&&...&&&&&&&&&&&&......
* ....&..&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&...........&&&&&&&&.....
* .......&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&..............&&&&&&&....
* .......&&&&&.&&&&&&&&&&&&&&&&&&..&&&&&&&&...&..........&&&&&&....
* ........&&&.....&&&&&&&&&&&&&.....&&&&&&&&&&...........&..&&&&...
* .......&&&........&&&.&&&&&&&&&.....&&&&&.................&&&&...
* .......&&&...............&&&&&&&.......&&&&&&&&............&&&...
* ........&&...................&&&&&&.........................&&&..
* .........&.....................&&&&........................&&....
* ...............................&&&.......................&&......
* ................................&&......................&&.......
* .................................&&..............................
* ..................................&..............................
*
* @Author: geovindu
* @Date: 2025-05-12 19:12:10
* @LastEditors: geovindu
* @LastEditTime: 2025-05-12 19:17:45
* @FilePath: \vue\vuejs\src\components\dupdf.vue
* @Description: geovindu
* @lib,packpage:
*
* @IDE: vscode
* @jslib: node 20 vue.js 3.0
* @OS: windows10
* @database: mysql 8.0 sql server 2019 postgreSQL 16
* Copyright (c) geovindu 2025 by geovindu@163.com, All Rights Reserved.
-->
<template>
<div class="container mx-auto p-4">
<h1 class="text-2xl font-bold mb-4">PDF查看器</h1>
<PdfViewer :pdfPath="pdfPath" @pageChanged="handlePageChanged" />
<div class="mt-4">
<input type="file" @change="handleFileSelect" accept=".pdf" class="border p-2 rounded">
</div>
</div>
</template>
<script>
import PdfViewer from './pdfjsViewer.vue';
export default {
name: 'App',
components: {
PdfViewer
},
data() {
return {
pdfPath: './09.pdf'
};
},
methods: {
handlePageChanged(currentPage, totalPages) {
console.log(`当前页码: ${currentPage}, 总页数: ${totalPages}`);
// 可以在这里更新父组件状态或执行其他操作
},
async handleFileSelect(e) {
const file = e.target.files[0];
if (file) {
const blob = new Blob([file], { type: file.type });
this.pdfPath = URL.createObjectURL(blob);
}
}
}
};
</script>
App.vue
<!--
*
* ┏┓ ┏┓+ +
* ┏┛┻━━━┛┻┓ + +
* ┃ ┃
* ┃ ━ ┃ ++ + + +
* ████━████ ┃+
* ┃ ┃ +
* ┃ ┻ ┃
* ┃ ┃ + +
* ┗━┓ ┏━┛
* ┃ ┃
* ┃ ┃ + + + +
* ┃ ┃
* ┃ ┃ + 神兽保佑
* ┃ ┃ 代码无bug
* ┃ ┃ +
* ┃ ┗━━━┓ + +
* ┃ ┣┓
* ┃ ┏┛
* ┗┓┓┏━┳┓┏┛ + + + +
* ┃┫┫ ┃┫┫
* ┗┻┛ ┗┻┛+ + + +
*
*
* @Author: geovindu
* @Date: 2024-08-05 08:57:12
* @LastEditors: geovindu
* @LastEditTime: 2024-10-16 09:20:45
* @FilePath: \vue\vuejs\src\App.vue
* @Description: geovindu
* @lib,packpage:
*
* @IDE: vscode
* @jslib: node 20 vue.js 3.0
* @OS: windows10
* @database: mysql 8.0 sql server 2019 postgreSQL 16
* Copyright (c) geovindu 2024 by geovindu@163.com, All Rights Reserved.
-->
<script setup>
import { RouterLink, RouterView } from 'vue-router'
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<header>
<img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" />
<div class="wrapper">
<HelloWorld msg="You did it!" />
<nav>
<RouterLink to="/">Home</RouterLink>
<RouterLink to="/about">About</RouterLink>
<RouterLink to="/doc">Word</RouterLink>
<RouterLink to="/excel">Excel</RouterLink>
<RouterLink to="/pdf">Pdf</RouterLink>
<RouterLink to="/pdfjs">Pdfjs</RouterLink>
<RouterLink to="/dupdfjs">duPdfjs</RouterLink>
</nav>
</div>
</header>
<RouterView />
</template>
<style scoped>
header {
line-height: 1.5;
max-height: 100vh;
}
.logo {
display: block;
margin: 0 auto 2rem;
}
nav {
width: 100%;
font-size: 12px;
text-align: center;
margin-top: 2rem;
}
nav a.router-link-exact-active {
color: var(--color-text);
}
nav a.router-link-exact-active:hover {
background-color: transparent;
}
nav a {
display: inline-block;
padding: 0 1rem;
border-left: 1px solid var(--color-border);
}
nav a:first-of-type {
border: 0;
}
@media (min-width: 1024px) {
header {
display: flex;
place-items: center;
padding-right: calc(var(--section-gap) / 2);
}
.logo {
margin: 0 2rem 0 0;
}
header .wrapper {
display: flex;
place-items: flex-start;
flex-wrap: wrap;
}
nav {
text-align: left;
margin-left: -1rem;
font-size: 1rem;
padding: 1rem 0;
margin-top: 1rem;
}
}
</style>
word,excel,ppt 在线预览暂不示例