From c89056d7f1f881a3ca610b53a13495d9ea182896 Mon Sep 17 00:00:00 2001 From: Phoenix <64720302+Concur-max@users.noreply.github.com> Date: Thu, 5 Jun 2025 16:21:39 +0800 Subject: [PATCH 1/9] edit --- src/components/editor/CustomEditor.vue | 1670 +++++++++++++++++ src/views/message/inner/panel/PanelFooter.vue | 4 +- vite.config.ts | 6 +- 3 files changed, 1676 insertions(+), 4 deletions(-) create mode 100644 src/components/editor/CustomEditor.vue diff --git a/src/components/editor/CustomEditor.vue b/src/components/editor/CustomEditor.vue new file mode 100644 index 0000000..27ddf87 --- /dev/null +++ b/src/components/editor/CustomEditor.vue @@ -0,0 +1,1670 @@ + + + + + \ No newline at end of file diff --git a/src/views/message/inner/panel/PanelFooter.vue b/src/views/message/inner/panel/PanelFooter.vue index dc5f8ad..b80e130 100644 --- a/src/views/message/inner/panel/PanelFooter.vue +++ b/src/views/message/inner/panel/PanelFooter.vue @@ -10,6 +10,7 @@ import { } from '@/store' import ws from '@/connect' import { ServePublishMessage, ServeSendVote } from '@/api/chat' +import CustomEditor from '@/components/editor/CustomEditor.vue' import { throttle, getVideoImage } from '@/utils/common' import { parseTime } from '@/utils/datetime' import Editor from '@/components/editor/Editor.vue' @@ -342,7 +343,8 @@ onMounted(() => { { vueJsx({}), compressPlugin(), UnoCSS(), - vueDevTools({ - launchEditor: 'trae', - }) + // vueDevTools({ + // launchEditor: 'trae', + // }) ], define: { __APP_ENV__: env.APP_ENV From f279248a51639a39e7f4b64a6410191f3d1f0d4c Mon Sep 17 00:00:00 2001 From: Phoenix <64720302+Concur-max@users.noreply.github.com> Date: Fri, 6 Jun 2025 10:44:17 +0800 Subject: [PATCH 2/9] =?UTF-8?q?feat(=E7=BC=96=E8=BE=91=E5=99=A8):=20?= =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E7=BC=96=E8=BE=91=E5=99=A8=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=B9=B6=E4=BC=98=E5=8C=96=E5=9B=BE=E7=89=87=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加对粘贴图片的支持,自动触发上传流程 - 优化图片插入逻辑,保留原始尺寸信息并改进显示效果 - 重构消息内容解析逻辑,完善数据结构 - 移除冗余的文件插入功能,专注于图片处理优化 - 调整编辑器样式,改进图片显示效果 --- src/components/editor/CustomEditor.vue | 202 +++++++++++++++++-------- src/utils/auth.js | 2 +- 2 files changed, 143 insertions(+), 61 deletions(-) diff --git a/src/components/editor/CustomEditor.vue b/src/components/editor/CustomEditor.vue index 27ddf87..1672b92 100644 --- a/src/components/editor/CustomEditor.vue +++ b/src/components/editor/CustomEditor.vue @@ -240,6 +240,22 @@ const insertMention = (member) => { const handlePaste = (event) => { event.preventDefault() + // 检查是否有图片 + const items = event.clipboardData?.items + if (items) { + for (let i = 0; i < items.length; i++) { + if (items[i].type.indexOf('image') !== -1) { + // 获取粘贴的图片文件 + const file = items[i].getAsFile() + if (file) { + // 使用现有的上传图片功能处理 + onUploadFile([file]) + return + } + } + } + } + // 获取粘贴的纯文本内容 const text = event.clipboardData?.getData('text/plain') || '' @@ -295,10 +311,19 @@ const handleKeydown = (event) => { console.log('editorContent.value', editorContent.value) console.log('editorHtml.value', editorHtml.value) // 确保编辑器内容不为空(文本、图片、文件或表情) - if (editorContent.value.trim() || - editorHtml.value.includes(' item.type)) + console.log('提及用户IDs:', messageData.mentionUids) + console.log('引用消息ID:', messageData.quoteId) + + // 继续发送消息 sendMessage() } } @@ -309,16 +334,26 @@ const handleKeydown = (event) => { // 发送消息 const sendMessage = () => { console.log('发送消息'); - // 检查编辑器是否有内容:文本、图片、文件或表情 - if (!editorContent.value.trim() && - !editorHtml.value.includes(' { const tempDiv = document.createElement('div') tempDiv.innerHTML = editorHtml.value + // 检查是否有引用元素 + const quoteElements = tempDiv.querySelectorAll('.editor-quote') + let quoteInfo = null + if (quoteElements.length > 0 && quoteData.value) { + quoteInfo = { + msg_id: quoteData.value.msg_id, + title: quoteData.value.title, + describe: quoteData.value.describe, + image: quoteData.value.image + } + } + let textContent = '' const nodes = Array.from(tempDiv.childNodes) @@ -368,6 +415,11 @@ const parseEditorContent = () => { 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) { @@ -377,8 +429,8 @@ const parseEditorContent = () => { } else if (node.tagName === 'IMG') { // 处理图片 const src = node.getAttribute('src') - const width = node.getAttribute('width') || '' - const height = node.getAttribute('height') || '' + 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()) { @@ -407,7 +459,9 @@ const parseEditorContent = () => { // 处理普通图片 items.push({ type: 3, - content: src + (width && height ? `?width=${width}&height=${height}` : '') + content: src + (width && height ? `?width=${width}&height=${height}` : ''), + width: width, + height: height }) } } else if (node.classList.contains('emoji')) { @@ -447,11 +501,19 @@ const parseEditorContent = () => { }) } - return { + // 构建完整的消息数据结构 + const result = { items: items.length > 0 ? items : [{ type: 1, content: '' }], mentionUids, quoteId: quoteData.value?.msg_id || 0 } + + // 如果有引用信息,添加到结果中 + if (quoteInfo) { + result.quoteInfo = quoteInfo + } + + return result } // 清空编辑器 @@ -479,17 +541,24 @@ const clearEditor = () => { // 插入图片 -const insertImage = (url, width, height) => { +const insertImage = (src, width, height) => { const selection = window.getSelection() if (!selection.rangeCount) return const range = selection.getRangeAt(0) + + // 创建图片元素 const img = document.createElement('img') - img.src = url - img.style.maxWidth = '200px' + img.src = src + img.className = 'editor-image' + img.alt = '图片' img.style.maxHeight = '200px' - if (width) img.setAttribute('width', width) - if (height) img.setAttribute('height', height) + img.style.maxWidth = '100%' + img.style.objectFit = 'contain' // 保持原始比例 + + // 存储原始尺寸信息,但不直接设置宽高属性 + if (width) img.setAttribute('data-original-width', width) + if (height) img.setAttribute('data-original-height', height) range.deleteContents() range.insertNode(img) @@ -502,35 +571,6 @@ const insertImage = (url, width, height) => { handleInput({ target: editorRef.value }) } -// 插入文件 -const insertFile = (url, fileName, fileSize) => { - const selection = window.getSelection() - if (!selection.rangeCount) return - - const range = selection.getRangeAt(0) - - // 创建文件链接元素 - const fileLink = document.createElement('a') - fileLink.href = url - fileLink.target = '_blank' - fileLink.className = 'editor-file' - fileLink.textContent = fileName - fileLink.setAttribute('data-size', formatFileSize(fileSize)) - fileLink.setAttribute('data-url', url) // 添加URL属性用于解析 - fileLink.setAttribute('data-name', fileName) // 添加文件名属性用于解析 - fileLink.setAttribute('data-size-raw', fileSize) // 添加原始文件大小属性用于解析 - - range.deleteContents() - range.insertNode(fileLink) - range.setStartAfter(fileLink) - range.collapse(true) - selection.removeAllRanges() - selection.addRange(range) - - editorRef.value.focus() - handleInput({ target: editorRef.value }) -} - // 格式化文件大小 const formatFileSize = (size) => { if (size < 1024) { @@ -547,21 +587,61 @@ const formatFileSize = (size) => { /** * 文件上传处理 - * @param e 上传事件对象 + * @param e 上传事件对象或文件数组 */ - async function onUploadFile(e) { - let file = e.target.files[0] - - e.target.value = null // 清空input,允许再次选择相同文件 - +// 文件上传处理 +async function onUploadFile(e) { + let files; + + // 判断参数类型 + if (Array.isArray(e)) { + // 直接传入的文件数组 + files = e; + } else { + // 传入的是事件对象 + files = e.target.files; + e.target.value = null; // 清空input,允许再次选择相同文件 + } + + // 确保有文件 + if (!files || files.length === 0) return; + + // 处理第一个文件 + const file = files[0]; + console.log("文件类型"+file.type) if (file.type.indexOf('image/') === 0) { console.log("进入图片") - // 处理图片文件 - 立即显示临时消息,然后上传 - let fn = emitCall('image_event', file, () => {}) - emit('editor-event', fn) - - return + // 创建临时URL + const tempUrl = URL.createObjectURL(file); + + // 创建图片对象以获取尺寸 + const image = new Image(); + image.src = tempUrl; + + image.onload = () => { + // 上传图片到服务器 + const form = new FormData(); + form.append('file', file); + form.append("source", "fonchain-chat"); // 图片来源标识 + form.append("urlParam", `width=${image.width}&height=${image.height}`); + + // 先将临时图片插入编辑器,不直接设置宽高,而是传递原始尺寸信息 + insertImage(tempUrl, image.width, image.height); + + // 上传图片并获取永久URL + uploadImg(form).then(({ code, data, message }) => { + if (code == 0) { + // 上传成功后,可以将临时URL替换为永久URL + // 但这里我们不做替换,因为临时URL在当前会话中已足够使用 + console.log('图片上传成功:', data.ori_url); + } else { + window['$message'].error(message); + } + }); + }; + + return; } if (file.type.indexOf('video/') === 0) { @@ -1536,12 +1616,14 @@ const onVoteSubmit = (data) => { * 添加圆角和鼠标指针样式 */ .editor-image { - max-width: 100px; - max-height: 100px; + 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 f4f176c..0383a0a 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'))||'46d71a72d8d845ad7ed23eba9bdde260e635407190c2ce1bf7fd22088e41682ea07773ec65cae8946d2003f264d55961f96e0fc5da10eb96d3a348c1664e9644ce2108c311309f398ae8ea1b8200bfd490e5cb6e8c52c9e5d493cbabb163368f8351420451a631dbfa749829ee4cda49b77b5ed2d3dced5d0f2b7dd9ee76ba5465c84a17c23af040cd92b6b2a4ea48befbb5c729dcdad0a9c9668befe84074cc24f78899c1d947f8e7f94c7eda5325b8ed698df729e76febb98549ef3482ae942fb4f4a1c92d21836fa784728f0c5483aab2760a991b6b36e6b10c84f840a6433a6ecc31dee36e8f1c6158818bc89d22726726265e9af0db370a54ea5ee002b43662d571b84c8468ac15330f79503a5cd5e72282d8bee92749b1a3c1b7fd87ae70b64b90e437e84c1b558c64a35e181b2ecf5db3007680c3607eac1edee7f59d' + return JSON.parse(localStorage.getItem('token'))||'46d71a72d8d845ad7ed23eba9bdde260e635407190c2ce1bf7fd22088e41682ea07773ec65cae8946d2003f264d55961f96e0fc5da10eb96d3a348c1664e9644ce2108c311309f398ae8ea1b8200bfd490e5cb6e8c52c9e5d493cbabb163368f8351420451a631dbfa749829ee4cda49b77b5ed2d3dced5d0f2b7dd9ee76ba5465c84a17c23af040cd92b6b2a4ea48befbb5c729dcdad0a9c9668befe84074cc24f78899c1d947f8e7f94c7eda5325b8ed698df729e76febb98549ef3482ae942fb4f4a1c92d21836fa784728f0c5483aab2760a991b6b36e6b10c84f840a6433a6ecc31dee36e8f1c6158818bc89d22403363066ad3c046839f7b2cf8a6186da017388f197c0c3b219b1c04e7d986e9774b72664a22a6075cee77da3584b7a2131365913796a5fcabc8f4594284e480a592a84a40a9aa7f5f27c951a53a369c' } /** From 17c136834675da6da785a1ea44ca3127862f0dc2 Mon Sep 17 00:00:00 2001 From: Phoenix <64720302+Concur-max@users.noreply.github.com> Date: Fri, 6 Jun 2025 11:52:55 +0800 Subject: [PATCH 3/9] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E5=8F=91=E9=80=81=E9=80=BB=E8=BE=91=E5=92=8C=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E5=99=A8=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在vite配置中启用vueDevTools工具 - 重构PanelFooter.vue中的图片消息发送逻辑,改为直接调用onSendMessage - 修改CustomEditor.vue的消息发送逻辑,支持分类型处理消息内容 - 增加编辑器引用元素的检查逻辑,避免无效引用 - 优化图片上传后的URL替换逻辑,确保编辑器内容更新 --- src/components/editor/CustomEditor.vue | 104 ++++++++++++------ src/views/message/inner/panel/PanelFooter.vue | 62 +---------- vite.config.ts | 6 +- 3 files changed, 76 insertions(+), 96 deletions(-) diff --git a/src/components/editor/CustomEditor.vue b/src/components/editor/CustomEditor.vue index 1672b92..73b0ff4 100644 --- a/src/components/editor/CustomEditor.vue +++ b/src/components/editor/CustomEditor.vue @@ -312,7 +312,16 @@ const handleKeydown = (event) => { console.log('editorHtml.value', editorHtml.value) // 确保编辑器内容不为空(文本、图片、文件或表情) // 由于我们已经在 handleInput 中处理了表情文本,editorContent.value 应该包含表情文本 - if (editorContent.value.trim()) { + // 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)) @@ -335,9 +344,9 @@ const handleKeydown = (event) => { const sendMessage = () => { console.log('发送消息'); // 检查编辑器是否有内容:文本内容(包括表情文本) - if (!editorContent.value.trim()) { - return - } + // if (!editorContent.value.trim()) { + // return + // } console.log('发送消息1'); const messageData = parseEditorContent() @@ -353,38 +362,51 @@ const sendMessage = () => { image: quoteData.value.image } : null }) - - if (editingMessage.value) { - // 编辑消息 - emit('editor-event', { - event: 'edit_message', - data: { - ...messageData, - msg_id: editingMessage.value.msg_id - }, - callBack: (success) => { - if (success) { - clearEditor() - editingMessage.value = null - } + messageData.items.forEach(item => { + // 处理文本内容 + if (item.type === 1) { + const data={ + items:[{ + content:item.content, + type:1 + }], + mentionUids:messageData.mentionUids, + mentions:[], + quoteId:messageData.quoteId, } - }) - } else { - // 发送新消息 - const eventType = messageData.items.length > 1 ? 'mixed_event' : - messageData.items[0].type === 1 ? 'text_event' : - messageData.items[0].type + '_event' - console.log('发送消息2',eventType); - emit('editor-event', { - event: eventType, - data: messageData, - callBack: (success) => { - if (success) { - clearEditor() + console.log('发送前',data) + emit( + 'editor-event', + emitCall('text_event', data,(ok)=>{ + console.log('发送后',ok) + }) + ) + }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){ + } - }) - } + }) + + } // 解析编辑器内容 @@ -505,7 +527,7 @@ const parseEditorContent = () => { const result = { items: items.length > 0 ? items : [{ type: 1, content: '' }], mentionUids, - quoteId: quoteData.value?.msg_id || 0 + quoteId: quoteElements.length > 0 && quoteData.value ? quoteData.value.msg_id ||'' : '' } // 如果有引用信息,添加到结果中 @@ -632,9 +654,19 @@ async function onUploadFile(e) { // 上传图片并获取永久URL uploadImg(form).then(({ code, data, message }) => { if (code == 0) { - // 上传成功后,可以将临时URL替换为永久URL - // 但这里我们不做替换,因为临时URL在当前会话中已足够使用 + // 上传成功后,将临时URL替换为永久URL console.log('图片上传成功:', data.ori_url); + + // 查找编辑器中刚插入的图片元素并替换其src为永久URL + const editorImages = editorRef.value.querySelectorAll('img.editor-image'); + // 查找最后插入的图片(通常是最近添加的那个) + const lastImage = editorImages[editorImages.length - 1]; + if (lastImage && lastImage.src === tempUrl) { + // 替换为永久URL + lastImage.src = data.ori_url; + // 触发输入事件更新编辑器内容 + handleInput({ target: editorRef.value }); + } } else { window['$message'].error(message); } diff --git a/src/views/message/inner/panel/PanelFooter.vue b/src/views/message/inner/panel/PanelFooter.vue index b80e130..bea5429 100644 --- a/src/views/message/inner/panel/PanelFooter.vue +++ b/src/views/message/inner/panel/PanelFooter.vue @@ -10,13 +10,13 @@ import { } from '@/store' import ws from '@/connect' import { ServePublishMessage, ServeSendVote } from '@/api/chat' -import CustomEditor from '@/components/editor/CustomEditor.vue' import { throttle, getVideoImage } from '@/utils/common' import { parseTime } from '@/utils/datetime' import Editor from '@/components/editor/Editor.vue' import MultiSelectFooter from './MultiSelectFooter.vue' import HistoryRecord from '@/components/talk/HistoryRecord.vue' import {scrollToBottom} from '@/utils/dom.ts' +import CustomEditor from '@/components/editor/CustomEditor.vue' const userStore = useUserStore() const talkStore = useTalkStore() const editorStore = useEditorStore() @@ -61,11 +61,11 @@ const onSendMessage = (data = {}, callBack: any) => { } ServePublishMessage(message) - .then(({ code, message }) => { + .then(({ code, message, msg }) => { if (code == 200) { callBack(true) } else { - window['$message'].warning(message) + window['$message'].warning(message || msg) } }) .catch(() => { @@ -95,60 +95,8 @@ const onSendTextEvent = throttle((value: any) => { }, 1000) // 发送图片消息 -const onSendImageEvent = ({ data }) => { - console.log('onSendImageEvent') - - // 先创建一个带有上传ID的临时消息对象,用于显示进度 - const uploadId = `image-${Date.now()}-${Math.floor(Math.random() * 1000)}` - - // 创建本地预览URL - const previewUrl = URL.createObjectURL(data) - - // 创建临时消息记录 - const tempMessage = { - msg_id: uploadId, - sequence: Date.now(), - talk_type: props.talk_type, - msg_type: 3, // 图片消息类型 - user_id: props.uid, - receiver_id: props.receiver_id, - is_revoke: 0, - is_mark: 0, - is_read: 1, - content: '', - created_at: parseTime(new Date(), '{y}-{m}-{d} {h}:{i}'), - extra: { - url: previewUrl, // 使用本地预览URL - size: data.size, - is_uploading: true, - upload_id: uploadId, - percentage: 0 - }, - isCheck: false, - send_status: 1, - float: 'right' // 我发送的消息显示在右侧 - } - - // 直接添加到对话记录中 - dialogueStore.addDialogueRecord(tempMessage) - nextTick(()=>{ - scrollToBottom() - }) - uploadsStore.initUploadFile( - data, - props.talk_type, - props.receiver_id, - uploadId, - async (percentage) => { - dialogueStore.updateUploadProgress(uploadId, percentage) - }, - async () => { - // 清理本地预览URL - URL.revokeObjectURL(previewUrl) - dialogueStore.batchDelDialogueRecord([uploadId]) - - } - ) +const onSendImageEvent = ({ data, callBack }) => { + onSendMessage({ type: 'image', ...data }, callBack) } // 发送视频消息 diff --git a/vite.config.ts b/vite.config.ts index 715bc77..83a792e 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -46,9 +46,9 @@ export default defineConfig(({ mode }) => { vueJsx({}), compressPlugin(), UnoCSS(), - // vueDevTools({ - // launchEditor: 'trae', - // }) + vueDevTools({ + launchEditor: 'trae', + }) ], define: { __APP_ENV__: env.APP_ENV From b18a6e5432330bd604812e5c8071a93a39711b97 Mon Sep 17 00:00:00 2001 From: Phoenix <64720302+Concur-max@users.noreply.github.com> Date: Fri, 6 Jun 2025 12:00:12 +0800 Subject: [PATCH 4/9] =?UTF-8?q?feat(=E7=BC=96=E8=BE=91=E5=99=A8):=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B8=85=E9=99=A4=E4=BA=8B=E4=BB=B6=E5=B8=B8?= =?UTF-8?q?=E9=87=8F=E5=B9=B6=E4=BC=98=E5=8C=96=E6=8F=90=E5=8F=8A=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在事件总线常量中添加 editor:clear 事件类型 优化提及功能,确保编辑器获得焦点后光标位置正确 --- src/components/editor/CustomEditor.vue | 14 ++++++++++++++ src/constant/event-bus.ts | 3 ++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/components/editor/CustomEditor.vue b/src/components/editor/CustomEditor.vue index 73b0ff4..754f90f 100644 --- a/src/components/editor/CustomEditor.vue +++ b/src/components/editor/CustomEditor.vue @@ -783,6 +783,20 @@ const onSubscribeMention = (data) => { // 确保编辑器获得焦点 editorRef.value?.focus() + // 如果编辑器为空或者光标不在编辑器内,将光标移动到编辑器末尾 + const selection = window.getSelection() + if (!selection.rangeCount || !editorRef.value.contains(selection.anchorNode)) { + const range = document.createRange() + if (editorRef.value.lastChild) { + range.setStartAfter(editorRef.value.lastChild) + } else { + range.setStart(editorRef.value, 0) + } + range.collapse(true) + selection.removeAllRanges() + selection.addRange(range) + } + // 插入@提及 insertMention(data) } diff --git a/src/constant/event-bus.ts b/src/constant/event-bus.ts index a437264..3abbb99 100644 --- a/src/constant/event-bus.ts +++ b/src/constant/event-bus.ts @@ -5,5 +5,6 @@ export const enum ContactConst { export const enum EditorConst { Mention = 'editor:mention', Quote = 'editor:quote', - Edit = 'editor:edit' + Edit = 'editor:edit', + Clear = 'editor:clear' } From 1ff26564c781e8125cdab2cf981bcc60de4dedab Mon Sep 17 00:00:00 2001 From: Phoenix <64720302+Concur-max@users.noreply.github.com> Date: Fri, 6 Jun 2025 13:43:39 +0800 Subject: [PATCH 5/9] =?UTF-8?q?refactor(editor):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=BC=95=E7=94=A8=E6=B6=88=E6=81=AF=E7=9A=84=E7=82=B9=E5=87=BB?= =?UTF-8?q?=E4=BA=8B=E4=BB=B6=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 使用事件委托统一处理引用消息的点击事件,包括关闭按钮点击和光标定位 移除重复的事件监听器,简化代码结构 修复引用消息ID字段从msg_id改为id的匹配问题 --- src/components/editor/CustomEditor.vue | 62 ++++++++++++++++++++------ 1 file changed, 48 insertions(+), 14 deletions(-) diff --git a/src/components/editor/CustomEditor.vue b/src/components/editor/CustomEditor.vue index 754f90f..335f632 100644 --- a/src/components/editor/CustomEditor.vue +++ b/src/components/editor/CustomEditor.vue @@ -375,6 +375,7 @@ const sendMessage = () => { quoteId:messageData.quoteId, } console.log('发送前',data) + console.log('quoteData',quoteData.value) emit( 'editor-event', emitCall('text_event', data,(ok)=>{ @@ -527,7 +528,7 @@ const parseEditorContent = () => { const result = { items: items.length > 0 ? items : [{ type: 1, content: '' }], mentionUids, - quoteId: quoteElements.length > 0 && quoteData.value ? quoteData.value.msg_id ||'' : '' + quoteId: quoteElements.length > 0 && quoteData.value ? quoteData.value.id ||'' : '' } // 如果有引用信息,添加到结果中 @@ -840,10 +841,15 @@ const onSubscribeQuote = (data) => { editor.appendChild(quoteElement) } - // 添加关闭按钮点击事件 - const closeBtn = quoteElement.querySelector('.quote-close') - if (closeBtn) { - closeBtn.addEventListener('click', () => { + // 使用事件委托处理引用元素的所有点击事件 + quoteElement.addEventListener('click', (e) => { + console.log('执行删除',e) + // 检查点击的是否是关闭按钮或其内部元素 + const closeButton = e.target.classList?.contains('quote-close') ? e.target : e.target.closest('.quote-close') + if (closeButton) { + // 阻止事件冒泡 + e.stopPropagation() + // 移除引用元素 quoteElement.remove() @@ -859,15 +865,8 @@ const onSubscribeQuote = (data) => { setTimeout(() => { editor.focus() }, 0) - }) - } - - // 注意:不调用saveDraft(),确保引用内容不会被保存到草稿中 - - // 添加点击整个引用卡片的事件 - quoteElement.addEventListener('click', (e) => { - // 如果不是点击关闭按钮,则设置光标到引用卡片后面 - if (!e.target.classList.contains('quote-close')) { + } else { + // 如果不是点击关闭按钮,则设置光标到引用卡片后面 const selection = window.getSelection() const range = document.createRange() range.setStartAfter(quoteElement) @@ -880,6 +879,13 @@ const onSubscribeQuote = (data) => { } }) + // 注意:不调用saveDraft(),确保引用内容不会被保存到草稿中 + + // 在同一个事件监听器中处理引用卡片的点击 + // 已经在上面的事件处理中添加了关闭按钮的处理逻辑 + // 这里只需要处理非关闭按钮的点击 + // 注意:由于事件委托的方式,不需要额外添加点击事件监听器 + // 监听键盘事件,处理删除操作 // 监听键盘事件,处理删除操作 const handleDeleteQuote = function(e) { @@ -1108,6 +1114,11 @@ onMounted(() => { bus.subscribe(EditorConst.Quote, onSubscribeQuote) bus.subscribe(EditorConst.Edit, onSubscribeEdit) bus.subscribe(EditorConst.Clear, onSubscribeClear) + + // 为编辑器添加点击事件监听器,用于处理引用消息关闭等 + if (editorRef.value) { + editorRef.value.addEventListener('click', handleEditorClick); + } // 点击外部隐藏mention document.addEventListener('click', (event) => { @@ -1133,6 +1144,11 @@ onBeforeUnmount(() => { bus.unsubscribe(EditorConst.Quote, onSubscribeQuote) bus.unsubscribe(EditorConst.Edit, onSubscribeEdit) bus.unsubscribe(EditorConst.Clear, onSubscribeClear) + + // 移除编辑器点击事件监听器 + if (editorRef.value) { + editorRef.value.removeEventListener('click', handleEditorClick); + } // 清理DOM事件监听器 const editor = editorRef.value @@ -1191,6 +1207,24 @@ const onVoteSubmit = (data) => { }) isShowVote.value = false } + +// 处理编辑器内部点击事件(用于关闭引用等) +const handleEditorClick = (event) => { + const closeButton = event.target.closest('.quote-close'); + + if (closeButton) { + const quoteElement = event.target.closest('.editor-quote'); + if (quoteElement) { + quoteElement.remove(); + quoteData.value = null; + + handleInput({ target: editorRef.value }); + + event.preventDefault(); + event.stopPropagation(); + } + } +};