l3能源成本分摊

This commit is contained in:
2025-12-09 16:43:45 +08:00
parent d919c5ce7a
commit a2d0cce233
36 changed files with 3297 additions and 1298 deletions

View File

@@ -3,21 +3,17 @@
<!-- 统计信息 -->
<div class="statistics-section">
<div class="stat-item">
<div class="stat-label">钢卷总数</div>
<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 class="stat-label">生产总数</div>
<div class="stat-value">{{ statistics.totalOperationCoils }}</div>
</div>
<div class="stat-item">
<div class="stat-label">总成本</div>
<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>
<!-- 检索条件 -->
@@ -65,6 +61,19 @@
<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">
<span v-if="scope.row.latestMeterReadStartTime && scope.row.latestMeterReadEndTime">
{{ formatDateOnly(scope.row.latestMeterReadStartTime) }} ~ {{ formatDateOnly(scope.row.latestMeterReadEndTime) }}
</span>
<span v-else-if="!scope.row.latestMeterReadStartTime && !scope.row.latestMeterReadEndTime" class="no-data-tip">
当前库区未绑定任何设备
</span>
<span v-else class="no-data-tip">
当前未完成任何数据录入
</span>
</template>
</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) }}
@@ -75,9 +84,9 @@
{{ formatNumber(scope.row.productionDuration / 60, 2) }}
</template>
</el-table-column>
<el-table-column prop="consumptionQty" label="综合消耗(单位)" min-width="120" align="right">
<el-table-column prop="allocationFactor" label="分摊比率(%)" min-width="120" align="right">
<template slot-scope="scope">
{{ formatNumber(scope.row.consumptionQty, 2) }}
{{ formatNumber(scope.row.allocationFactor * 100, 2) }}
</template>
</el-table-column>
<el-table-column prop="costAmount" label="综合成本(¥)" min-width="120" align="right" sortable>
@@ -85,11 +94,6 @@
<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>
@@ -220,7 +224,9 @@
</template>
<script>
import { listEnergyCoilDaily, listPendingActionCoilCost } from '@/api/ems/energyAllocation'
import { listEnergyCoilDaily, listPendingActionCoilCost, getPendingActionCoilCostStatistics } from '@/api/ems/energyAllocation'
import { getLatestMeterReadTime } from '@/api/ems/energyConsumption'
import { getPendingActionCoilCostDetail } from '@/api/ems/energyAllocation'
export default {
name: 'CoilCost',
@@ -234,7 +240,7 @@ export default {
warehouseList: [],
statistics: {
totalCoils: 0,
totalConsumption: 0,
avgAllocationFactor: 0,
totalCost: 0,
avgUnitCost: 0
},
@@ -252,6 +258,7 @@ export default {
mounted() {
this.loadWarehouseList();
this.loadCoilCostList();
this.loadLatestMeterReadTime();
},
methods: {
loadWarehouseList() {
@@ -261,15 +268,38 @@ export default {
},
loadCoilCostList() {
this.loading = true;
// 优先加载待操作钢卷的成本
// 同时加载统计数据和列表数据
this.fetchStatistics();
this.fetchPendingActionCoilCost();
},
fetchStatistics() {
// 加载统计数据只统计status=0,1的钢卷且按当前查询条件过滤
const params = {
enterCoilNo: this.queryParams.enterCoilNo || undefined,
currentCoilNo: this.queryParams.currentCoilNo || undefined,
warehouseId: this.queryParams.warehouseId
};
getPendingActionCoilCostStatistics(params).then(response => {
if (response && response.data) {
this.statistics = response.data;
}
}).catch(() => {
console.warn('加载统计数据失败');
});
},
fetchPendingActionCoilCost() {
// 先尝试加载待操作钢卷的成本
listPendingActionCoilCost({ pageNum: 1, pageSize: 10000 }).then(response => {
this.allCoilCostList = response.rows || [];
// 加载列表数据显示status=0,1,2的钢卷分页由后端完成
const params = {
pageNum: this.queryParams.pageNum,
pageSize: this.queryParams.pageSize,
enterCoilNo: this.queryParams.enterCoilNo || undefined,
currentCoilNo: this.queryParams.currentCoilNo || undefined,
warehouseId: this.queryParams.warehouseId
};
listPendingActionCoilCost(params).then(response => {
this.coilCostList = response.rows || [];
this.total = response.total || 0;
this.dataSource = 'pending';
this.applyFiltersAndPagination();
}).catch(() => {
// 如果待操作接口失败,则加载日常分摊数据
this.fetchDailyCoilCostData();
@@ -279,238 +309,67 @@ export default {
},
fetchDailyCoilCostData() {
this.loading = true;
listEnergyCoilDaily({ pageNum: 1, pageSize: 10000 }).then(response => {
this.allCoilCostList = response.rows || [];
const params = {
pageNum: this.queryParams.pageNum,
pageSize: this.queryParams.pageSize,
enterCoilNo: this.queryParams.enterCoilNo || undefined,
currentCoilNo: this.queryParams.currentCoilNo || undefined,
warehouseId: this.queryParams.warehouseId
};
listEnergyCoilDaily(params).then(response => {
this.coilCostList = response.rows || [];
this.total = response.total || 0;
this.dataSource = 'daily';
this.applyFiltersAndPagination();
}).catch(() => {
this.$message.error('加载钢卷能源成本失败');
this.allCoilCostList = [];
this.coilCostList = [];
}).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)
);
getAvgUnitCost() {
// 防止NaN显示如果没有数据或消耗为0显示0
if (!this.statistics.avgUnitCost || isNaN(this.statistics.avgUnitCost)) {
return '0.00';
}
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;
return this.formatNumber(this.statistics.avgUnitCost, 2);
},
handleSearch() {
this.queryParams.pageNum = 1;
this.applyFiltersAndPagination();
this.fetchStatistics();
this.fetchPendingActionCoilCost();
},
handleReset() {
this.queryParams.enterCoilNo = '';
this.queryParams.currentCoilNo = '';
this.queryParams.warehouseId = null;
this.queryParams.pageNum = 1;
this.applyFiltersAndPagination();
this.fetchStatistics();
this.fetchPendingActionCoilCost();
},
handlePageChange(pageNum) {
this.queryParams.pageNum = pageNum;
this.applyFiltersAndPagination();
this.fetchPendingActionCoilCost();
},
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);
this.queryParams.pageSize = pageSize
this.queryParams.pageNum = 1
this.fetchPendingActionCoilCost()
},
showDetail(coil) {
this.selectedCoil = coil;
this.detailDialogVisible = true;
if (!coil || !coil.coilId) {
this.$message.warning('未找到钢卷ID')
return
}
this.detailDialogVisible = true
this.selectedCoil = null
getPendingActionCoilCostDetail(coil.coilId)
.then(res => {
this.selectedCoil = res.data || coil
})
.catch(() => {
this.selectedCoil = coil
})
},
closeDetail() {
this.selectedCoil = null;
@@ -523,6 +382,41 @@ export default {
formatNumber(value, decimals = 2) {
if (value === null || value === undefined) return '0.00';
return parseFloat(value).toFixed(decimals);
},
formatDateTime(dateTime) {
if (!dateTime) return '-';
const date = new Date(dateTime);
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
},
formatDateOnly(dateTime) {
if (!dateTime) return '-';
const date = new Date(dateTime);
return date.toLocaleDateString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
});
},
loadLatestMeterReadTime() {
getLatestMeterReadTime().then(response => {
if (response && response.data) {
this.statistics.latestMeterReadStartTime = response.data.startTime;
this.statistics.latestMeterReadEndTime = response.data.endTime;
console.log('最近一次抄表时间范围:', {
startTime: response.data.startTime,
endTime: response.data.endTime
});
}
}).catch(() => {
console.warn('获取最近一次抄表时间失败');
});
}
}
};
@@ -561,6 +455,12 @@ export default {
&.cost-highlight {
color: #f56c6c;
}
&.time-range {
font-size: 32px;
color: #409eff;
font-weight: bold;
}
}
}
}
@@ -571,10 +471,25 @@ export default {
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%;
.table-header-info {
margin-bottom: 16px;
padding: 12px 16px;
background: #f5f7fa;
border-radius: 4px;
border-left: 3px solid #409eff;
.time-range-label {
font-size: 14px;
color: #606266;
font-weight: 500;
margin-right: 8px;
}
.time-range-value {
font-size: 14px;
color: #606266;
}
}
@@ -588,6 +503,11 @@ export default {
font-weight: bold;
}
.no-data-tip {
color: #e6a23c;
font-size: 12px;
}
.detail-content {
.section {
margin-bottom: 24px;

View File

@@ -0,0 +1,419 @@
<template>
<div class="coil-total-cost-page">
<el-card class="search-card">
<el-form :model="queryParams" inline label-width="100px" size="small">
<el-form-item label="入场卷号" required>
<el-input v-model="queryParams.enterCoilNo" placeholder="必填,支持模糊"></el-input>
</el-form-item>
<el-form-item label="当前卷号">
<el-input v-model="queryParams.currentCoilNo" placeholder="支持模糊"></el-input>
</el-form-item>
<el-form-item label="开始日期">
<el-date-picker v-model="queryParams.startDate" type="date" value-format="yyyy-MM-dd" placeholder="开始日期"></el-date-picker>
</el-form-item>
<el-form-item label="结束日期">
<el-date-picker v-model="queryParams.endDate" type="date" value-format="yyyy-MM-dd" placeholder="结束日期"></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleSearch">查询</el-button>
<el-button icon="el-icon-refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<el-row :gutter="20" class="summary-row">
<el-col :xs="24" :md="6">
<div class="summary-card">
<div class="label">能源成本</div>
<div class="value">¥ {{ formatNumber(energySummary.totalEnergyCost, 2) }}</div>
<div class="desc">耗能 {{ formatNumber(energySummary.totalConsumption, 2) }}时长 {{ formatNumber(energySummary.totalDuration, 1) }}h</div>
</div>
</el-col>
<el-col :xs="24" :md="6">
<div class="summary-card">
<div class="label">本日囤积成本</div>
<div class="value">¥ {{ formatNumber(stockSummary.todayCost, 2) }}</div>
<div class="desc">净重 {{ formatNumber(stockSummary.totalNetWeight, 2) }}毛重 {{ formatNumber(stockSummary.totalGrossWeight, 2) }}</div>
</div>
</el-col>
<el-col :xs="24" :md="6">
<div class="summary-card">
<div class="label">辅料成本</div>
<div class="value">¥ 0.00</div>
<div class="desc">暂不计</div>
</div>
</el-col>
<el-col :xs="24" :md="6">
<div class="summary-card highlight">
<div class="label">总成本</div>
<div class="value">¥ {{ formatNumber(totalCost, 2) }}</div>
<div class="desc">能源 + 囤积 + 辅料</div>
</div>
</el-col>
</el-row>
<el-card class="block-card">
<div slot="header" class="clearfix">
<span class="card-title">成本汇总按入场卷号</span>
</div>
<el-table :data="mergedRows" border stripe>
<el-table-column prop="enterCoilNo" label="入场卷号"></el-table-column>
<el-table-column prop="currentCoilNo" label="当前卷号"></el-table-column>
<el-table-column prop="energyCost" label="能源成本">
<template slot-scope="scope">¥ {{ formatNumber(scope.row.energyCost, 2) }}</template>
</el-table-column>
<el-table-column prop="stockCost" label="囤积成本(当日)">
<template slot-scope="scope">¥ {{ formatNumber(scope.row.stockCost, 2) }}</template>
</el-table-column>
<el-table-column prop="totalCost" label="总成本">
<template slot-scope="scope">¥ {{ formatNumber(scope.row.totalCost, 2) }}</template>
</el-table-column>
<el-table-column prop="totalNetWeight" label="净重">
<template slot-scope="scope">{{ formatNumber(scope.row.totalNetWeight, 2) }}</template>
</el-table-column>
<el-table-column prop="totalGrossWeight" label="毛重">
<template slot-scope="scope">{{ formatNumber(scope.row.totalGrossWeight, 2) }}</template>
</el-table-column>
<el-table-column label="操作" width="180">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="openDetail(scope.row)">查看详情</el-button>
</template>
</el-table-column>
</el-table>
<div class="table-actions">
<el-button size="mini" type="primary" icon="el-icon-download" @click="exportSummary">导出汇总</el-button>
</div>
<pagination
v-show="mergedTotal > 0"
:total="mergedTotal"
:page.sync="mergedQuery.pageNum"
:limit.sync="mergedQuery.pageSize"
@pagination="loadMerged"
/>
</el-card>
<el-dialog title="钢卷成本详情" :visible.sync="detailVisible" width="80%" :close-on-click-modal="false">
<div class="detail-section">
<div class="detail-title">能源分摊明细{{ detailEnterCoilNo }}</div>
<el-table :data="detailEnergyRows" stripe border max-height="300">
<el-table-column prop="enterCoilNo" label="入场卷号" width="140"></el-table-column>
<el-table-column prop="currentCoilNo" label="当前卷号" width="140"></el-table-column>
<el-table-column prop="warehouseName" label="库区" width="140"></el-table-column>
<el-table-column prop="duration" label="时长(h)" width="100">
<template slot-scope="scope">{{ formatNumber(scope.row.duration, 2) }}</template>
</el-table-column>
<el-table-column prop="consumption" label="分摊能耗" width="120">
<template slot-scope="scope">{{ formatNumber(scope.row.consumption, 4) }}</template>
</el-table-column>
<el-table-column prop="totalCost" label="分摊成本" width="120">
<template slot-scope="scope">¥ {{ formatNumber(scope.row.totalCost, 2) }}</template>
</el-table-column>
<el-table-column prop="startTime" label="开始时间" width="180">
<template slot-scope="scope">{{ formatTime(scope.row.startTime) }}</template>
</el-table-column>
<el-table-column prop="endTime" label="结束时间" width="180">
<template slot-scope="scope">{{ formatTime(scope.row.endTime) }}</template>
</el-table-column>
</el-table>
</div>
<div class="detail-section">
<div class="detail-title">囤积成本{{ detailEnterCoilNo }}</div>
<el-table :data="detailStockRow ? [detailStockRow] : []" stripe border>
<el-table-column prop="enterCoilNo" label="入场卷号"></el-table-column>
<el-table-column prop="totalNetWeight" label="净重">
<template slot-scope="scope">{{ formatNumber(scope.row.totalNetWeight, 2) }}</template>
</el-table-column>
<el-table-column prop="totalGrossWeight" label="毛重">
<template slot-scope="scope">{{ formatNumber(scope.row.totalGrossWeight, 2) }}</template>
</el-table-column>
<el-table-column prop="avgStorageDays" label="平均在库天数">
<template slot-scope="scope">{{ formatNumber(scope.row.avgStorageDays, 1) }}</template>
</el-table-column>
<el-table-column prop="todayCost" label="本日囤积成本">
<template slot-scope="scope">¥ {{ formatNumber(scope.row.todayCost != null ? scope.row.todayCost : scope.row.totalCost, 2) }}</template>
</el-table-column>
</el-table>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="detailVisible = false"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { fetchCoilTotalEnergySummary, fetchCoilTotalEnergyDetail } from '@/api/ems/energyCostReport'
import { fetchCoilTotalMerged } from '@/api/ems/energyCostReport'
import { exportCoilTotalMerged } from '@/api/ems/energyCostReport'
import { getStockpileCostList } from '@/api/wms/cost'
export default {
name: 'CoilTotalCost',
data() {
return {
queryParams: {
enterCoilNo: '',
currentCoilNo: '',
startDate: undefined,
endDate: undefined
},
energyQuery: {
pageNum: 1,
pageSize: 50
},
mergedQuery: {
pageNum: 1,
pageSize: 50
},
stockQuery: {
pageNum: 1,
pageSize: 50
},
energySummary: {
coilCount: 0,
totalDuration: 0,
totalConsumption: 0,
totalEnergyCost: 0
},
energyDetail: {
rows: [],
total: 0
},
mergedRows: [],
mergedTotal: 0,
stockDetail: {
rows: [],
total: 0
},
stockSummary: {
totalCost: 0,
todayCost: 0,
totalNetWeight: 0,
totalGrossWeight: 0
},
energyLoading: false,
stockLoading: false,
detailVisible: false,
detailEnterCoilNo: '',
detailEnergyRows: [],
detailStockRow: null
}
},
computed: {
totalCost() {
const energy = Number(this.energySummary.totalEnergyCost) || 0
const stock = Number(this.stockSummary.todayCost ?? this.stockSummary.totalCost) || 0
return energy + stock
}
},
mounted() {
this.handleSearch()
},
methods: {
handleSearch() {
this.energyQuery.pageNum = 1
this.mergedQuery.pageNum = 1
this.stockQuery.pageNum = 1
this.loadEnergy()
this.loadMerged()
this.loadStockDetail()
},
handleReset() {
this.queryParams = {
enterCoilNo: '',
currentCoilNo: '',
startDate: undefined,
endDate: undefined
}
this.energyQuery.pageNum = 1
this.mergedQuery.pageNum = 1
this.stockQuery.pageNum = 1
this.energySummary = { coilCount: 0, totalDuration: 0, totalConsumption: 0, totalEnergyCost: 0 }
this.energyDetail = { rows: [], total: 0 }
this.mergedRows = []
this.mergedTotal = 0
this.stockDetail = { rows: [], total: 0 }
this.stockSummary = { totalCost: 0, totalNetWeight: 0, totalGrossWeight: 0 }
this.handleSearch()
},
loadMerged() {
const params = {
...this.queryParams,
pageNum: this.mergedQuery.pageNum,
pageSize: this.mergedQuery.pageSize
}
fetchCoilTotalMerged(params).then(res => {
this.mergedRows = res.rows || []
this.mergedTotal = res.total || 0
})
},
loadEnergy() {
this.energyLoading = true
const params = { ...this.queryParams }
fetchCoilTotalEnergySummary(params).then(res => {
this.energySummary = res.data || this.energySummary
}).finally(() => {
this.loadEnergyDetail()
})
},
loadEnergyDetail() {
const params = {
...this.queryParams,
pageNum: this.energyQuery.pageNum,
pageSize: this.energyQuery.pageSize
}
fetchCoilTotalEnergyDetail(params).then(res => {
this.energyDetail = {
rows: res.rows || [],
total: res.total || 0
}
}).finally(() => {
this.energyLoading = false
})
},
loadStockDetail() {
this.stockLoading = true
const params = {
enterCoilNo: this.queryParams.enterCoilNo,
currentCoilNo: this.queryParams.currentCoilNo,
pageNum: this.stockQuery.pageNum,
pageSize: this.stockQuery.pageSize
}
getStockpileCostList(params).then(res => {
const data = res.data || {}
this.stockDetail = {
rows: data.rows || [],
total: data.total || 0
}
const summary = data.summary || {}
this.stockSummary = {
totalCost: summary.totalCost ?? 0,
todayCost: summary.todayCost ?? summary.totalCost ?? 0,
totalNetWeight: summary.totalNetWeight ?? 0,
totalGrossWeight: summary.totalGrossWeight ?? 0
}
}).finally(() => {
this.stockLoading = false
})
},
openDetail(row) {
if (!row || !row.enterCoilNo) return
this.detailEnterCoilNo = row.enterCoilNo
this.detailVisible = true
this.loadDetailEnergy(row.enterCoilNo)
this.loadDetailStock(row.enterCoilNo)
},
loadDetailEnergy(enterCoilNo) {
const params = {
enterCoilNo,
pageNum: 1,
pageSize: 200
}
fetchCoilTotalEnergyDetail(params).then(res => {
this.detailEnergyRows = res.rows || []
})
},
loadDetailStock(enterCoilNo) {
const params = {
enterCoilNo,
pageNum: 1,
pageSize: 1
}
getStockpileCostList(params).then(res => {
const data = res.data || {}
const rows = data.rows || []
this.detailStockRow = rows.find(item => item.enterCoilNo === enterCoilNo) || null
})
},
exportSummary() {
const params = { ...this.queryParams }
exportCoilTotalMerged(params).then(res => {
const blob = new Blob([res], { type: 'application/vnd.ms-excel' })
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = 'coil_cost_summary.xlsx'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(url)
})
},
formatNumber(val, digits = 2) {
const num = Number(val)
if (isNaN(num)) return '0'.padEnd(2 + digits, '0')
return num.toFixed(digits)
},
formatTime(val) {
if (!val) return '-'
const date = new Date(val)
if (Number.isNaN(date.getTime())) return val
return date.toLocaleString('zh-CN')
},
formatStatus(status) {
const map = { 0: '在产', 1: '在产', 2: '完成' }
return map[status] || '-'
}
}
}
</script>
<style scoped lang="scss">
.coil-total-cost-page {
padding: 20px;
.search-card {
margin-bottom: 20px;
}
.summary-row {
margin-bottom: 20px;
.summary-card {
background: #fff;
border: 1px solid #ebeef5;
border-radius: 6px;
padding: 16px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.04);
.label {
font-size: 13px;
color: #909399;
}
.value {
margin-top: 8px;
font-size: 26px;
font-weight: 700;
color: #303133;
}
.desc {
margin-top: 6px;
font-size: 12px;
color: #909399;
}
&.highlight {
background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
border-color: #409eff;
}
}
}
.block-card {
margin-bottom: 20px;
.card-title {
font-weight: 600;
font-size: 16px;
}
.sub {
margin-left: 12px;
color: #909399;
font-size: 12px;
}
.cost {
color: #f56c6c;
font-weight: 600;
}
}
}
</style>

View File

@@ -1,572 +0,0 @@
<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

@@ -88,24 +88,24 @@
</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">
<el-table-column prop="energyTypeName" label="能源类型" ></el-table-column>
<el-table-column prop="consumption" label="总消耗量" >
<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">
<el-table-column prop="consumptionUnit" label="单位" ></el-table-column>
<el-table-column prop="cost" label="总成本" >
<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">
<el-table-column prop="percentage" label="成本占比">
<template slot-scope="scope">
{{ formatNumber(scope.row.percentage, 2) }}%
</template>
</el-table-column>
<el-table-column prop="averageUnitCost" label="单位成本" width="120">
<el-table-column prop="averageUnitCost" label="单位成本" >
<template slot-scope="scope">
¥ {{ formatNumber(scope.row.averageUnitCost, 4) }}/单位
</template>
@@ -117,44 +117,77 @@
<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>
<span class="count">( {{ total }} )</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">
<el-table-column prop="coilCode" label="钢卷编号" ></el-table-column>
<el-table-column prop="startTime" label="开始时间" >
<template slot-scope="scope">
{{ formatTime(scope.row.startTime) }}
</template>
</el-table-column>
<el-table-column prop="endTime" label="完成时间" width="180">
<el-table-column prop="endTime" label="完成时间" >
<template slot-scope="scope">
{{ formatTime(scope.row.endTime) }}
</template>
</el-table-column>
<el-table-column prop="duration" label="生产时长" width="100">
<el-table-column prop="duration" label="生产时长">
<template slot-scope="scope">
{{ formatNumber(scope.row.duration, 1) }}h
</template>
</el-table-column>
<el-table-column prop="totalCost" label="能源成本" width="120">
<el-table-column prop="totalCost" label="能源成本">
<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">
<el-table-column label="操作"fixed="right">
<template slot-scope="scope">
<el-button type="primary" size="mini" @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="handleQuery"
/>
</el-card>
<!-- 钢卷详情弹窗 -->
<el-dialog
title="钢卷详情"
:visible.sync="coilDetailVisible"
width="600px"
:close-on-click-modal="false">
<el-skeleton :loading="coilDetailLoading" animated>
<el-descriptions :column="2" size="small" border>
<el-descriptions-item label="钢卷ID">{{ coilDetail.coilId || '-' }}</el-descriptions-item>
<el-descriptions-item label="状态">{{ formatActionStatus(coilDetail.actionStatus) }}</el-descriptions-item>
<el-descriptions-item label="入场卷号">{{ coilDetail.enterCoilNo || '-' }}</el-descriptions-item>
<el-descriptions-item label="当前卷号">{{ coilDetail.currentCoilNo || '-' }}</el-descriptions-item>
<el-descriptions-item label="库区ID">{{ coilDetail.warehouseId || '-' }}</el-descriptions-item>
<el-descriptions-item label="生产时长">{{ formatNumber(coilDetail.duration, 2) }} h</el-descriptions-item>
<el-descriptions-item label="分摊能耗">{{ formatNumber(coilDetail.consumption, 2) }}</el-descriptions-item>
<el-descriptions-item label="分摊成本">¥ {{ formatNumber(coilDetail.totalCost, 2) }}</el-descriptions-item>
<el-descriptions-item label="开始时间" :span="2">{{ formatTime(coilDetail.startTime) }}</el-descriptions-item>
<el-descriptions-item label="完成时间" :span="2">{{ formatTime(coilDetail.endTime) }}</el-descriptions-item>
</el-descriptions>
</el-skeleton>
<span slot="footer" class="dialog-footer">
<el-button @click="coilDetailVisible = false"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import * as echarts from 'echarts'
import { listEnergyCoilDaily, listWarehouse } from '@/api/ems/energyAllocation'
import * as echarts from 'echarts';
import { listWarehouse } from '@/api/ems/energyAllocation';
import { listWarehouseProductionStats, listWarehouseProductionDetail, getWarehouseProductionCoilDetail } from '@/api/ems/warehouseProduction';
export default {
name: 'WarehouseProduction',
@@ -162,11 +195,14 @@ export default {
return {
loading: false,
queryParams: {
logicWarehouseId: '',
startDate: '',
endDate: ''
logicWarehouseId: undefined,
startDate: undefined,
endDate: undefined,
pageNum: 1,
pageSize: 15
},
warehouseList: [],
total: 0,
statistics: {
coilCount: 0,
totalDuration: 0,
@@ -176,7 +212,10 @@ export default {
},
energyBreakdown: [],
coilList: [],
chartInstances: {}
chartInstances: {},
coilDetailVisible: false,
coilDetailLoading: false,
coilDetail: {}
};
},
mounted() {
@@ -185,181 +224,47 @@ export default {
},
methods: {
loadWarehouseList() {
// 调用后端API获取库区列表
listWarehouse().then(response => {
this.warehouseList = response.data || [];
if (this.warehouseList.length === 0) {
this.$message.warning('暂无库区数据');
}
listWarehouse().then(res => {
this.warehouseList = res.data || [];
}).catch(() => {
this.$message.error('加载库区列表失败');
// 加载模拟数据作为备用
this.warehouseList = [
{ id: 1, name: '库区A' },
{ id: 2, name: '库区B' },
{ id: 3, name: '库区C' }
];
this.warehouseList = [];
});
},
handleQuery() {
this.loading = true;
// 调用后端API获取库区生产统计
listEnergyCoilDaily(this.queryParams).then(response => {
const data = response.rows || [];
this.processWarehouseData(data);
this.initCharts();
Promise.all([
listWarehouseProductionStats(this.queryParams),
listWarehouseProductionDetail(this.queryParams)
]).then(([statRes, detailRes]) => {
const stat = statRes.data || {};
this.statistics = {
coilCount: stat.coilCount || 0,
totalDuration: stat.totalDuration || 0,
totalConsumption: stat.totalConsumption || 0,
totalCost: stat.totalCost || 0,
unitCost: stat.unitCost || 0
};
this.energyBreakdown = (stat.energyBreakdownList || []).map(item => ({
...item,
percentage: item.percentage || 0,
averageUnitCost: item.averageUnitCost || 0
}));
this.coilList = detailRes.rows || [];
this.total = detailRes.total || 0;
this.$nextTick(() => this.initCharts());
}).catch(() => {
this.$message.error('加载库区生产统计失败');
// 加载模拟数据作为备用
this.loadMockData();
this.$message.error('加载数据失败');
}).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: ''
logicWarehouseId: undefined,
startDate: undefined,
endDate: undefined,
pageNum: 1,
pageSize: 15
};
this.handleQuery();
},
@@ -368,94 +273,67 @@ export default {
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'
},
const dom = document.getElementById('energyCostPieChart');
if (!dom) return;
const ins = echarts.init(dom);
this.chartInstances.cost = ins;
ins.setOption({
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)'
}
}
data: this.energyBreakdown.map(i => ({ value: i.cost, name: i.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'
},
const dom = document.getElementById('energyConsumptionPieChart');
if (!dom) return;
const ins = echarts.init(dom);
this.chartInstances.consumption = ins;
ins.setOption({
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)'
}
}
data: this.energyBreakdown.map(i => ({ value: i.consumption, name: i.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 }
if (!row || !row.coilId) {
this.$message.warning('未找到钢卷ID');
return;
}
this.coilDetailVisible = true;
this.coilDetailLoading = true;
getWarehouseProductionCoilDetail(row.coilId).then(res => {
this.coilDetail = res.data || {};
}).finally(() => {
this.coilDetailLoading = false;
});
},
formatNumber(value, decimals = 2) {
const num = Number(value) || 0;
return num.toFixed(decimals);
},
formatTime(value) {
if (!value) return '-';
return value;
},
formatActionStatus(val) {
const map = { 0: '待上料', 1: '在产', 2: '完成' };
return map[val] || '-';
}
},
beforeDestroy() {

View File

@@ -191,7 +191,7 @@
</el-dialog>
<!-- 库区详情弹窗 -->
<el-dialog :title="`库区详情 - ${selectedWarehouse ? selectedWarehouse.warehouseName : ''}`" :visible.sync="warehouseDetailVisible" width="800px" append-to-body>
<el-dialog :title="`库区详情 - ${selectedWarehouse ? selectedWarehouse.warehouseName : ''}`" :visible.sync="warehouseDetailVisible" width="1000px" append-to-body>
<div v-if="selectedWarehouse" class="warehouse-detail">
<!-- 库区基本信息 -->
<div class="detail-section">
@@ -212,12 +212,66 @@
</el-row>
</div>
<!-- 可用设备卡片列表 -->
<div class="detail-section" v-if="availableMeters.length > 0">
<h3>可绑定设备 ({{ availableMeters.length }})</h3>
<div class="meter-cards-grid">
<div
v-for="meter in paginatedMeters"
:key="meter.meterId"
class="meter-card"
@click="bindMeterToWarehouse(meter)"
>
<div class="card-header">
<div class="meter-code">{{ meter.meterCode }}</div>
<el-tag type="success" size="small">{{ getEnergyName(meter.energyTypeId) }}</el-tag>
</div>
<div class="card-body">
<div class="info-row">
<span class="label">型号</span>
<span class="value">{{ meter.model || '-' }}</span>
</div>
<div class="info-row">
<span class="label">制造商</span>
<span class="value">{{ meter.manufacturer || '-' }}</span>
</div>
<div class="info-row">
<span class="label">安装日期</span>
<span class="value">{{ meter.installDate ? meter.installDate.substring(0, 10) : '-' }}</span>
</div>
<div class="info-row">
<span class="label">状态</span>
<span class="value">{{ meter.status === 1 ? '正常' : '异常' }}</span>
</div>
</div>
<div class="card-footer">
<el-button type="primary" size="small" @click.stop="bindMeterToWarehouse(meter)">绑定</el-button>
</div>
</div>
</div>
<!-- 分页 -->
<div class="pagination-container" v-if="meterTotalPages > 1">
<el-pagination
:current-page.sync="meterPageNum"
:page-size="meterPageSize"
:total="availableMeters.length"
layout="prev, pager, next"
@current-change="meterPageNum = $event"
/>
</div>
</div>
<!-- 无可用设备提示 -->
<div class="detail-section" v-else>
<el-empty description="暂无可绑定设备"></el-empty>
</div>
<!-- 绑定设备列表 -->
<div class="detail-section">
<div class="section-header">
<h3>绑定设备 ({{ getWarehouseDevices(selectedWarehouse.warehouseId).length }})</h3>
<div class="section-actions">
<el-button type="primary" size="small" @click="handleBindDevice(selectedWarehouse)">添加设备</el-button>
<el-button
v-if="selectedBindings.length > 0"
type="danger"
@@ -237,7 +291,7 @@
</el-button>
</div>
</div>
<el-table
:data="getWarehouseDevices(selectedWarehouse.warehouseId)"
size="small"
@@ -245,24 +299,14 @@
@selection-change="selectedBindings = $event"
>
<el-table-column type="selection" width="50" />
<el-table-column prop="meterCode" label="设备编号" width="150" />
<el-table-column label="能源类型" width="120">
<el-table-column prop="meterCode" label="设备编号" />
<el-table-column label="能源类型">
<template slot-scope="scope">
{{ getEnergyName(scope.row.energyTypeId) }}
</template>
</el-table-column>
<el-table-column prop="model" label="型号" width="120" />
<el-table-column prop="manufacturer" label="制造商" width="120" />
<el-table-column label="分摊模式" width="120">
<template slot-scope="scope">
{{ getModeLabel(scope.row.allocationMode) }}
</template>
</el-table-column>
<el-table-column label="权重" width="80">
<template slot-scope="scope">
{{ scope.row.weightRatio || '-' }}
</template>
</el-table-column>
<el-table-column prop="model" label="型号" />
<el-table-column prop="manufacturer" label="制造商" />
<el-table-column label="操作" width="100">
<template slot-scope="scope">
<el-button type="text" size="small" @click="handleEditBinding(scope.row)">编辑</el-button>
@@ -274,43 +318,9 @@
</div>
</el-dialog>
<!-- 绑定设备弹窗 -->
<el-dialog title="绑定设备" :visible.sync="bindDeviceDialogVisible" width="600px" append-to-body>
<div v-if="currentWarehouse" class="bind-device-form">
<el-form :model="bindForm" :rules="bindRules" ref="bindForm" label-width="100px">
<el-form-item label="选择设备" prop="meterId">
<el-select v-model="bindForm.meterId" placeholder="请选择设备" clearable>
<el-option
v-for="meter in availableMeters"
:key="meter.meterId"
:label="`${meter.meterCode} (${getEnergyName(meter.energyTypeId)})`"
:value="meter.meterId"
/>
</el-select>
</el-form-item>
<el-form-item label="分摊模式" prop="allocationMode">
<el-select v-model="bindForm.allocationMode">
<el-option label="重量 × 时长" value="weight_time" />
<el-option label="重量" value="weight" />
<el-option label="数量" value="count" />
</el-select>
</el-form-item>
<el-form-item label="权重系数" prop="weightRatio">
<el-input-number v-model="bindForm.weightRatio" :min="0" :step="0.1" />
</el-form-item>
<el-form-item label="仅待操作">
<el-switch v-model="bindForm.requirePendingAction" :active-value="1" :inactive-value="0" />
</el-form-item>
</el-form>
</div>
<div slot="footer" class="dialog-footer">
<el-button :loading="bindLoading" type="primary" @click="submitBindDevice"> </el-button>
<el-button @click="bindDeviceDialogVisible = false"> </el-button>
</div>
</el-dialog>
<!-- 编辑绑定弹窗 -->
<el-dialog title="编辑绑定参数" :visible.sync="editBindingDialogVisible" width="500px" append-to-body>
<el-dialog title="编辑绑定参数" :visible.sync="editBindingDialogVisible" width="500px" append-to-body">
<div v-if="editingBinding" class="edit-binding-form">
<div class="binding-info">
<div class="info-row">
@@ -323,25 +333,10 @@
</div>
</div>
<el-divider />
<el-form :model="editBindingForm" :rules="editBindingRules" ref="editBindingForm" label-width="100px">
<el-form-item label="分摊模式" prop="allocationMode">
<el-select v-model="editBindingForm.allocationMode">
<el-option label="重量 × 时长" value="weight_time" />
<el-option label="重量" value="weight" />
<el-option label="数量" value="count" />
</el-select>
</el-form-item>
<el-form-item label="权重系数" prop="weightRatio">
<el-input-number v-model="editBindingForm.weightRatio" :min="0" :step="0.1" />
</el-form-item>
<el-form-item label="仅待操作">
<el-switch v-model="editBindingForm.requirePendingAction" :active-value="1" :inactive-value="0" />
</el-form-item>
</el-form>
<div class="info-text">该绑定关系已保存无需编辑参数</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button :loading="bindLoading" type="primary" @click="submitEditBinding"> </el-button>
<el-button @click="editBindingDialogVisible = false"> </el-button>
<el-button @click="editBindingDialogVisible = false"> </el-button>
</div>
</el-dialog>
</div>
@@ -352,6 +347,7 @@ import { listMeter, getMeter, delMeter, addMeter, updateMeter } from "@/api/ems/
import { listEnergyType } from "@/api/ems/energyType";
import { listWarehouse } from '@/api/wms/warehouse'
import { fetchEnergyLinkMatrix, addEnergyLink, updateEnergyLink, deleteEnergyLink, batchDeleteEnergyLinks, exportEnergyLinks, getStatistics } from '@/api/ems/energyLink'
import { getLatestMeterReadTime } from '@/api/ems/energyConsumption'
export default {
name: "MeterNew",
@@ -405,50 +401,38 @@ export default {
warehouseDetailVisible: false,
selectedWarehouse: null,
// 绑定设备
bindDeviceDialogVisible: false,
currentWarehouse: null,
bindForm: {
meterId: undefined,
allocationMode: 'weight_time',
weightRatio: 1,
requirePendingAction: 1
},
bindRules: {
meterId: [{ required: true, message: '请选择设备', trigger: 'change' }],
allocationMode: [{ required: true, message: '请选择分摊模式', trigger: 'change' }],
weightRatio: [{ required: true, message: '请输入权重系数', trigger: 'blur' }]
},
// 设备卡片分页
meterPageNum: 1,
meterPageSize: 10,
// 编辑绑定
editBindingDialogVisible: false,
editingBinding: null,
editBindingForm: {
allocationMode: 'weight_time',
weightRatio: 1,
requirePendingAction: 1
},
editBindingRules: {
allocationMode: [{ required: true, message: '请选择分摊模式', trigger: 'change' }],
weightRatio: [{ required: true, message: '请输入权重系数', trigger: 'blur' }]
},
editBindingForm: {},
editBindingRules: {},
// 库区搜索
warehouseSearchKeyword: '',
// 批量操作
selectedBindings: [],
batchDeleteLoading: false
batchDeleteLoading: false,
// 最近抄表时间
latestMeterReadTime: {
latestMeterReadStartTime: null,
latestMeterReadEndTime: null
}
};
},
computed: {
availableMeters() {
// 返回未绑定到当前库区的设备
if (!this.currentWarehouse) return [];
if (!this.selectedWarehouse) return [];
const boundMeterIds = new Set();
(this.matrixData || []).forEach(wh => {
if (wh.warehouseId === this.currentWarehouse.warehouseId) {
if (wh.warehouseId === this.selectedWarehouse.warehouseId) {
(wh.links || []).forEach(link => {
if (link.meterId) boundMeterIds.add(link.meterId);
});
@@ -457,6 +441,18 @@ export default {
return this.meterList.filter(m => !boundMeterIds.has(m.meterId));
},
// 分页后的设备列表
paginatedMeters() {
const start = (this.meterPageNum - 1) * this.meterPageSize;
const end = start + this.meterPageSize;
return this.availableMeters.slice(start, end);
},
// 设备总页数
meterTotalPages() {
return Math.ceil(this.availableMeters.length / this.meterPageSize);
},
filteredWarehouses() {
// 库区搜索过滤
if (!this.warehouseSearchKeyword.trim()) {
@@ -476,6 +472,7 @@ export default {
this.loadWarehouseList();
this.loadMatrix();
this.loadStatistics();
this.loadLatestMeterReadTime();
},
methods: {
@@ -570,14 +567,6 @@ export default {
return energy ? energy.name : '-';
},
getModeLabel(mode) {
const modeDict = {
weight_time: '重量 × 时长',
weight: '按重量',
count: '按数量'
};
return modeDict[mode] || mode;
},
handleQuery() {
this.queryParams.pageNum = 1;
@@ -628,78 +617,35 @@ export default {
this.warehouseDetailVisible = true;
},
handleBindDevice(warehouse) {
this.currentWarehouse = warehouse;
this.bindForm = {
meterId: undefined,
allocationMode: 'weight_time',
weightRatio: 1,
requirePendingAction: 1
bindMeterToWarehouse(meter) {
if (!this.selectedWarehouse) {
this.$message.error('请先选择库区');
return;
}
const payload = {
warehouseId: this.selectedWarehouse.warehouseId,
meterId: meter.meterId,
energyTypeId: meter.energyTypeId
};
this.bindDeviceDialogVisible = true;
},
submitBindDevice() {
this.$refs.bindForm.validate(valid => {
if (!valid) return;
const { meterId, allocationMode, weightRatio, requirePendingAction } = this.bindForm;
const meter = this.meterList.find(m => m.meterId === meterId);
if (!meter) {
this.$message.error('设备不存在');
return;
}
const payload = {
warehouseId: this.currentWarehouse.warehouseId,
meterId: meter.meterId,
energyTypeId: meter.energyTypeId,
allocationMode,
weightRatio,
requirePendingAction
};
this.bindLoading = true;
addEnergyLink(payload)
.then(() => {
this.$modal.msgSuccess('绑定成功');
this.bindDeviceDialogVisible = false;
this.loadMatrix();
})
.finally(() => {
this.bindLoading = false;
});
});
this.bindLoading = true;
addEnergyLink(payload)
.then(() => {
this.$modal.msgSuccess('绑定成功');
this.loadMatrix();
})
.finally(() => {
this.bindLoading = false;
});
},
handleEditBinding(binding) {
this.editingBinding = binding;
this.editBindingForm = {
allocationMode: binding.allocationMode || 'weight_time',
weightRatio: binding.weightRatio || 1,
requirePendingAction: binding.requirePendingAction || 1
};
this.editBindingForm = {};
this.editBindingDialogVisible = true;
},
submitEditBinding() {
this.$refs.editBindingForm.validate(valid => {
if (!valid) return;
const { allocationMode, weightRatio, requirePendingAction } = this.editBindingForm;
const payload = {
linkId: this.editingBinding.linkId,
allocationMode,
weightRatio,
requirePendingAction
};
this.bindLoading = true;
updateEnergyLink(payload)
.then(() => {
this.$modal.msgSuccess('更新成功');
this.editBindingDialogVisible = false;
this.loadMatrix();
})
.finally(() => {
this.bindLoading = false;
});
});
this.editBindingDialogVisible = false;
},
handleDeleteBinding(binding) {
@@ -795,6 +741,20 @@ export default {
.catch(() => {
this.$message.error('绑定失败');
});
},
loadLatestMeterReadTime() {
getLatestMeterReadTime().then(response => {
if (response && response.data) {
this.latestMeterReadTime = {
latestMeterReadStartTime: response.data.startTime,
latestMeterReadEndTime: response.data.endTime
};
console.log('最近一次抄表时间范围:', this.latestMeterReadTime);
}
}).catch(() => {
console.warn('获取最近一次抄表时间失败');
});
}
}
};
@@ -1150,6 +1110,85 @@ export default {
padding: 20px 0;
}
.meter-cards-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 16px;
margin-bottom: 20px;
.meter-card {
border: 1px solid #e4e7eb;
border-radius: 8px;
padding: 16px;
background: #fff;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
flex-direction: column;
&:hover {
border-color: #409eff;
box-shadow: 0 2px 12px rgba(64, 158, 255, 0.2);
transform: translateY(-2px);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 1px solid #f0f0f0;
.meter-code {
font-size: 16px;
font-weight: 600;
color: #303133;
flex: 1;
word-break: break-word;
}
}
.card-body {
flex: 1;
margin-bottom: 12px;
.info-row {
display: flex;
justify-content: space-between;
align-items: flex-start;
padding: 6px 0;
font-size: 13px;
.label {
color: #909399;
min-width: 70px;
}
.value {
color: #303133;
text-align: right;
flex: 1;
word-break: break-word;
}
}
}
.card-footer {
display: flex;
gap: 8px;
justify-content: flex-end;
}
}
}
.pagination-container {
display: flex;
justify-content: center;
padding: 20px 0;
border-top: 1px solid #f0f0f0;
}
.fade-enter-active, .fade-leave-active {
transition: opacity 0.3s;
}