Compare commits
3 Commits
2ae34b1f9f
...
b332a5461e
| Author | SHA1 | Date | |
|---|---|---|---|
| b332a5461e | |||
| 02706a39a1 | |||
| e18c4b88be |
18
src/utils/agvRuntimeStatus.js
Normal file
18
src/utils/agvRuntimeStatus.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
const AGV_RUNTIME_STATUS = {
|
||||||
|
0: { label: '未知', type: 'info', className: 'unknown' },
|
||||||
|
1: { label: '已断开', type: 'info', className: 'offline' },
|
||||||
|
2: { label: '空闲', type: 'success', className: 'online' },
|
||||||
|
3: { label: '手动模式', type: 'primary', className: 'manual' },
|
||||||
|
4: { label: '自动运行', type: 'primary', className: 'running' },
|
||||||
|
5: { label: '充电中', type: 'warning', className: 'charging' },
|
||||||
|
6: { label: '已暂停', type: 'warning', className: 'paused' },
|
||||||
|
7: { label: '已停止', type: 'info', className: 'stopped' },
|
||||||
|
8: { label: '故障', type: 'danger', className: 'fault' },
|
||||||
|
9: { label: '急停', type: 'danger', className: 'emergency' },
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolveStatus = value => AGV_RUNTIME_STATUS[Number(value)] || AGV_RUNTIME_STATUS[0]
|
||||||
|
|
||||||
|
export const agvRuntimeStatusLabel = value => resolveStatus(value).label
|
||||||
|
export const agvRuntimeStatusType = value => resolveStatus(value).type
|
||||||
|
export const agvRuntimeStatusClass = value => resolveStatus(value).className
|
||||||
@ -11,7 +11,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="bot-name">{{ item.robotName }}</div>
|
<div class="bot-name">{{ item.robotName }}</div>
|
||||||
<div class="bot-local">{{ robotStatus[item.status] }}</div>
|
<div class="bot-local">{{ agvRuntimeStatusLabel(item.status) }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -122,7 +122,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, onMounted, nextTick } from "vue";
|
import { computed, onMounted, onUnmounted, nextTick } from "vue";
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import MapCanvas from "../MapCanvas.vue";
|
import MapCanvas from "../MapCanvas.vue";
|
||||||
import SvgIcon from "@/components/SvgIcon";
|
import SvgIcon from "@/components/SvgIcon";
|
||||||
@ -134,6 +134,7 @@ import { getJointStateApi, moveJApi, torqueOnApi, speedJApi, stopMotionApi, clea
|
|||||||
import { Remove, CirclePlus } from '@element-plus/icons-vue'
|
import { Remove, CirclePlus } from '@element-plus/icons-vue'
|
||||||
import VoiceConversation from "./VoiceConversation.vue";
|
import VoiceConversation from "./VoiceConversation.vue";
|
||||||
import IPlayer from "@/components/IPlayer/index.vue";
|
import IPlayer from "@/components/IPlayer/index.vue";
|
||||||
|
import { agvRuntimeStatusLabel } from '@/utils/agvRuntimeStatus'
|
||||||
|
|
||||||
const robotList = ref([])
|
const robotList = ref([])
|
||||||
const robotDevices = ref([])
|
const robotDevices = ref([])
|
||||||
@ -147,15 +148,18 @@ const loadRobotDevices = async (robotId) => {
|
|||||||
robotDevices.value = Array.isArray(response.data) ? response.data : []
|
robotDevices.value = Array.isArray(response.data) ? response.data : []
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchRobotList = async () => {
|
const fetchRobotList = async (refreshDevices = true) => {
|
||||||
try {
|
try {
|
||||||
const res = await getRobotList({
|
const res = await getRobotList({
|
||||||
robotType: 2,
|
robotType: 2,
|
||||||
})
|
})
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
|
const currentRobotId = activeBotData.value.robotId
|
||||||
robotList.value = res.rows
|
robotList.value = res.rows
|
||||||
activeBotData.value = robotList.value[0] || {}
|
const currentIndex = robotList.value.findIndex(item => item.robotId === currentRobotId)
|
||||||
await loadRobotDevices(activeBotData.value.robotId)
|
activeBot.value = currentIndex >= 0 ? currentIndex : 0
|
||||||
|
activeBotData.value = robotList.value[activeBot.value] || {}
|
||||||
|
if (refreshDevices) await loadRobotDevices(activeBotData.value.robotId)
|
||||||
} else {
|
} else {
|
||||||
console.error('获取机器人列表失败:', res.message)
|
console.error('获取机器人列表失败:', res.message)
|
||||||
}
|
}
|
||||||
@ -164,13 +168,6 @@ const fetchRobotList = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const robotStatus = {
|
|
||||||
0: '在线',
|
|
||||||
1: '离线',
|
|
||||||
2: '充电中',
|
|
||||||
3: '巡检中'
|
|
||||||
}
|
|
||||||
|
|
||||||
const activeBot = ref(0)
|
const activeBot = ref(0)
|
||||||
const activeBotData = ref({})
|
const activeBotData = ref({})
|
||||||
const changeBot = async (index) => {
|
const changeBot = async (index) => {
|
||||||
@ -307,11 +304,17 @@ const stop = async () => {
|
|||||||
|
|
||||||
const videoUrl = ref('https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8')
|
const videoUrl = ref('https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8')
|
||||||
|
|
||||||
|
let robotRefreshTimer
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchRobotList()
|
fetchRobotList()
|
||||||
|
robotRefreshTimer = window.setInterval(() => {
|
||||||
|
if (!document.hidden) fetchRobotList(false)
|
||||||
|
}, 5000)
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
|
window.clearInterval(robotRefreshTimer)
|
||||||
if (animationId.value) {
|
if (animationId.value) {
|
||||||
clearInterval(animationId.value)
|
clearInterval(animationId.value)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -382,6 +382,13 @@ const addRobot = (id, name,x, y, angleRad, color) => {
|
|||||||
applicationState.robots.push(robot);
|
applicationState.robots.push(robot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const parseRobotPosition = (currentPosition) => {
|
||||||
|
if (!currentPosition) return null
|
||||||
|
const values = String(currentPosition).split(',').map(Number)
|
||||||
|
if (values.length < 3 || values.some(value => !Number.isFinite(value))) return null
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
const moveRobot = (robot, dx, dy, dtheta = 0) => {
|
const moveRobot = (robot, dx, dy, dtheta = 0) => {
|
||||||
robot.x += dx;
|
robot.x += dx;
|
||||||
robot.y += dy;
|
robot.y += dy;
|
||||||
@ -398,7 +405,9 @@ onMounted(() => {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
bindEvent()
|
bindEvent()
|
||||||
props.robotList.forEach(item => {
|
props.robotList.forEach(item => {
|
||||||
const [x, y, angle] = item.currentPosition.split(',')
|
const position = parseRobotPosition(item.currentPosition)
|
||||||
|
if (!position) return
|
||||||
|
const [x, y, angle] = position
|
||||||
addRobot(item.id, item.robotName, x, y, angle, colorList[applicationState.robots.length % colorList.length])
|
addRobot(item.id, item.robotName, x, y, angle, colorList[applicationState.robots.length % colorList.length])
|
||||||
})
|
})
|
||||||
}, 300)
|
}, 300)
|
||||||
@ -412,13 +421,14 @@ watch(() => props.robotList,
|
|||||||
if (newVal.length > 0) {
|
if (newVal.length > 0) {
|
||||||
const ids = applicationState.robots.map(r => r.id);
|
const ids = applicationState.robots.map(r => r.id);
|
||||||
props.robotList.forEach(item => {
|
props.robotList.forEach(item => {
|
||||||
|
const position = parseRobotPosition(item.currentPosition)
|
||||||
|
if (!position) return
|
||||||
|
const [x, y, angle] = position
|
||||||
if (!ids.includes(item.id)) {
|
if (!ids.includes(item.id)) {
|
||||||
const [x, y, angle] = item.currentPosition.split(',')
|
|
||||||
addRobot(item.id, item.robotName, x, y, angle, colorList[applicationState.robots.length % colorList.length])
|
addRobot(item.id, item.robotName, x, y, angle, colorList[applicationState.robots.length % colorList.length])
|
||||||
} else {
|
} else {
|
||||||
applicationState.robots.forEach(robot => {
|
applicationState.robots.forEach(robot => {
|
||||||
if (robot.id === item.id) {
|
if (robot.id === item.id) {
|
||||||
const [x, y, angle] = item.currentPosition.split(',')
|
|
||||||
// moveRobot(robot, parseFloat(x) - robot.x, parseFloat(y) - robot.y, parseFloat(angle) - robot.angle)
|
// moveRobot(robot, parseFloat(x) - robot.x, parseFloat(y) - robot.y, parseFloat(angle) - robot.angle)
|
||||||
robot.x = x
|
robot.x = x
|
||||||
robot.y = y
|
robot.y = y
|
||||||
@ -476,4 +486,4 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
<div class="title-box">
|
<div class="title-box">
|
||||||
<div><SvgIcon name="bot" :color="index === 0 ? '#00D4FF' : '#FFB300'" /></div>
|
<div><SvgIcon name="bot" :color="index === 0 ? '#00D4FF' : '#FFB300'" /></div>
|
||||||
<div class="title">{{ item.robotName }}</div>
|
<div class="title">{{ item.robotName }}</div>
|
||||||
<div class="state online">{{ robotStatus[item.status] }}</div>
|
<div class="state" :class="agvRuntimeStatusClass(item.status)">{{ agvRuntimeStatusLabel(item.status) }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-box">
|
<div class="info-box">
|
||||||
<div class="item-box">
|
<div class="item-box">
|
||||||
@ -71,12 +71,9 @@
|
|||||||
<!-- <div class="split-line"></div> -->
|
<!-- <div class="split-line"></div> -->
|
||||||
<div class="alarm-container">
|
<div class="alarm-container">
|
||||||
<div class="container-title">告警记录</div>
|
<div class="container-title">告警记录</div>
|
||||||
<div class="alarm-box" v-for="item, index in alarmRecord">
|
<div class="alarm-box" v-for="item, index in 2">
|
||||||
<div class="alarm-title" :class="item.resultStatus">
|
<div class="title">#01 检测到异常噪声</div>
|
||||||
<div class="title">{{ item.resultName }}</div>
|
<div class="alarm-info">14:28:33 — 厂区A 东侧走廊</div>
|
||||||
<div class="status">{{ getOptionLabel(resultStatusOptions, item.resultStatus) }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="alarm-info">{{ item.createTime }}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -89,15 +86,9 @@ import MapCanvas from "./MapCanvas.vue";
|
|||||||
import { onMounted, onUnmounted } from "vue";
|
import { onMounted, onUnmounted } from "vue";
|
||||||
import { getRobotList } from '@/api/inspection/robot'
|
import { getRobotList } from '@/api/inspection/robot'
|
||||||
import { getRunTaskList } from '@/api/inspection/runTask'
|
import { getRunTaskList } from '@/api/inspection/runTask'
|
||||||
import { getInspectionResultList } from '@/api/inspection/result'
|
import { agvRuntimeStatusClass, agvRuntimeStatusLabel } from '@/utils/agvRuntimeStatus'
|
||||||
|
|
||||||
const robotList = ref([])
|
const robotList = ref([])
|
||||||
const robotStatus = {
|
|
||||||
"0": "在线",
|
|
||||||
"1": "离线",
|
|
||||||
"2": "充电中",
|
|
||||||
"3": "巡检中"
|
|
||||||
}
|
|
||||||
const getRobot = async () => {
|
const getRobot = async () => {
|
||||||
let robotType
|
let robotType
|
||||||
if (import.meta.env.VITE_INSPECTION_TYPE === 'inspection') {
|
if (import.meta.env.VITE_INSPECTION_TYPE === 'inspection') {
|
||||||
@ -127,7 +118,7 @@ const taskStatus = {
|
|||||||
}
|
}
|
||||||
const getRunningTask = async () => {
|
const getRunningTask = async () => {
|
||||||
let taskType
|
let taskType
|
||||||
if (import.meta.env.VITE_INSPECTION_TYPE = 'inspection') {
|
if (import.meta.env.VITE_INSPECTION_TYPE === 'inspection') {
|
||||||
taskType = '1'
|
taskType = '1'
|
||||||
} else {
|
} else {
|
||||||
taskType = '2'
|
taskType = '2'
|
||||||
@ -163,31 +154,18 @@ const centerCanvasView = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const alarmRecord = ref([])
|
let robotRefreshTimer
|
||||||
const resultStatusOptions = [
|
|
||||||
{ label: '待处理', value: 'PENDING' },
|
|
||||||
{ label: '正常', value: 'NORMAL' },
|
|
||||||
{ label: '异常', value: 'ABNORMAL' },
|
|
||||||
{ label: '识别失败', value: 'RECOGNIZE_FAILED' }
|
|
||||||
]
|
|
||||||
const getAlarmRecord = async () => {
|
|
||||||
const res = await getInspectionResultList({
|
|
||||||
pageNum: 1,
|
|
||||||
pageSize: 10
|
|
||||||
})
|
|
||||||
if (res.code === 200) {
|
|
||||||
alarmRecord.value = res.rows
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getOptionLabel = (options, value) => {
|
|
||||||
return options.find(item => item.value === value)?.label || value || '-'
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getRobot()
|
getRobot()
|
||||||
getRunningTask()
|
getRunningTask()
|
||||||
getAlarmRecord()
|
robotRefreshTimer = window.setInterval(() => {
|
||||||
|
if (!document.hidden) getRobot()
|
||||||
|
}, 5000)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.clearInterval(robotRefreshTimer)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -248,6 +226,31 @@ onMounted(() => {
|
|||||||
background: #0f83;
|
background: #0f83;
|
||||||
color: #0f8;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-box {
|
.info-box {
|
||||||
@ -411,45 +414,13 @@ onMounted(() => {
|
|||||||
padding: 16px;
|
padding: 16px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
|
|
||||||
.alarm-title {
|
.title {
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 14px;
|
|
||||||
font-family: Inter;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status {
|
|
||||||
font-size: 12px;
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 0 8px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.NORMAL {
|
|
||||||
color: #00D4FF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ABNORMAL {
|
|
||||||
color: #FF6B6B;
|
color: #FF6B6B;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: Inter;
|
||||||
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.PENDING {
|
|
||||||
color: #8899BB;
|
|
||||||
}
|
|
||||||
|
|
||||||
.RECOGNIZE_FAILED {
|
|
||||||
color: #FF6B6B;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.alarm-info {
|
.alarm-info {
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@ -459,4 +430,4 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -185,6 +185,7 @@ import { computed, getCurrentInstance, onBeforeUnmount, onMounted, reactive, ref
|
|||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { addRobot, deleteRobot, getRobotDevices, getRobotList, getRobotStatus, updateRobot } from '@/api/inspection/robot'
|
import { addRobot, deleteRobot, getRobotDevices, getRobotList, getRobotStatus, updateRobot } from '@/api/inspection/robot'
|
||||||
import { getMapList } from '@/api/inspection/map'
|
import { getMapList } from '@/api/inspection/map'
|
||||||
|
import { agvRuntimeStatusLabel, agvRuntimeStatusType } from '@/utils/agvRuntimeStatus'
|
||||||
|
|
||||||
const { proxy } = getCurrentInstance()
|
const { proxy } = getCurrentInstance()
|
||||||
const { robot_type } = proxy.useDict('robot_type')
|
const { robot_type } = proxy.useDict('robot_type')
|
||||||
@ -256,8 +257,8 @@ async function loadDevices(showLoading = true) {
|
|||||||
try { const res = await getRobotDevices(activeRobot.value.id); devices.value = res.data || [] } finally { deviceLoading.value = false }
|
try { const res = await getRobotDevices(activeRobot.value.id); devices.value = res.data || [] } finally { deviceLoading.value = false }
|
||||||
}
|
}
|
||||||
function endpointText(row) { return row.ipAddress && row.port ? `${row.ipAddress}:${row.port}` : '等待连接后自动获取' }
|
function endpointText(row) { return row.ipAddress && row.port ? `${row.ipAddress}:${row.port}` : '等待连接后自动获取' }
|
||||||
function runtimeStatusLabel(value) { return ({ 0: '在线', 1: '离线', 2: '充电中', 3: '巡检中' })[value] || '未知' }
|
const runtimeStatusLabel = agvRuntimeStatusLabel
|
||||||
function runtimeStatusType(value) { return ({ 0: 'success', 1: 'info', 2: 'warning', 3: 'primary' })[value] || 'info' }
|
const runtimeStatusType = agvRuntimeStatusType
|
||||||
function hasBattery(row) { return row.batteryLevel !== null && row.batteryLevel !== undefined && row.batteryLevel !== '' }
|
function hasBattery(row) { return row.batteryLevel !== null && row.batteryLevel !== undefined && row.batteryLevel !== '' }
|
||||||
function normalizeBattery(value) { return Math.max(0, Math.min(100, Math.round(Number(value) || 0))) }
|
function normalizeBattery(value) { return Math.max(0, Math.min(100, Math.round(Number(value) || 0))) }
|
||||||
function batteryColor(value) {
|
function batteryColor(value) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user