417 lines
12 KiB
Vue
417 lines
12 KiB
Vue
<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> |