refactor: 更新机器人相关组件,优化机器人信息获取逻辑,重构机器人菜单服务,增强代码可读性和维护性

This commit is contained in:
xudan 2025-09-11 11:48:48 +08:00
parent 360294c6a1
commit 080b60ab1b
9 changed files with 405 additions and 100 deletions

70
src/apis/amr/api.ts Normal file
View File

@ -0,0 +1,70 @@
import http from '@core/http';
const enum API {
// 控制接口 - 只包含右键菜单需要的
AMR = '/amr/control',
= '/amr/controlAmr',
= '/amr/acceptTask',
}
// 只保留右键菜单需要的API函数
/**
* AMR -
*/
export async function controlAmr(id: string, action: string): Promise<boolean> {
type B = { id: string; action: string };
type D = void;
try {
await http.post<D, B>(API.AMR, { id, action });
return true;
} catch (error) {
console.error('控制AMR失败:', error);
return false;
}
}
/**
* - IP
*/
export async function controlAmrTakeControl(ip: string): Promise<boolean> {
try {
await http.get(`${API.}/${ip}`);
return true;
} catch (error) {
console.error('抢占控制权失败:', error);
return false;
}
}
/**
*
*/
export async function setAcceptTask(id: string, acceptTask: boolean): Promise<boolean> {
type B = { acceptTask: number; ids: string };
type D = void;
try {
await http.put<D, B>(API., {
acceptTask: acceptTask ? 1 : 0,
ids: id
});
return true;
} catch (error) {
console.error('设置接单状态失败:', error);
return false;
}
}
/**
*
*/
export async function setAvailable(id: string): Promise<boolean> {
return await setAcceptTask(id, true);
}
/**
*
*/
export async function setUnavailable(id: string): Promise<boolean> {
return await setAcceptTask(id, false);
}

86
src/apis/amr/constant.ts Normal file
View File

@ -0,0 +1,86 @@
/**
* AMR相关常量定义 -
*/
import type { AmrAction } from './type';
// AMR操作常量 - 对应右键菜单的操作
export const AMR_ACTIONS: Record<AmrAction, string> = {
seize_control: '抢占控制权',
enable_orders: '可接单',
disable_orders: '不可接单',
pause: '暂停',
resume: '继续',
go_charge: '前往充电',
go_dock: '前往停靠',
navigate: '路径导航',
start: '启动',
stop: '停止',
reset: '重置',
diagnose: '诊断',
update: '更新',
custom_image: '自定义图片',
};
// AMR操作图标 - 对应右键菜单的图标
export const AMR_ACTION_ICONS: Record<AmrAction, string> = {
seize_control: '🎮',
enable_orders: '✅',
disable_orders: '❌',
pause: '⏸️',
resume: '▶️',
go_charge: '🔋',
go_dock: '🏠',
navigate: '🧭',
start: '🚀',
stop: '⏹️',
reset: '🔄',
diagnose: '🔧',
update: '📱',
custom_image: '🖼️',
};
// AMR操作分组 - 对应右键菜单的分组
export const AMR_ACTION_GROUPS = {
control: ['seize_control', 'enable_orders', 'disable_orders'] as AmrAction[],
runtime: ['pause', 'resume', 'start', 'stop'] as AmrAction[],
navigation: ['go_charge', 'go_dock', 'navigate'] as AmrAction[],
system: ['reset', 'diagnose', 'update', 'custom_image'] as AmrAction[],
};
// AMR操作分组标题 - 对应右键菜单的分组标题
export const AMR_ACTION_GROUP_TITLES = {
control: '控制权限',
runtime: '运行控制',
navigation: '导航控制',
system: '系统管理',
};
// AMR操作超时时间毫秒
export const AMR_ACTION_TIMEOUTS: Record<AmrAction, number> = {
seize_control: 5000,
enable_orders: 3000,
disable_orders: 3000,
pause: 3000,
resume: 3000,
go_charge: 10000,
go_dock: 10000,
navigate: 15000,
start: 5000,
stop: 3000,
reset: 10000,
diagnose: 30000,
update: 60000,
custom_image: 0, // 自定义图片不需要API调用
};
// AMR操作结果消息
export const AMR_ACTION_MESSAGES = {
success: '操作成功',
failed: '操作失败',
timeout: '操作超时',
noPermission: '无操作权限',
notConnected: 'AMR未连接',
busy: 'AMR忙碌中',
error: 'AMR故障中',
};

29
src/apis/amr/index.ts Normal file
View File

@ -0,0 +1,29 @@
/**
* AMR API
* API
*/
// 导出API函数
export * from './api';
// 导出类型定义
export * from './type';
// 导出常量
export * from './constant';
// 重新导出常用类型,方便使用
export type {
AmrAction,
AmrActionResult,
} from './type';
// 重新导出常用常量,方便使用
export {
AMR_ACTION_GROUP_TITLES,
AMR_ACTION_GROUPS,
AMR_ACTION_ICONS,
AMR_ACTION_MESSAGES,
AMR_ACTION_TIMEOUTS,
AMR_ACTIONS,
} from './constant';

27
src/apis/amr/type.ts Normal file
View File

@ -0,0 +1,27 @@
/**
* AMR相关类型定义 -
*/
// AMR操作类型 - 对应右键菜单的操作
export type AmrAction =
| 'seize_control' // 抢占控制权
| 'enable_orders' // 可接单
| 'disable_orders' // 不可接单
| 'pause' // 暂停
| 'resume' // 继续
| 'go_charge' // 前往充电
| 'go_dock' // 前往停靠
| 'navigate' // 路径导航
| 'start' // 启动
| 'stop' // 停止
| 'reset' // 重置
| 'diagnose' // 诊断
| 'update' // 更新
| 'custom_image'; // 自定义图片
// AMR操作结果
export interface AmrActionResult {
success: boolean;
message: string;
data?: any;
}

View File

@ -53,7 +53,8 @@
<script setup lang="ts">
import { computed, defineAsyncComponent, ref } from 'vue';
import type { RobotInfo, StorageLocationInfo } from '../../services/context-menu';
import type { RobotInfo } from '../../apis/robot';
import type { StorageLocationInfo } from '../../services/context-menu';
// 使 TypeScript
const DefaultMenu = defineAsyncComponent(() => import('./default-menu.vue'));

View File

@ -12,30 +12,30 @@
<!-- 左侧机器人信息区域 -->
<div v-if="robotInfo" class="robot-info-section">
<div class="robot-header">
<div class="robot-name">{{ robotInfo.name }}</div>
<div class="robot-status" :style="{ color: getStatusColor(robotInfo.status) }">
{{ getStatusText(robotInfo.status) }}
<div class="robot-name">{{ robotInfo.label }}</div>
<div class="robot-status" :style="{ color: getStatusColor(robotInfo.state as any || 'offline') }">
{{ getStatusText(robotInfo.state as any || 'offline') }}
</div>
</div>
<div class="robot-details">
<div v-if="robotInfo.batteryLevel !== undefined" class="detail-item">
<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.batteryLevel}%` }"
:class="getBatteryClass(robotInfo.batteryLevel)"
:style="{ width: `${robotInfo.battery}%` }"
:class="getBatteryClass(robotInfo.battery)"
></div>
</div>
<span class="battery-text">{{ robotInfo.batteryLevel }}%</span>
<span class="battery-text">{{ robotInfo.battery }}%</span>
</div>
</div>
<div v-if="robotInfo.currentTask" class="detail-item">
<span class="detail-label">当前任务:</span>
<span class="detail-value">{{ robotInfo.currentTask }}</span>
<div v-if="robotInfo.targetPoint" class="detail-item">
<span class="detail-label">目标点位:</span>
<span class="detail-value">{{ robotInfo.targetPoint }}</span>
</div>
</div>
</div>
@ -128,7 +128,8 @@
import { message } from 'ant-design-vue';
import { computed, ref } from 'vue';
import type { RobotAction, RobotInfo } from '../../services/context-menu/robot-menu.service';
import type { RobotInfo } from '../../apis/robot';
import type { RobotAction } from '../../services/context-menu/robot-menu.service';
import {
executeRobotAction,
getRobotStatusColor,
@ -166,7 +167,7 @@ const availableRobots = computed(() => {
//
if (props.robotInfo) {
return [{
name: props.robotInfo.name,
name: props.robotInfo.label,
type: 'robot', //
id: props.robotInfo.id
}];
@ -199,7 +200,7 @@ const getBatteryClass = (batteryLevel: number) => {
//
const handleCustomImage = () => {
if (!props.robotInfo?.name) {
if (!props.robotInfo?.label) {
message.error('未找到机器人信息');
return;
}
@ -207,7 +208,7 @@ const handleCustomImage = () => {
console.log('打开机器人图片设置:', props.robotInfo);
//
selectedRobotName.value = props.robotInfo.name;
selectedRobotName.value = props.robotInfo.label;
imageSettingsVisible.value = true;
console.log('设置模态框可见性:', imageSettingsVisible.value);

View File

@ -371,7 +371,8 @@ const backToCards = () => {
const handleEditorContextMenu = (penData: Record<string, unknown>) => {
console.log('EditorService自定义右键菜单事件:', penData);
handleContextMenuFromPenData(penData, contextMenuManager, {
storageLocationService: storageLocationService.value
storageLocationService: storageLocationService.value,
robotService: editor.value // EditorService
});
};

View File

@ -52,8 +52,8 @@ export function getMenuConfig(
return getStorageMenuConfig(type, data, services?.storageLocationService);
case 'robot':
// 机器人类型
return getRobotMenuConfig(data, services?.robotService);
// 机器人类型 - 只传递机器人ID和EditorService
return getRobotMenuConfig(data.id, services?.robotService);
case 'area':
// 区域类型

View File

@ -3,26 +3,16 @@
*
*/
import type { ParsedEventData } from './event-parser';
export interface RobotInfo {
id: string;
name: string;
status: 'online' | 'offline' | 'busy' | 'idle' | 'error';
batteryLevel?: number;
currentTask?: string;
position?: { x: number; y: number };
canAcceptOrders?: boolean; // 是否可接单
isControlled?: boolean; // 是否被控制
isPaused?: boolean; // 是否暂停
}
import * as AmrApi from '../../apis/amr';
import type { RobotInfo } from '../../apis/robot';
import { RobotState } from '../../apis/robot';
export interface RobotMenuConfig {
menuType: 'robot' | 'default';
robotInfo?: RobotInfo;
}
// 机器人操作类型
// 机器人操作类型 - 只包含右键菜单实际需要的操作
export type RobotAction =
| 'seize_control' // 抢占控制权
| 'enable_orders' // 可接单
@ -41,53 +31,45 @@ export type RobotAction =
/**
*
* @param data
* @param robotService
* @param robotId ID
* @param robotService EditorService
* @returns
*/
export function getRobotInfo(data: ParsedEventData, robotService?: any): RobotInfo | undefined {
const robotId = data.id;
export function getRobotInfo(robotId: string, robotService?: any): RobotInfo | undefined {
if (!robotId) {
console.warn('无法获取机器人ID');
return undefined;
}
// 如果提供了机器人服务,尝试使用它获取真实数据
// 直接通过EditorService获取完整的机器人数据WebSocket推送的数据
if (robotService && typeof robotService.getRobotById === 'function') {
try {
const robot = robotService.getRobotById(robotId);
if (robot) {
console.log('从RobotService获取到机器人信息:', robot);
return {
id: robot.id || robotId,
name: robot.name || robot.robotName || '未知机器人',
status: robot.status || 'offline',
batteryLevel: robot.batteryLevel || robot.battery,
currentTask: robot.currentTask || robot.task,
position: robot.position || { x: 0, y: 0 },
};
console.log('从EditorService获取到机器人信息:', robot);
// 直接返回EditorService中的RobotInfo数据无需转换
return robot;
}
} catch (error) {
console.error('获取机器人信息失败:', error);
}
}
// 回退到模拟数据
console.log('使用模拟机器人数据');
return generateMockRobotData(robotId, data.name);
// 如果无法获取机器人数据返回undefined
console.warn('无法获取机器人数据机器人ID:', robotId);
return undefined;
}
/**
* -
* @param data
* @param robotService
* @param robotId ID
* @param robotService EditorService
* @returns
*/
export function getRobotMenuConfig(data: ParsedEventData, robotService?: any): RobotMenuConfig {
console.log(`处理机器人类型机器人ID: ${data.id}`);
export function getRobotMenuConfig(robotId: string, robotService?: any): RobotMenuConfig {
console.log(`处理机器人类型机器人ID: ${robotId}`);
const robotInfo = getRobotInfo(data, robotService);
const robotInfo = getRobotInfo(robotId, robotService);
if (robotInfo) {
console.log('找到机器人信息:', robotInfo);
@ -107,33 +89,27 @@ export function getRobotMenuConfig(data: ParsedEventData, robotService?: any): R
*
* @param action
* @param robotInfo
* @param robotService
* @returns
*/
export async function executeRobotAction(
action: RobotAction,
robotInfo: RobotInfo,
robotService?: any
robotInfo: RobotInfo
): Promise<{ success: boolean; message: string }> {
try {
console.log(`执行机器人操作: ${action}`, robotInfo);
// 如果提供了机器人服务使用真实API
if (robotService && typeof robotService.executeAction === 'function') {
const result = await robotService.executeAction(action, robotInfo.id);
// 处理自定义图片操作
if (action === 'custom_image') {
return {
success: result.success || true,
message: result.message || `${action}操作成功`,
success: true,
message: '打开自定义图片设置',
};
}
// 模拟操作
await new Promise(resolve => setTimeout(resolve, 500)); // 模拟网络延迟
// 使用AMR API执行操作
const result = await executeAmrAction(action, robotInfo);
return result;
return {
success: true,
message: `机器人${robotInfo.name}${action}操作成功`,
};
} catch (error) {
console.error(`机器人${action}操作失败:`, error);
return {
@ -143,12 +119,141 @@ export async function executeRobotAction(
}
}
/**
* AMR操作 - API
* @param action
* @param robotInfo
* @returns
*/
async function executeAmrAction(
action: RobotAction,
robotInfo: RobotInfo
): Promise<{ success: boolean; message: string }> {
try {
let result: boolean = false;
switch (action) {
case 'seize_control':
// 抢占控制权 - 有对应API需要IP地址
if (!robotInfo.ip) {
console.error('抢占控制权失败: 机器人IP地址未提供');
return {
success: false,
message: '抢占控制权失败: 机器人IP地址未提供',
};
}
result = await AmrApi.controlAmrTakeControl(robotInfo.ip);
break;
case 'enable_orders':
// 可接单 - 有对应API
result = await AmrApi.setAvailable(robotInfo.id);
break;
case 'disable_orders':
// 不可接单 - 有对应API
result = await AmrApi.setUnavailable(robotInfo.id);
break;
case 'pause':
// 暂停 - 有对应API
result = await AmrApi.controlAmr(robotInfo.id, 'pause');
break;
case 'resume':
// 继续 - 有对应API
result = await AmrApi.controlAmr(robotInfo.id, 'resume');
break;
case 'start':
// 启动 - 有对应API
result = await AmrApi.controlAmr(robotInfo.id, 'start');
break;
case 'stop':
// 停止 - 有对应API
result = await AmrApi.controlAmr(robotInfo.id, 'stop');
break;
case 'go_charge':
// 前往充电 - 有对应API
result = await AmrApi.controlAmr(robotInfo.id, 'go_charge');
break;
case 'go_dock':
// 前往停靠 - 有对应API
result = await AmrApi.controlAmr(robotInfo.id, 'go_dock');
break;
case 'navigate':
// 路径导航 - 有对应API
result = await AmrApi.controlAmr(robotInfo.id, 'navigate');
break;
case 'reset':
// 重置 - 有对应API
result = await AmrApi.controlAmr(robotInfo.id, 'reset');
break;
case 'diagnose':
// 诊断 - 有对应API
result = await AmrApi.controlAmr(robotInfo.id, 'diagnose');
break;
case 'update':
// 更新 - 有对应API
result = await AmrApi.controlAmr(robotInfo.id, 'update');
break;
case 'custom_image':
// 自定义图片 - 没有对应API保持原有逻辑
console.log('自定义图片功能:打开图片设置模态框');
return {
success: true,
message: '打开自定义图片设置',
};
default:
// 其他操作 - 没有对应API保持原有打印信息逻辑
console.log(`执行机器人操作: ${action} (${robotInfo.id}) - 仅打印信息`);
return {
success: true,
message: `${action}操作(仅打印信息)`,
};
}
return {
success: result,
message: result ? `${action}操作成功` : `${action}操作失败`,
};
} catch (error) {
console.error(`AMR操作失败 (${action}):`, error);
return {
success: false,
message: `操作失败: ${error}`,
};
}
}
/**
*
* @param status
* @returns
*/
export function getRobotStatusText(status: string): string {
export function getRobotStatusText(status: string | number): string {
// 处理RobotState枚举值
if (typeof status === 'number') {
const statusMap: Record<number, string> = {
[RobotState.]: '任务执行中',
[RobotState.]: '充电中',
[RobotState.]: '停靠中',
[RobotState.]: '空闲中',
};
return statusMap[status] || '未知';
}
// 处理字符串状态(兼容旧格式)
const statusMap: Record<string, string> = {
'online': '在线',
'offline': '离线',
@ -165,7 +270,19 @@ export function getRobotStatusText(status: string): string {
* @param status
* @returns
*/
export function getRobotStatusColor(status: string): string {
export function getRobotStatusColor(status: string | number): string {
// 处理RobotState枚举值
if (typeof status === 'number') {
const colorMap: Record<number, string> = {
[RobotState.]: '#1890ff', // 蓝色 - 忙碌
[RobotState.]: '#faad14', // 橙色 - 充电
[RobotState.]: '#52c41a', // 绿色 - 正常
[RobotState.]: '#52c41a', // 绿色 - 正常
};
return colorMap[status] || '#d9d9d9';
}
// 处理字符串状态(兼容旧格式)
const colorMap: Record<string, string> = {
'online': '#52c41a',
'offline': '#d9d9d9',
@ -177,35 +294,6 @@ export function getRobotStatusColor(status: string): string {
return colorMap[status] || '#d9d9d9';
}
/**
*
* @param robotId ID
* @param robotName
* @returns
*/
export function generateMockRobotData(robotId: string, robotName?: string): RobotInfo {
const statuses: Array<'online' | 'offline' | 'busy' | 'idle' | 'error'> = ['online', 'busy', 'idle', 'offline'];
const tasks = ['空闲', '运输中', '充电中', '维护中', '等待指令'];
const randomStatus = statuses[Math.floor(Math.random() * statuses.length)];
const randomTask = tasks[Math.floor(Math.random() * tasks.length)];
const batteryLevel = Math.floor(Math.random() * 100) + 1;
return {
id: robotId,
name: robotName || `机器人-${robotId}`,
status: randomStatus,
batteryLevel,
currentTask: randomTask,
position: {
x: Math.floor(Math.random() * 1000),
y: Math.floor(Math.random() * 1000)
},
canAcceptOrders: Math.random() > 0.3, // 70%概率可接单
isControlled: Math.random() > 0.7, // 30%概率被控制
isPaused: Math.random() > 0.8, // 20%概率暂停
};
}
/**
*
@ -227,6 +315,7 @@ export function getRobotActionText(action: RobotAction): string {
'reset': '重置',
'diagnose': '诊断',
'update': '更新',
'custom_image': '自定义图片',
};
return actionMap[action] || action;
@ -252,6 +341,7 @@ export function getRobotActionIcon(action: RobotAction): string {
'reset': '🔄',
'diagnose': '🔧',
'update': '📱',
'custom_image': '🖼️',
};
return iconMap[action] || '⚙️';