opt:1.增加看板内容
This commit is contained in:
@@ -14,4 +14,11 @@ export function queryAllStationWithTaskStatus() {
|
||||
})
|
||||
}
|
||||
|
||||
export default { queryAllStations, queryAllStationWithTaskStatus }
|
||||
export function syncCurrentMapData() {
|
||||
return request({
|
||||
url: '/mapinfo/syncCurrentMapData',
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
export default { queryAllStations, queryAllStationWithTaskStatus, syncCurrentMapData }
|
||||
|
||||
@@ -103,7 +103,44 @@ export default {
|
||||
'submit_msg': 'Submitted successfully',
|
||||
'add_msg': 'Added successfully',
|
||||
'edit_msg': 'Edited successfully',
|
||||
'del_msg': 'Delete successfully'
|
||||
'del_msg': 'Delete successfully',
|
||||
'request_timeout': 'Network request timeout',
|
||||
'request_failed': 'Interface request failed',
|
||||
'error': 'Error'
|
||||
},
|
||||
'dashboard': {
|
||||
'today': 'Today',
|
||||
'week': 'This Week',
|
||||
'month': 'This Month',
|
||||
'total_tasks': 'Total Tasks',
|
||||
'executing_tasks': 'Executing',
|
||||
'completed_tasks': 'Completed',
|
||||
'cancellation_rate': 'Cancellation Rate',
|
||||
'completion_rate': 'Completion Rate',
|
||||
'online_vehicles': 'Online Vehicles',
|
||||
'current_tasks': 'Current Tasks',
|
||||
'refresh': 'Refresh',
|
||||
'task_code': 'Task Code',
|
||||
'task_type': 'Task Type',
|
||||
'target_point': 'Target Point',
|
||||
'status': 'Status',
|
||||
'executing_vehicle': 'Executing Vehicle',
|
||||
'create_time': 'Create Time',
|
||||
'task_pending': 'Pending',
|
||||
'task_executing': 'Executing',
|
||||
'task_completed': 'Completed',
|
||||
'task_cancelled': 'Cancelled',
|
||||
'vehicle_status': 'Vehicle Status',
|
||||
'vehicle_idle': 'Idle',
|
||||
'battery': 'Battery',
|
||||
'signal': 'Signal',
|
||||
'ice': 'Ice',
|
||||
'water': 'Water',
|
||||
'room_status': 'Room Status',
|
||||
'no_vehicle_data': 'No vehicle data',
|
||||
'no_room_data': 'No room data',
|
||||
'room_executing': 'Executing',
|
||||
'room_idle': 'Idle'
|
||||
},
|
||||
'monitor': {
|
||||
'sys': 'System',
|
||||
|
||||
@@ -53,6 +53,9 @@ export default {
|
||||
'failed': 'Go Home command failed, please try again'
|
||||
},
|
||||
'empty': 'No room data available',
|
||||
'query_failed': 'Failed to query room status, please check network'
|
||||
'query_failed': 'Failed to query room status, please check network',
|
||||
'sync_map': 'Sync Station',
|
||||
'sync_map_success': 'Map synced successfully',
|
||||
'sync_map_failed': 'Map sync failed, please try again'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,9 @@ export default {
|
||||
'failed': 'Lệnh Go Home thất bại, vui lòng thử lại'
|
||||
},
|
||||
'empty': 'Không có dữ liệu phòng',
|
||||
'query_failed': 'Truy vấn trạng thái phòng thất bại, kiểm tra mạng'
|
||||
'query_failed': 'Truy vấn trạng thái phòng thất bại, kiểm tra mạng',
|
||||
'sync_map': 'Đồng bộ bản đồ',
|
||||
'sync_map_success': 'Đồng bộ bản đồ thành công',
|
||||
'sync_map_failed': 'Đồng bộ bản đồ thất bại, vui lòng thử lại'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,9 @@ export default {
|
||||
'failed': 'Go Home 指令下发失败,请重试'
|
||||
},
|
||||
'empty': '暂无房间数据',
|
||||
'query_failed': '查询房间状态失败,请检查网络'
|
||||
'query_failed': '查询房间状态失败,请检查网络',
|
||||
'sync_map': '同步站点',
|
||||
'sync_map_success': '地图同步成功',
|
||||
'sync_map_failed': '地图同步失败,请重试'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +103,44 @@ export default {
|
||||
'submit_msg': '提交成功',
|
||||
'add_msg': '新增成功',
|
||||
'edit_msg': '编辑成功',
|
||||
'del_msg': '删除成功'
|
||||
'del_msg': '删除成功',
|
||||
'request_timeout': '网络请求超时',
|
||||
'request_failed': '接口请求失败',
|
||||
'error': '异常'
|
||||
},
|
||||
'dashboard': {
|
||||
'today': '今天',
|
||||
'week': '本周',
|
||||
'month': '本月',
|
||||
'total_tasks': '总任务数',
|
||||
'executing_tasks': '执行中',
|
||||
'completed_tasks': '已完成',
|
||||
'cancellation_rate': '取消率',
|
||||
'completion_rate': '完成率',
|
||||
'online_vehicles': '在线车辆',
|
||||
'current_tasks': '当前任务',
|
||||
'refresh': '刷新',
|
||||
'task_code': '任务编号',
|
||||
'task_type': '任务类型',
|
||||
'target_point': '目标点',
|
||||
'status': '状态',
|
||||
'executing_vehicle': '执行车辆',
|
||||
'create_time': '创建时间',
|
||||
'task_pending': '待执行',
|
||||
'task_executing': '执行中',
|
||||
'task_completed': '已完成',
|
||||
'task_cancelled': '已取消',
|
||||
'vehicle_status': '车辆状态',
|
||||
'vehicle_idle': '空闲',
|
||||
'battery': '电量',
|
||||
'signal': '信号',
|
||||
'ice': '冰',
|
||||
'water': '水',
|
||||
'room_status': '房间状态',
|
||||
'no_vehicle_data': '暂无车辆数据',
|
||||
'no_room_data': '暂无房间数据',
|
||||
'room_executing': '执行中',
|
||||
'room_idle': '空闲'
|
||||
},
|
||||
'monitor': {
|
||||
'sys': '系统',
|
||||
|
||||
@@ -5,6 +5,7 @@ import store from '../store'
|
||||
import { getToken } from '@/utils/auth'
|
||||
import Config from '@/settings'
|
||||
import Cookies from 'js-cookie'
|
||||
import i18n from '@/i18n'
|
||||
|
||||
const baseURLStr = window.g.prod.VUE_APP_BASE_API
|
||||
|
||||
@@ -55,7 +56,7 @@ service.interceptors.response.use(
|
||||
} catch (e) {
|
||||
if (error.toString().indexOf('Error: timeout') !== -1) {
|
||||
Notification.error({
|
||||
title: '网络请求超时',
|
||||
title: i18n.t('auto.common.request_timeout'),
|
||||
duration: 5000
|
||||
})
|
||||
return Promise.reject(error)
|
||||
@@ -82,7 +83,7 @@ service.interceptors.response.use(
|
||||
}
|
||||
} else {
|
||||
Notification.error({
|
||||
title: '接口请求失败',
|
||||
title: i18n.t('auto.common.request_failed'),
|
||||
duration: 5000
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<el-button size="small" type="warning" icon="el-icon-s-home" @click="openGoHome">Go Home</el-button>
|
||||
<el-button size="small" type="success" icon="el-icon-map-location" :loading="syncing" @click="syncMap">{{ $t('hotel.sync_map') }}</el-button>
|
||||
<el-button size="small" icon="el-icon-refresh" :loading="loading" @click="fetchData(true)">{{ $t('hotel.refresh') }}</el-button>
|
||||
<span class="update-time">{{ $t('hotel.last_update') }}: {{ lastUpdateTime }}</span>
|
||||
</div>
|
||||
@@ -172,7 +173,7 @@
|
||||
|
||||
<script>
|
||||
import { createTask, cancelTask, oneClickOperation } from '@/api/frobot/task/task'
|
||||
import { queryAllStationWithTaskStatus } from '@/api/frobot/map/station/station'
|
||||
import { queryAllStationWithTaskStatus, syncCurrentMapData } from '@/api/frobot/map/station/station'
|
||||
import { getVehicleList } from '@/api/frobot/vehicle/vehicle'
|
||||
|
||||
export default {
|
||||
@@ -197,6 +198,7 @@ export default {
|
||||
type: [{ required: true, message: this.$t('hotel.create.type_required'), trigger: 'change' }]
|
||||
},
|
||||
timer: null,
|
||||
syncing: false,
|
||||
vehicleList: [],
|
||||
goHomeDiaVisible: false,
|
||||
goingHome: false,
|
||||
@@ -227,7 +229,8 @@ export default {
|
||||
}
|
||||
this.lastUpdateTime = new Date().toLocaleTimeString()
|
||||
}).catch(err => {
|
||||
console.error('查询房间状态失败:', err)
|
||||
const errMsg = (err.response && err.response.data && err.response.data.message) || err.message
|
||||
console.error('查询房间状态失败:', errMsg)
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
@@ -264,8 +267,9 @@ export default {
|
||||
this.$message.error(res.message || this.$t('hotel.create.failed'))
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error('下发任务失败:', err)
|
||||
this.$message.error(this.$t('hotel.create.failed_retry'))
|
||||
const errMsg = (err.response && err.response.data && err.response.data.message) || err.message
|
||||
console.error('下发任务失败:', errMsg)
|
||||
this.$message.error(errMsg || this.$t('hotel.create.failed_retry'))
|
||||
}).finally(() => {
|
||||
this.submitting = false
|
||||
})
|
||||
@@ -281,8 +285,9 @@ export default {
|
||||
this.cancelDiaVisible = false
|
||||
setTimeout(() => this.fetchData(false), 800)
|
||||
}).catch(err => {
|
||||
console.error('取消任务失败:', err)
|
||||
this.$message.error(this.$t('hotel.cancel.failed'))
|
||||
const errMsg = (err.response && err.response.data && err.response.data.message) || err.message
|
||||
console.error('取消任务失败:', errMsg)
|
||||
this.$message.error(errMsg || this.$t('hotel.cancel.failed'))
|
||||
}).finally(() => {
|
||||
this.cancelling = false
|
||||
})
|
||||
@@ -302,6 +307,25 @@ export default {
|
||||
this.selectedRoom = null
|
||||
},
|
||||
|
||||
// 同步地图
|
||||
syncMap() {
|
||||
this.syncing = true
|
||||
syncCurrentMapData().then(res => {
|
||||
if (res.code === 200) {
|
||||
this.$message.success(this.$t('hotel.sync_map_success'))
|
||||
setTimeout(() => this.fetchData(false), 800)
|
||||
} else {
|
||||
this.$message.error(res.message || this.$t('hotel.sync_map_failed'))
|
||||
}
|
||||
}).catch(err => {
|
||||
const errMsg = (err.response && err.response.data && err.response.data.message) || err.message
|
||||
console.error('同步地图失败:', errMsg)
|
||||
this.$message.error(errMsg || this.$t('hotel.sync_map_failed'))
|
||||
}).finally(() => {
|
||||
this.syncing = false
|
||||
})
|
||||
},
|
||||
|
||||
// 加载车辆列表
|
||||
fetchVehicleList() {
|
||||
getVehicleList().then(res => {
|
||||
@@ -309,7 +333,8 @@ export default {
|
||||
this.vehicleList = Object.values(res.data)
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error('查询车辆列表失败:', err)
|
||||
const errMsg = (err.response && err.response.data && err.response.data.message) || err.message
|
||||
console.error('查询车辆列表失败:', errMsg)
|
||||
})
|
||||
},
|
||||
|
||||
@@ -335,8 +360,9 @@ export default {
|
||||
this.$message.error(res.message || 'Go Home 指令下发失败')
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error('Go Home 失败:', err)
|
||||
this.$message.error(this.$t('hotel.go_home.failed'))
|
||||
const errMsg = (err.response && err.response.data && err.response.data.message) || err.message
|
||||
console.error('Go Home 失败:', errMsg)
|
||||
this.$message.error(errMsg || this.$t('hotel.go_home.failed'))
|
||||
}).finally(() => {
|
||||
this.goingHome = false
|
||||
})
|
||||
|
||||
@@ -1,291 +1,521 @@
|
||||
<template>
|
||||
<div v-loading="!show" element-loading-text="数据加载中..." :style="!show ? 'height: 500px' : 'height: 100%'" class="app-container">
|
||||
<div v-if="show">
|
||||
<el-card class="box-card">
|
||||
<div style="color: #666;font-size: 13px;">
|
||||
<svg-icon icon-class="system" style="margin-right: 5px" />
|
||||
<span>
|
||||
{{ $t('auto.monitor.sys') }}:{{ data.sys.os }}
|
||||
</span>
|
||||
<span>
|
||||
IP:{{ data.sys.ip }}
|
||||
</span>
|
||||
<span>
|
||||
{{ $t('auto.monitor.day') }}:{{ data.sys.day }}
|
||||
</span>
|
||||
<i class="el-icon-refresh" style="margin-left: 40px" @click="init" />
|
||||
</div>
|
||||
</el-card>
|
||||
<el-card class="box-card">
|
||||
<div slot="header" class="clearfix">
|
||||
<span style="font-weight: bold;color: #666;font-size: 15px">{{ $t('auto.monitor.status') }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="6" style="margin-bottom: 10px">
|
||||
<div class="title">{{ $t('auto.monitor.cpu') }}</div>
|
||||
<el-tooltip placement="top-end">
|
||||
<div slot="content" style="font-size: 12px;">
|
||||
<div style="padding: 3px;">
|
||||
{{ data.cpu.name }}
|
||||
</div>
|
||||
<div style="padding: 3px">
|
||||
{{ data.cpu.package }}
|
||||
</div>
|
||||
<div style="padding: 3px">
|
||||
{{ data.cpu.core }}
|
||||
</div>
|
||||
<div style="padding: 3px">
|
||||
{{ data.cpu.logic }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<el-progress type="dashboard" :percentage="parseFloat(data.cpu.used)" />
|
||||
</div>
|
||||
</el-tooltip>
|
||||
<div class="footer">{{ data.cpu.coreNumber }} {{ $t('auto.monitor.core') }}</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="6" style="margin-bottom: 10px">
|
||||
<div class="title">{{ $t('auto.monitor.memory') }}</div>
|
||||
<el-tooltip placement="top-end">
|
||||
<div slot="content" style="font-size: 12px;">
|
||||
<div style="padding: 3px;">
|
||||
{{ $t('auto.monitor.tality') }}:{{ data.memory.total }}
|
||||
</div>
|
||||
<div style="padding: 3px">
|
||||
{{ $t('auto.monitor.used') }}:{{ data.memory.used }}
|
||||
</div>
|
||||
<div style="padding: 3px">
|
||||
{{ $t('auto.monitor.leisure') }}:{{ data.memory.available }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<el-progress type="dashboard" :percentage="parseFloat(data.memory.usageRate)" />
|
||||
</div>
|
||||
</el-tooltip>
|
||||
<div class="footer">{{ data.memory.used }} / {{ data.memory.total }}</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="6" style="margin-bottom: 10px">
|
||||
<div class="title">{{ $t('auto.monitor.exchange') }}</div>
|
||||
<el-tooltip placement="top-end">
|
||||
<div slot="content" style="font-size: 12px;">
|
||||
<div style="padding: 3px;">
|
||||
{{ $t('auto.monitor.tality') }}:{{ data.swap.total }}
|
||||
</div>
|
||||
<div style="padding: 3px">
|
||||
{{ $t('auto.monitor.used') }}:{{ data.swap.used }}
|
||||
</div>
|
||||
<div style="padding: 3px">
|
||||
{{ $t('auto.monitor.leisure') }}:{{ data.swap.available }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<el-progress type="dashboard" :percentage="parseFloat(data.swap.usageRate)" />
|
||||
</div>
|
||||
</el-tooltip>
|
||||
<div class="footer">{{ data.swap.used }} / {{ data.swap.total }}</div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="24" :md="6" :lg="6" :xl="6" style="margin-bottom: 10px">
|
||||
<div class="title">{{ $t('auto.monitor.disk') }}</div>
|
||||
<div class="content">
|
||||
<el-tooltip placement="top-end">
|
||||
<div slot="content" style="font-size: 12px;">
|
||||
<div style="padding: 3px">
|
||||
{{ $t('auto.monitor.tality') }}:{{ data.disk.total }}
|
||||
</div>
|
||||
<div style="padding: 3px">
|
||||
{{ $t('auto.monitor.leisure') }}:{{ data.disk.available }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<el-progress type="dashboard" :percentage="parseFloat(data.disk.usageRate)" />
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="footer">{{ data.disk.used }} / {{ data.disk.total }}</div>
|
||||
</el-col>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<div>
|
||||
<el-row :gutter="6">
|
||||
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12" style="margin-bottom: 10px">
|
||||
<el-card class="box-card">
|
||||
<div slot="header" class="clearfix">
|
||||
<span style="font-weight: bold;color: #666;font-size: 15px">{{ $t('auto.monitor.cpu_monitoring') }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<v-chart :options="cpuInfo" />
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12" style="margin-bottom: 10px">
|
||||
<el-card class="box-card">
|
||||
<div slot="header" class="clearfix">
|
||||
<span style="font-weight: bold;color: #666;font-size: 15px">{{ $t('auto.monitor.memory_monitoring') }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<v-chart :options="memoryInfo" />
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<div class="app-container task-dashboard">
|
||||
<!-- 时间范围切换 -->
|
||||
<div class="time-range-selector">
|
||||
<el-button-group>
|
||||
<el-button
|
||||
v-for="range in timeRanges"
|
||||
:key="range.value"
|
||||
:type="selectedTimeRange === range.value ? 'primary' : 'default'"
|
||||
size="small"
|
||||
@click="onTimeRangeChange(range.value)"
|
||||
>
|
||||
{{ $t(range.label) }}
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
</div>
|
||||
|
||||
<!-- 顶部统计卡片 -->
|
||||
<el-row :gutter="16" class="stat-row">
|
||||
<el-col :span="4">
|
||||
<div class="stat-card stat-total">
|
||||
<div class="stat-icon"><i class="el-icon-s-order" /></div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-num">{{ dashboardStats.totalTasks || 0 }}</div>
|
||||
<div class="stat-label">{{ $t('auto.dashboard.total_tasks') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<div class="stat-card stat-running">
|
||||
<div class="stat-icon"><i class="el-icon-loading" /></div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-num">{{ dashboardStats.executingTasks || 0 }}</div>
|
||||
<div class="stat-label">{{ $t('auto.dashboard.executing_tasks') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<div class="stat-card stat-done">
|
||||
<div class="stat-icon"><i class="el-icon-circle-check" /></div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-num">{{ dashboardStats.completedTasks || 0 }}</div>
|
||||
<div class="stat-label">{{ $t('auto.dashboard.completed_tasks') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<div class="stat-card stat-cancel">
|
||||
<div class="stat-icon"><i class="el-icon-circle-close" /></div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-num">{{ dashboardStats.cancellationRate ? dashboardStats.cancellationRate.toFixed(1) + '%' : '--' }}</div>
|
||||
<div class="stat-label">{{ $t('auto.dashboard.cancellation_rate') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<div class="stat-card stat-rate">
|
||||
<div class="stat-icon"><i class="el-icon-data-analysis" /></div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-num">{{ dashboardStats.completionRate ? dashboardStats.completionRate.toFixed(1) + '%' : '--' }}</div>
|
||||
<div class="stat-label">{{ $t('auto.dashboard.completion_rate') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<div class="stat-card stat-vehicle">
|
||||
<div class="stat-icon"><i class="el-icon-truck" /></div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-num">{{ stats.vehicles }}</div>
|
||||
<div class="stat-label">{{ $t('auto.dashboard.online_vehicles') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="16" style="margin-top:16px;">
|
||||
<!-- 左侧:当前任务列表 -->
|
||||
<el-col :span="16">
|
||||
<el-card class="board-card">
|
||||
<div slot="header" class="card-header">
|
||||
<span><i class="el-icon-s-order" /> {{ $t('auto.dashboard.current_tasks') }}</span>
|
||||
<el-button type="text" icon="el-icon-refresh" @click="fetchAll()">{{ $t('auto.dashboard.refresh') }}</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
v-loading="taskLoading"
|
||||
:data="taskList"
|
||||
size="small"
|
||||
:max-height="tableHeight"
|
||||
style="width:100%;"
|
||||
>
|
||||
<el-table-column prop="task_code" :label="$t('auto.dashboard.task_code')" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column prop="type" :label="$t('auto.dashboard.task_type')" width="100" />
|
||||
<el-table-column prop="destinations" :label="$t('auto.dashboard.target_point')" width="80" align="center" />
|
||||
<el-table-column :label="$t('auto.dashboard.status')" width="90" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="getTaskTagType(scope.row.status)" size="mini" effect="dark">
|
||||
{{ getTaskStatusText(scope.row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="processingVehicle" :label="$t('auto.dashboard.executing_vehicle')" width="100" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span v-if="scope.row.processingVehicle" style="color:#409eff;">
|
||||
<i class="el-icon-truck" /> {{ scope.row.processingVehicle }}
|
||||
</span>
|
||||
<span v-else style="color:#c0c4cc;">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="create_time" :label="$t('auto.dashboard.create_time')" min-width="140" show-overflow-tooltip />
|
||||
</el-table>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<!-- 右侧:车辆状态 + 房间状态 -->
|
||||
<el-col :span="8">
|
||||
<!-- 车辆状态 -->
|
||||
<el-card class="board-card" style="margin-bottom:16px;">
|
||||
<div slot="header" class="card-header">
|
||||
<span><i class="el-icon-truck" /> {{ $t('auto.dashboard.vehicle_status') }}</span>
|
||||
</div>
|
||||
<div v-loading="vehicleLoading" class="vehicle-list">
|
||||
<div v-for="v in vehicleList" :key="v.vehicleNumber" class="vehicle-item">
|
||||
<div class="vehicle-header">
|
||||
<div class="vehicle-name">
|
||||
<i class="el-icon-truck" /> {{ v.vehicleNumber }}
|
||||
<el-tag
|
||||
v-if="isVehicleRunning(v.vehicleNumber)"
|
||||
type="warning"
|
||||
size="mini"
|
||||
effect="dark"
|
||||
style="margin-left:6px;"
|
||||
>{{ $t('auto.dashboard.executing_tasks') }}</el-tag>
|
||||
<el-tag
|
||||
v-else
|
||||
type="success"
|
||||
size="mini"
|
||||
effect="plain"
|
||||
style="margin-left:6px;"
|
||||
>{{ $t('auto.dashboard.vehicle_idle') }}</el-tag>
|
||||
</div>
|
||||
<el-tag v-if="v.error_code !== 0" type="danger" size="mini" style="margin-left:6px;">{{ $t('auto.common.error') }}</el-tag>
|
||||
</div>
|
||||
<div class="vehicle-row">
|
||||
<div class="vehicle-item-cell">
|
||||
<span class="cell-label">{{ $t('auto.dashboard.battery') }}</span>
|
||||
<el-progress
|
||||
:percentage="v.batteryLevel"
|
||||
:color="getBatteryColor(v.batteryLevel)"
|
||||
:stroke-width="5"
|
||||
:format="() => v.batteryLevel + '%'"
|
||||
style="width:60px;"
|
||||
/>
|
||||
</div>
|
||||
<div class="vehicle-item-cell">
|
||||
<span class="cell-label">{{ $t('auto.dashboard.signal') }}</span>
|
||||
<el-tag :type="getSignalType(v.signalStrength)" size="mini">{{ v.signalStrength }}</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="vehicle-row">
|
||||
<div class="vehicle-item-cell">
|
||||
<span class="cell-label">{{ $t('auto.dashboard.ice') }}</span>
|
||||
<el-progress
|
||||
:percentage="v.iceCapacity || 0"
|
||||
:stroke-width="4"
|
||||
:format="() => (v.iceCapacity || 0) + '%'"
|
||||
style="width:60px;"
|
||||
color="#4facfe"
|
||||
/>
|
||||
</div>
|
||||
<div class="vehicle-item-cell">
|
||||
<span class="cell-label">{{ $t('auto.dashboard.water') }}</span>
|
||||
<el-progress
|
||||
:percentage="v.waterCapacity || 0"
|
||||
:stroke-width="4"
|
||||
:format="() => (v.waterCapacity || 0) + '%'"
|
||||
style="width:60px;"
|
||||
color="#43e97b"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="vehicleList.length === 0 && !vehicleLoading" class="empty-tip">{{ $t('auto.dashboard.no_vehicle_data') }}</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 房间任务状态 -->
|
||||
<el-card class="board-card">
|
||||
<div slot="header" class="card-header">
|
||||
<span><i class="el-icon-office-building" /> {{ $t('auto.dashboard.room_status') }}</span>
|
||||
</div>
|
||||
<div v-loading="roomLoading" class="room-mini-grid">
|
||||
<div
|
||||
v-for="room in roomList"
|
||||
:key="room.station_code"
|
||||
class="room-mini-card"
|
||||
:class="room.hasTask ? 'room-mini-busy' : 'room-mini-idle'"
|
||||
>
|
||||
<div class="room-mini-no">{{ room.station_name }}</div>
|
||||
<div class="room-mini-status">{{ room.hasTask ? $t('auto.dashboard.room_executing') : $t('auto.dashboard.room_idle') }}</div>
|
||||
</div>
|
||||
<div v-if="roomList.length === 0 && !roomLoading" class="empty-tip">{{ $t('auto.dashboard.no_room_data') }}</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ECharts from 'vue-echarts'
|
||||
import 'echarts/lib/chart/line'
|
||||
import 'echarts/lib/component/polar'
|
||||
import { initData } from '@/api/data'
|
||||
import { queryCurrentTaskInfo } from '@/api/frobot/task/task'
|
||||
import { getVehicleList } from '@/api/frobot/vehicle/vehicle'
|
||||
import { queryAllStationWithTaskStatus } from '@/api/frobot/map/station/station'
|
||||
import { queryDashboardData } from '@/api/dashboard'
|
||||
|
||||
export default {
|
||||
name: 'ServerMonitor',
|
||||
components: {
|
||||
'v-chart': ECharts
|
||||
},
|
||||
name: 'TaskDashboard',
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
monitor: null,
|
||||
url: 'api/monitor',
|
||||
data: {},
|
||||
cpuInfo: {
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: []
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
min: 0,
|
||||
max: 100,
|
||||
interval: 20
|
||||
},
|
||||
series: [{
|
||||
data: [],
|
||||
type: 'line',
|
||||
areaStyle: {
|
||||
normal: {
|
||||
color: 'rgb(32, 160, 255)' // 改变区域颜色
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: '#6fbae1',
|
||||
lineStyle: {
|
||||
color: '#6fbae1' // 改变折线颜色
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
},
|
||||
memoryInfo: {
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: []
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
min: 0,
|
||||
max: 100,
|
||||
interval: 20
|
||||
},
|
||||
series: [{
|
||||
data: [],
|
||||
type: 'line',
|
||||
areaStyle: {
|
||||
normal: {
|
||||
color: 'rgb(32, 160, 255)' // 改变区域颜色
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: '#6fbae1',
|
||||
lineStyle: {
|
||||
color: '#6fbae1' // 改变折线颜色
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
taskLoading: false,
|
||||
vehicleLoading: false,
|
||||
roomLoading: false,
|
||||
taskList: [],
|
||||
vehicleList: [],
|
||||
roomList: [],
|
||||
timer: null,
|
||||
tableHeight: 420,
|
||||
selectedTimeRange: 'today',
|
||||
timeRanges: [
|
||||
{ label: 'auto.dashboard.today', value: 'today' },
|
||||
{ label: 'auto.dashboard.week', value: 'week' },
|
||||
{ label: 'auto.dashboard.month', value: 'month' }
|
||||
],
|
||||
dashboardStats: {
|
||||
totalTasks: 0,
|
||||
executingTasks: 0,
|
||||
completedTasks: 0,
|
||||
canceledTasks: 0,
|
||||
completionRate: 0,
|
||||
cancellationRate: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.init()
|
||||
this.monitor = window.setInterval(() => {
|
||||
setTimeout(() => {
|
||||
this.init()
|
||||
}, 2)
|
||||
}, 3500)
|
||||
computed: {
|
||||
stats() {
|
||||
const total = this.taskList.length
|
||||
const running = this.taskList.filter(t => t.status === '1').length
|
||||
const done = this.taskList.filter(t => t.status === '2').length
|
||||
const cancelled = this.taskList.filter(t => t.status === '3').length
|
||||
const vehicles = this.vehicleList.length
|
||||
const finished = done + cancelled
|
||||
const doneRate = finished > 0 ? Math.round((done / finished) * 100) + '%' : '--'
|
||||
const cancelRate = finished > 0 ? Math.round((cancelled / finished) * 100) + '%' : '--'
|
||||
return { total, running, done, cancelled, vehicles, doneRate, cancelRate }
|
||||
}
|
||||
},
|
||||
destroyed() {
|
||||
clearInterval(this.monitor)
|
||||
created() {
|
||||
this.fetchDashboardStats()
|
||||
this.fetchAll(true)
|
||||
this.timer = setInterval(() => this.fetchAll(false), 3000)
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.timer) clearInterval(this.timer)
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
initData(this.url, {}).then(data => {
|
||||
this.data = data
|
||||
this.show = true
|
||||
if (this.cpuInfo.xAxis.data.length >= 8) {
|
||||
this.cpuInfo.xAxis.data.shift()
|
||||
this.memoryInfo.xAxis.data.shift()
|
||||
this.cpuInfo.series[0].data.shift()
|
||||
this.memoryInfo.series[0].data.shift()
|
||||
onTimeRangeChange(timeRange) {
|
||||
this.selectedTimeRange = timeRange
|
||||
this.fetchDashboardStats()
|
||||
},
|
||||
|
||||
fetchDashboardStats() {
|
||||
queryDashboardData(this.selectedTimeRange).then(res => {
|
||||
if (res.code === 200 && res.data) {
|
||||
this.dashboardStats = res.data
|
||||
} else {
|
||||
this.dashboardStats = {
|
||||
totalTasks: 0,
|
||||
executingTasks: 0,
|
||||
completedTasks: 0,
|
||||
canceledTasks: 0,
|
||||
completionRate: 0,
|
||||
cancellationRate: 0
|
||||
}
|
||||
}
|
||||
this.cpuInfo.xAxis.data.push(data.time)
|
||||
this.memoryInfo.xAxis.data.push(data.time)
|
||||
this.cpuInfo.series[0].data.push(parseFloat(data.cpu.used))
|
||||
this.memoryInfo.series[0].data.push(parseFloat(data.memory.usageRate))
|
||||
}).catch(err => {
|
||||
console.error('查询看板数据失败:', err)
|
||||
})
|
||||
},
|
||||
|
||||
fetchAll(showLoading = false) {
|
||||
this.fetchDashboardStats()
|
||||
this.fetchTasks(showLoading)
|
||||
this.fetchVehicles(showLoading)
|
||||
this.fetchRooms()
|
||||
},
|
||||
|
||||
fetchTasks(showLoading) {
|
||||
if (showLoading) this.taskLoading = true
|
||||
queryCurrentTaskInfo().then(res => {
|
||||
if (res && Array.isArray(res)) {
|
||||
this.taskList = res
|
||||
} else if (res && res.code === 200 && Array.isArray(res.data)) {
|
||||
this.taskList = res.data
|
||||
} else {
|
||||
this.taskList = []
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error('查询任务失败:', err)
|
||||
}).finally(() => {
|
||||
this.taskLoading = false
|
||||
})
|
||||
},
|
||||
|
||||
fetchVehicles(showLoading) {
|
||||
if (showLoading) this.vehicleLoading = true
|
||||
getVehicleList().then(res => {
|
||||
if (res.code === 200 && res.data) {
|
||||
this.vehicleList = Object.values(res.data)
|
||||
} else {
|
||||
this.vehicleList = []
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error('查询车辆失败:', err)
|
||||
}).finally(() => {
|
||||
this.vehicleLoading = false
|
||||
})
|
||||
},
|
||||
|
||||
fetchRooms() {
|
||||
queryAllStationWithTaskStatus().then(res => {
|
||||
if (res.code === 200 && Array.isArray(res.data)) {
|
||||
this.roomList = res.data
|
||||
} else {
|
||||
this.roomList = []
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error('查询房间状态失败:', err)
|
||||
})
|
||||
},
|
||||
|
||||
isVehicleRunning(vehicleNumber) {
|
||||
return this.taskList.some(
|
||||
t => t.processingVehicle === vehicleNumber && (t.status === '0' || t.status === '1')
|
||||
)
|
||||
},
|
||||
|
||||
getTaskTagType(status) {
|
||||
const map = { '0': 'info', '1': 'warning', '2': 'success', '3': 'danger' }
|
||||
return map[status] || 'info'
|
||||
},
|
||||
|
||||
getTaskStatusText(status) {
|
||||
const map = {
|
||||
'0': this.$t('auto.dashboard.task_pending'),
|
||||
'1': this.$t('auto.dashboard.task_executing'),
|
||||
'2': this.$t('auto.dashboard.task_completed'),
|
||||
'3': this.$t('auto.dashboard.task_cancelled')
|
||||
}
|
||||
return map[status] || status
|
||||
},
|
||||
|
||||
getBatteryColor(level) {
|
||||
if (level > 60) return '#67C23A'
|
||||
if (level > 30) return '#E6A23C'
|
||||
return '#F56C6C'
|
||||
},
|
||||
|
||||
getSignalType(strength) {
|
||||
if (strength >= 15) return 'success'
|
||||
if (strength >= 10) return 'warning'
|
||||
return 'danger'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
::v-deep .box-card {
|
||||
margin-bottom: 5px;
|
||||
span {
|
||||
margin-right: 28px;
|
||||
}
|
||||
.el-icon-refresh {
|
||||
margin-right: 10px;
|
||||
float: right;
|
||||
cursor:pointer;
|
||||
}
|
||||
}
|
||||
.cpu, .memory, .swap, .disk {
|
||||
width: 20%;
|
||||
float: left;
|
||||
padding-bottom: 20px;
|
||||
margin-right: 5%;
|
||||
}
|
||||
.title {
|
||||
text-align: center;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: #999;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.footer {
|
||||
text-align: center;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: #999;
|
||||
margin-top: -5px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.content {
|
||||
text-align: center;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
<style scoped>
|
||||
.task-dashboard {
|
||||
font-family: 'Noto Sans SC', 'Microsoft YaHei', sans-serif;
|
||||
}
|
||||
|
||||
/* ===== 时间范围选择 ===== */
|
||||
.time-range-selector {
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
/* ===== 统计卡片 ===== */
|
||||
.stat-row { margin-bottom: 4px; }
|
||||
.stat-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 18px 20px;
|
||||
border-radius: 10px;
|
||||
color: #fff;
|
||||
}
|
||||
.stat-total { background: linear-gradient(135deg, #667eea, #764ba2); }
|
||||
.stat-running { background: linear-gradient(135deg, #f6d365, #fda085); }
|
||||
.stat-done { background: linear-gradient(135deg, #43e97b, #38f9d7); }
|
||||
.stat-cancel { background: linear-gradient(135deg, #f5576c, #f093fb); }
|
||||
.stat-rate { background: linear-gradient(135deg, #4facfe, #00f2fe); }
|
||||
.stat-vehicle { background: linear-gradient(135deg, #43c6ac, #191654); }
|
||||
|
||||
.stat-icon i {
|
||||
font-size: 36px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
.stat-num {
|
||||
font-size: 32px;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
}
|
||||
.stat-label {
|
||||
font-size: 13px;
|
||||
opacity: 0.85;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* ===== 看板卡片 ===== */
|
||||
.board-card { border-radius: 8px; }
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
font-size: 14px;
|
||||
}
|
||||
.card-header i { margin-right: 6px; color: #409eff; }
|
||||
|
||||
/* ===== 车辆列表 ===== */
|
||||
.vehicle-list { max-height: 280px; overflow-y: auto; }
|
||||
.vehicle-item {
|
||||
padding: 10px 6px;
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
}
|
||||
.vehicle-item:last-child { border-bottom: none; }
|
||||
.vehicle-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.vehicle-name {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
flex: 1;
|
||||
}
|
||||
.vehicle-row {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.vehicle-item-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.cell-label {
|
||||
font-size: 11px;
|
||||
color: #909399;
|
||||
white-space: nowrap;
|
||||
min-width: 28px;
|
||||
}
|
||||
|
||||
/* ===== 房间迷你网格 ===== */
|
||||
.room-mini-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
max-height: 320px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.room-mini-card {
|
||||
width: 68px;
|
||||
height: 64px;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
.room-mini-idle {
|
||||
background: #f0f9eb;
|
||||
border-color: #b3e19d;
|
||||
}
|
||||
.room-mini-busy {
|
||||
background: #fdf6ec;
|
||||
border-color: #f5dab1;
|
||||
}
|
||||
.room-mini-no {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: #303133;
|
||||
line-height: 1;
|
||||
max-width: 62px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
}
|
||||
.room-mini-status {
|
||||
font-size: 10px;
|
||||
color: #909399;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
/* ===== 空数据 ===== */
|
||||
.empty-tip {
|
||||
text-align: center;
|
||||
color: #c0c4cc;
|
||||
padding: 20px 0;
|
||||
font-size: 13px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user