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="index === 0 ? '#00D4FF' : '#FFB300'" /></div>
|
2026-07-06 11:11:59 +08:00
|
|
|
<div class="title">{{ item.robotName }}</div>
|
2026-08-06 11:23:19 +08:00
|
|
|
<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='#00D4FF' />
|
|
|
|
|
<span>实时地图 — 工厂园区</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="map-controller">
|
|
|
|
|
<div class="btn-container">
|
2026-05-29 13:54:45 +08:00
|
|
|
<SvgIcon name="zoom-in" color='#00D4FF' :size="16" @click="zoomCanvas(true)" />
|
2026-05-25 09:10:10 +08:00
|
|
|
</div>
|
|
|
|
|
<div class="btn-container">
|
2026-05-29 13:54:45 +08:00
|
|
|
<SvgIcon name="zoom-out" color='#00D4FF' :size="16" @click="zoomCanvas(false)" />
|
2026-05-25 09:10:10 +08:00
|
|
|
</div>
|
|
|
|
|
<div class="btn-container">
|
2026-05-29 13:54:45 +08:00
|
|
|
<SvgIcon name="locate-fixed" color='#00D4FF' :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'
|
2026-08-06 11:23:19 +08:00
|
|
|
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
|
2026-08-06 11:23:19 +08:00
|
|
|
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
|
|
|
|
2026-08-06 11:23:19 +08:00
|
|
|
let robotRefreshTimer
|
|
|
|
|
|
2026-07-06 11:11:59 +08:00
|
|
|
onMounted(() => {
|
|
|
|
|
getRobot()
|
|
|
|
|
getRunningTask()
|
2026-08-06 11:23:19 +08:00
|
|
|
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">
|
|
|
|
|
.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;
|
|
|
|
|
}
|
2026-08-06 11:23:19 +08:00
|
|
|
|
|
|
|
|
.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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-08-06 11:23:19 +08:00
|
|
|
</style>
|