- 移除了vue-draggable-plus依赖,改用原生HTML5拖拽API

- 增强了图片拖拽功能的调试信息,便于问题排查
  - 优化了图片插入和拖拽处理逻辑
  - 修复了拖拽过程中图片和拖拽手柄的同步问题
This commit is contained in:
yuantao
2025-10-15 18:34:53 +08:00
parent 2131906f3b
commit 51014991cf

View File

@@ -32,9 +32,11 @@ const initialViewportHeight = ref(0)
// 初始化编辑器内容 // 初始化编辑器内容
onMounted(() => { onMounted(() => {
console.log('RichTextEditor mounted, initial content:', props.modelValue) console.log('Editor mounted')
if (editorRef.value) { if (editorRef.value) {
console.log('Editor ref available')
if (props.modelValue) { if (props.modelValue) {
console.log('Setting initial content')
try { try {
editorRef.value.innerHTML = props.modelValue editorRef.value.innerHTML = props.modelValue
content.value = props.modelValue content.value = props.modelValue
@@ -53,16 +55,34 @@ onMounted(() => {
// 记录初始视口高度 // 记录初始视口高度
initialViewportHeight.value = window.visualViewport?.height || window.innerHeight initialViewportHeight.value = window.visualViewport?.height || window.innerHeight
console.log('Initial viewport height:', initialViewportHeight.value)
// 初始化CSS变量 // 初始化CSS变量
document.documentElement.style.setProperty('--viewport-height', `${initialViewportHeight.value}px`) document.documentElement.style.setProperty('--viewport-height', `${initialViewportHeight.value}px`)
console.log('Set viewport height CSS variable')
// 添加虚拟键盘检测事件监听器 // 添加虚拟键盘检测事件监听器
if (window.visualViewport) { if (window.visualViewport) {
console.log('Adding viewport resize listener')
window.visualViewport.addEventListener('resize', handleViewportResize) window.visualViewport.addEventListener('resize', handleViewportResize)
} else { } else {
console.log('Adding window resize listener')
window.addEventListener('resize', handleWindowResize) 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) => { const handleImageDragStart = (e) => {
console.log('Drag start:', e.target)
console.log('Drag start event:', e)
e.dataTransfer.effectAllowed = 'move' e.dataTransfer.effectAllowed = 'move'
e.target.classList.add('dragging') 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) => { const handleImageDragOver = (e) => {
console.log('Drag over event:', e)
e.preventDefault() e.preventDefault()
e.dataTransfer.dropEffect = 'move' e.dataTransfer.dropEffect = 'move'
// Add visual feedback for drop target
const target = e.target const target = e.target
const draggingElement = document.querySelector('img.editor-image.dragging') console.log('Drag over target:', target)
if (target.classList && target.classList.contains('editor-image') && !target.classList.contains('dragging')) {
if (draggingElement && target !== draggingElement && target.classList.contains('editor-image')) { // Add a visual indicator for where the image will be dropped
const rect = target.getBoundingClientRect() target.style.boxShadow = '0 0 0 2px var(--primary)'
const midpoint = rect.top + rect.height / 2 console.log('Added visual feedback to target')
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)
}
}
} }
} }
// 处理图片拖拽释放 // 处理图片拖拽释放
const handleImageDrop = (e) => { const handleImageDrop = (e) => {
console.log('Drop event:', e)
e.preventDefault() e.preventDefault()
const draggingElement = document.querySelector('img.editor-image.dragging')
if (draggingElement) { // Get the dragged image source
draggingElement.classList.remove('dragging') const draggedImageSrc = e.dataTransfer.getData('text/plain')
handleInput() // 触发内容更新 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 handleImageDragEnd = (evt) => {
const draggingElement = document.querySelector('img.editor-image.dragging') console.log('Drag end:', evt)
if (draggingElement) {
draggingElement.classList.remove('dragging') // 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 = () => { const insertImage = () => {
console.log('Inserting image')
// 创建文件输入元素 // 创建文件输入元素
const fileInput = document.createElement('input') const fileInput = document.createElement('input')
fileInput.type = 'file' fileInput.type = 'file'
@@ -650,23 +747,29 @@ const insertImage = () => {
// 监听文件选择事件 // 监听文件选择事件
fileInput.addEventListener('change', function (event) { fileInput.addEventListener('change', function (event) {
console.log('File selected:', event.target.files)
const file = event.target.files[0] const file = event.target.files[0]
if (file && file.type.startsWith('image/')) { if (file && file.type.startsWith('image/')) {
console.log('Image file selected')
// 创建FileReader读取文件 // 创建FileReader读取文件
const reader = new FileReader() const reader = new FileReader()
reader.onload = function (e) { reader.onload = function (e) {
console.log('File read successfully')
// 获取图片数据URL // 获取图片数据URL
const imageDataUrl = e.target.result const imageDataUrl = e.target.result
console.log('Image data URL:', imageDataUrl)
// 获取当前选区 // 获取当前选区
const selection = window.getSelection() const selection = window.getSelection()
if (selection.rangeCount > 0) { if (selection.rangeCount > 0) {
const range = selection.getRangeAt(0) const range = selection.getRangeAt(0)
console.log('Current range:', range)
// 创建图片元素 // 创建图片元素
const img = document.createElement('img') const img = document.createElement('img')
img.src = imageDataUrl img.src = imageDataUrl
img.className = 'editor-image' img.className = 'editor-image'
img.setAttribute('data-draggable', 'true')
img.style.maxWidth = '100%' img.style.maxWidth = '100%'
img.style.height = 'auto' img.style.height = 'auto'
img.style.display = 'block' img.style.display = 'block'
@@ -678,10 +781,14 @@ const insertImage = () => {
img.style.background = 'var(--background-secondary)' img.style.background = 'var(--background-secondary)'
img.style.position = 'relative' img.style.position = 'relative'
img.style.outline = 'none' // 移除默认焦点轮廓 img.style.outline = 'none' // 移除默认焦点轮廓
img.draggable = true
console.log('Created image element:', img)
// 创建一个临时图片来获取原始尺寸 // 创建一个临时图片来获取原始尺寸
const tempImg = new Image() const tempImg = new Image()
tempImg.onload = function () { tempImg.onload = function () {
console.log('Temp image loaded')
// 获取CSS变量 // 获取CSS变量
const editorFontSize = getComputedStyle(document.documentElement).getPropertyValue('--editor-font-size').trim() || '16px' const editorFontSize = getComputedStyle(document.documentElement).getPropertyValue('--editor-font-size').trim() || '16px'
const editorLineHeight = getComputedStyle(document.documentElement).getPropertyValue('--editor-line-height').trim() || '1.6' const editorLineHeight = getComputedStyle(document.documentElement).getPropertyValue('--editor-line-height').trim() || '1.6'
@@ -735,11 +842,13 @@ const insertImage = () => {
// 将拖拽手柄添加到图片后面使用insertAdjacentElement // 将拖拽手柄添加到图片后面使用insertAdjacentElement
img.insertAdjacentElement('afterend', dragHandle) img.insertAdjacentElement('afterend', dragHandle)
console.log('Added drag handle to image')
} }
tempImg.src = imageDataUrl tempImg.src = imageDataUrl
// 添加事件监听器来显示/隐藏拖拽手柄 // 添加事件监听器来显示/隐藏拖拽手柄
img.addEventListener('focus', function() { img.addEventListener('focus', function() {
console.log('Image focused')
const dragHandle = img.nextElementSibling const dragHandle = img.nextElementSibling
if (dragHandle && dragHandle.classList.contains('image-drag-handle')) { if (dragHandle && dragHandle.classList.contains('image-drag-handle')) {
dragHandle.classList.add('visible') dragHandle.classList.add('visible')
@@ -748,6 +857,7 @@ const insertImage = () => {
}) })
img.addEventListener('blur', function() { img.addEventListener('blur', function() {
console.log('Image blurred')
const dragHandle = img.nextElementSibling const dragHandle = img.nextElementSibling
if (dragHandle && dragHandle.classList.contains('image-drag-handle')) { if (dragHandle && dragHandle.classList.contains('image-drag-handle')) {
// 延迟隐藏,确保用户有时间将鼠标移到手柄上 // 延迟隐藏,确保用户有时间将鼠标移到手柄上
@@ -761,6 +871,7 @@ const insertImage = () => {
}) })
img.addEventListener('mouseover', function() { img.addEventListener('mouseover', function() {
console.log('Mouse over image')
const dragHandle = img.nextElementSibling const dragHandle = img.nextElementSibling
if (dragHandle && dragHandle.classList.contains('image-drag-handle')) { if (dragHandle && dragHandle.classList.contains('image-drag-handle')) {
dragHandle.classList.add('visible') dragHandle.classList.add('visible')
@@ -769,6 +880,7 @@ const insertImage = () => {
}) })
img.addEventListener('mouseout', function(e) { img.addEventListener('mouseout', function(e) {
console.log('Mouse out of image')
const dragHandle = img.nextElementSibling const dragHandle = img.nextElementSibling
if (dragHandle && dragHandle.classList.contains('image-drag-handle')) { if (dragHandle && dragHandle.classList.contains('image-drag-handle')) {
// 延迟隐藏,确保用户有时间将鼠标移到手柄上 // 延迟隐藏,确保用户有时间将鼠标移到手柄上
@@ -785,6 +897,7 @@ const insertImage = () => {
setTimeout(() => { setTimeout(() => {
const dragHandle = img.nextElementSibling const dragHandle = img.nextElementSibling
if (dragHandle && dragHandle.classList.contains('image-drag-handle')) { if (dragHandle && dragHandle.classList.contains('image-drag-handle')) {
console.log('Adding event listeners to drag handle')
dragHandle.addEventListener('mouseover', function() { dragHandle.addEventListener('mouseover', function() {
dragHandle.classList.add('visible') dragHandle.classList.add('visible')
dragHandle.style.display = 'block' // 直接设置显示样式 dragHandle.style.display = 'block' // 直接设置显示样式
@@ -804,13 +917,12 @@ const insertImage = () => {
// 添加拖拽功能 // 添加拖拽功能
img.draggable = true img.draggable = true
img.addEventListener('dragstart', handleImageDragStart) img.setAttribute('data-draggable', 'true')
img.addEventListener('dragover', handleImageDragOver) console.log('Set draggable attributes')
img.addEventListener('drop', handleImageDrop)
img.addEventListener('dragend', handleImageDragEnd)
// 插入图片到当前光标位置 // 插入图片到当前光标位置
range.insertNode(img) range.insertNode(img)
console.log('Inserted image into editor')
// 调试信息 // 调试信息
console.log('Image inserted:', img) console.log('Image inserted:', img)
@@ -819,13 +931,16 @@ const insertImage = () => {
// 添加换行 // 添加换行
const br = document.createElement('br') const br = document.createElement('br')
img.parentNode.insertBefore(br, img.nextSibling) img.parentNode.insertBefore(br, img.nextSibling)
console.log('Added line break after image')
// 触发输入事件更新内容 // 触发输入事件更新内容
handleInput() handleInput()
console.log('Handled input event')
// 重新聚焦到编辑器 // 重新聚焦到编辑器
if (editorRef.value) { if (editorRef.value) {
editorRef.value.focus() editorRef.value.focus()
console.log('Focused editor')
} }
} }
} }
@@ -834,10 +949,12 @@ const insertImage = () => {
// 清理文件输入元素 // 清理文件输入元素
document.body.removeChild(fileInput) document.body.removeChild(fileInput)
console.log('Removed file input')
}) })
// 触发文件选择对话框 // 触发文件选择对话框
fileInput.click() fileInput.click()
console.log('Clicked file input')
} }
// 处理键盘事件 // 处理键盘事件
@@ -1063,13 +1180,17 @@ const handleToolbarFocusOut = () => {
// 调整已有图片的高度 // 调整已有图片的高度
const adjustExistingImages = () => { const adjustExistingImages = () => {
console.log('Adjusting existing images')
// 等待DOM更新完成 // 等待DOM更新完成
setTimeout(() => { setTimeout(() => {
if (editorRef.value) { if (editorRef.value) {
const images = editorRef.value.querySelectorAll('img.editor-image') const imageElements = editorRef.value.querySelectorAll('img.editor-image')
images.forEach(img => { console.log('Found image elements:', imageElements.length)
imageElements.forEach(img => {
console.log('Processing image:', img)
// 只处理还没有调整过高度的图片 // 只处理还没有调整过高度的图片
if (!img.dataset.heightAdjusted) { if (!img.dataset.heightAdjusted) {
console.log('Adjusting height for image')
// 创建一个临时图片来获取原始尺寸 // 创建一个临时图片来获取原始尺寸
const tempImg = new Image() const tempImg = new Image()
tempImg.onload = function () { tempImg.onload = function () {
@@ -1106,27 +1227,34 @@ const adjustExistingImages = () => {
// 标记图片已调整过高度 // 标记图片已调整过高度
img.dataset.heightAdjusted = 'true' img.dataset.heightAdjusted = 'true'
console.log('Adjusted image dimensions:', adjustedWidth, adjustedHeight)
} }
tempImg.src = img.src tempImg.src = img.src
} }
}) })
// 为现有图片添加拖拽功能 // 为现有图片添加拖拽功能
images.forEach(img => { imageElements.forEach(img => {
console.log('Adding drag functionality to image:', img)
// 确保图片有拖拽属性 // 确保图片有拖拽属性
if (!img.hasAttribute('draggable')) { if (!img.hasAttribute('draggable')) {
console.log('Setting draggable attribute')
img.draggable = true img.draggable = true
img.setAttribute('data-draggable', 'true')
// 添加拖拽事件监听器 // 添加拖拽事件监听器
img.addEventListener('dragstart', handleImageDragStart) img.addEventListener('dragstart', handleImageDragStart)
img.addEventListener('dragover', handleImageDragOver) img.addEventListener('dragover', handleImageDragOver)
img.addEventListener('drop', handleImageDrop) img.addEventListener('drop', handleImageDrop)
img.addEventListener('dragend', handleImageDragEnd) img.addEventListener('dragend', handleImageDragEnd)
console.log('Added drag event listeners')
} }
// 检查是否已存在拖拽手柄 // 检查是否已存在拖拽手柄
let existingHandle = img.nextElementSibling let existingHandle = img.nextElementSibling
console.log('Existing handle:', existingHandle)
if (!existingHandle || !existingHandle.classList.contains('image-drag-handle')) { if (!existingHandle || !existingHandle.classList.contains('image-drag-handle')) {
console.log('Creating drag handle')
// 创建拖拽手柄 // 创建拖拽手柄
const dragHandle = document.createElement('div') const dragHandle = document.createElement('div')
dragHandle.className = 'image-drag-handle' dragHandle.className = 'image-drag-handle'
@@ -1149,12 +1277,14 @@ const adjustExistingImages = () => {
// 将拖拽手柄添加到图片后面 // 将拖拽手柄添加到图片后面
img.insertAdjacentElement('afterend', dragHandle) img.insertAdjacentElement('afterend', dragHandle)
console.log('Added drag handle')
} }
// 确保拖拽手柄存在后再添加事件监听器 // 确保拖拽手柄存在后再添加事件监听器
setTimeout(() => { setTimeout(() => {
const dragHandle = img.nextElementSibling const dragHandle = img.nextElementSibling
if (dragHandle && dragHandle.classList.contains('image-drag-handle')) { if (dragHandle && dragHandle.classList.contains('image-drag-handle')) {
console.log('Adding event listeners to drag handle')
// 添加事件监听器来显示/隐藏拖拽手柄 // 添加事件监听器来显示/隐藏拖拽手柄
img.addEventListener('focus', function() { img.addEventListener('focus', function() {
dragHandle.classList.add('visible') dragHandle.classList.add('visible')
@@ -1211,7 +1341,7 @@ const adjustExistingImages = () => {
defineExpose({ defineExpose({
getContent: () => content.value, getContent: () => content.value,
setContent: newContent => { setContent: newContent => {
console.log('Setting content in editor:', newContent) console.log('Setting content:', newContent)
content.value = newContent || '' content.value = newContent || ''
if (editorRef.value) { if (editorRef.value) {
try { try {
@@ -1219,6 +1349,26 @@ defineExpose({
console.log('Content set successfully in editorRef') console.log('Content set successfully in editorRef')
// 调整已有图片的高度并添加拖拽功能 // 调整已有图片的高度并添加拖拽功能
adjustExistingImages() 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) { } catch (error) {
console.error('Failed to set innerHTML:', error) console.error('Failed to set innerHTML:', error)
// 备选方案使用textContent // 备选方案使用textContent
@@ -1239,6 +1389,26 @@ defineExpose({
console.log('Content set successfully after delay') console.log('Content set successfully after delay')
// 调整已有图片的高度并添加拖拽功能 // 调整已有图片的高度并添加拖拽功能
adjustExistingImages() 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) { } catch (error) {
console.error('Failed to set innerHTML after delay:', error) console.error('Failed to set innerHTML after delay:', error)
} }