word 搜索优化
This commit is contained in:
parent
3d8ea635a8
commit
d6bac94fda
|
|
@ -31,7 +31,7 @@
|
|||
"clipboard": "2.0.8",
|
||||
"core-js": "3.37.1",
|
||||
"crypto-js": "^4.2.0",
|
||||
"docx-preview": "^0.1.7",
|
||||
"docx-preview": "^0.3.7",
|
||||
"echarts": "5.4.0",
|
||||
"element-ui": "2.15.14",
|
||||
"file-saver": "2.0.5",
|
||||
|
|
|
|||
|
|
@ -50,7 +50,6 @@
|
|||
import * as docxPreview from 'docx-preview/dist/docx-preview.js'
|
||||
|
||||
const DEFAULT_DOC_URL = 'http://192.168.0.14:9090/smart-bid/technicalSolutionDatabase/2025/11/11/887b35d28b2149b6a7555fb639be9411.docx'
|
||||
// const DEFAULT_DOC_URL = 'http://192.168.0.14:9090/smart-bid/technicalSolutionDatabase/2025/11/12/efec85cd3793469d9d0a6991d7d08f9b.doc'
|
||||
|
||||
export default {
|
||||
name: 'DocumentSearchWord',
|
||||
|
|
@ -131,14 +130,26 @@ export default {
|
|||
await docxPreview.renderAsync(arrayBuffer, container, null, {
|
||||
className: 'docx-viewer',
|
||||
inWrapper: true,
|
||||
ignoreFonts: false,
|
||||
breakPages: false,
|
||||
hideWrapperOnPrint: false,
|
||||
ignoreWidth: false,
|
||||
ignoreHeight: false,
|
||||
ignoreFonts: false,
|
||||
breakPages: false,
|
||||
ignoreLastRenderedPageBreak: true,
|
||||
experimental: false,
|
||||
trimXmlDeclaration: true,
|
||||
useBase64URL: false,
|
||||
renderChanges: false,
|
||||
renderHeaders: true,
|
||||
renderFooters: true,
|
||||
renderFootnotes: true,
|
||||
renderEndnotes: true,
|
||||
renderComments: false,
|
||||
renderAltChunks: true,
|
||||
useMathMLPolyfill: false,
|
||||
debug: false,
|
||||
})
|
||||
this.wrapContentWithArticle(container)
|
||||
this.normalizeTableStyles(container)
|
||||
this.injectFontResolver(container)
|
||||
this.docRendered = true
|
||||
this.loading = false
|
||||
this.$nextTick(() => {
|
||||
|
|
@ -158,6 +169,11 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
getElementTextContent(element) {
|
||||
if (!element) return ''
|
||||
return (element.textContent || '').trim()
|
||||
},
|
||||
|
||||
prepareSearchSegments(forceReset = false) {
|
||||
if (!this.docRendered) {
|
||||
if (forceReset) {
|
||||
|
|
@ -182,12 +198,33 @@ export default {
|
|||
'.docx-viewer span',
|
||||
]
|
||||
|
||||
const elements = Array.from(container.querySelectorAll(selectors.join(',')))
|
||||
.filter((el) => (el.textContent || '').trim())
|
||||
const allElements = Array.from(container.querySelectorAll(selectors.join(',')))
|
||||
const targetTags = ['p', 'li', 'td', 'th', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'span']
|
||||
|
||||
const elements = allElements.filter((el) => {
|
||||
const text = this.getElementTextContent(el)
|
||||
if (text.length === 0) return false
|
||||
|
||||
let parent = el.parentElement
|
||||
while (parent && parent !== container) {
|
||||
const parentTag = parent.tagName ? parent.tagName.toLowerCase() : ''
|
||||
if (targetTags.includes(parentTag)) {
|
||||
return false
|
||||
}
|
||||
parent = parent.parentElement
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
elements.forEach((el, idx) => {
|
||||
if (typeof el.dataset.originalHtml === 'undefined') {
|
||||
el.dataset.originalHtml = el.innerHTML
|
||||
const cleanHtml = this.cleanHtmlFromMarks(el.innerHTML)
|
||||
el.dataset.originalHtml = cleanHtml
|
||||
} else {
|
||||
el.dataset.originalHtml = this.cleanHtmlFromMarks(el.dataset.originalHtml)
|
||||
}
|
||||
if (typeof el.dataset.originalText === 'undefined') {
|
||||
el.dataset.originalText = this.getElementTextContent(el)
|
||||
}
|
||||
el.dataset.segmentIndex = idx
|
||||
})
|
||||
|
|
@ -213,6 +250,92 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
cleanHtmlFromMarks(html) {
|
||||
if (!html) return html
|
||||
const tempDiv = document.createElement('div')
|
||||
tempDiv.innerHTML = html
|
||||
const marks = tempDiv.querySelectorAll('mark')
|
||||
marks.forEach(mark => {
|
||||
const parent = mark.parentNode
|
||||
while (mark.firstChild) {
|
||||
parent.insertBefore(mark.firstChild, mark)
|
||||
}
|
||||
parent.removeChild(mark)
|
||||
})
|
||||
return tempDiv.innerHTML
|
||||
},
|
||||
|
||||
highlightTextInNode(node, pattern, results, segmentIndex, parentElement, markIndexRef) {
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
const text = node.textContent
|
||||
if (!text || !text.trim()) return
|
||||
|
||||
pattern.lastIndex = 0
|
||||
const textMatches = []
|
||||
let match
|
||||
while ((match = pattern.exec(text)) !== null) {
|
||||
textMatches.push({
|
||||
index: match.index,
|
||||
length: match[0].length,
|
||||
text: match[0]
|
||||
})
|
||||
}
|
||||
|
||||
if (textMatches.length === 0) return
|
||||
|
||||
const parent = node.parentNode
|
||||
if (!parent) return
|
||||
|
||||
let lastIndex = 0
|
||||
const fragment = document.createDocumentFragment()
|
||||
|
||||
textMatches.forEach((matchInfo) => {
|
||||
if (lastIndex < matchInfo.index) {
|
||||
const beforeText = text.substring(lastIndex, matchInfo.index)
|
||||
if (beforeText) {
|
||||
fragment.appendChild(document.createTextNode(beforeText))
|
||||
}
|
||||
}
|
||||
|
||||
const mark = document.createElement('mark')
|
||||
mark.className = 'search-highlight'
|
||||
mark.textContent = matchInfo.text
|
||||
fragment.appendChild(mark)
|
||||
|
||||
results.push({
|
||||
element: mark,
|
||||
segmentIndex: segmentIndex,
|
||||
markIndex: markIndexRef.value,
|
||||
parentElement: parentElement
|
||||
})
|
||||
|
||||
markIndexRef.value++
|
||||
lastIndex = matchInfo.index + matchInfo.length
|
||||
})
|
||||
|
||||
if (lastIndex < text.length) {
|
||||
const afterText = text.substring(lastIndex)
|
||||
if (afterText) {
|
||||
fragment.appendChild(document.createTextNode(afterText))
|
||||
}
|
||||
}
|
||||
|
||||
if (fragment.childNodes.length > 0) {
|
||||
parent.replaceChild(fragment, node)
|
||||
}
|
||||
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
||||
const tagName = node.tagName ? node.tagName.toLowerCase() : ''
|
||||
if (tagName === 'mark' || tagName === 'script' || tagName === 'style' || tagName === 'noscript') {
|
||||
return
|
||||
}
|
||||
|
||||
const children = Array.from(node.childNodes)
|
||||
children.forEach(child => {
|
||||
this.highlightTextInNode(child, pattern, results, segmentIndex, parentElement, markIndexRef)
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
highlightMatches() {
|
||||
const keyword = this.keyword.trim()
|
||||
if (!keyword) {
|
||||
|
|
@ -229,42 +352,103 @@ export default {
|
|||
this.clearHighlights()
|
||||
|
||||
const results = []
|
||||
this.searchSegments.forEach((el) => {
|
||||
let totalTextMatches = 0
|
||||
|
||||
this.searchSegments.forEach((el, segmentIndex) => {
|
||||
const originalText = el.dataset.originalText
|
||||
if (typeof originalText === 'undefined' || !originalText) return
|
||||
|
||||
pattern.lastIndex = 0
|
||||
const textMatchCount = (originalText.match(pattern) || []).length
|
||||
if (textMatchCount === 0) return
|
||||
|
||||
totalTextMatches += textMatchCount
|
||||
|
||||
const originalHtml = el.dataset.originalHtml
|
||||
if (typeof originalHtml === 'undefined') return
|
||||
const text = el.textContent || ''
|
||||
pattern.lastIndex = 0
|
||||
if (!pattern.test(text)) {
|
||||
return
|
||||
|
||||
const cleanHtml = this.cleanHtmlFromMarks(originalHtml)
|
||||
el.innerHTML = cleanHtml
|
||||
el.dataset.originalHtml = cleanHtml
|
||||
|
||||
const existingMarks = el.querySelectorAll('mark')
|
||||
if (existingMarks.length > 0) {
|
||||
console.warn(`Segment ${segmentIndex} still has ${existingMarks.length} mark tags after cleaning`)
|
||||
}
|
||||
pattern.lastIndex = 0
|
||||
const highlightedHtml = originalHtml.replace(pattern, '<mark class="search-highlight">$1</mark>')
|
||||
el.innerHTML = highlightedHtml
|
||||
const marks = el.querySelectorAll('mark.search-highlight')
|
||||
marks.forEach((mark) => {
|
||||
results.push({ element: mark })
|
||||
|
||||
const markIndexRef = { value: 0 }
|
||||
const children = Array.from(el.childNodes)
|
||||
children.forEach(child => {
|
||||
this.highlightTextInNode(child, pattern, results, segmentIndex, el, markIndexRef)
|
||||
})
|
||||
|
||||
const createdMarks = el.querySelectorAll('mark.search-highlight')
|
||||
if (createdMarks.length !== textMatchCount) {
|
||||
console.warn(`Segment ${segmentIndex}: expected ${textMatchCount} marks, created ${createdMarks.length}`)
|
||||
}
|
||||
})
|
||||
|
||||
if (results.length !== totalTextMatches) {
|
||||
console.warn(`Total matches mismatch: expected ${totalTextMatches}, got ${results.length}`)
|
||||
}
|
||||
|
||||
this.searchResults = results
|
||||
|
||||
if (!results.length) {
|
||||
this.currentResultIndex = -1
|
||||
this.$message && this.$message.info(`未找到“${keyword}”相关内容`)
|
||||
this.$message && this.$message.info(`未找到"${keyword}"相关内容`)
|
||||
return
|
||||
}
|
||||
|
||||
this.currentResultIndex = 0
|
||||
this.updateActiveHighlight()
|
||||
this.navigateToResult(this.currentResultIndex)
|
||||
this.$nextTick(() => {
|
||||
this.navigateToResult(this.currentResultIndex)
|
||||
})
|
||||
},
|
||||
|
||||
getResultElement(result) {
|
||||
if (!result) return null
|
||||
|
||||
if (result.element && document.body.contains(result.element)) {
|
||||
return result.element
|
||||
}
|
||||
|
||||
if (result.parentElement && result.segmentIndex !== undefined && result.markIndex !== undefined) {
|
||||
const parent = result.parentElement
|
||||
if (document.body.contains(parent)) {
|
||||
const marks = parent.querySelectorAll('mark.search-highlight')
|
||||
if (marks[result.markIndex]) {
|
||||
result.element = marks[result.markIndex]
|
||||
return result.element
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result.segmentIndex !== undefined && result.markIndex !== undefined && this.searchSegments[result.segmentIndex]) {
|
||||
const segment = this.searchSegments[result.segmentIndex]
|
||||
if (document.body.contains(segment)) {
|
||||
const marks = segment.querySelectorAll('mark.search-highlight')
|
||||
if (marks[result.markIndex]) {
|
||||
result.element = marks[result.markIndex]
|
||||
result.parentElement = segment
|
||||
return result.element
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
},
|
||||
|
||||
updateActiveHighlight() {
|
||||
this.searchResults.forEach((item, idx) => {
|
||||
if (!item.element) return
|
||||
const element = this.getResultElement(item)
|
||||
if (!element) return
|
||||
|
||||
if (idx === this.currentResultIndex) {
|
||||
item.element.classList.add('is-active')
|
||||
element.classList.add('is-active', 'is-current')
|
||||
} else {
|
||||
item.element.classList.remove('is-active')
|
||||
element.classList.remove('is-active', 'is-current')
|
||||
}
|
||||
})
|
||||
},
|
||||
|
|
@ -279,18 +463,53 @@ export default {
|
|||
return offset
|
||||
},
|
||||
|
||||
navigateToResult(index) {
|
||||
navigateToResult(index, useSmoothScroll = false) {
|
||||
if (!this.searchResults.length || index < 0 || index >= this.searchResults.length) return
|
||||
this.currentResultIndex = index
|
||||
this.updateActiveHighlight()
|
||||
|
||||
const target = this.searchResults[index]?.element
|
||||
const wrapper = this.$refs.docWrapper
|
||||
if (!target || !wrapper) return
|
||||
this.$nextTick(() => {
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
const result = this.searchResults[this.currentResultIndex]
|
||||
const target = this.getResultElement(result)
|
||||
const wrapper = this.$refs.docWrapper
|
||||
if (!target || !wrapper) return
|
||||
|
||||
const absoluteTop = this.calculateOffsetTop(target, wrapper)
|
||||
const margin = 24
|
||||
wrapper.scrollTop = Math.max(absoluteTop - margin, 0)
|
||||
if (!document.body.contains(target)) {
|
||||
return
|
||||
}
|
||||
|
||||
const targetRect = target.getBoundingClientRect()
|
||||
if (targetRect.width === 0 && targetRect.height === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const wrapperRect = wrapper.getBoundingClientRect()
|
||||
const currentScrollTop = wrapper.scrollTop
|
||||
const targetTopRelativeToWrapper = targetRect.top - wrapperRect.top + currentScrollTop
|
||||
const margin = 24
|
||||
const desiredScrollTop = Math.max(targetTopRelativeToWrapper - margin, 0)
|
||||
|
||||
wrapper.scrollTop = desiredScrollTop
|
||||
} catch (error) {
|
||||
try {
|
||||
let absoluteTop = 0
|
||||
let node = target
|
||||
while (node && node !== wrapper) {
|
||||
absoluteTop += node.offsetTop || 0
|
||||
node = node.offsetParent
|
||||
}
|
||||
const margin = 24
|
||||
wrapper.scrollTop = Math.max(absoluteTop - margin, 0)
|
||||
} catch (e) {
|
||||
console.error('Scroll error:', e)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
async goToPrevious() {
|
||||
|
|
@ -355,139 +574,15 @@ export default {
|
|||
this.loadDocument()
|
||||
},
|
||||
|
||||
injectFontResolver(container) {
|
||||
if (!container || container.querySelector('.docx-fonts-resolver')) return
|
||||
const resolver = document.createElement('div')
|
||||
resolver.className = 'docx-fonts-resolver'
|
||||
resolver.innerHTML = [
|
||||
'微软雅黑', '宋体', '黑体', '仿宋', '楷体', '等线', 'Times New Roman', 'Arial'
|
||||
].map(font => `<span style="font-family:${font};">${font}</span>`).join('')
|
||||
container.appendChild(resolver)
|
||||
},
|
||||
|
||||
wrapContentWithArticle(container) {
|
||||
if (!container) return
|
||||
const sections = Array.from(container.querySelectorAll('.docx-wrapper section'))
|
||||
if (sections.length === 0) {
|
||||
this.wrapElementsInSections(container)
|
||||
return
|
||||
}
|
||||
sections.forEach((section) => {
|
||||
this.wrapElementsInSections(section)
|
||||
})
|
||||
},
|
||||
|
||||
wrapElementsInSections(node) {
|
||||
if (!node) return
|
||||
|
||||
// 获取所有需要包装的元素类型,但排除 header 和 footer 内的
|
||||
const selectors = [
|
||||
'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
||||
'ul', 'ol', 'blockquote', 'table'
|
||||
]
|
||||
|
||||
// 获取所有匹配的元素
|
||||
const allElements = Array.from(node.querySelectorAll(selectors.join(',')))
|
||||
|
||||
// 过滤:只保留最外层的元素(没有父元素也是目标选择器)
|
||||
const elements = allElements.filter(el => {
|
||||
// 检查元素是否在 header 或 footer 内
|
||||
let parent = el.parentElement
|
||||
while (parent && parent !== node) {
|
||||
const tag = parent.tagName ? parent.tagName.toLowerCase() : ''
|
||||
if (tag === 'header' || tag === 'footer') {
|
||||
return false // 在 header/footer 内,跳过
|
||||
}
|
||||
|
||||
// 如果父元素也是目标选择器之一,说明当前元素是嵌套的,应该跳过
|
||||
if (selectors.includes(tag)) {
|
||||
return false
|
||||
}
|
||||
|
||||
parent = parent.parentElement
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
if (elements.length === 0) return
|
||||
|
||||
// 按连续元素分组
|
||||
const elementGroups = this.groupConsecutiveElements(elements)
|
||||
|
||||
// 为每个元素组创建 article 包装
|
||||
elementGroups.forEach(group => {
|
||||
if (group.length > 0) {
|
||||
this.wrapElementGroupWithArticle(group)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
groupConsecutiveElements(elements) {
|
||||
const groups = []
|
||||
let currentGroup = []
|
||||
|
||||
elements.forEach((el, index) => {
|
||||
// 如果当前组为空,直接添加
|
||||
if (currentGroup.length === 0) {
|
||||
currentGroup.push(el)
|
||||
return
|
||||
}
|
||||
// 检查是否连续(在 DOM 中相邻)
|
||||
const lastEl = currentGroup[currentGroup.length - 1]
|
||||
let nextElement = lastEl.nextElementSibling
|
||||
|
||||
// 跳过空白文本节点等,找到下一个元素
|
||||
while (nextElement && nextElement.nodeType !== 1) {
|
||||
nextElement = nextElement.nextElementSibling
|
||||
}
|
||||
|
||||
if (nextElement === el) {
|
||||
// 连续的元素
|
||||
currentGroup.push(el)
|
||||
} else {
|
||||
// 不连续,开始新组
|
||||
groups.push([...currentGroup])
|
||||
currentGroup = [el]
|
||||
}
|
||||
|
||||
// 如果是最后一个元素,结束当前组
|
||||
if (index === elements.length - 1) {
|
||||
groups.push([...currentGroup])
|
||||
}
|
||||
})
|
||||
|
||||
return groups
|
||||
},
|
||||
|
||||
wrapElementGroupWithArticle(elements) {
|
||||
if (!elements || elements.length === 0) return
|
||||
|
||||
const firstElement = elements[0]
|
||||
const parent = firstElement.parentElement
|
||||
|
||||
// 创建 article 元素
|
||||
const article = document.createElement('article')
|
||||
article.className = 'docx-article-wrapper'
|
||||
|
||||
// 在第一个元素前插入 article
|
||||
parent.insertBefore(article, firstElement)
|
||||
|
||||
// 将所有元素移动到 article 中
|
||||
elements.forEach(el => {
|
||||
article.appendChild(el)
|
||||
})
|
||||
},
|
||||
|
||||
normalizeTableStyles(container) {
|
||||
if (!container) return
|
||||
const tables = container.querySelectorAll('table')
|
||||
tables.forEach((table) => {
|
||||
table.style.width = 'auto'
|
||||
table.style.display = 'table'
|
||||
table.style.tableLayout = 'auto'
|
||||
table.style.textAlign = 'left'
|
||||
table.style.margin = '0 auto'
|
||||
const pTags = table.querySelectorAll('td p.docx-viewer.docx-viewer_TableParagraph')
|
||||
const pTags = table.querySelectorAll('td p.docx-viewer_tableparagraph')
|
||||
pTags.forEach((p) => {
|
||||
p.style.lineHeight = '1.2'
|
||||
})
|
||||
|
|
@ -558,15 +653,22 @@ export default {
|
|||
overflow: hidden;
|
||||
position: relative;
|
||||
display: flex;
|
||||
min-height: 480px;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
max-height: 100%;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.doc-wrapper {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: 24px;
|
||||
background: #eef2ff;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.doc-content {
|
||||
|
|
@ -587,115 +689,13 @@ export default {
|
|||
border: 1px solid rgba(68, 112, 255, 0.18);
|
||||
}
|
||||
|
||||
::v-deep .docx-wrapper section {
|
||||
background: transparent !important;
|
||||
padding: 48px 56px !important;
|
||||
line-height: 1.75;
|
||||
font-size: 14px;
|
||||
color: #1f2a62;
|
||||
::v-deep .docx-viewer-wrapper>section.docx-viewer {
|
||||
background: white;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||
margin-bottom: 5px !important;
|
||||
}
|
||||
|
||||
|
||||
::v-deep .docx-wrapper h1,
|
||||
::v-deep .docx-wrapper h2,
|
||||
::v-deep .docx-wrapper h3,
|
||||
::v-deep .docx-wrapper h4,
|
||||
::v-deep .docx-wrapper h5,
|
||||
::v-deep .docx-wrapper h6 {
|
||||
color: #1b2f72;
|
||||
}
|
||||
|
||||
::v-deep .docx-wrapper .docx span {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.doc-content h1,
|
||||
.doc-content h2,
|
||||
.doc-content h3,
|
||||
.doc-content h4,
|
||||
.doc-content h5,
|
||||
.doc-content h6 {
|
||||
font-weight: 700;
|
||||
margin: 24px 0 12px;
|
||||
color: #1b2f72;
|
||||
}
|
||||
|
||||
.doc-content h1 {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.doc-content h2 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.doc-content h3 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.doc-content h4 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.doc-content h5 {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.doc-content h6 {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.doc-content p {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.doc-content ul,
|
||||
.doc-content ol {
|
||||
padding-left: 28px;
|
||||
margin: 8px 0 12px;
|
||||
}
|
||||
|
||||
.doc-content ul {
|
||||
list-style: disc;
|
||||
}
|
||||
|
||||
.doc-content ol {
|
||||
list-style: decimal;
|
||||
}
|
||||
|
||||
.doc-content blockquote {
|
||||
margin: 12px 0;
|
||||
padding: 12px 18px;
|
||||
background: rgba(68, 112, 255, 0.08);
|
||||
border-left: 4px solid rgba(68, 112, 255, 0.45);
|
||||
color: #3b4d86;
|
||||
}
|
||||
|
||||
.doc-content table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 16px 0;
|
||||
background: #ffffff;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
|
||||
.doc-content img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin: 12px auto;
|
||||
}
|
||||
|
||||
.doc-content hr {
|
||||
border: none;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||
margin: 24px 0;
|
||||
}
|
||||
|
||||
.doc-content sup {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
|
|
@ -728,14 +728,18 @@ export default {
|
|||
opacity: 0;
|
||||
}
|
||||
|
||||
.search-highlight {
|
||||
::v-deep .search-highlight {
|
||||
background: rgba(255, 241, 168, 0.9);
|
||||
padding: 0 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.search-highlight.is-active {
|
||||
::v-deep .search-highlight.is-active,
|
||||
::v-deep .search-highlight.is-current {
|
||||
background: #206dff !important;
|
||||
color: #ffffff;
|
||||
box-shadow: 0 0 0 2px rgba(32, 109, 255, 0.35);
|
||||
border-radius: 4px;
|
||||
transition: background 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in New Issue