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:visible="boxVisible"
v-model:width="width" v-model:width="width"
v-model:height="codeBlockEditorHeight" v-model:height="codeBlockEditorHeight"
:body-style="{ padding: '0 16px' }"
:title="content.name ? `${disabled ? '查看' : '编辑'}${content.name}` : '新增代码'" :title="content.name ? `${disabled ? '查看' : '编辑'}${content.name}` : '新增代码'"
:position="boxPosition" :position="boxPosition"
:before-close="beforeClose" :before-close="beforeClose"

View File

@ -1,6 +1,12 @@
<template> <template>
<Teleport to="body" v-if="visible"> <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"> <div ref="title" class="m-editor-float-box-title">
<slot name="title"> <slot name="title">
<span>{{ title }}</span> <span>{{ title }}</span>
@ -9,7 +15,7 @@
<TMagicButton link size="small" @click="closeHandler"><MIcon :icon="Close"></MIcon></TMagicButton> <TMagicButton link size="small" @click="closeHandler"><MIcon :icon="Close"></MIcon></TMagicButton>
</div> </div>
</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> <slot name="body"></slot>
</div> </div>
</div> </div>
@ -17,7 +23,7 @@
</template> </template>
<script setup lang="ts"> <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 { Close } from '@element-plus/icons-vue';
import VanillaMoveable from 'moveable'; import VanillaMoveable from 'moveable';
@ -39,6 +45,7 @@ const props = withDefaults(
defineProps<{ defineProps<{
position?: Position; position?: Position;
title?: string; title?: string;
bodyStyle?: CSSProperties;
beforeClose?: (_done: (_cancel?: boolean) => void) => void; beforeClose?: (_done: (_cancel?: boolean) => void) => void;
}>(), }>(),
{ {
@ -84,6 +91,31 @@ const style = computed(() => {
let moveable: VanillaMoveable | null = null; 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 = () => { const initMoveable = () => {
moveable = new VanillaMoveable(globalThis.document.body, { moveable = new VanillaMoveable(globalThis.document.body, {
className: 'm-editor-floating-box-moveable', className: 'm-editor-floating-box-moveable',
@ -101,6 +133,11 @@ const initMoveable = () => {
bounds: { left: 0, top: 0, right: 0, bottom: 0, position: 'css' }, 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) => { moveable.on('drag', (e) => {
e.target.style.transform = e.transform; e.target.style.transform = e.transform;
}); });
@ -115,6 +152,7 @@ const initMoveable = () => {
}; };
const destroyMoveable = () => { const destroyMoveable = () => {
hideDragMask();
moveable?.destroy(); moveable?.destroy();
moveable = null; moveable = null;
}; };

View File

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

View File

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

View File

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

View File

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