427 lines
11 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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>
<div class="robot-menu">
<!-- 机器人图片设置模态框 -->
<RobotImageSettingsModal
v-model:open="imageSettingsVisible"
:robots="robotInfo ? [{ name: robotInfo.label, type: 'robot', id: robotInfo.id }] : []"
:selected-robot-name="selectedRobotName"
@save="handleImageSettingsSave"
/>
<div class="menu-container">
<!-- 左侧机器人信息区域 -->
<div v-if="robotInfo" class="robot-info-section">
<div class="robot-header">
<div class="robot-name">{{ robotInfo.label }}</div>
<div class="robot-status" :style="{ color: getRobotStatusColor(robotInfo.state as any || 'offline') }">
{{ getRobotStatusText(robotInfo.state as any || 'offline') }}
</div>
</div>
<div class="robot-details">
<div v-if="robotInfo.battery !== undefined" class="detail-item">
<span class="detail-label">电量:</span>
<div class="battery-container">
<div class="battery-bar">
<div
class="battery-fill"
:style="{ width: `${robotInfo.battery}%` }"
:class="getBatteryClass(robotInfo.battery)"
></div>
</div>
<span class="battery-text">{{ robotInfo.battery }}%</span>
</div>
</div>
<div v-if="robotInfo.targetPoint" class="detail-item">
<span class="detail-label">目标点位:</span>
<span class="detail-value">{{ robotInfo.targetPoint }}</span>
</div>
</div>
</div>
<!-- 右侧机器人操作菜单 -->
<div class="robot-actions">
<div class="actions-grid">
<!-- 控制权限 -->
<div class="action-group">
<div class="action-group-title">控制权限</div>
<div class="action-item" @click="handleRobotAction('seize_control', '抢占控制权')">
<span class="action-icon">🎮</span>
<span>抢占控制权</span>
</div>
<div class="action-item" @click="handleRobotAction('enable_orders', '可接单')">
<span class="action-icon"></span>
<span>可接单</span>
</div>
<div class="action-item" @click="handleRobotAction('disable_orders', '不可接单')">
<span class="action-icon"></span>
<span>不可接单</span>
</div>
</div>
<!-- 运行控制 -->
<div class="action-group">
<div class="action-group-title">运行控制</div>
<div class="action-item" @click="handleRobotAction('pause', '暂停')">
<span class="action-icon"></span>
<span>暂停</span>
</div>
<div class="action-item" @click="handleRobotAction('resume', '继续')">
<span class="action-icon"></span>
<span>继续</span>
</div>
<div class="action-item" @click="handleRobotAction('start', '启动')">
<span class="action-icon">🚀</span>
<span>启动</span>
</div>
<div class="action-item" @click="handleRobotAction('stop', '停止')">
<span class="action-icon"></span>
<span>停止</span>
</div>
</div>
<!-- 导航控制 -->
<div class="action-group">
<div class="action-group-title">导航控制</div>
<div class="action-item" @click="handleRobotAction('go_charge', '前往充电')">
<span class="action-icon">🔋</span>
<span>前往充电</span>
</div>
<div class="action-item" @click="handleRobotAction('go_dock', '前往停靠')">
<span class="action-icon">🏠</span>
<span>前往停靠</span>
</div>
<div class="action-item" @click="handleRobotAction('navigate', '路径导航')">
<span class="action-icon">🧭</span>
<span>路径导航</span>
</div>
</div>
<!-- 系统管理 -->
<div class="action-group">
<div class="action-group-title">系统管理</div>
<div class="action-item" @click="handleRobotAction('custom_image', '自定义图片')">
<span class="action-icon">🖼</span>
<span>自定义图片</span>
</div>
<div class="action-item" @click="handleRobotAction('reset', '重置')">
<span class="action-icon">🔄</span>
<span>重置</span>
</div>
<div class="action-item" @click="handleRobotAction('diagnose', '诊断')">
<span class="action-icon">🔧</span>
<span>诊断</span>
</div>
<div class="action-item" @click="handleRobotAction('update', '更新')">
<span class="action-icon">📱</span>
<span>更新</span>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { message } from 'ant-design-vue';
import { computed, inject, type InjectionKey, ref, type ShallowRef } from 'vue';
import type { RobotInfo } from '../../apis/robot';
import type { RobotAction } from '../../services/context-menu/robot-menu.service';
import {
executeRobotAction,
getRobotStatusColor,
getRobotStatusText
} from '../../services/context-menu/robot-menu.service';
import type { EditorService } from '../../services/editor.service';
import RobotImageSettingsModal from '../modal/robot-image-settings-modal.vue';
interface Props {
robotId?: string; // 改为传递机器人ID而不是完整的机器人信息
token?: InjectionKey<ShallowRef<EditorService>>; // 添加 editor token
}
interface Emits {
(e: 'actionComplete', data: { action: RobotAction; robot: RobotInfo; success: boolean }): void;
(e: 'customImage', data: { robotInfo: RobotInfo }): void;
}
const props = defineProps<Props>();
const emit = defineEmits<Emits>();
// 注入 editor service
const editor = props.token ? inject(props.token) : null;
// 根据 robotId 获取机器人信息
const robotInfo = computed<RobotInfo | null>(() => {
if (!props.robotId || !editor?.value) return null;
return editor.value.getRobotById(props.robotId) || null;
});
// 图片设置模态框状态
const imageSettingsVisible = ref(false);
const selectedRobotName = ref('');
// 定义组件名称
defineOptions({
name: 'RobotMenu',
});
// 直接使用服务函数,无需包装
// 获取电量条样式类
const getBatteryClass = (batteryLevel: number) => {
if (batteryLevel > 50) return 'battery-high';
if (batteryLevel > 20) return 'battery-medium';
return 'battery-low';
};
// 处理机器人操作
const handleRobotAction = async (action: RobotAction | 'custom_image', actionName: string) => {
if (!robotInfo.value) return;
// 处理自定义图片操作
if (action === 'custom_image') {
if (!robotInfo.value?.label) {
message.error('未找到机器人信息');
return;
}
console.log('打开机器人图片设置:', robotInfo.value);
// 打开模态框,不关闭右键菜单
selectedRobotName.value = robotInfo.value.label;
imageSettingsVisible.value = true;
// 只触发自定义图片事件,不触发操作完成事件
emit('customImage', {
robotInfo: robotInfo.value
});
return;
}
try {
console.log(`执行机器人操作: ${action} (${actionName})`, robotInfo.value);
// 使用服务函数执行操作
const result = await executeRobotAction(action, robotInfo.value);
console.log('机器人操作结果:', result);
// 发送操作完成事件
emit('actionComplete', {
action,
robot: robotInfo.value,
success: result.success
});
} catch (error) {
console.error(`机器人${actionName}操作失败:`, error);
emit('actionComplete', {
action,
robot: robotInfo.value,
success: false
});
}
};
// 处理图片设置保存
const handleImageSettingsSave = (data: any) => {
console.log('机器人图片设置保存:', data);
message.success('机器人图片设置保存成功');
// 可以在这里添加额外的保存后处理逻辑
// 比如通知父组件更新机器人显示等
};
</script>
<style scoped>
.robot-menu {
width: 100%;
min-width: 320px;
max-width: 500px;
}
/* 主容器:左右布局 */
.menu-container {
display: flex;
min-height: 200px;
}
/* 左侧:机器人信息区域 */
.robot-info-section {
width: 140px;
padding: 12px;
background-color: #f8f9fa;
border-right: 1px solid #e9ecef;
display: flex;
flex-direction: column;
justify-content: flex-start;
}
.robot-header {
display: flex;
flex-direction: column;
gap: 6px;
margin-bottom: 12px;
}
.robot-name {
font-size: 14px;
font-weight: 600;
color: #000;
line-height: 1.2;
}
.robot-status {
font-size: 10px;
font-weight: 500;
padding: 2px 6px;
border-radius: 8px;
background-color: rgba(0, 0, 0, 0.05);
text-align: center;
white-space: nowrap;
}
.robot-details {
display: flex;
flex-direction: column;
gap: 8px;
}
.detail-item {
display: flex;
flex-direction: column;
gap: 4px;
font-size: 11px;
}
.detail-label {
color: #666;
font-size: 10px;
}
.detail-value {
color: #000;
font-size: 11px;
word-break: break-all;
}
/* 电量条样式 */
.battery-container {
display: flex;
flex-direction: column;
gap: 4px;
}
.battery-bar {
width: 100%;
height: 4px;
background-color: #e9ecef;
border-radius: 2px;
overflow: hidden;
}
.battery-fill {
height: 100%;
transition: width 0.3s ease;
border-radius: 2px;
}
.battery-fill.battery-high {
background-color: #52c41a;
}
.battery-fill.battery-medium {
background-color: #faad14;
}
.battery-fill.battery-low {
background-color: #ff4d4f;
}
.battery-text {
font-size: 10px;
color: #666;
text-align: center;
}
/* 右侧:操作区域 */
.robot-actions {
flex: 1;
padding: 8px;
overflow-y: auto;
}
.actions-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
height: 100%;
}
.action-group {
display: flex;
flex-direction: column;
min-height: 0;
}
.action-group-title {
font-size: 10px;
font-weight: 600;
color: #666;
padding: 4px 8px;
text-transform: uppercase;
letter-spacing: 0.5px;
background-color: #f5f5f5;
border-radius: 4px;
margin-bottom: 4px;
text-align: center;
}
.action-item {
display: flex;
align-items: center;
padding: 6px 8px;
cursor: pointer;
transition: background-color 0.2s;
font-size: 11px;
color: #000;
gap: 6px;
border-radius: 4px;
margin-bottom: 2px;
}
.action-item:hover {
background-color: #f0f0f0;
}
.action-icon {
font-size: 12px;
width: 14px;
text-align: center;
flex-shrink: 0;
}
/* 响应式设计 */
@media (max-width: 400px) {
.menu-container {
flex-direction: column;
}
.robot-info-section {
width: 100%;
border-right: none;
border-bottom: 1px solid #e9ecef;
}
.actions-grid {
grid-template-columns: 1fr;
}
}
</style>