Area拖拽指示器优化计划

This commit is contained in:
zqm
2026-01-20 09:27:36 +08:00
parent 28ab62bc03
commit e879a24afe
4 changed files with 637 additions and 85 deletions

View File

@@ -147,6 +147,9 @@ const props = defineProps({
}
})
// 组件卸载标记
const isUnmounted = ref(false)
// 使用全局事件总线和拖拽管理器
// 本地状态
@@ -446,6 +449,14 @@ const onDragStart = (e) => {
// 添加鼠标移动和释放事件监听器
const handleMouseMove = (moveEvent) => {
// 检查组件是否已卸载
if (isUnmounted.value) {
// 组件已卸载,清理事件监听器
document.removeEventListener('mousemove', handleMouseMove)
document.removeEventListener('mouseup', handleMouseUp)
return
}
// 计算移动距离
const deltaX = moveEvent.clientX - dragStartPos.value.x
const deltaY = moveEvent.clientY - dragStartPos.value.y
@@ -901,6 +912,9 @@ onMounted(() => {
// 组件卸载时清理状态
onUnmounted(() => {
// 设置组件卸载标记
isUnmounted.value = true
// 清理拖拽和调整大小状态
isDragging.value = false
currentDragId.value = null

View File

@@ -70,8 +70,8 @@
<!-- 上指示根据activeDockZone状态显示和高亮 -->
<svg
v-if="!props.hideEdgeIndicators"
width="41"
height="41"
:width="edgeIndicatorStyle.indicatorTop.width"
:height="edgeIndicatorStyle.indicatorTop.height"
viewBox="0 0 40 40"
aria-hidden="true"
class="indicator-top"
@@ -95,8 +95,8 @@
<!-- 右指示根据activeDockZone状态显示和高亮 -->
<svg
v-if="!props.hideEdgeIndicators"
width="41"
height="41"
:width="edgeIndicatorStyle.indicatorRight.width"
:height="edgeIndicatorStyle.indicatorRight.height"
viewBox="0 0 40 40"
aria-hidden="true"
class="indicator-right"
@@ -120,8 +120,8 @@
<!-- 下指示根据activeDockZone状态显示和高亮 -->
<svg
v-if="!props.hideEdgeIndicators"
width="41"
height="41"
:width="edgeIndicatorStyle.indicatorBottom.width"
:height="edgeIndicatorStyle.indicatorBottom.height"
viewBox="0 0 40 40"
aria-hidden="true"
class="indicator-bottom"
@@ -145,8 +145,8 @@
<!-- 左指示根据activeDockZone状态显示和高亮 -->
<svg
v-if="!props.hideEdgeIndicators"
width="41"
height="41"
:width="edgeIndicatorStyle.indicatorLeft.width"
:height="edgeIndicatorStyle.indicatorLeft.height"
viewBox="0 0 40 40"
aria-hidden="true"
class="indicator-left"
@@ -170,11 +170,11 @@
<!-- 中心区域容器包装中心指示区和中心指示器当主区域内有其他Area时显示 -->
<div v-if="!props.hideEdgeIndicators" class="center-dock-container">
<div v-if="!props.hideEdgeIndicators" class="center-dock-container" :style="centerContainerStyle">
<!-- 中心指示区一直可见 -->
<svg
width="235"
height="235"
:width="centerContainerStyle.width"
:height="centerContainerStyle.height"
viewBox="0 0 235 235"
aria-hidden="true"
class="indicator-center-area"
@@ -194,8 +194,8 @@
<!-- 上区指示 -->
<svg
width="41"
height="41"
:width="edgeIndicatorStyle.indicatorTop.width"
:height="edgeIndicatorStyle.indicatorTop.height"
viewBox="0 0 40 40"
aria-hidden="false"
class="dock-top-area"
@@ -224,8 +224,8 @@
<!-- 右区指示 -->
<svg
width="41"
height="41"
:width="edgeIndicatorStyle.indicatorRight.width"
:height="edgeIndicatorStyle.indicatorRight.height"
viewBox="0 0 40 40"
aria-hidden="false"
class="dock-right-area"
@@ -254,8 +254,8 @@
<!-- 下区指示 -->
<svg
width="41"
height="41"
:width="edgeIndicatorStyle.indicatorBottom.width"
:height="edgeIndicatorStyle.indicatorBottom.height"
viewBox="0 0 40 40"
aria-hidden="false"
class="dock-bottom-area"
@@ -284,8 +284,8 @@
<!-- 左区指示 -->
<svg
width="41"
height="41"
:width="edgeIndicatorStyle.indicatorLeft.width"
:height="edgeIndicatorStyle.indicatorLeft.height"
viewBox="0 0 40 40"
aria-hidden="false"
class="dock-left-area"
@@ -314,8 +314,8 @@
<!-- 中心指示区上方指示器位于中心指示区上边缘距离上边框5像素 -->
<svg
width="41"
height="41"
:width="edgeIndicatorStyle.indicatorTop.width"
:height="edgeIndicatorStyle.indicatorTop.height"
viewBox="0 0 40 40"
aria-hidden="true"
class="indicator-center-top"
@@ -338,8 +338,8 @@
<!-- 中心指示区右侧指示器位于中心指示区右边缘距离右边框5像素 -->
<svg
width="41"
height="41"
:width="edgeIndicatorStyle.indicatorRight.width"
:height="edgeIndicatorStyle.indicatorRight.height"
viewBox="0 0 40 40"
aria-hidden="true"
class="indicator-center-right"
@@ -362,8 +362,8 @@
<!-- 中心指示区下方指示器位于中心指示区下边缘距离下边框5像素 -->
<svg
width="41"
height="41"
:width="edgeIndicatorStyle.indicatorBottom.width"
:height="edgeIndicatorStyle.indicatorBottom.height"
viewBox="0 0 40 40"
aria-hidden="true"
class="indicator-center-bottom"
@@ -386,8 +386,8 @@
<!-- 中心指示区左侧指示器位于中心指示区左边缘距离左边框5像素 -->
<svg
width="41"
height="41"
:width="edgeIndicatorStyle.indicatorLeft.width"
:height="edgeIndicatorStyle.indicatorLeft.height"
viewBox="0 0 40 40"
aria-hidden="true"
class="indicator-center-left"
@@ -444,9 +444,13 @@ const props = defineProps({
visible: Boolean,
targetRect: Object,
mousePosition: Object,
hideEdgeIndicators: Boolean
hideEdgeIndicators: Boolean,
targetAreaId: String // 新增最上层的目标Area ID
})
// 调试模式开关
const debugMode = ref(false)
// 创建响应式的hoveredZone状态
const hoveredZone = ref(null)
// 鼠标是否悬停在中心指示器上(用于控制中心指示区的显示)
@@ -455,6 +459,24 @@ const isCenterIndicatorHovered = ref(false)
let mouseLeaveTimer = null
let centerLeaveTimer = null
// 安全获取targetRect属性的辅助函数
const getSafeTargetRect = (targetRect) => {
if (!targetRect) return { left: 0, top: 0, width: 0, height: 0 };
// 只提取需要的标准CSS属性
const { left, top, width, height } = targetRect;
return {
left: left || 0,
top: top || 0,
width: width || 0,
height: height || 0
};
};
// 安全版本的targetRect过滤掉可能的无效属性
const safeTargetRect = computed(() => getSafeTargetRect(props.targetRect));
// 处理鼠标进入指示器
const handleMouseEnter = (zone) => {
// 清除可能存在的离开定时器
@@ -519,37 +541,136 @@ const handleSubAreaMouseMove = (area, event) => {
}
// 检测鼠标所在的Area组件
const detectMouseArea = (mouseX, mouseY) => {
const detectMouseArea = (mouseX, mouseY, excludeAreaId = null) => {
try {
// 查找所有包含Area组件的DOM元素
const areaElements = document.querySelectorAll('.vs-area')
for (let element of areaElements) {
const rect = element.getBoundingClientRect()
// 调试日志
if (debugMode.value) {
console.log('🖱️ 鼠标位置:', {x: mouseX, y: mouseY}, '排除ID:', excludeAreaId);
}
// 如果提供了targetAreaId直接使用优先使用
if (props.targetAreaId) {
// 避免返回被排除的Area
if (excludeAreaId && props.targetAreaId === excludeAreaId) {
return null;
}
// 检查鼠标位置是否在当前Area元素内
if (mouseX >= rect.left && mouseX <= rect.right &&
mouseY >= rect.top && mouseY <= rect.bottom) {
return {
element: element,
rect: rect,
id: element.getAttribute('data-area-id') || element.dataset.id || 'unknown'
const targetElement = document.querySelector(`[data-area-id="${props.targetAreaId}"]`);
if (targetElement) {
const rect = targetElement.getBoundingClientRect();
// 验证鼠标确实在该元素内
if (mouseX >= rect.left && mouseX <= rect.right &&
mouseY >= rect.top && mouseY <= rect.bottom) {
return {
element: targetElement,
rect: rect,
id: props.targetAreaId
};
}
}
// 如果targetAreaId存在但元素不存在或鼠标不在其中继续使用其他方法检测
}
// 优化使用document.elementsFromPoint获取鼠标位置下的所有元素按视觉层级排序
try {
// 获取鼠标位置下的所有元素,按视觉层级从上到下排序
const elementsAtPoint = document.elementsFromPoint(mouseX, mouseY);
// 调试日志
if (debugMode.value) {
console.log('📌 鼠标位置的元素:', elementsAtPoint.map(el => el.tagName + '.' + el.className).join(', '));
}
// 筛选出Area元素
for (let element of elementsAtPoint) {
// 检查元素是否为Area或其子元素
let areaElement = element;
while (areaElement && areaElement !== document.body) {
if (areaElement.classList.contains('vs-area')) {
// 确认Area组件使用data-area-id属性存储ID见Area.vue第7行
const areaId = areaElement.getAttribute('data-area-id');
// 避免返回被排除的Area
if (excludeAreaId && areaId === excludeAreaId) {
areaElement = areaElement.parentElement;
continue;
}
const rect = areaElement.getBoundingClientRect();
return {
element: areaElement,
rect: rect,
id: areaId || 'unknown'
};
}
areaElement = areaElement.parentElement;
}
}
} catch (error) {
// 如果elementsFromPoint不被支持回退到查找所有Area元素的方法
const areaElements = document.querySelectorAll('.vs-area');
// 调试日志
if (debugMode.value) {
console.log('📋 找到', areaElements.length, '个Area元素');
}
// 按视觉层级排序Area元素
const sortedAreaElements = Array.from(areaElements).sort((a, b) => {
// 修复正确处理z-index为'auto'的情况
const getZIndex = (element) => {
const zIndexValue = window.getComputedStyle(element).zIndex;
// 当zIndex为'auto'时视为0否则转换为整数
return zIndexValue === 'auto' ? 0 : parseInt(zIndexValue || '0');
};
const zIndexA = getZIndex(a);
const zIndexB = getZIndex(b);
// DOM树中后出现的元素视觉层级更高
return zIndexB - zIndexA || (a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? 1 : -1);
});
// 检查每个Area元素
for (let element of sortedAreaElements) {
const rect = element.getBoundingClientRect();
// 检查鼠标位置是否在当前Area元素内
if (mouseX >= rect.left && mouseX <= rect.right &&
mouseY >= rect.top && mouseY <= rect.bottom) {
// 确认Area组件使用data-area-id属性存储ID见Area.vue第7行
const areaId = element.getAttribute('data-area-id');
// 避免返回被排除的Area
if (excludeAreaId && areaId === excludeAreaId) {
continue;
}
return {
element: element,
rect: rect,
id: areaId || 'unknown'
};
}
}
}
return null
return null;
} catch (error) {
// console.warn('检测鼠标区域时出错:', error)
return null
return null;
}
}
// 将detectMouseArea方法暴露为公共方法允许父组件调用
defineExpose({
detectMouseArea
});
// 计算基于鼠标位置的半透明依靠区样式
const computeRelianceAreaStyle = (area, zone) => {
if (!area || !props.targetRect) return {}
if (!area || !safeTargetRect.value) return {}
const { left: targetLeft, top: targetTop, width: targetWidth, height: targetHeight } = props.targetRect
const { left: targetLeft, top: targetTop, width: targetWidth, height: targetHeight } = safeTargetRect.value
const areaRect = area.rect
// 计算鼠标在Area内容区内的相对位置
@@ -615,18 +736,37 @@ onUnmounted(() => {
const indicatorStyle = computed(() => {
return {
position: 'absolute',
left: `${props.targetRect.left}px`,
top: `${props.targetRect.top}px`,
width: `${props.targetRect.width}px`,
height: `${props.targetRect.height}px`,
left: `${safeTargetRect.value.left}px`,
top: `${safeTargetRect.value.top}px`,
width: `${safeTargetRect.value.width}px`,
height: `${safeTargetRect.value.height}px`,
pointerEvents: props.visible ? 'auto' : 'none',
zIndex: 9999
}
})
// 计算是否应该显示指示器基于目标Area大小
const shouldShowIndicators = computed(() => {
if (!props.visible || !safeTargetRect.value) return false;
const { width, height } = safeTargetRect.value;
const minDisplayAreaSize = 80; // 提高最小Area尺寸当Area太小时不显示指示器
// 只有当目标Area的宽度和高度都大于最小尺寸时才显示指示器
// 这样可以避免在太小的Area上显示不合理的指示器
const show = width >= minDisplayAreaSize && height >= minDisplayAreaSize;
// 调试日志
if (debugMode.value) {
console.log('📏 目标Area尺寸:', {width, height}, '是否显示指示器:', show);
}
return show;
});
// 计算活动的停靠区域 - 只有当鼠标悬停在指示器上时才返回对应的区域
const activeDockZone = computed(() => {
if (!props.visible) return null
if (!props.visible || !shouldShowIndicators.value) return null
// 只有当鼠标悬停在某个指示器上时才返回对应的区域
return hoveredZone.value
@@ -636,7 +776,7 @@ const activeDockZone = computed(() => {
const previewAreaStyle = computed(() => {
if (!activeDockZone.value) return {};
const { left, top, width, height } = props.targetRect;
const { left, top, width, height } = safeTargetRect.value;
const threshold = 0.25;
// 根据不同的活动区域计算预览区域的样式
@@ -711,6 +851,103 @@ const enhancedPreviewAreaStyle = computed(() => {
return previewAreaStyle.value
})
// 边缘指示器尺寸计算保持现有CSS定位方式不变
const edgeIndicatorStyle = computed(() => {
if (!safeTargetRect.value) return {};
const { width, height } = safeTargetRect.value;
// 基础尺寸定义:
// - baseWidth: 水平指示器(上下)的宽度,也是垂直指示器(左右)的高度
// - baseHeight: 水平指示器(上下)的高度,也是垂直指示器(左右)的宽度
const baseWidth = 40; // 基础水平宽度/垂直高度上下指示器宽40px左右指示器高40px
const baseHeight = 20; // 基础水平高度/垂直宽度上下指示器高20px左右指示器宽20px
// 根据目标区域尺寸调整指示器大小
const minTargetSize = 100; // 从100px开始缩小与最小显示尺寸80px保持协调
const maxIndicatorSizeRatio = 0.25; // 指示器最大尺寸不超过目标区域的25%确保在小Area上不过大
let indicatorWidth = baseWidth;
let indicatorHeight = baseHeight;
const targetMinSize = Math.min(width, height);
if (targetMinSize < minTargetSize) {
// 按比例缩小边缘指示器
const scaleFactor = targetMinSize / minTargetSize;
// 计算缩小后的尺寸
let scaledWidth = Math.floor(baseWidth * scaleFactor);
let scaledHeight = Math.floor(baseHeight * scaleFactor);
// 确保指示器不超过目标区域的最大比例
const maxWidth = Math.floor(width * maxIndicatorSizeRatio);
const maxHeight = Math.floor(height * maxIndicatorSizeRatio);
// 取较小值作为最终尺寸
indicatorWidth = Math.max(15, Math.min(scaledWidth, maxWidth)); // 降低最小宽度到15px
indicatorHeight = Math.max(8, Math.min(scaledHeight, maxHeight)); // 降低最小高度到8px
}
return {
// 边缘指示器保持现有CSS的定位方式使用top/right/bottom/left相对于容器边缘
// 仅调整大小不修改定位属性避免与现有CSS冲突
// 水平边缘指示器(上、下):宽 > 高
indicatorTop: {
width: `${indicatorWidth}px`, // 水平指示器宽度
height: `${indicatorHeight}px` // 水平指示器高度
},
indicatorBottom: {
width: `${indicatorWidth}px`, // 水平指示器宽度
height: `${indicatorHeight}px` // 水平指示器高度
},
// 垂直边缘指示器(左、右):高 > 宽
indicatorLeft: {
width: `${indicatorHeight}px`, // 垂直指示器宽度(使用水平指示器的高度)
height: `${indicatorWidth}px` // 垂直指示器高度(使用水平指示器的宽度)
},
indicatorRight: {
width: `${indicatorHeight}px`, // 垂直指示器宽度(使用水平指示器的高度)
height: `${indicatorWidth}px` // 垂直指示器高度(使用水平指示器的宽度)
}
};
});
// 中心区域容器尺寸计算
const centerContainerStyle = computed(() => {
if (!safeTargetRect.value) return {};
const { width, height } = safeTargetRect.value;
const baseSize = 235; // 基础尺寸
const minSize = 80; // 降低最小尺寸确保在小Area上也能显示
// 降低最大可用尺寸比例避免在小Area上指示器过大
const maxAvailableSize = Math.min(width, height) * 0.6;
// 确定实际尺寸(只缩小,不放大)
const actualSize = Math.min(baseSize, maxAvailableSize);
const size = Math.max(actualSize, minSize);
// 额外限制对于特别小的目标Area进一步缩小中心容器
const tinyAreaThreshold = 100;
let finalSize = size;
if (Math.min(width, height) < tinyAreaThreshold) {
const tinyScaleFactor = Math.min(width, height) / tinyAreaThreshold;
finalSize = Math.max(minSize, Math.floor(size * tinyScaleFactor));
}
return {
position: 'absolute',
top: '50%',
left: '50%',
transform: `translate(-50%, -50%)`, // 只使用translate避免冲突
zIndex: 9999,
width: `${finalSize}px`,
height: `${finalSize}px`,
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
};
});
// 定义事件
// 使用事件总线替代直接emit

View File

@@ -2,13 +2,15 @@
<div class="dock-layout" ref="dockLayoutRef" style="display: flex; flex-direction: column; position: relative; width: 100%; height: 100%;">
<!-- 停靠指示器组件 - 设置高z-index确保显示在最顶层 -->
<DockIndicator
ref="dockIndicator"
:visible="showDockIndicator"
:target-rect="targetAreaRect"
:mouse-position="currentMousePosition"
:hide-edge-indicators="hideEdgeIndicators"
:target-area-id="targetAreaId" <!-- 新增: 传递最上层的目标Area ID -->
@zone-active="onDockZoneActive"
style="z-index: 9999;"
/>
></DockIndicator>
<!-- 主区域使用Render组件统一渲染 -->
<div class="main-area-container" style="position: relative; width: 100%; height: 100%;">
@@ -46,24 +48,21 @@
<script setup>
import { ref, computed, onMounted, onUnmounted, defineEmits } from 'vue'
import Area from './Area.vue';
import { getAreaHandler } from './handlers/AreaHandler';
// 获取AreaHandler实例
const areaHandler = getAreaHandler();
import Panel from './Panel.vue';
import TabPage from './TabPage.vue';
import DockIndicator from './DockIndicator.vue';
import ResizeBar from './ResizeBar.vue';
import Render from './Render.vue';
import { zIndexManager } from './dockLayers';
import { eventBus, EVENT_TYPES, emitEvent } from './eventBus';
import { areaActions } from './handlers/AreaHandler';
import { getAreaHandler, areaActions } from './handlers/AreaHandler';
import { dragStateActions } from './handlers/DragStateManager';
import { panelActions } from './handlers/PanelHandler';
import { tabPageActions } from './handlers/TabPageHandler';
import { globalEventActions } from './handlers/GlobalEventManager';
import { debounce } from '../utils/debounce';
// 获取AreaHandler实例
const areaHandler = getAreaHandler();
// 定义组件可以发出的事件
const emit = defineEmits([
@@ -107,6 +106,12 @@ const showDockIndicator = ref(false)
const currentMousePosition = ref({ x: 0, y: 0 })
const targetAreaRect = ref({ left: 0, top: 0, width: 0, height: 0 })
const activeDockZone = ref(null)
// 新增目标Area ID用于存储最上层的目标Area ID
const targetAreaId = ref(null)
// 新增dockIndicator ref用于访问DockIndicator组件实例
const dockIndicator = ref(null)
// 新增DOM元素缓存减少document.querySelector的调用次数
const areaElementCache = new Map()
// 主区域ResizeBar列表
const mainAreaResizeBars = ref([])
@@ -433,6 +438,68 @@ const handleAreaDragLeave = (event) => {
globalEventActions.handleDragLeave('floating-area');
};
/**
* 利用现有detectMouseArea方法获取最上层的非被拖动Area
* @param {Object} position - 鼠标位置 {x, y}
* @param {string} draggedAreaId - 当前被拖动的Area ID
* @returns {string|null} 最上层Area的ID或null
*/
const _getTopAreaAtPosition = (position, draggedAreaId) => {
try {
// 调试日志
if (debugMode.value) {
console.log('🔍 尝试在位置', position, '查找Area排除ID:', draggedAreaId);
}
// 直接使用dockIndicator ref访问组件实例
if (!dockIndicator.value) return null;
// 使用DockIndicator实例的detectMouseArea方法获取鼠标位置的Area排除被拖动的Area
const areaInfo = dockIndicator.value.detectMouseArea(position.x, position.y, draggedAreaId);
// 验证是否找到Area且不是当前被拖动的Area
if (areaInfo && areaInfo.id !== draggedAreaId) {
return areaInfo.id;
}
return null;
} catch (error) {
console.error('获取鼠标位置Area失败:', error);
return null;
}
};
/**
* 根据Area ID获取对应的DOM元素的矩形区域
* @param {string|null} areaId - Area的ID
* @returns {DOMRect|null} Area元素的矩形区域或null
*/
const _getAreaRect = (areaId) => {
try {
// 如果areaId为null或undefined直接返回null
if (!areaId) {
return null;
}
// 查找DOM元素
const areaElement = document.querySelector(`[data-area-id="${areaId}"]`);
if (!areaElement) {
if (debugMode.value) {
console.warn('❌ 未找到ID为', areaId, '的Area元素');
}
return null;
}
// 获取元素的边界矩形
return areaElement.getBoundingClientRect();
} catch (error) {
if (debugMode.value) {
console.error('❌ 获取Area矩形区域时出错:', error);
}
return null;
}
};
// 处理面板拖拽开始事件
const onPanelDragStart = async (event) => {
console.log(`📈 收到面板拖拽开始事件:`, { event });
@@ -967,7 +1034,7 @@ const addFloatingPanel = (panel) => {
children: {
type: 'TabPage',
id: `tabPage-${areaId}`,
tabPosition: ['top', 'right', 'bottom', 'left'][Math.floor(Math.random() * 4)],
tabPosition: 'top',
children: [
{
...safePanel,
@@ -1113,7 +1180,7 @@ const handleRisingEvent = async (data, event) => {
// 面板拖拽开始处理
const handlePanelDragStart = async (data) => {
if (debugMode) {
if (debugMode.value) {
console.log('👋 处理面板拖拽开始:', data);
}
@@ -1147,7 +1214,7 @@ const handlePanelDragStart = async (data) => {
// 面板拖拽移动处理
const handlePanelDragMove = async (data) => {
if (debugMode) {
if (debugMode.value) {
console.log('✋ 处理面板拖拽移动:', data);
}
@@ -1175,7 +1242,7 @@ const handlePanelDragMove = async (data) => {
// 面板拖拽结束处理
const handlePanelDragEnd = async (data) => {
if (debugMode) {
if (debugMode.value) {
console.log('✋ 处理面板拖拽结束:', data);
}
@@ -1206,7 +1273,7 @@ const handlePanelDragEnd = async (data) => {
// 面板拖拽取消处理
const handlePanelDragCancel = async (data) => {
if (debugMode) {
if (debugMode.value) {
console.log('✋ 处理面板拖拽取消:', data);
}
@@ -1222,10 +1289,14 @@ const handlePanelDragCancel = async (data) => {
// 区域拖拽开始处理
const handleAreaDragStart = async (data) => {
if (debugMode) {
if (debugMode.value) {
console.log('👋 处理区域拖拽开始:', data);
}
// 初始化指示器状态
targetAreaId.value = null;
targetAreaRect.value = { left: 0, top: 0, width: 0, height: 0 };
// 发送区域拖拽状态更新事件(下降事件)
eventBus.emit('area.drag.state.update', {
...data,
@@ -1233,55 +1304,122 @@ const handleAreaDragStart = async (data) => {
});
};
// 防抖的区域检测函数,移到外部以便清理
const debouncedDetectMouseArea = debounce((position, areaId) => {
const detectedAreaId = _getTopAreaAtPosition(position, areaId);
// 调试日志
if (debugMode.value) {
console.log('🎯 检测到的目标Area ID:', detectedAreaId);
}
// 确保targetAreaId和targetAreaRect始终同步更新保持一致的状态
// 首先获取检测到的Area的位置信息
const detectedAreaRect = _getAreaRect(detectedAreaId);
// 根据检测到的结果更新指示器状态
if (detectedAreaId && detectedAreaRect) {
// 有检测到有效的目标Area
if (debugMode.value) {
console.log('📐 更新指示器位置:', detectedAreaRect);
}
// 更新指示器的目标Area和位置
targetAreaId.value = detectedAreaId;
targetAreaRect.value = detectedAreaRect;
} else {
// 没有检测到有效的目标Area
if (debugMode.value) {
console.log('❌ 未检测到有效目标Area隐藏指示器');
}
// 清空指示器状态
targetAreaId.value = null;
targetAreaRect.value = { left: 0, top: 0, width: 0, height: 0 };
}
}, 16); // ~60fps
// 区域拖拽移动处理
const handleAreaDragMove = async (data) => {
if (debugMode) {
if (debugMode.value) {
console.log('✋ 处理区域拖拽移动:', data);
}
// 新增在前端组件层识别目标Area IDDOM操作
// 使用防抖优化的区域检测
debouncedDetectMouseArea(data.position, data.areaId);
// 新增:更新当前鼠标位置
currentMousePosition.value = data.position;
// 发送区域拖拽状态更新事件(下降事件)
eventBus.emit('area.drag.state.update', {
...data,
status: 'moving'
status: 'moving',
targetAreaId: targetAreaId.value, // 新增最上层的目标Area ID
targetRect: targetAreaRect.value // 新增目标Area的矩形区域
});
// 发送区域位置更新事件(下降事件)
eventBus.emit('area.position.update', {
...data,
position: data.position
position: data.position,
targetAreaId: targetAreaId.value, // 新增最上层的目标Area ID
targetRect: targetAreaRect.value // 新增目标Area的矩形区域
});
};
// 区域拖拽结束处理
const handleAreaDragEnd = async (data) => {
if (debugMode) {
if (debugMode.value) {
console.log('✋ 处理区域拖拽结束:', data);
}
// 重置targetAreaId和targetAreaRect
targetAreaId.value = null;
targetAreaRect.value = { left: 0, top: 0, width: 0, height: 0 };
// 清理防抖函数
debouncedDetectMouseArea.cancel();
// 发送区域拖拽状态更新事件(下降事件)
eventBus.emit('area.drag.state.update', {
...data,
status: 'ended'
status: 'ended',
targetAreaId: null,
targetRect: { left: 0, top: 0, width: 0, height: 0 }
});
};
// 区域拖拽取消处理
const handleAreaDragCancel = async (data) => {
if (debugMode) {
if (debugMode.value) {
console.log('✋ 处理区域拖拽取消:', data);
}
// 重置targetAreaId和targetAreaRect
targetAreaId.value = null;
targetAreaRect.value = { left: 0, top: 0, width: 0, height: 0 };
// 清理防抖函数
debouncedDetectMouseArea.cancel();
// 发送区域拖拽状态更新事件(下降事件)
eventBus.emit('area.drag.state.update', {
...data,
status: 'cancelled'
status: 'cancelled',
targetAreaId: null,
targetRect: { left: 0, top: 0, width: 0, height: 0 }
});
};
// 面板resize处理
const handlePanelResizeStart = async (data) => {
if (debugMode) {
console.log('👋 处理面板resize开始:', data);
if (debugMode.value) {
console.log('✋ 处理面板resize开始:', data);
}
eventBus.emit(EVENT_TYPES.AREA_RESIZE_START, {
@@ -1291,7 +1429,7 @@ const handlePanelResizeStart = async (data) => {
};
const handlePanelResizeMove = async (data) => {
if (debugMode) {
if (debugMode.value) {
console.log('✋ 处理面板resize移动:', data);
}
@@ -1307,7 +1445,7 @@ const handlePanelResizeMove = async (data) => {
};
const handlePanelResizeEnd = async (data) => {
if (debugMode) {
if (debugMode.value) {
console.log('✋ 处理面板resize结束:', data);
}
@@ -1319,7 +1457,7 @@ const handlePanelResizeEnd = async (data) => {
// 区域resize处理
const handleAreaResizeStart = async (data) => {
if (debugMode) {
if (debugMode.value) {
console.log('👋 处理区域resize开始:', data);
}
@@ -1330,7 +1468,7 @@ const handleAreaResizeStart = async (data) => {
};
const handleAreaResizeMove = async (data) => {
if (debugMode) {
if (debugMode.value) {
console.log('✋ 处理区域resize移动:', data);
}
@@ -1346,7 +1484,7 @@ const handleAreaResizeMove = async (data) => {
};
const handleAreaResizeEnd = async (data) => {
if (debugMode) {
if (debugMode.value) {
console.log('✋ 处理区域resize结束:', data);
}
@@ -1358,7 +1496,7 @@ const handleAreaResizeEnd = async (data) => {
// 标签页拖拽处理
const handleTabPageDragStart = async (data) => {
if (debugMode) {
if (debugMode.value) {
console.log('👋 处理标签页拖拽开始:', data);
}
@@ -1370,7 +1508,7 @@ const handleTabPageDragStart = async (data) => {
};
const handleTabPageDragMove = async (data) => {
if (debugMode) {
if (debugMode.value) {
console.log('✋ 处理标签页拖拽移动:', data);
}
@@ -1381,7 +1519,7 @@ const handleTabPageDragMove = async (data) => {
};
const handleTabPageDragEnd = async (data) => {
if (debugMode) {
if (debugMode.value) {
console.log('✋ 处理标签页拖拽结束:', data);
}
@@ -1392,7 +1530,7 @@ const handleTabPageDragEnd = async (data) => {
};
const handleTabPageDragCancel = async (data) => {
if (debugMode) {
if (debugMode.value) {
console.log('✋ 处理标签页拖拽取消:', data);
}
@@ -1512,6 +1650,8 @@ onUnmounted(() => {
// 清理事件监听器和其他资源
console.log('DockLayout component unmounted');
cleanup();
// 清理防抖函数
debouncedDetectMouseArea.cancel();
// 逐个移除事件监听器
unsubscribeFunctions.value.forEach(unsubscribe => unsubscribe());
unsubscribeFunctions.value = [];

View File

@@ -201,6 +201,92 @@ class AreaStateManager {
return Array.from(this.floatingAreas.values());
}
/**
* 更新Area的z-index
* @param {string} areaId - Area ID
* @param {number} zIndex - 新的z-index值
*/
updateAreaZIndex(areaId, zIndex) {
const area = this.states.get(areaId);
if (!area) return false;
const updatedArea = {
...area,
zIndex: zIndex
};
this.states.set(areaId, updatedArea);
// 如果是浮动Area同步更新
if (this.floatingAreas.has(areaId)) {
this.floatingAreas.set(areaId, updatedArea);
}
// 触发z-index更新事件
eventBus.emit(EVENT_TYPES.Z_INDEX_UPDATE, {
areaId,
zIndex,
timestamp: Date.now()
});
return true;
}
/**
* 获取按z-index排序的Area列表
* @param {boolean} ascending - 是否按升序排序,默认为降序
* @returns {Array} 按z-index排序的Area列表
*/
getAreasByZIndex(ascending = false) {
const areas = Array.from(this.states.values());
// 按z-index排序
return areas.sort((a, b) => {
// 处理z-index为undefined或null的情况视为0
const zIndexA = a.zIndex || 0;
const zIndexB = b.zIndex || 0;
if (ascending) {
return zIndexA - zIndexB;
} else {
return zIndexB - zIndexA;
}
});
}
/**
* 获取指定位置的最上层Area
* @param {number} x - 鼠标X坐标
* @param {number} y - 鼠标Y坐标
* @param {string} excludeAreaId - 排除的Area ID
* @returns {Object|null} 最上层的Area或null
*/
getTopAreaAtPosition(x, y, excludeAreaId = null) {
// 获取所有Area
const areas = Array.from(this.states.values());
// 按z-index降序排序
const sortedAreas = areas.sort((a, b) => {
const zIndexA = a.zIndex || 0;
const zIndexB = b.zIndex || 0;
return zIndexB - zIndexA;
});
// 检查每个Area是否包含指定坐标
for (const area of sortedAreas) {
// 排除指定的Area
if (area.id === excludeAreaId) continue;
// 检查坐标是否在Area范围内
if (x >= area.left && x <= area.left + area.width &&
y >= area.top && y <= area.top + area.height) {
return area;
}
}
return null;
}
/**
* 获取隐藏Area列表
* @returns {Array} 隐藏Area列表
@@ -299,6 +385,52 @@ class AreaStateManager {
historySize: this.history.length
};
}
/**
* 更新Area的z-index
* @param {string} areaId - Area ID
* @param {number} zIndex - 新的z-index值
*/
updateAreaZIndex(areaId, zIndex) {
const currentState = this.states.get(areaId);
if (!currentState) return;
const newState = { ...currentState, zIndex, lastUpdated: Date.now() };
this.states.set(areaId, newState);
// 如果是浮动Area也更新floatingAreas中的数据
if (this.floatingAreas.has(areaId)) {
this.floatingAreas.set(areaId, { ...this.floatingAreas.get(areaId), zIndex });
}
// 触发z-index更新事件
eventBus.emit(EVENT_TYPES.AREA_FLOATING_ZINDEX_CHANGE, {
areaId,
zIndex,
timestamp: Date.now()
});
}
/**
* 获取按z-index排序的Area列表
* @param {boolean} ascending - 是否按升序排序默认为降序z-index越高越靠前
* @returns {Array} 按z-index排序的Area列表
*/
getAreasByZIndex(ascending = false) {
const areas = Array.from(this.states.values());
// 按z-index排序默认降序
return areas.sort((a, b) => {
const zIndexA = a.zIndex || 0;
const zIndexB = b.zIndex || 0;
if (ascending) {
return zIndexA - zIndexB;
} else {
return zIndexB - zIndexA;
}
});
}
}
/**
@@ -1539,6 +1671,35 @@ export const areaActions = {
*/
updateState: (areaId, updates) => {
areaHandler.areaStateManager.updateState(areaId, updates);
},
/**
* 更新Area的z-index
* @param {string} areaId - Area ID
* @param {number} zIndex - 新的z-index值
*/
updateZIndex: (areaId, zIndex) => {
areaHandler.areaStateManager.updateAreaZIndex(areaId, zIndex);
},
/**
* 获取按z-index排序的Area列表
* @param {boolean} ascending - 是否按升序排序默认为降序z-index越高越靠前
* @returns {Array} 按z-index排序的Area列表
*/
getAreasByZIndex: (ascending = false) => {
return areaHandler.areaStateManager.getAreasByZIndex(ascending);
},
/**
* 获取指定位置的最上层Area
* @param {number} x - 鼠标X坐标
* @param {number} y - 鼠标Y坐标
* @param {string} excludeAreaId - 排除的Area ID
* @returns {Object|null} 最上层的Area或null
*/
getTopAreaAtPosition: (x, y, excludeAreaId = null) => {
return areaHandler.areaStateManager.getTopAreaAtPosition(x, y, excludeAreaId);
}
};