Files
klp-oa/klp-ui/src/views/ems/cost/cost.vue
2026-01-27 16:54:46 +08:00

439 lines
15 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="coil-total-cost-page">
<el-card class="search-card">
<el-form :model="queryParams" inline label-width="100px" size="small">
<el-form-item label="入场卷号" required>
<el-input v-model="queryParams.enterCoilNo" placeholder="必填,支持模糊"></el-input>
</el-form-item>
<el-form-item label="当前卷号">
<el-input v-model="queryParams.currentCoilNo" placeholder="支持模糊"></el-input>
</el-form-item>
<el-form-item label="开始日期">
<el-date-picker v-model="queryParams.startDate" type="date" value-format="yyyy-MM-dd" placeholder="开始日期"></el-date-picker>
</el-form-item>
<el-form-item label="结束日期">
<el-date-picker v-model="queryParams.endDate" type="date" value-format="yyyy-MM-dd" placeholder="结束日期"></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleSearch">查询</el-button>
<el-button icon="el-icon-refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<el-row :gutter="20" class="summary-row">
<el-col :xs="24" :md="6">
<div class="summary-card">
<div class="label">能源成本</div>
<div class="value">¥ {{ formatNumber(energySummary.totalEnergyCost, 2) }}</div>
<div class="desc">耗能 {{ formatNumber(energySummary.totalConsumption, 2) }}时长 {{ formatNumber(energySummary.totalDuration, 1) }}h</div>
</div>
</el-col>
<el-col :xs="24" :md="6">
<div class="summary-card">
<div class="label">本日囤积成本</div>
<div class="value">¥ {{ formatNumber(stockSummary.todayCost, 2) }}</div>
<div class="desc">净重 {{ formatNumber(stockSummary.totalNetWeight, 2) }}毛重 {{ formatNumber(stockSummary.totalGrossWeight, 2) }}</div>
</div>
</el-col>
<el-col :xs="24" :md="6">
<div class="summary-card">
<div class="label">辅料成本</div>
<div class="value">¥ {{ formatNumber(materialSummary.auxCost, 2) }}</div>
<div class="desc">备件 ¥ {{ formatNumber(materialSummary.spareCost, 2) }}</div>
</div>
</el-col>
<el-col :xs="24" :md="6">
<div class="summary-card highlight">
<div class="label">总成本</div>
<div class="value">¥ {{ formatNumber(totalCost, 2) }}</div>
<div class="desc">能源 + 囤积 + 辅料 + 备件</div>
</div>
</el-col>
</el-row>
<el-card class="block-card">
<div slot="header" class="clearfix">
<span class="card-title">成本汇总</span>
</div>
<el-table :data="mergedRows" border stripe>
<el-table-column prop="enterCoilNo" label="入场卷号"></el-table-column>
<el-table-column prop="currentCoilNo" label="当前卷号"></el-table-column>
<el-table-column prop="energyCost" label="能源成本">
<template slot-scope="scope">¥ {{ formatNumber(scope.row.energyCost, 2) }}</template>
</el-table-column>
<el-table-column prop="stockCost" label="囤积成本(当日)">
<template slot-scope="scope">¥ {{ formatNumber(scope.row.stockCost, 2) }}</template>
</el-table-column>
<el-table-column prop="auxCost" label="辅料成本">
<template slot-scope="scope">¥ {{ formatNumber(scope.row.auxCost, 2) }}</template>
</el-table-column>
<el-table-column prop="spareCost" label="备件成本">
<template slot-scope="scope">¥ {{ formatNumber(scope.row.spareCost, 2) }}</template>
</el-table-column>
<el-table-column prop="totalCost" label="总成本">
<template slot-scope="scope">¥ {{ formatNumber(scope.row.totalCost, 2) }}</template>
</el-table-column>
<el-table-column prop="totalNetWeight" label="净重">
<template slot-scope="scope">{{ formatNumber(scope.row.totalNetWeight, 2) }}</template>
</el-table-column>
<el-table-column prop="totalGrossWeight" label="毛重">
<template slot-scope="scope">{{ formatNumber(scope.row.totalGrossWeight, 2) }}</template>
</el-table-column>
<el-table-column label="操作" width="180">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="openDetail(scope.row)">查看详情</el-button>
</template>
</el-table-column>
</el-table>
<div class="table-actions">
<el-button size="mini" type="primary" icon="el-icon-download" @click="exportSummary">导出汇总</el-button>
</div>
<pagination
v-show="mergedTotal > 0"
:total="mergedTotal"
:page.sync="mergedQuery.pageNum"
:limit.sync="mergedQuery.pageSize"
@pagination="loadMerged"
/>
</el-card>
<el-dialog title="钢卷成本详情" :visible.sync="detailVisible" width="80%" :close-on-click-modal="false">
<div class="detail-section">
<div class="detail-title">能源分摊明细{{ detailEnterCoilNo }}</div>
<el-table :data="detailEnergyRows" stripe border max-height="300">
<el-table-column prop="enterCoilNo" label="入场卷号" width="140"></el-table-column>
<el-table-column prop="currentCoilNo" label="当前卷号" width="140"></el-table-column>
<el-table-column prop="warehouseName" label="库区" width="140"></el-table-column>
<el-table-column prop="duration" label="时长(h)" width="100">
<template slot-scope="scope">{{ formatNumber(scope.row.duration, 2) }}</template>
</el-table-column>
<el-table-column prop="consumption" label="分摊能耗" width="120">
<template slot-scope="scope">{{ formatNumber(scope.row.consumption, 4) }}</template>
</el-table-column>
<el-table-column prop="totalCost" label="分摊成本" width="120">
<template slot-scope="scope">¥ {{ formatNumber(scope.row.totalCost, 2) }}</template>
</el-table-column>
<el-table-column prop="startTime" label="开始时间" width="180">
<template slot-scope="scope">{{ formatTime(scope.row.startTime) }}</template>
</el-table-column>
<el-table-column prop="endTime" label="结束时间" width="180">
<template slot-scope="scope">{{ formatTime(scope.row.endTime) }}</template>
</el-table-column>
</el-table>
</div>
<div class="detail-section">
<div class="detail-title">囤积成本{{ detailEnterCoilNo }}</div>
<el-table :data="detailStockRow ? [detailStockRow] : []" stripe border>
<el-table-column prop="enterCoilNo" label="入场卷号"></el-table-column>
<el-table-column prop="totalNetWeight" label="净重">
<template slot-scope="scope">{{ formatNumber(scope.row.totalNetWeight, 2) }}</template>
</el-table-column>
<el-table-column prop="totalGrossWeight" label="毛重">
<template slot-scope="scope">{{ formatNumber(scope.row.totalGrossWeight, 2) }}</template>
</el-table-column>
<el-table-column prop="avgStorageDays" label="平均在库天数">
<template slot-scope="scope">{{ formatNumber(scope.row.avgStorageDays, 1) }}</template>
</el-table-column>
<el-table-column prop="todayCost" label="本日囤积成本">
<template slot-scope="scope">¥ {{ formatNumber(scope.row.todayCost != null ? scope.row.todayCost : scope.row.totalCost, 2) }}</template>
</el-table-column>
</el-table>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="detailVisible = false"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { fetchCoilTotalEnergySummary, fetchCoilTotalEnergyDetail } from '@/api/ems/energyCostReport'
import { fetchCoilTotalMerged } from '@/api/ems/energyCostReport'
import { exportCoilTotalMerged } from '@/api/ems/energyCostReport'
import { getStockpileCostList } from '@/api/wms/cost'
export default {
name: 'CoilTotalCost',
data() {
return {
queryParams: {
enterCoilNo: '',
currentCoilNo: '',
startDate: undefined,
endDate: undefined
},
energyQuery: {
pageNum: 1,
pageSize: 50
},
mergedQuery: {
pageNum: 1,
pageSize: 50
},
stockQuery: {
pageNum: 1,
pageSize: 50
},
energySummary: {
coilCount: 0,
totalDuration: 0,
totalConsumption: 0,
totalEnergyCost: 0
},
energyDetail: {
rows: [],
total: 0
},
mergedRows: [],
mergedTotal: 0,
stockDetail: {
rows: [],
total: 0
},
stockSummary: {
totalCost: 0,
todayCost: 0,
totalNetWeight: 0,
totalGrossWeight: 0
},
materialSummary: {
auxCost: 0,
spareCost: 0
},
energyLoading: false,
stockLoading: false,
detailVisible: false,
detailEnterCoilNo: '',
detailEnergyRows: [],
detailStockRow: null
}
},
computed: {
totalCost() {
const energy = Number(this.energySummary.totalEnergyCost) || 0
const stock = Number(this.stockSummary.todayCost ?? this.stockSummary.totalCost) || 0
const aux = Number(this.materialSummary.auxCost) || 0
const spare = Number(this.materialSummary.spareCost) || 0
return energy + stock + aux + spare
}
},
mounted() {
this.handleSearch()
},
methods: {
handleSearch() {
this.energyQuery.pageNum = 1
this.mergedQuery.pageNum = 1
this.stockQuery.pageNum = 1
this.loadEnergy()
this.loadMerged()
this.loadStockDetail()
},
handleReset() {
this.queryParams = {
enterCoilNo: '',
currentCoilNo: '',
startDate: undefined,
endDate: undefined
}
this.energyQuery.pageNum = 1
this.mergedQuery.pageNum = 1
this.stockQuery.pageNum = 1
this.energySummary = { coilCount: 0, totalDuration: 0, totalConsumption: 0, totalEnergyCost: 0 }
this.energyDetail = { rows: [], total: 0 }
this.mergedRows = []
this.mergedTotal = 0
this.stockDetail = { rows: [], total: 0 }
this.stockSummary = { totalCost: 0, totalNetWeight: 0, totalGrossWeight: 0 }
this.handleSearch()
},
loadMerged() {
const params = {
...this.queryParams,
pageNum: this.mergedQuery.pageNum,
pageSize: this.mergedQuery.pageSize
}
fetchCoilTotalMerged(params).then(res => {
this.mergedRows = res.rows || []
console.log(res)
this.mergedTotal = res.total || 0
const rows = this.mergedRows || []
this.materialSummary = {
auxCost: rows.reduce((sum, r) => sum + (Number(r.auxCost) || 0), 0),
spareCost: rows.reduce((sum, r) => sum + (Number(r.spareCost) || 0), 0)
}
})
},
loadEnergy() {
this.energyLoading = true
const params = { ...this.queryParams }
fetchCoilTotalEnergySummary(params).then(res => {
this.energySummary = res.data || this.energySummary
}).finally(() => {
this.loadEnergyDetail()
})
},
loadEnergyDetail() {
const params = {
...this.queryParams,
pageNum: this.energyQuery.pageNum,
pageSize: this.energyQuery.pageSize
}
fetchCoilTotalEnergyDetail(params).then(res => {
this.energyDetail = {
rows: res.rows || [],
total: res.total || 0
}
}).finally(() => {
this.energyLoading = false
})
},
loadStockDetail() {
this.stockLoading = true
const params = {
enterCoilNo: this.queryParams.enterCoilNo,
currentCoilNo: this.queryParams.currentCoilNo,
pageNum: this.stockQuery.pageNum,
pageSize: this.stockQuery.pageSize
}
getStockpileCostList(params).then(res => {
const data = res.data || {}
this.stockDetail = {
rows: data.rows || [],
total: data.total || 0
}
const summary = data.summary || {}
this.stockSummary = {
totalCost: summary.totalCost ?? 0,
todayCost: summary.todayCost ?? summary.totalCost ?? 0,
totalNetWeight: summary.totalNetWeight ?? 0,
totalGrossWeight: summary.totalGrossWeight ?? 0
}
}).finally(() => {
this.stockLoading = false
})
},
openDetail(row) {
if (!row || !row.enterCoilNo) return
this.detailEnterCoilNo = row.enterCoilNo
this.detailVisible = true
this.loadDetailEnergy(row.enterCoilNo)
this.loadDetailStock(row.enterCoilNo)
},
loadDetailEnergy(enterCoilNo) {
const params = {
enterCoilNo,
pageNum: 1,
pageSize: 200
}
fetchCoilTotalEnergyDetail(params).then(res => {
this.detailEnergyRows = res.rows || []
})
},
loadDetailStock(enterCoilNo) {
const params = {
enterCoilNo,
pageNum: 1,
pageSize: 1
}
getStockpileCostList(params).then(res => {
const data = res.data || {}
const rows = data.rows || []
this.detailStockRow = rows.find(item => item.enterCoilNo === enterCoilNo) || null
})
},
exportSummary() {
const params = { ...this.queryParams }
exportCoilTotalMerged(params).then(res => {
const blob = new Blob([res], { type: 'application/vnd.ms-excel' })
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = 'coil_cost_summary.xlsx'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(url)
})
},
formatNumber(val, digits = 2) {
const num = Number(val)
if (isNaN(num)) return '0'.padEnd(2 + digits, '0')
return num.toFixed(digits)
},
formatTime(val) {
if (!val) return '-'
const date = new Date(val)
if (Number.isNaN(date.getTime())) return val
return date.toLocaleString('zh-CN')
},
formatStatus(status) {
const map = { 0: '在产', 1: '在产', 2: '完成' }
return map[status] || '-'
}
}
}
</script>
<style scoped lang="scss">
.coil-total-cost-page {
padding: 20px;
.search-card {
margin-bottom: 20px;
}
.summary-row {
margin-bottom: 20px;
.summary-card {
background: #fff;
border: 1px solid #ebeef5;
border-radius: 6px;
padding: 16px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.04);
.label {
font-size: 13px;
color: #909399;
}
.value {
margin-top: 8px;
font-size: 26px;
font-weight: 700;
color: #303133;
}
.desc {
margin-top: 6px;
font-size: 12px;
color: #909399;
}
&.highlight {
background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
border-color: #409eff;
}
}
}
.block-card {
margin-bottom: 20px;
.card-title {
font-weight: 600;
font-size: 16px;
}
.sub {
margin-left: 12px;
color: #909399;
font-size: 12px;
}
.cost {
color: #f56c6c;
font-weight: 600;
}
}
}
</style>