feat: 添加AMR Redis状态接口及相关功能,支持在机器人详细卡片中查看AMR状态信息
This commit is contained in:
parent
bdaa6b2b21
commit
f69f699f4a
@ -1,6 +1,6 @@
|
||||
import http from '@core/http';
|
||||
|
||||
import type { RobotDetail, RobotGroup, RobotInfo } from './type';
|
||||
import type { AmrRedisState, RobotDetail, RobotGroup, RobotInfo } from './type';
|
||||
|
||||
const enum API {
|
||||
获取所有机器人 = '/robot/getAll',
|
||||
@ -9,6 +9,7 @@ const enum API {
|
||||
|
||||
批量抢占控制权 = '/robot/seizeByIds',
|
||||
同步组文件 = '/robot/syncByGroupId',
|
||||
获取AMR状态 = '/amr/redis',
|
||||
}
|
||||
|
||||
export async function getAllRobots(): Promise<Array<RobotInfo>> {
|
||||
@ -62,3 +63,14 @@ export async function syncGroupRobotsById(id: RobotGroup['id'], sid: RobotGroup[
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAmrRedisState(id: string): Promise<AmrRedisState | null> {
|
||||
type D = AmrRedisState;
|
||||
try {
|
||||
const data = await http.get<D>(`${API.获取AMR状态}/${id}`);
|
||||
return data ?? null;
|
||||
} catch (error) {
|
||||
console.debug(error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -46,3 +46,91 @@ export interface RobotRealtimeInfo extends RobotInfo {
|
||||
isWaring?: boolean; // 是否告警
|
||||
isFault?: boolean; // 是否故障
|
||||
}
|
||||
|
||||
// AMR Redis状态接口
|
||||
export interface AmrRedisState {
|
||||
headerid: number;
|
||||
timestamp: string;
|
||||
version: string;
|
||||
manufacturer: string;
|
||||
serialnumber: string;
|
||||
orderid: string;
|
||||
orderupdateid: number;
|
||||
lastnodeid: string;
|
||||
lastnodesequenceid: number;
|
||||
driving: boolean;
|
||||
waitingforinteractionzonerelease: boolean;
|
||||
paused: boolean;
|
||||
newbaserequest: boolean;
|
||||
distancesincelastnode: number;
|
||||
operatingmode: string;
|
||||
nodestates: any[];
|
||||
edgestates: any[];
|
||||
agvposition: {
|
||||
x: number;
|
||||
y: number;
|
||||
theta: number;
|
||||
mapid: string;
|
||||
mapdescription: string;
|
||||
positioninitialized: boolean;
|
||||
deviationrange: number;
|
||||
localizationscore: number;
|
||||
};
|
||||
velocity: {
|
||||
vx: number;
|
||||
vy: number;
|
||||
omega: number;
|
||||
};
|
||||
loads: Array<{
|
||||
loadid: string;
|
||||
loadtype: string;
|
||||
loadposition: string;
|
||||
boundingboxreference: {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
theta: number;
|
||||
};
|
||||
loaddimensions: {
|
||||
length: number;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
}>;
|
||||
actionstates: Array<{
|
||||
actionid: string;
|
||||
actiontype: string;
|
||||
actiondescription: string;
|
||||
actionstatus: string;
|
||||
resultdescription: string;
|
||||
}>;
|
||||
batterystate: {
|
||||
batterycharge: number;
|
||||
batteryvoltage: number;
|
||||
batteryhealth: number;
|
||||
charging: boolean;
|
||||
reach: number;
|
||||
};
|
||||
errors: Array<{
|
||||
errortype: string;
|
||||
errorreferences: Array<{
|
||||
referencekey: string;
|
||||
referencevalue: string;
|
||||
}>;
|
||||
errordescription: string;
|
||||
errorlevel: string;
|
||||
}>;
|
||||
information: Array<{
|
||||
infotype: string;
|
||||
inforeferences: Array<{
|
||||
referencekey: string;
|
||||
referencevalue: string;
|
||||
}>;
|
||||
infodescription: string;
|
||||
infolevel: string;
|
||||
}>;
|
||||
safetystate: {
|
||||
estop: string;
|
||||
fieldviolation: boolean;
|
||||
};
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { type RobotInfo, RobotState, RobotType } from '@api/robot';
|
||||
import { type AmrRedisState, getAmrRedisState, type RobotInfo, RobotState, RobotType } from '@api/robot';
|
||||
import type { EditorService } from '@core/editor.service';
|
||||
import sTheme from '@core/theme.service';
|
||||
import { computed, inject, type InjectionKey, type ShallowRef } from 'vue';
|
||||
import { computed, inject, type InjectionKey, ref, type ShallowRef } from 'vue';
|
||||
|
||||
type Props = {
|
||||
token: InjectionKey<ShallowRef<EditorService>>;
|
||||
@ -19,6 +19,49 @@ const robot = computed<RobotInfo | null>(() => {
|
||||
|
||||
const batteryIcon = computed<string>(() => (robot.value?.state === RobotState.充电中 ? 'battery_charge' : 'battery'));
|
||||
const stateDot = computed<string>(() => `state-${robot.value?.state}`);
|
||||
|
||||
// AMR详情相关状态
|
||||
const amrDetailVisible = ref(false);
|
||||
const amrDetailData = ref<AmrRedisState | null>(null);
|
||||
const lastUpdateTime = ref<string>('');
|
||||
|
||||
// 获取AMR详情
|
||||
const fetchAmrDetail = async () => {
|
||||
if (!robot.value?.id) return;
|
||||
// 先打开弹窗,数据未到时展示空
|
||||
amrDetailVisible.value = true;
|
||||
try {
|
||||
// 调用真实接口
|
||||
const data = await getAmrRedisState(robot.value.id);
|
||||
console.log(data + 'AMR全量redis接口返回数据');
|
||||
amrDetailData.value = data;
|
||||
lastUpdateTime.value = new Date().toLocaleString('zh-CN');
|
||||
} catch (error) {
|
||||
console.error('获取AMR详情失败:', error);
|
||||
// 如果接口调用失败,设置一个错误标记
|
||||
amrDetailData.value = { error: true, message: '接口调用失败' } as AmrRedisState & {
|
||||
error: boolean;
|
||||
message: string;
|
||||
};
|
||||
lastUpdateTime.value = new Date().toLocaleString('zh-CN');
|
||||
}
|
||||
};
|
||||
|
||||
// 格式化JSON显示
|
||||
const formatJson = (obj: unknown) => {
|
||||
try {
|
||||
return JSON.stringify(obj, null, 2);
|
||||
} catch {
|
||||
return String(obj);
|
||||
}
|
||||
};
|
||||
|
||||
// 弹窗关闭时重置状态
|
||||
const handleModalClose = () => {
|
||||
amrDetailVisible.value = false;
|
||||
amrDetailData.value = null;
|
||||
lastUpdateTime.value = '';
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -83,8 +126,198 @@ const stateDot = computed<string>(() => `state-${robot.value?.state}`);
|
||||
<a-typography-text :content="$t(robot.targetPoint ?? '-')" ellipsis />
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
|
||||
<!-- 查看AMR详情按钮 -->
|
||||
<div class="mt-16 text-center">
|
||||
<a-button type="primary" @click="fetchAmrDetail">
|
||||
{{ $t('查看AMR详情') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
<a-empty v-else :image="sTheme.empty" />
|
||||
|
||||
<!-- AMR详情弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="amrDetailVisible"
|
||||
:title="$t('AMR Redis状态详情')"
|
||||
width="80%"
|
||||
:footer="null"
|
||||
:destroy-on-close="true"
|
||||
@cancel="handleModalClose"
|
||||
>
|
||||
<template #title>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<div>{{ $t('AMR Redis状态详情') }}</div>
|
||||
<div v-if="robot?.id" class="text-xs text-gray-500 mt-1">{{ $t('机器人ID') }}: {{ robot.id }}</div>
|
||||
<div v-if="lastUpdateTime" class="text-xs text-gray-500 mt-1">
|
||||
{{ $t('最后更新') }}: {{ lastUpdateTime }}
|
||||
</div>
|
||||
</div>
|
||||
<a-button type="text" size="small" @click="fetchAmrDetail">
|
||||
<i class="icon redo mr-4" />
|
||||
{{ $t('刷新') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-if="amrDetailData" class="amr-detail-content">
|
||||
<!-- 如果数据解析失败,只显示原始数据 -->
|
||||
<div v-if="(amrDetailData as any).error" class="error-content">
|
||||
<a-alert
|
||||
:message="$t('数据解析失败')"
|
||||
:description="(amrDetailData as any).message"
|
||||
type="warning"
|
||||
show-icon
|
||||
class="mb-16"
|
||||
/>
|
||||
<a-typography-title :level="5">{{ $t('原始数据') }}</a-typography-title>
|
||||
<a-textarea :value="formatJson(amrDetailData)" :rows="25" readonly class="font-mono" />
|
||||
</div>
|
||||
|
||||
<!-- 如果数据正常,显示结构化内容 -->
|
||||
<a-tabs v-else>
|
||||
<a-tab-pane key="basic" :tab="$t('基本信息')">
|
||||
<a-descriptions :column="2" bordered>
|
||||
<a-descriptions-item :label="$t('序列号')">{{ amrDetailData.serialnumber || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item :label="$t('制造商')">{{ amrDetailData.manufacturer || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item :label="$t('版本')">{{ amrDetailData.version || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item :label="$t('订单ID')">{{ amrDetailData.orderid || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item :label="$t('最后节点')">{{ amrDetailData.lastnodeid || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item :label="$t('操作模式')">{{
|
||||
amrDetailData.operatingmode || '-'
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item :label="$t('是否行驶中')">{{
|
||||
amrDetailData.driving ? $t('是') : $t('否')
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item :label="$t('是否暂停')">{{
|
||||
amrDetailData.paused ? $t('是') : $t('否')
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item :label="$t('时间戳')" :span="2">{{
|
||||
amrDetailData.timestamp || '-'
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item :label="$t('距离上次节点')"
|
||||
>{{ (amrDetailData.distancesincelastnode || 0).toFixed(2) }}m</a-descriptions-item
|
||||
>
|
||||
<a-descriptions-item :label="$t('等待交互区释放')">{{
|
||||
amrDetailData.waitingforinteractionzonerelease ? $t('是') : $t('否')
|
||||
}}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="position" :tab="$t('位置信息')">
|
||||
<a-descriptions :column="2" bordered>
|
||||
<a-descriptions-item :label="$t('X坐标')">{{
|
||||
amrDetailData.agvposition?.x?.toFixed(4) || '-'
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item :label="$t('Y坐标')">{{
|
||||
amrDetailData.agvposition?.y?.toFixed(4) || '-'
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item :label="$t('角度')">{{
|
||||
amrDetailData.agvposition?.theta?.toFixed(4) || '-'
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item :label="$t('地图ID')">{{
|
||||
amrDetailData.agvposition?.mapid || '-'
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item :label="$t('定位分数')">{{
|
||||
amrDetailData.agvposition?.localizationscore?.toFixed(4) || '-'
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item :label="$t('偏差范围')">{{
|
||||
amrDetailData.agvposition?.deviationrange?.toFixed(4) || '-'
|
||||
}}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="battery" :tab="$t('电池状态')">
|
||||
<a-descriptions :column="2" bordered>
|
||||
<a-descriptions-item :label="$t('电池电量')"
|
||||
>{{ amrDetailData.batterystate?.batterycharge?.toFixed(1) || '-' }}%</a-descriptions-item
|
||||
>
|
||||
<a-descriptions-item :label="$t('电池电压')"
|
||||
>{{ amrDetailData.batterystate?.batteryvoltage?.toFixed(3) || '-' }}V</a-descriptions-item
|
||||
>
|
||||
<a-descriptions-item :label="$t('电池健康度')"
|
||||
>{{ amrDetailData.batterystate?.batteryhealth || '-' }}%</a-descriptions-item
|
||||
>
|
||||
<a-descriptions-item :label="$t('是否充电中')">{{
|
||||
amrDetailData.batterystate?.charging ? $t('是') : $t('否')
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item :label="$t('续航里程')"
|
||||
>{{ amrDetailData.batterystate?.reach?.toFixed(1) || '-' }}km</a-descriptions-item
|
||||
>
|
||||
</a-descriptions>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="velocity" :tab="$t('速度信息')">
|
||||
<a-descriptions :column="2" bordered>
|
||||
<a-descriptions-item :label="$t('X方向速度')"
|
||||
>{{ amrDetailData.velocity?.vx?.toFixed(3) || '-' }}m/s</a-descriptions-item
|
||||
>
|
||||
<a-descriptions-item :label="$t('Y方向速度')"
|
||||
>{{ amrDetailData.velocity?.vy?.toFixed(3) || '-' }}m/s</a-descriptions-item
|
||||
>
|
||||
<a-descriptions-item :label="$t('角速度')"
|
||||
>{{ amrDetailData.velocity?.omega?.toFixed(3) || '-' }}rad/s</a-descriptions-item
|
||||
>
|
||||
</a-descriptions>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="loads" :tab="$t('载货信息')">
|
||||
<a-table :data-source="amrDetailData.loads || []" :pagination="false" size="small">
|
||||
<a-table-column key="loadid" :title="$t('载货ID')" data-index="loadid" />
|
||||
<a-table-column key="loadtype" :title="$t('载货类型')" data-index="loadtype" />
|
||||
<a-table-column key="loadposition" :title="$t('载货位置')" data-index="loadposition" />
|
||||
<a-table-column key="dimensions" :title="$t('尺寸')">
|
||||
<template #default="{ record }">
|
||||
{{ record.loaddimensions?.length || '-' }}×{{ record.loaddimensions?.width || '-' }}×{{
|
||||
record.loaddimensions?.height || '-'
|
||||
}}
|
||||
</template>
|
||||
</a-table-column>
|
||||
</a-table>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="actions" :tab="$t('动作状态')">
|
||||
<a-table :data-source="amrDetailData.actionstates || []" :pagination="false" size="small">
|
||||
<a-table-column key="actionid" :title="$t('动作ID')" data-index="actionid" />
|
||||
<a-table-column key="actiontype" :title="$t('动作类型')" data-index="actiontype" />
|
||||
<a-table-column key="actionstatus" :title="$t('动作状态')" data-index="actionstatus" />
|
||||
<a-table-column key="resultdescription" :title="$t('结果描述')" data-index="resultdescription" />
|
||||
</a-table>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="errors" :tab="$t('错误信息')">
|
||||
<a-table :data-source="amrDetailData.errors || []" :pagination="false" size="small">
|
||||
<a-table-column key="errortype" :title="$t('错误类型')" data-index="errortype" />
|
||||
<a-table-column key="errorlevel" :title="$t('错误级别')" data-index="errorlevel" />
|
||||
<a-table-column key="errordescription" :title="$t('错误描述')" data-index="errordescription" />
|
||||
</a-table>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="safety" :tab="$t('安全状态')">
|
||||
<a-descriptions :column="2" bordered>
|
||||
<a-descriptions-item :label="$t('急停状态')">
|
||||
<a-tag :color="amrDetailData.safetystate?.estop === 'NONE' ? 'green' : 'red'">
|
||||
{{ amrDetailData.safetystate?.estop || '-' }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item :label="$t('场地违规')">
|
||||
<a-tag :color="amrDetailData.safetystate?.fieldviolation ? 'red' : 'green'">
|
||||
{{ amrDetailData.safetystate?.fieldviolation ? $t('是') : $t('否') }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="raw" :tab="$t('原始数据')">
|
||||
<a-textarea :value="formatJson(amrDetailData)" :rows="20" readonly class="font-mono" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
<div v-else>
|
||||
<a-empty :image="sTheme.empty" />
|
||||
</div>
|
||||
</a-modal>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
@ -112,4 +345,60 @@ const stateDot = computed<string>(() => `state-${robot.value?.state}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.amr-detail-content {
|
||||
.ant-tabs-content {
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.ant-descriptions {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.ant-table {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.font-mono {
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.mt-16 {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.error-content {
|
||||
.mb-16 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
// 弹窗标题样式
|
||||
:deep(.ant-modal-header) {
|
||||
.flex {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.text-xs {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.text-gray-500 {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.mt-1 {
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
Loading…
x
Reference in New Issue
Block a user