成本模块
This commit is contained in:
388
klp-ui/src/views/wms/cost/dashboard/index.vue
Normal file
388
klp-ui/src/views/wms/cost/dashboard/index.vue
Normal file
@@ -0,0 +1,388 @@
|
||||
<template>
|
||||
<div class="app-container cost-dashboard">
|
||||
<!-- 成本概览 -->
|
||||
<div class="overview-section">
|
||||
<div class="overview-item">
|
||||
<div class="overview-label">今日总成本</div>
|
||||
<div class="overview-value primary">{{ formatMoney(todayCost) }}</div>
|
||||
<div class="overview-desc">元</div>
|
||||
</div>
|
||||
<div class="overview-item">
|
||||
<div class="overview-label">在库钢卷数</div>
|
||||
<div class="overview-value warning">{{ totalCoils }}</div>
|
||||
<div class="overview-desc">个</div>
|
||||
</div>
|
||||
<div class="overview-item">
|
||||
<div class="overview-label">总净重</div>
|
||||
<div class="overview-value success">{{ formatWeight(totalNetWeight) }}</div>
|
||||
<div class="overview-desc">吨</div>
|
||||
</div>
|
||||
<div class="overview-item">
|
||||
<div class="overview-label">当前成本标准</div>
|
||||
<div class="overview-value info">{{ formatMoney(currentUnitCost) }}</div>
|
||||
<div class="overview-desc">元/吨/天</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 快速入口 -->
|
||||
<div class="action-section">
|
||||
<div class="section-header">
|
||||
<span>快速入口</span>
|
||||
<el-tooltip content="批量日计算改为后端定时任务,这里仅提供配置与明细入口" placement="top">
|
||||
<i class="el-icon-question"></i>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<el-button type="warning" icon="el-icon-setting" @click="goToStandardConfig">
|
||||
成本标准配置
|
||||
</el-button>
|
||||
<el-button type="info" icon="el-icon-view" @click="goToDetail">
|
||||
查看成本明细
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 成本趋势图表 -->
|
||||
<div class="chart-section">
|
||||
<div class="section-header">
|
||||
<span>成本趋势(近30天)</span>
|
||||
<el-date-picker
|
||||
v-model="trendDateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
size="small"
|
||||
style="width: 300px;"
|
||||
value-format="yyyy-MM-dd"
|
||||
@change="loadTrendData"
|
||||
/>
|
||||
</div>
|
||||
<div id="costTrendChart" style="height: 300px;"></div>
|
||||
</div>
|
||||
|
||||
<!-- 成本分布统计 -->
|
||||
<div class="stat-section">
|
||||
<div class="stat-item">
|
||||
<div class="section-header">
|
||||
<span>按库区统计</span>
|
||||
</div>
|
||||
<el-table :data="warehouseStats" stripe style="width: 100%">
|
||||
<el-table-column prop="warehouseName" label="库区名称" />
|
||||
<el-table-column prop="coilCount" label="钢卷数" align="right" />
|
||||
<el-table-column prop="totalCost" label="总成本" align="right">
|
||||
<template slot-scope="scope">
|
||||
{{ formatMoney(scope.row.totalCost) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="section-header">
|
||||
<span>按物品类型统计</span>
|
||||
</div>
|
||||
<el-table :data="itemTypeStats" stripe style="width: 100%">
|
||||
<el-table-column prop="itemType" label="物品类型">
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.itemType === 'raw_material' ? '原料' : '成品' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="coilCount" label="钢卷数" align="right" />
|
||||
<el-table-column prop="totalCost" label="总成本" align="right">
|
||||
<template slot-scope="scope">
|
||||
{{ formatMoney(scope.row.totalCost) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
queryCostSummary,
|
||||
queryCostTrend,
|
||||
getCurrentCostStandard,
|
||||
getCostOverview
|
||||
} from '@/api/wms/cost'
|
||||
import * as echarts from 'echarts'
|
||||
|
||||
export default {
|
||||
name: 'CostDashboard',
|
||||
data() {
|
||||
return {
|
||||
todayCost: 0,
|
||||
totalCoils: 0,
|
||||
totalNetWeight: 0,
|
||||
totalGrossWeight: 0,
|
||||
avgStorageDays: 0,
|
||||
currentUnitCost: 10000,
|
||||
trendDateRange: [],
|
||||
trendChart: null,
|
||||
warehouseStats: [],
|
||||
itemTypeStats: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initTrendDateRange()
|
||||
this.loadOverviewData()
|
||||
this.loadTrendData()
|
||||
this.loadStatsData()
|
||||
this.loadCurrentStandard()
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.trendChart) {
|
||||
this.trendChart.dispose()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initTrendDateRange() {
|
||||
const end = new Date()
|
||||
const start = new Date()
|
||||
start.setTime(start.getTime() - 30 * 24 * 60 * 60 * 1000)
|
||||
this.trendDateRange = [
|
||||
start.toISOString().split('T')[0],
|
||||
end.toISOString().split('T')[0]
|
||||
]
|
||||
},
|
||||
async loadOverviewData() {
|
||||
try {
|
||||
const res = await getCostOverview()
|
||||
if (res.code === 200 && res.data) {
|
||||
this.todayCost = res.data.totalCost || 0
|
||||
this.totalCoils = res.data.totalCoils || 0
|
||||
this.totalNetWeight = res.data.totalNetWeight || 0
|
||||
this.totalGrossWeight = res.data.totalGrossWeight || 0
|
||||
this.avgStorageDays = res.data.avgStorageDays || 0
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载概览数据失败:', error)
|
||||
}
|
||||
},
|
||||
async loadTrendData() {
|
||||
if (!this.trendDateRange || this.trendDateRange.length !== 2) return
|
||||
try {
|
||||
const startDate = this.trendDateRange[0] instanceof Date
|
||||
? this.trendDateRange[0].toISOString().split('T')[0]
|
||||
: this.trendDateRange[0]
|
||||
const endDate = this.trendDateRange[1] instanceof Date
|
||||
? this.trendDateRange[1].toISOString().split('T')[0]
|
||||
: this.trendDateRange[1]
|
||||
const res = await queryCostTrend(startDate, endDate)
|
||||
if (res.code === 200 && res.data) {
|
||||
this.renderTrendChart(res.data)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载趋势数据失败:', error)
|
||||
}
|
||||
},
|
||||
renderTrendChart(data) {
|
||||
this.$nextTick(() => {
|
||||
if (!this.trendChart) {
|
||||
this.trendChart = echarts.init(document.getElementById('costTrendChart'))
|
||||
}
|
||||
const dates = data.map(item => item.date)
|
||||
const costs = data.map(item => item.totalCost)
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
formatter: (params) => {
|
||||
return `${params[0].name}<br/>成本: ${this.formatMoney(params[0].value)}元`
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: dates,
|
||||
axisLine: { lineStyle: { color: '#C0C4CC' } }
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLine: { lineStyle: { color: '#C0C4CC' } },
|
||||
axisLabel: {
|
||||
formatter: (value) => {
|
||||
if (value >= 10000) {
|
||||
return (value / 10000).toFixed(1) + '万'
|
||||
}
|
||||
return value
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
data: costs,
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
itemStyle: { color: '#409EFF' },
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(64, 158, 255, 0.3)' },
|
||||
{ offset: 1, color: 'rgba(64, 158, 255, 0.1)' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
this.trendChart.setOption(option)
|
||||
})
|
||||
},
|
||||
async loadStatsData() {
|
||||
try {
|
||||
const today = new Date().toISOString().split('T')[0]
|
||||
const res = await queryCostSummary(today, today, 'warehouse', null)
|
||||
if (res.code === 200) {
|
||||
this.warehouseStats = res.data.details || []
|
||||
const itemTypeRes = await queryCostSummary(today, today, 'itemType', null)
|
||||
if (itemTypeRes.code === 200) {
|
||||
this.itemTypeStats = itemTypeRes.data.details || []
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载统计数据失败:', error)
|
||||
}
|
||||
},
|
||||
async loadCurrentStandard() {
|
||||
try {
|
||||
const res = await getCurrentCostStandard()
|
||||
if (res.code === 200 && res.data) {
|
||||
this.currentUnitCost = res.data.unitCost || 10000
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载成本标准失败:', error)
|
||||
}
|
||||
},
|
||||
goToStandardConfig() {
|
||||
this.$router.push('/wms/cost/standard')
|
||||
},
|
||||
goToDetail() {
|
||||
this.$router.push('/wms/cost/detail')
|
||||
},
|
||||
formatMoney(value) {
|
||||
if (!value) return '0.00'
|
||||
return Number(value).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
||||
},
|
||||
formatWeight(value) {
|
||||
if (!value) return '0.000'
|
||||
return Number(value).toFixed(3)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cost-dashboard {
|
||||
padding: 20px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.overview-section {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
padding: 20px;
|
||||
background: #fafafa;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ebeef5;
|
||||
|
||||
.overview-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
|
||||
.overview-label {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.overview-value {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
|
||||
&.primary {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
&.warning {
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
&.success {
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
&.info {
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
|
||||
.overview-desc {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-section {
|
||||
margin-bottom: 20px;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.chart-section {
|
||||
margin-bottom: 20px;
|
||||
padding: 20px;
|
||||
background: #fafafa;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.stat-section {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
background: #fafafa;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ebeef5;
|
||||
}
|
||||
</style>
|
||||
|
||||
568
klp-ui/src/views/wms/cost/detail/index.vue
Normal file
568
klp-ui/src/views/wms/cost/detail/index.vue
Normal file
@@ -0,0 +1,568 @@
|
||||
<template>
|
||||
<div class="cost-search-page">
|
||||
<div class="hero">
|
||||
<div class="hero-content">
|
||||
<h1>成本检索中心</h1>
|
||||
<p>基于 wms_material_coil 实时计算,输入入场钢卷号即可查看成本</p>
|
||||
<div class="search-bar">
|
||||
<el-input
|
||||
v-model.trim="searchForm.enterCoilNo"
|
||||
placeholder="请输入入场钢卷号(支持前缀)"
|
||||
clearable
|
||||
class="search-input"
|
||||
@input="handleInputChange"
|
||||
@keyup.enter.native="handleSearchClick"
|
||||
/>
|
||||
<el-date-picker
|
||||
v-model="searchForm.calcDate"
|
||||
type="date"
|
||||
placeholder="可选:指定计算日期"
|
||||
value-format="yyyy-MM-dd"
|
||||
class="search-date"
|
||||
:picker-options="datePickerOptions"
|
||||
@change="handleCalcDateChange"
|
||||
/>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-search"
|
||||
:loading="loading"
|
||||
@click="handleSearchClick"
|
||||
>
|
||||
检索成本
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="hero-meta">
|
||||
<span>未发货:计算到选择日期(默认今日)</span>
|
||||
<span>已发货:计算到发货前一天</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="result-wrapper">
|
||||
<div class="result-actions">
|
||||
<el-button type="text" icon="el-icon-setting" @click="goToStandardConfig">成本标准配置</el-button>
|
||||
<el-divider direction="vertical"></el-divider>
|
||||
<el-button type="text" icon="el-icon-tickets" @click="goToDetail">查看历史明细</el-button>
|
||||
</div>
|
||||
|
||||
<el-empty
|
||||
v-if="!searchExecuted && !loading"
|
||||
description="请输入入场钢卷号后开始检索"
|
||||
/>
|
||||
|
||||
<div v-else>
|
||||
<el-card v-loading="loading" shadow="never" class="summary-card">
|
||||
<div class="summary-header">
|
||||
<div>
|
||||
<div class="summary-label">入场钢卷号</div>
|
||||
<div class="summary-value">{{ summary.enterCoilNo || '-' }}</div>
|
||||
</div>
|
||||
<el-tag type="info" effect="plain">
|
||||
统计日期:{{ summary.calcDate || '-' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="summary-grid">
|
||||
<div class="grid-item">
|
||||
<div class="grid-label">总子钢卷数</div>
|
||||
<div class="grid-value primary">{{ summary.totalCoils }}</div>
|
||||
<div class="grid-desc">
|
||||
<span>在库 {{ summary.unshippedCount }}</span>
|
||||
<span>已发货 {{ summary.shippedCount }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<div class="grid-label">总毛重</div>
|
||||
<div class="grid-value">{{ formatWeight(summary.totalGrossWeight) }} 吨</div>
|
||||
<div class="grid-desc">毛重优先,缺失时用净重</div>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<div class="grid-label">总净重</div>
|
||||
<div class="grid-value">{{ formatWeight(summary.totalNetWeight) }} 吨</div>
|
||||
<div class="grid-desc">实时换算为吨</div>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<div class="grid-label">累计成本</div>
|
||||
<div class="grid-value accent">{{ formatMoney(summary.totalCost) }} 元</div>
|
||||
<div class="grid-desc">所有子钢卷累计值</div>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<div class="grid-label">平均在库天数</div>
|
||||
<div class="grid-value">{{ formatNumber(summary.avgStorageDays, 2) }} 天</div>
|
||||
<div class="grid-desc">含在库与已发货</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<div v-if="cards.length" class="card-section">
|
||||
<div class="card-grid">
|
||||
<div class="coil-card" v-for="card in cards" :key="card.coilId">
|
||||
<div class="card-header">
|
||||
<div>
|
||||
<div class="coil-no">{{ card.currentCoilNo }}</div>
|
||||
<div class="coil-meta">入库:{{ formatDate(card.startDate) }}</div>
|
||||
</div>
|
||||
<el-tag
|
||||
size="mini"
|
||||
:type="card.isShipped ? 'info' : 'success'"
|
||||
effect="plain"
|
||||
>
|
||||
{{ card.isShipped ? '已发货' : '在库' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">毛重(吨)</span>
|
||||
<span class="metric-value">{{ formatWeight(card.grossWeightTon) }}</span>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">净重(吨)</span>
|
||||
<span class="metric-value">{{ formatWeight(card.netWeightTon) }}</span>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">计费基准</span>
|
||||
<span class="metric-value">
|
||||
<el-tag size="mini" :type="card.weightBasis === 'gross' ? 'success' : 'info'" effect="plain">
|
||||
{{ card.weightBasis === 'gross' ? '毛重' : '净重' }}
|
||||
</el-tag>
|
||||
</span>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">在库天数</span>
|
||||
<span :class="['metric-value', getStorageDaysClass(card.storageDays)]">
|
||||
{{ card.storageDays || '-' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">单位成本</span>
|
||||
<span class="metric-value">{{ formatMoney(card.unitCost) }} 元/吨/天</span>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">累计成本</span>
|
||||
<span class="metric-value accent">{{ formatMoney(card.totalCost) }} 元</span>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">计费截至</span>
|
||||
<span class="metric-value">{{ formatDate(card.endDate) }}</span>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">所在库区</span>
|
||||
<span class="metric-value">{{ card.warehouseName || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pagination" v-if="pagination.total > pagination.pageSize">
|
||||
<el-pagination
|
||||
background
|
||||
layout="prev, pager, next"
|
||||
:page-size="pagination.pageSize"
|
||||
:total="pagination.total"
|
||||
:current-page.sync="pagination.pageNum"
|
||||
@current-change="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-empty
|
||||
v-else-if="!loading"
|
||||
description="暂无匹配钢卷"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { searchMaterialCost } from '@/api/wms/cost'
|
||||
|
||||
export default {
|
||||
name: 'CostSearchDashboard',
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
searchExecuted: false,
|
||||
searchForm: {
|
||||
enterCoilNo: '',
|
||||
calcDate: null
|
||||
},
|
||||
pagination: {
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
total: 0
|
||||
},
|
||||
summary: {
|
||||
enterCoilNo: '',
|
||||
calcDate: '',
|
||||
totalCoils: 0,
|
||||
shippedCount: 0,
|
||||
unshippedCount: 0,
|
||||
totalGrossWeight: 0,
|
||||
totalNetWeight: 0,
|
||||
totalCost: 0,
|
||||
avgStorageDays: 0
|
||||
},
|
||||
cards: [],
|
||||
debounceTimer: null,
|
||||
datePickerOptions: {
|
||||
disabledDate(time) {
|
||||
return time.getTime() > Date.now()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.debounceTimer) {
|
||||
clearTimeout(this.debounceTimer)
|
||||
this.debounceTimer = null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleInputChange() {
|
||||
this.pagination.pageNum = 1
|
||||
if (this.debounceTimer) {
|
||||
clearTimeout(this.debounceTimer)
|
||||
}
|
||||
this.debounceTimer = setTimeout(() => {
|
||||
this.fetchResults()
|
||||
}, 400)
|
||||
},
|
||||
handleCalcDateChange() {
|
||||
if (!this.searchForm.enterCoilNo) return
|
||||
this.pagination.pageNum = 1
|
||||
this.fetchResults()
|
||||
},
|
||||
handleSearchClick() {
|
||||
this.pagination.pageNum = 1
|
||||
this.fetchResults()
|
||||
},
|
||||
async fetchResults() {
|
||||
if (!this.searchForm.enterCoilNo) {
|
||||
this.resetResults(false)
|
||||
return
|
||||
}
|
||||
|
||||
this.loading = true
|
||||
const params = {
|
||||
enterCoilNo: this.searchForm.enterCoilNo.trim(),
|
||||
calcDate: this.searchForm.calcDate,
|
||||
pageNum: this.pagination.pageNum,
|
||||
pageSize: this.pagination.pageSize
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await searchMaterialCost(params)
|
||||
this.searchExecuted = true
|
||||
if (res.code === 200 && res.data) {
|
||||
const data = res.data
|
||||
const summaryData = data.summary || {}
|
||||
this.summary = {
|
||||
enterCoilNo: summaryData.enterCoilNo || params.enterCoilNo,
|
||||
calcDate: summaryData.calcDate || params.calcDate || this.formatDate(new Date()),
|
||||
totalCoils: summaryData.totalCoils || 0,
|
||||
shippedCount: summaryData.shippedCount || 0,
|
||||
unshippedCount: summaryData.unshippedCount || 0,
|
||||
totalGrossWeight: summaryData.totalGrossWeight || 0,
|
||||
totalNetWeight: summaryData.totalNetWeight || 0,
|
||||
totalCost: summaryData.totalCost || 0,
|
||||
avgStorageDays: summaryData.avgStorageDays || 0
|
||||
}
|
||||
this.cards = (data.records || []).map(item => ({
|
||||
...item,
|
||||
isShipped: item.isShipped === 1 || item.isShipped === true
|
||||
}))
|
||||
this.pagination.total = data.total || 0
|
||||
this.pagination.pageNum = data.pageNum || params.pageNum
|
||||
} else {
|
||||
this.resetResults(true)
|
||||
this.$message.warning(res.msg || '未查询到成本数据')
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error('检索失败,请稍后再试')
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
handlePageChange(page) {
|
||||
this.pagination.pageNum = page
|
||||
this.fetchResults()
|
||||
},
|
||||
resetResults(keepExecuted) {
|
||||
if (!keepExecuted) {
|
||||
this.searchExecuted = false
|
||||
}
|
||||
this.cards = []
|
||||
this.pagination.total = 0
|
||||
this.summary = {
|
||||
enterCoilNo: this.searchForm.enterCoilNo || '',
|
||||
calcDate: this.searchForm.calcDate || '',
|
||||
totalCoils: 0,
|
||||
shippedCount: 0,
|
||||
unshippedCount: 0,
|
||||
totalGrossWeight: 0,
|
||||
totalNetWeight: 0,
|
||||
totalCost: 0,
|
||||
avgStorageDays: 0
|
||||
}
|
||||
},
|
||||
goToStandardConfig() {
|
||||
this.$router.push('/wms/cost/standard')
|
||||
},
|
||||
goToDetail() {
|
||||
this.$router.push('/wms/cost/detail')
|
||||
},
|
||||
formatMoney(value) {
|
||||
if (!value) return '0.00'
|
||||
return Number(value).toLocaleString('zh-CN', {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
})
|
||||
},
|
||||
formatWeight(value) {
|
||||
if (!value) return '0.000'
|
||||
return Number(value).toFixed(3)
|
||||
},
|
||||
formatNumber(value, fraction = 2) {
|
||||
if (!value) return Number(0).toFixed(fraction)
|
||||
return Number(value).toFixed(fraction)
|
||||
},
|
||||
formatDate(value) {
|
||||
if (!value) return '-'
|
||||
if (value instanceof Date) {
|
||||
const yyyy = value.getFullYear()
|
||||
const mm = `${value.getMonth() + 1}`.padStart(2, '0')
|
||||
const dd = `${value.getDate()}`.padStart(2, '0')
|
||||
return `${yyyy}-${mm}-${dd}`
|
||||
}
|
||||
return value
|
||||
},
|
||||
getStorageDaysClass(days) {
|
||||
if (!days) return ''
|
||||
if (days >= 60) return 'storage-days-high'
|
||||
if (days >= 30) return 'storage-days-medium'
|
||||
return 'storage-days-normal'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cost-search-page {
|
||||
min-height: 100%;
|
||||
background: #eef1f4;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
.hero {
|
||||
background: linear-gradient(120deg, #f7f8fa 0%, #e2e5ec 100%);
|
||||
border-bottom: 1px solid #d5d9e0;
|
||||
padding: 48px 20px 36px;
|
||||
color: #2f3135;
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
|
||||
h1 {
|
||||
font-size: 34px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 24px;
|
||||
color: #5f646e;
|
||||
}
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.search-input {
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
.search-date {
|
||||
width: 220px;
|
||||
}
|
||||
}
|
||||
|
||||
.hero-meta {
|
||||
margin-top: 14px;
|
||||
font-size: 13px;
|
||||
color: #757a85;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.result-wrapper {
|
||||
max-width: 1100px;
|
||||
margin: -25px auto 0;
|
||||
padding: 0 20px 40px;
|
||||
}
|
||||
|
||||
.result-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 14px;
|
||||
|
||||
::v-deep .el-button--text {
|
||||
color: #5f6470;
|
||||
}
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
border: 1px solid #d7dbe2;
|
||||
border-radius: 10px;
|
||||
background: #fefefe;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.summary-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.summary-label {
|
||||
font-size: 13px;
|
||||
color: #80848f;
|
||||
}
|
||||
|
||||
.summary-value {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #2f3135;
|
||||
}
|
||||
}
|
||||
|
||||
.summary-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 14px;
|
||||
|
||||
.grid-item {
|
||||
padding: 14px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e2e5ea;
|
||||
background: linear-gradient(180deg, #f9fafc 0%, #f0f2f5 100%);
|
||||
|
||||
.grid-label {
|
||||
font-size: 13px;
|
||||
color: #7b808a;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.grid-value {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
color: #2f3135;
|
||||
|
||||
&.primary {
|
||||
color: #435d7a;
|
||||
}
|
||||
|
||||
&.accent {
|
||||
color: #aa7728;
|
||||
}
|
||||
}
|
||||
|
||||
.grid-desc {
|
||||
margin-top: 6px;
|
||||
font-size: 12px;
|
||||
color: #9a9fac;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-section {
|
||||
border: 1px solid #d7dbe2;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
background: #fdfdfd;
|
||||
}
|
||||
|
||||
.card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.coil-card {
|
||||
border: 1px solid #d0d4dc;
|
||||
border-radius: 10px;
|
||||
padding: 16px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 6px 18px rgba(32, 41, 58, 0.08);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.coil-no {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #2e3240;
|
||||
}
|
||||
|
||||
.coil-meta {
|
||||
font-size: 12px;
|
||||
color: #8a8f99;
|
||||
}
|
||||
}
|
||||
|
||||
.card-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.metric-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 13px;
|
||||
color: #5a5f6b;
|
||||
|
||||
.metric-value {
|
||||
font-weight: 600;
|
||||
color: #30343c;
|
||||
}
|
||||
|
||||
.metric-value.accent {
|
||||
color: #b4792b;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 20px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.storage-days-normal {
|
||||
color: #4c805c;
|
||||
}
|
||||
|
||||
.storage-days-medium {
|
||||
color: #b78b38;
|
||||
}
|
||||
|
||||
.storage-days-high {
|
||||
color: #b3473f;
|
||||
}
|
||||
</style>
|
||||
|
||||
363
klp-ui/src/views/wms/cost/standard/index.vue
Normal file
363
klp-ui/src/views/wms/cost/standard/index.vue
Normal file
@@ -0,0 +1,363 @@
|
||||
<template>
|
||||
<div class="app-container cost-standard">
|
||||
<!-- 当前有效标准提示 -->
|
||||
<div class="alert-section" v-if="currentStandard">
|
||||
<div class="alert-content">
|
||||
<i class="el-icon-info"></i>
|
||||
<span>当前有效标准:<strong>{{ formatMoney(currentStandard.unitCost) }}</strong> 元/吨/天(生效日期:{{ currentStandard.effectiveDate }})</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 查询条件 -->
|
||||
<div class="query-section">
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" label-width="100px">
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">新增成本标准</el-button>
|
||||
<el-button type="success" icon="el-icon-refresh" @click="loadCurrentStandard">刷新当前标准</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</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="standardList"
|
||||
stripe
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column prop="unitCost" label="单位成本" align="right">
|
||||
<template slot-scope="scope">
|
||||
<span class="cost-value">{{ formatMoney(scope.row.unitCost) }}</span> 元/吨/天
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="effectiveDate" label="生效日期" />
|
||||
<el-table-column prop="expireDate" label="失效日期">
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.expireDate || '当前有效' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.status === 1 ? 'success' : 'info'">
|
||||
{{ scope.row.status === 1 ? '有效' : '失效' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" />
|
||||
<el-table-column prop="createBy" label="创建人" />
|
||||
<el-table-column prop="createTime" label="创建时间" />
|
||||
<el-table-column label="操作" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="small" @click="handleUpdate(scope.row)">修改</el-button>
|
||||
<el-button
|
||||
type="text"
|
||||
size="small"
|
||||
@click="handleDelete(scope.row)"
|
||||
:disabled="scope.row.status === 1"
|
||||
>
|
||||
删除
|
||||
</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="dialogTitle"
|
||||
:visible.sync="dialogVisible"
|
||||
width="600px"
|
||||
>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="120px">
|
||||
<el-form-item label="单位成本" prop="unitCost">
|
||||
<el-input-number
|
||||
v-model="form.unitCost"
|
||||
:precision="2"
|
||||
:step="1000"
|
||||
:min="0"
|
||||
style="width: 100%;"
|
||||
/>
|
||||
<div style="color: #909399; font-size: 12px; margin-top: 5px;">
|
||||
单位:元/吨/天
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="生效日期" prop="effectiveDate">
|
||||
<el-date-picker
|
||||
v-model="form.effectiveDate"
|
||||
type="date"
|
||||
placeholder="选择生效日期"
|
||||
style="width: 100%;"
|
||||
value-format="yyyy-MM-dd"
|
||||
:picker-options="{
|
||||
disabledDate(time) {
|
||||
return time.getTime() < Date.now() - 8.64e7
|
||||
}
|
||||
}"
|
||||
/>
|
||||
<div style="color: #909399; font-size: 12px; margin-top: 5px;">
|
||||
生效日期不能早于今天
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input
|
||||
v-model="form.remark"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入备注说明"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm" :loading="submitting">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
listCostStandardConfig,
|
||||
getCostStandardConfig,
|
||||
addCostStandardConfig,
|
||||
updateCostStandardConfig,
|
||||
delCostStandardConfig,
|
||||
getCurrentCostStandard
|
||||
} from '@/api/wms/cost'
|
||||
|
||||
export default {
|
||||
name: 'CostStandard',
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
submitting: false,
|
||||
total: 0,
|
||||
standardList: [],
|
||||
currentStandard: null,
|
||||
dialogVisible: false,
|
||||
dialogTitle: '新增成本标准',
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 20
|
||||
},
|
||||
form: {
|
||||
configId: null,
|
||||
unitCost: 10000,
|
||||
effectiveDate: null,
|
||||
remark: null
|
||||
},
|
||||
rules: {
|
||||
unitCost: [
|
||||
{ required: true, message: '请输入单位成本', trigger: 'blur' },
|
||||
{ type: 'number', min: 0, message: '单位成本必须大于0', trigger: 'blur' }
|
||||
],
|
||||
effectiveDate: [
|
||||
{ required: true, message: '请选择生效日期', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
this.loadCurrentStandard()
|
||||
},
|
||||
methods: {
|
||||
async getList() {
|
||||
this.loading = true
|
||||
try {
|
||||
const res = await listCostStandardConfig(this.queryParams)
|
||||
if (res.code === 200) {
|
||||
this.standardList = res.rows || []
|
||||
this.total = res.total || 0
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error('加载成本标准列表失败')
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
async loadCurrentStandard() {
|
||||
try {
|
||||
const res = await getCurrentCostStandard()
|
||||
if (res.code === 200) {
|
||||
this.currentStandard = res.data
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载当前标准失败:', error)
|
||||
}
|
||||
},
|
||||
handleAdd() {
|
||||
this.dialogTitle = '新增成本标准'
|
||||
this.form = {
|
||||
configId: null,
|
||||
unitCost: 10000,
|
||||
effectiveDate: null,
|
||||
remark: null
|
||||
}
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.form && this.$refs.form.clearValidate()
|
||||
})
|
||||
},
|
||||
async handleUpdate(row) {
|
||||
this.dialogTitle = '修改成本标准'
|
||||
try {
|
||||
const res = await getCostStandardConfig(row.configId)
|
||||
if (res.code === 200) {
|
||||
this.form = {
|
||||
configId: res.data.configId,
|
||||
unitCost: res.data.unitCost,
|
||||
effectiveDate: res.data.effectiveDate,
|
||||
remark: res.data.remark
|
||||
}
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.form && this.$refs.form.clearValidate()
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error('加载成本标准详情失败')
|
||||
}
|
||||
},
|
||||
async handleDelete(row) {
|
||||
this.$confirm('确定要删除该成本标准吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
try {
|
||||
const res = await delCostStandardConfig([row.configId])
|
||||
if (res.code === 200) {
|
||||
this.$message.success('删除成功')
|
||||
this.getList()
|
||||
this.loadCurrentStandard()
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error('删除失败')
|
||||
}
|
||||
})
|
||||
},
|
||||
submitForm() {
|
||||
this.$refs.form.validate(async (valid) => {
|
||||
if (valid) {
|
||||
this.submitting = true
|
||||
try {
|
||||
if (this.form.configId) {
|
||||
// 修改
|
||||
const res = await updateCostStandardConfig(this.form)
|
||||
if (res.code === 200) {
|
||||
this.$message.success('修改成功')
|
||||
this.dialogVisible = false
|
||||
this.getList()
|
||||
this.loadCurrentStandard()
|
||||
}
|
||||
} else {
|
||||
// 新增
|
||||
const res = await addCostStandardConfig(this.form)
|
||||
if (res.code === 200) {
|
||||
this.$message.success('新增成功')
|
||||
this.dialogVisible = false
|
||||
this.getList()
|
||||
this.loadCurrentStandard()
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error(this.form.configId ? '修改失败' : '新增失败')
|
||||
} finally {
|
||||
this.submitting = false
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
formatMoney(value) {
|
||||
if (!value) return '0.00'
|
||||
return Number(value).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cost-standard {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.alert-section {
|
||||
margin-bottom: 20px;
|
||||
padding: 15px 20px;
|
||||
background: #ECF5FF;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #B3D8FF;
|
||||
|
||||
.alert-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #409EFF;
|
||||
font-size: 14px;
|
||||
|
||||
i {
|
||||
margin-right: 8px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-size: 16px;
|
||||
margin: 0 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.query-section {
|
||||
margin-bottom: 20px;
|
||||
padding: 20px;
|
||||
background: #FAFAFA;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #EBEEF5;
|
||||
}
|
||||
|
||||
.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: bold;
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,422 @@
|
||||
<template>
|
||||
<div>
|
||||
囤积成本页面
|
||||
<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>
|
||||
</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>
|
||||
|
||||
Reference in New Issue
Block a user