You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

469 lines
12 KiB

<template>
<div class="title">
设备监控
<el-button size="small" :icon="renderIcon('Refresh')" @click="refreshData"
style="margin-left: 5px;"
/>
<el-button size="small" :icon="renderIcon('fa Simplybuilt')" @click="simBootAll"
style="margin-left: 2px;" link
v-if="worldModelState.runState.isRunning && worldModelState.runState.isVirtual"
>全上线
</el-button>
<el-input v-model="searchKeyword" size="small" style="width: 240px" placeholder="Search">
<template #prefix>
<component :is="renderIcon('element Search')"></component>
</template>
</el-input>
<span class="close" @click="closeMe">
<component :is="renderIcon('element Close')" />
</span>
</div>
<div class="calc-left-panel">
<el-empty v-if="!isActivated || errorDescription" :description="errorDescription" style="width:100%;">
</el-empty>
<div v-else class="monitor-tool-wrap">
<div class="infor-row">
<div class="infor-item">
总数<span class="num">{{ total }}</span>
</div>
<div class="infor-item">
上线<span class="num num-green">{{ online }}</span>
</div>
<div class="infor-item">
下线<span class="num num-red">{{ offline }}</span>
</div>
<div class="infor-item">
空闲<span class="num num-orange">{{ idle }}</span>
</div>
</div>
<div class="list">
<div v-for="deviceInfo in deviceList" class="item" :class="selectedId === deviceInfo.id" @mousedown="()=>{ selectedId = deviceInfo.id }">
<div class="content">
<div class="row">
<div class="row-icon">
<component :is="renderIcon('antd DeploymentUnitOutlined')"></component>
</div>
<div class="row-content">
<div class="data-infor">
<el-button link style="margin-right: 2px; color:WHITE;" @click="copyDeviceId(deviceInfo)">ID:{{ deviceInfo.id }}</el-button>
<span :class="deviceInfo.isOnline?'green':'red'">[{{ getDeviceModeDesc(deviceInfo) }}]</span>
<div class="infor-right">
<span class="num-red" v-if="deviceInfo.isBlocked">阻挡</span>
<span class="num-red" v-if="deviceInfo.taskStatus === 'PAUSED'">暂停</span>
<span class="blue">{{ deviceInfo.logicX + '_' + deviceInfo.logicY + '(' + deviceInfo.direction + ')' }}</span>
</div>
</div>
<div class="data-infor">
<div class="progress">
<span :style="{width:getTaskProcessPercent(deviceInfo)+'%'}"></span>
<div class="text"> {{ getTaskProcessPercent(deviceInfo) }} %</div>
</div>
</div>
<div class="data-infor">
{{ getDeviceTaskTypeDesc(deviceInfo) }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import IWidgets from '../IWidgets.js'
import { worldModel } from '@/core/manager/WorldModel.js'
export default {
name: 'MonitorView',
mixins: [IWidgets],
data() {
return {
searchKeyword: '',
selectedId: '',
stopSubscribe: [],
/**
* 设备列表数据
* @type {Array<Partial<AgvStatusVo>>}
*/
deviceList: []
}
},
mounted() {
window['MonitorView'] = this
},
unmounted() {
window['MonitorView'] = null
this.undescribe()
},
methods: {
simBootAll() {
const viewport = window['viewport']
for (const deviceInfo of this.deviceList) {
if (deviceInfo.isOnline) {
continue
}
const view3DObject = Model.find3D(deviceInfo.id)
if (typeof view3DObject.bootForMonitor === 'function') {
view3DObject.bootForMonitor()
}
}
},
copyDeviceId(deviceInfo) {
navigator.clipboard.writeText(deviceInfo.id).then(() => {
system.msg('设备ID已复制到剪贴板', 'success')
}).catch(err => {
system.msg('无法复制设备ID:', 'error')
})
},
/**
* 获取任务进度百分比
*/
getTaskProcessPercent(deviceInfo) {
if (deviceInfo == null || !deviceInfo.taskTotalCount || !deviceInfo.taskCompleted) {
return 0
}
return Math.round((deviceInfo.taskCompleted / deviceInfo.taskTotalCount) * 100)
},
/**
* 处理设备存活消息
* @type {(type: BackendTopicType, topic: string, body: {
* id: string
* type: string
* online: boolean
* }) => void}
*/
onDeviceAliveMessage(type, topic, body) {
const deviceInfo = _.find(this.deviceList, device => device.id === body.id)
if (!deviceInfo) {
// 如果设备信息不存在,则添加新设备
this.deviceList.push({
id: body.id,
type: body.type,
isOnline: body.online
})
} else {
// 如果设备信息已存在,则更新在线状态
deviceInfo.isOnline = body.online
}
},
/**
* 处理设备状态消息
* @type {DeviceStatusFn}
*/
onDeviceStatusMessage(type, topic, body) {
const deviceInfo = _.find(this.deviceList, device => device.id === body.id)
if (!deviceInfo) {
return
}
_.assign(deviceInfo, body)
},
async subscribe() {
await this.refreshData()
// 订阅设备状态消息
this.stopSubscribe.push(
worldModel.backendMessageReceiver.subscribe('DeviceAlive', this.onDeviceAliveMessage.bind(this))
)
this.stopSubscribe.push(
worldModel.backendMessageReceiver.subscribe('DeviceStatus', this.onDeviceStatusMessage.bind(this))
)
},
async refreshData() {
this.deviceList = []
const res = await LCC.queryDeviceInfoList()
if (!res.success) {
return
}
this.deviceList = res.data
},
undescribe() {
// 取消订阅设备状态消息
for (const stopFn of this.stopSubscribe) {
stopFn()
}
this.stopSubscribe = []
},
getDeviceModeDesc(deviceInfo) {
if (!deviceInfo.isOnline) {
return '离线'
}
if (!deviceInfo.mode) {
return '在线'
}
switch (deviceInfo.mode) {
case 'AMR_FREE_MODE':
return '空闲模式'
case 'AMR_INIT_MODE':
return '初始化模式'
case 'AMR_TASK_MODE':
return '任务模式'
case 'AMR_SINGLE_ACTION_MODE':
return '单动作模式'
case 'AMR_MANUAL_MODE':
return '手动模式'
case 'AMR_HANDSET_MODE':
return '遥控器模式'
case 'AMR_CHARGE_MODE':
return '充电模式'
case 'AMR_TASK_INTERRUPT_MODE':
return '任务被中断模式'
case 'AMR_CUSTOMIZE_MODE':
return '自定义模式'
}
return deviceInfo.mode
},
getDeviceTaskTypeDesc(deviceInfo) {
switch (deviceInfo.bizTaskType) {
case 'MOVE':
return '移动 ' + deviceInfo.bizTaskFrom + ' -> ' + deviceInfo.bizTaskTo
case 'CHARGE':
return '充电' + deviceInfo.bizTaskFrom + ' -> ' + deviceInfo.bizTaskTo
case 'CARRY':
return '搬运 [' + deviceInfo.bizLpn + '] ' + deviceInfo.bizTaskFrom + ' -> ' + deviceInfo.bizTaskTo
case 'LOAD':
return '装载 [' + deviceInfo.bizLpn + '] ' + deviceInfo.bizTaskFrom + ' -> ' + deviceInfo.bizTaskTo
case 'UNLOAD':
return '卸载 [' + deviceInfo.bizLpn + '] ' + deviceInfo.bizTaskFrom + ' -> ' + deviceInfo.bizTaskTo
default:
return '无任务'
}
}
},
computed: {
/*
total: 0,
online: 0,
offline: 0,
running: 0,
idle: 0,
*/
total() {
return this.deviceList.length
},
online() {
return this.deviceList.filter(device => device.isOnline).length
},
offline() {
return this.deviceList.filter(device => !device.isOnline).length
},
idle() {
return this.deviceList.filter(device => device.mode === 'AMR_FREE_MODE').length
},
worldModelState() {
return worldModel.state
}
},
watch: {
errorDescription: {
handler(newVal) {
if (newVal) {
this.undescribe()
} else {
this.subscribe()
}
},
immediate: true
}
}
}
</script>
<style lang="less">
.monitor-tool-wrap {
width: 100%;
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
.infor-row {
display: flex;
flex-direction: row;
flex-wrap: wrap;
//background: #cdcccc;
padding: 3px 0;
.infor-item {
margin: 2px 8px;
font-size: 12px;
background: #f3e9c0;
border-radius: 3px;
padding: 0 5px;
}
.num {
display: inline-block;
padding: 0 3px;
font-weight: bold;
color: #333;
&:first-child {
margin-left: 0;
}
&:last-child {
margin-right: 0;
}
&.num-red {
color: red;
}
&.num-green {
color: green;
}
&.num-blue {
color: blue;
}
&.num-orange {
color: #d78f0b;
}
}
}
.list {
flex: 1;
overflow: auto;
font-size: 12px;
color: #fff;
.item {
display: flex;
flex-direction: column;
border: 2px solid #808080;
margin: 5px 5px;
border-radius: 5px;
background: #747a80;
&.selected {
border-color: #fa9d12;
}
.title {
display: flex;
align-items: center;
padding: 3px 10px;
border-bottom: 1px solid #81888e;
margin-bottom: 5px;
.num {
font-weight: bold;
margin-left: 5px;
}
.el-icon {
margin: 0 5px;
font-size: 15px;
}
}
.content {
flex: 1;
padding: 5px 0;
.row {
display: flex;
flex-direction: row;
margin: 0 5px;
.row-icon {
width: 75px;
height: 75px;
background: #3e454c;
flex-shrink: 0;
display: flex;
justify-content: center;
align-items: center;
.el-icon {
font-size: 70px;
color: #fff;
}
}
.row-content {
flex: 1;
margin-left: 5px;
}
}
.data-infor {
margin: 3px 0;
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
.infor-right {
flex: 1;
flex-shrink: 0;
span {
&.blue {
color: #3dc4ff;
}
&.name {
margin-right: 15px;
}
}
}
& > span {
margin-right: 3px;
&.green {
color: #00ff00;
}
&.red {
color: red;
}
& > svg {
position: relative;
top: 3px;
}
}
.progress {
flex: 1;
min-width: 80px;
border: 1px solid #dcdcdc;
height: 20px;
margin-left: 5px;
position: relative;
span {
font-size: 12px;
background: #7ac678;
display: inline-block;
width: 0;
text-align: center;
height: 18px;
}
.text {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
text-align: center;
}
}
& > svg {
margin-right: 5px;
position: relative;
top: 1px;
}
}
}
}
}
}
</style>