diff --git a/src/views/common/DocumentSearch.vue b/src/views/common/DocumentSearch.vue index 65ed3c4..7f6eaab 100644 --- a/src/views/common/DocumentSearch.vue +++ b/src/views/common/DocumentSearch.vue @@ -73,7 +73,7 @@ if (resolvedWorkerSrc) { pdfjsLib.GlobalWorkerOptions.workerSrc = null } -const DEFAULT_SCALE = 1 +const DEFAULT_SCALE = 1.1 const DEFAULT_PDF_URL = 'http://192.168.0.14:9090/smart-bid/technicalSolutionDatabase/2025/10/30/fe5b46ea37554516a71e7c0c486d3715.pdf' export default { @@ -867,6 +867,9 @@ export default { this.currentResultIndex = index this.scheduleActiveHighlightRefresh() + + // 等待高亮更新完成 + await this.$nextTick() const performScroll = () => { const target = this.searchResults[this.currentResultIndex]?.element @@ -876,24 +879,83 @@ export default { const container = target.closest('.pdf-page') || target if (!container) return - // 不做平滑动画,直接把匹配所在的 pdf-page 翻到可视区域 + // 手动计算位置并滚动,确保目标元素在视口中可见 if (!useSmoothScroll) { + // 获取目标元素和容器的位置信息 + const targetRect = target.getBoundingClientRect() + const wrapperRect = wrapper.getBoundingClientRect() + const containerRect = container.getBoundingClientRect() + + // 计算目标元素相对于 wrapper 的绝对位置 + const targetTop = container.offsetTop + (targetRect.top - containerRect.top) + const targetLeft = container.offsetLeft + (targetRect.left - containerRect.left) + const targetHeight = targetRect.height || 20 + const targetWidth = targetRect.width || 50 + + // 计算视口尺寸和边距 + const viewportHeight = wrapper.clientHeight + const viewportWidth = wrapper.clientWidth + const verticalPadding = 80 // 上下留出空间 + const horizontalPadding = 40 // 左右留出空间 + + // 垂直滚动:确保目标元素在视口中 + let scrollTop = wrapper.scrollTop + const targetTopInViewport = targetTop - wrapper.scrollTop + + if (targetTopInViewport < verticalPadding) { + // 目标元素在视口上方,向上滚动 + scrollTop = targetTop - verticalPadding + } else if (targetTopInViewport + targetHeight > viewportHeight - verticalPadding) { + // 目标元素在视口下方,向下滚动 + scrollTop = targetTop + targetHeight - viewportHeight + verticalPadding + } + + // 水平滚动:确保目标元素在视口中(如果 canvas 很宽) + let scrollLeft = wrapper.scrollLeft + const targetLeftInViewport = targetLeft - wrapper.scrollLeft + + if (targetLeftInViewport < horizontalPadding) { + // 目标元素在视口左侧,向左滚动 + scrollLeft = targetLeft - horizontalPadding + } else if (targetLeftInViewport + targetWidth > viewportWidth - horizontalPadding) { + // 目标元素在视口右侧,向右滚动 + scrollLeft = targetLeft + targetWidth - viewportWidth + horizontalPadding + } + + // 直接跳转到目标位置 this.cancelScrollAnimation() - wrapper.scrollTop = container.offsetTop + wrapper.scrollTop = Math.max(0, Math.min(scrollTop, wrapper.scrollHeight - viewportHeight)) + wrapper.scrollLeft = Math.max(0, Math.min(scrollLeft, wrapper.scrollWidth - viewportWidth)) return } + // 平滑滚动 const wrapperOffsetTop = container.offsetTop const containerHeight = container.offsetHeight || target.offsetHeight || 0 const desired = wrapperOffsetTop - Math.max((wrapper.clientHeight - containerHeight) / 2, 0) this.smoothScrollTo(wrapper, desired) } - if (useSmoothScroll) { - this.$nextTick(() => performScroll()) - } else { - performScroll() - } + // 等待 DOM 更新和元素渲染完成后再滚动 + await this.$nextTick() + // 使用双重 requestAnimationFrame 确保元素位置已完全计算 + requestAnimationFrame(() => { + requestAnimationFrame(() => { + // 再次检查元素是否存在(可能在渲染过程中被更新) + const currentTarget = this.searchResults[this.currentResultIndex]?.element + if (currentTarget && currentTarget.isConnected) { + performScroll() + } else { + // 如果元素不存在,尝试从 elements 数组中获取 + const result = this.searchResults[this.currentResultIndex] + if (result && result.elements && result.elements.length > 0) { + // 更新 element 引用 + result.element = result.elements[0] + performScroll() + } + } + }) + }) }, applyHighlightsToPage(pageIndex) {