From f5ae85453c5f3fd9bb90f12468d1f5b11fd96946 Mon Sep 17 00:00:00 2001 From: cwchen <1048842385@qq.com> Date: Fri, 28 Nov 2025 10:33:29 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96pdf=20=E6=90=9C=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/common/DocumentSearch.vue | 176 +++++++++++++++++++++++++++- 1 file changed, 174 insertions(+), 2 deletions(-) diff --git a/src/views/common/DocumentSearch.vue b/src/views/common/DocumentSearch.vue index 484a746..cff57a1 100644 --- a/src/views/common/DocumentSearch.vue +++ b/src/views/common/DocumentSearch.vue @@ -362,12 +362,17 @@ export default { const viewport = page.getViewport({ scale: this.scale }) // 渲染时允许更新尺寸(如果 viewport 与预设不同) this.ensureContainerDimensions(pageNumber, viewport, true) - container.classList.add('is-loading') - + + // 先移除旧 canvas(如果存在) const oldCanvas = container.querySelector('.pdf-canvas') if (oldCanvas) { oldCanvas.remove() } + + // 添加 is-loading 类并显示 loading + container.classList.add('is-loading') + // 确保显示 loading 图标 + this.showPageLoading(container) const canvas = document.createElement('canvas') canvas.className = 'pdf-canvas' @@ -405,6 +410,8 @@ export default { this.renderedPages.set(pageNumber, { container, viewport }) container.classList.remove('is-loading') + // canvas 渲染完成,隐藏 loading + this.hidePageLoading(container) return { page, viewport, container } }, @@ -475,6 +482,8 @@ export default { const container = this.pageContainers[pageNumber - 1] if (container) { container.classList.add('is-loading', 'is-loading-text') + // 显示 loading + this.showPageLoading(container) } try { await this.renderTextLayer(pageNumber, { visible: true, force: true }) @@ -489,6 +498,13 @@ export default { scheduleRender(pageNumber, { priority = false } = {}) { if (!pageNumber || pageNumber > this.totalPages || this.renderedPages.has(pageNumber)) return if (this.renderQueue.includes(pageNumber)) return + + // 在加入渲染队列时显示 loading + const container = this.pageContainers[pageNumber - 1] + if (container && !container.querySelector('.pdf-canvas')) { + this.showPageLoading(container) + } + if (priority) { this.renderQueue.unshift(pageNumber) } else { @@ -1146,6 +1162,11 @@ export default { if (entry.isIntersecting) { const page = Number(entry.target.dataset.page) if (page) { + const container = entry.target + // 如果页面还未渲染,显示 loading + if (!this.renderedPages.has(page) && !container.querySelector('.pdf-canvas')) { + this.showPageLoading(container) + } this.renderTextLayer(page, { visible: true, force: true }) this.scheduleRender(page, { priority: true }) } @@ -1180,6 +1201,127 @@ export default { }) }, + showPageLoading(container) { + if (!container) return + const pageNumber = Number(container.dataset.page) + + // 如果页面已经渲染完成,不显示 loading + if (pageNumber && this.renderedPages.has(pageNumber)) { + return + } + + // 检查是否已经有 canvas,如果有则不显示 loading(表示已经渲染完成) + const existingCanvas = container.querySelector('.pdf-canvas') + if (existingCanvas) { + return + } + + // 如果已经有 loading 在显示,不重复创建 + const existingLoading = container.querySelector('.page-loading-wrapper') + if (existingLoading && getComputedStyle(existingLoading).display !== 'none') { + return + } + + // 确保容器有尺寸,如果没有则设置最小高度 + const containerHeight = container.offsetHeight || parseInt(container.style.height) || 0 + const containerWidth = container.offsetWidth || parseInt(container.style.width) || 0 + + if (containerHeight < 100 || containerWidth < 100) { + // 尝试从缓存中获取 viewport 来设置尺寸 + if (this.pageCache.has(pageNumber)) { + try { + const page = this.pageCache.get(pageNumber) + const viewport = page.getViewport({ scale: this.scale }) + container.style.width = `${viewport.width}px` + container.style.height = `${viewport.height}px` + } catch (e) { + // 如果获取失败,设置最小尺寸 + if (!container.style.width) container.style.width = '800px' + if (!container.style.height) container.style.minHeight = '1000px' + } + } else { + // 如果没有缓存,设置默认尺寸 + if (!container.style.width) container.style.width = '800px' + if (!container.style.height) container.style.minHeight = '1000px' + } + } + + // 确保容器是 relative 定位 + if (getComputedStyle(container).position === 'static') { + container.style.position = 'relative' + } + + // 创建或获取 loading 容器和图标 + let loadingWrapper = container.querySelector('.page-loading-wrapper') + if (!loadingWrapper) { + loadingWrapper = document.createElement('div') + loadingWrapper.className = 'page-loading-wrapper' + + const loadingIcon = document.createElement('i') + loadingIcon.className = 'el-icon-loading page-loading-icon' + loadingWrapper.appendChild(loadingIcon) + + container.appendChild(loadingWrapper) + } else { + // 如果已存在,确保图标也存在 + let loadingIcon = loadingWrapper.querySelector('.page-loading-icon') + if (!loadingIcon) { + loadingIcon = document.createElement('i') + loadingIcon.className = 'el-icon-loading page-loading-icon' + loadingWrapper.appendChild(loadingIcon) + } + } + + // 强制设置所有样式,确保显示 - 简单的灰色旋转图标 + loadingWrapper.style.cssText = ` + position: absolute !important; + top: 33% !important; + left: 50% !important; + transform: translate(-50%, -50%) !important; + z-index: 1000 !important; + display: flex !important; + visibility: visible !important; + opacity: 1 !important; + align-items: center !important; + justify-content: center !important; + pointer-events: none !important; + ` + + // 确保图标样式和动画启动 + const icon = loadingWrapper.querySelector('.page-loading-icon') + if (icon) { + icon.style.cssText = ` + font-size: 24px !important; + color: #909399 !important; + display: block !important; + animation: pdf-page-spin 1s linear infinite !important; + ` + } + + // 使用 requestAnimationFrame 确保动画能正常启动 + this.$nextTick(() => { + requestAnimationFrame(() => { + if (loadingWrapper && loadingWrapper.parentNode) { + // 触发重排,确保动画开始 + void loadingWrapper.offsetHeight + // 确保图标动画也启动 + if (icon) { + // 强制重新应用动画 + icon.style.animation = 'none' + void icon.offsetHeight + icon.style.setProperty('animation', 'pdf-page-spin 1s linear infinite', 'important') + } + } + }) + }) + }, + hidePageLoading(container) { + if (!container) return + const loadingWrapper = container.querySelector('.page-loading-wrapper') + if (loadingWrapper) { + loadingWrapper.style.setProperty('display', 'none', 'important') + } + }, disconnectObserver() { if (this.observer) { this.observer.disconnect() @@ -1464,6 +1606,36 @@ export default { letter-spacing: 0.4px; } +.page-loading-wrapper { + position: absolute; + top: 33%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 100; + display: none; + pointer-events: none; + visibility: visible; + opacity: 1; + align-items: center; + justify-content: center; +} + +.page-loading-icon { + font-size: 24px; + color: #909399; + animation: pdf-page-spin 1s linear infinite; +} + +.pdf-page.placeholder .page-loading-wrapper, +.pdf-page.prefetched .page-loading-wrapper, +.pdf-page.is-loading .page-loading-wrapper, +.pdf-page[data-status="placeholder"] .page-loading-wrapper, +.pdf-page[data-status="prefetched"] .page-loading-wrapper, +.pdf-page[data-status="text-ready"] .page-loading-wrapper, +.pdf-page .page-loading-wrapper[style*="display: flex"] { + display: flex !important; +} + .pdf-canvas { width: 100%; display: block;