Files
xgy-oa/klp-ui/src/views/wms/cost/stockpile/index.vue
2025-12-02 17:58:16 +08:00

423 lines
12 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="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>
</div>
</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>