web-map/src/components/context-menu/context-menu.vue

251 lines
5.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<a-dropdown
v-if="visible"
:open="visible"
:trigger="[]"
:placement="dropdownPlacement"
:get-popup-container="getPopupContainer"
@open-change="handleOpenChange"
>
<div
ref="triggerRef"
class="context-menu-trigger"
:style="triggerStyle"
/>
<template #overlay>
<div class="context-menu-overlay">
<div class="context-menu-header">
<div class="menu-title">{{ headerTitle }}</div>
<div class="close-button" @click="handleCloseMenu" title="关闭菜单">
<span class="close-icon">×</span>
</div>
</div>
<!-- 库位菜单 -->
<StorageMenu
v-if="menuType === 'storage-background' && storageLocations?.length"
:storage-locations="storageLocations"
:menu-x="x"
:menu-y="y"
:main-menu-width="mainMenuWidth"
@action-complete="handleActionComplete"
/>
<!-- 机器人菜单 -->
<RobotMenu
v-else-if="menuType === 'robot' && robotInfo"
:robot-info="robotInfo"
@action-complete="handleActionComplete"
@custom-image="handleCustomImage"
/>
<!-- 默认菜单 -->
<DefaultMenu
v-else
@action-complete="handleActionComplete"
/>
</div>
</template>
</a-dropdown>
</template>
<script setup lang="ts">
import { computed, defineAsyncComponent, ref } from 'vue';
import type { RobotInfo, StorageLocationInfo } from '../../services/context-menu';
// 使用动态导入避免 TypeScript 错误
const DefaultMenu = defineAsyncComponent(() => import('./default-menu.vue'));
const RobotMenu = defineAsyncComponent(() => import('./robot-menu.vue'));
const StorageMenu = defineAsyncComponent(() => import('./storage-menu.vue'));
interface Props {
visible: boolean;
x?: number;
y?: number;
menuType?: 'default' | 'storage' | 'storage-background' | 'robot' | 'point' | 'area';
storageLocations?: StorageLocationInfo[];
robotInfo?: RobotInfo;
apiBaseUrl?: string;
}
interface Emits {
(e: 'close'): void;
(e: 'actionComplete', data: any): void;
(e: 'customImage', data: { robotInfo: RobotInfo }): void;
}
const props = withDefaults(defineProps<Props>(), {
x: 0,
y: 0,
menuType: 'default',
storageLocations: () => [],
apiBaseUrl: '',
});
const emit = defineEmits<Emits>();
// 定义组件名称
defineOptions({
name: 'ContextMenu',
});
// 触发器引用
const triggerRef = ref<HTMLElement | null>(null);
const mainMenuWidth = ref(200); // 默认宽度
// 触发器样式 - 创建一个不可见的定位点
const triggerStyle = computed(() => ({
position: 'fixed' as const,
left: `${props.x}px`,
top: `${props.y}px`,
width: '1px',
height: '1px',
pointerEvents: 'none' as const,
zIndex: 9999,
}));
// 使用 Ant Design 的自动边界检测,无需手动计算
const dropdownPlacement = 'bottomLeft' as const;
// 获取弹出层容器
const getPopupContainer = () => document.body;
// 动态标题
const headerTitle = computed(() => {
if (props.menuType === 'storage-background') return '库位状态';
if (props.menuType === 'storage') return '库位信息';
if (props.menuType === 'robot') return '机器人操作';
if (props.menuType === 'point') return '点位操作';
if (props.menuType === 'area') return '区域操作';
return '右键菜单';
});
// 处理下拉菜单开关变化
const handleOpenChange = (open: boolean) => {
if (!open) {
emit('close');
}
};
// 处理操作完成事件
const handleActionComplete = (data: any) => {
console.log('菜单操作完成:', data);
emit('actionComplete', data);
// 所有操作都不关闭菜单,只有关闭按钮才能关闭
};
// 处理自定义图片事件
const handleCustomImage = (data: { robotInfo: RobotInfo }) => {
console.log('打开自定义图片设置:', data.robotInfo);
emit('customImage', data);
// 不关闭菜单,只有关闭按钮才能关闭
};
// 处理关闭按钮点击
const handleCloseMenu = () => {
console.log('用户点击关闭按钮');
emit('close');
};
</script>
<style scoped>
/* 触发器样式 - 不可见定位点 */
.context-menu-trigger {
position: fixed;
width: 1px;
height: 1px;
pointer-events: none;
z-index: 9999;
}
/* 下拉菜单覆盖层样式 */
.context-menu-overlay {
background: white;
border: 1px solid #d9d9d9;
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
padding: 4px 0;
min-width: 120px;
user-select: none;
color: #000;
pointer-events: auto;
}
/* 黑色主题 */
:root[theme='dark'] .context-menu-overlay {
background: #141414;
border-color: #424242;
color: #ffffffd9;
}
/* 菜单头部 */
.context-menu-header {
background-color: #fafafa;
padding: 8px 12px;
border-radius: 6px 6px 0 0;
display: flex;
justify-content: space-between;
align-items: center;
}
/* 黑色主题菜单头部 */
:root[theme='dark'] .context-menu-header {
background-color: #212121;
}
.menu-title {
color: #000 !important;
font-size: 14px;
font-weight: 600;
}
/* 黑色主题标题 */
:root[theme='dark'] .menu-title {
color: #ffffffd9 !important;
}
/* 关闭按钮样式 */
.close-button {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
flex-shrink: 0;
}
.close-button:hover {
background-color: #ff4d4f;
color: white;
}
.close-icon {
font-size: 14px;
font-weight: bold;
line-height: 1;
color: #666;
transition: color 0.2s ease;
}
.close-button:hover .close-icon {
color: white;
}
/* 黑色主题关闭按钮 */
:root[theme='dark'] .close-button {
background-color: #424242;
}
:root[theme='dark'] .close-button:hover {
background-color: #ff4d4f;
}
:root[theme='dark'] .close-icon {
color: #ffffffd9;
}
</style>