From 628894a2540c78155411865c22df78f36d3546f1 Mon Sep 17 00:00:00 2001 From: Phoenix <64720302+Concur-max@users.noreply.github.com> Date: Mon, 9 Jun 2025 13:57:15 +0800 Subject: [PATCH] =?UTF-8?q?refactor(editor):=20=E4=BC=98=E5=8C=96mention?= =?UTF-8?q?=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91=E5=B9=B6=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E8=B0=83=E8=AF=95=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 移除调试用的console.log语句 重构mention列表过滤逻辑,使用startsWith替代includes 添加Backspace和Delete键删除mention元素的功能 优化键盘事件处理逻辑,减少不必要的DOM操作 --- src/components/editor/CustomEditor.vue | 130 +++++++++++++++++++++-- src/views/message/inner/IndexContent.vue | 2 - 2 files changed, 122 insertions(+), 10 deletions(-) diff --git a/src/components/editor/CustomEditor.vue b/src/components/editor/CustomEditor.vue index b22e712..0ec7906 100644 --- a/src/components/editor/CustomEditor.vue +++ b/src/components/editor/CustomEditor.vue @@ -33,7 +33,6 @@ const props = defineProps({ const emit = defineEmits(['editor-event']) -// 响应式数据 const editorRef = ref(null) const content = ref('') const isFocused = ref(false) @@ -49,7 +48,6 @@ const fileImageRef = ref(null) const uploadFileRef = ref(null) const emoticonRef = ref(null) -// 获取状态管理 const dialogueStore = useDialogueStore() const editorDraftStore = useEditorDraftStore() @@ -94,7 +92,7 @@ const toolbarConfig = computed(() => { return config }) -// 处理输入事件 - 优化版本,减少DOM操作 +// 处理输入事件 const handleInput = (event) => { const target = event.target @@ -162,9 +160,9 @@ const checkMention = (target) => { // 显示mention列表 const showMentionList = () => { const query = currentMentionQuery.value.toLowerCase() - mentionList.value = props.members.filter(member => - member.nickname.toLowerCase().includes(query) - ).slice(0, 10) + mentionList.value = props.members.filter(member => { + return member.value.toLowerCase().startsWith(query) + }) showMention.value = mentionList.value.length > 0 selectedMentionIndex.value = 0 @@ -310,7 +308,7 @@ const handlePaste = (event) => { } } -// 处理键盘事件 - 优化版本,减少不必要的日志和简化逻辑 +// 处理键盘事件 const handleKeydown = (event) => { // 处理@提及列表的键盘导航 if (showMention.value) { @@ -358,6 +356,122 @@ const handleKeydown = (event) => { return } + // 处理删除键(Backspace和Delete)删除mention元素 + if (event.key === 'Backspace' || event.key === 'Delete') { + const selection = window.getSelection() + if (!selection.rangeCount) return + + const range = selection.getRangeAt(0) + const editor = editorRef.value + + // 只处理光标位置的删除,不处理选中内容的删除 + if (range.collapsed) { + let targetMention = null + + // 获取光标位置信息 + const container = range.startContainer + const offset = range.startOffset + + if (event.key === 'Backspace') { + // Backspace:查找光标前面的mention元素 + if (container.nodeType === Node.TEXT_NODE) { + // 如果光标在文本节点的开头,检查前一个兄弟节点 + if (offset === 0) { + let prevSibling = container.previousSibling + while (prevSibling) { + if (prevSibling.nodeType === Node.ELEMENT_NODE && + prevSibling.classList && + prevSibling.classList.contains('mention')) { + targetMention = prevSibling + break + } + // 如果是文本节点且不为空,停止查找 + if (prevSibling.nodeType === Node.TEXT_NODE && prevSibling.textContent.trim()) { + break + } + prevSibling = prevSibling.previousSibling + } + } + } else if (container.nodeType === Node.ELEMENT_NODE) { + // 如果光标在元素节点中,检查前一个子节点 + if (offset > 0) { + let prevChild = container.childNodes[offset - 1] + // 如果前一个子节点是mention元素 + if (prevChild && prevChild.nodeType === Node.ELEMENT_NODE && + prevChild.classList && prevChild.classList.contains('mention')) { + targetMention = prevChild + } + // 如果前一个子节点是文本节点,检查它前面的兄弟节点 + else if (prevChild && prevChild.nodeType === Node.TEXT_NODE && !prevChild.textContent.trim()) { + let prevSibling = prevChild.previousSibling + while (prevSibling) { + if (prevSibling.nodeType === Node.ELEMENT_NODE && + prevSibling.classList && + prevSibling.classList.contains('mention')) { + targetMention = prevSibling + break + } + if (prevSibling.nodeType === Node.TEXT_NODE && prevSibling.textContent.trim()) { + break + } + prevSibling = prevSibling.previousSibling + } + } + } else if (offset === 0 && container === editor) { + // 特殊情况:光标在编辑器开头,检查第一个子节点是否是mention + const firstChild = container.firstChild + if (firstChild && firstChild.nodeType === Node.ELEMENT_NODE && + firstChild.classList && firstChild.classList.contains('mention')) { + targetMention = firstChild + } + } + } + } else if (event.key === 'Delete') { + // Delete:查找光标后面的mention元素 + if (container.nodeType === Node.TEXT_NODE) { + // 如果光标在文本节点的末尾,检查后一个兄弟节点 + if (offset === container.textContent.length) { + let nextSibling = container.nextSibling + while (nextSibling) { + if (nextSibling.nodeType === Node.ELEMENT_NODE && + nextSibling.classList && + nextSibling.classList.contains('mention')) { + targetMention = nextSibling + break + } + // 如果是文本节点且不为空,停止查找 + if (nextSibling.nodeType === Node.TEXT_NODE && nextSibling.textContent.trim()) { + break + } + nextSibling = nextSibling.nextSibling + } + } + } else if (container.nodeType === Node.ELEMENT_NODE) { + // 如果光标在元素节点中,检查后一个子节点 + if (offset < container.childNodes.length) { + const nextChild = container.childNodes[offset] + if (nextChild && nextChild.nodeType === Node.ELEMENT_NODE && + nextChild.classList && nextChild.classList.contains('mention')) { + targetMention = nextChild + } + } + } + } + + // 如果找到了要删除的mention元素 + if (targetMention) { + event.preventDefault() + + // 删除mention元素 + targetMention.remove() + + // 触发输入事件更新编辑器内容 + handleInput({ target: editor }) + return + } + } + } + // 处理Ctrl+Enter或Shift+Enter换行 if (event.key === 'Enter' && (event.ctrlKey || event.metaKey || event.shiftKey)) { // 手动插入换行符 @@ -402,7 +516,7 @@ const handleKeydown = (event) => { } } -// 发送消息 - 优化版本,移除不必要的日志,简化逻辑 +// 发送消息 const sendMessage = () => { // 解析编辑器内容 const messageData = parseEditorContent() diff --git a/src/views/message/inner/IndexContent.vue b/src/views/message/inner/IndexContent.vue index 346cb99..4c6a3ed 100644 --- a/src/views/message/inner/IndexContent.vue +++ b/src/views/message/inner/IndexContent.vue @@ -16,8 +16,6 @@ import avatarModule from '@/components/avatar-module/index.vue' const userStore = useUserStore() const dialogueStore = useDialogueStore() const uploadsStore = useUploadsStore() -console.log('dialogueStore', dialogueStore) - const members = computed(() => dialogueStore.members) const membersByAlphabet = computed(() => { if (state.searchMemberByAlphabet) {