Forráskód Böngészése

新增4-6系统接口

wangshuangpan 1 hónapja
szülő
commit
8b66fcdd11
42 módosított fájl, 10518 hozzáadás és 0 törlés
  1. 128 0
      pm-admin/src/main/java/com/pm/web/controller/patrol/PatrolPointController.java
  2. 85 0
      pm-admin/src/main/java/com/pm/web/controller/patrol/PatrolRecordController.java
  3. 275 0
      pm-admin/src/main/java/com/pm/web/controller/subsystem/EnergyDeviceController.java
  4. 476 0
      pm-admin/src/main/java/com/pm/web/controller/subsystem/IntrusionAlarmController.java
  5. 353 0
      pm-admin/src/main/java/com/pm/web/controller/subsystem/VideoDeviceController.java
  6. 59 0
      pm-system/src/main/java/com/pm/patrol/mapper/PatrolPointMapper.java
  7. 73 0
      pm-system/src/main/java/com/pm/patrol/mapper/PatrolRecordMapper.java
  8. 34 0
      pm-system/src/main/java/com/pm/patrol/service/IPatrolMonitorService.java
  9. 54 0
      pm-system/src/main/java/com/pm/patrol/service/IPatrolPointService.java
  10. 37 0
      pm-system/src/main/java/com/pm/patrol/service/IPatrolRecordService.java
  11. 85 0
      pm-system/src/main/java/com/pm/patrol/service/impl/PatrolMonitorServiceImpl.java
  12. 60 0
      pm-system/src/main/java/com/pm/patrol/service/impl/PatrolPointServiceImpl.java
  13. 48 0
      pm-system/src/main/java/com/pm/patrol/service/impl/PatrolRecordServiceImpl.java
  14. 133 0
      pm-system/src/main/java/com/pm/subsystem/mapper/EnergyDeviceMapper.java
  15. 169 0
      pm-system/src/main/java/com/pm/subsystem/mapper/IntrusionAlarmMapper.java
  16. 119 0
      pm-system/src/main/java/com/pm/subsystem/mapper/VideoDeviceMapper.java
  17. 165 0
      pm-system/src/main/java/com/pm/subsystem/service/IEnergyDeviceService.java
  18. 249 0
      pm-system/src/main/java/com/pm/subsystem/service/IIntrusionAlarmService.java
  19. 138 0
      pm-system/src/main/java/com/pm/subsystem/service/IVideoDeviceService.java
  20. 315 0
      pm-system/src/main/java/com/pm/subsystem/service/impl/EnergyDeviceServiceImpl.java
  21. 506 0
      pm-system/src/main/java/com/pm/subsystem/service/impl/IntrusionAlarmServiceImpl.java
  22. 296 0
      pm-system/src/main/java/com/pm/subsystem/service/impl/VideoDeviceServiceImpl.java
  23. 160 0
      pm-system/src/main/resources/mapper/patrol/PatrolPointMapper.xml
  24. 155 0
      pm-system/src/main/resources/mapper/patrol/PatrolRecordMapper.xml
  25. 249 0
      pm-system/src/main/resources/mapper/subsystem/EnergyDeviceMapper.xml
  26. 415 0
      pm-system/src/main/resources/mapper/subsystem/IntrusionAlarmMapper.xml
  27. 297 0
      pm-system/src/main/resources/mapper/subsystem/VideoDeviceMapper.xml
  28. 486 0
      pm_ui/src/api/mock/parking-mock-service.js
  29. 192 0
      pm_ui/src/api/mock/parking.js
  30. 163 0
      pm_ui/src/api/parking/monitor.js
  31. 106 0
      pm_ui/src/api/parking/space.js
  32. 22 0
      pm_ui/src/api/patrol/monitor.js
  33. 66 0
      pm_ui/src/api/patrol/point.js
  34. 28 0
      pm_ui/src/api/patrol/record.js
  35. 137 0
      pm_ui/src/api/subsystem/energy.js
  36. 393 0
      pm_ui/src/api/subsystem/intrusion.js
  37. 241 0
      pm_ui/src/api/subsystem/video.js
  38. 464 0
      pm_ui/src/views/nygl/index.vue
  39. 698 0
      pm_ui/src/views/parking/index.vue
  40. 773 0
      pm_ui/src/views/patrol/index.vue
  41. 848 0
      pm_ui/src/views/rqbjxt/index.vue
  42. 768 0
      pm_ui/src/views/spafjkxt/index.vue

+ 128 - 0
pm-admin/src/main/java/com/pm/web/controller/patrol/PatrolPointController.java

@@ -0,0 +1,128 @@
+package com.pm.web.controller.patrol;
+
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import com.pm.common.annotation.Log;
+import com.pm.common.core.controller.BaseController;
+import com.pm.common.core.domain.AjaxResult;
+import com.pm.common.enums.BusinessType;
+import com.pm.common.config.PageData;
+import com.pm.common.utils.poi.ExcelUtilPageData;
+import com.pm.patrol.service.IPatrolPointService;
+import com.pm.common.core.page.TableDataInfo;
+
+/**
+ * 巡更点信息Controller
+ *
+ * @author pm
+ * @date 2025-06-03
+ */
+@RestController
+@RequestMapping("/patrol/point")
+public class PatrolPointController extends BaseController {
+    
+    @Autowired
+    private IPatrolPointService patrolPointService;
+
+    /**
+     * 查询巡更点列表
+     */
+    @PreAuthorize("@ss.hasPermi('patrol:point:list')")
+    @GetMapping("/list")
+    public TableDataInfo list() {
+        PageData pd = this.getPageData();
+        startPage();
+        List<PageData> list = patrolPointService.selectPatrolPointList(pd);
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取巡更点详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('patrol:point:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
+        return success(patrolPointService.selectPatrolPointById(id));
+    }
+
+    /**
+     * 新增巡更点
+     */
+    @PreAuthorize("@ss.hasPermi('patrol:point:add')")
+    @Log(title = "巡更点信息", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody PageData pd) {
+        return toAjax(patrolPointService.insertPatrolPoint(pd));
+    }
+
+    /**
+     * 修改巡更点
+     */
+    @PreAuthorize("@ss.hasPermi('patrol:point:edit')")
+    @Log(title = "巡更点信息", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody PageData pd) {
+        return toAjax(patrolPointService.updatePatrolPoint(pd));
+    }
+
+    /**
+     * 删除巡更点
+     */
+    @PreAuthorize("@ss.hasPermi('patrol:point:remove')")
+    @Log(title = "巡更点信息", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids) {
+        return toAjax(patrolPointService.deletePatrolPointByIds(ids));
+    }
+
+    /**
+     * 修改巡更点状态
+     */
+    @PreAuthorize("@ss.hasPermi('patrol:point:edit')")
+    @Log(title = "巡更点状态", businessType = BusinessType.UPDATE)
+    @PutMapping("/changeStatus")
+    public AjaxResult changeStatus(@RequestBody PageData pd) {
+        return toAjax(patrolPointService.updatePatrolPointStatus(pd));
+    }
+
+    /**
+     * 导出巡更点列表
+     */
+    @PreAuthorize("@ss.hasPermi('patrol:point:export')")
+    @Log(title = "巡更点信息", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response) {
+        try {
+            PageData pd = this.getPageData();
+            List<PageData> list = patrolPointService.selectPatrolPointList(pd);
+            String filename = "巡更点信息数据";
+            String[] titles = {
+                    "巡更点ID,id",
+                    "巡更点编号,point_code",
+                    "巡更点名称,point_name",
+                    "区域ID,area_id",
+                    "区域名称,area_name",
+                    "楼栋ID,building_id",
+                    "楼栋名称,building_name",
+                    "楼层ID,floor_id",
+                    "楼层名称,floor_name",
+                    "X坐标,position_x",
+                    "Y坐标,position_y",
+                    "Z坐标,position_z",
+                    "巡更点类型,point_type",
+                    "巡更频率,patrol_frequency",
+                    "关联摄像头,camera_ids",
+                    "巡更设备ID,device_id",
+                    "二维码内容,qr_code",
+                    "是否启用,is_active",
+                    "备注,remark"
+            };
+            ExcelUtilPageData.exportXLSX(response, null, null, filename, titles, list);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+}

+ 85 - 0
pm-admin/src/main/java/com/pm/web/controller/patrol/PatrolRecordController.java

@@ -0,0 +1,85 @@
+package com.pm.web.controller.patrol;
+
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import com.pm.common.annotation.Log;
+import com.pm.common.core.controller.BaseController;
+import com.pm.common.core.domain.AjaxResult;
+import com.pm.common.enums.BusinessType;
+import com.pm.common.config.PageData;
+import com.pm.common.utils.poi.ExcelUtilPageData;
+import com.pm.patrol.service.IPatrolRecordService;
+import com.pm.common.core.page.TableDataInfo;
+
+/**
+ * 巡更记录Controller
+ *
+ * @author pm
+ * @date 2025-06-03
+ */
+@RestController
+@RequestMapping("/patrol/record")
+public class PatrolRecordController extends BaseController {
+    
+    @Autowired
+    private IPatrolRecordService patrolRecordService;
+
+    /**
+     * 查询巡更记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('patrol:record:list')")
+    @GetMapping("/list")
+    public TableDataInfo list() {
+        PageData pd = this.getPageData();
+        startPage();
+        List<PageData> list = patrolRecordService.selectPatrolRecordList(pd);
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取巡更记录详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('patrol:record:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
+        return success(patrolRecordService.selectPatrolRecordById(id));
+    }
+
+    /**
+     * 导出巡更记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('patrol:record:export')")
+    @Log(title = "巡更记录", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response) {
+        try {
+            PageData pd = this.getPageData();
+            List<PageData> list = patrolRecordService.selectPatrolRecordList(pd);
+            String filename = "巡更记录数据";
+            String[] titles = {
+                    "记录ID,id",
+                    "巡更记录ID,record_id",
+                    "巡更点编号,point_code",
+                    "巡更点名称,point_name",
+                    "巡更人员ID,patrol_person_id",
+                    "巡更人员姓名,patrol_person_name",
+                    "巡更时间,patrol_time",
+                    "巡更状态,patrol_status",
+                    "GPS经度,gps_longitude",
+                    "GPS纬度,gps_latitude",
+                    "巡检仪序列号,device_sn",
+                    "巡更路线ID,patrol_route_id",
+                    "巡更路线名称,patrol_route_name",
+                    "当日巡更次数,patrol_count",
+                    "异常信息,exception_info",
+                    "备注,remark"
+            };
+            ExcelUtilPageData.exportXLSX(response, null, null, filename, titles, list);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+}

+ 275 - 0
pm-admin/src/main/java/com/pm/web/controller/subsystem/EnergyDeviceController.java

@@ -0,0 +1,275 @@
+package com.pm.web.controller.subsystem;
+
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.pm.common.annotation.Log;
+import com.pm.common.core.controller.BaseController;
+import com.pm.common.core.domain.AjaxResult;
+import com.pm.common.enums.BusinessType;
+import com.pm.common.config.PageData;
+import com.pm.common.utils.poi.ExcelUtilPageData;
+import com.pm.subsystem.service.IEnergyDeviceService;
+import com.pm.common.core.page.TableDataInfo;
+
+/**
+ * 能源管理系统Controller
+ *
+ * @author lxf
+ * @date 2025-06-03
+ */
+@RestController
+@RequestMapping("/subsystem/energy")
+public class EnergyDeviceController extends BaseController
+{
+    @Autowired
+    private IEnergyDeviceService energyDeviceService;
+
+    /**
+     * 查询能源管理设备列表
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:energy:list')")
+    @GetMapping("/devices/list")
+    public TableDataInfo listDevices()
+    {
+        PageData pd = this.getPageData();
+        startPage();
+        List<PageData> list = energyDeviceService.selectEnergyDeviceList(pd);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出能源管理设备列表
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:energy:export')")
+    @Log(title = "能源管理设备", businessType = BusinessType.EXPORT)
+    @PostMapping("/devices/export")
+    public void exportDevices(HttpServletResponse response)
+    {
+        try {
+            PageData pd = this.getPageData();
+            List<PageData> list = energyDeviceService.selectEnergyDeviceList(pd);
+            String filename = "能源管理设备数据";
+            String[] titles = {
+                    "设备ID,id",
+                    "设备编码,deviceCode",
+                    "设备名称,deviceName",
+                    "设备类型,deviceType",
+                    "能耗类型,energyType",
+                    "楼栋,building",
+                    "楼层,floor",
+                    "房间号,roomNo",
+                    "当前功率,currentPower",
+                    "额定功率,ratedPower",
+                    "日能耗,dailyConsumption",
+                    "月能耗,monthlyConsumption",
+                    "年能耗,yearlyConsumption",
+                    "负载率,loadRate",
+                    "设备状态,deviceStatus",
+                    "是否在线,isOnline",
+                    "温度,temperature",
+                    "功率设置,powerSetting",
+                    "阈值,thresholdValue",
+                    "设备描述,description"
+            };
+            ExcelUtilPageData.exportXLSX(response, null, null, filename, titles, list);
+        }catch (Exception e){
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 获取能源管理设备详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:energy:query')")
+    @GetMapping(value = "/devices/{id}")
+    public AjaxResult getDeviceInfo(@PathVariable("id") Long id)
+    {
+        return success(energyDeviceService.selectEnergyDeviceById(id));
+    }
+
+    /**
+     * 新增能源管理设备
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:energy:add')")
+    @Log(title = "能源管理设备", businessType = BusinessType.INSERT)
+    @PostMapping("/devices")
+    public AjaxResult addDevice(@RequestBody PageData pd)
+    {
+        return toAjax(energyDeviceService.insertEnergyDevice(pd));
+    }
+
+    /**
+     * 修改能源管理设备
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:energy:edit')")
+    @Log(title = "能源管理设备", businessType = BusinessType.UPDATE)
+    @PutMapping("/devices")
+    public AjaxResult editDevice(@RequestBody PageData pd)
+    {
+        return toAjax(energyDeviceService.updateEnergyDevice(pd));
+    }
+
+    /**
+     * 删除能源管理设备
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:energy:remove')")
+    @Log(title = "能源管理设备", businessType = BusinessType.DELETE)
+    @DeleteMapping("/devices/{ids}")
+    public AjaxResult removeDevices(@PathVariable Long[] ids)
+    {
+        return toAjax(energyDeviceService.deleteEnergyDeviceByIds(ids));
+    }
+
+    /**
+     * 获取能耗统计数据
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:energy:statistics')")
+    @GetMapping("/statistics")
+    public AjaxResult getStatistics()
+    {
+        PageData pd = this.getPageData();
+        PageData statistics = energyDeviceService.getEnergyStatistics(pd);
+        return success(statistics);
+    }
+
+    /**
+     * 查询能耗历史记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:energy:history')")
+    @GetMapping("/history/list")
+    public TableDataInfo listHistory()
+    {
+        PageData pd = this.getPageData();
+        startPage();
+        List<PageData> list = energyDeviceService.selectEnergyHistoryList(pd);
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取实时能耗数据
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:energy:realtime')")
+    @GetMapping("/realtime/{deviceCode}")
+    public AjaxResult getRealTimeData(@PathVariable("deviceCode") String deviceCode)
+    {
+        PageData realTimeData = energyDeviceService.getRealTimeEnergyData(deviceCode);
+        return success(realTimeData);
+    }
+
+    /**
+     * 查询能耗告警信息列表
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:energy:alarm')")
+    @GetMapping("/alarms/list")
+    public TableDataInfo listAlarms()
+    {
+        PageData pd = this.getPageData();
+        startPage();
+        List<PageData> list = energyDeviceService.selectEnergyAlarmList(pd);
+        return getDataTable(list);
+    }
+
+    /**
+     * 处理能耗告警
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:energy:alarm:handle')")
+    @Log(title = "能耗告警处理", businessType = BusinessType.UPDATE)
+    @PutMapping("/alarms/{id}")
+    public AjaxResult handleAlarm(@PathVariable("id") Long id, @RequestBody PageData pd)
+    {
+        pd.put("id", id);
+        pd.put("handleUser", getUsername());
+        return toAjax(energyDeviceService.handleEnergyAlarm(pd));
+    }
+
+    /**
+     * 设备控制
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:energy:control')")
+    @Log(title = "设备控制", businessType = BusinessType.OTHER)
+    @PostMapping("/control")
+    public AjaxResult controlDevice(@RequestBody PageData pd)
+    {
+        pd.put("controlUser", getUsername());
+        int result = energyDeviceService.controlDevice(pd);
+        if (result > 0) {
+            return success("设备控制指令发送成功");
+        } else {
+            return error("设备控制指令发送失败");
+        }
+    }
+
+    /**
+     * 查询设备控制记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:energy:control:list')")
+    @GetMapping("/control/list")
+    public TableDataInfo listControlRecords()
+    {
+        PageData pd = this.getPageData();
+        startPage();
+        List<PageData> list = energyDeviceService.selectDeviceControlList(pd);
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取能耗曲线数据
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:energy:chart')")
+    @GetMapping("/chart")
+    public AjaxResult getChartData()
+    {
+        PageData pd = this.getPageData();
+        List<PageData> chartData = energyDeviceService.getEnergyChartData(pd);
+        return success(chartData);
+    }
+
+    /**
+     * 生成能耗报表
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:energy:report')")
+    @GetMapping("/report")
+    public AjaxResult generateReport()
+    {
+        PageData pd = this.getPageData();
+        PageData reportData = energyDeviceService.generateEnergyReport(pd);
+        return success(reportData);
+    }
+
+    /**
+     * 设置能耗阈值
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:energy:threshold')")
+    @Log(title = "设置能耗阈值", businessType = BusinessType.UPDATE)
+    @PostMapping("/threshold")
+    public AjaxResult setThreshold(@RequestBody PageData pd)
+    {
+        return toAjax(energyDeviceService.setEnergyThreshold(pd));
+    }
+
+    /**
+     * 数据同步
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:energy:sync')")
+    @Log(title = "能耗数据同步", businessType = BusinessType.OTHER)
+    @PostMapping("/sync")
+    public AjaxResult syncData(@RequestBody PageData pd)
+    {
+        int result = energyDeviceService.syncEnergyData(pd);
+        if (result > 0) {
+            return success("数据同步成功");
+        } else {
+            return error("数据同步失败");
+        }
+    }
+}

+ 476 - 0
pm-admin/src/main/java/com/pm/web/controller/subsystem/IntrusionAlarmController.java

@@ -0,0 +1,476 @@
+package com.pm.web.controller.subsystem;
+
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.pm.common.annotation.Log;
+import com.pm.common.core.controller.BaseController;
+import com.pm.common.core.domain.AjaxResult;
+import com.pm.common.enums.BusinessType;
+import com.pm.common.config.PageData;
+import com.pm.common.utils.poi.ExcelUtilPageData;
+import com.pm.subsystem.service.IIntrusionAlarmService;
+import com.pm.common.core.page.TableDataInfo;
+
+/**
+ * 入侵报警系统Controller
+ *
+ * @author lxf
+ * @date 2025-06-03
+ */
+@RestController
+@RequestMapping("/subsystem/intrusion")
+public class IntrusionAlarmController extends BaseController
+{
+    @Autowired
+    private IIntrusionAlarmService intrusionAlarmService;
+
+    // ==================== 报警点管理 ====================
+    
+    /**
+     * 查询报警点列表
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:intrusion:alarmpoints:list')")
+    @GetMapping("/alarmpoints/list")
+    public TableDataInfo listAlarmPoints()
+    {
+        PageData pd = this.getPageData();
+        startPage();
+        List<PageData> list = intrusionAlarmService.selectAlarmPointList(pd);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出报警点列表
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:intrusion:alarmpoints:export')")
+    @Log(title = "报警点", businessType = BusinessType.EXPORT)
+    @PostMapping("/alarmpoints/export")
+    public void exportAlarmPoints(HttpServletResponse response)
+    {
+        try {
+            PageData pd = this.getPageData();
+            List<PageData> list = intrusionAlarmService.selectAlarmPointList(pd);
+            String filename = "报警点数据";
+            String[] titles = {
+                    "设备ID,id",
+                    "设备编号,deviceCode",
+                    "设备名称,deviceName",
+                    "报警类型,alarmType",
+                    "防区编号,zoneCode",
+                    "防区名称,zoneName",
+                    "楼栋,building",
+                    "楼层,floor",
+                    "详细位置,locationDetail",
+                    "设备状态,deviceStatus",
+                    "布防状态,armStatus",
+                    "敏感度,sensitivity",
+                    "延时时间,delayTime"
+            };
+            ExcelUtilPageData.exportXLSX(response, null, null, filename, titles, list);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 获取报警点详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:intrusion:alarmpoints:query')")
+    @GetMapping(value = "/alarmpoints/{id}")
+    public AjaxResult getAlarmPointInfo(@PathVariable("id") Long id)
+    {
+        return success(intrusionAlarmService.selectAlarmPointById(id));
+    }
+
+    /**
+     * 新增报警点
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:intrusion:alarmpoints:add')")
+    @Log(title = "报警点", businessType = BusinessType.INSERT)
+    @PostMapping("/alarmpoints")
+    public AjaxResult addAlarmPoint(@RequestBody PageData pd)
+    {
+        return toAjax(intrusionAlarmService.insertAlarmPoint(pd));
+    }
+
+    /**
+     * 修改报警点
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:intrusion:alarmpoints:edit')")
+    @Log(title = "报警点", businessType = BusinessType.UPDATE)
+    @PutMapping("/alarmpoints")
+    public AjaxResult editAlarmPoint(@RequestBody PageData pd)
+    {
+        return toAjax(intrusionAlarmService.updateAlarmPoint(pd));
+    }
+
+    /**
+     * 删除报警点
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:intrusion:alarmpoints:remove')")
+    @Log(title = "报警点", businessType = BusinessType.DELETE)
+    @DeleteMapping("/alarmpoints/{ids}")
+    public AjaxResult removeAlarmPoints(@PathVariable Long[] ids)
+    {
+        return toAjax(intrusionAlarmService.deleteAlarmPointByIds(ids));
+    }
+
+    /**
+     * 切换布防状态
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:intrusion:alarmpoints:arm')")
+    @Log(title = "布防控制", businessType = BusinessType.OTHER)
+    @PostMapping("/alarmpoints/arm")
+    public AjaxResult toggleArmStatus(@RequestBody PageData pd)
+    {
+        int result = intrusionAlarmService.toggleArmStatus(pd);
+        if (result > 0) {
+            return success("布防状态切换成功");
+        } else {
+            return error("布防状态切换失败");
+        }
+    }
+
+    /**
+     * 批量布防/撤防
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:intrusion:alarmpoints:batch-arm')")
+    @Log(title = "批量布防控制", businessType = BusinessType.OTHER)
+    @PostMapping("/alarmpoints/batch-arm")
+    public AjaxResult batchArmControl(@RequestBody PageData pd)
+    {
+        int result = intrusionAlarmService.batchArmControl(pd);
+        return success("成功操作 " + result + " 个设备");
+    }
+
+    /**
+     * 测试报警设备
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:intrusion:alarmpoints:test')")
+    @Log(title = "设备测试", businessType = BusinessType.OTHER)
+    @PostMapping("/alarmpoints/test/{deviceCode}")
+    public AjaxResult testAlarmDevice(@PathVariable("deviceCode") String deviceCode)
+    {
+        int result = intrusionAlarmService.testAlarmDevice(deviceCode);
+        if (result > 0) {
+            return success("设备测试指令发送成功");
+        } else {
+            return error("设备测试指令发送失败");
+        }
+    }
+
+    /**
+     * 更新设备配置
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:intrusion:alarmpoints:config')")
+    @Log(title = "设备配置", businessType = BusinessType.UPDATE)
+    @PutMapping("/alarmpoints/config")
+    public AjaxResult updateDeviceConfig(@RequestBody PageData pd)
+    {
+        return toAjax(intrusionAlarmService.updateDeviceConfig(pd));
+    }
+
+    // ==================== 报警记录管理 ====================
+
+    /**
+     * 查询报警记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:intrusion:alarms:list')")
+    @GetMapping("/alarms/list")
+    public TableDataInfo listAlarms()
+    {
+        PageData pd = this.getPageData();
+        startPage();
+        List<PageData> list = intrusionAlarmService.selectAlarmList(pd);
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取报警记录详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:intrusion:alarms:query')")
+    @GetMapping(value = "/alarms/{id}")
+    public AjaxResult getAlarmInfo(@PathVariable("id") Long id)
+    {
+        return success(intrusionAlarmService.selectAlarmById(id));
+    }
+
+    /**
+     * 处理报警
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:intrusion:alarms:process')")
+    @Log(title = "报警处理", businessType = BusinessType.UPDATE)
+    @PutMapping("/alarms/process")
+    public AjaxResult processAlarm(@RequestBody PageData pd)
+    {
+        pd.put("handleUser", getUsername());
+        return toAjax(intrusionAlarmService.processAlarm(pd));
+    }
+
+    /**
+     * 批量处理报警
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:intrusion:alarms:batch-process')")
+    @Log(title = "批量报警处理", businessType = BusinessType.UPDATE)
+    @PutMapping("/alarms/batch-process")
+    public AjaxResult batchProcessAlarms(@RequestBody PageData pd)
+    {
+        pd.put("handleUser", getUsername());
+        int result = intrusionAlarmService.batchProcessAlarms(pd);
+        return success("成功处理 " + result + " 条报警");
+    }
+
+    /**
+     * 确认报警
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:intrusion:alarms:confirm')")
+    @Log(title = "确认报警", businessType = BusinessType.UPDATE)
+    @PutMapping("/alarms/confirm/{id}")
+    public AjaxResult confirmAlarm(@PathVariable("id") Long id)
+    {
+        return toAjax(intrusionAlarmService.confirmAlarm(id));
+    }
+
+    /**
+     * 忽略报警
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:intrusion:alarms:ignore')")
+    @Log(title = "忽略报警", businessType = BusinessType.UPDATE)
+    @PutMapping("/alarms/ignore/{id}")
+    public AjaxResult ignoreAlarm(@PathVariable("id") Long id, @RequestBody PageData pd)
+    {
+        pd.put("handleUser", getUsername());
+        return toAjax(intrusionAlarmService.ignoreAlarm(id, pd));
+    }
+
+    // ==================== 防区管理 ====================
+
+    /**
+     * 查询防区列表
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:intrusion:zones:list')")
+    @GetMapping("/zones/list")
+    public TableDataInfo listZones()
+    {
+        PageData pd = this.getPageData();
+        startPage();
+        List<PageData> list = intrusionAlarmService.selectZoneList(pd);
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取防区详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:intrusion:zones:query')")
+    @GetMapping(value = "/zones/{id}")
+    public AjaxResult getZoneInfo(@PathVariable("id") Long id)
+    {
+        return success(intrusionAlarmService.selectZoneById(id));
+    }
+
+    /**
+     * 新增防区
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:intrusion:zones:add')")
+    @Log(title = "防区", businessType = BusinessType.INSERT)
+    @PostMapping("/zones")
+    public AjaxResult addZone(@RequestBody PageData pd)
+    {
+        return toAjax(intrusionAlarmService.insertZone(pd));
+    }
+
+    /**
+     * 修改防区
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:intrusion:zones:edit')")
+    @Log(title = "防区", businessType = BusinessType.UPDATE)
+    @PutMapping("/zones")
+    public AjaxResult editZone(@RequestBody PageData pd)
+    {
+        return toAjax(intrusionAlarmService.updateZone(pd));
+    }
+
+    /**
+     * 删除防区
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:intrusion:zones:remove')")
+    @Log(title = "防区", businessType = BusinessType.DELETE)
+    @DeleteMapping("/zones/{id}")
+    public AjaxResult removeZone(@PathVariable("id") Long id)
+    {
+        return toAjax(intrusionAlarmService.deleteZoneById(id));
+    }
+
+    /**
+     * 防区布防/撤防
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:intrusion:zones:toggle')")
+    @Log(title = "防区状态切换", businessType = BusinessType.UPDATE)
+    @PutMapping("/zones/toggle/{id}")
+    public AjaxResult toggleZoneStatus(@PathVariable("id") Long id, @RequestBody PageData pd)
+    {
+        return toAjax(intrusionAlarmService.toggleZoneStatus(id, pd));
+    }
+
+    /**
+     * 批量防区操作
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:intrusion:zones:batch-control')")
+    @Log(title = "批量防区操作", businessType = BusinessType.OTHER)
+    @PostMapping("/zones/batch-control")
+    public AjaxResult batchZoneControl(@RequestBody PageData pd)
+    {
+        int result = intrusionAlarmService.batchZoneControl(pd);
+        return success("成功操作 " + result + " 个防区");
+    }
+
+    /**
+     * 查询防区设备列表
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:intrusion:zones:devices')")
+    @GetMapping("/zones/devices/{zoneCode}")
+    public AjaxResult listZoneDevices(@PathVariable("zoneCode") String zoneCode)
+    {
+        List<PageData> list = intrusionAlarmService.selectZoneDeviceList(zoneCode);
+        return success(list);
+    }
+
+    // ==================== 统计查询 ====================
+
+    /**
+     * 获取报警统计数据
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:intrusion:statistics')")
+    @GetMapping("/statistics")
+    public AjaxResult getAlarmStatistics()
+    {
+        PageData pd = this.getPageData();
+        PageData statistics = intrusionAlarmService.getAlarmStatistics(pd);
+        return success(statistics);
+    }
+
+    /**
+     * 获取实时状态
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:intrusion:realtime')")
+    @GetMapping("/realtime/status")
+    public AjaxResult getRealTimeStatus()
+    {
+        PageData realTimeStatus = intrusionAlarmService.getRealTimeStatus();
+        return success(realTimeStatus);
+    }
+
+    /**
+     * 获取报警趋势数据
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:intrusion:trend')")
+    @GetMapping("/statistics/trend")
+    public AjaxResult getAlarmTrend()
+    {
+        PageData pd = this.getPageData();
+        List<PageData> trendData = intrusionAlarmService.getAlarmTrend(pd);
+        return success(trendData);
+    }
+
+    // ==================== 其他功能 ====================
+
+    /**
+     * 设备健康检测
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:intrusion:health')")
+    @PostMapping("/health/check/{deviceCode}")
+    public AjaxResult checkDeviceHealth(@PathVariable("deviceCode") String deviceCode)
+    {
+        PageData healthData = intrusionAlarmService.checkDeviceHealth(deviceCode);
+        return success(healthData);
+    }
+
+    /**
+     * 获取设备配置
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:intrusion:config')")
+    @GetMapping("/config/{deviceCode}")
+    public AjaxResult getDeviceConfig(@PathVariable("deviceCode") String deviceCode)
+    {
+        PageData configData = intrusionAlarmService.getDeviceConfig(deviceCode);
+        return success(configData);
+    }
+
+    /**
+     * 重置设备
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:intrusion:reset')")
+    @Log(title = "设备重置", businessType = BusinessType.OTHER)
+    @PostMapping("/device/reset/{deviceCode}")
+    public AjaxResult resetDevice(@PathVariable("deviceCode") String deviceCode)
+    {
+        int result = intrusionAlarmService.resetDevice(deviceCode);
+        if (result > 0) {
+            return success("设备重置成功");
+        } else {
+            return error("设备重置失败");
+        }
+    }
+
+    /**
+     * 查询维护记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:intrusion:maintenance')")
+    @GetMapping("/maintenance/list")
+    public TableDataInfo listMaintenanceRecords()
+    {
+        PageData pd = this.getPageData();
+        startPage();
+        List<PageData> list = intrusionAlarmService.selectMaintenanceRecordList(pd);
+        return getDataTable(list);
+    }
+
+    /**
+     * 新增维护记录
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:intrusion:maintenance:add')")
+    @Log(title = "维护记录", businessType = BusinessType.INSERT)
+    @PostMapping("/maintenance")
+    public AjaxResult addMaintenanceRecord(@RequestBody PageData pd)
+    {
+        pd.put("createUser", getUsername());
+        return toAjax(intrusionAlarmService.addMaintenanceRecord(pd));
+    }
+
+    /**
+     * 生成报警报表
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:intrusion:report')")
+    @PostMapping("/report/generate")
+    public AjaxResult generateAlarmReport(@RequestBody PageData pd)
+    {
+        PageData reportData = intrusionAlarmService.generateAlarmReport(pd);
+        return success(reportData);
+    }
+
+    /**
+     * 数据同步
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:intrusion:sync')")
+    @Log(title = "入侵报警数据同步", businessType = BusinessType.OTHER)
+    @PostMapping("/sync")
+    public AjaxResult syncIntrusionData(@RequestBody PageData pd)
+    {
+        int result = intrusionAlarmService.syncIntrusionData(pd);
+        if (result > 0) {
+            return success("数据同步成功");
+        } else {
+            return error("数据同步失败");
+        }
+    }
+}

+ 353 - 0
pm-admin/src/main/java/com/pm/web/controller/subsystem/VideoDeviceController.java

@@ -0,0 +1,353 @@
+package com.pm.web.controller.subsystem;
+
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.pm.common.annotation.Log;
+import com.pm.common.core.controller.BaseController;
+import com.pm.common.core.domain.AjaxResult;
+import com.pm.common.enums.BusinessType;
+import com.pm.common.config.PageData;
+import com.pm.common.utils.poi.ExcelUtilPageData;
+import com.pm.subsystem.service.IVideoDeviceService;
+import com.pm.common.core.page.TableDataInfo;
+
+/**
+ * 视频监控系统Controller
+ *
+ * @author lxf
+ * @date 2025-06-03
+ */
+@RestController
+@RequestMapping("/subsystem/video")
+public class VideoDeviceController extends BaseController
+{
+    @Autowired
+    private IVideoDeviceService videoDeviceService;
+
+    /**
+     * 查询摄像机设备列表
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:video:list')")
+    @GetMapping("/cameras/list")
+    public TableDataInfo listCameras()
+    {
+        PageData pd = this.getPageData();
+        startPage();
+        List<PageData> list = videoDeviceService.selectCameraList(pd);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出摄像机设备列表
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:video:export')")
+    @Log(title = "摄像机设备", businessType = BusinessType.EXPORT)
+    @PostMapping("/cameras/export")
+    public void exportCameras(HttpServletResponse response)
+    {
+        try {
+            PageData pd = this.getPageData();
+            List<PageData> list = videoDeviceService.selectCameraList(pd);
+            String filename = "摄像机设备数据";
+            String[] titles = {
+                    "摄像机ID,id",
+                    "摄像机编号,cameraCode",
+                    "摄像机名称,cameraName",
+                    "监控区域,monitorArea",
+                    "楼栋,building",
+                    "楼层,floor",
+                    "详细位置,locationDetail",
+                    "IP地址,ipAddress",
+                    "端口,port",
+                    "品牌,brand",
+                    "型号,model",
+                    "分辨率,resolution",
+                    "帧率,frameRate",
+                    "设备状态,deviceStatus",
+                    "云台支持,ptzSupport",
+                    "红外支持,infraredSupport",
+                    "录像状态,recordingStatus"
+            };
+            ExcelUtilPageData.exportXLSX(response, null, null, filename, titles, list);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 获取摄像机设备详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:video:query')")
+    @GetMapping(value = "/cameras/{id}")
+    public AjaxResult getCameraInfo(@PathVariable("id") Long id)
+    {
+        return success(videoDeviceService.selectCameraById(id));
+    }
+
+    /**
+     * 新增摄像机设备
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:video:add')")
+    @Log(title = "摄像机设备", businessType = BusinessType.INSERT)
+    @PostMapping("/cameras")
+    public AjaxResult addCamera(@RequestBody PageData pd)
+    {
+        return toAjax(videoDeviceService.insertCamera(pd));
+    }
+
+    /**
+     * 修改摄像机设备
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:video:edit')")
+    @Log(title = "摄像机设备", businessType = BusinessType.UPDATE)
+    @PutMapping("/cameras")
+    public AjaxResult editCamera(@RequestBody PageData pd)
+    {
+        return toAjax(videoDeviceService.updateCamera(pd));
+    }
+
+    /**
+     * 删除摄像机设备
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:video:remove')")
+    @Log(title = "摄像机设备", businessType = BusinessType.DELETE)
+    @DeleteMapping("/cameras/{ids}")
+    public AjaxResult removeCameras(@PathVariable Long[] ids)
+    {
+        return toAjax(videoDeviceService.deleteCameraByIds(ids));
+    }
+
+    /**
+     * 获取摄像机视频流
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:video:stream')")
+    @GetMapping("/stream/{cameraCode}")
+    public AjaxResult getCameraStream(@PathVariable("cameraCode") String cameraCode)
+    {
+        PageData streamData = videoDeviceService.getCameraStream(cameraCode);
+        return success(streamData);
+    }
+
+    /**
+     * 云台控制
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:video:ptz')")
+    @Log(title = "云台控制", businessType = BusinessType.OTHER)
+    @PostMapping("/ptz/control")
+    public AjaxResult ptzControl(@RequestBody PageData pd)
+    {
+        pd.put("controlUser", getUsername());
+        int result = videoDeviceService.ptzControl(pd);
+        if (result > 0) {
+            return success("云台控制指令发送成功");
+        } else {
+            return error("云台控制指令发送失败");
+        }
+    }
+
+    /**
+     * 录像控制
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:video:recording')")
+    @Log(title = "录像控制", businessType = BusinessType.OTHER)
+    @PostMapping("/recording/control")
+    public AjaxResult recordingControl(@RequestBody PageData pd)
+    {
+        int result = videoDeviceService.recordingControl(pd);
+        if (result > 0) {
+            return success("录像控制指令执行成功");
+        } else {
+            return error("录像控制指令执行失败");
+        }
+    }
+
+    /**
+     * 抓拍
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:video:snapshot')")
+    @Log(title = "摄像机抓拍", businessType = BusinessType.OTHER)
+    @PostMapping("/snapshot/{cameraCode}")
+    public AjaxResult takeSnapshot(@PathVariable("cameraCode") String cameraCode)
+    {
+        int result = videoDeviceService.takeSnapshot(cameraCode);
+        if (result > 0) {
+            return success("抓拍成功");
+        } else {
+            return error("抓拍失败");
+        }
+    }
+
+    /**
+     * 查询联动规则列表
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:video:linkage')")
+    @GetMapping("/linkage/list")
+    public TableDataInfo listLinkageRules()
+    {
+        PageData pd = this.getPageData();
+        startPage();
+        List<PageData> list = videoDeviceService.selectLinkageRuleList(pd);
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取联动规则详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:video:linkage')")
+    @GetMapping(value = "/linkage/{id}")
+    public AjaxResult getLinkageRuleInfo(@PathVariable("id") Long id)
+    {
+        return success(videoDeviceService.selectLinkageRuleById(id));
+    }
+
+    /**
+     * 新增联动规则
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:video:linkage:add')")
+    @Log(title = "联动规则", businessType = BusinessType.INSERT)
+    @PostMapping("/linkage")
+    public AjaxResult addLinkageRule(@RequestBody PageData pd)
+    {
+        return toAjax(videoDeviceService.insertLinkageRule(pd));
+    }
+
+    /**
+     * 修改联动规则
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:video:linkage:edit')")
+    @Log(title = "联动规则", businessType = BusinessType.UPDATE)
+    @PutMapping("/linkage")
+    public AjaxResult editLinkageRule(@RequestBody PageData pd)
+    {
+        return toAjax(videoDeviceService.updateLinkageRule(pd));
+    }
+
+    /**
+     * 删除联动规则
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:video:linkage:remove')")
+    @Log(title = "联动规则", businessType = BusinessType.DELETE)
+    @DeleteMapping("/linkage/{id}")
+    public AjaxResult removeLinkageRule(@PathVariable("id") Long id)
+    {
+        return toAjax(videoDeviceService.deleteLinkageRuleById(id));
+    }
+
+    /**
+     * 启用/禁用联动规则
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:video:linkage:toggle')")
+    @Log(title = "联动规则状态切换", businessType = BusinessType.UPDATE)
+    @PutMapping("/linkage/toggle/{id}")
+    public AjaxResult toggleLinkageRule(@PathVariable("id") Long id, @RequestBody PageData pd)
+    {
+        return toAjax(videoDeviceService.toggleLinkageRule(id, pd));
+    }
+
+    /**
+     * 查询联动执行记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:video:linkage:records')")
+    @GetMapping("/linkage/records/list")
+    public TableDataInfo listLinkageRecords()
+    {
+        PageData pd = this.getPageData();
+        startPage();
+        List<PageData> list = videoDeviceService.selectLinkageRecordList(pd);
+        return getDataTable(list);
+    }
+
+    /**
+     * 查询录像文件列表
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:video:recordings')")
+    @GetMapping("/recordings/list")
+    public TableDataInfo listRecordingFiles()
+    {
+        PageData pd = this.getPageData();
+        startPage();
+        List<PageData> list = videoDeviceService.selectRecordingFileList(pd);
+        return getDataTable(list);
+    }
+
+    /**
+     * 查询预置位列表
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:video:presets')")
+    @GetMapping("/presets/{cameraCode}")
+    public AjaxResult listPresets(@PathVariable("cameraCode") String cameraCode)
+    {
+        List<PageData> list = videoDeviceService.selectPresetList(cameraCode);
+        return success(list);
+    }
+
+    /**
+     * 设置预置位
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:video:presets:set')")
+    @Log(title = "设置预置位", businessType = BusinessType.INSERT)
+    @PostMapping("/presets")
+    public AjaxResult setPreset(@RequestBody PageData pd)
+    {
+        return toAjax(videoDeviceService.setPreset(pd));
+    }
+
+    /**
+     * 删除预置位
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:video:presets:remove')")
+    @Log(title = "删除预置位", businessType = BusinessType.DELETE)
+    @DeleteMapping("/presets/{id}")
+    public AjaxResult deletePreset(@PathVariable("id") Long id)
+    {
+        return toAjax(videoDeviceService.deletePresetById(id));
+    }
+
+    /**
+     * 摄像机健康检测
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:video:health')")
+    @PostMapping("/health/check/{cameraCode}")
+    public AjaxResult checkCameraHealth(@PathVariable("cameraCode") String cameraCode)
+    {
+        PageData healthData = videoDeviceService.checkCameraHealth(cameraCode);
+        return success(healthData);
+    }
+
+    /**
+     * 获取摄像机统计数据
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:video:statistics')")
+    @GetMapping("/statistics")
+    public AjaxResult getCameraStatistics()
+    {
+        PageData statistics = videoDeviceService.getCameraStatistics();
+        return success(statistics);
+    }
+
+    /**
+     * 数据同步
+     */
+    @PreAuthorize("@ss.hasPermi('subsystem:video:sync')")
+    @Log(title = "视频数据同步", businessType = BusinessType.OTHER)
+    @PostMapping("/sync")
+    public AjaxResult syncVideoData(@RequestBody PageData pd)
+    {
+        int result = videoDeviceService.syncVideoData(pd);
+        if (result > 0) {
+            return success("数据同步成功");
+        } else {
+            return error("数据同步失败");
+        }
+    }
+}

+ 59 - 0
pm-system/src/main/java/com/pm/patrol/mapper/PatrolPointMapper.java

@@ -0,0 +1,59 @@
+// PatrolPointMapper.java
+package com.pm.patrol.mapper;
+
+import java.util.List;
+import com.pm.common.config.PageData;
+
+/**
+ * 巡更点信息Mapper接口
+ *
+ * @author pm
+ * @date 2025-06-03
+ */
+public interface PatrolPointMapper {
+    /**
+     * 查询巡更点信息
+     */
+    public PageData selectPatrolPointById(Long id);
+
+    /**
+     * 查询巡更点列表
+     */
+    public List<PageData> selectPatrolPointList(PageData pd);
+
+    /**
+     * 新增巡更点
+     */
+    public int insertPatrolPoint(PageData pd);
+
+    /**
+     * 修改巡更点
+     */
+    public int updatePatrolPoint(PageData pd);
+
+    /**
+     * 删除巡更点
+     */
+    public int deletePatrolPointById(Long id);
+
+    /**
+     * 批量删除巡更点
+     */
+    public int deletePatrolPointByIds(Long[] ids);
+
+    /**
+     * 修改巡更点状态
+     */
+    public int updatePatrolPointStatus(PageData pd);
+
+    /**
+     * 根据路线ID查询巡更点
+     */
+    public List<PageData> selectPointsByRouteId(String routeId);
+
+    /**
+     * 获取活跃巡更点总数
+     */
+    public int getTotalActivePoints();
+}
+

+ 73 - 0
pm-system/src/main/java/com/pm/patrol/mapper/PatrolRecordMapper.java

@@ -0,0 +1,73 @@
+// PatrolRecordMapper.java
+package com.pm.patrol.mapper;
+
+import java.util.List;
+import com.pm.common.config.PageData;
+
+/**
+ * 巡更记录Mapper接口
+ *
+ * @author pm
+ * @date 2025-06-03
+ */
+public interface PatrolRecordMapper {
+    /**
+     * 查询巡更记录
+     */
+    public PageData selectPatrolRecordById(Long id);
+
+    /**
+     * 查询巡更记录列表
+     */
+    public List<PageData> selectPatrolRecordList(PageData pd);
+
+    /**
+     * 新增巡更记录
+     */
+    public int insertPatrolRecord(PageData pd);
+
+    /**
+     * 修改巡更记录
+     */
+    public int updatePatrolRecord(PageData pd);
+
+    /**
+     * 删除巡更记录
+     */
+    public int deletePatrolRecordById(Long id);
+
+    /**
+     * 查询最近的巡更记录
+     */
+    public List<PageData> selectRecentPatrolRecords(PageData pd);
+
+    /**
+     * 获取今日巡更总数
+     */
+    public int getTodayPatrolCount(PageData pd);
+
+    /**
+     * 获取今日正常巡更数
+     */
+    public int getTodayNormalCount(PageData pd);
+
+    /**
+     * 获取今日异常巡更数
+     */
+    public int getTodayAbnormalCount(PageData pd);
+
+    /**
+     * 获取今日超时巡更数
+     */
+    public int getTodayTimeoutCount(PageData pd);
+
+    /**
+     * 获取活跃巡更人员
+     */
+    public List<PageData> getActivePersonnel(PageData pd);
+
+    /**
+     * 获取今日已巡更的点位数
+     */
+    public int getTodayPatrolledPoints(PageData pd);
+}

+ 34 - 0
pm-system/src/main/java/com/pm/patrol/service/IPatrolMonitorService.java

@@ -0,0 +1,34 @@
+// IPatrolMonitorService.java
+package com.pm.patrol.service;
+
+import java.util.List;
+import java.util.Map;
+import com.pm.common.config.PageData;
+
+/**
+ * 巡更监控Service接口
+ *
+ * @author pm
+ * @date 2025-06-03
+ */
+public interface IPatrolMonitorService {
+    /**
+     * 获取实时巡更数据
+     */
+    public List<PageData> getRealtimePatrolData();
+
+    /**
+     * 获取巡更路线信息
+     */
+    public PageData getPatrolRouteInfo(String routeId);
+
+    /**
+     * 获取摄像头信息
+     */
+    public PageData getCameraStreamInfo(String cameraId);
+
+    /**
+     * 获取巡更统计数据
+     */
+    public Map<String, Object> getPatrolStatistics(PageData pd);
+}

+ 54 - 0
pm-system/src/main/java/com/pm/patrol/service/IPatrolPointService.java

@@ -0,0 +1,54 @@
+// IPatrolPointService.java
+package com.pm.patrol.service;
+
+import java.util.List;
+import java.util.Map;
+
+import com.pm.common.config.PageData;
+
+/**
+ * 巡更点信息Service接口
+ *
+ * @author pm
+ * @date 2025-06-03
+ */
+public interface IPatrolPointService {
+    /**
+     * 查询巡更点信息
+     */
+    public PageData selectPatrolPointById(Long id);
+
+    /**
+     * 查询巡更点列表
+     */
+    public List<PageData> selectPatrolPointList(PageData pd);
+
+    /**
+     * 新增巡更点
+     */
+    public int insertPatrolPoint(PageData pd);
+
+    /**
+     * 修改巡更点
+     */
+    public int updatePatrolPoint(PageData pd);
+
+    /**
+     * 批量删除巡更点
+     */
+    public int deletePatrolPointByIds(Long[] ids);
+
+    /**
+     * 删除巡更点信息
+     */
+    public int deletePatrolPointById(Long id);
+
+    /**
+     * 修改巡更点状态
+     */
+    public int updatePatrolPointStatus(PageData pd);
+}
+
+
+
+

+ 37 - 0
pm-system/src/main/java/com/pm/patrol/service/IPatrolRecordService.java

@@ -0,0 +1,37 @@
+package com.pm.patrol.service;
+
+import java.util.List;
+import com.pm.common.config.PageData;
+
+/**
+ * 巡更记录Service接口
+ *
+ * @author pm
+ * @date 2025-06-03
+ */
+public interface IPatrolRecordService {
+    /**
+     * 查询巡更记录
+     */
+    public PageData selectPatrolRecordById(Long id);
+
+    /**
+     * 查询巡更记录列表
+     */
+    public List<PageData> selectPatrolRecordList(PageData pd);
+
+    /**
+     * 新增巡更记录
+     */
+    public int insertPatrolRecord(PageData pd);
+
+    /**
+     * 修改巡更记录
+     */
+    public int updatePatrolRecord(PageData pd);
+
+    /**
+     * 删除巡更记录
+     */
+    public int deletePatrolRecordById(Long id);
+}

+ 85 - 0
pm-system/src/main/java/com/pm/patrol/service/impl/PatrolMonitorServiceImpl.java

@@ -0,0 +1,85 @@
+package com.pm.patrol.service.impl;
+
+import java.util.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.pm.patrol.mapper.PatrolRecordMapper;
+import com.pm.patrol.mapper.PatrolPointMapper;
+import com.pm.patrol.service.IPatrolMonitorService;
+import com.pm.common.config.PageData;
+
+/**
+ * 巡更监控Service业务层处理
+ *
+ * @author pm
+ * @date 2025-06-03
+ */
+@Service
+public class PatrolMonitorServiceImpl implements IPatrolMonitorService {
+    
+    @Autowired
+    private PatrolRecordMapper patrolRecordMapper;
+    
+    @Autowired
+    private PatrolPointMapper patrolPointMapper;
+
+    @Override
+    public List<PageData> getRealtimePatrolData() {
+        // 获取最近的巡更记录(如最近30分钟内的)
+        PageData pd = new PageData();
+        pd.put("recentMinutes", 30);
+        return patrolRecordMapper.selectRecentPatrolRecords(pd);
+    }
+
+    @Override
+    public PageData getPatrolRouteInfo(String routeId) {
+        // 获取巡更路线信息,包括路线上的所有巡更点
+        PageData routeInfo = new PageData();
+        routeInfo.put("routeId", routeId);
+        
+        // 获取路线基本信息
+        // 这里可以调用路线相关的mapper
+        
+        // 获取路线上的巡更点
+        List<PageData> points = patrolPointMapper.selectPointsByRouteId(routeId);
+        routeInfo.put("points", points);
+        
+        return routeInfo;
+    }
+
+    @Override
+    public PageData getCameraStreamInfo(String cameraId) {
+        // 获取摄像头流信息
+        PageData cameraInfo = new PageData();
+        cameraInfo.put("cameraId", cameraId);
+        
+        // 这里应该调用第三方摄像头系统的接口获取流地址
+        // 模拟返回数据
+        cameraInfo.put("streamUrl", "rtsp://camera.example.com/stream/" + cameraId);
+        cameraInfo.put("status", "online");
+        
+        return cameraInfo;
+    }
+
+    @Override
+    public Map<String, Object> getPatrolStatistics(PageData pd) {
+        Map<String, Object> statistics = new HashMap<>();
+        
+        // 获取今日巡更统计
+        statistics.put("todayTotal", patrolRecordMapper.getTodayPatrolCount(pd));
+        statistics.put("todayNormal", patrolRecordMapper.getTodayNormalCount(pd));
+        statistics.put("todayAbnormal", patrolRecordMapper.getTodayAbnormalCount(pd));
+        statistics.put("todayTimeout", patrolRecordMapper.getTodayTimeoutCount(pd));
+        
+        // 获取活跃巡更人员
+        statistics.put("activePersonnel", patrolRecordMapper.getActivePersonnel(pd));
+        
+        // 获取巡更点覆盖率
+        int totalPoints = patrolPointMapper.getTotalActivePoints();
+        int patrolledPoints = patrolRecordMapper.getTodayPatrolledPoints(pd);
+        double coverage = totalPoints > 0 ? (double) patrolledPoints / totalPoints * 100 : 0;
+        statistics.put("coverage", String.format("%.2f", coverage));
+        
+        return statistics;
+    }
+}

+ 60 - 0
pm-system/src/main/java/com/pm/patrol/service/impl/PatrolPointServiceImpl.java

@@ -0,0 +1,60 @@
+package com.pm.patrol.service.impl;
+
+import java.util.List;
+import com.pm.common.utils.DateUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.pm.patrol.mapper.PatrolPointMapper;
+import com.pm.patrol.service.IPatrolPointService;
+import com.pm.common.config.PageData;
+
+/**
+ * 巡更点信息Service业务层处理
+ *
+ * @author pm
+ * @date 2025-06-03
+ */
+@Service
+public class PatrolPointServiceImpl implements IPatrolPointService {
+    
+    @Autowired
+    private PatrolPointMapper patrolPointMapper;
+
+    @Override
+    public PageData selectPatrolPointById(Long id) {
+        return patrolPointMapper.selectPatrolPointById(id);
+    }
+
+    @Override
+    public List<PageData> selectPatrolPointList(PageData pd) {
+        return patrolPointMapper.selectPatrolPointList(pd);
+    }
+
+    @Override
+    public int insertPatrolPoint(PageData pd) {
+        pd.put("createTime", DateUtils.getTime());
+        return patrolPointMapper.insertPatrolPoint(pd);
+    }
+
+    @Override
+    public int updatePatrolPoint(PageData pd) {
+        pd.put("updateTime", DateUtils.getTime());
+        return patrolPointMapper.updatePatrolPoint(pd);
+    }
+
+    @Override
+    public int deletePatrolPointByIds(Long[] ids) {
+        return patrolPointMapper.deletePatrolPointByIds(ids);
+    }
+
+    @Override
+    public int deletePatrolPointById(Long id) {
+        return patrolPointMapper.deletePatrolPointById(id);
+    }
+
+    @Override
+    public int updatePatrolPointStatus(PageData pd) {
+        pd.put("updateTime", DateUtils.getTime());
+        return patrolPointMapper.updatePatrolPointStatus(pd);
+    }
+}

+ 48 - 0
pm-system/src/main/java/com/pm/patrol/service/impl/PatrolRecordServiceImpl.java

@@ -0,0 +1,48 @@
+package com.pm.patrol.service.impl;
+
+import java.util.List;
+import com.pm.common.utils.DateUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.pm.patrol.mapper.PatrolRecordMapper;
+import com.pm.patrol.service.IPatrolRecordService;
+import com.pm.common.config.PageData;
+
+/**
+ * 巡更记录Service业务层处理
+ *
+ * @author pm
+ * @date 2025-06-03
+ */
+@Service
+public class PatrolRecordServiceImpl implements IPatrolRecordService {
+    
+    @Autowired
+    private PatrolRecordMapper patrolRecordMapper;
+
+    @Override
+    public PageData selectPatrolRecordById(Long id) {
+        return patrolRecordMapper.selectPatrolRecordById(id);
+    }
+
+    @Override
+    public List<PageData> selectPatrolRecordList(PageData pd) {
+        return patrolRecordMapper.selectPatrolRecordList(pd);
+    }
+
+    @Override
+    public int insertPatrolRecord(PageData pd) {
+        pd.put("createTime", DateUtils.getTime());
+        return patrolRecordMapper.insertPatrolRecord(pd);
+    }
+
+    @Override
+    public int updatePatrolRecord(PageData pd) {
+        return patrolRecordMapper.updatePatrolRecord(pd);
+    }
+
+    @Override
+    public int deletePatrolRecordById(Long id) {
+        return patrolRecordMapper.deletePatrolRecordById(id);
+    }
+}

+ 133 - 0
pm-system/src/main/java/com/pm/subsystem/mapper/EnergyDeviceMapper.java

@@ -0,0 +1,133 @@
+package com.pm.subsystem.mapper;
+
+import java.util.List;
+import com.pm.common.config.PageData;
+
+/**
+ * 能源管理设备Mapper接口
+ *
+ * @author lxf
+ * @date 2025-06-03
+ */
+public interface EnergyDeviceMapper
+{
+    /**
+     * 查询能源管理设备
+     *
+     * @param id 能源管理设备主键
+     * @return 能源管理设备
+     */
+    public PageData selectEnergyDeviceById(Long id);
+
+    /**
+     * 查询能源管理设备列表
+     *
+     * @param pd
+     * @return 能源管理设备集合
+     */
+    public List<PageData> selectEnergyDeviceList(PageData pd);
+
+    /**
+     * 新增能源管理设备
+     *
+     * @param pd
+     * @return 结果
+     */
+    public int insertEnergyDevice(PageData pd);
+
+    /**
+     * 修改能源管理设备
+     *
+     * @param pd
+     * @return 结果
+     */
+    public int updateEnergyDevice(PageData pd);
+
+    /**
+     * 删除能源管理设备
+     *
+     * @param id 能源管理设备主键
+     * @return 结果
+     */
+    public int deleteEnergyDeviceById(Long id);
+
+    /**
+     * 批量删除能源管理设备
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteEnergyDeviceByIds(Long[] ids);
+
+    /**
+     * 查询能耗统计数据
+     *
+     * @param pd
+     * @return 统计数据
+     */
+    public PageData selectEnergyStatistics(PageData pd);
+
+    /**
+     * 查询能耗历史记录
+     *
+     * @param pd
+     * @return 历史记录集合
+     */
+    public List<PageData> selectEnergyHistoryList(PageData pd);
+
+    /**
+     * 新增能耗历史记录
+     *
+     * @param pd
+     * @return 结果
+     */
+    public int insertEnergyHistory(PageData pd);
+
+    /**
+     * 查询实时能耗数据
+     *
+     * @param deviceCode 设备编码
+     * @return 实时数据
+     */
+    public PageData selectRealTimeEnergyData(String deviceCode);
+
+    /**
+     * 查询能耗告警信息
+     *
+     * @param pd
+     * @return 告警信息集合
+     */
+    public List<PageData> selectEnergyAlarmList(PageData pd);
+
+    /**
+     * 新增能耗告警
+     *
+     * @param pd
+     * @return 结果
+     */
+    public int insertEnergyAlarm(PageData pd);
+
+    /**
+     * 更新能耗告警状态
+     *
+     * @param pd
+     * @return 结果
+     */
+    public int updateEnergyAlarmStatus(PageData pd);
+
+    /**
+     * 查询设备控制记录
+     *
+     * @param pd
+     * @return 控制记录集合
+     */
+    public List<PageData> selectDeviceControlList(PageData pd);
+
+    /**
+     * 新增设备控制记录
+     *
+     * @param pd
+     * @return 结果
+     */
+    public int insertDeviceControl(PageData pd);
+}

+ 169 - 0
pm-system/src/main/java/com/pm/subsystem/mapper/IntrusionAlarmMapper.java

@@ -0,0 +1,169 @@
+package com.pm.subsystem.mapper;
+
+import java.util.List;
+import com.pm.common.config.PageData;
+
+/**
+ * 入侵报警系统Mapper接口
+ *
+ * @author lxf
+ * @date 2025-06-03
+ */
+public interface IntrusionAlarmMapper
+{
+    // ==================== 报警点管理 ====================
+    /**
+     * 查询报警点
+     */
+    public PageData selectAlarmPointById(Long id);
+
+    /**
+     * 查询报警点列表
+     */
+    public List<PageData> selectAlarmPointList(PageData pd);
+
+    /**
+     * 新增报警点
+     */
+    public int insertAlarmPoint(PageData pd);
+
+    /**
+     * 修改报警点
+     */
+    public int updateAlarmPoint(PageData pd);
+
+    /**
+     * 删除报警点
+     */
+    public int deleteAlarmPointById(Long id);
+
+    /**
+     * 批量删除报警点
+     */
+    public int deleteAlarmPointByIds(Long[] ids);
+
+    // ==================== 报警记录管理 ====================
+    /**
+     * 查询报警记录
+     */
+    public PageData selectAlarmById(Long id);
+
+    /**
+     * 查询报警记录列表
+     */
+    public List<PageData> selectAlarmList(PageData pd);
+
+    /**
+     * 新增报警记录
+     */
+    public int insertAlarm(PageData pd);
+
+    /**
+     * 修改报警记录
+     */
+    public int updateAlarm(PageData pd);
+
+    /**
+     * 删除报警记录
+     */
+    public int deleteAlarmById(Long id);
+
+    /**
+     * 批量删除报警记录
+     */
+    public int deleteAlarmByIds(Long[] ids);
+
+    // ==================== 防区管理 ====================
+    /**
+     * 查询防区
+     */
+    public PageData selectZoneById(Long id);
+
+    /**
+     * 查询防区列表
+     */
+    public List<PageData> selectZoneList(PageData pd);
+
+    /**
+     * 新增防区
+     */
+    public int insertZone(PageData pd);
+
+    /**
+     * 修改防区
+     */
+    public int updateZone(PageData pd);
+
+    /**
+     * 删除防区
+     */
+    public int deleteZoneById(Long id);
+
+    /**
+     * 查询防区设备列表
+     */
+    public List<PageData> selectZoneDeviceList(PageData pd);
+
+    // ==================== 联动规则管理 ====================
+    /**
+     * 查询联动规则列表
+     */
+    public List<PageData> selectLinkageRuleList(PageData pd);
+
+    /**
+     * 查询联动规则详情
+     */
+    public PageData selectLinkageRuleById(Long id);
+
+    /**
+     * 新增联动规则
+     */
+    public int insertLinkageRule(PageData pd);
+
+    /**
+     * 修改联动规则
+     */
+    public int updateLinkageRule(PageData pd);
+
+    /**
+     * 删除联动规则
+     */
+    public int deleteLinkageRuleById(Long id);
+
+    /**
+     * 查询联动执行记录
+     */
+    public List<PageData> selectLinkageRecordList(PageData pd);
+
+    /**
+     * 新增联动执行记录
+     */
+    public int insertLinkageRecord(PageData pd);
+
+    // ==================== 统计查询 ====================
+    /**
+     * 查询报警统计数据
+     */
+    public PageData selectAlarmStatistics(PageData pd);
+
+    /**
+     * 查询报警趋势数据
+     */
+    public List<PageData> selectAlarmTrend(PageData pd);
+
+    /**
+     * 查询实时状态
+     */
+    public PageData selectRealTimeStatus(PageData pd);
+
+    // ==================== 维护记录 ====================
+    /**
+     * 查询维护记录列表
+     */
+    public List<PageData> selectMaintenanceRecordList(PageData pd);
+
+    /**
+     * 新增维护记录
+     */
+    public int insertMaintenanceRecord(PageData pd);
+}

+ 119 - 0
pm-system/src/main/java/com/pm/subsystem/mapper/VideoDeviceMapper.java

@@ -0,0 +1,119 @@
+package com.pm.subsystem.mapper;
+
+import com.pm.common.config.PageData;
+
+import java.util.List;
+
+/**
+ * 视频监控设备Mapper接口
+ *
+ * @author lxf
+ * @date 2025-06-03
+ */
+public interface VideoDeviceMapper
+{
+    /**
+     * 查询摄像机设备
+     */
+    public PageData selectCameraById(Long id);
+
+    /**
+     * 查询摄像机设备列表
+     */
+    public List<PageData> selectCameraList(PageData pd);
+
+    /**
+     * 新增摄像机设备
+     */
+    public int insertCamera(PageData pd);
+
+    /**
+     * 修改摄像机设备
+     */
+    public int updateCamera(PageData pd);
+
+    /**
+     * 删除摄像机设备
+     */
+    public int deleteCameraById(Long id);
+
+    /**
+     * 批量删除摄像机设备
+     */
+    public int deleteCameraByIds(Long[] ids);
+
+    /**
+     * 查询联动规则列表
+     */
+    public List<PageData> selectLinkageRuleList(PageData pd);
+
+    /**
+     * 查询联动规则详情
+     */
+    public PageData selectLinkageRuleById(Long id);
+
+    /**
+     * 新增联动规则
+     */
+    public int insertLinkageRule(PageData pd);
+
+    /**
+     * 修改联动规则
+     */
+    public int updateLinkageRule(PageData pd);
+
+    /**
+     * 删除联动规则
+     */
+    public int deleteLinkageRuleById(Long id);
+
+    /**
+     * 查询联动执行记录
+     */
+    public List<PageData> selectLinkageRecordList(PageData pd);
+
+    /**
+     * 新增联动执行记录
+     */
+    public int insertLinkageRecord(PageData pd);
+
+    /**
+     * 查询录像文件列表
+     */
+    public List<PageData> selectRecordingFileList(PageData pd);
+
+    /**
+     * 新增录像文件记录
+     */
+    public int insertRecordingFile(PageData pd);
+
+    /**
+     * 查询预置位列表
+     */
+    public List<PageData> selectPresetList(PageData pd);
+
+    /**
+     * 新增预置位
+     */
+    public int insertPreset(PageData pd);
+
+    /**
+     * 删除预置位
+     */
+    public int deletePresetById(Long id);
+
+    /**
+     * 查询云台控制记录
+     */
+    public List<PageData> selectPtzControlList(PageData pd);
+
+    /**
+     * 新增云台控制记录
+     */
+    public int insertPtzControl(PageData pd);
+
+    /**
+     * 查询摄像机统计数据
+     */
+    public PageData selectCameraStatistics(PageData pd);
+}

+ 165 - 0
pm-system/src/main/java/com/pm/subsystem/service/IEnergyDeviceService.java

@@ -0,0 +1,165 @@
+package com.pm.subsystem.service;
+
+import java.util.List;
+import com.pm.common.config.PageData;
+
+/**
+ * 能源管理设备Service接口
+ *
+ * @author lxf
+ * @date 2025-06-03
+ */
+public interface IEnergyDeviceService
+{
+    /**
+     * 查询能源管理设备
+     *
+     * @param id 能源管理设备主键
+     * @return 能源管理设备
+     */
+    public PageData selectEnergyDeviceById(Long id);
+
+    /**
+     * 查询能源管理设备列表
+     *
+     * @param pd
+     * @return 能源管理设备集合
+     */
+    public List<PageData> selectEnergyDeviceList(PageData pd);
+
+    /**
+     * 新增能源管理设备
+     *
+     * @param pd
+     * @return 结果
+     */
+    public int insertEnergyDevice(PageData pd);
+
+    /**
+     * 修改能源管理设备
+     *
+     * @param pd
+     * @return 结果
+     */
+    public int updateEnergyDevice(PageData pd);
+
+    /**
+     * 批量删除能源管理设备
+     *
+     * @param ids 需要删除的能源管理设备主键集合
+     * @return 结果
+     */
+    public int deleteEnergyDeviceByIds(Long[] ids);
+
+    /**
+     * 删除能源管理设备信息
+     *
+     * @param id 能源管理设备主键
+     * @return 结果
+     */
+    public int deleteEnergyDeviceById(Long id);
+
+    /**
+     * 获取能耗统计数据
+     *
+     * @param pd
+     * @return 统计数据
+     */
+    public PageData getEnergyStatistics(PageData pd);
+
+    /**
+     * 查询能耗历史记录
+     *
+     * @param pd
+     * @return 历史记录集合
+     */
+    public List<PageData> selectEnergyHistoryList(PageData pd);
+
+    /**
+     * 新增能耗历史记录
+     *
+     * @param pd
+     * @return 结果
+     */
+    public int insertEnergyHistory(PageData pd);
+
+    /**
+     * 获取实时能耗数据
+     *
+     * @param deviceCode 设备编码
+     * @return 实时数据
+     */
+    public PageData getRealTimeEnergyData(String deviceCode);
+
+    /**
+     * 查询能耗告警信息
+     *
+     * @param pd
+     * @return 告警信息集合
+     */
+    public List<PageData> selectEnergyAlarmList(PageData pd);
+
+    /**
+     * 新增能耗告警
+     *
+     * @param pd
+     * @return 结果
+     */
+    public int insertEnergyAlarm(PageData pd);
+
+    /**
+     * 处理能耗告警
+     *
+     * @param pd
+     * @return 结果
+     */
+    public int handleEnergyAlarm(PageData pd);
+
+    /**
+     * 查询设备控制记录
+     *
+     * @param pd
+     * @return 控制记录集合
+     */
+    public List<PageData> selectDeviceControlList(PageData pd);
+
+    /**
+     * 设备控制
+     *
+     * @param pd
+     * @return 结果
+     */
+    public int controlDevice(PageData pd);
+
+    /**
+     * 获取能耗曲线数据
+     *
+     * @param pd
+     * @return 曲线数据集合
+     */
+    public List<PageData> getEnergyChartData(PageData pd);
+
+    /**
+     * 生成能耗报表
+     *
+     * @param pd
+     * @return 报表数据
+     */
+    public PageData generateEnergyReport(PageData pd);
+
+    /**
+     * 设置能耗阈值
+     *
+     * @param pd
+     * @return 结果
+     */
+    public int setEnergyThreshold(PageData pd);
+
+    /**
+     * 数据同步(从其他子系统获取数据)
+     *
+     * @param pd
+     * @return 结果
+     */
+    public int syncEnergyData(PageData pd);
+}

+ 249 - 0
pm-system/src/main/java/com/pm/subsystem/service/IIntrusionAlarmService.java

@@ -0,0 +1,249 @@
+package com.pm.subsystem.service;
+
+import java.util.List;
+import com.pm.common.config.PageData;
+
+/**
+ * 入侵报警系统Service接口
+ *
+ * @author lxf
+ * @date 2025-06-03
+ */
+public interface IIntrusionAlarmService
+{
+    // ==================== 报警点管理 ====================
+    /**
+     * 查询报警点
+     */
+    public PageData selectAlarmPointById(Long id);
+
+    /**
+     * 查询报警点列表
+     */
+    public List<PageData> selectAlarmPointList(PageData pd);
+
+    /**
+     * 新增报警点
+     */
+    public int insertAlarmPoint(PageData pd);
+
+    /**
+     * 修改报警点
+     */
+    public int updateAlarmPoint(PageData pd);
+
+    /**
+     * 批量删除报警点
+     */
+    public int deleteAlarmPointByIds(Long[] ids);
+
+    /**
+     * 删除报警点
+     */
+    public int deleteAlarmPointById(Long id);
+
+    /**
+     * 切换布防状态
+     */
+    public int toggleArmStatus(PageData pd);
+
+    /**
+     * 批量布防/撤防
+     */
+    public int batchArmControl(PageData pd);
+
+    /**
+     * 测试报警设备
+     */
+    public int testAlarmDevice(String deviceCode);
+
+    /**
+     * 更新设备配置
+     */
+    public int updateDeviceConfig(PageData pd);
+
+    // ==================== 报警记录管理 ====================
+    /**
+     * 查询报警记录
+     */
+    public PageData selectAlarmById(Long id);
+
+    /**
+     * 查询报警记录列表
+     */
+    public List<PageData> selectAlarmList(PageData pd);
+
+    /**
+     * 新增报警记录
+     */
+    public int insertAlarm(PageData pd);
+
+    /**
+     * 处理报警
+     */
+    public int processAlarm(PageData pd);
+
+    /**
+     * 批量处理报警
+     */
+    public int batchProcessAlarms(PageData pd);
+
+    /**
+     * 确认报警
+     */
+    public int confirmAlarm(Long alarmId);
+
+    /**
+     * 忽略报警
+     */
+    public int ignoreAlarm(Long alarmId, PageData pd);
+
+    // ==================== 防区管理 ====================
+    /**
+     * 查询防区
+     */
+    public PageData selectZoneById(Long id);
+
+    /**
+     * 查询防区列表
+     */
+    public List<PageData> selectZoneList(PageData pd);
+
+    /**
+     * 新增防区
+     */
+    public int insertZone(PageData pd);
+
+    /**
+     * 修改防区
+     */
+    public int updateZone(PageData pd);
+
+    /**
+     * 删除防区
+     */
+    public int deleteZoneById(Long id);
+
+    /**
+     * 防区布防/撤防
+     */
+    public int toggleZoneStatus(Long zoneId, PageData pd);
+
+    /**
+     * 批量防区操作
+     */
+    public int batchZoneControl(PageData pd);
+
+    /**
+     * 查询防区设备列表
+     */
+    public List<PageData> selectZoneDeviceList(String zoneCode);
+
+    /**
+     * 添加设备到防区
+     */
+    public int addDeviceToZone(PageData pd);
+
+    /**
+     * 从防区移除设备
+     */
+    public int removeDeviceFromZone(PageData pd);
+
+    // ==================== 联动规则管理 ====================
+    /**
+     * 查询联动规则列表
+     */
+    public List<PageData> selectLinkageRuleList(PageData pd);
+
+    /**
+     * 查询联动规则详情
+     */
+    public PageData selectLinkageRuleById(Long id);
+
+    /**
+     * 新增联动规则
+     */
+    public int insertLinkageRule(PageData pd);
+
+    /**
+     * 修改联动规则
+     */
+    public int updateLinkageRule(PageData pd);
+
+    /**
+     * 删除联动规则
+     */
+    public int deleteLinkageRuleById(Long id);
+
+    /**
+     * 启用/禁用联动规则
+     */
+    public int toggleLinkageRule(Long ruleId, PageData pd);
+
+    /**
+     * 查询联动执行记录
+     */
+    public List<PageData> selectLinkageRecordList(PageData pd);
+
+    /**
+     * 手动执行联动
+     */
+    public int executeLinkage(PageData pd);
+
+    // ==================== 统计查询 ====================
+    /**
+     * 获取报警统计数据
+     */
+    public PageData getAlarmStatistics(PageData pd);
+
+    /**
+     * 获取实时状态
+     */
+    public PageData getRealTimeStatus();
+
+    /**
+     * 获取报警趋势数据
+     */
+    public List<PageData> getAlarmTrend(PageData pd);
+
+    // ==================== 其他功能 ====================
+    /**
+     * 设备健康检测
+     */
+    public PageData checkDeviceHealth(String deviceCode);
+
+    /**
+     * 批量设备健康检测
+     */
+    public List<PageData> batchCheckDeviceHealth(List<String> deviceCodes);
+
+    /**
+     * 获取设备配置
+     */
+    public PageData getDeviceConfig(String deviceCode);
+
+    /**
+     * 重置设备
+     */
+    public int resetDevice(String deviceCode);
+
+    /**
+     * 查询维护记录
+     */
+    public List<PageData> selectMaintenanceRecordList(PageData pd);
+
+    /**
+     * 新增维护记录
+     */
+    public int addMaintenanceRecord(PageData pd);
+
+    /**
+     * 生成报警报表
+     */
+    public PageData generateAlarmReport(PageData pd);
+
+    /**
+     * 数据同步
+     */
+    public int syncIntrusionData(PageData pd);
+}

+ 138 - 0
pm-system/src/main/java/com/pm/subsystem/service/IVideoDeviceService.java

@@ -0,0 +1,138 @@
+package com.pm.subsystem.service;
+
+import java.util.List;
+import com.pm.common.config.PageData;
+
+/**
+ * 视频监控设备Service接口
+ *
+ * @author lxf
+ * @date 2025-06-03
+ */
+public interface IVideoDeviceService
+{
+    /**
+     * 查询摄像机设备
+     */
+    public PageData selectCameraById(Long id);
+
+    /**
+     * 查询摄像机设备列表
+     */
+    public List<PageData> selectCameraList(PageData pd);
+
+    /**
+     * 新增摄像机设备
+     */
+    public int insertCamera(PageData pd);
+
+    /**
+     * 修改摄像机设备
+     */
+    public int updateCamera(PageData pd);
+
+    /**
+     * 批量删除摄像机设备
+     */
+    public int deleteCameraByIds(Long[] ids);
+
+    /**
+     * 删除摄像机设备
+     */
+    public int deleteCameraById(Long id);
+
+    /**
+     * 获取摄像机视频流
+     */
+    public PageData getCameraStream(String cameraCode);
+
+    /**
+     * 云台控制
+     */
+    public int ptzControl(PageData pd);
+
+    /**
+     * 录像控制
+     */
+    public int recordingControl(PageData pd);
+
+    /**
+     * 抓拍
+     */
+    public int takeSnapshot(String cameraCode);
+
+    /**
+     * 查询联动规则列表
+     */
+    public List<PageData> selectLinkageRuleList(PageData pd);
+
+    /**
+     * 查询联动规则详情
+     */
+    public PageData selectLinkageRuleById(Long id);
+
+    /**
+     * 新增联动规则
+     */
+    public int insertLinkageRule(PageData pd);
+
+    /**
+     * 修改联动规则
+     */
+    public int updateLinkageRule(PageData pd);
+
+    /**
+     * 删除联动规则
+     */
+    public int deleteLinkageRuleById(Long id);
+
+    /**
+     * 启用/禁用联动规则
+     */
+    public int toggleLinkageRule(Long id, PageData pd);
+
+    /**
+     * 查询联动执行记录
+     */
+    public List<PageData> selectLinkageRecordList(PageData pd);
+
+    /**
+     * 执行联动规则
+     */
+    public int executeLinkageRule(PageData pd);
+
+    /**
+     * 查询录像文件列表
+     */
+    public List<PageData> selectRecordingFileList(PageData pd);
+
+    /**
+     * 查询预置位列表
+     */
+    public List<PageData> selectPresetList(String cameraCode);
+
+    /**
+     * 设置预置位
+     */
+    public int setPreset(PageData pd);
+
+    /**
+     * 删除预置位
+     */
+    public int deletePresetById(Long id);
+
+    /**
+     * 摄像机健康检测
+     */
+    public PageData checkCameraHealth(String cameraCode);
+
+    /**
+     * 获取摄像机统计数据
+     */
+    public PageData getCameraStatistics();
+
+    /**
+     * 数据同步
+     */
+    public int syncVideoData(PageData pd);
+}

+ 315 - 0
pm-system/src/main/java/com/pm/subsystem/service/impl/EnergyDeviceServiceImpl.java

@@ -0,0 +1,315 @@
+package com.pm.subsystem.service.impl;
+
+import java.util.List;
+import com.pm.common.utils.DateUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.pm.subsystem.mapper.EnergyDeviceMapper;
+import com.pm.subsystem.service.IEnergyDeviceService;
+import com.pm.common.config.PageData;
+
+/**
+ * 能源管理设备Service业务层处理
+ *
+ * @author lxf
+ * @date 2025-06-03
+ */
+@Service
+public class EnergyDeviceServiceImpl implements IEnergyDeviceService
+{
+    @Autowired
+    private EnergyDeviceMapper energyDeviceMapper;
+
+    /**
+     * 查询能源管理设备
+     *
+     * @param id 能源管理设备主键
+     * @return 能源管理设备
+     */
+    @Override
+    public PageData selectEnergyDeviceById(Long id)
+    {
+        return energyDeviceMapper.selectEnergyDeviceById(id);
+    }
+
+    /**
+     * 查询能源管理设备列表
+     *
+     * @param pd
+     * @return 能源管理设备
+     */
+    @Override
+    public List<PageData> selectEnergyDeviceList(PageData pd)
+    {
+        return energyDeviceMapper.selectEnergyDeviceList(pd);
+    }
+
+    /**
+     * 新增能源管理设备
+     *
+     * @param pd
+     * @return 结果
+     */
+    @Override
+    public int insertEnergyDevice(PageData pd)
+    {
+        pd.put("createTime", DateUtils.getTime());
+        pd.put("updateTime", DateUtils.getTime());
+        return energyDeviceMapper.insertEnergyDevice(pd);
+    }
+
+    /**
+     * 修改能源管理设备
+     *
+     * @param pd
+     * @return 结果
+     */
+    @Override
+    public int updateEnergyDevice(PageData pd)
+    {
+        pd.put("updateTime", DateUtils.getTime());
+        return energyDeviceMapper.updateEnergyDevice(pd);
+    }
+
+    /**
+     * 批量删除能源管理设备
+     *
+     * @param ids 需要删除的能源管理设备主键
+     * @return 结果
+     */
+    @Override
+    public int deleteEnergyDeviceByIds(Long[] ids)
+    {
+        return energyDeviceMapper.deleteEnergyDeviceByIds(ids);
+    }
+
+    /**
+     * 删除能源管理设备信息
+     *
+     * @param id 能源管理设备主键
+     * @return 结果
+     */
+    @Override
+    public int deleteEnergyDeviceById(Long id)
+    {
+        return energyDeviceMapper.deleteEnergyDeviceById(id);
+    }
+
+    /**
+     * 获取能耗统计数据
+     *
+     * @param pd
+     * @return 统计数据
+     */
+    @Override
+    public PageData getEnergyStatistics(PageData pd)
+    {
+        return energyDeviceMapper.selectEnergyStatistics(pd);
+    }
+
+    /**
+     * 查询能耗历史记录
+     *
+     * @param pd
+     * @return 历史记录集合
+     */
+    @Override
+    public List<PageData> selectEnergyHistoryList(PageData pd)
+    {
+        return energyDeviceMapper.selectEnergyHistoryList(pd);
+    }
+
+    /**
+     * 新增能耗历史记录
+     *
+     * @param pd
+     * @return 结果
+     */
+    @Override
+    public int insertEnergyHistory(PageData pd)
+    {
+        pd.put("createTime", DateUtils.getTime());
+        return energyDeviceMapper.insertEnergyHistory(pd);
+    }
+
+    /**
+     * 获取实时能耗数据
+     *
+     * @param deviceCode 设备编码
+     * @return 实时数据
+     */
+    @Override
+    public PageData getRealTimeEnergyData(String deviceCode)
+    {
+        return energyDeviceMapper.selectRealTimeEnergyData(deviceCode);
+    }
+
+    /**
+     * 查询能耗告警信息
+     *
+     * @param pd
+     * @return 告警信息集合
+     */
+    @Override
+    public List<PageData> selectEnergyAlarmList(PageData pd)
+    {
+        return energyDeviceMapper.selectEnergyAlarmList(pd);
+    }
+
+    /**
+     * 新增能耗告警
+     *
+     * @param pd
+     * @return 结果
+     */
+    @Override
+    public int insertEnergyAlarm(PageData pd)
+    {
+        pd.put("createTime", DateUtils.getTime());
+        pd.put("alarmTime", DateUtils.getTime());
+        return energyDeviceMapper.insertEnergyAlarm(pd);
+    }
+
+    /**
+     * 处理能耗告警
+     *
+     * @param pd
+     * @return 结果
+     */
+    @Override
+    public int handleEnergyAlarm(PageData pd)
+    {
+        pd.put("handleTime", DateUtils.getTime());
+        pd.put("handleStatus", 1); // 1-已处理
+        return energyDeviceMapper.updateEnergyAlarmStatus(pd);
+    }
+
+    /**
+     * 查询设备控制记录
+     *
+     * @param pd
+     * @return 控制记录集合
+     */
+    @Override
+    public List<PageData> selectDeviceControlList(PageData pd)
+    {
+        return energyDeviceMapper.selectDeviceControlList(pd);
+    }
+
+    /**
+     * 设备控制
+     *
+     * @param pd
+     * @return 结果
+     */
+    @Override
+    public int controlDevice(PageData pd)
+    {
+        // 1. 记录控制命令
+        pd.put("createTime", DateUtils.getTime());
+        pd.put("controlTime", DateUtils.getTime());
+        
+        // 2. 这里应该调用设备的实际控制接口,模拟调用成功
+        pd.put("controlResult", "success");
+        
+        // 3. 记录控制日志
+        int result = energyDeviceMapper.insertDeviceControl(pd);
+        
+        // 4. 更新设备状态
+        if (result > 0) {
+            PageData updatePd = new PageData();
+            updatePd.put("deviceCode", pd.get("deviceCode"));
+            updatePd.put("deviceStatus", pd.get("deviceStatus"));
+            updatePd.put("powerSetting", pd.get("powerSetting"));
+            updatePd.put("temperature", pd.get("temperature"));
+            updatePd.put("updateTime", DateUtils.getTime());
+            energyDeviceMapper.updateEnergyDevice(updatePd);
+        }
+        
+        return result;
+    }
+
+    /**
+     * 获取能耗曲线数据
+     *
+     * @param pd
+     * @return 曲线数据集合
+     */
+    @Override
+    public List<PageData> getEnergyChartData(PageData pd)
+    {
+        // 根据时间范围查询历史数据,用于绘制曲线图
+        return energyDeviceMapper.selectEnergyHistoryList(pd);
+    }
+
+    /**
+     * 生成能耗报表
+     *
+     * @param pd
+     * @return 报表数据
+     */
+    @Override
+    public PageData generateEnergyReport(PageData pd)
+    {
+        // 生成能耗报表的统计数据
+        PageData reportData = new PageData();
+        
+        // 获取基础统计数据
+        PageData statistics = energyDeviceMapper.selectEnergyStatistics(pd);
+        reportData.putAll(statistics);
+        
+        // 可以添加更多的报表计算逻辑
+        reportData.put("reportTime", DateUtils.getTime());
+        reportData.put("reportType", pd.get("reportType"));
+        
+        return reportData;
+    }
+
+    /**
+     * 设置能耗阈值
+     *
+     * @param pd
+     * @return 结果
+     */
+    @Override
+    public int setEnergyThreshold(PageData pd)
+    {
+        pd.put("updateTime", DateUtils.getTime());
+        return energyDeviceMapper.updateEnergyDevice(pd);
+    }
+
+    /**
+     * 数据同步(从其他子系统获取数据)
+     *
+     * @param pd
+     * @return 结果
+     */
+    @Override
+    public int syncEnergyData(PageData pd)
+    {
+        // 这里模拟从其他子系统同步数据的逻辑
+        // 实际项目中应该调用其他子系统的接口获取数据
+        
+        try {
+            // 1. 调用其他子系统接口获取最新的能耗数据
+            // List<PageData> remoteData = callRemoteEnergySystem();
+            
+            // 2. 更新本地数据库
+            // for (PageData data : remoteData) {
+            //     data.put("updateTime", DateUtils.getTime());
+            //     energyDeviceMapper.updateEnergyDevice(data);
+            // }
+            
+            // 3. 记录同步日志
+            PageData syncLog = new PageData();
+            syncLog.put("syncTime", DateUtils.getTime());
+            syncLog.put("syncResult", "success");
+            syncLog.put("syncCount", 10); // 模拟同步了10条数据
+            
+            return 1;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+}

+ 506 - 0
pm-system/src/main/java/com/pm/subsystem/service/impl/IntrusionAlarmServiceImpl.java

@@ -0,0 +1,506 @@
+package com.pm.subsystem.service.impl;
+
+import java.util.List;
+import java.util.ArrayList;
+import com.pm.common.utils.DateUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.pm.subsystem.mapper.IntrusionAlarmMapper;
+import com.pm.subsystem.service.IIntrusionAlarmService;
+import com.pm.common.config.PageData;
+
+/**
+ * 入侵报警系统Service业务层处理
+ *
+ * @author lxf
+ * @date 2025-06-03
+ */
+@Service
+public class IntrusionAlarmServiceImpl implements IIntrusionAlarmService
+{
+    @Autowired
+    private IntrusionAlarmMapper intrusionAlarmMapper;
+
+    // ==================== 报警点管理 ====================
+    @Override
+    public PageData selectAlarmPointById(Long id)
+    {
+        return intrusionAlarmMapper.selectAlarmPointById(id);
+    }
+
+    @Override
+    public List<PageData> selectAlarmPointList(PageData pd)
+    {
+        return intrusionAlarmMapper.selectAlarmPointList(pd);
+    }
+
+    @Override
+    public int insertAlarmPoint(PageData pd)
+    {
+        pd.put("createTime", DateUtils.getTime());
+        pd.put("updateTime", DateUtils.getTime());
+        return intrusionAlarmMapper.insertAlarmPoint(pd);
+    }
+
+    @Override
+    public int updateAlarmPoint(PageData pd)
+    {
+        pd.put("updateTime", DateUtils.getTime());
+        return intrusionAlarmMapper.updateAlarmPoint(pd);
+    }
+
+    @Override
+    public int deleteAlarmPointByIds(Long[] ids)
+    {
+        return intrusionAlarmMapper.deleteAlarmPointByIds(ids);
+    }
+
+    @Override
+    public int deleteAlarmPointById(Long id)
+    {
+        return intrusionAlarmMapper.deleteAlarmPointById(id);
+    }
+
+    @Override
+    public int toggleArmStatus(PageData pd)
+    {
+        String deviceCode = (String) pd.get("deviceCode");
+        Integer armStatus = (Integer) pd.get("armStatus");
+        
+        PageData updatePd = new PageData();
+        updatePd.put("deviceCode", deviceCode);
+        updatePd.put("armStatus", armStatus);
+        updatePd.put("updateTime", DateUtils.getTime());
+        
+        // 记录布防/撤防操作日志
+        PageData logPd = new PageData();
+        logPd.put("deviceCode", deviceCode);
+        logPd.put("operation", armStatus == 1 ? "布防" : "撤防");
+        logPd.put("operateTime", DateUtils.getTime());
+        logPd.put("createTime", DateUtils.getTime());
+        
+        return intrusionAlarmMapper.updateAlarmPoint(updatePd);
+    }
+
+    @Override
+    public int batchArmControl(PageData pd)
+    {
+        List<String> deviceCodes = (List<String>) pd.get("deviceCodes");
+        Integer armStatus = (Integer) pd.get("armStatus");
+        
+        int successCount = 0;
+        for (String deviceCode : deviceCodes) {
+            PageData updatePd = new PageData();
+            updatePd.put("deviceCode", deviceCode);
+            updatePd.put("armStatus", armStatus);
+            updatePd.put("updateTime", DateUtils.getTime());
+            
+            if (intrusionAlarmMapper.updateAlarmPoint(updatePd) > 0) {
+                successCount++;
+            }
+        }
+        
+        return successCount;
+    }
+
+    @Override
+    public int testAlarmDevice(String deviceCode)
+    {
+        // 模拟发送测试指令到设备
+        // 实际应该调用设备的测试接口
+        
+        // 记录测试日志
+        PageData testLog = new PageData();
+        testLog.put("deviceCode", deviceCode);
+        testLog.put("testType", "alarm_test");
+        testLog.put("testTime", DateUtils.getTime());
+        testLog.put("testResult", "success");
+        testLog.put("createTime", DateUtils.getTime());
+        
+        return 1; // 模拟测试成功
+    }
+
+    @Override
+    public int updateDeviceConfig(PageData pd)
+    {
+        pd.put("updateTime", DateUtils.getTime());
+        return intrusionAlarmMapper.updateAlarmPoint(pd);
+    }
+
+    // ==================== 报警记录管理 ====================
+    @Override
+    public PageData selectAlarmById(Long id)
+    {
+        return intrusionAlarmMapper.selectAlarmById(id);
+    }
+
+    @Override
+    public List<PageData> selectAlarmList(PageData pd)
+    {
+        return intrusionAlarmMapper.selectAlarmList(pd);
+    }
+
+    @Override
+    public int insertAlarm(PageData pd)
+    {
+        pd.put("createTime", DateUtils.getTime());
+        pd.put("alarmTime", DateUtils.getTime());
+        pd.put("handleStatus", 0); // 默认未处理
+        
+        // 新增报警记录
+        int result = intrusionAlarmMapper.insertAlarm(pd);
+        
+        // 触发联动规则
+        if (result > 0) {
+            triggerLinkageRules(pd);
+        }
+        
+        return result;
+    }
+
+    @Override
+    public int processAlarm(PageData pd)
+    {
+        pd.put("handleTime", DateUtils.getTime());
+        return intrusionAlarmMapper.updateAlarm(pd);
+    }
+
+    @Override
+    public int batchProcessAlarms(PageData pd)
+    {
+        List<Long> alarmIds = (List<Long>) pd.get("alarmIds");
+        Integer handleStatus = (Integer) pd.get("handleStatus");
+        String handleRemark = (String) pd.get("handleRemark");
+        
+        int successCount = 0;
+        for (Long alarmId : alarmIds) {
+            PageData updatePd = new PageData();
+            updatePd.put("id", alarmId);
+            updatePd.put("handleStatus", handleStatus);
+            updatePd.put("handleRemark", handleRemark);
+            updatePd.put("handleTime", DateUtils.getTime());
+            
+            if (intrusionAlarmMapper.updateAlarm(updatePd) > 0) {
+                successCount++;
+            }
+        }
+        
+        return successCount;
+    }
+
+    @Override
+    public int confirmAlarm(Long alarmId)
+    {
+        PageData pd = new PageData();
+        pd.put("id", alarmId);
+        pd.put("handleStatus", 1); // 确认状态
+        pd.put("handleTime", DateUtils.getTime());
+        
+        return intrusionAlarmMapper.updateAlarm(pd);
+    }
+
+    @Override
+    public int ignoreAlarm(Long alarmId, PageData pd)
+    {
+        pd.put("id", alarmId);
+        pd.put("handleStatus", 3); // 忽略状态
+        pd.put("handleTime", DateUtils.getTime());
+        
+        return intrusionAlarmMapper.updateAlarm(pd);
+    }
+
+    // ==================== 防区管理 ====================
+    @Override
+    public PageData selectZoneById(Long id)
+    {
+        return intrusionAlarmMapper.selectZoneById(id);
+    }
+
+    @Override
+    public List<PageData> selectZoneList(PageData pd)
+    {
+        return intrusionAlarmMapper.selectZoneList(pd);
+    }
+
+    @Override
+    public int insertZone(PageData pd)
+    {
+        pd.put("createTime", DateUtils.getTime());
+        pd.put("updateTime", DateUtils.getTime());
+        return intrusionAlarmMapper.insertZone(pd);
+    }
+
+    @Override
+    public int updateZone(PageData pd)
+    {
+        pd.put("updateTime", DateUtils.getTime());
+        return intrusionAlarmMapper.updateZone(pd);
+    }
+
+    @Override
+    public int deleteZoneById(Long id)
+    {
+        return intrusionAlarmMapper.deleteZoneById(id);
+    }
+
+    @Override
+    public int toggleZoneStatus(Long zoneId, PageData pd)
+    {
+        pd.put("id", zoneId);
+        pd.put("updateTime", DateUtils.getTime());
+        return intrusionAlarmMapper.updateZone(pd);
+    }
+
+    @Override
+    public int batchZoneControl(PageData pd)
+    {
+        List<Long> zoneIds = (List<Long>) pd.get("zoneIds");
+        Integer zoneStatus = (Integer) pd.get("zoneStatus");
+        
+        int successCount = 0;
+        for (Long zoneId : zoneIds) {
+            PageData updatePd = new PageData();
+            updatePd.put("id", zoneId);
+            updatePd.put("zoneStatus", zoneStatus);
+            updatePd.put("updateTime", DateUtils.getTime());
+            
+            if (intrusionAlarmMapper.updateZone(updatePd) > 0) {
+                successCount++;
+            }
+        }
+        
+        return successCount;
+    }
+
+    @Override
+    public List<PageData> selectZoneDeviceList(String zoneCode)
+    {
+        PageData pd = new PageData();
+        pd.put("zoneCode", zoneCode);
+        return intrusionAlarmMapper.selectZoneDeviceList(pd);
+    }
+
+    @Override
+    public int addDeviceToZone(PageData pd)
+    {
+        pd.put("createTime", DateUtils.getTime());
+        return 1; // 模拟添加成功
+    }
+
+    @Override
+    public int removeDeviceFromZone(PageData pd)
+    {
+        return 1; // 模拟移除成功
+    }
+
+    // ==================== 联动规则管理 ====================
+    @Override
+    public List<PageData> selectLinkageRuleList(PageData pd)
+    {
+        return intrusionAlarmMapper.selectLinkageRuleList(pd);
+    }
+
+    @Override
+    public PageData selectLinkageRuleById(Long id)
+    {
+        return intrusionAlarmMapper.selectLinkageRuleById(id);
+    }
+
+    @Override
+    public int insertLinkageRule(PageData pd)
+    {
+        pd.put("createTime", DateUtils.getTime());
+        pd.put("updateTime", DateUtils.getTime());
+        return intrusionAlarmMapper.insertLinkageRule(pd);
+    }
+
+    @Override
+    public int updateLinkageRule(PageData pd)
+    {
+        pd.put("updateTime", DateUtils.getTime());
+        return intrusionAlarmMapper.updateLinkageRule(pd);
+    }
+
+    @Override
+    public int deleteLinkageRuleById(Long id)
+    {
+        return intrusionAlarmMapper.deleteLinkageRuleById(id);
+    }
+
+    @Override
+    public int toggleLinkageRule(Long ruleId, PageData pd)
+    {
+        pd.put("id", ruleId);
+        pd.put("updateTime", DateUtils.getTime());
+        return intrusionAlarmMapper.updateLinkageRule(pd);
+    }
+
+    @Override
+    public List<PageData> selectLinkageRecordList(PageData pd)
+    {
+        return intrusionAlarmMapper.selectLinkageRecordList(pd);
+    }
+
+    @Override
+    public int executeLinkage(PageData pd)
+    {
+        // 执行联动规则
+        String linkageAction = (String) pd.get("linkageAction");
+        String targetDevice = (String) pd.get("targetDevice");
+        
+        // 记录联动执行
+        PageData linkageRecord = new PageData();
+        linkageRecord.put("ruleId", pd.get("ruleId"));
+        linkageRecord.put("triggerDevice", pd.get("triggerDevice"));
+        linkageRecord.put("targetDevice", targetDevice);
+        linkageRecord.put("linkageAction", linkageAction);
+        linkageRecord.put("executeTime", DateUtils.getTime());
+        linkageRecord.put("executeResult", "success");
+        linkageRecord.put("createTime", DateUtils.getTime());
+        
+        intrusionAlarmMapper.insertLinkageRecord(linkageRecord);
+        
+        return 1;
+    }
+
+    /**
+     * 触发联动规则(私有方法)
+     */
+    private void triggerLinkageRules(PageData alarmData) {
+        // 查询相关的联动规则
+        PageData ruleQuery = new PageData();
+        ruleQuery.put("triggerDeviceCode", alarmData.get("deviceCode"));
+        ruleQuery.put("ruleStatus", 1); // 只查询启用的规则
+        
+        List<PageData> rules = intrusionAlarmMapper.selectLinkageRuleList(ruleQuery);
+        
+        // 执行所有匹配的联动规则
+        for (PageData rule : rules) {
+            PageData executePd = new PageData();
+            executePd.put("ruleId", rule.get("id"));
+            executePd.put("triggerDevice", alarmData.get("deviceCode"));
+            executePd.put("targetDevice", rule.get("targetDevice"));
+            executePd.put("linkageAction", rule.get("linkageAction"));
+            
+            executeLinkage(executePd);
+        }
+    }
+
+    // ==================== 统计查询 ====================
+    @Override
+    public PageData getAlarmStatistics(PageData pd)
+    {
+        return intrusionAlarmMapper.selectAlarmStatistics(pd);
+    }
+
+    @Override
+    public PageData getRealTimeStatus()
+    {
+        return intrusionAlarmMapper.selectRealTimeStatus(new PageData());
+    }
+
+    @Override
+    public List<PageData> getAlarmTrend(PageData pd)
+    {
+        return intrusionAlarmMapper.selectAlarmTrend(pd);
+    }
+
+    // ==================== 其他功能 ====================
+    @Override
+    public PageData checkDeviceHealth(String deviceCode)
+    {
+        // 模拟设备健康检测
+        PageData health = new PageData();
+        health.put("deviceCode", deviceCode);
+        health.put("isOnline", 1);
+        health.put("signalStrength", 85);
+        health.put("batteryLevel", 78);
+        health.put("lastHeartbeat", DateUtils.getTime());
+        health.put("healthStatus", "正常");
+        
+        return health;
+    }
+
+    @Override
+    public List<PageData> batchCheckDeviceHealth(List<String> deviceCodes)
+    {
+        List<PageData> healthList = new ArrayList<>();
+        for (String deviceCode : deviceCodes) {
+            healthList.add(checkDeviceHealth(deviceCode));
+        }
+        return healthList;
+    }
+
+    @Override
+    public PageData getDeviceConfig(String deviceCode)
+    {
+        PageData config = new PageData();
+        config.put("deviceCode", deviceCode);
+        config.put("sensitivity", 75);
+        config.put("delayTime", 10);
+        config.put("armMode", "auto");
+        config.put("reportInterval", 30);
+        
+        return config;
+    }
+
+    @Override
+    public int resetDevice(String deviceCode)
+    {
+        // 模拟设备重置
+        PageData resetLog = new PageData();
+        resetLog.put("deviceCode", deviceCode);
+        resetLog.put("operation", "reset");
+        resetLog.put("operateTime", DateUtils.getTime());
+        resetLog.put("result", "success");
+        resetLog.put("createTime", DateUtils.getTime());
+        
+        return 1;
+    }
+
+    @Override
+    public List<PageData> selectMaintenanceRecordList(PageData pd)
+    {
+        return intrusionAlarmMapper.selectMaintenanceRecordList(pd);
+    }
+
+    @Override
+    public int addMaintenanceRecord(PageData pd)
+    {
+        pd.put("createTime", DateUtils.getTime());
+        return intrusionAlarmMapper.insertMaintenanceRecord(pd);
+    }
+
+    @Override
+    public PageData generateAlarmReport(PageData pd)
+    {
+        // 生成报警报表数据
+        PageData reportData = new PageData();
+        
+        // 获取基础统计数据
+        PageData statistics = intrusionAlarmMapper.selectAlarmStatistics(pd);
+        reportData.putAll(statistics);
+        
+        // 获取趋势数据
+        List<PageData> trendData = intrusionAlarmMapper.selectAlarmTrend(pd);
+        reportData.put("trendData", trendData);
+        
+        reportData.put("reportTime", DateUtils.getTime());
+        reportData.put("reportType", pd.get("reportType"));
+        
+        return reportData;
+    }
+
+    @Override
+    public int syncIntrusionData(PageData pd)
+    {
+        // 模拟从其他子系统同步数据
+        try {
+            // 这里应该调用其他子系统的接口获取数据
+            return 1;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+}

+ 296 - 0
pm-system/src/main/java/com/pm/subsystem/service/impl/VideoDeviceServiceImpl.java

@@ -0,0 +1,296 @@
+package com.pm.subsystem.service.impl;
+
+import java.util.List;
+import com.pm.common.utils.DateUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.pm.subsystem.mapper.VideoDeviceMapper;
+import com.pm.subsystem.service.IVideoDeviceService;
+import com.pm.common.config.PageData;
+
+/**
+ * 视频监控设备Service业务层处理
+ *
+ * @author lxf
+ * @date 2025-06-03
+ */
+@Service
+public class VideoDeviceServiceImpl implements IVideoDeviceService
+{
+    @Autowired
+    private VideoDeviceMapper videoDeviceMapper;
+
+    @Override
+    public PageData selectCameraById(Long id)
+    {
+        return videoDeviceMapper.selectCameraById(id);
+    }
+
+    @Override
+    public List<PageData> selectCameraList(PageData pd)
+    {
+        return videoDeviceMapper.selectCameraList(pd);
+    }
+
+    @Override
+    public int insertCamera(PageData pd)
+    {
+        pd.put("createTime", DateUtils.getTime());
+        pd.put("updateTime", DateUtils.getTime());
+        return videoDeviceMapper.insertCamera(pd);
+    }
+
+    @Override
+    public int updateCamera(PageData pd)
+    {
+        pd.put("updateTime", DateUtils.getTime());
+        return videoDeviceMapper.updateCamera(pd);
+    }
+
+    @Override
+    public int deleteCameraByIds(Long[] ids)
+    {
+        return videoDeviceMapper.deleteCameraByIds(ids);
+    }
+
+    @Override
+    public int deleteCameraById(Long id)
+    {
+        return videoDeviceMapper.deleteCameraById(id);
+    }
+
+    @Override
+    public PageData getCameraStream(String cameraCode)
+    {
+        // 模拟获取视频流地址
+        PageData result = new PageData();
+        result.put("cameraCode", cameraCode);
+        result.put("streamUrl", "rtmp://192.168.1.100:1935/live/" + cameraCode);
+        result.put("hlsUrl", "http://192.168.1.100:8080/hls/" + cameraCode + ".m3u8");
+        result.put("rtspUrl", "rtsp://192.168.1.100:554/live/" + cameraCode);
+        
+        return result;
+    }
+
+    @Override
+    public int ptzControl(PageData pd)
+    {
+        // 1. 记录云台控制命令
+        pd.put("createTime", DateUtils.getTime());
+        pd.put("controlTime", DateUtils.getTime());
+        
+        // 2. 这里应该调用摄像机的实际云台控制接口
+        // 模拟调用成功
+        pd.put("controlResult", "success");
+        
+        // 3. 记录控制日志
+        return videoDeviceMapper.insertPtzControl(pd);
+    }
+
+    @Override
+    public int recordingControl(PageData pd)
+    {
+        String action = (String) pd.get("action");
+        String cameraCode = (String) pd.get("cameraCode");
+        
+        // 模拟录像控制逻辑
+        if ("start".equals(action)) {
+            // 开始录像
+            PageData updatePd = new PageData();
+            updatePd.put("cameraCode", cameraCode);
+            updatePd.put("recordingStatus", 1);
+            updatePd.put("updateTime", DateUtils.getTime());
+            videoDeviceMapper.updateCamera(updatePd);
+            
+            // 创建录像文件记录
+            PageData recordingFile = new PageData();
+            recordingFile.put("cameraCode", cameraCode);
+            recordingFile.put("fileName", cameraCode + "_" + DateUtils.getTime() + ".mp4");
+            recordingFile.put("filePath", "/recordings/" + cameraCode + "/");
+            recordingFile.put("startTime", DateUtils.getTime());
+            recordingFile.put("status", 1); // 录像中
+            recordingFile.put("createTime", DateUtils.getTime());
+            videoDeviceMapper.insertRecordingFile(recordingFile);
+            
+        } else if ("stop".equals(action)) {
+            // 停止录像
+            PageData updatePd = new PageData();
+            updatePd.put("cameraCode", cameraCode);
+            updatePd.put("recordingStatus", 0);
+            updatePd.put("updateTime", DateUtils.getTime());
+            videoDeviceMapper.updateCamera(updatePd);
+        }
+        
+        return 1;
+    }
+
+    @Override
+    public int takeSnapshot(String cameraCode)
+    {
+        // 模拟抓拍逻辑
+        PageData snapshot = new PageData();
+        snapshot.put("cameraCode", cameraCode);
+        snapshot.put("fileName", cameraCode + "_snapshot_" + System.currentTimeMillis() + ".jpg");
+        snapshot.put("filePath", "/snapshots/" + cameraCode + "/");
+        snapshot.put("captureTime", DateUtils.getTime());
+        snapshot.put("createTime", DateUtils.getTime());
+        
+        // 这里应该调用实际的抓拍接口
+        // 模拟抓拍成功
+        return 1;
+    }
+
+    @Override
+    public List<PageData> selectLinkageRuleList(PageData pd)
+    {
+        return videoDeviceMapper.selectLinkageRuleList(pd);
+    }
+
+    @Override
+    public PageData selectLinkageRuleById(Long id)
+    {
+        return videoDeviceMapper.selectLinkageRuleById(id);
+    }
+
+    @Override
+    public int insertLinkageRule(PageData pd)
+    {
+        pd.put("createTime", DateUtils.getTime());
+        pd.put("updateTime", DateUtils.getTime());
+        return videoDeviceMapper.insertLinkageRule(pd);
+    }
+
+    @Override
+    public int updateLinkageRule(PageData pd)
+    {
+        pd.put("updateTime", DateUtils.getTime());
+        return videoDeviceMapper.updateLinkageRule(pd);
+    }
+
+    @Override
+    public int deleteLinkageRuleById(Long id)
+    {
+        return videoDeviceMapper.deleteLinkageRuleById(id);
+    }
+
+    @Override
+    public int toggleLinkageRule(Long id, PageData pd)
+    {
+        pd.put("id", id);
+        pd.put("updateTime", DateUtils.getTime());
+        return videoDeviceMapper.updateLinkageRule(pd);
+    }
+
+    @Override
+    public List<PageData> selectLinkageRecordList(PageData pd)
+    {
+        return videoDeviceMapper.selectLinkageRecordList(pd);
+    }
+
+    @Override
+    public int executeLinkageRule(PageData pd)
+    {
+        // 执行联动规则的逻辑
+        String triggerDeviceCode = (String) pd.get("triggerDeviceCode");
+        String linkageCameraCode = (String) pd.get("linkageCameraCode");
+        String linkageAction = (String) pd.get("linkageAction");
+        
+        // 记录联动执行
+        PageData linkageRecord = new PageData();
+        linkageRecord.put("ruleId", pd.get("ruleId"));
+        linkageRecord.put("triggerDeviceCode", triggerDeviceCode);
+        linkageRecord.put("linkageCameraCode", linkageCameraCode);
+        linkageRecord.put("linkageAction", linkageAction);
+        linkageRecord.put("executeTime", DateUtils.getTime());
+        linkageRecord.put("executeResult", "success");
+        linkageRecord.put("createTime", DateUtils.getTime());
+        
+        videoDeviceMapper.insertLinkageRecord(linkageRecord);
+        
+        // 根据联动动作执行对应操作
+        switch (linkageAction) {
+            case "start_recording":
+                PageData recordingPd = new PageData();
+                recordingPd.put("cameraCode", linkageCameraCode);
+                recordingPd.put("action", "start");
+                recordingControl(recordingPd);
+                break;
+            case "goto_preset":
+                PageData ptzPd = new PageData();
+                ptzPd.put("cameraCode", linkageCameraCode);
+                ptzPd.put("command", "PRESET_1");
+                ptzPd.put("speed", 5);
+                ptzControl(ptzPd);
+                break;
+            case "capture_image":
+                takeSnapshot(linkageCameraCode);
+                break;
+        }
+        
+        return 1;
+    }
+
+    @Override
+    public List<PageData> selectRecordingFileList(PageData pd)
+    {
+        return videoDeviceMapper.selectRecordingFileList(pd);
+    }
+
+    @Override
+    public List<PageData> selectPresetList(String cameraCode)
+    {
+        PageData pd = new PageData();
+        pd.put("cameraCode", cameraCode);
+        return videoDeviceMapper.selectPresetList(pd);
+    }
+
+    @Override
+    public int setPreset(PageData pd)
+    {
+        pd.put("createTime", DateUtils.getTime());
+        return videoDeviceMapper.insertPreset(pd);
+    }
+
+    @Override
+    public int deletePresetById(Long id)
+    {
+        return videoDeviceMapper.deletePresetById(id);
+    }
+
+    @Override
+    public PageData checkCameraHealth(String cameraCode)
+    {
+        // 模拟摄像机健康检测
+        PageData health = new PageData();
+        health.put("cameraCode", cameraCode);
+        health.put("isOnline", 1);
+        health.put("cpuUsage", 45.6);
+        health.put("memoryUsage", 67.8);
+        health.put("diskUsage", 23.4);
+        health.put("networkStatus", "正常");
+        health.put("lastCheckTime", DateUtils.getTime());
+        
+        return health;
+    }
+
+    @Override
+    public PageData getCameraStatistics()
+    {
+        return videoDeviceMapper.selectCameraStatistics(new PageData());
+    }
+
+    @Override
+    public int syncVideoData(PageData pd)
+    {
+        // 模拟从其他子系统同步视频设备数据
+        try {
+            // 这里应该调用其他子系统的接口获取摄像机数据
+            // 更新本地数据库
+            
+            return 1;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return 0;
+        }
+    }
+}

+ 160 - 0
pm-system/src/main/resources/mapper/patrol/PatrolPointMapper.xml

@@ -0,0 +1,160 @@
+<?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">
+
+<!-- PatrolPointMapper.xml -->
+<mapper namespace="com.pm.patrol.mapper.PatrolPointMapper">
+
+    <resultMap type="pd" id="PatrolPointResult">
+        <result property="id"    column="id"    />
+        <result property="pointCode"    column="point_code"    />
+        <result property="pointName"    column="point_name"    />
+        <result property="areaId"    column="area_id"    />
+        <result property="areaName"    column="area_name"    />
+        <result property="buildingId"    column="building_id"    />
+        <result property="buildingName"    column="building_name"    />
+        <result property="floorId"    column="floor_id"    />
+        <result property="floorName"    column="floor_name"    />
+        <result property="positionX"    column="position_x"    />
+        <result property="positionY"    column="position_y"    />
+        <result property="positionZ"    column="position_z"    />
+        <result property="pointType"    column="point_type"    />
+        <result property="patrolFrequency"    column="patrol_frequency"    />
+        <result property="cameraIds"    column="camera_ids"    />
+        <result property="deviceId"    column="device_id"    />
+        <result property="qrCode"    column="qr_code"    />
+        <result property="isActive"    column="is_active"    />
+        <result property="remark"    column="remark"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="updateTime"    column="update_time"    />
+    </resultMap>
+
+    <sql id="selectPatrolPointVo">
+        select id, point_code, point_name, area_id, area_name, building_id, building_name,
+               floor_id, floor_name, position_x, position_y, position_z, point_type,
+               patrol_frequency, camera_ids, device_id, qr_code, is_active, remark,
+               create_time, update_time
+        from patrol_point_info
+    </sql>
+
+    <select id="selectPatrolPointList" parameterType="pd" resultMap="PatrolPointResult">
+        <include refid="selectPatrolPointVo"/>
+        <where>
+            <if test="pointCode != null and pointCode != ''"> and point_code like concat('%', #{pointCode}, '%')</if>
+            <if test="pointName != null and pointName != ''"> and point_name like concat('%', #{pointName}, '%')</if>
+            <if test="buildingId != null and buildingId != ''"> and building_id = #{buildingId}</if>
+            <if test="floorId != null and floorId != ''"> and floor_id = #{floorId}</if>
+            <if test="pointType != null and pointType != ''"> and point_type = #{pointType}</if>
+            <if test="isActive != null"> and is_active = #{isActive}</if>
+        </where>
+        order by create_time desc
+    </select>
+
+    <select id="selectPatrolPointById" parameterType="Long" resultMap="PatrolPointResult">
+        <include refid="selectPatrolPointVo"/>
+        where id = #{id}
+    </select>
+
+    <insert id="insertPatrolPoint" parameterType="pd" useGeneratedKeys="true" keyProperty="id">
+        insert into patrol_point_info
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="pointCode != null and pointCode != ''">point_code,</if>
+            <if test="pointName != null and pointName != ''">point_name,</if>
+            <if test="areaId != null">area_id,</if>
+            <if test="areaName != null">area_name,</if>
+            <if test="buildingId != null">building_id,</if>
+            <if test="buildingName != null">building_name,</if>
+            <if test="floorId != null">floor_id,</if>
+            <if test="floorName != null">floor_name,</if>
+            <if test="positionX != null">position_x,</if>
+            <if test="positionY != null">position_y,</if>
+            <if test="positionZ != null">position_z,</if>
+            <if test="pointType != null">point_type,</if>
+            <if test="patrolFrequency != null">patrol_frequency,</if>
+            <if test="cameraIds != null">camera_ids,</if>
+            <if test="deviceId != null">device_id,</if>
+            <if test="qrCode != null">qr_code,</if>
+            <if test="isActive != null">is_active,</if>
+            <if test="remark != null">remark,</if>
+            create_time
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="pointCode != null and pointCode != ''">#{pointCode},</if>
+            <if test="pointName != null and pointName != ''">#{pointName},</if>
+            <if test="areaId != null">#{areaId},</if>
+            <if test="areaName != null">#{areaName},</if>
+            <if test="buildingId != null">#{buildingId},</if>
+            <if test="buildingName != null">#{buildingName},</if>
+            <if test="floorId != null">#{floorId},</if>
+            <if test="floorName != null">#{floorName},</if>
+            <if test="positionX != null">#{positionX},</if>
+            <if test="positionY != null">#{positionY},</if>
+            <if test="positionZ != null">#{positionZ},</if>
+            <if test="pointType != null">#{pointType},</if>
+            <if test="patrolFrequency != null">#{patrolFrequency},</if>
+            <if test="cameraIds != null">#{cameraIds},</if>
+            <if test="deviceId != null">#{deviceId},</if>
+            <if test="qrCode != null">#{qrCode},</if>
+            <if test="isActive != null">#{isActive},</if>
+            <if test="remark != null">#{remark},</if>
+            now()
+        </trim>
+    </insert>
+
+    <update id="updatePatrolPoint" parameterType="pd">
+        update patrol_point_info
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="pointCode != null and pointCode != ''">point_code = #{pointCode},</if>
+            <if test="pointName != null and pointName != ''">point_name = #{pointName},</if>
+            <if test="areaId != null">area_id = #{areaId},</if>
+            <if test="areaName != null">area_name = #{areaName},</if>
+            <if test="buildingId != null">building_id = #{buildingId},</if>
+            <if test="buildingName != null">building_name = #{buildingName},</if>
+            <if test="floorId != null">floor_id = #{floorId},</if>
+            <if test="floorName != null">floor_name = #{floorName},</if>
+            <if test="positionX != null">position_x = #{positionX},</if>
+            <if test="positionY != null">position_y = #{positionY},</if>
+            <if test="positionZ != null">position_z = #{positionZ},</if>
+            <if test="pointType != null">point_type = #{pointType},</if>
+            <if test="patrolFrequency != null">patrol_frequency = #{patrolFrequency},</if>
+            <if test="cameraIds != null">camera_ids = #{cameraIds},</if>
+            <if test="deviceId != null">device_id = #{deviceId},</if>
+            <if test="qrCode != null">qr_code = #{qrCode},</if>
+            <if test="isActive != null">is_active = #{isActive},</if>
+            <if test="remark != null">remark = #{remark},</if>
+            update_time = now()
+        </trim>
+        where id = #{id}
+    </update>
+
+    <update id="updatePatrolPointStatus" parameterType="pd">
+        update patrol_point_info
+        set is_active = #{isActive},
+            update_time = now()
+        where id = #{id}
+    </update>
+
+    <delete id="deletePatrolPointById" parameterType="Long">
+        delete from patrol_point_info where id = #{id}
+    </delete>
+
+    <delete id="deletePatrolPointByIds" parameterType="String">
+        delete from patrol_point_info where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+
+    <select id="selectPointsByRouteId" parameterType="String" resultMap="PatrolPointResult">
+        <include refid="selectPatrolPointVo"/>
+        where is_active = 1
+        <!-- 这里需要关联路线表,暂时模拟 -->
+        order by point_code
+    </select>
+
+    <select id="getTotalActivePoints" resultType="int">
+        select count(*) from patrol_point_info where is_active = 1
+    </select>
+
+</mapper>

+ 155 - 0
pm-system/src/main/resources/mapper/patrol/PatrolRecordMapper.xml

@@ -0,0 +1,155 @@
+<?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.pm.patrol.mapper.PatrolRecordMapper">
+
+    <resultMap type="pd" id="PatrolRecordResult">
+        <result property="id"    column="id"    />
+        <result property="recordId"    column="record_id"    />
+        <result property="pointCode"    column="point_code"    />
+        <result property="pointName"    column="point_name"    />
+        <result property="patrolPersonId"    column="patrol_person_id"    />
+        <result property="patrolPersonName"    column="patrol_person_name"    />
+        <result property="patrolTime"    column="patrol_time"    />
+        <result property="patrolStatus"    column="patrol_status"    />
+        <result property="gpsLongitude"    column="gps_longitude"    />
+        <result property="gpsLatitude"    column="gps_latitude"    />
+        <result property="deviceSn"    column="device_sn"    />
+        <result property="patrolRouteId"    column="patrol_route_id"    />
+        <result property="patrolRouteName"    column="patrol_route_name"    />
+        <result property="patrolCount"    column="patrol_count"    />
+        <result property="exceptionInfo"    column="exception_info"    />
+        <result property="images"    column="images"    />
+        <result property="remark"    column="remark"    />
+        <result property="createTime"    column="create_time"    />
+    </resultMap>
+
+    <sql id="selectPatrolRecordVo">
+        select id, record_id, point_code, point_name, patrol_person_id, patrol_person_name,
+        patrol_time, patrol_status, gps_longitude, gps_latitude, device_sn,
+        patrol_route_id, patrol_route_name, patrol_count, exception_info,
+        images, remark, create_time
+        from patrol_record_info
+    </sql>
+
+    <select id="selectPatrolRecordList" parameterType="pd" resultMap="PatrolRecordResult">
+        <include refid="selectPatrolRecordVo"/>
+        <where>
+            <if test="pointCode != null and pointCode != ''"> and point_code = #{pointCode}</if>
+            <if test="patrolPersonName != null and patrolPersonName != ''">
+                and patrol_person_name like concat('%', #{patrolPersonName}, '%')
+            </if>
+            <if test="patrolRouteId != null and patrolRouteId != ''"> and patrol_route_id = #{patrolRouteId}</if>
+            <if test="patrolStatus != null"> and patrol_status = #{patrolStatus}</if>
+            <if test="beginTime != null and beginTime != ''">
+                and patrol_time >= #{beginTime}
+            </if>
+            <if test="endTime != null and endTime != ''">
+                and patrol_time &lt;= #{endTime}
+            </if>
+        </where>
+        order by patrol_time desc
+    </select>
+
+    <select id="selectPatrolRecordById" parameterType="Long" resultMap="PatrolRecordResult">
+        <include refid="selectPatrolRecordVo"/>
+        where id = #{id}
+    </select>
+
+    <select id="selectRecentPatrolRecords" parameterType="pd" resultMap="PatrolRecordResult">
+        <include refid="selectPatrolRecordVo"/>
+        where patrol_time >= date_sub(now(), interval #{recentMinutes} minute)
+        order by patrol_time desc
+    </select>
+
+    <select id="getTodayPatrolCount" parameterType="pd" resultType="int">
+        select count(*) from patrol_record_info
+        where date(patrol_time) = curdate()
+    </select>
+
+    <select id="getTodayNormalCount" parameterType="pd" resultType="int">
+        select count(*) from patrol_record_info
+        where date(patrol_time) = curdate() and patrol_status = 1
+    </select>
+
+    <select id="getTodayAbnormalCount" parameterType="pd" resultType="int">
+        select count(*) from patrol_record_info
+        where date(patrol_time) = curdate() and patrol_status = 0
+    </select>
+
+    <select id="getTodayTimeoutCount" parameterType="pd" resultType="int">
+        select count(*) from patrol_record_info
+        where date(patrol_time) = curdate() and patrol_status = 2
+    </select>
+
+    <select id="getActivePersonnel" parameterType="pd" resultMap="PatrolRecordResult">
+        select distinct patrol_person_id, patrol_person_name, count(*) as patrol_count
+        from patrol_record_info
+        where date(patrol_time) = curdate()
+        group by patrol_person_id, patrol_person_name
+        order by patrol_count desc
+    </select>
+
+    <select id="getTodayPatrolledPoints" parameterType="pd" resultType="int">
+        select count(distinct point_code) from patrol_record_info
+        where date(patrol_time) = curdate()
+    </select>
+
+    <insert id="insertPatrolRecord" parameterType="pd" useGeneratedKeys="true" keyProperty="id">
+        insert into patrol_record_info
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="recordId != null and recordId != ''">record_id,</if>
+            <if test="pointCode != null and pointCode != ''">point_code,</if>
+            <if test="pointName != null">point_name,</if>
+            <if test="patrolPersonId != null">patrol_person_id,</if>
+            <if test="patrolPersonName != null">patrol_person_name,</if>
+            <if test="patrolTime != null">patrol_time,</if>
+            <if test="patrolStatus != null">patrol_status,</if>
+            <if test="gpsLongitude != null">gps_longitude,</if>
+            <if test="gpsLatitude != null">gps_latitude,</if>
+            <if test="deviceSn != null">device_sn,</if>
+            <if test="patrolRouteId != null">patrol_route_id,</if>
+            <if test="patrolRouteName != null">patrol_route_name,</if>
+            <if test="patrolCount != null">patrol_count,</if>
+            <if test="exceptionInfo != null">exception_info,</if>
+            <if test="images != null">images,</if>
+            <if test="remark != null">remark,</if>
+            create_time
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="recordId != null and recordId != ''">#{recordId},</if>
+            <if test="pointCode != null and pointCode != ''">#{pointCode},</if>
+            <if test="pointName != null">#{pointName},</if>
+            <if test="patrolPersonId != null">#{patrolPersonId},</if>
+            <if test="patrolPersonName != null">#{patrolPersonName},</if>
+            <if test="patrolTime != null">#{patrolTime},</if>
+            <if test="patrolStatus != null">#{patrolStatus},</if>
+            <if test="gpsLongitude != null">#{gpsLongitude},</if>
+            <if test="gpsLatitude != null">#{gpsLatitude},</if>
+            <if test="deviceSn != null">#{deviceSn},</if>
+            <if test="patrolRouteId != null">#{patrolRouteId},</if>
+            <if test="patrolRouteName != null">#{patrolRouteName},</if>
+            <if test="patrolCount != null">#{patrolCount},</if>
+            <if test="exceptionInfo != null">#{exceptionInfo},</if>
+            <if test="images != null">#{images},</if>
+            <if test="remark != null">#{remark},</if>
+            now()
+        </trim>
+    </insert>
+
+    <update id="updatePatrolRecord" parameterType="pd">
+        update patrol_record_info
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="exceptionInfo != null">exception_info = #{exceptionInfo},</if>
+            <if test="images != null">images = #{images},</if>
+            <if test="remark != null">remark = #{remark},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deletePatrolRecordById" parameterType="Long">
+        delete from patrol_record_info where id = #{id}
+    </delete>
+
+</mapper>

+ 249 - 0
pm-system/src/main/resources/mapper/subsystem/EnergyDeviceMapper.xml

@@ -0,0 +1,249 @@
+<?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.pm.subsystem.mapper.EnergyDeviceMapper">
+
+    <resultMap type="pd" id="EnergyDeviceResult">
+        <result property="id"    column="id"    />
+        <result property="deviceCode"    column="device_code"    />
+        <result property="deviceName"    column="device_name"    />
+        <result property="deviceType"    column="device_type"    />
+        <result property="energyType"    column="energy_type"    />
+        <result property="building"    column="building"    />
+        <result property="floor"    column="floor"    />
+        <result property="roomNo"    column="room_no"    />
+        <result property="currentPower"    column="current_power"    />
+        <result property="ratedPower"    column="rated_power"    />
+        <result property="dailyConsumption"    column="daily_consumption"    />
+        <result property="monthlyConsumption"    column="monthly_consumption"    />
+        <result property="yearlyConsumption"    column="yearly_consumption"    />
+        <result property="loadRate"    column="load_rate"    />
+        <result property="deviceStatus"    column="device_status"    />
+        <result property="isOnline"    column="is_online"    />
+        <result property="temperature"    column="temperature"    />
+        <result property="powerSetting"    column="power_setting"    />
+        <result property="thresholdValue"    column="threshold_value"    />
+        <result property="description"    column="description"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="updateTime"    column="update_time"    />
+    </resultMap>
+
+    <sql id="selectEnergyDeviceVo">
+        select id, device_code, device_name, device_type, energy_type, building, floor, room_no,
+               current_power, rated_power, daily_consumption, monthly_consumption, yearly_consumption,
+               load_rate, device_status, is_online, temperature, power_setting, threshold_value,
+               description, create_time, update_time
+        from energy_device
+    </sql>
+
+    <select id="selectEnergyDeviceList" parameterType="pd" resultMap="EnergyDeviceResult">
+        <include refid="selectEnergyDeviceVo"/>
+        <where>
+            <if test="deviceCode != null and deviceCode != ''"> and device_code like concat('%', #{deviceCode}, '%')</if>
+            <if test="deviceName != null and deviceName != ''"> and device_name like concat('%', #{deviceName}, '%')</if>
+            <if test="energyType != null and energyType != ''"> and energy_type = #{energyType}</if>
+            <if test="building != null and building != ''"> and building = #{building}</if>
+            <if test="floor != null and floor != ''"> and floor = #{floor}</if>
+            <if test="deviceStatus != null"> and device_status = #{deviceStatus}</if>
+            <if test="isOnline != null"> and is_online = #{isOnline}</if>
+            <if test="beginTime != null and beginTime != ''"> and date_format(update_time,'%y%m%d') &gt;= date_format(#{beginTime},'%y%m%d')</if>
+            <if test="endTime != null and endTime != ''"> and date_format(update_time,'%y%m%d') &lt;= date_format(#{endTime},'%y%m%d')</if>
+        </where>
+        order by update_time desc
+    </select>
+
+    <select id="selectEnergyDeviceById" parameterType="pd" resultMap="EnergyDeviceResult">
+        <include refid="selectEnergyDeviceVo"/>
+        where id = #{id}
+    </select>
+
+    <insert id="insertEnergyDevice" parameterType="pd" useGeneratedKeys="true" keyProperty="id">
+        insert into energy_device
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="deviceCode != null and deviceCode != ''">device_code,</if>
+            <if test="deviceName != null and deviceName != ''">device_name,</if>
+            <if test="deviceType != null and deviceType != ''">device_type,</if>
+            <if test="energyType != null and energyType != ''">energy_type,</if>
+            <if test="building != null and building != ''">building,</if>
+            <if test="floor != null and floor != ''">floor,</if>
+            <if test="roomNo != null and roomNo != ''">room_no,</if>
+            <if test="currentPower != null">current_power,</if>
+            <if test="ratedPower != null">rated_power,</if>
+            <if test="dailyConsumption != null">daily_consumption,</if>
+            <if test="monthlyConsumption != null">monthly_consumption,</if>
+            <if test="yearlyConsumption != null">yearly_consumption,</if>
+            <if test="loadRate != null">load_rate,</if>
+            <if test="deviceStatus != null">device_status,</if>
+            <if test="isOnline != null">is_online,</if>
+            <if test="temperature != null">temperature,</if>
+            <if test="powerSetting != null">power_setting,</if>
+            <if test="thresholdValue != null">threshold_value,</if>
+            <if test="description != null and description != ''">description,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateTime != null">update_time,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="deviceCode != null and deviceCode != ''">#{deviceCode},</if>
+            <if test="deviceName != null and deviceName != ''">#{deviceName},</if>
+            <if test="deviceType != null and deviceType != ''">#{deviceType},</if>
+            <if test="energyType != null and energyType != ''">#{energyType},</if>
+            <if test="building != null and building != ''">#{building},</if>
+            <if test="floor != null and floor != ''">#{floor},</if>
+            <if test="roomNo != null and roomNo != ''">#{roomNo},</if>
+            <if test="currentPower != null">#{currentPower},</if>
+            <if test="ratedPower != null">#{ratedPower},</if>
+            <if test="dailyConsumption != null">#{dailyConsumption},</if>
+            <if test="monthlyConsumption != null">#{monthlyConsumption},</if>
+            <if test="yearlyConsumption != null">#{yearlyConsumption},</if>
+            <if test="loadRate != null">#{loadRate},</if>
+            <if test="deviceStatus != null">#{deviceStatus},</if>
+            <if test="isOnline != null">#{isOnline},</if>
+            <if test="temperature != null">#{temperature},</if>
+            <if test="powerSetting != null">#{powerSetting},</if>
+            <if test="thresholdValue != null">#{thresholdValue},</if>
+            <if test="description != null and description != ''">#{description},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+        </trim>
+    </insert>
+
+    <update id="updateEnergyDevice" parameterType="pd">
+        update energy_device
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="deviceName != null and deviceName != ''">device_name = #{deviceName},</if>
+            <if test="deviceType != null and deviceType != ''">device_type = #{deviceType},</if>
+            <if test="energyType != null and energyType != ''">energy_type = #{energyType},</if>
+            <if test="building != null and building != ''">building = #{building},</if>
+            <if test="floor != null and floor != ''">floor = #{floor},</if>
+            <if test="roomNo != null and roomNo != ''">room_no = #{roomNo},</if>
+            <if test="currentPower != null">current_power = #{currentPower},</if>
+            <if test="ratedPower != null">rated_power = #{ratedPower},</if>
+            <if test="dailyConsumption != null">daily_consumption = #{dailyConsumption},</if>
+            <if test="monthlyConsumption != null">monthly_consumption = #{monthlyConsumption},</if>
+            <if test="yearlyConsumption != null">yearly_consumption = #{yearlyConsumption},</if>
+            <if test="loadRate != null">load_rate = #{loadRate},</if>
+            <if test="deviceStatus != null">device_status = #{deviceStatus},</if>
+            <if test="isOnline != null">is_online = #{isOnline},</if>
+            <if test="temperature != null">temperature = #{temperature},</if>
+            <if test="powerSetting != null">power_setting = #{powerSetting},</if>
+            <if test="thresholdValue != null">threshold_value = #{thresholdValue},</if>
+            <if test="description != null and description != ''">description = #{description},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteEnergyDeviceById" parameterType="Long">
+        delete from energy_device where id = #{id}
+    </delete>
+
+    <delete id="deleteEnergyDeviceByIds" parameterType="String">
+        delete from energy_device where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+
+    <!-- 查询能耗统计数据 -->
+    <select id="selectEnergyStatistics" parameterType="pd" resultType="pd">
+        select
+            coalesce(sum(case when energy_type = 'electricity' and date(update_time) = curdate() then daily_consumption else 0 end), 0) as todayElectricity,
+            coalesce(sum(case when energy_type = 'water' and date(update_time) = curdate() then daily_consumption else 0 end), 0) as todayWater,
+            coalesce(sum(case when energy_type = 'gas' and date(update_time) = curdate() then daily_consumption else 0 end), 0) as todayGas,
+            coalesce(sum(case when energy_type = 'electricity' then monthly_consumption else 0 end), 0) as monthElectricity,
+            coalesce(sum(case when energy_type = 'water' then monthly_consumption else 0 end), 0) as monthWater,
+            coalesce(sum(case when energy_type = 'gas' then monthly_consumption else 0 end), 0) as monthGas,
+            count(case when is_online = 1 then 1 end) as onlineDevices,
+            count(*) as totalDevices,
+            count(case when device_status = 2 then 1 end) as alarmDevices
+        from energy_device
+    </select>
+
+    <!-- 查询能耗历史记录 -->
+    <select id="selectEnergyHistoryList" parameterType="pd" resultType="pd">
+        select id, device_code, device_name, energy_type, consumption_value,
+        power_value, record_time, create_time
+        from energy_history
+        <where>
+            <if test="deviceCode != null and deviceCode != ''"> and device_code = #{deviceCode}</if>
+            <if test="energyType != null and energyType != ''"> and energy_type = #{energyType}</if>
+            <if test="beginTime != null and beginTime != ''"> and date_format(record_time,'%y%m%d') &gt;= date_format(#{beginTime},'%y%m%d')</if>
+            <if test="endTime != null and endTime != ''"> and date_format(record_time,'%y%m%d') &lt;= date_format(#{endTime},'%y%m%d')</if>
+        </where>
+        order by record_time desc
+    </select>
+
+    <!-- 新增能耗历史记录 -->
+    <insert id="insertEnergyHistory" parameterType="pd">
+        insert into energy_history (device_code, device_name, energy_type, consumption_value, power_value, record_time, create_time)
+        values (#{deviceCode}, #{deviceName}, #{energyType}, #{consumptionValue}, #{powerValue}, #{recordTime}, #{createTime})
+    </insert>
+
+    <!-- 查询实时能耗数据 -->
+    <select id="selectRealTimeEnergyData" parameterType="String" resultType="pd">
+        select device_code, device_name, energy_type, current_power,
+               daily_consumption, temperature, device_status, is_online, update_time
+        from energy_device
+        where device_code = #{deviceCode}
+    </select>
+
+    <!-- 查询能耗告警信息 -->
+    <select id="selectEnergyAlarmList" parameterType="pd" resultType="pd">
+        select id, device_code, device_name, alarm_type, alarm_level, alarm_message,
+        alarm_value, threshold_value, alarm_time, handle_status, handle_user,
+        handle_time, create_time
+        from energy_alarm
+        <where>
+            <if test="deviceCode != null and deviceCode != ''"> and device_code like concat('%', #{deviceCode}, '%')</if>
+            <if test="deviceName != null and deviceName != ''"> and device_name like concat('%', #{deviceName}, '%')</if>
+            <if test="alarmType != null and alarmType != ''"> and alarm_type = #{alarmType}</if>
+            <if test="alarmLevel != null"> and alarm_level = #{alarmLevel}</if>
+            <if test="handleStatus != null"> and handle_status = #{handleStatus}</if>
+            <if test="beginTime != null and beginTime != ''"> and date_format(alarm_time,'%y%m%d') &gt;= date_format(#{beginTime},'%y%m%d')</if>
+            <if test="endTime != null and endTime != ''"> and date_format(alarm_time,'%y%m%d') &lt;= date_format(#{endTime},'%y%m%d')</if>
+        </where>
+        order by alarm_time desc
+    </select>
+
+    <!-- 新增能耗告警 -->
+    <insert id="insertEnergyAlarm" parameterType="pd" useGeneratedKeys="true" keyProperty="id">
+        insert into energy_alarm (device_code, device_name, alarm_type, alarm_level, alarm_message,
+                                  alarm_value, threshold_value, alarm_time, handle_status, create_time)
+        values (#{deviceCode}, #{deviceName}, #{alarmType}, #{alarmLevel}, #{alarmMessage},
+                #{alarmValue}, #{thresholdValue}, #{alarmTime}, 0, #{createTime})
+    </insert>
+
+    <!-- 更新能耗告警状态 -->
+    <update id="updateEnergyAlarmStatus" parameterType="pd">
+        update energy_alarm
+        set handle_status = #{handleStatus}, handle_user = #{handleUser},
+            handle_time = #{handleTime}, handle_remark = #{handleRemark}
+        where id = #{id}
+    </update>
+
+    <!-- 查询设备控制记录 -->
+    <select id="selectDeviceControlList" parameterType="pd" resultType="pd">
+        select id, device_code, device_name, control_type, control_value,
+        control_user, control_time, control_result, create_time
+        from device_control
+        <where>
+            <if test="deviceCode != null and deviceCode != ''"> and device_code like concat('%', #{deviceCode}, '%')</if>
+            <if test="deviceName != null and deviceName != ''"> and device_name like concat('%', #{deviceName}, '%')</if>
+            <if test="controlType != null and controlType != ''"> and control_type = #{controlType}</if>
+            <if test="controlUser != null and controlUser != ''"> and control_user = #{controlUser}</if>
+            <if test="beginTime != null and beginTime != ''"> and date_format(control_time,'%y%m%d') &gt;= date_format(#{beginTime},'%y%m%d')</if>
+            <if test="endTime != null and endTime != ''"> and date_format(control_time,'%y%m%d') &lt;= date_format(#{endTime},'%y%m%d')</if>
+        </where>
+        order by control_time desc
+    </select>
+
+    <!-- 新增设备控制记录 -->
+    <insert id="insertDeviceControl" parameterType="pd" useGeneratedKeys="true" keyProperty="id">
+        insert into device_control (device_code, device_name, control_type, control_value,
+                                    control_user, control_time, control_result, create_time)
+        values (#{deviceCode}, #{deviceName}, #{controlType}, #{controlValue},
+                #{controlUser}, #{controlTime}, #{controlResult}, #{createTime})
+    </insert>
+
+</mapper>

+ 415 - 0
pm-system/src/main/resources/mapper/subsystem/IntrusionAlarmMapper.xml

@@ -0,0 +1,415 @@
+<?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.pm.subsystem.mapper.IntrusionAlarmMapper">
+
+    <!-- ==================== 报警点相关查询 ==================== -->
+    <resultMap type="pd" id="AlarmPointResult">
+        <result property="id"    column="id"    />
+        <result property="deviceCode"    column="device_code"    />
+        <result property="deviceName"    column="device_name"    />
+        <result property="alarmType"    column="alarm_type"    />
+        <result property="zoneCode"    column="zone_code"    />
+        <result property="zoneName"    column="zone_name"    />
+        <result property="building"    column="building"    />
+        <result property="floor"    column="floor"    />
+        <result property="locationDetail"    column="location_detail"    />
+        <result property="brand"    column="brand"    />
+        <result property="model"    column="model"    />
+        <result property="deviceStatus"    column="device_status"    />
+        <result property="armStatus"    column="arm_status"    />
+        <result property="sensitivity"    column="sensitivity"    />
+        <result property="delayTime"    column="delay_time"    />
+        <result property="linkageCameras"    column="linkage_cameras"    />
+        <result property="lastAlarmTime"    column="last_alarm_time"    />
+        <result property="installTime"    column="install_time"    />
+        <result property="lastMaintainTime"    column="last_maintain_time"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="updateTime"    column="update_time"    />
+    </resultMap>
+
+    <sql id="selectAlarmPointVo">
+        select id, device_code, device_name, alarm_type, zone_code, zone_name, building, floor,
+               location_detail, brand, model, device_status, arm_status, sensitivity, delay_time,
+               linkage_cameras, last_alarm_time, install_time, last_maintain_time, create_time, update_time
+        from alarm_point
+    </sql>
+
+    <select id="selectAlarmPointList" parameterType="pd" resultMap="AlarmPointResult">
+        <include refid="selectAlarmPointVo"/>
+        <where>
+            <if test="deviceCode != null and deviceCode != ''"> and device_code like concat('%', #{deviceCode}, '%')</if>
+            <if test="deviceName != null and deviceName != ''"> and device_name like concat('%', #{deviceName}, '%')</if>
+            <if test="alarmType != null and alarmType != ''"> and alarm_type = #{alarmType}</if>
+            <if test="zone != null and zone != ''"> and zone_code = #{zone}</if>
+            <if test="building != null and building != ''"> and building = #{building}</if>
+            <if test="floor != null and floor != ''"> and floor = #{floor}</if>
+            <if test="deviceStatus != null"> and device_status = #{deviceStatus}</if>
+            <if test="armStatus != null"> and arm_status = #{armStatus}</if>
+            <if test="beginTime != null and beginTime != ''"> and date_format(create_time,'%y%m%d') &gt;= date_format(#{beginTime},'%y%m%d')</if>
+            <if test="endTime != null and endTime != ''"> and date_format(create_time,'%y%m%d') &lt;= date_format(#{endTime},'%y%m%d')</if>
+        </where>
+        order by create_time desc
+    </select>
+
+    <select id="selectAlarmPointById" parameterType="pd" resultMap="AlarmPointResult">
+        <include refid="selectAlarmPointVo"/>
+        where id = #{id}
+    </select>
+
+    <insert id="insertAlarmPoint" parameterType="pd" useGeneratedKeys="true" keyProperty="id">
+        insert into alarm_point
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="deviceCode != null and deviceCode != ''">device_code,</if>
+            <if test="deviceName != null and deviceName != ''">device_name,</if>
+            <if test="alarmType != null and alarmType != ''">alarm_type,</if>
+            <if test="zoneCode != null and zoneCode != ''">zone_code,</if>
+            <if test="zoneName != null and zoneName != ''">zone_name,</if>
+            <if test="building != null and building != ''">building,</if>
+            <if test="floor != null and floor != ''">floor,</if>
+            <if test="locationDetail != null and locationDetail != ''">location_detail,</if>
+            <if test="brand != null and brand != ''">brand,</if>
+            <if test="model != null and model != ''">model,</if>
+            <if test="deviceStatus != null">device_status,</if>
+            <if test="armStatus != null">arm_status,</if>
+            <if test="sensitivity != null">sensitivity,</if>
+            <if test="delayTime != null">delay_time,</if>
+            <if test="linkageCameras != null and linkageCameras != ''">linkage_cameras,</if>
+            <if test="lastAlarmTime != null">last_alarm_time,</if>
+            <if test="installTime != null">install_time,</if>
+            <if test="lastMaintainTime != null">last_maintain_time,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateTime != null">update_time,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="deviceCode != null and deviceCode != ''">#{deviceCode},</if>
+            <if test="deviceName != null and deviceName != ''">#{deviceName},</if>
+            <if test="alarmType != null and alarmType != ''">#{alarmType},</if>
+            <if test="zoneCode != null and zoneCode != ''">#{zoneCode},</if>
+            <if test="zoneName != null and zoneName != ''">#{zoneName},</if>
+            <if test="building != null and building != ''">#{building},</if>
+            <if test="floor != null and floor != ''">#{floor},</if>
+            <if test="locationDetail != null and locationDetail != ''">#{locationDetail},</if>
+            <if test="brand != null and brand != ''">#{brand},</if>
+            <if test="model != null and model != ''">#{model},</if>
+            <if test="deviceStatus != null">#{deviceStatus},</if>
+            <if test="armStatus != null">#{armStatus},</if>
+            <if test="sensitivity != null">#{sensitivity},</if>
+            <if test="delayTime != null">#{delayTime},</if>
+            <if test="linkageCameras != null and linkageCameras != ''">#{linkageCameras},</if>
+            <if test="lastAlarmTime != null">#{lastAlarmTime},</if>
+            <if test="installTime != null">#{installTime},</if>
+            <if test="lastMaintainTime != null">#{lastMaintainTime},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+        </trim>
+    </insert>
+
+    <update id="updateAlarmPoint" parameterType="pd">
+        update alarm_point
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="deviceName != null and deviceName != ''">device_name = #{deviceName},</if>
+            <if test="alarmType != null and alarmType != ''">alarm_type = #{alarmType},</if>
+            <if test="zoneCode != null and zoneCode != ''">zone_code = #{zoneCode},</if>
+            <if test="zoneName != null and zoneName != ''">zone_name = #{zoneName},</if>
+            <if test="building != null and building != ''">building = #{building},</if>
+            <if test="floor != null and floor != ''">floor = #{floor},</if>
+            <if test="locationDetail != null and locationDetail != ''">location_detail = #{locationDetail},</if>
+            <if test="brand != null and brand != ''">brand = #{brand},</if>
+            <if test="model != null and model != ''">model = #{model},</if>
+            <if test="deviceStatus != null">device_status = #{deviceStatus},</if>
+            <if test="armStatus != null">arm_status = #{armStatus},</if>
+            <if test="sensitivity != null">sensitivity = #{sensitivity},</if>
+            <if test="delayTime != null">delay_time = #{delayTime},</if>
+            <if test="linkageCameras != null">linkage_cameras = #{linkageCameras},</if>
+            <if test="lastAlarmTime != null">last_alarm_time = #{lastAlarmTime},</if>
+            <if test="installTime != null">install_time = #{installTime},</if>
+            <if test="lastMaintainTime != null">last_maintain_time = #{lastMaintainTime},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+        </trim>
+        <where>
+            <if test="id != null">id = #{id}</if>
+            <if test="deviceCode != null and deviceCode != ''">or device_code = #{deviceCode}</if>
+        </where>
+    </update>
+
+    <delete id="deleteAlarmPointById" parameterType="Long">
+        delete from alarm_point where id = #{id}
+    </delete>
+
+    <delete id="deleteAlarmPointByIds" parameterType="String">
+        delete from alarm_point where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+
+    <!-- ==================== 报警记录相关查询 ==================== -->
+    <select id="selectAlarmList" parameterType="pd" resultType="pd">
+        select id, device_code, device_name, alarm_type, alarm_level, alarm_message, building, floor,
+        zone_code, zone_name, alarm_time, handle_status, handle_user, handle_time, handle_remark,
+        linkage_status, linkage_result, create_time
+        from alarm_record
+        <where>
+            <if test="deviceCode != null and deviceCode != ''"> and device_code like concat('%', #{deviceCode}, '%')</if>
+            <if test="deviceName != null and deviceName != ''"> and device_name like concat('%', #{deviceName}, '%')</if>
+            <if test="alarmType != null and alarmType != ''"> and alarm_type = #{alarmType}</if>
+            <if test="alarmLevel != null"> and alarm_level = #{alarmLevel}</if>
+            <if test="handleStatus != null"> and handle_status = #{handleStatus}</if>
+            <if test="zone != null and zone != ''"> and zone_code = #{zone}</if>
+            <if test="building != null and building != ''"> and building = #{building}</if>
+            <if test="linkageStatus != null"> and linkage_status = #{linkageStatus}</if>
+            <if test="beginTime != null and beginTime != ''"> and date_format(alarm_time,'%y%m%d') &gt;= date_format(#{beginTime},'%y%m%d')</if>
+            <if test="endTime != null and endTime != ''"> and date_format(alarm_time,'%y%m%d') &lt;= date_format(#{endTime},'%y%m%d')</if>
+        </where>
+        order by alarm_time desc
+    </select>
+
+    <select id="selectAlarmById" parameterType="pd" resultType="pd">
+        select id, device_code, device_name, alarm_type, alarm_level, alarm_message, building, floor,
+               zone_code, zone_name, alarm_time, handle_status, handle_user, handle_time, handle_remark,
+               linkage_status, linkage_result, create_time
+        from alarm_record
+        where id = #{id}
+    </select>
+
+    <insert id="insertAlarm" parameterType="pd" useGeneratedKeys="true" keyProperty="id">
+        insert into alarm_record (device_code, device_name, alarm_type, alarm_level, alarm_message, building, floor,
+                                  zone_code, zone_name, alarm_time, handle_status, linkage_status, create_time)
+        values (#{deviceCode}, #{deviceName}, #{alarmType}, #{alarmLevel}, #{alarmMessage}, #{building}, #{floor},
+                #{zoneCode}, #{zoneName}, #{alarmTime}, #{handleStatus}, #{linkageStatus}, #{createTime})
+    </insert>
+
+    <update id="updateAlarm" parameterType="pd">
+        update alarm_record
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="handleStatus != null">handle_status = #{handleStatus},</if>
+            <if test="handleUser != null and handleUser != ''">handle_user = #{handleUser},</if>
+            <if test="handleTime != null">handle_time = #{handleTime},</if>
+            <if test="handleRemark != null">handle_remark = #{handleRemark},</if>
+            <if test="linkageStatus != null">linkage_status = #{linkageStatus},</if>
+            <if test="linkageResult != null">linkage_result = #{linkageResult},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteAlarmById" parameterType="Long">
+        delete from alarm_record where id = #{id}
+    </delete>
+
+    <delete id="deleteAlarmByIds" parameterType="String">
+        delete from alarm_record where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+
+    <!-- ==================== 防区相关查询 ==================== -->
+    <select id="selectZoneList" parameterType="pd" resultType="pd">
+        select id, zone_code, zone_name, zone_description, zone_status, device_count, online_count,
+        linkage_cameras, arm_mode, arm_schedule, create_time, update_time
+        from alarm_zone
+        <where>
+            <if test="zoneCode != null and zoneCode != ''"> and zone_code like concat('%', #{zoneCode}, '%')</if>
+            <if test="zoneName != null and zoneName != ''"> and zone_name like concat('%', #{zoneName}, '%')</if>
+            <if test="zoneStatus != null"> and zone_status = #{zoneStatus}</if>
+        </where>
+        order by zone_code
+    </select>
+
+    <select id="selectZoneById" parameterType="pd" resultType="pd">
+        select id, zone_code, zone_name, zone_description, zone_status, device_count, online_count,
+               linkage_cameras, arm_mode, arm_schedule, create_time, update_time
+        from alarm_zone
+        where id = #{id}
+    </select>
+
+    <insert id="insertZone" parameterType="pd" useGeneratedKeys="true" keyProperty="id">
+        insert into alarm_zone (zone_code, zone_name, zone_description, zone_status, device_count, online_count,
+                                linkage_cameras, arm_mode, arm_schedule, create_time, update_time)
+        values (#{zoneCode}, #{zoneName}, #{zoneDescription}, #{zoneStatus}, #{deviceCount}, #{onlineCount},
+                #{linkageCameras}, #{armMode}, #{armSchedule}, #{createTime}, #{updateTime})
+    </insert>
+
+    <update id="updateZone" parameterType="pd">
+        update alarm_zone
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="zoneName != null and zoneName != ''">zone_name = #{zoneName},</if>
+            <if test="zoneDescription != null">zone_description = #{zoneDescription},</if>
+            <if test="zoneStatus != null">zone_status = #{zoneStatus},</if>
+            <if test="deviceCount != null">device_count = #{deviceCount},</if>
+            <if test="onlineCount != null">online_count = #{onlineCount},</if>
+            <if test="linkageCameras != null">linkage_cameras = #{linkageCameras},</if>
+            <if test="armMode != null and armMode != ''">arm_mode = #{armMode},</if>
+            <if test="armSchedule != null">arm_schedule = #{armSchedule},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteZoneById" parameterType="Long">
+        delete from alarm_zone where id = #{id}
+    </delete>
+
+    <!-- 查询防区设备列表 -->
+    <select id="selectZoneDeviceList" parameterType="pd" resultType="pd">
+        select device_code, device_name, alarm_type, device_status, arm_status, sensitivity, create_time
+        from alarm_point
+        where zone_code = #{zoneCode}
+        order by device_code
+    </select>
+
+    <!-- ==================== 联动规则相关查询 ==================== -->
+    <select id="selectLinkageRuleList" parameterType="pd" resultType="pd">
+        select id, rule_name, trigger_device_code, trigger_condition, target_device, linkage_action,
+        delay_time, rule_status, priority, create_time, update_time
+        from intrusion_linkage_rule
+        <where>
+            <if test="ruleName != null and ruleName != ''"> and rule_name like concat('%', #{ruleName}, '%')</if>
+            <if test="triggerDeviceCode != null and triggerDeviceCode != ''"> and trigger_device_code = #{triggerDeviceCode}</if>
+            <if test="targetDevice != null and targetDevice != ''"> and target_device = #{targetDevice}</if>
+            <if test="ruleStatus != null"> and rule_status = #{ruleStatus}</if>
+        </where>
+        order by priority desc, create_time desc
+    </select>
+
+    <select id="selectLinkageRuleById" parameterType="Long" resultType="pd">
+        select id, rule_name, trigger_device_code, trigger_condition, target_device, linkage_action,
+               delay_time, rule_status, priority, create_time, update_time
+        from intrusion_linkage_rule
+        where id = #{id}
+    </select>
+
+    <insert id="insertLinkageRule" parameterType="pd" useGeneratedKeys="true" keyProperty="id">
+        insert into intrusion_linkage_rule (rule_name, trigger_device_code, trigger_condition, target_device,
+                                            linkage_action, delay_time, rule_status, priority, create_time, update_time)
+        values (#{ruleName}, #{triggerDeviceCode}, #{triggerCondition}, #{targetDevice},
+                #{linkageAction}, #{delayTime}, #{ruleStatus}, #{priority}, #{createTime}, #{updateTime})
+    </insert>
+
+    <update id="updateLinkageRule" parameterType="pd">
+        update intrusion_linkage_rule
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="ruleName != null and ruleName != ''">rule_name = #{ruleName},</if>
+            <if test="triggerDeviceCode != null and triggerDeviceCode != ''">trigger_device_code = #{triggerDeviceCode},</if>
+            <if test="triggerCondition != null and triggerCondition != ''">trigger_condition = #{triggerCondition},</if>
+            <if test="targetDevice != null and targetDevice != ''">target_device = #{targetDevice},</if>
+            <if test="linkageAction != null and linkageAction != ''">linkage_action = #{linkageAction},</if>
+            <if test="delayTime != null">delay_time = #{delayTime},</if>
+            <if test="ruleStatus != null">rule_status = #{ruleStatus},</if>
+            <if test="priority != null">priority = #{priority},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteLinkageRuleById" parameterType="Long">
+        delete from intrusion_linkage_rule where id = #{id}
+    </delete>
+
+    <!-- 联动执行记录查询 -->
+    <select id="selectLinkageRecordList" parameterType="pd" resultType="pd">
+        select r.id, r.rule_id, r.trigger_device, r.target_device, r.linkage_action,
+        r.execute_time, r.execute_result, r.execute_detail, r.create_time,
+        l.rule_name
+        from intrusion_linkage_record r
+        left join intrusion_linkage_rule l on r.rule_id = l.id
+        <where>
+            <if test="triggerDevice != null and triggerDevice != ''"> and r.trigger_device = #{triggerDevice}</if>
+            <if test="targetDevice != null and targetDevice != ''"> and r.target_device = #{targetDevice}</if>
+            <if test="executeResult != null and executeResult != ''"> and r.execute_result = #{executeResult}</if>
+            <if test="beginTime != null and beginTime != ''"> and date_format(r.execute_time,'%y%m%d') &gt;= date_format(#{beginTime},'%y%m%d')</if>
+            <if test="endTime != null and endTime != ''"> and date_format(r.execute_time,'%y%m%d') &lt;= date_format(#{endTime},'%y%m%d')</if>
+        </where>
+        order by r.execute_time desc
+    </select>
+
+    <insert id="insertLinkageRecord" parameterType="pd" useGeneratedKeys="true" keyProperty="id">
+        insert into intrusion_linkage_record (rule_id, trigger_device, target_device, linkage_action,
+                                              execute_time, execute_result, execute_detail, create_time)
+        values (#{ruleId}, #{triggerDevice}, #{targetDevice}, #{linkageAction},
+                #{executeTime}, #{executeResult}, #{executeDetail}, #{createTime})
+    </insert>
+
+    <!-- ==================== 统计查询 ==================== -->
+    <select id="selectAlarmStatistics" parameterType="pd" resultType="pd">
+        select
+            -- 今日报警数
+            coalesce(sum(case when date(alarm_time) = curdate() then 1 else 0 end), 0) as todayAlarms,
+            -- 未处理报警数
+            coalesce(sum(case when handle_status = 0 then 1 else 0 end), 0) as unhandledAlarms,
+            -- 在线设备数(从报警点表查询)
+            (select count(*) from alarm_point where device_status = 1) as onlineDevices,
+            -- 总设备数
+            (select count(*) from alarm_point) as totalDevices,
+            -- 布防防区数
+            (select count(*) from alarm_zone where zone_status = 1) as armedZones,
+            -- 总防区数
+            (select count(*) from alarm_zone) as totalZones
+        from alarm_record
+    </select>
+
+    <select id="selectRealTimeStatus" parameterType="pd" resultType="pd">
+        select
+            -- 在线设备统计
+            count(case when device_status = 1 then 1 end) as onlineDevices,
+            count(case when device_status = 0 then 1 end) as offlineDevices,
+            count(case when device_status = 2 then 1 end) as alarmDevices,
+            count(case when device_status = 3 then 1 end) as faultDevices,
+            -- 布防状态统计
+            count(case when arm_status = 1 then 1 end) as armedDevices,
+            count(case when arm_status = 0 then 1 end) as disarmedDevices,
+            -- 各类型设备统计
+            count(case when alarm_type = 'infrared' then 1 end) as infraredDevices,
+            count(case when alarm_type = 'door_magnetic' then 1 end) as doorMagneticDevices,
+            count(case when alarm_type = 'glass_break' then 1 end) as glassBreakDevices,
+            count(case when alarm_type = 'vibration' then 1 end) as vibrationDevices
+        from alarm_point
+    </select>
+
+    <select id="selectAlarmTrend" parameterType="pd" resultType="pd">
+        select
+        date_format(alarm_time, '%Y-%m-%d') as alarmDate,
+        count(*) as alarmCount,
+        count(case when alarm_level = 1 then 1 end) as lowLevelCount,
+        count(case when alarm_level = 2 then 1 end) as mediumLevelCount,
+        count(case when alarm_level = 3 then 1 end) as highLevelCount,
+        count(case when alarm_level = 4 then 1 end) as emergencyLevelCount,
+        count(case when handle_status = 2 then 1 end) as handledCount
+        from alarm_record
+        <where>
+            <if test="beginTime != null and beginTime != ''"> and date_format(alarm_time,'%y%m%d') &gt;= date_format(#{beginTime},'%y%m%d')</if>
+            <if test="endTime != null and endTime != ''"> and date_format(alarm_time,'%y%m%d') &lt;= date_format(#{endTime},'%y%m%d')</if>
+        </where>
+        group by date_format(alarm_time, '%Y-%m-%d')
+        order by alarmDate desc
+        limit 30
+    </select>
+
+    <!-- ==================== 维护记录相关查询 ==================== -->
+    <select id="selectMaintenanceRecordList" parameterType="pd" resultType="pd">
+        select id, device_code, device_name, maintenance_type, maintenance_content, maintenance_user,
+        maintenance_time, maintenance_result, next_maintenance_time, create_user, create_time
+        from maintenance_record
+        <where>
+            <if test="deviceCode != null and deviceCode != ''"> and device_code like concat('%', #{deviceCode}, '%')</if>
+            <if test="deviceName != null and deviceName != ''"> and device_name like concat('%', #{deviceName}, '%')</if>
+            <if test="maintenanceType != null and maintenanceType != ''"> and maintenance_type = #{maintenanceType}</if>
+            <if test="maintenanceUser != null and maintenanceUser != ''"> and maintenance_user = #{maintenanceUser}</if>
+            <if test="beginTime != null and beginTime != ''"> and date_format(maintenance_time,'%y%m%d') &gt;= date_format(#{beginTime},'%y%m%d')</if>
+            <if test="endTime != null and endTime != ''"> and date_format(maintenance_time,'%y%m%d') &lt;= date_format(#{endTime},'%y%m%d')</if>
+        </where>
+        order by maintenance_time desc
+    </select>
+
+    <insert id="insertMaintenanceRecord" parameterType="pd" useGeneratedKeys="true" keyProperty="id">
+        insert into maintenance_record (device_code, device_name, maintenance_type, maintenance_content,
+                                        maintenance_user, maintenance_time, maintenance_result, next_maintenance_time,
+                                        create_user, create_time)
+        values (#{deviceCode}, #{deviceName}, #{maintenanceType}, #{maintenanceContent},
+                #{maintenanceUser}, #{maintenanceTime}, #{maintenanceResult}, #{nextMaintenanceTime},
+                #{createUser}, #{createTime})
+    </insert>
+
+</mapper>

+ 297 - 0
pm-system/src/main/resources/mapper/subsystem/VideoDeviceMapper.xml

@@ -0,0 +1,297 @@
+<?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.pm.subsystem.mapper.VideoDeviceMapper">
+
+    <resultMap type="pd" id="CameraResult">
+        <result property="id"    column="id"    />
+        <result property="cameraCode"    column="camera_code"    />
+        <result property="cameraName"    column="camera_name"    />
+        <result property="monitorArea"    column="monitor_area"    />
+        <result property="building"    column="building"    />
+        <result property="floor"    column="floor"    />
+        <result property="locationDetail"    column="location_detail"    />
+        <result property="ipAddress"    column="ip_address"    />
+        <result property="port"    column="port"    />
+        <result property="brand"    column="brand"    />
+        <result property="model"    column="model"    />
+        <result property="resolution"    column="resolution"    />
+        <result property="frameRate"    column="frame_rate"    />
+        <result property="deviceStatus"    column="device_status"    />
+        <result property="ptzSupport"    column="ptz_support"    />
+        <result property="infraredSupport"    column="infrared_support"    />
+        <result property="recordingStatus"    column="recording_status"    />
+        <result property="storagePath"    column="storage_path"    />
+        <result property="lastOnlineTime"    column="last_online_time"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="updateTime"    column="update_time"    />
+    </resultMap>
+
+    <sql id="selectCameraVo">
+        select id, camera_code, camera_name, monitor_area, building, floor, location_detail,
+               ip_address, port, brand, model, resolution, frame_rate, device_status,
+               ptz_support, infrared_support, recording_status, storage_path,
+               last_online_time, create_time, update_time
+        from camera_device
+    </sql>
+
+    <select id="selectCameraList" parameterType="pd" resultMap="CameraResult">
+        <include refid="selectCameraVo"/>
+        <where>
+            <if test="cameraCode != null and cameraCode != ''"> and camera_code like concat('%', #{cameraCode}, '%')</if>
+            <if test="cameraName != null and cameraName != ''"> and camera_name like concat('%', #{cameraName}, '%')</if>
+            <if test="monitorArea != null and monitorArea != ''"> and monitor_area like concat('%', #{monitorArea}, '%')</if>
+            <if test="building != null and building != ''"> and building = #{building}</if>
+            <if test="floor != null and floor != ''"> and floor = #{floor}</if>
+            <if test="deviceStatus != null"> and device_status = #{deviceStatus}</if>
+            <if test="ptzSupport != null"> and ptz_support = #{ptzSupport}</if>
+            <if test="recordingStatus != null"> and recording_status = #{recordingStatus}</if>
+            <if test="beginTime != null and beginTime != ''"> and date_format(create_time,'%y%m%d') &gt;= date_format(#{beginTime},'%y%m%d')</if>
+            <if test="endTime != null and endTime != ''"> and date_format(create_time,'%y%m%d') &lt;= date_format(#{endTime},'%y%m%d')</if>
+        </where>
+        order by create_time desc
+    </select>
+
+    <select id="selectCameraById" parameterType="pd" resultMap="CameraResult">
+        <include refid="selectCameraVo"/>
+        where id = #{id}
+    </select>
+
+    <insert id="insertCamera" parameterType="pd" useGeneratedKeys="true" keyProperty="id">
+        insert into camera_device
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="cameraCode != null and cameraCode != ''">camera_code,</if>
+            <if test="cameraName != null and cameraName != ''">camera_name,</if>
+            <if test="monitorArea != null and monitorArea != ''">monitor_area,</if>
+            <if test="building != null and building != ''">building,</if>
+            <if test="floor != null and floor != ''">floor,</if>
+            <if test="locationDetail != null and locationDetail != ''">location_detail,</if>
+            <if test="ipAddress != null and ipAddress != ''">ip_address,</if>
+            <if test="port != null">port,</if>
+            <if test="brand != null and brand != ''">brand,</if>
+            <if test="model != null and model != ''">model,</if>
+            <if test="resolution != null and resolution != ''">resolution,</if>
+            <if test="frameRate != null">frame_rate,</if>
+            <if test="deviceStatus != null">device_status,</if>
+            <if test="ptzSupport != null">ptz_support,</if>
+            <if test="infraredSupport != null">infrared_support,</if>
+            <if test="recordingStatus != null">recording_status,</if>
+            <if test="storagePath != null and storagePath != ''">storage_path,</if>
+            <if test="lastOnlineTime != null">last_online_time,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateTime != null">update_time,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="cameraCode != null and cameraCode != ''">#{cameraCode},</if>
+            <if test="cameraName != null and cameraName != ''">#{cameraName},</if>
+            <if test="monitorArea != null and monitorArea != ''">#{monitorArea},</if>
+            <if test="building != null and building != ''">#{building},</if>
+            <if test="floor != null and floor != ''">#{floor},</if>
+            <if test="locationDetail != null and locationDetail != ''">#{locationDetail},</if>
+            <if test="ipAddress != null and ipAddress != ''">#{ipAddress},</if>
+            <if test="port != null">#{port},</if>
+            <if test="brand != null and brand != ''">#{brand},</if>
+            <if test="model != null and model != ''">#{model},</if>
+            <if test="resolution != null and resolution != ''">#{resolution},</if>
+            <if test="frameRate != null">#{frameRate},</if>
+            <if test="deviceStatus != null">#{deviceStatus},</if>
+            <if test="ptzSupport != null">#{ptzSupport},</if>
+            <if test="infraredSupport != null">#{infraredSupport},</if>
+            <if test="recordingStatus != null">#{recordingStatus},</if>
+            <if test="storagePath != null and storagePath != ''">#{storagePath},</if>
+            <if test="lastOnlineTime != null">#{lastOnlineTime},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+        </trim>
+    </insert>
+
+    <update id="updateCamera" parameterType="pd">
+        update camera_device
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="cameraName != null and cameraName != ''">camera_name = #{cameraName},</if>
+            <if test="monitorArea != null and monitorArea != ''">monitor_area = #{monitorArea},</if>
+            <if test="building != null and building != ''">building = #{building},</if>
+            <if test="floor != null and floor != ''">floor = #{floor},</if>
+            <if test="locationDetail != null and locationDetail != ''">location_detail = #{locationDetail},</if>
+            <if test="ipAddress != null and ipAddress != ''">ip_address = #{ipAddress},</if>
+            <if test="port != null">port = #{port},</if>
+            <if test="brand != null and brand != ''">brand = #{brand},</if>
+            <if test="model != null and model != ''">model = #{model},</if>
+            <if test="resolution != null and resolution != ''">resolution = #{resolution},</if>
+            <if test="frameRate != null">frame_rate = #{frameRate},</if>
+            <if test="deviceStatus != null">device_status = #{deviceStatus},</if>
+            <if test="ptzSupport != null">ptz_support = #{ptzSupport},</if>
+            <if test="infraredSupport != null">infrared_support = #{infraredSupport},</if>
+            <if test="recordingStatus != null">recording_status = #{recordingStatus},</if>
+            <if test="storagePath != null and storagePath != ''">storage_path = #{storagePath},</if>
+            <if test="lastOnlineTime != null">last_online_time = #{lastOnlineTime},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+        </trim>
+        <where>
+            <if test="id != null">id = #{id}</if>
+            <if test="cameraCode != null and cameraCode != ''">or camera_code = #{cameraCode}</if>
+        </where>
+    </update>
+
+    <delete id="deleteCameraById" parameterType="Long">
+        delete from camera_device where id = #{id}
+    </delete>
+
+    <delete id="deleteCameraByIds" parameterType="String">
+        delete from camera_device where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+
+    <!-- 联动规则相关查询 -->
+    <select id="selectLinkageRuleList" parameterType="pd" resultType="pd">
+        select id, rule_name, trigger_device_code, trigger_device_name, trigger_condition,
+        linkage_camera_code, linkage_camera_name, linkage_action, delay_time,
+        rule_status, create_time, update_time
+        from video_linkage_rule
+        <where>
+            <if test="ruleName != null and ruleName != ''"> and rule_name like concat('%', #{ruleName}, '%')</if>
+            <if test="triggerDevice != null and triggerDevice != ''"> and trigger_device_name like concat('%', #{triggerDevice}, '%')</if>
+            <if test="linkageCamera != null and linkageCamera != ''"> and linkage_camera_name like concat('%', #{linkageCamera}, '%')</if>
+            <if test="ruleStatus != null"> and rule_status = #{ruleStatus}</if>
+        </where>
+        order by create_time desc
+    </select>
+
+    <select id="selectLinkageRuleById" parameterType="Long" resultType="pd">
+        select id, rule_name, trigger_device_code, trigger_device_name, trigger_condition,
+               linkage_camera_code, linkage_camera_name, linkage_action, delay_time,
+               rule_status, create_time, update_time
+        from video_linkage_rule
+        where id = #{id}
+    </select>
+
+    <insert id="insertLinkageRule" parameterType="pd" useGeneratedKeys="true" keyProperty="id">
+        insert into video_linkage_rule (rule_name, trigger_device_code, trigger_device_name, trigger_condition,
+                                        linkage_camera_code, linkage_camera_name, linkage_action, delay_time,
+                                        rule_status, create_time, update_time)
+        values (#{ruleName}, #{triggerDeviceCode}, #{triggerDeviceName}, #{triggerCondition},
+                #{linkageCameraCode}, #{linkageCameraName}, #{linkageAction}, #{delayTime},
+                #{ruleStatus}, #{createTime}, #{updateTime})
+    </insert>
+
+    <update id="updateLinkageRule" parameterType="pd">
+        update video_linkage_rule
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="ruleName != null and ruleName != ''">rule_name = #{ruleName},</if>
+            <if test="triggerDeviceCode != null and triggerDeviceCode != ''">trigger_device_code = #{triggerDeviceCode},</if>
+            <if test="triggerDeviceName != null and triggerDeviceName != ''">trigger_device_name = #{triggerDeviceName},</if>
+            <if test="triggerCondition != null and triggerCondition != ''">trigger_condition = #{triggerCondition},</if>
+            <if test="linkageCameraCode != null and linkageCameraCode != ''">linkage_camera_code = #{linkageCameraCode},</if>
+            <if test="linkageCameraName != null and linkageCameraName != ''">linkage_camera_name = #{linkageCameraName},</if>
+            <if test="linkageAction != null and linkageAction != ''">linkage_action = #{linkageAction},</if>
+            <if test="delayTime != null">delay_time = #{delayTime},</if>
+            <if test="ruleStatus != null">rule_status = #{ruleStatus},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteLinkageRuleById" parameterType="Long">
+        delete from video_linkage_rule where id = #{id}
+    </delete>
+
+    <!-- 联动执行记录查询 -->
+    <select id="selectLinkageRecordList" parameterType="pd" resultType="pd">
+        select r.id, r.rule_id, r.trigger_device_code, r.linkage_camera_code, r.linkage_action,
+        r.execute_time, r.execute_result, r.create_time,
+        l.rule_name
+        from video_linkage_record r
+        left join video_linkage_rule l on r.rule_id = l.id
+        <where>
+            <if test="triggerDeviceCode != null and triggerDeviceCode != ''"> and r.trigger_device_code = #{triggerDeviceCode}</if>
+            <if test="linkageCameraCode != null and linkageCameraCode != ''"> and r.linkage_camera_code = #{linkageCameraCode}</if>
+            <if test="executeResult != null and executeResult != ''"> and r.execute_result = #{executeResult}</if>
+            <if test="beginTime != null and beginTime != ''"> and date_format(r.execute_time,'%y%m%d') &gt;= date_format(#{beginTime},'%y%m%d')</if>
+            <if test="endTime != null and endTime != ''"> and date_format(r.execute_time,'%y%m%d') &lt;= date_format(#{endTime},'%y%m%d')</if>
+        </where>
+        order by r.execute_time desc
+    </select>
+
+    <insert id="insertLinkageRecord" parameterType="pd" useGeneratedKeys="true" keyProperty="id">
+        insert into video_linkage_record (rule_id, trigger_device_code, linkage_camera_code, linkage_action,
+                                          execute_time, execute_result, create_time)
+        values (#{ruleId}, #{triggerDeviceCode}, #{linkageCameraCode}, #{linkageAction},
+                #{executeTime}, #{executeResult}, #{createTime})
+    </insert>
+
+    <!-- 录像文件查询 -->
+    <select id="selectRecordingFileList" parameterType="pd" resultType="pd">
+        select id, camera_code, camera_name, file_name, file_path, file_size,
+        start_time, end_time, duration, status, create_time
+        from recording_file
+        <where>
+            <if test="cameraCode != null and cameraCode != ''"> and camera_code = #{cameraCode}</if>
+            <if test="cameraName != null and cameraName != ''"> and camera_name like concat('%', #{cameraName}, '%')</if>
+            <if test="status != null"> and status = #{status}</if>
+            <if test="beginTime != null and beginTime != ''"> and date_format(start_time,'%y%m%d') &gt;= date_format(#{beginTime},'%y%m%d')</if>
+            <if test="endTime != null and endTime != ''"> and date_format(start_time,'%y%m%d') &lt;= date_format(#{endTime},'%y%m%d')</if>
+        </where>
+        order by start_time desc
+    </select>
+
+    <insert id="insertRecordingFile" parameterType="pd" useGeneratedKeys="true" keyProperty="id">
+        insert into recording_file (camera_code, camera_name, file_name, file_path, file_size,
+                                    start_time, end_time, duration, status, create_time)
+        values (#{cameraCode}, #{cameraName}, #{fileName}, #{filePath}, #{fileSize},
+                #{startTime}, #{endTime}, #{duration}, #{status}, #{createTime})
+    </insert>
+
+    <!-- 预置位查询 -->
+    <select id="selectPresetList" parameterType="pd" resultType="pd">
+        select id, camera_code, preset_id, preset_name, position_x, position_y, zoom_level, create_time
+        from camera_preset
+        <where>
+            <if test="cameraCode != null and cameraCode != ''"> and camera_code = #{cameraCode}</if>
+        </where>
+        order by preset_id
+    </select>
+
+    <insert id="insertPreset" parameterType="pd" useGeneratedKeys="true" keyProperty="id">
+        insert into camera_preset (camera_code, preset_id, preset_name, position_x, position_y, zoom_level, create_time)
+        values (#{cameraCode}, #{presetId}, #{presetName}, #{positionX}, #{positionY}, #{zoomLevel}, #{createTime})
+    </insert>
+
+    <delete id="deletePresetById" parameterType="Long">
+        delete from camera_preset where id = #{id}
+    </delete>
+
+    <!-- 云台控制记录 -->
+    <select id="selectPtzControlList" parameterType="pd" resultType="pd">
+        select id, camera_code, command, speed, control_user, control_time, control_result, create_time
+        from ptz_control_record
+        <where>
+            <if test="cameraCode != null and cameraCode != ''"> and camera_code = #{cameraCode}</if>
+            <if test="controlUser != null and controlUser != ''"> and control_user = #{controlUser}</if>
+            <if test="beginTime != null and beginTime != ''"> and date_format(control_time,'%y%m%d') &gt;= date_format(#{beginTime},'%y%m%d')</if>
+            <if test="endTime != null and endTime != ''"> and date_format(control_time,'%y%m%d') &lt;= date_format(#{endTime},'%y%m%d')</if>
+        </where>
+        order by control_time desc
+    </select>
+
+    <insert id="insertPtzControl" parameterType="pd" useGeneratedKeys="true" keyProperty="id">
+        insert into ptz_control_record (camera_code, command, speed, control_user, control_time, control_result, create_time)
+        values (#{cameraCode}, #{command}, #{speed}, #{controlUser}, #{controlTime}, #{controlResult}, #{createTime})
+    </insert>
+
+    <!-- 摄像机统计数据 -->
+    <select id="selectCameraStatistics" parameterType="pd" resultType="pd">
+        select
+            count(*) as totalCameras,
+            count(case when device_status = 1 then 1 end) as onlineCameras,
+            count(case when device_status = 0 then 1 end) as offlineCameras,
+            count(case when device_status = 2 then 1 end) as faultCameras,
+            count(case when recording_status = 1 then 1 end) as recordingCameras,
+            count(case when ptz_support = 1 then 1 end) as ptzCameras,
+            count(case when infrared_support = 1 then 1 end) as infraredCameras
+        from camera_device
+    </select>
+
+</mapper>

+ 486 - 0
pm_ui/src/api/mock/parking-mock-service.js

@@ -0,0 +1,486 @@
+// mock/parking-mock-service.js - 停车场管理模拟数据服务
+
+// 模拟数据存储
+let mockSpaceData = [
+    {
+        id: 1,
+        space_code: "A101",
+        space_name: "A区101号",
+        building_id: "B1",
+        building_name: "A栋",
+        floor_id: "B1",
+        floor_name: "B1层",
+        space_status: 0, // 空闲
+        space_type: "normal",
+        license_plate: null,
+        entry_time: null,
+        parking_fee: 0,
+        is_online: 1,
+        device_id: "DEV001",
+        position_x: 120.123456,
+        position_y: 30.123456,
+        position_z: -3.0,
+        remark: "靠近电梯口",
+        create_time: "2024-01-01 10:00:00",
+        update_time: "2024-01-15 14:30:00"
+    },
+    {
+        id: 2,
+        space_code: "A102",
+        space_name: "A区102号",
+        building_id: "B1",
+        building_name: "A栋",
+        floor_id: "B1",
+        floor_name: "B1层",
+        space_status: 1, // 占用
+        space_type: "normal",
+        license_plate: "粤A88888",
+        entry_time: "2024-01-15 08:30:00",
+        parking_fee: 45.50,
+        is_online: 1,
+        device_id: "DEV002",
+        position_x: 120.123466,
+        position_y: 30.123466,
+        position_z: -3.0,
+        remark: null,
+        create_time: "2024-01-01 10:00:00",
+        update_time: "2024-01-15 08:30:00"
+    },
+    {
+        id: 3,
+        space_code: "A103",
+        space_name: "A区103号",
+        building_id: "B1",
+        building_name: "A栋",
+        floor_id: "B1",
+        floor_name: "B1层",
+        space_status: 2, // 预约
+        space_type: "vip",
+        license_plate: null,
+        entry_time: null,
+        parking_fee: 0,
+        is_online: 1,
+        device_id: "DEV003",
+        position_x: 120.123476,
+        position_y: 30.123476,
+        position_z: -3.0,
+        remark: "VIP专属车位",
+        create_time: "2024-01-01 10:00:00",
+        update_time: "2024-01-15 10:00:00"
+    },
+    {
+        id: 4,
+        space_code: "B201",
+        space_name: "B区201号",
+        building_id: "B2",
+        building_name: "B栋",
+        floor_id: "B2",
+        floor_name: "B2层",
+        space_status: 1, // 占用
+        space_type: "normal",
+        license_plate: "粤B12345",
+        entry_time: "2024-01-15 14:20:00",
+        parking_fee: 12.00,
+        is_online: 1,
+        device_id: "DEV004",
+        position_x: 120.124456,
+        position_y: 30.124456,
+        position_z: -6.0,
+        remark: null,
+        create_time: "2024-01-02 10:00:00",
+        update_time: "2024-01-15 14:20:00"
+    },
+    {
+        id: 5,
+        space_code: "B202",
+        space_name: "B区202号",
+        building_id: "B2",
+        building_name: "B栋",
+        floor_id: "B2",
+        floor_name: "B2层",
+        space_status: 0, // 空闲
+        space_type: "disabled",
+        license_plate: null,
+        entry_time: null,
+        parking_fee: 0,
+        is_online: 1,
+        device_id: "DEV005",
+        position_x: 120.124466,
+        position_y: 30.124466,
+        position_z: -6.0,
+        remark: "残疾人专用车位,加宽设计",
+        create_time: "2024-01-02 10:00:00",
+        update_time: "2024-01-02 10:00:00"
+    },
+    {
+        id: 6,
+        space_code: "C301",
+        space_name: "C区301号",
+        building_id: "B3",
+        building_name: "C栋",
+        floor_id: "F1",
+        floor_name: "1层",
+        space_status: 3, // 故障
+        space_type: "normal",
+        license_plate: null,
+        entry_time: null,
+        parking_fee: 0,
+        is_online: 0,
+        device_id: "DEV006",
+        position_x: 120.125456,
+        position_y: 30.125456,
+        position_z: 3.0,
+        remark: "地锁故障,维修中",
+        create_time: "2024-01-03 10:00:00",
+        update_time: "2024-01-15 09:00:00"
+    }
+];
+
+// 模拟API响应延迟
+const delay = (ms = 300) => new Promise(resolve => setTimeout(resolve, ms));
+
+// 模拟API服务
+const mockService = {
+    // 获取车位列表
+    async listParkingSpace(query) {
+        await delay();
+
+        let filteredData = [...mockSpaceData];
+
+        // 模拟搜索过滤
+        if (query.spaceCode) {
+            filteredData = filteredData.filter(item =>
+                item.space_code.includes(query.spaceCode)
+            );
+        }
+        if (query.spaceName) {
+            filteredData = filteredData.filter(item =>
+                item.space_name.includes(query.spaceName)
+            );
+        }
+        if (query.buildingId) {
+            filteredData = filteredData.filter(item =>
+                item.building_id === query.buildingId
+            );
+        }
+        if (query.floorId) {
+            filteredData = filteredData.filter(item =>
+                item.floor_id === query.floorId
+            );
+        }
+        if (query.spaceStatus !== null && query.spaceStatus !== undefined) {
+            filteredData = filteredData.filter(item =>
+                item.space_status === query.spaceStatus
+            );
+        }
+        if (query.spaceType) {
+            filteredData = filteredData.filter(item =>
+                item.space_type === query.spaceType
+            );
+        }
+
+        // 模拟分页
+        const pageNum = query.pageNum || 1;
+        const pageSize = query.pageSize || 10;
+        const start = (pageNum - 1) * pageSize;
+        const end = start + pageSize;
+
+        return {
+            code: 200,
+            msg: "查询成功",
+            total: filteredData.length,
+            rows: filteredData.slice(start, end)
+        };
+    },
+
+    // 获取单个车位详情
+    async getParkingSpace(id) {
+        await delay();
+        const space = mockSpaceData.find(item => item.id === parseInt(id));
+
+        if (space) {
+            return {
+                code: 200,
+                msg: "查询成功",
+                data: { ...space }
+            };
+        } else {
+            return {
+                code: 404,
+                msg: "车位不存在",
+                data: null
+            };
+        }
+    },
+
+    // 新增车位
+    async addParkingSpace(data) {
+        await delay();
+        const newSpace = {
+            ...data,
+            id: mockSpaceData.length + 1,
+            space_status: 0,
+            license_plate: null,
+            entry_time: null,
+            parking_fee: 0,
+            is_online: 1,
+            create_time: new Date().toLocaleString(),
+            update_time: new Date().toLocaleString()
+        };
+
+        mockSpaceData.push(newSpace);
+
+        return {
+            code: 200,
+            msg: "新增成功",
+            data: newSpace
+        };
+    },
+
+    // 更新车位
+    async updateParkingSpace(data) {
+        await delay();
+        const index = mockSpaceData.findIndex(item => item.id === data.id);
+
+        if (index !== -1) {
+            mockSpaceData[index] = {
+                ...mockSpaceData[index],
+                ...data,
+                update_time: new Date().toLocaleString()
+            };
+
+            return {
+                code: 200,
+                msg: "修改成功",
+                data: mockSpaceData[index]
+            };
+        } else {
+            return {
+                code: 404,
+                msg: "车位不存在",
+                data: null
+            };
+        }
+    },
+
+    // 删除车位
+    async delParkingSpace(ids) {
+        await delay();
+        const idArray = Array.isArray(ids) ? ids : [ids];
+
+        idArray.forEach(id => {
+            const index = mockSpaceData.findIndex(item => item.id === parseInt(id));
+            if (index !== -1) {
+                mockSpaceData.splice(index, 1);
+            }
+        });
+
+        return {
+            code: 200,
+            msg: "删除成功",
+            data: null
+        };
+    },
+
+    // 导出车位
+    async exportParkingSpace(query) {
+        await delay();
+        // 模拟返回文件下载链接
+        return {
+            code: 200,
+            msg: "/download/parking_space_export_20240115.xlsx",
+            data: null
+        };
+    },
+
+    // 获取统计数据
+    async getParkingStatistics() {
+        await delay();
+
+        const totalSpaces = mockSpaceData.length;
+        const freeSpaces = mockSpaceData.filter(s => s.space_status === 0).length;
+        const occupiedSpaces = mockSpaceData.filter(s => s.space_status === 1).length;
+        const reservedSpaces = mockSpaceData.filter(s => s.space_status === 2).length;
+        const faultSpaces = mockSpaceData.filter(s => s.space_status === 3).length;
+
+        return {
+            code: 200,
+            msg: "success",
+            data: {
+                totalSpaces,
+                freeSpaces,
+                occupiedSpaces,
+                reservedSpaces,
+                faultSpaces,
+                occupancyRate: ((occupiedSpaces / totalSpaces) * 100).toFixed(2),
+                todayEntries: 126,
+                todayExits: 98,
+                todayRevenue: 3456.80
+            }
+        };
+    },
+
+    // 获取实时数据
+    async getRealtimeData() {
+        await delay();
+
+        const recentEntries = [
+            {
+                id: 101,
+                license_plate: "粤A66666",
+                space_code: "A201",
+                entry_time: "2024-01-15 16:45:23",
+                space_type: "normal"
+            },
+            {
+                id: 102,
+                license_plate: "粤B99999",
+                space_code: "B105",
+                entry_time: "2024-01-15 16:42:15",
+                space_type: "vip"
+            },
+            {
+                id: 103,
+                license_plate: "粤C88888",
+                space_code: "C203",
+                entry_time: "2024-01-15 16:38:45",
+                space_type: "normal"
+            },
+            {
+                id: 104,
+                license_plate: "粤A12345",
+                space_code: "A108",
+                entry_time: "2024-01-15 16:35:20",
+                space_type: "normal"
+            }
+        ];
+
+        const recentExits = [
+            {
+                id: 201,
+                license_plate: "粤D55555",
+                space_code: "B203",
+                entry_time: "2024-01-15 08:30:00",
+                exit_time: "2024-01-15 16:40:00",
+                duration: "8小时10分",
+                fee: 96.00
+            },
+            {
+                id: 202,
+                license_plate: "粤E77777",
+                space_code: "A105",
+                entry_time: "2024-01-15 14:00:00",
+                exit_time: "2024-01-15 16:35:00",
+                duration: "2小时35分",
+                fee: 25.00
+            },
+            {
+                id: 203,
+                license_plate: "粤F33333",
+                space_code: "C101",
+                entry_time: "2024-01-15 09:15:00",
+                exit_time: "2024-01-15 16:30:00",
+                duration: "7小时15分",
+                fee: 72.00
+            }
+        ];
+
+        // 模拟楼层车位数据
+        const floorSpaces = [];
+        const floors = ['B1', 'B2'];
+        let spaceId = 1;
+
+        floors.forEach(floor => {
+            for (let i = 1; i <= 20; i++) {
+                const randomStatus = Math.random();
+                let status = 0; // 默认空闲
+                let licensePlate = null;
+
+                if (randomStatus < 0.5) {
+                    status = 1; // 50%占用
+                    licensePlate = `粤${String.fromCharCode(65 + Math.floor(Math.random() * 26))}${Math.floor(10000 + Math.random() * 90000)}`;
+                } else if (randomStatus < 0.7) {
+                    status = 0; // 20%空闲
+                } else if (randomStatus < 0.9) {
+                    status = 2; // 20%预约
+                } else {
+                    status = 3; // 10%故障
+                }
+
+                floorSpaces.push({
+                    id: spaceId++,
+                    space_code: `A${floor}${String(i).padStart(3, '0')}`,
+                    floor_id: floor,
+                    space_status: status,
+                    license_plate: licensePlate
+                });
+            }
+        });
+
+        return {
+            code: 200,
+            msg: "success",
+            data: {
+                recentEntries,
+                recentExits,
+                floorSpaces
+            }
+        };
+    },
+
+    // 获取报表数据
+    async getParkingReport(params) {
+        await delay();
+
+        const dates = [];
+        const occupancyRates = [];
+        const revenues = [];
+        const details = [];
+
+        // 生成7天的数据
+        for (let i = 6; i >= 0; i--) {
+            const date = new Date();
+            date.setDate(date.getDate() - i);
+            const dateStr = date.toISOString().split('T')[0];
+
+            const occupancyRate = 40 + Math.random() * 30; // 40-70%
+            const revenue = 3000 + Math.random() * 4000; // 3000-7000
+
+            dates.push(dateStr);
+            occupancyRates.push(parseFloat(occupancyRate.toFixed(1)));
+            revenues.push(parseFloat(revenue.toFixed(2)));
+
+            details.push({
+                date: dateStr,
+                totalSpaces: 500,
+                usedSpaces: Math.floor(500 * occupancyRate / 100),
+                occupancyRate: occupancyRate.toFixed(1),
+                entryCount: 150 + Math.floor(Math.random() * 200),
+                exitCount: 140 + Math.floor(Math.random() * 190),
+                revenue: revenue.toFixed(2)
+            });
+        }
+
+        return {
+            code: 200,
+            msg: "success",
+            data: {
+                dates,
+                occupancyRates,
+                revenues,
+                details,
+                summary: {
+                    avgOccupancyRate: (occupancyRates.reduce((a, b) => a + b) / 7).toFixed(1),
+                    totalRevenue: revenues.reduce((a, b) => a + b).toFixed(2),
+                    totalEntries: 2156,
+                    totalExits: 2098,
+                    peakOccupancyRate: Math.max(...occupancyRates).toFixed(1),
+                    peakDate: dates[occupancyRates.indexOf(Math.max(...occupancyRates))]
+                }
+            }
+        };
+    }
+};
+
+// 导出模拟服务
+export default mockService;

+ 192 - 0
pm_ui/src/api/mock/parking.js

@@ -0,0 +1,192 @@
+// mock/parking.js - 停车场管理模拟数据
+
+import Mock from 'mockjs'
+
+// 车位列表数据
+Mock.mock(/\/parking\/space\/list/, 'get', () => {
+    const list = []
+    const buildings = ['A栋', 'B栋', 'C栋']
+    const floors = ['B2', 'B1', 'F1', 'F2']
+    const types = ['normal', 'vip', 'disabled']
+    const statuses = [0, 0, 0, 1, 1, 2, 3] // 更多空闲状态
+
+    for (let i = 1; i <= 100; i++) {
+        const building = buildings[Math.floor(Math.random() * buildings.length)]
+        const floor = floors[Math.floor(Math.random() * floors.length)]
+        const type = types[Math.floor(Math.random() * types.length)]
+        const status = statuses[Math.floor(Math.random() * statuses.length)]
+
+        const space = {
+            id: i,
+            space_code: `${building.charAt(0)}${floor}${String(i).padStart(3, '0')}`,
+            space_name: `${building}${floor}-${i}号`,
+            building_id: building.charAt(0),
+            building_name: building,
+            floor_id: floor,
+            floor_name: floor + '层',
+            space_status: status,
+            space_type: type,
+            license_plate: status === 1 ? Mock.mock(/^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-HJ-NP-Z0-9]{5}$/) : null,
+            entry_time: status === 1 ? Mock.mock('@datetime("yyyy-MM-dd HH:mm:ss")') : null,
+            parking_fee: status === 1 ? Mock.mock('@float(0, 200, 2, 2)') : 0,
+            is_online: Mock.mock('@integer(0, 1)'),
+            device_id: `DEV${String(i).padStart(3, '0')}`,
+            position_x: Mock.mock('@float(120, 121, 6, 6)'),
+            position_y: Mock.mock('@float(30, 31, 6, 6)'),
+            position_z: floor.startsWith('B') ? -parseInt(floor.charAt(1)) * 3 : parseInt(floor.charAt(1)) * 3,
+            remark: Mock.mock('@csentence(0, 20)'),
+            create_time: '2024-01-01 10:00:00',
+            update_time: Mock.mock('@datetime("yyyy-MM-dd HH:mm:ss")')
+        }
+        list.push(space)
+    }
+
+    return {
+        code: 200,
+        msg: "查询成功",
+        data: {
+            total: list.length,
+            rows: list.slice(0, 10)
+        }
+    }
+})
+
+// 统计数据
+Mock.mock(/\/parking\/monitor\/statistics/, 'get', () => {
+    const total = 500
+    const occupied = Mock.mock('@integer(200, 300)')
+    const reserved = Mock.mock('@integer(10, 30)')
+    const fault = Mock.mock('@integer(0, 10)')
+    const free = total - occupied - reserved - fault
+
+    return {
+        code: 200,
+        msg: "success",
+        data: {
+            totalSpaces: total,
+            freeSpaces: free,
+            occupiedSpaces: occupied,
+            reservedSpaces: reserved,
+            faultSpaces: fault,
+            occupancyRate: ((occupied / total) * 100).toFixed(2),
+            todayEntries: Mock.mock('@integer(300, 500)'),
+            todayExits: Mock.mock('@integer(280, 480)'),
+            todayRevenue: Mock.mock('@float(5000, 10000, 2, 2)')
+        }
+    }
+})
+
+// 实时数据
+Mock.mock(/\/parking\/monitor\/realtime/, 'get', () => {
+    const recentEntries = []
+    const recentExits = []
+
+    // 最近入场
+    for (let i = 0; i < 5; i++) {
+        recentEntries.push({
+            id: 1000 + i,
+            license_plate: Mock.mock(/^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-HJ-NP-Z0-9]{5}$/),
+            space_code: Mock.mock(/^[ABC][BF][12]\d{3}$/),
+            entry_time: Mock.mock('@datetime("yyyy-MM-dd HH:mm:ss")'),
+            space_type: Mock.mock('@pick(["normal", "vip", "disabled"])')
+        })
+    }
+
+    // 最近出场
+    for (let i = 0; i < 5; i++) {
+        const entryTime = new Date(Date.now() - Mock.mock('@integer(1, 10)') * 3600000)
+        const exitTime = new Date()
+        const duration = Math.floor((exitTime - entryTime) / 60000)
+        const hours = Math.floor(duration / 60)
+        const minutes = duration % 60
+
+        recentExits.push({
+            id: 2000 + i,
+            license_plate: Mock.mock(/^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-HJ-NP-Z0-9]{5}$/),
+            space_code: Mock.mock(/^[ABC][BF][12]\d{3}$/),
+            entry_time: Mock.mock('@datetime("yyyy-MM-dd HH:mm:ss")'),
+            exit_time: Mock.mock('@datetime("yyyy-MM-dd HH:mm:ss")'),
+            duration: `${hours}小时${minutes}分`,
+            fee: Mock.mock('@float(10, 100, 2, 2)')
+        })
+    }
+
+    // 楼层车位
+    const floorSpaces = []
+    const floors = ['B1', 'B2']
+    floors.forEach(floor => {
+        for (let i = 1; i <= 20; i++) {
+            const status = Mock.mock('@integer(0, 3)')
+            floorSpaces.push({
+                id: i + (floor === 'B1' ? 0 : 20),
+                space_code: `A${floor}${String(i).padStart(3, '0')}`,
+                floor_id: floor,
+                space_status: status,
+                license_plate: status === 1 ? Mock.mock(/^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-HJ-NP-Z0-9]{5}$/) : null
+            })
+        }
+    })
+
+    return {
+        code: 200,
+        msg: "success",
+        data: {
+            recentEntries,
+            recentExits,
+            floorSpaces
+        }
+    }
+})
+
+// 报表数据
+Mock.mock(/\/parking\/monitor\/report/, 'get', () => {
+    const days = 7
+    const dates = []
+    const occupancyRates = []
+    const revenues = []
+    const details = []
+
+    for (let i = days - 1; i >= 0; i--) {
+        const date = new Date()
+        date.setDate(date.getDate() - i)
+        const dateStr = date.toISOString().split('T')[0]
+
+        const occupancyRate = Mock.mock('@float(40, 60, 1, 1)')
+        const revenue = Mock.mock('@float(4000, 8000, 0, 0)')
+
+        dates.push(dateStr)
+        occupancyRates.push(occupancyRate)
+        revenues.push(revenue)
+
+        details.push({
+            date: dateStr,
+            totalSpaces: 500,
+            usedSpaces: Math.floor(500 * occupancyRate / 100),
+            occupancyRate: occupancyRate,
+            entryCount: Mock.mock('@integer(300, 500)'),
+            exitCount: Mock.mock('@integer(280, 480)'),
+            revenue: revenue
+        })
+    }
+
+    return {
+        code: 200,
+        msg: "success",
+        data: {
+            dates,
+            occupancyRates,
+            revenues,
+            details,
+            summary: {
+                avgOccupancyRate: (occupancyRates.reduce((a, b) => a + b) / days).toFixed(1),
+                totalRevenue: revenues.reduce((a, b) => a + b),
+                totalEntries: Mock.mock('@integer(2000, 3000)'),
+                totalExits: Mock.mock('@integer(1900, 2900)'),
+                peakOccupancyRate: Math.max(...occupancyRates),
+                peakDate: dates[occupancyRates.indexOf(Math.max(...occupancyRates))]
+            }
+        }
+    }
+})
+
+export default Mock

+ 163 - 0
pm_ui/src/api/parking/monitor.js

@@ -0,0 +1,163 @@
+// 使用模拟服务代替真实请求
+import mockService from '@/api/mock/parking-mock-service'
+
+// 获取停车场统计数据
+export function getParkingStatistics() {
+    return mockService.getParkingStatistics()
+}
+
+// 获取实时监控数据
+export function getRealtimeData() {
+    return mockService.getRealtimeData()
+}
+
+// 获取停车场报表数据
+export function getParkingReport(params) {
+    return mockService.getParkingReport(params)
+}
+
+// 获取楼层车位分布
+export function getFloorDistribution(buildingId) {
+    return request({
+        url: '/parking/monitor/distribution/' + buildingId,
+        method: 'get'
+    })
+}
+
+// 获取最近入场记录
+export function getRecentEntries(limit = 10) {
+    return request({
+        url: '/parking/monitor/entries',
+        method: 'get',
+        params: { limit }
+    })
+}
+
+// 获取最近出场记录
+export function getRecentExits(limit = 10) {
+    return request({
+        url: '/parking/monitor/exits',
+        method: 'get',
+        params: { limit }
+    })
+}
+
+// 获取车位占用率趋势
+export function getOccupancyTrend(params) {
+    return request({
+        url: '/parking/monitor/occupancy-trend',
+        method: 'get',
+        params: params
+    })
+}
+
+// 获取收入趋势
+export function getRevenueTrend(params) {
+    return request({
+        url: '/parking/monitor/revenue-trend',
+        method: 'get',
+        params: params
+    })
+}
+
+// 导出报表
+export function exportReport(params) {
+    return request({
+        url: '/parking/monitor/report/export',
+        method: 'get',
+        params: params,
+        responseType: 'blob'
+    })
+}
+
+// 获取异常车位列表
+export function getAbnormalSpaces() {
+    return request({
+        url: '/parking/monitor/abnormal',
+        method: 'get'
+    })
+}
+
+// 获取长时停车列表
+export function getLongTermParking(hours = 24) {
+    return request({
+        url: '/parking/monitor/long-term',
+        method: 'get',
+        params: { hours }
+    })
+}
+
+// 获取车位使用热力图数据
+export function getHeatmapData(params) {
+    return request({
+        url: '/parking/monitor/heatmap',
+        method: 'get',
+        params: params
+    })
+}
+
+// 获取高峰时段分析
+export function getPeakAnalysis(params) {
+    return request({
+        url: '/parking/monitor/peak-analysis',
+        method: 'get',
+        params: params
+    })
+}
+
+// 获取车位周转率
+export function getTurnoverRate(params) {
+    return request({
+        url: '/parking/monitor/turnover-rate',
+        method: 'get',
+        params: params
+    })
+}
+
+// 获取实时告警信息
+export function getRealtimeAlerts() {
+    return request({
+        url: '/parking/monitor/alerts',
+        method: 'get'
+    })
+}
+
+// WebSocket连接配置
+export function getWebSocketConfig() {
+    return request({
+        url: '/parking/monitor/ws-config',
+        method: 'get'
+    })
+}
+
+
+
+
+
+
+
+
+
+
+export function getRealtimePatrol() {
+    return request({
+        url: '/patrol/monitor/realtime',
+        method: 'get'
+    })
+}
+
+// 获取巡更路线数据
+export function getPatrolRoute(routeId) {
+    return request({
+        url: '/patrol/monitor/route/' + routeId,
+        method: 'get'
+    })
+}
+
+// 获取摄像头视频流
+export function getCameraStream(cameraId) {
+    return request({
+        url: '/patrol/monitor/camera/' + cameraId,
+        method: 'get'
+    })
+}

+ 106 - 0
pm_ui/src/api/parking/space.js

@@ -0,0 +1,106 @@
+// 使用模拟服务代替真实请求
+import mockService from '@/api/mock/parking-mock-service'
+// 查询车位列表
+export function listParkingSpace(query) {
+    return mockService.listParkingSpace(query)
+}
+
+// 查询车位详细
+export function getParkingSpace(id) {
+    return mockService.getParkingSpace(id)
+}
+
+// 新增车位
+export function addParkingSpace(data) {
+    return mockService.addParkingSpace(data)
+}
+
+// 修改车位
+export function updateParkingSpace(data) {
+    return mockService.updateParkingSpace(data)
+}
+
+// 删除车位
+export function delParkingSpace(id) {
+    return mockService.delParkingSpace(id)
+}
+
+// 导出车位
+export function exportParkingSpace(query) {
+    return mockService.exportParkingSpace(query)
+}
+
+// 批量更新车位状态
+export function updateParkingSpaceStatus(data) {
+    return request({
+        url: '/parking/space/status',
+        method: 'put',
+        data: data
+    })
+}
+
+// 获取指定楼栋的车位列表
+export function listSpaceByBuilding(buildingId) {
+    return request({
+        url: '/parking/space/building/' + buildingId,
+        method: 'get'
+    })
+}
+
+// 获取空闲车位列表
+export function listFreeSpace(query) {
+    return request({
+        url: '/parking/space/free',
+        method: 'get',
+        params: query
+    })
+}
+
+// 车位预约
+export function reserveSpace(data) {
+    return request({
+        url: '/parking/space/reserve',
+        method: 'post',
+        data: data
+    })
+}
+
+// 取消预约
+export function cancelReservation(spaceId) {
+    return request({
+        url: '/parking/space/reserve/' + spaceId,
+        method: 'delete'
+    })
+}
+
+// 获取车位占用历史
+export function getSpaceHistory(spaceId, query) {
+    return request({
+        url: '/parking/space/history/' + spaceId,
+        method: 'get',
+        params: query
+    })
+}
+
+// 批量导入车位
+export function importParkingSpace(file) {
+    const formData = new FormData()
+    formData.append('file', file)
+    return request({
+        url: '/parking/space/import',
+        method: 'post',
+        data: formData,
+        headers: {
+            'Content-Type': 'multipart/form-data'
+        }
+    })
+}
+
+// 下载车位导入模板
+export function downloadTemplate() {
+    return request({
+        url: '/parking/space/template',
+        method: 'get',
+        responseType: 'blob'
+    })
+}

+ 22 - 0
pm_ui/src/api/patrol/monitor.js

@@ -0,0 +1,22 @@
+export function getRealtimePatrol() {
+    return request({
+        url: '/patrol/monitor/realtime',
+        method: 'get'
+    })
+}
+
+// 获取巡更路线数据
+export function getPatrolRoute(routeId) {
+    return request({
+        url: '/patrol/monitor/route/' + routeId,
+        method: 'get'
+    })
+}
+
+// 获取摄像头视频流
+export function getCameraStream(cameraId) {
+    return request({
+        url: '/patrol/monitor/camera/' + cameraId,
+        method: 'get'
+    })
+}

+ 66 - 0
pm_ui/src/api/patrol/point.js

@@ -0,0 +1,66 @@
+import request from '@/utils/request'
+
+// 查询巡更点列表
+export function listPatrolPoint(query) {
+    return request({
+        url: '/patrol/point/list',
+        method: 'get',
+        params: query
+    })
+}
+
+// 查询巡更点详细
+export function getPatrolPoint(id) {
+    return request({
+        url: '/patrol/point/' + id,
+        method: 'get'
+    })
+}
+
+// 新增巡更点
+export function addPatrolPoint(data) {
+    return request({
+        url: '/patrol/point',
+        method: 'post',
+        data: data
+    })
+}
+
+// 修改巡更点
+export function updatePatrolPoint(data) {
+    return request({
+        url: '/patrol/point',
+        method: 'put',
+        data: data
+    })
+}
+
+// 删除巡更点
+export function delPatrolPoint(id) {
+    return request({
+        url: '/patrol/point/' + id,
+        method: 'delete'
+    })
+}
+
+// 修改巡更点状态
+export function changePointStatus(id, status) {
+    const data = {
+        id: id,
+        isActive: status
+    }
+    return request({
+        url: '/patrol/point/changeStatus',
+        method: 'put',
+        data: data
+    })
+}
+
+// 导出巡更点
+export function exportPatrolPoint(query) {
+    return request({
+        url: '/patrol/point/export',
+        method: 'post',
+        params: query
+    })
+}

+ 28 - 0
pm_ui/src/api/patrol/record.js

@@ -0,0 +1,28 @@
+import request from '@/utils/request'
+
+// 查询巡更记录列表
+export function listPatrolRecord(query) {
+    return request({
+        url: '/patrol/record/list',
+        method: 'get',
+        params: query
+    })
+}
+
+// 查询巡更记录详细
+export function getPatrolRecord(id) {
+    return request({
+        url: '/patrol/record/' + id,
+        method: 'get'
+    })
+}
+
+// 导出巡更记录
+export function exportPatrolRecord(query) {
+    return request({
+        url: '/patrol/record/export',
+        method: 'post',
+        params: query
+    })
+}
+

+ 137 - 0
pm_ui/src/api/subsystem/energy.js

@@ -0,0 +1,137 @@
+import request from '@/utils/request'
+
+// 查询能耗设备列表
+export function listEnergyDevices(query) {
+    return request({
+        url: '/subsystem/energy/devices/list',
+        method: 'get',
+        params: query
+    })
+}
+
+// 查询能耗设备详细
+export function getEnergyDevice(deviceId) {
+    return request({
+        url: '/subsystem/energy/devices/' + deviceId,
+        method: 'get'
+    })
+}
+
+// 新增能耗设备
+export function addEnergyDevice(data) {
+    return request({
+        url: '/subsystem/energy/devices',
+        method: 'post',
+        data: data
+    })
+}
+
+// 修改能耗设备
+export function updateEnergyDevice(data) {
+    return request({
+        url: '/subsystem/energy/devices',
+        method: 'put',
+        data: data
+    })
+}
+
+// 删除能耗设备
+export function delEnergyDevice(deviceId) {
+    return request({
+        url: '/subsystem/energy/devices/' + deviceId,
+        method: 'delete'
+    })
+}
+
+// 导出能耗设备
+export function exportEnergyDevice(query) {
+    return request({
+        url: '/subsystem/energy/devices/export',
+        method: 'post',
+        params: query
+    })
+}
+
+// 获取能耗统计数据
+export function getEnergyStatistics(query) {
+    return request({
+        url: '/subsystem/energy/statistics',
+        method: 'get',
+        params: query
+    })
+}
+
+// 获取能耗曲线数据
+export function getEnergyChart(deviceCode, timeRange) {
+    return request({
+        url: '/subsystem/energy/chart',
+        method: 'get',
+        params: {
+            deviceCode: deviceCode,
+            beginTime: timeRange[0],
+            endTime: timeRange[1]
+        }
+    })
+}
+
+// 设备控制
+export function controlDevice(data) {
+    return request({
+        url: '/subsystem/energy/control',
+        method: 'post',
+        data: data
+    })
+}
+
+// 获取实时能耗数据
+export function getRealTimeEnergyData(deviceCode) {
+    return request({
+        url: '/subsystem/energy/realtime/' + deviceCode,
+        method: 'get'
+    })
+}
+
+// 查询能耗历史记录
+export function getEnergyHistory(query) {
+    return request({
+        url: '/subsystem/energy/history/list',
+        method: 'get',
+        params: query
+    })
+}
+
+// 获取能耗报表
+export function getEnergyReport(query) {
+    return request({
+        url: '/subsystem/energy/report',
+        method: 'get',
+        params: query
+    })
+}
+
+// 设置能耗阈值
+export function setEnergyThreshold(data) {
+    return request({
+        url: '/subsystem/energy/threshold',
+        method: 'post',
+        data: data
+    })
+}
+
+// 获取能耗告警信息
+export function getEnergyAlarms(query) {
+    return request({
+        url: '/subsystem/energy/alarms/list',
+        method: 'get',
+        params: query
+    })
+}
+
+// 处理能耗告警
+export function handleEnergyAlarm(alarmId, data) {
+    return request({
+        url: '/subsystem/energy/alarms/' + alarmId,
+        method: 'put',
+        data: data
+    })
+}

+ 393 - 0
pm_ui/src/api/subsystem/intrusion.js

@@ -0,0 +1,393 @@
+import request from '@/utils/request'
+
+// 查询报警点列表
+export function listAlarmPoints(query) {
+    return request({
+        url: '/subsystem/intrusion/alarmpoints/list',
+        method: 'get',
+        params: query
+    })
+}
+
+// 查询报警点详细
+export function getAlarmPoint(pointId) {
+    return request({
+        url: '/subsystem/intrusion/alarmpoints/' + pointId,
+        method: 'get'
+    })
+}
+
+// 新增报警点
+export function addAlarmPoint(data) {
+    return request({
+        url: '/subsystem/intrusion/alarmpoints',
+        method: 'post',
+        data: data
+    })
+}
+
+// 修改报警点
+export function updateAlarmPoint(data) {
+    return request({
+        url: '/subsystem/intrusion/alarmpoints',
+        method: 'put',
+        data: data
+    })
+}
+
+// 删除报警点
+export function delAlarmPoint(pointId) {
+    return request({
+        url: '/subsystem/intrusion/alarmpoints/' + pointId,
+        method: 'delete'
+    })
+}
+
+// 导出报警点
+export function exportAlarmPoint(query) {
+    return request({
+        url: '/subsystem/intrusion/alarmpoints/export',
+        method: 'post',
+        params: query
+    })
+}
+
+// 切换布防状态
+export function toggleArmStatus(data) {
+    return request({
+        url: '/subsystem/intrusion/alarmpoints/arm',
+        method: 'post',
+        data: data
+    })
+}
+
+// 批量布防/撤防
+export function batchArmControl(data) {
+    return request({
+        url: '/subsystem/intrusion/alarmpoints/batch-arm',
+        method: 'post',
+        data: data
+    })
+}
+
+// 测试报警设备
+export function testAlarmDevice(deviceCode) {
+    return request({
+        url: '/subsystem/intrusion/alarmpoints/test/' + deviceCode,
+        method: 'post'
+    })
+}
+
+// 更新设备配置
+export function updateDeviceConfig(data) {
+    return request({
+        url: '/subsystem/intrusion/alarmpoints/config',
+        method: 'put',
+        data: data
+    })
+}
+
+// 查询报警记录列表
+export function listAlarms(query) {
+    return request({
+        url: '/subsystem/intrusion/alarms/list',
+        method: 'get',
+        params: query
+    })
+}
+
+// 查询报警记录详细
+export function getAlarm(alarmId) {
+    return request({
+        url: '/subsystem/intrusion/alarms/' + alarmId,
+        method: 'get'
+    })
+}
+
+// 处理报警
+export function processAlarm(data) {
+    return request({
+        url: '/subsystem/intrusion/alarms/process',
+        method: 'put',
+        data: data
+    })
+}
+
+// 批量处理报警
+export function batchProcessAlarms(data) {
+    return request({
+        url: '/subsystem/intrusion/alarms/batch-process',
+        method: 'put',
+        data: data
+    })
+}
+
+// 确认报警
+export function confirmAlarm(alarmId) {
+    return request({
+        url: '/subsystem/intrusion/alarms/confirm/' + alarmId,
+        method: 'put'
+    })
+}
+
+// 忽略报警
+export function ignoreAlarm(alarmId, data) {
+    return request({
+        url: '/subsystem/intrusion/alarms/ignore/' + alarmId,
+        method: 'put',
+        data: data
+    })
+}
+
+// 查询防区列表
+export function listZones(query) {
+    return request({
+        url: '/subsystem/intrusion/zones/list',
+        method: 'get',
+        params: query
+    })
+}
+
+// 查询防区详细
+export function getZone(zoneId) {
+    return request({
+        url: '/subsystem/intrusion/zones/' + zoneId,
+        method: 'get'
+    })
+}
+
+// 新增防区
+export function addZone(data) {
+    return request({
+        url: '/subsystem/intrusion/zones',
+        method: 'post',
+        data: data
+    })
+}
+
+// 修改防区
+export function updateZone(data) {
+    return request({
+        url: '/subsystem/intrusion/zones',
+        method: 'put',
+        data: data
+    })
+}
+
+// 删除防区
+export function delZone(zoneId) {
+    return request({
+        url: '/subsystem/intrusion/zones/' + zoneId,
+        method: 'delete'
+    })
+}
+
+// 防区布防/撤防
+export function toggleZoneStatus(zoneId, data) {
+    return request({
+        url: '/subsystem/intrusion/zones/toggle/' + zoneId,
+        method: 'put',
+        data: data
+    })
+}
+
+// 批量防区操作
+export function batchZoneControl(data) {
+    return request({
+        url: '/subsystem/intrusion/zones/batch-control',
+        method: 'post',
+        data: data
+    })
+}
+
+// 查询防区设备列表
+export function listZoneDevices(zoneCode) {
+    return request({
+        url: '/subsystem/intrusion/zones/devices/' + zoneCode,
+        method: 'get'
+    })
+}
+
+// 添加设备到防区
+export function addDeviceToZone(data) {
+    return request({
+        url: '/subsystem/intrusion/zones/add-device',
+        method: 'post',
+        data: data
+    })
+}
+
+// 从防区移除设备
+export function removeDeviceFromZone(data) {
+    return request({
+        url: '/subsystem/intrusion/zones/remove-device',
+        method: 'post',
+        data: data
+    })
+}
+
+// 查询联动规则列表
+export function listLinkageRules(query) {
+    return request({
+        url: '/subsystem/intrusion/linkage/list',
+        method: 'get',
+        params: query
+    })
+}
+
+// 新增联动规则
+export function addLinkageRule(data) {
+    return request({
+        url: '/subsystem/intrusion/linkage',
+        method: 'post',
+        data: data
+    })
+}
+
+// 修改联动规则
+export function updateLinkageRule(data) {
+    return request({
+        url: '/subsystem/intrusion/linkage',
+        method: 'put',
+        data: data
+    })
+}
+
+// 删除联动规则
+export function delLinkageRule(ruleId) {
+    return request({
+        url: '/subsystem/intrusion/linkage/' + ruleId,
+        method: 'delete'
+    })
+}
+
+// 启用/禁用联动规则
+export function toggleLinkageRule(ruleId, data) {
+    return request({
+        url: '/subsystem/intrusion/linkage/toggle/' + ruleId,
+        method: 'put',
+        data: data
+    })
+}
+
+// 查询联动执行记录
+export function listLinkageRecords(query) {
+    return request({
+        url: '/subsystem/intrusion/linkage/records/list',
+        method: 'get',
+        params: query
+    })
+}
+
+// 手动执行联动
+export function executeLinkage(data) {
+    return request({
+        url: '/subsystem/intrusion/linkage/execute',
+        method: 'post',
+        data: data
+    })
+}
+
+// 获取报警统计数据
+export function getAlarmStatistics(query) {
+    return request({
+        url: '/subsystem/intrusion/statistics',
+        method: 'get',
+        params: query
+    })
+}
+
+// 获取实时状态
+export function getRealTimeStatus() {
+    return request({
+        url: '/subsystem/intrusion/realtime/status',
+        method: 'get'
+    })
+}
+
+// 获取报警趋势数据
+export function getAlarmTrend(query) {
+    return request({
+        url: '/subsystem/intrusion/statistics/trend',
+        method: 'get',
+        params: query
+    })
+}
+
+// 设备健康检测
+export function checkDeviceHealth(deviceCode) {
+    return request({
+        url: '/subsystem/intrusion/health/check/' + deviceCode,
+        method: 'post'
+    })
+}
+
+// 批量设备健康检测
+export function batchCheckDeviceHealth(deviceCodes) {
+    return request({
+        url: '/subsystem/intrusion/health/batch-check',
+        method: 'post',
+        data: { deviceCodes: deviceCodes }
+    })
+}
+
+// 获取设备配置
+export function getDeviceConfig(deviceCode) {
+    return request({
+        url: '/subsystem/intrusion/config/' + deviceCode,
+        method: 'get'
+    })
+}
+
+// 重置设备
+export function resetDevice(deviceCode) {
+    return request({
+        url: '/subsystem/intrusion/device/reset/' + deviceCode,
+        method: 'post'
+    })
+}
+
+// 查询维护记录
+export function listMaintenanceRecords(query) {
+    return request({
+        url: '/subsystem/intrusion/maintenance/list',
+        method: 'get',
+        params: query
+    })
+}
+
+// 新增维护记录
+export function addMaintenanceRecord(data) {
+    return request({
+        url: '/subsystem/intrusion/maintenance',
+        method: 'post',
+        data: data
+    })
+}
+
+// 导出报警记录
+export function exportAlarms(query) {
+    return request({
+        url: '/subsystem/intrusion/alarms/export',
+        method: 'post',
+        params: query
+    })
+}
+
+// 生成报警报表
+export function generateAlarmReport(query) {
+    return request({
+        url: '/subsystem/intrusion/report/generate',
+        method: 'post',
+        params: query
+    })
+}
+
+// 数据同步
+export function syncIntrusionData(data) {
+    return request({
+        url: '/subsystem/intrusion/sync',
+        method: 'post',
+        data: data
+    })
+}

+ 241 - 0
pm_ui/src/api/subsystem/video.js

@@ -0,0 +1,241 @@
+import request from '@/utils/request'
+
+// 查询摄像机列表
+export function listCameras(query) {
+    return request({
+        url: '/subsystem/video/cameras/list',
+        method: 'get',
+        params: query
+    })
+}
+
+// 查询摄像机详细
+export function getCamera(cameraId) {
+    return request({
+        url: '/subsystem/video/cameras/' + cameraId,
+        method: 'get'
+    })
+}
+
+// 新增摄像机
+export function addCamera(data) {
+    return request({
+        url: '/subsystem/video/cameras',
+        method: 'post',
+        data: data
+    })
+}
+
+// 修改摄像机
+export function updateCamera(data) {
+    return request({
+        url: '/subsystem/video/cameras',
+        method: 'put',
+        data: data
+    })
+}
+
+// 删除摄像机
+export function delCamera(cameraId) {
+    return request({
+        url: '/subsystem/video/cameras/' + cameraId,
+        method: 'delete'
+    })
+}
+
+// 导出摄像机
+export function exportCamera(query) {
+    return request({
+        url: '/subsystem/video/cameras/export',
+        method: 'post',
+        params: query
+    })
+}
+
+// 获取摄像机视频流
+export function getCameraStream(cameraCode) {
+    return request({
+        url: '/subsystem/video/stream/' + cameraCode,
+        method: 'get'
+    })
+}
+
+// 云台控制
+export function ptzControl(data) {
+    return request({
+        url: '/subsystem/video/ptz/control',
+        method: 'post',
+        data: data
+    })
+}
+
+// 录像控制
+export function recordingControl(data) {
+    return request({
+        url: '/subsystem/video/recording/control',
+        method: 'post',
+        data: data
+    })
+}
+
+// 抓拍
+export function takeCameraSnapshot(cameraCode) {
+    return request({
+        url: '/subsystem/video/snapshot/' + cameraCode,
+        method: 'post'
+    })
+}
+
+// 查询录像文件列表
+export function listRecordingFiles(query) {
+    return request({
+        url: '/subsystem/video/recordings/list',
+        method: 'get',
+        params: query
+    })
+}
+
+// 播放录像
+export function playRecording(recordingId) {
+    return request({
+        url: '/subsystem/video/recordings/play/' + recordingId,
+        method: 'get'
+    })
+}
+
+// 下载录像
+export function downloadRecording(recordingId) {
+    return request({
+        url: '/subsystem/video/recordings/download/' + recordingId,
+        method: 'get',
+        responseType: 'blob'
+    })
+}
+
+// 查询联动规则列表
+export function listLinkageRules(query) {
+    return request({
+        url: '/subsystem/video/linkage/list',
+        method: 'get',
+        params: query
+    })
+}
+
+// 查询联动规则详细
+export function getLinkageRule(ruleId) {
+    return request({
+        url: '/subsystem/video/linkage/' + ruleId,
+        method: 'get'
+    })
+}
+
+// 新增联动规则
+export function addLinkageRule(data) {
+    return request({
+        url: '/subsystem/video/linkage',
+        method: 'post',
+        data: data
+    })
+}
+
+// 修改联动规则
+export function updateLinkageRule(data) {
+    return request({
+        url: '/subsystem/video/linkage',
+        method: 'put',
+        data: data
+    })
+}
+
+// 删除联动规则
+export function deleteLinkageRule(ruleId) {
+    return request({
+        url: '/subsystem/video/linkage/' + ruleId,
+        method: 'delete'
+    })
+}
+
+// 启用/禁用联动规则
+export function toggleLinkageRule(ruleId, data) {
+    return request({
+        url: '/subsystem/video/linkage/toggle/' + ruleId,
+        method: 'put',
+        data: data
+    })
+}
+
+// 查询联动执行记录
+export function listLinkageRecords(query) {
+    return request({
+        url: '/subsystem/video/linkage/records/list',
+        method: 'get',
+        params: query
+    })
+}
+
+// 查询预置位列表
+export function listPresets(cameraCode) {
+    return request({
+        url: '/subsystem/video/presets/' + cameraCode,
+        method: 'get'
+    })
+}
+
+// 设置预置位
+export function setPreset(data) {
+    return request({
+        url: '/subsystem/video/presets',
+        method: 'post',
+        data: data
+    })
+}
+
+// 删除预置位
+export function deletePreset(presetId) {
+    return request({
+        url: '/subsystem/video/presets/' + presetId,
+        method: 'delete'
+    })
+}
+
+// 查询摄像机状态统计
+export function getCameraStatistics() {
+    return request({
+        url: '/subsystem/video/statistics',
+        method: 'get'
+    })
+}
+
+// 摄像机健康检测
+export function checkCameraHealth(cameraCode) {
+    return request({
+        url: '/subsystem/video/health/check/' + cameraCode,
+        method: 'post'
+    })
+}
+
+// 批量摄像机健康检测
+export function batchCheckCameraHealth(cameraCodes) {
+    return request({
+        url: '/subsystem/video/health/batch-check',
+        method: 'post',
+        data: { cameraCodes: cameraCodes }
+    })
+}
+
+// 获取摄像机配置
+export function getCameraConfig(cameraCode) {
+    return request({
+        url: '/subsystem/video/config/' + cameraCode,
+        method: 'get'
+    })
+}
+
+// 更新摄像机配置
+export function updateCameraConfig(data) {
+    return request({
+        url: '/subsystem/video/config',
+        method: 'put',
+        data: data
+    })
+}

+ 464 - 0
pm_ui/src/views/nygl/index.vue

@@ -0,0 +1,464 @@
+<template>
+  <div class="app-container">
+    <el-tabs v-model="activeTab">
+      <!-- 能耗监测标签页 -->
+      <el-tab-pane label="能耗监测" name="monitoring">
+        <el-form :model="energyQuery" ref="energyQueryRef" :inline="true" label-width="80px">
+          <el-form-item label="设备编码" prop="deviceCode">
+            <el-input v-model="energyQuery.deviceCode" placeholder="请输入设备编码" clearable />
+          </el-form-item>
+          <el-form-item label="设备名称" prop="deviceName">
+            <el-input v-model="energyQuery.deviceName" placeholder="请输入设备名称" clearable />
+          </el-form-item>
+          <el-form-item label="能耗类型" prop="energyType">
+            <el-select v-model="energyQuery.energyType" placeholder="请选择能耗类型" clearable>
+              <el-option label="电" value="electricity" />
+              <el-option label="水" value="water" />
+              <el-option label="煤" value="coal" />
+              <el-option label="气" value="gas" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="楼栋" prop="building">
+            <el-select v-model="energyQuery.building" placeholder="请选择楼栋" clearable>
+              <el-option label="A栋" value="A" />
+              <el-option label="B栋" value="B" />
+              <el-option label="C栋" value="C" />
+              <el-option label="D栋" value="D" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="楼层" prop="floor">
+            <el-input v-model="energyQuery.floor" placeholder="请输入楼层" clearable />
+          </el-form-item>
+          <el-form-item label="时间范围" prop="timeRange">
+            <el-date-picker
+                v-model="energyQuery.timeRange"
+                type="daterange"
+                range-separator="至"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+                value-format="YYYY-MM-DD"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" icon="Search" @click="getEnergyList">搜索</el-button>
+            <el-button icon="Refresh" @click="resetEnergyQuery">重置</el-button>
+          </el-form-item>
+        </el-form>
+
+        <el-table v-loading="energyLoading" :data="energyList" stripe border>
+          <el-table-column label="设备编码" prop="deviceCode"  />
+          <el-table-column label="设备名称" prop="deviceName"  />
+          <el-table-column label="能耗类型" prop="energyType" >
+            <template #default="scope">
+              <el-tag :type="getEnergyTypeTag(scope.row.energyType)">
+                {{ getEnergyTypeText(scope.row.energyType) }}
+              </el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column label="位置信息" >
+            <template #default="scope">
+              <div>{{ scope.row.building }}栋-{{ scope.row.floor }}层</div>
+              <div class="text-gray-500">{{ scope.row.roomNo }}</div>
+            </template>
+          </el-table-column>
+          <el-table-column label="当前功率" prop="currentPower" >
+            <template #default="scope">
+              <span>{{ scope.row.currentPower }} kW</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="今日能耗" prop="dailyConsumption">
+            <template #default="scope">
+              <span>{{ scope.row.dailyConsumption }} {{ getUnit(scope.row.energyType) }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="月度能耗" prop="monthlyConsumption" >
+            <template #default="scope">
+              <span>{{ scope.row.monthlyConsumption }} {{ getUnit(scope.row.energyType) }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="设备状态" prop="deviceStatus" >
+            <template #default="scope">
+              <el-tag :type="scope.row.deviceStatus === 1 ? 'success' : scope.row.deviceStatus === 2 ? 'warning' : 'danger'">
+                {{ getStatusText(scope.row.deviceStatus) }}
+              </el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column label="更新时间" prop="updateTime" >
+            <template #default="scope">
+              <span>{{ parseTime(scope.row.updateTime) }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="操作"  fixed="right" width="270px">
+            <template #default="scope">
+              <el-button link type="primary" icon="View" @click="handleEnergyDetail(scope.row)">详情</el-button>
+              <el-button link type="success" icon="TrendCharts" @click="handleEnergyChart(scope.row)">能耗曲线</el-button>
+              <el-button link type="warning" icon="Setting" @click="handleDeviceControl(scope.row)">设备控制</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <pagination
+            v-show="energyTotal > 0"
+            :total="energyTotal"
+            v-model:page="energyQuery.pageNum"
+            v-model:limit="energyQuery.pageSize"
+            @pagination="getEnergyList"
+        />
+      </el-tab-pane>
+
+      <!-- 能耗统计标签页 -->
+      <el-tab-pane label="能耗统计" name="statistics">
+        <el-row :gutter="20">
+          <el-col :span="6">
+            <el-card class="box-card">
+              <div slot="header" class="clearfix">
+                <span>今日总能耗</span>
+              </div>
+              <div class="text item">
+                <div class="energy-stat">
+                  <div class="energy-item">
+                    <i class="el-icon-lightning" style="color: #f56c6c;"></i>
+                    <span>电: {{ statisticsData.todayElectricity }} kWh</span>
+                  </div>
+                  <div class="energy-item">
+                    <i class="el-icon-water" style="color: #409eff;"></i>
+                    <span>水: {{ statisticsData.todayWater }} m³</span>
+                  </div>
+                  <div class="energy-item">
+                    <i class="el-icon-fire" style="color: #67c23a;"></i>
+                    <span>气: {{ statisticsData.todayGas }} m³</span>
+                  </div>
+                </div>
+              </div>
+            </el-card>
+          </el-col>
+          <el-col :span="6">
+            <el-card class="box-card">
+              <div slot="header" class="clearfix">
+                <span>本月总能耗</span>
+              </div>
+              <div class="text item">
+                <div class="energy-stat">
+                  <div class="energy-item">
+                    <i class="el-icon-lightning" style="color: #f56c6c;"></i>
+                    <span>电: {{ statisticsData.monthElectricity }} kWh</span>
+                  </div>
+                  <div class="energy-item">
+                    <i class="el-icon-water" style="color: #409eff;"></i>
+                    <span>水: {{ statisticsData.monthWater }} m³</span>
+                  </div>
+                  <div class="energy-item">
+                    <i class="el-icon-fire" style="color: #67c23a;"></i>
+                    <span>气: {{ statisticsData.monthGas }} m³</span>
+                  </div>
+                </div>
+              </div>
+            </el-card>
+          </el-col>
+          <el-col :span="6">
+            <el-card class="box-card">
+              <div slot="header" class="clearfix">
+                <span>在线设备数</span>
+              </div>
+              <div class="text item">
+                <div class="device-stat">
+                  <div class="stat-number">{{ statisticsData.onlineDevices }}</div>
+                  <div class="stat-text">/ {{ statisticsData.totalDevices }} 台</div>
+                </div>
+              </div>
+            </el-card>
+          </el-col>
+          <el-col :span="6">
+            <el-card class="box-card">
+              <div slot="header" class="clearfix">
+                <span>告警设备数</span>
+              </div>
+              <div class="text item">
+                <div class="device-stat">
+                  <div class="stat-number" style="color: #f56c6c;">{{ statisticsData.alarmDevices }}</div>
+                  <div class="stat-text">台设备异常</div>
+                </div>
+              </div>
+            </el-card>
+          </el-col>
+        </el-row>
+      </el-tab-pane>
+    </el-tabs>
+
+    <!-- 能耗详情抽屉 -->
+    <el-drawer
+        v-model="detailVisible"
+        title="设备能耗详情"
+        direction="rtl"
+        size="50%"
+    >
+      <el-descriptions :column="2" border>
+        <el-descriptions-item label="设备编码">{{ currentDevice.deviceCode }}</el-descriptions-item>
+        <el-descriptions-item label="设备名称">{{ currentDevice.deviceName }}</el-descriptions-item>
+        <el-descriptions-item label="能耗类型">{{ getEnergyTypeText(currentDevice.energyType) }}</el-descriptions-item>
+        <el-descriptions-item label="设备状态">
+          <el-tag :type="currentDevice.deviceStatus === 1 ? 'success' : currentDevice.deviceStatus === 2 ? 'warning' : 'danger'">
+            {{ getStatusText(currentDevice.deviceStatus) }}
+          </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="位置">{{ currentDevice.building }}栋-{{ currentDevice.floor }}层-{{ currentDevice.roomNo }}</el-descriptions-item>
+        <el-descriptions-item label="当前功率">{{ currentDevice.currentPower }} kW</el-descriptions-item>
+        <el-descriptions-item label="今日能耗">{{ currentDevice.dailyConsumption }} {{ getUnit(currentDevice.energyType) }}</el-descriptions-item>
+        <el-descriptions-item label="月度能耗">{{ currentDevice.monthlyConsumption }} {{ getUnit(currentDevice.energyType) }}</el-descriptions-item>
+        <el-descriptions-item label="年度能耗">{{ currentDevice.yearlyConsumption }} {{ getUnit(currentDevice.energyType) }}</el-descriptions-item>
+        <el-descriptions-item label="额定功率">{{ currentDevice.ratedPower }} kW</el-descriptions-item>
+        <el-descriptions-item label="负载率">{{ currentDevice.loadRate }}%</el-descriptions-item>
+        <el-descriptions-item label="更新时间">{{ parseTime(currentDevice.updateTime) }}</el-descriptions-item>
+      </el-descriptions>
+    </el-drawer>
+
+    <!-- 能耗曲线图抽屉 -->
+    <el-drawer
+        v-model="chartVisible"
+        title="能耗曲线分析"
+        direction="rtl"
+        size="60%"
+    >
+      <div id="energyChart" style="width: 100%; height: 400px;"></div>
+    </el-drawer>
+
+    <!-- 设备控制抽屉 -->
+    <el-drawer
+        v-model="controlVisible"
+        title="设备控制"
+        direction="rtl"
+        size="40%"
+    >
+      <el-form :model="controlForm" label-width="100px">
+        <el-form-item label="设备名称">
+          <el-input v-model="controlForm.deviceName" disabled />
+        </el-form-item>
+        <el-form-item label="设备状态">
+          <el-radio-group v-model="controlForm.deviceStatus">
+            <el-radio :label="1">启动</el-radio>
+            <el-radio :label="0">停止</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="功率设置" v-if="controlForm.energyType === 'electricity'">
+          <el-slider v-model="controlForm.powerSetting" :min="0" :max="100" show-input />
+        </el-form-item>
+        <el-form-item label="温度设置" v-if="controlForm.deviceType === 'air_conditioner'">
+          <el-input-number v-model="controlForm.temperature" :min="16" :max="30" :step="1" />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="handleDeviceControlSubmit">确认控制</el-button>
+          <el-button @click="controlVisible = false">取消</el-button>
+        </el-form-item>
+      </el-form>
+    </el-drawer>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, watch, onMounted } from 'vue'
+import { listEnergyDevices, getEnergyStatistics, controlDevice } from '@/api/subsystem/energy'
+
+const { proxy } = getCurrentInstance()
+const activeTab = ref('monitoring')
+
+// 监听activeTab的变化
+watch(activeTab, (newVal) => {
+  if (newVal === 'statistics') {
+    getStatisticsData()
+  }
+})
+
+// 能耗监测查询相关
+const energyQuery = reactive({
+  pageNum: 1,
+  pageSize: 10,
+  deviceCode: null,
+  deviceName: null,
+  energyType: null,
+  building: null,
+  floor: null,
+  timeRange: []
+})
+const energyList = ref([])
+const energyTotal = ref(0)
+const energyLoading = ref(false)
+
+// 统计数据
+const statisticsData = ref({
+  todayElectricity: 0,
+  todayWater: 0,
+  todayGas: 0,
+  monthElectricity: 0,
+  monthWater: 0,
+  monthGas: 0,
+  onlineDevices: 0,
+  totalDevices: 0,
+  alarmDevices: 0
+})
+
+// 详情相关
+const detailVisible = ref(false)
+const currentDevice = ref({})
+
+// 图表相关
+const chartVisible = ref(false)
+
+// 控制相关
+const controlVisible = ref(false)
+const controlForm = ref({
+  deviceCode: '',
+  deviceName: '',
+  deviceStatus: 1,
+  powerSetting: 50,
+  temperature: 24,
+  energyType: '',
+  deviceType: ''
+})
+
+// 能耗类型标签
+const getEnergyTypeTag = (type) => {
+  const tags = {
+    'electricity': 'danger',
+    'water': 'primary',
+    'coal': 'info',
+    'gas': 'success'
+  }
+  return tags[type] || 'info'
+}
+
+// 能耗类型文本
+const getEnergyTypeText = (type) => {
+  const texts = {
+    'electricity': '电',
+    'water': '水',
+    'coal': '煤',
+    'gas': '气'
+  }
+  return texts[type] || '未知'
+}
+
+// 单位
+const getUnit = (type) => {
+  const units = {
+    'electricity': 'kWh',
+    'water': 'm³',
+    'coal': 't',
+    'gas': 'm³'
+  }
+  return units[type] || ''
+}
+
+// 设备状态文本
+const getStatusText = (status) => {
+  const texts = ['离线', '在线', '故障', '维护']
+  return texts[status] || '未知'
+}
+
+// 查询能耗设备列表
+function getEnergyList() {
+  energyLoading.value = true
+  const params = {
+    ...energyQuery,
+    pageNum: energyQuery.pageNum,
+    pageSize: energyQuery.pageSize
+  }
+  if (energyQuery.timeRange && energyQuery.timeRange.length === 2) {
+    params.beginTime = energyQuery.timeRange[0]
+    params.endTime = energyQuery.timeRange[1]
+  }
+  listEnergyDevices(params).then(response => {
+    energyList.value = response.rows
+    energyTotal.value = response.total
+    energyLoading.value = false
+  })
+}
+
+// 重置查询
+function resetEnergyQuery() {
+  proxy.resetForm('energyQueryRef')
+  energyQuery.pageNum = 1
+  getEnergyList()
+}
+
+// 获取统计数据
+function getStatisticsData() {
+  getEnergyStatistics().then(response => {
+    statisticsData.value = response.data
+  })
+}
+
+// 查看详情
+function handleEnergyDetail(row) {
+  currentDevice.value = row
+  detailVisible.value = true
+}
+
+// 查看能耗曲线
+function handleEnergyChart(row) {
+  currentDevice.value = row
+  chartVisible.value = true
+  // 这里可以集成图表库如ECharts来显示能耗曲线
+}
+
+// 设备控制
+function handleDeviceControl(row) {
+  controlForm.value = {
+    deviceCode: row.deviceCode,
+    deviceName: row.deviceName,
+    deviceStatus: row.deviceStatus,
+    powerSetting: row.powerSetting || 50,
+    temperature: row.temperature || 24,
+    energyType: row.energyType,
+    deviceType: row.deviceType
+  }
+  controlVisible.value = true
+}
+
+// 提交设备控制
+function handleDeviceControlSubmit() {
+  controlDevice(controlForm.value).then(response => {
+    proxy.$modal.msgSuccess('设备控制指令发送成功')
+    controlVisible.value = false
+    getEnergyList()
+  })
+}
+
+// 初始化
+onMounted(() => {
+  getEnergyList()
+  getStatisticsData()
+})
+</script>
+
+<style scoped>
+.energy-stat {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+}
+
+.energy-item {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.device-stat {
+  text-align: center;
+}
+
+.stat-number {
+  font-size: 24px;
+  font-weight: bold;
+  color: #409eff;
+}
+
+.stat-text {
+  font-size: 14px;
+  color: #666;
+}
+
+.box-card {
+  margin-bottom: 20px;
+}
+</style>

+ 698 - 0
pm_ui/src/views/parking/index.vue

@@ -0,0 +1,698 @@
+<template>
+  <div class="app-container">
+    <el-tabs v-model="activeTab">
+      <!-- 车位管理标签页 -->
+      <el-tab-pane label="车位管理" name="spaces">
+        <el-form :model="spaceQuery" :inline="true" label-width="80px">
+          <el-form-item label="车位编号">
+            <el-input v-model="spaceQuery.spaceCode" placeholder="请输入车位编号" clearable />
+          </el-form-item>
+          <el-form-item label="车位名称">
+            <el-input v-model="spaceQuery.spaceName" placeholder="请输入车位名称" clearable />
+          </el-form-item>
+          <el-form-item label="楼栋">
+            <el-select v-model="spaceQuery.buildingId" placeholder="请选择楼栋" clearable>
+              <el-option v-for="building in buildingList" :key="building.id" :label="building.name" :value="building.id" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="车位状态">
+            <el-select v-model="spaceQuery.spaceStatus" placeholder="请选择状态" clearable>
+              <el-option label="空闲" :value="0" />
+              <el-option label="占用" :value="1" />
+              <el-option label="预约" :value="2" />
+              <el-option label="故障" :value="3" />
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" @click="getSpaceList">搜索</el-button>
+            <el-button @click="resetSpaceQuery">重置</el-button>
+          </el-form-item>
+        </el-form>
+
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <el-button type="primary" plain @click="handleAddSpace">新增</el-button>
+          </el-col>
+        </el-row>
+
+        <el-table v-loading="spaceLoading" :data="spaceList" stripe border>
+          <el-table-column label="车位编号" prop="space_code"  />
+          <el-table-column label="车位名称" prop="space_name"  />
+          <el-table-column label="楼栋" prop="building_name"  />
+          <el-table-column label="楼层" prop="floor_name" />
+          <el-table-column label="车位状态" prop="space_status">
+            <template #default="scope">
+              <el-tag :type="statusTag(scope.row.space_status)">
+                {{ statusText(scope.row.space_status) }}
+              </el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column label="车牌号" prop="license_plate"  />
+          <el-table-column label="入场时间" prop="entry_time"  />
+          <el-table-column label="停车费用" prop="parking_fee" >
+            <template #default="scope">
+              ¥{{ scope.row.parking_fee || '0.00' }}
+            </template>
+          </el-table-column>
+          <el-table-column label="车位类型" prop="space_type" >
+            <template #default="scope">
+              <el-tag :type="typeTag(scope.row.space_type)">
+                {{ typeText(scope.row.space_type) }}
+              </el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column label="操作" fixed="right" width="200">
+            <template #default="scope">
+              <el-button link type="primary" @click="handleUpdateSpace(scope.row)">修改</el-button>
+              <el-button link type="primary" @click="handleViewSpace(scope.row)">详情</el-button>
+              <el-button link type="danger" @click="handleDeleteSpace(scope.row)">删除</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <el-pagination
+            v-model:current-page="spaceQuery.pageNum"
+            v-model:page-size="spaceQuery.pageSize"
+            :page-sizes="[10, 20, 50]"
+            :total="spaceTotal"
+            layout="total, sizes, prev, pager, next, jumper"
+            @size-change="getSpaceList"
+            @current-change="getSpaceList"
+        />
+      </el-tab-pane>
+
+      <!-- 实时监控标签页 -->
+      <el-tab-pane label="实时监控" name="monitor">
+        <div class="monitor-container">
+          <!-- 统计卡片 -->
+          <el-row :gutter="20" class="statistics-row">
+            <el-col :span="6">
+              <el-card>
+                <el-statistic title="总车位数" :value="statistics.totalSpaces" />
+              </el-card>
+            </el-col>
+            <el-col :span="6">
+              <el-card>
+                <el-statistic title="空闲车位" :value="statistics.freeSpaces" />
+              </el-card>
+            </el-col>
+            <el-col :span="6">
+              <el-card>
+                <el-statistic title="占用车位" :value="statistics.occupiedSpaces" />
+              </el-card>
+            </el-col>
+            <el-col :span="6">
+              <el-card>
+                <el-statistic title="占用率" :value="Number(statistics.occupancyRate)" suffix="%" />
+              </el-card>
+            </el-col>
+          </el-row>
+
+          <!-- 楼层车位分布 -->
+          <el-row :gutter="20" style="margin-top: 20px;">
+            <el-col :span="24">
+              <el-card>
+                <template #header>
+                  <div class="card-header">
+                    <span>车位分布图</span>
+                  </div>
+                </template>
+                <div class="parking-map">
+                  <div v-for="floor in currentFloors" :key="floor.id" class="floor-section">
+                    <h4>{{ floor.name }}</h4>
+                    <div class="parking-grid">
+                      <div v-for="space in getFloorSpaces(floor.id)" :key="space.id"
+                           :class="['parking-space', `status-${space.space_status}`]"
+                           @click="handleSpaceClick(space)">
+                        <div class="space-number">{{ space.space_code }}</div>
+                        <div class="space-info">
+                          <span v-if="space.space_status === 1">{{ space.license_plate }}</span>
+                          <span v-else-if="space.space_status === 0">空闲</span>
+                          <span v-else-if="space.space_status === 2">预约</span>
+                          <span v-else>故障</span>
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </el-card>
+            </el-col>
+          </el-row>
+
+          <!-- 实时进出记录 -->
+          <el-row :gutter="20" style="margin-top: 20px;">
+            <el-col :span="12">
+              <el-card>
+                <template #header>
+                  <span>最近入场</span>
+                </template>
+                <el-table :data="recentEntries" height="300">
+                  <el-table-column label="车牌号" prop="license_plate" />
+                  <el-table-column label="车位" prop="space_code" />
+                  <el-table-column label="入场时间" prop="entry_time" />
+                </el-table>
+              </el-card>
+            </el-col>
+            <el-col :span="12">
+              <el-card>
+                <template #header>
+                  <span>最近出场</span>
+                </template>
+                <el-table :data="recentExits" height="300">
+                  <el-table-column label="车牌号" prop="license_plate" />
+                  <el-table-column label="车位" prop="space_code" />
+                  <el-table-column label="停车时长" prop="duration" />
+                  <el-table-column label="费用" prop="fee">
+                    <template #default="scope">
+                      ¥{{ scope.row.fee }}
+                    </template>
+                  </el-table-column>
+                </el-table>
+              </el-card>
+            </el-col>
+          </el-row>
+        </div>
+      </el-tab-pane>
+
+      <!-- 统计报表标签页 -->
+      <el-tab-pane label="统计报表" name="report">
+        <div class="report-container">
+          <el-form :model="reportQuery" :inline="true" label-width="80px">
+            <el-form-item label="统计类型">
+              <el-select v-model="reportQuery.reportType" placeholder="请选择">
+                <el-option label="日报表" value="daily" />
+                <el-option label="周报表" value="weekly" />
+                <el-option label="月报表" value="monthly" />
+              </el-select>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" @click="generateReport">生成报表</el-button>
+            </el-form-item>
+          </el-form>
+
+          <!-- 详细数据表格 -->
+          <el-card style="margin-top: 20px;">
+            <template #header>
+              <span>详细数据</span>
+            </template>
+            <el-table :data="reportData" stripe border>
+              <el-table-column label="日期" prop="date" />
+              <el-table-column label="总车位数" prop="totalSpaces" />
+              <el-table-column label="使用车位" prop="usedSpaces" />
+              <el-table-column label="占用率" prop="occupancyRate">
+                <template #default="scope">
+                  {{ scope.row.occupancyRate }}%
+                </template>
+              </el-table-column>
+              <el-table-column label="车辆进入" prop="entryCount" />
+              <el-table-column label="车辆离开" prop="exitCount" />
+              <el-table-column label="收入金额" prop="revenue">
+                <template #default="scope">
+                  ¥{{ scope.row.revenue }}
+                </template>
+              </el-table-column>
+            </el-table>
+          </el-card>
+        </div>
+      </el-tab-pane>
+    </el-tabs>
+
+    <!-- 车位新增/修改对话框 -->
+    <el-dialog :title="spaceTitle" v-model="spaceOpen" width="600px">
+      <el-form ref="spaceFormRef" :model="spaceForm" :rules="spaceRules" label-width="100px">
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="车位编号" prop="spaceCode">
+              <el-input v-model="spaceForm.spaceCode" placeholder="请输入车位编号" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="车位名称" prop="spaceName">
+              <el-input v-model="spaceForm.spaceName" placeholder="请输入车位名称" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="楼栋" prop="buildingId">
+              <el-select v-model="spaceForm.buildingId" placeholder="请选择楼栋" style="width: 100%">
+                <el-option v-for="building in buildingList" :key="building.id" :label="building.name" :value="building.id" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="楼层" prop="floorId">
+              <el-select v-model="spaceForm.floorId" placeholder="请选择楼层" style="width: 100%">
+                <el-option v-for="floor in floorList" :key="floor.id" :label="floor.name" :value="floor.id" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="车位类型" prop="spaceType">
+              <el-select v-model="spaceForm.spaceType" placeholder="请选择类型" style="width: 100%">
+                <el-option label="普通" value="normal" />
+                <el-option label="VIP" value="vip" />
+                <el-option label="残疾人" value="disabled" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitSpaceForm">确 定</el-button>
+          <el-button @click="cancelSpace">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 车位详情对话框 -->
+    <el-dialog title="车位详情" v-model="spaceDetailOpen" width="800px">
+      <el-descriptions :column="2" border>
+        <el-descriptions-item label="车位编号">{{ spaceDetail.space_code }}</el-descriptions-item>
+        <el-descriptions-item label="车位名称">{{ spaceDetail.space_name }}</el-descriptions-item>
+        <el-descriptions-item label="楼栋">{{ spaceDetail.building_name }}</el-descriptions-item>
+        <el-descriptions-item label="楼层">{{ spaceDetail.floor_name }}</el-descriptions-item>
+        <el-descriptions-item label="车位类型">
+          <el-tag :type="typeTag(spaceDetail.space_type)">
+            {{ typeText(spaceDetail.space_type) }}
+          </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="车位状态">
+          <el-tag :type="statusTag(spaceDetail.space_status)">
+            {{ statusText(spaceDetail.space_status) }}
+          </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="车牌号">{{ spaceDetail.license_plate || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="入场时间">{{ spaceDetail.entry_time || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="停车费用">¥{{ spaceDetail.parking_fee || '0.00' }}</el-descriptions-item>
+        <el-descriptions-item label="备注" :span="2">{{ spaceDetail.remark || '-' }}</el-descriptions-item>
+      </el-descriptions>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import {ref, reactive, onMounted, onUnmounted} from 'vue'
+import {
+  listParkingSpace,
+  getParkingSpace,
+  addParkingSpace,
+  updateParkingSpace,
+  delParkingSpace
+} from '@/api/parking/space'
+import {getParkingStatistics, getRealtimeData, getParkingReport} from '@/api/parking/monitor'
+import {ElMessage, ElMessageBox} from 'element-plus'
+
+const activeTab = ref('spaces')
+
+// 车位相关
+const spaceQuery = reactive({
+  pageNum: 1,
+  pageSize: 10,
+  spaceCode: null,
+  spaceName: null,
+  buildingId: null,
+  floorId: null,
+  spaceStatus: null,
+  spaceType: null
+})
+const spaceList = ref([])
+const spaceTotal = ref(0)
+const spaceLoading = ref(false)
+const spaceTitle = ref('')
+const spaceOpen = ref(false)
+const spaceForm = ref({})
+const spaceFormRef = ref(null)
+const spaceDetailOpen = ref(false)
+const spaceDetail = ref({})
+const spaceRules = {
+  spaceCode: [{required: true, message: '车位编号不能为空', trigger: 'blur'}],
+  spaceName: [{required: true, message: '车位名称不能为空', trigger: 'blur'}],
+  buildingId: [{required: true, message: '请选择楼栋', trigger: 'change'}],
+  floorId: [{required: true, message: '请选择楼层', trigger: 'change'}],
+  spaceType: [{required: true, message: '请选择车位类型', trigger: 'change'}]
+}
+
+// 监控相关
+const statistics = ref({
+  totalSpaces: 0,
+  freeSpaces: 0,
+  occupiedSpaces: 0,
+  occupancyRate: 0
+})
+const currentFloors = ref([])
+const floorSpaces = ref([])
+const recentEntries = ref([])
+const recentExits = ref([])
+const monitorTimer = ref(null)
+
+// 报表相关
+const reportQuery = reactive({
+  reportType: 'daily',
+  dateRange: []
+})
+const reportData = ref([])
+
+// 其他数据
+const buildingList = ref([])
+const floorList = ref([])
+
+// 状态相关函数
+const statusTag = (status) => {
+  const map = {0: 'success', 1: 'warning', 2: 'info', 3: 'danger'}
+  return map[status] || 'info'
+}
+
+const statusText = (status) => {
+  const map = {0: '空闲', 1: '占用', 2: '预约', 3: '故障'}
+  return map[status] || '未知'
+}
+
+const typeTag = (type) => {
+  const map = {normal: '', vip: 'warning', disabled: 'info'}
+  return map[type] || ''
+}
+
+const typeText = (type) => {
+  const map = {normal: '普通', vip: 'VIP', disabled: '残疾人'}
+  return map[type] || type
+}
+
+// 查询车位列表
+async function getSpaceList() {
+  spaceLoading.value = true
+  try {
+    const response = await listParkingSpace(spaceQuery)
+    spaceList.value = response.rows
+    spaceTotal.value = response.total
+  } catch (error) {
+    ElMessage.error('获取车位列表失败')
+  } finally {
+    spaceLoading.value = false
+  }
+}
+
+// 重置查询
+function resetSpaceQuery() {
+  spaceQuery.spaceCode = null
+  spaceQuery.spaceName = null
+  spaceQuery.buildingId = null
+  spaceQuery.floorId = null
+  spaceQuery.spaceStatus = null
+  spaceQuery.spaceType = null
+  getSpaceList()
+}
+
+// 新增车位
+function handleAddSpace() {
+  resetSpace()
+  spaceOpen.value = true
+  spaceTitle.value = '添加车位'
+}
+
+// 修改车位
+async function handleUpdateSpace(row) {
+  resetSpace()
+  try {
+    const response = await getParkingSpace(row.id)
+    spaceForm.value = response.data
+    spaceOpen.value = true
+    spaceTitle.value = '修改车位'
+  } catch (error) {
+    ElMessage.error('获取车位信息失败')
+  }
+}
+
+// 删除车位
+function handleDeleteSpace(row) {
+  ElMessageBox.confirm(`是否确认删除车位编号为"${row.space_code}"的数据项?`, '警告', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  }).then(async () => {
+    try {
+      await delParkingSpace(row.id)
+      ElMessage.success('删除成功')
+      getSpaceList()
+    } catch (error) {
+      ElMessage.error('删除失败')
+    }
+  })
+}
+
+// 查看车位详情
+async function handleViewSpace(row) {
+  try {
+    const response = await getParkingSpace(row.id)
+    spaceDetail.value = response.data
+    spaceDetailOpen.value = true
+  } catch (error) {
+    ElMessage.error('获取车位详情失败')
+  }
+}
+
+// 提交车位表单
+async function submitSpaceForm() {
+  const valid = await spaceFormRef.value.validate()
+  if (valid) {
+    try {
+      if (spaceForm.value.id) {
+        await updateParkingSpace(spaceForm.value)
+        ElMessage.success('修改成功')
+      } else {
+        await addParkingSpace(spaceForm.value)
+        ElMessage.success('新增成功')
+      }
+      spaceOpen.value = false
+      getSpaceList()
+    } catch (error) {
+      ElMessage.error('操作失败')
+    }
+  }
+}
+
+// 取消车位对话框
+function cancelSpace() {
+  spaceOpen.value = false
+  resetSpace()
+}
+
+// 重置车位表单
+function resetSpace() {
+  spaceForm.value = {
+    id: null,
+    spaceCode: null,
+    spaceName: null,
+    buildingId: null,
+    floorId: null,
+    spaceType: 'normal',
+    spaceStatus: 0,
+    positionX: null,
+    positionY: null,
+    positionZ: null,
+    deviceId: null,
+    isOnline: 1,
+    remark: null
+  }
+  if (spaceFormRef.value) {
+    spaceFormRef.value.resetFields()
+  }
+}
+
+// 获取楼层车位
+function getFloorSpaces(floorId) {
+  return floorSpaces.value.filter(space => space.floor_id === floorId)
+}
+
+// 点击车位
+function handleSpaceClick(space) {
+  if (space.space_status === 1) {
+    ElMessage.info(`车位 ${space.space_code} 已被 ${space.license_plate} 占用`)
+  }
+}
+
+// 加载统计数据
+async function loadStatistics() {
+  try {
+    const response = await getParkingStatistics()
+    statistics.value = response.data
+  } catch (error) {
+    console.error('获取统计数据失败', error)
+  }
+}
+
+// 加载实时数据
+async function loadRealtimeData() {
+  try {
+    const response = await getRealtimeData()
+    const data = response.data
+    recentEntries.value = data.recentEntries || []
+    recentExits.value = data.recentExits || []
+    floorSpaces.value = data.floorSpaces || []
+  } catch (error) {
+    console.error('获取实时数据失败', error)
+  }
+}
+
+// 启动实时监控
+function startMonitor() {
+  loadStatistics()
+  loadRealtimeData()
+  if (monitorTimer.value) {
+    clearInterval(monitorTimer.value)
+  }
+  monitorTimer.value = setInterval(() => {
+    loadStatistics()
+    loadRealtimeData()
+  }, 10000) // 每10秒刷新一次
+}
+
+// 生成报表
+async function generateReport() {
+  try {
+    const response = await getParkingReport(reportQuery)
+    reportData.value = response.data.details || []
+    ElMessage.success('报表生成成功')
+  } catch (error) {
+    ElMessage.error('生成报表失败')
+  }
+}
+
+// 初始化数据
+function initData() {
+  buildingList.value = [
+    {id: 'B1', name: 'A栋'},
+    {id: 'B2', name: 'B栋'},
+    {id: 'B3', name: 'C栋'}
+  ]
+
+  floorList.value = [
+    {id: 'B1', name: 'B1层'},
+    {id: 'B2', name: 'B2层'},
+    {id: 'F1', name: '1层'}
+  ]
+
+  currentFloors.value = [
+    {id: 'B1', name: 'B1层'},
+    {id: 'B2', name: 'B2层'}
+  ]
+}
+
+// 监听标签页切换
+let watchActiveTab = null
+onMounted(() => {
+  initData()
+  getSpaceList()
+
+  watchActiveTab = () => {
+    if (activeTab.value === 'monitor') {
+      startMonitor()
+    } else if (monitorTimer.value) {
+      clearInterval(monitorTimer.value)
+    }
+  }
+})
+
+onUnmounted(() => {
+  if (monitorTimer.value) {
+    clearInterval(monitorTimer.value)
+  }
+})
+</script>
+
+<style scoped>
+.app-container {
+  padding: 20px;
+}
+
+.mb8 {
+  margin-bottom: 8px;
+}
+
+.statistics-row {
+  margin-bottom: 20px;
+}
+
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.parking-map {
+  padding: 20px;
+}
+
+.floor-section {
+  margin-bottom: 30px;
+}
+
+.floor-section h4 {
+  margin-bottom: 15px;
+  font-size: 16px;
+  font-weight: bold;
+}
+
+.parking-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
+  gap: 10px;
+}
+
+.parking-space {
+  border: 2px solid #dcdfe6;
+  border-radius: 4px;
+  padding: 10px;
+  text-align: center;
+  cursor: pointer;
+  transition: all 0.3s;
+  background-color: #f5f7fa;
+}
+
+.parking-space:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+}
+
+.parking-space.status-0 {
+  background-color: #f0f9ff;
+  border-color: #67c23a;
+}
+
+.parking-space.status-1 {
+  background-color: #fdf6ec;
+  border-color: #e6a23c;
+}
+
+.parking-space.status-2 {
+  background-color: #f4f4f5;
+  border-color: #909399;
+}
+
+.parking-space.status-3 {
+  background-color: #fef0f0;
+  border-color: #f56c6c;
+}
+
+.space-number {
+  font-weight: bold;
+  font-size: 14px;
+  margin-bottom: 5px;
+}
+
+.space-info {
+  font-size: 12px;
+  color: #606266;
+}
+
+.report-container {
+  padding: 20px;
+}
+
+.dialog-footer {
+  text-align: right;
+}
+</style>

+ 773 - 0
pm_ui/src/views/patrol/index.vue

@@ -0,0 +1,773 @@
+<template>
+  <div class="app-container">
+    <el-tabs v-model="activeTab">
+      <!-- 巡更点管理标签页 -->
+      <el-tab-pane label="巡更点管理" name="points">
+        <el-form :model="pointQuery" ref="pointQueryRef" :inline="true" label-width="85px">
+          <el-form-item label="巡更点编号" label-width="85px" prop="pointCode">
+            <el-input v-model="pointQuery.pointCode" placeholder="请输入巡更点编号" clearable />
+          </el-form-item>
+          <el-form-item label="巡更点名称" prop="pointName">
+            <el-input v-model="pointQuery.pointName" placeholder="请输入巡更点名称" clearable />
+          </el-form-item>
+          <el-form-item label="楼栋" prop="buildingId">
+            <el-select v-model="pointQuery.buildingId" placeholder="请选择楼栋" clearable>
+              <el-option v-for="building in buildingList" :key="building.id" :label="building.name" :value="building.id" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="楼层" prop="floorId">
+            <el-select v-model="pointQuery.floorId" placeholder="请选择楼层" clearable>
+              <el-option v-for="floor in floorList" :key="floor.id" :label="floor.name" :value="floor.id" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="巡更点类型" prop="pointType">
+            <el-select v-model="pointQuery.pointType" placeholder="请选择类型" clearable>
+              <el-option label="普通" value="normal" />
+              <el-option label="重点" value="key" />
+              <el-option label="紧急" value="emergency" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="是否启用" prop="isActive">
+            <el-select v-model="pointQuery.isActive" placeholder="请选择" clearable>
+              <el-option label="启用" :value="1" />
+              <el-option label="禁用" :value="0" />
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" icon="Search" @click="getPointList">搜索</el-button>
+            <el-button icon="Refresh" @click="resetPointQuery">重置</el-button>
+          </el-form-item>
+        </el-form>
+
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <el-button type="primary" plain icon="Plus" @click="handleAddPoint" v-hasPermi="['patrol:point:add']">新增</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdatePoint" v-hasPermi="['patrol:point:edit']">修改</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDeletePoint" v-hasPermi="['patrol:point:remove']">删除</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="info" plain icon="View" @click="handleView3D">三维展示</el-button>
+          </el-col>
+        </el-row>
+
+        <el-table v-loading="pointLoading" :data="pointList" stripe border @selection-change="handleSelectionChange">
+          <el-table-column type="selection" width="55" align="center" />
+          <el-table-column label="巡更点编号" prop="pointCode"  />
+          <el-table-column label="巡更点名称" prop="pointName"  />
+          <el-table-column label="楼栋" prop="buildingName"  />
+          <el-table-column label="楼层" prop="floorName"  />
+          <el-table-column label="类型" prop="pointType" >
+            <template #default="scope">
+              <el-tag :type="pointTypeTag(scope.row.pointType)">
+                {{ pointTypeText(scope.row.pointType) }}
+              </el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column label="巡更频率" prop="patrolFrequency" >
+            <template #default="scope">
+              {{ frequencyText(scope.row.patrolFrequency) }}
+            </template>
+          </el-table-column>
+          <el-table-column label="关联摄像头" prop="cameraIds" width="150" show-overflow-tooltip />
+          <el-table-column label="状态" prop="isActive" width="80">
+            <template #default="scope">
+              <el-switch v-model="scope.row.isActive" :active-value="1" :inactive-value="0" @change="(value) => handleStatusChange(scope.row, value)" />
+            </template>
+          </el-table-column>
+          <el-table-column label="创建时间" prop="createTime" width="160" />
+          <el-table-column label="操作" fixed="right" width="200">
+            <template #default="scope">
+              <el-button link type="primary" icon="Edit" @click="handleUpdatePoint(scope.row)">修改</el-button>
+              <el-button link type="primary" icon="View" @click="handleViewPoint(scope.row)">详情</el-button>
+              <el-button link type="danger" icon="Delete" @click="handleDeletePoint(scope.row)">删除</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <pagination v-show="pointTotal > 0" :total="pointTotal" v-model:page="pointQuery.pageNum" v-model:limit="pointQuery.pageSize" @pagination="getPointList" />
+      </el-tab-pane>
+
+      <!-- 巡更记录标签页 -->
+      <el-tab-pane label="巡更记录" name="records">
+        <el-form :model="recordQuery" ref="recordQueryRef" :inline="true" label-width="80px">
+          <el-form-item label="巡更点" prop="pointCode">
+            <el-select v-model="recordQuery.pointCode" placeholder="请选择巡更点" clearable>
+              <el-option v-for="point in allPointList" :key="point.point_code" :label="point.point_name" :value="point.point_code" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="巡更人员" prop="patrolPersonName">
+            <el-input v-model="recordQuery.patrolPersonName" placeholder="请输入巡更人员" clearable />
+          </el-form-item>
+          <el-form-item label="巡更路线" prop="patrolRouteId">
+            <el-select v-model="recordQuery.patrolRouteId" placeholder="请选择路线" clearable>
+              <el-option v-for="route in routeList" :key="route.id" :label="route.name" :value="route.id" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="巡更状态" prop="patrolStatus">
+            <el-select v-model="recordQuery.patrolStatus" placeholder="请选择状态" clearable>
+              <el-option label="正常" :value="1" />
+              <el-option label="异常" :value="0" />
+              <el-option label="超时" :value="2" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="时间范围" prop="timeRange">
+            <el-date-picker v-model="recordQuery.timeRange" type="datetimerange" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" value-format="YYYY-MM-DD HH:mm:ss" />
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" icon="Search" @click="getRecordList">搜索</el-button>
+            <el-button icon="Refresh" @click="resetRecordQuery">重置</el-button>
+          </el-form-item>
+        </el-form>
+
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <el-button type="warning" plain icon="Download" @click="handleExportRecord" v-hasPermi="['patrol:record:export']">导出</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="info" plain icon="Monitor" @click="handleRealTimeMonitor">实时监控</el-button>
+          </el-col>
+        </el-row>
+
+        <el-table v-loading="recordLoading" :data="recordList" stripe border>
+          <el-table-column label="记录ID" prop="recordId" width="150" />
+          <el-table-column label="巡更点" prop="pointName" width="150" />
+          <el-table-column label="巡更人员" prop="patrolPersonName" width="120" />
+          <el-table-column label="巡更时间" prop="patrolTime" width="160" />
+          <el-table-column label="巡更路线" prop="patrolRouteName" width="150" />
+          <el-table-column label="状态" prop="patrolStatus" width="100">
+            <template #default="scope">
+              <el-tag :type="statusTag(scope.row.patrolStatus)">
+                {{ statusText(scope.row.patrolStatus) }}
+              </el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column label="GPS位置" width="150">
+            <template #default="scope">
+              <span v-if="scope.row.gps_longitude && scope.row.gps_latitude">
+                {{ scope.row.gps_longitude }}, {{ scope.row.gps_latitude }}
+              </span>
+              <span v-else>-</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="巡检仪" prop="deviceSn" width="150" />
+          <el-table-column label="当日次数" prop="patrolCount" width="100" />
+          <el-table-column label="异常信息" prop="exceptionInfo" width="200" show-overflow-tooltip />
+          <el-table-column label="操作" fixed="right" width="150">
+            <template #default="scope">
+              <el-button link type="primary" icon="View" @click="handleViewRecord(scope.row)">详情</el-button>
+              <el-button link type="primary" icon="Camera" @click="handleViewCamera(scope.row)" v-if="scope.row.pointCode">监控</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <pagination v-show="recordTotal > 0" :total="recordTotal" v-model:page="recordQuery.pageNum" v-model:limit="recordQuery.pageSize" @pagination="getRecordList" />
+      </el-tab-pane>
+
+      <!-- 实时监控标签页 -->
+      <el-tab-pane label="实时监控" name="monitor">
+        <div class="monitor-container">
+          <el-row :gutter="20">
+            <el-col :span="16">
+              <el-card class="monitor-map">
+                <template #header>
+                  <div class="card-header">
+                    <span>巡更路线实时监控</span>
+                    <el-button type="text" @click="refreshMonitor">刷新</el-button>
+                  </div>
+                </template>
+                <div id="patrolMap" style="height: 500px;"></div>
+              </el-card>
+            </el-col>
+            <el-col :span="8">
+              <el-card class="monitor-info">
+                <template #header>
+                  <span>实时巡更信息</span>
+                </template>
+                <div class="monitor-list">
+                  <el-scrollbar height="450px">
+                    <div v-for="item in realtimeList" :key="item.id" class="monitor-item">
+                      <div class="monitor-item-header">
+                        <span class="monitor-person">{{ item.patrolPersonName }}</span>
+                        <el-tag :type="statusTag(item.patrolStatus)" size="small">
+                          {{ statusText(item.patrolStatus) }}
+                        </el-tag>
+                      </div>
+                      <div class="monitor-item-info">
+                        <p>巡更点:{{ item.pointName }}</p>
+                        <p>时间:{{ item.patrolTime }}</p>
+                        <p>设备:{{ item.deviceSn }}</p>
+                      </div>
+                    </div>
+                  </el-scrollbar>
+                </div>
+              </el-card>
+            </el-col>
+          </el-row>
+        </div>
+      </el-tab-pane>
+    </el-tabs>
+
+    <!-- 巡更点新增/修改对话框 -->
+    <el-dialog :title="pointTitle" v-model="pointOpen" width="600px" append-to-body>
+      <el-form ref="pointRef" :model="pointForm" :rules="pointRules" label-width="100px">
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="巡更点编号" prop="pointCode">
+              <el-input v-model="pointForm.pointCode" placeholder="请输入巡更点编号" :disabled="pointForm.id != null" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="巡更点名称" prop="pointName">
+              <el-input v-model="pointForm.pointName" placeholder="请输入巡更点名称" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="楼栋" prop="buildingId">
+              <el-select v-model="pointForm.buildingId" placeholder="请选择楼栋" @change="handleBuildingChange" style="width: 100%">
+                <el-option v-for="building in buildingList" :key="building.id" :label="building.name" :value="building.id" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="楼层" prop="floorId">
+              <el-select v-model="pointForm.floorId" placeholder="请选择楼层" style="width: 100%">
+                <el-option v-for="floor in floorList" :key="floor.id" :label="floor.name" :value="floor.id" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="巡更点类型" prop="pointType">
+              <el-select v-model="pointForm.pointType" placeholder="请选择类型" style="width: 100%">
+                <el-option label="普通" value="normal" />
+                <el-option label="重点" value="key" />
+                <el-option label="紧急" value="emergency" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="巡更频率" prop="patrolFrequency">
+              <el-select v-model="pointForm.patrolFrequency" placeholder="请选择频率" style="width: 100%">
+                <el-option label="每小时" value="hourly" />
+                <el-option label="每日" value="daily" />
+                <el-option label="每周" value="weekly" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="8">
+            <el-form-item label="X坐标" prop="positionX">
+              <el-input-number v-model="pointForm.positionX" :precision="6" :step="0.1" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="Y坐标" prop="positionY">
+              <el-input-number v-model="pointForm.positionY" :precision="6" :step="0.1" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="Z坐标" prop="positionZ">
+              <el-input-number v-model="pointForm.positionZ" :precision="6" :step="0.1" style="width: 100%" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="关联摄像头" prop="cameraIds">
+              <el-select v-model="pointForm.cameraIds" multiple placeholder="请选择关联摄像头" style="width: 100%">
+                <el-option v-for="camera in cameraList" :key="camera.id" :label="camera.name" :value="camera.id" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="备注" prop="remark">
+              <el-input v-model="pointForm.remark" type="textarea" placeholder="请输入备注" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitPointForm">确 定</el-button>
+          <el-button @click="cancelPoint">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 巡更记录详情对话框 -->
+    <el-dialog title="巡更记录详情" v-model="recordDetailOpen" width="800px" append-to-body>
+      <el-descriptions :column="2" border>
+        <el-descriptions-item label="记录ID">{{ recordDetail.recordId }}</el-descriptions-item>
+        <el-descriptions-item label="巡更点">{{ recordDetail.pointName }}</el-descriptions-item>
+        <el-descriptions-item label="巡更人员">{{ recordDetail.patrolPersonName }}</el-descriptions-item>
+        <el-descriptions-item label="巡更时间">{{ recordDetail.patrolTime }}</el-descriptions-item>
+        <el-descriptions-item label="巡更路线">{{ recordDetail.patrolRouteName }}</el-descriptions-item>
+        <el-descriptions-item label="巡更状态">
+          <el-tag :type="statusTag(recordDetail.patrolStatus)">
+            {{ statusText(recordDetail.patrolStatus) }}
+          </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="GPS位置">
+          {{ recordDetail.gps_longitude }}, {{ recordDetail.gpsLatitude }}
+        </el-descriptions-item>
+        <el-descriptions-item label="巡检仪">{{ recordDetail.deviceSn }}</el-descriptions-item>
+        <el-descriptions-item label="当日次数">{{ recordDetail.patrolCount }}</el-descriptions-item>
+        <el-descriptions-item label="异常信息" :span="2">{{ recordDetail.exceptionInfo || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="备注" :span="2">{{ recordDetail.remark || '-' }}</el-descriptions-item>
+      </el-descriptions>
+      <div v-if="recordDetail.images && recordDetail.images.length > 0" class="image-preview">
+        <h4>现场照片</h4>
+        <el-image v-for="(img, index) in recordDetail.images" :key="index" :src="img" :preview-src-list="recordDetail.images" fit="cover" style="width: 150px; height: 150px; margin: 5px" />
+      </div>
+    </el-dialog>
+
+    <!-- 摄像头监控对话框 -->
+    <el-dialog title="摄像头监控" v-model="cameraOpen" width="900px" append-to-body>
+      <div class="camera-container">
+        <video ref="videoPlayer" controls autoplay style="width: 100%; max-height: 500px;"></video>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, onUnmounted } from 'vue'
+import { listPatrolPoint, getPatrolPoint, addPatrolPoint, updatePatrolPoint, delPatrolPoint, changePointStatus } from '@/api/patrol/point'
+import { listPatrolRecord, getPatrolRecord, exportPatrolRecord } from '@/api/patrol/record'
+import { getRealtimePatrol } from '@/api/patrol/monitor'
+
+const { proxy } = getCurrentInstance()
+const activeTab = ref('points')
+
+// 巡更点相关
+const pointQuery = reactive({
+  pageNum: 1,
+  pageSize: 10,
+  pointCode: null,
+  pointName: null,
+  buildingId: null,
+  floorId: null,
+  pointType: null,
+  isActive: null
+})
+const pointList = ref([])
+const isIniting = ref(true)
+const pointTotal = ref(0)
+const pointLoading = ref(false)
+const pointTitle = ref('')
+const pointOpen = ref(false)
+const pointForm = ref({})
+const pointRules = {
+  pointCode: [{ required: true, message: '巡更点编号不能为空', trigger: 'blur' }],
+  pointName: [{ required: true, message: '巡更点名称不能为空', trigger: 'blur' }],
+  buildingId: [{ required: true, message: '请选择楼栋', trigger: 'change' }],
+  floorId: [{ required: true, message: '请选择楼层', trigger: 'change' }],
+  pointType: [{ required: true, message: '请选择巡更点类型', trigger: 'change' }]
+}
+
+// 巡更记录相关
+const recordQuery = reactive({
+  pageNum: 1,
+  pageSize: 10,
+  pointCode: null,
+  patrolPersonName: null,
+  patrolRouteId: null,
+  patrolStatus: null,
+  timeRange: []
+})
+const recordList = ref([])
+const recordTotal = ref(0)
+const recordLoading = ref(false)
+const recordDetailOpen = ref(false)
+const recordDetail = ref({})
+
+// 实时监控相关
+const realtimeList = ref([])
+const monitorTimer = ref(null)
+
+// 其他数据
+const buildingList = ref([])
+const floorList = ref([])
+const allPointList = ref([])
+const routeList = ref([])
+const cameraList = ref([])
+const cameraOpen = ref(false)
+const ids = ref([])
+const single = ref(true)
+const multiple = ref(true)
+
+// 状态相关函数
+const pointTypeTag = (type) => {
+  const map = { normal: '', key: 'warning', emergency: 'danger' }
+  return map[type] || ''
+}
+
+const pointTypeText = (type) => {
+  const map = { normal: '普通', key: '重点', emergency: '紧急' }
+  return map[type] || type
+}
+
+const frequencyText = (freq) => {
+  const map = { hourly: '每小时', daily: '每日', weekly: '每周' }
+  return map[freq] || freq
+}
+
+const statusTag = (status) => {
+  const map = { 0: 'danger', 1: 'success', 2: 'warning' }
+  return map[status] || 'info'
+}
+
+const statusText = (status) => {
+  const map = { 0: '异常', 1: '正常', 2: '超时' }
+  return map[status] || '未知'
+}
+
+// 查询巡更点列表
+function getPointList() {
+  pointLoading.value = true
+  listPatrolPoint(pointQuery).then(response => {
+    pointList.value = response.rows
+    pointTotal.value = response.total
+    pointLoading.value = false
+   // 数据加载完成,结束初始化阶段
+    // 延迟关闭初始化标志,确保 DOM 完全渲染完成
+     // 延迟关闭初始化标志,确保 DOM 完全渲染完成
+    setTimeout(() => {
+      isIniting.value = false
+    }, 100)
+  })
+}
+
+// 查询巡更记录列表
+function getRecordList() {
+  recordLoading.value = true
+
+  const params = { ...recordQuery }
+  if (recordQuery.timeRange && recordQuery.timeRange.length === 2) {
+    params.beginTime = recordQuery.timeRange[0]
+    params.endTime = recordQuery.timeRange[1]
+  }
+  listPatrolRecord(params).then(response => {
+    recordList.value = response.rows
+    recordTotal.value = response.total
+    recordLoading.value = false
+  })
+  
+}
+
+// 重置查询
+function resetPointQuery() {
+  proxy.resetForm('pointQueryRef')
+  getPointList()
+}
+
+function resetRecordQuery() {
+  proxy.resetForm('recordQueryRef')
+  getRecordList()
+}
+
+// 多选框选中数据
+function handleSelectionChange(selection) {
+  ids.value = selection.map(item => item.id)
+  single.value = selection.length !== 1
+  multiple.value = !selection.length
+}
+
+// 状态修改
+function handleStatusChange(row, value) {
+  if (isIniting.value) return
+  const text = row.isActive === 1 ? '启用' : '停用'
+  proxy.$modal.confirm(`确认要${text}"${row.pointName}"巡更点吗?`).then(() => {
+    return changePointStatus(row.id, row.isActive)
+  }).then(() => {
+    proxy.$modal.msgSuccess(`${text}成功`)
+  }).catch(() => {
+    row.isActive = row.isActive === 1 ? 0 : 1
+  })
+}
+
+// 新增巡更点
+function handleAddPoint() {
+  resetPoint()
+  pointOpen.value = true
+  pointTitle.value = '添加巡更点'
+}
+
+// 修改巡更点
+function handleUpdatePoint(row) {
+  resetPoint()
+  const pointId = row.id || ids.value[0]
+  getPatrolPoint(pointId).then(response => {
+    pointForm.value = response.data
+    if (pointForm.value.cameraIds && typeof pointForm.value.cameraIds === 'string') {
+      pointForm.value.cameraIds = pointForm.value.cameraIds.split(',')
+    }
+    pointOpen.value = true
+    pointTitle.value = '修改巡更点'
+  })
+}
+
+// 删除巡更点
+function handleDeletePoint(row) {
+  const pointIds = row.id || ids.value
+  proxy.$modal.confirm(`是否确认删除巡更点编号为"${row.point_code || pointIds}"的数据项?`).then(() => {
+    return delPatrolPoint(pointIds)
+  }).then(() => {
+    getPointList()
+    proxy.$modal.msgSuccess('删除成功')
+  })
+}
+
+// 查看巡更点详情
+function handleViewPoint(row) {
+  handleUpdatePoint(row)
+}
+
+// 提交巡更点表单
+function submitPointForm() {
+  proxy.$refs['pointRef'].validate(valid => {
+    if (valid) {
+      const submitData = { ...pointForm.value }
+      if (submitData.cameraIds && Array.isArray(submitData.cameraIds)) {
+        submitData.cameraIds = submitData.cameraIds.join(',')
+      }
+      if (submitData.id != null) {
+        updatePatrolPoint(submitData).then(() => {
+          proxy.$modal.msgSuccess('修改成功')
+          pointOpen.value = false
+          getPointList()
+        })
+      } else {
+        addPatrolPoint(submitData).then(() => {
+          proxy.$modal.msgSuccess('新增成功')
+          pointOpen.value = false
+          getPointList()
+        })
+      }
+    }
+  })
+}
+
+// 取消巡更点对话框
+function cancelPoint() {
+  pointOpen.value = false
+  resetPoint()
+}
+
+// 重置巡更点表单
+function resetPoint() {
+  pointForm.value = {
+    id: null,
+    pointCode: null,
+    pointName: null,
+    buildingId: null,
+    floorId: null,
+    pointType: 'normal',
+    patrolFrequency: 'daily',
+    positionX: null,
+    positionY: null,
+    positionZ: null,
+    cameraIds: [],
+    isActive: 1,
+    remark: null
+  }
+  proxy.resetForm('pointRef')
+}
+
+// 查看巡更记录详情
+function handleViewRecord(row) {
+  getPatrolRecord(row.id).then(response => {
+    recordDetail.value = response.data
+    if (recordDetail.value.images && typeof recordDetail.value.images === 'string') {
+      recordDetail.value.images = JSON.parse(recordDetail.value.images)
+    }
+    recordDetailOpen.value = true
+  })
+}
+
+// 导出巡更记录
+function handleExportRecord() {
+  proxy.$modal.confirm('确认导出巡更记录吗?').then(() => {
+    const params = { ...recordQuery }
+    if (recordQuery.timeRange && recordQuery.timeRange.length === 2) {
+      params.beginTime = recordQuery.timeRange[0]
+      params.endTime = recordQuery.timeRange[1]
+    }
+    exportPatrolRecord(params).then(response => {
+      proxy.download(response.msg)
+    })
+  })
+}
+
+// 实时监控相关
+function handleRealTimeMonitor() {
+  activeTab.value = 'monitor'
+  startRealtimeMonitor()
+}
+
+function startRealtimeMonitor() {
+  getRealtimeData()
+  if (monitorTimer.value) {
+    clearInterval(monitorTimer.value)
+  }
+  monitorTimer.value = setInterval(() => {
+    getRealtimeData()
+  }, 5000) // 每5秒刷新一次
+}
+
+function getRealtimeData() {
+  getRealtimePatrol().then(response => {
+    realtimeList.value = response.data || []
+    updateMapMarkers()
+  })
+}
+
+function updateMapMarkers() {
+  // 更新地图标记点,这里需要集成具体的地图组件
+  console.log('更新地图标记点')
+}
+
+function refreshMonitor() {
+  getRealtimeData()
+  proxy.$modal.msgSuccess('刷新成功')
+}
+
+// 查看摄像头
+function handleViewCamera(row) {
+  // 这里需要调用摄像头接口获取视频流
+  cameraOpen.value = true
+  // 模拟加载视频流
+  proxy.$nextTick(() => {
+    if (proxy.$refs.videoPlayer) {
+      // proxy.$refs.videoPlayer.src = getCameraStreamUrl(row.camera_ids)
+    }
+  })
+}
+
+// 3D展示
+function handleView3D() {
+  // 跳转到3D展示页面或打开3D展示对话框
+  proxy.$router.push('/patrol/3d-view')
+}
+
+// 楼栋变化
+function handleBuildingChange(val) {
+  // 根据楼栋加载楼层数据
+  loadFloorsByBuilding(val)
+}
+
+// 加载楼层数据
+function loadFloorsByBuilding(buildingId) {
+  // 调用接口加载楼层数据
+  floorList.value = [
+    { id: 'F1', name: '1楼' },
+    { id: 'F2', name: '2楼' },
+    { id: 'F3', name: '3楼' }
+  ]
+}
+
+// 初始化数据
+function initData() {
+  // 模拟数据,实际项目中从接口获取
+  buildingList.value = [
+    { id: 'B1', name: 'A栋' },
+    { id: 'B2', name: 'B栋' },
+    { id: 'B3', name: 'C栋' }
+  ]
+
+  routeList.value = [
+    { id: 'R1', name: '日常巡更路线' },
+    { id: 'R2', name: '夜间巡更路线' },
+    { id: 'R3', name: '应急巡更路线' }
+  ]
+
+  cameraList.value = [
+    { id: 'C1', name: '1号摄像头' },
+    { id: 'C2', name: '2号摄像头' },
+    { id: 'C3', name: '3号摄像头' }
+  ]
+
+  // 加载所有巡更点
+  listPatrolPoint({ pageSize: 999 }).then(response => {
+    allPointList.value = response.rows
+  })
+}
+
+onMounted(() => {
+  initData()
+  getPointList()
+})
+
+onUnmounted(() => {
+  if (monitorTimer.value) {
+    clearInterval(monitorTimer.value)
+  }
+})
+</script>
+
+<style scoped>
+.monitor-container {
+  padding: 20px;
+}
+
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.monitor-list {
+  padding: 10px;
+}
+
+.monitor-item {
+  border-bottom: 1px solid #e6e6e6;
+  padding: 10px 0;
+}
+
+.monitor-item:last-child {
+  border-bottom: none;
+}
+
+.monitor-item-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 5px;
+}
+
+.monitor-person {
+  font-weight: bold;
+  font-size: 14px;
+}
+
+.monitor-item-info p {
+  margin: 3px 0;
+  font-size: 12px;
+  color: #666;
+}
+
+.image-preview {
+  margin-top: 20px;
+}
+
+.image-preview h4 {
+  margin-bottom: 10px;
+}
+
+.camera-container {
+  text-align: center;
+  background: #000;
+  padding: 20px;
+}
+</style>

+ 848 - 0
pm_ui/src/views/rqbjxt/index.vue

@@ -0,0 +1,848 @@
+<template>
+  <div class="app-container">
+    <el-tabs v-model="activeTab">
+      <!-- 报警点管理标签页 -->
+      <el-tab-pane label="报警点管理" name="alarmPoints">
+        <el-form :model="alarmPointQuery" ref="alarmPointQueryRef" :inline="true" label-width="80px">
+          <el-form-item label="设备编号" prop="deviceCode">
+            <el-input v-model="alarmPointQuery.deviceCode" placeholder="请输入设备编号" clearable />
+          </el-form-item>
+          <el-form-item label="设备名称" prop="deviceName">
+            <el-input v-model="alarmPointQuery.deviceName" placeholder="请输入设备名称" clearable />
+          </el-form-item>
+          <el-form-item label="报警类型" prop="alarmType">
+            <el-select v-model="alarmPointQuery.alarmType" placeholder="请选择报警类型" clearable>
+              <el-option label="入侵检测" value="intrusion" />
+              <el-option label="门磁报警" value="door_magnetic" />
+              <el-option label="红外探测" value="infrared" />
+              <el-option label="玻璃破碎" value="glass_break" />
+              <el-option label="震动检测" value="vibration" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="防区" prop="zone">
+            <el-select v-model="alarmPointQuery.zone" placeholder="请选择防区" clearable>
+              <el-option label="防区1" value="zone1" />
+              <el-option label="防区2" value="zone2" />
+              <el-option label="防区3" value="zone3" />
+              <el-option label="防区4" value="zone4" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="设备状态" prop="deviceStatus">
+            <el-select v-model="alarmPointQuery.deviceStatus" placeholder="请选择状态" clearable>
+              <el-option label="正常" :value="1" />
+              <el-option label="报警" :value="2" />
+              <el-option label="故障" :value="3" />
+              <el-option label="离线" :value="0" />
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" icon="Search" @click="getAlarmPointList">搜索</el-button>
+            <el-button icon="Refresh" @click="resetAlarmPointQuery">重置</el-button>
+          </el-form-item>
+        </el-form>
+
+        <el-table v-loading="alarmPointLoading" :data="alarmPointList" stripe border>
+          <el-table-column label="设备编号" prop="deviceCode"  />
+          <el-table-column label="设备名称" prop="deviceName"  />
+          <el-table-column label="报警类型" prop="alarmType">
+            <template #default="scope">
+              <el-tag :type="getAlarmTypeTag(scope.row.alarmType)">
+                {{ getAlarmTypeText(scope.row.alarmType) }}
+              </el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column label="防区信息" >
+            <template #default="scope">
+              <div>{{ scope.row.zoneName }}</div>
+              <div class="text-gray-500">{{ scope.row.zoneCode }}</div>
+            </template>
+          </el-table-column>
+          <el-table-column label="位置信息" >
+            <template #default="scope">
+              <div>{{ scope.row.building }}栋-{{ scope.row.floor }}层</div>
+              <div class="text-gray-500">{{ scope.row.locationDetail }}</div>
+            </template>
+          </el-table-column>
+          <el-table-column label="设备状态" prop="device_status" >
+            <template #default="scope">
+              <el-tag :type="getDeviceStatusTag(scope.row.deviceStatus)">
+                {{ getDeviceStatusText(scope.row.deviceStatus) }}
+              </el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column label="布防状态" prop="armStatus" >
+            <template #default="scope">
+              <el-tag :type="scope.row.armStatus === 1 ? 'success' : 'info'">
+                {{ scope.row.armStatus === 1 ? '布防' : '撤防' }}
+              </el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column label="敏感度" prop="sensitivity" width="80">
+            <template #default="scope">
+              <span>{{ scope.row.sensitivity }}%</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="最后报警时间" prop="lastAlarmTime">
+            <template #default="scope">
+              <span>{{ parseTime(scope.row.lastAlarmTime) }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="操作" width="340" fixed="right">
+            <template #default="scope">
+              <el-button link type="primary" icon="View" @click="handleAlarmPointDetail(scope.row)">详情</el-button>
+              <el-button
+                  link
+                  :type="scope.row.armStatus === 1 ? 'warning' : 'success'"
+                  icon="Switch"
+                  @click="handleToggleArm(scope.row)"
+              >
+                {{ scope.row.armStatus === 1 ? '撤防' : '布防' }}
+              </el-button>
+              <el-button link type="info" icon="Setting" @click="handleDeviceConfig(scope.row)">配置</el-button>
+              <el-button link type="danger" icon="Bell" @click="handleTestAlarm(scope.row)">测试</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <pagination
+            v-show="alarmPointTotal > 0"
+            :total="alarmPointTotal"
+            v-model:page="alarmPointQuery.pageNum"
+            v-model:limit="alarmPointQuery.pageSize"
+            @pagination="getAlarmPointList"
+        />
+      </el-tab-pane>
+
+      <!-- 实时报警标签页 -->
+      <el-tab-pane label="实时报警" name="realTimeAlarms">
+        <el-form :model="alarmQuery" ref="alarmQueryRef" :inline="true" label-width="80px">
+          <el-form-item label="报警级别" prop="alarmLevel">
+            <el-select v-model="alarmQuery.alarmLevel" placeholder="请选择报警级别" clearable>
+              <el-option label="低级" :value="1" />
+              <el-option label="中级" :value="2" />
+              <el-option label="高级" :value="3" />
+              <el-option label="紧急" :value="4" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="处理状态" prop="handleStatus">
+            <el-select v-model="alarmQuery.handleStatus" placeholder="请选择处理状态" clearable>
+              <el-option label="未处理" :value="0" />
+              <el-option label="处理中" :value="1" />
+              <el-option label="已处理" :value="2" />
+              <el-option label="已忽略" :value="3" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="防区" prop="zone">
+            <el-select v-model="alarmQuery.zone" placeholder="请选择防区" clearable>
+              <el-option label="防区1" value="zone1" />
+              <el-option label="防区2" value="zone2" />
+              <el-option label="防区3" value="zone3" />
+              <el-option label="防区4" value="zone4" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="时间范围" prop="timeRange">
+            <el-date-picker
+                v-model="alarmQuery.timeRange"
+                type="daterange"
+                range-separator="至"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+                value-format="YYYY-MM-DD"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" icon="Search" @click="getAlarmList">搜索</el-button>
+            <el-button icon="Refresh" @click="resetAlarmQuery">重置</el-button>
+          </el-form-item>
+        </el-form>
+
+        <el-row :gutter="20" class="mb8">
+          <el-col :span="6">
+            <el-statistic title="今日报警数" :value="statisticsData.todayAlarms" />
+          </el-col>
+          <el-col :span="6">
+            <el-statistic title="未处理报警" :value="statisticsData.unhandledAlarms" />
+          </el-col>
+          <el-col :span="6">
+            <el-statistic title="在线设备" :value="statisticsData.onlineDevices" />
+          </el-col>
+          <el-col :span="6">
+            <el-statistic title="布防防区" :value="statisticsData.armedZones" />
+          </el-col>
+        </el-row>
+
+        <el-table v-loading="alarmLoading" :data="alarmList" stripe border>
+          <el-table-column label="报警时间" prop="alarm_time" >
+            <template #default="scope">
+              <span>{{ parseTime(scope.row.alarm_time) }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="设备信息" >
+            <template #default="scope">
+              <div>{{ scope.row.device_name }}</div>
+              <div class="text-gray-500">{{ scope.row.device_code }}</div>
+            </template>
+          </el-table-column>
+          <el-table-column label="报警类型" prop="alarm_type" >
+            <template #default="scope">
+              <el-tag :type="getAlarmTypeTag(scope.row.alarm_type)">
+                {{ getAlarmTypeText(scope.row.alarm_type) }}
+              </el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column label="报警级别" prop="alarm_level">
+            <template #default="scope">
+              <el-tag :type="getAlarmLevelTag(scope.row.alarm_level)">
+                {{ getAlarmLevelText(scope.row.alarm_level) }}
+              </el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column label="位置信息">
+            <template #default="scope">
+              <div>{{ scope.row.building }}栋-{{ scope.row.floor }}层</div>
+              <div class="text-gray-500">{{ scope.row.zone_name }}</div>
+            </template>
+          </el-table-column>
+          <el-table-column label="报警内容" prop="alarm_message" width="200" show-overflow-tooltip />
+          <el-table-column label="处理状态" prop="handle_status" width="100">
+            <template #default="scope">
+              <el-tag :type="getHandleStatusTag(scope.row.handle_status)">
+                {{ getHandleStatusText(scope.row.handle_status) }}
+              </el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column label="处理人" prop="handle_user"  />
+          <el-table-column label="联动状态" prop="linkage_status">
+            <template #default="scope">
+              <el-tag :type="scope.row.linkage_status === 1 ? 'success' : 'info'">
+                {{ scope.row.linkage_status === 1 ? '已联动' : '未联动' }}
+              </el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column label="操作" width="200" fixed="right">
+            <template #default="scope">
+              <el-button link type="primary" icon="View" @click="handleAlarmDetail(scope.row)">详情</el-button>
+              <el-button
+                  link
+                  type="warning"
+                  icon="Edit"
+                  @click="handleAlarmProcess(scope.row)"
+                  v-if="scope.row.handle_status === 0 || scope.row.handle_status === 1"
+              >处理</el-button>
+              <el-button link type="success" icon="VideoCamera" @click="handleViewVideo(scope.row)">查看视频</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <pagination
+            v-show="alarmTotal > 0"
+            :total="alarmTotal"
+            v-model:page="alarmQuery.pageNum"
+            v-model:limit="alarmQuery.pageSize"
+            @pagination="getAlarmList"
+        />
+      </el-tab-pane>
+
+      <!-- 防区管理标签页 -->
+      <el-tab-pane label="防区管理" name="zones">
+        <el-form :model="zoneQuery" ref="zoneQueryRef" :inline="true" label-width="80px">
+          <el-form-item label="防区名称" prop="zoneName">
+            <el-input v-model="zoneQuery.zoneName" placeholder="请输入防区名称" clearable />
+          </el-form-item>
+          <el-form-item label="防区状态" prop="zoneStatus">
+            <el-select v-model="zoneQuery.zoneStatus" placeholder="请选择状态" clearable>
+              <el-option label="布防" :value="1" />
+              <el-option label="撤防" :value="0" />
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" icon="Search" @click="getZoneList">搜索</el-button>
+            <el-button icon="Refresh" @click="resetZoneQuery">重置</el-button>
+          </el-form-item>
+        </el-form>
+
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <el-button type="primary" plain icon="Plus" @click="handleAddZone">新增防区</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="success" plain icon="Upload" @click="handleBatchArm">批量布防</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="warning" plain icon="Download" @click="handleBatchDisarm">批量撤防</el-button>
+          </el-col>
+        </el-row>
+
+        <el-table v-loading="zoneLoading" :data="zoneList" stripe border @selection-change="handleZoneSelectionChange">
+          <el-table-column type="selection" width="65" />
+          <el-table-column label="防区编号" prop="zone_code"  />
+          <el-table-column label="防区名称" prop="zone_name"  />
+          <el-table-column label="防区描述" prop="zone_description"  show-overflow-tooltip />
+          <el-table-column label="防区状态" prop="zone_status">
+            <template #default="scope">
+              <el-tag :type="scope.row.zone_status === 1 ? 'success' : 'info'">
+                {{ scope.row.zone_status === 1 ? '布防' : '撤防' }}
+              </el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column label="设备数量" prop="deviceCount" >
+            <template #default="scope">
+              <span>{{ scope.row.device_count }}台</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="在线设备" prop="online_count">
+            <template #default="scope">
+              <span>{{ scope.row.online_count }}/{{ scope.row.device_count }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="联动摄像机" prop="linkage_cameras"  show-overflow-tooltip />
+          <el-table-column label="创建时间" prop="create_time">
+            <template #default="scope">
+              <span>{{ parseTime(scope.row.create_time) }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="操作" width="340" fixed="right">
+            <template #default="scope">
+              <el-button link type="primary" icon="View" @click="handleZoneDetail(scope.row)">详情</el-button>
+              <el-button link type="success" icon="Edit" @click="handleEditZone(scope.row)">修改</el-button>
+              <el-button
+                  link
+                  :type="scope.row.zone_status === 1 ? 'warning' : 'success'"
+                  icon="Switch"
+                  @click="handleToggleZone(scope.row)"
+              >
+                {{ scope.row.zone_status === 1 ? '撤防' : '布防' }}
+              </el-button>
+              <el-button link type="danger" icon="Delete" @click="handleDeleteZone(scope.row)">删除</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <pagination
+            v-show="zoneTotal > 0"
+            :total="zoneTotal"
+            v-model:page="zoneQuery.pageNum"
+            v-model:limit="zoneQuery.pageSize"
+            @pagination="getZoneList"
+        />
+      </el-tab-pane>
+    </el-tabs>
+
+    <!-- 报警点详情抽屉 -->
+    <el-drawer v-model="alarmPointDetailVisible" title="报警点详情" direction="rtl" size="50%">
+      <el-descriptions :column="2" border>
+        <el-descriptions-item label="设备编号">{{ currentAlarmPoint.deviceCode }}</el-descriptions-item>
+        <el-descriptions-item label="设备名称">{{ currentAlarmPoint.deviceName }}</el-descriptions-item>
+        <el-descriptions-item label="报警类型">{{ getAlarmTypeText(currentAlarmPoint.alarmType) }}</el-descriptions-item>
+        <el-descriptions-item label="防区">{{ currentAlarmPoint.zoneName }}</el-descriptions-item>
+        <el-descriptions-item label="位置">{{ currentAlarmPoint.building }}栋-{{ currentAlarmPoint.floor }}层-{{ currentAlarmPoint.locationDetail }}</el-descriptions-item>
+        <el-descriptions-item label="品牌型号">{{ currentAlarmPoint.brand }} {{ currentAlarmPoint.model }}</el-descriptions-item>
+        <el-descriptions-item label="设备状态">
+          <el-tag :type="getDeviceStatusTag(currentAlarmPoint.deviceStatus)">
+            {{ getDeviceStatusText(currentAlarmPoint.deviceStatus) }}
+          </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="布防状态">
+          <el-tag :type="currentAlarmPoint.armStatus === 1 ? 'success' : 'info'">
+            {{ currentAlarmPoint.armStatus === 1 ? '布防' : '撤防' }}
+          </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="敏感度">{{ currentAlarmPoint.sensitivity }}%</el-descriptions-item>
+        <el-descriptions-item label="延时时间">{{ currentAlarmPoint.delayTime }}秒</el-descriptions-item>
+        <el-descriptions-item label="联动摄像机">{{ currentAlarmPoint.linkageCameras }}</el-descriptions-item>
+        <el-descriptions-item label="最后报警时间">{{ parseTime(currentAlarmPoint.lastAlarmTime) }}</el-descriptions-item>
+        <el-descriptions-item label="安装时间">{{ parseTime(currentAlarmPoint.installTime) }}</el-descriptions-item>
+        <el-descriptions-item label="最后维护时间">{{ parseTime(currentAlarmPoint.lastMaintainTime) }}</el-descriptions-item>
+      </el-descriptions>
+    </el-drawer>
+
+    <!-- 报警详情抽屉 -->
+    <el-drawer v-model="alarmDetailVisible" title="报警详情" direction="rtl" size="50%">
+      <el-descriptions :column="2" border>
+        <el-descriptions-item label="报警时间">{{ parseTime(currentAlarm.last_alarm_time) }}</el-descriptions-item>
+        <el-descriptions-item label="设备名称">{{ currentAlarm.device_name }}</el-descriptions-item>
+        <el-descriptions-item label="设备编号">{{ currentAlarm.device_code }}</el-descriptions-item>
+        <el-descriptions-item label="报警类型">{{ getAlarmTypeText(currentAlarm.alarm_type) }}</el-descriptions-item>
+        <el-descriptions-item label="报警级别">
+          <el-tag :type="getAlarmLevelTag(currentAlarm.alarm_level)">
+            {{ getAlarmLevelText(currentAlarm.alarm_level) }}
+          </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="位置">{{ currentAlarm.building }}栋-{{ currentAlarm.floor }}层</el-descriptions-item>
+        <el-descriptions-item label="防区">{{ currentAlarm.zone_name }}</el-descriptions-item>
+        <el-descriptions-item label="报警内容">{{ currentAlarm.alarm_message }}</el-descriptions-item>
+        <el-descriptions-item label="处理状态">
+          <el-tag :type="getHandleStatusTag(currentAlarm.handles_satus)">
+            {{ getHandleStatusText(currentAlarm.handles_satus) }}
+          </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="处理人">{{ currentAlarm.handle_user }}</el-descriptions-item>
+        <el-descriptions-item label="处理时间">{{ parseTime(currentAlarm.handle_time) }}</el-descriptions-item>
+        <el-descriptions-item label="处理备注">{{ currentAlarm.handle_remark }}</el-descriptions-item>
+        <el-descriptions-item label="联动状态">
+          <el-tag :type="currentAlarm.linkages_status === 1 ? 'success' : 'info'">
+            {{ currentAlarm.linkages_status === 1 ? '已联动' : '未联动' }}
+          </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="联动结果">{{ currentAlarm.linkage_result }}</el-descriptions-item>
+      </el-descriptions>
+    </el-drawer>
+
+    <!-- 报警处理对话框 -->
+    <el-dialog v-model="processDialogVisible" title="报警处理" width="600px">
+      <el-form :model="processForm" :rules="processRules" ref="processFormRef" label-width="100px">
+        <el-form-item label="处理状态" prop="handleStatus">
+          <el-radio-group v-model="processForm.handleStatus">
+            <el-radio :label="1">处理中</el-radio>
+            <el-radio :label="2">已处理</el-radio>
+            <el-radio :label="3">已忽略</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="处理备注" prop="handleRemark">
+          <el-input
+              v-model="processForm.handleRemark"
+              type="textarea"
+              :rows="4"
+              placeholder="请输入处理备注"
+          />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="processDialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="submitProcessForm">确定</el-button>
+        </span>
+      </template>
+    </el-dialog>
+
+    <!-- 设备配置对话框 -->
+    <el-dialog v-model="configDialogVisible" title="设备配置" width="600px">
+      <el-form :model="configForm" :rules="configRules" ref="configFormRef" label-width="100px">
+        <el-form-item label="敏感度" prop="sensitivity">
+          <el-slider v-model="configForm.sensitivity" :min="1" :max="100" show-input />
+        </el-form-item>
+        <el-form-item label="延时时间" prop="delayTime">
+          <el-input-number v-model="configForm.delayTime" :min="0" :max="300" controls-position="right" />
+          <span style="margin-left: 8px;">秒</span>
+        </el-form-item>
+        <el-form-item label="联动摄像机" prop="linkageCameras">
+          <el-select v-model="configForm.linkageCameras" multiple placeholder="选择联动摄像机">
+            <el-option
+                v-for="camera in cameraOptions"
+                :key="camera.code"
+                :label="camera.name"
+                :value="camera.code"
+            />
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="configDialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="submitConfigForm">确定</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, watch, onMounted } from 'vue'
+import {
+  listAlarmPoints,
+  listAlarms,
+  listZones,
+  getAlarmStatistics,
+  toggleArmStatus,
+  testAlarmDevice,
+  processAlarm,
+  updateDeviceConfig
+} from '@/api/subsystem/intrusion'
+
+const { proxy } = getCurrentInstance()
+const activeTab = ref('alarmPoints')
+
+// 监听activeTab的变化
+watch(activeTab, (newVal) => {
+  if (newVal === 'realTimeAlarms') {
+    getAlarmList()
+    getStatisticsData()
+  } else if (newVal === 'zones') {
+    getZoneList()
+  }
+})
+
+// 报警点查询相关
+const alarmPointQuery = reactive({
+  pageNum: 1,
+  pageSize: 10,
+  deviceCode: null,
+  deviceName: null,
+  alarmType: null,
+  zone: null,
+  deviceStatus: null
+})
+const alarmPointList = ref([])
+const alarmPointTotal = ref(0)
+const alarmPointLoading = ref(false)
+
+// 报警查询相关
+const alarmQuery = reactive({
+  pageNum: 1,
+  pageSize: 10,
+  alarmLevel: null,
+  handleStatus: null,
+  zone: null,
+  timeRange: []
+})
+const alarmList = ref([])
+const alarmTotal = ref(0)
+const alarmLoading = ref(false)
+
+// 防区查询相关
+const zoneQuery = reactive({
+  pageNum: 1,
+  pageSize: 10,
+  zoneName: null,
+  zoneStatus: null
+})
+const zoneList = ref([])
+const zoneTotal = ref(0)
+const zoneLoading = ref(false)
+const selectedZones = ref([])
+
+// 统计数据
+const statisticsData = ref({
+  todayAlarms: 0,
+  unhandledAlarms: 0,
+  onlineDevices: 0,
+  armedZones: 0
+})
+
+// 详情相关
+const alarmPointDetailVisible = ref(false)
+const currentAlarmPoint = ref({})
+
+const alarmDetailVisible = ref(false)
+const currentAlarm = ref({})
+
+// 报警处理相关
+const processDialogVisible = ref(false)
+const processForm = ref({
+  id: null,
+  handleStatus: 2,
+  handleRemark: ''
+})
+const processRules = {
+  handleStatus: [{ required: true, message: '请选择处理状态', trigger: 'change' }],
+  handleRemark: [{ required: true, message: '请输入处理备注', trigger: 'blur' }]
+}
+
+// 设备配置相关
+const configDialogVisible = ref(false)
+const configForm = ref({
+  deviceCode: '',
+  sensitivity: 50,
+  delayTime: 10,
+  linkageCameras: []
+})
+const configRules = {
+  sensitivity: [{ required: true, message: '请设置敏感度', trigger: 'change' }],
+  delayTime: [{ required: true, message: '请设置延时时间', trigger: 'change' }]
+}
+
+// 摄像机选项(模拟数据)
+const cameraOptions = ref([
+  { code: 'CAM001', name: '一层大厅摄像机' },
+  { code: 'CAM002', name: '二层走廊摄像机' },
+  { code: 'CAM003', name: '三层办公区摄像机' }
+])
+
+// 报警类型标签
+const getAlarmTypeTag = (type) => {
+  const tags = {
+    'intrusion': 'danger',
+    'door_magnetic': 'warning',
+    'infrared': 'primary',
+    'glass_break': 'danger',
+    'vibration': 'warning'
+  }
+  return tags[type] || 'info'
+}
+
+// 报警类型文本
+const getAlarmTypeText = (type) => {
+  const texts = {
+    'intrusion': '入侵检测',
+    'door_magnetic': '门磁报警',
+    'infrared': '红外探测',
+    'glass_break': '玻璃破碎',
+    'vibration': '震动检测'
+  }
+  return texts[type] || '未知'
+}
+
+// 设备状态标签
+const getDeviceStatusTag = (status) => {
+  const tags = ['danger', 'success', 'danger', 'warning']
+  return tags[status] || 'info'
+}
+
+// 设备状态文本
+const getDeviceStatusText = (status) => {
+  const texts = ['离线', '正常', '报警', '故障']
+  return texts[status] || '未知'
+}
+
+// 报警级别标签
+const getAlarmLevelTag = (level) => {
+  const tags = ['info', 'success', 'warning', 'danger']
+  return tags[level - 1] || 'info'
+}
+
+// 报警级别文本
+const getAlarmLevelText = (level) => {
+  const texts = ['', '低级', '中级', '高级', '紧急']
+  return texts[level] || '未知'
+}
+
+// 处理状态标签
+const getHandleStatusTag = (status) => {
+  const tags = ['danger', 'warning', 'success', 'info']
+  return tags[status] || 'info'
+}
+
+// 处理状态文本
+const getHandleStatusText = (status) => {
+  const texts = ['未处理', '处理中', '已处理', '已忽略']
+  return texts[status] || '未知'
+}
+
+// 查询报警点列表
+function getAlarmPointList() {
+  alarmPointLoading.value = true
+  const params = { ...alarmPointQuery }
+  listAlarmPoints(params).then(response => {
+    alarmPointList.value = response.rows
+    alarmPointTotal.value = response.total
+    alarmPointLoading.value = false
+  })
+}
+
+// 重置报警点查询
+function resetAlarmPointQuery() {
+  proxy.resetForm('alarmPointQueryRef')
+  alarmPointQuery.pageNum = 1
+  getAlarmPointList()
+}
+
+// 查询报警列表
+function getAlarmList() {
+  alarmLoading.value = true
+  const params = {
+    ...alarmQuery,
+    pageNum: alarmQuery.pageNum,
+    pageSize: alarmQuery.pageSize
+  }
+  if (alarmQuery.timeRange && alarmQuery.timeRange.length === 2) {
+    params.beginTime = alarmQuery.timeRange[0]
+    params.endTime = alarmQuery.timeRange[1]
+  }
+  listAlarms(params).then(response => {
+    alarmList.value = response.rows
+    alarmTotal.value = response.total
+    alarmLoading.value = false
+  })
+}
+
+// 重置报警查询
+function resetAlarmQuery() {
+  proxy.resetForm('alarmQueryRef')
+  alarmQuery.pageNum = 1
+  getAlarmList()
+}
+
+// 查询防区列表
+function getZoneList() {
+  zoneLoading.value = true
+  const params = { ...zoneQuery }
+  listZones(params).then(response => {
+    zoneList.value = response.rows
+    zoneTotal.value = response.total
+    zoneLoading.value = false
+  })
+}
+
+// 重置防区查询
+function resetZoneQuery() {
+  proxy.resetForm('zoneQueryRef')
+  zoneQuery.pageNum = 1
+  getZoneList()
+}
+
+// 获取统计数据
+function getStatisticsData() {
+  getAlarmStatistics().then(response => {
+    statisticsData.value = response.data
+  })
+}
+
+// 查看报警点详情
+function handleAlarmPointDetail(row) {
+  currentAlarmPoint.value = row
+  alarmPointDetailVisible.value = true
+}
+
+// 切换布防状态
+function handleToggleArm(row) {
+  const action = row.armStatus === 1 ? '撤防' : '布防'
+  proxy.$modal.confirm(`确认${action}该设备吗?`).then(() => {
+    toggleArmStatus({
+      deviceCode: row.deviceCode,
+      armStatus: row.armStatus === 1 ? 0 : 1
+    }).then(response => {
+      proxy.$modal.msgSuccess(`${action}成功`)
+      getAlarmPointList()
+    })
+  })
+}
+
+// 设备配置
+function handleDeviceConfig(row) {
+  configForm.value = {
+    deviceCode: row.deviceCode,
+    sensitivity: row.sensitivity || 50,
+    delayTime: row.delayTime || 10,
+    linkageCameras: row.linkageCameras ? row.linkageCameras.split(',') : []
+  }
+  configDialogVisible.value = true
+}
+
+// 提交设备配置
+function submitConfigForm() {
+  proxy.$refs['configFormRef'].validate((valid) => {
+    if (valid) {
+      updateDeviceConfig(configForm.value).then(response => {
+        proxy.$modal.msgSuccess('配置更新成功')
+        configDialogVisible.value = false
+        getAlarmPointList()
+      })
+    }
+  })
+}
+
+// 测试报警
+function handleTestAlarm(row) {
+  proxy.$modal.confirm('确认测试该报警设备吗?').then(() => {
+    testAlarmDevice(row.deviceCode).then(response => {
+      proxy.$modal.msgSuccess('测试指令发送成功')
+    })
+  })
+}
+
+// 查看报警详情
+function handleAlarmDetail(row) {
+  currentAlarm.value = row
+  alarmDetailVisible.value = true
+}
+
+// 处理报警
+function handleAlarmProcess(row) {
+  processForm.value = {
+    id: row.id,
+    handleStatus: 2,
+    handleRemark: ''
+  }
+  processDialogVisible.value = true
+}
+
+// 提交报警处理
+function submitProcessForm() {
+  proxy.$refs['processFormRef'].validate((valid) => {
+    if (valid) {
+      processAlarm(processForm.value).then(response => {
+        proxy.$modal.msgSuccess('报警处理成功')
+        processDialogVisible.value = false
+        getAlarmList()
+      })
+    }
+  })
+}
+
+// 查看联动视频
+function handleViewVideo(row) {
+  proxy.$modal.msgWarning('联动视频查看功能开发中...')
+}
+
+// 防区选择变化
+function handleZoneSelectionChange(selection) {
+  selectedZones.value = selection
+}
+
+// 新增防区
+function handleAddZone() {
+  proxy.$modal.msgWarning('新增防区功能开发中...')
+}
+
+// 批量布防
+function handleBatchArm() {
+  if (selectedZones.value.length === 0) {
+    proxy.$modal.msgWarning('请选择要布防的防区')
+    return
+  }
+  proxy.$modal.msgWarning('批量布防功能开发中...')
+}
+
+// 批量撤防
+function handleBatchDisarm() {
+  if (selectedZones.value.length === 0) {
+    proxy.$modal.msgWarning('请选择要撤防的防区')
+    return
+  }
+  proxy.$modal.msgWarning('批量撤防功能开发中...')
+}
+
+// 查看防区详情
+function handleZoneDetail(row) {
+  proxy.$modal.msgWarning('防区详情功能开发中...')
+}
+
+// 修改防区
+function handleEditZone(row) {
+  proxy.$modal.msgWarning('修改防区功能开发中...')
+}
+
+// 切换防区状态
+function handleToggleZone(row) {
+  const action = row.zoneStatus === 1 ? '撤防' : '布防'
+  proxy.$modal.confirm(`确认${action}该防区吗?`).then(() => {
+    proxy.$modal.msgSuccess(`${action}成功`)
+    getZoneList()
+  })
+}
+
+// 删除防区
+function handleDeleteZone(row) {
+  proxy.$modal.confirm('确认删除该防区吗?').then(() => {
+    proxy.$modal.msgSuccess('删除成功')
+    getZoneList()
+  })
+}
+
+// 初始化
+onMounted(() => {
+  getAlarmPointList()
+  getStatisticsData()
+})
+</script>
+
+<style scoped>
+.mb8 {
+  margin-bottom: 8px;
+}
+
+.text-gray-500 {
+  color: #6b7280;
+  font-size: 12px;
+}
+</style>

+ 768 - 0
pm_ui/src/views/spafjkxt/index.vue

@@ -0,0 +1,768 @@
+<template>
+  <div class="app-container">
+    <el-tabs v-model="activeTab">
+      <!-- 摄像机管理标签页 -->
+      <el-tab-pane label="摄像机管理" name="cameras">
+        <el-form :model="cameraQuery" ref="cameraQueryRef" :inline="true" label-width="98px">
+          <el-form-item label="摄像机编号" prop="cameraCode">
+            <el-input v-model="cameraQuery.cameraCode" placeholder="请输入摄像机编号" clearable />
+          </el-form-item>
+          <el-form-item label="摄像机名称" prop="cameraName">
+            <el-input v-model="cameraQuery.cameraName" placeholder="请输入摄像机名称" clearable />
+          </el-form-item>
+          <el-form-item label="监控区域" prop="monitorArea">
+            <el-input v-model="cameraQuery.monitorArea" placeholder="请输入监控区域" clearable />
+          </el-form-item>
+          <el-form-item label="楼栋" prop="building">
+            <el-select v-model="cameraQuery.building" placeholder="请选择楼栋" clearable>
+              <el-option label="A栋" value="A" />
+              <el-option label="B栋" value="B" />
+              <el-option label="C栋" value="C" />
+              <el-option label="D栋" value="D" />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="设备状态" prop="deviceStatus">
+            <el-select v-model="cameraQuery.deviceStatus" placeholder="请选择状态" clearable>
+              <el-option label="在线" :value="1" />
+              <el-option label="离线" :value="0" />
+              <el-option label="故障" :value="2" />
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" icon="Search" @click="getCameraList">搜索</el-button>
+            <el-button icon="Refresh" @click="resetCameraQuery">重置</el-button>
+          </el-form-item>
+        </el-form>
+
+        <el-table v-loading="cameraLoading" :data="cameraList" stripe border>
+          <el-table-column label="摄像机编号" prop="cameraCode" width="150" />
+          <el-table-column label="摄像机名称" prop="cameraName"  />
+          <el-table-column label="监控区域" prop="monitorArea" width="120" />
+          <el-table-column label="位置信息" width="150">
+            <template #default="scope">
+              <div>{{ scope.row.building }}栋-{{ scope.row.floor }}层</div>
+              <div class="text-gray-500">{{ scope.row.location_detail }}</div>
+            </template>
+          </el-table-column>
+          <el-table-column label="IP地址" prop="ipAddress" width="120" />
+          <el-table-column label="端口" prop="port" width="80" />
+          <el-table-column label="品牌型号" width="120">
+            <template #default="scope">
+              <div>{{ scope.row.brand }}</div>
+              <div class="text-gray-500">{{ scope.row.model }}</div>
+            </template>
+          </el-table-column>
+          <el-table-column label="分辨率" prop="resolution" width="100" />
+          <el-table-column label="设备状态" prop="deviceStatus" width="100">
+            <template #default="scope">
+              <el-tag :type="scope.row.deviceStatus === 1 ? 'success' : scope.row.deviceStatus === 2 ? 'warning' : 'danger'">
+                {{ getStatusText(scope.row.deviceStatus) }}
+              </el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column label="云台控制" prop="ptzSupport" width="100">
+            <template #default="scope">
+              <el-tag :type="scope.row.ptzSupport === 1 ? 'success' : 'info'">
+                {{ scope.row.ptzSupport === 1 ? '支持' : '不支持' }}
+              </el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column label="录像状态" prop="recordingStatus" width="100">
+            <template #default="scope">
+              <el-tag :type="scope.row.recordingStatus === 1 ? 'success' : 'danger'">
+                {{ scope.row.recordingStatus === 1 ? '录像中' : '未录像' }}
+              </el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column label="操作" width="400" fixed="right">
+            <template #default="scope">
+              <el-button link type="primary" icon="VideoCamera" @click="handleVideoPreview(scope.row)">实时预览</el-button>
+              <el-button link type="success" icon="Setting" @click="handlePtzControl(scope.row)" v-if="scope.row.ptzSupport === 1">云台控制</el-button>
+              <el-button link type="warning" icon="View" @click="handleCameraDetail(scope.row)">详情</el-button>
+              <el-button link type="info" icon="VideoPlay" @click="handlePlayback(scope.row)">录像回放</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <pagination
+            v-show="cameraTotal > 0"
+            :total="cameraTotal"
+            v-model:page="cameraQuery.pageNum"
+            v-model:limit="cameraQuery.pageSize"
+            @pagination="getCameraList"
+        />
+      </el-tab-pane>
+
+      <!-- 告警联动标签页 -->
+      <el-tab-pane label="告警联动" name="linkage">
+        <el-form :model="linkageQuery" ref="linkageQueryRef" :inline="true" label-width="98px">
+          <el-form-item label="联动规则名称" prop="ruleName">
+            <el-input v-model="linkageQuery.ruleName" placeholder="请输入联动规则名称" clearable />
+          </el-form-item>
+          <el-form-item label="触发设备" prop="triggerDevice">
+            <el-input v-model="linkageQuery.triggerDevice" placeholder="请输入触发设备" clearable />
+          </el-form-item>
+          <el-form-item label="联动摄像机" prop="linkageCamera">
+            <el-input v-model="linkageQuery.linkageCamera" placeholder="请输入联动摄像机" clearable />
+          </el-form-item>
+          <el-form-item label="规则状态" prop="ruleStatus">
+            <el-select v-model="linkageQuery.ruleStatus" placeholder="请选择状态" clearable>
+              <el-option label="启用" :value="1" />
+              <el-option label="禁用" :value="0" />
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" icon="Search" @click="getLinkageList">搜索</el-button>
+            <el-button icon="Refresh" @click="resetLinkageQuery">重置</el-button>
+          </el-form-item>
+        </el-form>
+
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <el-button
+                type="primary"
+                plain
+                icon="Plus"
+                @click="handleAddLinkage"
+            >新增联动规则</el-button>
+          </el-col>
+        </el-row>
+
+        <el-table v-loading="linkageLoading" :data="linkageList" stripe border>
+          <el-table-column label="规则名称" prop="ruleName"  />
+          <el-table-column label="触发设备" prop="triggerDeviceName"  />
+          <el-table-column label="触发条件" prop="triggerCondition"  />
+          <el-table-column label="联动摄像机" prop="linkageCameraNname"  />
+          <el-table-column label="联动动作" prop="linkageAction" >
+            <template #default="scope">
+              <el-tag>{{ getLinkageActionText(scope.row.linkageAction) }}</el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column label="延迟时间" prop="delayTime" width="100">
+            <template #default="scope">
+              <span>{{ scope.row.delay_time }}秒</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="规则状态" prop="ruleStatus" width="100">
+            <template #default="scope">
+              <el-tag :type="scope.row.ruleStatus === 1 ? 'success' : 'danger'">
+                {{ scope.row.ruleStatus === 1 ? '启用' : '禁用' }}
+              </el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column label="创建时间" prop="createTime" width="150">
+            <template #default="scope">
+              <span>{{ parseTime(scope.row.createTime) }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="操作" width="250" fixed="right">
+            <template #default="scope">
+              <el-button link type="primary" icon="Edit" @click="handleEditLinkage(scope.row)">修改</el-button>
+              <el-button link type="danger" icon="Delete" @click="handleDeleteLinkage(scope.row)">删除</el-button>
+              <el-button link type="warning" icon="Switch" @click="handleToggleLinkage(scope.row)">
+                {{ scope.row.ruleStatus === 1 ? '禁用' : '启用' }}
+              </el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <pagination
+            v-show="linkageTotal > 0"
+            :total="linkageTotal"
+            v-model:page="linkageQuery.pageNum"
+            v-model:limit="linkageQuery.pageSize"
+            @pagination="getLinkageList"
+        />
+      </el-tab-pane>
+    </el-tabs>
+
+    <!-- 实时视频预览抽屉 -->
+    <el-drawer
+        v-model="videoVisible"
+        title="实时视频预览"
+        direction="rtl"
+        size="60%"
+    >
+      <div class="video-container">
+        <div class="video-player">
+          <video ref="videoPlayer" controls autoplay muted style="width: 100%; height: 400px; background: #000;">
+            <source :src="currentVideoUrl" type="application/x-mpegURL">
+            您的浏览器不支持视频播放
+          </video>
+        </div>
+        <div class="video-info">
+          <el-descriptions :column="2" border>
+            <el-descriptions-item label="摄像机名称">{{ currentCamera.cameraName }}</el-descriptions-item>
+            <el-descriptions-item label="监控区域">{{ currentCamera.monitorArea }}</el-descriptions-item>
+            <el-descriptions-item label="位置">{{ currentCamera.building }}栋-{{ currentCamera.floor }}层</el-descriptions-item>
+            <el-descriptions-item label="分辨率">{{ currentCamera.resolution }}</el-descriptions-item>
+            <el-descriptions-item label="设备状态">
+              <el-tag :type="currentCamera.deviceStatus === 1 ? 'success' : 'danger'">
+                {{ getStatusText(currentCamera.deviceStatus) }}
+              </el-tag>
+            </el-descriptions-item>
+            <el-descriptions-item label="录像状态">
+              <el-tag :type="currentCamera.recordingStatus === 1 ? 'success' : 'danger'">
+                {{ currentCamera.recordingStatus === 1 ? '录像中' : '未录像' }}
+              </el-tag>
+            </el-descriptions-item>
+          </el-descriptions>
+        </div>
+        <div class="video-controls">
+          <el-button type="primary" @click="startRecording" v-if="currentCamera.recordingStatus === 0">开始录像</el-button>
+          <el-button type="danger" @click="stopRecording" v-if="currentCamera.recordingStatus === 1">停止录像</el-button>
+          <el-button type="success" @click="takeSnapshot">抓拍</el-button>
+          <el-button type="warning" @click="handlePtzControl(currentCamera)" v-if="currentCamera.ptzSupport === 1">云台控制</el-button>
+        </div>
+      </div>
+    </el-drawer>
+
+    <!-- 云台控制抽屉 -->
+    <el-drawer
+        v-model="ptzVisible"
+        title="云台控制"
+        direction="rtl"
+        size="40%"
+    >
+      <div class="ptz-container">
+        <div class="ptz-direction">
+          <h4>方向控制</h4>
+          <div class="direction-grid">
+            <div class="direction-row">
+              <el-button @click="ptzControl('UP')" icon="ArrowUp" class="direction-btn">上</el-button>
+            </div>
+            <div class="direction-row">
+              <el-button @click="ptzControl('LEFT')" icon="ArrowLeft" class="direction-btn">左</el-button>
+              <el-button @click="ptzControl('STOP')" icon="Close" class="direction-btn stop-btn">停止</el-button>
+              <el-button @click="ptzControl('RIGHT')" icon="ArrowRight" class="direction-btn">右</el-button>
+            </div>
+            <div class="direction-row">
+              <el-button @click="ptzControl('DOWN')" icon="ArrowDown" class="direction-btn">下</el-button>
+            </div>
+          </div>
+        </div>
+
+        <div class="ptz-zoom">
+          <h4>变焦控制</h4>
+          <div class="zoom-controls">
+            <el-button @click="ptzControl('ZOOM_IN')" icon="ZoomIn">放大</el-button>
+            <el-button @click="ptzControl('ZOOM_OUT')" icon="ZoomOut">缩小</el-button>
+          </div>
+        </div>
+
+        <div class="ptz-focus">
+          <h4>聚焦控制</h4>
+          <div class="focus-controls">
+            <el-button @click="ptzControl('FOCUS_NEAR')" icon="Minus">近焦</el-button>
+            <el-button @click="ptzControl('FOCUS_FAR')" icon="Plus">远焦</el-button>
+            <el-button @click="ptzControl('AUTO_FOCUS')" icon="Aim">自动聚焦</el-button>
+          </div>
+        </div>
+
+        <div class="ptz-preset">
+          <h4>预置位控制</h4>
+          <el-form :model="presetForm" :inline="true">
+            <el-form-item label="预置位">
+              <el-select v-model="presetForm.presetId" placeholder="选择预置位">
+                <el-option
+                    v-for="preset in presetList"
+                    :key="preset.id"
+                    :label="preset.name"
+                    :value="preset.id"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item>
+              <el-button @click="gotoPreset">转到</el-button>
+              <el-button @click="setPreset">设置</el-button>
+              <el-button @click="deletePreset">删除</el-button>
+            </el-form-item>
+          </el-form>
+        </div>
+
+        <div class="ptz-speed">
+          <h4>控制速度</h4>
+          <el-slider v-model="ptzSpeed" :min="1" :max="10" show-input />
+        </div>
+      </div>
+    </el-drawer>
+
+    <!-- 摄像机详情抽屉 -->
+    <el-drawer
+        v-model="detailVisible"
+        title="摄像机详情"
+        direction="rtl"
+        size="50%"
+    >
+      <el-descriptions :column="2" border>
+        <el-descriptions-item label="摄像机编号">{{ currentCamera.cameraCode }}</el-descriptions-item>
+        <el-descriptions-item label="摄像机名称">{{ currentCamera.cameraName }}</el-descriptions-item>
+        <el-descriptions-item label="监控区域">{{ currentCamera.monitorArea }}</el-descriptions-item>
+        <el-descriptions-item label="位置">{{ currentCamera.building }}栋-{{ currentCamera.floor }}层-{{ currentCamera.locationDetail }}</el-descriptions-item>
+        <el-descriptions-item label="IP地址">{{ currentCamera.ipAddress }}</el-descriptions-item>
+        <el-descriptions-item label="端口">{{ currentCamera.port }}</el-descriptions-item>
+        <el-descriptions-item label="品牌">{{ currentCamera.brand }}</el-descriptions-item>
+        <el-descriptions-item label="型号">{{ currentCamera.model }}</el-descriptions-item>
+        <el-descriptions-item label="分辨率">{{ currentCamera.resolution }}</el-descriptions-item>
+        <el-descriptions-item label="帧率">{{ currentCamera.frameRate }} fps</el-descriptions-item>
+        <el-descriptions-item label="云台支持">
+          <el-tag :type="currentCamera.ptzSupport === 1 ? 'success' : 'info'">
+            {{ currentCamera.ptzSupport === 1 ? '支持' : '不支持' }}
+          </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="红外夜视">
+          <el-tag :type="currentCamera.infraredSupport === 1 ? 'success' : 'info'">
+            {{ currentCamera.infraredSupport === 1 ? '支持' : '不支持' }}
+          </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="设备状态">
+          <el-tag :type="currentCamera.deviceStatus === 1 ? 'success' : currentCamera.deviceStatus === 2 ? 'warning' : 'danger'">
+            {{ getStatusText(currentCamera.deviceStatus) }}
+          </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="录像状态">
+          <el-tag :type="currentCamera.recordingStatus === 1 ? 'success' : 'danger'">
+            {{ currentCamera.recordingStatus === 1 ? '录像中' : '未录像' }}
+          </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="存储路径">{{ currentCamera.storagePath }}</el-descriptions-item>
+        <el-descriptions-item label="最后在线时间">{{ parseTime(currentCamera.lastOnlineTime) }}</el-descriptions-item>
+      </el-descriptions>
+    </el-drawer>
+
+    <!-- 联动规则配置对话框 -->
+    <el-dialog v-model="linkageDialogVisible" :title="linkageDialogTitle" width="600px">
+      <el-form :model="linkageForm" :rules="linkageRules" ref="linkageFormRef" label-width="120px">
+        <el-form-item label="规则名称" prop="ruleName">
+          <el-input v-model="linkageForm.ruleName" placeholder="请输入联动规则名称" />
+        </el-form-item>
+        <el-form-item label="触发设备" prop="triggerDeviceCode">
+          <el-select v-model="linkageForm.triggerDeviceCode" placeholder="请选择触发设备" filterable>
+            <el-option
+                v-for="device in triggerDeviceList"
+                :key="device.code"
+                :label="device.name"
+                :value="device.code"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="触发条件" prop="triggerCondition">
+          <el-select v-model="linkageForm.triggerCondition" placeholder="请选择触发条件">
+            <el-option label="设备报警" value="deviceAlarm" />
+            <el-option label="入侵检测" value="intrusionDetection" />
+            <el-option label="门禁异常" value="accessAbnormal" />
+            <el-option label="火灾报警" value="fireAlarm" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="联动摄像机" prop="linkageCameraCode">
+          <el-select v-model="linkageForm.linkageCameraCode" placeholder="请选择联动摄像机" filterable>
+            <el-option
+                v-for="camera in cameraList"
+                :key="camera.cameraCode"
+                :label="camera.cameraName"
+                :value="camera.cameraCode"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="联动动作" prop="linkageAction">
+          <el-select v-model="linkageForm.linkageAction" placeholder="请选择联动动作">
+            <el-option label="开始录像" value="start_recording" />
+            <el-option label="转到预置位" value="goto_preset" />
+            <el-option label="自动跟踪" value="auto_tracking" />
+            <el-option label="抓拍图片" value="capture_image" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="延迟时间" prop="delayTime">
+          <el-input-number v-model="linkageForm.delayTime" :min="0" :max="300" controls-position="right" />
+          <span style="margin-left: 8px;">秒</span>
+        </el-form-item>
+        <el-form-item label="规则状态" prop="ruleStatus">
+          <el-radio-group v-model="linkageForm.ruleStatus">
+            <el-radio :label="1">启用</el-radio>
+            <el-radio :label="0">禁用</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="linkageDialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="submitLinkageForm">确定</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, watch, onMounted } from 'vue'
+import { listCameras, getCameraStream, ptzControl as ptzControlApi, recordingControl, takeCameraSnapshot } from '@/api/subsystem/video'
+import { listLinkageRules, addLinkageRule, updateLinkageRule, deleteLinkageRule, toggleLinkageRule } from '@/api/subsystem/video'
+
+const { proxy } = getCurrentInstance()
+const activeTab = ref('cameras')
+
+// 监听activeTab的变化
+watch(activeTab, (newVal) => {
+  if (newVal === 'linkage') {
+    getLinkageList()
+  }
+})
+
+// 摄像机查询相关
+const cameraQuery = reactive({
+  pageNum: 1,
+  pageSize: 10,
+  cameraCode: null,
+  cameraName: null,
+  monitorArea: null,
+  building: null,
+  deviceStatus: null
+})
+const cameraList = ref([])
+const cameraTotal = ref(0)
+const cameraLoading = ref(false)
+
+// 联动规则查询相关
+const linkageQuery = reactive({
+  pageNum: 1,
+  pageSize: 10,
+  ruleName: null,
+  triggerDevice: null,
+  linkageCamera: null,
+  ruleStatus: null
+})
+const linkageList = ref([])
+const linkageTotal = ref(0)
+const linkageLoading = ref(false)
+
+// 视频预览相关
+const videoVisible = ref(false)
+const currentCamera = ref({})
+const currentVideoUrl = ref('')
+
+// 云台控制相关
+const ptzVisible = ref(false)
+const ptzSpeed = ref(5)
+const presetForm = ref({
+  presetId: null
+})
+const presetList = ref([
+  { id: 1, name: '预置位1' },
+  { id: 2, name: '预置位2' },
+  { id: 3, name: '预置位3' }
+])
+
+// 详情相关
+const detailVisible = ref(false)
+
+// 联动规则配置相关
+const linkageDialogVisible = ref(false)
+const linkageDialogTitle = ref('')
+const linkageForm = ref({
+  id: null,
+  ruleName: '',
+  triggerDeviceCode: '',
+  triggerCondition: '',
+  linkageCameraCode: '',
+  linkageAction: '',
+  delayTime: 0,
+  ruleStatus: 1
+})
+const linkageRules = {
+  ruleName: [{ required: true, message: '请输入规则名称', trigger: 'blur' }],
+  triggerDeviceCode: [{ required: true, message: '请选择触发设备', trigger: 'change' }],
+  triggerCondition: [{ required: true, message: '请选择触发条件', trigger: 'change' }],
+  linkageCameraCode: [{ required: true, message: '请选择联动摄像机', trigger: 'change' }],
+  linkageAction: [{ required: true, message: '请选择联动动作', trigger: 'change' }]
+}
+
+// 触发设备列表(模拟数据,实际应从其他子系统获取)
+const triggerDeviceList = ref([
+  { code: 'ALARM001', name: '火灾报警器001' },
+  { code: 'DOOR001', name: '门禁001' },
+  { code: 'MOTION001', name: '红外探测器001' }
+])
+
+// 设备状态文本
+const getStatusText = (status) => {
+  const texts = ['离线', '在线', '故障']
+  return texts[status] || '未知'
+}
+
+// 联动动作文本
+const getLinkageActionText = (action) => {
+  const texts = {
+    'start_recording': '开始录像',
+    'goto_preset': '转到预置位',
+    'auto_tracking': '自动跟踪',
+    'capture_image': '抓拍图片'
+  }
+  return texts[action] || '未知'
+}
+
+// 查询摄像机列表
+function getCameraList() {
+  cameraLoading.value = true
+  const params = {
+    ...cameraQuery,
+    pageNum: cameraQuery.pageNum,
+    pageSize: cameraQuery.pageSize
+  }
+  listCameras(params).then(response => {
+    cameraList.value = response.rows
+    cameraTotal.value = response.total
+    cameraLoading.value = false
+  })
+}
+
+// 重置摄像机查询
+function resetCameraQuery() {
+  proxy.resetForm('cameraQueryRef')
+  cameraQuery.pageNum = 1
+  getCameraList()
+}
+
+// 查询联动规则列表
+function getLinkageList() {
+  linkageLoading.value = true
+  const params = {
+    ...linkageQuery,
+    pageNum: linkageQuery.pageNum,
+    pageSize: linkageQuery.pageSize
+  }
+  listLinkageRules(params).then(response => {
+    linkageList.value = response.rows
+    linkageTotal.value = response.total
+    linkageLoading.value = false
+  })
+}
+
+// 重置联动规则查询
+function resetLinkageQuery() {
+  proxy.resetForm('linkageQueryRef')
+  linkageQuery.pageNum = 1
+  getLinkageList()
+}
+
+// 实时视频预览
+function handleVideoPreview(row) {
+  currentCamera.value = row
+  videoVisible.value = true
+
+  // 获取视频流地址
+  getCameraStream(row.camera_code).then(response => {
+    currentVideoUrl.value = response.data.streamUrl
+  })
+}
+
+// 云台控制
+function handlePtzControl(row) {
+  currentCamera.value = row
+  ptzVisible.value = true
+}
+
+// 云台方向控制
+function ptzControl(direction) {
+  const params = {
+    cameraCode: currentCamera.value.cameraCode,
+    command: direction,
+    speed: ptzSpeed.value
+  }
+  ptzControlApi(params).then(response => {
+    proxy.$modal.msgSuccess('云台控制指令发送成功')
+  })
+}
+
+// 转到预置位
+function gotoPreset() {
+  if (!presetForm.value.presetId) {
+    proxy.$modal.msgWarning('请选择预置位')
+    return
+  }
+  ptzControl(`PRESET_${presetForm.value.presetId}`)
+}
+
+// 设置预置位
+function setPreset() {
+  if (!presetForm.value.presetId) {
+    proxy.$modal.msgWarning('请选择预置位')
+    return
+  }
+  ptzControl(`SET_PRESET_${presetForm.value.presetId}`)
+}
+
+// 删除预置位
+function deletePreset() {
+  if (!presetForm.value.presetId) {
+    proxy.$modal.msgWarning('请选择预置位')
+    return
+  }
+  ptzControl(`DEL_PRESET_${presetForm.value.presetId}`)
+}
+
+// 开始录像
+function startRecording() {
+  recordingControl({
+    cameraCode: currentCamera.value.cameraCode,
+    action: 'start'
+  }).then(response => {
+    proxy.$modal.msgSuccess('开始录像成功')
+    currentCamera.value.recordingStatus = 1
+    getCameraList()
+  })
+}
+
+// 停止录像
+function stopRecording() {
+  recordingControl({
+    cameraCode: currentCamera.value.cameraCode,
+    action: 'stop'
+  }).then(response => {
+    proxy.$modal.msgSuccess('停止录像成功')
+    currentCamera.value.recordingStatus = 0
+    getCameraList()
+  })
+}
+
+// 抓拍
+function takeSnapshot() {
+  takeCameraSnapshot(currentCamera.value.cameraCode).then(response => {
+    proxy.$modal.msgSuccess('抓拍成功,图片已保存')
+  })
+}
+
+// 查看摄像机详情
+function handleCameraDetail(row) {
+  currentCamera.value = row
+  detailVisible.value = true
+}
+
+// 录像回放
+function handlePlayback(row) {
+  proxy.$modal.msgInfo('录像回放功能开发中...')
+}
+
+// 新增联动规则
+function handleAddLinkage() {
+  linkageForm.value = {
+    id: null,
+    ruleName: '',
+    triggerDeviceCode: '',
+    triggerCondition: '',
+    linkageCameraCode: '',
+    linkageAction: '',
+    delayTime: 0,
+    ruleStatus: 1
+  }
+  linkageDialogTitle.value = '新增联动规则'
+  linkageDialogVisible.value = true
+}
+
+// 修改联动规则
+function handleEditLinkage(row) {
+  linkageForm.value = { ...row }
+  linkageDialogTitle.value = '修改联动规则'
+  linkageDialogVisible.value = true
+}
+
+// 删除联动规则
+function handleDeleteLinkage(row) {
+  proxy.$modal.confirm('确认删除该联动规则吗?').then(() => {
+    deleteLinkageRule(row.id).then(response => {
+      proxy.$modal.msgSuccess('删除成功')
+      getLinkageList()
+    })
+  })
+}
+
+// 启用/禁用联动规则
+function handleToggleLinkage(row) {
+  const action = row.rule_status === 1 ? '禁用' : '启用'
+  proxy.$modal.confirm(`确认${action}该联动规则吗?`).then(() => {
+    toggleLinkageRule(row.id, { ruleStatus: row.rule_status === 1 ? 0 : 1 }).then(response => {
+      proxy.$modal.msgSuccess(`${action}成功`)
+      getLinkageList()
+    })
+  })
+}
+
+// 提交联动规则表单
+function submitLinkageForm() {
+  proxy.$refs['linkageFormRef'].validate((valid) => {
+    if (valid) {
+      if (linkageForm.value.id) {
+        updateLinkageRule(linkageForm.value).then(response => {
+          proxy.$modal.msgSuccess('修改成功')
+          linkageDialogVisible.value = false
+          getLinkageList()
+        })
+      } else {
+        addLinkageRule(linkageForm.value).then(response => {
+          proxy.$modal.msgSuccess('新增成功')
+          linkageDialogVisible.value = false
+          getLinkageList()
+        })
+      }
+    }
+  })
+}
+
+// 初始化
+onMounted(() => {
+  getCameraList()
+})
+</script>
+
+<style scoped>
+.video-container {
+  padding: 20px;
+}
+
+.video-player {
+  margin-bottom: 20px;
+}
+
+.video-info {
+  margin-bottom: 20px;
+}
+
+.video-controls {
+  text-align: center;
+}
+
+.ptz-container {
+  padding: 20px;
+}
+
+.ptz-direction, .ptz-zoom, .ptz-focus, .ptz-preset, .ptz-speed {
+  margin-bottom: 30px;
+}
+
+.direction-grid {
+  text-align: center;
+}
+
+.direction-row {
+  margin-bottom: 10px;
+}
+
+.direction-btn {
+  width: 60px;
+  height: 60px;
+  margin: 5px;
+}
+
+.stop-btn {
+  background-color: #f56c6c;
+  border-color: #f56c6c;
+  color: white;
+}
+
+.zoom-controls, .focus-controls {
+  text-align: center;
+}
+
+.zoom-controls .el-button, .focus-controls .el-button {
+  margin: 0 10px;
+}
+</style>