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

309 lines
7.4 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"
:z-index="998"
>
<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' && robotId"
:robot-id="robotId"
:token="token"
@action-complete="handleActionComplete"
@custom-image="handleCustomImage"
/>
<!-- 默认菜单 -->
<DefaultMenu
v-else
@action-complete="handleActionComplete"
/>
</div>
</template>
</a-dropdown>
</template>
<script setup lang="ts">
import { message } from 'ant-design-vue';
import { computed, defineAsyncComponent, type InjectionKey, ref, type ShallowRef } from 'vue';
import type { StorageLocationInfo } from '../../services/context-menu';
import type { EditorService } from '../../services/editor.service';
// 使用动态导入避免 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[];
robotId?: string; // 改为传递机器人ID
token?: InjectionKey<ShallowRef<EditorService>>; // 添加 editor token
apiBaseUrl?: string;
}
interface Emits {
(e: 'close'): void;
(e: 'actionComplete', data: any): void;
(e: 'customImage', data: { robotInfo: any }): void; // 使用 any 类型,因为 RobotInfo 不再直接导入
}
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);
// 根据操作结果显示相应的提示消息
if (data.success) {
const actionName = getActionDisplayName(data.action);
message.success(`${actionName}操作成功`);
} else {
const actionName = getActionDisplayName(data.action);
message.error(`${actionName}操作失败`);
}
emit('actionComplete', data);
// 所有操作都不关闭菜单,只有关闭按钮才能关闭
};
/**
* 获取操作显示名称
* @param action 操作类型
* @returns 显示名称
*/
const getActionDisplayName = (action: string): string => {
const actionMap: Record<string, string> = {
// 机器人操作
'seize_control': '抢占控制权',
'enable_orders': '可接单',
'disable_orders': '不可接单',
'pause': '暂停',
'resume': '继续',
'go_charge': '前往充电',
'go_dock': '前往停靠',
'navigate': '路径导航',
'start': '启动',
'stop': '停止',
'reset': '重置',
'diagnose': '诊断',
'update': '更新',
'custom_image': '自定义图片',
// 库位操作
'lock': '锁定',
'unlock': '解锁',
'disable': '禁用',
'enable': '启用',
'occupy': '占用',
'release': '释放',
'set_empty_tray': '设置空托盘',
'clear_empty_tray': '清除空托盘',
// 默认操作
'refresh': '刷新',
'view_info': '查看信息',
'settings': '设置',
};
return actionMap[action] || action;
};
// 处理自定义图片事件
const handleCustomImage = (data: { robotInfo: any }) => {
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>