From 7fea56f70468b05c214c004ae17cd2a6c8e58a83 Mon Sep 17 00:00:00 2001 From: Phoenix <64720302+Concur-max@users.noreply.github.com> Date: Mon, 9 Jun 2025 11:37:39 +0800 Subject: [PATCH] =?UTF-8?q?refactor(editor):=20=E4=BC=98=E5=8C=96=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E5=99=A8=E8=BE=93=E5=85=A5=E5=A4=84=E7=90=86=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E5=92=8C=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重构输入事件处理函数,减少不必要的DOM操作 - 简化键盘事件处理逻辑,移除冗余日志 - 优化消息发送逻辑,增加内容检查 - 改进引用元素处理,增强交互体验 - 统一表情处理逻辑,使用switch语句替代if-else - 优化草稿保存和加载机制,使用DocumentFragment提高性能 - 清理冗余代码和注释,保持代码简洁 --- src/components/editor/CustomEditor.vue | 746 ++++++++++++------------- src/utils/auth.js | 2 +- 2 files changed, 362 insertions(+), 386 deletions(-) diff --git a/src/components/editor/CustomEditor.vue b/src/components/editor/CustomEditor.vue index 129f8eb..30b43ec 100644 --- a/src/components/editor/CustomEditor.vue +++ b/src/components/editor/CustomEditor.vue @@ -94,7 +94,7 @@ const toolbarConfig = computed(() => { return config }) -// 处理输入事件 +// 处理输入事件 - 优化版本,减少DOM操作 const handleInput = (event) => { const target = event.target @@ -108,24 +108,33 @@ const handleInput = (event) => { let textContent = editorClone.textContent || '' // 将表情图片的 alt 属性添加到文本内容中 - emojiImages.forEach(emoji => { - const altText = emoji.getAttribute('alt') - if (altText) { - textContent += altText - } - }) + if (emojiImages.length > 0) { + emojiImages.forEach(emoji => { + const altText = emoji.getAttribute('alt') + if (altText) { + textContent += altText + } + }) + } - // 更新编辑器内容(包含表情文本) + // 更新逻辑文本内容 editorContent.value = textContent + + // 检查是否需要清空编辑器以显示placeholder + const isEmpty = textContent.trim() === '' && + !target.querySelector('img, .editor-file, .mention') + + if (isEmpty && target.innerHTML !== '') { + target.innerHTML = '' + } + + // 更新HTML内容 editorHtml.value = target.innerHTML || '' - - // 检查@mention + + // 后续操作 checkMention(target) - - // 保存草稿 saveDraft() - - // 发送输入事件 + emit('editor-event', { event: 'input_event', data: editorContent.value @@ -301,7 +310,7 @@ const handlePaste = (event) => { } } -// 处理键盘事件 +// 处理键盘事件 - 优化版本,减少不必要的日志和简化逻辑 const handleKeydown = (event) => { // 处理@提及列表的键盘导航 if (showMention.value) { @@ -313,11 +322,8 @@ const handleKeydown = (event) => { nextTick(() => { const mentionList = document.querySelector('.mention-list ul') const selectedItem = mentionList?.children[selectedMentionIndex.value] - if (mentionList && selectedItem) { - // 如果选中项在可视区域上方,滚动到选中项 - if (selectedItem.offsetTop < mentionList.scrollTop) { - mentionList.scrollTop = selectedItem.offsetTop - } + if (mentionList && selectedItem && selectedItem.offsetTop < mentionList.scrollTop) { + mentionList.scrollTop = selectedItem.offsetTop } }) break @@ -329,7 +335,6 @@ const handleKeydown = (event) => { const mentionList = document.querySelector('.mention-list ul') const selectedItem = mentionList?.children[selectedMentionIndex.value] if (mentionList && selectedItem) { - // 如果选中项在可视区域下方,滚动到选中项 const itemBottom = selectedItem.offsetTop + selectedItem.offsetHeight const listBottom = mentionList.scrollTop + mentionList.clientHeight if (itemBottom > listBottom) { @@ -341,8 +346,9 @@ const handleKeydown = (event) => { case 'Enter': case 'Tab': event.preventDefault() - if (mentionList.value[selectedMentionIndex.value]) { - insertMention(mentionList.value[selectedMentionIndex.value]) + const selectedMember = mentionList.value[selectedMentionIndex.value] + if (selectedMember) { + insertMention(selectedMember) } break case 'Escape': @@ -351,146 +357,104 @@ const handleKeydown = (event) => { } return } - console.log('键盘事件:', event.key, 'Ctrl:', event.ctrlKey, 'Meta:', event.metaKey, 'Shift:', event.shiftKey); // 处理Ctrl+Enter或Shift+Enter换行 if (event.key === 'Enter' && (event.ctrlKey || event.metaKey || event.shiftKey)) { - console.log('Ctrl+Enter或Shift+Enter换行'); - // 不阻止默认行为,允许插入换行符 // 手动插入换行符 - const selection = window.getSelection(); + const selection = window.getSelection() if (selection && selection.rangeCount > 0) { - const range = selection.getRangeAt(0); - const br = document.createElement('br'); - range.deleteContents(); - range.insertNode(br); + const range = selection.getRangeAt(0) + const br = document.createElement('br') + range.deleteContents() + range.insertNode(br) // 在换行符后添加一个空文本节点,并将光标移动到这个节点 - const textNode = document.createTextNode(''); - range.setStartAfter(br); - range.insertNode(textNode); - range.setStartAfter(textNode); - range.collapse(true); - selection.removeAllRanges(); - selection.addRange(range); + const textNode = document.createTextNode('') + range.setStartAfter(br) + range.insertNode(textNode) + range.setStartAfter(textNode) + range.collapse(true) + selection.removeAllRanges() + selection.addRange(range) // 触发输入事件更新编辑器内容 - handleInput({ target: editorRef.value }); + handleInput({ target: editorRef.value }) } // 阻止默认行为,防止触发表单提交 - event.preventDefault(); - return; + event.preventDefault() + return } // 处理Enter键发送消息(只有在没有按Ctrl/Cmd/Shift时才发送) if (event.key === 'Enter' && !event.ctrlKey && !event.metaKey && !event.shiftKey) { - console.log('Enter发送消息'); - event.preventDefault(); - console.log('editorContent.value', editorContent.value); - console.log('editorHtml.value', editorHtml.value); - // 确保编辑器内容不为空(文本、图片、文件或表情) - // 由于我们已经在 handleInput 中处理了表情文本,editorContent.value 应该包含表情文本 - // if (editorContent.value.trim()) { - if (true) { - // 检查引用元素是否存在,如果不存在但 quoteData 有值,则清除 quoteData - const editor = editorRef.value - const quoteElement = editor?.querySelector('.editor-quote') - if (!quoteElement && quoteData.value) { - console.log('引用元素已被删除,但 quoteData 仍有值,清除 quoteData') - quoteData.value = null - } - - // 解析并输出编辑器内容 - const messageData = parseEditorContent() - console.log('编辑器内容解析结果:', JSON.stringify(messageData, null, 2)) - - // 输出详细信息 - console.log('消息项目数量:', messageData.items.length) - console.log('消息项目类型:', messageData.items.map(item => item.type)) - console.log('提及用户IDs:', messageData.mentionUids) - console.log('引用消息ID:', messageData.quoteId) - - // 继续发送消息 - sendMessage() + event.preventDefault() + + // 检查引用元素是否存在,如果不存在但 quoteData 有值,则清除 quoteData + const editor = editorRef.value + const quoteElement = editor?.querySelector('.editor-quote') + if (!quoteElement && quoteData.value) { + quoteData.value = null } + + // 解析编辑器内容并发送消息 + sendMessage() } - - // Ctrl+Enter换行由WangEditor的onKeyDown处理,这里不需要额外处理 } -// 发送消息 +// 发送消息 - 优化版本,移除不必要的日志,简化逻辑 const sendMessage = () => { - console.log('发送消息'); - // 检查编辑器是否有内容:文本内容(包括表情文本) - // if (!editorContent.value.trim()) { - // return - // } - console.log('发送消息1'); + // 解析编辑器内容 const messageData = parseEditorContent() - // 输出完整的消息数据结构 - console.log('完整消息数据:', { - items: messageData.items, - mentionUids: messageData.mentionUids, - quoteId: messageData.quoteId, - quoteData: quoteData.value ? { - id: quoteData.value.id, - title: quoteData.value.title, - describe: quoteData.value.describe, - image: quoteData.value.image - } : null - }) + // 检查是否有内容可发送 + if (messageData.items.length === 0 || + (messageData.items.length === 1 && + messageData.items[0].type === 1 && + !messageData.items[0].content.trim())) { + return // 没有内容,不发送 + } + + // 处理不同类型的消息 messageData.items.forEach(item => { // 处理文本内容 - if (item.type === 1) { - const data={ - items:[{ - content:item.content, - type:1 + if (item.type === 1 && item.content.trim()) { + const data = { + items: [{ + content: item.content, + type: 1 }], - mentionUids:messageData.mentionUids, - mentions:[], - quoteId:messageData.quoteId, + mentionUids: messageData.mentionUids, + mentions: [], + quoteId: messageData.quoteId, } - console.log('发送前',data) - console.log('quoteData',quoteData.value) + emit( 'editor-event', - emitCall('text_event', data,(ok)=>{ - console.log('发送后',ok) - }) + emitCall('text_event', data) ) - }else if(item.type === 2){ - //图片消息 - }else if(item.type === 3){ - console.log('发送图片消息') - const data={ - height:0, - width:0, - size:10000, - url:item.content, - } - emit( - 'editor-event', - emitCall( - 'image_event', - data, - (ok) => { - // 成功发送后清空编辑器 - } - ) - ) - }else if(item.type === 4){ - + } else if (item.type === 3) { // 图片消息 + const data = { + height: 0, + width: 0, + size: 10000, + url: item.content, } - clearEditor() + + emit( + 'editor-event', + emitCall('image_event', data) + ) + } else if (item.type === 4) { // 文件消息 + // 文件消息处理逻辑 + } }) - - + + // 发送后清空编辑器 + clearEditor() } -// 解析编辑器内容 +// 解析编辑器内容 const parseEditorContent = () => { const items = [] const mentionUids = [] @@ -501,102 +465,120 @@ const parseEditorContent = () => { // 检查是否有引用元素 const quoteElements = tempDiv.querySelectorAll('.editor-quote') - let quoteInfo = null - if (quoteElements.length > 0 && quoteData.value) { - quoteInfo = { - id: quoteData.value.id, - title: quoteData.value.title, - describe: quoteData.value.describe, - image: quoteData.value.image + const hasQuote = quoteElements.length > 0 && quoteData.value + const quoteId = hasQuote ? quoteData.value.id || '' : '' + + // 移除引用元素,避免重复处理 + quoteElements.forEach(quote => quote.remove()) + + let textContent = '' + + // 处理文本节点和元素节点 + const processNode = (node) => { + if (node.nodeType === Node.TEXT_NODE) { + // 文本节点直接添加内容 + textContent += node.textContent + return + } + + if (node.nodeType !== Node.ELEMENT_NODE) return + + // 处理不同类型的元素 + if (node.classList.contains('mention')) { + // 处理@提及 + const userId = node.getAttribute('data-user-id') + if (userId) { + mentionUids.push(parseInt(userId)) + } + textContent += node.textContent + } else if (node.tagName === 'IMG') { + // 处理图片 + processImage(node) + } else if (node.classList.contains('emoji')) { + // 处理emoji元素 + textContent += node.getAttribute('alt') || node.textContent + } else if (node.classList.contains('editor-file')) { + // 处理文件 + processFile(node) + } else if (node.childNodes.length) { + // 处理有子节点的元素 + Array.from(node.childNodes).forEach(processNode) + } else { + // 其他元素,添加文本内容 + textContent += node.textContent } } - let textContent = '' - const nodes = Array.from(tempDiv.childNodes) - - nodes.forEach(node => { - if (node.nodeType === Node.TEXT_NODE) { - textContent += node.textContent - } else if (node.nodeType === Node.ELEMENT_NODE) { - // 跳过引用元素的处理,因为我们已经单独处理了 - if (node.classList.contains('editor-quote')) { - return - } - - if (node.classList.contains('mention')) { - const userId = node.getAttribute('data-user-id') - if (userId) { - mentionUids.push(parseInt(userId)) - } - textContent += node.textContent - } else if (node.tagName === 'IMG') { - // 处理图片 - const src = node.getAttribute('src') - const width = node.getAttribute('data-original-width') || node.getAttribute('width') || '' - const height = node.getAttribute('data-original-height') || node.getAttribute('height') || '' - const isEmoji = node.classList.contains('editor-emoji') - - if (textContent.trim()) { - items.push({ - type: 1, - content: textContent.trim() - }) - textContent = '' - } - - if (isEmoji) { - // 处理表情图片 - const altText = node.getAttribute('alt') || '' - if (altText) { - // 如果有alt文本,将表情作为文本处理 - textContent += altText - } else { - // 否则作为图片处理 - items.push({ - type: 3, - content: src + (width && height ? `?width=${width}&height=${height}` : ''), - isEmoji: true - }) - } - } else { - // 处理普通图片 - items.push({ - type: 3, - content: src + (width && height ? `?width=${width}&height=${height}` : ''), - width: width, - height: height - }) - } - } else if (node.classList.contains('emoji')) { - textContent += node.getAttribute('alt') || node.textContent - } else if (node.classList.contains('editor-file')) { - // 处理文件 - const fileUrl = node.getAttribute('data-url') - const fileName = node.getAttribute('data-name') - const fileSize = node.getAttribute('data-size') - - if (textContent.trim()) { - items.push({ - type: 1, - content: textContent.trim() - }) - textContent = '' - } - - if (fileUrl && fileName) { - items.push({ - type: 'file', - content: fileUrl, - name: fileName, - size: node.getAttribute('data-size-raw') || fileSize || 0 - }) - } - } else { - textContent += node.textContent - } + // 处理图片元素 + const processImage = (node) => { + const src = node.getAttribute('src') + const width = node.getAttribute('data-original-width') || node.getAttribute('width') || '' + const height = node.getAttribute('data-original-height') || node.getAttribute('height') || '' + const isEmoji = node.classList.contains('editor-emoji') + + // 如果有累积的文本内容,先添加到items + if (textContent.trim()) { + items.push({ + type: 1, + content: textContent.trim() + }) + textContent = '' } - }) + + if (isEmoji) { + // 处理表情图片 + const altText = node.getAttribute('alt') || '' + if (altText) { + // 如果有alt文本,将表情作为文本处理 + textContent += altText + } else { + // 否则作为图片处理 + items.push({ + type: 3, + content: src + (width && height ? `?width=${width}&height=${height}` : ''), + isEmoji: true + }) + } + } else { + // 处理普通图片 + items.push({ + type: 3, + content: src + (width && height ? `?width=${width}&height=${height}` : ''), + width: width, + height: height + }) + } + } + // 处理文件元素 + const processFile = (node) => { + const fileUrl = node.getAttribute('data-url') + const fileName = node.getAttribute('data-name') + const fileSize = node.getAttribute('data-size') + + // 如果有累积的文本内容,先添加到items + if (textContent.trim()) { + items.push({ + type: 1, + content: textContent.trim() + }) + textContent = '' + } + + if (fileUrl && fileName) { + items.push({ + type: 4, // 使用数字类型保持一致性 + content: fileUrl, + name: fileName, + size: node.getAttribute('data-size-raw') || fileSize || 0 + }) + } + } + + // 处理所有顶级节点 + Array.from(tempDiv.childNodes).forEach(processNode) + + // 处理剩余的文本内容 if (textContent.trim()) { items.push({ type: 1, @@ -605,30 +587,28 @@ const parseEditorContent = () => { } // 构建完整的消息数据结构 - const result = { + return { items: items.length > 0 ? items : [{ type: 1, content: '' }], mentionUids, - quoteId: quoteElements.length > 0 && quoteData.value ? quoteData.value.id ||'' : '' + quoteId } - - // 如果有引用信息,添加到结果中 - if (quoteInfo) { - result.quoteInfo = quoteInfo - } - - return result } -// 清空编辑器 +// 清空编辑器 const clearEditor = () => { + // 一次性清空所有编辑器相关状态 editorContent.value = '' editorHtml.value = '' + quoteData.value = null + + // 清空DOM内容 if (editorRef.value) { editorRef.value.innerHTML = '' + // 立即设置焦点,提高响应速度 + nextTick(() => editorRef.value.focus()) } - // 清除引用数据 - quoteData.value = null + // 隐藏@提及列表 hideMentionList() // 清空草稿 @@ -655,8 +635,8 @@ const insertImage = (src, width, height) => { img.src = src img.className = 'editor-image' img.alt = '图片' - img.style.maxHeight = '200px' - img.style.maxWidth = '100%' + img.style.maxHeight = '150px' + img.style.maxWidth = '150px' img.style.objectFit = 'contain' // 保持原始比例 // 存储原始尺寸信息,但不直接设置宽高属性 @@ -712,30 +692,25 @@ const onUploadSendImg=async (eventFile)=>{ } } async function onUploadFile(e) { - let file = e.target.files[0] - - e.target.value = null // 清空input,允许再次选择相同文件 - - console.log("文件类型"+file.type) + const file = e.target.files[0] + if (!file) return + + // 清空input,允许再次选择相同文件 + e.target.value = null + if (file.type.indexOf('image/') === 0) { - console.log("进入图片") // 处理图片文件 - 立即显示临时消息,然后上传 - let fn = emitCall('image_event', file, () => {}) - emit('editor-event', fn) + emit('editor-event', emitCall('image_event', file)) return } if (file.type.indexOf('video/') === 0) { - console.log("进入视频") // 处理视频文件 - let fn = emitCall('video_event', file, () => {}) - emit('editor-event', fn) + emit('editor-event', emitCall('video_event', file)) } else { - console.log("进入其他") // 处理其他类型文件 - let fn = emitCall('file_event', file, () => {}) - emit('editor-event', fn) + emit('editor-event', emitCall('file_event', file)) } } @@ -745,29 +720,29 @@ const onEmoticonEvent = (emoji) => { emoticonRef.value?.setShow(false) // 处理不同类型的表情 - if (emoji.type === 'text') { - // 文本表情 - insertTextEmoji(emoji.value) - } else if (emoji.type === 'image') { - // 图片表情 - insertImageEmoji(emoji.img, emoji.value) - } else if (emoji.type === 'emoji') { - // 系统表情 - insertTextEmoji(emoji.value) - } else if (emoji.type === 1) { - // 兼容旧版表情格式 - if (emoji.img) { - insertImageEmoji(emoji.img, emoji.value) - } else { + switch (emoji.type) { + case 'text': + case 'emoji': + // 文本表情和系统表情都使用相同的处理方式 insertTextEmoji(emoji.value) - } - } else { - // 发送整个表情包 - emit('editor-event', { - event: 'emoticon_event', - data: emoji.value || emoji.id, - callBack: () => {} - }) + break + + case 'image': + // 图片表情 + insertImageEmoji(emoji.img, emoji.value) + break + + case 1: // 兼容旧版表情格式 + emoji.img ? insertImageEmoji(emoji.img, emoji.value) : insertTextEmoji(emoji.value) + break + + default: + // 发送整个表情包 + emit('editor-event', { + event: 'emoticon_event', + data: emoji.value || emoji.id + }) + break } } @@ -892,28 +867,23 @@ const onSubscribeQuote = (data) => { // 使用事件委托处理引用元素的所有点击事件 quoteElement.addEventListener('click', (e) => { - console.log('执行删除',e) // 检查点击的是否是关闭按钮或其内部元素 const closeButton = e.target.classList?.contains('quote-close') ? e.target : e.target.closest('.quote-close') + + // 阻止事件冒泡,避免触发编辑器的其他点击事件 + e.stopPropagation() + if (closeButton) { - // 阻止事件冒泡 - e.stopPropagation() - - // 移除引用元素 + // 移除引用元素并清除引用数据 quoteElement.remove() - - // 清除引用数据 quoteData.value = null - // 不触发handleInput,避免保存草稿 - // 只更新编辑器内容变量 + // 只更新编辑器内容变量,不触发handleInput,避免保存草稿 editorContent.value = editor.textContent || '' editorHtml.value = editor.innerHTML || '' - // 确保编辑器获得焦点 - setTimeout(() => { - editor.focus() - }, 0) + // 使用nextTick确保DOM更新后再设置焦点 + nextTick(() => editor.focus()) } else { // 如果不是点击关闭按钮,则设置光标到引用卡片后面 const selection = window.getSelection() @@ -923,7 +893,7 @@ const onSubscribeQuote = (data) => { selection.removeAllRanges() selection.addRange(range) - // 确保编辑器获得焦点 + // 立即设置焦点 editor.focus() } }) @@ -935,11 +905,11 @@ const onSubscribeQuote = (data) => { // 这里只需要处理非关闭按钮的点击 // 注意:由于事件委托的方式,不需要额外添加点击事件监听器 - // 监听键盘事件,处理删除操作 - // 监听键盘事件,处理删除操作 + // 监听键盘事件,处理引用元素的删除操作 const handleDeleteQuote = function(e) { - // 检查是否是删除键(Backspace 或 Delete) - if (e.key === 'Backspace' || e.key === 'Delete') { + // 只处理删除键(Backspace 或 Delete) + if (e.key !== 'Backspace' && e.key !== 'Delete') return; + const selection = window.getSelection(); if (selection.rangeCount === 0) return; @@ -952,25 +922,32 @@ const onSubscribeQuote = (data) => { return; } + // 获取引用元素在子节点中的索引 + const quoteIndex = Array.from(editor.childNodes).indexOf(quoteElement); + // 检查光标是否在引用卡片前面(Backspace)或后面(Delete) const isBeforeQuote = e.key === 'Backspace' && range.collapsed && range.startContainer === editor && - Array.from(editor.childNodes).indexOf(quoteElement) === range.startOffset; + quoteIndex === range.startOffset; const isAfterQuote = e.key === 'Delete' && range.collapsed && range.startContainer === editor && - Array.from(editor.childNodes).indexOf(quoteElement) === range.startOffset - 1; + quoteIndex === range.startOffset - 1; if (isBeforeQuote || isAfterQuote) { + // 阻止默认删除行为 + e.preventDefault(); + + // 移除引用元素并清除引用数据 quoteElement.remove(); quoteData.value = null; + + // 更新编辑器内容 handleInput({ target: editor }); - e.preventDefault(); } - } -}; + }; editor.addEventListener('keydown', handleDeleteQuote); @@ -1062,38 +1039,34 @@ const onSubscribeClear = () => { // 保存草稿 const saveDraft = () => { - if (!indexName.value) return + if (!indexName.value || !editorRef.value) return // 获取编辑器内容,但排除引用元素 - let contentToSave = '' - let htmlToSave = '' + // 使用DocumentFragment进行高效的DOM操作 + const fragment = document.createDocumentFragment() + const tempDiv = document.createElement('div') + tempDiv.innerHTML = editorRef.value.innerHTML + fragment.appendChild(tempDiv) - if (editorRef.value) { - // 临时保存引用元素 - const quoteElements = [] - const editorQuotes = editorRef.value.querySelectorAll('.editor-quote') - - // 克隆编辑器内容 - const clonedEditor = editorRef.value.cloneNode(true) - - // 从克隆的编辑器中移除引用元素 - const clonedQuotes = clonedEditor.querySelectorAll('.editor-quote') - clonedQuotes.forEach(quote => quote.remove()) - - // 获取不包含引用的内容 - contentToSave = clonedEditor.textContent || '' - htmlToSave = clonedEditor.innerHTML || '' - } + // 从临时DOM中移除引用元素 + const quoteElements = tempDiv.querySelectorAll('.editor-quote') + quoteElements.forEach(quote => quote.remove()) + + // 获取不包含引用的内容 + const contentToSave = tempDiv.textContent || '' + const htmlToSave = tempDiv.innerHTML || '' // 检查是否有实际内容(不包括引用) - const hasContent = contentToSave.trim() || htmlToSave.includes(' 0 || + htmlToSave.includes(' { } } -// 加载草稿 +// 加载草稿 const loadDraft = () => { if (!indexName.value) return - // 延迟处理,确保DOM已渲染 - setTimeout(() => { + // 使用nextTick确保DOM已渲染,更可预测且性能更好 + nextTick(() => { // 保存当前引用数据的临时副本 const currentQuoteData = quoteData.value // 清除当前引用数据,避免重复添加 quoteData.value = null + // 如果编辑器引用不存在,直接返回 + if (!editorRef.value) return + + // 先清空编辑器内容 + editorRef.value.innerHTML = '' + editorContent.value = '' + editorHtml.value = '' + + // 获取草稿数据 const draft = editorDraftStore.items[indexName.value] + + // 如果有草稿,恢复草稿内容 if (draft) { try { const draftData = JSON.parse(draft) - // 恢复编辑器内容(不包含引用) - if (editorRef.value) { - // 先清空编辑器内容,包括引用元素 - editorRef.value.innerHTML = '' - - // 恢复草稿内容 - editorRef.value.innerHTML = draftData.html || '' - editorContent.value = draftData.content || '' - editorHtml.value = draftData.html || '' - - // 如果有引用数据,重新添加到编辑器 - if (currentQuoteData) { - // 重新调用引用函数添加引用元素 - onSubscribeQuote(currentQuoteData) - } - } + // 恢复草稿内容 + editorRef.value.innerHTML = draftData.html || '' + editorContent.value = draftData.content || '' + editorHtml.value = draftData.html || '' } catch (error) { - console.error('加载草稿失败:', error) - } - } else { - // 没有草稿则清空编辑器内容,但保留引用 - if (editorRef.value) { - // 清空编辑器 - editorRef.value.innerHTML = '' - editorContent.value = '' - editorHtml.value = '' - - // 如果有引用数据,重新添加到编辑器 - if (currentQuoteData) { - onSubscribeQuote(currentQuoteData) - } + console.warn('加载草稿失败,使用空内容', error) } } - }, 0) + + // 如果有引用数据,重新添加到编辑器(无论是否有草稿) + if (currentQuoteData) { + onSubscribeQuote(currentQuoteData) + } + }) } // 监听会话变化,加载对应草稿 watch(indexName, loadDraft, { immediate: true }) +// 处理点击文档事件,隐藏@提及列表 +const handleDocumentClick = (event) => { + if (!editorRef.value?.contains(event.target)) { + hideMentionList() + } +} + // 组件挂载 onMounted(() => { - bus.subscribe(EditorConst.Mention, onSubscribeMention) - bus.subscribe(EditorConst.Quote, onSubscribeQuote) - bus.subscribe(EditorConst.Edit, onSubscribeEdit) - bus.subscribe(EditorConst.Clear, onSubscribeClear) - - // 为编辑器添加点击事件监听器,用于处理引用消息关闭等 - if (editorRef.value) { - editorRef.value.addEventListener('click', handleEditorClick); - } + // 订阅所有编辑器相关事件 + const subscriptions = [ + [EditorConst.Mention, onSubscribeMention], + [EditorConst.Quote, onSubscribeQuote], + [EditorConst.Edit, onSubscribeEdit], + [EditorConst.Clear, onSubscribeClear] + ] - // 点击外部隐藏mention - document.addEventListener('click', (event) => { - if (!editorRef.value?.contains(event.target)) { - hideMentionList() - } + // 批量订阅事件 + subscriptions.forEach(([event, handler]) => { + bus.subscribe(event, handler) }) + + // 为编辑器添加点击事件监听器 + editorRef.value?.addEventListener('click', handleEditorClick) + + // 点击外部隐藏mention - 使用命名函数便于清理 + document.addEventListener('click', handleDocumentClick) // 初始加载草稿 loadDraft() @@ -1182,24 +1155,27 @@ onMounted(() => { /** * 组件生命周期钩子 - 组件卸载前 - * - * onBeforeUnmount是Vue 3的生命周期钩子,在组件卸载前执行 - * 在这里用于清理事件订阅,防止内存泄漏 - * 使用bus.unsubscribe取消订阅之前通过bus.subscribe注册的事件处理函数 + * 清理所有事件订阅和监听器,防止内存泄漏 */ onBeforeUnmount(() => { - // 取消订阅所有编辑器相关事件,防止内存泄漏和事件监听器残留 - bus.unsubscribe(EditorConst.Mention, onSubscribeMention) - bus.unsubscribe(EditorConst.Quote, onSubscribeQuote) - bus.unsubscribe(EditorConst.Edit, onSubscribeEdit) - bus.unsubscribe(EditorConst.Clear, onSubscribeClear) + // 取消订阅所有编辑器相关事件 + const subscriptions = [ + [EditorConst.Mention, onSubscribeMention], + [EditorConst.Quote, onSubscribeQuote], + [EditorConst.Edit, onSubscribeEdit], + [EditorConst.Clear, onSubscribeClear] + ] + + // 批量取消订阅 + subscriptions.forEach(([event, handler]) => { + bus.unsubscribe(event, handler) + }) // 移除编辑器点击事件监听器 - if (editorRef.value) { - editorRef.value.removeEventListener('click', handleEditorClick); - } + editorRef.value?.removeEventListener('click', handleEditorClick) - // 清理DOM事件监听器 + // 移除文档点击事件监听器 + document.removeEventListener('click', handleDocumentClick) const editor = editorRef.value if (editor && handleDeleteQuote) { editor.removeEventListener('keydown', handleDeleteQuote) @@ -1729,16 +1705,16 @@ const handleEditorClick = (event) => { * 限制编辑器中插入的图片大小 * 添加圆角和鼠标指针样式 */ - .editor-image { - max-width: 300px; - max-height: 200px; - border-radius: 3px; - background-color: #48484d; - margin: 0px 2px; - cursor: pointer; - object-fit: contain; /* 保持原始比例 */ - display: inline-block; /* 确保图片正确显示 */ - } + // .editor-image { + // max-width: 300px; + // max-height: 200px; + // border-radius: 3px; + // background-color: #48484d; + // margin: 0px 2px; + // cursor: pointer; + // object-fit: contain; /* 保持原始比例 */ + // display: inline-block; /* 确保图片正确显示 */ + // } /** * 表情样式 diff --git a/src/utils/auth.js b/src/utils/auth.js index 0383a0a..2ec121f 100644 --- a/src/utils/auth.js +++ b/src/utils/auth.js @@ -18,7 +18,7 @@ export function isLoggedIn() { */ export function getAccessToken() { // return storage.get(AccessToken) || '' - return JSON.parse(localStorage.getItem('token'))||'46d71a72d8d845ad7ed23eba9bdde260e635407190c2ce1bf7fd22088e41682ea07773ec65cae8946d2003f264d55961f96e0fc5da10eb96d3a348c1664e9644ce2108c311309f398ae8ea1b8200bfd490e5cb6e8c52c9e5d493cbabb163368f8351420451a631dbfa749829ee4cda49b77b5ed2d3dced5d0f2b7dd9ee76ba5465c84a17c23af040cd92b6b2a4ea48befbb5c729dcdad0a9c9668befe84074cc24f78899c1d947f8e7f94c7eda5325b8ed698df729e76febb98549ef3482ae942fb4f4a1c92d21836fa784728f0c5483aab2760a991b6b36e6b10c84f840a6433a6ecc31dee36e8f1c6158818bc89d22403363066ad3c046839f7b2cf8a6186da017388f197c0c3b219b1c04e7d986e9774b72664a22a6075cee77da3584b7a2131365913796a5fcabc8f4594284e480a592a84a40a9aa7f5f27c951a53a369c' + return JSON.parse(localStorage.getItem('token'))||'46d71a72d8d845ad7ed23eba9bdde260e635407190c2ce1bf7fd22088e41682ea07773ec65cae8946d2003f264d55961f96e0fc5da10eb96d3a348c1664e9644ce2108c311309f398ae8ea1b8200bfd490e5cb6e8c52c9e5d493cbabb163368f8351420451a631dbfa749829ee4cda49b77b5ed2d3dced5d0f2b7dd9ee76ba5465c84a17c23af040cd92b6b2a4ea48befbb5c729dcdad0a9c9668befe84074cc24f78899c1d947f8e7f94c7eda5325b8ed698df729e76febb98549ef3482ae942fb4f4a1c92d21836fa784728f0c5483aab2760a991b6b36e6b10c84f840a6433a6ecc31dee36e8f1c6158818bc89d22c9c2f9b60a57573e8b08cdf47105e1ba85550c21fa55526e8a00bf316c623eb67abf749622c48beab908d61d3db7b22ed3eb6aa8a08c77680ad4d8a3458c1e72f97ba2b8480674df77f0501a34e82b58' } /**