Merge branch '0.8.X' of https://gitee.com/hdka/klp-oa into 0.8.X
This commit is contained in:
39
klp-ui/src/api/da/oee.js
Normal file
39
klp-ui/src/api/da/oee.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// OEE 汇总(两条产线 KPI + 日趋势)
|
||||
export function fetchOeeSummary(query) {
|
||||
return request({
|
||||
url: '/oee/line/summary',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 7 大损失汇总
|
||||
export function fetchOeeLoss7(query) {
|
||||
return request({
|
||||
url: '/oee/line/loss7',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 停机/损失事件明细
|
||||
export function fetchOeeEvents(query) {
|
||||
return request({
|
||||
url: '/oee/line/events',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 导出 Word 报表
|
||||
export function exportOeeWord(query) {
|
||||
return request({
|
||||
url: '/oee/line/exportWord',
|
||||
method: 'get',
|
||||
params: query,
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -89,13 +89,13 @@ export function batchCalculateCost(calcDate) {
|
||||
})
|
||||
}
|
||||
|
||||
// 按入场钢卷号维度计算成本
|
||||
export function calculateCostByEnterCoilNo(enterCoilNo, calcDate) {
|
||||
// 按当前钢卷号维度计算成本(单卷详情)
|
||||
export function calculateCostByCurrentCoilNo(currentCoilNo, calcDate) {
|
||||
return request({
|
||||
url: '/wms/cost/coil/calculateByEnterCoilNo',
|
||||
url: '/wms/cost/coil/calculateByCurrentCoilNo',
|
||||
method: 'post',
|
||||
params: {
|
||||
enterCoilNo,
|
||||
currentCoilNo,
|
||||
calcDate
|
||||
}
|
||||
})
|
||||
|
||||
384
klp-ui/src/views/da/oee/index.vue
Normal file
384
klp-ui/src/views/da/oee/index.vue
Normal file
@@ -0,0 +1,384 @@
|
||||
<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>
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
|
||||
<el-card class="block-card">
|
||||
<div slot="header" class="clearfix">
|
||||
<span class="card-title">成本汇总(按入场卷号)</span>
|
||||
<span class="card-title">成本汇总</span>
|
||||
</div>
|
||||
<el-table :data="mergedRows" border stripe>
|
||||
<el-table-column prop="enterCoilNo" label="入场卷号"></el-table-column>
|
||||
@@ -257,6 +257,7 @@ export default {
|
||||
}
|
||||
fetchCoilTotalMerged(params).then(res => {
|
||||
this.mergedRows = res.rows || []
|
||||
console.log(res)
|
||||
this.mergedTotal = res.total || 0
|
||||
|
||||
const rows = this.mergedRows || []
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
style="width: 100%"
|
||||
@sort-change="handleSortChange"
|
||||
>
|
||||
<el-table-column prop="enterCoilNo" label="入场钢卷号" />
|
||||
<el-table-column prop="currentCoilNo" label="当前钢卷号" />
|
||||
<el-table-column prop="coilCount" label="子钢卷数" align="right" />
|
||||
<el-table-column prop="totalGrossWeight" label="总毛重(吨)" align="right">
|
||||
<template slot-scope="scope">
|
||||
@@ -112,7 +112,7 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="small" @click="viewCoilDetail(scope.row)">查看子钢卷成本</el-button>
|
||||
<el-button type="text" size="small" @click="viewCoilDetail(scope.row)">查看详情</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -126,56 +126,45 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 入场钢卷号下子钢卷详情对话框 -->
|
||||
<!-- 当前钢卷成本详情对话框 -->
|
||||
<el-dialog
|
||||
title="入场钢卷号下子钢卷成本详情"
|
||||
title="当前钢卷成本详情"
|
||||
:visible.sync="showDetailDialog"
|
||||
width="900px"
|
||||
>
|
||||
<div v-if="detailEnterCoilNo" style="margin-bottom: 10px;">
|
||||
入场钢卷号:<strong>{{ detailEnterCoilNo }}</strong>
|
||||
</div>
|
||||
<el-table :data="detailList" stripe style="width: 100%">
|
||||
<el-table-column prop="currentCoilNo" label="当前钢卷号" />
|
||||
<el-table-column prop="grossWeightTon" label="毛重(吨)" align="right">
|
||||
<template slot-scope="scope">
|
||||
{{ formatWeight(scope.row.grossWeightTon) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="netWeightTon" label="净重(吨)" align="right">
|
||||
<template slot-scope="scope">
|
||||
{{ formatWeight(scope.row.netWeightTon) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="storageDays" label="在库天数" align="right">
|
||||
<template slot-scope="scope">
|
||||
<span :class="getStorageDaysClass(scope.row.storageDays)">
|
||||
{{ scope.row.storageDays || '-' }}
|
||||
<div v-if="detailInfo">
|
||||
<el-form label-width="140px" size="small">
|
||||
<el-form-item label="当前钢卷号">
|
||||
<span>{{ detailCoilNo }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="毛重(吨)">
|
||||
<span>{{ formatWeight(detailInfo.grossWeightTon) }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="净重(吨)">
|
||||
<span>{{ formatWeight(detailInfo.netWeightTon) }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="在库天数">
|
||||
<span :class="getStorageDaysClass(detailInfo.storageDays)">
|
||||
{{ detailInfo.storageDays || '-' }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="unitCost" label="单位成本(元/吨/天)" align="right">
|
||||
<template slot-scope="scope">
|
||||
{{ formatMoney(scope.row.unitCost) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="dailyCost" label="日成本(元)" align="right">
|
||||
<template slot-scope="scope">
|
||||
{{ formatMoney(scope.row.dailyCost) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="totalCost" label="累计成本(元)" align="right">
|
||||
<template slot-scope="scope">
|
||||
<span class="cost-total">{{ formatMoney(scope.row.totalCost) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-form-item>
|
||||
<el-form-item label="单位成本(元/吨/天)">
|
||||
<span>{{ formatMoney(detailInfo.unitCost) }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="日成本(元)">
|
||||
<span>{{ formatMoney(detailInfo.dailyCost) }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="累计成本(元)">
|
||||
<span class="cost-total">{{ formatMoney(detailInfo.totalCost) }}</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { calculateCostByEnterCoilNo, getStockpileCostList } from '@/api/wms/cost'
|
||||
import { calculateCostByCurrentCoilNo, getStockpileCostList } from '@/api/wms/cost'
|
||||
|
||||
export default {
|
||||
name: 'CostStockpile',
|
||||
@@ -197,8 +186,8 @@ export default {
|
||||
currentCoilNo: null
|
||||
},
|
||||
showDetailDialog: false,
|
||||
detailEnterCoilNo: null,
|
||||
detailList: []
|
||||
detailCoilNo: null,
|
||||
detailInfo: null
|
||||
}
|
||||
},
|
||||
created() {
|
||||
@@ -272,10 +261,10 @@ export default {
|
||||
},
|
||||
async viewCoilDetail(row) {
|
||||
try {
|
||||
const res = await calculateCostByEnterCoilNo(row.enterCoilNo, null)
|
||||
const res = await calculateCostByCurrentCoilNo(row.currentCoilNo, null)
|
||||
if (res.code === 200 && res.data && !res.data.error) {
|
||||
this.detailEnterCoilNo = row.enterCoilNo
|
||||
this.detailList = res.data.coilDetails || []
|
||||
this.detailCoilNo = row.currentCoilNo
|
||||
this.detailInfo = res.data
|
||||
this.showDetailDialog = true
|
||||
} else {
|
||||
this.$message.error(res.data?.error || '加载详情失败')
|
||||
|
||||
Reference in New Issue
Block a user