From 51014991cfeffe696c21522e7d22129570ef2082 Mon Sep 17 00:00:00 2001 From: yuantao Date: Wed, 15 Oct 2025 18:34:53 +0800 Subject: [PATCH] =?UTF-8?q?=20=20-=20=E7=A7=BB=E9=99=A4=E4=BA=86vue-dragga?= =?UTF-8?q?ble-plus=E4=BE=9D=E8=B5=96=EF=BC=8C=E6=94=B9=E7=94=A8=E5=8E=9F?= =?UTF-8?q?=E7=94=9FHTML5=E6=8B=96=E6=8B=BDAPI=20=20=20-=20=E5=A2=9E?= =?UTF-8?q?=E5=BC=BA=E4=BA=86=E5=9B=BE=E7=89=87=E6=8B=96=E6=8B=BD=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E7=9A=84=E8=B0=83=E8=AF=95=E4=BF=A1=E6=81=AF=EF=BC=8C?= =?UTF-8?q?=E4=BE=BF=E4=BA=8E=E9=97=AE=E9=A2=98=E6=8E=92=E6=9F=A5=20=20=20?= =?UTF-8?q?-=20=E4=BC=98=E5=8C=96=E4=BA=86=E5=9B=BE=E7=89=87=E6=8F=92?= =?UTF-8?q?=E5=85=A5=E5=92=8C=E6=8B=96=E6=8B=BD=E5=A4=84=E7=90=86=E9=80=BB?= =?UTF-8?q?=E8=BE=91=20=20=20-=20=E4=BF=AE=E5=A4=8D=E4=BA=86=E6=8B=96?= =?UTF-8?q?=E6=8B=BD=E8=BF=87=E7=A8=8B=E4=B8=AD=E5=9B=BE=E7=89=87=E5=92=8C?= =?UTF-8?q?=E6=8B=96=E6=8B=BD=E6=89=8B=E6=9F=84=E7=9A=84=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/RichTextEditor.vue | 260 ++++++++++++++++++++++++------ 1 file changed, 215 insertions(+), 45 deletions(-) diff --git a/src/components/RichTextEditor.vue b/src/components/RichTextEditor.vue index 23074df..47c30b3 100644 --- a/src/components/RichTextEditor.vue +++ b/src/components/RichTextEditor.vue @@ -32,9 +32,11 @@ const initialViewportHeight = ref(0) // 初始化编辑器内容 onMounted(() => { - console.log('RichTextEditor mounted, initial content:', props.modelValue) + console.log('Editor mounted') if (editorRef.value) { + console.log('Editor ref available') if (props.modelValue) { + console.log('Setting initial content') try { editorRef.value.innerHTML = props.modelValue content.value = props.modelValue @@ -53,16 +55,34 @@ onMounted(() => { // 记录初始视口高度 initialViewportHeight.value = window.visualViewport?.height || window.innerHeight + console.log('Initial viewport height:', initialViewportHeight.value) // 初始化CSS变量 document.documentElement.style.setProperty('--viewport-height', `${initialViewportHeight.value}px`) + console.log('Set viewport height CSS variable') // 添加虚拟键盘检测事件监听器 if (window.visualViewport) { + console.log('Adding viewport resize listener') window.visualViewport.addEventListener('resize', handleViewportResize) } else { + console.log('Adding window resize listener') window.addEventListener('resize', handleWindowResize) } + + // 为已有图片添加拖拽事件监听器 + setTimeout(() => { + console.log('Adding drag event listeners to existing images') + const imageElements = editorRef.value.querySelectorAll('img.editor-image') + console.log('Found existing images:', imageElements.length) + imageElements.forEach(img => { + console.log('Adding drag listeners to image:', img) + img.addEventListener('dragstart', handleImageDragStart) + img.addEventListener('dragover', handleImageDragOver) + img.addEventListener('drop', handleImageDrop) + img.addEventListener('dragend', handleImageDragEnd) + }) + }, 0) }) // 组件卸载时移除事件监听器 @@ -579,66 +599,143 @@ const insertTodoList = () => { // 处理图片拖拽开始 const handleImageDragStart = (e) => { + console.log('Drag start:', e.target) + console.log('Drag start event:', e) e.dataTransfer.effectAllowed = 'move' e.target.classList.add('dragging') + // Store the dragged element's index or identifier + e.dataTransfer.setData('text/plain', e.target.src) + console.log('Set drag data:', e.target.src) } // 处理图片拖拽经过 const handleImageDragOver = (e) => { + console.log('Drag over event:', e) e.preventDefault() e.dataTransfer.dropEffect = 'move' + // Add visual feedback for drop target const target = e.target - const draggingElement = document.querySelector('img.editor-image.dragging') - - if (draggingElement && target !== draggingElement && target.classList.contains('editor-image')) { - const rect = target.getBoundingClientRect() - const midpoint = rect.top + rect.height / 2 - - if (e.clientY < midpoint) { - // 在目标元素上方插入 - target.parentNode.insertBefore(draggingElement, target) - // 同时移动拖拽手柄 - const targetHandle = target.nextElementSibling - const draggingHandle = draggingElement.nextElementSibling - if (targetHandle && targetHandle.classList.contains('image-drag-handle') && - draggingHandle && draggingHandle.classList.contains('image-drag-handle')) { - target.parentNode.insertBefore(draggingHandle, targetHandle) - } - } else { - // 在目标元素下方插入 - target.parentNode.insertBefore(draggingElement, target.nextSibling) - // 同时移动拖拽手柄 - const targetHandle = target.nextElementSibling - const draggingHandle = draggingElement.nextElementSibling - if (targetHandle && targetHandle.classList.contains('image-drag-handle') && - draggingHandle && draggingHandle.classList.contains('image-drag-handle')) { - target.parentNode.insertBefore(draggingHandle, targetHandle.nextSibling) - } - } + console.log('Drag over target:', target) + if (target.classList && target.classList.contains('editor-image') && !target.classList.contains('dragging')) { + // Add a visual indicator for where the image will be dropped + target.style.boxShadow = '0 0 0 2px var(--primary)' + console.log('Added visual feedback to target') } } // 处理图片拖拽释放 const handleImageDrop = (e) => { + console.log('Drop event:', e) e.preventDefault() - const draggingElement = document.querySelector('img.editor-image.dragging') - if (draggingElement) { - draggingElement.classList.remove('dragging') - handleInput() // 触发内容更新 + + // Get the dragged image source + const draggedImageSrc = e.dataTransfer.getData('text/plain') + console.log('Dragged image source:', draggedImageSrc) + + // Get the drop target + const target = e.target + console.log('Drop target:', target) + + // Check if we're dropping on an image + if (target.classList && target.classList.contains('editor-image')) { + console.log('Dropping on an image') + // Reset all image styles + const images = editorRef.value.querySelectorAll('.editor-image') + images.forEach(img => { + img.style.boxShadow = '0 1px 5px rgba(0, 0, 0, 0.18)' + }) + + // Find the dragged image element + const draggedImage = Array.from(images).find(img => img.src === draggedImageSrc) + console.log('Dragged image element:', draggedImage) + + if (draggedImage && draggedImage !== target) { + console.log('Valid drag operation, moving image') + // Get the parent element + const parent = target.parentNode + console.log('Parent element:', parent) + + // Create a clone of the dragged image + const clonedImage = draggedImage.cloneNode(true) + console.log('Cloned image:', clonedImage) + + // Copy over event listeners by re-adding them + clonedImage.draggable = true + clonedImage.setAttribute('data-draggable', 'true') + clonedImage.addEventListener('dragstart', handleImageDragStart) + clonedImage.addEventListener('dragover', handleImageDragOver) + clonedImage.addEventListener('drop', handleImageDrop) + clonedImage.addEventListener('dragend', handleImageDragEnd) + + // Also clone the drag handle if it exists + const originalDragHandle = draggedImage.nextElementSibling + let clonedDragHandle = null + if (originalDragHandle && originalDragHandle.classList.contains('image-drag-handle')) { + console.log('Cloning drag handle') + clonedDragHandle = originalDragHandle.cloneNode(true) + // Add event listeners to the cloned drag handle + clonedDragHandle.addEventListener('mouseover', function() { + clonedDragHandle.classList.add('visible') + clonedDragHandle.style.display = 'block' + }) + clonedDragHandle.addEventListener('mouseout', function(e) { + setTimeout(() => { + if (!clonedImage.matches(':hover') && document.activeElement !== clonedImage) { + clonedDragHandle.classList.remove('visible') + clonedDragHandle.style.display = 'none' + } + }, 100) + }) + } + + // Insert the cloned image before the target + parent.insertBefore(clonedImage, target) + console.log('Inserted cloned image') + + // Insert the drag handle if it exists + if (clonedDragHandle) { + parent.insertBefore(clonedDragHandle, clonedImage.nextSibling) + console.log('Inserted cloned drag handle') + } + + // Remove the original dragged image and its drag handle + if (originalDragHandle && originalDragHandle.classList.contains('image-drag-handle')) { + originalDragHandle.remove() + console.log('Removed original drag handle') + } + draggedImage.remove() + console.log('Removed original image') + + // Update content + handleInput() + console.log('Updated content after drag') + } else { + console.log('Invalid drag operation') + } + } else { + console.log('Not dropping on an image') } } // 处理图片拖拽结束 -const handleImageDragEnd = (e) => { - const draggingElement = document.querySelector('img.editor-image.dragging') - if (draggingElement) { - draggingElement.classList.remove('dragging') - } +const handleImageDragEnd = (evt) => { + console.log('Drag end:', evt) + + // Reset styles of all images + const images = editorRef.value.querySelectorAll('.editor-image') + images.forEach(img => { + img.style.boxShadow = '0 1px 5px rgba(0, 0, 0, 0.18)' + img.classList.remove('dragging') + }) + + // Update content after drag operation + handleInput() } // 插入图片 const insertImage = () => { + console.log('Inserting image') // 创建文件输入元素 const fileInput = document.createElement('input') fileInput.type = 'file' @@ -650,23 +747,29 @@ const insertImage = () => { // 监听文件选择事件 fileInput.addEventListener('change', function (event) { + console.log('File selected:', event.target.files) const file = event.target.files[0] if (file && file.type.startsWith('image/')) { + console.log('Image file selected') // 创建FileReader读取文件 const reader = new FileReader() reader.onload = function (e) { + console.log('File read successfully') // 获取图片数据URL const imageDataUrl = e.target.result + console.log('Image data URL:', imageDataUrl) // 获取当前选区 const selection = window.getSelection() if (selection.rangeCount > 0) { const range = selection.getRangeAt(0) + console.log('Current range:', range) // 创建图片元素 const img = document.createElement('img') img.src = imageDataUrl img.className = 'editor-image' + img.setAttribute('data-draggable', 'true') img.style.maxWidth = '100%' img.style.height = 'auto' img.style.display = 'block' @@ -678,10 +781,14 @@ const insertImage = () => { img.style.background = 'var(--background-secondary)' img.style.position = 'relative' img.style.outline = 'none' // 移除默认焦点轮廓 + img.draggable = true + + console.log('Created image element:', img) // 创建一个临时图片来获取原始尺寸 const tempImg = new Image() tempImg.onload = function () { + console.log('Temp image loaded') // 获取CSS变量 const editorFontSize = getComputedStyle(document.documentElement).getPropertyValue('--editor-font-size').trim() || '16px' const editorLineHeight = getComputedStyle(document.documentElement).getPropertyValue('--editor-line-height').trim() || '1.6' @@ -735,11 +842,13 @@ const insertImage = () => { // 将拖拽手柄添加到图片后面(使用insertAdjacentElement) img.insertAdjacentElement('afterend', dragHandle) + console.log('Added drag handle to image') } tempImg.src = imageDataUrl // 添加事件监听器来显示/隐藏拖拽手柄 img.addEventListener('focus', function() { + console.log('Image focused') const dragHandle = img.nextElementSibling if (dragHandle && dragHandle.classList.contains('image-drag-handle')) { dragHandle.classList.add('visible') @@ -748,6 +857,7 @@ const insertImage = () => { }) img.addEventListener('blur', function() { + console.log('Image blurred') const dragHandle = img.nextElementSibling if (dragHandle && dragHandle.classList.contains('image-drag-handle')) { // 延迟隐藏,确保用户有时间将鼠标移到手柄上 @@ -761,6 +871,7 @@ const insertImage = () => { }) img.addEventListener('mouseover', function() { + console.log('Mouse over image') const dragHandle = img.nextElementSibling if (dragHandle && dragHandle.classList.contains('image-drag-handle')) { dragHandle.classList.add('visible') @@ -769,6 +880,7 @@ const insertImage = () => { }) img.addEventListener('mouseout', function(e) { + console.log('Mouse out of image') const dragHandle = img.nextElementSibling if (dragHandle && dragHandle.classList.contains('image-drag-handle')) { // 延迟隐藏,确保用户有时间将鼠标移到手柄上 @@ -785,6 +897,7 @@ const insertImage = () => { setTimeout(() => { const dragHandle = img.nextElementSibling if (dragHandle && dragHandle.classList.contains('image-drag-handle')) { + console.log('Adding event listeners to drag handle') dragHandle.addEventListener('mouseover', function() { dragHandle.classList.add('visible') dragHandle.style.display = 'block' // 直接设置显示样式 @@ -804,13 +917,12 @@ const insertImage = () => { // 添加拖拽功能 img.draggable = true - img.addEventListener('dragstart', handleImageDragStart) - img.addEventListener('dragover', handleImageDragOver) - img.addEventListener('drop', handleImageDrop) - img.addEventListener('dragend', handleImageDragEnd) + img.setAttribute('data-draggable', 'true') + console.log('Set draggable attributes') // 插入图片到当前光标位置 range.insertNode(img) + console.log('Inserted image into editor') // 调试信息 console.log('Image inserted:', img) @@ -819,13 +931,16 @@ const insertImage = () => { // 添加换行 const br = document.createElement('br') img.parentNode.insertBefore(br, img.nextSibling) + console.log('Added line break after image') // 触发输入事件更新内容 handleInput() + console.log('Handled input event') // 重新聚焦到编辑器 if (editorRef.value) { editorRef.value.focus() + console.log('Focused editor') } } } @@ -834,10 +949,12 @@ const insertImage = () => { // 清理文件输入元素 document.body.removeChild(fileInput) + console.log('Removed file input') }) // 触发文件选择对话框 fileInput.click() + console.log('Clicked file input') } // 处理键盘事件 @@ -1063,13 +1180,17 @@ const handleToolbarFocusOut = () => { // 调整已有图片的高度 const adjustExistingImages = () => { + console.log('Adjusting existing images') // 等待DOM更新完成 setTimeout(() => { if (editorRef.value) { - const images = editorRef.value.querySelectorAll('img.editor-image') - images.forEach(img => { + const imageElements = editorRef.value.querySelectorAll('img.editor-image') + console.log('Found image elements:', imageElements.length) + imageElements.forEach(img => { + console.log('Processing image:', img) // 只处理还没有调整过高度的图片 if (!img.dataset.heightAdjusted) { + console.log('Adjusting height for image') // 创建一个临时图片来获取原始尺寸 const tempImg = new Image() tempImg.onload = function () { @@ -1106,27 +1227,34 @@ const adjustExistingImages = () => { // 标记图片已调整过高度 img.dataset.heightAdjusted = 'true' + console.log('Adjusted image dimensions:', adjustedWidth, adjustedHeight) } tempImg.src = img.src } }) // 为现有图片添加拖拽功能 - images.forEach(img => { + imageElements.forEach(img => { + console.log('Adding drag functionality to image:', img) // 确保图片有拖拽属性 if (!img.hasAttribute('draggable')) { + console.log('Setting draggable attribute') img.draggable = true + img.setAttribute('data-draggable', 'true') // 添加拖拽事件监听器 img.addEventListener('dragstart', handleImageDragStart) img.addEventListener('dragover', handleImageDragOver) img.addEventListener('drop', handleImageDrop) img.addEventListener('dragend', handleImageDragEnd) + console.log('Added drag event listeners') } // 检查是否已存在拖拽手柄 let existingHandle = img.nextElementSibling + console.log('Existing handle:', existingHandle) if (!existingHandle || !existingHandle.classList.contains('image-drag-handle')) { + console.log('Creating drag handle') // 创建拖拽手柄 const dragHandle = document.createElement('div') dragHandle.className = 'image-drag-handle' @@ -1149,12 +1277,14 @@ const adjustExistingImages = () => { // 将拖拽手柄添加到图片后面 img.insertAdjacentElement('afterend', dragHandle) + console.log('Added drag handle') } // 确保拖拽手柄存在后再添加事件监听器 setTimeout(() => { const dragHandle = img.nextElementSibling if (dragHandle && dragHandle.classList.contains('image-drag-handle')) { + console.log('Adding event listeners to drag handle') // 添加事件监听器来显示/隐藏拖拽手柄 img.addEventListener('focus', function() { dragHandle.classList.add('visible') @@ -1211,7 +1341,7 @@ const adjustExistingImages = () => { defineExpose({ getContent: () => content.value, setContent: newContent => { - console.log('Setting content in editor:', newContent) + console.log('Setting content:', newContent) content.value = newContent || '' if (editorRef.value) { try { @@ -1219,6 +1349,26 @@ defineExpose({ console.log('Content set successfully in editorRef') // 调整已有图片的高度并添加拖拽功能 adjustExistingImages() + // 为图片添加拖拽事件监听器 + setTimeout(() => { + console.log('Adding drag event listeners to images in setContent') + const imageElements = editorRef.value.querySelectorAll('img.editor-image') + console.log('Found images in setContent:', imageElements.length) + imageElements.forEach(img => { + console.log('Adding drag listeners to image in setContent:', img) + // 先移除可能已有的事件监听器,避免重复 + img.removeEventListener('dragstart', handleImageDragStart) + img.removeEventListener('dragover', handleImageDragOver) + img.removeEventListener('drop', handleImageDrop) + img.removeEventListener('dragend', handleImageDragEnd) + + // 重新添加事件监听器 + img.addEventListener('dragstart', handleImageDragStart) + img.addEventListener('dragover', handleImageDragOver) + img.addEventListener('drop', handleImageDrop) + img.addEventListener('dragend', handleImageDragEnd) + }) + }, 0) } catch (error) { console.error('Failed to set innerHTML:', error) // 备选方案:使用textContent @@ -1239,6 +1389,26 @@ defineExpose({ console.log('Content set successfully after delay') // 调整已有图片的高度并添加拖拽功能 adjustExistingImages() + // 为图片添加拖拽事件监听器 + setTimeout(() => { + console.log('Adding drag event listeners to images in delayed setContent') + const imageElements = editorRef.value.querySelectorAll('img.editor-image') + console.log('Found images in delayed setContent:', imageElements.length) + imageElements.forEach(img => { + console.log('Adding drag listeners to image in delayed setContent:', img) + // 先移除可能已有的事件监听器,避免重复 + img.removeEventListener('dragstart', handleImageDragStart) + img.removeEventListener('dragover', handleImageDragOver) + img.removeEventListener('drop', handleImageDrop) + img.removeEventListener('dragend', handleImageDragEnd) + + // 重新添加事件监听器 + img.addEventListener('dragstart', handleImageDragStart) + img.addEventListener('dragover', handleImageDragOver) + img.addEventListener('drop', handleImageDrop) + img.addEventListener('dragend', handleImageDragEnd) + }) + }, 0) } catch (error) { console.error('Failed to set innerHTML after delay:', error) }