253 lines
5.8 KiB
Vue
253 lines
5.8 KiB
Vue
<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' && 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>
|