From 5e06278ded356016ad4129d915278d75ef56f249 Mon Sep 17 00:00:00 2001 From: yuantao Date: Thu, 16 Oct 2025 13:49:59 +0800 Subject: [PATCH] =?UTF-8?q?"=E4=BF=AE=E5=A4=8D=E6=89=93=E5=BC=80=E5=B7=B2?= =?UTF-8?q?=E6=9C=89=E4=BE=BF=E7=AD=BE=E6=97=B6=E5=88=A0=E9=99=A4=E6=8C=89?= =?UTF-8?q?=E9=92=AE=E6=97=A0=E6=B3=95=E6=98=BE=E7=A4=BA=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98=EF=BC=8C=E5=B9=B6=E6=B8=85=E7=90=86=E8=B0=83=E8=AF=95?= =?UTF-8?q?=E4=BB=A3=E7=A0=81"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/RichTextEditor.vue | 973 +++++++++++++++++++++++++----- 1 file changed, 817 insertions(+), 156 deletions(-) diff --git a/src/components/RichTextEditor.vue b/src/components/RichTextEditor.vue index a78d961..c84bbee 100644 --- a/src/components/RichTextEditor.vue +++ b/src/components/RichTextEditor.vue @@ -29,6 +29,18 @@ const content = ref(props.modelValue || '') const isToolbarVisible = ref(false) const isKeyboardVisible = ref(false) const initialViewportHeight = ref(0) +const dragState = ref({ + isDragging: false, + draggedImage: null, + startX: 0, + startY: 0, + currentY: 0, + longPressTimer: null, + isLongPress: false, + indicator: null, + lastCheckTime: 0, + lastMoveTime: 0 +}) // 初始化编辑器内容 onMounted(() => { @@ -87,8 +99,21 @@ onMounted(() => { container.addEventListener('touchcancel', handleTouchCancel) // 为删除按钮添加点击事件 - const deleteBtn = container.querySelector('.image-delete-btn') + let deleteBtn = container.querySelector('.image-delete-btn') + if (!deleteBtn) { + // 如果删除按钮不存在,创建它 + console.log('Delete button not found in mounted hook, creating new one') + deleteBtn = document.createElement('div') + deleteBtn.className = 'image-delete-btn' + deleteBtn.style.cssText = 'position: absolute; top: 8px; right: 8px; width: 24px; height: 24px; cursor: pointer; z-index: 1000; display: none; transition: opacity 0.2s ease; touch-action: manipulation;' + container.appendChild(deleteBtn) + } + if (deleteBtn) { + // 先移除可能已有的事件监听器,避免重复 + deleteBtn.removeEventListener('click', null) + deleteBtn.removeEventListener('touchend', null) + deleteBtn.addEventListener('click', function(e) { e.stopPropagation(); container.remove(); @@ -103,26 +128,63 @@ onMounted(() => { } // 为图片容器添加短按事件以显示/隐藏删除按钮 - let touchStartTime = 0; - container.addEventListener('touchstart', function(e) { - touchStartTime = Date.now(); - }); + // 先移除可能已有的事件监听器,避免重复 + const touchStartHandler = container._touchStartHandler; + const touchEndHandler = container._touchEndHandler; - container.addEventListener('touchend', function(e) { + if (touchStartHandler) { + container.removeEventListener('touchstart', touchStartHandler); + } + + if (touchEndHandler) { + container.removeEventListener('touchend', touchEndHandler); + } + + let touchStartTime = 0; + const newTouchStartHandler = function(e) { + touchStartTime = Date.now(); + }; + + const newTouchEndHandler = function(e) { const touchDuration = Date.now() - touchStartTime; + console.log('Touch end event triggered in mounted hook, duration:', touchDuration, 'isLongPress:', dragState.value.isLongPress); // 短按(小于200ms)且非长按拖拽状态时切换删除按钮显示 if (touchDuration < 200 && !dragState.value.isLongPress) { e.stopPropagation(); + console.log('Short tap detected in mounted hook, toggling delete button visibility'); // 切换删除按钮的显示状态 if (deleteBtn) { - if (deleteBtn.style.display === 'none' || deleteBtn.style.display === '') { - deleteBtn.style.display = 'block'; - } else { + console.log('Current delete button display style in mounted hook:', deleteBtn.style.display); + // 检查删除按钮当前是否可见 + const computedStyle = getComputedStyle(deleteBtn); + const isCurrentlyVisible = deleteBtn.style.display === 'block' || + computedStyle.display === 'block' || + (deleteBtn.style.display !== 'none' && + computedStyle.display !== 'none'); + + console.log('Delete button current styles in mounted hook - inline:', deleteBtn.style.display, 'computed:', computedStyle.display); + + if (isCurrentlyVisible) { deleteBtn.style.display = 'none'; + console.log('Delete button hidden in mounted hook'); + } else { + deleteBtn.style.display = 'block'; + console.log('Delete button displayed in mounted hook'); } + } else { + console.log('Delete button not found in mounted hook'); } + } else { + console.log('Not a short tap or isLongPress is true in mounted hook'); } - }); + }; + + container.addEventListener('touchstart', newTouchStartHandler); + container.addEventListener('touchend', newTouchEndHandler); + + // 保存事件处理函数的引用,以便后续移除 + container._touchStartHandler = newTouchStartHandler; + container._touchEndHandler = newTouchEndHandler; }) }, 0) }) @@ -134,6 +196,39 @@ onUnmounted(() => { } else { window.removeEventListener('resize', handleWindowResize) } + + // 清理所有图片容器的事件监听器 + if (editorRef.value) { + const imageContainers = editorRef.value.querySelectorAll('.image-container') + imageContainers.forEach(container => { + // 移除拖拽事件监听器 + container.removeEventListener('touchstart', handleTouchStart) + container.removeEventListener('touchmove', handleTouchMove) + container.removeEventListener('touchend', handleTouchEnd) + container.removeEventListener('touchcancel', handleTouchCancel) + + // 移除短按事件监听器 + const touchStartHandler = container._touchStartHandler; + const touchEndHandler = container._touchEndHandler; + + if (touchStartHandler) { + container.removeEventListener('touchstart', touchStartHandler); + delete container._touchStartHandler; + } + + if (touchEndHandler) { + container.removeEventListener('touchend', touchEndHandler); + delete container._touchEndHandler; + } + + // 移除删除按钮事件监听器 + const deleteBtn = container.querySelector('.image-delete-btn') + if (deleteBtn) { + deleteBtn.removeEventListener('click', null) + deleteBtn.removeEventListener('touchend', null) + } + }) + } }) // 工具栏配置 @@ -180,8 +275,8 @@ const tools = ref([ // 处理输入事件 const handleInput = () => { if (editorRef.value) { - // 获取编辑器内容 - let innerHTML = editorRef.value.innerHTML + // 获取编辑器内容(不清理,保持功能完整) + let innerHTML = editorRef.value.innerHTML; // 处理换行符,确保在段落之间有明确的分隔 innerHTML = innerHTML.replace(/<\/p>

/g, '

\n

') @@ -639,20 +734,6 @@ const insertTodoList = () => { } } -// 图片拖拽相关状态 -const dragState = ref({ - isDragging: false, - draggedImage: null, - startX: 0, - startY: 0, - currentY: 0, - longPressTimer: null, - isLongPress: false, - indicator: null, - lastCheckTime: 0, - lastMoveTime: 0 -}) - // 重置拖拽状态 const resetDragState = () => { // 清除长按定时器 @@ -723,9 +804,42 @@ const insertImage = () => { // 获取当前选区 const selection = window.getSelection() if (selection.rangeCount > 0) { - const range = selection.getRangeAt(0) + let range = selection.getRangeAt(0) console.log('Current range:', range) + // 检查选区是否在图片容器内部,如果是则调整到容器后面 + const startContainer = range.startContainer + let imageContainer = null + + // 如果startContainer是图片容器本身 + if (startContainer.classList && startContainer.classList.contains('image-container')) { + imageContainer = startContainer + } + // 如果startContainer是图片容器的子元素 + else if (startContainer.parentNode && startContainer.parentNode.classList && + startContainer.parentNode.classList.contains('image-container')) { + imageContainer = startContainer.parentNode + } + // 向上查找父元素 + else { + let parent = startContainer.parentNode + while (parent && parent !== editorRef.value) { + if (parent.classList && parent.classList.contains('image-container')) { + imageContainer = parent + break + } + parent = parent.parentNode + } + } + + // 如果选区在图片容器内部,调整到容器后面 + if (imageContainer) { + console.log('Selection is inside image container, adjusting range') + range = document.createRange() + range.setStartAfter(imageContainer) + range.collapse(true) + } + // 创建图片容器 const imgContainer = document.createElement('div') imgContainer.className = 'image-container' @@ -748,13 +862,18 @@ const insertImage = () => { img.style.background = 'var(--background-secondary)' img.style.position = 'relative' img.style.outline = 'none' // 移除默认焦点轮廓 + img.style.userSelect = 'none' // 防止选中 + img.style.webkitUserSelect = 'none' // 防止选中 + img.style.mozUserSelect = 'none' // 防止选中 + img.style.msUserSelect = 'none' // 防止选中 + img.style.webkitTouchCallout = 'none' // 防止长按弹出菜单 + img.style.webkitTapHighlightColor = 'transparent' // 防止点击高亮 img.draggable = true // 创建删除按钮 - const deleteBtn = document.createElement('img') - deleteBtn.src = '/assets/icons/drawable-xxhdpi/item_image_btn_unbrella_delete.png' + const deleteBtn = document.createElement('div') deleteBtn.className = 'image-delete-btn' - deleteBtn.style.cssText = 'position: absolute; top: 8px; right: 8px; width: 24px; height: 24px; cursor: pointer; z-index: 10; display: none; transition: opacity 0.2s ease; touch-action: manipulation;' + deleteBtn.style.cssText = 'position: absolute; top: 8px; right: 8px; width: 24px; height: 24px; cursor: pointer; z-index: 1000; display: none; transition: opacity 0.2s ease; touch-action: manipulation;' // 将图片和删除按钮添加到容器中 imgContainer.appendChild(img) @@ -800,12 +919,23 @@ const insertImage = () => { tempImg.src = imageDataUrl // 添加触摸事件监听器实现拖拽功能 + // 先移除可能已有的事件监听器,避免重复 + imgContainer.removeEventListener('touchstart', handleTouchStart) + imgContainer.removeEventListener('touchmove', handleTouchMove) + imgContainer.removeEventListener('touchend', handleTouchEnd) + imgContainer.removeEventListener('touchcancel', handleTouchCancel) + + // 重新添加事件监听器 imgContainer.addEventListener('touchstart', handleTouchStart) imgContainer.addEventListener('touchmove', handleTouchMove) imgContainer.addEventListener('touchend', handleTouchEnd) imgContainer.addEventListener('touchcancel', handleTouchCancel) // 为删除按钮添加点击事件(鼠标和触摸) + // 先移除可能已有的事件监听器,避免重复 + deleteBtn.removeEventListener('click', null) + deleteBtn.removeEventListener('touchend', null) + deleteBtn.addEventListener('click', function(e) { e.stopPropagation(); imgContainer.remove(); @@ -820,23 +950,56 @@ const insertImage = () => { // 为图片容器添加短按事件以显示/隐藏删除按钮 let touchStartTime = 0; - imgContainer.addEventListener('touchstart', function(e) { + const touchStartHandler = function(e) { touchStartTime = Date.now(); - }); + }; - imgContainer.addEventListener('touchend', function(e) { + const touchEndHandler = function(e) { const touchDuration = Date.now() - touchStartTime; + console.log('Touch end event triggered, duration:', touchDuration, 'isLongPress:', dragState.value.isLongPress); // 短按(小于200ms)且非长按拖拽状态时切换删除按钮显示 if (touchDuration < 200 && !dragState.value.isLongPress) { e.stopPropagation(); + console.log('Short tap detected, toggling delete button visibility'); // 切换删除按钮的显示状态 - if (deleteBtn.style.display === 'none' || deleteBtn.style.display === '') { - deleteBtn.style.display = 'block'; + if (deleteBtn) { + console.log('Current delete button display style:', deleteBtn.style.display); + // 检查删除按钮当前是否可见 + const computedStyle = getComputedStyle(deleteBtn); + const isCurrentlyVisible = deleteBtn.style.display === 'block' || + computedStyle.display === 'block' || + (deleteBtn.style.display !== 'none' && + computedStyle.display !== 'none'); + + console.log('Delete button current styles - inline:', deleteBtn.style.display, 'computed:', computedStyle.display); + console.log('Delete button background image:', computedStyle.backgroundImage); + console.log('Delete button width:', computedStyle.width, 'height:', computedStyle.height); + console.log('Delete button position:', computedStyle.position); + console.log('Delete button z-index:', computedStyle.zIndex); + + if (isCurrentlyVisible) { + deleteBtn.style.display = 'none'; + console.log('Delete button hidden'); + } else { + deleteBtn.style.display = 'block'; + console.log('Delete button displayed'); + // 添加调试样式以确保可见 + deleteBtn.style.backgroundColor = 'rgba(255, 0, 0, 0.3)'; // 半透明红色背景用于调试 + } } else { - deleteBtn.style.display = 'none'; + console.log('Delete button not found'); } + } else { + console.log('Not a short tap or isLongPress is true'); } - }); + }; + + imgContainer.addEventListener('touchstart', touchStartHandler); + imgContainer.addEventListener('touchend', touchEndHandler); + + // 保存事件处理函数的引用,以便后续移除 + imgContainer._touchStartHandler = touchStartHandler; + imgContainer._touchEndHandler = touchEndHandler; console.log('Added touch event listeners') @@ -852,16 +1015,27 @@ const insertImage = () => { const br = document.createElement('br') imgContainer.parentNode.insertBefore(br, imgContainer.nextSibling) console.log('Added line break after image container') + + // 修正选区位置,避免嵌套插入 + // 使用setTimeout确保DOM更新完成后再设置选区 + setTimeout(() => { + const newRange = document.createRange(); + newRange.setStartAfter(br); + newRange.collapse(true); + const sel = window.getSelection(); + sel.removeAllRanges(); + sel.addRange(newRange); + + // 重新聚焦到编辑器 + if (editorRef.value) { + editorRef.value.focus() + console.log('Focused editor') + } + }, 0); // 触发输入事件更新内容 handleInput() console.log('Handled input event') - - // 重新聚焦到编辑器 - if (editorRef.value) { - editorRef.value.focus() - console.log('Focused editor') - } } } reader.readAsDataURL(file) @@ -934,11 +1108,13 @@ const handleTouchStart = (e) => { const img = e.target if (!img.classList.contains('editor-image')) return - // 防止图片被选中 + // 防止图片被选中 - 在触摸开始时就应用 img.style.userSelect = 'none' img.style.webkitUserSelect = 'none' img.style.mozUserSelect = 'none' img.style.msUserSelect = 'none' + img.style.webkitTouchCallout = 'none' + img.style.webkitTapHighlightColor = 'transparent' // 清除之前的定时器 if (dragState.value.longPressTimer) { @@ -949,7 +1125,7 @@ const handleTouchStart = (e) => { dragState.value.startX = e.touches[0].clientX dragState.value.startY = e.touches[0].clientY - // 设置长按检测定时器(300毫秒) + // 设置长按检测定时器(500毫秒) dragState.value.longPressTimer = setTimeout(() => { dragState.value.isLongPress = true dragState.value.draggedImage = img @@ -958,10 +1134,10 @@ const handleTouchStart = (e) => { // 添加拖拽样式 img.classList.add('dragging') - img.style.opacity = '0.9' - img.style.transform = 'scale(0.98)' + img.style.opacity = '0.85' + img.style.transform = 'scale(0.96)' img.style.zIndex = '999' - img.style.transition = 'all 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94)' + img.style.transition = 'all 0.15s cubic-bezier(0.25, 0.46, 0.45, 0.94)' // 添加拖拽指示器 const indicator = document.createElement('div') @@ -971,33 +1147,34 @@ const handleTouchStart = (e) => { indicator.style.left = '50%' indicator.style.transform = 'translate(-50%, -50%)' indicator.style.padding = '8px 16px' - indicator.style.background = 'rgba(0, 0, 0, 0.8)' + indicator.style.background = 'rgba(0, 0, 0, 0.85)' indicator.style.color = 'white' indicator.style.borderRadius = '16px' indicator.style.fontSize = '14px' indicator.style.fontWeight = '500' indicator.style.zIndex = '1000' indicator.style.opacity = '0' - indicator.style.transition = 'opacity 0.15s ease-out' + indicator.style.transition = 'opacity 0.1s ease-out' + indicator.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.3)' indicator.textContent = '拖拽排序' document.body.appendChild(indicator) // 渐显指示器 setTimeout(() => { indicator.style.opacity = '1' - }, 5) + }, 1) // 保存指示器引用以便后续移除 dragState.value.indicator = indicator // 添加震动反馈(如果设备支持) if (navigator.vibrate) { - navigator.vibrate(10) + navigator.vibrate(15) } // 阻止页面滚动 e.preventDefault() - }, 300) // 300毫秒长按触发拖拽 + }, 500) // 500毫秒长按触发拖拽 } // 处理触摸移动事件 @@ -1011,13 +1188,13 @@ const handleTouchMove = (e) => { // 防止图片被选中 e.preventDefault() - // 如果还没有触发长按,检查是否移动过多(超过8px则取消长按) + // 如果还没有触发长按,检查是否移动过多(超过6px则取消长按) if (!dragState.value.isLongPress) { const deltaX = Math.abs(currentX - dragState.value.startX) const deltaY = Math.abs(currentY - dragState.value.startY) const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY) - if (distance > 8) { + if (distance > 6) { // 移动过多,取消长按 if (dragState.value.longPressTimer) { clearTimeout(dragState.value.longPressTimer) @@ -1036,9 +1213,12 @@ const handleTouchMove = (e) => { // 计算位移 const deltaY = dragState.value.currentY - dragState.value.startY - // 更新图片位置,添加缓动效果 - const easeFactor = 0.9 // 调整缓动因子使拖拽更跟手 - img.style.transform = `translateY(${deltaY * easeFactor}px) scale(0.98)` + // 使用requestAnimationFrame确保流畅的动画 + requestAnimationFrame(() => { + // 更新图片位置,添加缓动效果 + const easeFactor = 0.95 // 调整缓动因子使拖拽更跟手 + img.style.transform = `translateY(${deltaY * easeFactor}px) scale(0.96)` + }); // 使用节流优化,避免过于频繁的检查 if (!dragState.value.lastMoveTime) { @@ -1046,8 +1226,8 @@ const handleTouchMove = (e) => { } const now = Date.now() - // 限制检查频率为每25ms一次,提高响应速度 - if (now - dragState.value.lastMoveTime >= 25) { + // 限制检查频率为每16ms一次(约60fps),提高响应速度 + if (now - dragState.value.lastMoveTime >= 16) { dragState.value.lastMoveTime = now checkAndSwapImages(img, deltaY) } @@ -1069,26 +1249,26 @@ const handleTouchEnd = (e) => { // 重置拖拽状态 const img = dragState.value.draggedImage - // 添加释放动画 - img.style.transition = 'all 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94)' + // 添加更流畅的释放动画 + img.style.transition = 'all 0.15s cubic-bezier(0.25, 0.46, 0.45, 0.94)' img.style.transform = 'translateY(0) scale(1)' img.style.opacity = '1' // 移除拖拽指示器 if (dragState.value.indicator) { const indicator = dragState.value.indicator - indicator.style.transition = 'opacity 0.15s ease-out' + indicator.style.transition = 'opacity 0.1s ease-out' indicator.style.opacity = '0' setTimeout(() => { if (indicator.parentNode) { indicator.parentNode.removeChild(indicator) } - }, 150) + }, 100) } // 添加震动反馈(如果设备支持) if (navigator.vibrate) { - navigator.vibrate(5) + navigator.vibrate(8) } // 延迟重置样式以显示动画 @@ -1098,7 +1278,7 @@ const handleTouchEnd = (e) => { img.style.zIndex = '' img.style.transition = '' } - }, 200) + }, 150) // 重置状态 dragState.value.isLongPress = false @@ -1128,20 +1308,20 @@ const handleTouchCancel = (e) => { const img = dragState.value.draggedImage // 添加取消动画 - img.style.transition = 'all 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94)' + img.style.transition = 'all 0.15s cubic-bezier(0.25, 0.46, 0.45, 0.94)' img.style.transform = 'translateY(0) scale(1)' img.style.opacity = '1' // 移除拖拽指示器 if (dragState.value.indicator) { const indicator = dragState.value.indicator - indicator.style.transition = 'opacity 0.15s ease-out' + indicator.style.transition = 'opacity 0.1s ease-out' indicator.style.opacity = '0' setTimeout(() => { if (indicator.parentNode) { indicator.parentNode.removeChild(indicator) } - }, 150) + }, 100) } // 延迟重置样式以显示动画 @@ -1151,7 +1331,7 @@ const handleTouchCancel = (e) => { img.style.zIndex = '' img.style.transition = '' } - }, 200) + }, 150) // 重置状态 dragState.value.isLongPress = false @@ -1170,7 +1350,7 @@ const checkAndSwapImages = (draggedImg, deltaY) => { // 计算拖拽图片的中心位置 const draggedRect = draggedImg.getBoundingClientRect() - const draggedCenterY = draggedRect.top + draggedRect.height / 2 + deltaY * 0.9 // 调整缓动因子以匹配触摸移动 + const draggedCenterY = draggedRect.top + draggedRect.height / 2 + deltaY * 0.95 // 调整缓动因子以匹配触摸移动 // 查找最近的图片进行交换 for (let i = 0; i < allImages.length; i++) { @@ -1181,8 +1361,8 @@ const checkAndSwapImages = (draggedImg, deltaY) => { const targetCenterY = targetRect.top + targetRect.height / 2 // 检查是否与目标图片重叠,使用更精确的碰撞检测 - // 当拖拽图片覆盖目标图片高度的三分之二时触发排序 - const overlapThreshold = targetRect.height * 0.67 + // 当拖拽图片覆盖目标图片高度的60%时触发排序 + const overlapThreshold = targetRect.height * 0.6 const distance = Math.abs(draggedCenterY - targetCenterY) if (distance < overlapThreshold) { @@ -1195,32 +1375,71 @@ const checkAndSwapImages = (draggedImg, deltaY) => { // 交换两张图片的位置 const swapImages = (img1, img2) => { + // 为交换添加平滑过渡效果 const parent1 = img1.parentNode const parent2 = img2.parentNode + // 添加交换动画类 + img1.classList.add('swap-animation') + img2.classList.add('swap-animation') + // 如果两张图片在同一父元素中 if (parent1 === parent2) { - // 直接交换DOM位置,避免复杂的动画导致的闪烁 - const tempMarker = document.createElement('div') - parent1.insertBefore(tempMarker, img1) - parent1.insertBefore(img1, img2) - parent1.insertBefore(img2, tempMarker) - tempMarker.remove() + // 计算两个图片的位置差 + const rect1 = img1.getBoundingClientRect() + const rect2 = img2.getBoundingClientRect() + const deltaY = rect2.top - rect1.top + + // 添加临时的变换动画 + img1.style.transform = `translateY(${deltaY}px)` + img2.style.transform = `translateY(${-deltaY}px)` + + // 在动画完成后交换DOM位置 + setTimeout(() => { + // 移除临时变换 + img1.style.transform = '' + img2.style.transform = '' + + // 移除交换动画类 + img1.classList.remove('swap-animation') + img2.classList.remove('swap-animation') + + // 交换DOM位置 + const tempMarker = document.createElement('div') + parent1.insertBefore(tempMarker, img1) + parent1.insertBefore(img1, img2) + parent1.insertBefore(img2, tempMarker) + tempMarker.remove() + + // 触发内容更新 + handleInput() + + // 自动退出排序模式,提高响应速度 + setTimeout(() => { + resetDragState() + }, 10) + }, 200) } else { // 不同父元素的情况(更复杂,需要特殊处理) // 这里简化处理,实际项目中可能需要更复杂的逻辑 + // 移除交换动画类 + img1.classList.remove('swap-animation') + img2.classList.remove('swap-animation') + const temp = document.createElement('div') parent1.insertBefore(temp, img1) parent2.insertBefore(img1, img2) parent1.insertBefore(img2, temp) temp.remove() + + // 触发内容更新 + handleInput() + + // 自动退出排序模式,提高响应速度 + setTimeout(() => { + resetDragState() + }, 10) } - - // 触发内容更新 - handleInput() - - // 自动退出排序模式 - resetDragState() } // 更新工具栏状态 @@ -1392,6 +1611,233 @@ const handleToolbarFocusOut = () => { }, 200) // 增加延迟时间,确保有足够时间处理点击事件 } +// 包装孤立的图片(没有被.image-container包装的图片) +const wrapOrphanedImages = () => { + if (!editorRef.value) return; + + // 查找所有没有被.image-container包装的图片 + const images = editorRef.value.querySelectorAll('img:not(.editor-image)'); + console.log('Found orphaned images:', images.length); + + images.forEach(img => { + // 检查图片是否已经在.image-container中 + if (img.closest('.image-container')) return; + + console.log('Wrapping orphaned image'); + + // 检查图片的父元素是否是.image-container,避免嵌套 + if (img.parentNode && img.parentNode.classList && img.parentNode.classList.contains('image-container')) { + console.log('Image is already in image-container, checking for delete button'); + // 确保图片有正确的类名 + img.className = 'editor-image'; + img.setAttribute('data-draggable', 'true'); + + // 为已存在的图片容器添加删除按钮事件监听器 + const imgContainer = img.parentNode; + const deleteBtn = imgContainer.querySelector('.image-delete-btn'); + if (deleteBtn) { + console.log('Found existing delete button, adding event listeners'); + // 先移除可能已有的事件监听器,避免重复 + deleteBtn.removeEventListener('click', null); + deleteBtn.removeEventListener('touchend', null); + + deleteBtn.addEventListener('click', function(e) { + e.stopPropagation(); + imgContainer.remove(); + handleInput(); + }); + + deleteBtn.addEventListener('touchend', function(e) { + e.stopPropagation(); + imgContainer.remove(); + handleInput(); + }); + + // 为图片容器添加短按事件以显示/隐藏删除按钮 + // 先移除可能已有的事件监听器,避免重复 + const touchStartHandler = imgContainer._touchStartHandler; + const touchEndHandler = imgContainer._touchEndHandler; + + if (touchStartHandler) { + imgContainer.removeEventListener('touchstart', touchStartHandler); + } + + if (touchEndHandler) { + imgContainer.removeEventListener('touchend', touchEndHandler); + } + + let touchStartTime = 0; + const newTouchStartHandler = function(e) { + touchStartTime = Date.now(); + }; + + const newTouchEndHandler = function(e) { + const touchDuration = Date.now() - touchStartTime; + console.log('Touch end event triggered for existing image, duration:', touchDuration, 'isLongPress:', dragState.value.isLongPress); + // 短按(小于200ms)且非长按拖拽状态时切换删除按钮显示 + if (touchDuration < 200 && !dragState.value.isLongPress) { + e.stopPropagation(); + console.log('Short tap detected for existing image, toggling delete button visibility'); + // 切换删除按钮的显示状态 + if (deleteBtn) { + console.log('Current delete button display style for existing image:', deleteBtn.style.display); + // 检查删除按钮当前是否可见 + const computedStyle = getComputedStyle(deleteBtn); + const isCurrentlyVisible = deleteBtn.style.display === 'block' || + computedStyle.display === 'block' || + (deleteBtn.style.display !== 'none' && + computedStyle.display !== 'none'); + + console.log('Delete button current styles for existing image - inline:', deleteBtn.style.display, 'computed:', computedStyle.display); + + if (isCurrentlyVisible) { + deleteBtn.style.display = 'none'; + console.log('Delete button hidden for existing image'); + } else { + deleteBtn.style.display = 'block'; + console.log('Delete button displayed for existing image'); + } + } else { + console.log('Delete button not found for existing image'); + } + } else { + console.log('Not a short tap or isLongPress is true for existing image'); + } + }; + + imgContainer.addEventListener('touchstart', newTouchStartHandler); + imgContainer.addEventListener('touchend', newTouchEndHandler); + + // 保存事件处理函数的引用,以便后续移除 + imgContainer._touchStartHandler = newTouchStartHandler; + imgContainer._touchEndHandler = newTouchEndHandler; + } + return; + } + + // 创建图片容器 + const imgContainer = document.createElement('div'); + imgContainer.className = 'image-container'; + imgContainer.style.position = 'relative'; + imgContainer.style.display = 'inline-block'; + + // 设置图片样式 + img.className = 'editor-image'; + img.setAttribute('data-draggable', 'true'); + img.style.maxWidth = '100%'; + img.style.height = 'auto'; + img.style.display = 'block'; + img.style.objectFit = 'cover'; + img.style.boxSizing = 'border-box'; + img.style.border = '0.625rem solid white'; + img.style.borderRadius = '0.2rem'; + img.style.boxShadow = '0 1px 5px rgba(0, 0, 0, 0.18)'; + img.style.background = 'var(--background-secondary)'; + img.style.position = 'relative'; + img.style.outline = 'none'; + img.style.userSelect = 'none'; // 防止选中 + img.style.webkitUserSelect = 'none'; // 防止选中 + img.style.mozUserSelect = 'none'; // 防止选中 + img.style.msUserSelect = 'none'; // 防止选中 + img.style.webkitTouchCallout = 'none'; // 防止长按弹出菜单 + img.style.webkitTapHighlightColor = 'transparent'; // 防止点击高亮 + img.draggable = true; + + // 创建删除按钮 + const deleteBtn = document.createElement('div'); + deleteBtn.className = 'image-delete-btn'; + deleteBtn.style.cssText = 'position: absolute; top: 8px; right: 8px; width: 24px; height: 24px; cursor: pointer; z-index: 1000; display: none; transition: opacity 0.2s ease; touch-action: manipulation;'; + + // 将图片和删除按钮添加到容器中 + imgContainer.appendChild(img); + imgContainer.appendChild(deleteBtn); + + // 替换原来的图片 + img.parentNode.replaceChild(imgContainer, img); + + // 为新包装的图片添加事件监听器 + // 先移除可能已有的事件监听器,避免重复 + imgContainer.removeEventListener('touchstart', handleTouchStart) + imgContainer.removeEventListener('touchmove', handleTouchMove) + imgContainer.removeEventListener('touchend', handleTouchEnd) + imgContainer.removeEventListener('touchcancel', handleTouchCancel) + + // 重新添加事件监听器 + imgContainer.addEventListener('touchstart', handleTouchStart) + imgContainer.addEventListener('touchmove', handleTouchMove) + imgContainer.addEventListener('touchend', handleTouchEnd) + imgContainer.addEventListener('touchcancel', handleTouchCancel) + + // 为删除按钮添加点击事件 + // 先移除可能已有的事件监听器,避免重复 + deleteBtn.removeEventListener('click', null) + deleteBtn.removeEventListener('touchend', null) + + deleteBtn.addEventListener('click', function(e) { + e.stopPropagation(); + imgContainer.remove(); + handleInput(); + }); + + deleteBtn.addEventListener('touchend', function(e) { + e.stopPropagation(); + imgContainer.remove(); + handleInput(); + }); + + // 为图片容器添加短按事件以显示/隐藏删除按钮 + let touchStartTime = 0; + const touchStartHandler = function(e) { + touchStartTime = Date.now(); + }; + + const touchEndHandler = function(e) { + const touchDuration = Date.now() - touchStartTime; + console.log('Touch end event triggered in wrapOrphanedImages, duration:', touchDuration, 'isLongPress:', dragState.value.isLongPress); + // 短按(小于200ms)且非长按拖拽状态时切换删除按钮显示 + if (touchDuration < 200 && !dragState.value.isLongPress) { + e.stopPropagation(); + console.log('Short tap detected in wrapOrphanedImages, toggling delete button visibility'); + // 切换删除按钮的显示状态 + if (deleteBtn) { + console.log('Current delete button display style in wrapOrphanedImages:', deleteBtn.style.display); + // 检查删除按钮当前是否可见 + const computedStyle = getComputedStyle(deleteBtn); + const isCurrentlyVisible = deleteBtn.style.display === 'block' || + computedStyle.display === 'block' || + (deleteBtn.style.display !== 'none' && + computedStyle.display !== 'none'); + + console.log('Delete button current styles - inline:', deleteBtn.style.display, 'computed:', computedStyle.display); + console.log('Delete button background image:', computedStyle.backgroundImage); + console.log('Delete button width:', computedStyle.width, 'height:', computedStyle.height); + console.log('Delete button position:', computedStyle.position); + console.log('Delete button z-index:', computedStyle.zIndex); + + if (isCurrentlyVisible) { + deleteBtn.style.display = 'none'; + console.log('Delete button hidden in wrapOrphanedImages'); + } else { + deleteBtn.style.display = 'block'; + console.log('Delete button displayed in wrapOrphanedImages'); + } + } else { + console.log('Delete button not found in wrapOrphanedImages'); + } + } else { + console.log('Not a short tap or isLongPress is true in wrapOrphanedImages'); + } + }; + + imgContainer.addEventListener('touchstart', touchStartHandler); + imgContainer.addEventListener('touchend', touchEndHandler); + + // 保存事件处理函数的引用,以便后续移除 + imgContainer._touchStartHandler = touchStartHandler; + imgContainer._touchEndHandler = touchEndHandler; + }); +} + // 调整已有图片的高度 const adjustExistingImages = () => { console.log('Adjusting existing images') @@ -1456,64 +1902,152 @@ const adjustExistingImages = () => { if (!img) return console.log('Adding drag functionality to image:', img) - // 添加触摸事件监听器 - if (!img.hasAttribute('data-touch-listeners')) { - console.log('Adding touch event listeners') - // 为图片容器添加事件监听器 - container.addEventListener('touchstart', handleTouchStart) - container.addEventListener('touchmove', handleTouchMove) - container.addEventListener('touchend', handleTouchEnd) - container.addEventListener('touchcancel', handleTouchCancel) - - // 为删除按钮添加点击事件 - const deleteBtn = container.querySelector('.image-delete-btn') - if (deleteBtn) { - deleteBtn.addEventListener('click', function(e) { - e.stopPropagation(); - container.remove(); - handleInput(); - }); - - deleteBtn.addEventListener('touchend', function(e) { - e.stopPropagation(); - container.remove(); - handleInput(); - }); - } - - // 为图片容器添加短按事件以显示/隐藏删除按钮 - let touchStartTime = 0; - container.addEventListener('touchstart', function(e) { - touchStartTime = Date.now(); - }); - - container.addEventListener('touchend', function(e) { - const touchDuration = Date.now() - touchStartTime; - // 短按(小于200ms)且非长按拖拽状态时切换删除按钮显示 - if (touchDuration < 200 && !dragState.value.isLongPress) { - e.stopPropagation(); - // 切换删除按钮的显示状态 - if (deleteBtn) { - if (deleteBtn.style.display === 'none' || deleteBtn.style.display === '') { - deleteBtn.style.display = 'block'; - } else { - deleteBtn.style.display = 'none'; - } - } - } - }); - - img.setAttribute('data-touch-listeners', 'true') - console.log('Added touch event listeners') + // 为图片容器添加事件监听器(总是添加,确保功能正常) + // 先移除可能已有的事件监听器,避免重复 + container.removeEventListener('touchstart', handleTouchStart) + container.removeEventListener('touchmove', handleTouchMove) + container.removeEventListener('touchend', handleTouchEnd) + container.removeEventListener('touchcancel', handleTouchCancel) + + // 重新添加事件监听器 + container.addEventListener('touchstart', handleTouchStart) + container.addEventListener('touchmove', handleTouchMove) + container.addEventListener('touchend', handleTouchEnd) + container.addEventListener('touchcancel', handleTouchCancel) + + // 为删除按钮添加点击事件 + let deleteBtn = container.querySelector('.image-delete-btn') + if (!deleteBtn) { + // 如果删除按钮不存在,创建它 + console.log('Delete button not found, creating new one') + deleteBtn = document.createElement('div') + deleteBtn.className = 'image-delete-btn' + deleteBtn.style.cssText = 'position: absolute; top: 8px; right: 8px; width: 24px; height: 24px; cursor: pointer; z-index: 1000; display: none; transition: opacity 0.2s ease; touch-action: manipulation;' + container.appendChild(deleteBtn) } + + if (deleteBtn) { + // 先移除可能已有的事件监听器,避免重复 + deleteBtn.removeEventListener('click', null) + deleteBtn.removeEventListener('touchend', null) + + deleteBtn.addEventListener('click', function(e) { + e.stopPropagation(); + container.remove(); + handleInput(); + }); + + deleteBtn.addEventListener('touchend', function(e) { + e.stopPropagation(); + container.remove(); + handleInput(); + }); + } + + // 为图片容器添加短按事件以显示/隐藏删除按钮 + // 先移除可能已有的事件监听器,避免重复 + const touchStartHandler = container._touchStartHandler; + const touchEndHandler = container._touchEndHandler; + + if (touchStartHandler) { + container.removeEventListener('touchstart', touchStartHandler); + } + + if (touchEndHandler) { + container.removeEventListener('touchend', touchEndHandler); + } + + let touchStartTime = 0; + const newTouchStartHandler = function(e) { + touchStartTime = Date.now(); + }; + + const newTouchEndHandler = function(e) { + const touchDuration = Date.now() - touchStartTime; + console.log('Touch end event triggered in adjustExistingImages, duration:', touchDuration, 'isLongPress:', dragState.value.isLongPress); + // 短按(小于200ms)且非长按拖拽状态时切换删除按钮显示 + if (touchDuration < 200 && !dragState.value.isLongPress) { + e.stopPropagation(); + console.log('Short tap detected in adjustExistingImages, toggling delete button visibility'); + // 切换删除按钮的显示状态 + if (deleteBtn) { + console.log('Current delete button display style in adjustExistingImages:', deleteBtn.style.display); + // 检查删除按钮当前是否可见 + const computedStyle = getComputedStyle(deleteBtn); + const isCurrentlyVisible = deleteBtn.style.display === 'block' || + computedStyle.display === 'block' || + (deleteBtn.style.display !== 'none' && + computedStyle.display !== 'none'); + + console.log('Delete button current styles - inline:', deleteBtn.style.display, 'computed:', computedStyle.display); + + if (isCurrentlyVisible) { + deleteBtn.style.display = 'none'; + console.log('Delete button hidden in adjustExistingImages'); + } else { + deleteBtn.style.display = 'block'; + console.log('Delete button displayed in adjustExistingImages'); + } + } else { + console.log('Delete button not found in adjustExistingImages'); + } + } else { + console.log('Not a short tap or isLongPress is true in adjustExistingImages'); + } + }; + + container.addEventListener('touchstart', newTouchStartHandler); + container.addEventListener('touchend', newTouchEndHandler); + + // 保存事件处理函数的引用,以便后续移除 + container._touchStartHandler = newTouchStartHandler; + container._touchEndHandler = newTouchEndHandler; + + img.setAttribute('data-touch-listeners', 'true') + console.log('Added touch event listeners') }) } }, 0) } +// 清理动态添加的属性(仅在保存时移除临时属性,保留必要属性) +const cleanContentForSave = () => { + if (!editorRef.value) return content.value; + + // 创建一个临时的div来操作内容 + const tempDiv = document.createElement('div'); + tempDiv.innerHTML = editorRef.value.innerHTML; + + // 移除图片上的临时动态属性 + const images = tempDiv.querySelectorAll('img.editor-image'); + images.forEach(img => { + // 移除拖拽时的临时样式属性 + img.style.removeProperty('z-index'); + img.style.removeProperty('transition'); + img.style.removeProperty('transform'); + img.style.removeProperty('opacity'); + + // 移除拖拽时的临时类名 + img.classList.remove('dragging'); + img.classList.remove('swap-animation'); + + // 移除临时的数据属性(保留必要的属性如data-draggable) + img.removeAttribute('data-height-adjusted'); + img.removeAttribute('data-touch-listeners'); + }); + + // 移除拖拽指示器(如果存在) + const indicators = tempDiv.querySelectorAll('.drag-indicator'); + indicators.forEach(indicator => { + indicator.remove(); + }); + + return tempDiv.innerHTML; +}; + // 暴露方法给父组件 defineExpose({ - getContent: () => content.value, + getContent: () => cleanContentForSave(), setContent: newContent => { console.log('Setting content:', newContent) content.value = newContent || '' @@ -1521,6 +2055,14 @@ defineExpose({ try { editorRef.value.innerHTML = content.value console.log('Content set successfully in editorRef') + // 重置拖拽状态,确保isLongPress为false + dragState.value.isLongPress = false + dragState.value.draggedImage = null + dragState.value.startX = 0 + dragState.value.startY = 0 + dragState.value.currentY = 0 + // 确保所有图片都被正确包装在.image-container中 + wrapOrphanedImages() // 调整已有图片的高度并添加拖拽功能 adjustExistingImages() // 为图片添加拖拽事件监听器 @@ -1548,6 +2090,10 @@ defineExpose({ // 为删除按钮添加点击事件 const deleteBtn = container.querySelector('.image-delete-btn') if (deleteBtn) { + // 先移除可能已有的事件监听器,避免重复 + deleteBtn.removeEventListener('click', null) + deleteBtn.removeEventListener('touchend', null) + deleteBtn.addEventListener('click', function(e) { e.stopPropagation(); container.remove(); @@ -1562,26 +2108,72 @@ defineExpose({ } // 为图片容器添加短按事件以显示/隐藏删除按钮 - let touchStartTime = 0; - container.addEventListener('touchstart', function(e) { - touchStartTime = Date.now(); - }); + // 先移除可能已有的事件监听器,避免重复 + const touchStartHandler = container._touchStartHandler; + const touchEndHandler = container._touchEndHandler; - container.addEventListener('touchend', function(e) { + if (touchStartHandler) { + container.removeEventListener('touchstart', touchStartHandler); + } + + if (touchEndHandler) { + container.removeEventListener('touchend', touchEndHandler); + } + + let touchStartTime = 0; + const newTouchStartHandler = function(e) { + touchStartTime = Date.now(); + }; + + const newTouchEndHandler = function(e) { const touchDuration = Date.now() - touchStartTime; + console.log('Touch end event triggered in setContent, duration:', touchDuration, 'isLongPress:', dragState.value.isLongPress); // 短按(小于200ms)且非长按拖拽状态时切换删除按钮显示 if (touchDuration < 200 && !dragState.value.isLongPress) { e.stopPropagation(); + console.log('Short tap detected in setContent, toggling delete button visibility'); // 切换删除按钮的显示状态 if (deleteBtn) { - if (deleteBtn.style.display === 'none' || deleteBtn.style.display === '') { - deleteBtn.style.display = 'block'; - } else { + console.log('Current delete button display style in setContent:', deleteBtn.style.display); + // 检查删除按钮当前是否可见 + const computedStyle = getComputedStyle(deleteBtn); + const isCurrentlyVisible = deleteBtn.style.display === 'block' || + computedStyle.display === 'block' || + (deleteBtn.style.display !== 'none' && + computedStyle.display !== 'none'); + + console.log('Delete button current styles - inline:', deleteBtn.style.display, 'computed:', computedStyle.display); + console.log('Delete button background image:', computedStyle.backgroundImage); + console.log('Delete button width:', computedStyle.width, 'height:', computedStyle.height); + console.log('Delete button position:', computedStyle.position); + console.log('Delete button z-index:', computedStyle.zIndex); + + if (isCurrentlyVisible) { deleteBtn.style.display = 'none'; + console.log('Delete button hidden in setContent'); + } else { + deleteBtn.style.display = 'block'; + console.log('Delete button displayed in setContent'); + // 添加调试样式以确保可见 + deleteBtn.style.backgroundColor = 'rgba(255, 0, 0, 0.3)'; // 半透明红色背景用于调试 } + } else { + console.log('Delete button not found in setContent'); } + } else { + console.log('Not a short tap or isLongPress is true in setContent'); } - }); + }; + + container.addEventListener('touchstart', newTouchStartHandler); + container.addEventListener('touchend', newTouchEndHandler); + + // 保存事件处理函数的引用,以便后续移除 + container._touchStartHandler = newTouchStartHandler; + container._touchEndHandler = newTouchEndHandler; + + img.setAttribute('data-touch-listeners', 'true') + console.log('Added touch event listeners') }) }, 0) } catch (error) { @@ -1602,6 +2194,14 @@ defineExpose({ try { editorRef.value.innerHTML = content.value console.log('Content set successfully after delay') + // 重置拖拽状态,确保isLongPress为false + dragState.value.isLongPress = false + dragState.value.draggedImage = null + dragState.value.startX = 0 + dragState.value.startY = 0 + dragState.value.currentY = 0 + // 确保所有图片都被正确包装在.image-container中 + wrapOrphanedImages() // 调整已有图片的高度并添加拖拽功能 adjustExistingImages() // 为图片添加拖拽事件监听器 @@ -1629,34 +2229,84 @@ defineExpose({ // 为删除按钮添加点击事件 const deleteBtn = container.querySelector('.image-delete-btn') if (deleteBtn) { + // 先移除可能已有的事件监听器,避免重复 + deleteBtn.removeEventListener('click', null) + deleteBtn.removeEventListener('touchend', null) + deleteBtn.addEventListener('click', function(e) { e.stopPropagation(); container.remove(); handleInput(); }); + + deleteBtn.addEventListener('touchend', function(e) { + e.stopPropagation(); + container.remove(); + handleInput(); + }); } // 为图片容器添加短按事件以显示/隐藏删除按钮 - let touchStartTime = 0; - container.addEventListener('touchstart', function(e) { - touchStartTime = Date.now(); - }); + // 先移除可能已有的事件监听器,避免重复 + const touchStartHandler = container._touchStartHandler; + const touchEndHandler = container._touchEndHandler; - container.addEventListener('touchend', function(e) { + if (touchStartHandler) { + container.removeEventListener('touchstart', touchStartHandler); + } + + if (touchEndHandler) { + container.removeEventListener('touchend', touchEndHandler); + } + + let touchStartTime = 0; + const newTouchStartHandler = function(e) { + touchStartTime = Date.now(); + }; + + const newTouchEndHandler = function(e) { const touchDuration = Date.now() - touchStartTime; + console.log('Touch end event triggered in delayed setContent, duration:', touchDuration, 'isLongPress:', dragState.value.isLongPress); // 短按(小于200ms)且非长按拖拽状态时切换删除按钮显示 if (touchDuration < 200 && !dragState.value.isLongPress) { e.stopPropagation(); + console.log('Short tap detected in delayed setContent, toggling delete button visibility'); // 切换删除按钮的显示状态 if (deleteBtn) { - if (deleteBtn.style.display === 'none' || deleteBtn.style.display === '') { - deleteBtn.style.display = 'block'; - } else { + console.log('Current delete button display style in delayed setContent:', deleteBtn.style.display); + // 检查删除按钮当前是否可见 + const computedStyle = getComputedStyle(deleteBtn); + const isCurrentlyVisible = deleteBtn.style.display === 'block' || + computedStyle.display === 'block' || + (deleteBtn.style.display !== 'none' && + computedStyle.display !== 'none'); + + console.log('Delete button current styles - inline:', deleteBtn.style.display, 'computed:', computedStyle.display); + + if (isCurrentlyVisible) { deleteBtn.style.display = 'none'; + console.log('Delete button hidden in delayed setContent'); + } else { + deleteBtn.style.display = 'block'; + console.log('Delete button displayed in delayed setContent'); } + } else { + console.log('Delete button not found in delayed setContent'); } + } else { + console.log('Not a short tap or isLongPress is true in delayed setContent'); } - }); + }; + + container.addEventListener('touchstart', newTouchStartHandler); + container.addEventListener('touchend', newTouchEndHandler); + + // 保存事件处理函数的引用,以便后续移除 + container._touchStartHandler = newTouchStartHandler; + container._touchEndHandler = newTouchEndHandler; + + img.setAttribute('data-touch-listeners', 'true') + console.log('Added touch event listeners') }) }, 0) } catch (error) { @@ -1909,9 +2559,15 @@ defineExpose({ width: 24px; height: 24px; cursor: pointer; - z-index: 10; + z-index: 1000; display: none; transition: opacity 0.2s ease; + /* 使用背景图片而不是背景色和边框,确保图标正确显示 */ + background-image: url('/assets/icons/drawable-xxhdpi/item_image_btn_unbrella_delete.png'); + background-size: contain; + background-repeat: no-repeat; + background-position: center; + background-color: transparent; /* 确保背景透明 */ } :deep(.editor-content .editor-image.draggable) { @@ -1919,8 +2575,8 @@ defineExpose({ } :deep(.editor-content .editor-image.dragging) { - opacity: 0.9; - transform: scale(0.98); + opacity: 0.85; + transform: scale(0.96); z-index: 999; transition: transform 0.15s cubic-bezier(0.25, 0.46, 0.45, 0.94), opacity 0.15s ease; /* 优化过渡效果 */ box-shadow: 0 12px 25px rgba(0, 0, 0, 0.22); @@ -1931,6 +2587,11 @@ defineExpose({ -ms-user-select: none; } +/* 图片交换动画 */ +:deep(.editor-content .editor-image.swap-animation) { + transition: transform 0.2s ease-out; +} + /* 待办事项样式 */ :deep(.todo-container) { display: flex;