feat(report): 新增设备报表相关组件及功能

- 新增设备故障分析报表页面,支持按设备编码、故障类型和时间范围查询
- 实现故障次数Top10柱状图展示及数据表格展示
- 新增设备OEE分析报表页面,支持按设备编码和时间范围查询
- 实现设备OEE对比柱状图及相关数据表展示,支持百分比格式化显示
- 新增维修工时统计报表页面,支持按执行人和时间范围查询
- 实现维修工时Top10柱状图及数据表格展示,展示工单数、合计工时和平均工时
- 各报表均实现查询、重置功能及加载状态显示
- 使用echarts实现柱状图可视化展示
- 统一样式及布局,保障用户体验一致性
master
zangch@mesnac.com 4 weeks ago
parent fc5a25ad71
commit e5f64dc5a9

@ -0,0 +1,172 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="90px">
<el-form-item label="设备编码">
<el-input
v-model="queryParams.deviceCode"
placeholder="请输入设备编码"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="故障类型">
<el-input
v-model="queryParams.faultType"
placeholder="请输入故障类型"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="时间范围">
<el-date-picker
v-model="daterangeBeginTime"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="yyyy-MM-dd"
style="width: 240px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<div class="charts-container">
<el-card class="chart-card">
<div slot="header" class="chart-title">设备故障次数 Top10</div>
<div ref="barChart" class="chart-content"></div>
</el-card>
</div>
<el-table v-loading="loading" :data="reportList" style="margin-top: 10px">
<el-table-column label="设备编码" align="center" prop="DEVICE_CODE" width="140" />
<el-table-column label="设备名称" align="center" prop="DEVICE_NAME" show-overflow-tooltip />
<el-table-column label="故障类型" align="center" prop="FAULT_TYPE" width="120" />
<el-table-column label="故障次数" align="center" prop="FAULT_COUNT" width="100" />
<el-table-column label="故障时长(分钟)" align="center" prop="FAULT_DURATION_MINUTES" width="140" />
</el-table>
</div>
</template>
<script>
import * as echarts from 'echarts'
import { deviceFaultAnalysisList } from '@/api/report/reportAPI'
import { parseTime } from '@/utils/ruoyi'
export default {
name: 'DeviceFaultAnalysisReport',
data() {
return {
loading: false,
showSearch: true,
reportList: [],
daterangeBeginTime: [],
queryParams: {
deviceCode: null,
faultType: null,
beginTime: null,
endTime: null
},
barChart: null
}
},
created() {
const today = parseTime(new Date(), '{y}-{m}-{d}')
this.daterangeBeginTime = [today, today]
this.handleQuery()
},
mounted() {
this.$nextTick(() => {
this.barChart = echarts.init(this.$refs.barChart)
})
},
methods: {
/** 查询报表数据 */
getList() {
this.loading = true
if (this.daterangeBeginTime && this.daterangeBeginTime.length === 2) {
this.queryParams.beginTime = this.daterangeBeginTime[0]
this.queryParams.endTime = this.daterangeBeginTime[1]
} else {
this.queryParams.beginTime = null
this.queryParams.endTime = null
}
deviceFaultAnalysisList(this.queryParams).then(res => {
this.reportList = res.data || []
this.initBarChart()
this.loading = false
}).catch(() => {
this.loading = false
})
},
/** 搜索按钮 */
handleQuery() {
this.getList()
},
/** 重置按钮 */
resetQuery() {
this.queryParams.deviceCode = null
this.queryParams.faultType = null
const today = parseTime(new Date(), '{y}-{m}-{d}')
this.daterangeBeginTime = [today, today]
this.handleQuery()
},
/** 初始化柱状图 */
initBarChart() {
if (!this.barChart) return
const topList = (this.reportList || []).slice(0, 10)
const xData = topList.map(item => item.DEVICE_NAME || item.DEVICE_CODE)
const yData = topList.map(item => item.FAULT_COUNT || 0)
const option = {
tooltip: {
trigger: 'axis'
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: xData,
axisLabel: {
interval: 0,
rotate: 30
}
},
yAxis: {
type: 'value'
},
series: [
{
name: '故障次数',
type: 'bar',
data: yData
}
]
}
this.barChart.setOption(option)
}
}
}
</script>
<style scoped>
.charts-container {
margin-top: 10px;
}
.chart-card {
width: 100%;
}
.chart-content {
height: 320px;
}
.chart-title {
font-size: 16px;
font-weight: bold;
}
</style>

@ -0,0 +1,195 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="90px">
<el-form-item label="设备编码">
<el-input
v-model="queryParams.deviceCode"
placeholder="请输入设备编码"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="时间范围">
<el-date-picker
v-model="daterangeBeginTime"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="yyyy-MM-dd"
style="width: 240px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<div class="charts-container">
<el-card class="chart-card">
<div slot="header" class="chart-title">设备 OEE 对比</div>
<div ref="barChart" class="chart-content"></div>
</el-card>
</div>
<el-table v-loading="loading" :data="reportList" style="margin-top: 10px">
<el-table-column label="设备编码" align="center" prop="DEVICE_CODE" width="140" />
<el-table-column label="设备名称" align="center" prop="DEVICE_NAME" show-overflow-tooltip />
<el-table-column label="可用率" align="center" prop="AVAILABILITY" width="100">
<template slot-scope="scope">
<span>{{ formatPercent(scope.row.AVAILABILITY) }}</span>
</template>
</el-table-column>
<el-table-column label="性能" align="center" prop="PERFORMANCE" width="100">
<template slot-scope="scope">
<span>{{ formatPercent(scope.row.PERFORMANCE) }}</span>
</template>
</el-table-column>
<el-table-column label="良品率" align="center" prop="QUALITY" width="100">
<template slot-scope="scope">
<span>{{ formatPercent(scope.row.QUALITY) }}</span>
</template>
</el-table-column>
<el-table-column label="OEE" align="center" prop="OEE" width="100">
<template slot-scope="scope">
<span>{{ formatPercent(scope.row.OEE) }}</span>
</template>
</el-table-column>
<el-table-column label="计划时间(分钟)" align="center" prop="PLANNED_TIME_MINUTES" width="140" />
<el-table-column label="停机时间(分钟)" align="center" prop="DOWNTIME_MINUTES" width="140" />
</el-table>
</div>
</template>
<script>
import * as echarts from 'echarts'
import { deviceOeeAnalysisList } from '@/api/report/reportAPI'
import { parseTime } from '@/utils/ruoyi'
export default {
name: 'DeviceOeeAnalysisReport',
data() {
return {
loading: false,
showSearch: true,
reportList: [],
daterangeBeginTime: [],
queryParams: {
deviceCode: null,
beginTime: null,
endTime: null
},
barChart: null
}
},
created() {
const today = parseTime(new Date(), '{y}-{m}-{d}')
this.daterangeBeginTime = [today, today]
this.handleQuery()
},
mounted() {
this.$nextTick(() => {
this.barChart = echarts.init(this.$refs.barChart)
})
},
methods: {
/** 查询报表数据 */
getList() {
this.loading = true
if (this.daterangeBeginTime && this.daterangeBeginTime.length === 2) {
this.queryParams.beginTime = this.daterangeBeginTime[0]
this.queryParams.endTime = this.daterangeBeginTime[1]
} else {
this.queryParams.beginTime = null
this.queryParams.endTime = null
}
deviceOeeAnalysisList(this.queryParams).then(res => {
this.reportList = res.data || []
this.initBarChart()
this.loading = false
}).catch(() => {
this.loading = false
})
},
/** 搜索按钮 */
handleQuery() {
this.getList()
},
/** 重置按钮 */
resetQuery() {
this.queryParams.deviceCode = null
const today = parseTime(new Date(), '{y}-{m}-{d}')
this.daterangeBeginTime = [today, today]
this.handleQuery()
},
/** 初始化柱状图 */
initBarChart() {
if (!this.barChart) return
const xData = (this.reportList || []).map(item => item.DEVICE_NAME || item.DEVICE_CODE)
const yData = (this.reportList || []).map(item => item.OEE || 0)
const option = {
tooltip: {
trigger: 'axis',
formatter: params => {
if (!params || !params.length) return ''
const p = params[0]
return `${p.name}<br/>OEE${this.formatPercent(p.data)}`
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: xData,
axisLabel: {
interval: 0,
rotate: 30
}
},
yAxis: {
type: 'value',
axisLabel: {
formatter: value => `${(value * 100).toFixed(0)}%`
}
},
series: [
{
name: 'OEE',
type: 'bar',
data: yData
}
]
}
this.barChart.setOption(option)
},
/** 百分比格式化 */
formatPercent(val) {
if (val == null || val === undefined) return '-'
const num = Number(val)
if (isNaN(num)) return '-'
return (num * 100).toFixed(2) + '%'
}
}
}
</script>
<style scoped>
.charts-container {
margin-top: 10px;
}
.chart-card {
width: 100%;
}
.chart-content {
height: 320px;
}
.chart-title {
font-size: 16px;
font-weight: bold;
}
</style>

@ -0,0 +1,161 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="90px">
<el-form-item label="执行人">
<el-input
v-model="queryParams.executorName"
placeholder="请输入执行人"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="时间范围">
<el-date-picker
v-model="daterangeBeginTime"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="yyyy-MM-dd"
style="width: 240px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<div class="charts-container">
<el-card class="chart-card">
<div slot="header" class="chart-title">维修工时 Top10按执行人</div>
<div ref="barChart" class="chart-content"></div>
</el-card>
</div>
<el-table v-loading="loading" :data="reportList" style="margin-top: 10px">
<el-table-column label="执行人" align="center" prop="EXECUTOR_NAME" />
<el-table-column label="工单数量" align="center" prop="ORDER_COUNT" width="120" />
<el-table-column label="合计工时(h)" align="center" prop="TOTAL_HOURS" width="140" />
<el-table-column label="平均工时(h)" align="center" prop="AVG_HOURS" width="140" />
</el-table>
</div>
</template>
<script>
import * as echarts from 'echarts'
import { repairHoursStatList } from '@/api/report/reportAPI'
import { parseTime } from '@/utils/ruoyi'
export default {
name: 'RepairHoursStatReport',
data() {
return {
loading: false,
showSearch: true,
reportList: [],
daterangeBeginTime: [],
queryParams: {
executorName: null,
beginTime: null,
endTime: null
},
barChart: null
}
},
created() {
const today = parseTime(new Date(), '{y}-{m}-{d}')
this.daterangeBeginTime = [today, today]
this.handleQuery()
},
mounted() {
this.$nextTick(() => {
this.barChart = echarts.init(this.$refs.barChart)
})
},
methods: {
/** 查询报表数据 */
getList() {
this.loading = true
if (this.daterangeBeginTime && this.daterangeBeginTime.length === 2) {
this.queryParams.beginTime = this.daterangeBeginTime[0]
this.queryParams.endTime = this.daterangeBeginTime[1]
} else {
this.queryParams.beginTime = null
this.queryParams.endTime = null
}
repairHoursStatList(this.queryParams).then(res => {
this.reportList = res.data || []
this.initBarChart()
this.loading = false
}).catch(() => {
this.loading = false
})
},
/** 搜索按钮 */
handleQuery() {
this.getList()
},
/** 重置按钮 */
resetQuery() {
this.queryParams.executorName = null
const today = parseTime(new Date(), '{y}-{m}-{d}')
this.daterangeBeginTime = [today, today]
this.handleQuery()
},
/** 初始化柱状图 */
initBarChart() {
if (!this.barChart) return
const topList = (this.reportList || []).slice(0, 10)
const xData = topList.map(item => item.EXECUTOR_NAME || '')
const yData = topList.map(item => item.TOTAL_HOURS || 0)
const option = {
tooltip: {
trigger: 'axis'
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: xData,
axisLabel: {
interval: 0,
rotate: 30
}
},
yAxis: {
type: 'value'
},
series: [
{
name: '合计工时',
type: 'bar',
data: yData
}
]
}
this.barChart.setOption(option)
}
}
}
</script>
<style scoped>
.charts-container {
margin-top: 10px;
}
.chart-card {
width: 100%;
}
.chart-content {
height: 320px;
}
.chart-title {
font-size: 16px;
font-weight: bold;
}
</style>
Loading…
Cancel
Save