835 lines
28 KiB
Vue
835 lines
28 KiB
Vue
<template>
|
||
<div class="oee-report-page">
|
||
<!-- 查询条件概览(去掉大标题,只保留区间提示) -->
|
||
<el-card class="oee-header-card" shadow="never">
|
||
<div class="oee-header">
|
||
<div class="oee-title-block">
|
||
<div class="oee-subtitle">
|
||
查询区间:
|
||
<span v-if="queryRange && queryRange.length === 2">
|
||
{{ queryRange[0] }} ~ {{ queryRange[1] }}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
<div class="oee-query-bar">
|
||
<el-select v-model="lineType" size="small" style="width: 120px">
|
||
<el-option label="酸轧线" value="acid" />
|
||
<el-option label="镀锌一线" value="galvanize1" />
|
||
</el-select>
|
||
<el-date-picker
|
||
v-model="queryRange"
|
||
type="daterange"
|
||
unlink-panels
|
||
size="small"
|
||
start-placeholder="开始日期"
|
||
end-placeholder="结束日期"
|
||
value-format="yyyy-MM-dd"
|
||
:picker-options="pickerOptions"
|
||
/>
|
||
<el-button
|
||
type="primary"
|
||
size="small"
|
||
icon="el-icon-search"
|
||
@click="handleSearch"
|
||
>
|
||
查询
|
||
</el-button>
|
||
<el-button
|
||
size="small"
|
||
icon="el-icon-refresh"
|
||
@click="handleReset"
|
||
>
|
||
重置
|
||
</el-button>
|
||
<el-button
|
||
size="small"
|
||
icon="el-icon-document"
|
||
@click="handleExportWord"
|
||
>
|
||
导出 Word
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
|
||
<el-alert
|
||
v-if="lineType === 'galvanize1'"
|
||
title="镀锌二级数据未完全使用,故而停机时间可能有错误"
|
||
type="warning"
|
||
:closable="false"
|
||
class="oee-top-warning"
|
||
show-icon
|
||
/>
|
||
|
||
<el-row :gutter="16" class="oee-main-row">
|
||
<!-- 左侧:报表主体(Word 风格) -->
|
||
<el-col :span="18">
|
||
<el-card class="oee-word-card" shadow="never">
|
||
<!-- KPI 一览(报表头部表格) -->
|
||
<div class="oee-section-title">一、KPI 总览(酸轧线 SY)</div>
|
||
<div class="oee-help-text">
|
||
展示所选期间酸轧线整体 OEE 及 A/P/Q 等关键指标,用于快速判断本期综合表现好坏。
|
||
</div>
|
||
<el-table
|
||
:data="[kpi]"
|
||
border
|
||
size="mini"
|
||
class="oee-kpi-table"
|
||
v-loading="loading.summary"
|
||
>
|
||
<el-table-column prop="oee" label="OEE (%)" align="center">
|
||
<template slot-scope="scope">
|
||
<span>{{ formatPercent(scope.row.oee) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="availability" label="时间稼动率 A (%)" align="center">
|
||
<template slot-scope="scope">
|
||
<span>{{ formatPercent(scope.row.availability) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="performanceTon" label="性能稼动率 P_ton (%)" align="center">
|
||
<template slot-scope="scope">
|
||
<span>{{ formatPercent(scope.row.performanceTon) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="quality" label="良品率(%)" align="center">
|
||
<template slot-scope="scope">
|
||
<span>{{ formatPercent(scope.row.quality) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="qualifiedRate" label="合格品率(%)" align="center">
|
||
<template slot-scope="scope">
|
||
<span>{{ formatPercent(scope.row.qualifiedRate) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="defectRate" label="次品率 (%)" align="center">
|
||
<template slot-scope="scope">
|
||
<span>{{ formatPercent(scope.row.defectRate) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="pendingRate" label="待判级率 (%)" align="center">
|
||
<template slot-scope="scope">
|
||
<span>{{ formatPercent(scope.row.pendingRate) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="loadingTimeMin" label="负荷时间 (min)" align="center" />
|
||
<el-table-column prop="downtimeMin" label="停机时间 (min)" align="center" />
|
||
<el-table-column prop="runTimeMin" label="运转时间 (min)" align="center" />
|
||
<el-table-column prop="totalOutputTon" label="总产量 (吨)" align="center">
|
||
<template slot-scope="scope">
|
||
<span>{{ formatNumber(scope.row.totalOutputTon) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="totalOutputCoil" label="总产量 (卷)" align="center" />
|
||
<el-table-column prop="goodOutputTon" label="良品(吨)" align="center">
|
||
<template slot-scope="scope">
|
||
<span>{{ formatNumber(scope.row.goodOutputTon) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="abOutputTon" label="合格(吨)" align="center">
|
||
<template slot-scope="scope">
|
||
<span>{{ formatNumber(scope.row.abOutputTon) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="defectOutputTon" label="次品(吨)" align="center">
|
||
<template slot-scope="scope">
|
||
<span>{{ formatNumber(scope.row.defectOutputTon) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="pendingOutputTon" label="待判(吨)" align="center">
|
||
<template slot-scope="scope">
|
||
<span>{{ formatNumber(scope.row.pendingOutputTon) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<!-- 日明细(趋势表格风格,方便导出 Word) -->
|
||
<div class="oee-section-title">二、日明细(用于趋势分析)</div>
|
||
<div class="oee-help-text">
|
||
按天拆分 A/P/Q 及产量等指标,用于观察本月趋势、波动点以及与重大事件的对应关系。
|
||
</div>
|
||
<el-table
|
||
:data="summaryList"
|
||
border
|
||
size="mini"
|
||
class="oee-daily-table"
|
||
v-loading="loading.summary"
|
||
>
|
||
<el-table-column prop="statDate" label="日期" />
|
||
<el-table-column prop="oee" label="OEE (%)" >
|
||
<template slot-scope="scope">
|
||
{{ formatPercent(scope.row.oee) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="availability" label="A (%)" >
|
||
<template slot-scope="scope">
|
||
{{ formatPercent(scope.row.availability) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="performanceTon" label="P_ton (%)">
|
||
<template slot-scope="scope">
|
||
{{ formatPercent(scope.row.performanceTon) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="quality" label="良品率 (%)">
|
||
<template slot-scope="scope">
|
||
{{ formatPercent(scope.row.quality) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="qualifiedRate" label="合格品率 (%)">
|
||
<template slot-scope="scope">
|
||
{{ formatPercent(scope.row.qualifiedRate) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="defectRate" label="次品率 (%)">
|
||
<template slot-scope="scope">
|
||
{{ formatPercent(scope.row.defectRate) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="pendingRate" label="待判级率 (%)">
|
||
<template slot-scope="scope">
|
||
{{ formatPercent(scope.row.pendingRate) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="loadingTimeMin" label="负荷 (min)"/>
|
||
<el-table-column prop="downtimeMin" label="停机 (min)"/>
|
||
<el-table-column prop="runTimeMin" label="运转 (min)"/>
|
||
<el-table-column prop="totalOutputTon" label="总产量 (吨)">
|
||
<template slot-scope="scope">
|
||
{{ formatNumber(scope.row.totalOutputTon) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="totalOutputCoil" label="总产量 (卷)" />
|
||
<el-table-column prop="goodOutputTon" label="良品(吨)">
|
||
<template slot-scope="scope">
|
||
{{ formatNumber(scope.row.goodOutputTon) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="abOutputTon" label="合格(吨)">
|
||
<template slot-scope="scope">
|
||
{{ formatNumber(scope.row.abOutputTon) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="defectOutputTon" label="次品(吨)">
|
||
<template slot-scope="scope">
|
||
{{ formatNumber(scope.row.defectOutputTon) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="pendingOutputTon" label="待判(吨)">
|
||
<template slot-scope="scope">
|
||
{{ formatNumber(scope.row.pendingOutputTon) }}
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<!-- OEE/A/P/Q 趋势图 -->
|
||
<oee-trend-chart :data="summaryList" />
|
||
|
||
<!-- 理论节拍(统计口径) -->
|
||
<div class="oee-section-title">三、理论节拍(统计口径)</div>
|
||
<div class="oee-help-text">
|
||
基于历史“优良日”统计得到的理论节拍(中位数),用于作为性能稼动率计算的稳定标尺。
|
||
</div>
|
||
<div
|
||
v-if="idealCycle && idealCycle.dailyComparePoints && idealCycle.dailyComparePoints.length"
|
||
class="oee-reg-section"
|
||
>
|
||
<div class="reg-chart-block">
|
||
<div class="reg-chart-title">日粒度:理论耗时 vs 实际运转时间</div>
|
||
<div class="oee-help-text">
|
||
按天对比“理想应耗时间”和“实际运转时间”,可一眼看出哪几天效率偏低、存在明显损失空间。
|
||
</div>
|
||
<div ref="regDailyCompareChart" class="reg-chart"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 7 大损失 -->
|
||
<div class="oee-section-title">四、7 大损失汇总(按 stop_type 分类)</div>
|
||
<div class="oee-help-text">
|
||
将所有停机事件按 stop_type 归类,统计时间占比和次数,用于确定“先从哪几类损失下手改善”。
|
||
</div>
|
||
<el-table
|
||
:data="loss7List"
|
||
border
|
||
size="mini"
|
||
class="oee-loss7-table"
|
||
v-loading="loading.loss7"
|
||
>
|
||
<el-table-column prop="lossCategoryName" label="损失类别" min-width="160" />
|
||
<el-table-column prop="lossTimeMin" label="损失时间 (min)" width="140" />
|
||
<el-table-column prop="lossTimeRate" label="占比 (%)" width="110">
|
||
<template slot-scope="scope">
|
||
{{ formatPercent(scope.row.lossTimeRate) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="count" label="次数" width="90" />
|
||
<el-table-column prop="avgDurationMin" label="平均时长 (min)" width="140">
|
||
<template slot-scope="scope">
|
||
{{ formatNumber(scope.row.avgDurationMin) }}
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<!-- 7 大损失帕累托图 -->
|
||
<oee-loss-pareto :data="loss7List" />
|
||
|
||
<!-- 停机事件明细 -->
|
||
<div class="oee-section-title">五、停机/损失事件明细</div>
|
||
<div class="oee-help-text">
|
||
罗列每一条停机/损失事件,包含时间段、区域、机组和备注,方便班组和工艺人员对照现场记录进行原因分析。
|
||
</div>
|
||
<el-table
|
||
:data="eventList"
|
||
border
|
||
size="mini"
|
||
class="oee-events-table"
|
||
v-loading="loading.events"
|
||
>
|
||
<el-table-column prop="startDate" label="开始时间" min-width="150">
|
||
<template slot-scope="scope">
|
||
{{ formatDateTime(scope.row.startDate) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="endDate" label="结束时间" min-width="150">
|
||
<template slot-scope="scope">
|
||
{{ formatDateTime(scope.row.endDate) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="duration" label="时长 (s)" width="100" />
|
||
<el-table-column prop="durationMin" label="时长 (min)" width="110">
|
||
<template slot-scope="scope">
|
||
{{ Math.max(1, Math.floor((scope.row.duration || 0) / 60)) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="stopType" label="停机类型 (stop_type)" min-width="160" />
|
||
<el-table-column prop="area" label="区域" width="100" />
|
||
<el-table-column prop="unit" label="机组" width="100" />
|
||
<el-table-column prop="shift" label="班次" width="80" />
|
||
<el-table-column prop="crew" label="班组" width="80" />
|
||
<el-table-column prop="remark" label="备注" min-width="180" show-overflow-tooltip />
|
||
</el-table>
|
||
|
||
<!-- 停机 TopN 条形图 -->
|
||
<oee-stoppage-top :data="eventList" :top-n="10" />
|
||
</el-card>
|
||
</el-col>
|
||
|
||
<!-- 右侧:公式与口径说明(支撑材料) -->
|
||
<el-col :span="6">
|
||
<el-card class="oee-formula-card" shadow="never">
|
||
<div slot="header" class="clearfix">
|
||
<span>公式与口径说明</span>
|
||
</div>
|
||
<div class="formula-block">
|
||
<div class="formula-title">OEE 总公式</div>
|
||
<div class="formula-eq">OEE = A × P × Q</div>
|
||
<ul class="formula-list">
|
||
<li>A(时间稼动率) = (负荷时间 − 停机时间) / 负荷时间</li>
|
||
<li>P(性能稼动率,吨维度) = (理论节拍 × 产量吨) / 实际运转时间</li>
|
||
<li>Q(良品率) = A系列吨 / 总产量吨</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="formula-block">
|
||
<div class="formula-title">关键字段定义</div>
|
||
<ul class="formula-list">
|
||
<li><b>负荷时间</b>:计划生产时间扣除计划停机后的时间</li>
|
||
<li><b>停机时间</b>:所有停机/中断(按 stop_type 汇总)的总时长</li>
|
||
<li><b>实际运转时间</b>:负荷时间 − 停机时间</li>
|
||
<li><b>理论节拍</b>:使用整体酸轧库中位数学习得出</li>
|
||
<li><b>A系列</b>:良品;<b>B系列</b>:合格品;<b>C/D系列</b>:次品;<b>O系列</b>:待判级</li>
|
||
<li><b>合格品率(AB)</b> = (A+B) / 总量;<b>次品率(CD)</b> = CD / 总量;<b>待判级率(O)</b> = O / 总量</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="formula-block" v-if="idealCycle">
|
||
<div class="formula-title">当前理论节拍(统计口径)</div>
|
||
<el-descriptions :column="1" size="small" class="reg-desc">
|
||
<el-descriptions-item label="理论节拍 (min/吨)">
|
||
{{ formatNumber(idealCycle.idealCycleTimeMinPerTon) }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="生产节拍中位数 (min/吨)">
|
||
{{ formatNumber(idealCycle.medianCycleTimeMinPerTon) }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="样本天数">
|
||
{{ idealCycle.sampleDays || 0 }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="吨数下限 (吨)">
|
||
{{ formatNumber(idealCycle.minWeightTon) }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="停机占比上限">
|
||
{{
|
||
idealCycle.maxDowntimeRate != null
|
||
? (Number(idealCycle.maxDowntimeRate) * 100).toFixed(0) + '%'
|
||
: '-'
|
||
}}
|
||
</el-descriptions-item>
|
||
</el-descriptions>
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
</el-row>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import {
|
||
exportOeeWord,
|
||
fetchOeeSummary,
|
||
fetchOeeLoss7,
|
||
fetchOeeEvents,
|
||
fetchOeeIdealCycle
|
||
} from '@/api/da/oee'
|
||
import * as echarts from 'echarts'
|
||
import OeeTrendChart from './components/OeeTrendChart.vue'
|
||
import OeeLossPareto from './components/OeeLossPareto.vue'
|
||
import OeeStoppageTop from './components/OeeStoppageTop.vue'
|
||
|
||
export default {
|
||
name: 'DaOeeIndex',
|
||
components: {
|
||
OeeTrendChart,
|
||
OeeLossPareto,
|
||
OeeStoppageTop
|
||
},
|
||
data() {
|
||
const today = new Date()
|
||
const firstDay = new Date(today.getFullYear(), today.getMonth(), 1)
|
||
return {
|
||
lineType: 'acid',
|
||
queryRange: [
|
||
this.formatDate(firstDay),
|
||
this.formatDate(today)
|
||
],
|
||
pickerOptions: {
|
||
disabledDate() {
|
||
// 不限制选择范围,保留扩展空间
|
||
return false
|
||
}
|
||
},
|
||
loading: {
|
||
summary: false,
|
||
loss7: false,
|
||
events: false,
|
||
idealCycle: false,
|
||
export: false
|
||
},
|
||
summaryList: [],
|
||
loss7List: [],
|
||
eventList: [],
|
||
idealCycle: null,
|
||
regDailyCompareChart: null
|
||
}
|
||
},
|
||
computed: {
|
||
kpi() {
|
||
const list = Array.isArray(this.summaryList) ? this.summaryList : []
|
||
if (list.length === 0) {
|
||
return {
|
||
oee: 0,
|
||
availability: 0,
|
||
performanceTon: 0,
|
||
quality: 0,
|
||
qualifiedRate: 0,
|
||
defectRate: 0,
|
||
pendingRate: 0,
|
||
loadingTimeMin: 0,
|
||
downtimeMin: 0,
|
||
runTimeMin: 0,
|
||
totalOutputTon: 0,
|
||
totalOutputCoil: 0,
|
||
goodOutputTon: 0,
|
||
abOutputTon: 0,
|
||
defectOutputTon: 0,
|
||
pendingOutputTon: 0
|
||
}
|
||
}
|
||
let sumLoading = 0
|
||
let sumDowntime = 0
|
||
let sumRun = 0
|
||
let sumTotalTon = 0
|
||
let sumGoodTon = 0
|
||
let sumAbTon = 0
|
||
let sumDefectTon = 0
|
||
let sumPendingTon = 0
|
||
let sumCoil = 0
|
||
let sumOee = 0
|
||
let sumA = 0
|
||
let sumP = 0
|
||
let sumQ = 0
|
||
let sumQualifiedRate = 0
|
||
let sumDefectRate = 0
|
||
let sumPendingRate = 0
|
||
|
||
list.forEach(row => {
|
||
sumLoading += row.loadingTimeMin || 0
|
||
sumDowntime += row.downtimeMin || 0
|
||
sumRun += row.runTimeMin || 0
|
||
sumTotalTon += Number(row.totalOutputTon || 0)
|
||
sumGoodTon += Number(row.goodOutputTon || 0)
|
||
sumAbTon += Number(row.abOutputTon || 0)
|
||
sumDefectTon += Number(row.defectOutputTon || 0)
|
||
sumPendingTon += Number(row.pendingOutputTon || 0)
|
||
sumCoil += row.totalOutputCoil || 0
|
||
sumOee += Number(row.oee || 0)
|
||
sumA += Number(row.availability || 0)
|
||
sumP += Number(row.performanceTon || 0)
|
||
sumQ += Number(row.quality || 0)
|
||
sumQualifiedRate += Number(row.qualifiedRate || 0)
|
||
sumDefectRate += Number(row.defectRate || 0)
|
||
sumPendingRate += Number(row.pendingRate || 0)
|
||
})
|
||
|
||
const n = list.length
|
||
return {
|
||
oee: n ? sumOee / n : 0,
|
||
availability: n ? sumA / n : 0,
|
||
performanceTon: n ? sumP / n : 0,
|
||
quality: n ? sumQ / n : 0,
|
||
qualifiedRate: n ? sumQualifiedRate / n : 0,
|
||
defectRate: n ? sumDefectRate / n : 0,
|
||
pendingRate: n ? sumPendingRate / n : 0,
|
||
loadingTimeMin: sumLoading,
|
||
downtimeMin: sumDowntime,
|
||
runTimeMin: sumRun,
|
||
totalOutputTon: sumTotalTon,
|
||
totalOutputCoil: sumCoil,
|
||
goodOutputTon: sumGoodTon,
|
||
abOutputTon: sumAbTon,
|
||
defectOutputTon: sumDefectTon,
|
||
pendingOutputTon: sumPendingTon
|
||
}
|
||
}
|
||
},
|
||
created() {
|
||
this.loadAll()
|
||
},
|
||
mounted() {
|
||
window.addEventListener('resize', this.handleResize)
|
||
},
|
||
beforeDestroy() {
|
||
window.removeEventListener('resize', this.handleResize)
|
||
if (this.regDailyCompareChart) {
|
||
this.regDailyCompareChart.dispose()
|
||
this.regDailyCompareChart = null
|
||
}
|
||
},
|
||
methods: {
|
||
formatDate(d) {
|
||
const y = d.getFullYear()
|
||
const m = (`0${d.getMonth() + 1}`).slice(-2)
|
||
const day = (`0${d.getDate()}`).slice(-2)
|
||
return `${y}-${m}-${day}`
|
||
},
|
||
formatPercent(value) {
|
||
if (value === null || value === undefined || isNaN(value)) {
|
||
return '-'
|
||
}
|
||
const num = Number(value)
|
||
return num.toFixed(1)
|
||
},
|
||
formatNumber(value) {
|
||
if (value === null || value === undefined || value === '') {
|
||
return '-'
|
||
}
|
||
const num = Number(value)
|
||
if (isNaN(num)) return '-'
|
||
return num.toFixed(2)
|
||
},
|
||
formatDateTime(val) {
|
||
if (!val) return ''
|
||
// 后端可能返回字符串或时间戳,这里做容错
|
||
if (typeof val === 'string') return val
|
||
try {
|
||
const d = new Date(val)
|
||
const y = d.getFullYear()
|
||
const m = (`0${d.getMonth() + 1}`).slice(-2)
|
||
const day = (`0${d.getDate()}`).slice(-2)
|
||
const hh = (`0${d.getHours()}`).slice(-2)
|
||
const mm = (`0${d.getMinutes()}`).slice(-2)
|
||
const ss = (`0${d.getSeconds()}`).slice(-2)
|
||
return `${y}-${m}-${day} ${hh}:${mm}:${ss}`
|
||
} catch (e) {
|
||
return ''
|
||
}
|
||
},
|
||
buildQuery() {
|
||
const [start, end] = this.queryRange || []
|
||
return {
|
||
lineType: this.lineType,
|
||
startDate: start,
|
||
endDate: end
|
||
}
|
||
},
|
||
async loadAll() {
|
||
await Promise.all([
|
||
this.loadSummary(),
|
||
this.loadLoss7(),
|
||
this.loadEvents(),
|
||
this.loadIdealCycle()
|
||
])
|
||
},
|
||
async loadSummary() {
|
||
this.loading.summary = true
|
||
try {
|
||
const res = await fetchOeeSummary(this.buildQuery())
|
||
// 兼容后端直接返回数组或 TableDataInfo { rows, total } 两种结构
|
||
|
||
this.summaryList = res.data
|
||
|
||
} catch (e) {
|
||
this.$message.error('加载酸轧日汇总失败')
|
||
} finally {
|
||
this.loading.summary = false
|
||
}
|
||
},
|
||
async loadLoss7() {
|
||
this.loading.loss7 = true
|
||
try {
|
||
const res = await fetchOeeLoss7(this.buildQuery())
|
||
// 兼容后端直接返回数组或 TableDataInfo { rows, total } 两种结构
|
||
this.loss7List = res.data
|
||
|
||
} catch (e) {
|
||
this.$message.error('加载酸轧 7 大损失失败')
|
||
} finally {
|
||
this.loading.loss7 = false
|
||
}
|
||
},
|
||
async loadEvents() {
|
||
this.loading.events = true
|
||
try {
|
||
const res = await fetchOeeEvents(this.buildQuery())
|
||
// 后端 TableDataInfo 结构:{ rows, total }
|
||
this.eventList = (res && Array.isArray(res.rows)) ? res.rows : []
|
||
} catch (e) {
|
||
this.$message.error('加载酸轧停机事件失败')
|
||
} finally {
|
||
this.loading.events = false
|
||
}
|
||
},
|
||
async loadIdealCycle() {
|
||
this.loading.idealCycle = true
|
||
try {
|
||
const res = await fetchOeeIdealCycle(this.buildQuery())
|
||
this.idealCycle = res && res.data ? res.data : null
|
||
this.$nextTick(() => {
|
||
this.renderIdealCycleChart()
|
||
})
|
||
} catch (e) {
|
||
this.$message.error('加载理论节拍失败')
|
||
} finally {
|
||
this.loading.idealCycle = false
|
||
}
|
||
},
|
||
handleSearch() {
|
||
if (!this.queryRange || this.queryRange.length !== 2) {
|
||
this.$message.warning('请选择查询日期范围')
|
||
return
|
||
}
|
||
this.loadAll()
|
||
},
|
||
handleReset() {
|
||
const today = new Date()
|
||
const firstDay = new Date(today.getFullYear(), today.getMonth(), 1)
|
||
this.queryRange = [this.formatDate(firstDay), this.formatDate(today)]
|
||
this.loadAll()
|
||
},
|
||
async handleExportWord() {
|
||
this.loading.export = true
|
||
try {
|
||
const res = await exportOeeWord(this.buildQuery())
|
||
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报表_${this.queryRange[0]}_${this.queryRange[1]}.docx`
|
||
a.click()
|
||
window.URL.revokeObjectURL(url)
|
||
} catch (e) {
|
||
this.$message.error('导出 Word 失败')
|
||
} finally {
|
||
this.loading.export = false
|
||
}
|
||
},
|
||
renderIdealCycleChart() {
|
||
if (!this.idealCycle) return
|
||
const dailyPoints = Array.isArray(this.idealCycle.dailyComparePoints)
|
||
? this.idealCycle.dailyComparePoints
|
||
: []
|
||
if (this.$refs.regDailyCompareChart && dailyPoints.length) {
|
||
if (!this.regDailyCompareChart) {
|
||
this.regDailyCompareChart = echarts.init(this.$refs.regDailyCompareChart)
|
||
}
|
||
const categories = dailyPoints.map(d => d.statDate)
|
||
const actualRun = dailyPoints.map(d => Number(d.actualRunTimeMin || 0))
|
||
const theoretical = dailyPoints.map(d => Number(d.theoreticalTimeMin || 0))
|
||
const compareOption = {
|
||
tooltip: { trigger: 'axis' },
|
||
grid: { left: 40, right: 20, top: 30, bottom: 40 },
|
||
legend: { top: 0, left: 'center' },
|
||
xAxis: {
|
||
type: 'category',
|
||
data: categories,
|
||
axisLabel: { fontSize: 10 }
|
||
},
|
||
yAxis: {
|
||
type: 'value',
|
||
name: '时间 (min)',
|
||
nameLocation: 'middle',
|
||
nameGap: 35
|
||
},
|
||
series: [
|
||
{
|
||
name: '实际运转时间',
|
||
type: 'line',
|
||
data: actualRun,
|
||
smooth: true,
|
||
symbolSize: 4,
|
||
itemStyle: { color: '#409EFF' }
|
||
},
|
||
{
|
||
name: '理论耗时',
|
||
type: 'line',
|
||
data: theoretical,
|
||
smooth: true,
|
||
symbolSize: 4,
|
||
itemStyle: { color: '#E6A23C' }
|
||
}
|
||
]
|
||
}
|
||
this.regDailyCompareChart.setOption(compareOption)
|
||
}
|
||
},
|
||
handleResize() {
|
||
this.regDailyCompareChart && this.regDailyCompareChart.resize()
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.oee-report-page {
|
||
padding: 16px;
|
||
background: #f5f7fa;
|
||
}
|
||
|
||
.oee-header-card {
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.oee-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.oee-title-block {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.oee-title {
|
||
font-size: 20px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
}
|
||
|
||
.oee-subtitle {
|
||
margin-top: 4px;
|
||
font-size: 13px;
|
||
color: #606266;
|
||
}
|
||
|
||
.oee-query-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.oee-top-warning {
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.oee-main-row {
|
||
margin-top: 8px;
|
||
}
|
||
|
||
.oee-word-card {
|
||
min-height: 600px;
|
||
background: #ffffff;
|
||
border-radius: 4px;
|
||
font-size: 12px;
|
||
line-height: 1.5;
|
||
overflow-x: hidden;
|
||
}
|
||
|
||
.oee-section-title {
|
||
margin: 12px 0 6px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
}
|
||
|
||
.oee-help-text {
|
||
margin: 0 0 6px;
|
||
font-size: 12px;
|
||
color: #909399;
|
||
}
|
||
|
||
.oee-kpi-table,
|
||
.oee-daily-table,
|
||
.oee-loss7-table,
|
||
.oee-events-table {
|
||
font-size: 12px;
|
||
}
|
||
|
||
.oee-formula-card {
|
||
background: #ffffff;
|
||
}
|
||
|
||
.formula-block {
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.formula-title {
|
||
font-weight: 600;
|
||
margin-bottom: 6px;
|
||
color: #303133;
|
||
}
|
||
|
||
.formula-eq {
|
||
font-family: 'Times New Roman', serif;
|
||
font-style: italic;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.formula-list {
|
||
padding-left: 16px;
|
||
margin: 0;
|
||
}
|
||
|
||
.formula-list li {
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.reg-desc {
|
||
font-size: 12px;
|
||
}
|
||
|
||
.reg-chart {
|
||
width: 100%;
|
||
height: 260px;
|
||
border: 1px solid #ebeef5;
|
||
border-radius: 4px;
|
||
box-sizing: border-box;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.reg-chart canvas {
|
||
max-width: 100% !important;
|
||
}
|
||
</style>
|
||
|