feat: 处理冲突
This commit is contained in:
commit
ea9314581e
@ -92,10 +92,10 @@ export function getRobotLocation(robotId) {
|
||||
* @param {*} robotId
|
||||
* @returns
|
||||
*/
|
||||
export function getRobotStatus(robotId) {
|
||||
export function getRobotStatus(id) {
|
||||
return request({
|
||||
url: `/inspection/robot/{robotId}/sync-status`,
|
||||
method: 'get'
|
||||
url: `/inspection/robot/${id}/sync-status`,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -46,6 +46,29 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="运行状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="runtimeStatusType(row.status)" effect="plain">{{ runtimeStatusLabel(row.status) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="电量" width="145">
|
||||
<template #default="{ row }">
|
||||
<div v-if="hasBattery(row)" class="battery-cell">
|
||||
<el-progress :percentage="normalizeBattery(row.batteryLevel)" :stroke-width="8"
|
||||
:color="batteryColor(row.batteryLevel)" />
|
||||
</div>
|
||||
<span v-else class="secondary">尚未获取</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="当前位置" min-width="210" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.currentPosition" class="position-text">{{ positionText(row.currentPosition) }}</span>
|
||||
<span v-else class="secondary">尚未获取AGV位姿</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="当前地图" min-width="135" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ row.currentMapName || row.robotMapName || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="当前端点" min-width="175">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.ipAddress && row.port">{{ row.ipAddress }}:{{ row.port }}</span>
|
||||
@ -69,8 +92,10 @@
|
||||
<el-table-column label="类型" prop="robotType" width="120">
|
||||
<template #default="{ row }">{{ robotTypeLabel(row.robotType) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="220" fixed="right">
|
||||
<el-table-column label="操作" width="245" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" icon="Refresh" :loading="syncingRobotIds.has(row.id)"
|
||||
:disabled="row.connectStatus !== '1'" @click="syncRuntimeState(row)">状态</el-button>
|
||||
<el-button link type="primary" icon="View" :disabled="!row.robotId" @click="openDevices(row)">设备</el-button>
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(row)">编辑</el-button>
|
||||
<el-button link type="danger" icon="Delete" @click="handleArchive(row)">归档</el-button>
|
||||
@ -115,6 +140,8 @@
|
||||
</el-form-item>
|
||||
<el-form-item label="当前端点"><span>{{ endpointText(form) }}</span></el-form-item>
|
||||
<el-form-item label="边缘节点"><span>{{ form.quicNodeId || '-' }}</span></el-form-item>
|
||||
<el-form-item label="AGV电量"><span>{{ hasBattery(form) ? `${normalizeBattery(form.batteryLevel)}%` : '尚未获取' }}</span></el-form-item>
|
||||
<el-form-item label="AGV位姿"><span>{{ form.currentPosition ? positionText(form.currentPosition) : '尚未获取' }}</span></el-form-item>
|
||||
</template>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@ -154,9 +181,9 @@
|
||||
</template>
|
||||
|
||||
<script setup name="InspectionRobot">
|
||||
import { computed, getCurrentInstance, onBeforeUnmount, reactive, ref } from 'vue'
|
||||
import { computed, getCurrentInstance, onBeforeUnmount, onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { addRobot, deleteRobot, getRobotDevices, getRobotList, updateRobot } from '@/api/inspection/robot'
|
||||
import { addRobot, deleteRobot, getRobotDevices, getRobotList, getRobotStatus, updateRobot } from '@/api/inspection/robot'
|
||||
import { getMapList } from '@/api/inspection/map'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
@ -175,7 +202,9 @@ const devices = ref([])
|
||||
const deviceKeyword = ref('')
|
||||
const deviceOnline = ref('')
|
||||
const activeRobot = ref({})
|
||||
let refreshTimer
|
||||
const syncingRobotIds = reactive(new Set())
|
||||
let deviceRefreshTimer
|
||||
let listRefreshTimer
|
||||
|
||||
const queryParams = reactive({ pageNum: 1, pageSize: 10, robotId: '', robotName: '', connectStatus: '' })
|
||||
const emptyForm = () => ({ id: '', robotId: '', robotName: '', robotCode: '', robotModel: '', robotType: '', currentMapId: '' })
|
||||
@ -193,9 +222,9 @@ const filteredDevices = computed(() => devices.value.filter(item => {
|
||||
return (!keyword || text.includes(keyword)) && (!deviceOnline.value || item.onlineStatus === deviceOnline.value)
|
||||
}))
|
||||
|
||||
async function getList() {
|
||||
loading.value = true
|
||||
try { const res = await getRobotList(queryParams); robotList.value = res.rows || []; total.value = res.total || 0 } finally { loading.value = false }
|
||||
async function getList(showLoading = true) {
|
||||
if (showLoading) loading.value = true
|
||||
try { const res = await getRobotList(queryParams); robotList.value = res.rows || []; total.value = res.total || 0 } finally { if (showLoading) loading.value = false }
|
||||
}
|
||||
function handleQuery() { queryParams.pageNum = 1; getList() }
|
||||
function resetQuery() { Object.assign(queryParams, { pageNum: 1, robotId: '', robotName: '', connectStatus: '' }); getList() }
|
||||
@ -219,7 +248,7 @@ async function handleArchive(row) {
|
||||
}
|
||||
async function openDevices(row) {
|
||||
activeRobot.value = row; deviceOpen.value = true; deviceKeyword.value = ''; deviceOnline.value = ''; await loadDevices()
|
||||
clearInterval(refreshTimer); refreshTimer = setInterval(() => { if (deviceOpen.value) loadDevices(false) }, 5000)
|
||||
clearInterval(deviceRefreshTimer); deviceRefreshTimer = setInterval(() => { if (deviceOpen.value) loadDevices(false) }, 5000)
|
||||
}
|
||||
async function loadDevices(showLoading = true) {
|
||||
if (!activeRobot.value.id) return
|
||||
@ -227,6 +256,33 @@ async function loadDevices(showLoading = true) {
|
||||
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 runtimeStatusLabel(value) { return ({ 0: '在线', 1: '离线', 2: '充电中', 3: '巡检中' })[value] || '未知' }
|
||||
function runtimeStatusType(value) { return ({ 0: 'success', 1: 'info', 2: 'warning', 3: 'primary' })[value] || 'info' }
|
||||
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 batteryColor(value) {
|
||||
const level = normalizeBattery(value)
|
||||
if (level <= 20) return '#f56c6c'
|
||||
if (level <= 40) return '#e6a23c'
|
||||
return '#36a269'
|
||||
}
|
||||
function positionText(value) {
|
||||
const values = String(value || '').split(',').map(item => Number(item))
|
||||
if (values.length < 2 || values.some(item => !Number.isFinite(item))) return value || '-'
|
||||
const theta = values.length > 2 ? `,θ ${values[2].toFixed(2)}` : ''
|
||||
return `X ${values[0].toFixed(2)},Y ${values[1].toFixed(2)}${theta}`
|
||||
}
|
||||
async function syncRuntimeState(row) {
|
||||
if (!row.id || syncingRobotIds.has(row.id)) return
|
||||
syncingRobotIds.add(row.id)
|
||||
try {
|
||||
const res = await getRobotStatus(row.id)
|
||||
if (res.data) Object.assign(row, res.data)
|
||||
ElMessage.success('机器人运行状态已刷新')
|
||||
} finally {
|
||||
syncingRobotIds.delete(row.id)
|
||||
}
|
||||
}
|
||||
function robotTypeLabel(value) { return robot_type.value?.find(item => String(item.value) === String(value))?.label || value || '-' }
|
||||
const kindNames = { 1: 'AGV', 2: '机械臂', 3: '电池', 4: '仿生头', 5: '摄像头', 6: 'CAN总线', 7: '灵巧手', 8: '夹爪', 9: '麦克风', 10: '电机', 11: '电机系统', 12: '机器人', 13: '扬声器' }
|
||||
const stateNames = { 1: '停用', 2: '初始化', 3: '已注册', 4: '就绪', 5: '运行中', 6: '已停止', 7: '错误' }
|
||||
@ -235,9 +291,19 @@ function managerStateLabel(value) { return stateNames[value] || '-' }
|
||||
function healthLabel(row) { if (row.hasError === '1') return '故障'; return ({ 1: '健康', 2: '降级', 3: '故障' })[row.healthStatus] || '未知' }
|
||||
function healthTag(row) { return row.hasError === '1' || row.healthStatus === 3 ? 'danger' : row.healthStatus === 2 ? 'warning' : row.healthStatus === 1 ? 'success' : 'info' }
|
||||
|
||||
onBeforeUnmount(() => clearInterval(refreshTimer))
|
||||
getList()
|
||||
getMapList({ pageNum: 1, pageSize: 10000 }).then(res => { mapList.value = (res.rows || []).map(item => ({ label: item.mapName, value: item.id })) })
|
||||
onMounted(() => {
|
||||
getList()
|
||||
listRefreshTimer = setInterval(() => {
|
||||
if (!document.hidden && !formOpen.value) getList(false)
|
||||
}, 5000)
|
||||
getMapList({ pageNum: 1, pageSize: 10000 }).then(res => {
|
||||
mapList.value = (res.rows || []).map(item => ({ label: item.mapName, value: item.id }))
|
||||
})
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
clearInterval(deviceRefreshTimer)
|
||||
clearInterval(listRefreshTimer)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@ -247,6 +313,9 @@ getMapList({ pageNum: 1, pageSize: 10000 }).then(res => { mapList.value = (res.r
|
||||
.drawer-title { font-size: 16px; margin-bottom: 4px; }
|
||||
.secondary { color: var(--el-text-color-secondary); font-size: 12px; line-height: 20px; }
|
||||
.identity-text { font-family: ui-monospace, SFMono-Regular, Consolas, monospace; }
|
||||
.battery-cell { width: 118px; }
|
||||
.battery-cell :deep(.el-progress__text) { min-width: 34px; font-size: 12px !important; }
|
||||
.position-text { font-family: ui-monospace, SFMono-Regular, Consolas, monospace; font-size: 12px; }
|
||||
.fault-tag { margin-left: 6px; }
|
||||
.robot-form { margin-top: 22px; }
|
||||
.device-toolbar { display: grid; grid-template-columns: minmax(220px, 1fr) 150px auto; gap: 10px; margin-bottom: 16px; }
|
||||
|
||||
Loading…
Reference in New Issue
Block a user