fix(editor): 修复 FloatingBox 拖动时鼠标进入 iframe 区域事件丢失

- 拖拽/缩放开始时插入全屏透明遮罩盖住 iframe,结束时移除,避免事件被 iframe 吞掉
- 修正拖拽标题时 z-index 竞态导致遮罩被浮窗盖住的问题
- 将 body 内边距从 scss 抽取为 bodyStyle 透传,复用方按需自定义
This commit is contained in:
roymondchen 2026-07-02 20:00:43 +08:00
parent c57ef89715
commit 284be0d276
6 changed files with 55 additions and 4 deletions

View File

@ -4,6 +4,7 @@
v-model:visible="boxVisible"
v-model:width="width"
v-model:height="codeBlockEditorHeight"
:body-style="{ padding: '0 16px' }"
:title="content.name ? `${disabled ? '查看' : '编辑'}${content.name}` : '新增代码'"
:position="boxPosition"
:before-close="beforeClose"

View File

@ -1,6 +1,12 @@
<template>
<Teleport to="body" v-if="visible">
<div ref="target" class="m-editor-float-box" :style="{ ...style, zIndex: curZIndex }" @mousedown="nextZIndex">
<div
ref="target"
class="m-editor-float-box"
v-bind="$attrs"
:style="{ ...style, zIndex: curZIndex }"
@mousedown="nextZIndex"
>
<div ref="title" class="m-editor-float-box-title">
<slot name="title">
<span>{{ title }}</span>
@ -9,7 +15,7 @@
<TMagicButton link size="small" @click="closeHandler"><MIcon :icon="Close"></MIcon></TMagicButton>
</div>
</div>
<div class="m-editor-float-box-body" :style="{ height: `${bodyHeight}px` }">
<div class="m-editor-float-box-body" :style="{ height: `${bodyHeight}px`, ...bodyStyle }">
<slot name="body"></slot>
</div>
</div>
@ -17,7 +23,7 @@
</template>
<script setup lang="ts">
import { computed, nextTick, onBeforeUnmount, provide, ref, useTemplateRef, watch } from 'vue';
import { computed, type CSSProperties, nextTick, onBeforeUnmount, provide, ref, useTemplateRef, watch } from 'vue';
import { Close } from '@element-plus/icons-vue';
import VanillaMoveable from 'moveable';
@ -39,6 +45,7 @@ const props = withDefaults(
defineProps<{
position?: Position;
title?: string;
bodyStyle?: CSSProperties;
beforeClose?: (_done: (_cancel?: boolean) => void) => void;
}>(),
{
@ -84,6 +91,31 @@ const style = computed(() => {
let moveable: VanillaMoveable | null = null;
// / iframe iframe iframe
let dragMask: HTMLDivElement | null = null;
const showDragMask = () => {
if (!dragMask) {
dragMask = globalThis.document.createElement('div');
dragMask.className = 'm-editor-float-box-drag-mask';
}
globalThis.document.body.appendChild(dragMask);
// root @mousedown="nextZIndex" dragStart z-index
// iframe nextTick z-index
const setMaskZIndex = () => {
if (dragMask) {
dragMask.style.zIndex = `${curZIndex.value + 1}`;
}
};
setMaskZIndex();
nextTick(setMaskZIndex);
};
const hideDragMask = () => {
dragMask?.parentNode?.removeChild(dragMask);
};
const initMoveable = () => {
moveable = new VanillaMoveable(globalThis.document.body, {
className: 'm-editor-floating-box-moveable',
@ -101,6 +133,11 @@ const initMoveable = () => {
bounds: { left: 0, top: 0, right: 0, bottom: 0, position: 'css' },
});
moveable.on('dragStart', showDragMask);
moveable.on('resizeStart', showDragMask);
moveable.on('dragEnd', hideDragMask);
moveable.on('resizeEnd', hideDragMask);
moveable.on('drag', (e) => {
e.target.style.transform = e.transform;
});
@ -115,6 +152,7 @@ const initMoveable = () => {
};
const destroyMoveable = () => {
hideDragMask();
moveable?.destroy();
moveable = null;
};

View File

@ -8,6 +8,7 @@
</div>
<FloatingBox
:body-style="{ padding: '0 16px' }"
v-model:visible="addDialogVisible"
v-model:width="width"
v-model:height="editorHeight"
@ -28,6 +29,7 @@
</FloatingBox>
<FloatingBox
:body-style="{ padding: '0 16px' }"
v-model:visible="addFromJsonDialogVisible"
v-model:width="width"
v-model:height="editorHeight"

View File

@ -7,6 +7,7 @@
</div>
<FloatingBox
:body-style="{ padding: '0 16px' }"
v-model:visible="addDialogVisible"
v-model:width="width"
v-model:height="editorHeight"

View File

@ -1,5 +1,6 @@
<template>
<FloatingBox
:body-style="{ padding: '0 16px' }"
v-model:visible="boxVisible"
v-model:width="width"
v-model:height="editorHeight"

View File

@ -25,10 +25,18 @@
.m-editor-float-box-body {
overflow: auto;
flex: 1;
padding: 0 16px;
}
}
.m-editor-floating-box-moveable {
opacity: 0;
}
.m-editor-float-box-drag-mask {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: transparent;
}