ether-admin/src/views/energy/ConsumptionRecord.vue

417 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

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

<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { Button, Select, Space, message, Card, Statistic, Row, Col, Table, DatePicker, InputNumber, Form } from 'ant-design-vue'
import type { ColumnsType } from 'ant-design-vue/es/table'
import { ReloadOutlined } from '@ant-design/icons-vue'
import {
getEnergyMeters,
getEnergyMeter,
recordEnergyConsumption,
getEnergyConsumption,
type EnergyMeter,
type EnergyConsumption
} from '@/api/energy'
import { getProjectSelectorList } from '@/api/project'
// 能源类型映射
const energyTypeMap: Record<string, string> = {
ELECTRICITY: '电力',
WATER: '水',
GAS: '燃气',
CENTRAL_HEATING: '集中供热',
CENTRAL_COOLING: '集中供冷'
}
// 项目选择选项
const projectOptions = ref<{ value: string; label: string }[]>([])
// 计量点选项
const meterOptions = ref<{ value: string; label: string; energyType: string }[]>([])
// 查询参数
const queryParams = reactive({
projectId: '',
meterId: ''
})
// 数据状态
const loading = ref(false)
const recordLoading = ref(false)
const consumptionList = ref<EnergyConsumption[]>([])
const selectedMeter = ref<EnergyMeter | null>(null)
const lastRecord = ref<EnergyConsumption | null>(null)
// 录入表单
const formState = reactive({
meterId: '',
currentReading: undefined as number | undefined,
recordedBy: ''
})
// 计算消耗量和费用
const calculatedConsumption = computed(() => {
if (!selectedMeter.value || formState.currentReading === undefined || lastRecord.value === null) {
return { consumption: 0, amount: 0 }
}
const consumption = formState.currentReading - lastRecord.value.currentReading
const amount = consumption * (selectedMeter.value.unitPrice || 0)
return { consumption: Math.max(0, consumption), amount }
})
// 表格列定义
const columns: ColumnsType = [
{ title: '记录日期', dataIndex: 'consumptionDate', key: 'consumptionDate', width: 120 },
{ title: '上次读数', dataIndex: 'previousReading', key: 'previousReading', width: 120 },
{ title: '当前读数', dataIndex: 'currentReading', key: 'currentReading', width: 120 },
{ title: '消耗量', dataIndex: 'consumption', key: 'consumption', width: 100 },
{ title: '费用(元)', dataIndex: 'amount', key: 'amount', width: 100 },
{ title: '记录方式', dataIndex: 'recordMethod', key: 'recordMethod', width: 100 }
]
// 获取项目列表
const fetchProjects = async () => {
try {
const res = await getProjectSelectorList()
projectOptions.value = (res.data.data || []).map((item: any) => ({
value: item.id,
label: item.name
}))
} catch {
message.error('获取项目列表失败')
}
}
// 获取计量点列表
const fetchMeters = async () => {
if (!queryParams.projectId) {
meterOptions.value = []
return
}
try {
const res = await getEnergyMeters(queryParams.projectId)
const data = res.data.data
const meters: EnergyMeter[] = data.content || data || []
meterOptions.value = meters.map(m => ({
value: m.id!,
label: `${m.meterName} (${m.meterCode})`,
energyType: m.energyType
}))
queryParams.meterId = ''
selectedMeter.value = null
lastRecord.value = null
formState.currentReading = undefined
} catch {
message.error('获取计量点列表失败')
}
}
// 获取计量点详情和上条记录
const fetchMeterDetail = async () => {
if (!queryParams.meterId) {
selectedMeter.value = null
lastRecord.value = null
return
}
try {
const meterRes = await getEnergyMeter(queryParams.meterId)
selectedMeter.value = meterRes.data.data
// 获取历史记录找最近一条
const historyRes = await getEnergyConsumption(queryParams.meterId)
const history = historyRes.data.data || []
if (history.length > 0) {
lastRecord.value = history[0]
} else {
lastRecord.value = null
}
} catch {
message.error('获取计量点详情失败')
}
}
// 获取历史记录
const fetchHistory = async () => {
if (!queryParams.meterId) return
loading.value = true
try {
const res = await getEnergyConsumption(queryParams.meterId)
consumptionList.value = res.data.data || []
} catch {
message.error('获取能耗记录失败')
} finally {
loading.value = false
}
}
// 选择项目
const handleProjectChange = () => {
queryParams.meterId = ''
selectedMeter.value = null
lastRecord.value = null
formState.currentReading = undefined
consumptionList.value = []
fetchMeters()
}
// 选择计量点
const handleMeterChange = () => {
fetchMeterDetail()
fetchHistory()
}
// 提交记录
const handleSubmit = async () => {
if (!formState.meterId) {
message.warning('请选择计量点')
return
}
if (formState.currentReading === undefined) {
message.warning('请输入当前读数')
return
}
if (lastRecord.value && formState.currentReading < lastRecord.value.currentReading) {
message.warning('当前读数不能小于上次读数')
return
}
recordLoading.value = true
try {
await recordEnergyConsumption({
meterId: formState.meterId,
currentReading: formState.currentReading!,
recordedBy: formState.recordedBy
})
message.success('录入成功')
// 刷新数据
fetchMeterDetail()
fetchHistory()
formState.currentReading = undefined
} catch {
message.error('录入失败')
} finally {
recordLoading.value = false
}
}
// 重置
const handleReset = () => {
queryParams.projectId = ''
queryParams.meterId = ''
selectedMeter.value = null
lastRecord.value = null
formState.currentReading = undefined
formState.recordedBy = ''
consumptionList.value = []
meterOptions.value = []
}
onMounted(() => {
fetchProjects()
})
</script>
<template>
<div class="page-container">
<!-- 页面标题 -->
<div class="page-header">
<h2 class="page-title">能耗录入</h2>
</div>
<Row :gutter="24">
<!-- 左侧录入表单 -->
<Col :span="10">
<Card title="录入能耗数据" class="record-card">
<!-- 筛选区 -->
<div class="filter-section">
<Form layout="vertical">
<Form.Item label="选择项目">
<Select
v-model:value="queryParams.projectId"
placeholder="请选择项目"
style="width: 100%"
:options="projectOptions"
@change="handleProjectChange"
/>
</Form.Item>
<Form.Item label="选择计量点">
<Select
v-model:value="queryParams.meterId"
placeholder="请选择计量点"
style="width: 100%"
:options="meterOptions"
@change="handleMeterChange"
/>
</Form.Item>
</Form>
</div>
<!-- 计量点信息 -->
<div v-if="selectedMeter" class="meter-info">
<Row :gutter="16">
<Col :span="12">
<Statistic title="能源类型" :value="energyTypeMap[selectedMeter.energyType] || selectedMeter.energyType" />
</Col>
<Col :span="12">
<Statistic title="单价" :value="selectedMeter.unitPrice || 0" suffix="元" :precision="2" />
</Col>
</Row>
<Row :gutter="16" style="margin-top: 16px">
<Col :span="12">
<Statistic title="额定容量" :value="selectedMeter.ratedCapacity || 0" suffix="kW" />
</Col>
<Col :span="12">
<Statistic title="安装位置" :value="selectedMeter.installationLocation || '-'" />
</Col>
</Row>
</div>
<!-- 上次记录 -->
<div v-if="lastRecord" class="last-record">
<h4>上次记录</h4>
<Row :gutter="16">
<Col :span="12">
<Statistic title="记录日期" :value="lastRecord.consumptionDate" />
</Col>
<Col :span="12">
<Statistic title="上次读数" :value="lastRecord.currentReading" />
</Col>
</Row>
</div>
<!-- 录入表单 -->
<div class="record-form">
<Form layout="vertical">
<Form.Item label="当前读数">
<InputNumber
v-model:value="formState.currentReading"
placeholder="请输入当前读数"
style="width: 100%"
:min="0"
:precision="2"
/>
</Form.Item>
<Form.Item label="记录人">
<Select
v-model:value="formState.recordedBy"
placeholder="请输入记录人"
style="width: 100%"
allow-clear
/>
</Form.Item>
</Form>
<!-- 计算结果预览 -->
<div v-if="formState.currentReading !== undefined" class="calculation-preview">
<Row :gutter="16">
<Col :span="12">
<Statistic title="消耗量" :value="calculatedConsumption.consumption" :precision="2" />
</Col>
<Col :span="12">
<Statistic title="费用" :value="calculatedConsumption.amount" suffix="元" :precision="2" />
</Col>
</Row>
</div>
<Space style="width: 100%; justify-content: flex-end; margin-top: 16px">
<Button @click="handleReset">
<ReloadOutlined /> 重置
</Button>
<Button type="primary" :loading="recordLoading" @click="handleSubmit">
提交记录
</Button>
</Space>
</div>
</Card>
</Col>
<!-- 右侧:历史记录 -->
<Col :span="14">
<Card title="历史记录" class="history-card">
<a-table
:columns="columns"
:data-source="consumptionList"
:loading="loading"
:row-key="(record: EnergyConsumption) => record.id || record.consumptionDate"
:pagination="{
pageSize: 10,
showSizeChanger: true,
showTotal: (total: number) => `${total}`
}"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'consumption'">
{{ record.consumption }} kWh
</template>
<template v-else-if="column.key === 'amount'">
{{ record.amount ? `¥${record.amount.toFixed(2)}` : '-' }}
</template>
<template v-else-if="column.key === 'recordMethod'">
{{ record.recordMethod === 'MANUAL' ? '手动录入' : '自动抄表' }}
</template>
</template>
</a-table>
<a-empty v-if="!queryParams.meterId" description="请先选择计量点" />
</Card>
</Col>
</Row>
</div>
</template>
<style scoped>
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
}
.page-title {
margin: 0;
font-size: 20px;
font-weight: 500;
color: #262626;
}
.record-card,
.history-card {
height: calc(100vh - 200px);
overflow-y: auto;
}
.filter-section {
margin-bottom: 24px;
padding-bottom: 24px;
border-bottom: 1px solid #e8e8e8;
}
.meter-info {
margin-bottom: 24px;
padding: 16px;
background: #fafafa;
border-radius: 8px;
}
.last-record {
margin-bottom: 24px;
padding: 16px;
background: #f6ffed;
border: 1px solid #b7eb8f;
border-radius: 8px;
}
.last-record h4 {
margin: 0 0 16px 0;
color: #52c41a;
}
.record-form {
margin-top: 24px;
}
.calculation-preview {
margin-top: 16px;
padding: 16px;
background: #e6f7ff;
border: 1px solid #91d5ff;
border-radius: 8px;
}
</style>