提交 59089e47 authored 作者: 朱政's avatar 朱政

feat:智库原文搜索高亮开发

上级 945bd0af
流水线 #593 已失败 于阶段
in 52 秒
...@@ -326,7 +326,79 @@ export default { ...@@ -326,7 +326,79 @@ export default {
} }
} }
if (matchList.value.length > 0) jumpTo(0); if (matchList.value.length > 0) {
// 先把所有命中都标黄,再把“当前命中”改成蓝底
renderAllHighlights();
jumpTo(0);
}
};
const setActiveHighlight = (idx) => {
const all = document.querySelectorAll('.highlight-rect[data-match-idx]');
all.forEach((el) => {
const isActive = Number(el.getAttribute('data-match-idx')) === Number(idx);
if (isActive) {
el.classList.add('highlight-rect--active');
} else {
el.classList.remove('highlight-rect--active');
}
});
};
const renderAllHighlights = () => {
clearHighlights();
const list = Array.isArray(matchList.value) ? matchList.value : [];
list.forEach((m, idx) => {
if (!m || m.fallback) return;
const layer = overlayMap[m.pageNum];
if (!layer) return;
const pageWrap = layer.closest('.page-wrap');
const container = (pageWrap || layer);
const containerRect = container.getBoundingClientRect();
const segs = Array.isArray(m?.segments) ? m.segments : [];
for (const seg of segs) {
const segEl = seg?.el;
if (!segEl) continue;
const textNode = segEl.firstChild;
if (!textNode || textNode.nodeType !== Node.TEXT_NODE) continue;
try {
const range = document.createRange();
range.setStart(textNode, Math.max(0, seg.startIdx ?? 0));
range.setEnd(textNode, Math.max(0, seg.endIdx ?? 0));
const rectList = Array.from(range.getClientRects());
if (rectList.length) {
rectList.forEach(r => {
const mark = document.createElement('div');
mark.className = 'highlight-rect';
mark.setAttribute('data-match-idx', String(idx));
mark.style.zIndex = '5';
mark.style.left = (r.left - containerRect.left) + 'px';
mark.style.top = (r.top - containerRect.top) + 'px';
mark.style.width = r.width + 'px';
mark.style.height = r.height + 'px';
container.appendChild(mark);
});
} else {
const r = segEl.getBoundingClientRect();
if (r.width > 0 && r.height > 0) {
const mark = document.createElement('div');
mark.className = 'highlight-rect';
mark.setAttribute('data-match-idx', String(idx));
mark.style.zIndex = '5';
mark.style.left = (r.left - containerRect.left) + 'px';
mark.style.top = (r.top - containerRect.top) + 'px';
mark.style.width = r.width + 'px';
mark.style.height = r.height + 'px';
container.appendChild(mark);
}
}
range.detach?.();
} catch (e) {
// ignore
}
}
});
setActiveHighlight(matchIdx.value);
}; };
// 跳转到第 N 个匹配项 // 跳转到第 N 个匹配项
...@@ -342,55 +414,7 @@ export default { ...@@ -342,55 +414,7 @@ export default {
const firstSeg = m?.segments?.[0]; const firstSeg = m?.segments?.[0];
const el = firstSeg?.el; const el = firstSeg?.el;
if (!el) return; if (!el) return;
clearHighlights(); setActiveHighlight(idx);
const layer = overlayMap[m.pageNum];
if (!layer) return;
const pageWrap = layer.closest('.page-wrap');
// 用 Range 精确计算“子串”在页面上的矩形位置,再画黄色块(支持跨 span)
const containerRect = (pageWrap || layer).getBoundingClientRect();
const segs = Array.isArray(m?.segments) ? m.segments : [];
for (const seg of segs) {
const segEl = seg?.el;
if (!segEl) continue;
const textNode = segEl.firstChild;
if (!textNode || textNode.nodeType !== Node.TEXT_NODE) continue;
try {
const range = document.createRange();
range.setStart(textNode, Math.max(0, seg.startIdx ?? 0));
range.setEnd(textNode, Math.max(0, seg.endIdx ?? 0));
const rectList = Array.from(range.getClientRects());
if (rectList.length) {
rectList.forEach(r => {
const mark = document.createElement('div');
mark.className = 'highlight-rect';
mark.style.zIndex = '5';
mark.style.left = (r.left - containerRect.left) + 'px';
mark.style.top = (r.top - containerRect.top) + 'px';
mark.style.width = r.width + 'px';
mark.style.height = r.height + 'px';
(pageWrap || layer).appendChild(mark);
});
} else {
// Range 兜底为空时:用 span 自身的矩形画块(精度低,但尽量可见)
const r = segEl.getBoundingClientRect();
if (r.width > 0 && r.height > 0) {
const mark = document.createElement('div');
mark.className = 'highlight-rect';
mark.style.zIndex = '5';
mark.style.left = (r.left - containerRect.left) + 'px';
mark.style.top = (r.top - containerRect.top) + 'px';
mark.style.width = r.width + 'px';
mark.style.height = r.height + 'px';
(pageWrap || layer).appendChild(mark);
}
}
range.detach?.();
} catch (e) {
// ignore
}
}
// 优先只滚动右侧 report-box,避免触发整页滚动导致 header 遮挡 // 优先只滚动右侧 report-box,避免触发整页滚动导致 header 遮挡
const container = el.closest('.report-box'); const container = el.closest('.report-box');
...@@ -510,6 +534,10 @@ canvas { ...@@ -510,6 +534,10 @@ canvas {
z-index: 5; z-index: 5;
} }
.textLayer :deep(.highlight-rect--active) {
background: rgb(184, 222, 254);
}
.page-wrap :deep(.highlight-rect) { .page-wrap :deep(.highlight-rect) {
position: absolute; position: absolute;
background: #ff0; background: #ff0;
...@@ -519,6 +547,10 @@ canvas { ...@@ -519,6 +547,10 @@ canvas {
z-index: 3; z-index: 3;
} }
.page-wrap :deep(.highlight-rect--active) {
background: rgb(184, 222, 254);
}
.loading { .loading {
position: absolute; position: absolute;
top: 50%; top: 50%;
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论