需求:输入框中有输入内容,如有敏感词则需要标记
下面这个也可以,有用点个赞
el-input输入框敏感词关键词高亮标红_el-input 内容高亮显示-CSDN博客
直接给代码
ContentEditable.vue
<template>
<div
ref="editableDiv"
contenteditable="true"
@input="handleInput"
@keydown="handleKeyDown"
@compositionstart="handleCompositionStart"
@compositionend="handleCompositionEnd"
:class="['editable-box ', { empty: true }]"
:data-placeholder="placeholder"
></div>
</template>
<script>
export default {
props: {
value: {
type: String,
default: ""
},
placeholder: {
type: String,
default: "请输入内容"
},
keywords: {
type: Array,
default: () => []
}
},
data: () => ({
lastHTML: "",
isComposing: false,
isFocused: false
}),
watch: {
value: {
immediate: true,
handler(newVal) {
console.log(newVal, this.$refs?.editableDiv?.innerHTML)
if (newVal !== this.cleanContent(this.$refs?.editableDiv?.innerHTML)) {
this.$nextTick(() => {
this.safeUpdate(newVal)
})
}
}
}
},
mounted() {
this.$refs.editableDiv.addEventListener("focus", () => (this.isFocused = true))
this.$refs.editableDiv.addEventListener("blur", () => (this.isFocused = false))
},
methods: {
// 安全更新内容(带光标保护)
safeUpdate(newVal) {
if (!this.$refs.editableDiv) return
const selection = window.getSelection()
const hadFocus = this.isFocused
const { range, offset } = this.saveCursorPosition()
// 更新内容
this.lastHTML = this.highlightKeywords(newVal)
if (this.$refs.editableDiv) {
this.$refs.editableDiv.innerHTML = this.lastHTML
}
// 恢复光标
if (hadFocus) {
this.$nextTick(() => {
this.restoreCursorPosition(range, offset)
if (!this.isFocused) this.$refs.editableDiv.focus()
})
}
},
// 智能光标保存
saveCursorPosition() {
const sel = window.getSelection()
if (!sel.rangeCount) return { range: null, offset: 0 }
const range = sel.getRangeAt(0)
const preRange = range.cloneRange()
preRange.selectNodeContents(this.$refs.editableDiv)
preRange.setEnd(range.startContainer, range.startOffset)
return {
range: range,
offset: preRange.toString().length
}
},
// 精准光标恢复
restoreCursorPosition(originalRange, targetOffset) {
const walker = document.createTreeWalker(this.$refs.editableDiv, NodeFilter.SHOW_TEXT, null, false)
let cumulative = 0
let targetNode = null
let targetPos = 0
while (walker.nextNode()) {
const node = walker.currentNode
const len = node.textContent.length
if (cumulative + len >= targetOffset) {
targetNode = node
targetPos = targetOffset - cumulative
break
}
cumulative += len
}
const range = document.createRange()
if (targetNode) {
range.setStart(targetNode, Math.min(targetPos, targetNode.textContent.length))
} else {
range.selectNodeContents(this.$refs.editableDiv)
range.collapse(false)
}
range.collapse(true)
const sel = window.getSelection()
sel.removeAllRanges()
sel.addRange(range)
},
removeSpaces(text) {
return text.replace(/\s/g, "")
},
// 安全更新内容(保持光标位置)
updateContent(newText) {
const el = this.$refs.editableDiv
const selection = window.getSelection()
const range = selection.getRangeAt(0)
// 保存光标状态
const anchorNode = range.startContainer
const anchorOffset = range.startOffset
// 更新内容
el.textContent = newText
// 恢复光标
const newRange = document.createRange()
newRange.setStart(anchorNode, Math.min(anchorOffset, newText.length))
newRange.collapse(true)
selection.removeAllRanges()
selection.addRange(newRange)
},
handleInput(e) {
if (this.isComposing) return
const originalText = e.target.textContent
const processedText = this.removeSpaces(originalText)
if (processedText === this.lastHTML) return
if (originalText !== processedText) {
this.$nextTick(() => {
this.updateContent(processedText)
})
}
const cleanText = this.cleanContent(processedText)
this.lastHTML = processedText
this.$emit("input", cleanText)
this.safeUpdate(cleanText)
},
// 输入处理(优化防抖)
handleInput1(e) {
if (this.isComposing) return
const currentHTML = e.target.innerHTML
if (currentHTML === this.lastHTML) return
const cleanText = this.cleanContent(currentHTML)
this.lastHTML = currentHTML
this.$emit("input", cleanText)
this.safeUpdate(cleanText)
},
// 关键词高亮处理
highlightKeywords(text) {
const div = document.createElement("div")
div.textContent = text
const rawText = div.innerHTML
return this.keywords.reduce((html, keyword) => {
const escaped = this.escapeRegExp(keyword)
const regex = new RegExp(`(${escaped})`, "gi")
return html.replace(regex, '<mark class="keyword-highlight">$1</mark>')
}, rawText)
},
// 清理HTML标签
cleanContent(html) {
const div = document.createElement("div")
div.innerHTML = html
return div.textContent
},
// 转义正则特殊字符
escapeRegExp(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
},
// 中文输入法处理
handleCompositionStart() {
this.isComposing = true
},
handleCompositionEnd() {
this.isComposing = false
this.handleInput({ target: this.$refs.editableDiv })
},
// 按键处理(防止回车换行)
handleKeyDown(e) {
if (e.key === "Enter") {
e.preventDefault()
this.$emit("enter")
}
if (e.key === " " || e.keyCode === 32) {
e.preventDefault()
return false
}
}
}
}
</script>
<style>
.editable-box {
min-height: 40px;
padding: 8px;
border: 1px solid #dcdfe6;
border-radius: 4px;
line-height: 1.5;
text-align: left;
white-space: pre-wrap;
outline: none;
&:focus {
border-color: #409eff;
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
}
&.empty:empty::before {
content: attr(data-placeholder);
color: #c0c4cc;
pointer-events: none;
}
}
.keyword-highlight {
background-color: #fff3d8;
padding: 0 2px;
border-radius: 2px;
}
</style>
父组件调用
<template>
<div>
<!-- 组件使用 -->
<ContentEditable
v-model="content"
:keywords="['Vue', 'Element UI']"
placeholder="输入包含关键词的内 容"
@enter="submit"
/>
<!-- 显示原始文本 -->
<div>当前内容:{{ content }}</div>
</div>
</template>
<script>
import ContentEditable from "../components/ContentEditable.vue"
export default {
name: "App",
components: { ContentEditable },
filters: {},
data() {
return {
content: "尝试输入包含Vue或Element UI的文本",
keywords: ["Vue", "Element UI"]
}
},
computed: {},
watch: {},
methods: {
submit() {}
}
}
</script>
<style></style>