pdf 加载
This commit is contained in:
parent
c88199a40c
commit
c36ed499b6
|
|
@ -61,7 +61,7 @@ if (resolvedWorkerSrc) {
|
|||
pdfjsLib.GlobalWorkerOptions.workerSrc = null
|
||||
}
|
||||
|
||||
const DEFAULT_SCALE = 1.25
|
||||
const DEFAULT_SCALE = 1
|
||||
|
||||
export default {
|
||||
name: 'DocumentSearch',
|
||||
|
|
@ -91,6 +91,13 @@ export default {
|
|||
pageTextDivs: [],
|
||||
searchResults: [],
|
||||
currentResultIndex: -1,
|
||||
renderedPages: new Map(),
|
||||
observer: null,
|
||||
pageContainers: [],
|
||||
totalPages: 0,
|
||||
isDocumentReady: false,
|
||||
renderQueue: [],
|
||||
isProcessingQueue: false,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
|
@ -99,6 +106,9 @@ export default {
|
|||
this.loadDocument()
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.disconnectObserver()
|
||||
},
|
||||
watch: {
|
||||
'$route.query.url'(newUrl, oldUrl) {
|
||||
if (newUrl !== oldUrl) {
|
||||
|
|
@ -147,40 +157,63 @@ export default {
|
|||
|
||||
async renderAllPages() {
|
||||
if (!this.pdfDoc) return
|
||||
|
||||
console.log('解析的pdf总数:', this.pdfDoc.numPages)
|
||||
const container = this.$refs.pdfWrapper
|
||||
if (!container) return
|
||||
container.innerHTML = ''
|
||||
container.style.minWidth = ''
|
||||
this.pageTextDivs = []
|
||||
this.renderedPages.clear()
|
||||
this.pageContainers = []
|
||||
this.totalPages = this.pdfDoc.numPages
|
||||
this.renderQueue = []
|
||||
this.isProcessingQueue = false
|
||||
|
||||
const pageCount = this.pdfDoc.numPages
|
||||
for (let pageNumber = 1; pageNumber <= pageCount; pageNumber += 1) {
|
||||
const pageViewport = await this.renderSinglePage(pageNumber, container)
|
||||
// 设置容器最小宽度以避免文本层换行异常
|
||||
container.style.minWidth = `${Math.ceil(pageViewport.width)}px`
|
||||
const fragment = document.createDocumentFragment()
|
||||
for (let pageNumber = 1; pageNumber <= this.totalPages; pageNumber += 1) {
|
||||
const placeholder = document.createElement('div')
|
||||
placeholder.className = 'pdf-page placeholder'
|
||||
placeholder.dataset.page = pageNumber
|
||||
fragment.appendChild(placeholder)
|
||||
this.pageContainers.push(placeholder)
|
||||
}
|
||||
|
||||
container.appendChild(fragment)
|
||||
container.scrollTop = 0
|
||||
|
||||
this.setupIntersectionObserver()
|
||||
this.observeInitialPages()
|
||||
this.isDocumentReady = true
|
||||
},
|
||||
|
||||
async renderSinglePage(pageNumber, container) {
|
||||
async renderSinglePage(pageNumber) {
|
||||
if (!this.pdfDoc) return
|
||||
if (this.renderedPages.has(pageNumber)) return
|
||||
|
||||
const index = pageNumber - 1
|
||||
const container = this.pageContainers[index]
|
||||
if (!container || container.classList.contains('rendering')) return
|
||||
|
||||
container.classList.add('rendering')
|
||||
try {
|
||||
const page = await this.pdfDoc.getPage(pageNumber)
|
||||
const viewport = page.getViewport({ scale: this.scale })
|
||||
|
||||
const pageWrapper = document.createElement('div')
|
||||
pageWrapper.className = 'pdf-page'
|
||||
pageWrapper.style.width = `${viewport.width}px`
|
||||
pageWrapper.style.height = `${viewport.height}px`
|
||||
container.appendChild(pageWrapper)
|
||||
container.classList.remove('placeholder')
|
||||
container.style.width = `${viewport.width}px`
|
||||
container.style.height = `${viewport.height}px`
|
||||
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.className = 'pdf-canvas'
|
||||
const outputScale = window.devicePixelRatio || 1
|
||||
const deviceScale = window.devicePixelRatio || 1
|
||||
const outputScale = Math.min(deviceScale, 1.5)
|
||||
canvas.width = viewport.width * outputScale
|
||||
canvas.height = viewport.height * outputScale
|
||||
canvas.style.width = `${viewport.width}px`
|
||||
canvas.style.height = `${viewport.height}px`
|
||||
pageWrapper.appendChild(canvas)
|
||||
container.appendChild(canvas)
|
||||
|
||||
if (this.$refs.pdfWrapper && !this.$refs.pdfWrapper.style.minWidth) {
|
||||
this.$refs.pdfWrapper.style.minWidth = `${Math.ceil(viewport.width)}px`
|
||||
}
|
||||
|
||||
const canvasContext = canvas.getContext('2d')
|
||||
const renderContext = {
|
||||
|
|
@ -196,11 +229,11 @@ export default {
|
|||
textLayerDiv.className = 'textLayer'
|
||||
textLayerDiv.style.width = `${viewport.width}px`
|
||||
textLayerDiv.style.height = `${viewport.height}px`
|
||||
pageWrapper.appendChild(textLayerDiv)
|
||||
container.appendChild(textLayerDiv)
|
||||
|
||||
const textLayer = new TextLayerBuilder({
|
||||
textLayerDiv,
|
||||
pageIndex: pageNumber - 1,
|
||||
pageIndex: index,
|
||||
viewport,
|
||||
eventBus: this.eventBus,
|
||||
})
|
||||
|
|
@ -213,9 +246,40 @@ export default {
|
|||
textDivs.forEach((div) => {
|
||||
div.dataset.originalText = div.textContent
|
||||
})
|
||||
this.pageTextDivs.push(textDivs)
|
||||
this.pageTextDivs[index] = textDivs
|
||||
this.renderedPages.set(pageNumber, { container, viewport })
|
||||
if (this.observer) {
|
||||
this.observer.unobserve(container)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`渲染第 ${pageNumber} 页失败`, error)
|
||||
} finally {
|
||||
container.classList.remove('rendering')
|
||||
}
|
||||
},
|
||||
|
||||
return viewport
|
||||
scheduleRender(pageNumber, { priority = false } = {}) {
|
||||
if (!pageNumber || this.renderedPages.has(pageNumber)) return
|
||||
if (this.renderQueue.includes(pageNumber)) return
|
||||
if (priority) {
|
||||
this.renderQueue.unshift(pageNumber)
|
||||
} else {
|
||||
this.renderQueue.push(pageNumber)
|
||||
}
|
||||
this.processRenderQueue()
|
||||
},
|
||||
|
||||
async processRenderQueue() {
|
||||
if (this.isProcessingQueue) return
|
||||
this.isProcessingQueue = true
|
||||
try {
|
||||
while (this.renderQueue.length) {
|
||||
const pageNumber = this.renderQueue.shift()
|
||||
await this.renderSinglePage(pageNumber)
|
||||
}
|
||||
} finally {
|
||||
this.isProcessingQueue = false
|
||||
}
|
||||
},
|
||||
|
||||
handleSearch: debounce(function () {
|
||||
|
|
@ -223,8 +287,9 @@ export default {
|
|||
this.resetSearch()
|
||||
return
|
||||
}
|
||||
if (!this.pageTextDivs.length) {
|
||||
this.$message.warning('PDF 正在加载,请稍候再试')
|
||||
const hasRenderedContent = this.pageTextDivs.some((divs) => divs && divs.length)
|
||||
if (!hasRenderedContent) {
|
||||
this.$message.warning('页面正在准备内容,请稍后或滚动加载后再试')
|
||||
return
|
||||
}
|
||||
this.highlightMatches()
|
||||
|
|
@ -243,6 +308,9 @@ export default {
|
|||
const results = []
|
||||
|
||||
this.pageTextDivs.forEach((textDivs, pageIndex) => {
|
||||
if (!textDivs || !textDivs.length) {
|
||||
return
|
||||
}
|
||||
textDivs.forEach((div) => {
|
||||
const original = div.dataset.originalText || div.textContent || ''
|
||||
if (!original) return
|
||||
|
|
@ -324,6 +392,7 @@ export default {
|
|||
|
||||
clearHighlights() {
|
||||
this.pageTextDivs.forEach((textDivs) => {
|
||||
if (!textDivs || !textDivs.length) return
|
||||
textDivs.forEach((div) => {
|
||||
const original = div.dataset.originalText
|
||||
if (typeof original !== 'undefined') {
|
||||
|
|
@ -338,6 +407,11 @@ export default {
|
|||
this.searchResults = []
|
||||
this.currentResultIndex = -1
|
||||
this.pageTextDivs = []
|
||||
this.renderedPages.clear()
|
||||
this.pageContainers = []
|
||||
this.totalPages = 0
|
||||
this.isDocumentReady = false
|
||||
this.disconnectObserver()
|
||||
const wrapper = this.$refs.pdfWrapper
|
||||
if (wrapper) {
|
||||
wrapper.innerHTML = ''
|
||||
|
|
@ -349,6 +423,50 @@ export default {
|
|||
if (!this.pdfUrl) return
|
||||
this.loadDocument()
|
||||
},
|
||||
|
||||
setupIntersectionObserver() {
|
||||
this.disconnectObserver()
|
||||
if (!this.$refs.pdfWrapper) return
|
||||
|
||||
const options = {
|
||||
root: this.$refs.pdfWrapper,
|
||||
rootMargin: '400px 0px',
|
||||
threshold: 0.01,
|
||||
}
|
||||
|
||||
this.observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
const page = Number(entry.target.dataset.page)
|
||||
if (page) {
|
||||
this.scheduleRender(page)
|
||||
}
|
||||
}
|
||||
})
|
||||
}, options)
|
||||
|
||||
this.pageContainers.forEach((container) => this.observer.observe(container))
|
||||
},
|
||||
|
||||
observeInitialPages() {
|
||||
if (!this.pageContainers.length) return
|
||||
const initial = this.pageContainers.slice(0, 2)
|
||||
initial.forEach((container) => {
|
||||
const page = Number(container.dataset.page)
|
||||
if (page) {
|
||||
this.scheduleRender(page, { priority: true })
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
disconnectObserver() {
|
||||
if (this.observer) {
|
||||
this.observer.disconnect()
|
||||
this.observer = null
|
||||
}
|
||||
this.renderQueue = []
|
||||
this.isProcessingQueue = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
@ -407,16 +525,45 @@ export default {
|
|||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: 24px;
|
||||
background: linear-gradient(180deg, #eef3ff 0%, #ffffff 100%);
|
||||
// background: linear-gradient(180deg, #eef3ff 0%, #ffffff 100%);
|
||||
background: #eaeaea;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.pdf-page {
|
||||
position: relative;
|
||||
margin: 0 auto 24px;
|
||||
margin: 0 auto;
|
||||
margin-top: 18px;
|
||||
margin-bottom: 18px;
|
||||
box-shadow: 0 10px 30px rgba(25, 64, 158, 0.12);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.pdf-page:first-of-type {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.pdf-page:not(:last-of-type) {
|
||||
margin-bottom: 26px;
|
||||
}
|
||||
|
||||
.pdf-page.placeholder {
|
||||
box-shadow: none;
|
||||
background: linear-gradient(135deg, rgba(233, 239, 255, 0.7), rgba(255, 255, 255, 0.7));
|
||||
border: 1px dashed rgba(112, 143, 255, 0.4);
|
||||
min-height: 240px;
|
||||
color: #7f8bff;
|
||||
font-size: 13px;
|
||||
letter-spacing: 0.6px;
|
||||
}
|
||||
|
||||
.pdf-page.placeholder::after {
|
||||
content: '正在准备页面...';
|
||||
}
|
||||
|
||||
.pdf-canvas {
|
||||
|
|
|
|||
Loading…
Reference in New Issue