Area拖拽指示器优化计划
This commit is contained in:
@@ -147,6 +147,9 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 组件卸载标记
|
||||||
|
const isUnmounted = ref(false)
|
||||||
|
|
||||||
// 使用全局事件总线和拖拽管理器
|
// 使用全局事件总线和拖拽管理器
|
||||||
|
|
||||||
// 本地状态
|
// 本地状态
|
||||||
@@ -446,6 +449,14 @@ const onDragStart = (e) => {
|
|||||||
|
|
||||||
// 添加鼠标移动和释放事件监听器
|
// 添加鼠标移动和释放事件监听器
|
||||||
const handleMouseMove = (moveEvent) => {
|
const handleMouseMove = (moveEvent) => {
|
||||||
|
// 检查组件是否已卸载
|
||||||
|
if (isUnmounted.value) {
|
||||||
|
// 组件已卸载,清理事件监听器
|
||||||
|
document.removeEventListener('mousemove', handleMouseMove)
|
||||||
|
document.removeEventListener('mouseup', handleMouseUp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 计算移动距离
|
// 计算移动距离
|
||||||
const deltaX = moveEvent.clientX - dragStartPos.value.x
|
const deltaX = moveEvent.clientX - dragStartPos.value.x
|
||||||
const deltaY = moveEvent.clientY - dragStartPos.value.y
|
const deltaY = moveEvent.clientY - dragStartPos.value.y
|
||||||
@@ -901,6 +912,9 @@ onMounted(() => {
|
|||||||
|
|
||||||
// 组件卸载时清理状态
|
// 组件卸载时清理状态
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
|
// 设置组件卸载标记
|
||||||
|
isUnmounted.value = true
|
||||||
|
|
||||||
// 清理拖拽和调整大小状态
|
// 清理拖拽和调整大小状态
|
||||||
isDragging.value = false
|
isDragging.value = false
|
||||||
currentDragId.value = null
|
currentDragId.value = null
|
||||||
|
|||||||
@@ -70,8 +70,8 @@
|
|||||||
<!-- 上指示:根据activeDockZone状态显示和高亮 -->
|
<!-- 上指示:根据activeDockZone状态显示和高亮 -->
|
||||||
<svg
|
<svg
|
||||||
v-if="!props.hideEdgeIndicators"
|
v-if="!props.hideEdgeIndicators"
|
||||||
width="41"
|
:width="edgeIndicatorStyle.indicatorTop.width"
|
||||||
height="41"
|
:height="edgeIndicatorStyle.indicatorTop.height"
|
||||||
viewBox="0 0 40 40"
|
viewBox="0 0 40 40"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="indicator-top"
|
class="indicator-top"
|
||||||
@@ -95,8 +95,8 @@
|
|||||||
<!-- 右指示:根据activeDockZone状态显示和高亮 -->
|
<!-- 右指示:根据activeDockZone状态显示和高亮 -->
|
||||||
<svg
|
<svg
|
||||||
v-if="!props.hideEdgeIndicators"
|
v-if="!props.hideEdgeIndicators"
|
||||||
width="41"
|
:width="edgeIndicatorStyle.indicatorRight.width"
|
||||||
height="41"
|
:height="edgeIndicatorStyle.indicatorRight.height"
|
||||||
viewBox="0 0 40 40"
|
viewBox="0 0 40 40"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="indicator-right"
|
class="indicator-right"
|
||||||
@@ -120,8 +120,8 @@
|
|||||||
<!-- 下指示:根据activeDockZone状态显示和高亮 -->
|
<!-- 下指示:根据activeDockZone状态显示和高亮 -->
|
||||||
<svg
|
<svg
|
||||||
v-if="!props.hideEdgeIndicators"
|
v-if="!props.hideEdgeIndicators"
|
||||||
width="41"
|
:width="edgeIndicatorStyle.indicatorBottom.width"
|
||||||
height="41"
|
:height="edgeIndicatorStyle.indicatorBottom.height"
|
||||||
viewBox="0 0 40 40"
|
viewBox="0 0 40 40"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="indicator-bottom"
|
class="indicator-bottom"
|
||||||
@@ -145,8 +145,8 @@
|
|||||||
<!-- 左指示:根据activeDockZone状态显示和高亮 -->
|
<!-- 左指示:根据activeDockZone状态显示和高亮 -->
|
||||||
<svg
|
<svg
|
||||||
v-if="!props.hideEdgeIndicators"
|
v-if="!props.hideEdgeIndicators"
|
||||||
width="41"
|
:width="edgeIndicatorStyle.indicatorLeft.width"
|
||||||
height="41"
|
:height="edgeIndicatorStyle.indicatorLeft.height"
|
||||||
viewBox="0 0 40 40"
|
viewBox="0 0 40 40"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="indicator-left"
|
class="indicator-left"
|
||||||
@@ -170,11 +170,11 @@
|
|||||||
|
|
||||||
|
|
||||||
<!-- 中心区域容器:包装中心指示区和中心指示器(当主区域内有其他Area时显示) -->
|
<!-- 中心区域容器:包装中心指示区和中心指示器(当主区域内有其他Area时显示) -->
|
||||||
<div v-if="!props.hideEdgeIndicators" class="center-dock-container">
|
<div v-if="!props.hideEdgeIndicators" class="center-dock-container" :style="centerContainerStyle">
|
||||||
<!-- 中心指示区:一直可见 -->
|
<!-- 中心指示区:一直可见 -->
|
||||||
<svg
|
<svg
|
||||||
width="235"
|
:width="centerContainerStyle.width"
|
||||||
height="235"
|
:height="centerContainerStyle.height"
|
||||||
viewBox="0 0 235 235"
|
viewBox="0 0 235 235"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="indicator-center-area"
|
class="indicator-center-area"
|
||||||
@@ -194,8 +194,8 @@
|
|||||||
|
|
||||||
<!-- 上区指示 -->
|
<!-- 上区指示 -->
|
||||||
<svg
|
<svg
|
||||||
width="41"
|
:width="edgeIndicatorStyle.indicatorTop.width"
|
||||||
height="41"
|
:height="edgeIndicatorStyle.indicatorTop.height"
|
||||||
viewBox="0 0 40 40"
|
viewBox="0 0 40 40"
|
||||||
aria-hidden="false"
|
aria-hidden="false"
|
||||||
class="dock-top-area"
|
class="dock-top-area"
|
||||||
@@ -224,8 +224,8 @@
|
|||||||
|
|
||||||
<!-- 右区指示 -->
|
<!-- 右区指示 -->
|
||||||
<svg
|
<svg
|
||||||
width="41"
|
:width="edgeIndicatorStyle.indicatorRight.width"
|
||||||
height="41"
|
:height="edgeIndicatorStyle.indicatorRight.height"
|
||||||
viewBox="0 0 40 40"
|
viewBox="0 0 40 40"
|
||||||
aria-hidden="false"
|
aria-hidden="false"
|
||||||
class="dock-right-area"
|
class="dock-right-area"
|
||||||
@@ -254,8 +254,8 @@
|
|||||||
|
|
||||||
<!-- 下区指示 -->
|
<!-- 下区指示 -->
|
||||||
<svg
|
<svg
|
||||||
width="41"
|
:width="edgeIndicatorStyle.indicatorBottom.width"
|
||||||
height="41"
|
:height="edgeIndicatorStyle.indicatorBottom.height"
|
||||||
viewBox="0 0 40 40"
|
viewBox="0 0 40 40"
|
||||||
aria-hidden="false"
|
aria-hidden="false"
|
||||||
class="dock-bottom-area"
|
class="dock-bottom-area"
|
||||||
@@ -284,8 +284,8 @@
|
|||||||
|
|
||||||
<!-- 左区指示 -->
|
<!-- 左区指示 -->
|
||||||
<svg
|
<svg
|
||||||
width="41"
|
:width="edgeIndicatorStyle.indicatorLeft.width"
|
||||||
height="41"
|
:height="edgeIndicatorStyle.indicatorLeft.height"
|
||||||
viewBox="0 0 40 40"
|
viewBox="0 0 40 40"
|
||||||
aria-hidden="false"
|
aria-hidden="false"
|
||||||
class="dock-left-area"
|
class="dock-left-area"
|
||||||
@@ -314,8 +314,8 @@
|
|||||||
|
|
||||||
<!-- 中心指示区上方指示器:位于中心指示区上边缘,距离上边框5像素 -->
|
<!-- 中心指示区上方指示器:位于中心指示区上边缘,距离上边框5像素 -->
|
||||||
<svg
|
<svg
|
||||||
width="41"
|
:width="edgeIndicatorStyle.indicatorTop.width"
|
||||||
height="41"
|
:height="edgeIndicatorStyle.indicatorTop.height"
|
||||||
viewBox="0 0 40 40"
|
viewBox="0 0 40 40"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="indicator-center-top"
|
class="indicator-center-top"
|
||||||
@@ -338,8 +338,8 @@
|
|||||||
|
|
||||||
<!-- 中心指示区右侧指示器:位于中心指示区右边缘,距离右边框5像素 -->
|
<!-- 中心指示区右侧指示器:位于中心指示区右边缘,距离右边框5像素 -->
|
||||||
<svg
|
<svg
|
||||||
width="41"
|
:width="edgeIndicatorStyle.indicatorRight.width"
|
||||||
height="41"
|
:height="edgeIndicatorStyle.indicatorRight.height"
|
||||||
viewBox="0 0 40 40"
|
viewBox="0 0 40 40"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="indicator-center-right"
|
class="indicator-center-right"
|
||||||
@@ -362,8 +362,8 @@
|
|||||||
|
|
||||||
<!-- 中心指示区下方指示器:位于中心指示区下边缘,距离下边框5像素 -->
|
<!-- 中心指示区下方指示器:位于中心指示区下边缘,距离下边框5像素 -->
|
||||||
<svg
|
<svg
|
||||||
width="41"
|
:width="edgeIndicatorStyle.indicatorBottom.width"
|
||||||
height="41"
|
:height="edgeIndicatorStyle.indicatorBottom.height"
|
||||||
viewBox="0 0 40 40"
|
viewBox="0 0 40 40"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="indicator-center-bottom"
|
class="indicator-center-bottom"
|
||||||
@@ -386,8 +386,8 @@
|
|||||||
|
|
||||||
<!-- 中心指示区左侧指示器:位于中心指示区左边缘,距离左边框5像素 -->
|
<!-- 中心指示区左侧指示器:位于中心指示区左边缘,距离左边框5像素 -->
|
||||||
<svg
|
<svg
|
||||||
width="41"
|
:width="edgeIndicatorStyle.indicatorLeft.width"
|
||||||
height="41"
|
:height="edgeIndicatorStyle.indicatorLeft.height"
|
||||||
viewBox="0 0 40 40"
|
viewBox="0 0 40 40"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="indicator-center-left"
|
class="indicator-center-left"
|
||||||
@@ -444,9 +444,13 @@ const props = defineProps({
|
|||||||
visible: Boolean,
|
visible: Boolean,
|
||||||
targetRect: Object,
|
targetRect: Object,
|
||||||
mousePosition: Object,
|
mousePosition: Object,
|
||||||
hideEdgeIndicators: Boolean
|
hideEdgeIndicators: Boolean,
|
||||||
|
targetAreaId: String // 新增:最上层的目标Area ID
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 调试模式开关
|
||||||
|
const debugMode = ref(false)
|
||||||
|
|
||||||
// 创建响应式的hoveredZone状态
|
// 创建响应式的hoveredZone状态
|
||||||
const hoveredZone = ref(null)
|
const hoveredZone = ref(null)
|
||||||
// 鼠标是否悬停在中心指示器上(用于控制中心指示区的显示)
|
// 鼠标是否悬停在中心指示器上(用于控制中心指示区的显示)
|
||||||
@@ -455,6 +459,24 @@ const isCenterIndicatorHovered = ref(false)
|
|||||||
let mouseLeaveTimer = null
|
let mouseLeaveTimer = null
|
||||||
let centerLeaveTimer = 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) => {
|
const handleMouseEnter = (zone) => {
|
||||||
// 清除可能存在的离开定时器
|
// 清除可能存在的离开定时器
|
||||||
@@ -519,37 +541,136 @@ const handleSubAreaMouseMove = (area, event) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 检测鼠标所在的Area组件
|
// 检测鼠标所在的Area组件
|
||||||
const detectMouseArea = (mouseX, mouseY) => {
|
const detectMouseArea = (mouseX, mouseY, excludeAreaId = null) => {
|
||||||
try {
|
try {
|
||||||
// 查找所有包含Area组件的DOM元素
|
// 调试日志
|
||||||
const areaElements = document.querySelectorAll('.vs-area')
|
if (debugMode.value) {
|
||||||
|
console.log('🖱️ 鼠标位置:', {x: mouseX, y: mouseY}, '排除ID:', excludeAreaId);
|
||||||
|
}
|
||||||
|
// 如果提供了targetAreaId,直接使用(优先使用)
|
||||||
|
if (props.targetAreaId) {
|
||||||
|
// 避免返回被排除的Area
|
||||||
|
if (excludeAreaId && props.targetAreaId === excludeAreaId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
for (let element of areaElements) {
|
const targetElement = document.querySelector(`[data-area-id="${props.targetAreaId}"]`);
|
||||||
const rect = element.getBoundingClientRect()
|
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存在但元素不存在或鼠标不在其中,继续使用其他方法检测
|
||||||
|
}
|
||||||
|
|
||||||
// 检查鼠标位置是否在当前Area元素内
|
// 优化:使用document.elementsFromPoint获取鼠标位置下的所有元素,按视觉层级排序
|
||||||
if (mouseX >= rect.left && mouseX <= rect.right &&
|
try {
|
||||||
mouseY >= rect.top && mouseY <= rect.bottom) {
|
// 获取鼠标位置下的所有元素,按视觉层级从上到下排序
|
||||||
return {
|
const elementsAtPoint = document.elementsFromPoint(mouseX, mouseY);
|
||||||
element: element,
|
|
||||||
rect: rect,
|
// 调试日志
|
||||||
id: element.getAttribute('data-area-id') || element.dataset.id || 'unknown'
|
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) {
|
} catch (error) {
|
||||||
// console.warn('检测鼠标区域时出错:', error)
|
// console.warn('检测鼠标区域时出错:', error)
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 将detectMouseArea方法暴露为公共方法,允许父组件调用
|
||||||
|
defineExpose({
|
||||||
|
detectMouseArea
|
||||||
|
});
|
||||||
|
|
||||||
// 计算基于鼠标位置的半透明依靠区样式
|
// 计算基于鼠标位置的半透明依靠区样式
|
||||||
const computeRelianceAreaStyle = (area, zone) => {
|
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
|
const areaRect = area.rect
|
||||||
|
|
||||||
// 计算鼠标在Area内容区内的相对位置
|
// 计算鼠标在Area内容区内的相对位置
|
||||||
@@ -615,18 +736,37 @@ onUnmounted(() => {
|
|||||||
const indicatorStyle = computed(() => {
|
const indicatorStyle = computed(() => {
|
||||||
return {
|
return {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
left: `${props.targetRect.left}px`,
|
left: `${safeTargetRect.value.left}px`,
|
||||||
top: `${props.targetRect.top}px`,
|
top: `${safeTargetRect.value.top}px`,
|
||||||
width: `${props.targetRect.width}px`,
|
width: `${safeTargetRect.value.width}px`,
|
||||||
height: `${props.targetRect.height}px`,
|
height: `${safeTargetRect.value.height}px`,
|
||||||
pointerEvents: props.visible ? 'auto' : 'none',
|
pointerEvents: props.visible ? 'auto' : 'none',
|
||||||
zIndex: 9999
|
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(() => {
|
const activeDockZone = computed(() => {
|
||||||
if (!props.visible) return null
|
if (!props.visible || !shouldShowIndicators.value) return null
|
||||||
|
|
||||||
// 只有当鼠标悬停在某个指示器上时才返回对应的区域
|
// 只有当鼠标悬停在某个指示器上时才返回对应的区域
|
||||||
return hoveredZone.value
|
return hoveredZone.value
|
||||||
@@ -636,7 +776,7 @@ const activeDockZone = computed(() => {
|
|||||||
const previewAreaStyle = computed(() => {
|
const previewAreaStyle = computed(() => {
|
||||||
if (!activeDockZone.value) return {};
|
if (!activeDockZone.value) return {};
|
||||||
|
|
||||||
const { left, top, width, height } = props.targetRect;
|
const { left, top, width, height } = safeTargetRect.value;
|
||||||
const threshold = 0.25;
|
const threshold = 0.25;
|
||||||
|
|
||||||
// 根据不同的活动区域计算预览区域的样式
|
// 根据不同的活动区域计算预览区域的样式
|
||||||
@@ -711,6 +851,103 @@ const enhancedPreviewAreaStyle = computed(() => {
|
|||||||
return previewAreaStyle.value
|
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
|
// 使用事件总线替代直接emit
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,15 @@
|
|||||||
<div class="dock-layout" ref="dockLayoutRef" style="display: flex; flex-direction: column; position: relative; width: 100%; height: 100%;">
|
<div class="dock-layout" ref="dockLayoutRef" style="display: flex; flex-direction: column; position: relative; width: 100%; height: 100%;">
|
||||||
<!-- 停靠指示器组件 - 设置高z-index确保显示在最顶层 -->
|
<!-- 停靠指示器组件 - 设置高z-index确保显示在最顶层 -->
|
||||||
<DockIndicator
|
<DockIndicator
|
||||||
|
ref="dockIndicator"
|
||||||
:visible="showDockIndicator"
|
:visible="showDockIndicator"
|
||||||
:target-rect="targetAreaRect"
|
:target-rect="targetAreaRect"
|
||||||
:mouse-position="currentMousePosition"
|
:mouse-position="currentMousePosition"
|
||||||
:hide-edge-indicators="hideEdgeIndicators"
|
:hide-edge-indicators="hideEdgeIndicators"
|
||||||
|
:target-area-id="targetAreaId" <!-- 新增: 传递最上层的目标Area ID -->
|
||||||
@zone-active="onDockZoneActive"
|
@zone-active="onDockZoneActive"
|
||||||
style="z-index: 9999;"
|
style="z-index: 9999;"
|
||||||
/>
|
></DockIndicator>
|
||||||
|
|
||||||
<!-- 主区域使用Render组件统一渲染 -->
|
<!-- 主区域使用Render组件统一渲染 -->
|
||||||
<div class="main-area-container" style="position: relative; width: 100%; height: 100%;">
|
<div class="main-area-container" style="position: relative; width: 100%; height: 100%;">
|
||||||
@@ -46,24 +48,21 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted, onUnmounted, defineEmits } from 'vue'
|
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 DockIndicator from './DockIndicator.vue';
|
||||||
import ResizeBar from './ResizeBar.vue';
|
import ResizeBar from './ResizeBar.vue';
|
||||||
import Render from './Render.vue';
|
import Render from './Render.vue';
|
||||||
|
|
||||||
import { zIndexManager } from './dockLayers';
|
import { zIndexManager } from './dockLayers';
|
||||||
import { eventBus, EVENT_TYPES, emitEvent } from './eventBus';
|
import { eventBus, EVENT_TYPES, emitEvent } from './eventBus';
|
||||||
import { areaActions } from './handlers/AreaHandler';
|
import { getAreaHandler, areaActions } from './handlers/AreaHandler';
|
||||||
import { dragStateActions } from './handlers/DragStateManager';
|
import { dragStateActions } from './handlers/DragStateManager';
|
||||||
import { panelActions } from './handlers/PanelHandler';
|
import { panelActions } from './handlers/PanelHandler';
|
||||||
import { tabPageActions } from './handlers/TabPageHandler';
|
import { tabPageActions } from './handlers/TabPageHandler';
|
||||||
import { globalEventActions } from './handlers/GlobalEventManager';
|
import { globalEventActions } from './handlers/GlobalEventManager';
|
||||||
|
import { debounce } from '../utils/debounce';
|
||||||
|
|
||||||
|
// 获取AreaHandler实例
|
||||||
|
const areaHandler = getAreaHandler();
|
||||||
|
|
||||||
// 定义组件可以发出的事件
|
// 定义组件可以发出的事件
|
||||||
const emit = defineEmits([
|
const emit = defineEmits([
|
||||||
@@ -107,6 +106,12 @@ const showDockIndicator = ref(false)
|
|||||||
const currentMousePosition = ref({ x: 0, y: 0 })
|
const currentMousePosition = ref({ x: 0, y: 0 })
|
||||||
const targetAreaRect = ref({ left: 0, top: 0, width: 0, height: 0 })
|
const targetAreaRect = ref({ left: 0, top: 0, width: 0, height: 0 })
|
||||||
const activeDockZone = ref(null)
|
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列表
|
// 主区域ResizeBar列表
|
||||||
const mainAreaResizeBars = ref([])
|
const mainAreaResizeBars = ref([])
|
||||||
@@ -433,6 +438,68 @@ const handleAreaDragLeave = (event) => {
|
|||||||
globalEventActions.handleDragLeave('floating-area');
|
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) => {
|
const onPanelDragStart = async (event) => {
|
||||||
console.log(`📈 收到面板拖拽开始事件:`, { event });
|
console.log(`📈 收到面板拖拽开始事件:`, { event });
|
||||||
@@ -967,7 +1034,7 @@ const addFloatingPanel = (panel) => {
|
|||||||
children: {
|
children: {
|
||||||
type: 'TabPage',
|
type: 'TabPage',
|
||||||
id: `tabPage-${areaId}`,
|
id: `tabPage-${areaId}`,
|
||||||
tabPosition: ['top', 'right', 'bottom', 'left'][Math.floor(Math.random() * 4)],
|
tabPosition: 'top',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
...safePanel,
|
...safePanel,
|
||||||
@@ -1113,7 +1180,7 @@ const handleRisingEvent = async (data, event) => {
|
|||||||
|
|
||||||
// 面板拖拽开始处理
|
// 面板拖拽开始处理
|
||||||
const handlePanelDragStart = async (data) => {
|
const handlePanelDragStart = async (data) => {
|
||||||
if (debugMode) {
|
if (debugMode.value) {
|
||||||
console.log('👋 处理面板拖拽开始:', data);
|
console.log('👋 处理面板拖拽开始:', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1147,7 +1214,7 @@ const handlePanelDragStart = async (data) => {
|
|||||||
|
|
||||||
// 面板拖拽移动处理
|
// 面板拖拽移动处理
|
||||||
const handlePanelDragMove = async (data) => {
|
const handlePanelDragMove = async (data) => {
|
||||||
if (debugMode) {
|
if (debugMode.value) {
|
||||||
console.log('✋ 处理面板拖拽移动:', data);
|
console.log('✋ 处理面板拖拽移动:', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1175,7 +1242,7 @@ const handlePanelDragMove = async (data) => {
|
|||||||
|
|
||||||
// 面板拖拽结束处理
|
// 面板拖拽结束处理
|
||||||
const handlePanelDragEnd = async (data) => {
|
const handlePanelDragEnd = async (data) => {
|
||||||
if (debugMode) {
|
if (debugMode.value) {
|
||||||
console.log('✋ 处理面板拖拽结束:', data);
|
console.log('✋ 处理面板拖拽结束:', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1206,7 +1273,7 @@ const handlePanelDragEnd = async (data) => {
|
|||||||
|
|
||||||
// 面板拖拽取消处理
|
// 面板拖拽取消处理
|
||||||
const handlePanelDragCancel = async (data) => {
|
const handlePanelDragCancel = async (data) => {
|
||||||
if (debugMode) {
|
if (debugMode.value) {
|
||||||
console.log('✋ 处理面板拖拽取消:', data);
|
console.log('✋ 处理面板拖拽取消:', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1222,10 +1289,14 @@ const handlePanelDragCancel = async (data) => {
|
|||||||
|
|
||||||
// 区域拖拽开始处理
|
// 区域拖拽开始处理
|
||||||
const handleAreaDragStart = async (data) => {
|
const handleAreaDragStart = async (data) => {
|
||||||
if (debugMode) {
|
if (debugMode.value) {
|
||||||
console.log('👋 处理区域拖拽开始:', data);
|
console.log('👋 处理区域拖拽开始:', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 初始化指示器状态
|
||||||
|
targetAreaId.value = null;
|
||||||
|
targetAreaRect.value = { left: 0, top: 0, width: 0, height: 0 };
|
||||||
|
|
||||||
// 发送区域拖拽状态更新事件(下降事件)
|
// 发送区域拖拽状态更新事件(下降事件)
|
||||||
eventBus.emit('area.drag.state.update', {
|
eventBus.emit('area.drag.state.update', {
|
||||||
...data,
|
...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) => {
|
const handleAreaDragMove = async (data) => {
|
||||||
if (debugMode) {
|
if (debugMode.value) {
|
||||||
console.log('✋ 处理区域拖拽移动:', data);
|
console.log('✋ 处理区域拖拽移动:', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 新增:在前端组件层识别目标Area ID(DOM操作)
|
||||||
|
// 使用防抖优化的区域检测
|
||||||
|
debouncedDetectMouseArea(data.position, data.areaId);
|
||||||
|
|
||||||
|
// 新增:更新当前鼠标位置
|
||||||
|
currentMousePosition.value = data.position;
|
||||||
|
|
||||||
// 发送区域拖拽状态更新事件(下降事件)
|
// 发送区域拖拽状态更新事件(下降事件)
|
||||||
eventBus.emit('area.drag.state.update', {
|
eventBus.emit('area.drag.state.update', {
|
||||||
...data,
|
...data,
|
||||||
status: 'moving'
|
status: 'moving',
|
||||||
|
targetAreaId: targetAreaId.value, // 新增:最上层的目标Area ID
|
||||||
|
targetRect: targetAreaRect.value // 新增:目标Area的矩形区域
|
||||||
});
|
});
|
||||||
|
|
||||||
// 发送区域位置更新事件(下降事件)
|
// 发送区域位置更新事件(下降事件)
|
||||||
eventBus.emit('area.position.update', {
|
eventBus.emit('area.position.update', {
|
||||||
...data,
|
...data,
|
||||||
position: data.position
|
position: data.position,
|
||||||
|
targetAreaId: targetAreaId.value, // 新增:最上层的目标Area ID
|
||||||
|
targetRect: targetAreaRect.value // 新增:目标Area的矩形区域
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 区域拖拽结束处理
|
// 区域拖拽结束处理
|
||||||
const handleAreaDragEnd = async (data) => {
|
const handleAreaDragEnd = async (data) => {
|
||||||
if (debugMode) {
|
if (debugMode.value) {
|
||||||
console.log('✋ 处理区域拖拽结束:', data);
|
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', {
|
eventBus.emit('area.drag.state.update', {
|
||||||
...data,
|
...data,
|
||||||
status: 'ended'
|
status: 'ended',
|
||||||
|
targetAreaId: null,
|
||||||
|
targetRect: { left: 0, top: 0, width: 0, height: 0 }
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 区域拖拽取消处理
|
// 区域拖拽取消处理
|
||||||
const handleAreaDragCancel = async (data) => {
|
const handleAreaDragCancel = async (data) => {
|
||||||
if (debugMode) {
|
if (debugMode.value) {
|
||||||
console.log('✋ 处理区域拖拽取消:', data);
|
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', {
|
eventBus.emit('area.drag.state.update', {
|
||||||
...data,
|
...data,
|
||||||
status: 'cancelled'
|
status: 'cancelled',
|
||||||
|
targetAreaId: null,
|
||||||
|
targetRect: { left: 0, top: 0, width: 0, height: 0 }
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 面板resize处理
|
// 面板resize处理
|
||||||
const handlePanelResizeStart = async (data) => {
|
const handlePanelResizeStart = async (data) => {
|
||||||
if (debugMode) {
|
if (debugMode.value) {
|
||||||
console.log('👋 处理面板resize开始:', data);
|
console.log('✋ 处理面板resize开始:', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
eventBus.emit(EVENT_TYPES.AREA_RESIZE_START, {
|
eventBus.emit(EVENT_TYPES.AREA_RESIZE_START, {
|
||||||
@@ -1291,7 +1429,7 @@ const handlePanelResizeStart = async (data) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handlePanelResizeMove = async (data) => {
|
const handlePanelResizeMove = async (data) => {
|
||||||
if (debugMode) {
|
if (debugMode.value) {
|
||||||
console.log('✋ 处理面板resize移动:', data);
|
console.log('✋ 处理面板resize移动:', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1307,7 +1445,7 @@ const handlePanelResizeMove = async (data) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handlePanelResizeEnd = async (data) => {
|
const handlePanelResizeEnd = async (data) => {
|
||||||
if (debugMode) {
|
if (debugMode.value) {
|
||||||
console.log('✋ 处理面板resize结束:', data);
|
console.log('✋ 处理面板resize结束:', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1319,7 +1457,7 @@ const handlePanelResizeEnd = async (data) => {
|
|||||||
|
|
||||||
// 区域resize处理
|
// 区域resize处理
|
||||||
const handleAreaResizeStart = async (data) => {
|
const handleAreaResizeStart = async (data) => {
|
||||||
if (debugMode) {
|
if (debugMode.value) {
|
||||||
console.log('👋 处理区域resize开始:', data);
|
console.log('👋 处理区域resize开始:', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1330,7 +1468,7 @@ const handleAreaResizeStart = async (data) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleAreaResizeMove = async (data) => {
|
const handleAreaResizeMove = async (data) => {
|
||||||
if (debugMode) {
|
if (debugMode.value) {
|
||||||
console.log('✋ 处理区域resize移动:', data);
|
console.log('✋ 处理区域resize移动:', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1346,7 +1484,7 @@ const handleAreaResizeMove = async (data) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleAreaResizeEnd = async (data) => {
|
const handleAreaResizeEnd = async (data) => {
|
||||||
if (debugMode) {
|
if (debugMode.value) {
|
||||||
console.log('✋ 处理区域resize结束:', data);
|
console.log('✋ 处理区域resize结束:', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1358,7 +1496,7 @@ const handleAreaResizeEnd = async (data) => {
|
|||||||
|
|
||||||
// 标签页拖拽处理
|
// 标签页拖拽处理
|
||||||
const handleTabPageDragStart = async (data) => {
|
const handleTabPageDragStart = async (data) => {
|
||||||
if (debugMode) {
|
if (debugMode.value) {
|
||||||
console.log('👋 处理标签页拖拽开始:', data);
|
console.log('👋 处理标签页拖拽开始:', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1370,7 +1508,7 @@ const handleTabPageDragStart = async (data) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleTabPageDragMove = async (data) => {
|
const handleTabPageDragMove = async (data) => {
|
||||||
if (debugMode) {
|
if (debugMode.value) {
|
||||||
console.log('✋ 处理标签页拖拽移动:', data);
|
console.log('✋ 处理标签页拖拽移动:', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1381,7 +1519,7 @@ const handleTabPageDragMove = async (data) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleTabPageDragEnd = async (data) => {
|
const handleTabPageDragEnd = async (data) => {
|
||||||
if (debugMode) {
|
if (debugMode.value) {
|
||||||
console.log('✋ 处理标签页拖拽结束:', data);
|
console.log('✋ 处理标签页拖拽结束:', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1392,7 +1530,7 @@ const handleTabPageDragEnd = async (data) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleTabPageDragCancel = async (data) => {
|
const handleTabPageDragCancel = async (data) => {
|
||||||
if (debugMode) {
|
if (debugMode.value) {
|
||||||
console.log('✋ 处理标签页拖拽取消:', data);
|
console.log('✋ 处理标签页拖拽取消:', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1512,6 +1650,8 @@ onUnmounted(() => {
|
|||||||
// 清理事件监听器和其他资源
|
// 清理事件监听器和其他资源
|
||||||
console.log('DockLayout component unmounted');
|
console.log('DockLayout component unmounted');
|
||||||
cleanup();
|
cleanup();
|
||||||
|
// 清理防抖函数
|
||||||
|
debouncedDetectMouseArea.cancel();
|
||||||
// 逐个移除事件监听器
|
// 逐个移除事件监听器
|
||||||
unsubscribeFunctions.value.forEach(unsubscribe => unsubscribe());
|
unsubscribeFunctions.value.forEach(unsubscribe => unsubscribe());
|
||||||
unsubscribeFunctions.value = [];
|
unsubscribeFunctions.value = [];
|
||||||
|
|||||||
@@ -201,6 +201,92 @@ class AreaStateManager {
|
|||||||
return Array.from(this.floatingAreas.values());
|
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列表
|
* 获取隐藏Area列表
|
||||||
* @returns {Array} 隐藏Area列表
|
* @returns {Array} 隐藏Area列表
|
||||||
@@ -299,6 +385,52 @@ class AreaStateManager {
|
|||||||
historySize: this.history.length
|
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) => {
|
updateState: (areaId, updates) => {
|
||||||
areaHandler.areaStateManager.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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user