设置停靠指示器显示在最顶层:将DockIndicator组件的z-index值从999提高到9999并在父组件中添加相同设置

This commit is contained in:
zqm
2025-11-07 15:41:44 +08:00
parent 054e499863
commit f19c0aba60
2 changed files with 533 additions and 0 deletions

View File

@@ -0,0 +1,399 @@
<template>
<div v-if="visible" class="dock-indicator" :style="indicatorStyle">
<!-- 上侧停靠指示器 -->
<div v-if="activeZones.top" class="dock-zone dock-zone-top" @dragenter="onZoneEnter('top')" @dragleave="onZoneLeave('top')" @dragover.prevent>
<div class="dock-zone-indicator">
<svg width="40" height="20" viewBox="0 0 40 20" aria-hidden="true">
<rect x="0" y="0" width="40" height="20" fill="rgba(79, 114, 179, 0.3)" />
<rect x="2" y="2" width="36" height="16" fill="rgba(79, 114, 179, 0.5)" />
</svg>
</div>
</div>
<!-- 左侧停靠指示器 -->
<div v-if="activeZones.left" class="dock-zone dock-zone-left" @dragenter="onZoneEnter('left')" @dragleave="onZoneLeave('left')" @dragover.prevent>
<div class="dock-zone-indicator">
<svg width="20" height="40" viewBox="0 0 20 40" aria-hidden="true">
<rect x="0" y="0" width="20" height="40" fill="rgba(79, 114, 179, 0.3)" />
<rect x="2" y="2" width="16" height="36" fill="rgba(79, 114, 179, 0.5)" />
</svg>
</div>
</div>
<!-- 右侧停靠指示器 -->
<div v-if="activeZones.right" class="dock-zone dock-zone-right" @dragenter="onZoneEnter('right')" @dragleave="onZoneLeave('right')" @dragover.prevent>
<div class="dock-zone-indicator">
<svg width="20" height="40" viewBox="0 0 20 40" aria-hidden="true">
<rect x="0" y="0" width="20" height="40" fill="rgba(79, 114, 179, 0.3)" />
<rect x="2" y="2" width="16" height="36" fill="rgba(79, 114, 179, 0.5)" />
</svg>
</div>
</div>
<!-- 下侧停靠指示器 -->
<div v-if="activeZones.bottom" class="dock-zone dock-zone-bottom" @dragenter="onZoneEnter('bottom')" @dragleave="onZoneLeave('bottom')" @dragover.prevent>
<div class="dock-zone-indicator">
<svg width="40" height="20" viewBox="0 0 40 20" aria-hidden="true">
<rect x="0" y="0" width="40" height="20" fill="rgba(79, 114, 179, 0.3)" />
<rect x="2" y="2" width="36" height="16" fill="rgba(79, 114, 179, 0.5)" />
</svg>
</div>
</div>
<!-- 中央停靠指示器 -->
<div v-if="activeZones.center" class="dock-zone dock-zone-center" @dragenter="onZoneEnter('center')" @dragleave="onZoneLeave('center')" @dragover.prevent>
<div class="dock-zone-indicator">
<svg width="60" height="60" viewBox="0 0 60 60" aria-hidden="true">
<rect x="0" y="0" width="60" height="60" fill="rgba(79, 114, 179, 0.3)" rx="5" />
<rect x="5" y="5" width="50" height="50" fill="rgba(79, 114, 179, 0.5)" rx="3" />
</svg>
</div>
</div>
<!-- 1. 定义可复用组件symbol封装所有渐变和路径只写一次 -->
<svg width="0" height="0" viewBox="0 0 40 40" aria-hidden="true">
<defs>
<!-- 渐变定义共用只写一次 -->
<linearGradient
id="lightGradient"
x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#DCE3F5" />
<stop offset="100%" stop-color="#B7BED1" />
</linearGradient>
<linearGradient
id="Area"
x1="50%" y1="0%" x2="50%" y2="100%">
<stop offset="0%" stop-color="#F0E2BC" />
<stop offset="100%" stop-color="#B09556" />
</linearGradient>
</defs>
<!-- 封装所有图形为 symbolid 用于后续调用 -->
<symbol id="shared-border" viewBox="0 0 40 40">
<path
fill="#61697E"
fill-rule="evenodd"
clip-rule="evenodd"
d="M0 0 L40 0 L40 40 L0 40 Z M1 1 L39 1 L39 39 L1 39 Z" />
<path
fill="#A1A9C4"
fill-rule="evenodd"
clip-rule="evenodd"
d="M1 1 L39 1 L39 39 L1 39 Z M4 5 L5 4 L35 4 L36 5 L36 35 L35 36 L5 36 L4 35 Z" />
<path
fill="#7E869C"
fill-rule="evenodd"
clip-rule="evenodd"
d="M4 5 L5 4 L35 4 L36 5 L36 35 L35 36 L5 36 L4 35 Z M6 7 L7 6 L33 6 L34 7 L34 33 L33 34 L7 34 L6 33 Z" />
<path
fill="url(#lightGradient)"
fill-rule="evenodd"
clip-rule="evenodd"
d="M6 7 L7 6 L33 6 L34 7 L34 33 L33 34 L7 34 L6 33 Z" />
</symbol>
<symbol id="shared-icon" viewBox="0 0 40 40">
<use xlink:href="#shared-border" />
<path
fill="#4C5E83"
fill-rule="evenodd"
clip-rule="evenodd"
d="M16 30 L20 26 L23 30 Z" />
</symbol>
</svg>
<!-- 2. 第一个 SVG不旋转直接调用共用组件 -->
<svg width="41" height="41" viewBox="0 0 40 40" aria-hidden="true">
<use xlink:href="#shared-icon" />
<path
fill="#4C5E83"
fill-rule="evenodd"
clip-rule="evenodd"
d="M8 8 L32 8 L32 20 L31 21 L9 21 L8 20 Z" />
<path
fill="url(#Area)"
fill-rule="evenodd"
clip-rule="evenodd"
d="M10 13 L30 13 L30 19 L10 19 Z" />
</svg>
<!-- 3. 第二个 SVG旋转90度调用同一共用组件 -->
<svg width="41" height="41" viewBox="0 0 40 40" aria-hidden="true">
<use xlink:href="#shared-icon" transform="rotate(90 20 20)" />
<path
fill="#4C5E83"
fill-rule="evenodd"
clip-rule="evenodd"
d="M19 8 L32 8 L32 31 L31 32 L20 32 L19 31 Z" />
<path
fill="url(#Area)"
fill-rule="evenodd"
clip-rule="evenodd"
d="M21 14 L30 14 L30 30 L21 30 Z" />
</svg>
<!-- 4. 第三个 SVG旋转180度调用同一共用组件 -->
<svg width="41" height="41" viewBox="0 0 40 40" aria-hidden="true">
<use xlink:href="#shared-icon" transform="rotate(180 20 20)" />
<path
fill="#4C5E83"
fill-rule="evenodd"
clip-rule="evenodd"
d="M8 19 L32 19 L32 31 L31 32 L9 32 L8 31 Z" />
<path
fill="url(#Area)"
fill-rule="evenodd"
clip-rule="evenodd"
d="M10 24 L30 24 L30 30 L10 30 Z" />
</svg>
<!-- 5. 第四个 SVG旋转270度调用同一共用组件 -->
<svg width="41" height="41" viewBox="0 0 40 40" aria-hidden="true">
<use xlink:href="#shared-icon" transform="rotate(270 20 20)" />
<path
fill="#4C5E83"
fill-rule="evenodd"
clip-rule="evenodd"
d="M8 8 L21 8 L21 31 L19 32 L9 32 L8 31 Z" />
<path
fill="url(#Area)"
fill-rule="evenodd"
clip-rule="evenodd"
d="M10 14 L19 14 L19 30 L10 30 Z" />
</svg>
<!-- 6. 第五个 SVG正中的按钮调用同一共用组件 -->
<svg width="41" height="41" viewBox="0 0 40 40" aria-hidden="true">
<use xlink:href="#shared-border" transform="rotate(270 20 20)" />
<path
fill="#4C5E83"
fill-rule="evenodd"
clip-rule="evenodd"
d="M8 8 L32 8 L32 31 L31 32 L9 32 L8 31 Z" />
<path
fill="url(#Area)"
fill-rule="evenodd"
clip-rule="evenodd"
d="M10 14 L30 14 L30 30 L10 30 Z" />
</svg>
</div>
</template>
<script setup>
import { ref, computed, watch } from 'vue'
// Props定义
const props = defineProps({
// 是否可见
visible: {
type: Boolean,
default: false
},
// 目标区域的位置和大小信息
targetRect: {
type: Object,
default: () => ({
left: 0,
top: 0,
width: 0,
height: 0
})
},
// 鼠标位置
mousePosition: {
type: Object,
default: () => ({
x: 0,
y: 0
})
},
// 停靠区域的激活阈值
threshold: {
type: Number,
default: 30
}
})
// 定义事件
const emit = defineEmits(['zone-enter', 'zone-leave', 'zone-active'])
// 状态管理
const activeZones = ref({
top: false,
left: false,
right: false,
bottom: false,
center: false
})
const selectedZone = ref(null)
// 计算指示器的样式
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`,
pointerEvents: props.visible ? 'auto' : 'none',
zIndex: 9999
}
})
// 监听鼠标位置,更新活动区域
watch([() => props.mousePosition, () => props.targetRect, () => props.threshold], () => {
if (!props.visible) return
const { x, y } = props.mousePosition
const { left, top, width, height } = props.targetRect
// 重置所有区域
activeZones.value = {
top: false,
left: false,
right: false,
bottom: false,
center: false
}
// 检查鼠标是否在目标区域内
if (x >= left && x <= left + width && y >= top && y <= top + height) {
// 检查各个停靠区域
if (y <= top + props.threshold) {
activeZones.value.top = true
} else if (y >= top + height - props.threshold) {
activeZones.value.bottom = true
} else if (x <= left + props.threshold) {
activeZones.value.left = true
} else if (x >= left + width - props.threshold) {
activeZones.value.right = true
} else {
// 如果不在边缘,检查是否在中心区域
const centerX = left + width / 2
const centerY = top + height / 2
const centerRadius = Math.min(width, height) / 4
if (Math.sqrt(Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2)) <= centerRadius) {
activeZones.value.center = true
}
}
}
}, { immediate: true, deep: true })
// 当进入某个停靠区域
const onZoneEnter = (zone) => {
selectedZone.value = zone
emit('zone-enter', zone)
emit('zone-active', zone)
}
// 当离开某个停靠区域
const onZoneLeave = (zone) => {
if (selectedZone.value === zone) {
selectedZone.value = null
emit('zone-leave', zone)
emit('zone-active', null)
}
}
// 暴露方法给父组件
defineExpose({
activeZones,
selectedZone,
reset: () => {
selectedZone.value = null
activeZones.value = {
top: false,
left: false,
right: false,
bottom: false,
center: false
}
}
})
</script>
<style scoped>
.dock-indicator {
position: absolute;
box-sizing: border-box;
}
.dock-zone {
position: absolute;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.2s ease;
}
.dock-zone:hover {
background-color: rgba(79, 114, 179, 0.1);
}
/* 上侧停靠区域 */
.dock-zone-top {
top: 0;
left: 0;
width: 100%;
height: 30px;
}
/* 左侧停靠区域 */
.dock-zone-left {
top: 0;
left: 0;
width: 30px;
height: 100%;
}
/* 右侧停靠区域 */
.dock-zone-right {
top: 0;
right: 0;
width: 30px;
height: 100%;
}
/* 下侧停靠区域 */
.dock-zone-bottom {
bottom: 0;
left: 0;
width: 100%;
height: 30px;
}
/* 中央停靠区域 */
.dock-zone-center {
top: 50%;
left: 50%;
width: 60px;
height: 60px;
transform: translate(-50%, -50%);
border-radius: 50%;
}
.dock-zone-indicator {
display: flex;
align-items: center;
justify-content: center;
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0% {
opacity: 0.7;
}
50% {
opacity: 1;
}
100% {
opacity: 0.7;
}
}
/* 活动状态的样式 */
.dock-zone.active {
background-color: rgba(79, 114, 179, 0.2);
}
.dock-zone.active .dock-zone-indicator svg rect {
fill: rgba(79, 114, 179, 0.8);
}
</style>

View File

@@ -1,11 +1,22 @@
<template> <template>
<div class="dock-layout" ref="dockLayoutRef" style="display: flex; flex-direction: column; position: relative;"> <div class="dock-layout" ref="dockLayoutRef" style="display: flex; flex-direction: column; position: relative;">
<!-- 停靠指示器组件 - 设置高z-index确保显示在最顶层 -->
<DockIndicator
:visible="showDockIndicator"
:target-rect="targetAreaRect"
:mouse-position="currentMousePosition"
@zone-active="onDockZoneActive"
style="z-index: 9999;"
/>
<!-- 主区域 --> <!-- 主区域 -->
<Area <Area
:WindowState="windowState" :WindowState="windowState"
:showTitleBar="false" :showTitleBar="false"
title="主区域" title="主区域"
:style="{ position: 'relative', width: '100%', height: '100%', zIndex: 1 }" :style="{ position: 'relative', width: '100%', height: '100%', zIndex: 1 }"
@dragover="handleMainAreaDragOver"
@dragleave="handleMainAreaDragLeave"
> >
</Area> </Area>
<!-- 浮动区域直接渲染不使用额外的div包装 --> <!-- 浮动区域直接渲染不使用额外的div包装 -->
@@ -64,6 +75,8 @@
@dragStart="onPanelDragStart(area.id, $event)" @dragStart="onPanelDragStart(area.id, $event)"
@dragMove="onPanelDragMove(area.id, $event)" @dragMove="onPanelDragMove(area.id, $event)"
@dragEnd="onPanelDragEnd" @dragEnd="onPanelDragEnd"
@dragover="handleAreaDragOver"
@dragleave="handleAreaDragLeave"
/> />
</TabPage> </TabPage>
</Area> </Area>
@@ -75,6 +88,7 @@ import { ref, defineExpose, nextTick, watch } from 'vue'
import Area from './Area.vue'; import Area from './Area.vue';
import Panel from './Panel.vue'; import Panel from './Panel.vue';
import TabPage from './TabPage.vue'; import TabPage from './TabPage.vue';
import DockIndicator from './DockIndicator.vue';
// 主区域状态 // 主区域状态
const windowState = ref('最大化') const windowState = ref('最大化')
@@ -88,6 +102,12 @@ const dockLayoutRef = ref(null)
// 区域ID计数器 // 区域ID计数器
let areaIdCounter = 1 let areaIdCounter = 1
// 停靠指示器相关状态
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)
// Panel拖拽相关状态 // Panel拖拽相关状态
const panelDragState = ref({ const panelDragState = ref({
isDragging: false, isDragging: false,
@@ -273,6 +293,12 @@ const onPanelDragStart = (areaId, event) => {
y: area.y y: area.y
} }
console.log('Panel拖拽开始移动Area:', areaId) console.log('Panel拖拽开始移动Area:', areaId)
// 初始化鼠标位置跟踪
currentMousePosition.value = {
x: event.clientX,
y: event.clientY
}
} }
} }
@@ -302,6 +328,12 @@ const onPanelDragMove = (areaId, event) => {
area.x = newLeft area.x = newLeft
area.y = newTop area.y = newTop
// 更新鼠标位置
currentMousePosition.value = {
x: event.clientX,
y: event.clientY
}
// 调试信息 // 调试信息
console.log('Panel拖拽移动Area新位置:', { x: newLeft, y: newTop }) console.log('Panel拖拽移动Area新位置:', { x: newLeft, y: newTop })
} }
@@ -313,6 +345,16 @@ const onPanelDragEnd = () => {
console.log('Panel拖拽结束') console.log('Panel拖拽结束')
panelDragState.value.isDragging = false panelDragState.value.isDragging = false
panelDragState.value.currentAreaId = null panelDragState.value.currentAreaId = null
// 隐藏停靠指示器
showDockIndicator.value = false
activeDockZone.value = null
// 如果有活动的停靠区域,可以在这里处理停靠逻辑
if (activeDockZone.value) {
console.log('停靠到区域:', activeDockZone.value)
// 这里可以实现具体的停靠逻辑
}
} }
// TabPage拖拽开始 // TabPage拖拽开始
@@ -395,6 +437,98 @@ watch(floatingAreas, (newAreas) => {
}); });
}, { deep: true }); }, { deep: true });
// 处理主区域的dragover事件
const handleMainAreaDragOver = (event) => {
event.preventDefault()
if (panelDragState.value.isDragging || tabDragState.value.isDragging) {
// 获取主区域的位置和大小
const mainAreaElement = event.currentTarget
const rect = mainAreaElement.getBoundingClientRect()
// 更新目标区域信息并显示停靠指示器
targetAreaRect.value = {
left: rect.left,
top: rect.top,
width: rect.width,
height: rect.height
}
showDockIndicator.value = true
// 更新鼠标位置
currentMousePosition.value = {
x: event.clientX,
y: event.clientY
}
}
}
// 处理主区域的dragleave事件
const handleMainAreaDragLeave = () => {
// 检查鼠标是否真的离开了区域(可能只是进入了子元素)
setTimeout(() => {
const activeElement = document.activeElement
const dockLayout = dockLayoutRef.value
// 如果活动元素不是dockLayout的后代隐藏指示器
if (!dockLayout || (activeElement && !dockLayout.contains(activeElement))) {
showDockIndicator.value = false
activeDockZone.value = null
}
}, 50)
}
// 处理浮动区域的dragover事件
const handleAreaDragOver = (event, areaId) => {
event.preventDefault()
if (panelDragState.value.isDragging || tabDragState.value.isDragging) {
// 避免自身停靠到自身
if (areaId !== panelDragState.value.currentAreaId && areaId !== tabDragState.value.currentAreaId) {
// 获取目标区域的位置和大小
const areaElement = event.currentTarget
const rect = areaElement.getBoundingClientRect()
// 更新目标区域信息并显示停靠指示器
targetAreaRect.value = {
left: rect.left,
top: rect.top,
width: rect.width,
height: rect.height
}
showDockIndicator.value = true
// 更新鼠标位置
currentMousePosition.value = {
x: event.clientX,
y: event.clientY
}
}
}
}
// 处理浮动区域的dragleave事件
const handleAreaDragLeave = () => {
// 延迟检查,避免快速移动时的闪烁
setTimeout(() => {
const activeElement = document.activeElement
const dockLayout = dockLayoutRef.value
// 如果活动元素不是dockLayout的后代隐藏指示器
if (!dockLayout || (activeElement && !dockLayout.contains(activeElement))) {
showDockIndicator.value = false
activeDockZone.value = null
}
}, 50)
}
// 处理停靠区域激活事件
const onDockZoneActive = (zone) => {
activeDockZone.value = zone
}
// 处理Panel最大化同步事件 // 处理Panel最大化同步事件
const onPanelMaximizeSync = ({ areaId, maximized }) => { const onPanelMaximizeSync = ({ areaId, maximized }) => {
// 查找对应的Area // 查找对应的Area