二级系统联合寻找数据

This commit is contained in:
2026-02-04 15:22:34 +08:00
parent d42b8ffef2
commit 5b3938e13f
19 changed files with 2167 additions and 352 deletions

View File

@@ -0,0 +1,127 @@
<template>
<div class="oee-chart" ref="chartRef"></div>
</template>
<script>
import * as echarts from 'echarts'
export default {
name: 'OeeLossPareto',
props: {
data: {
type: Array,
default: () => []
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
window.addEventListener('resize', this.handleResize)
},
beforeDestroy() {
window.removeEventListener('resize', this.handleResize)
if (this.chart) {
this.chart.dispose()
this.chart = null
}
},
watch: {
data: {
deep: true,
immediate: true,
handler() {
this.render()
}
}
},
methods: {
toFixed3(val) {
const n = Number(val)
return Number.isFinite(n) ? n.toFixed(3) : '-'
},
initChart() {
if (!this.$refs.chartRef) return
this.chart = echarts.init(this.$refs.chartRef)
},
render() {
if (!this.chart) this.initChart()
if (!this.chart) return
const list = Array.isArray(this.data) ? this.data : []
const names = list.map(d => d.lossCategoryName)
const mins = list.map(d => Number(d.lossTimeMin || 0))
const percents = list.map(d => Number(d.lossTimeRate || 0))
const cum = []
percents.reduce((acc, cur) => {
const v = acc + cur
cum.push(Math.min(100, v))
return v
}, 0)
const option = {
tooltip: {
trigger: 'axis',
formatter: params => {
const title = (params && params[0] && params[0].axisValue) || ''
const lines = (params || []).map(p => {
const suffix = p.seriesName === '累计占比' ? ' %' : ' min'
return `${p.marker}${p.seriesName}${this.toFixed3(p.value)}${suffix}`
})
return [title, ...lines].join('<br/>')
}
},
grid: { left: 40, right: 60, top: 30, bottom: 80 },
xAxis: {
type: 'category',
data: names,
axisLabel: { interval: 0, rotate: 40, fontSize: 10 }
},
yAxis: [
{
type: 'value',
name: '损失时间 (min)',
axisLabel: { formatter: v => this.toFixed3(v) }
},
{
type: 'value',
name: '累计占比 (%)',
min: 0,
max: 100,
position: 'right',
axisLabel: { formatter: v => this.toFixed3(v) }
}
],
series: [
{
name: '损失时间',
type: 'bar',
data: mins,
itemStyle: { color: '#F56C6C' }
},
{
name: '累计占比',
type: 'line',
yAxisIndex: 1,
data: cum,
smooth: true,
itemStyle: { color: '#409EFF' }
}
]
}
this.chart.setOption(option)
},
handleResize() {
this.chart && this.chart.resize()
}
}
}
</script>
<style scoped>
.oee-chart {
width: 100%;
height: 260px;
}
</style>

View File

@@ -0,0 +1,106 @@
<template>
<div class="oee-chart" ref="chartRef"></div>
</template>
<script>
import * as echarts from 'echarts'
export default {
name: 'OeeStoppageTop',
props: {
data: {
type: Array,
default: () => []
},
topN: {
type: Number,
default: 10
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
window.addEventListener('resize', this.handleResize)
},
beforeDestroy() {
window.removeEventListener('resize', this.handleResize)
if (this.chart) {
this.chart.dispose()
this.chart = null
}
},
watch: {
data: {
deep: true,
immediate: true,
handler() {
this.render()
}
}
},
methods: {
toFixed3(val) {
const n = Number(val)
return Number.isFinite(n) ? n.toFixed(3) : '-'
},
initChart() {
if (!this.$refs.chartRef) return
this.chart = echarts.init(this.$refs.chartRef)
},
render() {
if (!this.chart) this.initChart()
if (!this.chart) return
const list = Array.isArray(this.data) ? this.data.slice() : []
list.sort((a, b) => (b.duration || 0) - (a.duration || 0))
const top = list.slice(0, this.topN)
const names = top.map((d, idx) => `${idx + 1}. ${d.stopType || '未知'}`)
const mins = top.map(d => Number(((d.duration || 0) / 60).toFixed(3)))
const option = {
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
formatter: params => {
const p = params && params[0]
if (!p) return ''
return `${p.name}<br/>${p.marker}${p.seriesName}${this.toFixed3(p.value)} min`
}
},
grid: { left: 140, right: 20, top: 20, bottom: 20 },
xAxis: {
type: 'value',
name: '停机时间 (min)',
axisLabel: { formatter: v => this.toFixed3(v) }
},
yAxis: {
type: 'category',
data: names,
axisLabel: { fontSize: 11 }
},
series: [
{
name: '停机时间 (min)',
type: 'bar',
data: mins,
itemStyle: { color: '#67C23A' }
}
]
}
this.chart.setOption(option)
},
handleResize() {
this.chart && this.chart.resize()
}
}
}
</script>
<style scoped>
.oee-chart {
width: 100%;
height: 260px;
}
</style>

View File

@@ -0,0 +1,97 @@
<template>
<div class="oee-chart" ref="chartRef"></div>
</template>
<script>
import * as echarts from 'echarts'
export default {
name: 'OeeTrendChart',
props: {
data: {
type: Array,
default: () => []
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
window.addEventListener('resize', this.handleResize)
},
beforeDestroy() {
window.removeEventListener('resize', this.handleResize)
if (this.chart) {
this.chart.dispose()
this.chart = null
}
},
watch: {
data: {
deep: true,
immediate: true,
handler() {
this.render()
}
}
},
methods: {
toFixed3(val) {
const n = Number(val)
return Number.isFinite(n) ? n.toFixed(3) : '-'
},
initChart() {
if (!this.$refs.chartRef) return
this.chart = echarts.init(this.$refs.chartRef)
},
render() {
if (!this.chart) this.initChart()
if (!this.chart) return
const list = Array.isArray(this.data) ? this.data : []
const x = list.map(d => d.statDate)
const oee = list.map(d => Number(d.oee || 0))
const a = list.map(d => Number(d.availability || 0))
const p = list.map(d => Number(d.performanceTon || 0))
const q = list.map(d => Number(d.quality || 0))
const option = {
tooltip: {
trigger: 'axis',
formatter: params => {
const title = (params && params[0] && params[0].axisValue) || ''
const lines = (params || []).map(p0 => `${p0.marker}${p0.seriesName}${this.toFixed3(p0.value)} %`)
return [title, ...lines].join('<br/>')
}
},
legend: { top: 0, left: 'center' },
grid: { left: 40, right: 20, top: 30, bottom: 40 },
xAxis: { type: 'category', data: x },
yAxis: {
type: 'value',
name: '百分比 (%)',
axisLabel: { formatter: v => this.toFixed3(v) }
},
series: [
{ name: 'OEE', type: 'line', data: oee, smooth: true },
{ name: 'A', type: 'line', data: a, smooth: true },
{ name: 'P_ton', type: 'line', data: p, smooth: true },
{ name: 'Q', type: 'line', data: q, smooth: true }
]
}
this.chart.setOption(option)
},
handleResize() {
this.chart && this.chart.resize()
}
}
}
</script>
<style scoped>
.oee-chart {
width: 100%;
height: 260px;
}
</style>

View File

@@ -0,0 +1,745 @@
<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-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-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="良品率 Q (%)" align="center">
<template slot-scope="scope">
<span>{{ formatPercent(scope.row.quality) }}</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="defectOutputTon" label="次品量 (吨)" align="center">
<template slot-scope="scope">
<span>{{ formatNumber(scope.row.defectOutputTon) }}</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="Q (%)">
<template slot-scope="scope">
{{ formatPercent(scope.row.quality) }}
</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="defectOutputTon" label="次品 (吨)">
<template slot-scope="scope">
{{ formatNumber(scope.row.defectOutputTon) }}
</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良品率 = 良品吨 / 总产量吨</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>良品/次品</b> WMS `quality_status` 判断C+/C/C-/D+/D/D- </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 {
queryRange: [
this.formatDate(firstDay),
this.formatDate(today)
],
pickerOptions: {
disabledDate(time) {
// 不限制选择范围,保留扩展空间
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,
loadingTimeMin: 0,
downtimeMin: 0,
runTimeMin: 0,
totalOutputTon: 0,
totalOutputCoil: 0,
goodOutputTon: 0,
defectOutputTon: 0
}
}
let sumLoading = 0
let sumDowntime = 0
let sumRun = 0
let sumTotalTon = 0
let sumGoodTon = 0
let sumDefectTon = 0
let sumCoil = 0
let sumOee = 0
let sumA = 0
let sumP = 0
let sumQ = 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)
sumDefectTon += Number(row.defectOutputTon || 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)
})
const n = list.length
const defectAgg = Math.max(0, sumTotalTon - sumGoodTon)
return {
oee: n ? sumOee / n : 0,
availability: n ? sumA / n : 0,
performanceTon: n ? sumP / n : 0,
quality: n ? sumQ / n : 0,
loadingTimeMin: sumLoading,
downtimeMin: sumDowntime,
runTimeMin: sumRun,
totalOutputTon: sumTotalTon,
totalOutputCoil: sumCoil,
goodOutputTon: sumGoodTon,
defectOutputTon: defectAgg || sumDefectTon
}
}
},
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 {
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-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>