设置停靠指示器显示在最顶层:将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>