feat: 处理冲突

This commit is contained in:
zhanghao 2026-08-05 12:41:29 +08:00
commit ea9314581e
2 changed files with 83 additions and 14 deletions

View File

@ -92,10 +92,10 @@ export function getRobotLocation(robotId) {
* @param {*} robotId * @param {*} robotId
* @returns * @returns
*/ */
export function getRobotStatus(robotId) { export function getRobotStatus(id) {
return request({ return request({
url: `/inspection/robot/{robotId}/sync-status`, url: `/inspection/robot/${id}/sync-status`,
method: 'get' method: 'post'
}) })
} }

View File

@ -46,6 +46,29 @@
</el-tag> </el-tag>
</template> </template>
</el-table-column> </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"> <el-table-column label="当前端点" min-width="175">
<template #default="{ row }"> <template #default="{ row }">
<span v-if="row.ipAddress && row.port">{{ row.ipAddress }}:{{ row.port }}</span> <span v-if="row.ipAddress && row.port">{{ row.ipAddress }}:{{ row.port }}</span>
@ -69,8 +92,10 @@
<el-table-column label="类型" prop="robotType" width="120"> <el-table-column label="类型" prop="robotType" width="120">
<template #default="{ row }">{{ robotTypeLabel(row.robotType) }}</template> <template #default="{ row }">{{ robotTypeLabel(row.robotType) }}</template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="220" fixed="right"> <el-table-column label="操作" width="245" fixed="right">
<template #default="{ row }"> <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="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="primary" icon="Edit" @click="handleUpdate(row)">编辑</el-button>
<el-button link type="danger" icon="Delete" @click="handleArchive(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>
<el-form-item label="当前端点"><span>{{ endpointText(form) }}</span></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="边缘节点"><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> </template>
</el-form> </el-form>
<template #footer> <template #footer>
@ -154,9 +181,9 @@
</template> </template>
<script setup name="InspectionRobot"> <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 { 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' import { getMapList } from '@/api/inspection/map'
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
@ -175,7 +202,9 @@ const devices = ref([])
const deviceKeyword = ref('') const deviceKeyword = ref('')
const deviceOnline = ref('') const deviceOnline = ref('')
const activeRobot = 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 queryParams = reactive({ pageNum: 1, pageSize: 10, robotId: '', robotName: '', connectStatus: '' })
const emptyForm = () => ({ id: '', robotId: '', robotName: '', robotCode: '', robotModel: '', robotType: '', currentMapId: '' }) 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) return (!keyword || text.includes(keyword)) && (!deviceOnline.value || item.onlineStatus === deviceOnline.value)
})) }))
async function getList() { async function getList(showLoading = true) {
loading.value = true if (showLoading) loading.value = true
try { const res = await getRobotList(queryParams); robotList.value = res.rows || []; total.value = res.total || 0 } finally { loading.value = false } 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 handleQuery() { queryParams.pageNum = 1; getList() }
function resetQuery() { Object.assign(queryParams, { pageNum: 1, robotId: '', robotName: '', connectStatus: '' }); getList() } function resetQuery() { Object.assign(queryParams, { pageNum: 1, robotId: '', robotName: '', connectStatus: '' }); getList() }
@ -219,7 +248,7 @@ async function handleArchive(row) {
} }
async function openDevices(row) { async function openDevices(row) {
activeRobot.value = row; deviceOpen.value = true; deviceKeyword.value = ''; deviceOnline.value = ''; await loadDevices() 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) { async function loadDevices(showLoading = true) {
if (!activeRobot.value.id) return 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 } 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] || '未知' }
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 || '-' } 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 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: '错误' } 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 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' } function healthTag(row) { return row.hasError === '1' || row.healthStatus === 3 ? 'danger' : row.healthStatus === 2 ? 'warning' : row.healthStatus === 1 ? 'success' : 'info' }
onBeforeUnmount(() => clearInterval(refreshTimer)) onMounted(() => {
getList() getList()
getMapList({ pageNum: 1, pageSize: 10000 }).then(res => { mapList.value = (res.rows || []).map(item => ({ label: item.mapName, value: item.id })) }) 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> </script>
<style scoped lang="scss"> <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; } .drawer-title { font-size: 16px; margin-bottom: 4px; }
.secondary { color: var(--el-text-color-secondary); font-size: 12px; line-height: 20px; } .secondary { color: var(--el-text-color-secondary); font-size: 12px; line-height: 20px; }
.identity-text { font-family: ui-monospace, SFMono-Regular, Consolas, monospace; } .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; } .fault-tag { margin-left: 6px; }
.robot-form { margin-top: 22px; } .robot-form { margin-top: 22px; }
.device-toolbar { display: grid; grid-template-columns: minmax(220px, 1fr) 150px auto; gap: 10px; margin-bottom: 16px; } .device-toolbar { display: grid; grid-template-columns: minmax(220px, 1fr) 150px auto; gap: 10px; margin-bottom: 16px; }