搜索优化
This commit is contained in:
parent
10a35036f5
commit
a1ebf7c660
|
|
@ -93,6 +93,7 @@ export default {
|
|||
pageTextDivs: [],
|
||||
searchResults: [],
|
||||
currentResultIndex: -1,
|
||||
pageMatchRanges: [],
|
||||
renderedPages: new Map(),
|
||||
observer: null,
|
||||
pageContainers: [],
|
||||
|
|
@ -364,6 +365,10 @@ export default {
|
|||
container.classList.remove('is-loading-text')
|
||||
}
|
||||
|
||||
if (this.keyword.trim() && this.pageMatchRanges && this.pageMatchRanges.length) {
|
||||
this.applyHighlightsToPage(index)
|
||||
}
|
||||
|
||||
if (container.dataset.status !== 'rendered') {
|
||||
container.dataset.status = visible ? 'rendered' : 'text-ready'
|
||||
if (!visible) {
|
||||
|
|
@ -532,6 +537,8 @@ export default {
|
|||
console.error('全文预处理失败', error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.highlightMatches()
|
||||
} finally {
|
||||
this.searching = false
|
||||
|
|
@ -564,6 +571,7 @@ export default {
|
|||
const escapedKeyword = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
const pattern = new RegExp(`(${escapedKeyword})`, 'gi')
|
||||
const results = []
|
||||
this.pageMatchRanges = Array.from({ length: this.totalPages || 0 }, () => [])
|
||||
|
||||
this.pageTextDivs.forEach((textDivs, pageIndex) => {
|
||||
if (!textDivs || !textDivs.length) {
|
||||
|
|
@ -572,10 +580,11 @@ export default {
|
|||
|
||||
const segments = []
|
||||
let position = 0
|
||||
textDivs.forEach((div) => {
|
||||
textDivs.forEach((div, divIndex) => {
|
||||
const original = div.dataset.originalText || div.textContent || ''
|
||||
segments.push({
|
||||
div,
|
||||
divIndex,
|
||||
text: original,
|
||||
start: position,
|
||||
end: position + original.length,
|
||||
|
|
@ -607,6 +616,7 @@ export default {
|
|||
start,
|
||||
end,
|
||||
elements: [],
|
||||
segments: [],
|
||||
})
|
||||
|
||||
let segIndex = segments.findIndex((seg) => start < seg.end && end > seg.start)
|
||||
|
|
@ -625,6 +635,11 @@ export default {
|
|||
matchIndex,
|
||||
})
|
||||
perDivHighlights.set(seg.div, ranges)
|
||||
matchRecords[matchIndex].segments.push({
|
||||
divIndex: seg.divIndex,
|
||||
start: highlightStart,
|
||||
end: highlightEnd,
|
||||
})
|
||||
}
|
||||
if (end <= seg.end) break
|
||||
currentStart = seg.end
|
||||
|
|
@ -671,6 +686,8 @@ export default {
|
|||
})
|
||||
})
|
||||
|
||||
const pageMatches = this.pageMatchRanges[pageIndex] || []
|
||||
|
||||
matchRecords.forEach((record) => {
|
||||
if (!record.elements.length) return
|
||||
const newIndex = results.length
|
||||
|
|
@ -682,7 +699,13 @@ export default {
|
|||
element: record.elements[0],
|
||||
elements: record.elements,
|
||||
})
|
||||
pageMatches.push({
|
||||
matchIndex: newIndex,
|
||||
segments: record.segments.map((item) => ({ ...item })),
|
||||
})
|
||||
})
|
||||
|
||||
this.pageMatchRanges[pageIndex] = pageMatches
|
||||
})
|
||||
|
||||
this.searchResults = results
|
||||
|
|
@ -700,6 +723,8 @@ export default {
|
|||
|
||||
if (!skipNavigate) {
|
||||
this.navigateToResult(this.currentResultIndex, false)
|
||||
} else {
|
||||
this.updateCurrentResultActiveState()
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -720,18 +745,7 @@ export default {
|
|||
}
|
||||
|
||||
this.currentResultIndex = index
|
||||
this.searchResults.forEach((item, idx) => {
|
||||
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')
|
||||
}
|
||||
})
|
||||
})
|
||||
this.updateCurrentResultActiveState()
|
||||
|
||||
const target = this.searchResults[this.currentResultIndex]?.element
|
||||
if (!target) return
|
||||
|
|
@ -749,6 +763,99 @@ export default {
|
|||
})
|
||||
},
|
||||
|
||||
applyHighlightsToPage(pageIndex) {
|
||||
if (!this.keyword || !this.pageMatchRanges || !this.pageMatchRanges.length) return
|
||||
const textDivs = this.pageTextDivs[pageIndex]
|
||||
if (!textDivs || !textDivs.length) return
|
||||
|
||||
const pageMatches = this.pageMatchRanges[pageIndex]
|
||||
if (!pageMatches || !pageMatches.length) return
|
||||
|
||||
textDivs.forEach((div) => {
|
||||
const original = div.dataset.originalText
|
||||
if (typeof original !== 'undefined') {
|
||||
div.textContent = original
|
||||
}
|
||||
})
|
||||
|
||||
const perDivRanges = new Map()
|
||||
pageMatches.forEach((match) => {
|
||||
if (!match || !Array.isArray(match.segments)) return
|
||||
match.segments.forEach((segment) => {
|
||||
const { divIndex, start, end } = segment || {}
|
||||
if (typeof divIndex !== 'number') return
|
||||
const ranges = perDivRanges.get(divIndex) || []
|
||||
ranges.push({
|
||||
start,
|
||||
end,
|
||||
matchIndex: match.matchIndex,
|
||||
})
|
||||
perDivRanges.set(divIndex, ranges)
|
||||
})
|
||||
})
|
||||
|
||||
const updatedElements = new Map()
|
||||
|
||||
perDivRanges.forEach((ranges, divIndex) => {
|
||||
const div = textDivs[divIndex]
|
||||
if (!div) return
|
||||
const original = div.dataset.originalText || ''
|
||||
if (!original) return
|
||||
|
||||
const sorted = ranges
|
||||
.slice()
|
||||
.filter((item) => typeof item.start === 'number' && typeof item.end === 'number')
|
||||
.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 += `<mark class="search-highlight" data-match-index="${matchIndex}">${this.escapeForHtml(text)}</mark>`
|
||||
cursor = end
|
||||
})
|
||||
if (cursor < original.length) {
|
||||
html += this.escapeForHtml(original.slice(cursor))
|
||||
}
|
||||
div.innerHTML = html
|
||||
|
||||
const marks = div.querySelectorAll('mark.search-highlight')
|
||||
marks.forEach((mark) => {
|
||||
const matchIndex = Number(mark.dataset.matchIndex)
|
||||
if (Number.isNaN(matchIndex)) return
|
||||
const elements = updatedElements.get(matchIndex) || []
|
||||
elements.push(mark)
|
||||
updatedElements.set(matchIndex, elements)
|
||||
})
|
||||
})
|
||||
|
||||
updatedElements.forEach((elements, matchIndex) => {
|
||||
if (!this.searchResults[matchIndex]) return
|
||||
this.searchResults[matchIndex].elements = elements
|
||||
this.searchResults[matchIndex].element = elements[0] || null
|
||||
})
|
||||
|
||||
this.updateCurrentResultActiveState()
|
||||
},
|
||||
|
||||
updateCurrentResultActiveState() {
|
||||
this.searchResults.forEach((item, idx) => {
|
||||
if (!item) return
|
||||
const elements = item.elements && item.elements.length ? item.elements : item.element ? [item.element] : []
|
||||
elements.forEach((el) => {
|
||||
if (!el) return
|
||||
if (idx === this.currentResultIndex) {
|
||||
el.classList.add('is-active')
|
||||
} else {
|
||||
el.classList.remove('is-active')
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
goToPrevious() {
|
||||
if (!this.searchResults.length) return
|
||||
const idx = (this.currentResultIndex - 1 + this.searchResults.length) % this.searchResults.length
|
||||
|
|
@ -766,6 +873,7 @@ export default {
|
|||
this.clearHighlights()
|
||||
this.searchResults = []
|
||||
this.currentResultIndex = -1
|
||||
this.pageMatchRanges = []
|
||||
},
|
||||
|
||||
clearHighlights() {
|
||||
|
|
@ -784,6 +892,7 @@ export default {
|
|||
this.clearHighlights()
|
||||
this.searchResults = []
|
||||
this.currentResultIndex = -1
|
||||
this.pageMatchRanges = []
|
||||
this.pageTextDivs = []
|
||||
this.renderedPages.clear()
|
||||
this.pageContainers = []
|
||||
|
|
|
|||
Loading…
Reference in New Issue