From 9aa251ce57e36120a1f6c3b4835ba3bcd604448d Mon Sep 17 00:00:00 2001 From: roymondchen Date: Thu, 2 Jul 2026 20:50:03 +0800 Subject: [PATCH] =?UTF-8?q?fix(editor):=20=E5=BB=B6=E8=BF=9F=20FloatingBox?= =?UTF-8?q?=20=E6=8B=96=E6=8B=BD=E9=81=AE=E7=BD=A9=E5=88=B0=E5=AE=9E?= =?UTF-8?q?=E9=99=85=E4=BD=8D=E7=A7=BB=E6=97=B6=E6=98=BE=E7=A4=BA=EF=BC=8C?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=85=B3=E9=97=AD=E6=8C=89=E9=92=AE=E7=82=B9?= =?UTF-8?q?=E5=87=BB=E5=A4=B1=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../editor/src/components/FloatingBox.vue | 15 ++++++-- .../tests/unit/components/FloatingBox.spec.ts | 35 ++++++++++++++++--- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/packages/editor/src/components/FloatingBox.vue b/packages/editor/src/components/FloatingBox.vue index da0cbbc3..9b5e69d2 100644 --- a/packages/editor/src/components/FloatingBox.vue +++ b/packages/editor/src/components/FloatingBox.vue @@ -98,15 +98,20 @@ let moveable: VanillaMoveable | null = null; // 拖拽/缩放时用于覆盖 iframe 的遮罩,防止鼠标进入 iframe 区域后事件被 iframe 吞掉导致拖拽丢失 let dragMask: HTMLDivElement | null = null; +let dragMaskVisible = false; const showDragMask = () => { + if (dragMaskVisible) { + return; + } if (!dragMask) { dragMask = globalThis.document.createElement('div'); dragMask.className = 'm-editor-float-box-drag-mask'; } globalThis.document.body.appendChild(dragMask); + dragMaskVisible = true; - // 拖拽标题时,root 上的 @mousedown="nextZIndex" 会在 dragStart 之后把浮窗 z-index 抬高, + // 拖拽标题时,root 上的 @mousedown="nextZIndex" 会把浮窗 z-index 抬高, // 若此时才读取会拿到旧值导致遮罩被浮窗(及其内部 iframe)盖住,故用 nextTick 在 z-index 稳定后再设置到浮窗之上 const setMaskZIndex = () => { if (dragMask) { @@ -119,6 +124,7 @@ const showDragMask = () => { const hideDragMask = () => { dragMask?.parentNode?.removeChild(dragMask); + dragMaskVisible = false; }; const initMoveable = () => { @@ -138,8 +144,11 @@ const initMoveable = () => { bounds: { left: 0, top: 0, right: 0, bottom: 0, position: 'css' }, }); - moveable.on('dragStart', showDragMask); - moveable.on('resizeStart', showDragMask); + // 仅在真正发生拖拽/缩放位移时插入遮罩:moveable 的 dragStart/resizeStart 在 mousedown 时即触发, + // 若此时就盖遮罩,会盖住浮窗本身导致 mouseup 落在遮罩上、关闭按钮的 click 无法触发(点击关闭不了)。 + // 改为在 drag/resize(实际位移)时才显示,纯点击不再触发遮罩。 + moveable.on('drag', showDragMask); + moveable.on('resize', showDragMask); moveable.on('dragEnd', hideDragMask); moveable.on('resizeEnd', hideDragMask); diff --git a/packages/editor/tests/unit/components/FloatingBox.spec.ts b/packages/editor/tests/unit/components/FloatingBox.spec.ts index f864b02b..0609959e 100644 --- a/packages/editor/tests/unit/components/FloatingBox.spec.ts +++ b/packages/editor/tests/unit/components/FloatingBox.spec.ts @@ -9,10 +9,13 @@ import { mount } from '@vue/test-utils'; import FloatingBox from '@editor/components/FloatingBox.vue'; -const moveableHandlers = new Map void>(); +const moveableHandlers = new Map void)[]>(); const destroyMock = vi.fn(); let lastInstance: any; +const emitMoveable = (event: string, ...args: any[]) => + (moveableHandlers.get(event) || []).forEach((fn) => fn(...args)); + vi.mock('moveable', () => { class FakeMoveable { public target: any; @@ -26,7 +29,9 @@ vi.mock('moveable', () => { moveableHandlers.clear(); } public on(event: string, fn: (...args: any[]) => void) { - moveableHandlers.set(event, fn); + const list = moveableHandlers.get(event) || []; + list.push(fn); + moveableHandlers.set(event, list); return this; } public destroy() { @@ -144,7 +149,7 @@ describe('FloatingBox.vue', () => { }); await new Promise((r) => setTimeout(r, 0)); const target = document.createElement('div'); - moveableHandlers.get('resize')?.({ + emitMoveable('resize', { width: 200, height: 300, target, @@ -163,7 +168,7 @@ describe('FloatingBox.vue', () => { }); await new Promise((r) => setTimeout(r, 0)); const target = document.createElement('div'); - moveableHandlers.get('drag')?.({ target, transform: 'translate(10px,20px)' }); + emitMoveable('drag', { target, transform: 'translate(10px,20px)' }); expect(target.style.transform.replace(/\s+/g, '')).toBe('translate(10px,20px)'); }); @@ -237,4 +242,26 @@ describe('FloatingBox.vue', () => { wrapper.unmount(); expect(destroyMock).toHaveBeenCalled(); }); + + test('dragStart 时不显示拖拽遮罩,仅在真正 drag 时显示,dragEnd 时移除', async () => { + const wrapper = mount(FloatingBox as any, { + props: { visible: true }, + attachTo: document.body, + }); + await new Promise((r) => setTimeout(r, 0)); + await wrapper.vm.$nextTick(); + + // mousedown(dragStart)不应立即盖遮罩,否则会盖住关闭按钮导致点击失效 + emitMoveable('dragStart'); + expect(document.querySelector('.m-editor-float-box-drag-mask')).toBeNull(); + + // 真正发生位移时才显示遮罩 + emitMoveable('drag', { target: document.body, transform: 'translate(0,0)' }); + expect(document.querySelector('.m-editor-float-box-drag-mask')).not.toBeNull(); + + // 拖拽结束移除遮罩 + emitMoveable('dragEnd'); + expect(document.querySelector('.m-editor-float-box-drag-mask')).toBeNull(); + wrapper.unmount(); + }); });