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'
}
/**