2025-12-01 11:56:31 +08:00
|
|
|
|
<template>
|
2025-12-02 17:58:16 +08:00
|
|
|
|
<div class="app-container cost-stockpile">
|
|
|
|
|
|
<!-- 查询条件 -->
|
|
|
|
|
|
<div class="query-section">
|
|
|
|
|
|
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" label-width="120px">
|
|
|
|
|
|
<el-form-item label="入场钢卷号">
|
|
|
|
|
|
<el-input
|
|
|
|
|
|
v-model="queryParams.enterCoilNo"
|
|
|
|
|
|
placeholder="请输入入场钢卷号"
|
|
|
|
|
|
clearable
|
|
|
|
|
|
style="width: 200px;"
|
|
|
|
|
|
@keyup.enter.native="handleQuery"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="当前钢卷号">
|
|
|
|
|
|
<el-input
|
|
|
|
|
|
v-model="queryParams.currentCoilNo"
|
|
|
|
|
|
placeholder="请输入当前钢卷号"
|
|
|
|
|
|
clearable
|
|
|
|
|
|
style="width: 200px;"
|
|
|
|
|
|
@keyup.enter.native="handleQuery"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</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-refresh-left" @click="refreshCosts" :loading="refreshing">
|
|
|
|
|
|
刷新成本
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-form>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 汇总信息 -->
|
|
|
|
|
|
<div class="summary-section">
|
|
|
|
|
|
<div class="summary-item">
|
|
|
|
|
|
<div class="summary-label">在库钢卷数</div>
|
|
|
|
|
|
<div class="summary-value warning">{{ total }}</div>
|
|
|
|
|
|
<div class="summary-desc">个</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="summary-item">
|
|
|
|
|
|
<div class="summary-label">总净重</div>
|
|
|
|
|
|
<div class="summary-value success">{{ formatWeight(totalNetWeight) }}</div>
|
|
|
|
|
|
<div class="summary-desc">吨</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="summary-item">
|
|
|
|
|
|
<div class="summary-label">总毛重</div>
|
|
|
|
|
|
<div class="summary-value success">{{ formatWeight(totalGrossWeight) }}</div>
|
|
|
|
|
|
<div class="summary-desc">吨</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="summary-item">
|
|
|
|
|
|
<div class="summary-label">总囤积成本</div>
|
|
|
|
|
|
<div class="summary-value primary">{{ formatMoney(totalCost) }}</div>
|
|
|
|
|
|
<div class="summary-desc">元</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="summary-item">
|
|
|
|
|
|
<div class="summary-label">平均在库天数</div>
|
|
|
|
|
|
<div class="summary-value info">{{ avgStorageDays }}</div>
|
|
|
|
|
|
<div class="summary-desc">天</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 钢卷列表 -->
|
|
|
|
|
|
<div class="table-section">
|
|
|
|
|
|
<div class="section-header">
|
|
|
|
|
|
<span>囤积成本明细</span>
|
|
|
|
|
|
<el-tooltip content="按入场钢卷号聚类展示囤积成本,点击查看详情可查看该入场卷下每个子钢卷的成本" placement="top">
|
|
|
|
|
|
<i class="el-icon-question"></i>
|
|
|
|
|
|
</el-tooltip>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<el-table
|
|
|
|
|
|
v-loading="loading"
|
|
|
|
|
|
:data="coilList"
|
|
|
|
|
|
stripe
|
|
|
|
|
|
style="width: 100%"
|
|
|
|
|
|
@sort-change="handleSortChange"
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-table-column prop="enterCoilNo" label="入场钢卷号" />
|
|
|
|
|
|
<el-table-column prop="coilCount" label="子钢卷数" align="right" />
|
|
|
|
|
|
<el-table-column prop="totalGrossWeight" label="总毛重(吨)" align="right">
|
|
|
|
|
|
<template slot-scope="scope">
|
|
|
|
|
|
{{ formatWeight(scope.row.totalGrossWeight) }}
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="totalNetWeight" label="总净重(吨)" align="right">
|
|
|
|
|
|
<template slot-scope="scope">
|
|
|
|
|
|
{{ formatWeight(scope.row.totalNetWeight) }}
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="avgStorageDays" label="平均在库天数" align="right" sortable="custom">
|
|
|
|
|
|
<template slot-scope="scope">
|
|
|
|
|
|
<span :class="getStorageDaysClass(scope.row.avgStorageDays)">
|
|
|
|
|
|
{{ scope.row.avgStorageDays || '-' }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="maxStorageDays" label="最大在库天数" align="right" sortable="custom">
|
|
|
|
|
|
<template slot-scope="scope">
|
|
|
|
|
|
<span :class="getStorageDaysClass(scope.row.maxStorageDays)">
|
|
|
|
|
|
{{ scope.row.maxStorageDays || '-' }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="totalCost" label="累计成本(元)" align="right" sortable="custom">
|
|
|
|
|
|
<template slot-scope="scope">
|
|
|
|
|
|
<span class="cost-total">{{ formatMoney(scope.row.totalCost) }}</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</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>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
</el-table>
|
|
|
|
|
|
|
|
|
|
|
|
<pagination
|
|
|
|
|
|
v-show="total > 0"
|
|
|
|
|
|
:total="total"
|
|
|
|
|
|
:page.sync="queryParams.pageNum"
|
|
|
|
|
|
:limit.sync="queryParams.pageSize"
|
|
|
|
|
|
@pagination="getList"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 入场钢卷号下子钢卷详情对话框 -->
|
|
|
|
|
|
<el-dialog
|
|
|
|
|
|
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 || '-' }}
|
|
|
|
|
|
</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-dialog>
|
2025-12-01 11:56:31 +08:00
|
|
|
|
</div>
|
2025-12-02 17:58:16 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
import { calculateCostByEnterCoilNo, getStockpileCostList } from '@/api/wms/cost'
|
|
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
|
name: 'CostStockpile',
|
|
|
|
|
|
data() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
loading: false,
|
|
|
|
|
|
refreshing: false,
|
|
|
|
|
|
total: 0,
|
|
|
|
|
|
totalNetWeight: 0,
|
|
|
|
|
|
totalGrossWeight: 0,
|
|
|
|
|
|
totalCost: 0,
|
|
|
|
|
|
avgStorageDays: 0,
|
|
|
|
|
|
coilList: [],
|
|
|
|
|
|
queryParams: {
|
|
|
|
|
|
pageNum: 1,
|
|
|
|
|
|
pageSize: 50,
|
|
|
|
|
|
enterCoilNo: null,
|
|
|
|
|
|
currentCoilNo: null
|
|
|
|
|
|
},
|
|
|
|
|
|
showDetailDialog: false,
|
|
|
|
|
|
detailEnterCoilNo: null,
|
|
|
|
|
|
detailList: []
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
created() {
|
|
|
|
|
|
this.getList()
|
|
|
|
|
|
},
|
|
|
|
|
|
methods: {
|
|
|
|
|
|
async getList() {
|
|
|
|
|
|
this.loading = true
|
|
|
|
|
|
try {
|
|
|
|
|
|
const params = {
|
|
|
|
|
|
enterCoilNo: this.queryParams.enterCoilNo,
|
|
|
|
|
|
currentCoilNo: this.queryParams.currentCoilNo,
|
|
|
|
|
|
pageNum: this.queryParams.pageNum,
|
|
|
|
|
|
pageSize: this.queryParams.pageSize
|
|
|
|
|
|
}
|
|
|
|
|
|
const res = await getStockpileCostList(params)
|
|
|
|
|
|
if (res.code === 200 && res.data) {
|
|
|
|
|
|
this.coilList = res.data.rows || []
|
|
|
|
|
|
this.total = res.data.total || 0
|
|
|
|
|
|
const summary = res.data.summary || {}
|
|
|
|
|
|
this.totalNetWeight = summary.totalNetWeight || 0
|
|
|
|
|
|
this.totalGrossWeight = summary.totalGrossWeight || 0
|
|
|
|
|
|
this.totalCost = summary.totalCost || 0
|
|
|
|
|
|
this.avgStorageDays = summary.avgStorageDays || 0
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
this.$message.error('加载钢卷列表失败')
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
this.loading = false
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
async refreshCosts() {
|
|
|
|
|
|
this.refreshing = true
|
|
|
|
|
|
try {
|
|
|
|
|
|
await this.getList()
|
|
|
|
|
|
this.$message.success('已重新计算并刷新列表')
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
this.$message.error('刷新成本失败')
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
this.refreshing = false
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
handleQuery() {
|
|
|
|
|
|
this.queryParams.pageNum = 1
|
|
|
|
|
|
this.getList()
|
|
|
|
|
|
},
|
|
|
|
|
|
resetQuery() {
|
|
|
|
|
|
this.queryParams = {
|
|
|
|
|
|
pageNum: 1,
|
|
|
|
|
|
pageSize: 50,
|
|
|
|
|
|
enterCoilNo: null,
|
|
|
|
|
|
currentCoilNo: null
|
|
|
|
|
|
}
|
|
|
|
|
|
this.getList()
|
|
|
|
|
|
},
|
|
|
|
|
|
handleSortChange({ prop, order }) {
|
|
|
|
|
|
if (order === 'ascending') {
|
|
|
|
|
|
this.coilList.sort((a, b) => {
|
|
|
|
|
|
const valA = a[prop] || 0
|
|
|
|
|
|
const valB = b[prop] || 0
|
|
|
|
|
|
return valA - valB
|
|
|
|
|
|
})
|
|
|
|
|
|
} else if (order === 'descending') {
|
|
|
|
|
|
this.coilList.sort((a, b) => {
|
|
|
|
|
|
const valA = a[prop] || 0
|
|
|
|
|
|
const valB = b[prop] || 0
|
|
|
|
|
|
return valB - valA
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
async viewCoilDetail(row) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await calculateCostByEnterCoilNo(row.enterCoilNo, null)
|
|
|
|
|
|
if (res.code === 200 && res.data && !res.data.error) {
|
|
|
|
|
|
this.detailEnterCoilNo = row.enterCoilNo
|
|
|
|
|
|
this.detailList = res.data.coilDetails || []
|
|
|
|
|
|
this.showDetailDialog = true
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.$message.error(res.data?.error || '加载详情失败')
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
this.$message.error('加载详情失败')
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
getStorageDaysClass(days) {
|
|
|
|
|
|
if (!days) return ''
|
|
|
|
|
|
if (days >= 30) return 'storage-days-high'
|
|
|
|
|
|
if (days >= 15) return 'storage-days-medium'
|
|
|
|
|
|
return 'storage-days-normal'
|
|
|
|
|
|
},
|
|
|
|
|
|
formatMoney(value) {
|
|
|
|
|
|
if (!value) return '0.00'
|
|
|
|
|
|
return Number(value).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
|
|
|
|
|
},
|
|
|
|
|
|
formatWeight(value) {
|
|
|
|
|
|
if (!value) return '0.000'
|
|
|
|
|
|
// 如果值大于1000,可能是kg单位,需要转换
|
|
|
|
|
|
const weight = Number(value) > 1000 ? Number(value) / 1000 : Number(value)
|
|
|
|
|
|
return weight.toFixed(3)
|
|
|
|
|
|
},
|
|
|
|
|
|
formatDateTime(value) {
|
|
|
|
|
|
if (!value) return '-'
|
|
|
|
|
|
const date = new Date(value)
|
|
|
|
|
|
return date.toLocaleString('zh-CN', {
|
|
|
|
|
|
year: 'numeric',
|
|
|
|
|
|
month: '2-digit',
|
|
|
|
|
|
day: '2-digit',
|
|
|
|
|
|
hour: '2-digit',
|
|
|
|
|
|
minute: '2-digit'
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
|
.cost-stockpile {
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
background: #FFFFFF;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.query-section {
|
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
background: #FAFAFA;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
border: 1px solid #EBEEF5;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.summary-section {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 20px;
|
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
background: #FAFAFA;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
border: 1px solid #EBEEF5;
|
|
|
|
|
|
|
|
|
|
|
|
.summary-item {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
|
|
|
|
|
|
.summary-label {
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
color: #606266;
|
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.summary-value {
|
|
|
|
|
|
font-size: 28px;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
margin-bottom: 5px;
|
|
|
|
|
|
|
|
|
|
|
|
&.primary {
|
|
|
|
|
|
color: #409EFF;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.warning {
|
|
|
|
|
|
color: #E6A23C;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.success {
|
|
|
|
|
|
color: #67C23A;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.info {
|
|
|
|
|
|
color: #909399;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.summary-desc {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: #909399;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.table-section {
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
background: #FAFAFA;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
border: 1px solid #EBEEF5;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.section-header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
margin-bottom: 15px;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
color: #303133;
|
|
|
|
|
|
|
|
|
|
|
|
.el-icon-question {
|
|
|
|
|
|
color: #909399;
|
|
|
|
|
|
cursor: help;
|
|
|
|
|
|
margin-left: 5px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cost-value {
|
|
|
|
|
|
color: #409EFF;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cost-total {
|
|
|
|
|
|
color: #E6A23C;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.storage-days-normal {
|
|
|
|
|
|
color: #67C23A;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.storage-days-medium {
|
|
|
|
|
|
color: #E6A23C;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.storage-days-high {
|
|
|
|
|
|
color: #F56C6C;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|