2025-12-08 11:06:22 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div class="point-menu">
|
|
|
|
|
|
<!-- 菜单头部:显示站点信息 -->
|
|
|
|
|
|
<div class="menu-header">
|
|
|
|
|
|
<div class="point-info">
|
|
|
|
|
|
<div class="point-details">
|
|
|
|
|
|
<div class="point-name">{{ pointInfo?.name || '未知站点' }}</div>
|
|
|
|
|
|
<div class="point-id">ID: {{ pointInfo?.id }}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 菜单分割线 -->
|
2025-12-08 13:57:04 +08:00
|
|
|
|
<a-divider style="margin: 6px 0;" />
|
2025-12-08 11:06:22 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 菜单选项 -->
|
2025-12-08 13:57:04 +08:00
|
|
|
|
<a-menu
|
|
|
|
|
|
:selectable="false"
|
|
|
|
|
|
class="menu-options"
|
|
|
|
|
|
>
|
|
|
|
|
|
<a-menu-item
|
|
|
|
|
|
key="navigate"
|
|
|
|
|
|
:disabled="!canNavigate"
|
2025-12-08 11:06:22 +08:00
|
|
|
|
@click="handleNavigateToPoint"
|
|
|
|
|
|
>
|
2025-12-08 13:57:04 +08:00
|
|
|
|
导航至此站点
|
|
|
|
|
|
</a-menu-item>
|
|
|
|
|
|
</a-menu>
|
2025-12-08 11:06:22 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 机器人选择弹窗 -->
|
|
|
|
|
|
<RobotSelectorModal
|
|
|
|
|
|
v-model:open="showRobotSelector"
|
|
|
|
|
|
:target-point-name="pointInfo?.name"
|
|
|
|
|
|
@confirm="handleRobotSelected"
|
|
|
|
|
|
@cancel="handleRobotSelectorCancel"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
import { message } from 'ant-design-vue';
|
|
|
|
|
|
import { computed, ref } from 'vue';
|
|
|
|
|
|
|
|
|
|
|
|
import type { RobotInfo } from '../../apis/robot';
|
|
|
|
|
|
import { executeNavigateToPoint } from '../../services/context-menu/point-menu.service';
|
|
|
|
|
|
import RobotSelectorModal from '../modal/robot-selector-modal.vue';
|
|
|
|
|
|
|
|
|
|
|
|
interface Props {
|
|
|
|
|
|
menuType?: 'point' | 'default';
|
|
|
|
|
|
pointInfo?: {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
type: string;
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface Emits {
|
|
|
|
|
|
(e: 'actionComplete', data: {
|
|
|
|
|
|
action: string;
|
|
|
|
|
|
point: any;
|
|
|
|
|
|
success: boolean;
|
|
|
|
|
|
message?: string;
|
|
|
|
|
|
}): void;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const props = defineProps<Props>();
|
|
|
|
|
|
const emit = defineEmits<Emits>();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 响应式数据
|
|
|
|
|
|
const showRobotSelector = ref(false);
|
|
|
|
|
|
const loading = ref(false);
|
|
|
|
|
|
|
|
|
|
|
|
// 计算属性
|
|
|
|
|
|
const pointInfo = computed(() => props.pointInfo);
|
|
|
|
|
|
|
|
|
|
|
|
// 判断是否可以进行导航
|
|
|
|
|
|
const canNavigate = computed(() => {
|
|
|
|
|
|
return pointInfo.value?.name && pointInfo.value?.id;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 处理导航到站点
|
|
|
|
|
|
const handleNavigateToPoint = async () => {
|
|
|
|
|
|
if (!canNavigate.value) {
|
|
|
|
|
|
message.warning('站点信息不完整,无法导航');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 打开机器人选择弹窗
|
|
|
|
|
|
showRobotSelector.value = true;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 处理机器人选择
|
|
|
|
|
|
const handleRobotSelected = async (data: { robot: RobotInfo; targetPointName: string }) => {
|
|
|
|
|
|
if (!pointInfo.value) {
|
|
|
|
|
|
message.error('站点信息丢失,无法执行导航');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
loading.value = true;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
console.log('选择机器人进行导航:', {
|
|
|
|
|
|
robot: data.robot.label,
|
|
|
|
|
|
targetPoint: data.targetPointName,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 调用导航API
|
|
|
|
|
|
const result = await executeNavigateToPoint(
|
|
|
|
|
|
pointInfo.value.id,
|
|
|
|
|
|
pointInfo.value.name,
|
2025-12-08 11:18:11 +08:00
|
|
|
|
data.robot.label,
|
2025-12-08 11:06:22 +08:00
|
|
|
|
data.robot.label
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 发送操作完成事件
|
|
|
|
|
|
emit('actionComplete', {
|
|
|
|
|
|
action: 'navigateToPoint',
|
|
|
|
|
|
point: pointInfo.value,
|
|
|
|
|
|
success: result.success,
|
|
|
|
|
|
message: result.message,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 显示结果提示
|
|
|
|
|
|
if (result.success) {
|
|
|
|
|
|
message.success(`已为机器人 ${data.robot.label} 下发导航任务到 ${data.targetPointName}`);
|
2025-12-08 11:18:11 +08:00
|
|
|
|
}
|
2025-12-08 11:06:22 +08:00
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('导航操作失败:', error);
|
|
|
|
|
|
|
|
|
|
|
|
// 发送失败事件
|
|
|
|
|
|
emit('actionComplete', {
|
|
|
|
|
|
action: 'navigateToPoint',
|
|
|
|
|
|
point: pointInfo.value,
|
|
|
|
|
|
success: false,
|
|
|
|
|
|
message: undefined, // 让全局错误处理显示
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
message.error('导航操作失败');
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
loading.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 处理机器人选择弹窗取消
|
|
|
|
|
|
const handleRobotSelectorCancel = () => {
|
|
|
|
|
|
showRobotSelector.value = false;
|
|
|
|
|
|
console.log('用户取消了机器人选择');
|
|
|
|
|
|
};
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.point-menu {
|
2025-12-08 13:57:04 +08:00
|
|
|
|
min-width: 280px;
|
|
|
|
|
|
max-width: 320px;
|
|
|
|
|
|
padding: 4px;
|
2025-12-08 11:06:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 菜单头部 */
|
|
|
|
|
|
.menu-header {
|
2025-12-08 13:57:04 +08:00
|
|
|
|
padding: 4px 8px;
|
2025-12-08 11:06:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.point-info {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
2025-12-08 13:57:04 +08:00
|
|
|
|
gap: 8px;
|
2025-12-08 11:06:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.point-icon {
|
2025-12-08 13:57:04 +08:00
|
|
|
|
font-size: 16px;
|
2025-12-08 11:06:22 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.point-details {
|
|
|
|
|
|
flex: 1;
|
2025-12-08 13:57:04 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
2025-12-08 11:06:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.point-name {
|
2025-12-08 13:57:04 +08:00
|
|
|
|
font-size: 13px;
|
2025-12-08 11:06:22 +08:00
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.point-id {
|
2025-12-08 13:57:04 +08:00
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
color: var(--text-color-secondary, #8c8c8c);
|
2025-12-08 11:06:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 菜单选项 */
|
|
|
|
|
|
.menu-options {
|
2025-12-08 13:57:04 +08:00
|
|
|
|
padding: 0 2px;
|
2025-12-08 11:06:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.menu-item {
|
2025-12-08 13:57:04 +08:00
|
|
|
|
padding: 6px 8px;
|
|
|
|
|
|
margin: 2px 0;
|
|
|
|
|
|
border-radius: 4px;
|
2025-12-08 11:06:22 +08:00
|
|
|
|
cursor: pointer;
|
2025-12-08 13:57:04 +08:00
|
|
|
|
transition: all 0.2s;
|
2025-12-08 11:06:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.menu-item:hover:not(.disabled) {
|
2025-12-08 13:57:04 +08:00
|
|
|
|
background-color: var(--hover-bg, #f5f5f5);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 确保菜单项不超出容器宽度 */
|
|
|
|
|
|
.menu-item-content {
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
width: 100%;
|
2025-12-08 11:06:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.menu-item.disabled {
|
|
|
|
|
|
opacity: 0.6;
|
|
|
|
|
|
cursor: not-allowed;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.menu-item-content {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
2025-12-08 13:57:04 +08:00
|
|
|
|
gap: 8px;
|
2025-12-08 11:06:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.menu-icon {
|
2025-12-08 13:57:04 +08:00
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
width: 16px;
|
2025-12-08 11:06:22 +08:00
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.menu-text {
|
2025-12-08 13:57:04 +08:00
|
|
|
|
font-size: 13px;
|
2025-12-08 11:06:22 +08:00
|
|
|
|
font-weight: 500;
|
2025-12-08 13:57:04 +08:00
|
|
|
|
color: var(--text-color, #262626);
|
2025-12-08 11:06:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-08 13:57:04 +08:00
|
|
|
|
/* 深色主题样式 */
|
|
|
|
|
|
:root([data-theme='dark']) .point-menu {
|
|
|
|
|
|
background-color: #1f1f1f;
|
2025-12-08 11:06:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-08 13:57:04 +08:00
|
|
|
|
:root([data-theme='dark']) .point-name {
|
|
|
|
|
|
color: #ffffff;
|
2025-12-08 11:06:22 +08:00
|
|
|
|
}
|
2025-12-08 13:57:04 +08:00
|
|
|
|
|
|
|
|
|
|
:root([data-theme='dark']) .point-id {
|
|
|
|
|
|
color: #a0a0a0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:root([data-theme='dark']) .menu-item:hover:not(.disabled) {
|
|
|
|
|
|
background-color: #333333;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:root([data-theme='dark']) .menu-text {
|
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 兼容其他可能的深色主题类名 */
|
|
|
|
|
|
.dark .point-menu,
|
|
|
|
|
|
[data-theme="dark"] .point-menu,
|
|
|
|
|
|
.theme-dark .point-menu {
|
|
|
|
|
|
background-color: #1f1f1f;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.dark .point-name,
|
|
|
|
|
|
[data-theme="dark"] .point-name,
|
|
|
|
|
|
.theme-dark .point-name {
|
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.dark .point-id,
|
|
|
|
|
|
[data-theme="dark"] .point-id,
|
|
|
|
|
|
.theme-dark .point-id {
|
|
|
|
|
|
color: #a0a0a0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.dark .menu-item:hover:not(.disabled),
|
|
|
|
|
|
[data-theme="dark"] .menu-item:hover:not(.disabled),
|
|
|
|
|
|
.theme-dark .menu-item:hover:not(.disabled) {
|
|
|
|
|
|
background-color: #333333;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.dark .menu-text,
|
|
|
|
|
|
[data-theme="dark"] .menu-text,
|
|
|
|
|
|
.theme-dark .menu-text {
|
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-08 11:06:22 +08:00
|
|
|
|
</style>
|