From 10a35036f5d37399ae002d6703e70d64b4e974ac Mon Sep 17 00:00:00 2001
From: cwchen <1048842385@qq.com>
Date: Mon, 10 Nov 2025 18:12:34 +0800
Subject: [PATCH] =?UTF-8?q?=E6=96=87=E6=A1=A3=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 | 146 ++++++++++++++++++++++++----
1 file changed, 126 insertions(+), 20 deletions(-)
diff --git a/src/views/common/DocumentSearch.vue b/src/views/common/DocumentSearch.vue
index 7cd1338..cafc390 100644
--- a/src/views/common/DocumentSearch.vue
+++ b/src/views/common/DocumentSearch.vue
@@ -569,26 +569,118 @@ export default {
if (!textDivs || !textDivs.length) {
return
}
+
+ const segments = []
+ let position = 0
textDivs.forEach((div) => {
const original = div.dataset.originalText || div.textContent || ''
- if (!original) return
- pattern.lastIndex = 0
- if (!pattern.test(original)) {
- pattern.lastIndex = 0
- div.textContent = original
- return
- }
- pattern.lastIndex = 0
+ segments.push({
+ div,
+ text: original,
+ start: position,
+ end: position + original.length,
+ })
+ position += original.length
+ })
- const highlighted = original.replace(pattern, '$1')
- div.innerHTML = highlighted
+ if (!segments.length) {
+ return
+ }
+
+ const pageText = segments.map((seg) => seg.text).join('')
+ if (!pageText) {
+ return
+ }
+
+ pattern.lastIndex = 0
+ const perDivHighlights = new Map()
+ const matchRecords = []
+
+ let match
+ while ((match = pattern.exec(pageText)) !== null) {
+ if (!match[0]) continue
+ const start = match.index
+ const end = start + match[0].length
+ const matchIndex = matchRecords.length
+ matchRecords.push({
+ pageIndex,
+ start,
+ end,
+ elements: [],
+ })
+
+ let segIndex = segments.findIndex((seg) => start < seg.end && end > seg.start)
+ if (segIndex === -1) continue
+
+ let currentStart = start
+ while (segIndex < segments.length && currentStart < end) {
+ const seg = segments[segIndex]
+ const highlightStart = Math.max(0, currentStart - seg.start)
+ const highlightEnd = Math.min(seg.text.length, end - seg.start)
+ if (highlightEnd > highlightStart) {
+ const ranges = perDivHighlights.get(seg.div) || []
+ ranges.push({
+ start: highlightStart,
+ end: highlightEnd,
+ matchIndex,
+ })
+ perDivHighlights.set(seg.div, ranges)
+ }
+ if (end <= seg.end) break
+ currentStart = seg.end
+ segIndex += 1
+ }
+ }
+
+ if (!matchRecords.length) {
+ return
+ }
+
+ perDivHighlights.forEach((ranges, div) => {
+ const original = div.dataset.originalText || div.textContent || ''
+ if (!original) return
+
+ const sorted = ranges
+ .slice()
+ .sort((a, b) => (a.start === b.start ? a.end - b.end : a.start - b.start))
+
+ let cursor = 0
+ let html = ''
+ sorted.forEach(({ start, end, matchIndex }) => {
+ if (start > cursor) {
+ html += this.escapeForHtml(original.slice(cursor, start))
+ }
+ const text = original.slice(start, end)
+ html += `${this.escapeForHtml(text)}`
+ cursor = end
+ })
+ if (cursor < original.length) {
+ html += this.escapeForHtml(original.slice(cursor))
+ }
+ div.innerHTML = html
+ })
+
+ perDivHighlights.forEach((_ranges, div) => {
const marks = div.querySelectorAll('mark.search-highlight')
marks.forEach((mark) => {
- mark.dataset.matchIndex = results.length
- results.push({
- pageIndex,
- element: mark,
- })
+ const matchIndex = Number(mark.dataset.matchIndex)
+ if (!Number.isNaN(matchIndex) && matchRecords[matchIndex]) {
+ matchRecords[matchIndex].elements.push(mark)
+ mark.dataset.pageIndex = String(pageIndex)
+ }
+ })
+ })
+
+ matchRecords.forEach((record) => {
+ if (!record.elements.length) return
+ const newIndex = results.length
+ record.elements.forEach((mark) => {
+ mark.dataset.matchIndex = String(newIndex)
+ })
+ results.push({
+ pageIndex,
+ element: record.elements[0],
+ elements: record.elements,
})
})
})
@@ -629,11 +721,16 @@ export default {
this.currentResultIndex = index
this.searchResults.forEach((item, idx) => {
- if (idx === this.currentResultIndex) {
- item.element.classList.add('is-active')
- } else {
- item.element.classList.remove('is-active')
- }
+ if (!item) return
+ const elements = item.elements && item.elements.length ? item.elements : [item.element]
+ elements.forEach((el) => {
+ if (!el) return
+ if (idx === this.currentResultIndex) {
+ el.classList.add('is-active')
+ } else {
+ el.classList.remove('is-active')
+ }
+ })
})
const target = this.searchResults[this.currentResultIndex]?.element
@@ -702,6 +799,15 @@ export default {
}
},
+ escapeForHtml(text = '') {
+ return text
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''')
+ },
+
reload() {
if (!this.pdfUrl) return
this.loadDocument()