Files
klp-oa/klp-ui/src/views/ems/table/index.vue
砂糖 9967d8be46 feat(ems): 添加产线信息显示和视图切换功能
refactor(dashboard): 移除过时的趋势分析组件

- 在仪表盘表格中添加产线信息显示
- 在设备管理页面增加卡片/表格视图切换功能
- 删除不再使用的趋势分析组件
2026-04-30 13:06:14 +08:00

441 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="ems-table-container">
<div class="left-panel">
<h3>能源类型</h3>
<el-tree v-loading="leftLoading" :data="energyTypes" node-key="energyTypeId" @node-click="handleEnergyTypeSelect" :props="treeProps" />
</div>
<div class="right-panel" v-loading="rightLoading">
<div v-if="!selectedEnergyType" class="empty-state">
<el-empty description="请选择能源类型" />
</div>
<div v-else-if="!meters.length" class="empty-state">
<el-empty description="该能源类型下暂无设备,请先添加设备">
<el-button type="primary" @click="handleAddMeter">添加设备</el-button>
</el-empty>
</div>
<div v-else>
<!-- 增加日期筛选默认选中当前月份用户可以切换月份 -->
<div style="margin-bottom: 16px; display: flex; align-items: center; flex-wrap: wrap; gap: 10px;">
<el-date-picker
v-model="selectedMonth"
type="month"
placeholder="选择月份"
format="yyyy-MM"
value-format="yyyy-MM"
@change="handleMonthChange"
style="width: 150px;"
/>
<el-button icon="el-icon-refresh" @click="handleRefresh">刷新</el-button>
</div>
<!-- 配置选项 -->
<div style="margin-bottom: 16px; padding: 10px; background: #f5f7fa; border-radius: 4px;">
<el-checkbox v-model="configOptions.showSumRow">显示求和行</el-checkbox>
<el-checkbox v-model="configOptions.sumSubMeters" style="margin-left: 20px;">对分表求和排除总表</el-checkbox>
<el-checkbox v-model="configOptions.showError" style="margin-left: 20px;" :disabled="!canShowError">显示误差计算</el-checkbox>
</div>
<el-table :data="tableData" style="width: 100%" border :show-summary="configOptions.showSumRow" :summary-method="getSummaries">
<el-table-column prop="date" label="日期" width="120" />
<el-table-column v-for="meter in meters" :key="meter.meterId" :prop="`meter_${meter.meterId}`"
:label="meter.meterCode + '(' + (meter.productionLine || '暂无') + ')'">
<template slot-scope="scope">
<input v-model="scope.row[`meter_${meter.meterId}`]" class="nob"
@change="(e) => handleCellChange(scope.row, meter.meterId, e)" />
</template>
</el-table-column>
<!-- 分表求和列 -->
<el-table-column v-if="configOptions.sumSubMeters" label="分表合计" width="120">
<template slot-scope="scope">
{{ getSubMetersSum(scope.row).toFixed(2) }}
</template>
</el-table-column>
<!-- 误差列 -->
<el-table-column v-if="configOptions.showError && canShowError" label="误差" width="120">
<template slot-scope="scope">
{{ getRowError(scope.row).toFixed(2) }}
</template>
</el-table-column>
<!-- 误差率列 -->
<el-table-column v-if="configOptions.showError && canShowError" label="误差率" width="120">
<template slot-scope="scope">
{{ getRowErrorPercent(scope.row).toFixed(2) }}%
</template>
</el-table-column>
</el-table>
</div>
</div>
</div>
</template>
<script>
import { listEnergyType } from "@/api/ems/energyType";
import { listMeter } from "@/api/ems/meter";
import { listEnergyRecord, updateEnergyRecord, addEnergyRecord } from "@/api/ems/energyRecord";
import dayjs from "dayjs";
export default {
name: "EnergyTable",
data() {
// 初始化当前月份
const currentMonth = dayjs().format("YYYY-MM");
return {
energyTypes: [],
selectedEnergyType: null,
meters: [],
tableData: [],
treeProps: {
children: "children",
label: "name"
},
leftLoading: false,
rightLoading: false,
selectedMonth: currentMonth,
originalData: [], // 用于存储原始数据
configOptions: {
showSumRow: false,
sumSubMeters: false,
showError: false
},
sumRowData: {} // 存储求和行数据
};
},
computed: {
// 判断是否可以显示误差计算(需要有且仅有一个总表)
canShowError() {
const totalMeters = this.meters.filter(m => m.isTotalMeter === 1);
return totalMeters.length === 1;
}
},
mounted() {
this.loadEnergyTypes();
},
methods: {
// 加载能源类型列表
async loadEnergyTypes() {
this.leftLoading = true;
const res = await listEnergyType({ pageSize: 999 });
this.energyTypes = res.rows || [];
this.leftLoading = false;
},
// 刷新数据
async handleRefresh() {
if (this.selectedEnergyType) {
this.rightLoading = true;
await this.loadEnergyRecords();
this.rightLoading = false;
}
},
// 处理能源类型选择
async handleEnergyTypeSelect(data) {
this.selectedEnergyType = data.energyTypeId;
this.rightLoading = true;
await this.loadMeters();
await this.loadEnergyRecords();
this.rightLoading = false;
},
// 处理月份变化
async handleMonthChange() {
if (this.selectedEnergyType) {
this.rightLoading = true;
await this.loadEnergyRecords();
this.rightLoading = false;
}
},
// 加载设备列表
async loadMeters() {
const res = await listMeter({ energyTypeId: this.selectedEnergyType });
this.meters = res.rows || [];
},
// 加载能源记录
async loadEnergyRecords() {
// 使用 dayjs 获取指定月份的第一天和最后一天
const startDate = dayjs(this.selectedMonth).startOf('month').format('YYYY-MM-DD') + ' 00:00:00';
const endDate = dayjs(this.selectedMonth).endOf('month').format('YYYY-MM-DD') + ' 23:59:59';
const res = await listEnergyRecord({
energyTypeId: this.selectedEnergyType,
recordStartDate: startDate,
recordEndDate: endDate
});
// 存储原始数据
this.originalData = res.rows || [];
this.processTableData(res.rows || []);
},
// 处理表格数据
processTableData(records) {
// 按日期分组
const dateMap = new Map();
records.forEach(record => {
if (!dateMap.has(record.recordDate)) {
dateMap.set(record.recordDate, {
date: record.recordDate,
// 存储能源记录ID映射
energyRecordIds: {}
});
}
dateMap.get(record.recordDate)[`meter_${record.meterId}`] = record.consumption;
dateMap.get(record.recordDate).energyRecordIds[`meter_${record.meterId}`] = record.energyRecordId;
});
// 生成当月的所有日期
const monthDates = this.generateMonthDates(this.selectedMonth);
// 为每个日期创建表格行
this.tableData = monthDates.map(date => {
const row = {
date: date,
energyRecordIds: {}
};
// 为每个表计添加默认值
this.meters.forEach(meter => {
if (dateMap.has(date)) {
row[`meter_${meter.meterId}`] = dateMap.get(date)[`meter_${meter.meterId}`] || 0;
row.energyRecordIds[`meter_${meter.meterId}`] = dateMap.get(date).energyRecordIds?.[`meter_${meter.meterId}`] || null;
} else {
row[`meter_${meter.meterId}`] = 0;
row.energyRecordIds[`meter_${meter.meterId}`] = null;
}
});
return row;
});
// 按日期升序排序从1日到30日
this.tableData.sort((a, b) => {
return new Date(a.date) - new Date(b.date);
});
},
// 生成当月的所有日期
generateMonthDates(month) {
const dates = [];
const start = dayjs(month).startOf('month');
const end = dayjs(month).endOf('month');
let current = start;
while (current.isBefore(end) || current.isSame(end, 'day')) {
dates.push(current.format('YYYY-MM-DD'));
current = current.add(1, 'day');
}
return dates;
},
// 初始化空记录
initEmptyRecord() {
const today = new Date().toISOString().split('T')[0];
const emptyRecord = {
date: today,
energyRecordIds: {}
};
this.meters.forEach(meter => {
emptyRecord[`meter_${meter.meterId}`] = 0;
emptyRecord.energyRecordIds[`meter_${meter.meterId}`] = null;
});
this.tableData.push(emptyRecord);
},
// 添加空记录
addEmptyRecord() {
const today = new Date().toISOString().split('T')[0];
const emptyRecord = {
date: today,
energyRecordIds: {}
};
this.meters.forEach(meter => {
emptyRecord[`meter_${meter.meterId}`] = '';
emptyRecord.energyRecordIds[`meter_${meter.meterId}`] = null;
});
this.tableData.unshift(emptyRecord);
},
// 处理单元格变化
async handleCellChange(row, meterId) {
// 查找是否存在日期和表计ID都对应的记录
const existingRecord = this.tableData.find(r => r.date === row.date && r.energyRecordIds?.[`meter_${meterId}`]);
const value = row[`meter_${meterId}`];
const energyRecordId = existingRecord?.energyRecordIds?.[`meter_${meterId}`] || null;
// 构建记录对象,匹配后端实体类
const record = {
energyRecordId: energyRecordId,
meterId: meterId,
energyId: this.selectedEnergyType,
consumption: value,
recordDate: row.date
};
try {
if (energyRecordId) {
// 存在记录ID调用修改接口
await updateEnergyRecord(record);
} else {
// 不存在记录ID调用新增接口
const { data: newRecordId } = await addEnergyRecord(record);
this.originalData.push({
...record,
energyRecordId: newRecordId
});
// 重新整理原始数据
this.processTableData(this.originalData);
// 将新的ID存储用于后续修改
// this.tableData.push({
// ...record,
// energyRecordId: newRecordId
// });
}
this.$message.success('保存成功');
} catch (error) {
console.error('保存失败:', error);
this.$message.error('保存失败');
// 恢复原值
this.loadEnergyRecords();
}
},
// 处理添加设备点击
handleAddMeter() {
this.$router.push({ path: '/ems/meter/manage' });
},
// 计算一行中所有分表的和
getSubMetersSum(row) {
const subMeters = this.meters.filter(m => m.isTotalMeter !== 1);
return subMeters.reduce((sum, meter) => {
const value = Number(row[`meter_${meter.meterId}`]) || 0;
return sum + value;
}, 0);
},
// 计算一行的误差(总表 - 分表合计)
getRowError(row) {
if (!this.canShowError) return 0;
const totalMeter = this.meters.find(m => m.isTotalMeter === 1);
const totalValue = Number(row[`meter_${totalMeter.meterId}`]) || 0;
const subMetersSum = this.getSubMetersSum(row);
return totalValue - subMetersSum;
},
// 计算一行的误差百分比
getRowErrorPercent(row) {
if (!this.canShowError) return 0;
const totalMeter = this.meters.find(m => m.isTotalMeter === 1);
const totalValue = Number(row[`meter_${totalMeter.meterId}`]) || 0;
if (totalValue === 0) return 0;
const error = this.getRowError(row);
return (error / totalValue) * 100;
},
// 计算表格汇总行
getSummaries(param) {
const { columns, data } = param;
const sums = [];
// 如果不显示求和行,返回空数组
if (!this.configOptions.showSumRow) {
return sums;
}
columns.forEach((column, index) => {
if (index === 0) {
sums[index] = '合计';
return;
}
// 获取当前列对应的表计
const prop = column.property;
if (prop && prop.startsWith('meter_')) {
// 计算该列的和
const values = data.map(item => Number(item[prop]) || 0);
const sum = values.reduce((prev, curr) => prev + curr, 0);
sums[index] = sum.toFixed(2);
return;
}
// 分表合计列的汇总
if (column.label === '分表合计' && this.configOptions.sumSubMeters) {
const sum = data.reduce((total, row) => total + this.getSubMetersSum(row), 0);
sums[index] = sum.toFixed(2);
return;
}
// 误差列的汇总
if (column.label === '误差' && this.configOptions.showError && this.canShowError) {
const sum = data.reduce((total, row) => total + this.getRowError(row), 0);
sums[index] = sum.toFixed(2);
return;
}
// 误差率列不进行汇总
if (column.label === '误差率') {
sums[index] = '-';
return;
}
sums[index] = '';
});
return sums;
},
}
};
</script>
<style scoped>
.ems-table-container {
display: flex;
height: 100%;
}
.left-panel {
width: 200px;
border-right: 1px solid #e6e6e6;
padding: 16px;
overflow-y: auto;
}
.right-panel {
flex: 1;
padding: 16px;
overflow-y: auto;
}
.empty-state {
display: flex;
justify-content: center;
align-items: center;
height: 400px;
}
.nob {
border: none;
outline: none;
margin: 0;
padding: 0;
width: 100%;
height: 100%;
line-height: 23px;
}
.nob:focus {
border: 1px dotted black;
}
</style>