This commit is contained in:
aaron 2025-03-17 09:32:34 +08:00
parent d3f48ca8cd
commit 8240c5e447
4 changed files with 2272 additions and 1 deletions

View File

@ -7,7 +7,8 @@
"postcss": "^8.4.49", "postcss": "^8.4.49",
"tailwindcss": "^3.4.17", "tailwindcss": "^3.4.17",
"vue": "^3.3.0", "vue": "^3.3.0",
"vue-router": "^4.5.0" "vue-router": "^4.5.0",
"pdfjs-dist": "^4.0.269"
}, },
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

1973
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,287 @@
<template>
<div class="pdf-container">
<div v-if="errorMessage" class="error-message">{{ errorMessage }}</div>
<!-- 美观的加载动画 -->
<div v-if="isLoading" class="loading-container">
<div class="loading-spinner">
<div class="spinner-circle"></div>
<div class="spinner-circle-inner"></div>
</div>
<div class="loading-text">加载中...</div>
</div>
<!-- 使用iframe直接嵌入PDF -->
<iframe
v-if="!isLoading && !errorMessage && pdfUrl"
:src="pdfUrl"
class="pdf-iframe"
frameborder="0"
@load="iframeLoaded"
@error="iframeError"
></iframe>
<!-- 保留原始canvas渲染方式作为备选 -->
<div v-if="useCanvas && !isLoading && !errorMessage" ref="pdfContainer" class="canvas-container">
<canvas ref="pdfCanvas"></canvas>
</div>
</div>
</template>
<script>
import { ref, onMounted, watch } from 'vue'
import { useRoute } from 'vue-router'
export default {
name: 'PdfViewer',
props: {
pdfUrl: {
type: String,
default: ''
}
},
setup(props) {
const route = useRoute()
const pdfContainer = ref(null)
const pdfCanvas = ref(null)
const isLoading = ref(true)
const errorMessage = ref('')
const useCanvas = ref(false) // 使iframe
let pdfDoc = null
// URLPDF
const getPdfUrl = () => {
let url = props.pdfUrl || route.query.url || ''
// URL@
if (url.startsWith('@')) {
url = url.substring(1)
}
return url
}
// iframe
const iframeLoaded = () => {
isLoading.value = false
}
// iframe
const iframeError = () => {
errorMessage.value = '无法加载PDF文档尝试使用备选方式加载'
useCanvas.value = true // 使canvas
loadPdfWithCanvas()
}
// 使canvasPDF
const loadPdfWithCanvas = async () => {
const url = getPdfUrl()
if (!url) {
errorMessage.value = '未提供PDF文件URL'
isLoading.value = false
return
}
isLoading.value = true
errorMessage.value = ''
try {
// PDF.js
const pdfjs = await import('pdfjs-dist')
// worker
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.min.js`
// PDF
pdfDoc = await pdfjs.getDocument(url).promise
//
renderPage(1)
isLoading.value = false
} catch (error) {
console.error('加载PDF失败:', error)
errorMessage.value = '无法加载PDF文档请检查URL是否正确'
isLoading.value = false
}
}
//
const renderPage = async (pageNum) => {
if (!pdfDoc) return
try {
//
const page = await pdfDoc.getPage(pageNum)
//
const viewport = page.getViewport({ scale: 1.0 })
// canvas
const canvas = pdfCanvas.value
const context = canvas.getContext('2d')
canvas.height = viewport.height
canvas.width = viewport.width
// PDFcanvas
const renderContext = {
canvasContext: context,
viewport: viewport
}
await page.render(renderContext).promise
} catch (error) {
console.error('渲染PDF页面失败:', error)
errorMessage.value = '渲染PDF页面失败'
}
}
//
const init = () => {
isLoading.value = true
errorMessage.value = ''
// 使iframe
if (!useCanvas.value) {
setTimeout(() => {
if (isLoading.value) {
isLoading.value = false
}
}, 3000) // 3
}
}
// URL
watch(() => getPdfUrl(), () => {
init()
})
onMounted(() => {
init()
})
return {
pdfContainer,
pdfCanvas,
isLoading,
errorMessage,
useCanvas,
pdfUrl: getPdfUrl(),
iframeLoaded,
iframeError
}
}
}
</script>
<style scoped>
.pdf-container {
margin: 0;
padding: 0;
width: 100%;
height: 100vh;
overflow: hidden;
background: transparent;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.error-message {
color: red;
padding: 10px;
text-align: center;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(255, 255, 255, 0.9);
border-radius: 5px;
padding: 15px 20px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
max-width: 80%;
}
/* 加载动画容器 */
.loading-container {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
/* 加载动画 */
.loading-spinner {
position: relative;
width: 60px;
height: 60px;
margin-bottom: 15px;
}
.spinner-circle {
position: absolute;
width: 100%;
height: 100%;
border: 3px solid transparent;
border-top-color: #FFCC00;
border-radius: 50%;
animation: spin 1.5s linear infinite;
}
.spinner-circle-inner {
position: absolute;
top: 10px;
left: 10px;
right: 10px;
bottom: 10px;
border: 3px solid transparent;
border-top-color: #FF9900;
border-radius: 50%;
animation: spin 1s linear infinite reverse;
}
.loading-text {
font-size: 16px;
color: #333;
margin-top: 5px;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.pdf-iframe {
width: 100%;
height: 100%;
border: none;
display: block;
background: transparent;
}
.canvas-container {
width: 100%;
height: 100%;
overflow: auto;
display: flex;
justify-content: center;
align-items: flex-start;
background: transparent;
}
canvas {
background: transparent;
}
</style>

View File

@ -8,6 +8,7 @@ import CommunityRequest from '../components/CommunityRequest.vue'
import PartnerRequest from '../components/PartnerRequest.vue' import PartnerRequest from '../components/PartnerRequest.vue'
import ImageRecognition from '../components/ImageRecognition.vue' import ImageRecognition from '../components/ImageRecognition.vue'
import SystemHealth from '../components/SystemHealth.vue' import SystemHealth from '../components/SystemHealth.vue'
import PdfViewer from '../components/PdfViewer.vue'
const routes = [ const routes = [
{ {
@ -79,6 +80,15 @@ const routes = [
title: '系统健康状态', title: '系统健康状态',
requiresAuth: true requiresAuth: true
} }
},
{
path: '/pdf-viewer',
name: 'PdfViewer',
component: PdfViewer,
meta: {
title: 'PDF查看器',
requiresAuth: false
}
} }
] ]