CMVR-BID/src/views/inspection/cockpit/components/MapOverview.vue

434 lines
12 KiB
Vue
Raw Normal View History

2026-05-25 09:10:10 +08:00
<template>
<div class="page-container">
<div class="left-container">
<div class="robot-container">
<div class="robot-state">机器人状态</div>
2026-07-06 11:11:59 +08:00
<div class="robot-info" v-for="item, index in robotList">
2026-05-25 09:10:10 +08:00
<div class="title-box">
<div><SvgIcon name="bot" color="var(--min-primary)" /></div>
2026-07-06 11:11:59 +08:00
<div class="title">{{ item.robotName }}</div>
<div class="state" :class="agvRuntimeStatusClass(item.status)">{{ agvRuntimeStatusLabel(item.status) }}</div>
2026-05-25 09:10:10 +08:00
</div>
<div class="info-box">
<div class="item-box">
<div class="item-title">编号</div>
2026-07-06 11:11:59 +08:00
<div class="item-detail">{{ item.robotCode }}</div>
2026-05-25 09:10:10 +08:00
</div>
<div class="item-box">
<div class="item-title">当前任务</div>
2026-07-06 11:11:59 +08:00
<div class="item-detail">{{ getRunningTaskByRobotId(item.id) }}</div>
2026-05-25 09:10:10 +08:00
</div>
<div class="item-box">
<div class="item-title">电量</div>
2026-07-06 11:11:59 +08:00
<div class="item-detail">{{ item.batteryLevel }}%</div>
2026-05-25 09:10:10 +08:00
</div>
<div class="item-box">
2026-07-06 11:11:59 +08:00
<div class="item-title">IP端口</div>
<div class="item-detail">{{ `${item.ipAddress}: ${item.port}` }}</div>
2026-05-25 09:10:10 +08:00
</div>
</div>
</div>
</div>
<div class="split-line"></div>
<div class="task-container">
<div class="container-title">当前任务</div>
2026-07-06 11:11:59 +08:00
<div class="task-box" v-for="item, index in taskList">
<div class="title">{{ item.taskName }}</div>
2026-05-25 09:10:10 +08:00
<div class="task-info">
2026-07-06 11:11:59 +08:00
<div>{{ taskStatus[item.status] }}</div>
<div>{{ item.remark }}</div>
2026-05-25 09:10:10 +08:00
</div>
</div>
</div>
</div>
<div class="map-container">
<div class="title-container">
<div class="title">
<SvgIcon name="map" color="var(--min-primary)" />
2026-05-25 09:10:10 +08:00
<span>实时地图 — 工厂园区</span>
</div>
<div class="map-controller">
<div class="btn-container">
<SvgIcon name="zoom-in" color="var(--min-primary)" :size="16" @click="zoomCanvas(true)" />
2026-05-25 09:10:10 +08:00
</div>
<div class="btn-container">
<SvgIcon name="zoom-out" color="var(--min-primary)" :size="16" @click="zoomCanvas(false)" />
2026-05-25 09:10:10 +08:00
</div>
<div class="btn-container">
<SvgIcon name="locate-fixed" color="var(--min-primary)" :size="16" @click="centerCanvasView" />
2026-05-25 09:10:10 +08:00
</div>
</div>
</div>
<div class="map-box">
2026-08-05 14:17:16 +08:00
<MapCanvas v-if="robotList.length > 0" :robotList="robotList" ref="mapCanvasRef" />
2026-05-25 09:10:10 +08:00
</div>
</div>
<div class="right-container">
2026-06-12 17:27:59 +08:00
<!-- <div class="sensor-container">
2026-05-25 09:10:10 +08:00
<div class="sensor-title">传感器数据</div>
2026-06-12 17:27:59 +08:00
</div> -->
<!-- <div class="split-line"></div> -->
2026-05-25 09:10:10 +08:00
<div class="alarm-container">
<div class="container-title">告警记录</div>
<div class="alarm-box" v-for="item, index in 2">
<div class="title">#01 检测到异常噪声</div>
<div class="alarm-info">14:28:33 — 厂区A 东侧走廊</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import SvgIcon from "@/components/SvgIcon";
import MapCanvas from "./MapCanvas.vue";
import { onMounted, onUnmounted } from "vue";
2026-07-06 11:11:59 +08:00
import { getRobotList } from '@/api/inspection/robot'
import { getRunTaskList } from '@/api/inspection/runTask'
import { agvRuntimeStatusClass, agvRuntimeStatusLabel } from '@/utils/agvRuntimeStatus'
2026-06-12 17:27:59 +08:00
2026-07-06 11:11:59 +08:00
const robotList = ref([])
const getRobot = async () => {
let robotType
2026-08-05 14:17:16 +08:00
if (import.meta.env.VITE_INSPECTION_TYPE === 'inspection') {
2026-07-06 11:11:59 +08:00
robotType = '2'
} else {
robotType = '1'
}
const res = await getRobotList({
robotType
})
if (res.code === 200) {
robotList.value = res.rows
2026-08-05 14:17:16 +08:00
// getRobot()
2026-07-06 11:11:59 +08:00
}
}
const taskList = ref([])
const taskStatus = {
"0": "已完成",
"1": "执行中",
"2": "失败",
"3": "已暂停",
"4": "已终止",
"5": "未执行"
}
const getRunningTask = async () => {
let taskType
if (import.meta.env.VITE_INSPECTION_TYPE === 'inspection') {
2026-07-06 11:11:59 +08:00
taskType = '1'
} else {
taskType = '2'
}
const res = await getRunTaskList({
2026-07-28 09:22:22 +08:00
taskType,
statusList: ['1', '3']
2026-07-06 11:11:59 +08:00
})
if (res.code === 200) {
taskList.value = res.rows
}
}
const getRunningTaskByRobotId = (robotId) => {
const task = taskList.value.find(item => item.robotId === robotId)
if (task) {
return task.taskName
}
return '暂无'
}
2026-05-25 09:10:10 +08:00
2026-05-29 13:54:45 +08:00
const mapCanvasRef = ref(null)
2026-05-25 09:10:10 +08:00
2026-05-29 13:54:45 +08:00
const zoomCanvas = (isZoomIn) => {
if (mapCanvasRef.value) {
mapCanvasRef.value.zoomCanvas(isZoomIn);
}
}
const centerCanvasView = () => {
if (mapCanvasRef.value) {
mapCanvasRef.value.centerCanvasView();
}
}
2026-07-06 11:11:59 +08:00
let robotRefreshTimer
2026-07-06 11:11:59 +08:00
onMounted(() => {
getRobot()
getRunningTask()
robotRefreshTimer = window.setInterval(() => {
if (!document.hidden) getRobot()
}, 5000)
})
onUnmounted(() => {
window.clearInterval(robotRefreshTimer)
2026-07-06 11:11:59 +08:00
})
2026-05-25 09:10:10 +08:00
</script>
<style lang="scss" scoped>
2026-05-25 09:10:10 +08:00
.page-container {
height: 100%;
width: 100%;
display: flex;
.left-container {
width: 300px;
height: 100%;
padding: 16px;
.robot-container {
max-height: 50%;
2026-08-05 11:53:00 +08:00
overflow: hidden;
2026-05-25 09:10:10 +08:00
overflow-y: auto;
.robot-state {
color: #fff;
font-size: 16px;
font-weight: bold;
margin-bottom: 16px;
}
.robot-info {
width: 268px;
height: 148px;
background: #0F1E38;
border-radius: 10px;
// border: 1px solid #00D4FF;
padding: 16px;
margin-bottom: 16px;
.title-box {
display: flex;
justify-content: space-between;
margin-bottom: 12px;
.title {
color: #fff;
font-size: 14px;
font-family: Inter;
font-weight: bold;
}
.state {
font-size: 12px;
border-radius: 12px;
padding: 0 8px;
display: flex;
align-items: center;
justify-content: center;
}
.online {
background: #0f83;
color: #0f8;
}
.unknown,
.offline,
.stopped {
background: rgba(148, 163, 184, 0.2);
color: #cbd5e1;
}
.manual,
.running {
background: rgba(0, 212, 255, 0.18);
color: #00d4ff;
}
.charging,
.paused {
background: rgba(255, 179, 0, 0.2);
color: #ffb300;
}
.fault,
.emergency {
background: rgba(255, 77, 79, 0.2);
color: #ff6b6d;
}
2026-05-25 09:10:10 +08:00
}
.info-box {
.item-box {
display: flex;
justify-content: space-between;
margin: 6px 0;
.item-title {
color: #89B;
font-size: 12px;
}
.item-detail {
color: #e0e8f0;
font-size: 12px;
}
}
}
}
.robot-info.last-child {
margin-bottom: 0;
}
}
.split-line {
width: 100%;
height: 1px;
margin: 16px 0;
background: #1E3A5F;
}
.task-container {
max-height: 50%;
overflow-y: auto;
.container-title {
color: #fff;
font-size: 16px;
font-weight: bold;
margin-bottom: 16px;
}
.task-box {
height: 72px;
background: #0F1E38;
border-radius: 8px;
padding: 16px;
margin-bottom: 16px;
.title {
color: #fff;
text-align: center;
font-size: 14px;
font-family: Inter;
font-weight: bold;
}
.task-info {
display: flex;
justify-content: space-between;
align-items: center;
margin: 10px 0;
font-size: 12px;
color: #E0E8F0;
}
}
}
}
.map-container {
flex: 1;
border: 1px solid #1E3A5F;
border-top: 0;
.title-container {
height: 52px;
padding: 0 16px;
display: flex;
align-items: center;
justify-content: space-between;
.title {
color: #fff;
font-size: 16px;
font-weight: bold;
display: flex;
align-items: center;
gap: 12px;
}
.map-controller {
display: flex;
align-items: center;
gap: 12px;
.btn-container {
width: 32px;
height: 32px;
border-radius: 8px;
background: #1E3A5F;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid transparent;
&:hover {
border-color: #00D4FF;
cursor: pointer;
}
}
}
}
.map-box {
height: calc(100% - 52px);
}
}
.right-container {
width: 280px;
height: 100%;
padding: 16px;
.sensor-container {
max-height: 50%;
height: 45%;
overflow-y: auto;
.sensor-title {
color: #fff;
font-size: 16px;
font-weight: bold;
margin-bottom: 16px;
}
}
.split-line {
width: 100%;
height: 1px;
margin: 16px 0;
background: #1E3A5F;
}
.alarm-container {
max-height: 50%;
overflow-y: auto;
.container-title {
color: #fff;
font-size: 16px;
font-weight: bold;
margin-bottom: 16px;
}
.alarm-box {
height: 72px;
background: #0F1E38;
border-radius: 8px;
padding: 16px;
margin-bottom: 16px;
.title {
color: #FF6B6B;
font-size: 14px;
font-family: Inter;
font-weight: bold;
}
.alarm-info {
margin: 10px 0;
font-size: 12px;
color: #8899BB;
}
}
}
}
}
</style>