1 changed files with 276 additions and 74 deletions
@ -1,99 +1,301 @@ |
|||||
<template> |
<template> |
||||
<div class="header_body"> |
<div class="pdf-preview-container"> |
||||
<header-nav |
<!-- 头部导航栏 --> |
||||
:slefBack="true" |
<div class="pdf-header"> |
||||
@goBack="goBack" |
<div class="page-navigation"> |
||||
> |
<div class="page-controls"> |
||||
</header-nav> |
|
||||
|
<div class="page-tool"> |
||||
|
<div class="page-tool-item" @click="prevPage">上一页</div> |
||||
|
<div class="page-tool-item">{{state.pageNum}}/{{state.numPages}}</div> |
||||
|
<div class="page-tool-item" @click="nextPage">下一页</div> |
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- PDF预览区域 --> |
||||
|
<div class="pdf-content"> |
||||
|
<!-- PDF内容显示区域 --> |
||||
|
<div class="pdf-display-area"> |
||||
|
<div v-for="item in state.numPages" :key="item" class='pdf-item' :id="`pdf-page-${item}`"> |
||||
|
<vue-pdf-embed :source="state.source" class="vue-pdf-embed" :page="item" /> |
||||
|
</div> |
||||
</div> |
</div> |
||||
<div id="vue-pdf-view"> |
|
||||
<div id="page-view"> |
|
||||
<vue-pdf-embed v-if="pdfState.isShow" :source="pdfState.pdfSource" :src="pdfState.pdfSource.url" :page="pdfState.pageNum" /> |
|
||||
</div> |
</div> |
||||
</div> |
</div> |
||||
</template> |
</template> |
||||
<script setup name="register"> |
|
||||
|
|
||||
|
<script setup lang="ts"> |
||||
|
import { ref, onMounted, onBeforeUnmount, watch, computed, reactive } from 'vue'; |
||||
import VuePdfEmbed from "vue-pdf-embed"; |
import VuePdfEmbed from "vue-pdf-embed"; |
||||
import {reactive, ref, nextTick} from "vue"; |
|
||||
import {baseUrl} from "@/config/env"; |
|
||||
import { createLoadingTask } from "vue3-pdfjs"; |
import { createLoadingTask } from "vue3-pdfjs"; |
||||
|
import {baseUrl} from "@/config/env"; |
||||
|
import {useRoute} from "vue-router"; |
||||
|
|
||||
|
const pdfFile = 'http://192.168.110.36:85/profile/upload/2024/03/29/40eb1a97ca27c3369370e90632d92db0.pdf' |
||||
|
|
||||
|
const route = useRoute(); |
||||
|
const state = reactive({ |
||||
|
source: pdfFile, // 预览pdf文件地址 |
||||
|
pageNum: 1, // 当前页面 |
||||
|
scale: 1, // 缩放比例 |
||||
|
numPages: 0, // 总页数 |
||||
|
}); |
||||
|
|
||||
|
const loadingTask: any = ref(null) |
||||
|
|
||||
const {useMy} = $globalStore; |
const pdfInit = () => { |
||||
|
loadingTask.value = createLoadingTask(state.source); |
||||
|
loadingTask.value.promise.then((pdf: { numPages: number }) => { |
||||
|
state.numPages = pdf.numPages; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
const route = useRoute(); |
// 滚动到指定页面 |
||||
const pdfState = reactive({ |
let isManualNavigation = false; |
||||
isShow : false, |
const scrollToPage = (page: number) => { |
||||
pdfSource: { |
// 设置手动导航标志 |
||||
url: ref("http://localhost:85/profile//2025/06/04/a1f9937950780c9c5b5acb5acaa8478f.pdf") |
isManualNavigation = true; |
||||
}, |
|
||||
// 当前页 |
// 先立即更新页码状态 |
||||
pageNum: 1, |
const validPage = Math.max(1, Math.min(page, state.numPages)); |
||||
// 总页数 |
state.pageNum = validPage; |
||||
numPages: 1, |
|
||||
}) |
// 执行滚动 |
||||
const scaleData = ref(1.0); |
const element = document.getElementById(`pdf-page-${validPage}`); |
||||
const pageHeight = ref('100%'); |
if (element) { |
||||
|
element.scrollIntoView({ |
||||
// 放大 |
behavior: 'smooth', |
||||
function zoomIn() { |
block: 'start' |
||||
scaleData.value += 0.1; |
}); |
||||
pageHeight.value = (parseInt(pageHeight.value) - 5.0) + '%'; |
} |
||||
} |
|
||||
// 缩小 |
// 滚动结束后清除标志 |
||||
function zoomOut() { |
setTimeout(() => { |
||||
scaleData.value -= 0.1; |
isManualNavigation = false; |
||||
pageHeight.value = (parseInt(pageHeight.value) + 5.0) + '%'; |
}, 1000); |
||||
} |
}; |
||||
// 页面 放大/缩小 还原默认值 |
|
||||
function pageRest() { |
// 上一页 |
||||
scaleData.value = 1.0; |
const prevPage = (): void => { |
||||
pageHeight.value = '100%'; |
if (state.pageNum > 1) { |
||||
} |
scrollToPage(state.pageNum - 1); |
||||
|
} |
||||
|
}; |
||||
//初始化参数,获取文件 |
|
||||
onMounted(() => { |
// 下一页 |
||||
console.log("获取路由参数:", route?.query?.url); |
const nextPage = (): void => { |
||||
pdfState.pdfSource.url = baseUrl +route?.query?.url; |
const nextPage = state.pageNum + 1; |
||||
pdfState.isShow=true; |
if (nextPage <= state.numPages) { |
||||
|
scrollToPage(nextPage); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// 跳转到指定页 |
||||
|
const goToPage = (): void => { |
||||
|
// 确保输入是有效数字 |
||||
|
let page = parseInt(String(state.pageNum)); |
||||
|
if (isNaN(page) || page < 1) { |
||||
|
page = 1; |
||||
|
} else if (page > state.numPages) { |
||||
|
page = state.numPages; |
||||
|
} |
||||
|
|
||||
|
// 更新输入框显示正确的页码 |
||||
|
state.pageNum = page; |
||||
|
scrollToPage(page); |
||||
|
}; |
||||
|
|
||||
|
// 处理滚动事件,更新当前页码(带防抖) |
||||
|
let scrollTimeout: number | null = null; |
||||
|
const handleScroll = () => { |
||||
|
// 如果是手动导航触发的滚动,则不更新页码 |
||||
|
if (isManualNavigation) return; |
||||
|
|
||||
|
if (scrollTimeout) { |
||||
|
clearTimeout(scrollTimeout); |
||||
|
} |
||||
|
|
||||
|
scrollTimeout = setTimeout(() => { |
||||
|
const container = document.querySelector('.pdf-content'); |
||||
|
if (!container) return; |
||||
|
|
||||
|
const scrollPosition = container.scrollTop; |
||||
|
const items = document.querySelectorAll('.pdf-item'); |
||||
|
const containerHeight = container.clientHeight; |
||||
|
const threshold = containerHeight * 0.3; // 30%视口高度作为阈值 |
||||
|
|
||||
|
let currentPage = state.pageNum; |
||||
|
|
||||
|
// 精确检测当前可见页面 |
||||
|
items.forEach((item) => { |
||||
|
const pageNum = parseInt(item.id.replace('pdf-page-', '')); |
||||
|
const rect = item.getBoundingClientRect(); |
||||
|
|
||||
|
// 如果页面顶部在视口阈值范围内,则认为是当前页 |
||||
|
if (rect.top >= -threshold && rect.top <= threshold) { |
||||
|
currentPage = pageNum; |
||||
|
} |
||||
}); |
}); |
||||
|
|
||||
// 返回 |
// 只有当页码确实变化时才更新 |
||||
const goBack = () => { |
if (currentPage !== state.pageNum) { |
||||
route.go(-1); |
state.pageNum = currentPage; |
||||
|
} |
||||
|
}, 150) as unknown as number; // 适当增加防抖时间 |
||||
}; |
}; |
||||
|
|
||||
|
|
||||
|
// 组件挂载时 |
||||
|
onMounted(() => { |
||||
|
state.source = baseUrl +route?.query?.url; |
||||
|
// 检查浏览器PDF支持 |
||||
|
const isPdfSupported = 'application/pdf' in navigator.mimeTypes; |
||||
|
console.log('浏览器原生支持PDF:', isPdfSupported); |
||||
|
|
||||
|
// 添加滚动事件监听 |
||||
|
const contentEl = document.querySelector('.pdf-content'); |
||||
|
if (contentEl) { |
||||
|
contentEl.addEventListener('scroll', handleScroll); |
||||
|
} |
||||
|
|
||||
|
pdfInit(); |
||||
|
}); |
||||
|
|
||||
|
onBeforeUnmount(() => { |
||||
|
// 移除滚动事件监听 |
||||
|
const contentEl = document.querySelector('.pdf-content'); |
||||
|
if (contentEl) { |
||||
|
contentEl.removeEventListener('scroll', handleScroll); |
||||
|
} |
||||
|
}); |
||||
</script> |
</script> |
||||
|
|
||||
<style scoped> |
<style lang="scss" scoped> |
||||
#vue-pdf-view{ |
.pdf-preview-container { |
||||
position: absolute; |
display: flex; |
||||
left: 50%; |
flex-direction: column; |
||||
transform: translatex(-50%) scale(1.0); |
|
||||
width: 70%; |
|
||||
height: 100%; |
height: 100%; |
||||
|
background-color: #fff; |
||||
|
border-radius: 4px; |
||||
|
overflow: hidden; |
||||
|
|
||||
|
.pdf-header { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
padding: 10px 16px; |
||||
|
background-color: #fff; |
||||
|
border-bottom: 1px solid #e0e0e0; |
||||
|
|
||||
|
.back-button { |
||||
|
cursor: pointer; |
||||
|
display: inline-block; |
||||
|
padding: 5px 5px 0px; |
||||
|
border-radius: 4px; |
||||
|
transition: background-color 0.2s; |
||||
|
|
||||
|
&:hover { |
||||
|
background-color: #f0f0f0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.pdf-actions { |
||||
|
display: flex; |
||||
|
gap: 10px; |
||||
|
} |
||||
|
|
||||
|
.page-navigation { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
|
||||
|
.page-controls { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
padding: 2px 8px; |
||||
|
|
||||
|
|
||||
|
.page-input { |
||||
|
width: 50px; |
||||
|
margin: 0 5px; |
||||
|
|
||||
|
:deep(.el-input__inner) { |
||||
text-align: center; |
text-align: center; |
||||
|
padding: 0 5px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
/* 内容溢出显示滚动条 */ |
.total-pages { |
||||
overflow: scroll; |
margin-left: 5px; |
||||
/* 隐藏X、y轴滚动条*/ |
color: #606266; |
||||
overflow-y: hidden; |
|
||||
overflow-x: hidden; |
|
||||
} |
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.pdf-content { |
||||
|
flex: 1; |
||||
|
overflow: auto; |
||||
|
|
||||
/* 设置滚动栏的宽、高*/ |
/* 自定义滚动条样式 */ |
||||
#vue-pdf-view::-webkit-scrollbar { |
&::-webkit-scrollbar { |
||||
width: 8px; |
width: 6px; |
||||
height: 8px; |
height: 6px; |
||||
} |
} |
||||
#vue-pdf-view::-webkit-scrollbar-thumb { |
|
||||
border-radius: 15px; |
&::-webkit-scrollbar-track { |
||||
background-color: #eee; |
background: rgba(0, 0, 0, 0.05); |
||||
|
border-radius: 3px; |
||||
} |
} |
||||
|
|
||||
</style> |
&::-webkit-scrollbar-thumb { |
||||
|
background: rgba(0, 0, 0, 0.15); |
||||
|
border-radius: 3px; |
||||
|
transition: background 0.2s; |
||||
|
|
||||
|
&:hover { |
||||
|
background: rgba(0, 0, 0, 0.25); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.pdf-loading, |
||||
|
.pdf-error { |
||||
|
height: 100%; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
.pdf-display-area { |
||||
|
.pdf-item { |
||||
|
margin: 16px 0px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.page-tool { |
||||
|
position: absolute; |
||||
|
bottom: 35px; |
||||
|
padding-left: 15px; |
||||
|
padding-right: 15px; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
background: rgb(66, 66, 66); |
||||
|
color: white; |
||||
|
border-radius: 19px; |
||||
|
z-index: 100; |
||||
|
cursor: pointer; |
||||
|
margin-left: 50%; |
||||
|
transform: translateX(-50%); |
||||
|
} |
||||
|
.page-tool-item { |
||||
|
padding: 8px 15px; |
||||
|
padding-left: 10px; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
</style> |
||||
|
|||||
Loading…
Reference in new issue