upate
This commit is contained in:
parent
d3f48ca8cd
commit
8240c5e447
@ -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
1973
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
287
src/components/PdfViewer.vue
Normal file
287
src/components/PdfViewer.vue
Normal 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
|
||||||
|
|
||||||
|
// 获取URL参数中的PDF地址
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用canvas方式加载PDF
|
||||||
|
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
|
||||||
|
|
||||||
|
// 渲染PDF页面到canvas
|
||||||
|
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>
|
||||||
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user