Files
JoyD/AutoRobot/Windows/Robot/Web/src/DockLayout/DockIndicator.vue

403 lines
11 KiB
Vue
Raw Normal View History

<template>
<div v-if="visible" class="dock-indicator" :style="indicatorStyle">
<!-- 半透明区域框根据活动区域显示 -->
<div
v-if="activeDockZone"
class="dock-preview-area"
:style="previewAreaStyle"
></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>
<!-- 上指示根据activeDockZone状态显示和高亮 -->
<svg
width="41"
height="41"
viewBox="0 0 40 40"
aria-hidden="true"
class="indicator-top"
:class="{ 'active': activeDockZone === 'top' }"
@mouseenter="handleMouseEnter('top')"
@mouseleave="handleMouseLeave"
>
<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>
<!-- 右指示根据activeDockZone状态显示和高亮 -->
<svg
width="41"
height="41"
viewBox="0 0 40 40"
aria-hidden="true"
class="indicator-right"
:class="{ 'active': activeDockZone === 'right' }"
@mouseenter="handleMouseEnter('right')"
@mouseleave="handleMouseLeave"
>
<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>
<!-- 下指示根据activeDockZone状态显示和高亮 -->
<svg
width="41"
height="41"
viewBox="0 0 40 40"
aria-hidden="true"
class="indicator-bottom"
:class="{ 'active': activeDockZone === 'bottom' }"
@mouseenter="handleMouseEnter('bottom')"
@mouseleave="handleMouseLeave"
>
<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>
<!-- 左指示根据activeDockZone状态显示和高亮 -->
<svg
width="41"
height="41"
viewBox="0 0 40 40"
aria-hidden="true"
class="indicator-left"
:class="{ 'active': activeDockZone === 'left' }"
@mouseenter="handleMouseEnter('left')"
@mouseleave="handleMouseLeave"
>
<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>
<!-- 中心指示器根据activeDockZone状态显示和高亮 -->
<svg
width="41"
height="41"
viewBox="0 0 40 40"
aria-hidden="true"
class="indicator-center"
:class="{ 'active': activeDockZone === 'center' }"
@mouseenter="handleMouseEnter('center')"
@mouseleave="handleMouseLeave"
>
<use xlink:href="#shared-border" />
<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 { computed, watch, ref, onUnmounted } 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
})
}
})
// 鼠标悬停在哪个指示器上
const hoveredZone = ref(null)
// 延迟定时器
let mouseLeaveTimer = null
// 处理鼠标进入指示器
const handleMouseEnter = (zone) => {
// 清除可能存在的离开定时器
if (mouseLeaveTimer) {
clearTimeout(mouseLeaveTimer)
mouseLeaveTimer = null
}
hoveredZone.value = zone
}
// 处理鼠标离开指示器
const handleMouseLeave = () => {
// 添加短暂延迟,避免快速进出导致的闪烁
mouseLeaveTimer = setTimeout(() => {
hoveredZone.value = null
mouseLeaveTimer = null
}, 100)
}
// 清理定时器
onUnmounted(() => {
if (mouseLeaveTimer) {
clearTimeout(mouseLeaveTimer)
}
})
// 计算指示器的样式
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
}
})
// 计算活动的停靠区域 - 只有当鼠标悬停在指示器上时才返回对应的区域
const activeDockZone = computed(() => {
if (!props.visible) return null
// 只有当鼠标悬停在某个指示器上时才返回对应的区域
return hoveredZone.value
})
// 计算半透明区域框的样式
const previewAreaStyle = computed(() => {
if (!activeDockZone.value) return {};
const { left, top, width, height } = props.targetRect;
const threshold = 0.25;
// 根据不同的活动区域计算预览区域的样式
switch (activeDockZone.value) {
case 'top':
return {
position: 'absolute',
left: `${left}px`,
top: `${top}px`,
width: `${width}px`,
height: `${height * threshold}px`
};
case 'bottom':
return {
position: 'absolute',
left: `${left}px`,
top: `${top + height * (1 - threshold)}px`,
width: `${width}px`,
height: `${height * threshold}px`
};
case 'left':
return {
position: 'absolute',
left: `${left}px`,
top: `${top}px`,
width: `${width * threshold}px`,
height: `${height}px`
};
case 'right':
return {
position: 'absolute',
left: `${left + width * (1 - threshold)}px`,
top: `${top}px`,
width: `${width * threshold}px`,
height: `${height}px`
};
case 'center':
return {
position: 'absolute',
left: `${left + width * threshold}px`,
top: `${top + height * threshold}px`,
width: `${width * (1 - 2 * threshold)}px`,
height: `${height * (1 - 2 * threshold)}px`
};
default:
return {};
}
})
// 定义事件
const emit = defineEmits(['zone-active'])
// 监听activeDockZone变化触发事件
watch(activeDockZone, (newZone) => {
emit('zone-active', newZone)
})
</script>
<style scoped>
.dock-indicator {
position: absolute;
box-sizing: border-box;
}
/* 半透明区域框样式 */
.dock-preview-area {
background-color: rgba(161, 169, 196, 0.3); /* 半透明背景 */
border: 2px dashed #61697E; /* 虚线边框 */
box-sizing: border-box;
z-index: 9998; /* 确保在指示器下方 */
transition: all 0.2s ease; /* 平滑过渡效果 */
pointer-events: none; /* 确保区域框不干扰鼠标事件 */
}
/* 上指示器定位在目标区域的顶端中间上边缘距dock-layout上边缘5像素 */
.indicator-top {
position: absolute;
top: 5px;
left: 50%;
transform: translateX(-50%);
opacity: 0.7; /* 默认半透明 */
transition: opacity 0.2s;
}
/* 右指示器定位在目标区域的右侧中间右边缘距dock-layout右边缘5像素 */
.indicator-right {
position: absolute;
top: 50%;
right: 5px;
transform: translateY(-50%);
opacity: 0.7; /* 默认半透明 */
transition: opacity 0.2s;
}
/* 下指示器定位在目标区域的底部中间下边缘距dock-layout下边缘5像素 */
.indicator-bottom {
position: absolute;
bottom: 5px;
left: 50%;
transform: translateX(-50%);
opacity: 0.7; /* 默认半透明 */
transition: opacity 0.2s;
}
/* 左指示器定位在目标区域的左侧中间左边缘距dock-layout左边缘5像素 */
.indicator-left {
position: absolute;
top: 50%;
left: 5px;
transform: translateY(-50%);
opacity: 0.7; /* 默认半透明 */
transition: opacity 0.2s;
}
/* 中心指示器:定位在目标区域的中心 */
.indicator-center {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
opacity: 0.7; /* 默认半透明 */
transition: opacity 0.2s;
}
/* 活动状态样式 */
.indicator-top.active,
.indicator-right.active,
.indicator-bottom.active,
.indicator-left.active,
.indicator-center.active {
opacity: 1; /* 完全不透明 */
transform: scale(1.1); /* 稍微放大 */
transition: all 0.2s;
}
</style>