用水统计

This commit is contained in:
yaojian
2026-03-03 10:15:08 +08:00
parent b8f1889c68
commit d0e4071836
3 changed files with 562 additions and 13 deletions

View File

@@ -12,3 +12,39 @@ export const lookDetails = (roomNo: string) => {
});
};
/**
* 水电统计
* @param params 统计参数
*/
export const getStats = (params: any) => {
return request({
url: '/stuwork/watermonthreport/stats',
method: 'get',
params
});
};
/**
* 水电趋势分析
* @param params 查询参数
*/
export const getTrend = (params: any) => {
return request({
url: '/stuwork/watermonthreport/trend',
method: 'get',
params
});
};
/**
* 按楼层统计水电
* @param params 查询参数
*/
export const getFloorStats = (params: any) => {
return request({
url: '/stuwork/watermonthreport/floorStats',
method: 'get',
params
});
};

View File

@@ -69,23 +69,30 @@
水费明细列表
</span>
<div class="header-actions">
<el-button
icon="FolderAdd"
type="primary"
<el-button
icon="FolderAdd"
type="primary"
@click="formDialogRef.openDialog()">
新增明细
</el-button>
<el-button
icon="Download"
type="success"
class="ml10"
<el-button
icon="DataAnalysis"
type="warning"
class="ml10"
@click="handleStats">
统计分析
</el-button>
<el-button
icon="Download"
type="success"
class="ml10"
@click="handleExport">
导出数据
</el-button>
<el-button
icon="Setting"
type="warning"
class="ml10"
<el-button
icon="Setting"
type="info"
class="ml10"
@click="handleInitWaterOrder">
批量初始化订单
</el-button>
@@ -235,6 +242,9 @@
<!-- 详情对话框 -->
<DetailDialog ref="detailDialogRef" />
<!-- 统计分析对话框 -->
<StatsDialog ref="statsDialogRef" />
<!-- 批量初始化对话框-->
<el-dialog v-model="initDialogVisible" title="批量初始化订单" :width="500" :close-on-click-modal="false" draggable>
<el-form ref="initFormRef" :model="initForm" :rules="initRules" label-width="120px">
@@ -259,7 +269,7 @@
<script setup lang="ts" name="WaterDetail">
import { reactive, ref, onMounted, computed, nextTick } from 'vue'
import { useRoute } from 'vue-router'
import { useRoute, useRouter } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObjs, initWaterOrder } from "/@/api/stuwork/waterdetail";
import { getBuildingList } from "/@/api/stuwork/dormbuilding";
@@ -267,16 +277,19 @@ import { useMessage, useMessageBox } from "/@/hooks/message";
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import FormDialog from './form.vue';
import DetailDialog from './detail.vue';
import { List, OfficeBuilding, House, UserFilled, Money, Setting, Menu, Search, Document } from '@element-plus/icons-vue'
import StatsDialog from './stats.vue';
import { List, OfficeBuilding, House, UserFilled, Money, Setting, Menu, Search, Document, DataAnalysis } from '@element-plus/icons-vue'
import { useTableColumnControl } from '/@/hooks/tableColumn'
// 定义变量
const route = useRoute()
const router = useRouter()
const searchFormRef = ref()
const columnControlRef = ref<any>()
const formDialogRef = ref()
const detailDialogRef = ref()
const statsDialogRef = ref()
const initFormRef = ref()
const showSearch = ref(true)
const buildingList = ref<any[]>([])
@@ -402,6 +415,11 @@ const handleExport = () => {
useMessage().warning('功能开发中')
}
// 统计分析
const handleStats = () => {
statsDialogRef.value.openDialog()
}
// 批量初始化订单
const handleInitWaterOrder = () => {
initDialogVisible.value = true

View File

@@ -0,0 +1,495 @@
<template>
<el-dialog
v-model="visible"
title="水电统计分析"
width="90%"
:close-on-click-modal="false"
draggable
destroy-on-close
@open="handleOpen"
@closed="handleClosed">
<!-- 查询条件 -->
<div class="query-bar">
<el-form :inline="true" class="query-form">
<el-form-item label="年份">
<el-date-picker
v-model="queryParams.year"
type="year"
placeholder="选择年份"
format="YYYY"
value-format="YYYY"
style="width: 120px" />
</el-form-item>
<el-form-item label="月份">
<el-select v-model="queryParams.month" placeholder="选择月份" clearable style="width: 100px">
<el-option v-for="m in 12" :key="m" :label="m + '月'" :value="m" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</div>
<!-- 统计卡片 -->
<el-row :gutter="16" class="stats-row">
<el-col :span="6">
<div class="stats-card water">
<el-icon class="stats-icon"><Odometer /></el-icon>
<div class="stats-info">
<div class="stats-label">总用水量</div>
<div class="stats-value">{{ statsData.totalWaterUsage || 0 }} <span class="unit"></span></div>
</div>
</div>
</el-col>
<el-col :span="6">
<div class="stats-card water-cost">
<el-icon class="stats-icon"><Money /></el-icon>
<div class="stats-info">
<div class="stats-label">用水费用</div>
<div class="stats-value">¥{{ formatMoney(statsData.totalWaterCost) }}</div>
</div>
</div>
</el-col>
<el-col :span="6">
<div class="stats-card electricity">
<el-icon class="stats-icon"><Odometer /></el-icon>
<div class="stats-info">
<div class="stats-label">总用电量</div>
<div class="stats-value">{{ statsData.totalElectricityUsage || 0 }} <span class="unit"></span></div>
</div>
</div>
</el-col>
<el-col :span="6">
<div class="stats-card electricity-cost">
<el-icon class="stats-icon"><Money /></el-icon>
<div class="stats-info">
<div class="stats-label">用电费用</div>
<div class="stats-value">¥{{ formatMoney(statsData.totalElectricityCost) }}</div>
</div>
</div>
</el-col>
</el-row>
<!-- 图表区域 -->
<el-row :gutter="16" class="chart-row">
<el-col :span="12">
<el-card shadow="never" class="chart-card">
<template #header>
<span class="chart-title">水电用量趋势</span>
</template>
<div ref="trendChartRef" class="chart-container"></div>
</el-card>
</el-col>
<el-col :span="12">
<el-card shadow="never" class="chart-card">
<template #header>
<span class="chart-title">楼层用水用电分布</span>
</template>
<div ref="floorChartRef" class="chart-container"></div>
</el-card>
</el-col>
</el-row>
<!-- 楼层统计表格 -->
<el-card shadow="never" class="table-card">
<template #header>
<span class="chart-title">楼层水电统计明细</span>
</template>
<el-table :data="floorStatsData" v-loading="floorLoading" stripe border size="small" max-height="250">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="floorName" label="楼层" align="center" width="80" />
<el-table-column prop="waterUsage" label="用水量(m³)" align="center">
<template #default="scope">
<span class="water-text">{{ formatNumber(scope.row.waterUsage) }}</span>
</template>
</el-table-column>
<el-table-column prop="waterCost" label="用水费用(元)" align="center">
<template #default="scope">
<span class="water-text">¥{{ formatMoney(scope.row.waterCost) }}</span>
</template>
</el-table-column>
<el-table-column prop="electricityUsage" label="用电量(度)" align="center">
<template #default="scope">
<span class="electricity-text">{{ formatNumber(scope.row.electricityUsage) }}</span>
</template>
</el-table-column>
<el-table-column prop="electricityCost" label="用电费用(元)" align="center">
<template #default="scope">
<span class="electricity-text">¥{{ formatMoney(scope.row.electricityCost) }}</span>
</template>
</el-table-column>
</el-table>
</el-card>
<template #footer>
<el-button @click="visible = false">关闭</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, nextTick } from 'vue'
import { getStats, getTrend, getFloorStats } from '/@/api/stuwork/watermonthreport'
import { useMessage } from '/@/hooks/message'
import { Odometer, Money } from '@element-plus/icons-vue'
import * as echarts from 'echarts'
const visible = ref(false)
// 查询参数
const queryParams = reactive({
year: new Date().getFullYear().toString(),
month: new Date().getMonth() + 1
})
// 统计数据
const statsData = ref<any>({})
const trendData = ref<any[]>([])
const floorStatsData = ref<any[]>([])
const floorLoading = ref(false)
// 图表引用
const trendChartRef = ref<HTMLElement>()
const floorChartRef = ref<HTMLElement>()
let trendChart: echarts.ECharts | null = null
let floorChart: echarts.ECharts | null = null
// 打开弹窗
const openDialog = () => {
visible.value = true
}
// 格式化金额
const formatMoney = (value: any) => {
if (!value) return '0.00'
return Number(value).toFixed(2)
}
// 格式化数字
const formatNumber = (value: any) => {
if (!value) return '0'
return Number(value).toFixed(2)
}
// 弹窗打开时
const handleOpen = () => {
resetQuery()
nextTick(() => {
handleQuery()
})
}
// 弹窗关闭时
const handleClosed = () => {
trendChart?.dispose()
floorChart?.dispose()
trendChart = null
floorChart = null
}
// 重置查询参数
const resetQuery = () => {
queryParams.year = new Date().getFullYear().toString()
queryParams.month = new Date().getMonth() + 1
}
// 查询统计数据
const handleQuery = async () => {
await Promise.all([
getStatsData(),
getTrendData(),
getFloorData()
])
}
// 重置查询
const handleReset = () => {
resetQuery()
handleQuery()
}
// 获取统计数据
const getStatsData = async () => {
try {
const res = await getStats({
year: queryParams.year,
month: queryParams.month,
statsType: 'school'
})
if (res.data) {
statsData.value = res.data
}
} catch (err: any) {
useMessage().error(err.msg || '获取统计数据失败')
}
}
// 获取趋势数据
const getTrendData = async () => {
try {
const res = await getTrend({
year: queryParams.year
})
if (res.data && res.data.trendList) {
trendData.value = res.data.trendList
nextTick(() => renderTrendChart())
}
} catch (err: any) {
useMessage().error(err.msg || '获取趋势数据失败')
}
}
// 获取楼层数据
const getFloorData = async () => {
floorLoading.value = true
try {
const res = await getFloorStats({
year: queryParams.year,
month: queryParams.month
})
if (res.data) {
floorStatsData.value = res.data
nextTick(() => renderFloorChart())
}
} catch (err: any) {
useMessage().error(err.msg || '获取楼层数据失败')
} finally {
floorLoading.value = false
}
}
// 渲染趋势图表
const renderTrendChart = () => {
if (!trendChartRef.value) return
if (trendChart) {
trendChart.dispose()
}
trendChart = echarts.init(trendChartRef.value)
const option = {
tooltip: {
trigger: 'axis',
axisPointer: { type: 'cross' }
},
legend: {
data: ['用水量', '用电量'],
top: 0
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
top: 40,
containLabel: true
},
xAxis: {
type: 'category',
data: trendData.value.map(d => d.timeLabel)
},
yAxis: [
{
type: 'value',
name: '用水量(m³)',
position: 'left'
},
{
type: 'value',
name: '用电量(度)',
position: 'right'
}
],
series: [
{
name: '用水量',
type: 'line',
smooth: true,
itemStyle: { color: '#409eff' },
areaStyle: { color: 'rgba(64, 158, 255, 0.2)' },
data: trendData.value.map(d => d.waterUsage)
},
{
name: '用电量',
type: 'line',
smooth: true,
yAxisIndex: 1,
itemStyle: { color: '#f56c6c' },
areaStyle: { color: 'rgba(245, 108, 108, 0.2)' },
data: trendData.value.map(d => d.electricityUsage)
}
]
}
trendChart.setOption(option)
}
// 渲染楼层图表
const renderFloorChart = () => {
if (!floorChartRef.value) return
if (floorChart) {
floorChart.dispose()
}
floorChart = echarts.init(floorChartRef.value)
const option = {
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' }
},
legend: {
data: ['用水量', '用电量'],
top: 0
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
top: 40,
containLabel: true
},
xAxis: {
type: 'category',
data: floorStatsData.value.map(d => d.floorName)
},
yAxis: [
{
type: 'value',
name: '用水量(m³)'
},
{
type: 'value',
name: '用电量(度)',
position: 'right'
}
],
series: [
{
name: '用水量',
type: 'bar',
itemStyle: { color: '#409eff' },
data: floorStatsData.value.map(d => d.waterUsage)
},
{
name: '用电量',
type: 'bar',
yAxisIndex: 1,
itemStyle: { color: '#f56c6c' },
data: floorStatsData.value.map(d => d.electricityUsage)
}
]
}
floorChart.setOption(option)
}
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
.query-bar {
margin-bottom: 16px;
padding: 12px 16px;
background: #f5f7fa;
border-radius: 4px;
}
.stats-row {
margin-bottom: 16px;
}
.stats-card {
display: flex;
align-items: center;
padding: 16px;
border-radius: 8px;
background: linear-gradient(135deg, #f5f7fa 0%, #e4e7ed 100%);
.stats-icon {
font-size: 36px;
margin-right: 12px;
}
.stats-info {
flex: 1;
.stats-label {
font-size: 13px;
color: #909399;
margin-bottom: 4px;
}
.stats-value {
font-size: 22px;
font-weight: bold;
.unit {
font-size: 12px;
font-weight: normal;
}
}
}
&.water {
background: linear-gradient(135deg, #ecf5ff 0%, #d9ecff 100%);
.stats-icon { color: #409eff; }
.stats-value { color: #409eff; }
}
&.water-cost {
background: linear-gradient(135deg, #f0f9eb 0%, #e1f3d8 100%);
.stats-icon { color: #67c23a; }
.stats-value { color: #67c23a; }
}
&.electricity {
background: linear-gradient(135deg, #fdf6ec 0%, #faecd8 100%);
.stats-icon { color: #e6a23c; }
.stats-value { color: #e6a23c; }
}
&.electricity-cost {
background: linear-gradient(135deg, #fef0f0 0%, #fde2e2 100%);
.stats-icon { color: #f56c6c; }
.stats-value { color: #f56c6c; }
}
}
.chart-row {
margin-bottom: 16px;
}
.chart-card {
.chart-title {
font-size: 14px;
font-weight: 500;
}
.chart-container {
height: 280px;
}
}
.table-card {
.chart-title {
font-size: 14px;
font-weight: 500;
}
}
.water-text {
color: #409eff;
font-weight: bold;
}
.electricity-text {
color: #f56c6c;
font-weight: bold;
}
</style>