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