opt:1.增加看板内容

This commit is contained in:
2026-03-20 15:32:11 +08:00
parent 3154f371cf
commit 94aab470c0
9 changed files with 632 additions and 285 deletions

View File

@@ -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 }

View File

@@ -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',

View File

@@ -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'
}
}

View File

@@ -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'
}
}

View File

@@ -53,6 +53,9 @@ export default {
'failed': 'Go Home 指令下发失败,请重试'
},
'empty': '暂无房间数据',
'query_failed': '查询房间状态失败,请检查网络'
'query_failed': '查询房间状态失败,请检查网络',
'sync_map': '同步站点',
'sync_map_success': '地图同步成功',
'sync_map_failed': '地图同步失败,请重试'
}
}

View File

@@ -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': '系统',

View File

@@ -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
})
}

View File

@@ -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
})

View File

@@ -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>