From 4f55d3cb4bc644e4534106336f2047af1a4db5df Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期五, 29 五月 2026 18:09:46 +0800
Subject: [PATCH] feat(config): 添加新环境配置并扩展设备台账功能

---
 src/main/java/com/ruoyi/device/service/impl/DeviceAreaServiceImpl.java              |  190 +++++
 src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java  |  185 ++++-
 src/main/resources/mapper/device/DeviceMaintenanceMapper.xml                        |    9 
 src/main/java/com/ruoyi/device/dto/DeviceMaintenanceDto.java                        |    3 
 src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskScheduleUtils.java    |  171 +++++
 src/main/java/com/ruoyi/inspectiontask/pojo/TimingTask.java                         |    5 
 src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskJob.java              |   37 
 src/main/java/com/ruoyi/inspectiontask/dto/InspectionTaskDto.java                   |   13 
 src/main/java/com/ruoyi/device/vo/DeviceRepairVo.java                               |    3 
 src/main/java/com/ruoyi/http/service/impl/RealTimeEnergyConsumptionServiceImpl.java |  441 +++++++++++-
 src/main/java/com/ruoyi/device/service/impl/DeviceRepairServiceImpl.java            |   10 
 src/main/java/com/ruoyi/device/service/impl/DeviceMaintenanceServiceImpl.java       |   18 
 src/main/resources/application-dev.yml                                              |    2 
 src/main/resources/mapper/device/DeviceLedgerMapper.xml                             |   22 
 src/main/java/com/ruoyi/device/pojo/DeviceArea.java                                 |   57 +
 src/main/java/com/ruoyi/device/service/IDeviceAreaService.java                      |   25 
 src/main/java/com/ruoyi/inspectiontask/service/TimingTaskService.java               |    2 
 src/main/java/com/ruoyi/device/pojo/DeviceLedger.java                               |    9 
 src/main/java/com/ruoyi/inspectiontask/dto/TimingTaskDto.java                       |    2 
 src/main/resources/mapper/inspectiontask/InspectionTaskMapper.xml                   |   64 +
 src/main/java/com/ruoyi/device/vo/DeviceMaintenanceVo.java                          |    2 
 src/main/java/com/ruoyi/device/dto/DeviceAreaTreeDto.java                           |   26 
 src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTask.java                     |   10 
 src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java      |   62 +
 src/main/java/com/ruoyi/device/pojo/DeviceRepair.java                               |    3 
 src/main/java/com/ruoyi/inspectiontask/service/InspectionTaskService.java           |    2 
 src/main/java/com/ruoyi/device/pojo/MaintenanceTask.java                            |    6 
 src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskServiceImpl.java         |   47 +
 src/main/resources/application-dlsmls-pro.yml                                       |  268 ++++++++
 src/main/java/com/ruoyi/device/dto/DeviceLedgerDto.java                             |   14 
 src/main/resources/mapper/device/DeviceRepairMapper.xml                             |   19 
 src/main/java/com/ruoyi/device/controller/DeviceAreaController.java                 |   68 ++
 src/main/java/com/ruoyi/inspectiontask/controller/TimingTaskController.java         |   24 
 src/main/java/com/ruoyi/device/mapper/DeviceAreaMapper.java                         |   14 
 src/main/java/com/ruoyi/inspectiontask/mapper/InspectionTaskMapper.java             |    6 
 src/main/java/com/ruoyi/http/service/controller/JclyController.java                 |   79 ++
 src/main/java/com/ruoyi/device/pojo/DeviceMaintenance.java                          |    3 
 37 files changed, 1,798 insertions(+), 123 deletions(-)

diff --git a/src/main/java/com/ruoyi/device/controller/DeviceAreaController.java b/src/main/java/com/ruoyi/device/controller/DeviceAreaController.java
new file mode 100644
index 0000000..45dacd2
--- /dev/null
+++ b/src/main/java/com/ruoyi/device/controller/DeviceAreaController.java
@@ -0,0 +1,68 @@
+package com.ruoyi.device.controller;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.device.pojo.DeviceArea;
+import com.ruoyi.device.service.IDeviceAreaService;
+import com.ruoyi.framework.aspectj.lang.annotation.Log;
+import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
+import com.ruoyi.framework.web.domain.AjaxResult;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@Api(tags = "璁惧鍖哄煙绠$悊")
+@RestController
+@RequestMapping("/device/area")
+public class DeviceAreaController {
+
+    @Autowired
+    private IDeviceAreaService deviceAreaService;
+
+    @GetMapping("/tree")
+    @ApiOperation("璁惧鍖哄煙鏍�")
+    public AjaxResult tree() {
+        return AjaxResult.success(deviceAreaService.listTree());
+    }
+
+    @GetMapping("/treeWithDevices")
+    @ApiOperation("璁惧鍖哄煙鏍�-鍖呭惈璁惧鍒楄〃")
+    public AjaxResult treeWithDevices() {
+        return AjaxResult.success(deviceAreaService.listTreeWithDevices());
+    }
+
+    @GetMapping("/page")
+    @ApiOperation("璁惧鍖哄煙鍒嗛〉")
+    public AjaxResult page(Page page, DeviceArea deviceArea) {
+        return AjaxResult.success(deviceAreaService.queryPage(page, deviceArea));
+    }
+
+    @GetMapping("/{id}")
+    @ApiOperation("璁惧鍖哄煙璇︽儏")
+    public AjaxResult detail(@PathVariable Long id) {
+        return AjaxResult.success(deviceAreaService.getById(id));
+    }
+
+    @PostMapping
+    @ApiOperation("鏂板璁惧鍖哄煙")
+    @Log(title = "璁惧鍖哄煙", businessType = BusinessType.INSERT)
+    public AjaxResult add(@RequestBody DeviceArea deviceArea) {
+        return deviceAreaService.saveDeviceArea(deviceArea);
+    }
+
+    @PutMapping
+    @ApiOperation("淇敼璁惧鍖哄煙")
+    @Log(title = "璁惧鍖哄煙", businessType = BusinessType.UPDATE)
+    public AjaxResult update(@RequestBody DeviceArea deviceArea) {
+        return deviceAreaService.updateDeviceArea(deviceArea);
+    }
+
+    @DeleteMapping
+    @ApiOperation("鍒犻櫎璁惧鍖哄煙")
+    @Log(title = "璁惧鍖哄煙", businessType = BusinessType.DELETE)
+    public AjaxResult delete(@RequestBody List<Long> ids) {
+        return deviceAreaService.removeDeviceAreas(ids);
+    }
+}
diff --git a/src/main/java/com/ruoyi/device/dto/DeviceAreaTreeDto.java b/src/main/java/com/ruoyi/device/dto/DeviceAreaTreeDto.java
new file mode 100644
index 0000000..8cff44b
--- /dev/null
+++ b/src/main/java/com/ruoyi/device/dto/DeviceAreaTreeDto.java
@@ -0,0 +1,26 @@
+package com.ruoyi.device.dto;
+
+import com.ruoyi.device.pojo.DeviceLedger;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class DeviceAreaTreeDto {
+
+    private Long id;
+
+    private Long parentId;
+
+    private String areaName;
+
+    private String label;
+
+    private Long sort;
+
+    private String remark;
+
+    private List<DeviceLedger> deviceList;
+
+    private List<DeviceAreaTreeDto> children;
+}
diff --git a/src/main/java/com/ruoyi/device/dto/DeviceLedgerDto.java b/src/main/java/com/ruoyi/device/dto/DeviceLedgerDto.java
index 8ceb915..2a73c9b 100644
--- a/src/main/java/com/ruoyi/device/dto/DeviceLedgerDto.java
+++ b/src/main/java/com/ruoyi/device/dto/DeviceLedgerDto.java
@@ -5,6 +5,7 @@
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.ruoyi.dto.DateQueryDto;
+import io.swagger.annotations.ApiModelProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import org.springframework.format.annotation.DateTimeFormat;
@@ -162,4 +163,17 @@
 
     @Schema(description = "璁惧绫诲瀷")
     private String type;
+
+    @ApiModelProperty("鏄惁涓虹墿鑱旇澶� 0-鍚� 1-鏄�")
+    private Integer isIotDevice;
+
+    @ApiModelProperty("澶栭儴缂栫爜")
+    private String externalCode;
+
+    @Schema(description = "璁惧鍖哄煙ID")
+    private Long areaId;
+
+    @Schema(description = "璁惧鍖哄煙鍚嶇О")
+    @TableField(exist = false)
+    private String areaName;
 }
diff --git a/src/main/java/com/ruoyi/device/dto/DeviceMaintenanceDto.java b/src/main/java/com/ruoyi/device/dto/DeviceMaintenanceDto.java
index 127d82b..f344cb3 100644
--- a/src/main/java/com/ruoyi/device/dto/DeviceMaintenanceDto.java
+++ b/src/main/java/com/ruoyi/device/dto/DeviceMaintenanceDto.java
@@ -13,6 +13,9 @@
 @Data
 public class DeviceMaintenanceDto extends DeviceMaintenance {
 
+    @Schema(description = "璁惧鍖哄煙鍚嶇О")
+    private String areaName;
+
 
     @Schema(description = "璁惧淇濆吇id")
     private Long id;
diff --git a/src/main/java/com/ruoyi/device/mapper/DeviceAreaMapper.java b/src/main/java/com/ruoyi/device/mapper/DeviceAreaMapper.java
new file mode 100644
index 0000000..f60ce8a
--- /dev/null
+++ b/src/main/java/com/ruoyi/device/mapper/DeviceAreaMapper.java
@@ -0,0 +1,14 @@
+package com.ruoyi.device.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.device.pojo.DeviceArea;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+@Mapper
+public interface DeviceAreaMapper extends BaseMapper<DeviceArea> {
+
+    IPage<DeviceArea> queryPage(Page page, @Param("deviceArea") DeviceArea deviceArea);
+}
diff --git a/src/main/java/com/ruoyi/device/pojo/DeviceArea.java b/src/main/java/com/ruoyi/device/pojo/DeviceArea.java
new file mode 100644
index 0000000..5ca6115
--- /dev/null
+++ b/src/main/java/com/ruoyi/device/pojo/DeviceArea.java
@@ -0,0 +1,57 @@
+package com.ruoyi.device.pojo;
+
+import com.baomidou.mybatisplus.annotation.*;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+@TableName("device_area")
+@ApiModel("璁惧鍖哄煙")
+public class DeviceArea {
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @ApiModelProperty("鍖哄煙鍚嶇О")
+    private String areaName;
+
+    @ApiModelProperty("鐖剁骇ID")
+    private Long parentId;
+
+    @ApiModelProperty("鎺掑簭")
+    private Long sort;
+
+    @ApiModelProperty("澶囨敞")
+    private String remark;
+
+    @ApiModelProperty("鍒涘缓鏃堕棿")
+    @TableField(fill = FieldFill.INSERT)
+    private LocalDateTime createTime;
+
+    @ApiModelProperty("鏇存柊鏃堕棿")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private LocalDateTime updateTime;
+
+    @ApiModelProperty("鍒涘缓鐢ㄦ埛")
+    @TableField(fill = FieldFill.INSERT)
+    private Integer createUser;
+
+    @ApiModelProperty("鏇存柊鐢ㄦ埛")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Integer updateUser;
+
+    @ApiModelProperty("绉熸埛ID")
+    @TableField(fill = FieldFill.INSERT)
+    private Long tenantId;
+
+    @ApiModelProperty("閮ㄩ棬ID")
+    @TableField(fill = FieldFill.INSERT)
+    private Long deptId;
+
+    @TableField(exist = false)
+    @ApiModelProperty("鐖剁骇鍚嶇О")
+    private String parentName;
+}
diff --git a/src/main/java/com/ruoyi/device/pojo/DeviceLedger.java b/src/main/java/com/ruoyi/device/pojo/DeviceLedger.java
index 61239ac..4f21280 100644
--- a/src/main/java/com/ruoyi/device/pojo/DeviceLedger.java
+++ b/src/main/java/com/ruoyi/device/pojo/DeviceLedger.java
@@ -4,6 +4,7 @@
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import org.springframework.format.annotation.DateTimeFormat;
@@ -54,6 +55,8 @@
      * 渚涘簲鍟嗗悕绉�
      */
     private String supplierName;
+
+    private Long areaId;
 
     /**
      * 鍗曚綅
@@ -161,4 +164,10 @@
 
     @TableField(fill = FieldFill.INSERT)
     private Long deptId;
+
+    @ApiModelProperty("鏄惁涓虹墿鑱旇澶� 0-鍚� 1-鏄�")
+    private Integer isIotDevice;
+
+    @ApiModelProperty("澶栭儴缂栫爜")
+    private String externalCode;
 }
diff --git a/src/main/java/com/ruoyi/device/pojo/DeviceMaintenance.java b/src/main/java/com/ruoyi/device/pojo/DeviceMaintenance.java
index b635e5f..f4ebc48 100644
--- a/src/main/java/com/ruoyi/device/pojo/DeviceMaintenance.java
+++ b/src/main/java/com/ruoyi/device/pojo/DeviceMaintenance.java
@@ -22,6 +22,9 @@
     @Schema(description = "璁惧鍙拌处id")
     private Long deviceLedgerId;
 
+    @Schema(description = "璁惧鍖哄煙ID")
+    private Long areaId;
+
     @Schema(description = "淇濆吇浠诲姟id")
     private Long maintenanceTaskId;
 
diff --git a/src/main/java/com/ruoyi/device/pojo/DeviceRepair.java b/src/main/java/com/ruoyi/device/pojo/DeviceRepair.java
index c9efa55..16fd672 100644
--- a/src/main/java/com/ruoyi/device/pojo/DeviceRepair.java
+++ b/src/main/java/com/ruoyi/device/pojo/DeviceRepair.java
@@ -22,6 +22,9 @@
     @Schema(description = "璁惧鍙拌处id")
     private Long deviceLedgerId;
 
+    @Schema(description = "璁惧鍖哄煙ID")
+    private Long areaId;
+
     private String deviceName;
 
     private String deviceModel;
diff --git a/src/main/java/com/ruoyi/device/pojo/MaintenanceTask.java b/src/main/java/com/ruoyi/device/pojo/MaintenanceTask.java
index 350a09d..0f1e7ee 100644
--- a/src/main/java/com/ruoyi/device/pojo/MaintenanceTask.java
+++ b/src/main/java/com/ruoyi/device/pojo/MaintenanceTask.java
@@ -37,6 +37,9 @@
     @TableId(type = IdType.AUTO)
     private Long id;
 
+    @Schema(description = "璁惧鍖哄煙ID")
+    private Long areaId;
+
     @Schema(description = "璁惧鍚嶇О")
     @Excel(name = "淇濆吇浠诲姟鍚嶇О")
     private String taskName;
@@ -116,4 +119,7 @@
 
     @TableField(fill = FieldFill.INSERT)
     private Long deptId;
+
+    @TableField(exist = false)
+    private String areaName;
 }
diff --git a/src/main/java/com/ruoyi/device/service/IDeviceAreaService.java b/src/main/java/com/ruoyi/device/service/IDeviceAreaService.java
new file mode 100644
index 0000000..ea25046
--- /dev/null
+++ b/src/main/java/com/ruoyi/device/service/IDeviceAreaService.java
@@ -0,0 +1,25 @@
+package com.ruoyi.device.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.ruoyi.device.dto.DeviceAreaTreeDto;
+import com.ruoyi.device.pojo.DeviceArea;
+import com.ruoyi.framework.web.domain.AjaxResult;
+
+import java.util.List;
+
+public interface IDeviceAreaService extends IService<DeviceArea> {
+
+    IPage<DeviceArea> queryPage(Page page, DeviceArea deviceArea);
+
+    List<DeviceAreaTreeDto> listTree();
+
+    List<DeviceAreaTreeDto> listTreeWithDevices();
+
+    AjaxResult saveDeviceArea(DeviceArea deviceArea);
+
+    AjaxResult updateDeviceArea(DeviceArea deviceArea);
+
+    AjaxResult removeDeviceAreas(List<Long> ids);
+}
diff --git a/src/main/java/com/ruoyi/device/service/impl/DeviceAreaServiceImpl.java b/src/main/java/com/ruoyi/device/service/impl/DeviceAreaServiceImpl.java
new file mode 100644
index 0000000..bf8081c
--- /dev/null
+++ b/src/main/java/com/ruoyi/device/service/impl/DeviceAreaServiceImpl.java
@@ -0,0 +1,190 @@
+package com.ruoyi.device.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.bean.BeanUtils;
+import com.ruoyi.device.dto.DeviceAreaTreeDto;
+import com.ruoyi.device.mapper.DeviceAreaMapper;
+import com.ruoyi.device.mapper.DeviceLedgerMapper;
+import com.ruoyi.device.pojo.DeviceArea;
+import com.ruoyi.device.pojo.DeviceLedger;
+import com.ruoyi.device.service.IDeviceAreaService;
+import com.ruoyi.framework.web.domain.AjaxResult;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import java.util.*;
+
+@Service
+public class DeviceAreaServiceImpl extends ServiceImpl<DeviceAreaMapper, DeviceArea> implements IDeviceAreaService {
+
+    @Autowired
+    private DeviceAreaMapper deviceAreaMapper;
+
+    @Autowired
+    private DeviceLedgerMapper deviceLedgerMapper;
+
+    @Override
+    public IPage<DeviceArea> queryPage(Page page, DeviceArea deviceArea) {
+        return deviceAreaMapper.queryPage(page, deviceArea);
+    }
+
+    @Override
+    public List<DeviceAreaTreeDto> listTree() {
+        return buildTree(Collections.emptyMap());
+    }
+
+    @Override
+    public List<DeviceAreaTreeDto> listTreeWithDevices() {
+        LambdaQueryWrapper<DeviceLedger> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.isNotNull(DeviceLedger::getAreaId);
+        queryWrapper.orderByAsc(DeviceLedger::getId);
+        List<DeviceLedger> deviceLedgers = deviceLedgerMapper.selectList(queryWrapper);
+
+        Map<Long, List<DeviceLedger>> deviceMap = new HashMap<>();
+        for (DeviceLedger deviceLedger : deviceLedgers) {
+            deviceMap.computeIfAbsent(deviceLedger.getAreaId(), key -> new ArrayList<>()).add(deviceLedger);
+        }
+        return buildTree(deviceMap);
+    }
+
+    @Override
+    public AjaxResult saveDeviceArea(DeviceArea deviceArea) {
+        if (deviceArea == null || !StringUtils.hasText(deviceArea.getAreaName())) {
+            return AjaxResult.error("鍖哄煙鍚嶇О涓嶈兘涓虹┖");
+        }
+        normalizeDeviceArea(deviceArea);
+        if (existsSameName(deviceArea.getParentId(), deviceArea.getAreaName(), null)) {
+            return AjaxResult.error("鍚岀骇鍖哄煙鍚嶇О宸插瓨鍦�");
+        }
+        if (deviceArea.getParentId() != null && deviceAreaMapper.selectById(deviceArea.getParentId()) == null) {
+            return AjaxResult.error("鐖剁骇鍖哄煙涓嶅瓨鍦�");
+        }
+        return this.save(deviceArea) ? AjaxResult.success() : AjaxResult.error();
+    }
+
+    @Override
+    public AjaxResult updateDeviceArea(DeviceArea deviceArea) {
+        if (deviceArea == null || deviceArea.getId() == null) {
+            return AjaxResult.error("鍖哄煙ID涓嶈兘涓虹┖");
+        }
+        if (!StringUtils.hasText(deviceArea.getAreaName())) {
+            return AjaxResult.error("鍖哄煙鍚嶇О涓嶈兘涓虹┖");
+        }
+        normalizeDeviceArea(deviceArea);
+        DeviceArea current = deviceAreaMapper.selectById(deviceArea.getId());
+        if (current == null) {
+            return AjaxResult.error("鍖哄煙涓嶅瓨鍦�");
+        }
+        if (deviceArea.getParentId() != null && deviceArea.getId().equals(deviceArea.getParentId())) {
+            return AjaxResult.error("鐖剁骇鍖哄煙涓嶈兘閫夋嫨鑷韩");
+        }
+        if (isDescendant(deviceArea.getId(), deviceArea.getParentId())) {
+            return AjaxResult.error("鐖剁骇鍖哄煙涓嶈兘閫夋嫨瀛愯妭鐐�");
+        }
+        if (deviceArea.getParentId() != null && deviceAreaMapper.selectById(deviceArea.getParentId()) == null) {
+            return AjaxResult.error("鐖剁骇鍖哄煙涓嶅瓨鍦�");
+        }
+        if (existsSameName(deviceArea.getParentId(), deviceArea.getAreaName(), deviceArea.getId())) {
+            return AjaxResult.error("鍚岀骇鍖哄煙鍚嶇О宸插瓨鍦�");
+        }
+        return this.updateById(deviceArea) ? AjaxResult.success() : AjaxResult.error();
+    }
+
+    @Override
+    public AjaxResult removeDeviceAreas(List<Long> ids) {
+        if (CollectionUtils.isEmpty(ids)) {
+            return AjaxResult.error("璇烽�夋嫨瑕佸垹闄ょ殑鍖哄煙");
+        }
+        LambdaQueryWrapper<DeviceArea> childWrapper = new LambdaQueryWrapper<>();
+        childWrapper.in(DeviceArea::getParentId, ids);
+        if (deviceAreaMapper.selectCount(childWrapper) > 0) {
+            return AjaxResult.error("瀛樺湪瀛愬尯鍩燂紝涓嶈兘鐩存帴鍒犻櫎");
+        }
+        return this.removeBatchByIds(ids) ? AjaxResult.success() : AjaxResult.error();
+    }
+
+    private List<DeviceAreaTreeDto> buildTree(Map<Long, List<DeviceLedger>> deviceMap) {
+        LambdaQueryWrapper<DeviceArea> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.and(wrapper -> wrapper.isNull(DeviceArea::getParentId).or().eq(DeviceArea::getParentId, 0L));
+        queryWrapper.orderByAsc(DeviceArea::getSort).orderByAsc(DeviceArea::getId);
+        List<DeviceArea> rootList = deviceAreaMapper.selectList(queryWrapper);
+
+        List<DeviceAreaTreeDto> tree = new ArrayList<>();
+        for (DeviceArea deviceArea : rootList) {
+            DeviceAreaTreeDto node = toTreeDto(deviceArea, deviceMap);
+            node.setChildren(buildChildren(deviceArea.getId(), deviceMap));
+            tree.add(node);
+        }
+        return tree;
+    }
+
+    private void normalizeDeviceArea(DeviceArea deviceArea) {
+        if (deviceArea.getParentId() != null && deviceArea.getParentId() == 0L) {
+            deviceArea.setParentId(null);
+        }
+        if (deviceArea.getSort() == null) {
+            deviceArea.setSort(0L);
+        }
+    }
+
+    private boolean existsSameName(Long parentId, String areaName, Long excludeId) {
+        LambdaQueryWrapper<DeviceArea> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(DeviceArea::getAreaName, areaName);
+        if (parentId == null) {
+            queryWrapper.and(wrapper -> wrapper.isNull(DeviceArea::getParentId).or().eq(DeviceArea::getParentId, 0L));
+        } else {
+            queryWrapper.eq(DeviceArea::getParentId, parentId);
+        }
+        if (excludeId != null) {
+            queryWrapper.ne(DeviceArea::getId, excludeId);
+        }
+        return deviceAreaMapper.selectCount(queryWrapper) > 0;
+    }
+
+    private boolean isDescendant(Long currentId, Long parentId) {
+        if (currentId == null || parentId == null) {
+            return false;
+        }
+        Long cursor = parentId;
+        while (cursor != null && cursor != 0L) {
+            if (currentId.equals(cursor)) {
+                return true;
+            }
+            DeviceArea parent = deviceAreaMapper.selectById(cursor);
+            if (parent == null) {
+                return false;
+            }
+            cursor = parent.getParentId();
+        }
+        return false;
+    }
+
+    private List<DeviceAreaTreeDto> buildChildren(Long parentId, Map<Long, List<DeviceLedger>> deviceMap) {
+        LambdaQueryWrapper<DeviceArea> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(DeviceArea::getParentId, parentId);
+        queryWrapper.orderByAsc(DeviceArea::getSort).orderByAsc(DeviceArea::getId);
+        List<DeviceArea> children = deviceAreaMapper.selectList(queryWrapper);
+
+        List<DeviceAreaTreeDto> result = new ArrayList<>();
+        for (DeviceArea child : children) {
+            DeviceAreaTreeDto node = toTreeDto(child, deviceMap);
+            node.setChildren(buildChildren(child.getId(), deviceMap));
+            result.add(node);
+        }
+        return result;
+    }
+
+    private DeviceAreaTreeDto toTreeDto(DeviceArea deviceArea, Map<Long, List<DeviceLedger>> deviceMap) {
+        DeviceAreaTreeDto dto = new DeviceAreaTreeDto();
+        BeanUtils.copyProperties(deviceArea, dto);
+        dto.setLabel(deviceArea.getAreaName());
+        dto.setDeviceList(new ArrayList<>(deviceMap.getOrDefault(deviceArea.getId(), Collections.emptyList())));
+        dto.setChildren(new ArrayList<>());
+        return dto;
+    }
+}
diff --git a/src/main/java/com/ruoyi/device/service/impl/DeviceMaintenanceServiceImpl.java b/src/main/java/com/ruoyi/device/service/impl/DeviceMaintenanceServiceImpl.java
index fbf03ad..d1a9821 100644
--- a/src/main/java/com/ruoyi/device/service/impl/DeviceMaintenanceServiceImpl.java
+++ b/src/main/java/com/ruoyi/device/service/impl/DeviceMaintenanceServiceImpl.java
@@ -11,7 +11,9 @@
 import com.ruoyi.device.dto.DeviceMaintenanceDto;
 import com.ruoyi.device.execl.DeviceMaintenanceExeclDto;
 import com.ruoyi.device.mapper.DeviceMaintenanceMapper;
+import com.ruoyi.device.pojo.DeviceLedger;
 import com.ruoyi.device.pojo.DeviceMaintenance;
+import com.ruoyi.device.service.IDeviceLedgerService;
 import com.ruoyi.device.service.IDeviceMaintenanceService;
 import com.ruoyi.device.vo.DeviceMaintenanceVo;
 import com.ruoyi.device.vo.DeviceRepairVo;
@@ -36,6 +38,7 @@
 public class DeviceMaintenanceServiceImpl extends ServiceImpl<DeviceMaintenanceMapper, DeviceMaintenance> implements IDeviceMaintenanceService {
 
     private final DeviceMaintenanceMapper deviceMaintenanceMapper;
+    private final IDeviceLedgerService deviceLedgerService;
     private final SparePartsMapper sparePartsMapper;
     private final SparePartsRequisitionRecordService sparePartsRequisitionRecordService;
     private final FileUtil fileUtil;
@@ -49,6 +52,12 @@
     @Override
     @Transactional(rollbackFor = Exception.class)
     public AjaxResult saveDeviceRepair(DeviceMaintenanceDto deviceMaintenance) {
+        DeviceLedger byId = deviceLedgerService.getById(deviceMaintenance.getDeviceLedgerId());
+        if (byId != null) {
+            deviceMaintenance.setDeviceName(byId.getDeviceName());
+            deviceMaintenance.setDeviceModel(byId.getDeviceModel());
+            deviceMaintenance.setAreaId(byId.getAreaId());
+        }
         boolean save = this.save(deviceMaintenance);
         if (save){
             // 澶勭悊鍥剧墖涓婁紶
@@ -62,6 +71,15 @@
     @Transactional(rollbackFor = Exception.class)
     public AjaxResult updateDeviceDeviceMaintenance(DeviceMaintenanceDto deviceMaintenance) {
         DeviceMaintenance oldDeviceMaintenance = this.getById(deviceMaintenance.getId());
+        Long effectiveDeviceLedgerId = deviceMaintenance.getDeviceLedgerId() != null
+                ? deviceMaintenance.getDeviceLedgerId()
+                : oldDeviceMaintenance.getDeviceLedgerId();
+        DeviceLedger byId = deviceLedgerService.getById(effectiveDeviceLedgerId);
+        if (byId != null) {
+            deviceMaintenance.setDeviceName(byId.getDeviceName());
+            deviceMaintenance.setDeviceModel(byId.getDeviceModel());
+            deviceMaintenance.setAreaId(byId.getAreaId());
+        }
         // 澶勭悊澶囦欢浣跨敤鎯呭喌
         if (com.github.xiaoymin.knife4j.core.util.CollectionUtils.isNotEmpty(deviceMaintenance.getSparePartsUseList())) {
             List<Long> sparePartIds = new ArrayList<>();
diff --git a/src/main/java/com/ruoyi/device/service/impl/DeviceRepairServiceImpl.java b/src/main/java/com/ruoyi/device/service/impl/DeviceRepairServiceImpl.java
index e5a73ac..9a3c383 100644
--- a/src/main/java/com/ruoyi/device/service/impl/DeviceRepairServiceImpl.java
+++ b/src/main/java/com/ruoyi/device/service/impl/DeviceRepairServiceImpl.java
@@ -67,6 +67,7 @@
         DeviceLedger byId = deviceLedgerService.getById(deviceRepairDto.getDeviceLedgerId());
         deviceRepairDto.setDeviceName(byId.getDeviceName());
         deviceRepairDto.setDeviceModel(byId.getDeviceModel());
+        deviceRepairDto.setAreaId(byId.getAreaId());
         if (deviceRepairDto.getStatus() == null) {
             deviceRepairDto.setStatus(STATUS_PENDING_REPAIR);
         }
@@ -86,6 +87,15 @@
         if (oldDeviceRepair == null) {
             return AjaxResult.error("鎶ヤ慨璁板綍涓嶅瓨鍦�");
         }
+        Long effectiveDeviceLedgerId = deviceRepairDto.getDeviceLedgerId() != null
+                ? deviceRepairDto.getDeviceLedgerId()
+                : oldDeviceRepair.getDeviceLedgerId();
+        DeviceLedger byId = deviceLedgerService.getById(effectiveDeviceLedgerId);
+        if (byId != null) {
+            deviceRepairDto.setDeviceName(byId.getDeviceName());
+            deviceRepairDto.setDeviceModel(byId.getDeviceModel());
+            deviceRepairDto.setAreaId(byId.getAreaId());
+        }
         if (deviceRepairDto.getStatus() != null
                 && deviceRepairDto.getStatus() == STATUS_COMPLETED
                 && (oldDeviceRepair.getStatus() == null
diff --git a/src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskServiceImpl.java b/src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskServiceImpl.java
index d0ba21f..1450a35 100644
--- a/src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskServiceImpl.java
+++ b/src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskServiceImpl.java
@@ -4,7 +4,11 @@
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.ruoyi.common.utils.bean.BeanUtils;
+import com.ruoyi.device.mapper.DeviceAreaMapper;
+import com.ruoyi.device.mapper.DeviceLedgerMapper;
 import com.ruoyi.device.mapper.MaintenanceTaskMapper;
+import com.ruoyi.device.pojo.DeviceArea;
+import com.ruoyi.device.pojo.DeviceLedger;
 import com.ruoyi.device.pojo.MaintenanceTask;
 import com.ruoyi.device.service.MaintenanceTaskService;
 import com.ruoyi.framework.web.domain.AjaxResult;
@@ -32,10 +36,16 @@
     private final SysUserMapper sysUserMapper;
     private final TimingTaskServiceImpl timingTaskService;
     private final MaintenanceTaskScheduler maintenanceTaskScheduler;
+    private final DeviceLedgerMapper deviceLedgerMapper;
+    private final DeviceAreaMapper deviceAreaMapper;
 
     @Override
     public AjaxResult listPage(Page page, MaintenanceTask maintenanceTask) {
-        Page<MaintenanceTask> taskPage = maintenanceTaskMapper.selectPage(page, new QueryWrapper<MaintenanceTask>().orderByDesc("create_time"));
+        QueryWrapper<MaintenanceTask> queryWrapper = new QueryWrapper<MaintenanceTask>().orderByDesc("create_time");
+        if (maintenanceTask.getAreaId() != null) {
+            queryWrapper.eq("area_id", maintenanceTask.getAreaId());
+        }
+        Page<MaintenanceTask> taskPage = maintenanceTaskMapper.selectPage(page, queryWrapper);
         // 2. 濡傛灉娌℃湁鏁版嵁锛岀洿鎺ヨ繑鍥炵┖鍒嗛〉
         if (taskPage.getRecords().isEmpty()) {
             return AjaxResult.success(taskPage);
@@ -57,10 +67,33 @@
             List<SysUser> users = sysUserMapper.selectUserByIds((new ArrayList<>(userIds)));
             users.forEach(user -> userNickNameMap.put(user.getUserId(), user.getNickName()));
         }
+        Map<Long, DeviceLedger> ledgerMap = new HashMap<>();
+        Set<Long> areaIds = new HashSet<>();
         taskPage.getRecords().forEach(task -> {
-            // 璁剧疆鐧昏浜烘樀绉�
+            if (task.getTaskId() != null) {
+                DeviceLedger deviceLedger = deviceLedgerMapper.selectById(task.getTaskId());
+                if (deviceLedger != null) {
+                    ledgerMap.put(task.getTaskId(), deviceLedger);
+                    if (deviceLedger.getAreaId() != null) {
+                        areaIds.add(deviceLedger.getAreaId());
+                    }
+                }
+            }
+        });
+        Map<Long, String> areaNameMap = new HashMap<>();
+        if (!areaIds.isEmpty()) {
+            List<DeviceArea> areas = deviceAreaMapper.selectBatchIds(new ArrayList<>(areaIds));
+            areas.forEach(area -> areaNameMap.put(area.getId(), area.getAreaName()));
+        }
+        taskPage.getRecords().forEach(task -> {
+            // 鐠佸墽鐤嗛惂鏄忣唶娴滅儤妯�缁�?
             if (task.getRegistrantId() != null) {
-                task.setRegistrant(userNickNameMap.getOrDefault(task.getRegistrantId(), "鏈煡鐢ㄦ埛"));
+                task.setRegistrant(userNickNameMap.getOrDefault(task.getRegistrantId(), "閺堫亞鐓¢悽銊﹀煕"));
+            }
+            DeviceLedger deviceLedger = ledgerMap.get(task.getTaskId());
+            if (deviceLedger != null) {
+                task.setAreaId(deviceLedger.getAreaId());
+                task.setAreaName(areaNameMap.getOrDefault(deviceLedger.getAreaId(), ""));
             }
         });
         return AjaxResult.success(taskPage);
@@ -68,6 +101,10 @@
 
     @Override
     public AjaxResult add(MaintenanceTask maintenanceTask) {
+        DeviceLedger deviceLedger = deviceLedgerMapper.selectById(maintenanceTask.getTaskId());
+        if (deviceLedger != null) {
+            maintenanceTask.setAreaId(deviceLedger.getAreaId());
+        }
         maintenanceTask.setActive(true);
         // 璁$畻棣栨鎵ц鏃堕棿
         TimingTask task = new TimingTask();
@@ -85,6 +122,10 @@
     @Override
     public AjaxResult updateByMaintenanceTaskId(MaintenanceTask maintenanceTask) {
         MaintenanceTask maintenanceTask1 = maintenanceTaskMapper.selectById(maintenanceTask.getId());
+        DeviceLedger deviceLedger = deviceLedgerMapper.selectById(maintenanceTask.getTaskId());
+        if (deviceLedger != null) {
+            maintenanceTask.setAreaId(deviceLedger.getAreaId());
+        }
         if (maintenanceTask1 == null) {
             return AjaxResult.warn("娌℃湁姝ゆ暟鎹�");
         }
diff --git a/src/main/java/com/ruoyi/device/vo/DeviceMaintenanceVo.java b/src/main/java/com/ruoyi/device/vo/DeviceMaintenanceVo.java
index f71c5cb..06720c3 100644
--- a/src/main/java/com/ruoyi/device/vo/DeviceMaintenanceVo.java
+++ b/src/main/java/com/ruoyi/device/vo/DeviceMaintenanceVo.java
@@ -10,6 +10,8 @@
 
 @Data
 public class DeviceMaintenanceVo extends DeviceMaintenance {
+    @Schema(description = "璁惧鍖哄煙鍚嶇О")
+    private String areaName;
     @Schema(description = "璁惧淇濆吇id")
     private Long id;
 
diff --git a/src/main/java/com/ruoyi/device/vo/DeviceRepairVo.java b/src/main/java/com/ruoyi/device/vo/DeviceRepairVo.java
index e90c501..ce93b81 100644
--- a/src/main/java/com/ruoyi/device/vo/DeviceRepairVo.java
+++ b/src/main/java/com/ruoyi/device/vo/DeviceRepairVo.java
@@ -10,6 +10,9 @@
 @Data
 public class DeviceRepairVo extends DeviceRepair {
 
+    @Schema(description = "璁惧鍖哄煙鍚嶇О")
+    private String areaName;
+
     @Schema(description = "鎶ヤ慨鏃堕棿瀛楃涓�")
     private String repairTimeStr;
 
diff --git a/src/main/java/com/ruoyi/http/service/controller/JclyController.java b/src/main/java/com/ruoyi/http/service/controller/JclyController.java
new file mode 100644
index 0000000..00bbb74
--- /dev/null
+++ b/src/main/java/com/ruoyi/http/service/controller/JclyController.java
@@ -0,0 +1,79 @@
+package com.ruoyi.http.service.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.device.pojo.DeviceLedger;
+import com.ruoyi.device.service.IDeviceLedgerService;
+import com.ruoyi.framework.web.controller.BaseController;
+import com.ruoyi.framework.web.domain.AjaxResult;
+import com.ruoyi.http.service.impl.RealTimeEnergyConsumptionServiceImpl;
+import io.swagger.annotations.Api;
+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.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@RestController
+@RequestMapping("/iot")
+@Api(tags = "鏁伴噰鎺ュ彛")
+public class JclyController extends BaseController {
+
+    @Autowired
+    private RealTimeEnergyConsumptionServiceImpl realTimeEnergyConsumptionService;
+
+    @Autowired
+    private IDeviceLedgerService deviceLedgerService;
+
+    /**
+     * 瀹炴椂鑾峰彇娓╂箍搴︼紝浜屾哀鍖栫⒊鏁版嵁
+     */
+    @GetMapping("/getRealData")
+    public AjaxResult getRealData() {
+        List<DeviceLedger> iotDevices = deviceLedgerService.list(new LambdaQueryWrapper<DeviceLedger>()
+                .eq(DeviceLedger::getIsIotDevice, 1)
+                .isNotNull(DeviceLedger::getExternalCode)
+                .ne(DeviceLedger::getExternalCode, ""));
+
+        Map<String, String> guidDeviceNameMap = iotDevices.stream()
+                .filter(item -> StringUtils.isNotEmpty(item.getExternalCode()))
+                .collect(Collectors.toMap(
+                        item -> item.getExternalCode().trim(),
+                        item -> StringUtils.isNotEmpty(item.getDeviceName()) ? item.getDeviceName().trim() : "",
+                        (oldValue, newValue) -> StringUtils.isNotEmpty(oldValue) ? oldValue : newValue,
+                        LinkedHashMap::new
+                ));
+        List<String> guidList = new ArrayList<>(guidDeviceNameMap.keySet());
+
+        List<Map<String, String>> maps = realTimeEnergyConsumptionService
+                .getRealData(guidList);
+        for (Map<String, String> item : maps) {
+            String guid = item.get("guid");
+            if (StringUtils.isNotEmpty(guid)) {
+                String deviceName = guidDeviceNameMap.get(guid.trim());
+                if (StringUtils.isNotEmpty(deviceName)) {
+                    item.put("deviceName", deviceName);
+                }
+            }
+        }
+        return AjaxResult.success(maps);
+    }
+
+    /**
+     * 鑾峰彇鍘嗗彶鏁版嵁
+     */
+    @GetMapping("/getHistoryData")
+    public AjaxResult getHistoryData(@RequestParam(value = "guid") String guid,
+                                     @RequestParam(value = "startTime") long startTime,
+                                     @RequestParam(value = "endTime") long endTime) {
+        List<Map<String, String>> maps = realTimeEnergyConsumptionService.getHistoryData(guid, startTime, endTime);
+        return AjaxResult.success(maps);
+    }
+
+}
diff --git a/src/main/java/com/ruoyi/http/service/impl/RealTimeEnergyConsumptionServiceImpl.java b/src/main/java/com/ruoyi/http/service/impl/RealTimeEnergyConsumptionServiceImpl.java
index 6b10ba2..9cab13f 100644
--- a/src/main/java/com/ruoyi/http/service/impl/RealTimeEnergyConsumptionServiceImpl.java
+++ b/src/main/java/com/ruoyi/http/service/impl/RealTimeEnergyConsumptionServiceImpl.java
@@ -3,15 +3,16 @@
 import com.alibaba.fastjson2.JSON;
 import com.alibaba.fastjson2.JSONArray;
 import com.alibaba.fastjson2.JSONObject;
+import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.common.utils.http.HttpUtils;
 import com.ruoyi.http.service.RealTimeEnergyConsumptionService;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Service;
 
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
 
 /**
  * @author :yys
@@ -21,110 +22,438 @@
 @Slf4j
 public class RealTimeEnergyConsumptionServiceImpl implements RealTimeEnergyConsumptionService {
 
+    private static final long REMOTE_CACHE_TTL_SECONDS_30 = 30L;
+
     private static final String URL = "https://new.e-elitech.cn/api/data-api";
 
     private static final String TOKEN_URL = "/elitechAccess/getToken";
 
-    private static final String REAL_TIME_URL = "/elitechAccess/v2/getRealTimeData"; //鑾峰彇璁惧瀹炴椂鏁版嵁
+    private static final String REAL_TIME_URL = "/elitechAccess/v2/getRealTimeData";
+
+    private static final String REAL_HISTORY_URL = URL + "/elitechAccess/v2/getHistoryData";
 
     private static final String KET_ID = "75804708";
 
     private static final String KEY_SECRET = "xTUGToozKpYgUPqTsZzB";
 
-    private static final String USER_NAME = "鐢ㄦ埛30773662";
+    private static final String USER_NAME = "\u7528\u623730773662";
 
     private static final String PASS_WORD = "y17775163675";
 
-    private static final String DEVICE_GUID = "90444196515214284663";
+    private static final String REAL_TIME_CACHE_PREFIX = "JCLY:REAL_TIME:";
 
+    private static final String HISTORY_CACHE_PREFIX = "JCLY:HISTORY:";
 
-    /**
-     * 鏍规嵁paramCode鎻愬彇鎺㈠ご鍙傛暟
-     * @param paramList 璁惧鍙傛暟鏁扮粍
-     * @param targetCode 鐩爣鎺㈠ご缂栫爜
-     * @return 鎺㈠ご鍙傛暟瀵硅薄锛堝寘鍚玭ame/value/unit锛�
-     */
+    private static final String TOKEN_CACHE_KEY = "JCLY_TOKEN:";
+
+    private static final String STATUS_KEY = "status";
+
+    private static final String STATUS_MESSAGE_KEY = "statusMessage";
+
+    private static final String STATUS_ONLINE = "鍦ㄧ嚎";
+
+    private static final String STATUS_OFFLINE = "offline";
+
+    private static final String STATUS_ERROR = "error";
+
+    @Autowired
+    private RedisTemplate<String, String> redisTemplate;
+
     private static JSONObject getProbeParam(JSONArray paramList, String targetCode) {
+        if (paramList == null) {
+            return new JSONObject();
+        }
         for (int i = 0; i < paramList.size(); i++) {
             JSONObject paramObj = paramList.getJSONObject(i);
             if (targetCode.equals(paramObj.getString("paramCode"))) {
                 return paramObj;
             }
         }
-        return new JSONObject(); // 鏈尮閰嶅埌杩斿洖绌哄璞★紝閬垮厤绌烘寚閽�
+        return new JSONObject();
     }
 
-    /**
-     * 瀹炴椂鑾峰彇娓╂箍搴︼紝浜屾哀鍖栫⒊鏁版嵁
-     */
-    public static void main(String[] args) {
-        String realTimeData = getRealTimeData(getToken());
-        Map<String, Object> map = JSON.parseObject(realTimeData, Map.class);
-        if(map.get("code").equals(0)){
-            // 1. 瑙f瀽澶栧眰data涓篔SON鏁扮粍锛堟帴鍙h繑鍥炵殑璁惧鍒楄〃锛�
-            JSONArray deviceList = JSON.parseArray(map.get("data").toString());
-            // 2. 閬嶅巻璁惧鍒楄〃锛堟澶勪粎鍙栫涓�涓澶囷紝鑻ユ湁澶氫釜璁惧鍙惊鐜鐞嗭級
-            if (!deviceList.isEmpty()) {
-                JSONObject deviceObj = deviceList.getJSONObject(0);
-                // 3. 瑙f瀽璁惧鍐呯殑鍙傛暟鏁扮粍锛堟墍鏈塸aramCode瀵瑰簲鐨勫弬鏁帮級
-                JSONArray paramList = deviceObj.getJSONArray("data");
+    public List<Map<String, String>> getHistoryData(String guid, long startTime, long endTime) {
+        List<Map<String, String>> resultList = new ArrayList<>();
+        String token = getToken();
+        try {
+            String historyData = requestHistoryData(token, guid, startTime, endTime);
+            JSONObject resultObj = JSON.parseObject(historyData);
+            if (resultObj == null) {
+                resultList.add(buildStatusItem(guid, STATUS_ERROR, "history response is empty"));
+                return resultList;
+            }
 
-                // 4. 瀹氫箟鐩爣鎺㈠ご鐨刾aramCode锛屾寜闇�鎵╁睍
-                String[] targetCodes = {"0100", "0110", "0120", "0130"};
-                for (String code : targetCodes) {
-                    // 5. 閬嶅巻鍙傛暟鏁扮粍锛屽尮閰嶇洰鏍噋aramCode
-                    for (int i = 0; i < paramList.size(); i++) {
-                        JSONObject paramObj = paramList.getJSONObject(i);
-                        String currentCode = paramObj.getString("paramCode");
-                        if (code.equals(currentCode)) {
-                            // 6. 鎻愬彇鏍稿績瀛楁锛堝�笺�佸崟浣嶃�佹帰澶村悕绉帮級
-                            String paramName = paramObj.getString("paramName"); // 鎺㈠ご1/鎺㈠ご2...
-                            String value = paramObj.getString("value");       // 鏁板�硷紙345.80/24.90...锛�
-                            String unitCode = paramObj.getString("unitCode"); // 鍗曚綅锛圠ux/鈩�/%RH/ppm锛�
+            Integer code = resultObj.getInteger("code");
+            if (!Integer.valueOf(0).equals(code)) {
+                resultList.add(buildStatusItem(guid, resolveStatusByCodeOrMessage(code, resultObj.getString("msg")), resultObj.getString("msg")));
+                return resultList;
+            }
 
-                            // 7. 涓氬姟澶勭悊锛氭墦鍗�/璧嬪��/瀛樺偍绛夛紙鎸夐渶淇敼锛�
-                            System.out.println(paramName + "锛�" + value + " " + unitCode);
-                            // 鍖归厤鍒板悗鐩存帴璺冲嚭鍐呭眰寰幆锛屾彁鍗囨晥鐜�
-                            break;
+            JSONArray historyList = resultObj.getJSONArray("data");
+            if (historyList == null || historyList.isEmpty()) {
+                resultList.add(buildStatusItem(guid, STATUS_OFFLINE, "no history data"));
+                return resultList;
+            }
+
+            for (int i = 0; i < historyList.size(); i++) {
+                JSONObject historyObj = historyList.getJSONObject(i);
+                Map<String, String> historyItem = new HashMap<>();
+                historyItem.put("guid", firstNonBlank(
+                        historyObj.getString("deviceGuid"),
+                        historyObj.getString("guid"),
+                        guid
+                ));
+                historyItem.put("subUId", stringValue(historyObj.get("subUid")));
+                historyItem.put("monitorTimeStamp", stringValue(historyObj.get("monitorTimeStamp")));
+                historyItem.put("monitorTimeStr", historyObj.getString("monitorTimeStr"));
+                historyItem.put("position", historyObj.getString("position"));
+                historyItem.put("address", historyObj.getString("address"));
+                historyItem.put(STATUS_KEY, resolveStatusByAlarmState(historyObj.get("alarmState"), STATUS_ONLINE));
+
+                JSONArray paramList = historyObj.getJSONArray("data");
+                if (paramList != null && !paramList.isEmpty()) {
+                    for (int j = 0; j < paramList.size(); j++) {
+                        JSONObject paramObj = paramList.getJSONObject(j);
+                        String paramCode = paramObj.getString("paramCode");
+                        String value = paramObj.getString("value");
+                        String unitCode = paramObj.getString("unitCode");
+                        String fullValue = concatValueWithUnit(value, unitCode);
+                        if ("0100".equals(paramCode)) {
+                            historyItem.put("light", fullValue);
+                        } else if ("0110".equals(paramCode)) {
+                            historyItem.put("temperature", fullValue);
+                        } else if ("0120".equals(paramCode)) {
+                            historyItem.put("humidity", fullValue);
+                        } else if ("0130".equals(paramCode)) {
+                            historyItem.put("co2", fullValue);
+                        } else if ("0042".equals(paramCode)) {
+                            historyItem.put("battery", fullValue);
+                        } else if (paramCode != null) {
+                            historyItem.put(paramCode, fullValue);
                         }
                     }
                 }
+                resultList.add(historyItem);
             }
+            return resultList;
+        } catch (Exception ex) {
+            log.error("history data parse/request failed, guid={}", guid, ex);
+            resultList.add(buildStatusItem(guid, STATUS_ERROR, ex.getMessage()));
+            return resultList;
         }
+    }
+
+    public List<Map<String, String>> getRealData(List<String> guidList) {
+        log.info("start get real data");
+        List<Map<String, String>> listMaps = new ArrayList<>();
+        if (guidList == null || guidList.isEmpty()) {
+            return listMaps;
+        }
+
+        String token = getToken();
+        try {
+            String realTimeData = getRealTimeData(token, guidList);
+            JSONObject batchResp = JSON.parseObject(realTimeData);
+            Integer code = batchResp == null ? null : batchResp.getInteger("code");
+            if (Integer.valueOf(0).equals(code)) {
+                parseRealDataResponse(batchResp, guidList, listMaps);
+                return listMaps;
+            }
+            log.warn("batch getRealData failed, fallback one by one. code={}, msg={}", code, batchResp == null ? null : batchResp.getString("msg"));
+        } catch (Exception ex) {
+            log.error("batch getRealData exception, fallback one by one", ex);
+        }
+
+        for (String guid : guidList) {
+            listMaps.add(fetchSingleDeviceRealData(token, guid));
+        }
+        return listMaps;
+    }
+
+    public static void main(String[] args) {
         System.out.println();
     }
 
-    public static String getToken(){
+    public String getToken() {
+        String cachedToken = sanitizeToken(redisTemplate.opsForValue().get(TOKEN_CACHE_KEY));
+        if (cachedToken != null) {
+            return cachedToken;
+        }
         Map<String, String> param = new HashMap<>();
         param.put("keyId", KET_ID);
         param.put("keySecret", KEY_SECRET);
         param.put("userName", USER_NAME);
         param.put("password", PASS_WORD);
-        log.info("璇锋眰鍙傛暟锛歿}", JSON.toJSONString( param));
+        log.info("request token payload: {}", JSON.toJSONString(param));
         String result = HttpUtils.sendPostJson(URL + TOKEN_URL, JSON.toJSONString(param));
-        log.info("杩斿洖缁撴灉锛歿}", result);
+        log.info("request token response: {}", result);
         Map<String, Object> map = JSON.parseObject(result, Map.class);
-        if (map.get("code").equals(0)) {
+        if (Integer.valueOf(0).equals(map.get("code"))) {
             Object token = map.get("data");
             log.info("token:{}", token);
+            redisTemplate.opsForValue().set(TOKEN_CACHE_KEY, token.toString(), 60 * 60 * 12);
             return token.toString();
+        }
+        log.error("get token failed, response={}", result);
+        return null;
+    }
+
+    private String sanitizeToken(String token) {
+        if (token == null) {
+            return null;
+        }
+        String cleanedToken = token.replace("\0", "").trim();
+        return cleanedToken.isEmpty() ? null : cleanedToken;
+    }
+
+    private String firstNonBlank(String... values) {
+        for (String value : values) {
+            if (value != null && !value.trim().isEmpty()) {
+                return value;
+            }
+        }
+        return null;
+    }
+
+    private String stringValue(Object value) {
+        return value == null ? null : String.valueOf(value);
+    }
+
+    private String refreshToken() {
+        redisTemplate.delete(TOKEN_CACHE_KEY);
+        return getToken();
+    }
+
+    private boolean isUnauthorizedException(Exception ex) {
+        if (ex == null || ex.getMessage() == null) {
+            return false;
+        }
+        String msg = ex.getMessage().toLowerCase();
+        return msg.contains("401") || msg.contains("unauthorized");
+    }
+
+    private boolean isUnauthorizedResponse(String result) {
+        if (result == null || result.trim().isEmpty()) {
+            return false;
+        }
+        try {
+            JSONObject obj = JSON.parseObject(result);
+            if (obj == null) {
+                return false;
+            }
+            Integer code = obj.getInteger("code");
+            if (code != null && (code == 401 || code == 403)) {
+                return true;
+            }
+            String msg = obj.getString("msg");
+            return msg != null && msg.toLowerCase().contains("unauthorized");
+        } catch (Exception ignore) {
+            return result.contains("401");
+        }
+    }
+
+    private String requestWithTokenRetry(String url, String payload, String token, String scene) {
+        String usedToken = sanitizeToken(token);
+        if (usedToken == null) {
+            usedToken = refreshToken();
+        }
+        try {
+            String result = HttpUtils.sendPostJson(url, payload, usedToken);
+            if (!isUnauthorizedResponse(result) && StringUtils.isNotEmpty(result)) {
+                return result;
+            }
+            log.warn("{} got unauthorized response, refresh token and retry once", scene);
+        } catch (Exception ex) {
+            if (!isUnauthorizedException(ex)) {
+                throw ex;
+            }
+            log.warn("{} got 401 exception, refresh token and retry once", scene);
+        }
+
+        String newToken = refreshToken();
+        if (newToken == null) {
+            throw new RuntimeException("refresh token failed");
+        }
+        return HttpUtils.sendPostJson(url, payload, newToken);
+    }
+
+    private String concatValueWithUnit(String value, String unitCode) {
+        if (value == null) {
+            return null;
+        }
+        return value + (unitCode == null ? "" : unitCode);
+    }
+
+    private Map<String, String> buildStatusItem(String guid, String status, String statusMessage) {
+        Map<String, String> result = new HashMap<>();
+        result.put("guid", guid);
+        result.put(STATUS_KEY, status);
+        if (statusMessage != null && !statusMessage.trim().isEmpty()) {
+            result.put(STATUS_MESSAGE_KEY, statusMessage);
         }
         return result;
     }
 
-    public static String getRealTimeData(String  token){
+    private String resolveStatusByCodeOrMessage(Integer code, String message) {
+        if (code != null && (code == 5 || code == 1003 || code == 1004)) {
+            return STATUS_OFFLINE;
+        }
+        if (message == null) {
+            return STATUS_ERROR;
+        }
+        String lowered = message.toLowerCase();
+        if (lowered.contains("offline") || message.contains("绂荤嚎")) {
+            return STATUS_OFFLINE;
+        }
+        return STATUS_ERROR;
+    }
+
+    private void parseRealDataResponse(JSONObject responseObj, List<String> requestedGuids, List<Map<String, String>> output) {
+        JSONArray deviceList = responseObj.getJSONArray("data");
+        Set<String> returnedGuids = new HashSet<>();
+        if (deviceList != null) {
+            for (int deviceIndex = 0; deviceIndex < deviceList.size(); deviceIndex++) {
+                JSONObject deviceObj = deviceList.getJSONObject(deviceIndex);
+                Map<String, String> deviceData = parseSingleDevice(deviceObj, null);
+                String guid = deviceData.get("guid");
+                if (guid != null) {
+                    returnedGuids.add(guid);
+                }
+                output.add(deviceData);
+            }
+        }
+
+        for (String requestGuid : requestedGuids) {
+            if (!returnedGuids.contains(requestGuid)) {
+                output.add(buildStatusItem(requestGuid, STATUS_OFFLINE, "device not returned by remote API"));
+            }
+        }
+    }
+
+    private Map<String, String> fetchSingleDeviceRealData(String token, String guid) {
+        try {
+            String singleResult = getRealTimeData(token, Collections.singletonList(guid));
+            JSONObject singleObj = JSON.parseObject(singleResult);
+            if (singleObj == null) {
+                return buildStatusItem(guid, STATUS_ERROR, "single device response is empty");
+            }
+            Integer code = singleObj.getInteger("code");
+            if (!Integer.valueOf(0).equals(code)) {
+                return buildStatusItem(guid, resolveStatusByCodeOrMessage(code, singleObj.getString("msg")), singleObj.getString("msg"));
+            }
+
+            JSONArray dataList = singleObj.getJSONArray("data");
+            if (dataList == null || dataList.isEmpty()) {
+                return buildStatusItem(guid, STATUS_OFFLINE, "single device response has no data");
+            }
+            return parseSingleDevice(dataList.getJSONObject(0), guid);
+        } catch (Exception ex) {
+            log.error("single getRealData failed, guid={}", guid, ex);
+            return buildStatusItem(guid, STATUS_ERROR, ex.getMessage());
+        }
+    }
+
+    private Map<String, String> parseSingleDevice(JSONObject deviceObj, String fallbackGuid) {
+        Map<String, String> deviceData = new HashMap<>();
+        String deviceGuid = firstNonBlank(
+                deviceObj.getString("deviceGuid"),
+                deviceObj.getString("guid"),
+                deviceObj.getString("devGuid"),
+                deviceObj.getString("sn"),
+                fallbackGuid
+        );
+        if (deviceGuid != null) {
+            deviceData.put("guid", deviceGuid);
+        }
+
+        JSONArray paramList = deviceObj.getJSONArray("data");
+        if (paramList == null || paramList.isEmpty()) {
+            deviceData.put(STATUS_KEY, STATUS_OFFLINE);
+            deviceData.put(STATUS_MESSAGE_KEY, "device data is empty");
+            return deviceData;
+        }
+
+        deviceData.put(STATUS_KEY, resolveStatusByAlarmState(deviceObj.get("alarmState"), STATUS_ONLINE));
+        for (int i = 0; i < paramList.size(); i++) {
+            JSONObject paramObj = paramList.getJSONObject(i);
+            String code = paramObj.getString("paramCode");
+            String value = paramObj.getString("value");
+            String unitCode = paramObj.getString("unitCode");
+            String fullValue = concatValueWithUnit(value, unitCode);
+            if ("0100".equals(code)) {
+                deviceData.put("light", fullValue);
+            } else if ("0110".equals(code)) {
+                deviceData.put("temperature", fullValue);
+            } else if ("0120".equals(code)) {
+                deviceData.put("humidity", fullValue);
+            } else if ("0130".equals(code)) {
+                deviceData.put("co2", fullValue);
+            } else if ("0042".equals(code)) {
+                deviceData.put("battery", fullValue);
+            }
+        }
+        return deviceData;
+    }
+
+    private String resolveStatusByAlarmState(Object alarmState, String defaultStatus) {
+        if (alarmState == null) {
+            return defaultStatus;
+        }
+        if (alarmState instanceof Boolean) {
+            return (Boolean) alarmState ? STATUS_OFFLINE : STATUS_ONLINE;
+        }
+        String normalized = String.valueOf(alarmState).trim().toLowerCase();
+        if ("true".equals(normalized) || "1".equals(normalized)) {
+            return STATUS_OFFLINE;
+        }
+        if ("false".equals(normalized) || "0".equals(normalized)) {
+            return STATUS_ONLINE;
+        }
+        return defaultStatus;
+    }
+
+    public String getRealTimeData(String token, List<String> guidList) {
         Map<String, Object> param = new HashMap<>();
         param.put("keyId", KET_ID);
         param.put("keySecret", KEY_SECRET);
-        param.put("deviceGuids", Collections.singletonList(DEVICE_GUID));
-        log.info("璇锋眰鍙傛暟锛歿}", JSON.toJSONString( param));
-        String result = HttpUtils.sendPostJson(URL + REAL_TIME_URL, JSON.toJSONString(param),token);
-        log.info("杩斿洖缁撴灉锛歿}", result);
+        param.put("deviceGuids", guidList);
+        log.info("request realtime payload: {}", JSON.toJSONString(param));
+        String cacheKey = REAL_TIME_CACHE_PREFIX + JSON.toJSONString(param);
+        String cachedResult = sanitizeToken(redisTemplate.opsForValue().get(cacheKey));
+        if (cachedResult != null) {
+            log.info("hit realtime cache: {}", cacheKey);
+            return cachedResult;
+        }
+        String result = requestWithTokenRetry(URL + REAL_TIME_URL, JSON.toJSONString(param), token, "getRealTimeData");
+        log.info("request realtime response: {}", result);
+        cacheRemoteResponse(cacheKey, result);
         return result;
     }
 
+    public String requestHistoryData(String token, String guid, long startTime, long endTime) {
+        Map<String, Object> param = new HashMap<>();
+        param.put("keyId", KET_ID);
+        param.put("keySecret", KEY_SECRET);
+        param.put("deviceGuid", guid);
+        param.put("startTime", startTime);
+        param.put("endTime", endTime);
+        log.info("request history payload: {}", JSON.toJSONString(param));
+        String cacheKey = HISTORY_CACHE_PREFIX + JSON.toJSONString(param);
+        String cachedResult = sanitizeToken(redisTemplate.opsForValue().get(cacheKey));
+        if (cachedResult != null) {
+            log.info("hit history cache: {}", cacheKey);
+            return cachedResult;
+        }
+        String result = requestWithTokenRetry(REAL_HISTORY_URL, JSON.toJSONString(param), token, "getHistoryData");
+        log.info("request history response: {}", result);
+        cacheRemoteResponse(cacheKey, result);
+        return result;
+    }
 
-
-
-
+    private void cacheRemoteResponse(String cacheKey, String result) {
+        if (result == null || result.trim().isEmpty()) {
+            return;
+        }
+        redisTemplate.opsForValue().set(cacheKey, result, REMOTE_CACHE_TTL_SECONDS_30, TimeUnit.SECONDS);
+    }
 }
diff --git a/src/main/java/com/ruoyi/inspectiontask/controller/TimingTaskController.java b/src/main/java/com/ruoyi/inspectiontask/controller/TimingTaskController.java
index ad2e338..7fc021c 100644
--- a/src/main/java/com/ruoyi/inspectiontask/controller/TimingTaskController.java
+++ b/src/main/java/com/ruoyi/inspectiontask/controller/TimingTaskController.java
@@ -7,8 +7,11 @@
 import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
 import com.ruoyi.framework.web.controller.BaseController;
 import com.ruoyi.framework.web.domain.R;
+import com.ruoyi.inspectiontask.dto.InspectionTaskDto;
 import com.ruoyi.inspectiontask.dto.TimingTaskDto;
+import com.ruoyi.inspectiontask.pojo.InspectionTask;
 import com.ruoyi.inspectiontask.pojo.TimingTask;
+import com.ruoyi.inspectiontask.service.InspectionTaskService;
 import com.ruoyi.inspectiontask.service.TimingTaskService;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import io.swagger.v3.oas.annotations.Operation;
@@ -28,6 +31,7 @@
 public class TimingTaskController extends BaseController {
 
     private TimingTaskService timingTaskService;
+    private InspectionTaskService inspectionTaskService;
 
     /**
      * 瀹氭椂宸℃浠诲姟琛ㄦ煡璇�
@@ -63,6 +67,26 @@
     }
 
     /**
+     * 淇敼鍚敤鐘舵��
+     */
+    @PostMapping("/changeEnable")
+    @Operation(summary = "鍚敤鎴栫鐢ㄥ畾鏃朵换鍔�")
+    @Log(title = "瀹氭椂浠诲姟", businessType = BusinessType.UPDATE)
+    public R changeEnable(@RequestBody TimingTask timingTask) throws SchedulerException {
+        return R.ok(timingTaskService.changeEnable(timingTask.getId(), timingTask.getIsEnabled()));
+    }
+
+    /**
+     * 褰撴棩宸℃璁板綍
+     */
+    @GetMapping("/recordList/{timingId}")
+    @Operation(summary = "鎸夊畾鏃朵换鍔℃煡璇㈠贰妫�璁板綍")
+    public R<IPage<InspectionTaskDto>> recordList(Page<InspectionTask> page, @PathVariable Long timingId) {
+        IPage<InspectionTaskDto> list = inspectionTaskService.selectInspectionTaskRecordList(page, timingId);
+        return R.ok(list);
+    }
+
+    /**
      * 瀹氭椂宸℃浠诲姟琛ㄥ垹闄�
      */
     @DeleteMapping("/delTimingTask")
diff --git a/src/main/java/com/ruoyi/inspectiontask/dto/InspectionTaskDto.java b/src/main/java/com/ruoyi/inspectiontask/dto/InspectionTaskDto.java
index a207cde..5e960de 100644
--- a/src/main/java/com/ruoyi/inspectiontask/dto/InspectionTaskDto.java
+++ b/src/main/java/com/ruoyi/inspectiontask/dto/InspectionTaskDto.java
@@ -1,10 +1,13 @@
 package com.ruoyi.inspectiontask.dto;
 
+import com.fasterxml.jackson.annotation.JsonFormat;
 import com.ruoyi.basic.dto.StorageBlobDTO;
 import com.ruoyi.basic.dto.StorageBlobVO;
 import com.ruoyi.inspectiontask.pojo.InspectionTask;
 import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
 
+import java.time.LocalDateTime;
 import java.util.List;
 
 @Data
@@ -25,4 +28,14 @@
     private List<StorageBlobVO> commonFileListAfterVO; //鐢熶骇鍚�
     private List<StorageBlobVO> commonFileListBeforeVO; //鐢熶骇鍓�
 
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime createTimeStart;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime createTimeEnd;
+
+    private String areaName;
 }
diff --git a/src/main/java/com/ruoyi/inspectiontask/dto/TimingTaskDto.java b/src/main/java/com/ruoyi/inspectiontask/dto/TimingTaskDto.java
index ad26f89..2a70d57 100644
--- a/src/main/java/com/ruoyi/inspectiontask/dto/TimingTaskDto.java
+++ b/src/main/java/com/ruoyi/inspectiontask/dto/TimingTaskDto.java
@@ -9,4 +9,6 @@
 public class TimingTaskDto extends TimingTask {
 
     private List<String> inspector;
+
+    private String areaName;
 }
diff --git a/src/main/java/com/ruoyi/inspectiontask/mapper/InspectionTaskMapper.java b/src/main/java/com/ruoyi/inspectiontask/mapper/InspectionTaskMapper.java
index 9c79059..d0e180b 100644
--- a/src/main/java/com/ruoyi/inspectiontask/mapper/InspectionTaskMapper.java
+++ b/src/main/java/com/ruoyi/inspectiontask/mapper/InspectionTaskMapper.java
@@ -1,11 +1,17 @@
 package com.ruoyi.inspectiontask.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.inspectiontask.dto.InspectionTaskDto;
 import com.ruoyi.inspectiontask.pojo.InspectionTask;
+import org.apache.ibatis.annotations.Param;
 
 /**
  * @author :yys
  * @date : 2025/9/19 10:46
  */
 public interface InspectionTaskMapper extends BaseMapper<InspectionTask> {
+
+    IPage<InspectionTask> selectInspectionTaskAggregatePage(Page<InspectionTask> page, @Param("dto") InspectionTaskDto dto);
 }
diff --git a/src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTask.java b/src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTask.java
index 4e7efad..ccca52e 100644
--- a/src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTask.java
+++ b/src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTask.java
@@ -3,6 +3,7 @@
 import com.baomidou.mybatisplus.annotation.*;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.ruoyi.framework.aspectj.lang.annotation.Excel;
+import io.swagger.annotations.ApiModelProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import org.springframework.format.annotation.DateTimeFormat;
@@ -36,6 +37,10 @@
 
     @Schema(description = "璁惧id")
     private Integer taskId;
+
+    @ApiModelProperty(value = "宸℃鐘舵�侊細1=寰呭贰妫�锛�2=宸插贰妫�")
+    @Excel(name = "宸℃鐘舵��", readConverterExp = "1=寰呭贰妫�,2=宸插贰妫�")
+    private Integer inspectionStatus;
 
     @Schema(description = "宸℃浜篒D")
     private String inspectorId;
@@ -113,4 +118,9 @@
 
     @TableField(fill = FieldFill.INSERT)
     private Long deptId;
+
+    private Long areaId;
+
+    @Schema(description = "鏉ユ簮瀹氭椂浠诲姟ID")
+    private Long timingId;
 }
diff --git a/src/main/java/com/ruoyi/inspectiontask/pojo/TimingTask.java b/src/main/java/com/ruoyi/inspectiontask/pojo/TimingTask.java
index 43a3edc..ecbfe34 100644
--- a/src/main/java/com/ruoyi/inspectiontask/pojo/TimingTask.java
+++ b/src/main/java/com/ruoyi/inspectiontask/pojo/TimingTask.java
@@ -117,4 +117,9 @@
 
     @TableField(fill = FieldFill.INSERT)
     private Long deptId;
+
+    private Long areaId;
+
+    @Schema(description = "璁惧ID闆嗗悎")
+    private String taskIdsStr;
 }
diff --git a/src/main/java/com/ruoyi/inspectiontask/service/InspectionTaskService.java b/src/main/java/com/ruoyi/inspectiontask/service/InspectionTaskService.java
index de175f2..4d6e6c6 100644
--- a/src/main/java/com/ruoyi/inspectiontask/service/InspectionTaskService.java
+++ b/src/main/java/com/ruoyi/inspectiontask/service/InspectionTaskService.java
@@ -14,6 +14,8 @@
 
     IPage<InspectionTaskDto> selectInspectionTaskList(Page<InspectionTask> page, InspectionTaskDto inspectionTaskDto);
 
+    IPage<InspectionTaskDto> selectInspectionTaskRecordList(Page<InspectionTask> page, Long timingId);
+
     int addOrEditInspectionTask(InspectionTaskDto inspectionTaskDto);
 
     int delByIds(Long[] ids);
diff --git a/src/main/java/com/ruoyi/inspectiontask/service/TimingTaskService.java b/src/main/java/com/ruoyi/inspectiontask/service/TimingTaskService.java
index 8d00032..192c29d 100644
--- a/src/main/java/com/ruoyi/inspectiontask/service/TimingTaskService.java
+++ b/src/main/java/com/ruoyi/inspectiontask/service/TimingTaskService.java
@@ -17,6 +17,8 @@
 
     int addOrEditTimingTask(TimingTaskDto timingTaskDto) throws SchedulerException;
 
+    int changeEnable(Long id, Integer isEnabled) throws SchedulerException;
+
     int delByIds(Long[] ids);
 
     void updateTaskExecutionTime(Long taskId);
diff --git a/src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java b/src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java
index 9729b40..f21e386 100644
--- a/src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java
+++ b/src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java
@@ -12,7 +12,9 @@
 import com.ruoyi.common.utils.SecurityUtils;
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.common.utils.bean.BeanUtils;
+import com.ruoyi.device.mapper.DeviceAreaMapper;
 import com.ruoyi.device.mapper.DeviceLedgerMapper;
+import com.ruoyi.device.pojo.DeviceArea;
 import com.ruoyi.device.mapper.DeviceRepairMapper;
 import com.ruoyi.device.pojo.DeviceLedger;
 import com.ruoyi.device.pojo.DeviceRepair;
@@ -28,6 +30,8 @@
 import org.springframework.transaction.annotation.Transactional;
 
 import java.time.format.DateTimeFormatter;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.util.Date;
 import java.util.*;
 import java.util.function.Function;
@@ -53,30 +57,21 @@
 
     private final DeviceLedgerMapper deviceLedgerMapper;
 
+    private final DeviceAreaMapper deviceAreaMapper;
+
     private static final String INSPECTION_RESULT_ABNORMAL = "0";
     private static final String INSPECTION_RESULT_NORMAL = "1";
     private static final int REPAIR_STATUS_PENDING = 0;
 
     @Override
     public IPage<InspectionTaskDto> selectInspectionTaskList(Page<InspectionTask> page, InspectionTaskDto inspectionTaskDto) {
-        LambdaQueryWrapper<InspectionTask> queryWrapper = new LambdaQueryWrapper<>();
-        queryWrapper.orderByDesc(InspectionTask::getCreateTime);
-        if (StringUtils.isNotBlank(inspectionTaskDto.getTaskName())) {
-            queryWrapper.like(InspectionTask::getTaskName, inspectionTaskDto.getTaskName());
-        }
-        if (StringUtils.isNotBlank(inspectionTaskDto.getInspectionProject())) {
-            queryWrapper.like(InspectionTask::getInspectionProject, inspectionTaskDto.getInspectionProject());
-        }
-        queryWrapper.orderByDesc(InspectionTask::getCreateTime);
-        IPage<InspectionTask> entityPage = inspectionTaskMapper.selectPage(page, queryWrapper);
+        IPage<InspectionTask> entityPage = inspectionTaskMapper.selectInspectionTaskAggregatePage(page, inspectionTaskDto);
 
-        //  鏃犳暟鎹彁鍓嶈繑鍥�
         if (CollectionUtils.isEmpty(entityPage.getRecords())) {
             return new Page<>(entityPage.getCurrent(), entityPage.getSize(), entityPage.getTotal());
         }
-        //鐧昏浜篿ds
+
         List<Long> registrantIds = entityPage.getRecords().stream().map(InspectionTask::getRegistrantId).collect(Collectors.toList());
-        // 鎵归噺鏌ヨ鐧昏浜�
         Map<Long, SysUser> sysUserMap;
         if (!registrantIds.isEmpty()) {
             List<SysUser> sysUsers = sysUserMapper.selectUsersByIds(registrantIds);
@@ -84,9 +79,9 @@
         } else {
             sysUserMap = new HashMap<>();
         }
-        //鑾峰彇鎵�鏈変笉閲嶅鐨勭敤鎴稩D
+
         Set<Long> allUserIds = entityPage.getRecords().stream()
-                .map(InspectionTask::getInspectorId) // 鑾峰彇"2,3"杩欐牱鐨勫瓧绗︿覆
+                .map(InspectionTask::getInspectorId)
                 .filter(StringUtils::isNotBlank)
                 .flatMap(idsStr -> Arrays.stream(idsStr.split(",")))
                 .map(idStr -> {
@@ -99,7 +94,6 @@
                 .filter(Objects::nonNull)
                 .collect(Collectors.toSet());
 
-        // 浣跨敤SQL鎵归噺鏌ヨ鐢ㄦ埛淇℃伅
         Map<Long, String> userIdToNameMap = allUserIds.isEmpty()
                 ? Collections.emptyMap()
                 : sysUserMapper.selectUsersByIds(new ArrayList<>(allUserIds))
@@ -108,42 +102,138 @@
                         SysUser::getUserId,
                         SysUser::getNickName,
                         (existing, replacement) -> existing));
-        List<InspectionTaskDto> dtoList = entityPage.getRecords().stream().map(inspectionTask -> {
-            InspectionTaskDto dto = new InspectionTaskDto();
-            BeanUtils.copyProperties(inspectionTask, dto);  // 澶嶅埗涓诲璞″睘鎬�
 
-            // 璁剧疆鐧昏浜�
-            SysUser sysUser = sysUserMap.get(inspectionTask.getRegistrantId());
-            if (sysUser != null) {
-                dto.setRegistrant(sysUser.getNickName());
-            }
-            // 澶勭悊宸℃浜哄悕绉�
-            if (StringUtils.isNotBlank(inspectionTask.getInspectorId())) {
-                String inspectorNames = Arrays.stream(inspectionTask.getInspectorId().split(","))
-                        .map(String::trim)
-                        .map(idStr -> {
-                            try {
-                                Long userId = Long.parseLong(idStr);
-                                return userIdToNameMap.getOrDefault(userId, "鏈煡鐢ㄦ埛(" + idStr + ")");
-                            } catch (NumberFormatException e) {
-                                return "鏃犳晥ID(" + idStr + ")";
-                            }
-                        })
-                        .collect(Collectors.joining(","));
-                dto.setInspector(inspectorNames);
-            }
+        Set<Long> areaIds = entityPage.getRecords().stream()
+                .map(InspectionTask::getAreaId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+        Map<Long, String> areaNameMap = new HashMap<>();
+        if (!areaIds.isEmpty()) {
+            List<DeviceArea> areas = deviceAreaMapper.selectBatchIds(new ArrayList<>(areaIds));
+            areas.forEach(area -> areaNameMap.put(area.getId(), area.getAreaName()));
+        }
 
-            dto.setDateStr(inspectionTask.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
-            // 鍒濆鍖栦笁涓檮浠跺垪琛�
-            dto.setCommonFileListVO(fileUtil.getStorageBlobVOsByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum.FILE, RecordTypeEnum.INSPECTION_TASK, inspectionTask.getId()));
-            dto.setCommonFileListAfterVO(fileUtil.getStorageBlobVOsByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum.AFTER_FILE, RecordTypeEnum.INSPECTION_TASK, inspectionTask.getId()));
-            dto.setCommonFileListBeforeVO(fileUtil.getStorageBlobVOsByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum.BEFORE_FILE, RecordTypeEnum.INSPECTION_TASK, inspectionTask.getId()));
+        List<InspectionTaskDto> dtoList = buildInspectionTaskDtoList(
+                entityPage.getRecords(),
+                sysUserMap,
+                userIdToNameMap,
+                areaNameMap
+        );
 
+        IPage<InspectionTaskDto> resultPage = new Page<>();
+        BeanUtils.copyProperties(entityPage, resultPage);
+        resultPage.setRecords(dtoList);
+        return resultPage;
+    }
 
-            return dto;
-        }).collect(Collectors.toList());
+    private List<InspectionTaskDto> buildInspectionTaskDtoList(List<InspectionTask> records,
+                                                               Map<Long, SysUser> sysUserMap,
+                                                               Map<Long, String> userIdToNameMap,
+                                                               Map<Long, String> areaNameMap) {
+        if (CollectionUtils.isEmpty(records)) {
+            return Collections.emptyList();
+        }
+        return records.stream()
+                .map(record -> buildInspectionTaskDto(record, sysUserMap, userIdToNameMap, areaNameMap))
+                .collect(Collectors.toList());
+    }
 
-        // 7. 鏋勫缓杩斿洖鍒嗛〉瀵硅薄
+    private InspectionTaskDto buildInspectionTaskDto(InspectionTask baseTask,
+                                                     Map<Long, SysUser> sysUserMap,
+                                                     Map<Long, String> userIdToNameMap,
+                                                     Map<Long, String> areaNameMap) {
+        InspectionTaskDto dto = new InspectionTaskDto();
+        BeanUtils.copyProperties(baseTask, dto);
+
+        SysUser sysUser = sysUserMap.get(baseTask.getRegistrantId());
+        if (sysUser != null) {
+            dto.setRegistrant(sysUser.getNickName());
+        }
+        if (StringUtils.isNotBlank(baseTask.getInspectorId())) {
+            String inspectorNames = Arrays.stream(baseTask.getInspectorId().split(","))
+                    .map(String::trim)
+                    .map(idStr -> {
+                        try {
+                            Long userId = Long.parseLong(idStr);
+                            return userIdToNameMap.getOrDefault(userId, "鏈煡鐢ㄦ埛(" + idStr + ")");
+                        } catch (NumberFormatException e) {
+                            return "鏃犳晥ID(" + idStr + ")";
+                        }
+                    })
+                    .collect(Collectors.joining(","));
+            dto.setInspector(inspectorNames);
+        }
+
+        if (baseTask.getCreateTime() != null) {
+            dto.setDateStr(baseTask.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
+        }
+        if (baseTask.getAreaId() != null) {
+            dto.setAreaName(areaNameMap.getOrDefault(baseTask.getAreaId(), ""));
+        }
+        dto.setCommonFileListVO(fileUtil.getStorageBlobVOsByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum.FILE, RecordTypeEnum.INSPECTION_TASK, baseTask.getId()));
+        dto.setCommonFileListAfterVO(fileUtil.getStorageBlobVOsByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum.AFTER_FILE, RecordTypeEnum.INSPECTION_TASK, baseTask.getId()));
+        dto.setCommonFileListBeforeVO(fileUtil.getStorageBlobVOsByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum.BEFORE_FILE, RecordTypeEnum.INSPECTION_TASK, baseTask.getId()));
+        return dto;
+    }
+
+    @Override
+    public IPage<InspectionTaskDto> selectInspectionTaskRecordList(Page<InspectionTask> page, Long timingId) {
+        InspectionTaskDto queryDto = new InspectionTaskDto();
+        queryDto.setTimingId(timingId);
+        queryDto.setCreateTimeStart(LocalDate.now().atStartOfDay());
+        queryDto.setCreateTimeEnd(queryDto.getCreateTimeStart().plusDays(1));
+
+        IPage<InspectionTask> entityPage = inspectionTaskMapper.selectInspectionTaskAggregatePage(page, queryDto);
+        if (CollectionUtils.isEmpty(entityPage.getRecords())) {
+            return new Page<>(entityPage.getCurrent(), entityPage.getSize(), entityPage.getTotal());
+        }
+
+        List<Long> registrantIds = entityPage.getRecords().stream().map(InspectionTask::getRegistrantId).collect(Collectors.toList());
+        Map<Long, SysUser> sysUserMap;
+        if (!registrantIds.isEmpty()) {
+            List<SysUser> sysUsers = sysUserMapper.selectUsersByIds(registrantIds);
+            sysUserMap = sysUsers.stream().collect(Collectors.toMap(SysUser::getUserId, Function.identity()));
+        } else {
+            sysUserMap = new HashMap<>();
+        }
+
+        Set<Long> allUserIds = entityPage.getRecords().stream()
+                .map(InspectionTask::getInspectorId)
+                .filter(StringUtils::isNotBlank)
+                .flatMap(idsStr -> Arrays.stream(idsStr.split(",")))
+                .map(idStr -> {
+                    try {
+                        return Long.parseLong(idStr.trim());
+                    } catch (NumberFormatException e) {
+                        return null;
+                    }
+                })
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+
+        Map<Long, String> userIdToNameMap = allUserIds.isEmpty()
+                ? Collections.emptyMap()
+                : sysUserMapper.selectUsersByIds(new ArrayList<>(allUserIds))
+                .stream()
+                .collect(Collectors.toMap(
+                        SysUser::getUserId,
+                        SysUser::getNickName,
+                        (existing, replacement) -> existing));
+
+        Set<Long> areaIds = entityPage.getRecords().stream()
+                .map(InspectionTask::getAreaId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+        Map<Long, String> areaNameMap = new HashMap<>();
+        if (!areaIds.isEmpty()) {
+            List<DeviceArea> areas = deviceAreaMapper.selectBatchIds(new ArrayList<>(areaIds));
+            areas.forEach(area -> areaNameMap.put(area.getId(), area.getAreaName()));
+        }
+
+        List<InspectionTaskDto> dtoList = entityPage.getRecords().stream()
+                .map(task -> buildInspectionTaskDto(task, sysUserMap, userIdToNameMap, areaNameMap))
+                .collect(Collectors.toList());
+
         IPage<InspectionTaskDto> resultPage = new Page<>();
         BeanUtils.copyProperties(entityPage, resultPage);
         resultPage.setRecords(dtoList);
@@ -166,6 +256,7 @@
         BeanUtils.copyProperties(inspectionTaskDto, inspectionTask);
         inspectionTask.setRegistrantId(SecurityUtils.getLoginUser().getUserId());
         inspectionTask.setRegistrant(SecurityUtils.getLoginUser().getUsername());
+        inspectionTask.setInspectionStatus(2);
         fillAcceptanceInfo(inspectionTask, oldInspectionTask);
         int i;
         if (Objects.isNull(inspectionTaskDto.getId())) {
diff --git a/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskJob.java b/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskJob.java
index adc2416..c6f15de 100644
--- a/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskJob.java
+++ b/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskJob.java
@@ -15,9 +15,12 @@
 import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.time.YearMonth;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 @Component
 @DisallowConcurrentExecution // 绂佹骞跺彂鎵ц鍚屼竴涓狫ob
@@ -56,9 +59,12 @@
 //                throw new JobExecutionException("瀹氭椂浠诲姟宸茬鐢�: " + taskId);
 //            }
 
-            // 2. 鍒涘缓骞朵繚瀛樺贰妫�浠诲姟璁板綍 - 杩欏氨鏄偍鎻愪緵鐨勪唬鐮佸簲璇ユ斁鐨勪綅缃�
-            InspectionTask inspectionTask = createInspectionTask(timingTask);
-            inspectionTaskMapper.insert(inspectionTask);
+            // 2. 瑙f瀽璁惧ID鍒楄〃锛屼负姣忎釜璁惧鍒涘缓宸℃浠诲姟璁板綍
+            List<Integer> deviceIds = resolveDeviceIds(timingTask);
+            for (Integer deviceId : deviceIds) {
+                InspectionTask inspectionTask = createInspectionTask(timingTask, deviceId);
+                inspectionTaskMapper.insert(inspectionTask);
+            }
 
             // 3. 鏇存柊瀹氭椂浠诲姟鐨勬墽琛屾椂闂�
             if (!tasks.isEmpty()) {
@@ -97,16 +103,32 @@
         }
     }
 
-    // 杩欏氨鏄偍鎻愪緵鐨勪唬鐮佸皝瑁呮垚鐨勬柟娉�
-    private InspectionTask createInspectionTask(TimingTask timingTask) {
+    private List<Integer> resolveDeviceIds(TimingTask timingTask) throws JobExecutionException {
+        if (StringUtils.isNotBlank(timingTask.getTaskIdsStr())) {
+            return Arrays.stream(timingTask.getTaskIdsStr().split(","))
+                    .map(String::trim)
+                    .filter(StringUtils::isNotBlank)
+                    .map(Integer::parseInt)
+                    .collect(Collectors.toList());
+        }
+        if (timingTask.getTaskId() != null) {
+            List<Integer> list = new ArrayList<>();
+            list.add(timingTask.getTaskId());
+            return list;
+        }
+        throw new JobExecutionException("瀹氭椂浠诲姟 " + timingTask.getId() + " 鏈厤缃澶嘔D");
+    }
+
+    private InspectionTask createInspectionTask(TimingTask timingTask, Integer deviceId) {
         InspectionTask inspectionTask = new InspectionTask();
 
-        // 澶嶅埗鍩烘湰灞炴��
         inspectionTask.setTaskName(timingTask.getTaskName());
         inspectionTask.setInspectionProject(timingTask.getInspectionProject());
-        inspectionTask.setTaskId(timingTask.getTaskId());
+        inspectionTask.setTaskId(deviceId);
         inspectionTask.setInspectorId(timingTask.getInspectorIds());
         inspectionTask.setInspectionLocation(timingTask.getInspectionLocation());
+        inspectionTask.setAreaId(timingTask.getAreaId());
+        inspectionTask.setTimingId(timingTask.getId());
         String remarks = "鑷姩鐢熸垚鑷畾鏃朵换鍔D: " + timingTask.getId();
         if (StringUtils.isNotBlank(timingTask.getRemarks())) {
             remarks = remarks + "锛�" + timingTask.getRemarks();
@@ -116,6 +138,7 @@
         inspectionTask.setFrequencyType(timingTask.getFrequencyType());
         inspectionTask.setFrequencyDetail(timingTask.getFrequencyDetail());
         inspectionTask.setTenantId(timingTask.getTenantId());
+        inspectionTask.setInspectionStatus(1);
 
         return inspectionTask;
     }
diff --git a/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskScheduleUtils.java b/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskScheduleUtils.java
new file mode 100644
index 0000000..5c488d2
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskScheduleUtils.java
@@ -0,0 +1,171 @@
+package com.ruoyi.inspectiontask.service.impl;
+
+import java.time.DayOfWeek;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.YearMonth;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.HashSet;
+import java.util.Set;
+
+final class TimingTaskScheduleUtils {
+
+    private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm");
+
+    private TimingTaskScheduleUtils() {
+    }
+
+    static LocalDateTime calculateFirstExecutionTime(String frequencyType, String frequencyDetail) {
+        return calculateNextExecutionTime(frequencyType, frequencyDetail, LocalDateTime.now().minusSeconds(1));
+    }
+
+    static LocalDateTime calculateNextExecutionTime(String frequencyType, String frequencyDetail, LocalDateTime currentTime) {
+        if (frequencyType == null || frequencyDetail == null) {
+            throw new IllegalArgumentException("浠诲姟鍙傛暟涓嶈兘涓虹┖");
+        }
+        switch (frequencyType.toUpperCase()) {
+            case "DAILY":
+                return calculateDailyNextTime(frequencyDetail, currentTime);
+            case "WEEKLY":
+                return calculateWeeklyNextTime(frequencyDetail, currentTime);
+            case "MONTHLY":
+                return calculateMonthlyNextTime(frequencyDetail, currentTime);
+            case "QUARTERLY":
+                return calculateQuarterlyNextTime(frequencyDetail, currentTime);
+            default:
+                throw new IllegalArgumentException("涓嶆敮鎸佺殑棰戠巼绫诲瀷: " + frequencyType);
+        }
+    }
+
+    private static LocalDateTime calculateDailyNextTime(String timeStr, LocalDateTime current) {
+        LocalTime executionTime = parseTime(timeStr);
+        LocalDateTime nextTime = LocalDateTime.of(current.toLocalDate(), executionTime);
+        return current.isBefore(nextTime) ? nextTime : nextTime.plusDays(1);
+    }
+
+    private static LocalDateTime calculateWeeklyNextTime(String detail, LocalDateTime current) {
+        String[] parts = validateAndSplit(detail, ",", 2);
+        Set<DayOfWeek> targetDays = parseDayOfWeeks(parts[0]);
+        LocalTime time = parseTime(parts[1]);
+
+        LocalDateTime nextTime = current;
+        for (int i = 0; i < 366; i++) {
+            nextTime = nextTime.plusDays(1);
+            if (targetDays.contains(nextTime.getDayOfWeek())) {
+                return LocalDateTime.of(nextTime.toLocalDate(), time);
+            }
+        }
+        throw new IllegalArgumentException("鏃犳硶鎵惧埌涓嬩竴娆℃墽琛屾椂闂�");
+    }
+
+    private static LocalDateTime calculateMonthlyNextTime(String detail, LocalDateTime current) {
+        String[] parts = validateAndSplit(detail, ",", 2);
+        int dayOfMonth = validateDayOfMonth(parts[0]);
+        LocalTime time = parseTime(parts[1]);
+
+        for (int i = 0; i < 24; i++) {
+            YearMonth targetYearMonth = YearMonth.from(current).plusMonths(i);
+            int adjustedDay = Math.min(dayOfMonth, targetYearMonth.lengthOfMonth());
+            LocalDateTime target = LocalDateTime.of(
+                    targetYearMonth.getYear(),
+                    targetYearMonth.getMonthValue(),
+                    adjustedDay,
+                    time.getHour(),
+                    time.getMinute()
+            );
+            if (target.isAfter(current)) {
+                return target;
+            }
+        }
+        throw new IllegalArgumentException("鏃犳硶鎵惧埌涓嬩竴娆℃墽琛屾椂闂�");
+    }
+
+    private static LocalDateTime calculateQuarterlyNextTime(String detail, LocalDateTime current) {
+        String[] parts = validateAndSplit(detail, ",", 3);
+        int startMonth = validateMonth(parts[0]);
+        int dayOfMonth = validateDayOfMonth(parts[1]);
+        LocalTime time = parseTime(parts[2]);
+
+        YearMonth anchor = YearMonth.of(current.getYear(), startMonth);
+        for (int i = 0; i < 12; i++) {
+            YearMonth targetYearMonth = anchor.plusMonths(3L * i);
+            int adjustedDay = Math.min(dayOfMonth, targetYearMonth.lengthOfMonth());
+            LocalDateTime target = LocalDateTime.of(
+                    targetYearMonth.getYear(),
+                    targetYearMonth.getMonthValue(),
+                    adjustedDay,
+                    time.getHour(),
+                    time.getMinute()
+            );
+            if (target.isAfter(current)) {
+                return target;
+            }
+        }
+        throw new IllegalArgumentException("鏃犳硶鎵惧埌涓嬩竴娆℃墽琛屾椂闂�");
+    }
+
+    private static LocalTime parseTime(String timeStr) {
+        try {
+            return LocalTime.parse(timeStr, TIME_FORMATTER);
+        } catch (DateTimeParseException e) {
+            throw new IllegalArgumentException("鏃堕棿鏍煎紡蹇呴』涓篐H:mm", e);
+        }
+    }
+
+    private static String[] validateAndSplit(String input, String delimiter, int expectedParts) {
+        String[] parts = input.split(delimiter);
+        if (parts.length != expectedParts) {
+            throw new IllegalArgumentException("鍙傛暟鏍煎紡閿欒");
+        }
+        return parts;
+    }
+
+    private static int validateDayOfMonth(String dayStr) {
+        int day = Integer.parseInt(dayStr.trim());
+        if (day < 1 || day > 31) {
+            throw new IllegalArgumentException("鏃ユ湡蹇呴』鍦�1-31涔嬮棿");
+        }
+        return day;
+    }
+
+    private static int validateMonth(String monthStr) {
+        int month = Integer.parseInt(monthStr.trim());
+        if (month < 1 || month > 12) {
+            throw new IllegalArgumentException("鏈堜唤蹇呴』鍦�1-12涔嬮棿");
+        }
+        return month;
+    }
+
+    private static Set<DayOfWeek> parseDayOfWeeks(String dayOfWeekStr) {
+        Set<DayOfWeek> days = new HashSet<>();
+        for (String dayStr : dayOfWeekStr.split("\\|")) {
+            switch (dayStr.trim().toUpperCase()) {
+                case "MON":
+                    days.add(DayOfWeek.MONDAY);
+                    break;
+                case "TUE":
+                    days.add(DayOfWeek.TUESDAY);
+                    break;
+                case "WED":
+                    days.add(DayOfWeek.WEDNESDAY);
+                    break;
+                case "THU":
+                    days.add(DayOfWeek.THURSDAY);
+                    break;
+                case "FRI":
+                    days.add(DayOfWeek.FRIDAY);
+                    break;
+                case "SAT":
+                    days.add(DayOfWeek.SATURDAY);
+                    break;
+                case "SUN":
+                    days.add(DayOfWeek.SUNDAY);
+                    break;
+                default:
+                    throw new IllegalArgumentException("鏃犳晥鐨勬槦鏈熷嚑: " + dayStr);
+            }
+        }
+        return days;
+    }
+}
diff --git a/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java b/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java
index ebe615d..4508625 100644
--- a/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java
+++ b/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java
@@ -6,6 +6,8 @@
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.common.utils.bean.BeanUtils;
+import com.ruoyi.device.mapper.DeviceAreaMapper;
+import com.ruoyi.device.pojo.DeviceArea;
 import com.ruoyi.inspectiontask.dto.TimingTaskDto;
 import com.ruoyi.inspectiontask.mapper.TimingTaskMapper;
 import com.ruoyi.inspectiontask.pojo.TimingTask;
@@ -35,6 +37,7 @@
     private final TimingTaskMapper timingTaskMapper;
     private final TimingTaskScheduler timingTaskScheduler;
     private final SysUserMapper sysUserMapper;
+    private final DeviceAreaMapper deviceAreaMapper;
     private static final int ENABLED = 1;
     private static final int DISABLED = 0;
 
@@ -52,6 +55,12 @@
         }
         if (timingTask.getIsEnabled() != null) {
             queryWrapper.eq(TimingTask::getIsEnabled, timingTask.getIsEnabled());
+        }
+        if (timingTask.getAreaId() != null) {
+            queryWrapper.eq(TimingTask::getAreaId, timingTask.getAreaId());
+        }
+        if (StringUtils.isNotBlank(timingTask.getTaskIdsStr())) {
+            queryWrapper.like(TimingTask::getTaskIdsStr, timingTask.getTaskIdsStr());
         }
         queryWrapper.orderByDesc(TimingTask::getCreateTime);
         IPage<TimingTask> taskPage = timingTaskMapper.selectPage(page, queryWrapper);
@@ -89,6 +98,17 @@
             users.forEach(user -> userNickNameMap.put(user.getUserId(), user.getNickName()));
         }
 
+        // 4.1 鎵归噺鏌ヨ鍖哄煙鍚嶇О
+        Set<Long> areaIds = taskPage.getRecords().stream()
+                .map(TimingTask::getAreaId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+        Map<Long, String> areaNameMap = new HashMap<>();
+        if (!areaIds.isEmpty()) {
+            List<DeviceArea> areas = deviceAreaMapper.selectBatchIds(new ArrayList<>(areaIds));
+            areas.forEach(area -> areaNameMap.put(area.getId(), area.getAreaName()));
+        }
+
         // 5. 杞崲涓篋TO
         List<TimingTaskDto> dtoList = taskPage.getRecords().stream().map(task -> {
             TimingTaskDto dto = new TimingTaskDto();
@@ -112,6 +132,11 @@
                 dto.setInspector(inspectorNickNames);
             }
 
+            // 璁剧疆鍖哄煙鍚嶇О
+            if (task.getAreaId() != null) {
+                dto.setAreaName(areaNameMap.getOrDefault(task.getAreaId(), ""));
+            }
+
             return dto;
         }).collect(Collectors.toList());
 
@@ -122,7 +147,7 @@
     }
 
     @Override
-    @Transactional
+    @Transactional(rollbackFor = Exception.class)
     public int addOrEditTimingTask(TimingTaskDto timingTaskDto) throws SchedulerException {
         TimingTask oldTimingTask = null;
         if (Objects.nonNull(timingTaskDto.getId())) {
@@ -179,6 +204,34 @@
             }
             return result;
         }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int changeEnable(Long id, Integer isEnabled) throws SchedulerException {
+        TimingTask oldTimingTask = timingTaskMapper.selectById(id);
+        if (oldTimingTask == null) {
+            throw new IllegalArgumentException("瀹氭椂浠诲姟涓嶅瓨鍦�");
+        }
+        TimingTask update = new TimingTask();
+        update.setId(id);
+        update.setIsEnabled(resolveEnabledValue(isEnabled, oldTimingTask));
+        update.setActive(ENABLED == update.getIsEnabled());
+
+        int result = timingTaskMapper.updateById(update);
+        if (result <= 0) {
+            return result;
+        }
+
+        boolean enabled = isEnabled(update.getIsEnabled(), update.isActive());
+        if (!enabled) {
+            timingTaskScheduler.unscheduleTimingTask(id);
+        } else if (oldTimingTask.getIsEnabled() != null && oldTimingTask.getIsEnabled() == DISABLED) {
+            timingTaskScheduler.scheduleTimingTask(oldTimingTask);
+        } else {
+            timingTaskScheduler.resumeTimingTask(id);
+        }
+        return result;
     }
 
     public LocalDateTime calculateFirstExecutionTime(TimingTask task) {
@@ -315,7 +368,7 @@
     }
 
     private LocalDateTime calculateCustomFirstExecution(String frequencyDetail) {
-        return null;
+        return TimingTaskScheduleUtils.calculateFirstExecutionTime("QUARTERLY", frequencyDetail);
     }
 
     @Override
@@ -421,7 +474,7 @@
     /**
      * 璁$畻姣忓搴︿换鍔$殑涓嬫鎵ц鏃堕棿
      */
-    private LocalDateTime calculateQuarterlyNextTime(String detail, LocalDateTime current) {
+    private LocalDateTime calculateQuarterlyNextTimeLegacy(String detail, LocalDateTime current) {
         String[] parts = detail.split(",");
         int quarterMonth = Integer.parseInt(parts[0]); // 1=绗�1涓湀锛�2=绗�2涓湀锛�3=绗�3涓湀
         int dayOfMonth = Integer.parseInt(parts[1]);
@@ -457,6 +510,9 @@
     /**
      * 瑙f瀽鏄熸湡鍑犲瓧绗︿覆
      */
+    private LocalDateTime calculateQuarterlyNextTime(String detail, LocalDateTime current) {
+        return TimingTaskScheduleUtils.calculateNextExecutionTime("QUARTERLY", detail, current);
+    }
     private Set<DayOfWeek> parseDayOfWeeks(String dayOfWeekStr) {
         Set<DayOfWeek> days = new HashSet<>();
         String[] dayStrs = dayOfWeekStr.split("\\|");
diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml
index f1a5bf9..1af2880 100644
--- a/src/main/resources/application-dev.yml
+++ b/src/main/resources/application-dev.yml
@@ -74,7 +74,7 @@
     druid:
       # 涓诲簱鏁版嵁婧�
       master:
-        url: jdbc:mysql://localhost:3306/product-inventory-management-new-pro?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+        url: jdbc:mysql://localhost:3306/product-inventory-management-dlsmls-pro?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
         username: root
         password: 123456
       # 浠庡簱鏁版嵁婧�
diff --git a/src/main/resources/application-dlsmls-pro.yml b/src/main/resources/application-dlsmls-pro.yml
new file mode 100644
index 0000000..02541fb
--- /dev/null
+++ b/src/main/resources/application-dlsmls-pro.yml
@@ -0,0 +1,268 @@
+# 椤圭洰鐩稿叧閰嶇疆
+ruoyi:
+  # 鍚嶇О
+  name: RuoYi
+  # 鐗堟湰
+  version: 3.8.9
+  # 鐗堟潈骞翠唤
+  copyrightYear: 2025
+  # 鏂囦欢璺緞 绀轰緥锛� Windows閰嶇疆D:/ruoyi/uploadPath锛孡inux閰嶇疆 /home/ruoyi/uploadPath锛�
+  profile: /javaWork/product-inventory-management/file
+
+  # 鑾峰彇ip鍦板潃寮�鍏�
+  addressEnabled: false
+  # 楠岃瘉鐮佺被鍨� math 鏁板瓧璁$畻 char 瀛楃楠岃瘉
+  captchaType: math
+  # 鍗忓悓瀹℃壒缂栧彿鍓嶇紑(閰嶇疆鏂囦欢鍚庣紑鍛藉悕)
+  approvalNumberPrefix: NEW
+
+  # 涓帹 Unipush 閰嶇疆
+  getui:
+    appId: PfjyAAE0FK64FaO1w2CMb1
+    appKey: zTMb831OEL6J4GK1uE3Ob4
+    masterSecret: K1GFtsv42v61tXGnF7SGE5
+    domain: https://restapi.getui.cn/v2/
+    # 绂荤嚎鎺ㄩ�佷娇鐢ㄧ殑鍖呭悕/缁勪欢鍚�
+    intentComponent: uni.app.UNI099A590/io.dcloud.PandoraEntry
+
+# 寮�鍙戠幆澧冮厤缃�
+server:
+  # 鏈嶅姟鍣ㄧ殑HTTP绔彛锛岄粯璁や负8080
+  port: 9003
+  servlet:
+    # 搴旂敤鐨勮闂矾寰�
+    context-path: /
+  tomcat:
+    # tomcat鐨刄RI缂栫爜
+    uri-encoding: UTF-8
+    # 杩炴帴鏁版弧鍚庣殑鎺掗槦鏁帮紝榛樿涓�100
+    accept-count: 1000
+    threads:
+      # tomcat鏈�澶х嚎绋嬫暟锛岄粯璁や负200
+      max: 800
+      # Tomcat鍚姩鍒濆鍖栫殑绾跨▼鏁帮紝榛樿鍊�10
+      min-spare: 100
+
+# 鏃ュ織閰嶇疆
+logging:
+  level:
+    com.ruoyi: warn
+    org.springframework: warn
+
+minio:
+  endpoint: http://114.132.189.42/
+  port: 7019
+  secure: false
+  accessKey: admin
+  secretKey: 12345678
+  preview-expiry: 24 # 棰勮鍦板潃榛樿24灏忔椂
+  default-bucket: jxc
+# 鐢ㄦ埛閰嶇疆
+user:
+  password:
+    # 瀵嗙爜鏈�澶ч敊璇鏁�
+    maxRetryCount: 5
+    # 瀵嗙爜閿佸畾鏃堕棿锛堥粯璁�10鍒嗛挓锛�
+    lockTime: 10
+
+# Spring閰嶇疆
+spring:
+  datasource:
+    type: com.alibaba.druid.pool.DruidDataSource
+    driverClassName: com.mysql.cj.jdbc.Driver
+    druid:
+      # 涓诲簱鏁版嵁婧�
+      master:
+        url: jdbc:mysql://172.17.0.1:3306/product-inventory-management-dlsmls-pro?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+        username: root
+        password: xd@123456..
+      # 浠庡簱鏁版嵁婧�
+      slave:
+        # 浠庢暟鎹簮寮�鍏�/榛樿鍏抽棴
+        enabled: false
+        url:
+        username:
+        password:
+      # 鍒濆杩炴帴鏁�
+      initialSize: 5
+      # 鏈�灏忚繛鎺ユ睜鏁伴噺
+      minIdle: 10
+      # 鏈�澶ц繛鎺ユ睜鏁伴噺
+      maxActive: 20
+      # 閰嶇疆鑾峰彇杩炴帴绛夊緟瓒呮椂鐨勬椂闂�
+      maxWait: 60000
+      # 閰嶇疆杩炴帴瓒呮椂鏃堕棿
+      connectTimeout: 30000
+      # 閰嶇疆缃戠粶瓒呮椂鏃堕棿
+      socketTimeout: 60000
+      # 閰嶇疆闂撮殧澶氫箙鎵嶈繘琛屼竴娆℃娴嬶紝妫�娴嬮渶瑕佸叧闂殑绌洪棽杩炴帴锛屽崟浣嶆槸姣
+      timeBetweenEvictionRunsMillis: 60000
+      # 閰嶇疆涓�涓繛鎺ュ湪姹犱腑鏈�灏忕敓瀛樼殑鏃堕棿锛屽崟浣嶆槸姣
+      minEvictableIdleTimeMillis: 300000
+      # 閰嶇疆涓�涓繛鎺ュ湪姹犱腑鏈�澶х敓瀛樼殑鏃堕棿锛屽崟浣嶆槸姣
+      maxEvictableIdleTimeMillis: 900000
+      # 閰嶇疆妫�娴嬭繛鎺ユ槸鍚︽湁鏁�
+      validationQuery: SELECT 1 FROM DUAL
+      testWhileIdle: true
+      testOnBorrow: false
+      testOnReturn: false
+      webStatFilter:
+        enabled: true
+      statViewServlet:
+        enabled: true
+        # 璁剧疆鐧藉悕鍗曪紝涓嶅~鍒欏厑璁告墍鏈夎闂�
+        allow:
+        url-pattern: /druid/*
+        # 鎺у埗鍙扮鐞嗙敤鎴峰悕鍜屽瘑鐮�
+        login-username: ruoyi
+        login-password: 123456
+      filter:
+        stat:
+          enabled: true
+          # 鎱QL璁板綍
+          log-slow-sql: true
+          slow-sql-millis: 1000
+          merge-sql: true
+        wall:
+          config:
+            multi-statement-allow: true
+  # 璧勬簮淇℃伅
+  messages:
+    # 鍥介檯鍖栬祫婧愭枃浠惰矾寰�
+    basename: i18n/messages
+  # 鏂囦欢涓婁紶
+  servlet:
+    multipart:
+      # 鍗曚釜鏂囦欢澶у皬
+      max-file-size: 1GB
+      # 璁剧疆鎬讳笂浼犵殑鏂囦欢澶у皬
+      max-request-size: 2GB
+  # 鏈嶅姟妯″潡
+  devtools:
+    restart:
+      # 鐑儴缃插紑鍏�
+      enabled: false
+  # redis 閰嶇疆
+  data:
+    mongodb:
+      uri: mongodb://114.132.189.42:9028/chat_memory_db_dlsmls-pro
+    # redis 閰嶇疆
+    redis:
+      # 鍦板潃
+#      host: 127.0.0.1
+      host: 172.17.0.1
+      # 绔彛锛岄粯璁や负6379
+      port: 6379
+      # 鏁版嵁搴撶储寮�
+      database: 4
+      # 瀵嗙爜
+      #    password: root2022!
+      password:
+
+      # 杩炴帴瓒呮椂鏃堕棿
+      timeout: 10s
+      lettuce:
+        pool:
+          # 杩炴帴姹犱腑鐨勬渶灏忕┖闂茶繛鎺�
+          min-idle: 0
+          # 杩炴帴姹犱腑鐨勬渶澶х┖闂茶繛鎺�
+          max-idle: 8
+          # 杩炴帴姹犵殑鏈�澶ф暟鎹簱杩炴帴鏁�
+          max-active: 8
+          # #杩炴帴姹犳渶澶ч樆濉炵瓑寰呮椂闂达紙浣跨敤璐熷�艰〃绀烘病鏈夐檺鍒讹級
+          max-wait: -1ms
+
+  # Quartz瀹氭椂浠诲姟閰嶇疆锛堟柊澧為儴鍒嗭級
+  quartz:
+    job-store-type: jdbc  # 浣跨敤鏁版嵁搴撳瓨鍌�
+    jdbc:
+      initialize-schema: never  # 棣栨杩愯鏃惰嚜鍔ㄥ垱寤鸿〃缁撴瀯锛屾垚鍔熷悗鏀逛负never
+      schema: classpath:org/quartz/impl/jdbcjobstore/tables_mysql_innodb.sql  # MySQL琛ㄧ粨鏋勮剼鏈�
+    properties:
+      org:
+        quartz:
+          scheduler:
+            instanceName: RuoYiScheduler
+            instanceId: AUTO
+          jobStore:
+            class: org.quartz.impl.jdbcjobstore.JobStoreTX
+            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate  # MySQL閫傞厤
+            tablePrefix: qrtz_  # 琛ㄥ悕鍓嶇紑锛屼笌鑴氭湰涓�鑷�
+            isClustered: false  # 鍗曡妭鐐规ā寮忥紙闆嗙兢闇�鏀逛负true锛�
+            clusterCheckinInterval: 10000
+            txIsolationLevelSerializable: true
+          threadPool:
+            class: org.quartz.simpl.SimpleThreadPool
+            threadCount: 10  # 绾跨▼姹犲ぇ灏�
+            threadPriority: 5
+            makeThreadsDaemons: true
+          updateCheck: false  # 鍏抽棴鐗堟湰妫�鏌�
+# token閰嶇疆
+token:
+  # 浠ょ墝鑷畾涔夋爣璇�
+  header: Authorization
+  # 浠ょ墝瀵嗛挜
+  secret: xpAVjhCjQDaDB7mjPAzMDSbQWXNu2zYkTdDNUsPMS5Xx8QMmQVYN7n74eZrYJxDJ
+  # 浠ょ墝鏈夋晥鏈燂紙榛樿30鍒嗛挓锛�
+  expireTime: 450
+
+# MyBatis Plus閰嶇疆
+mybatis-plus:
+  # 鎼滅储鎸囧畾鍖呭埆鍚�   鏍规嵁鑷繁鐨勯」鐩潵
+  typeAliasesPackage: com.ruoyi.**.pojo
+  # 閰嶇疆mapper鐨勬壂鎻忥紝鎵惧埌鎵�鏈夌殑mapper.xml鏄犲皠鏂囦欢
+  mapperLocations: classpath*:mapper/**/*Mapper.xml
+  # 鍔犺浇鍏ㄥ眬鐨勯厤缃枃浠�
+  configLocation: classpath:mybatis/mybatis-config.xml
+  global-config:
+    enable-sql-runner: true
+    db-config:
+      id-type: auto
+
+# PageHelper鍒嗛〉鎻掍欢
+pagehelper:
+  helperDialect: mysql
+  supportMethodsArguments: true
+  params: count=countSql
+
+# Swagger閰嶇疆
+swagger:
+  # 鏄惁寮�鍚痵wagger
+  enabled: true
+  # 璇锋眰鍓嶇紑
+  pathMapping: /dev-api
+
+# 闃叉XSS鏀诲嚮
+xss:
+  # 杩囨护寮�鍏�
+  enabled: true
+  # 鎺掗櫎閾炬帴锛堝涓敤閫楀彿鍒嗛殧锛�
+  excludes: /system/notice
+  # 鍖归厤閾炬帴
+  urlPatterns: /system/*,/monitor/*,/tool/*
+
+# 浠g爜鐢熸垚
+gen:
+  # 浣滆��
+  author: ruoyi
+  # 榛樿鐢熸垚鍖呰矾寰� system 闇�鏀规垚鑷繁鐨勬ā鍧楀悕绉� 濡� system monitor tool
+  packageName: com.ruoyi.project.system
+  # 鑷姩鍘婚櫎琛ㄥ墠缂�锛岄粯璁ゆ槸true
+  autoRemovePre: false
+  # 琛ㄥ墠缂�锛堢敓鎴愮被鍚嶄笉浼氬寘鍚〃鍓嶇紑锛屽涓敤閫楀彿鍒嗛殧锛�
+  tablePrefix: sys_
+  # 鏄惁鍏佽鐢熸垚鏂囦欢瑕嗙洊鍒版湰鍦帮紙鑷畾涔夎矾寰勶級锛岄粯璁や笉鍏佽
+  allowOverwrite: false
+
+# 鏂囦欢涓婁紶閰嶇疆
+file:
+  temp-dir: /javaWork/product-inventory-management/file/temp/uploads   # 涓存椂鐩綍
+  upload-dir: /javaWork/product-inventory-management/file/prod/uploads # 姝e紡鐩綍
+  path: /javaWork/product-inventory-management/file # 涓婁紶鐩綍
+  urlPrefix: /prod-api/common # 閾炬帴鍓嶇紑
+  domain: http://1.15.17.182:9074 # 鍩熷悕鍓嶇紑
+  expired: 120 # 杩囨湡鏃堕棿(鍗曚綅:鍒嗛挓)
+  useLimit: 10 # 浣跨敤娆℃暟
+  compress: true # 鏄惁鍘嬬缉
+  needCompressSize: 10MB # 鍘嬬缉闃堝��
+  compressQuality: 0.5 # 鍘嬬缉璐ㄩ噺(0.0-1.0)
diff --git a/src/main/resources/mapper/device/DeviceLedgerMapper.xml b/src/main/resources/mapper/device/DeviceLedgerMapper.xml
index f3b674e..efae5cc 100644
--- a/src/main/resources/mapper/device/DeviceLedgerMapper.xml
+++ b/src/main/resources/mapper/device/DeviceLedgerMapper.xml
@@ -31,9 +31,14 @@
         dl.tenant_id,
         dl.is_depr,
         dl.annual_depreciation_amount,
-        dl.type
+        dl.type,
+        dl.area_id,
+        dl.is_iot_device,
+        dl.external_code,
+        da.area_name AS areaName
         FROM device_ledger dl
         left join sys_user su on dl.create_user = su.user_id
+        left join device_area da on dl.area_id = da.id
         <where>
             <!-- 璁惧鍚嶇О -->
             <if test="deviceLedger.deviceName != null and deviceLedger.deviceName != ''">
@@ -76,6 +81,21 @@
             <if test="deviceLedger.tenantId != null">
                 AND tenant_id = #{deviceLedger.tenantId}
             </if>
+
+            <!-- 璁惧鍖哄煙 -->
+            <if test="deviceLedger.areaId != null">
+                AND dl.area_id = #{deviceLedger.areaId}
+            </if>
+
+            <!-- 鏄惁鐗╄仈璁惧 -->
+            <if test="deviceLedger.isIotDevice != null">
+                AND dl.is_iot_device = #{deviceLedger.isIotDevice}
+            </if>
+
+            <!-- 澶栭儴缂栫爜 -->
+            <if test="deviceLedger.externalCode != null and deviceLedger.externalCode != ''">
+                AND dl.external_code LIKE CONCAT('%', #{deviceLedger.externalCode}, '%')
+            </if>
         </where>
         ORDER BY create_time DESC
     </select>
diff --git a/src/main/resources/mapper/device/DeviceMaintenanceMapper.xml b/src/main/resources/mapper/device/DeviceMaintenanceMapper.xml
index cb05e72..a25d13a 100644
--- a/src/main/resources/mapper/device/DeviceMaintenanceMapper.xml
+++ b/src/main/resources/mapper/device/DeviceMaintenanceMapper.xml
@@ -8,6 +8,7 @@
     <select id="queryPage" resultType="com.ruoyi.device.dto.DeviceMaintenanceDto">
         select dm.id,
         dm.device_ledger_id,
+        dm.area_id,
         dm.maintenance_plan_time,
         dm.maintenance_actually_time,
         dm.maintenance_result,
@@ -21,9 +22,11 @@
         dl.device_name,
         dm.machinery_category,
         dl.device_model,
+        da.area_name,
         su.nick_name as create_user_name
         from device_maintenance dm
         left join device_ledger dl on dm.device_ledger_id = dl.id
+        left join device_area da on dl.area_id = da.id
         left join sys_user su on dm.create_user = su.user_id
         <where>
             <if test="deviceMaintenanceDto.deviceName != null">
@@ -48,12 +51,16 @@
                 and dm.maintenance_actually_time >= str_to_date(#{deviceMaintenanceDto.maintenanceActuallyTime}, '%Y-%m-%d')
                 and dm.maintenance_actually_time &lt; date_add(str_to_date(#{deviceMaintenanceDto.maintenanceActuallyTime}, '%Y-%m-%d'), interval 1 day)
             </if>
+            <if test="deviceMaintenanceDto.areaId != null">
+                and dm.area_id = #{deviceMaintenanceDto.areaId}
+            </if>
         </where>
         order by dm.create_time desc
     </select>
     <select id="detailById" resultType="com.ruoyi.device.vo.DeviceMaintenanceVo">
         select dm.id,
                dm.device_ledger_id,
+               dm.area_id,
                dm.maintenance_plan_time,
                dm.maintenance_actually_time,
                dm.maintenance_result,
@@ -67,9 +74,11 @@
                dm.maintenance_actually_name,
                dl.device_name,
                dl.device_model,
+               da.area_name,
                su.user_name as create_user_name
         from device_maintenance dm
                  left join device_ledger dl on dm.device_ledger_id = dl.id
+                 left join device_area da on dl.area_id = da.id
                  left join sys_user su on dm.create_user = su.user_id
         where dm.id = #{id}
     </select>
diff --git a/src/main/resources/mapper/device/DeviceRepairMapper.xml b/src/main/resources/mapper/device/DeviceRepairMapper.xml
index 10a0abf..65215dd 100644
--- a/src/main/resources/mapper/device/DeviceRepairMapper.xml
+++ b/src/main/resources/mapper/device/DeviceRepairMapper.xml
@@ -8,6 +8,7 @@
     <select id="queryPage" resultType="com.ruoyi.device.vo.DeviceRepairVo">
         select dr.id,
                dr.device_ledger_id,
+               dr.area_id,
                 dr.repair_time,
                 dr.repair_name,
                 dr.remark,
@@ -22,12 +23,14 @@
                 dr.update_time,
                 dr.create_user,
                 dr.update_user,
-               dr.tenant_id,
-               dl.device_name,
-               dl.device_model,
-               dr.machinery_category
+                dr.tenant_id,
+                dl.device_name,
+                dl.device_model,
+               dr.machinery_category,
+               da.area_name
         from device_repair dr
         left join device_ledger dl on dr.device_ledger_id = dl.id
+        left join device_area da on dl.area_id = da.id
         <where>
             <if test="deviceRepairDto.deviceName != null">
                 and dl.device_name like concat('%',#{deviceRepairDto.deviceName},'%')
@@ -51,12 +54,16 @@
             <if test="deviceRepairDto.maintenanceTimeStr != null and deviceRepairDto.maintenanceTimeStr != '' ">
                 and dr.maintenance_time like concat('%',#{deviceRepairDto.maintenanceTimeStr},'%')
             </if>
+            <if test="deviceRepairDto.areaId != null">
+                and dr.area_id = #{deviceRepairDto.areaId}
+            </if>
         </where>
         order by dr.create_time desc
     </select>
     <select id="detailById" resultType="com.ruoyi.device.vo.DeviceRepairVo">
         select dr.id,
                dr.device_ledger_id,
+               dr.area_id,
                dr.repair_time,
                dr.repair_name,
                dr.remark,
@@ -74,9 +81,11 @@
                dr.tenant_id,
                dl.device_name,
                dr.machinery_category,
-               dl.device_model
+               dl.device_model,
+               da.area_name
         from device_repair dr
                  left join device_ledger dl on dr.device_ledger_id = dl.id
+                 left join device_area da on dl.area_id = da.id
         where dr.id = #{id}
     </select>
 
diff --git a/src/main/resources/mapper/inspectiontask/InspectionTaskMapper.xml b/src/main/resources/mapper/inspectiontask/InspectionTaskMapper.xml
new file mode 100644
index 0000000..6113fb5
--- /dev/null
+++ b/src/main/resources/mapper/inspectiontask/InspectionTaskMapper.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.inspectiontask.mapper.InspectionTaskMapper">
+
+    <select id="selectInspectionTaskAggregatePage" resultType="com.ruoyi.inspectiontask.pojo.InspectionTask">
+        SELECT
+            MIN(id) AS id,
+            GROUP_CONCAT(DISTINCT task_name ORDER BY create_time SEPARATOR ',') AS task_name,
+            MAX(inspection_project) AS inspection_project,
+            MAX(task_id) AS task_id,
+            CASE
+                WHEN SUM(CASE WHEN inspection_status = 1 THEN 1 ELSE 0 END) > 0 THEN 1
+                WHEN SUM(CASE WHEN inspection_status = 2 THEN 1 ELSE 0 END) > 0 THEN 2
+                ELSE MAX(inspection_status)
+            END AS inspection_status,
+            MAX(inspector_id) AS inspector_id,
+            MAX(inspector) AS inspector,
+            MAX(remarks) AS remarks,
+            MAX(inspection_result) AS inspection_result,
+            MAX(abnormal_description) AS abnormal_description,
+            MAX(device_repair_id) AS device_repair_id,
+            MAX(acceptance_user_id) AS acceptance_user_id,
+            MAX(acceptance_name) AS acceptance_name,
+            MAX(registrant_id) AS registrant_id,
+            MAX(registrant) AS registrant,
+            MAX(frequency_type) AS frequency_type,
+            MAX(frequency_detail) AS frequency_detail,
+            MAX(inspection_location) AS inspection_location,
+            MAX(deleted) AS deleted,
+            MAX(create_user) AS create_user,
+            MAX(create_time) AS create_time,
+            MAX(update_user) AS update_user,
+            MAX(update_time) AS update_time,
+            MAX(tenant_id) AS tenant_id,
+            MAX(dept_id) AS dept_id,
+            MAX(area_id) AS area_id,
+            MAX(timing_id) AS timing_id
+        FROM inspection_task
+        <where>
+            deleted = 0
+            <if test="dto != null and dto.taskName != null and dto.taskName != ''">
+                AND task_name LIKE CONCAT('%', #{dto.taskName}, '%')
+            </if>
+            <if test="dto != null and dto.inspectionProject != null and dto.inspectionProject != ''">
+                AND inspection_project LIKE CONCAT('%', #{dto.inspectionProject}, '%')
+            </if>
+            <if test="dto != null and dto.areaId != null">
+                AND area_id = #{dto.areaId}
+            </if>
+            <if test="dto != null and dto.timingId != null">
+                AND timing_id = #{dto.timingId}
+            </if>
+            <if test="dto != null and dto.createTimeStart != null">
+                AND create_time <![CDATA[>=]]> #{dto.createTimeStart}
+            </if>
+            <if test="dto != null and dto.createTimeEnd != null">
+                AND create_time <![CDATA[<]]> #{dto.createTimeEnd}
+            </if>
+        </where>
+        GROUP BY IFNULL(timing_id, id)
+        ORDER BY create_time DESC
+    </select>
+</mapper>

--
Gitblit v1.9.3