700 lines
20 KiB
Vue
700 lines
20 KiB
Vue
<template>
|
|
<div class="app-container">
|
|
<!-- 统计卡片 -->
|
|
<el-row :gutter="20" class="stats-row">
|
|
<el-col :span="4" >
|
|
<el-card class="stats-card">
|
|
<div class="stats-content">
|
|
<div class="stats-icon agv-icon">
|
|
<i class="el-icon-truck"></i>
|
|
</div>
|
|
<div class="stats-info">
|
|
<div class="stats-number">{{ deviceStats.agvCount }}</div>
|
|
<div class="stats-label">AGV设备总数</div>
|
|
</div>
|
|
</div>
|
|
</el-card>
|
|
</el-col>
|
|
<el-col :span="4">
|
|
<el-card class="stats-card">
|
|
<div class="stats-content">
|
|
<div class="stats-icon ddj-icon">
|
|
<i class="el-icon-cpu"></i>
|
|
</div>
|
|
<div class="stats-info">
|
|
<div class="stats-number">{{ deviceStats.ddjCount }}</div>
|
|
<div class="stats-label">堆垛机设备总数</div>
|
|
</div>
|
|
</div>
|
|
</el-card>
|
|
</el-col>
|
|
<el-col :span="4">
|
|
<el-card class="stats-card">
|
|
<div class="stats-content">
|
|
<div class="stats-icon ctu-icon">
|
|
<i class="el-icon-s-grid"></i>
|
|
</div>
|
|
<div class="stats-info">
|
|
<div class="stats-number">{{ deviceStats.ssjCount }}</div>
|
|
<div class="stats-label">输送机设备总数</div>
|
|
</div>
|
|
</div>
|
|
</el-card>
|
|
</el-col>
|
|
<el-col :span="4">
|
|
<el-card class="stats-card">
|
|
<div class="stats-content">
|
|
<div class="stats-icon queue-icon">
|
|
<i class="el-icon-time"></i>
|
|
</div>
|
|
<div class="stats-info">
|
|
<div class="stats-number">{{ taskStats.pendingCount }}</div>
|
|
<div class="stats-label">待执行任务</div> <!-- 待执行任务 -->
|
|
</div>
|
|
</div>
|
|
</el-card>
|
|
</el-col>
|
|
<el-col :span="4">
|
|
<el-card class="stats-card">
|
|
<div class="stats-content">
|
|
<div class="stats-icon running-icon">
|
|
<i class="el-icon-loading"></i>
|
|
</div>
|
|
<div class="stats-info">
|
|
<div class="stats-number">{{ taskStats.runningCount }}</div>
|
|
<div class="stats-label">执行中任务</div> <!-- 执行中任务 -->
|
|
</div>
|
|
</div>
|
|
</el-card>
|
|
</el-col>
|
|
<el-col :span="4">
|
|
<el-card class="stats-card">
|
|
<div class="stats-content">
|
|
<div class="stats-icon finish-icon">
|
|
<i class="el-icon-check"></i>
|
|
</div>
|
|
<div class="stats-info">
|
|
<div class="stats-number">{{ taskStats.finishedCount }}</div>
|
|
<div class="stats-label">已完成任务</div> <!-- 已完成任务 -->
|
|
</div>
|
|
</div>
|
|
</el-card>
|
|
</el-col>
|
|
</el-row>
|
|
|
|
<!-- 图表区域 -->
|
|
<el-row :gutter="20" class="chart-row">
|
|
<el-col :span="12">
|
|
<el-card class="chart-card">
|
|
<div slot="header" class="card-header">
|
|
<span>任务类型分布</span> <!-- 任务类型分布 -->
|
|
</div>
|
|
<div id="taskTypeChart" style="height: 300px;"></div>
|
|
</el-card>
|
|
</el-col>
|
|
<el-col :span="12">
|
|
<el-card class="chart-card">
|
|
<div slot="header" class="card-header">
|
|
<span>设备状态分布</span> <!-- 设备状态分布 -->
|
|
</div>
|
|
<div id="deviceStatusChart" style="height: 300px;"></div>
|
|
</el-card>
|
|
</el-col>
|
|
</el-row>
|
|
|
|
<!-- 实时任务展示 -->
|
|
<el-row :gutter="20" class="task-row">
|
|
<el-col :span="12">
|
|
<el-card class="task-card">
|
|
<div slot="header" class="card-header">
|
|
<span>正执行作业列表</span> <!-- 当前任务队列 -->
|
|
<el-button type="text" @click="refreshTasks">刷新</el-button> <!-- 刷新 -->
|
|
</div>
|
|
<el-table
|
|
:data="currentTasks"
|
|
style="width: 100%"
|
|
height="300"
|
|
v-loading="loading.tasks">
|
|
<el-table-column prop="inOrOut" label="作业类型" width="100">
|
|
<template slot-scope="scope">
|
|
<dict-tag :options="dict.type.in_or_out" :value="scope.row.inOrOut"/>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="jobId" label="作业ID" width="80"/>
|
|
<el-table-column prop="taskNo" label="任务号" width="80"/>
|
|
<el-table-column prop="executionPhase" label="执行阶段" width="80">
|
|
<template slot-scope="scope">
|
|
<dict-tag :options="dict.type.job_ep" :value="scope.row.executionPhase"/>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="status" :label="$t('index.status')" width="100">
|
|
<template slot-scope="scope">
|
|
<el-tag :type="getStatusType(scope.row.status)">
|
|
<dict-tag :options="dict.type.task_status" :value="scope.row.status"/>
|
|
</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="createTime" label="创建时间" show-overflow-tooltip/>
|
|
</el-table>
|
|
</el-card>
|
|
</el-col>
|
|
<el-col :span="12">
|
|
<el-card class="task-card">
|
|
<div slot="header" class="card-header">
|
|
<span>{{ $t('index.runningDeviceList') }}</span> <!-- 正在运行设备列表 -->
|
|
<el-button type="text" @click="refreshDevices">{{ $t('index.refresh') }}</el-button> <!-- 刷新 -->
|
|
</div>
|
|
<el-table
|
|
:data="deviceList"
|
|
style="width: 100%"
|
|
height="300"
|
|
v-loading="loading.devices">
|
|
<el-table-column prop="deviceCode" :label="$t('index.deviceCode')" width="200"/> <!-- 设备编码 -->
|
|
<el-table-column prop="deviceName" :label="$t('index.deviceName')" width="200"/> <!-- 设备名称 -->
|
|
<el-table-column prop="deviceType" :label="$t('index.type')" width="150"> <!-- 类型 -->
|
|
<template slot-scope="scope">
|
|
<el-tag>{{scope.row.deviceType}}</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="taskNo" :label="$t('index.currentTask')" show-overflow-tooltip/> <!-- 当前任务 -->
|
|
</el-table>
|
|
</el-card>
|
|
</el-col>
|
|
</el-row>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import * as echarts from 'echarts'
|
|
import { getDeviceStats, getTaskStats, getCurrentTasks, getDeviceList, getTaskTypeChart, getDeviceStatusChart } from '@/api/index'
|
|
|
|
export default {
|
|
name: 'Index',
|
|
dicts: ['task_status', 'wms_task_type','job_ep','in_or_out'],
|
|
data() {
|
|
return {
|
|
// 统计数据
|
|
deviceStats: {
|
|
agvCount: 0,
|
|
ctuCount: 0,
|
|
ssjCount: 0
|
|
},
|
|
taskStats: {
|
|
pendingCount: 0,
|
|
runningCount: 0,
|
|
finishedCount: 0
|
|
},
|
|
// 当前任务列表
|
|
currentTasks: [],
|
|
// 设备列表
|
|
deviceList: [],
|
|
// 加载状态
|
|
loading: {
|
|
tasks: false,
|
|
devices: false
|
|
},
|
|
// 图表实例
|
|
taskTypeChart: null,
|
|
deviceStatusChart: null,
|
|
// 定时器
|
|
refreshTimer: null
|
|
}
|
|
},
|
|
created() {
|
|
this.initData()
|
|
// 添加页面可见性监听
|
|
this.handleVisibilityChange = this.handleVisibilityChange.bind(this)
|
|
document.addEventListener('visibilitychange', this.handleVisibilityChange)
|
|
},
|
|
mounted() {
|
|
this.$nextTick(() => {
|
|
this.initCharts()
|
|
})
|
|
// 页面挂载后启动定时器
|
|
this.startTimer()
|
|
},
|
|
activated() {
|
|
// keep-alive组件激活时启动定时器
|
|
this.startTimer()
|
|
},
|
|
deactivated() {
|
|
// keep-alive组件失活时停止定时器
|
|
this.stopTimer()
|
|
},
|
|
beforeDestroy() {
|
|
// 清除定时器
|
|
this.stopTimer()
|
|
// 移除页面可见性监听
|
|
document.removeEventListener('visibilitychange', this.handleVisibilityChange)
|
|
// 销毁图表
|
|
if (this.taskTypeChart) {
|
|
this.taskTypeChart.dispose()
|
|
}
|
|
if (this.deviceStatusChart) {
|
|
this.deviceStatusChart.dispose()
|
|
}
|
|
},
|
|
watch: {
|
|
// 监听路由变化
|
|
'$route'(to, from) {
|
|
// 当路由变化时,检查是否为首页
|
|
if (to.path === '/index' || to.name === 'Index' || to.path === '/') {
|
|
this.startTimer() // 进入首页时启动定时器
|
|
} else {
|
|
this.stopTimer() // 离开首页时停止定时器
|
|
}
|
|
}
|
|
},
|
|
methods: {
|
|
// 启动定时器
|
|
startTimer() {
|
|
// 如果定时器已存在,先清除
|
|
this.stopTimer()
|
|
// 只有在页面可见且为首页时才启动定时器
|
|
if (this.isPageVisible() && this.isIndexPage()) {
|
|
this.refreshTimer = setInterval(() => {
|
|
this.refreshData()
|
|
}, 8000) // 5秒刷新一次
|
|
console.log('定时器已启动') // 定时器已启动
|
|
}
|
|
},
|
|
// 停止定时器
|
|
stopTimer() {
|
|
if (this.refreshTimer) {
|
|
clearInterval(this.refreshTimer)
|
|
this.refreshTimer = null
|
|
console.log('定时器已停止') // 定时器已停止
|
|
}
|
|
},
|
|
// 检查页面是否可见
|
|
isPageVisible() {
|
|
return document.visibilityState === 'visible'
|
|
},
|
|
// 检查是否为首页
|
|
isIndexPage() {
|
|
return this.$route.path === '/index' || this.$route.name === 'Index' || this.$route.path === '/'
|
|
},
|
|
// 处理页面可见性变化
|
|
handleVisibilityChange() {
|
|
if (this.isPageVisible() && this.isIndexPage()) {
|
|
this.startTimer() // 页面变为可见且为首页时启动定时器
|
|
} else {
|
|
this.stopTimer() // 页面不可见时停止定时器
|
|
}
|
|
},
|
|
// 初始化数据
|
|
async initData() {
|
|
await Promise.all([
|
|
this.loadDeviceStats(),
|
|
this.loadTaskStats(),
|
|
this.loadCurrentTasks(),
|
|
this.loadDeviceList()
|
|
])
|
|
},
|
|
// 刷新数据
|
|
async refreshData() {
|
|
await this.initData()
|
|
this.updateCharts()
|
|
},
|
|
// 加载设备统计
|
|
async loadDeviceStats() {
|
|
try {
|
|
const response = await getDeviceStats()
|
|
if (response.code === 200) {
|
|
this.deviceStats = response.data
|
|
}
|
|
} catch (error) {
|
|
console.error('加载设备统计失败:', error) // 加载设备统计失败
|
|
this.$message.error(this.$t('index.loadDeviceStatsError')) // 加载设备统计失败
|
|
}
|
|
},
|
|
// 加载任务统计
|
|
async loadTaskStats() {
|
|
try {
|
|
const response = await getTaskStats()
|
|
if (response.code === 200) {
|
|
this.taskStats = response.data
|
|
}
|
|
} catch (error) {
|
|
console.error('加载任务统计失败:', error) // 加载任务统计失败
|
|
this.$message.error(this.$t('index.loadTaskStatsError')) // 加载任务统计失败
|
|
}
|
|
},
|
|
// 加载当前任务
|
|
async loadCurrentTasks() {
|
|
this.loading.tasks = true
|
|
try {
|
|
const response = await getCurrentTasks()
|
|
if (response.code === 200) {
|
|
this.currentTasks = response.data
|
|
}
|
|
} catch (error) {
|
|
console.error('加载任务列表失败:', error) // 加载任务列表失败
|
|
this.$message.error(this.$t('index.loadTaskListError')) // 加载任务列表失败
|
|
} finally {
|
|
this.loading.tasks = false
|
|
}
|
|
},
|
|
// 加载设备列表
|
|
async loadDeviceList() {
|
|
this.loading.devices = true
|
|
try {
|
|
const response = await getDeviceList()
|
|
if (response.code === 200) {
|
|
this.deviceList = response.data
|
|
}
|
|
} catch (error) {
|
|
console.error('加载设备列表失败:', error) // 加载设备列表失败
|
|
this.$message.error(this.$t('index.loadDeviceListError')) // 加载设备列表失败
|
|
} finally {
|
|
this.loading.devices = false
|
|
}
|
|
},
|
|
// 初始化图表
|
|
initCharts() {
|
|
this.initTaskTypeChart()
|
|
this.initDeviceStatusChart()
|
|
},
|
|
// 初始化任务类型图表
|
|
async initTaskTypeChart() {
|
|
const chartDom = document.getElementById('taskTypeChart')
|
|
this.taskTypeChart = echarts.init(chartDom)
|
|
|
|
try {
|
|
const response = await getTaskTypeChart()
|
|
let chartData = []
|
|
if (response.code === 200 && response.data.length > 0) {
|
|
chartData = response.data.map(item => ({
|
|
...item,
|
|
name: this.selectDictLabel(this.dict.type.wms_task_type, item.name)
|
|
}))
|
|
} else {
|
|
// 默认数据
|
|
chartData = [
|
|
{ value: 45, name: this.$t('index.inboundTask') }, // 入库任务
|
|
{ value: 35, name: this.$t('index.outboundTask') }, // 出库任务
|
|
{ value: 20, name: this.$t('index.transferTask') } // 移库任务
|
|
]
|
|
}
|
|
|
|
const option = {
|
|
tooltip: {
|
|
trigger: 'item'
|
|
},
|
|
legend: {
|
|
orient: 'vertical',
|
|
left: 'left'
|
|
},
|
|
series: [
|
|
{
|
|
name: this.$t('index.taskTypeName'), // 任务类型
|
|
type: 'pie',
|
|
radius: '50%',
|
|
data: chartData,
|
|
emphasis: {
|
|
itemStyle: {
|
|
shadowBlur: 10,
|
|
shadowOffsetX: 0,
|
|
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
this.taskTypeChart.setOption(option)
|
|
} catch (error) {
|
|
console.error('加载任务类型图表数据失败:', error) // 加载任务类型图表数据失败
|
|
}
|
|
},
|
|
// 初始化设备状态图表
|
|
async initDeviceStatusChart() {
|
|
const chartDom = document.getElementById('deviceStatusChart')
|
|
this.deviceStatusChart = echarts.init(chartDom)
|
|
|
|
try {
|
|
const response = await getDeviceStatusChart()
|
|
let runningData = [0, 0, 0]
|
|
let idleData = [0, 0, 0]
|
|
|
|
if (response.code === 200) {
|
|
const statusData = response.data
|
|
|
|
// AGV数据
|
|
if (statusData.AGV) {
|
|
runningData[0] = statusData.AGV['运行'] || 0 // 运行
|
|
idleData[0] = statusData.AGV['其他'] || 0 // 其他
|
|
}
|
|
// CTU数据
|
|
if (statusData.DDJ) {
|
|
runningData[1] = statusData.DDJ['运行'] || 0 // 运行
|
|
idleData[1] = statusData.DDJ['其他'] || 0 // 其他
|
|
}
|
|
// SSJ数据
|
|
if (statusData.SSJ) {
|
|
runningData[2] = statusData.SSJ['运行'] || 0 // 运行
|
|
idleData[2] = statusData.SSJ['其他'] || 0 // 其他
|
|
}
|
|
} else {
|
|
// 默认数据
|
|
runningData = [8, 4, 2]
|
|
idleData = [7, 4, 3]
|
|
}
|
|
|
|
const option = {
|
|
tooltip: {
|
|
trigger: 'axis',
|
|
axisPointer: {
|
|
type: 'shadow'
|
|
}
|
|
},
|
|
legend: {
|
|
data: [this.$t('index.running'), this.$t('index.other')] // 运行, 其他
|
|
},
|
|
xAxis: {
|
|
type: 'category',
|
|
data: ['AGV', 'DDJ', 'SSJ']
|
|
},
|
|
yAxis: {
|
|
type: 'value'
|
|
},
|
|
series: [
|
|
{
|
|
name: this.$t('index.running'), // 运行
|
|
type: 'bar',
|
|
data: runningData,
|
|
itemStyle: { color: '#67C23A' }
|
|
},
|
|
{
|
|
name: this.$t('index.other'), // 其他
|
|
type: 'bar',
|
|
data: idleData,
|
|
itemStyle: { color: '#909399' }
|
|
}
|
|
]
|
|
}
|
|
this.deviceStatusChart.setOption(option)
|
|
} catch (error) {
|
|
console.error('加载设备状态图表数据失败:', error) // 加载设备状态图表数据失败
|
|
// 使用默认数据
|
|
const option = {
|
|
tooltip: {
|
|
trigger: 'axis',
|
|
axisPointer: {
|
|
type: 'shadow'
|
|
}
|
|
},
|
|
legend: {
|
|
data: [this.$t('index.running'), this.$t('index.other')] // 运行, 其他
|
|
},
|
|
xAxis: {
|
|
type: 'category',
|
|
data: ['AGV', 'DDJ', 'SSJ']
|
|
},
|
|
yAxis: {
|
|
type: 'value'
|
|
},
|
|
series: [
|
|
{
|
|
name: this.$t('index.running'), // 运行
|
|
type: 'bar',
|
|
data: [8, 4, 2],
|
|
itemStyle: { color: '#67C23A' }
|
|
},
|
|
{
|
|
name: this.$t('index.other'), // 其他
|
|
type: 'bar',
|
|
data: [7, 4, 3],
|
|
itemStyle: { color: '#909399' }
|
|
}
|
|
]
|
|
}
|
|
this.deviceStatusChart.setOption(option)
|
|
}
|
|
},
|
|
// 更新图表
|
|
async updateCharts() {
|
|
if (this.taskTypeChart) {
|
|
await this.initTaskTypeChart()
|
|
}
|
|
if (this.deviceStatusChart) {
|
|
await this.initDeviceStatusChart()
|
|
}
|
|
},
|
|
// 刷新任务
|
|
refreshTasks() {
|
|
this.loadCurrentTasks()
|
|
},
|
|
// 刷新设备
|
|
refreshDevices() {
|
|
this.loadDeviceList()
|
|
},
|
|
// 获取状态类型
|
|
getStatusType(status) {
|
|
const statusMap = {
|
|
'0': 'info',
|
|
'1': 'warning',
|
|
'2': 'primary',
|
|
'3': 'success',
|
|
'4': 'warning',
|
|
}
|
|
return statusMap[status] || 'info'
|
|
},
|
|
|
|
// 获取设备类型文本
|
|
getDeviceTypeText(type) {
|
|
const typeMap = {
|
|
'1': 'AGV',
|
|
'2': 'CTU',
|
|
'3': 'SSJ'
|
|
}
|
|
return typeMap[type] || this.$t('index.unknown') // 未知
|
|
},
|
|
// 获取设备状态类型
|
|
getDeviceStatusType(status) {
|
|
const statusMap = {
|
|
'运行中': 'success',
|
|
'空闲': 'info',
|
|
'故障': 'danger',
|
|
'维护': 'warning'
|
|
}
|
|
return statusMap[status] || 'info'
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.app-container {
|
|
padding: 20px;
|
|
background-color: #f5f5f5;
|
|
}
|
|
|
|
.stats-row {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.chart-row {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.task-row {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.stats-card {
|
|
height: 120px;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
.stats-card:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.stats-content {
|
|
display: flex;
|
|
align-items: center;
|
|
height: 100%;
|
|
padding: 20px;
|
|
}
|
|
|
|
.stats-icon {
|
|
width: 60px;
|
|
height: 60px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin-right: 20px;
|
|
font-size: 24px;
|
|
color: white;
|
|
}
|
|
|
|
.agv-icon {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
}
|
|
|
|
.ddj-icon {
|
|
background: linear-gradient(135deg, #dcb536 0%, #e5845b 100%);
|
|
}
|
|
|
|
.ctu-icon {
|
|
background: linear-gradient(135deg, #36d1dc 0%, #5b86e5 100%);
|
|
}
|
|
|
|
.queue-icon {
|
|
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
|
}
|
|
|
|
.running-icon {
|
|
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
|
}
|
|
|
|
.finish-icon {
|
|
background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
|
|
}
|
|
|
|
.stats-info {
|
|
flex: 1;
|
|
}
|
|
|
|
.stats-number {
|
|
font-size: 32px;
|
|
font-weight: bold;
|
|
color: #303133;
|
|
line-height: 1;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.stats-label {
|
|
font-size: 14px;
|
|
color: #909399;
|
|
}
|
|
|
|
.chart-card,
|
|
.task-card {
|
|
height: 400px;
|
|
}
|
|
|
|
.card-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
font-weight: bold;
|
|
color: #303133;
|
|
}
|
|
|
|
.el-table {
|
|
font-size: 12px;
|
|
}
|
|
|
|
.el-table th {
|
|
background-color: #fafafa;
|
|
}
|
|
|
|
.el-tag {
|
|
font-size: 12px;
|
|
}
|
|
|
|
/* 响应式设计 */
|
|
@media (max-width: 768px) {
|
|
.stats-row .el-col {
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.chart-row .el-col,
|
|
.task-row .el-col {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.stats-content {
|
|
padding: 15px;
|
|
}
|
|
|
|
.stats-number {
|
|
font-size: 24px;
|
|
}
|
|
}
|
|
</style>
|