CMVR-BID/src/views/device/register/index.vue
zhanghao 7225f7ea60 feat: update development environment API URLs and add neumorphism styles
- Changed VITE_API_URL and VITE_WS_URL in .env.development to new IP addresses.
- Imported neumorphism.scss for global theme overrides in main.js.
- Modified device registration and terminal views to remove certain columns and adjust layout.
- Updated intelligence test views to comment out unused fields and adjust form items.
- Adjusted user management view to comment out tree panel component.
- Updated test configurations and detect views to comment out group-related fields.
- Increased column widths in corpus views for better layout.
2026-08-27 13:51:37 +08:00

185 lines
8.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="app-container robot-device-page">
<section class="page-heading">
<div>
<h2>机器人设备</h2>
<p>设备由机器人在线上报,切换机器人后自动刷新。</p>
</div>
<div class="robot-selector">
<span>当前机器人</span>
<el-select v-model="selectedRobotId" filterable placeholder="请选择机器人"
:loading="robotLoading" @change="loadDevices">
<el-option v-for="robot in robotOptions" :key="robot.value"
:label="robot.label" :value="robot.value" />
</el-select>
<el-button :icon="Refresh" circle title="刷新设备" :loading="loading" @click="loadDevices" />
</div>
</section>
<section class="status-strip">
<div><strong>{{ devices.length }}</strong><span>设备总数</span></div>
<div><strong class="online">{{ onlineCount }}</strong><span>在线设备</span></div>
<div><strong class="fault">{{ faultCount }}</strong><span>故障设备</span></div>
<div class="filters">
<el-input v-model="keyword" clearable :prefix-icon="Search" placeholder="搜索名称或设备 ID" />
<el-select v-model="deviceKind" clearable placeholder="全部类型">
<el-option v-for="item in deviceKinds" :key="item.value"
:label="item.label" :value="item.value" />
</el-select>
</div>
</section>
<el-table v-loading="loading" :data="filteredDevices" height="calc(100vh - 285px)"
empty-text="当前机器人尚未上报设备">
<el-table-column label="设备" min-width="220">
<template #default="{ row }">
<div class="device-name">
<span class="device-icon"><el-icon><Cpu /></el-icon></span>
<div><b>{{ row.deviceName || row.deviceId }}</b><small>{{ row.deviceId }}</small></div>
</div>
</template>
</el-table-column>
<el-table-column label="类型" >
<template #default="{ row }">{{ kindLabel(row.deviceKind, row.typeName) }}</template>
</el-table-column>
<el-table-column label="运行状态" align="center">
<template #default="{ row }">
<el-tag :type="row.onlineStatus === '1' ? 'success' : 'info'" effect="light">
{{ row.onlineStatus === '1' ? '在线' : '离线' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="健康状态" align="center">
<template #default="{ row }">
<el-tag :type="isFault(row) ? 'danger' : 'success'" effect="plain">
{{ isFault(row) ? '故障' : '正常' }}
</el-tag>
</template>
</el-table-column>
<!-- <el-table-column prop="errorMessage" label="状态信息" min-width="180" show-overflow-tooltip>
<template #default="{ row }">{{ row.errorMessage || '运行正常' }}</template>
</el-table-column> -->
<!-- <el-table-column prop="lastSeenTime" label="最后上报" width="180" /> -->
<el-table-column label="操作" fixed="right" align="center">
<template #default="{ row }">
<el-button v-if="controlRoute(row.deviceKind)" link type="primary"
:disabled="row.onlineStatus !== '1'" @click="openControl(row)">示教</el-button>
<span v-else class="muted">-</span>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script setup>
import { computed, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { Cpu, Refresh, Search } from '@element-plus/icons-vue'
import { getRobotList, getRobotDevicesByRobotId } from '@/api/inspection/robot'
const router = useRouter()
const robotLoading = ref(false)
const loading = ref(false)
const selectedRobotId = ref('')
const robotOptions = ref([])
const devices = ref([])
const keyword = ref('')
const deviceKind = ref()
const deviceKinds = [
{ value: 1, label: '移动底盘' }, { value: 2, label: '机械臂' },
{ value: 3, label: '电池' }, { value: 4, label: '仿生头' },
{ value: 5, label: '摄像头' }, { value: 6, label: 'CAN 总线' },
{ value: 7, label: '灵巧手' }, { value: 8, label: '夹爪' },
{ value: 9, label: '麦克风' }, { value: 10, label: '电机' },
{ value: 11, label: '电机系统' }, { value: 12, label: '机器人控制器' },
{ value: 13, label: '扬声器' }
]
const kindLabel = (kind, fallback) => deviceKinds.find(item => item.value === kind)?.label || fallback || '未知设备'
const isFault = (device) => device.hasError === '1' || Number(device.healthStatus) === 3
const onlineCount = computed(() => devices.value.filter(item => item.onlineStatus === '1').length)
const faultCount = computed(() => devices.value.filter(isFault).length)
const filteredDevices = computed(() => {
const search = keyword.value.trim().toLowerCase()
return devices.value.filter(item => {
const matchesKind = deviceKind.value == null || item.deviceKind === deviceKind.value
const matchesSearch = !search || `${item.deviceName || ''} ${item.deviceId || ''}`.toLowerCase().includes(search)
return matchesKind && matchesSearch
})
})
const loadRobots = async () => {
robotLoading.value = true
try {
const response = await getRobotList({ pageNum: 1, pageSize: 1000 })
robotOptions.value = (response.rows || []).map(robot => ({
value: robot.robotId,
label: `${robot.robotName || robot.robotId} (${robot.robotId})`,
online: robot.connectStatus === '1'
})).filter(item => item.value)
const preferred = robotOptions.value.find(item => item.online) || robotOptions.value[0]
selectedRobotId.value = preferred?.value || ''
} finally {
robotLoading.value = false
}
}
const loadDevices = async () => {
devices.value = []
if (!selectedRobotId.value) return
loading.value = true
try {
const response = await getRobotDevicesByRobotId(selectedRobotId.value, { onlineOnly: false })
devices.value = Array.isArray(response.data) ? response.data : []
} finally {
loading.value = false
}
}
const controlRoute = (kind) => ({
2: 'mechanical_arm', 4: 'head', 5: 'camera', 7: 'hand', 9: 'microphone', 13: 'speaker'
})[kind]
const openControl = (device) => {
router.push({
path: `/device/${controlRoute(device.deviceKind)}/${device.id}`,
query: { robotId: selectedRobotId.value, deviceId: device.deviceId }
})
}
onMounted(async () => {
await loadRobots()
await loadDevices()
})
</script>
<style lang="scss" scoped>
.robot-device-page { min-height: 100%; background: #f5f7fa; }
.page-heading { display: flex; align-items: center; justify-content: space-between; gap: 24px; padding: 4px 0 20px; }
.page-heading h2 { margin: 0 0 5px; color: #1f2937; font-size: 22px; letter-spacing: 0; }
.page-heading p { margin: 0; color: #8492a6; font-size: 13px; }
.robot-selector { display: flex; align-items: center; gap: 10px; color: #606266; font-size: 13px; }
.robot-selector .el-select { width: 330px; }
.status-strip { display: flex; align-items: center; gap: 34px; margin-bottom: 14px; padding: 14px 18px; border: 1px solid #e4e7ed; border-radius: 6px; background: #fff; }
.status-strip > div:not(.filters) { display: grid; min-width: 74px; }
.status-strip strong { color: #303133; font-size: 24px; line-height: 28px; }
.status-strip strong.online { color: #16a34a; }
.status-strip strong.fault { color: #dc2626; }
.status-strip span { color: #909399; font-size: 12px; }
.filters { display: flex; flex: 1; justify-content: flex-end; gap: 10px; }
.filters .el-input { width: 240px; }
.filters .el-select { width: 150px; }
.device-name { display: flex; align-items: center; gap: 12px; }
.device-icon { display: grid; width: 34px; height: 34px; place-items: center; border-radius: 6px; background: #ecf5ff; color: #409eff; }
.device-name div { display: grid; min-width: 0; }
.device-name b { color: #303133; font-weight: 600; }
.device-name small { overflow: hidden; color: #909399; text-overflow: ellipsis; }
.muted { color: #c0c4cc; }
@media (max-width: 900px) {
.page-heading, .status-strip { align-items: stretch; flex-direction: column; }
.robot-selector, .filters { justify-content: flex-start; flex-wrap: wrap; }
.robot-selector .el-select, .filters .el-input { width: 100%; }
}
</style>