feat(停机统计): 优化停机统计页面并添加图表展示功能

重构停机统计页面,主要变更包括:
1. 添加核心指标卡片展示停机次数、总时长和作业率
2. 新增按天汇总的折线图和按类型汇总的饼图
3. 优化数据展示逻辑,增加无数据状态处理
4. 移除冗余CSS样式,简化DOM结构
5. 添加图表自适应功能,优化移动端显示
This commit is contained in:
砂糖
2026-01-21 13:02:22 +08:00
parent 9d35f39906
commit 8139143ecc
2 changed files with 419 additions and 66 deletions

View File

@@ -87,7 +87,7 @@
<div class="section-header"> <div class="section-header">
<span class="section-title">停机详情</span> <span class="section-title">停机详情</span>
</div> </div>
<div class="detail-list"> <div>
<div v-if="tableData.length === 0" class="empty-state"> <div v-if="tableData.length === 0" class="empty-state">
<span class="empty-text">暂无停机记录</span> <span class="empty-text">暂无停机记录</span>
</div> </div>
@@ -104,7 +104,7 @@
<div class="section-header"> <div class="section-header">
<span class="section-title">停机趋势</span> <span class="section-title">停机趋势</span>
</div> </div>
<div class="chart-wrapper trend-chart" ref="trendChart" id="trendChart"></div> <div class="chart-wrapper trend-chart" v-show="activeTab !== 'day'" ref="trendChart" id="trendChart"></div>
</div> </div>
</div> </div>
</template> </template>
@@ -342,7 +342,24 @@ export default {
loading.close() loading.close()
console.log('停机统计响应:', response) console.log('停机统计响应:', response)
if (response.code === 200 && response.rows && response.rows.length > 0) { if (response.code !== 200 || !response.rows || response.rows.length === 0) {
console.log('暂无停机数据')
this.tableData = []
this.summaryData = [
{ label: '停机时间', value: 0, unit: 'min' },
{ label: '停机次数', value: 0, unit: '次' },
{ label: '作业率', value: 100, unit: '%' }
]
this.crewPieData = []
this.typePieData = []
this.trendXData = []
// ✅【修复】清空所有图表数据
this.clearChart(this.trendChart)
this.renderPieChart('crew', [])
this.renderPieChart('type', [])
return
}
this.tableData = response.rows.map(item => ({ this.tableData = response.rows.map(item => ({
time: this.formatDateTime(item.startDate) + ' - ' + this.formatDateTime(item.endDate), time: this.formatDateTime(item.startDate) + ' - ' + this.formatDateTime(item.endDate),
duration: this.secondsToMinutes(item.duration) + 'min', duration: this.secondsToMinutes(item.duration) + 'min',
@@ -388,22 +405,6 @@ export default {
this.trendXData = [] this.trendXData = []
} }
} }
} else {
console.log('暂无停机数据')
this.tableData = []
this.summaryData = [
{ label: '停机时间', value: 0, unit: 'min' },
{ label: '停机次数', value: 0, unit: '次' },
{ label: '作业率', value: 100, unit: '%' }
]
this.crewPieData = []
this.typePieData = []
this.trendXData = []
// ✅【修复】清空所有图表数据
this.clearChart(this.trendChart)
this.renderPieChart('crew', [])
this.renderPieChart('type', [])
}
}).catch(error => { }).catch(error => {
loading.close() loading.close()
console.error('加载停机数据失败:', error) console.error('加载停机数据失败:', error)
@@ -807,6 +808,7 @@ export default {
.trend-chart { .trend-chart {
height: 250px !important; height: 250px !important;
min-height: 250px; min-height: 250px;
max-height: 300px;
} }
// ✅【修复】饼图容器增加固定高度,解决高度塌陷 // ✅【修复】饼图容器增加固定高度,解决高度塌陷
@@ -845,13 +847,6 @@ export default {
color: #909399; color: #909399;
} }
.detail-list {
background: #fff;
border-radius: 4px;
border: 1px solid #e4e7ed;
overflow: hidden;
}
.detail-item { .detail-item {
padding: 12px; padding: 12px;
border-bottom: 1px solid #f5f7fa; border-bottom: 1px solid #f5f7fa;

View File

@@ -13,7 +13,51 @@
<el-button type="primary" icon="el-icon-search" style="margin-left: 10px;" @click="handleQuery">查询</el-button> <el-button type="primary" icon="el-icon-search" style="margin-left: 10px;" @click="handleQuery">查询</el-button>
</div> </div>
<!-- 停机统计表格 全部修改适配真实JSON数据字段 --> <!-- 核心指标卡片区域 -->
<div class="indicator-cards" style="display: flex; gap: 16px; margin: 16px 0; flex-wrap: wrap;">
<el-card class="card-item" style="flex: 1; min-width: 200px;">
<div class="card-content">
<p class="card-label">停机次数</p>
<p class="card-value">{{ stopCount }}</p>
</div>
</el-card>
<el-card class="card-item" style="flex: 1; min-width: 200px;">
<div class="card-content">
<p class="card-label">停机总时长</p>
<p class="card-value">{{ formatDuration(totalStopDuration) }}</p>
</div>
</el-card>
<el-card class="card-item" style="flex: 1; min-width: 200px;">
<div class="card-content">
<p class="card-label">作业率</p>
<p class="card-value">{{ operationRate }}%</p>
</div>
</el-card>
</div>
<!-- 图表区域 -->
<div class="charts-container" style="display: flex; gap: 16px; margin: 16px 0; flex-wrap: wrap;">
<!-- 按天汇总折线图 -->
<el-card style="flex: 1; min-width: 400px;">
<template #header>
<div class="chart-header">
<span>按天停机统计</span>
</div>
</template>
<div ref="lineChart" style="width: 100%; height: 400px;"></div>
</el-card>
<!-- 按类型汇总饼图 -->
<el-card style="flex: 1; min-width: 400px;">
<template #header>
<div class="chart-header">
<span>按停机类型统计</span>
</div>
</template>
<div ref="pieChart" style="width: 100%; height: 400px;"></div>
</el-card>
</div>
<!-- 停机统计表格 -->
<el-table <el-table
:data="stoppageList" :data="stoppageList"
border border
@@ -33,14 +77,8 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="insdate" label="数据录入时间" align="center" width="200" /> <el-table-column prop="insdate" label="数据录入时间" align="center" width="200" />
<!-- 生产相关附属字段 -->
<el-table-column prop="coilid" label="钢卷号" align="center" width="120" /> <el-table-column prop="coilid" label="钢卷号" align="center" width="120" />
<el-table-column prop="shift" label="班次" align="center" width="100" /> <el-table-column prop="shift" label="班次" align="center" width="100" />
<!-- <el-table-column prop="crew" label="班组人员" align="center" width="120" />
<el-table-column prop="area" label="区域" align="center" width="100" />
<el-table-column prop="unit" label="机组" align="center" width="100" />
<el-table-column prop="seton" label="开机人" align="center" width="100" /> -->
<!-- 备注字段 -->
<el-table-column prop="remark" label="备注" align="center" min-width="220" show-overflow-tooltip /> <el-table-column prop="remark" label="备注" align="center" min-width="220" show-overflow-tooltip />
</el-table> </el-table>
</div> </div>
@@ -48,6 +86,14 @@
<script> <script>
import { listStoppage } from '@/api/lines/zinc/stoppage' import { listStoppage } from '@/api/lines/zinc/stoppage'
// 引入ECharts核心及需要的组件
import * as echarts from 'echarts/core'
import { LineChart, PieChart } from 'echarts/charts'
import { TitleComponent, TooltipComponent, LegendComponent, GridComponent } from 'echarts/components'
import { CanvasRenderer } from 'echarts/renderers'
// 注册ECharts组件
echarts.use([TitleComponent, TooltipComponent, LegendComponent, GridComponent, LineChart, PieChart, CanvasRenderer])
export default { export default {
data() { data() {
@@ -57,9 +103,16 @@ export default {
loading: false, // 加载状态 loading: false, // 加载状态
month: '', // 选中的月份 yyyy-MM格式 month: '', // 选中的月份 yyyy-MM格式
queryParams: { queryParams: {
startDate: '', // 开始时间 yyyy-MM-dd✅ 与后端字段名一致 startDate: '', // 开始时间 yyyy-MM-dd
endDate: '' // 结束时间 yyyy-MM-dd✅ 与后端字段名一致 endDate: '' // 结束时间 yyyy-MM-dd
} },
// 指标卡数据
stopCount: 0, // 停机次数
totalStopDuration: 0, // 停机总时长(分钟)
operationRate: 0, // 作业率(百分比)
// 图表实例
lineChartInstance: null, // 折线图实例
pieChartInstance: null // 饼图实例
} }
}, },
created() { created() {
@@ -67,8 +120,22 @@ export default {
this.initDefaultMonth() this.initDefaultMonth()
}, },
mounted() { mounted() {
// 初始化图表实例
this.initCharts()
// 页面加载时,默认查询当月停机数据 // 页面加载时,默认查询当月停机数据
this.listStoppage() this.listStoppage()
// 监听窗口大小变化,自适应图表
window.addEventListener('resize', this.resizeCharts)
},
beforeDestroy() {
// 销毁图表实例,防止内存泄漏
if (this.lineChartInstance) {
this.lineChartInstance.dispose()
}
if (this.pieChartInstance) {
this.pieChartInstance.dispose()
}
window.removeEventListener('resize', this.resizeCharts)
}, },
methods: { methods: {
// 格式化持续时间:将分钟数转换为"X天X小时X分钟"格式 // 格式化持续时间:将分钟数转换为"X天X小时X分钟"格式
@@ -94,6 +161,7 @@ export default {
return result || '0分钟' return result || '0分钟'
}, },
/** 初始化默认月份和起止时间 */ /** 初始化默认月份和起止时间 */
initDefaultMonth() { initDefaultMonth() {
const now = new Date() const now = new Date()
@@ -129,6 +197,254 @@ export default {
this.listStoppage() this.listStoppage()
}, },
/** 初始化图表实例 */
initCharts() {
// 初始化折线图
this.lineChartInstance = echarts.init(this.$refs.lineChart)
// 初始化饼图
this.pieChartInstance = echarts.init(this.$refs.pieChart)
},
/** 图表自适应大小 */
resizeCharts() {
if (this.lineChartInstance) {
this.lineChartInstance.resize()
}
if (this.pieChartInstance) {
this.pieChartInstance.resize()
}
},
/** 计算核心指标数据 */
calculateIndicators() {
// 1. 计算停机次数
this.stopCount = this.stoppageList.length
// 2. 计算停机总时长(分钟)
this.totalStopDuration = this.stoppageList.reduce((sum, item) => {
const duration = Number(item.duration) || 0
return sum + duration
}, 0)
// 3. 计算作业率:作业率 = (当月总分钟数 - 停机总分钟数) / 当月总分钟数 * 100%
if (this.month) {
const [year, month] = this.month.split('-').map(Number)
// 获取当月的总天数
const daysInMonth = new Date(year, month, 0).getDate()
// 当月总分钟数
const totalMinutesInMonth = daysInMonth * 24 * 60
// 作业时长(分钟)
const operationMinutes = totalMinutesInMonth - this.totalStopDuration
// 计算作业率保留2位小数
this.operationRate = totalMinutesInMonth > 0
? (operationMinutes / totalMinutesInMonth * 100).toFixed(2)
: 0
} else {
this.operationRate = 0
}
},
/** 工具函数:获取两个日期之间的所有日期(含起止日期) */
getAllDatesBetween(startDateStr, endDateStr) {
const dates = []
const start = new Date(startDateStr)
const end = new Date(endDateStr)
// 只保留日期部分,清除时分秒
start.setHours(0, 0, 0, 0)
end.setHours(0, 0, 0, 0)
let current = new Date(start)
while (current <= end) {
// 转成 MM月dd日 格式
const month = current.getMonth() + 1
const day = current.getDate()
dates.push(`${month}${day}`)
// 日期+1
current.setDate(current.getDate() + 1)
}
return dates
},
/** 处理折线图数据(按天汇总停机次数和时长,跨天记录每天+1 */
handleLineChartData() {
if (!this.stoppageList.length) {
// 无数据时的提示
this.lineChartInstance.setOption({
title: { text: '暂无停机数据', left: 'center' },
xAxis: { data: [] },
series: [{ data: [] }, { data: [] }]
})
return
}
// 1. 构建当月所有日期的数组用于x轴
const [year, month] = this.month.split('-').map(Number)
const daysInMonth = new Date(year, month, 0).getDate()
const dateLabels = Array.from({ length: daysInMonth }, (_, i) => `${month}${i+1}`)
// 2. 初始化所有日期的统计值为0
const dayMap = {}
dateLabels.forEach(date => {
dayMap[date] = { count: 0, duration: 0 }
})
// 3. 遍历停机数据,按【时间区间内所有日期】统计次数,按【开始日期】统计时长
this.stoppageList.forEach(item => {
if (!item.startDate || !item.endDate) return
// 获取停机区间内的所有日期
const relateDates = this.getAllDatesBetween(item.startDate, item.endDate)
// 遍历所有涉及日期,次数+1
relateDates.forEach(dateKey => {
if (dayMap[dateKey]) {
dayMap[dateKey].count += 1
}
})
// 时长:按开始日期统计(如需按天拆分时长,可在此处处理)
const startMonth = new Date(item.startDate).getMonth() + 1
const startDay = new Date(item.startDate).getDate()
const startDateKey = `${startMonth}${startDay}`
if (dayMap[startDateKey]) {
dayMap[startDateKey].duration += Number(item.duration) || 0
}
})
// 4. 提取x轴和series数据
const xAxisData = dateLabels
const countData = xAxisData.map(date => dayMap[date].count)
const durationData = xAxisData.map(date => dayMap[date].duration)
// 5. 设置折线图配置
const lineOption = {
tooltip: {
trigger: 'axis',
formatter: (params) => {
const date = params[0].name
const count = params[0].value
const duration = this.formatDuration(params[1].value)
return `${date}<br/>停机次数:${count}<br/>停机时长:${duration}`
}
},
legend: {
data: ['停机次数', '停机时长(分钟)'],
top: 0
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: xAxisData
},
yAxis: [
{
type: 'value',
name: '停机次数',
min: 0,
axisLabel: {
formatter: '{value}'
}
},
{
type: 'value',
name: '停机时长(分钟)',
min: 0,
axisLabel: {
formatter: '{value}'
},
position: 'right'
}
],
series: [
{
name: '停机次数',
type: 'line',
smooth: true,
data: countData
},
{
name: '停机时长(分钟)',
type: 'line',
smooth: true,
yAxisIndex: 1,
data: durationData
}
]
}
this.lineChartInstance.setOption(lineOption)
},
/** 处理饼图数据(按停机类型汇总) */
handlePieChartData() {
if (!this.stoppageList.length) {
// 无数据时的提示
this.pieChartInstance.setOption({
title: { text: '暂无停机数据', left: 'center' },
series: [{ data: [] }]
})
return
}
// 1. 按停机类型分组统计时长
const typeMap = {}
this.stoppageList.forEach(item => {
const type = item.stopType || '未知类型'
const duration = Number(item.duration) || 0
typeMap[type] = (typeMap[type] || 0) + duration
})
// 2. 转换为饼图数据格式
const pieData = Object.entries(typeMap).map(([name, value]) => ({
name,
value,
label: {
show: true,
formatter: '{b}: {c}分钟 ({d}%)'
}
}))
// 3. 设置饼图配置
const pieOption = {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c}分钟 ({d}%)'
},
legend: {
orient: 'vertical',
left: 'left',
top: 'center'
},
series: [
{
name: '停机时长',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
label: {
show: false
},
emphasis: {
label: {
show: true,
fontSize: 16,
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: pieData
}
]
}
this.pieChartInstance.setOption(pieOption)
},
/** 核心:查询停机统计列表数据,带时间筛选参数 */ /** 核心:查询停机统计列表数据,带时间筛选参数 */
async listStoppage() { async listStoppage() {
this.loading = true this.loading = true
@@ -138,11 +454,23 @@ export default {
// 适配后端返回格式:数组/分页对象都兼容 // 适配后端返回格式:数组/分页对象都兼容
this.stoppageList = res.data || res || [] this.stoppageList = res.data || res || []
this.total = res.total || this.stoppageList.length this.total = res.total || this.stoppageList.length
// 计算核心指标
this.calculateIndicators()
// 更新图表数据
this.handleLineChartData()
this.handlePieChartData()
} catch (err) { } catch (err) {
this.$message.error('查询停机统计数据失败!') this.$message.error('查询停机统计数据失败!')
console.error('停机统计查询异常:', err) console.error('停机统计查询异常:', err)
this.stoppageList = [] this.stoppageList = []
this.total = 0 this.total = 0
// 重置指标和图表
this.stopCount = 0
this.totalStopDuration = 0
this.operationRate = 0
this.handleLineChartData()
this.handlePieChartData()
} finally { } finally {
this.loading = false this.loading = false
} }
@@ -162,4 +490,34 @@ export default {
border-radius: 4px; border-radius: 4px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05); box-shadow: 0 2px 4px rgba(0,0,0,0.05);
} }
/* 指标卡样式 */
.indicator-cards {
width: 100%;
}
.card-item {
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.card-content {
text-align: center;
padding: 8px 0;
}
.card-label {
font-size: 14px;
color: #666;
margin: 0 0 8px 0;
}
.card-value {
font-size: 24px;
font-weight: 600;
color: #1989fa;
margin: 0;
}
/* 图表样式 */
.charts-container {
width: 100%;
}
.chart-header {
font-weight: 600;
font-size: 14px;
}
</style> </style>