Преглед на файлове

项目总览相关接口

hanzhengyi преди 1 седмица
родител
ревизия
1cac234e8b
променени са 45 файла, в които са добавени 2408 реда и са изтрити 145 реда
  1. 7 6
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsAnalysisController.java
  2. 0 37
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsAuthController.java
  3. 32 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsDeviceEventController.java
  4. 4 4
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsGatewayController.java
  5. 34 25
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsModelController.java
  6. 99 4
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsOverviewController.java
  7. 64 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsProjectController.java
  8. 12 11
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsReportController.java
  9. 31 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsSpaceController.java
  10. 92 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/DmpProduct.java
  11. 71 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsDeviceEvent.java
  12. 47 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsProjectConfiguration.java
  13. 59 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsProjectConversionFactor.java
  14. 44 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsProjectDeviceSystem.java
  15. 11 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/DmpProductMapper.java
  16. 11 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsDeviceEventMapper.java
  17. 11 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsProjectConfigurationMapper.java
  18. 11 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsProjectConversionFactorMapper.java
  19. 11 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsProjectDeviceSystemMapper.java
  20. 0 14
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsAuthService.java
  21. 18 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsDeviceEventService.java
  22. 18 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsDeviceReportService.java
  23. 7 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsModelService.java
  24. 44 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsOverviewService.java
  25. 11 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsProjectConfigurationService.java
  26. 36 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsProjectService.java
  27. 31 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsSpaceService.java
  28. 0 30
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsAuthServiceImpl.java
  29. 174 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsDeviceEventServiceImpl.java
  30. 126 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsDeviceReportServiceImpl.java
  31. 70 2
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsModelServiceImpl.java
  32. 647 5
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsOverviewServiceImpl.java
  33. 16 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsProjectConfigurationServiceImpl.java
  34. 63 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsProjectServiceImpl.java
  35. 136 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsSpaceServiceImpl.java
  36. 46 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/DeviceEnergyReportItemVO.java
  37. 37 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/DeviceEnergyReportRequest.java
  38. 25 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/DeviceEventDTO.java
  39. 24 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/DeviceEventVO.java
  40. 20 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsOverviewDeviceSystemStatVO.java
  41. 20 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsOverviewEnergyItemVO.java
  42. 94 7
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsProjectResponse.java
  43. 44 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsSpaceForestNodeVO.java
  44. 28 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EnergyTypeWrapperProductVO.java
  45. 22 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/SimpleProductVO.java

+ 7 - 6
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsAnalysisController.java

@@ -9,15 +9,16 @@ import org.springframework.web.bind.annotation.*;
 /**
  * 能耗分析接口
  * 趋势、指标、分类占比、区域分析、对比分析
+ * 基础路径前缀:/analysis(网关再加 /prod-api/service-ems)
  */
 @RestController
-@RequestMapping("/prod-api/service-ems")
+@RequestMapping("/analysis")
 public class EmsAnalysisController {
 
     @Autowired
     private EmsAnalysisService emsAnalysisService;
 
-    @GetMapping("/analysis/trend")
+    @GetMapping("/trend")
     public ApiResult<EmsTrendResponse> getTrend(
             @RequestParam(required = false) Long projectId,
             @RequestParam String timeDimension,
@@ -26,7 +27,7 @@ public class EmsAnalysisController {
         return ApiResult.success(emsAnalysisService.getTrend(projectId, timeDimension, timeValue, energyTypeId));
     }
 
-    @GetMapping("/analysis/trend/indicators")
+    @GetMapping("/trend/indicators")
     public ApiResult<EmsTrendIndicatorsResponse> getTrendIndicators(
             @RequestParam(required = false) Long projectId,
             @RequestParam String timeDimension,
@@ -35,7 +36,7 @@ public class EmsAnalysisController {
         return ApiResult.success(emsAnalysisService.getTrendIndicators(projectId, timeDimension, timeValue, energyTypeId));
     }
 
-    @GetMapping("/analysis/trend/category")
+    @GetMapping("/trend/category")
     public ApiResult<EmsTrendCategoryResponse> getTrendCategory(
             @RequestParam(required = false) Long projectId,
             @RequestParam String timeDimension,
@@ -44,7 +45,7 @@ public class EmsAnalysisController {
         return ApiResult.success(emsAnalysisService.getTrendCategory(projectId, timeDimension, timeValue, energyTypeId));
     }
 
-    @GetMapping("/analysis/region")
+    @GetMapping("/region")
     public ApiResult<EmsRegionAnalysisResponse> getRegionAnalysis(
             @RequestParam(required = false) Long projectId,
             @RequestParam(required = false) String regionIds,
@@ -54,7 +55,7 @@ public class EmsAnalysisController {
         return ApiResult.success(emsAnalysisService.getRegionAnalysis(projectId, regionIds, timeDimension, timeValue, energyTypeId));
     }
 
-    @PostMapping("/analysis/compare")
+    @PostMapping("/compare")
     public ApiResult<EmsCompareResponse> getCompare(@RequestBody EmsCompareRequest request) {
         return ApiResult.success(emsAnalysisService.getCompare(request));
     }

+ 0 - 37
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsAuthController.java

@@ -1,37 +0,0 @@
-package com.usky.ems.controller.web;
-
-import com.usky.common.core.bean.ApiResult;
-import com.usky.ems.service.EmsAuthService;
-import com.usky.ems.service.vo.EmsLoginRequest;
-import com.usky.ems.service.vo.EmsLoginResponse;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-/**
- * 能源系统认证接口
- *
- * POST /prod-api/service-ems/auth/login  用户登录
- * POST /prod-api/service-ems/auth/logout 退出登录
- */
-@RestController
-@RequestMapping("/prod-api/service-ems")
-public class EmsAuthController {
-
-    @Autowired
-    private EmsAuthService emsAuthService;
-
-    @PostMapping("/auth/login")
-    public ApiResult<EmsLoginResponse> login(@RequestBody EmsLoginRequest request) {
-        EmsLoginResponse data = emsAuthService.login(request);
-        return ApiResult.success(data);
-    }
-
-    @PostMapping("/auth/logout")
-    public ApiResult<Void> logout() {
-        emsAuthService.logout();
-        return ApiResult.success();
-    }
-}

+ 32 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsDeviceEventController.java

@@ -0,0 +1,32 @@
+package com.usky.ems.controller.web;
+
+import com.usky.common.core.bean.ApiResult;
+import com.usky.ems.service.EmsDeviceEventService;
+import com.usky.ems.service.vo.DeviceEventDTO;
+import com.usky.ems.service.vo.DeviceEventVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * 设备事件接口
+ *
+ * POST /device-event/list  查询设备事件列表
+ */
+@RestController
+@RequestMapping("/device-event")
+public class EmsDeviceEventController {
+
+    @Autowired
+    private EmsDeviceEventService emsDeviceEventService;
+
+    @PostMapping("/list")
+    public ApiResult<List<DeviceEventVO>> list(@RequestBody DeviceEventDTO dto) {
+        return ApiResult.success(emsDeviceEventService.list(dto));
+    }
+}
+

+ 4 - 4
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsGatewayController.java

@@ -11,10 +11,10 @@ import org.springframework.web.bind.annotation.*;
 
 /**
  * 能源能耗 - 网关/设备查询模块 API
- * 基础路径:/prod-api/service-ems
+ * 基础路径前缀:/device/gateway(网关再加 /prod-api/service-ems
  */
 @RestController
-@RequestMapping("/prod-api/service-ems")
+@RequestMapping("/device/gateway")
 public class EmsGatewayController {
 
     @Autowired
@@ -23,7 +23,7 @@ public class EmsGatewayController {
     /**
      * 网关列表(分页)
      */
-    @GetMapping("/device/gateway/list")
+    @GetMapping("/list")
     public ApiResult<CommonPage<EmsGatewayListItem>> gatewayList(EmsGatewayPageRequest request) {
         return ApiResult.success(emsGatewayQueryService.listGateways(request));
     }
@@ -31,7 +31,7 @@ public class EmsGatewayController {
     /**
      * 网关详情
      */
-    @GetMapping("/device/gateway/{id}")
+    @GetMapping("/{id}")
     public ApiResult<EmsGatewayDetailResponse> getGateway(@PathVariable String id) {
         return ApiResult.success(emsGatewayQueryService.getGatewayById(id));
     }

+ 34 - 25
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsModelController.java

@@ -10,10 +10,10 @@ import java.util.List;
 
 /**
  * 能源能耗 - 模型/结构模块 API(项目层级树、能源类型、建筑/区域/楼层/网关/通道/设备/属性点位 CRUD)
- * 基础路径:/prod-api/service-ems
+ * 基础路径前缀/model(网关再加 /prod-api/service-ems
  */
 @RestController
-@RequestMapping("/prod-api/service-ems")
+@RequestMapping("/model")
 public class EmsModelController {
 
     @Autowired
@@ -22,7 +22,7 @@ public class EmsModelController {
     /**
      * 获取项目层级树(建筑、区域、楼层、网关)
      */
-    @GetMapping("/model/structure/tree")
+    @GetMapping("/structure/tree")
     public ApiResult<EmsStructureTreeNode> getStructureTree(
             @RequestParam(required = false) Long projectId,
             @RequestParam(required = false, defaultValue = "true") Boolean includeGateway) {
@@ -32,132 +32,141 @@ public class EmsModelController {
     /**
      * 能源类型列表(电、水、气)
      */
-    @GetMapping("/model/energy-type/list")
+    @GetMapping("/energy-type/list")
     public ApiResult<List<EmsEnergyTypeVO>> getEnergyTypeList() {
         return ApiResult.success(emsModelService.getEnergyTypeList());
     }
 
-    @PostMapping("/model/building")
+    /**
+     * 展示已关联的能源类型及其下属分项/产品列表
+     * 数据来源:leo.ems_energy_item_code.energy_type 分组
+     */
+    @GetMapping("/energy-type/associated")
+    public ApiResult<List<EnergyTypeWrapperProductVO>> showAssociatedEnergyTypes() {
+        return ApiResult.success(emsModelService.showAssociatedEnergyTypes());
+    }
+
+    @PostMapping("/building")
     public ApiResult<EmsIdResponse> createBuilding(@RequestBody EmsModelSaveRequest request) {
         Long id = emsModelService.createBuilding(request);
         return ApiResult.success(new EmsIdResponse(id));
     }
 
-    @PutMapping("/model/building/{id}")
+    @PutMapping("/building/{id}")
     public ApiResult<Void> updateBuilding(@PathVariable Long id, @RequestBody EmsModelSaveRequest request) {
         emsModelService.updateBuilding(id, request);
         return ApiResult.success();
     }
 
-    @DeleteMapping("/model/building/{id}")
+    @DeleteMapping("/building/{id}")
     public ApiResult<Void> deleteBuilding(@PathVariable Long id) {
         emsModelService.deleteBuilding(id);
         return ApiResult.success();
     }
 
-    @PostMapping("/model/region")
+    @PostMapping("/region")
     public ApiResult<EmsIdResponse> createRegion(@RequestBody EmsModelSaveRequest request) {
         Long id = emsModelService.createRegion(request);
         return ApiResult.success(new EmsIdResponse(id));
     }
 
-    @PutMapping("/model/region/{id}")
+    @PutMapping("/region/{id}")
     public ApiResult<Void> updateRegion(@PathVariable Long id, @RequestBody EmsModelSaveRequest request) {
         emsModelService.updateRegion(id, request);
         return ApiResult.success();
     }
 
-    @DeleteMapping("/model/region/{id}")
+    @DeleteMapping("/region/{id}")
     public ApiResult<Void> deleteRegion(@PathVariable Long id) {
         emsModelService.deleteRegion(id);
         return ApiResult.success();
     }
 
-    @PostMapping("/model/floor")
+    @PostMapping("/floor")
     public ApiResult<EmsIdResponse> createFloor(@RequestBody EmsModelSaveRequest request) {
         Long id = emsModelService.createFloor(request);
         return ApiResult.success(new EmsIdResponse(id));
     }
 
-    @PutMapping("/model/floor/{id}")
+    @PutMapping("/floor/{id}")
     public ApiResult<Void> updateFloor(@PathVariable Long id, @RequestBody EmsModelSaveRequest request) {
         emsModelService.updateFloor(id, request);
         return ApiResult.success();
     }
 
-    @DeleteMapping("/model/floor/{id}")
+    @DeleteMapping("/floor/{id}")
     public ApiResult<Void> deleteFloor(@PathVariable Long id) {
         emsModelService.deleteFloor(id);
         return ApiResult.success();
     }
 
-    @PostMapping("/model/gateway")
+    @PostMapping("/gateway")
     public ApiResult<EmsIdResponse> createGateway(@RequestBody EmsModelSaveRequest request) {
         String id = emsModelService.createGateway(request);
         return ApiResult.success(new EmsIdResponse(id));
     }
 
-    @PutMapping("/model/gateway/{id}")
+    @PutMapping("/gateway/{id}")
     public ApiResult<Void> updateGateway(@PathVariable String id, @RequestBody EmsModelSaveRequest request) {
         emsModelService.updateGateway(id, request);
         return ApiResult.success();
     }
 
-    @DeleteMapping("/model/gateway/{id}")
+    @DeleteMapping("/gateway/{id}")
     public ApiResult<Void> deleteGateway(@PathVariable String id) {
         emsModelService.deleteGateway(id);
         return ApiResult.success();
     }
 
-    @PostMapping("/model/channel")
+    @PostMapping("/channel")
     public ApiResult<EmsIdResponse> createChannel(@RequestBody EmsModelSaveRequest request) {
         Long id = emsModelService.createChannel(request);
         return ApiResult.success(new EmsIdResponse(id));
     }
 
-    @PutMapping("/model/channel/{id}")
+    @PutMapping("/channel/{id}")
     public ApiResult<Void> updateChannel(@PathVariable Long id, @RequestBody EmsModelSaveRequest request) {
         emsModelService.updateChannel(id, request);
         return ApiResult.success();
     }
 
-    @DeleteMapping("/model/channel/{id}")
+    @DeleteMapping("/channel/{id}")
     public ApiResult<Void> deleteChannel(@PathVariable Long id) {
         emsModelService.deleteChannel(id);
         return ApiResult.success();
     }
 
-    @PostMapping("/model/device")
+    @PostMapping("/device")
     public ApiResult<EmsIdResponse> createDevice(@RequestBody EmsModelSaveRequest request) {
         String id = emsModelService.createDevice(request);
         return ApiResult.success(new EmsIdResponse(id));
     }
 
-    @PutMapping("/model/device/{id}")
+    @PutMapping("/device/{id}")
     public ApiResult<Void> updateDevice(@PathVariable String id, @RequestBody EmsModelSaveRequest request) {
         emsModelService.updateDevice(id, request);
         return ApiResult.success();
     }
 
-    @DeleteMapping("/model/device/{id}")
+    @DeleteMapping("/device/{id}")
     public ApiResult<Void> deleteDevice(@PathVariable String id) {
         emsModelService.deleteDevice(id);
         return ApiResult.success();
     }
 
-    @PostMapping("/model/attribute-point")
+    @PostMapping("/attribute-point")
     public ApiResult<EmsIdResponse> createAttributePoint(@RequestBody EmsModelSaveRequest request) {
         Long id = emsModelService.createAttributePoint(request);
         return ApiResult.success(new EmsIdResponse(id));
     }
 
-    @PutMapping("/model/attribute-point/{id}")
+    @PutMapping("/attribute-point/{id}")
     public ApiResult<Void> updateAttributePoint(@PathVariable Long id, @RequestBody EmsModelSaveRequest request) {
         emsModelService.updateAttributePoint(id, request);
         return ApiResult.success();
     }
 
-    @DeleteMapping("/model/attribute-point/{id}")
+    @DeleteMapping("/attribute-point/{id}")
     public ApiResult<Void> deleteAttributePoint(@PathVariable Long id) {
         emsModelService.deleteAttributePoint(id);
         return ApiResult.success();

+ 99 - 4
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsOverviewController.java

@@ -5,15 +5,17 @@ import com.usky.ems.service.EmsOverviewService;
 import com.usky.ems.service.vo.EmsProjectResponse;
 import com.usky.ems.service.vo.EmsSummaryRequest;
 import com.usky.ems.service.vo.EmsSummaryResponse;
+import com.usky.ems.service.vo.EmsOverviewDeviceSystemStatVO;
+import com.usky.ems.service.vo.EmsOverviewEnergyItemVO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
 /**
  * 能源能耗 - 概览模块 API
- * 基础路径:/prod-api/service-ems
+ * 基础路径前缀/overview(网关再加 /prod-api/service-ems
  */
 @RestController
-@RequestMapping("/prod-api/service-ems")
+@RequestMapping("/overview")
 public class EmsOverviewController {
 
     @Autowired
@@ -22,7 +24,7 @@ public class EmsOverviewController {
     /**
      * 获取项目信息
      */
-    @GetMapping("/overview/project")
+    @GetMapping("/project")
     public ApiResult<EmsProjectResponse> getProject(@RequestParam(required = false) Long projectId) {
         return ApiResult.success(emsOverviewService.getProject(projectId));
     }
@@ -30,9 +32,102 @@ public class EmsOverviewController {
     /**
      * 获取项目数据概括(时间维度联动)
      */
-    @GetMapping("/overview/summary")
+    @GetMapping("/summary")
     public ApiResult<EmsSummaryResponse> getSummary(EmsSummaryRequest request) {
         return ApiResult.success(emsOverviewService.getSummary(
                 request.getProjectId(), request.getTimeDimension(), request.getTimeValue()));
     }
+
+    /**
+     * 获取概览页能源类型条目(如电/水/气)
+     */
+    @GetMapping("/item")
+    public ApiResult<java.util.List<EmsOverviewEnergyItemVO>> queryOverviewItem(
+            @RequestParam(required = false) Long projectId) {
+        return ApiResult.success(emsOverviewService.queryOverviewItem(projectId));
+    }
+
+    /**
+     * 获取概览页设备系统统计(每个系统下设备数量)
+     */
+    @GetMapping("/device-info")
+    public ApiResult<java.util.List<EmsOverviewDeviceSystemStatVO>> queryOverviewDeviceInfo(
+            @RequestParam(required = false) Long projectId) {
+        return ApiResult.success(emsOverviewService.queryOverviewDeviceInfo(projectId));
+    }
+
+    /**
+     * 分类能耗统计(模拟数据)
+     * 参数说明:
+     * - dateType:时间类型(1-日,2-月,3-年等,暂仅作为占位,不影响当前模拟结果)
+     * - itemCode:能耗条目编码
+     * - energyType:能耗类型
+     * - projectId:项目 ID(可选,当前实现未做真实查询,仅占位)
+     */
+    @GetMapping("/classification-energy")
+    public ApiResult<java.util.Map<String, Object>> queryClassificationEnergy(
+            @RequestParam Integer dateType,
+            @RequestParam String itemCode,
+            @RequestParam Integer energyType,
+            @RequestParam(required = false) Long projectId) {
+        return ApiResult.success(emsOverviewService.queryClassificationEnergy(dateType, itemCode, energyType, projectId));
+    }
+
+    /**
+     * 能耗用能趋势(模拟数据)
+     * 参数说明:
+     * - dateType:时间类型(1-日,2-月,3-年)
+     * - itemCode:能耗条目编码
+     * - spaceId:空间ID(可选,当前模拟实现未使用)
+     */
+    @GetMapping("/energy-trend")
+    public ApiResult<java.util.List<java.util.Map<String, Object>>> queryEnergyTrend(
+            @RequestParam Integer dateType,
+            @RequestParam String itemCode,
+            @RequestParam(required = false) Long spaceId) {
+        return ApiResult.success(emsOverviewService.queryEnergyTrend(dateType, itemCode, spaceId));
+    }
+
+    /**
+     * 建筑能耗分析(模拟数据)
+     * 参数说明:
+     * - dateType:时间类型(1-日,2-月,3-年)
+     * - itemCode:能耗条目编码
+     * - spaceId:空间ID(可选,当前模拟实现未使用)
+     */
+    @GetMapping("/building-ranking")
+    public ApiResult<java.util.List<java.util.Map<String, Object>>> queryBuildingRanking(
+            @RequestParam Integer dateType,
+            @RequestParam String itemCode,
+            @RequestParam(required = false) Long spaceId) {
+        return ApiResult.success(emsOverviewService.queryBuildingRanking(dateType, itemCode, spaceId));
+    }
+
+    /**
+     * 单位综合能耗、综合累计能耗、分类能耗占比(总览顶部)
+     * 参数说明:
+     * - dateType:时间类型(1-日,2-月,3-年)
+     * - projectId:项目ID(可选,不传则取第一个项目)
+     */
+    @GetMapping("/top")
+    public ApiResult<java.util.Map<String, Object>> queryOverviewTop(
+            @RequestParam Integer dateType,
+            @RequestParam(required = false) Long projectId) {
+        return ApiResult.success(emsOverviewService.queryOverviewTop(dateType, projectId));
+    }
+
+    /**
+     * 分项能耗占比
+     * 参数说明:
+     * - dateType:时间类型(1-日,2-月,3-年)
+     * - itemCode:分项编码(作为父项,展开下级分项)
+     * - projectId:项目ID(可选,不传则取第一个项目)
+     */
+    @GetMapping("/item-ratio")
+    public ApiResult<java.util.List<java.util.Map<String, Object>>> queryItemRatio(
+            @RequestParam Integer dateType,
+            @RequestParam String itemCode,
+            @RequestParam(required = false) Long projectId) {
+        return ApiResult.success(emsOverviewService.queryItemRatio(dateType, itemCode, projectId));
+    }
 }

+ 64 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsProjectController.java

@@ -0,0 +1,64 @@
+package com.usky.ems.controller.web;
+
+import com.usky.common.core.bean.ApiResult;
+import com.usky.ems.domain.EmsProject;
+import com.usky.ems.service.EmsProjectService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 项目管理接口(ems_project)
+ *
+ * POST   /project       新增项目
+ * PUT    /project/{id}  修改项目
+ * DELETE /project/{id}  删除项目
+ * GET    /project/config/status  获取项目配置开关状态
+ */
+@RestController
+@RequestMapping("/project")
+public class EmsProjectController {
+
+    @Autowired
+    private EmsProjectService emsProjectService;
+
+    /**
+     * 新增项目
+     */
+    @PostMapping
+    public ApiResult<Long> create(@RequestBody EmsProject project) {
+        Long id = emsProjectService.create(project);
+        return ApiResult.success(id);
+    }
+
+    /**
+     * 修改项目
+     */
+    @PutMapping("/{id}")
+    public ApiResult<Void> update(@PathVariable Long id, @RequestBody EmsProject project) {
+        project.setId(id);
+        emsProjectService.update(project);
+        return ApiResult.success();
+    }
+
+    /**
+     * 删除项目
+     */
+    @DeleteMapping("/{id}")
+    public ApiResult<Void> delete(@PathVariable Long id) {
+        emsProjectService.delete(id);
+        return ApiResult.success();
+    }
+
+    /**
+     * 获取项目配置开关状态
+     *
+     * @param projectId 项目ID
+     * @param name      配置名称
+     */
+    @GetMapping("/config/status")
+    public ApiResult<Boolean> getConfigurationsStatus(@RequestParam Long projectId,
+                                                      @RequestParam String name) {
+        return ApiResult.success(emsProjectService.getConfigurationsStatus(projectId, name));
+    }
+}
+

+ 12 - 11
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsReportController.java

@@ -12,16 +12,17 @@ import java.util.List;
 /**
  * 统计报表接口
  * 能源报表、区域报表、采集报表
+ * 基础路径前缀:/report(网关再加 /prod-api/service-ems)
  */
 @RestController
-@RequestMapping("/prod-api/service-ems")
+@RequestMapping("/report")
 public class EmsReportController {
 
     @Autowired
     private EmsReportService emsReportService;
 
     // ---------- 能源报表 ----------
-    @GetMapping("/report/energy/devices")
+    @GetMapping("/energy/devices")
     public ApiResult<EmsReportDevicesResponse> getEnergyDevices(
             @RequestParam Long energyTypeId,
             @RequestParam(required = false) String keyword,
@@ -29,12 +30,12 @@ public class EmsReportController {
         return ApiResult.success(emsReportService.getEnergyDevices(energyTypeId, keyword, projectId));
     }
 
-    @PostMapping("/report/energy/statistics")
+    @PostMapping("/energy/statistics")
     public ApiResult<EmsEnergyStatisticsResponse> getEnergyStatistics(@RequestBody EmsEnergyStatisticsRequest request) {
         return ApiResult.success(emsReportService.getEnergyStatistics(request));
     }
 
-    @GetMapping("/report/energy/export")
+    @GetMapping("/energy/export")
     public void exportEnergy(
             @RequestParam String deviceIds,
             @RequestParam(required = false) String attributePointIds,
@@ -46,7 +47,7 @@ public class EmsReportController {
     }
 
     // ---------- 区域报表 ----------
-    @GetMapping("/report/region/devices")
+    @GetMapping("/region/devices")
     public ApiResult<EmsReportDevicesResponse> getRegionDevices(
             @RequestParam Long energyTypeId,
             @RequestParam(required = false) Long regionId,
@@ -55,14 +56,14 @@ public class EmsReportController {
         return ApiResult.success(emsReportService.getRegionDevices(energyTypeId, regionId, keyword, projectId));
     }
 
-    @PostMapping("/report/region/statistics")
+    @PostMapping("/region/statistics")
     public ApiResult<EmsEnergyStatisticsResponse> getRegionStatistics(
             @RequestBody EmsEnergyStatisticsRequest request,
             @RequestParam(required = false) List<Long> regionIds) {
         return ApiResult.success(emsReportService.getRegionStatistics(request, regionIds));
     }
 
-    @GetMapping("/report/region/export")
+    @GetMapping("/region/export")
     public void exportRegion(
             @RequestParam String deviceIds,
             @RequestParam(required = false) String attributePointIds,
@@ -75,7 +76,7 @@ public class EmsReportController {
     }
 
     // ---------- 采集报表 ----------
-    @GetMapping("/report/collection/devices")
+    @GetMapping("/collection/devices")
     public ApiResult<EmsReportDevicesResponse> getCollectionDevices(
             @RequestParam Long energyTypeId,
             @RequestParam(required = false) String keyword,
@@ -83,17 +84,17 @@ public class EmsReportController {
         return ApiResult.success(emsReportService.getCollectionDevices(energyTypeId, keyword, projectId));
     }
 
-    @PostMapping("/report/collection/realtime")
+    @PostMapping("/collection/realtime")
     public ApiResult<EmsCollectionRealtimeResponse> getCollectionRealtime(@RequestBody EmsCollectionRealtimeRequest request) {
         return ApiResult.success(emsReportService.getCollectionRealtime(request));
     }
 
-    @PostMapping("/report/collection/statistics")
+    @PostMapping("/collection/statistics")
     public ApiResult<EmsEnergyStatisticsResponse> getCollectionStatistics(@RequestBody EmsEnergyStatisticsRequest request) {
         return ApiResult.success(emsReportService.getCollectionStatistics(request));
     }
 
-    @GetMapping("/report/collection/export")
+    @GetMapping("/collection/export")
     public void exportCollection(
             @RequestParam String deviceIds,
             @RequestParam(required = false) String attributePointIds,

+ 31 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsSpaceController.java

@@ -0,0 +1,31 @@
+package com.usky.ems.controller.web;
+
+import com.usky.common.core.bean.ApiResult;
+import com.usky.ems.service.EmsSpaceService;
+import com.usky.ems.service.vo.EmsSpaceForestNodeVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 空间树接口
+ *
+ * GET /space/tree  获取空间树
+ */
+@RestController
+@RequestMapping("/space")
+public class EmsSpaceController {
+
+    @Autowired
+    private EmsSpaceService emsSpaceService;
+
+    /**
+     * 获取空间树(根据 leo.ems_space 构建)
+     */
+    @GetMapping("/tree")
+    public ApiResult<EmsSpaceForestNodeVO> tree() {
+        return ApiResult.success(emsSpaceService.tree());
+    }
+}
+

+ 92 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/DmpProduct.java

@@ -0,0 +1,92 @@
+package com.usky.ems.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 产品信息表(dmp_product)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("dmp_product")
+public class DmpProduct implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /** 主键id */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /** 产品名称 */
+    private String productName;
+
+    /** 接入方式(1 设备直连;2 网关接入) */
+    private Integer accessMode;
+
+    /** 网络类型(1 WIFI;2 移动蜂窝数据;3 NB-IoT;4 以太网) */
+    private Integer networkType;
+
+    /** 设备类型(501 监控系统;502 门禁系统;503 梯控系统;504 机房系统;509 环境系统;510 照明系统) */
+    private Integer deviceType;
+
+    /** 通信协议(1 MQTT;2 TCP 设备直连;3 HTTP) */
+    private Integer comProtocol;
+
+    /** 认证方式 */
+    private String authMode;
+
+    /** 设备型号 */
+    private String deviceModel;
+
+    /** 产品描述 */
+    private String productDescribe;
+
+    /** 厂家名称 */
+    private String factoryName;
+
+    /** 厂家联系人 */
+    private String factoryPerson;
+
+    /** 厂家联系电话 */
+    private String factoryPhone;
+
+    /** 资质证书1 */
+    private String certificateUrl1;
+
+    /** 资质证书2 */
+    private String certificateUrl2;
+
+    /** 资质证书3 */
+    private String certificateUrl3;
+
+    /** 协议文档 */
+    private String agreementUrl;
+
+    /** 删除标识 */
+    private Integer deleteFlag;
+
+    /** 创建人 */
+    private String createdBy;
+
+    /** 创建时间 */
+    private LocalDateTime createdTime;
+
+    /** 更新人 */
+    private String updatedBy;
+
+    /** 更新时间 */
+    private LocalDateTime updatedTime;
+
+    /** 租户号 */
+    private Integer tenantId;
+
+    /** 产品编码 */
+    private String productCode;
+}
+

+ 71 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsDeviceEvent.java

@@ -0,0 +1,71 @@
+package com.usky.ems.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 设备事件记录(leo.ems_device_event)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("ems_device_event")
+public class EmsDeviceEvent implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @TableField("project_id")
+    private Long projectId;
+
+    private String sn;
+
+    @TableField("device_id")
+    private String deviceId;
+
+    private String identifier;
+
+    /** 设备类型 1:设备 2:网关 */
+    @TableField("device_type")
+    private Integer deviceType;
+
+    /** 事件类型 1:通讯告警 2:功能告警 3:数据异常 */
+    @TableField("event_type")
+    private Integer eventType;
+
+    @TableField("suppressed_to")
+    private LocalDateTime suppressedTo;
+
+    /** 确认类型 0:未确认 1:自动确认 2:人工确认 */
+    @TableField("confirmation_type")
+    private Integer confirmationType;
+
+    @TableField("task_sn")
+    private String taskSn;
+
+    private String content;
+
+    @TableField("start_time")
+    private LocalDateTime startTime;
+
+    @TableField("updated_by")
+    private Long updatedBy;
+
+    @TableField("update_time")
+    private LocalDateTime updateTime;
+
+    @TableField("created_by")
+    private Long createdBy;
+
+    @TableField("create_time")
+    private LocalDateTime createTime;
+}
+

+ 47 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsProjectConfiguration.java

@@ -0,0 +1,47 @@
+package com.usky.ems.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 项目配置信息(leo.ems_project_configuration)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("ems_project_configuration")
+public class EmsProjectConfiguration implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @TableField("project_id")
+    private Long projectId;
+
+    private String name;
+
+    private String value;
+
+    private String remark;
+
+    @TableField("updated_by")
+    private Long updatedBy;
+
+    @TableField("update_time")
+    private LocalDateTime updateTime;
+
+    @TableField("created_by")
+    private Long createdBy;
+
+    @TableField("create_time")
+    private LocalDateTime createTime;
+}
+

+ 59 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsProjectConversionFactor.java

@@ -0,0 +1,59 @@
+package com.usky.ems.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * 项目能耗折算系数(leo.ems_project_conversion_factor)
+ * 用于将不同能源类型折算为标准煤等统一口径。
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("ems_project_conversion_factor")
+public class EmsProjectConversionFactor implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @TableField("project_id")
+    private Long projectId;
+
+    /**
+     * 能源类型编码,对应能源类型 ID(如 1=电,2=水,3=气)
+     */
+    @TableField("energy_type")
+    private Integer energyType;
+
+    /**
+     * 系数名称,例如 "coal"、"price" 等
+     */
+    private String name;
+
+    /**
+     * 系数数值
+     */
+    private BigDecimal value;
+
+    @TableField("updated_by")
+    private Long updatedBy;
+
+    @TableField("update_time")
+    private LocalDateTime updateTime;
+
+    @TableField("created_by")
+    private Long createdBy;
+
+    @TableField("create_time")
+    private LocalDateTime createTime;
+}
+

+ 44 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/domain/EmsProjectDeviceSystem.java

@@ -0,0 +1,44 @@
+package com.usky.ems.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 项目设备系统(leo.ems_project_device_system)
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("ems_project_device_system")
+public class EmsProjectDeviceSystem implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @TableField("project_id")
+    private Long projectId;
+
+    @TableField("device_system")
+    private Integer deviceSystem;
+
+    @TableField("updated_by")
+    private Long updatedBy;
+
+    @TableField("update_time")
+    private LocalDateTime updateTime;
+
+    @TableField("created_by")
+    private Long createdBy;
+
+    @TableField("create_time")
+    private LocalDateTime createTime;
+}
+

+ 11 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/DmpProductMapper.java

@@ -0,0 +1,11 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.DmpProduct;
+
+/**
+ * 产品信息表 Mapper(dmp_product)
+ */
+public interface DmpProductMapper extends CrudMapper<DmpProduct> {
+}
+

+ 11 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsDeviceEventMapper.java

@@ -0,0 +1,11 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.EmsDeviceEvent;
+
+/**
+ * 设备事件记录 Mapper(leo.ems_device_event)
+ */
+public interface EmsDeviceEventMapper extends CrudMapper<EmsDeviceEvent> {
+}
+

+ 11 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsProjectConfigurationMapper.java

@@ -0,0 +1,11 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.EmsProjectConfiguration;
+
+/**
+ * 项目配置信息 Mapper(leo.ems_project_configuration)
+ */
+public interface EmsProjectConfigurationMapper extends CrudMapper<EmsProjectConfiguration> {
+}
+

+ 11 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsProjectConversionFactorMapper.java

@@ -0,0 +1,11 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.EmsProjectConversionFactor;
+
+/**
+ * 项目能耗折算系数 Mapper(leo.ems_project_conversion_factor)
+ */
+public interface EmsProjectConversionFactorMapper extends CrudMapper<EmsProjectConversionFactor> {
+}
+

+ 11 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/mapper/EmsProjectDeviceSystemMapper.java

@@ -0,0 +1,11 @@
+package com.usky.ems.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.ems.domain.EmsProjectDeviceSystem;
+
+/**
+ * 项目设备系统 Mapper(leo.ems_project_device_system)
+ */
+public interface EmsProjectDeviceSystemMapper extends CrudMapper<EmsProjectDeviceSystem> {
+}
+

+ 0 - 14
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsAuthService.java

@@ -1,14 +0,0 @@
-package com.usky.ems.service;
-
-import com.usky.ems.service.vo.EmsLoginRequest;
-import com.usky.ems.service.vo.EmsLoginResponse;
-
-/**
- * 能源系统认证服务(登录/登出)
- */
-public interface EmsAuthService {
-
-    EmsLoginResponse login(EmsLoginRequest request);
-
-    void logout();
-}

+ 18 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsDeviceEventService.java

@@ -0,0 +1,18 @@
+package com.usky.ems.service;
+
+import com.usky.ems.service.vo.DeviceEventDTO;
+import com.usky.ems.service.vo.DeviceEventVO;
+
+import java.util.List;
+
+/**
+ * 设备事件服务(EmsDeviceEvent 子模块)
+ */
+public interface EmsDeviceEventService {
+
+    /**
+     * 按条件查询设备事件列表
+     */
+    List<DeviceEventVO> list(DeviceEventDTO dto);
+}
+

+ 18 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsDeviceReportService.java

@@ -0,0 +1,18 @@
+package com.usky.ems.service;
+
+import com.usky.ems.service.vo.DeviceEnergyReportRequest;
+import com.usky.ems.service.vo.DeviceEnergyReportItemVO;
+
+import java.util.List;
+
+/**
+ * 设备能耗报表相关服务(EmsDevice 子模块)
+ */
+public interface EmsDeviceReportService {
+
+    /**
+     * 查询能耗报表用的设备列表,并按名称进行排序。
+     */
+    List<DeviceEnergyReportItemVO> showEnergyReportDevices(DeviceEnergyReportRequest request);
+}
+

+ 7 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsModelService.java

@@ -3,6 +3,7 @@ package com.usky.ems.service;
 import com.usky.ems.service.vo.EmsEnergyTypeVO;
 import com.usky.ems.service.vo.EmsModelSaveRequest;
 import com.usky.ems.service.vo.EmsStructureTreeNode;
+import com.usky.ems.service.vo.EnergyTypeWrapperProductVO;
 
 import java.util.List;
 
@@ -21,6 +22,12 @@ public interface EmsModelService {
      */
     List<EmsEnergyTypeVO> getEnergyTypeList();
 
+    /**
+     * 展示已关联的能源类型及其下属分项/产品列表
+     * 数据来源:leo.ems_energy_item_code.energy_type 分组
+     */
+    List<EnergyTypeWrapperProductVO> showAssociatedEnergyTypes();
+
     /** 建筑:新增 */
     Long createBuilding(EmsModelSaveRequest request);
     /** 建筑:编辑 */

+ 44 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsOverviewService.java

@@ -2,6 +2,10 @@ package com.usky.ems.service;
 
 import com.usky.ems.service.vo.EmsProjectResponse;
 import com.usky.ems.service.vo.EmsSummaryResponse;
+import com.usky.ems.service.vo.EmsOverviewEnergyItemVO;
+import com.usky.ems.service.vo.EmsOverviewDeviceSystemStatVO;
+
+import java.util.Map;
 
 /**
  * 能源总览服务(overview)
@@ -17,4 +21,44 @@ public interface EmsOverviewService {
      * 获取项目数据概括(时间维度联动)
      */
     EmsSummaryResponse getSummary(Long projectId, String timeDimension, String timeValue);
+
+    /**
+     * 获取概览页能源类型条目(如电/水/气)
+     */
+    java.util.List<EmsOverviewEnergyItemVO> queryOverviewItem(Long projectId);
+
+    /**
+     * 获取概览页设备系统统计(每个系统下设备数量)
+     */
+    java.util.List<EmsOverviewDeviceSystemStatVO> queryOverviewDeviceInfo(Long projectId);
+
+    /**
+     * 分类能耗统计(按时间维度、能耗类型等)
+     * 先按 Map 结构返回,后续可以再封装 VO。
+     */
+    Map<String, Object> queryClassificationEnergy(Integer dateType, String itemCode, Integer energyType, Long projectId);
+
+    /**
+     * 能耗用能趋势(按时间维度:日/月/年)
+     * 返回当前期与去年同期的用能趋势数据列表(当前为模拟数据实现)。
+     */
+    java.util.List<java.util.Map<String, Object>> queryEnergyTrend(Integer dateType, String itemCode, Long spaceId);
+
+    /**
+     * 建筑能耗分析(建筑/楼层能耗排名)
+     * 返回按能耗从高到低排序的建筑(或楼层)名称及其能耗值列表(当前为模拟数据实现)。
+     */
+    java.util.List<java.util.Map<String, Object>> queryBuildingRanking(Integer dateType, String itemCode, Long spaceId);
+
+    /**
+     * 单位综合能耗、综合累计能耗、分类能耗占比(总览页顶部)
+     * 部分数据来自真实表(项目、折标系数),能耗数值按规则模拟。
+     */
+    Map<String, Object> queryOverviewTop(Integer dateType, Long projectId);
+
+    /**
+     * 分项能耗占比(按分项编码 itemCode 展开下级条目)
+     * 部分数据(分项定义)来自 leo.ems_energy_item_code,能耗数值按规则模拟。
+     */
+    java.util.List<java.util.Map<String, Object>> queryItemRatio(Integer dateType, String itemCode, Long projectId);
 }

+ 11 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsProjectConfigurationService.java

@@ -0,0 +1,11 @@
+package com.usky.ems.service;
+
+import com.usky.common.mybatis.core.CrudService;
+import com.usky.ems.domain.EmsProjectConfiguration;
+
+/**
+ * 项目配置信息服务
+ */
+public interface EmsProjectConfigurationService extends CrudService<EmsProjectConfiguration> {
+}
+

+ 36 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsProjectService.java

@@ -0,0 +1,36 @@
+package com.usky.ems.service;
+
+import com.usky.common.mybatis.core.CrudService;
+import com.usky.ems.domain.EmsProject;
+import com.usky.ems.domain.EmsProjectConfiguration;
+
+/**
+ * 项目服务(leo.ems_project)
+ */
+public interface EmsProjectService extends CrudService<EmsProject> {
+
+    /**
+     * 新增项目
+     */
+    Long create(EmsProject project);
+
+    /**
+     * 修改项目
+     */
+    void update(EmsProject project);
+
+    /**
+     * 删除项目
+     */
+    void delete(Long id);
+
+    /**
+     * 获取项目配置开关状态:根据当前项目ID与配置名称,判断 value 是否为 "1"
+     *
+     * @param projectId 项目 ID
+     * @param name      配置名称
+     * @return true 表示开启(value="1"),false 表示未开启/未配置
+     */
+    Boolean getConfigurationsStatus(Long projectId, String name);
+}
+

+ 31 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/EmsSpaceService.java

@@ -0,0 +1,31 @@
+package com.usky.ems.service;
+
+import com.usky.common.mybatis.core.CrudService;
+import com.usky.ems.domain.EmsSpace;
+import com.usky.ems.service.vo.EmsSpaceForestNodeVO;
+import java.util.List;
+
+/**
+ * 空间服务(leo.ems_space)
+ */
+public interface EmsSpaceService extends CrudService<EmsSpace> {
+
+    /**
+     * 构建空间树
+     *
+     * @return 根节点(如存在多个根,则返回一个虚拟根节点)
+     */
+    EmsSpaceForestNodeVO tree();
+
+    /**
+     * 获取当前用户在指定空间下有权限访问的空间ID列表
+     * 目前简单返回传入的 spaceId 本身,后续可根据实际权限模型扩展
+     */
+    List<Long> getAuthorizedSpaceIds(Long spaceId);
+
+    /**
+     * 递归获取某空间节点下的所有子节点(不包含自身)
+     */
+    List<EmsSpace> recursiveAllChildrenNode(Long spaceId);
+}
+

+ 0 - 30
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsAuthServiceImpl.java

@@ -1,30 +0,0 @@
-package com.usky.ems.service.impl;
-
-import com.usky.ems.service.EmsAuthService;
-import com.usky.ems.service.vo.EmsLoginRequest;
-import com.usky.ems.service.vo.EmsLoginResponse;
-import org.springframework.stereotype.Service;
-
-import java.util.UUID;
-
-/**
- * 能源系统认证服务实现(预留与统一认证对接,当前返回占位 token)
- */
-@Service
-public class EmsAuthServiceImpl implements EmsAuthService {
-
-    private static final long EXPIRES_IN_SECONDS = 7200L;
-
-    @Override
-    public EmsLoginResponse login(EmsLoginRequest request) {
-        EmsLoginResponse resp = new EmsLoginResponse();
-        resp.setAccessToken("Bearer " + UUID.randomUUID().toString().replace("-", ""));
-        resp.setExpiresIn(EXPIRES_IN_SECONDS);
-        return resp;
-    }
-
-    @Override
-    public void logout() {
-        // 预留:使当前 token 失效
-    }
-}

+ 174 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsDeviceEventServiceImpl.java

@@ -0,0 +1,174 @@
+package com.usky.ems.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.usky.common.core.exception.BusinessException;
+import com.usky.ems.domain.EmsDevice;
+import com.usky.ems.domain.EmsDeviceEvent;
+import com.usky.ems.domain.EmsGateway;
+import com.usky.ems.domain.EmsSpace;
+import com.usky.ems.mapper.EmsDeviceEventMapper;
+import com.usky.ems.mapper.EmsDeviceMapper;
+import com.usky.ems.mapper.EmsGatewayMapper;
+import com.usky.ems.service.EmsDeviceEventService;
+import com.usky.ems.service.EmsSpaceService;
+import com.usky.ems.service.vo.DeviceEventDTO;
+import com.usky.ems.service.vo.DeviceEventVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * 设备事件服务实现(全部逻辑在 Service 层)
+ */
+@Service
+public class EmsDeviceEventServiceImpl implements EmsDeviceEventService {
+
+    @Autowired
+    private EmsSpaceService emsSpaceService;
+
+    @Autowired
+    private EmsDeviceMapper emsDeviceMapper;
+
+    @Autowired
+    private EmsGatewayMapper emsGatewayMapper;
+
+    @Autowired
+    private EmsDeviceEventMapper emsDeviceEventMapper;
+
+    @Override
+    public List<DeviceEventVO> list(DeviceEventDTO dto) {
+        if (dto == null || dto.getInstallationLocation() == null) {
+            throw new BusinessException("安装位置不存在");
+        }
+
+        EmsSpace space = emsSpaceService.getById(dto.getInstallationLocation());
+        if (space == null) {
+            throw new BusinessException("安装位置不存在");
+        }
+
+        // 1) 递归获取所有子空间 + 本空间
+        List<EmsSpace> spaceList = new ArrayList<>(emsSpaceService.recursiveAllChildrenNode(dto.getInstallationLocation()));
+        spaceList.add(space);
+
+        List<Long> spaceIds = spaceList.stream()
+                .map(EmsSpace::getId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+
+        Map<Long, String> spaceMap = spaceList.stream()
+                .filter(s -> s.getId() != null)
+                .collect(Collectors.toMap(EmsSpace::getId, EmsSpace::getName, (a, b) -> a));
+
+        // 2) 根据 deviceType 取设备/网关列表及其名称、空间映射
+        Integer deviceType = dto.getDeviceType();
+        if (deviceType == null || (deviceType != 1 && deviceType != 2)) {
+            throw new BusinessException("设备类型错误");
+        }
+
+        List<String> ids;
+        Map<String, String> nameMap;
+        Map<String, Long> spaceIdMap;
+
+        if (deviceType == 1) {
+            List<EmsDevice> devices = emsDeviceMapper.selectList(new LambdaQueryWrapper<EmsDevice>()
+                    .in(EmsDevice::getInstallationLocation, spaceIds));
+            if (devices == null || devices.isEmpty()) {
+                return Collections.emptyList();
+            }
+            ids = devices.stream().map(EmsDevice::getId).filter(Objects::nonNull).collect(Collectors.toList());
+            nameMap = devices.stream().filter(d -> d.getId() != null)
+                    .collect(Collectors.toMap(EmsDevice::getId, EmsDevice::getName, (a, b) -> a));
+            spaceIdMap = devices.stream().filter(d -> d.getId() != null)
+                    .collect(Collectors.toMap(EmsDevice::getId, EmsDevice::getInstallationLocation, (a, b) -> a));
+        } else {
+            List<EmsGateway> gateways = emsGatewayMapper.selectList(new LambdaQueryWrapper<EmsGateway>()
+                    .in(EmsGateway::getSpaceId, spaceIds));
+            if (gateways == null || gateways.isEmpty()) {
+                return Collections.emptyList();
+            }
+            ids = gateways.stream().map(EmsGateway::getId).filter(Objects::nonNull).collect(Collectors.toList());
+            nameMap = gateways.stream().filter(g -> g.getId() != null)
+                    .collect(Collectors.toMap(EmsGateway::getId, EmsGateway::getName, (a, b) -> a));
+            spaceIdMap = gateways.stream().filter(g -> g.getId() != null)
+                    .collect(Collectors.toMap(EmsGateway::getId, EmsGateway::getSpaceId, (a, b) -> a));
+        }
+
+        if (ids.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        // 3) 查事件表 ems_device_event
+        LambdaQueryWrapper<EmsDeviceEvent> eventWrapper = new LambdaQueryWrapper<>();
+        eventWrapper.eq(EmsDeviceEvent::getDeviceType, deviceType)
+                .in(EmsDeviceEvent::getDeviceId, ids);
+
+        if (dto.getStartTime() != null && dto.getEndTime() != null) {
+            eventWrapper.between(EmsDeviceEvent::getStartTime, dto.getStartTime(), dto.getEndTime());
+        } else if (dto.getStartTime() != null) {
+            eventWrapper.ge(EmsDeviceEvent::getStartTime, dto.getStartTime());
+        } else if (dto.getEndTime() != null) {
+            eventWrapper.le(EmsDeviceEvent::getStartTime, dto.getEndTime());
+        }
+
+        List<EmsDeviceEvent> eventList = emsDeviceEventMapper.selectList(eventWrapper);
+        if (eventList == null || eventList.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        LocalDateTime now = LocalDateTime.now();
+        List<DeviceEventVO> result = new ArrayList<>(eventList.size());
+        for (EmsDeviceEvent deviceEvent : eventList) {
+            DeviceEventVO vo = new DeviceEventVO();
+            vo.setDeviceId(deviceEvent.getDeviceId());
+            vo.setDeviceName(nameMap.get(deviceEvent.getDeviceId()));
+            vo.setContent(deviceEvent.getContent());
+            Long sid = spaceIdMap.get(deviceEvent.getDeviceId());
+            vo.setInstallationLocation(sid == null ? null : spaceMap.get(sid));
+            vo.setEventType(deviceEvent.getEventType());
+            vo.setStartTime(deviceEvent.getStartTime());
+            vo.setIdentifier(deviceEvent.getIdentifier());
+            vo.setDuration(formatDuration(deviceEvent.getStartTime(), now));
+            result.add(vo);
+        }
+
+        result.sort(Comparator.comparing(DeviceEventVO::getDeviceName,
+                Comparator.nullsLast(String.CASE_INSENSITIVE_ORDER)));
+
+        return result;
+    }
+
+    private String formatDuration(LocalDateTime start, LocalDateTime end) {
+        if (start == null || end == null) {
+            return null;
+        }
+        Duration duration = Duration.between(start, end);
+        long minutes = duration.toMinutes();
+        if (minutes < 0) {
+            minutes = 0;
+        }
+        if (minutes >= 1440L) {
+            long days = minutes / 1440L;
+            long hours = (minutes % 1440L) / 60L;
+            long mins = minutes % 60L;
+            return days + "天" + hours + "小时" + mins + "分钟";
+        }
+        if (minutes >= 60L) {
+            long hours = minutes / 60L;
+            long mins = minutes % 60L;
+            return hours + "小时" + mins + "分钟";
+        }
+        return minutes + "分钟";
+    }
+}
+

+ 126 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsDeviceReportServiceImpl.java

@@ -0,0 +1,126 @@
+package com.usky.ems.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.usky.ems.domain.EmsDevice;
+import com.usky.ems.domain.EmsDeviceFunction;
+import com.usky.ems.mapper.EmsDeviceFunctionMapper;
+import com.usky.ems.mapper.EmsDeviceMapper;
+import com.usky.ems.service.EmsDeviceReportService;
+import com.usky.ems.service.EmsSpaceService;
+import com.usky.ems.service.vo.DeviceEnergyReportItemVO;
+import com.usky.ems.service.vo.DeviceEnergyReportRequest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 设备能耗报表相关服务实现(全部逻辑在 Service 层,Mapper 仅用标准 CRUD)
+ */
+@Service
+public class EmsDeviceReportServiceImpl implements EmsDeviceReportService {
+
+    @Autowired
+    private EmsSpaceService emsSpaceService;
+
+    @Autowired
+    private EmsDeviceMapper emsDeviceMapper;
+
+    @Autowired
+    private EmsDeviceFunctionMapper emsDeviceFunctionMapper;
+
+    @Override
+    public List<DeviceEnergyReportItemVO> showEnergyReportDevices(DeviceEnergyReportRequest dto) {
+        if (dto == null || dto.getProjectId() == null) {
+            return Collections.emptyList();
+        }
+
+        // 1. 授权空间
+        List<Long> spaceIds = emsSpaceService.getAuthorizedSpaceIds(dto.getSpaceId());
+
+        // 2. 在 Service 层用 LambdaQueryWrapper 查 ems_device
+        LambdaQueryWrapper<EmsDevice> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(EmsDevice::getProjectId, dto.getProjectId());
+
+        if (dto.getProductIdList() != null && !dto.getProductIdList().isEmpty()) {
+            wrapper.in(EmsDevice::getProductId, dto.getProductIdList());
+        }
+        if (dto.getDeviceSystem() != null) {
+            wrapper.eq(EmsDevice::getDeviceSystem, dto.getDeviceSystem());
+        }
+        if (spaceIds != null && !spaceIds.isEmpty()) {
+            wrapper.and(w -> w.in(EmsDevice::getInstallationLocation, spaceIds)
+                    .or().in(EmsDevice::getMonitoringLocation, spaceIds));
+        }
+        if (dto.getGatewayId() != null && !dto.getGatewayId().isEmpty()) {
+            wrapper.eq(EmsDevice::getGatewayId, dto.getGatewayId());
+        }
+        if (dto.getChannelId() != null) {
+            wrapper.eq(EmsDevice::getChannelId, dto.getChannelId());
+        }
+
+        List<EmsDevice> devices = emsDeviceMapper.selectList(wrapper);
+        if (devices == null || devices.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        // 3. 转为 List<DeviceEnergyReportItemVO>
+        List<DeviceEnergyReportItemVO> deviceList = devices.stream()
+                .map(this::deviceToVo)
+                .collect(Collectors.toList());
+
+        // 4. 按 identifier 过滤:只保留在 ems_device_function 中存在该 identifier 的设备
+        if (dto.getIdentifier() != null && !dto.getIdentifier().isEmpty()) {
+            List<String> deviceIds = deviceList.stream()
+                    .map(DeviceEnergyReportItemVO::getId)
+                    .filter(Objects::nonNull)
+                    .filter(id -> !id.isEmpty())
+                    .collect(Collectors.toList());
+
+            if (!deviceIds.isEmpty()) {
+                LambdaQueryWrapper<EmsDeviceFunction> funcWrapper = new LambdaQueryWrapper<>();
+                funcWrapper.select(EmsDeviceFunction::getDeviceId)
+                        .in(EmsDeviceFunction::getDeviceId, deviceIds)
+                        .eq(EmsDeviceFunction::getIdentifier, dto.getIdentifier());
+                List<EmsDeviceFunction> deviceFunctions = emsDeviceFunctionMapper.selectList(funcWrapper);
+                if (deviceFunctions != null && !deviceFunctions.isEmpty()) {
+                    Set<String> meetDeviceIds = deviceFunctions.stream()
+                            .map(EmsDeviceFunction::getDeviceId)
+                            .collect(Collectors.toSet());
+                    deviceList = deviceList.stream()
+                            .filter(vo -> meetDeviceIds.contains(vo.getId()))
+                            .collect(Collectors.toList());
+                }
+            }
+        }
+
+        // 5. 按 name 排序(不区分大小写)
+        if (!deviceList.isEmpty()) {
+            deviceList.sort(Comparator.comparing(
+                    DeviceEnergyReportItemVO::getName,
+                    Comparator.nullsLast(String.CASE_INSENSITIVE_ORDER)
+            ));
+        }
+
+        return deviceList;
+    }
+
+    private DeviceEnergyReportItemVO deviceToVo(EmsDevice d) {
+        DeviceEnergyReportItemVO vo = new DeviceEnergyReportItemVO();
+        vo.setId(d.getId());
+        vo.setName(d.getName());
+        vo.setNumber(d.getNumber());
+        vo.setProjectId(d.getProjectId());
+        vo.setProductId(d.getProductId());
+        vo.setInstallationLocation(d.getInstallationLocation());
+        vo.setMonitoringLocation(d.getMonitoringLocation());
+        vo.setChannelId(d.getChannelId());
+        vo.setGatewayId(d.getGatewayId());
+        vo.setDeviceSystem(d.getDeviceSystem());
+        vo.setStatus(d.getStatus());
+        vo.setLocation(d.getLocation());
+        return vo;
+    }
+}
+

+ 70 - 2
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsModelServiceImpl.java

@@ -4,14 +4,20 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.usky.ems.domain.*;
 import com.usky.ems.mapper.*;
 import com.usky.ems.service.EmsModelService;
+import com.usky.ems.service.vo.EnergyTypeWrapperProductVO;
 import com.usky.ems.service.vo.EmsEnergyTypeVO;
 import com.usky.ems.service.vo.EmsModelSaveRequest;
 import com.usky.ems.service.vo.EmsStructureTreeNode;
+import com.usky.ems.service.vo.SimpleProductVO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 /**
  * 基础建模服务实现(结构树、能源类型、建筑/区域/楼层/网关/通道/设备/属性点位 CRUD)
@@ -38,7 +44,7 @@ public class EmsModelServiceImpl implements EmsModelService {
     @Autowired
     private EmsDeviceFunctionMapper emsDeviceFunctionMapper;
     @Autowired
-    private EmsEnergyItemCodeMapper emsEnergyItemCodeMapper;
+    private DmpProductMapper dmpProductMapper;
 
     private static final int SPACE_TYPE_PROJECT = 1;
     private static final int SPACE_TYPE_REGION = 2;
@@ -123,6 +129,68 @@ public class EmsModelServiceImpl implements EmsModelService {
         return result;
     }
 
+    /**
+     * 展示已关联的类型及其下属产品列表
+     * 这里按 dmp_product.deviceType 进行分组,每个产品作为一个 SimpleProductVO 返回。
+     */
+    @Override
+    public List<EnergyTypeWrapperProductVO> showAssociatedEnergyTypes() {
+        List<DmpProduct> products = dmpProductMapper.selectList(
+                new LambdaQueryWrapper<DmpProduct>()
+                        .eq(DmpProduct::getDeleteFlag, 0)
+        );
+        if (products == null || products.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        Map<Integer, List<SimpleProductVO>> energyMaps = products.stream()
+                .collect(Collectors.groupingBy(
+                        DmpProduct::getDeviceType,
+                        Collectors.mapping(item -> new SimpleProductVO(
+                                item.getProductCode(),
+                                item.getProductName()
+                        ), Collectors.toList())
+                ));
+
+        List<EnergyTypeWrapperProductVO> result = new ArrayList<>();
+        energyMaps.forEach((type, list) -> {
+            String typeName = deviceTypeName(type);
+            EnergyTypeWrapperProductVO vo = new EnergyTypeWrapperProductVO(type, typeName);
+            vo.setProductList(list);
+            result.add(vo);
+        });
+
+        result.sort((o1, o2) -> {
+            if (o1.getEnergyType() == null) return -1;
+            if (o2.getEnergyType() == null) return 1;
+            return o1.getEnergyType().compareTo(o2.getEnergyType());
+        });
+        return result;
+    }
+
+    /** 根据 dmp_product.deviceType 获取类型名称 */
+    private String deviceTypeName(Integer deviceType) {
+        if (deviceType == null) {
+            return "";
+        }
+        switch (deviceType) {
+            case 501:
+                return "监控系统";
+            case 502:
+                return "门禁系统";
+            case 503:
+                return "梯控系统";
+            case 504:
+                return "机房系统";
+            case 509:
+                return "环境系统";
+            case 510:
+                return "照明系统";
+            default:
+                return "";
+        }
+    }
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Long createBuilding(EmsModelSaveRequest request) {

+ 647 - 5
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsOverviewServiceImpl.java

@@ -1,14 +1,39 @@
 package com.usky.ems.service.impl;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.usky.ems.domain.EmsDevice;
 import com.usky.ems.domain.EmsProject;
+import com.usky.ems.domain.EmsSpace;
+import com.usky.ems.domain.EmsProjectDeviceSystem;
+import com.usky.ems.domain.EmsProjectConversionFactor;
+import com.usky.ems.domain.EmsEnergyItemCode;
+import com.usky.ems.mapper.EmsDeviceMapper;
 import com.usky.ems.mapper.EmsProjectMapper;
+import com.usky.ems.mapper.EmsProjectDeviceSystemMapper;
+import com.usky.ems.mapper.EmsProjectConversionFactorMapper;
+import com.usky.ems.mapper.EmsEnergyItemCodeMapper;
+import com.usky.ems.service.EmsModelService;
 import com.usky.ems.service.EmsOverviewService;
+import com.usky.ems.service.EmsSpaceService;
+import com.usky.ems.service.vo.EmsEnergyTypeVO;
+import com.usky.ems.service.vo.EmsOverviewDeviceSystemStatVO;
+import com.usky.ems.service.vo.EmsOverviewEnergyItemVO;
 import com.usky.ems.service.vo.EmsProjectResponse;
 import com.usky.ems.service.vo.EmsSummaryResponse;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.temporal.TemporalAdjusters;
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 /**
  * 能源总览服务实现
@@ -19,6 +44,24 @@ public class EmsOverviewServiceImpl implements EmsOverviewService {
     @Autowired
     private EmsProjectMapper emsProjectMapper;
 
+    @Autowired
+    private EmsProjectDeviceSystemMapper emsProjectDeviceSystemMapper;
+
+    @Autowired
+    private EmsModelService emsModelService;
+
+    @Autowired
+    private EmsDeviceMapper emsDeviceMapper;
+
+    @Autowired
+    private EmsSpaceService emsSpaceService;
+
+    @Autowired
+    private EmsProjectConversionFactorMapper emsProjectConversionFactorMapper;
+
+    @Autowired
+    private EmsEnergyItemCodeMapper emsEnergyItemCodeMapper;
+
     @Override
     public EmsProjectResponse getProject(Long projectId) {
         EmsProject project;
@@ -33,13 +76,57 @@ public class EmsOverviewServiceImpl implements EmsOverviewService {
         }
         EmsProjectResponse resp = new EmsProjectResponse();
         resp.setId(project.getId());
+        resp.setSpaceId(project.getSpaceId());
         resp.setName(project.getName());
-        resp.setCode(project.getAbbreviation());
-        resp.setDescription(project.getIntroduction());
+        resp.setAbbreviation(project.getAbbreviation());
         resp.setArea(project.getArea());
-        resp.setResidentPopulation(project.getResidentPopulation());
-        resp.setCreateTime(project.getCreateTime());
-        resp.setUpdateTime(project.getUpdateTime());
+        resp.setCommonArea(project.getCommonArea());
+        resp.setAirConditionedArea(project.getAirConditionedArea());
+        // 源字段为 Integer,这里转为 BigDecimal 以匹配 VO 定义
+        if (project.getResidentPopulation() != null) {
+            resp.setResidentPopulation(java.math.BigDecimal.valueOf(project.getResidentPopulation()));
+        }
+
+        // 设备系统集合
+        List<EmsProjectDeviceSystem> sysList = emsProjectDeviceSystemMapper.selectList(
+                new LambdaQueryWrapper<EmsProjectDeviceSystem>()
+                        .eq(EmsProjectDeviceSystem::getProjectId, project.getId())
+        );
+        if (sysList != null && !sysList.isEmpty()) {
+            resp.setDeviceSystemList(sysList.stream()
+                    .map(EmsProjectDeviceSystem::getDeviceSystem)
+                    .collect(Collectors.toList()));
+        }
+
+        resp.setProvinceCode(project.getProvinceCode());
+        resp.setCityCode(project.getCityCode());
+        resp.setDistrictCode(project.getDistrictCode());
+        resp.setProvinceName(project.getProvinceName());
+        resp.setCityName(project.getCityName());
+        resp.setDistrictName(project.getDistrictName());
+        resp.setLocation(project.getLocation());
+        resp.setAddress(project.getAddress());
+        resp.setTypeId(project.getTypeId());
+        resp.setTypeName(project.getTypeName());
+        resp.setImage(project.getImage());
+        // imageUrl 视实际文件服务而定,这里暂不拼接,直接沿用 image 字段或留空
+        resp.setImageUrl(project.getImage());
+        resp.setIntroduction(project.getIntroduction());
+        resp.setPlatformName(project.getPlatformName());
+        resp.setLogo(project.getLogo());
+        resp.setLogoUrl(project.getLogo());
+        resp.setLogoMin(project.getLogoMin());
+        resp.setLogoMinUrl(project.getLogoMin());
+        resp.setUpdatedBy(project.getUpdatedBy());
+        resp.setCreatedBy(project.getCreatedBy());
+        if (project.getUpdateTime() != null) {
+            resp.setUpdateTime(project.getUpdateTime().toString());
+        }
+        if (project.getCreateTime() != null) {
+            resp.setCreateTime(project.getCreateTime().toString());
+        }
+        // 单位能耗暂无法从现有表直接计算,先置为空,后续可按业务补充
+        resp.setAreaCoal(null);
         return resp;
     }
 
@@ -50,4 +137,559 @@ public class EmsOverviewServiceImpl implements EmsOverviewService {
         resp.setTimeValue(timeValue);
         return resp;
     }
+
+    @Override
+    public List<EmsOverviewEnergyItemVO> queryOverviewItem(Long projectId) {
+        // 当前实现:直接基于能源类型列表(电/水/气)构造概览条目
+        List<EmsEnergyTypeVO> types = emsModelService.getEnergyTypeList();
+        return types.stream().map(t -> {
+            EmsOverviewEnergyItemVO vo = new EmsOverviewEnergyItemVO();
+            vo.setName(t.getName());
+            vo.setItemCode(t.getCode());
+            vo.setEnergyType(t.getId() != null ? t.getId().intValue() : null);
+            return vo;
+        }).collect(Collectors.toList());
+    }
+
+    @Override
+    public List<EmsOverviewDeviceSystemStatVO> queryOverviewDeviceInfo(Long projectId) {
+        Long pid = resolveProjectId(projectId);
+        if (pid == null) {
+            return java.util.Collections.emptyList();
+        }
+
+        // 1. 查询项目已配置的设备系统
+        List<EmsProjectDeviceSystem> systemList = emsProjectDeviceSystemMapper.selectList(
+                new LambdaQueryWrapper<EmsProjectDeviceSystem>()
+                        .eq(EmsProjectDeviceSystem::getProjectId, pid)
+        );
+        if (systemList == null || systemList.isEmpty()) {
+            return java.util.Collections.emptyList();
+        }
+        List<Integer> systems = systemList.stream()
+                .map(EmsProjectDeviceSystem::getDeviceSystem)
+                .collect(Collectors.toList());
+
+        // 2. 查询设备:同项目、启用状态、设备系统在上述集合中
+        List<EmsDevice> devices = emsDeviceMapper.selectList(
+                new LambdaQueryWrapper<EmsDevice>()
+                        .eq(EmsDevice::getProjectId, pid)
+                        .eq(EmsDevice::getStatus, 1)
+                        .in(EmsDevice::getDeviceSystem, systems)
+        );
+        java.util.Map<Integer, Long> deviceCountMap = devices.stream()
+                .filter(d -> d.getDeviceSystem() != null)
+                .collect(Collectors.groupingBy(EmsDevice::getDeviceSystem, Collectors.counting()));
+
+        // 3. 组装统计 VO
+        List<EmsOverviewDeviceSystemStatVO> result = new java.util.ArrayList<>();
+        for (EmsProjectDeviceSystem s : systemList) {
+            Integer code = s.getDeviceSystem();
+            EmsOverviewDeviceSystemStatVO vo = new EmsOverviewDeviceSystemStatVO();
+            vo.setDeviceSystem(code);
+            vo.setName(deviceSystemName(code));
+            vo.setValue(deviceCountMap.getOrDefault(code, 0L).intValue());
+            result.add(vo);
+        }
+        return result;
+    }
+
+    /**
+     * 分类能耗统计(模拟数据版本)
+     * 说明:
+     * - 目前不接真实能耗库,仅按固定示例数据做一套完整计算逻辑,保证前端联调正常。
+     * - 返回结构与原系统保持一致:Map<String, Object>。
+     */
+    @Override
+    public Map<String, Object> queryClassificationEnergy(Integer dateType, String itemCode, Integer energyType, Long projectId) {
+        Map<String, Object> result = new HashMap<>();
+
+        // 基础模拟数据:本期/环比/同比能耗(单位:kWh 或折算后的统一单位)
+        BigDecimal consume = new BigDecimal("100.00");
+        BigDecimal sequentialCon = new BigDecimal("80.00");   // 上一期
+        BigDecimal pariPassCon = new BigDecimal("90.00");     // 同期
+
+        // 模拟项目能耗折标系数、单价
+        BigDecimal coal = new BigDecimal("0.70");              // 折标系数(每单位能耗折算标煤)
+        BigDecimal unitPrice = new BigDecimal("1.00");         // 单价(每单位能耗金额)
+
+        // 本期费用 = 本期能耗 * 单价
+        BigDecimal cost = consume.multiply(unitPrice).setScale(2, RoundingMode.UP);
+        BigDecimal sequentialCost = sequentialCon.multiply(unitPrice).setScale(2, RoundingMode.UP);
+        BigDecimal pariPassCost = pariPassCon.multiply(unitPrice).setScale(2, RoundingMode.UP);
+
+        // 标煤量 = 能耗 * 折标系数
+        BigDecimal coalAmount = consume.multiply(coal).setScale(2, RoundingMode.UP);
+        BigDecimal sequentialCoal = sequentialCon.multiply(coal).setScale(2, RoundingMode.UP);
+        BigDecimal pariPassCoal = pariPassCon.multiply(coal).setScale(2, RoundingMode.UP);
+
+        // CO2 排放量 = 标煤量 * 系数 2.4589(固定常量,模拟用)
+        BigDecimal co2Factor = new BigDecimal("2.4589");
+        BigDecimal co2Amount = coalAmount.multiply(co2Factor).setScale(2, RoundingMode.UP);
+        BigDecimal sequentialCo2 = sequentialCoal.multiply(co2Factor).setScale(2, RoundingMode.UP);
+        BigDecimal pariPassCo2 = pariPassCoal.multiply(co2Factor).setScale(2, RoundingMode.UP);
+
+        result.put("consume", consume);
+        result.put("sequentialCon", sequentialCon);
+        result.put("pariPassCon", pariPassCon);
+        result.put("cost", cost);
+        result.put("sequentialCost", sequentialCost);
+        result.put("pariPassCost", pariPassCost);
+        result.put("coalAmount", coalAmount);
+        result.put("sequentialCoal", sequentialCoal);
+        result.put("pariPassCoal", pariPassCoal);
+        result.put("co2Amount", co2Amount);
+        result.put("sequentialCo2", sequentialCo2);
+        result.put("pariPassCo2", pariPassCo2);
+
+        return result;
+    }
+
+    /**
+     * 能耗用能趋势(模拟数据版本)
+     * 参考原系统 queryEnergyTrend 逻辑,仅保留时间维度与同比的结构,数据采用固定规则模拟。
+     */
+    @Override
+    public List<Map<String, Object>> queryEnergyTrend(Integer dateType, String itemCode, Long spaceId) {
+        List<Map<String, Object>> list = new ArrayList<>();
+
+        if (dateType == null || itemCode == null || itemCode.trim().isEmpty()) {
+            return list;
+        }
+
+        // 当前期与去年同期的时间范围
+        LocalDateTime endThisTime;
+        LocalDateTime startThisTime;
+        LocalDateTime endOldTime;
+        LocalDateTime startOldTime;
+
+        if (dateType == 1) {
+            // 日:按小时
+            endThisTime = LocalDateTime.of(LocalDate.now(), LocalTime.MAX);
+        } else if (dateType == 2) {
+            // 月:按天
+            endThisTime = LocalDateTime.of(LocalDate.now().with(TemporalAdjusters.lastDayOfMonth()), LocalTime.MAX);
+        } else if (dateType == 3) {
+            // 年:按月
+            endThisTime = LocalDateTime.of(LocalDate.now().with(TemporalAdjusters.lastDayOfYear()), LocalTime.MAX);
+        } else {
+            return list;
+        }
+
+        startThisTime = getStartTime(dateType, endThisTime);
+        endOldTime = endThisTime.minusYears(1);
+        startOldTime = getStartTime(dateType, endOldTime);
+
+        // 按时间步长循环生成当前期与去年同期的模拟趋势数据
+        LocalDateTime curTime = startThisTime;
+        LocalDateTime oldTime = startOldTime;
+        int index = 0;
+
+        while (!curTime.isAfter(endThisTime)) {
+            Map<String, Object> row = new HashMap<>();
+            // 时间标签:当前期时间
+            row.put("time", curTime.toString());
+            // 当前期用能:示例 100 + index * 5
+            BigDecimal currentValue = BigDecimal.valueOf(100 + index * 5L);
+            // 去年同期用能:示例 80 + index * 5
+            BigDecimal lastYearValue = BigDecimal.valueOf(80 + index * 5L);
+            row.put("currentValue", currentValue);
+            row.put("lastYearValue", lastYearValue);
+            // 去年同期时间标签
+            row.put("lastYearTime", oldTime.toString());
+
+            list.add(row);
+
+            // 根据时间类型推进时间
+            if (dateType == 1) {
+                curTime = curTime.plusHours(1);
+                oldTime = oldTime.plusHours(1);
+            } else if (dateType == 2) {
+                curTime = curTime.plusDays(1);
+                oldTime = oldTime.plusDays(1);
+            } else {
+                curTime = curTime.plusMonths(1);
+                oldTime = oldTime.plusMonths(1);
+            }
+
+            index++;
+        }
+
+        return list;
+    }
+
+    /**
+     * 计算指定时间类型的开始时间
+     * dateType: 1-日(当天 00:00:00)、2-月(当月第一天 00:00:00)、3-年(当年第一天 00:00:00)
+     */
+    private LocalDateTime getStartTime(Integer dateType, LocalDateTime endTime) {
+        if (dateType == null || endTime == null) {
+            return null;
+        }
+        if (dateType == 1) {
+            return LocalDateTime.of(endTime.toLocalDate(), LocalTime.MIN);
+        } else if (dateType == 2) {
+            LocalDate firstDayOfMonth = endTime.toLocalDate().withDayOfMonth(1);
+            return LocalDateTime.of(firstDayOfMonth, LocalTime.MIN);
+        } else if (dateType == 3) {
+            LocalDate firstDayOfYear = endTime.toLocalDate().with(TemporalAdjusters.firstDayOfYear());
+            return LocalDateTime.of(firstDayOfYear, LocalTime.MIN);
+        }
+        return LocalDateTime.of(endTime.toLocalDate(), LocalTime.MIN);
+    }
+
+    /**
+     * 建筑能耗分析(模拟数据版本)
+     * 参考原系统 queryBuildingRanking:按指定时间维度与能耗条目,对建筑/楼层进行能耗排名。
+     * 当前实现:空间信息(建筑/楼层)从 leo.ems_space 表真实查询,仅能耗值使用规则模拟。
+     */
+    @Override
+    public List<Map<String, Object>> queryBuildingRanking(Integer dateType, String itemCode, Long spaceId) {
+        List<Map<String, Object>> resultList = new ArrayList<>();
+
+        if (dateType == null || itemCode == null || itemCode.trim().isEmpty()) {
+            return resultList;
+        }
+
+        // 1. 确定项目及根空间
+        Long projectId = resolveProjectId(null);
+        if (projectId == null) {
+            return resultList;
+        }
+        EmsProject project = emsProjectMapper.selectById(projectId);
+        if (project == null || project.getSpaceId() == null) {
+            return resultList;
+        }
+        Long rootSpaceId = project.getSpaceId();
+
+        // 2. 查询空间列表:
+        // - 未指定 spaceId:取根空间下 type=3 的建筑
+        // - 指定 spaceId:取该空间下 type=4 的楼层
+        List<EmsSpace> spaceList;
+        if (spaceId == null) {
+            spaceList = emsSpaceService.list(
+                    new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<EmsSpace>()
+                            .eq(EmsSpace::getRootId, rootSpaceId)
+                            .eq(EmsSpace::getType, 3) // 3 = 建筑
+            );
+        } else {
+            spaceList = emsSpaceService.list(
+                    new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<EmsSpace>()
+                            .eq(EmsSpace::getParentId, spaceId)
+                            .eq(EmsSpace::getType, 4) // 4 = 楼层
+            );
+        }
+        if (spaceList == null || spaceList.isEmpty()) {
+            return resultList;
+        }
+
+        // 3. 为每个空间模拟能耗值(真实系统中此处应从 TSDB 或能耗统计表获取)
+        BigDecimal base = new BigDecimal("1000");
+        BigDecimal step = new BigDecimal("150");
+        BigDecimal factor;
+        if (dateType == 1) {
+            factor = BigDecimal.ONE;
+        } else if (dateType == 2) {
+            factor = new BigDecimal("1.5");
+        } else if (dateType == 3) {
+            factor = new BigDecimal("2.0");
+        } else {
+            factor = BigDecimal.ONE;
+        }
+
+        for (int i = 0; i < spaceList.size(); i++) {
+            EmsSpace space = spaceList.get(i);
+            if (space == null) {
+                continue;
+            }
+            Map<String, Object> map = new HashMap<>();
+            map.put("name", space.getName());
+            BigDecimal value = base.subtract(step.multiply(BigDecimal.valueOf(i))).multiply(factor);
+            map.put("value", value);
+            resultList.add(map);
+        }
+
+        // 4. 按 value 从大到小排序,形成能耗排名
+        resultList.sort((m1, m2) -> {
+            BigDecimal v1 = (BigDecimal) m1.get("value");
+            BigDecimal v2 = (BigDecimal) m2.get("value");
+            if (v1 == null && v2 == null) return 0;
+            if (v1 == null) return 1;
+            if (v2 == null) return -1;
+            return v2.compareTo(v1);
+        });
+
+        return resultList;
+    }
+
+    /**
+     * 单位综合能耗、综合累计能耗、分类能耗占比(总览顶部)
+     * - 能源类型来自 EmsModelService.getEnergyTypeList(电/水/气)
+     * - 折算系数来自 leo.ems_project_conversion_factor(优先取项目专属,其次 project_id=0 的公共配置)
+     * - 真实 TSDB 能耗数据暂不可用,当前按规则模拟各能源类型的能耗值。
+     */
+    @Override
+    public Map<String, Object> queryOverviewTop(Integer dateType, Long projectId) {
+        Map<String, Object> resultMap = new HashMap<>();
+        if (dateType == null) {
+            return resultMap;
+        }
+
+        // 时间范围,目前仅用于保持与原接口结构一致
+        LocalDateTime endTime = LocalDateTime.now();
+        LocalDateTime startTime = getStartTime(dateType, endTime);
+        if (startTime == null) {
+            return resultMap;
+        }
+
+        Long pid = resolveProjectId(projectId);
+        if (pid == null) {
+            return resultMap;
+        }
+
+        // 项目信息(含面积、根空间 ID)
+        EmsProjectResponse project = getProject(pid);
+        if (project == null || project.getSpaceId() == null) {
+            return resultMap;
+        }
+
+        // 1. 能源类型列表(电/水/气)
+        List<EmsEnergyTypeVO> energyTypes = emsModelService.getEnergyTypeList();
+        if (energyTypes == null || energyTypes.isEmpty()) {
+            return resultMap;
+        }
+
+        List<Integer> typeIds = energyTypes.stream()
+                .filter(t -> t.getId() != null)
+                .map(t -> t.getId().intValue())
+                .collect(Collectors.toList());
+        if (typeIds.isEmpty()) {
+            return resultMap;
+        }
+
+        // 2. 查询折算系数(项目专属 + 公共)
+        List<EmsProjectConversionFactor> factorList = emsProjectConversionFactorMapper.selectList(
+                new LambdaQueryWrapper<EmsProjectConversionFactor>()
+                        .in(EmsProjectConversionFactor::getEnergyType, typeIds)
+                        .in(EmsProjectConversionFactor::getProjectId, pid, 0L)
+        );
+        if (factorList == null || factorList.isEmpty()) {
+            return resultMap;
+        }
+
+        // 优先使用项目专属配置;若没有则退回公共配置
+        Map<Integer, List<EmsProjectConversionFactor>> groupedByType = factorList.stream()
+                .collect(Collectors.groupingBy(EmsProjectConversionFactor::getEnergyType));
+
+        // 3. 模拟各能源类型能耗值(真实实现应从 TSDB 或历史统计表获取)
+        Map<Integer, BigDecimal> consumeMap = new HashMap<>();
+        BigDecimal base = new BigDecimal("1000");
+        BigDecimal step = new BigDecimal("200");
+        BigDecimal factor;
+        if (dateType == 1) {
+            factor = BigDecimal.ONE;
+        } else if (dateType == 2) {
+            factor = new BigDecimal("1.5");
+        } else if (dateType == 3) {
+            factor = new BigDecimal("2.0");
+        } else {
+            factor = BigDecimal.ONE;
+        }
+
+        int index = 0;
+        for (Integer typeId : typeIds) {
+            BigDecimal value = base.subtract(step.multiply(BigDecimal.valueOf(index))).multiply(factor);
+            if (value.compareTo(BigDecimal.ZERO) < 0) {
+                value = BigDecimal.ZERO;
+            }
+            consumeMap.put(typeId, value);
+            index++;
+        }
+
+        BigDecimal totalCon = consumeMap.values().stream()
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+
+        // 4. 计算各能源类型折算标煤与碳排放、占比
+        BigDecimal coalTotal = BigDecimal.ZERO;
+        BigDecimal co2Total = BigDecimal.ZERO;
+        List<Map<String, Object>> ratioList = new ArrayList<>();
+
+        for (EmsEnergyTypeVO typeVO : energyTypes) {
+            if (typeVO.getId() == null) {
+                continue;
+            }
+            Integer typeId = typeVO.getId().intValue();
+            BigDecimal con = consumeMap.get(typeId);
+            if (con == null) {
+                continue;
+            }
+
+            List<EmsProjectConversionFactor> conversionFactorList = groupedByType.get(typeId);
+            if (conversionFactorList == null || conversionFactorList.isEmpty()) {
+                continue;
+            }
+
+            // 先筛出项目专属配置
+            List<EmsProjectConversionFactor> projectFactors = conversionFactorList.stream()
+                    .filter(f -> pid.equals(f.getProjectId()))
+                    .collect(Collectors.toList());
+            List<EmsProjectConversionFactor> effectiveFactors = projectFactors.isEmpty()
+                    ? conversionFactorList
+                    : projectFactors;
+
+            Map<String, BigDecimal> factorMap = effectiveFactors.stream()
+                    .filter(f -> f.getName() != null && f.getValue() != null)
+                    .collect(Collectors.toMap(EmsProjectConversionFactor::getName, EmsProjectConversionFactor::getValue,
+                            (oldVal, newVal) -> oldVal));
+
+            BigDecimal coal = factorMap.get("coal");
+            if (coal == null) {
+                continue;
+            }
+
+            BigDecimal coalAmount = coal.multiply(con).setScale(2, RoundingMode.UP);
+            BigDecimal co2Amount = coalAmount.multiply(new BigDecimal("2.4589")).setScale(2, RoundingMode.UP);
+            coalTotal = coalTotal.add(coalAmount);
+            co2Total = co2Total.add(co2Amount);
+
+            Map<String, Object> ratioMap = new HashMap<>();
+            ratioMap.put("name", typeVO.getName());
+            ratioMap.put("consume", coalAmount);
+            ratioMap.put("totalConsume", totalCon.multiply(coal).setScale(2, RoundingMode.UP));
+            ratioList.add(ratioMap);
+        }
+
+        // 5. 单位综合能耗(每平米标煤量)
+        BigDecimal unitCoal = BigDecimal.ZERO;
+        if (project.getArea() != null
+                && project.getArea().compareTo(BigDecimal.ZERO) != 0
+                && coalTotal.compareTo(BigDecimal.ZERO) != 0) {
+            unitCoal = coalTotal.divide(project.getArea(), 2, RoundingMode.UP);
+        }
+
+        resultMap.put("unitCoal", unitCoal);
+        resultMap.put("ratioList", ratioList);
+        resultMap.put("coalTotal", coalTotal);
+        resultMap.put("co2Total", co2Total);
+        return resultMap;
+    }
+
+    /**
+     * 分项能耗占比
+     * - 分项定义来自 leo.ems_energy_item_code:给定 itemCode 作为父项,按 parent_code 查询下级分项;
+     * - 若无下级分项,则仅返回父项自身;
+     * - 各分项及汇总能耗数值暂按规则模拟。
+     */
+    @Override
+    public List<Map<String, Object>> queryItemRatio(Integer dateType, String itemCode, Long projectId) {
+        List<Map<String, Object>> resultList = new ArrayList<>();
+        if (dateType == null || itemCode == null || itemCode.trim().isEmpty()) {
+            return resultList;
+        }
+
+        Long pid = resolveProjectId(projectId);
+        if (pid == null) {
+            return resultList;
+        }
+
+        // 当前实现中 startTime/spaceId 仅为预留,不参与模拟计算
+        LocalDateTime endTime = LocalDateTime.now();
+        LocalDateTime startTime = getStartTime(dateType, endTime);
+        if (startTime == null) {
+            return resultList;
+        }
+
+        // 查询父分项编码
+        EmsEnergyItemCode parent = emsEnergyItemCodeMapper.selectOne(
+                new LambdaQueryWrapper<EmsEnergyItemCode>()
+                        .eq(EmsEnergyItemCode::getCode, itemCode)
+        );
+        if (parent == null) {
+            return resultList;
+        }
+
+        // 模拟父分项总能耗
+        BigDecimal total = simulateItemConsume(itemCode, dateType, 0);
+
+        // 查询子分项列表(按 parent_code)
+        List<EmsEnergyItemCode> children = emsEnergyItemCodeMapper.selectList(
+                new LambdaQueryWrapper<EmsEnergyItemCode>()
+                        .eq(EmsEnergyItemCode::getParentCode, parent.getCode())
+        );
+
+        if (children == null || children.isEmpty()) {
+            // 无下级分项:仅返回父项自身
+            Map<String, Object> map = new HashMap<>();
+            map.put("name", parent.getName());
+            map.put("consume", total);
+            map.put("total", total);
+            resultList.add(map);
+            return resultList;
+        }
+
+        int index = 0;
+        for (EmsEnergyItemCode child : children) {
+            if (child == null) continue;
+            BigDecimal consume = simulateItemConsume(child.getCode(), dateType, index);
+            Map<String, Object> map = new HashMap<>();
+            map.put("name", child.getName());
+            map.put("consume", consume);
+            map.put("total", total);
+            resultList.add(map);
+            index++;
+        }
+
+        return resultList;
+    }
+
+    /**
+     * 分项能耗模拟函数
+     * 根据 dateType 与 index 返回不同量级的示例值。
+     */
+    private BigDecimal simulateItemConsume(String code, Integer dateType, int index) {
+        BigDecimal base = new BigDecimal("500");
+        BigDecimal step = new BigDecimal("80");
+        BigDecimal factor;
+        if (dateType != null && dateType == 1) {
+            factor = BigDecimal.ONE;
+        } else if (dateType != null && dateType == 2) {
+            factor = new BigDecimal("1.5");
+        } else if (dateType != null && dateType == 3) {
+            factor = new BigDecimal("2.0");
+        } else {
+            factor = BigDecimal.ONE;
+        }
+        BigDecimal value = base.subtract(step.multiply(BigDecimal.valueOf(index))).multiply(factor);
+        if (value.compareTo(BigDecimal.ZERO) < 0) {
+            value = BigDecimal.ZERO;
+        }
+        return value.setScale(2, RoundingMode.UP);
+    }
+
+    private Long resolveProjectId(Long projectId) {
+        if (projectId != null) return projectId;
+        List<EmsProject> list = emsProjectMapper.selectList(null);
+        return list.isEmpty() ? null : list.get(0).getId();
+    }
+
+    /** 根据设备系统编码返回名称(与 dmp_product.deviceType 含义保持一致) */
+    private String deviceSystemName(Integer deviceSystem) {
+        if (deviceSystem == null) return "";
+        switch (deviceSystem) {
+            case 501:
+                return "监控系统";
+            case 502:
+                return "门禁系统";
+            case 503:
+                return "梯控系统";
+            case 504:
+                return "机房系统";
+            case 509:
+                return "环境系统";
+            case 510:
+                return "照明系统";
+            default:
+                return "";
+        }
+    }
 }

+ 16 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsProjectConfigurationServiceImpl.java

@@ -0,0 +1,16 @@
+package com.usky.ems.service.impl;
+
+import com.usky.common.mybatis.core.AbstractCrudService;
+import com.usky.ems.domain.EmsProjectConfiguration;
+import com.usky.ems.mapper.EmsProjectConfigurationMapper;
+import com.usky.ems.service.EmsProjectConfigurationService;
+import org.springframework.stereotype.Service;
+
+/**
+ * 项目配置信息服务实现
+ */
+@Service
+public class EmsProjectConfigurationServiceImpl extends AbstractCrudService<EmsProjectConfigurationMapper, EmsProjectConfiguration>
+        implements EmsProjectConfigurationService {
+}
+

+ 63 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsProjectServiceImpl.java

@@ -0,0 +1,63 @@
+package com.usky.ems.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.usky.common.mybatis.core.AbstractCrudService;
+import com.usky.ems.domain.EmsProject;
+import com.usky.ems.domain.EmsProjectConfiguration;
+import com.usky.ems.mapper.EmsProjectMapper;
+import com.usky.ems.mapper.EmsProjectConfigurationMapper;
+import com.usky.ems.service.EmsProjectService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+
+/**
+ * 项目服务实现(leo.ems_project)
+ */
+@Service
+public class EmsProjectServiceImpl extends AbstractCrudService<EmsProjectMapper, EmsProject> implements EmsProjectService {
+
+    @Autowired
+    private EmsProjectConfigurationMapper emsProjectConfigurationMapper;
+
+    @Override
+    public Long create(EmsProject project) {
+        LocalDateTime now = LocalDateTime.now();
+        project.setCreateTime(now);
+        project.setUpdateTime(now);
+        this.save(project);
+        return project.getId();
+    }
+
+    @Override
+    public void update(EmsProject project) {
+        if (project.getId() == null) {
+            return;
+        }
+        project.setUpdateTime(LocalDateTime.now());
+        this.updateById(project);
+    }
+
+    @Override
+    public void delete(Long id) {
+        if (id == null) {
+            return;
+        }
+        this.removeById(id);
+    }
+
+    @Override
+    public Boolean getConfigurationsStatus(Long projectId, String name) {
+        if (projectId == null || name == null) {
+            return false;
+        }
+        LambdaQueryWrapper<EmsProjectConfiguration> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(EmsProjectConfiguration::getProjectId, projectId)
+                .eq(EmsProjectConfiguration::getName, name)
+                .last("LIMIT 1");
+        EmsProjectConfiguration cfg = emsProjectConfigurationMapper.selectOne(wrapper);
+        return cfg != null && "1".equals(cfg.getValue());
+    }
+}
+

+ 136 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/impl/EmsSpaceServiceImpl.java

@@ -0,0 +1,136 @@
+package com.usky.ems.service.impl;
+
+import com.usky.common.mybatis.core.AbstractCrudService;
+import com.usky.ems.domain.EmsSpace;
+import com.usky.ems.mapper.EmsSpaceMapper;
+import com.usky.ems.service.EmsSpaceService;
+import com.usky.ems.service.vo.EmsSpaceForestNodeVO;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * 空间服务实现(leo.ems_space)
+ */
+@Service
+public class EmsSpaceServiceImpl extends AbstractCrudService<EmsSpaceMapper, EmsSpace> implements EmsSpaceService {
+
+    @Override
+    public EmsSpaceForestNodeVO tree() {
+        List<EmsSpace> allSpaces = this.list();
+        if (allSpaces == null || allSpaces.isEmpty()) {
+            return null;
+        }
+
+        // 1. 转为 VO 列表
+        List<EmsSpaceForestNodeVO> allNodes = allSpaces.stream()
+                .map(this::toNode)
+                .collect(Collectors.toList());
+
+        // 2. 按名称排序,尽量保持稳定的展示顺序
+        allNodes.sort(Comparator.comparing(
+                EmsSpaceForestNodeVO::getName,
+                Comparator.nullsFirst(String::compareToIgnoreCase)
+        ));
+
+        // 3. 建立 id -> 节点 映射,方便父子关联
+        Map<Long, EmsSpaceForestNodeVO> idNodeMap = new HashMap<>();
+        for (EmsSpaceForestNodeVO node : allNodes) {
+            if (node.getId() != null) {
+                idNodeMap.put(node.getId(), node);
+            }
+        }
+
+        // 4. 组装父子关系,并收集根节点
+        List<EmsSpaceForestNodeVO> roots = new ArrayList<>();
+        for (EmsSpaceForestNodeVO node : allNodes) {
+            Long parentId = node.getParentId();
+            if (parentId != null && parentId != 0L) {
+                EmsSpaceForestNodeVO parent = idNodeMap.get(parentId);
+                if (parent != null) {
+                    node.setParentSpaceName(parent.getName());
+                    parent.getChildren().add(node);
+                } else {
+                    // 找不到父节点时,视为根节点,避免数据丢失
+                    roots.add(node);
+                }
+            } else {
+                // parentId 为空或为 0,视为根节点
+                roots.add(node);
+            }
+        }
+
+        if (roots.isEmpty()) {
+            return null;
+        }
+
+        // 5. 若只有一个根节点,直接返回;否则构造一个虚拟根节点
+        List<EmsSpaceForestNodeVO> distinctRoots = roots.stream()
+                .filter(Objects::nonNull)
+                .distinct()
+                .collect(Collectors.toList());
+
+        if (distinctRoots.size() == 1) {
+            return distinctRoots.get(0);
+        }
+
+        EmsSpaceForestNodeVO virtualRoot = new EmsSpaceForestNodeVO();
+        virtualRoot.setId(0L);
+        virtualRoot.setName("ROOT");
+        virtualRoot.setChildren(distinctRoots);
+        return virtualRoot;
+    }
+
+    @Override
+    public List<Long> getAuthorizedSpaceIds(Long spaceId) {
+        if (spaceId == null) {
+            return Collections.emptyList();
+        }
+        return Collections.singletonList(spaceId);
+    }
+
+    @Override
+    public List<EmsSpace> recursiveAllChildrenNode(Long spaceId) {
+        if (spaceId == null) {
+            return Collections.emptyList();
+        }
+        List<EmsSpace> result = new ArrayList<>();
+        collectChildren(spaceId, result);
+        return result;
+    }
+
+    private void collectChildren(Long parentId, List<EmsSpace> out) {
+        List<EmsSpace> children = this.list(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<EmsSpace>()
+                .eq(EmsSpace::getParentId, parentId));
+        if (children == null || children.isEmpty()) {
+            return;
+        }
+        out.addAll(children);
+        for (EmsSpace child : children) {
+            if (child != null && child.getId() != null) {
+                collectChildren(child.getId(), out);
+            }
+        }
+    }
+
+    private EmsSpaceForestNodeVO toNode(EmsSpace space) {
+        EmsSpaceForestNodeVO vo = new EmsSpaceForestNodeVO();
+        vo.setId(space.getId());
+        vo.setName(space.getName());
+        vo.setParentId(space.getParentId());
+        vo.setType(space.getType());
+        vo.setRootId(space.getRootId());
+        vo.setPath(space.getPath());
+        vo.setPathName(space.getPathName());
+        vo.setDeep(space.getDeep());
+        return vo;
+    }
+}
+

+ 46 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/DeviceEnergyReportItemVO.java

@@ -0,0 +1,46 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+/**
+ * 能耗报表设备项(用于 showEnergyReportDevices 返回结构)
+ */
+@Data
+public class DeviceEnergyReportItemVO {
+
+    /** 设备ID */
+    private String id;
+
+    /** 设备名称 */
+    private String name;
+
+    /** 设备编号 */
+    private String number;
+
+    /** 项目ID */
+    private Long projectId;
+
+    /** 产品ID */
+    private Long productId;
+
+    /** 安装位置(空间ID) */
+    private Long installationLocation;
+
+    /** 监测位置(空间ID) */
+    private Long monitoringLocation;
+
+    /** 通道ID */
+    private Long channelId;
+
+    /** 网关ID */
+    private String gatewayId;
+
+    /** 设备系统 */
+    private Integer deviceSystem;
+
+    /** 状态 */
+    private Integer status;
+
+    /** 位置描述 */
+    private String location;
+}

+ 37 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/DeviceEnergyReportRequest.java

@@ -0,0 +1,37 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 设备能耗报表查询请求(EmsDevice 子模块)
+ */
+@Data
+public class DeviceEnergyReportRequest {
+
+    /** 空间ID(可选,用于授权空间过滤) */
+    private Long spaceId;
+
+    /** 产品ID列表(可选,对应 ems_device.product_id 或 dmp_product.id) */
+    private List<Long> productIdList;
+
+    /** 能源类型(可选,自定义约定) */
+    private Integer energyType;
+
+    /** 设备系统(可选,对应 ems_project_device_system.device_system 或自定义枚举) */
+    private Integer deviceSystem;
+
+    /** 项目ID(必填,用于多项目场景区分数据) */
+    private Long projectId;
+
+    /** 网关ID(可选) */
+    private String gatewayId;
+
+    /** 通道ID(可选) */
+    private Long channelId;
+
+    /** 功能标识符(可选,如 total_kwh,用于过滤具备某属性点位的设备) */
+    private String identifier;
+}
+

+ 25 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/DeviceEventDTO.java

@@ -0,0 +1,25 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 设备事件查询参数
+ */
+@Data
+public class DeviceEventDTO {
+
+    /** 安装位置(空间ID) */
+    private Long installationLocation;
+
+    /** 设备类型 1:设备 2:网关 */
+    private Integer deviceType;
+
+    /** 查询开始时间 */
+    private LocalDateTime startTime;
+
+    /** 查询结束时间 */
+    private LocalDateTime endTime;
+}
+

+ 24 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/DeviceEventVO.java

@@ -0,0 +1,24 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 设备事件展示对象
+ */
+@Data
+public class DeviceEventVO {
+
+    private String deviceId;
+    private String deviceName;
+    private String content;
+    /** 安装位置名称 */
+    private String installationLocation;
+    private Integer eventType;
+    private LocalDateTime startTime;
+    private String identifier;
+    /** 持续时长(中文展示) */
+    private String duration;
+}
+

+ 20 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsOverviewDeviceSystemStatVO.java

@@ -0,0 +1,20 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+/**
+ * 概览页设备系统统计条目
+ */
+@Data
+public class EmsOverviewDeviceSystemStatVO {
+
+    /** 设备系统编码 */
+    private Integer deviceSystem;
+
+    /** 设备系统名称(如监控系统、门禁系统等) */
+    private String name;
+
+    /** 设备数量 */
+    private Integer value;
+}
+

+ 20 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsOverviewEnergyItemVO.java

@@ -0,0 +1,20 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+/**
+ * 概览页能源类型条目(类似 EnergyTypeEnum)
+ */
+@Data
+public class EmsOverviewEnergyItemVO {
+
+    /** 名称,例如:电、水、气 */
+    private String name;
+
+    /** 条目编码,例如:electric、water、gas */
+    private String itemCode;
+
+    /** 能源类型编码,例如:1=电、2=水、3=气 */
+    private Integer energyType;
+}
+

+ 94 - 7
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsProjectResponse.java

@@ -1,23 +1,110 @@
 package com.usky.ems.service.vo;
 
+import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
 import java.math.BigDecimal;
-import java.time.LocalDateTime;
+import java.util.List;
 
 /**
  * 项目信息响应(数据总览 - 获取项目信息)
- * 字段名与 leo.ems_project 一致
  */
 @Data
 public class EmsProjectResponse {
 
+    @ApiModelProperty("自增ID")
     private Long id;
+
+    @ApiModelProperty("空间ID")
+    private Long spaceId;
+
+    @ApiModelProperty("名称")
     private String name;
-    private String code;
-    private String description;
+
+    @ApiModelProperty("简称")
+    private String abbreviation;
+
+    @ApiModelProperty("项目面积")
     private BigDecimal area;
-    private Integer residentPopulation;
-    private LocalDateTime createTime;
-    private LocalDateTime updateTime;
+
+    @ApiModelProperty("公区面积")
+    private BigDecimal commonArea;
+
+    @ApiModelProperty("空调面积")
+    private BigDecimal airConditionedArea;
+
+    @ApiModelProperty("常驻人数")
+    private BigDecimal residentPopulation;
+
+    @ApiModelProperty("设备系统集合")
+    private List<Integer> deviceSystemList;
+
+    @ApiModelProperty("省code")
+    private String provinceCode;
+
+    @ApiModelProperty("市code")
+    private String cityCode;
+
+    @ApiModelProperty("区code")
+    private String districtCode;
+
+    @ApiModelProperty("省")
+    private String provinceName;
+
+    @ApiModelProperty("市")
+    private String cityName;
+
+    @ApiModelProperty("区")
+    private String districtName;
+
+    @ApiModelProperty("地理位置")
+    private String location;
+
+    @ApiModelProperty("项目地址")
+    private String address;
+
+    @ApiModelProperty("项目类型ID")
+    private Integer typeId;
+
+    @ApiModelProperty("项目类型")
+    private String typeName;
+
+    @ApiModelProperty("项目图片")
+    private String image;
+
+    @ApiModelProperty("项目图片url")
+    private String imageUrl;
+
+    @ApiModelProperty("项目简介")
+    private String introduction;
+
+    @ApiModelProperty("平台名称")
+    private String platformName;
+
+    @ApiModelProperty("平台大logo")
+    private String logo;
+
+    @ApiModelProperty("平台大logo地址")
+    private String logoUrl;
+
+    @ApiModelProperty("平台小logo")
+    private String logoMin;
+
+    @ApiModelProperty("平台小logo地址")
+    private String logoMinUrl;
+
+    @ApiModelProperty("更新人")
+    private Long updatedBy;
+
+    @ApiModelProperty("记录更新时间")
+    private String updateTime;
+
+    @ApiModelProperty("创建人")
+    private Long createdBy;
+
+    @ApiModelProperty("记录创建时间")
+    private String createTime;
+
+    @ApiModelProperty("单位能耗")
+    private BigDecimal areaCoal;
 }

+ 44 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EmsSpaceForestNodeVO.java

@@ -0,0 +1,44 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 空间树节点(对应 leo.ems_space)
+ */
+@Data
+public class EmsSpaceForestNodeVO {
+
+    /** 节点 ID(ems_space.id) */
+    private Long id;
+
+    /** 名称(ems_space.name 或扩展名称) */
+    private String name;
+
+    /** 父节点 ID(ems_space.parent_id) */
+    private Long parentId;
+
+    /** 空间类型:1项目 2区域 3建筑 4楼层 5房间(ems_space.type) */
+    private Integer type;
+
+    /** 根节点 ID(ems_space.root_id) */
+    private Long rootId;
+
+    /** 节点路径(ems_space.path) */
+    private String path;
+
+    /** 节点路径名称(ems_space.path_name) */
+    private String pathName;
+
+    /** 深度(ems_space.deep) */
+    private Integer deep;
+
+    /** 父空间名称(便于前端展示) */
+    private String parentSpaceName;
+
+    /** 子节点列表 */
+    private List<EmsSpaceForestNodeVO> children = new ArrayList<>();
+}
+

+ 28 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/EnergyTypeWrapperProductVO.java

@@ -0,0 +1,28 @@
+package com.usky.ems.service.vo;
+
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 能源/设备类型包装视图对象:一个类型下挂载多个产品
+ */
+@Data
+public class EnergyTypeWrapperProductVO {
+
+    /** 类型编码:可表示能源类型或设备类型 */
+    private Integer energyType;
+
+    /** 类型名称,如“电”“水”“气”或“监控系统”等 */
+    private String energyTypeName;
+
+    /** 当前能源类型下的产品/分项列表 */
+    private List<SimpleProductVO> productList = new ArrayList<>();
+
+    public EnergyTypeWrapperProductVO(Integer energyType, String energyTypeName) {
+        this.energyType = energyType;
+        this.energyTypeName = energyTypeName;
+    }
+}
+

+ 22 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/service/vo/SimpleProductVO.java

@@ -0,0 +1,22 @@
+package com.usky.ems.service.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 简单产品视图对象
+ * 在本模块中,用于承载某个能源类型/设备类型下的产品信息(来源于 dmp_product)
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class SimpleProductVO {
+
+    /** 产品标识,这里使用 dmp_product.productCode */
+    private String productId;
+
+    /** 产品名称,这里使用 dmp_product.productName */
+    private String productName;
+}
+