Files
klp-oa/klp-ui/src/views/ems/cost/cost.vue

439 lines
15 KiB
Vue
Raw Normal View History

2025-12-09 16:43:45 +08:00
<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>
2026-01-20 15:20:19 +08:00
<div class="value">¥ {{ formatNumber(materialSummary.auxCost, 2) }}</div>
<div class="desc">备件 ¥ {{ formatNumber(materialSummary.spareCost, 2) }}</div>
2025-12-09 16:43:45 +08:00
</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>
2026-01-20 15:20:19 +08:00
<div class="desc">能源 + 囤积 + 辅料 + 备件</div>
2025-12-09 16:43:45 +08:00
</div>
</el-col>
</el-row>
<el-card class="block-card">
<div slot="header" class="clearfix">
2026-01-27 16:54:46 +08:00
<span class="card-title">成本汇总</span>
2025-12-09 16:43:45 +08:00
</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>
2026-01-20 15:20:19 +08:00
<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>
2025-12-09 16:43:45 +08:00
<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
},
2026-01-20 15:20:19 +08:00
materialSummary: {
auxCost: 0,
spareCost: 0
},
2025-12-09 16:43:45 +08:00
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
2026-01-20 15:20:19 +08:00
const aux = Number(this.materialSummary.auxCost) || 0
const spare = Number(this.materialSummary.spareCost) || 0
return energy + stock + aux + spare
2025-12-09 16:43:45 +08:00
}
},
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 || []
2026-01-27 16:54:46 +08:00
console.log(res)
2025-12-09 16:43:45 +08:00
this.mergedTotal = res.total || 0
2026-01-20 15:20:19 +08:00
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)
}
2025-12-09 16:43:45 +08:00
})
},
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>