learshaw 2 өдөр өмнө
parent
commit
80a76ed4af

+ 0 - 16
ems-ui-cloud/src/api/device/elecMeterH.js

@@ -1,25 +1,9 @@
 import request from '@/utils/request'
 
-// 查询用电计量-小时列表
-export function listElecMeterH(query) {
-    return request({
-        url: '/ems/elecMeterH/list',
-        method: 'get',
-        params: query
-    })
-}
-
 /**
  * 查询设施用能统计数据
  *
  */
-export function getNumElecMeterH(query) {
-    return request({
-        url: '/ems/elecMeterH/staByTime',
-        method: 'get',
-        params: query
-    })
-}
 
 // 新增用电计量-小时
 export function addElecMeterH(data) {

+ 45 - 1
ems-ui-cloud/src/api/device/energyConsumption.js

@@ -1,5 +1,27 @@
 import request from '@/utils/request'
 
+// ==================== 电表计小时抄表 ====================
+/**
+ * 查询表计小时抄表
+ * @param {Object} query 查询参数
+ * @returns {Promise} 区域用电统计列表
+ */
+export function getElecMdevHourList(query) {
+  return request({
+    url: '/ems/consumption/mdev/elec/hour/list',
+    method: 'get',
+    params: query
+  })
+}
+
+export function getElecMdevHourSummary(query) {
+  return request({
+    url: '/ems/consumption/mdev/elec/hour/summary',
+    method: 'get',
+    params: query
+  })
+}
+
 // ==================== 区域用电统计 ====================
 
 /**
@@ -9,7 +31,7 @@ import request from '@/utils/request'
  */
 export function getElechourTypes(query) {
   return request({
-    url: '/ems/consumption/area/elec/hourTypes',
+    url: '/ems/consumption/elec/hourTypes',
     method: 'get',
     params: query
   })
@@ -55,6 +77,28 @@ export function exportAreaElecConsumption(query) {
   })
 }
 
+// ==================== 水表计小时抄表 ====================
+/**
+ * 查询表计小时抄表
+ * @param {Object} query 查询参数
+ * @returns {Promise} 区域用水统计列表
+ */
+export function getWaterMdevHourList(query) {
+  return request({
+    url: '/ems/consumption/mdev/water/hour/list',
+    method: 'get',
+    params: query
+  })
+}
+
+export function getWaterMdevHourSummary(query) {
+  return request({
+    url: '/ems/consumption/mdev/water/hour/summary',
+    method: 'get',
+    params: query
+  })
+}
+
 // ==================== 区域用水统计 ====================
 
 /**

+ 0 - 21
ems-ui-cloud/src/api/device/waterMeterH.js

@@ -8,27 +8,6 @@ export function getWaterDayAvg(param) {
   })
 }
 
-/**
- * 查询设施用能统计数据
- *
- */
-export function getNumWaterMeterH(query) {
-  return request({
-    url: '/ems/waterMeterH/staByTime',
-    method: 'get',
-    params: query
-  })
-}
-
-// 查询用水计量-小时列表
-export function listWaterMeterH(query) {
-  return request({
-    url: '/ems/waterMeterH/list',
-    method: 'get',
-    params: query
-  })
-}
-
 // 查询用水计量-小时详细
 export function getWaterMeterH(id) {
   return request({

+ 242 - 145
ems-ui-cloud/src/views/analysis/report/statement-consume.vue

@@ -32,7 +32,22 @@
             default-expand-all
             highlight-current
             @node-click="handleNodeClick"
-          />
+          >
+            <template #default="{ node, data }">
+              <el-tooltip
+                class="tree-node-tooltip"
+                effect="dark"
+                :content="data.label"
+                placement="right"
+                :disabled="!isTextOverflow(data.label)"
+              >
+                <div class="tree-node">
+                  <i class="el-icon-folder-opened node-icon"></i>
+                  <span class="node-label">{{ data.label }}</span>
+                </div>
+              </el-tooltip>
+            </template>
+          </el-tree>
         </div>
       </el-col>
 
@@ -89,46 +104,39 @@
 
         <!-- 统计汇总卡片 - 用电 -->
         <div v-if="queryParams.energyType === 'elec'">
-          <el-row :gutter="20" style="margin-bottom: 15px">
-            <!-- 第一行:总用电量和总电费 -->
-            <el-col :span="12">
-              <el-card class="summary-card compact-card">
-                <div slot="header">
-                  <i class="el-icon-lightning summary-icon elec-icon"></i>
-                  <span>总用电量</span>
-                </div>
-                <div class="summary-value">
-                  {{ formatNumber(summary.totalElecQuantity) }} <span class="unit">kWh</span>
-                </div>
-              </el-card>
-            </el-col>
-            <el-col :span="12">
-              <el-card class="summary-card compact-card">
-                <div slot="header">
-                  <i class="el-icon-money summary-icon cost-icon"></i>
-                  <span>总电费</span>
-                </div>
-                <div class="summary-value">
-                  {{ formatNumber(summary.totalElecCost, 2) }} <span class="unit">元</span>
-                </div>
-              </el-card>
-            </el-col>
-          </el-row>
+          <!-- 总量统计行 - 超紧凑设计 -->
+          <div class="summary-row-compact">
+            <el-card class="summary-card-compact">
+              <div class="compact-content">
+                <i class="el-icon-lightning compact-icon elec-icon"></i>
+                <span class="compact-label">总用电量</span>
+                <span class="compact-value">{{ formatNumber(summary.totalElecQuantity) }}</span>
+                <span class="compact-unit">kWh</span>
+              </div>
+            </el-card>
+            <el-card class="summary-card-compact">
+              <div class="compact-content">
+                <i class="el-icon-money compact-icon cost-icon"></i>
+                <span class="compact-label">总电费</span>
+                <span class="compact-value">{{ formatNumber(summary.totalElecCost, 2) }}</span>
+                <span class="compact-unit">元</span>
+              </div>
+            </el-card>
+          </div>
 
-          <!-- 第二行:动态峰谷电统计 -->
-          <div class="peak-valley-container">
+          <!-- 峰谷电统计 - 超紧凑横向布局 -->
+          <div v-if="supportedMeterTypes && supportedMeterTypes.length > 0" class="peak-valley-compact-row">
             <el-card
               v-for="meterType in supportedMeterTypes"
               :key="meterType.value"
-              class="peak-valley-card compact-card"
+              class="peak-card-compact"
             >
-              <div slot="header" class="clearfix">
-                <span>{{ meterType.name }}</span>
-                <span class="peak-type-tag" :class="meterType.tagClass">{{ meterType.tagName }}</span>
-              </div>
-              <div class="peak-valley-content">
-                <div class="quantity">{{ formatNumber(getMeterTypeQuantity(meterType.value)) }} kWh</div>
-                <div class="percentage">占比 {{ calculatePercentage(getMeterTypeQuantity(meterType.value), summary.totalElecQuantity) }}%</div>
+              <div class="peak-compact-content">
+                <span class="peak-badge" :class="meterType.tagClass">{{ meterType.tagName }}</span>
+                <div class="peak-data">
+                  <span class="peak-quantity">{{ formatNumber(getMeterTypeQuantity(meterType.value)) }} kWh</span>
+                  <span class="peak-percent">占比 {{ calculatePercentage(getMeterTypeQuantity(meterType.value), summary.totalElecQuantity) }}%</span>
+                </div>
               </div>
             </el-card>
           </div>
@@ -136,30 +144,24 @@
 
         <!-- 统计汇总卡片 - 用水 -->
         <div v-if="queryParams.energyType === 'water'">
-          <el-row :gutter="20" style="margin-bottom: 15px">
-            <el-col :span="12">
-              <el-card class="summary-card compact-card">
-                <div slot="header">
-                  <i class="el-icon-set-up summary-icon water-icon"></i>
-                  <span>总用水量</span>
-                </div>
-                <div class="summary-value">
-                  {{ formatNumber(summary.totalWaterQuantity) }} <span class="unit">m³</span>
-                </div>
-              </el-card>
-            </el-col>
-            <el-col :span="12">
-              <el-card class="summary-card compact-card">
-                <div slot="header">
-                  <i class="el-icon-money summary-icon cost-icon"></i>
-                  <span>总水费</span>
-                </div>
-                <div class="summary-value">
-                  {{ formatNumber(summary.totalWaterCost, 2) }} <span class="unit">元</span>
-                </div>
-              </el-card>
-            </el-col>
-          </el-row>
+          <div class="summary-row-compact">
+            <el-card class="summary-card-compact">
+              <div class="compact-content">
+                <i class="el-icon-set-up compact-icon water-icon"></i>
+                <span class="compact-label">总用水量</span>
+                <span class="compact-value">{{ formatNumber(summary.totalWaterQuantity) }}</span>
+                <span class="compact-unit">m³</span>
+              </div>
+            </el-card>
+            <el-card class="summary-card-compact">
+              <div class="compact-content">
+                <i class="el-icon-money compact-icon cost-icon"></i>
+                <span class="compact-label">总水费</span>
+                <span class="compact-value">{{ formatNumber(summary.totalWaterCost, 2) }}</span>
+                <span class="compact-unit">元</span>
+              </div>
+            </el-card>
+          </div>
         </div>
 
         <!-- 数据表格 - 用电 -->
@@ -756,7 +758,11 @@ export default {
         throw new Error('文件下载失败')
       }
     },
-
+    isTextOverflow(text) {
+      // 简单判断:超过一定字符数就显示tooltip
+      // 可以根据实际情况调整这个数值
+      return text && text.length > 8
+    },
     // 获取当前时间字符串
     getCurrentTimeStr() {
       const now = new Date()
@@ -876,33 +882,93 @@ export default {
 </script>
 
 <style scoped>
+/* 树容器样式 - 参考附件2的美化设计 */
 .tree-container {
   height: calc(100vh - 200px);
   overflow-y: auto;
+  border: 1px solid #e8e8e8;
+  border-radius: 4px;
+  padding: 10px;
+  background-color: #fafafa;
 }
 
-/* 压缩卡片高度 */
-.compact-card {
-  min-height: auto !important;
+/* 树节点样式 */
+.tree-node {
+  display: flex;
+  align-items: center;
+  width: 100%;
+  padding: 2px 0;
+  transition: all 0.3s;
+  cursor: pointer;
 }
 
-.compact-card .el-card__header {
-  padding: 10px 15px !important;
-  border-bottom: 1px solid #ebeef5;
+.node-icon {
+  margin-right: 8px;
+  font-size: 16px;
+  transition: all 0.3s;
+  color: #409EFF;
 }
 
-.compact-card .el-card__body {
-  padding: 15px !important;
+.node-label {
+  flex: 1;
+  font-size: 14px;
 }
 
-.summary-card {
-  text-align: center;
+/* 节点hover效果 */
+.tree-node:hover {
+  background-color: #f0f7ff;
+  border-radius: 4px;
+  padding-left: 4px;
+}
+
+.tree-node:hover .node-icon {
+  color: #2b7bff;
+  transform: scale(1.1);
+}
+
+/* 高亮当前选中的节点 */
+/deep/ .el-tree-node.is-current > .el-tree-node__content .tree-node {
+  background-color: #e6f7ff;
+  border-radius: 4px;
+  padding-left: 4px;
+}
+
+/deep/ .el-tree-node.is-current > .el-tree-node__content .node-icon {
+  color: #1890ff;
 }
 
-.summary-icon {
+/* 超紧凑统计卡片样式 */
+.summary-row-compact {
+  display: flex;
+  gap: 12px;
+  margin-bottom: 10px;
+}
+
+.summary-card-compact {
+  flex: 1;
+  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
+  transition: all 0.3s;
+}
+
+.summary-card-compact:hover {
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
+  transform: translateY(-1px);
+}
+
+.summary-card-compact .el-card__body {
+  padding: 8px 12px !important;
+}
+
+.compact-content {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  height: 28px;
+}
+
+.compact-icon {
   font-size: 16px;
-  margin-right: 6px;
-  vertical-align: middle;
+  flex-shrink: 0;
 }
 
 .elec-icon {
@@ -917,146 +983,177 @@ export default {
   color: #E6A23C;
 }
 
-.summary-value {
-  font-size: 24px;
+.compact-label {
+  font-size: 13px;
+  color: #606266;
+  flex-shrink: 0;
+}
+
+.compact-value {
+  font-size: 18px;
   font-weight: bold;
   color: #409EFF;
-  padding: 10px 0;
+  margin-left: auto;
 }
 
-.unit {
-  font-size: 14px;
-  font-weight: normal;
+.compact-unit {
+  font-size: 11px;
   color: #909399;
-  margin-left: 5px;
+  flex-shrink: 0;
 }
 
-.peak-valley-card {
-  text-align: center;
+/* 峰谷电超紧凑横向布局 */
+.peak-valley-compact-row {
+  display: flex;
+  gap: 10px;
+  margin-bottom: 10px;
 }
 
-.peak-valley-card .el-card__header {
-  padding: 8px 12px !important;
+.peak-card-compact {
+  flex: 1;
+  min-width: 0;
+  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
+  transition: all 0.3s;
 }
 
-.clearfix:after {
-  display: table;
-  content: "";
-  clear: both;
+.peak-card-compact:hover {
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
+  transform: translateY(-1px);
 }
 
-.peak-type-tag {
-  float: right;
+.peak-card-compact .el-card__body {
+  padding: 6px 10px !important;
+}
+
+.peak-compact-content {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  height: 24px;
+}
+
+.peak-badge {
+  display: inline-block;
   padding: 2px 6px;
   font-size: 11px;
   border-radius: 3px;
   color: white;
+  font-weight: 500;
+  min-width: 32px;
+  text-align: center;
+  flex-shrink: 0;
 }
 
 /* 峰谷电类型标签样式 */
 .peak-tag {
-  background-color: #F56C6C;
+  background: linear-gradient(135deg, #F56C6C 0%, #f44336 100%);
 }
 
 .high-peak-tag {
-  background-color: #E6A23C;
+  background: linear-gradient(135deg, #E6A23C 0%, #FF9800 100%);
 }
 
 .normal-tag {
-  background-color: #909399;
+  background: linear-gradient(135deg, #909399 0%, #606266 100%);
 }
 
 .valley-tag {
-  background-color: #67C23A;
+  background: linear-gradient(135deg, #67C23A 0%, #4CAF50 100%);
 }
 
-/* 新增深谷标签样式 */
 .deep-valley-tag {
-  background-color: #409EFF;
   background: linear-gradient(135deg, #409EFF 0%, #1976D2 100%);
 }
 
-.peak-valley-content {
-  padding: 10px 0;
-}
-
-.quantity {
-  font-size: 18px;
-  font-weight: bold;
-  color: #303133;
-  margin-bottom: 6px;
+.peak-data {
+  flex: 1;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  gap: 10px;
 }
 
-.percentage {
+.peak-quantity {
   font-size: 13px;
-  color: #909399;
-}
-
-.el-tree .el-tree-node.is-leaf > .el-tree-node__content {
-  color: #333;
-  cursor: pointer;
+  font-weight: 600;
+  color: #303133;
 }
 
-.el-tree .el-tree-node:not(.is-leaf) > .el-tree-node__content {
+.peak-percent {
+  font-size: 11px;
   color: #909399;
-  cursor: not-allowed;
+  background-color: #f4f4f5;
+  padding: 1px 6px;
+  border-radius: 10px;
+  white-space: nowrap;
 }
 
 /* 响应式布局调整 */
 @media (max-width: 1680px) {
-  .peak-valley-card .quantity {
+  .compact-value {
     font-size: 16px;
   }
 
-  .peak-valley-card .percentage {
+  .peak-quantity {
     font-size: 12px;
   }
 }
 
 @media (max-width: 1366px) {
-  .peak-valley-card .quantity {
-    font-size: 14px;
+  .peak-valley-compact-row {
+    flex-wrap: wrap;
   }
 
-  .peak-type-tag {
-    font-size: 10px;
-    padding: 1px 4px;
+  .peak-card-compact {
+    flex: 1 1 calc(33.333% - 7px);
+    min-width: 180px;
   }
-}
 
-/* 峰谷电容器flex布局 */
-.peak-valley-container {
-  display: flex;
-  gap: 15px;
-  margin-bottom: 15px;
+  .compact-value {
+    font-size: 15px;
+  }
 }
 
-.peak-valley-container .peak-valley-card {
-  flex: 1;
-  min-width: 0; /* 防止内容溢出 */
+@media (max-width: 768px) {
+  .summary-row-compact {
+    flex-direction: column;
+    gap: 8px;
+  }
+
+  .peak-card-compact {
+    flex: 1 1 calc(50% - 5px);
+  }
 }
 
-/* 响应式调整 - 小屏幕时换行 */
-@media (max-width: 1366px) {
-  .peak-valley-container {
+@media (max-width: 480px) {
+  .peak-card-compact {
+    flex: 1 1 100%;
+  }
+
+  .compact-content {
+    height: auto;
     flex-wrap: wrap;
   }
 
-  .peak-valley-container .peak-valley-card {
-    flex: 1 1 calc(33.333% - 10px); /* 一行显示3个 */
-    min-width: 200px;
+  .peak-compact-content {
+    height: auto;
+    flex-direction: column;
+    align-items: flex-start;
+    gap: 5px;
   }
-}
 
-@media (max-width: 768px) {
-  .peak-valley-container .peak-valley-card {
-    flex: 1 1 calc(50% - 7.5px); /* 一行显示2个 */
+  .peak-data {
+    width: 100%;
   }
 }
 
-@media (max-width: 480px) {
-  .peak-valley-container .peak-valley-card {
-    flex: 1 1 100%; /* 一行显示1个 */
-  }
+/* 过渡动画 */
+.el-card {
+  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+/* 加载动画优化 */
+.el-loading-mask {
+  background-color: rgba(255, 255, 255, 0.9);
 }
 </style>

+ 187 - 37
ems-ui-cloud/src/views/devmgr/meterData/index.vue

@@ -27,7 +27,9 @@
             @node-click="handleNodeClick"
           >
             <template #default="{ node, data }">
-              <div class="tree-node" :class="{ 'device-node': data.type === 'device', 'area-node': data.type === 'area' }">
+              <div class="tree-node"
+                   :class="{ 'device-node': data.type === 'device', 'area-node': data.type === 'area' }"
+              >
                 <i v-if="data.type === 'area'" class="el-icon-folder node-icon area-icon"></i>
                 <i v-if="data.type === 'device'" class="el-icon-monitor node-icon device-icon"></i>
                 <span class="node-label">{{ data.label }}</span>
@@ -46,7 +48,9 @@
         </el-tabs>
 
         <!-- 查询表单 -->
-        <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="80px">
+        <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch"
+                 label-width="80px"
+        >
           <el-form-item label="抄表周期">
             <el-date-picker
               v-model="daterangeRecordTime"
@@ -81,7 +85,7 @@
                     <span class="inline-label">总用电量</span>
                   </div>
                   <div class="inline-card-right">
-                    <span class="inline-value">{{ formatNumber(numElecMeterHData.total.quantity) }}</span>
+                    <span class="inline-value">{{ formatNumber(numElecMeterHData && numElecMeterHData.total ? numElecMeterHData.total.quantity : 0) }}</span>
                     <span class="inline-unit">kWh</span>
                   </div>
                 </div>
@@ -94,7 +98,7 @@
                     <span class="inline-label">总电费</span>
                   </div>
                   <div class="inline-card-right">
-                    <span class="inline-value">{{ formatNumber(numElecMeterHData.total.useCost, 2) }}</span>
+                    <span class="inline-value">{{ formatNumber(numElecMeterHData && numElecMeterHData.total ? numElecMeterHData.total.useCost : 0, 2) }}</span>
                     <span class="inline-unit">元</span>
                   </div>
                 </div>
@@ -118,13 +122,11 @@
                       <span class="peak-value">{{ formatNumber(getMeterTypeData(meterType.value, 'quantity')) }}</span>
                       <span class="peak-unit">kWh</span>
                     </div>
-                    <div class="peak-inline-info">
-                      <span class="peak-label">费用</span>
-                      <span class="peak-value">{{ formatNumber(getMeterTypeData(meterType.value, 'useCost'), 2) }}</span>
-                      <span class="peak-unit">元</span>
-                    </div>
+
                     <div class="peak-percentage">
-                      占比 {{ calculatePercentage(getMeterTypeData(meterType.value, 'quantity'), numElecMeterHData.total.quantity) }}%
+                      占比 {{
+                        numElecMeterHData && numElecMeterHData.total && numElecMeterHData.total.quantity > 0 ? calculatePercentage(getMeterTypeData(meterType.value, 'quantity'), numElecMeterHData.total.quantity) : '0.0'
+                      }}%
                     </div>
                   </div>
                 </div>
@@ -169,8 +171,8 @@
 
         <!-- 电表数据表格 -->
         <el-table v-if="activeTab === 'first'" v-loading="loading" :data="elecMeterHList" style="margin-top: 20px">
-          <el-table-column label="表计名称" align="left" prop="deviceName" width="250px" />
-          <el-table-column label="表计编号" align="left" prop="deviceCode" />
+          <el-table-column label="表计名称" align="left" prop="deviceName" width="250px"/>
+          <el-table-column label="表计编号" align="left" prop="deviceCode"/>
           <el-table-column label="抄表时间" align="center" prop="recordTime" width="180">
             <template slot-scope="scope">
               <span>{{ formatDateTime(scope.row.recordTime) }}</span>
@@ -181,7 +183,9 @@
               <el-tag :type="getMeterTypeTagType(scope.row.meterType)" size="small">
                 {{ getElecMeterType(scope.row.meterType) }}
               </el-tag>
-              <span style="margin-left: 5px; color: #909399;">{{ scope.row.meterUnitPrice }} 元/kWh</span>
+              <span style="margin-left: 5px; color: #909399;">{{
+                  formatNumber(scope.row.meterUnitPrice, 2)
+                }} 元/kWh</span>
             </template>
           </el-table-column>
           <el-table-column label="用电量(kWh)" align="center" prop="elecQuantity">
@@ -198,24 +202,18 @@
 
         <!-- 水表数据表格 -->
         <el-table v-if="activeTab === 'second'" v-loading="loading" :data="waterMeterHList" style="margin-top: 20px">
-          <el-table-column label="表计名称" align="left" prop="deviceName" width="250" />
-          <el-table-column label="表计编号" align="left" prop="deviceCode" />
+          <el-table-column label="表计名称" align="left" prop="deviceName" width="250"/>
+          <el-table-column label="表计编号" align="left" prop="deviceCode"/>
           <el-table-column label="抄表时间" align="center" prop="recordTime" width="180">
             <template slot-scope="scope">
               <span>{{ formatDateTime(scope.row.recordTime) }}</span>
             </template>
           </el-table-column>
-          <el-table-column label="时间序列" align="center" prop="timeIndex" />
           <el-table-column label="用水量(m³)" align="center" prop="waterQuantity">
             <template slot-scope="scope">
               <span>{{ formatNumber(scope.row.waterQuantity) }}</span>
             </template>
           </el-table-column>
-          <el-table-column label="单位水价(元/m³)" align="center" prop="meterUnitPrice">
-            <template slot-scope="scope">
-              <span>{{ formatNumber(scope.row.meterUnitPrice, 2) }}</span>
-            </template>
-          </el-table-column>
           <el-table-column label="小时水费(元)" align="center" prop="useWaterCost">
             <template slot-scope="scope">
               <span>{{ formatNumber(scope.row.useWaterCost, 2) }}</span>
@@ -237,10 +235,8 @@
 </template>
 
 <script>
-import { listElecMeterH, getNumElecMeterH, getElecDayAvg } from '@/api/device/elecMeterH'
-import { listWaterMeterH, getNumWaterMeterH } from '@/api/device/waterMeterH'
 import { getDeviceTree } from '@/api/device/meterDevice'
-import { getElechourTypes } from '@/api/device/energyConsumption'
+import { getElechourTypes, getElecMdevHourList, getElecMdevHourSummary, getWaterMdevHourList, getWaterMdevHourSummary } from '@/api/device/energyConsumption'
 
 export default {
   name: 'MeterReading',
@@ -399,19 +395,26 @@ export default {
       // 根据已有数据判断支持的类型
       const availableTypes = []
       if (this.numElecMeterHData) {
+        // 尖峰电量
         if (this.numElecMeterHData.peakElec && this.numElecMeterHData.peakElec.quantity > 0) {
           availableTypes.push('2')
         }
+        // 峰段电量
         if (this.numElecMeterHData.highElec && this.numElecMeterHData.highElec.quantity > 0) {
           availableTypes.push('1')
         }
+        // 平段电量
         if (this.numElecMeterHData.normalElec && this.numElecMeterHData.normalElec.quantity > 0) {
           availableTypes.push('0')
         }
+        // 谷段电量
         if (this.numElecMeterHData.lowElec && this.numElecMeterHData.lowElec.quantity > 0) {
           availableTypes.push('-1')
         }
-        // 深谷电量暂不支持,需要后端提供
+        // 深谷电量
+        if (this.numElecMeterHData.deepValleyElec && this.numElecMeterHData.deepValleyElec.quantity > 0) {
+          availableTypes.push('-2')
+        }
       }
 
       // 如果没有任何数据,返回空数组
@@ -438,6 +441,14 @@ export default {
 
     // Tab切换处理
     handleTabChange() {
+      // 清空之前的数据,防止数据污染
+      this.elecMeterHList = []
+      this.waterMeterHList = []
+      this.numElecMeterHData = null
+      this.numWaterMeterHData = null
+      this.supportedMeterTypes = []
+      this.total = 0
+
       if (this.activeTab === 'first') {
         this.queryParams.meterCls = '45'
       } else if (this.activeTab === 'second') {
@@ -520,7 +531,7 @@ export default {
     // 查询用电计量-小时列表
     getElecList() {
       this.loading = true
-      listElecMeterH(this.queryParams).then(response => {
+      getElecMdevHourList(this.queryParams).then(response => {
         this.elecMeterHList = response.rows
         this.total = response.total
         this.loading = false
@@ -552,7 +563,7 @@ export default {
         this.queryParams.meterCls = '45'
         await this.loadSupportedMeterTypes()
         this.getElecList()
-        this.getNumElecMeterH()
+        this.getElecMdevHourSummary()
       } else if (this.activeTab === 'second') {
         this.queryParams.meterCls = '70'
         this.getWaterList()
@@ -561,18 +572,95 @@ export default {
     },
 
     // 获取用电统计数据
-    async getNumElecMeterH() {
+    async getElecMdevHourSummary() {
       const query = {
         deviceCode: this.queryParams.deviceCode,
         startRecTime: this.queryParams.startRecTime,
         endRecTime: this.queryParams.endRecTime
       }
       try {
-        const response = await getNumElecMeterH(query)
-        this.numElecMeterHData = response.data
+        const response = await getElecMdevHourSummary(query)
+        // 处理空数据情况
+        if (!response.data || (typeof response.data === 'object' && Object.keys(response.data).length === 0)) {
+          this.numElecMeterHData = {
+            total: {
+              quantity: 0,
+              useCost: 0
+            },
+            deepValleyElec: { quantity: 0, useCost: 0 },
+            lowElec: { quantity: 0, useCost: 0 },
+            normalElec: { quantity: 0, useCost: 0 },
+            highElec: { quantity: 0, useCost: 0 },
+            peakElec: { quantity: 0, useCost: 0 }
+          }
+        } else {
+          const flatData = response.data
+
+          // 将扁平数据转换为原来的嵌套结构
+          this.numElecMeterHData = {
+            total: {
+              areaCode: flatData.areaCode,
+              objType: flatData.objType,
+              objCode: flatData.objCode,
+              objName: flatData.objName,
+              quantity: flatData.totalElecQuantity || 0,
+              useCost: flatData.totalElecCost || 0
+            },
+            deepValleyElec: {
+              areaCode: flatData.areaCode,
+              objType: flatData.objType,
+              objCode: '-2',
+              objName: 'deepValleyElec',
+              quantity: flatData.deepValleyQuantity || 0,
+              useCost: 0 // 深谷费用暂时无法直接从新接口获取,设置为0
+            },
+            lowElec: {
+              areaCode: flatData.areaCode,
+              objType: flatData.objType,
+              objCode: '-1',
+              objName: 'lowElec',
+              quantity: flatData.valleyQuantity || 0,
+              useCost: 0 // 谷段费用暂时无法直接从新接口获取,设置为0
+            },
+            normalElec: {
+              areaCode: flatData.areaCode,
+              objType: flatData.objType,
+              objCode: '0',
+              objName: 'normalElec',
+              quantity: flatData.normalQuantity || 0,
+              useCost: 0 // 平段费用暂时无法直接从新接口获取,设置为0
+            },
+            highElec: {
+              areaCode: flatData.areaCode,
+              objType: flatData.objType,
+              objCode: '1',
+              objName: 'highElec',
+              quantity: flatData.peakQuantity || 0,
+              useCost: 0 // 峰段费用暂时无法直接从新接口获取,设置为0
+            },
+            peakElec: {
+              areaCode: flatData.areaCode,
+              objType: flatData.objType,
+              objCode: '2',
+              objName: 'peakElec',
+              quantity: flatData.sharpPeakQuantity || 0,
+              useCost: 0 // 尖峰费用暂时无法直接从新接口获取,设置为0
+            }
+          }
+        }
+
         // 重新加载支持的电量类型
         await this.loadSupportedMeterTypes()
       } catch (error) {
+        // 出错时设置默认空数据结构
+        this.numElecMeterHData = {
+          total: { quantity: 0, useCost: 0 },
+          deepValleyElec: { quantity: 0, useCost: 0 },
+          lowElec: { quantity: 0, useCost: 0 },
+          normalElec: { quantity: 0, useCost: 0 },
+          highElec: { quantity: 0, useCost: 0 },
+          peakElec: { quantity: 0, useCost: 0 }
+        }
         this.$message.error('获取用电统计数据失败')
       }
     },
@@ -585,9 +673,26 @@ export default {
         endRecTime: this.queryParams.endRecTime
       }
       try {
-        const response = await getNumWaterMeterH(query)
-        this.numWaterMeterHData = response.data
+        const response = await getWaterMdevHourSummary(query)
+        // 处理空数据情况
+        if (!response.data || (typeof response.data === 'object' && Object.keys(response.data).length === 0)) {
+          this.numWaterMeterHData = {
+            quantity: 0,
+            useCost: 0
+          }
+        } else {
+          // 修改字段映射:使用新的字段名
+          this.numWaterMeterHData = {
+            quantity: response.data.totalWaterQuantity || 0,
+            useCost: response.data.totalWaterCost || 0
+          }
+        }
       } catch (error) {
+        // 出错时设置默认空数据结构
+        this.numWaterMeterHData = {
+          quantity: 0,
+          useCost: 0
+        }
         this.$message.error('获取用水统计数据失败')
       }
     },
@@ -607,8 +712,8 @@ export default {
         : `水表读数_${new Date().getTime()}.xlsx`
 
       const url = this.activeTab === 'first'
-        ? 'ems/elecMeterH/export'
-        : 'ems/waterMeterH/export'
+        ? 'ems/consumption/mdev/elec/hour/export'
+        : 'ems/consumption/mdev/water/hour/export'
 
       this.download(url, { ...this.queryParams }, filename)
     },
@@ -648,7 +753,7 @@ export default {
 
     // 获取用水总计数据
     getWaterTotal(field) {
-      if (this.numWaterMeterHData && this.numWaterMeterHData[field]) {
+      if (this.numWaterMeterHData && this.numWaterMeterHData[field] !== undefined) {
         return this.numWaterMeterHData[field]
       }
       // 如果没有统计数据,尝试从列表数据计算
@@ -668,6 +773,51 @@ export default {
         return '0.' + '0'.repeat(decimals)
       }
       return Number(num).toFixed(decimals)
+    },
+
+    // 查询用电计量-小时列表
+    getElecList() {
+      this.loading = true
+      getElecMdevHourList(this.queryParams).then(response => {
+        // 处理空数据情况
+        this.elecMeterHList = response.rows || []
+        this.total = response.total || 0
+        this.loading = false
+      }).catch(error => {
+        console.error('获取用电列表失败:', error)
+        this.elecMeterHList = []
+        this.total = 0
+        this.loading = false
+      })
+    },
+
+    // 查询用水计量-小时列表
+    getWaterList() {
+      this.loading = true
+      getWaterMdevHourList(this.queryParams).then(response => {
+        // 处理空数据情况
+        this.waterMeterHList = response.rows || []
+        this.total = response.total || 0
+        this.loading = false
+      }).catch(error => {
+        console.error('获取用水列表失败:', error)
+        this.waterMeterHList = []
+        this.total = 0
+        this.loading = false
+      })
+    },
+
+    // 树结构加载
+    async getAreaTreeSelect() {
+      try {
+        await getDeviceTree(0, this.queryParams.meterCls, 0).then(response => {
+          // 处理空数据情况
+          this.areaOptions = response.data || []
+        })
+      } catch (error) {
+        console.error('加载设备树失败:', error)
+        this.areaOptions = []
+      }
     }
   }
 }
@@ -743,13 +893,13 @@ export default {
 }
 
 /* 高亮当前选中的节点 */
-/deep/ .el-tree-node.is-current > .el-tree-node__content .device-node {
+.el-tree-node.is-current > .el-tree-node__content .device-node {
   background-color: #e6f7ff;
   border-radius: 4px;
   padding-left: 4px;
 }
 
-/deep/ .el-tree-node.is-current > .el-tree-node__content .device-icon {
+.el-tree-node.is-current > .el-tree-node__content .device-icon {
   color: #1890ff;
 }
 

+ 778 - 289
ems-ui-cloud/src/views/devmgr/meterRead/index.vue

@@ -1,88 +1,225 @@
 <template>
   <div class="app-container">
     <el-row :gutter="20">
+      <!-- 左侧树形选择器 -->
       <el-col :span="4" :xs="24">
         <div class="head-container">
-          <el-input v-model="areaName" placeholder="请输入服务区名称" clearable size="small" prefix-icon="el-icon-search"
-                    style="margin-bottom: 20px" />
+          <el-input
+            v-model="areaName"
+            placeholder="请输入区域名称"
+            clearable
+            size="small"
+            prefix-icon="el-icon-search"
+            style="margin-bottom: 20px"
+            @input="filterTree"
+          />
         </div>
-        <div class="head-container">
-          <el-tree :data="areaOptions" :props="defaultProps" :expand-on-click-node="false" :filter-node-method="filterNode" ref="tree"
-                   node-key="id" default-expand-all highlight-current @node-click="handleNodeClick" />
+        <div class="head-container tree-container">
+          <el-tree
+            :data="areaOptions"
+            :props="defaultProps"
+            :expand-on-click-node="false"
+            :filter-node-method="filterNode"
+            ref="tree"
+            node-key="id"
+            default-expand-all
+            highlight-current
+            @node-click="handleNodeClick"
+          >
+            <template #default="{ node, data }">
+              <div class="tree-node" :class="{ 'device-node': data.type === 'device', 'area-node': data.type === 'area' }">
+                <i v-if="data.type === 'area'" class="el-icon-folder node-icon area-icon"></i>
+                <i v-if="data.type === 'device'" class="el-icon-monitor node-icon device-icon"></i>
+                <span class="node-label">{{ data.label }}</span>
+              </div>
+            </template>
+          </el-tree>
+        </div>
+
+        <!-- 无数据提示 -->
+        <div v-if="showEmptyTip" class="empty-tip">
+          <el-empty description="当前没有手动抄表设备" :image-size="100"></el-empty>
         </div>
       </el-col>
+
+      <!-- 右侧内容区域 -->
       <el-col :span="20" :xs="24">
+        <!-- Tab切换 -->
         <el-tabs v-model="activeTab" @tab-click="handleTabChange">
-          <el-tab-pane label="电表抄报" name="first">
-          </el-tab-pane>
-          <el-tab-pane label="水表抄报" name="second">
-          </el-tab-pane>
+          <el-tab-pane label="电表抄报" name="first"></el-tab-pane>
+          <el-tab-pane label="水表抄报" name="second"></el-tab-pane>
         </el-tabs>
 
-        <!-- 填报按钮 -->
-        <el-button type="primary" icon="el-icon-info" size="mini" @click="handleRecord">填报</el-button>
-        <!-- 历史抄表记录 -->
-        <el-form ref="recListForm" :model="recListForm" label-width="80px">
-          <el-form-item label="年份" prop="year">
-            <el-date-picker v-model="recListForm.year" :clearable="false" @change="getHistoryList" value-format="yyyy" type="year"
-                            placeholder="选择年份">
-            </el-date-picker>
-          </el-form-item>
-          <el-form-item label="计量设备" prop="year">
-            <el-input disabled :value="recListForm.deviceCode" />
-          </el-form-item>
-          <el-form-item label="抄表历史" prop="recList">
-            <el-table v-loading="loading" :data="recListForm.recList" max-height="280px">
-              <el-table-column label="年份" align="center" prop="year" />
-              <el-table-column label="月份" align="center" prop="meterMonth" >
-                <template slot-scope="scope">
-                  <!-- 使用 JavaScript 字符串方法截取月份部分 -->
-                  {{ scope.row.meterMonth.slice(-2) }}
-                </template>
-              </el-table-column>
-              <el-table-column label="抄表示数" align="center" prop="meterReading" />
-              <el-table-column label="用量" align="center" prop="increase" />
-              <el-table-column label="抄表日期" align="center" prop="meterTime" />
-              <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
-                <template slot-scope="scope">
-                  <el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="!canEdit(scope.row.meterMonth)"
-                             @click="updateRecord(scope.row)">修改</el-button>
-                </template>
-              </el-table-column>
-            </el-table>
-          </el-form-item>
-        </el-form>
-
-        <!--  填报  -->
-        <el-dialog :title="fillTitle" :visible.sync="fillFormOpen" width="500px" append-to-body>
-          <el-form ref="fillForm" :model="fillForm" label-width="150px">
+        <!-- 有设备时显示操作区域 -->
+        <div v-if="hasManualDevices">
+          <!-- 填报按钮 -->
+          <el-button
+            type="primary"
+            icon="el-icon-edit"
+            size="mini"
+            @click="handleRecord"
+            :disabled="!currentDevice"
+          >
+            填报
+          </el-button>
+
+          <!-- 历史抄表记录 -->
+          <el-form ref="recListForm" :model="recListForm" label-width="100px" style="margin-top: 20px">
+            <el-form-item label="年份" prop="year">
+              <el-date-picker
+                v-model="recListForm.year"
+                :clearable="false"
+                @change="getHistoryList"
+                value-format="yyyy"
+                type="year"
+                placeholder="选择年份"
+              />
+            </el-form-item>
+
+            <el-form-item label="计量设备" prop="deviceCode">
+              <el-input disabled :value="currentDeviceName" placeholder="请选择左侧设备" />
+            </el-form-item>
+
+            <el-form-item label="抄表历史" prop="recList">
+              <el-table v-loading="loading" :data="recListForm.recList" max-height="400px">
+                <el-table-column label="年份" align="center" prop="year" width="80" />
+                <el-table-column label="月份" align="center" prop="meterMonth" width="80">
+                  <template slot-scope="scope">
+                    {{ scope.row.meterMonth ? scope.row.meterMonth.slice(-2) : '-' }}
+                  </template>
+                </el-table-column>
+                <el-table-column label="抄表示数" align="center" prop="meterReading">
+                  <template slot-scope="scope">
+                    {{ formatNumber(scope.row.meterReading) }}
+                  </template>
+                </el-table-column>
+                <el-table-column label="用量" align="center" prop="increase">
+                  <template slot-scope="scope">
+                    <span :class="{ 'text-success': scope.row.increase > 0 }">
+                      {{ formatNumber(scope.row.increase) }}
+                    </span>
+                  </template>
+                </el-table-column>
+                <el-table-column label="抄表日期" align="center" prop="meterTime" width="180">
+                  <template slot-scope="scope">
+                    {{ formatDate(scope.row.meterTime) }}
+                  </template>
+                </el-table-column>
+                <el-table-column label="操作" align="center" width="100">
+                  <template slot-scope="scope">
+                    <el-button
+                      type="text"
+                      icon="el-icon-edit"
+                      size="mini"
+                      :disabled="!canEdit(scope.row.meterMonth)"
+                      @click="updateRecord(scope.row)"
+                    >
+                      修改
+                    </el-button>
+                  </template>
+                </el-table-column>
+              </el-table>
+
+              <!-- 无数据提示 -->
+              <div v-if="recListForm.recList.length === 0 && !loading" class="table-empty">
+                <el-empty description="暂无抄表记录" :image-size="60"></el-empty>
+              </div>
+            </el-form-item>
+          </el-form>
+        </div>
+
+        <!-- 无设备提示 -->
+        <div v-else class="no-device-container">
+          <el-empty
+            :description="emptyDescription"
+            :image-size="200"
+          >
+            <template v-if="activeTab === 'first'">
+              <div class="empty-footer">
+                <p>当前没有配置手动抄表的电表设备</p>
+                <p class="sub-text">如需添加,请联系系统管理员</p>
+              </div>
+            </template>
+            <template v-else>
+              <div class="empty-footer">
+                <p>当前没有配置手动抄表的水表设备</p>
+                <p class="sub-text">如需添加,请联系系统管理员</p>
+              </div>
+            </template>
+          </el-empty>
+        </div>
+
+        <!-- 填报对话框 -->
+        <el-dialog
+          :title="fillTitle"
+          :visible.sync="fillFormOpen"
+          width="500px"
+          append-to-body
+          :close-on-click-modal="false"
+        >
+          <el-form ref="fillForm" :model="fillForm" :rules="fillRules" label-width="120px">
             <el-form-item label="表计编号">
               <el-input disabled :value="fillForm.deviceCode" />
             </el-form-item>
+
             <el-form-item label="上次抄表日期">
-              <el-input disabled :value="fillForm.lastTime" />
+              <el-input disabled :value="formatDate(fillForm.lastTime) || '无'" />
             </el-form-item>
+
             <el-form-item label="上次抄表示数">
-              <el-input disabled :value="fillForm.lastReading" />
+              <el-input disabled :value="formatNumber(fillForm.lastReading) || '0'" />
             </el-form-item>
-            <hr>
-            <el-form-item label="本次抄表月份">
-              <el-input disabled :value="currentMonth" />
+
+            <el-divider></el-divider>
+
+            <el-form-item label="抄表月份" prop="selectedMonth">
+              <el-date-picker
+                v-model="fillForm.selectedMonth"
+                :disabled="!ifAdd"
+                :clearable="false"
+                type="month"
+                value-format="yyyyMM"
+                format="yyyy年MM月"
+                placeholder="选择抄表月份"
+                :picker-options="monthPickerOptions"
+                @change="handleMonthChange"
+                style="width: 100%;"
+              >
+              </el-date-picker>
+              <div v-if="monthWarning" class="warning-text">
+                <i class="el-icon-warning"></i>
+                {{ monthWarning }}
+              </div>
             </el-form-item>
-            <el-form-item required :rules="[{required:true,message:'请输入本次抄表示数'}]" label="本次抄表示数" prop="meterReading">
-              <el-input v-model="fillForm.meterReading" oninput="value=value.replace(/[^\d]/g,'')" placeholder="请输入本次抄表示数" />
+
+            <el-form-item label="本次抄表示数" prop="meterReading">
+              <el-input
+                v-model="fillForm.meterReading"
+                placeholder="请输入本次抄表示数"
+                @input="handleMeterReadingInput"
+              >
+                <template slot="append">{{ activeTab === 'first' ? 'kWh' : 'm³' }}</template>
+              </el-input>
             </el-form-item>
-            <el-form-item label="综合倍率" prop="magnification">
-              <el-input disabled :value="fillForm.magnification" />
+
+            <el-form-item label="综合倍率">
+              <el-input disabled :value="fillForm.magnification || 1" />
             </el-form-item>
-            <el-form-item :label="`参考增量(${activeTab==='first'?'度':'吨'})`">
-              <el-input disabled :value="increase" />
+
+            <el-form-item :label="`参考增量`">
+              <el-input disabled :value="formatNumber(increase)">
+                <template slot="append">{{ activeTab === 'first' ? 'kWh' : 'm³' }}</template>
+              </el-input>
+              <div v-if="increaseWarning" class="warning-text">
+                <i class="el-icon-warning"></i>
+                {{ increaseWarning }}
+              </div>
             </el-form-item>
           </el-form>
 
           <div slot="footer" class="dialog-footer">
-            <el-button type="primary" @click="submitFillForm">确 定</el-button>
             <el-button @click="fillCancel">取 消</el-button>
+            <el-button type="primary" @click="submitFillForm" :loading="submitLoading">确 定</el-button>
           </div>
         </el-dialog>
       </el-col>
@@ -91,60 +228,51 @@
 </template>
 
 <script>
-import { listDevice } from '@/api/device/meterDevice'
 import { listMeterReadingByParam, getLastRecord, addMeterReading, updateMeterReading } from '@/api/device/meterRead'
 import { getDeviceTree } from '@/api/device/meterDevice'
-import Treeselect from '@riophae/vue-treeselect'
-import '@riophae/vue-treeselect/dist/vue-treeselect.css'
 import { dateFormat } from '@/utils/index.js'
+
 export default {
-  name: 'Device',
-  components: { Treeselect },
+  name: 'ManualMeterReading',
   data() {
     const nowDay = new Date()
     return {
+      // 当前月份
       currentMonth: dateFormat(nowDay, 'yyyyMM'),
+      // 当前激活的标签页
       activeTab: 'first',
       // 遮罩层
-      loading: true,
-      // 总条数
-      total: 0,
-      // 计量设备表格数据
-      deviceList: [],
-      // 弹出层标题
-      title: '',
-      // 记录表单开启
-      recListOpen: false,
+      loading: false,
+      // 提交中
+      submitLoading: false,
       // 填报表单开启
       fillFormOpen: false,
       // 区域名称
-      areaName: undefined,
+      areaName: '',
+      // 树形数据
       areaOptions: [],
+      // 树形控件配置
       defaultProps: {
         children: 'children',
         label: 'label'
       },
-      // 能源分类树
-      emsClsOptions: [
-        { code: 45, name: '电表' },
-        { code: 70, name: '水表' }
-      ],
       // 查询参数
       queryParams: {
-        pageNum: 1,
-        pageSize: 10,
-        areaCode: null,
-        deviceCode: null,
-        deviceName: null,
-        meterCls: 45
+        meterCls: 45  // 45-电表,70-水表
       },
-      // 电表表单参数
+      // 当前选中的设备
+      currentDevice: null,
+      // 当前设备名称
+      currentDeviceName: '',
+      // 抄表历史表单
       recListForm: {
-        year: '',
+        year: dateFormat(nowDay, 'yyyy'),
         deviceCode: '',
         recList: []
       },
+      // 填报表单标题
       fillTitle: '',
+      // 填报表单
       fillForm: {
         id: '',
         deviceCode: '',
@@ -154,265 +282,626 @@ export default {
         meterTime: '',
         meterReading: '',
         increase: '',
-        magnification: 1
+        magnification: 1,
+        selectedMonth: '' // 新增:选择的抄表月份
       },
-      curRow: {},
-      ifAdd: true // 判断 新增还是修改
+      // 填报表单校验规则
+      fillRules: {
+        selectedMonth: [
+          { required: true, message: '请选择抄表月份', trigger: 'change' }
+        ],
+        meterReading: [
+          { required: true, message: '请输入本次抄表示数', trigger: 'blur' },
+          { validator: this.validateMeterReading, trigger: 'blur' }
+        ]
+      },
+      // 是否新增
+      ifAdd: true,
+      // 是否有手动抄表设备
+      hasManualDevices: false,
+      // 显示空提示
+      showEmptyTip: false,
+      // 月份选择器配置
+      monthPickerOptions: {
+        disabledDate: (date) => {
+          // 禁用未来月份(超过当前月份的不允许选择)
+          const currentDate = new Date()
+          const currentYear = currentDate.getFullYear()
+          const currentMonth = currentDate.getMonth()
+
+          return date.getTime() > new Date(currentYear, currentMonth).getTime()
+        }
+      },
+      // 月份警告信息
+      monthWarning: '',
+      // 已存在的月份记录(用于检查冲突)
+      existingMonths: []
     }
   },
   computed: {
+    // 计算增量
     increase() {
       const { meterReading, magnification, lastReading } = this.fillForm
       if (meterReading && magnification) {
-        return (meterReading - (lastReading || 0)) * magnification
+        const reading = parseFloat(meterReading) || 0
+        const last = parseFloat(lastReading) || 0
+        const mag = parseFloat(magnification) || 1
+        return (reading - last) * mag
+      }
+      return 0
+    },
+    // 增量警告
+    increaseWarning() {
+      if (this.increase < 0) {
+        return '增量为负数,请检查抄表示数是否正确'
+      }
+      if (this.increase === 0 && this.fillForm.meterReading) {
+        return '增量为0,请确认是否正常'
       }
       return ''
+    },
+    // 空提示描述
+    emptyDescription() {
+      return this.activeTab === 'first'
+        ? '暂无手动抄表电表设备'
+        : '暂无手动抄表水表设备'
     }
   },
   created() {
-    this.getAreaTreeSelect().then(() => {
-      this.getList();
-      /** 自动触发第一个 id 非 null 的树节点*/
-      this.$nextTick(() => {
-        const firstValidNode = this.findFirstValidNode(this.areaOptions);
-        if (firstValidNode) {
-          this.handleNodeClick(firstValidNode);
-        }
-      });
-    });
+    this.init()
   },
   methods: {
-    /** 查询计量设备列表 */
-    getList() {
+    // 初始化
+    async init() {
+      await this.getAreaTreeSelect()
+    },
+
+    // 获取设备树
+    async getAreaTreeSelect() {
       this.loading = true
-      listDevice(this.queryParams).then(response => {
-        this.deviceList = response.rows.map(item=>({
-          ...item,
-          meterMonth:'',
-          lastTime:'',
-          lastReading:'',
-        }))
-        this.total = response.total
-        this.loading = false
-        this.deviceList.forEach(async item => {
-          await getLastRecord(item.areaCode, item.deviceCode).then(({ data }) => {
-            if (data) {
-              item.lastTime = data.meterTime
-              item.meterMonth = data.meterMonth
-              item.lastReading = data.meterReading
+      try {
+        // colMode=1 表示获取手动抄表类型的设备
+        const response = await getDeviceTree(0, this.queryParams.meterCls, 1)
+        this.areaOptions = response.data || []
+
+        // 判断是否有手动抄表设备
+        this.hasManualDevices = this.hasDevicesInTree(this.areaOptions)
+        this.showEmptyTip = !this.hasManualDevices
+
+        if (this.hasManualDevices) {
+          // 自动选择第一个设备节点
+          this.$nextTick(() => {
+            const firstDeviceNode = this.findFirstDeviceNode(this.areaOptions)
+            if (firstDeviceNode) {
+              this.handleNodeClick(firstDeviceNode)
+              // 高亮选中的节点
+              this.$refs.tree.setCurrentKey(firstDeviceNode.id)
             }
           })
-        })
-
-      })
-    },
-    /** 搜索按钮操作 */
-    handleQuery() {
-      this.queryParams.pageNum = 1
-      this.getList()
-    },
-    /** 重置按钮操作 */
-    resetQuery() {
-      this.resetForm('queryForm')
-      this.handleQuery()
+        }
+      } catch (error) {
+        console.error('获取设备树失败:', error)
+        this.$message.error('获取设备数据失败')
+        this.areaOptions = []
+        this.hasManualDevices = false
+        this.showEmptyTip = true
+      } finally {
+        this.loading = false
+      }
     },
-    handleTabChange() {
-      if (this.activeTab === 'first') {
-        // 电表抄表
-        this.queryParams.meterCls = '45'
-      } else if (this.activeTab === 'second') {
-        // 水表抄表
-        this.queryParams.meterCls = '70'
+
+    // 检查树中是否有设备
+    hasDevicesInTree(nodes) {
+      for (const node of nodes) {
+        if (node.type === 'device') {
+          return true
+        }
+        if (node.children && node.children.length > 0) {
+          if (this.hasDevicesInTree(node.children)) {
+            return true
+          }
+        }
       }
-      /** 清理数据*/
-      this.recListForm = {
-        year: '',
-        deviceCode: '',
-        recList: []
-      };
-      this.fillForm = this.$options.data().fillForm;
-      this.fillFormOpen = false;
-      this.getAreaTreeSelect() // 重新加载设备树
-      this.handleQuery()
+      return false
     },
 
-    async getAreaTreeSelect() {
-      await getDeviceTree(0, this.queryParams.meterCls, 1).then(response => {
-        this.areaOptions = response.data || []
-      }).catch(error => {
-        console.error('获取设备树失败:', error)
-      })
+    // 查找第一个设备节点
+    findFirstDeviceNode(nodes) {
+      for (const node of nodes) {
+        if (node.type === 'device') {
+          return node
+        }
+        if (node.children && node.children.length > 0) {
+          const foundNode = this.findFirstDeviceNode(node.children)
+          if (foundNode) return foundNode
+        }
+      }
+      return null
     },
 
-    // 筛选节点
+    // 筛选节点
     filterNode(value, data) {
       if (!value) return true
       return data.label.indexOf(value) !== -1
     },
-    // 节点单击事件
+
+    // 过滤树
+    filterTree() {
+      this.$refs.tree.filter(this.areaName)
+    },
+
+    // 节点点击事件
     handleNodeClick(data) {
-      if (data.id === null) {
-        this.$message.warning('该节点不是设备不可点击');
-        return;
+      // 只处理设备节点
+      if (data.type !== 'device') {
+        return
       }
-      this.recListForm.year = dateFormat(new Date(), 'yyyy')
+
+      this.currentDevice = data
+      this.currentDeviceName = data.label
       this.recListForm.deviceCode = data.id
-      listMeterReadingByParam({
-        deviceCode: data.id,
-        year: dateFormat(new Date(), 'yyyy'),
-        orderFlag: 'desc'
-      }).then(response => {
-        this.recListForm.recList = response.data || []
-        this.curRow =  this.recListForm.recList;
-      })
-    },
-    /** 第一个 id 非 null 的节点*/
-    findFirstValidNode(nodes) {
-      for (const node of nodes) {
-        if (node.id !== null) {
-          return node;
-        }
-        if (node.children && node.children.length > 0) {
-          const foundNode = this.findFirstValidNode(node.children);
-          if (foundNode) return foundNode;
-        }
-      }
-      return null;
+
+      // 获取该设备的抄表历史
+      this.getHistoryList()
     },
 
-    getHistoryList() {
+    // 获取历史记录列表
+    async getHistoryList() {
+      if (!this.recListForm.deviceCode) {
+        this.recListForm.recList = []
+        return
+      }
 
-      listMeterReadingByParam({
-        deviceCode: this.recListForm.deviceCode,
-        year: this.recListForm.year,
-        orderFlag: 'desc'
-      }).then(response => {
+      this.loading = true
+      try {
+        const response = await listMeterReadingByParam({
+          deviceCode: this.recListForm.deviceCode,
+          year: this.recListForm.year,
+          orderFlag: 'desc'
+        })
         this.recListForm.recList = response.data || []
-      })
+
+        // 更新已存在的月份列表
+        this.existingMonths = this.recListForm.recList.map(item => item.meterMonth)
+      } catch (error) {
+        console.error('获取历史记录失败:', error)
+        this.$message.error('获取历史记录失败')
+        this.recListForm.recList = []
+        this.existingMonths = []
+      } finally {
+        this.loading = false
+      }
     },
 
-    /** 新填报*/
-    handleRecord() {
-      this.fillForm = this.$options.data().fillForm;
-      this.resetForm('fillForm');
-      this.fillTitle = '填报抄表记录';
-
-      /** 获取最新抄表记录*/
-      const latestRecord = this.recListForm.recList[0];
-      if (!latestRecord) {
-        this.$modal.msgError('没有可填报的记录');
-        return;
+    // Tab切换处理
+    async handleTabChange() {
+      // 切换计量类型
+      this.queryParams = {
+        meterCls: this.activeTab === 'first' ? '45' : '70'
       }
 
-      // 填充填报表单数据
-      this.fillForm.deviceCode = this.recListForm.deviceCode;
-      this.fillForm.areaCode = latestRecord.areaCode;
-      this.fillForm.magnification = latestRecord.magnification || 1;
-      this.fillForm.lastTime = latestRecord.meterTime;
-      this.fillForm.lastReading = latestRecord.meterReading;
-      this.fillFormOpen = true; // 打开填报表单
+      // 重置数据
+      this.currentDevice = null
+      this.currentDeviceName = ''
+      this.recListForm.deviceCode = ''
+      this.recListForm.recList = []
+      this.fillForm = this.$options.data().fillForm
+      this.fillFormOpen = false
+      this.existingMonths = []
+
+      // 重新加载设备树
+      await this.getAreaTreeSelect()
     },
-    // async handleRecord(row) {
-    //   this.fillForm = this.$options.data().fillForm
-    //   this.resetForm('fillForm')
-    //   this.fillTitle = row.deviceName
-    //   this.fillForm.deviceCode = row.deviceCode
-    //   this.fillForm.areaCode = row.areaCode
-    //   this.fillForm.magnification = row.magnification
-    //   this.fillForm.lastTime = row.lastTime
-    //   this.fillForm.lastReading = row.lastReading
-    //   this.ifAdd = true
-    // let existFlag = false
-    // // 获取上次填报记录
-    // await getLastRecord(row.areaCode, row.deviceCode).then(({ data }) => {
-    //   if (data) {
-    //     if (data.meterMonth === this.currentMonth) {
-    //       existFlag = true
-    //     } else {
-    //       this.fillForm.lastTime = data.meterTime
-    //       this.fillForm.lastReading = data.meterReading
-    //     }
-    //   }
-    // })
-    // if (row.meterMonth===this.currentMonth) {
-    //   this.$modal.msgError('当前月份已填报')
-    // } else {
-    //   this.fillFormOpen = true
-    //   // }
-    // },
+
+    // 判断是否可以编辑(只能编辑当月记录)
     canEdit(meterMonth) {
       return meterMonth === this.currentMonth
     },
-    canFill (lastTime) {
-      return dateFormat(new Date(lastTime),'yyyyMM')!==this.currentMonth
+
+    // 新增填报
+    async handleRecord() {
+      if (!this.currentDevice) {
+        this.$message.warning('请先选择设备')
+        return
+      }
+
+      this.ifAdd = true
+      this.fillForm = this.$options.data().fillForm
+      this.fillTitle = `填报抄表记录 - ${this.currentDeviceName}`
+      this.monthWarning = ''
+
+      // 设置基础信息
+      this.fillForm.deviceCode = this.currentDevice.id
+      this.fillForm.areaCode = this.getAreaCode(this.currentDevice)
+
+      // 默认选择当前月份(但用户可以修改)
+      this.fillForm.selectedMonth = this.currentMonth
+
+      // 获取最新的抄表记录作为上次记录
+      this.updateLastReadingInfo()
+
+      this.fillFormOpen = true
     },
-    /**新修改*/
+
+    // 修改记录
     updateRecord(row) {
-      this.ifAdd = false;
-      this.fillFormOpen = true;
-      this.fillForm = this.$options.data().fillForm;
-      this.resetForm('fillForm');
-      this.fillTitle = `修改抄表记录 - ${row.deviceCode}`;
-
-      // 获取当前选中的设备信息
-      const currentDevice = this.recListForm.recList.find(
-        (item) => item.deviceCode === row.deviceCode
-      );
-      this.fillForm.deviceCode = row.deviceCode;
-      this.fillForm.areaCode = currentDevice.areaCode;
-      this.fillForm.magnification = currentDevice.magnification || 1;
-      this.fillForm.id = row.id;
-      this.fillForm.lastTime = row.meterTime;
-      this.fillForm.lastReading = row.meterReading;
-      this.fillForm.meterReading = row.meterReading;
+      if (!this.canEdit(row.meterMonth)) {
+        this.$message.warning('只能修改当月的抄表记录')
+        return
+      }
+
+      this.ifAdd = false
+      this.fillForm = this.$options.data().fillForm
+      this.fillTitle = `修改抄表记录 - ${this.currentDeviceName}`
+      this.monthWarning = ''
+
+      // 填充表单数据
+      this.fillForm.id = row.id
+      this.fillForm.deviceCode = row.deviceCode
+      this.fillForm.areaCode = row.areaCode
+      this.fillForm.magnification = row.magnification || 1
+      this.fillForm.meterReading = row.meterReading
+      this.fillForm.selectedMonth = row.meterMonth
+
+      // 查找该记录月份之前最近的一条记录作为上次抄表信息
+      this.findPreviousRecord(row.meterMonth)
+
+      this.fillFormOpen = true
     },
-    // updateRecord(row) {
-    //   this.ifAdd = false
-    //   this.fillFormOpen = true
-    //   this.fillForm = this.$options.data().fillForm
-    //   this.resetForm('fillForm')
-    //   this.fillTitle = this.curRow.objName
-    //   this.fillForm.deviceCode = this.curRow.deviceCode
-    //   this.fillForm.areaCode = this.curRow.areaCode
-    //   this.fillForm.magnification = this.curRow.magnification
-    //   this.fillForm.id = row.id
-    //   this.fillForm.lastTime = row.lastTime
-    //   this.fillForm.lastReading = row.lastReading
-    //   this.fillForm.meterReading = row.meterReading
-    // },
+
+    // 处理月份选择变化
+    handleMonthChange(selectedMonth) {
+      if (!selectedMonth) return
+
+      this.monthWarning = ''
+
+      // 检查是否与已存在的记录冲突
+      if (this.ifAdd && this.existingMonths.includes(selectedMonth)) {
+        this.monthWarning = '该月份已存在抄表记录,请选择其他月份或进行修改操作'
+        return
+      }
+
+      // 根据选择的月份更新上次抄表信息
+      this.updateLastReadingInfo(selectedMonth)
+    },
+
+    // 更新上次抄表信息
+    updateLastReadingInfo(selectedMonth = null) {
+      const targetMonth = selectedMonth || this.fillForm.selectedMonth
+      if (!targetMonth || !this.recListForm.recList.length) {
+        // 没有历史记录时
+        this.fillForm.lastTime = ''
+        this.fillForm.lastReading = ''
+        this.fillForm.magnification = 1
+        return
+      }
+
+      this.findPreviousRecord(targetMonth)
+    },
+
+    // 查找指定月份之前的记录作为上次抄表信息
+    findPreviousRecord(targetMonth) {
+      if (!targetMonth || !this.recListForm.recList.length) {
+        this.fillForm.lastTime = ''
+        this.fillForm.lastReading = ''
+        this.fillForm.magnification = this.fillForm.magnification || 1
+        return
+      }
+
+      // 查找目标月份之前最近的一条记录
+      const sortedRecords = [...this.recListForm.recList].sort((a, b) => {
+        return b.meterMonth.localeCompare(a.meterMonth)
+      })
+
+      let lastRecord = null
+      for (const record of sortedRecords) {
+        // 跳过当前正在修改的记录(如果是修改操作)
+        if (!this.ifAdd && record.meterMonth === targetMonth) {
+          continue
+        }
+
+        if (record.meterMonth < targetMonth) {
+          lastRecord = record
+          break
+        }
+      }
+
+      if (lastRecord) {
+        this.fillForm.lastTime = lastRecord.meterTime
+        this.fillForm.lastReading = lastRecord.meterReading
+        // 如果当前记录没有设置倍率,使用上次记录的倍率
+        if (!this.fillForm.magnification) {
+          this.fillForm.magnification = lastRecord.magnification || 1
+        }
+      } else {
+        // 如果没找到更早的记录,说明这是最早的记录
+        this.fillForm.lastTime = ''
+        this.fillForm.lastReading = '0'
+        this.fillForm.magnification = this.fillForm.magnification || 1
+      }
+    },
+
+    // 提交填报表单
     submitFillForm() {
-      this.$refs['fillForm'].validate(valid => {
-        if (valid) {
-          this.fillForm.increase = this.increase
+      this.$refs['fillForm'].validate(async valid => {
+        if (!valid) return
+
+        // 检查月份冲突
+        if (this.ifAdd && this.existingMonths.includes(this.fillForm.selectedMonth)) {
+          this.$message.error('该月份已存在抄表记录,请选择其他月份')
+          return
+        }
+
+        this.submitLoading = true
+
+        // 设置提交数据
+        const submitData = {
+          ...this.fillForm,
+          increase: this.increase,
+          meterMonth: this.fillForm.selectedMonth, // 使用选择的月份
+          meterTime: this.generateMeterTime(this.fillForm.selectedMonth) // 根据月份生成抄表时间
+        }
+
+        try {
           if (this.ifAdd) {
-            addMeterReading(this.fillForm).then(({ code, msg }) => {
-              if (code === 200) {
-                this.$modal.msgSuccess('填报成功')
-                this.fillFormOpen = false
-              } else {
-                this.$modal.msgError(msg)
-              }
-            })
+            const response = await addMeterReading(submitData)
+            if (response.code === 200) {
+              this.$message.success('填报成功')
+              this.fillFormOpen = false
+              this.getHistoryList()
+            } else {
+              this.$message.error(response.msg || '填报失败')
+            }
           } else {
-            updateMeterReading(this.fillForm).then(({ code, msg }) => {
-              if (code === 200) {
-                this.$modal.msgSuccess('修改成功')
-                this.getHistoryList()
-                this.fillFormOpen = false
-              } else {
-                this.$modal.msgError(msg)
-              }
-            })
+            const response = await updateMeterReading(submitData)
+            if (response.code === 200) {
+              this.$message.success('修改成功')
+              this.fillFormOpen = false
+              this.getHistoryList()
+            } else {
+              this.$message.error(response.msg || '修改失败')
+            }
           }
+        } catch (error) {
+          console.error('提交失败:', error)
+          this.$message.error('操作失败')
+        } finally {
+          this.submitLoading = false
         }
       })
     },
+
+    // 根据月份生成抄表时间(月末日期)
+    generateMeterTime(monthStr) {
+      if (!monthStr || monthStr.length !== 6) return ''
+
+      const year = parseInt(monthStr.substring(0, 4))
+      const month = parseInt(monthStr.substring(4, 6))
+
+      // 获取该月的最后一天
+      const lastDay = new Date(year, month, 0).getDate()
+      return `${year}-${month.toString().padStart(2, '0')}-${lastDay}`
+    },
+
+    // 取消填报
     fillCancel() {
       this.fillFormOpen = false
+      this.monthWarning = ''
+      this.resetForm('fillForm')
+    },
+
+    // 重置表单
+    resetForm(formName) {
+      if (this.$refs[formName]) {
+        this.$refs[formName].resetFields()
+      }
+    },
+
+    // 处理抄表示数输入
+    handleMeterReadingInput(value) {
+      // 只允许输入数字和小数点
+      this.fillForm.meterReading = value.replace(/[^\d.]/g, '')
+    },
+
+    // 验证抄表示数
+    validateMeterReading(rule, value, callback) {
+      if (!value) {
+        callback(new Error('请输入抄表示数'))
+      } else if (isNaN(value)) {
+        callback(new Error('请输入有效的数字'))
+      } else if (parseFloat(value) < 0) {
+        callback(new Error('抄表示数不能为负数'))
+      } else if (this.fillForm.lastReading && parseFloat(value) < parseFloat(this.fillForm.lastReading)) {
+        callback(new Error('本次抄表示数不能小于上次抄表示数'))
+      } else {
+        callback()
+      }
+    },
+
+    // 获取区域编码
+    getAreaCode(node) {
+      // 从节点向上查找区域编码
+      // 这里简化处理,实际可能需要递归查找父节点
+      return node.areaCode || node.parentCode || ''
+    },
+
+    // 格式化数字
+    formatNumber(value, decimals = 2) {
+      if (value === null || value === undefined || value === '') return '-'
+      const num = parseFloat(value)
+      if (isNaN(num)) return '-'
+      return num.toFixed(decimals)
+    },
+
+    // 格式化日期
+    formatDate(date) {
+      if (!date) return '-'
+      return dateFormat(new Date(date), 'yyyy-MM-dd')
     },
-    closeRecList() {
-      this.recListOpen = false
+
+    // 格式化月份
+    formatMonth(month) {
+      if (!month || month.length !== 6) return month
+      return `${month.substring(0, 4)}年${month.substring(4, 6)}月`
     }
   }
 }
 </script>
+
+<style scoped>
+/* 树容器样式 */
+.tree-container {
+  height: calc(100vh - 250px);
+  overflow-y: auto;
+  border: 1px solid #e8e8e8;
+  border-radius: 4px;
+  padding: 10px;
+  background-color: #fafafa;
+}
+
+/* 树节点样式 */
+.tree-node {
+  display: flex;
+  align-items: center;
+  width: 100%;
+  padding: 4px 0;
+  transition: all 0.3s;
+}
+
+.node-icon {
+  margin-right: 8px;
+  font-size: 16px;
+  transition: color 0.3s;
+}
+
+.area-icon {
+  color: #909399;
+}
+
+.device-icon {
+  color: #409EFF;
+}
+
+.node-label {
+  flex: 1;
+  font-size: 14px;
+}
+
+/* 区域节点样式 */
+.area-node {
+  cursor: default;
+  opacity: 0.8;
+}
+
+/* 设备节点样式 */
+.device-node {
+  cursor: pointer;
+}
+
+.device-node:hover {
+  background-color: #f0f7ff;
+  border-radius: 4px;
+  padding-left: 4px;
+}
+
+.device-node:hover .device-icon {
+  color: #2b7bff;
+  transform: scale(1.1);
+}
+
+/* 高亮当前选中的节点 */
+.el-tree-node.is-current > .el-tree-node__content .device-node {
+  background-color: #e6f7ff;
+  border-radius: 4px;
+  padding-left: 4px;
+}
+
+.el-tree-node.is-current > .el-tree-node__content .device-icon {
+  color: #1890ff;
+  font-weight: bold;
+}
+
+/* 空提示样式 */
+.empty-tip {
+  margin-top: 20px;
+  text-align: center;
+  color: #909399;
+}
+
+.no-device-container {
+  margin-top: 100px;
+  text-align: center;
+}
+
+.empty-footer {
+  margin-top: 20px;
+}
+
+.empty-footer p {
+  margin: 5px 0;
+  color: #606266;
+}
+
+.empty-footer .sub-text {
+  font-size: 12px;
+  color: #909399;
+}
+
+/* 表格空数据 */
+.table-empty {
+  padding: 40px 0;
+}
+
+/* 警告文字 */
+.warning-text {
+  color: #E6A23C;
+  font-size: 12px;
+  margin-top: 5px;
+}
+
+.warning-text i {
+  margin-right: 4px;
+}
+
+/* 成功文字 */
+.text-success {
+  color: #67C23A;
+  font-weight: 500;
+}
+
+/* 对话框底部 */
+.dialog-footer {
+  text-align: right;
+}
+
+/* 分隔线样式 */
+.el-divider {
+  margin: 15px 0;
+}
+
+/* 表单项间距调整 */
+.el-form-item {
+  margin-bottom: 18px;
+}
+
+/* 按钮组样式 */
+.button-group {
+  margin-bottom: 15px;
+}
+
+/* 月份选择器样式 */
+ .el-date-editor.el-input {
+  width: 100%;
+}
+
+/* 响应式布局 */
+@media (max-width: 768px) {
+  .tree-container {
+    height: 200px;
+  }
+
+  .el-col-xs-24 {
+    margin-bottom: 20px;
+  }
+}
+</style>