l3能源成本分摊(部分完成留存)

This commit is contained in:
2025-12-07 17:23:47 +08:00
parent b6328a94da
commit 59951b77c3
100 changed files with 14350 additions and 847 deletions

View File

@@ -0,0 +1,630 @@
<template>
<div class="coil-cost-list">
<!-- 统计信息 -->
<div class="statistics-section">
<div class="stat-item">
<div class="stat-label">钢卷总数</div>
<div class="stat-value">{{ statistics.totalCoils }}</div>
</div>
<div class="stat-item">
<div class="stat-label">总消耗量</div>
<div class="stat-value">{{ formatNumber(statistics.totalConsumption, 2) }}</div>
</div>
<div class="stat-item">
<div class="stat-label">总成本</div>
<div class="stat-value cost-highlight">¥ {{ formatNumber(statistics.totalCost, 2) }}</div>
</div>
<div class="stat-item">
<div class="stat-label">平均单位成本</div>
<div class="stat-value">¥ {{ formatNumber(statistics.avgUnitCost, 2) }}</div>
</div>
</div>
<!-- 检索条件 -->
<div class="search-section">
<el-row :gutter="20">
<el-col :xs="24" :sm="12" :md="6">
<el-input
v-model="queryParams.enterCoilNo"
placeholder="入场钢卷号"
clearable
@keyup.enter="handleSearch"
></el-input>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<el-input
v-model="queryParams.currentCoilNo"
placeholder="当前钢卷号"
clearable
@keyup.enter="handleSearch"
></el-input>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<el-select
v-model="queryParams.warehouseId"
placeholder="选择逻辑库区"
clearable
>
<el-option
v-for="warehouse in warehouseList"
:key="warehouse.warehouseId"
:label="warehouse.warehouseName"
:value="warehouse.warehouseId"
></el-option>
</el-select>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<el-button type="primary" @click="handleSearch" icon="el-icon-search">查询</el-button>
<el-button @click="handleReset" icon="el-icon-refresh">重置</el-button>
</el-col>
</el-row>
</div>
<!-- 钢卷成本列表 -->
<el-table :data="coilCostList" stripe border v-loading="loading" :default-sort="{ prop: 'costAmount', order: 'descending' }">
<el-table-column prop="currentCoilNo" label="当前卷号" min-width="120"></el-table-column>
<el-table-column prop="enterCoilNo" label="入场卷号" min-width="120"></el-table-column>
<el-table-column prop="warehouseName" label="逻辑库区" min-width="120"></el-table-column>
<el-table-column label="生产时间" min-width="200">
<template slot-scope="scope">
{{ formatTime(null, null, scope.row.scanTime) }} - {{ formatTime(null, null, scope.row.completeTime) }}
</template>
</el-table-column>
<el-table-column prop="productionDuration" label="生产时长(分钟)" min-width="120" align="right">
<template slot-scope="scope">
{{ formatNumber(scope.row.productionDuration / 60, 2) }}
</template>
</el-table-column>
<el-table-column prop="consumptionQty" label="综合消耗(单位)" min-width="120" align="right">
<template slot-scope="scope">
{{ formatNumber(scope.row.consumptionQty, 2) }}
</template>
</el-table-column>
<el-table-column prop="costAmount" label="综合成本(¥)" min-width="120" align="right" sortable>
<template slot-scope="scope">
<span class="cost-value">{{ formatNumber(scope.row.costAmount, 2) }}</span>
</template>
</el-table-column>
<el-table-column label="单位成本(¥/h)" min-width="120" align="right">
<template slot-scope="scope">
{{ formatNumber(scope.row.costAmount / (scope.row.productionDuration / 60), 2) }}
</template>
</el-table-column>
<el-table-column label="操作" width="100" fixed="right">
<template slot-scope="scope">
<el-button type="primary" size="small" @click="showDetail(scope.row)">详情</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
class="pagination"
@size-change="handlePageSizeChange"
@current-change="handlePageChange"
:current-page="queryParams.pageNum"
:page-sizes="[50, 100, 200, 500]"
:page-size="queryParams.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
></el-pagination>
<!-- 详情对话框 -->
<el-dialog title="钢卷成本详情" :visible.sync="detailDialogVisible" width="90%" @close="closeDetail">
<div v-if="selectedCoil" class="detail-content">
<!-- 基本信息 -->
<div class="section">
<h4 class="section-title">基本信息</h4>
<el-row :gutter="20">
<el-col :xs="24" :sm="12" :md="6">
<div class="info-item">
<span class="label">当前卷号:</span>
<span class="value">{{ selectedCoil.currentCoilNo }}</span>
</div>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<div class="info-item">
<span class="label">入场卷号:</span>
<span class="value">{{ selectedCoil.enterCoilNo }}</span>
</div>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<div class="info-item">
<span class="label">逻辑库区:</span>
<span class="value">{{ selectedCoil.warehouseName }}</span>
</div>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<div class="info-item">
<span class="label">生产时长:</span>
<span class="value">{{ formatNumber(selectedCoil.productionDuration / 60, 2) }} 小时</span>
</div>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<div class="info-item">
<span class="label">开始时间:</span>
<span class="value">{{ formatTime(null, null, selectedCoil.scanTime) }}</span>
</div>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<div class="info-item">
<span class="label">结束时间:</span>
<span class="value">{{ formatTime(null, null, selectedCoil.completeTime) }}</span>
</div>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<div class="info-item">
<span class="label">综合消耗:</span>
<span class="value">{{ formatNumber(selectedCoil.consumptionQty, 2) }}</span>
</div>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<div class="info-item">
<span class="label">综合成本:</span>
<span class="value highlight">¥ {{ formatNumber(selectedCoil.costAmount, 2) }}</span>
</div>
</el-col>
</el-row>
</div>
<!-- 成本汇总 -->
<div class="section">
<h4 class="section-title">成本汇总</h4>
<el-row :gutter="20">
<el-col :xs="24" :sm="12" :md="8">
<div class="info-item">
<span class="label">综合消耗量:</span>
<span class="value">{{ formatNumber(selectedCoil.consumptionQty, 2) }}</span>
</div>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<div class="info-item">
<span class="label">能源费率:</span>
<span class="value">{{ selectedCoil.energyRate ? ('¥ ' + formatNumber(selectedCoil.energyRate, 4)) : '-' }}</span>
</div>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<div class="info-item">
<span class="label">综合成本:</span>
<span class="value highlight">¥ {{ formatNumber(selectedCoil.costAmount, 2) }}</span>
</div>
</el-col>
</el-row>
<div style="margin-top: 12px; padding: 12px; background-color: #f5f7fa; border-radius: 4px;">
<p style="margin: 0; font-size: 12px; color: #606266;">
综合成本为该钢卷在生产时间内所有设备产生的能源成本总和包括电气等所有能源类型
</p>
</div>
</div>
<!-- 相关设备 -->
<div class="section" v-if="selectedCoil.relatedMeters && selectedCoil.relatedMeters.length > 0">
<h4 class="section-title">相关设备</h4>
<el-table :data="selectedCoil.relatedMeters" stripe border size="small">
<el-table-column prop="meterCode" label="设备编号" min-width="120"></el-table-column>
<el-table-column prop="energyTypeName" label="能源类型" min-width="100"></el-table-column>
<el-table-column prop="model" label="型号" min-width="100"></el-table-column>
<el-table-column prop="manufacturer" label="制造商" min-width="100"></el-table-column>
<el-table-column prop="status" label="状态" min-width="80">
<template slot-scope="scope">
<el-tag :type="scope.row.status === 0 ? 'success' : 'info'">
{{ scope.row.status === 0 ? '在用' : '停用' }}
</el-tag>
</template>
</el-table-column>
</el-table>
</div>
</div>
</el-dialog>
</div>
</template>
<script>
import { listEnergyCoilDaily, listPendingActionCoilCost } from '@/api/ems/energyAllocation'
export default {
name: 'CoilCost',
data() {
return {
loading: false,
detailDialogVisible: false,
selectedCoil: null,
coilCostList: [],
total: 0,
warehouseList: [],
statistics: {
totalCoils: 0,
totalConsumption: 0,
totalCost: 0,
avgUnitCost: 0
},
queryParams: {
pageNum: 1,
pageSize: 50,
enterCoilNo: '',
currentCoilNo: '',
warehouseId: null
},
allCoilCostList: [], // 保存所有数据用于统计
dataSource: 'pending' // 'pending' 或 'daily'
};
},
mounted() {
this.loadWarehouseList();
this.loadCoilCostList();
},
methods: {
loadWarehouseList() {
// TODO: 从后端加载库区列表,这里暂时使用空数组
// 实际应该调用 API 获取库区列表
this.warehouseList = [];
},
loadCoilCostList() {
this.loading = true;
// 优先加载待操作钢卷的成本
this.fetchPendingActionCoilCost();
},
fetchPendingActionCoilCost() {
// 先尝试加载待操作钢卷的成本
listPendingActionCoilCost({ pageNum: 1, pageSize: 10000 }).then(response => {
this.allCoilCostList = response.rows || [];
this.dataSource = 'pending';
this.applyFiltersAndPagination();
}).catch(() => {
// 如果待操作接口失败,则加载日常分摊数据
this.fetchDailyCoilCostData();
}).finally(() => {
this.loading = false;
});
},
fetchDailyCoilCostData() {
this.loading = true;
listEnergyCoilDaily({ pageNum: 1, pageSize: 10000 }).then(response => {
this.allCoilCostList = response.rows || [];
this.dataSource = 'daily';
this.applyFiltersAndPagination();
}).catch(() => {
this.$message.error('加载钢卷能源成本失败');
this.allCoilCostList = [];
}).finally(() => {
this.loading = false;
});
},
applyFiltersAndPagination() {
// 应用过滤条件
let filteredList = this.allCoilCostList;
if (this.queryParams.enterCoilNo) {
filteredList = filteredList.filter(item =>
item.enterCoilNo && item.enterCoilNo.includes(this.queryParams.enterCoilNo)
);
}
if (this.queryParams.currentCoilNo) {
filteredList = filteredList.filter(item =>
item.currentCoilNo && item.currentCoilNo.includes(this.queryParams.currentCoilNo)
);
}
if (this.queryParams.warehouseId) {
filteredList = filteredList.filter(item =>
item.warehouseId === this.queryParams.warehouseId
);
}
// 计算统计数据
this.calculateStatistics(filteredList);
// 分页处理
this.total = filteredList.length;
const start = (this.queryParams.pageNum - 1) * this.queryParams.pageSize;
const end = start + this.queryParams.pageSize;
this.coilCostList = filteredList.slice(start, end);
},
calculateStatistics(list) {
this.statistics.totalCoils = list.length;
this.statistics.totalConsumption = list.reduce((sum, item) => sum + (item.consumptionQty || 0), 0);
this.statistics.totalCost = list.reduce((sum, item) => sum + (item.costAmount || 0), 0);
// 计算平均单位成本(总成本 / 总时长小时数)
const totalHours = list.reduce((sum, item) => sum + (item.productionDuration || 0) / 60, 0);
this.statistics.avgUnitCost = totalHours > 0 ? this.statistics.totalCost / totalHours : 0;
},
handleSearch() {
this.queryParams.pageNum = 1;
this.applyFiltersAndPagination();
},
handleReset() {
this.queryParams.enterCoilNo = '';
this.queryParams.currentCoilNo = '';
this.queryParams.warehouseId = null;
this.queryParams.pageNum = 1;
this.applyFiltersAndPagination();
},
handlePageChange(pageNum) {
this.queryParams.pageNum = pageNum;
this.applyFiltersAndPagination();
},
handlePageSizeChange(pageSize) {
this.queryParams.pageSize = pageSize;
this.queryParams.pageNum = 1;
this.applyFiltersAndPagination();
},
loadMockData() {
setTimeout(() => {
this.coilCostList = [
{
coilCode: 'COIL-20231201-001',
logicWarehouseName: '库区A',
startTime: '2023-12-01 08:00:00',
endTime: '2023-12-01 16:30:00',
duration: 8.5,
totalCost: 1250.50,
energyCosts: [
{
energyTypeId: 1,
energyTypeName: '电',
consumption: 850,
consumptionUnit: 'kWh',
rate: 1.2,
cost: 1020,
percentage: 81.55
},
{
energyTypeId: 2,
energyTypeName: '水',
consumption: 120,
consumptionUnit: '吨',
rate: 1.5,
cost: 180,
percentage: 14.39
},
{
energyTypeId: 3,
energyTypeName: '气',
consumption: 45,
consumptionUnit: '立方米',
rate: 3.2,
cost: 50.5,
percentage: 4.04
}
],
relatedMeters: [
{
meterCode: 'METER-001',
energyTypeName: '电',
model: 'DL-2000',
manufacturer: '施耐德',
status: 0
},
{
meterCode: 'METER-002',
energyTypeName: '水',
model: 'WM-500',
manufacturer: '西门子',
status: 0
}
]
},
{
coilCode: 'COIL-20231201-002',
logicWarehouseName: '库区B',
startTime: '2023-12-01 09:00:00',
endTime: '2023-12-01 17:00:00',
duration: 8.0,
totalCost: 980.30,
energyCosts: [
{
energyTypeId: 1,
energyTypeName: '电',
consumption: 700,
consumptionUnit: 'kWh',
rate: 1.2,
cost: 840,
percentage: 85.75
},
{
energyTypeId: 2,
energyTypeName: '水',
consumption: 80,
consumptionUnit: '吨',
rate: 1.5,
cost: 120,
percentage: 12.24
},
{
energyTypeId: 3,
energyTypeName: '气',
consumption: 20,
consumptionUnit: '立方米',
rate: 3.2,
cost: 20.3,
percentage: 2.07
}
],
relatedMeters: [
{
meterCode: 'METER-003',
energyTypeName: '电',
model: 'DL-2000',
manufacturer: '施耐德',
status: 0
}
]
},
{
coilCode: 'COIL-20231201-003',
logicWarehouseName: '库区C',
startTime: '2023-12-01 10:00:00',
endTime: '2023-12-01 18:30:00',
duration: 8.5,
totalCost: 1520.80,
energyCosts: [
{
energyTypeId: 1,
energyTypeName: '电',
consumption: 950,
consumptionUnit: 'kWh',
rate: 1.2,
cost: 1140,
percentage: 74.93
},
{
energyTypeId: 2,
energyTypeName: '水',
consumption: 150,
consumptionUnit: '吨',
rate: 1.5,
cost: 225,
percentage: 14.79
},
{
energyTypeId: 3,
energyTypeName: '气',
consumption: 80,
consumptionUnit: '立方米',
rate: 3.2,
cost: 155.8,
percentage: 10.24
}
],
relatedMeters: [
{
meterCode: 'METER-004',
energyTypeName: '电',
model: 'DL-3000',
manufacturer: '施耐德',
status: 0
},
{
meterCode: 'METER-005',
energyTypeName: '水',
model: 'WM-600',
manufacturer: '西门子',
status: 0
}
]
}
];
}, 500);
},
showDetail(coil) {
this.selectedCoil = coil;
this.detailDialogVisible = true;
},
closeDetail() {
this.selectedCoil = null;
this.detailDialogVisible = false;
},
formatTime(row, column, cellValue) {
if (!cellValue) return '-';
return new Date(cellValue).toLocaleString('zh-CN');
},
formatNumber(value, decimals = 2) {
if (value === null || value === undefined) return '0.00';
return parseFloat(value).toFixed(decimals);
}
}
};
</script>
<style scoped lang="scss">
.coil-cost-list {
padding: 20px;
background: #f6f7fb;
min-height: calc(100vh - 100px);
.statistics-section {
display: flex;
gap: 20px;
margin-bottom: 20px;
padding: 20px;
background: #fafafa;
border-radius: 4px;
border: 1px solid #ebeef5;
.stat-item {
flex: 1;
text-align: center;
.stat-label {
font-size: 14px;
color: #606266;
margin-bottom: 10px;
}
.stat-value {
font-size: 32px;
font-weight: bold;
color: #409eff;
&.cost-highlight {
color: #f56c6c;
}
}
}
}
.search-section {
background: #ffffff;
padding: 16px;
border-radius: 10px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
::v-deep .el-input,
::v-deep .el-select {
width: 100%;
}
}
.pagination {
margin-top: 20px;
text-align: right;
}
.cost-value {
color: #f56c6c;
font-weight: bold;
}
.detail-content {
.section {
margin-bottom: 24px;
.section-title {
font-size: 14px;
font-weight: bold;
color: #303133;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 2px solid #409eff;
}
}
.info-item {
display: flex;
flex-direction: column;
gap: 4px;
.label {
font-size: 12px;
color: #909399;
font-weight: 500;
}
.value {
font-size: 14px;
color: #303133;
font-weight: 500;
&.highlight {
color: #f56c6c;
font-size: 16px;
font-weight: bold;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,572 @@
<template>
<div class="energy-cost-summary-page">
<!-- 查询条件 -->
<el-card class="search-card">
<el-form :model="queryParams" label-width="100px" size="small">
<el-row :gutter="20">
<el-col :xs="24" :sm="12" :md="6">
<el-form-item label="统计维度:">
<el-select v-model="queryParams.dimension" placeholder="请选择维度" @change="handleQuery">
<el-option label="按钢卷" value="coil"></el-option>
<el-option label="按库区" value="warehouse"></el-option>
<el-option label="按时间" value="time"></el-option>
<el-option label="按能源" value="energy"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<el-form-item label="开始日期:">
<el-date-picker v-model="queryParams.startDate" type="date" placeholder="选择开始日期" value-format="yyyy-MM-dd" @change="handleQuery"></el-date-picker>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<el-form-item label="结束日期:">
<el-date-picker v-model="queryParams.endDate" type="date" placeholder="选择结束日期" value-format="yyyy-MM-dd" @change="handleQuery"></el-date-picker>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<el-button type="primary" icon="el-icon-search" size="small" @click="handleQuery">查询</el-button>
<el-button icon="el-icon-refresh" size="small" @click="resetQuery">重置</el-button>
</el-col>
</el-row>
</el-form>
</el-card>
<!-- 按钢卷统计 -->
<div v-if="queryParams.dimension === 'coil'">
<el-card class="summary-card" v-loading="loading">
<div slot="header" class="clearfix">
<span class="card-title">钢卷能源成本汇总</span>
</div>
<el-table :data="coilSummaryList" stripe border max-height="600">
<el-table-column prop="coilCode" label="钢卷编号" width="150"></el-table-column>
<el-table-column prop="logicWarehouseName" label="逻辑库区" width="120"></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-column prop="duration" label="生产时长" width="100">
<template slot-scope="scope">
{{ formatNumber(scope.row.duration, 1) }}h
</template>
</el-table-column>
<el-table-column prop="totalCost" label="总成本" width="120">
<template slot-scope="scope">
<span class="cost-value">¥ {{ formatNumber(scope.row.totalCost, 2) }}</span>
</template>
</el-table-column>
<el-table-column prop="unitCost" label="单位成本" width="120">
<template slot-scope="scope">
¥ {{ formatNumber(scope.row.unitCost, 2) }}/h
</template>
</el-table-column>
<el-table-column label="操作" width="100" fixed="right">
<template slot-scope="scope">
<el-button type="primary" size="mini" @click="viewCoilDetail(scope.row)">详情</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
</div>
<!-- 按库区统计 -->
<div v-if="queryParams.dimension === 'warehouse'">
<el-card class="summary-card" v-loading="loading">
<div slot="header" class="clearfix">
<span class="card-title">库区能源成本汇总</span>
</div>
<el-table :data="warehouseSummaryList" stripe border max-height="600">
<el-table-column prop="logicWarehouseName" label="逻辑库区" width="150"></el-table-column>
<el-table-column prop="coilCount" label="生产钢卷数" width="120">
<template slot-scope="scope">
{{ scope.row.coilCount }}
</template>
</el-table-column>
<el-table-column prop="totalDuration" label="总生产时长" width="120">
<template slot-scope="scope">
{{ formatNumber(scope.row.totalDuration, 1) }}h
</template>
</el-table-column>
<el-table-column prop="totalCost" label="总成本" width="120">
<template slot-scope="scope">
<span class="cost-value">¥ {{ formatNumber(scope.row.totalCost, 2) }}</span>
</template>
</el-table-column>
<el-table-column prop="averageUnitCost" label="平均单位成本" width="140">
<template slot-scope="scope">
¥ {{ formatNumber(scope.row.averageUnitCost, 2) }}/
</template>
</el-table-column>
<el-table-column label="操作" width="100" fixed="right">
<template slot-scope="scope">
<el-button type="primary" size="mini" @click="viewWarehouseDetail(scope.row)">详情</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
</div>
<!-- 按时间统计 -->
<div v-if="queryParams.dimension === 'time'">
<el-card class="chart-card" v-loading="loading">
<div slot="header" class="clearfix">
<span class="card-title">能源成本趋势</span>
</div>
<div id="costTrendChart" style="height: 400px;"></div>
</el-card>
<el-card class="summary-card" v-loading="loading">
<div slot="header" class="clearfix">
<span class="card-title">按日期统计</span>
</div>
<el-table :data="timeSummaryList" stripe border max-height="600">
<el-table-column prop="date" label="日期" width="150"></el-table-column>
<el-table-column prop="coilCount" label="生产钢卷数" width="120">
<template slot-scope="scope">
{{ scope.row.coilCount }}
</template>
</el-table-column>
<el-table-column prop="totalDuration" label="总生产时长" width="120">
<template slot-scope="scope">
{{ formatNumber(scope.row.totalDuration, 1) }}h
</template>
</el-table-column>
<el-table-column prop="totalCost" label="总成本" width="120">
<template slot-scope="scope">
<span class="cost-value">¥ {{ formatNumber(scope.row.totalCost, 2) }}</span>
</template>
</el-table-column>
<el-table-column prop="averageUnitCost" label="平均单位成本" width="140">
<template slot-scope="scope">
¥ {{ formatNumber(scope.row.averageUnitCost, 2) }}/
</template>
</el-table-column>
</el-table>
</el-card>
</div>
<!-- 按能源统计 -->
<div v-if="queryParams.dimension === 'energy'">
<el-card class="chart-card" v-loading="loading">
<div slot="header" class="clearfix">
<span class="card-title">能源成本占比</span>
</div>
<div id="energyCostPieChart" style="height: 400px;"></div>
</el-card>
<el-card class="summary-card" v-loading="loading">
<div slot="header" class="clearfix">
<span class="card-title">能源成本明细</span>
</div>
<el-table :data="energySummaryList" stripe border max-height="600">
<el-table-column prop="energyTypeName" label="能源类型" width="150"></el-table-column>
<el-table-column prop="totalConsumption" label="总消耗量" width="120">
<template slot-scope="scope">
{{ formatNumber(scope.row.totalConsumption, 2) }}
</template>
</el-table-column>
<el-table-column prop="consumptionUnit" label="单位" width="100"></el-table-column>
<el-table-column prop="totalCost" label="总成本" width="120">
<template slot-scope="scope">
<span class="cost-value">¥ {{ formatNumber(scope.row.totalCost, 2) }}</span>
</template>
</el-table-column>
<el-table-column prop="percentage" label="成本占比" width="120">
<template slot-scope="scope">
{{ formatNumber(scope.row.percentage, 2) }}%
</template>
</el-table-column>
<el-table-column prop="averageRate" label="平均费率" width="120">
<template slot-scope="scope">
¥ {{ formatNumber(scope.row.averageRate, 4) }}
</template>
</el-table-column>
</el-table>
</el-card>
</div>
</div>
</template>
<script>
import * as echarts from 'echarts'
import { listEnergyCoilDaily } from '@/api/ems/energyAllocation'
export default {
name: 'EnergyCostSummary',
data() {
return {
loading: false,
queryParams: {
dimension: 'coil',
startDate: '',
endDate: '',
pageNum: 1,
pageSize: 100
},
coilSummaryList: [],
warehouseSummaryList: [],
timeSummaryList: [],
energySummaryList: [],
chartInstances: {}
};
},
mounted() {
this.handleQuery();
},
methods: {
handleQuery() {
this.loading = true;
// 调用后端API获取钢卷能源成本数据
listEnergyCoilDaily(this.queryParams).then(response => {
const data = response.rows || [];
// 根据选择的维度处理数据
if (this.queryParams.dimension === 'coil') {
this.coilSummaryList = data;
} else {
this.processDataByDimension(data);
}
this.initCharts();
}).catch(() => {
this.$message.error('加载能源成本数据失败');
// 加载模拟数据作为备用
this.loadMockData();
}).finally(() => {
this.loading = false;
});
},
processDataByDimension(data) {
if (this.queryParams.dimension === 'warehouse') {
// 按库区聚合
const warehouseMap = {};
data.forEach(item => {
const key = item.warehouseId;
if (!warehouseMap[key]) {
warehouseMap[key] = {
warehouseId: item.warehouseId,
logicWarehouseName: item.logicWarehouseName,
coilCount: 0,
totalDuration: 0,
totalCost: 0
};
}
warehouseMap[key].coilCount++;
warehouseMap[key].totalDuration += (item.allocationBasisDays || 0);
warehouseMap[key].totalCost += (item.costAmount || 0);
});
this.warehouseSummaryList = Object.values(warehouseMap).map(w => ({
...w,
averageUnitCost: w.coilCount > 0 ? (w.totalCost / w.coilCount).toFixed(2) : 0
}));
} else if (this.queryParams.dimension === 'time') {
// 按时间聚合
const timeMap = {};
data.forEach(item => {
const date = item.calcDate;
if (!timeMap[date]) {
timeMap[date] = {
date: date,
coilCount: 0,
totalDuration: 0,
totalCost: 0
};
}
timeMap[date].coilCount++;
timeMap[date].totalDuration += (item.allocationBasisDays || 0);
timeMap[date].totalCost += (item.costAmount || 0);
});
this.timeSummaryList = Object.values(timeMap).map(t => ({
...t,
averageUnitCost: t.coilCount > 0 ? (t.totalCost / t.coilCount).toFixed(2) : 0
}));
} else if (this.queryParams.dimension === 'energy') {
// 按能源类型聚合
const energyMap = {};
data.forEach(item => {
const key = item.energyTypeId;
if (!energyMap[key]) {
energyMap[key] = {
energyTypeId: item.energyTypeId,
energyTypeName: item.energyTypeName,
totalConsumption: 0,
totalCost: 0
};
}
energyMap[key].totalConsumption += (item.consumptionQty || 0);
energyMap[key].totalCost += (item.costAmount || 0);
});
const totalCost = Object.values(energyMap).reduce((sum, e) => sum + e.totalCost, 0);
this.energySummaryList = Object.values(energyMap).map(e => ({
...e,
percentage: totalCost > 0 ? ((e.totalCost / totalCost) * 100).toFixed(2) : 0,
averageRate: e.totalConsumption > 0 ? (e.totalCost / e.totalConsumption).toFixed(4) : 0
}));
}
},
loadMockData() {
setTimeout(() => {
this.coilSummaryList = [
{
coilCode: 'COIL-20231201-001',
logicWarehouseName: '库区A',
startTime: '2023-12-01 08:00:00',
endTime: '2023-12-01 16:30:00',
duration: 8.5,
totalCost: 1250.50,
unitCost: 147.12
},
{
coilCode: 'COIL-20231201-002',
logicWarehouseName: '库区A',
startTime: '2023-12-01 17:00:00',
endTime: '2023-12-02 01:30:00',
duration: 8.5,
totalCost: 1248.75,
unitCost: 146.88
}
];
this.warehouseSummaryList = [
{
logicWarehouseName: '库区A',
coilCount: 45,
totalDuration: 382.5,
totalCost: 56325.50,
averageUnitCost: 1251.67
},
{
logicWarehouseName: '库区B',
coilCount: 38,
totalDuration: 323.0,
totalCost: 47850.25,
averageUnitCost: 1259.22
}
];
this.timeSummaryList = [
{
date: '2023-12-01',
coilCount: 12,
totalDuration: 102.0,
totalCost: 15125.50,
averageUnitCost: 1260.46
},
{
date: '2023-12-02',
coilCount: 15,
totalDuration: 127.5,
totalCost: 18950.75,
averageUnitCost: 1263.38
},
{
date: '2023-12-03',
coilCount: 18,
totalDuration: 153.0,
totalCost: 22750.25,
averageUnitCost: 1263.91
}
];
this.energySummaryList = [
{
energyTypeId: 1,
energyTypeName: '电',
totalConsumption: 32500,
consumptionUnit: 'kWh',
totalCost: 45875,
percentage: 81.45,
averageRate: 1.4115
},
{
energyTypeId: 2,
energyTypeName: '水',
totalConsumption: 4200,
consumptionUnit: '吨',
totalCost: 8400,
percentage: 14.91,
averageRate: 2.0
},
{
energyTypeId: 3,
energyTypeName: '气',
totalConsumption: 1550,
consumptionUnit: '立方米',
totalCost: 2050.5,
percentage: 3.64,
averageRate: 1.3229
}
];
this.$nextTick(() => {
this.initCharts();
});
}, 500);
},
resetQuery() {
this.queryParams = {
dimension: 'coil',
startDate: '',
endDate: ''
};
this.handleQuery();
},
initCharts() {
if (this.queryParams.dimension === 'time') {
this.initCostTrendChart();
} else if (this.queryParams.dimension === 'energy') {
this.initEnergyCostPieChart();
}
},
initCostTrendChart() {
const chartDom = document.getElementById('costTrendChart');
if (!chartDom) return;
const instance = echarts.init(chartDom);
this.chartInstances.trend = instance;
const option = {
tooltip: {
trigger: 'axis'
},
legend: {
data: ['总成本', '平均单位成本']
},
xAxis: {
type: 'category',
data: this.timeSummaryList.map(item => item.date)
},
yAxis: [
{
type: 'value',
name: '总成本',
axisLabel: {
formatter: '¥{value}'
}
},
{
type: 'value',
name: '平均单位成本',
axisLabel: {
formatter: '¥{value}'
}
}
],
series: [
{
name: '总成本',
type: 'bar',
data: this.timeSummaryList.map(item => item.totalCost),
yAxisIndex: 0
},
{
name: '平均单位成本',
type: 'line',
data: this.timeSummaryList.map(item => item.averageUnitCost),
yAxisIndex: 1
}
]
};
instance.setOption(option);
},
initEnergyCostPieChart() {
const chartDom = document.getElementById('energyCostPieChart');
if (!chartDom) return;
const instance = echarts.init(chartDom);
this.chartInstances.energy = instance;
const option = {
tooltip: {
trigger: 'item',
formatter: '{b}: ¥{c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'left'
},
series: [
{
name: '能源成本',
type: 'pie',
radius: '50%',
data: this.energySummaryList.map(item => ({
value: item.totalCost,
name: item.energyTypeName
})),
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
instance.setOption(option);
},
formatTime(time) {
if (!time) return '-';
return new Date(time).toLocaleString('zh-CN');
},
formatNumber(value, decimals = 2) {
if (value === null || value === undefined) return '0.00';
return parseFloat(value).toFixed(decimals);
},
viewCoilDetail(row) {
this.$router.push({
path: '/ems/cost/coilCost',
query: { coilId: row.coilId }
});
},
viewWarehouseDetail(row) {
this.$router.push({
path: '/ems/cost/warehouseProduction',
query: { logicWarehouseId: row.logicWarehouseId }
});
}
},
beforeDestroy() {
Object.values(this.chartInstances).forEach(instance => {
instance?.dispose();
});
}
};
</script>
<style scoped lang="scss">
.energy-cost-summary-page {
padding: 20px;
.search-card {
margin-bottom: 20px;
}
.chart-card,
.summary-card {
margin-bottom: 20px;
.card-title {
font-size: 16px;
font-weight: bold;
color: #303133;
}
}
.cost-value {
color: #f56c6c;
font-weight: bold;
}
}
</style>

View File

@@ -0,0 +1,367 @@
<template>
<div class="energy-cost-page">
<section class="filter-card">
<el-form :inline="true" :model="filters" size="small">
<el-form-item label="日期范围">
<el-date-picker
v-model="dateRange"
type="daterange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="yyyy-MM-dd"
@change="handleDateChange"
/>
</el-form-item>
<el-form-item label="能源类型">
<el-select v-model="filters.energyTypeId" placeholder="全部" clearable @change="refreshAll">
<el-option v-for="item in energyTypeList" :key="item.energyTypeId" :label="item.name" :value="item.energyTypeId" />
</el-select>
</el-form-item>
<el-form-item label="库区">
<el-select v-model="filters.warehouseId" placeholder="全部" clearable filterable @change="refreshAll">
<el-option v-for="item in warehouseList" :key="item.warehouseId" :label="item.warehouseName" :value="item.warehouseId" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="refreshAll">查询</el-button>
<el-button icon="el-icon-refresh" @click="resetFilter">重置</el-button>
</el-form-item>
</el-form>
</section>
<section class="overview-grid">
<div class="metal-card" v-for="item in overviewCards" :key="item.key">
<div class="card-label">{{ item.label }}</div>
<div class="card-value">{{ item.value }}</div>
<div class="card-sub">{{ item.sub }}</div>
</div>
</section>
<section class="content-grid">
<div class="metal-panel">
<div class="panel-header">
<div class="panel-title">能源成本汇总</div>
<el-radio-group v-model="groupBy" size="small" @change="loadSummary">
<el-radio-button label="energyType">能源</el-radio-button>
<el-radio-button label="warehouse">库区</el-radio-button>
<el-radio-button label="meter">仪表</el-radio-button>
<el-radio-button label="task">任务</el-radio-button>
</el-radio-group>
</div>
<el-table
:data="summaryList"
height="300px"
stripe
v-loading="summaryLoading"
>
<el-table-column label="分组" prop="groupName" min-width="120" />
<el-table-column label="能耗 (kWh)" prop="totalConsumption" min-width="120">
<template slot-scope="scope">{{ formatNumber(scope.row.totalConsumption) }}</template>
</el-table-column>
<el-table-column label="费用 (元)" prop="totalCost" min-width="120">
<template slot-scope="scope">{{ formatNumber(scope.row.totalCost) }}</template>
</el-table-column>
<el-table-column label="钢卷数" prop="coilCount" min-width="100" />
</el-table>
</div>
<div class="metal-panel">
<div class="panel-header">
<div class="panel-title">能源明细</div>
<div class="panel-actions">
<el-input v-model="detailFilters.currentCoilNo" placeholder="当前卷号" size="small" clearable @keyup.enter.native="loadDetail" />
<el-button size="small" type="primary" @click="loadDetail">刷新</el-button>
</div>
</div>
<el-table
:data="detailList"
height="300px"
stripe
v-loading="detailLoading"
>
<el-table-column label="日期" prop="calcDate" min-width="110" />
<el-table-column label="当前卷号" prop="currentCoilNo" min-width="140" />
<el-table-column label="能源类型" prop="energyTypeId" min-width="100" />
<el-table-column label="能耗" prop="consumptionQty" min-width="100">
<template slot-scope="scope">{{ formatNumber(scope.row.consumptionQty) }}</template>
</el-table-column>
<el-table-column label="费用 (元)" prop="costAmount" min-width="110">
<template slot-scope="scope">{{ formatNumber(scope.row.costAmount) }}</template>
</el-table-column>
<el-table-column label="分摊系数" prop="allocationFactor" min-width="110">
<template slot-scope="scope">{{ toPercent(scope.row.allocationFactor) }}</template>
</el-table-column>
</el-table>
<pagination
v-show="detailTotal > 0"
:total="detailTotal"
:page.sync="detailFilters.pageNum"
:limit.sync="detailFilters.pageSize"
@pagination="loadDetail"
/>
</div>
</section>
</div>
</template>
<script>
import { fetchEnergyOverview, fetchEnergySummary, fetchEnergyDetail } from '@/api/ems/energyCostReport'
import { listEnergyType } from '@/api/ems/energyType'
import { listWarehouse } from '@/api/wms/warehouse'
export default {
name: 'EnergyCostDashboard',
data() {
return {
filters: {
startDate: '',
endDate: '',
energyTypeId: undefined,
warehouseId: undefined
},
dateRange: [],
energyTypeList: [],
warehouseList: [],
overview: {
coilCount: 0,
totalConsumption: 0,
totalCost: 0
},
summaryList: [],
summaryLoading: false,
groupBy: 'energyType',
detailList: [],
detailTotal: 0,
detailLoading: false,
detailFilters: {
pageNum: 1,
pageSize: 10,
currentCoilNo: ''
}
}
},
computed: {
overviewCards() {
return [
{
key: 'coil',
label: '钢卷数量',
value: this.formatNumber(this.overview.coilCount),
sub: 'Coils involved'
},
{
key: 'consumption',
label: '总能耗 (kWh)',
value: this.formatNumber(this.overview.totalConsumption),
sub: 'Total consumption'
},
{
key: 'cost',
label: '总费用 (元)',
value: '¥' + this.formatNumber(this.overview.totalCost),
sub: 'Total cost'
}
]
}
},
created() {
this.initMeta()
},
methods: {
initMeta() {
listEnergyType({ pageNum: 1, pageSize: 999 }).then(res => {
this.energyTypeList = res.rows || []
})
listWarehouse().then(res => {
this.warehouseList = res.data || []
})
this.refreshAll()
},
handleDateChange(range) {
if (range && range.length === 2) {
this.filters.startDate = range[0]
this.filters.endDate = range[1]
} else {
this.filters.startDate = ''
this.filters.endDate = ''
}
this.refreshAll()
},
resetFilter() {
this.dateRange = []
this.filters = {
startDate: '',
endDate: '',
energyTypeId: undefined,
warehouseId: undefined
}
this.detailFilters.currentCoilNo = ''
this.refreshAll()
},
refreshAll() {
this.loadOverview()
this.loadSummary()
this.detailFilters.pageNum = 1
this.loadDetail()
},
loadOverview() {
fetchEnergyOverview(this.mergeFilters()).then(res => {
this.overview = res.data || { coilCount: 0, totalConsumption: 0, totalCost: 0 }
})
},
loadSummary() {
this.summaryLoading = true
fetchEnergySummary({ ...this.mergeFilters(), groupBy: this.groupBy }).then(res => {
this.summaryList = res.data || []
}).finally(() => {
this.summaryLoading = false
})
},
loadDetail() {
this.detailLoading = true
const params = {
...this.mergeFilters(),
...this.detailFilters
}
fetchEnergyDetail(params).then(res => {
this.detailList = res.rows || []
this.detailTotal = res.total || 0
}).finally(() => {
this.detailLoading = false
})
},
mergeFilters() {
const { startDate, endDate, energyTypeId, warehouseId } = this.filters
return {
startDate,
endDate,
energyTypeId,
warehouseId
}
},
formatNumber(val) {
if (val === undefined || val === null) return '-'
const num = Number(val)
if (Number.isNaN(num)) return val
return num.toLocaleString(undefined, { maximumFractionDigits: 2 })
},
toPercent(val) {
if (!val) return '0%'
return (Number(val) * 100).toFixed(2) + '%'
}
}
}
</script>
<style lang="scss" scoped>
.energy-cost-page {
display: flex;
flex-direction: column;
gap: 16px;
padding: 16px;
background: #0f131a;
min-height: calc(100vh - 100px);
}
.filter-card {
background: linear-gradient(135deg, #1f242b, #181c23);
border: 1px solid #2f353f;
border-radius: 10px;
padding: 16px;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
}
.overview-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 16px;
}
.metal-card {
background: linear-gradient(160deg, #2b3038, #1c1f27);
border: 1px solid #3c434f;
border-radius: 12px;
padding: 16px;
color: #d9dee9;
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.35);
min-height: 120px;
display: flex;
flex-direction: column;
justify-content: center;
}
.card-label {
font-size: 14px;
color: #8f96a3;
text-transform: uppercase;
}
.card-value {
margin-top: 8px;
font-size: 28px;
font-weight: 600;
color: #f6f7fb;
}
.card-sub {
margin-top: 4px;
font-size: 12px;
color: #707684;
}
.content-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(420px, 1fr));
gap: 16px;
}
.metal-panel {
background: #1b1f27;
border: 1px solid #303641;
border-radius: 12px;
padding: 16px;
box-shadow: 0 12px 25px rgba(0, 0, 0, 0.35);
display: flex;
flex-direction: column;
min-height: 380px;
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.panel-title {
font-size: 16px;
color: #f0f3ff;
letter-spacing: 0.5px;
}
.panel-actions {
display: flex;
gap: 8px;
align-items: center;
}
.el-table {
background: transparent;
color: #dfe3ee;
}
.el-table th,
.el-table td {
background-color: transparent !important;
}
.el-table::before {
background-color: transparent;
}
.pagination {
margin-top: 10px;
align-self: flex-end;
}
</style>

View File

@@ -0,0 +1,540 @@
<template>
<div class="warehouse-production-page">
<!-- 查询条件 -->
<el-card class="search-card">
<el-form :model="queryParams" label-width="120px" size="small">
<el-row :gutter="20">
<el-col :xs="24" :sm="12" :md="6">
<el-form-item label="逻辑库区:">
<el-select v-model="queryParams.logicWarehouseId" placeholder="请选择库区" clearable @change="handleQuery">
<el-option v-for="item in warehouseList" :key="item.warehouseId" :label="item.warehouseName" :value="item.warehouseId"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<el-form-item label="开始日期:">
<el-date-picker v-model="queryParams.startDate" type="date" placeholder="选择开始日期" value-format="yyyy-MM-dd" @change="handleQuery"></el-date-picker>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<el-form-item label="结束日期:">
<el-date-picker v-model="queryParams.endDate" type="date" placeholder="选择结束日期" value-format="yyyy-MM-dd" @change="handleQuery"></el-date-picker>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<el-button type="primary" icon="el-icon-search" size="small" @click="handleQuery">查询</el-button>
<el-button icon="el-icon-refresh" size="small" @click="resetQuery">重置</el-button>
</el-col>
</el-row>
</el-form>
</el-card>
<!-- 统计信息卡片 -->
<el-row :gutter="20" class="statistics-row">
<el-col :xs="24" :sm="12" :md="6">
<div class="stat-card">
<div class="stat-label">生产钢卷数</div>
<div class="stat-value">{{ statistics.coilCount }}</div>
<div class="stat-unit"></div>
</div>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<div class="stat-card">
<div class="stat-label">总生产时长</div>
<div class="stat-value">{{ formatNumber(statistics.totalDuration, 1) }}</div>
<div class="stat-unit">小时</div>
</div>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<div class="stat-card">
<div class="stat-label">总能源成本</div>
<div class="stat-value">¥ {{ formatNumber(statistics.totalCost, 2) }}</div>
<div class="stat-unit"></div>
</div>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<div class="stat-card highlight">
<div class="stat-label">单位成本</div>
<div class="stat-value">¥ {{ formatNumber(statistics.unitCost, 2) }}</div>
<div class="stat-unit">/</div>
</div>
</el-col>
</el-row>
<!-- 能源成本分解 -->
<el-row :gutter="20">
<el-col :xs="24" :md="12">
<el-card class="chart-card" v-loading="loading">
<div slot="header" class="clearfix">
<span class="card-title">能源成本占比</span>
</div>
<div id="energyCostPieChart" style="height: 350px;"></div>
</el-card>
</el-col>
<el-col :xs="24" :md="12">
<el-card class="chart-card" v-loading="loading">
<div slot="header" class="clearfix">
<span class="card-title">能源消耗占比</span>
</div>
<div id="energyConsumptionPieChart" style="height: 350px;"></div>
</el-card>
</el-col>
</el-row>
<!-- 能源成本明细表 -->
<el-card class="detail-card" v-loading="loading">
<div slot="header" class="clearfix">
<span class="card-title">能源成本明细</span>
</div>
<el-table :data="energyBreakdown" stripe border>
<el-table-column prop="energyTypeName" label="能源类型" width="150"></el-table-column>
<el-table-column prop="consumption" label="总消耗量" width="120">
<template slot-scope="scope">
{{ formatNumber(scope.row.consumption, 2) }}
</template>
</el-table-column>
<el-table-column prop="consumptionUnit" label="单位" width="100"></el-table-column>
<el-table-column prop="cost" label="总成本" width="120">
<template slot-scope="scope">
<span class="cost-value">¥ {{ formatNumber(scope.row.cost, 2) }}</span>
</template>
</el-table-column>
<el-table-column prop="percentage" label="成本占比" width="120">
<template slot-scope="scope">
{{ formatNumber(scope.row.percentage, 2) }}%
</template>
</el-table-column>
<el-table-column prop="averageUnitCost" label="单位成本" width="120">
<template slot-scope="scope">
¥ {{ formatNumber(scope.row.averageUnitCost, 4) }}/单位
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 钢卷列表 -->
<el-card class="coil-list-card" v-loading="loading">
<div slot="header" class="clearfix">
<span class="card-title">该库区生产的钢卷列表</span>
<span class="count">( {{ coilList.length }} )</span>
</div>
<el-table :data="coilList" stripe border max-height="500">
<el-table-column prop="coilCode" label="钢卷编号" width="150"></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-column prop="duration" label="生产时长" width="100">
<template slot-scope="scope">
{{ formatNumber(scope.row.duration, 1) }}h
</template>
</el-table-column>
<el-table-column prop="totalCost" label="能源成本" width="120">
<template slot-scope="scope">
<span class="cost-value">¥ {{ formatNumber(scope.row.totalCost, 2) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="100" fixed="right">
<template slot-scope="scope">
<el-button type="primary" size="mini" @click="viewCoilDetail(scope.row)">查看详情</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
</div>
</template>
<script>
import * as echarts from 'echarts'
import { listEnergyCoilDaily, listWarehouse } from '@/api/ems/energyAllocation'
export default {
name: 'WarehouseProduction',
data() {
return {
loading: false,
queryParams: {
logicWarehouseId: '',
startDate: '',
endDate: ''
},
warehouseList: [],
statistics: {
coilCount: 0,
totalDuration: 0,
totalConsumption: 0,
totalCost: 0,
unitCost: 0
},
energyBreakdown: [],
coilList: [],
chartInstances: {}
};
},
mounted() {
this.loadWarehouseList();
this.handleQuery();
},
methods: {
loadWarehouseList() {
// 调用后端API获取库区列表
listWarehouse().then(response => {
this.warehouseList = response.data || [];
if (this.warehouseList.length === 0) {
this.$message.warning('暂无库区数据');
}
}).catch(() => {
this.$message.error('加载库区列表失败');
// 加载模拟数据作为备用
this.warehouseList = [
{ id: 1, name: '库区A' },
{ id: 2, name: '库区B' },
{ id: 3, name: '库区C' }
];
});
},
handleQuery() {
this.loading = true;
// 调用后端API获取库区生产统计
listEnergyCoilDaily(this.queryParams).then(response => {
const data = response.rows || [];
this.processWarehouseData(data);
this.initCharts();
}).catch(() => {
this.$message.error('加载库区生产统计失败');
// 加载模拟数据作为备用
this.loadMockData();
}).finally(() => {
this.loading = false;
});
},
processWarehouseData(data) {
if (!data || data.length === 0) {
this.statistics = {
coilCount: 0,
totalDuration: 0,
totalConsumption: 0,
totalCost: 0,
unitCost: 0
};
this.energyBreakdown = [];
this.coilList = [];
return;
}
// 统计基本信息
const coilCount = new Set(data.map(d => d.coilId)).size;
const totalDuration = data.reduce((sum, d) => sum + (d.allocationBasisDays || 0), 0);
const totalCost = data.reduce((sum, d) => sum + (d.costAmount || 0), 0);
const totalConsumption = data.reduce((sum, d) => sum + (d.consumptionQty || 0), 0);
this.statistics = {
coilCount: coilCount,
totalDuration: totalDuration.toFixed(2),
totalConsumption: totalConsumption.toFixed(2),
totalCost: totalCost.toFixed(2),
unitCost: coilCount > 0 ? (totalCost / coilCount).toFixed(2) : 0
};
// 按能源类型聚合
const energyMap = {};
data.forEach(item => {
const key = item.energyTypeId;
if (!energyMap[key]) {
energyMap[key] = {
energyTypeId: item.energyTypeId,
energyTypeName: item.energyTypeName,
consumption: 0,
consumptionUnit: item.consumptionUnit || '单位',
cost: 0
};
}
energyMap[key].consumption += (item.consumptionQty || 0);
energyMap[key].cost += (item.costAmount || 0);
});
this.energyBreakdown = Object.values(energyMap).map(e => ({
...e,
percentage: totalCost > 0 ? ((e.cost / totalCost) * 100).toFixed(2) : 0,
averageUnitCost: e.consumption > 0 ? (e.cost / e.consumption).toFixed(4) : 0
}));
// 按钢卷聚合
const coilMap = {};
data.forEach(item => {
const key = item.coilId;
if (!coilMap[key]) {
coilMap[key] = {
coilId: item.coilId,
coilCode: item.currentCoilNo || item.enterCoilNo,
startTime: item.calcDate,
endTime: item.calcDate,
duration: item.allocationBasisDays || 0,
totalCost: 0
};
}
coilMap[key].totalCost += (item.costAmount || 0);
});
this.coilList = Object.values(coilMap);
},
loadMockData() {
setTimeout(() => {
this.statistics = {
coilCount: 45,
totalDuration: 382.5,
totalConsumption: 38250,
totalCost: 56325.50,
unitCost: 1251.67
};
this.energyBreakdown = [
{
energyTypeId: 1,
energyTypeName: '电',
consumption: 32500,
consumptionUnit: 'kWh',
cost: 45875,
percentage: 81.45,
averageUnitCost: 1.4115
},
{
energyTypeId: 2,
energyTypeName: '水',
consumption: 4200,
consumptionUnit: '吨',
cost: 8400,
percentage: 14.91,
averageUnitCost: 2.0
},
{
energyTypeId: 3,
energyTypeName: '气',
consumption: 1550,
consumptionUnit: '立方米',
cost: 2050.5,
percentage: 3.64,
averageUnitCost: 1.3229
}
];
this.coilList = [
{
coilCode: 'COIL-20231201-001',
startTime: '2023-12-01 08:00:00',
endTime: '2023-12-01 16:30:00',
duration: 8.5,
totalCost: 1250.50
},
{
coilCode: 'COIL-20231201-002',
startTime: '2023-12-01 17:00:00',
endTime: '2023-12-02 01:30:00',
duration: 8.5,
totalCost: 1248.75
},
{
coilCode: 'COIL-20231202-001',
startTime: '2023-12-02 08:00:00',
endTime: '2023-12-02 16:15:00',
duration: 8.25,
totalCost: 1205.30
}
];
this.$nextTick(() => {
this.initCharts();
});
}, 500);
},
resetQuery() {
this.queryParams = {
logicWarehouseId: '',
startDate: '',
endDate: ''
};
this.handleQuery();
},
initCharts() {
this.initCostPieChart();
this.initConsumptionPieChart();
},
initCostPieChart() {
const chartDom = document.getElementById('energyCostPieChart');
if (!chartDom) return;
const instance = echarts.init(chartDom);
this.chartInstances.cost = instance;
const option = {
tooltip: {
trigger: 'item',
formatter: '{b}: ¥{c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'left'
},
series: [
{
name: '能源成本',
type: 'pie',
radius: '50%',
data: this.energyBreakdown.map(item => ({
value: item.cost,
name: item.energyTypeName
})),
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
instance.setOption(option);
},
initConsumptionPieChart() {
const chartDom = document.getElementById('energyConsumptionPieChart');
if (!chartDom) return;
const instance = echarts.init(chartDom);
this.chartInstances.consumption = instance;
const option = {
tooltip: {
trigger: 'item',
formatter: '{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'left'
},
series: [
{
name: '能源消耗',
type: 'pie',
radius: '50%',
data: this.energyBreakdown.map(item => ({
value: item.consumption,
name: item.energyTypeName
})),
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
instance.setOption(option);
},
formatTime(time) {
if (!time) return '-';
return new Date(time).toLocaleString('zh-CN');
},
formatNumber(value, decimals = 2) {
if (value === null || value === undefined) return '0.00';
return parseFloat(value).toFixed(decimals);
},
viewCoilDetail(row) {
this.$router.push({
path: '/ems/cost/coilCost',
query: { coilId: row.coilId }
});
}
},
beforeDestroy() {
Object.values(this.chartInstances).forEach(instance => {
instance?.dispose();
});
}
};
</script>
<style scoped lang="scss">
.warehouse-production-page {
padding: 20px;
.search-card {
margin-bottom: 20px;
}
.statistics-row {
margin-bottom: 20px;
.stat-card {
background: #ffffff;
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 20px;
text-align: center;
transition: all 0.3s ease;
&:hover {
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
&.highlight {
background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
border-color: #409eff;
}
.stat-label {
font-size: 12px;
color: #909399;
margin-bottom: 8px;
}
.stat-value {
font-size: 28px;
font-weight: bold;
color: #303133;
margin-bottom: 4px;
}
.stat-unit {
font-size: 12px;
color: #606266;
}
}
}
.chart-card,
.detail-card,
.coil-list-card {
margin-bottom: 20px;
.card-title {
font-size: 16px;
font-weight: bold;
color: #303133;
}
.count {
font-size: 12px;
color: #909399;
margin-left: 10px;
}
}
.cost-value {
color: #f56c6c;
font-weight: bold;
}
}
</style>