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()