385 lines
12 KiB
Vue
385 lines
12 KiB
Vue
|
|
<template>
|
|||
|
|
<div class="oee-page">
|
|||
|
|
<!-- 查询条件 -->
|
|||
|
|
<el-card class="oee-card query-card" shadow="never">
|
|||
|
|
<el-form :inline="true" :model="queryParams" size="small">
|
|||
|
|
<el-form-item label="日期范围">
|
|||
|
|
<el-date-picker
|
|||
|
|
v-model="dateRange"
|
|||
|
|
type="daterange"
|
|||
|
|
range-separator="至"
|
|||
|
|
start-placeholder="开始日期"
|
|||
|
|
end-placeholder="结束日期"
|
|||
|
|
value-format="yyyy-MM-dd"
|
|||
|
|
/>
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item label="产线">
|
|||
|
|
<el-select v-model="queryParams.lineIds" multiple collapse-tags style="min-width: 200px">
|
|||
|
|
<el-option label="酸轧线" value="SY" />
|
|||
|
|
<el-option label="镀锌一线" value="DX1" />
|
|||
|
|
</el-select>
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item>
|
|||
|
|
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
|
|||
|
|
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
|
|||
|
|
<el-button type="success" icon="el-icon-printer" @click="handlePrint">打印报表</el-button>
|
|||
|
|
<el-button type="warning" icon="el-icon-document" @click="handleExportWord">导出 Word</el-button>
|
|||
|
|
</el-form-item>
|
|||
|
|
</el-form>
|
|||
|
|
</el-card>
|
|||
|
|
|
|||
|
|
<!-- 1. 指标总览(报告形式表格) -->
|
|||
|
|
<el-card class="oee-card kpi-card" shadow="never">
|
|||
|
|
<div class="card-header">一、产线 OEE 指标总览</div>
|
|||
|
|
<el-table :data="lineSummary" border size="small">
|
|||
|
|
<el-table-column prop="lineName" label="产线" width="120" />
|
|||
|
|
<el-table-column label="OEE">
|
|||
|
|
<template slot-scope="scope">
|
|||
|
|
{{ toPercent(scope.row.total && scope.row.total.oee) }}
|
|||
|
|
</template>
|
|||
|
|
</el-table-column>
|
|||
|
|
<el-table-column label="时间稼动率 A">
|
|||
|
|
<template slot-scope="scope">
|
|||
|
|
{{ toPercent(scope.row.total && scope.row.total.availability) }}
|
|||
|
|
</template>
|
|||
|
|
</el-table-column>
|
|||
|
|
<el-table-column label="性能稼动率 P">
|
|||
|
|
<template slot-scope="scope">
|
|||
|
|
{{ toPercent(scope.row.total && scope.row.total.performance) }}
|
|||
|
|
</template>
|
|||
|
|
</el-table-column>
|
|||
|
|
<el-table-column label="良品率 Q">
|
|||
|
|
<template slot-scope="scope">
|
|||
|
|
{{ toPercent(scope.row.total && scope.row.total.quality) }}
|
|||
|
|
</template>
|
|||
|
|
</el-table-column>
|
|||
|
|
<el-table-column label="负荷时间(min)" width="130">
|
|||
|
|
<template slot-scope="scope">
|
|||
|
|
{{ (scope.row.total && scope.row.total.loadingTimeMin) || 0 }}
|
|||
|
|
</template>
|
|||
|
|
</el-table-column>
|
|||
|
|
<el-table-column label="停机时间(min)" width="130">
|
|||
|
|
<template slot-scope="scope">
|
|||
|
|
{{ (scope.row.total && scope.row.total.downtimeMin) || 0 }}
|
|||
|
|
</template>
|
|||
|
|
</el-table-column>
|
|||
|
|
<el-table-column label="运转时间(min)" width="130">
|
|||
|
|
<template slot-scope="scope">
|
|||
|
|
{{ (scope.row.total && scope.row.total.runTimeMin) || 0 }}
|
|||
|
|
</template>
|
|||
|
|
</el-table-column>
|
|||
|
|
</el-table>
|
|||
|
|
</el-card>
|
|||
|
|
|
|||
|
|
<!-- 2. 趋势(报告中的图 1 占位) -->
|
|||
|
|
<el-card class="oee-card chart-card" shadow="never">
|
|||
|
|
<div class="card-header">二、OEE 与三大指标日趋势(图 1,占位)</div>
|
|||
|
|
<div class="chart-placeholder">
|
|||
|
|
<span>图 1:OEE / A / P / Q 日趋势曲线(后续接入图表组件)</span>
|
|||
|
|
</div>
|
|||
|
|
</el-card>
|
|||
|
|
|
|||
|
|
<!-- 3. 7 大损失 -->
|
|||
|
|
<el-card class="oee-card loss-card" shadow="never">
|
|||
|
|
<div class="card-header">三、7 大损失汇总</div>
|
|||
|
|
<el-tabs v-model="activeLossLine">
|
|||
|
|
<el-tab-pane
|
|||
|
|
v-for="line in lossByLine"
|
|||
|
|
:key="line.lineId"
|
|||
|
|
:label="line.lineName"
|
|||
|
|
:name="line.lineId"
|
|||
|
|
>
|
|||
|
|
<el-table :data="line.losses" border size="small">
|
|||
|
|
<el-table-column prop="lossCategoryName" label="损失类别" />
|
|||
|
|
<el-table-column prop="lossTimeMin" label="损失时间(min)" width="140" />
|
|||
|
|
</el-table>
|
|||
|
|
</el-tab-pane>
|
|||
|
|
</el-tabs>
|
|||
|
|
</el-card>
|
|||
|
|
|
|||
|
|
<!-- 4. 事件明细 -->
|
|||
|
|
<el-card class="oee-card event-card" shadow="never">
|
|||
|
|
<div class="card-header">停机 / 损失事件明细</div>
|
|||
|
|
<el-table :data="eventList" border size="small">
|
|||
|
|
<el-table-column prop="lineName" label="产线" width="100" />
|
|||
|
|
<el-table-column prop="eventStartTime" label="开始时间" width="160" />
|
|||
|
|
<el-table-column prop="eventEndTime" label="结束时间" width="160" />
|
|||
|
|
<el-table-column prop="durationMin" label="时长(min)" width="100" />
|
|||
|
|
<el-table-column prop="lossCategoryName" label="损失类别" width="120" />
|
|||
|
|
<el-table-column prop="rawReasonName" label="原因" />
|
|||
|
|
</el-table>
|
|||
|
|
<pagination
|
|||
|
|
v-show="eventTotal > 0"
|
|||
|
|
:total="eventTotal"
|
|||
|
|
:page.sync="eventQuery.pageNum"
|
|||
|
|
:limit.sync="eventQuery.pageSize"
|
|||
|
|
@pagination="loadEvents"
|
|||
|
|
/>
|
|||
|
|
</el-card>
|
|||
|
|
|
|||
|
|
<!-- 5. 公式与口径说明(MathType 风格) -->
|
|||
|
|
<el-card class="oee-card formula-card" shadow="never">
|
|||
|
|
<div class="card-header">五、OEE 公式与口径说明</div>
|
|||
|
|
<div class="formula-content">
|
|||
|
|
<div class="formula-row">
|
|||
|
|
<span class="formula-label">总公式:</span>
|
|||
|
|
<div class="equation">
|
|||
|
|
<span class="var">OEE</span>
|
|||
|
|
<span>=</span>
|
|||
|
|
<span class="var">A</span>
|
|||
|
|
<span>×</span>
|
|||
|
|
<span class="var">P</span>
|
|||
|
|
<span>×</span>
|
|||
|
|
<span class="var">Q</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="formula-row">
|
|||
|
|
<span class="formula-label">时间稼动率 A:</span>
|
|||
|
|
<div class="equation">
|
|||
|
|
<span class="var">A</span>
|
|||
|
|
<span>=</span>
|
|||
|
|
<span class="frac">
|
|||
|
|
<span class="num">负荷时间 − 停机时间</span>
|
|||
|
|
<span class="line"></span>
|
|||
|
|
<span class="den">负荷时间</span>
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="formula-row">
|
|||
|
|
<span class="formula-label">性能稼动率 P:</span>
|
|||
|
|
<div class="equation">
|
|||
|
|
<span class="var">P</span>
|
|||
|
|
<span>=</span>
|
|||
|
|
<span class="frac">
|
|||
|
|
<span class="num">理论加工时间 × 加工数量</span>
|
|||
|
|
<span class="line"></span>
|
|||
|
|
<span class="den">实际运转时间</span>
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="formula-note">
|
|||
|
|
注:当前阶段按 P = 1 处理,待接入标准节拍 / 标准速度后启用上述公式。
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="formula-row">
|
|||
|
|
<span class="formula-label">良品率 Q:</span>
|
|||
|
|
<div class="equation">
|
|||
|
|
<span class="var">Q</span>
|
|||
|
|
<span>=</span>
|
|||
|
|
<span class="frac">
|
|||
|
|
<span class="num">良品数</span>
|
|||
|
|
<span class="line"></span>
|
|||
|
|
<span class="den">总产量</span>
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="formula-note">
|
|||
|
|
注:本系统中,良品数通过钢卷质量状态 <code>quality_status = 0</code> 统计,总产量为对应库区内所有钢卷数量。
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</el-card>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
import { fetchOeeSummary, fetchOeeLoss7, fetchOeeEvents, exportOeeWord } from '@/api/da/oee'
|
|||
|
|
|
|||
|
|
export default {
|
|||
|
|
name: 'OeeReport',
|
|||
|
|
data() {
|
|||
|
|
const today = this.$moment ? this.$moment().format('YYYY-MM-DD') : ''
|
|||
|
|
return {
|
|||
|
|
dateRange: [today, today],
|
|||
|
|
queryParams: {
|
|||
|
|
startDate: today,
|
|||
|
|
endDate: today,
|
|||
|
|
lineIds: ['SY', 'DX1']
|
|||
|
|
},
|
|||
|
|
lineSummary: [],
|
|||
|
|
lossByLine: [],
|
|||
|
|
activeLossLine: 'SY',
|
|||
|
|
eventList: [],
|
|||
|
|
eventTotal: 0,
|
|||
|
|
eventQuery: {
|
|||
|
|
pageNum: 1,
|
|||
|
|
pageSize: 10
|
|||
|
|
},
|
|||
|
|
loading: false
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
created() {
|
|||
|
|
this.handleQuery()
|
|||
|
|
},
|
|||
|
|
methods: {
|
|||
|
|
buildBaseQuery() {
|
|||
|
|
const [start, end] = this.dateRange || []
|
|||
|
|
return {
|
|||
|
|
startDate: start,
|
|||
|
|
endDate: end,
|
|||
|
|
lineIds: (this.queryParams.lineIds || []).join(',')
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
handleQuery() {
|
|||
|
|
const baseQuery = this.buildBaseQuery()
|
|||
|
|
this.loading = true
|
|||
|
|
Promise.all([
|
|||
|
|
fetchOeeSummary(baseQuery),
|
|||
|
|
fetchOeeLoss7(baseQuery)
|
|||
|
|
])
|
|||
|
|
.then(([summaryRes, lossRes]) => {
|
|||
|
|
this.lineSummary = summaryRes.data || []
|
|||
|
|
this.lossByLine = (lossRes.data && lossRes.data.byLine) || []
|
|||
|
|
if (this.lossByLine.length && !this.lossByLine.find(l => l.lineId === this.activeLossLine)) {
|
|||
|
|
this.activeLossLine = this.lossByLine[0].lineId
|
|||
|
|
}
|
|||
|
|
this.eventQuery.pageNum = 1
|
|||
|
|
this.loadEvents()
|
|||
|
|
})
|
|||
|
|
.finally(() => {
|
|||
|
|
this.loading = false
|
|||
|
|
})
|
|||
|
|
},
|
|||
|
|
resetQuery() {
|
|||
|
|
const today = this.$moment ? this.$moment().format('YYYY-MM-DD') : ''
|
|||
|
|
this.dateRange = [today, today]
|
|||
|
|
this.queryParams.lineIds = ['SY', 'DX1']
|
|||
|
|
this.handleQuery()
|
|||
|
|
},
|
|||
|
|
loadEvents() {
|
|||
|
|
const baseQuery = this.buildBaseQuery()
|
|||
|
|
const params = Object.assign({}, baseQuery, this.eventQuery)
|
|||
|
|
fetchOeeEvents(params).then(res => {
|
|||
|
|
this.eventList = res.rows || []
|
|||
|
|
this.eventTotal = res.total || 0
|
|||
|
|
})
|
|||
|
|
},
|
|||
|
|
toPercent(v) {
|
|||
|
|
if (v == null) return '-'
|
|||
|
|
const num = Number(v)
|
|||
|
|
if (isNaN(num)) return '-'
|
|||
|
|
return (num * 100).toFixed(1) + '%'
|
|||
|
|
},
|
|||
|
|
handlePrint() {
|
|||
|
|
window.print()
|
|||
|
|
},
|
|||
|
|
handleExportWord() {
|
|||
|
|
const baseQuery = this.buildBaseQuery()
|
|||
|
|
exportOeeWord(baseQuery).then(res => {
|
|||
|
|
const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' })
|
|||
|
|
const url = window.URL.createObjectURL(blob)
|
|||
|
|
const a = document.createElement('a')
|
|||
|
|
a.href = url
|
|||
|
|
a.download = `OEE报表_${baseQuery.startDate}_${baseQuery.endDate}.docx`
|
|||
|
|
a.click()
|
|||
|
|
window.URL.revokeObjectURL(url)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style scoped>
|
|||
|
|
.oee-page {
|
|||
|
|
padding: 10px;
|
|||
|
|
}
|
|||
|
|
.oee-card {
|
|||
|
|
margin-bottom: 12px;
|
|||
|
|
}
|
|||
|
|
.card-header {
|
|||
|
|
font-weight: 600;
|
|||
|
|
margin-bottom: 8px;
|
|||
|
|
}
|
|||
|
|
.line-kpi {
|
|||
|
|
border: 1px solid #ebeef5;
|
|||
|
|
border-radius: 4px;
|
|||
|
|
padding: 8px 12px;
|
|||
|
|
}
|
|||
|
|
.line-title {
|
|||
|
|
font-weight: 600;
|
|||
|
|
margin-bottom: 6px;
|
|||
|
|
}
|
|||
|
|
.kpi-items {
|
|||
|
|
display: flex;
|
|||
|
|
flex-wrap: wrap;
|
|||
|
|
}
|
|||
|
|
.kpi-item {
|
|||
|
|
min-width: 120px;
|
|||
|
|
margin-right: 12px;
|
|||
|
|
margin-bottom: 4px;
|
|||
|
|
}
|
|||
|
|
.kpi-label {
|
|||
|
|
font-size: 12px;
|
|||
|
|
color: #909399;
|
|||
|
|
}
|
|||
|
|
.kpi-value {
|
|||
|
|
font-size: 14px;
|
|||
|
|
font-weight: 600;
|
|||
|
|
}
|
|||
|
|
.kpi-value.primary {
|
|||
|
|
color: #409eff;
|
|||
|
|
font-size: 16px;
|
|||
|
|
}
|
|||
|
|
.kpi-footer {
|
|||
|
|
margin-top: 4px;
|
|||
|
|
font-size: 12px;
|
|||
|
|
color: #909399;
|
|||
|
|
display: flex;
|
|||
|
|
flex-wrap: wrap;
|
|||
|
|
}
|
|||
|
|
.kpi-footer span {
|
|||
|
|
margin-right: 12px;
|
|||
|
|
}
|
|||
|
|
.chart-placeholder {
|
|||
|
|
height: 260px;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
color: #909399;
|
|||
|
|
border: 1px dashed #e4e7ed;
|
|||
|
|
border-radius: 4px;
|
|||
|
|
}
|
|||
|
|
.formula-content {
|
|||
|
|
font-size: 13px;
|
|||
|
|
line-height: 1.6;
|
|||
|
|
}
|
|||
|
|
.formula-row {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: flex-start;
|
|||
|
|
margin-bottom: 8px;
|
|||
|
|
}
|
|||
|
|
.formula-label {
|
|||
|
|
min-width: 110px;
|
|||
|
|
color: #606266;
|
|||
|
|
}
|
|||
|
|
.equation {
|
|||
|
|
display: inline-flex;
|
|||
|
|
align-items: center;
|
|||
|
|
flex-wrap: wrap;
|
|||
|
|
}
|
|||
|
|
.equation .var {
|
|||
|
|
font-weight: 600;
|
|||
|
|
margin: 0 2px;
|
|||
|
|
}
|
|||
|
|
.frac {
|
|||
|
|
display: inline-flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
align-items: center;
|
|||
|
|
margin: 0 4px;
|
|||
|
|
font-family: 'Times New Roman', serif;
|
|||
|
|
}
|
|||
|
|
.frac .num,
|
|||
|
|
.frac .den {
|
|||
|
|
padding: 0 4px;
|
|||
|
|
}
|
|||
|
|
.frac .line {
|
|||
|
|
border-top: 1px solid #303133;
|
|||
|
|
width: 100%;
|
|||
|
|
margin: 1px 0;
|
|||
|
|
}
|
|||
|
|
.formula-note {
|
|||
|
|
margin-left: 110px;
|
|||
|
|
font-size: 12px;
|
|||
|
|
color: #909399;
|
|||
|
|
}
|
|||
|
|
</style>
|
|||
|
|
|