Browse Source

代码提交

luogang 9 months ago
parent
commit
9f95e0b7ce
57 changed files with 2536 additions and 686 deletions
  1. 1 1
      ruoyi-admin/src/main/resources/application.yml
  2. 0 1
      ruoyi-common/pom.xml
  3. 0 6
      ruoyi-common/ruoyi-common-bom/pom.xml
  4. 0 46
      ruoyi-common/ruoyi-common-job/pom.xml
  5. 0 37
      ruoyi-common/ruoyi-common-job/src/main/java/org/dromara/common/job/config/SnailJobConfig.java
  6. 0 1
      ruoyi-common/ruoyi-common-job/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  7. 5 1
      ruoyi-modules/ruoyi-traffic/pom.xml
  8. 10 7
      ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/controller/SenEventsMediaController.java
  9. 12 0
      ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/controller/SenEventsReportController.java
  10. 10 0
      ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/controller/SenMonitorPointController.java
  11. 13 0
      ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/controller/SenMonitorPointMediaController.java
  12. 11 0
      ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/controller/SenMonitorTaskController.java
  13. 5 0
      ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/domain/SenEvents.java
  14. 5 1
      ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/domain/SenEventsReport.java
  15. 6 6
      ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/domain/bo/SenEventsReportBo.java
  16. 31 1
      ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/domain/vo/SenEventsReportVo.java
  17. 11 3
      ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/domain/vo/SenMonitorTaskVo.java
  18. 6 1
      ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/mapper/SenMonitorTaskAlgorithmsMapper.java
  19. 2 0
      ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/service/ISenEventsMediaService.java
  20. 2 0
      ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/service/ISenEventsReportService.java
  21. 2 0
      ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/service/ISenMonitorPointMediaService.java
  22. 3 0
      ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/service/ISenMonitorPointService.java
  23. 3 0
      ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/service/ISenMonitorTaskService.java
  24. 7 0
      ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/service/impl/SenEventsMediaServiceImpl.java
  25. 61 4
      ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/service/impl/SenEventsReportServiceImpl.java
  26. 10 2
      ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/service/impl/SenEventsServiceImpl.java
  27. 6 0
      ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/service/impl/SenMonitorPointMediaServiceImpl.java
  28. 71 73
      ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/service/impl/SenMonitorPointServiceImpl.java
  29. 28 3
      ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/service/impl/SenMonitorTaskServiceImpl.java
  30. 17 0
      ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/task/EventReportTask.java
  31. 7 13
      ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/task/EventTask.java
  32. 14 0
      ruoyi-modules/ruoyi-traffic/src/main/resources/mapper/SenMonitorTaskAlgorithmsMapper.xml
  33. 3 0
      traffic-ui/.env.development
  34. 3 0
      traffic-ui/.env.production
  35. 0 191
      traffic-ui/html/ie.html
  36. 1 0
      traffic-ui/index.html
  37. 0 0
      traffic-ui/public/js/jessibuca/decoder.js
  38. BIN
      traffic-ui/public/js/jessibuca/decoder.wasm
  39. 637 0
      traffic-ui/public/js/jessibuca/jessibuca.d.ts
  40. 0 0
      traffic-ui/public/js/jessibuca/jessibuca.js
  41. 19 2
      traffic-ui/src/api/manage/monitorPoint/index.ts
  42. 10 0
      traffic-ui/src/api/manage/monitorTask/index.ts
  43. 8 0
      traffic-ui/src/api/sense/events/index.ts
  44. 11 0
      traffic-ui/src/api/sense/eventsReport/index.ts
  45. 6 6
      traffic-ui/src/assets/styles/element-ui.scss
  46. 266 0
      traffic-ui/src/components/JessibucaPlayer/index.vue
  47. 358 0
      traffic-ui/src/components/JessibucaPlayer/index1.vue
  48. 216 0
      traffic-ui/src/components/JessibucaPlayer/useDraw.js
  49. 3 0
      traffic-ui/src/types/components.d.ts
  50. 40 14
      traffic-ui/src/views/index.vue
  51. 148 4
      traffic-ui/src/views/manage/monitorPoint/videoConfig.vue
  52. 67 41
      traffic-ui/src/views/sense/dealKeyEvents/index.vue
  53. 49 219
      traffic-ui/src/views/sense/eventsReport/index.vue
  54. 1 1
      traffic-ui/src/views/sense/flowDetail/index.vue
  55. 244 0
      traffic-ui/src/views/sense/offence/index.vue
  56. 86 0
      traffic-ui/src/views/sense/video/index.vue
  57. 1 1
      traffic-ui/src/views/system/user/index.vue

+ 1 - 1
ruoyi-admin/src/main/resources/application.yml

@@ -273,7 +273,7 @@ websocket:
 
 mqtt:
   server:
-    enabled: true               # 是否开启服务端,默认:true
+    enabled: false               # 是否开启服务端,默认:true
     #    ip: 0.0.0.0                # 服务端 ip 默认为空,0.0.0.0,建议不要设置
     port: 1883                  # 端口,默认:1883
     name: Mica-Mqtt-Server      # 名称,默认:Mica-Mqtt-Server

+ 0 - 1
ruoyi-common/pom.xml

@@ -16,7 +16,6 @@
         <module>ruoyi-common-doc</module>
         <module>ruoyi-common-excel</module>
         <module>ruoyi-common-idempotent</module>
-        <module>ruoyi-common-job</module>
         <module>ruoyi-common-log</module>
         <module>ruoyi-common-mail</module>
         <module>ruoyi-common-mybatis</module>

+ 0 - 6
ruoyi-common/ruoyi-common-bom/pom.xml

@@ -47,12 +47,6 @@
                 <version>${revision}</version>
             </dependency>
 
-            <!-- 调度模块 -->
-            <dependency>
-                <groupId>org.dromara</groupId>
-                <artifactId>ruoyi-common-job</artifactId>
-                <version>${revision}</version>
-            </dependency>
 
             <!-- 日志记录 -->
             <dependency>

+ 0 - 46
ruoyi-common/ruoyi-common-job/pom.xml

@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xmlns="http://maven.apache.org/POM/4.0.0"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <parent>
-        <groupId>org.dromara</groupId>
-        <artifactId>ruoyi-common</artifactId>
-        <version>${revision}</version>
-    </parent>
-    <modelVersion>4.0.0</modelVersion>
-
-    <artifactId>ruoyi-common-job</artifactId>
-
-    <description>
-        ruoyi-common-job 定时任务
-    </description>
-
-    <dependencies>
-
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-autoconfigure</artifactId>
-        </dependency>
-
-        <!-- SnailJob client -->
-        <dependency>
-            <groupId>com.aizuda</groupId>
-            <artifactId>snail-job-client-starter</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>com.aizuda</groupId>
-            <artifactId>snail-job-client-job-core</artifactId>
-        </dependency>
-
-        <dependency>
-            <groupId>org.projectlombok</groupId>
-            <artifactId>lombok</artifactId>
-        </dependency>
-
-        <dependency>
-            <groupId>org.dromara</groupId>
-            <artifactId>ruoyi-common-core</artifactId>
-        </dependency>
-
-    </dependencies>
-</project>

+ 0 - 37
ruoyi-common/ruoyi-common-job/src/main/java/org/dromara/common/job/config/SnailJobConfig.java

@@ -1,37 +0,0 @@
-package org.dromara.common.job.config;
-
-import ch.qos.logback.classic.Logger;
-import ch.qos.logback.classic.LoggerContext;
-import ch.qos.logback.classic.spi.ILoggingEvent;
-import com.aizuda.snailjob.client.common.appender.SnailLogbackAppender;
-import com.aizuda.snailjob.client.common.event.SnailClientStartingEvent;
-import com.aizuda.snailjob.client.starter.EnableSnailJob;
-import org.slf4j.LoggerFactory;
-import org.springframework.boot.autoconfigure.AutoConfiguration;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
-import org.springframework.context.event.EventListener;
-import org.springframework.scheduling.annotation.EnableScheduling;
-
-/**
- * 启动定时任务
- *
- * @author opensnail
- * @date 2024-05-17
- */
-@AutoConfiguration
-@ConditionalOnProperty(prefix = "snail-job", name = "enabled", havingValue = "true")
-@EnableScheduling
-@EnableSnailJob
-public class SnailJobConfig {
-
-    @EventListener(SnailClientStartingEvent.class)
-    public void onStarting(SnailClientStartingEvent event) {
-        LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
-        SnailLogbackAppender<ILoggingEvent> ca = new SnailLogbackAppender<>();
-        ca.setName("snail_log_appender");
-        ca.start();
-        Logger rootLogger = lc.getLogger(Logger.ROOT_LOGGER_NAME);
-        rootLogger.addAppender(ca);
-    }
-
-}

+ 0 - 1
ruoyi-common/ruoyi-common-job/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -1 +0,0 @@
-org.dromara.common.job.config.SnailJobConfig

+ 5 - 1
ruoyi-modules/ruoyi-traffic/pom.xml

@@ -166,13 +166,17 @@
             <version>${iotucy.version}</version>
             <artifactId>iot-modbus</artifactId>
         </dependency>
-
         <dependency>
             <groupId>org.codehaus.groovy</groupId>
             <artifactId>groovy</artifactId>
             <version>3.0.17</version>
         </dependency>
         <dependency>
+            <groupId>org.quartz-scheduler</groupId>
+            <artifactId>quartz</artifactId>
+            <version>2.3.2</version>
+        </dependency>
+        <dependency>
             <groupId>org.dromara</groupId>
             <artifactId>ruoyi-system</artifactId>
         </dependency>

+ 10 - 7
ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/controller/SenEventsMediaController.java

@@ -32,7 +32,7 @@ import org.dromara.common.mybatis.core.page.TableDataInfo;
 @Validated
 @RequiredArgsConstructor
 @RestController
-@RequestMapping("/dromara/eventsMedia")
+@RequestMapping("/manage/eventsMedia")
 public class SenEventsMediaController extends BaseController {
 
     private final ISenEventsMediaService senEventsMediaService;
@@ -41,7 +41,6 @@ public class SenEventsMediaController extends BaseController {
      * 查询事件媒介
 列表
      */
-    @SaCheckPermission("dromara:eventsMedia:list")
     @GetMapping("/list")
     public TableDataInfo<SenEventsMediaVo> list(SenEventsMediaBo bo, PageQuery pageQuery) {
         return senEventsMediaService.queryPageList(bo, pageQuery);
@@ -50,7 +49,6 @@ public class SenEventsMediaController extends BaseController {
     /**
      * 导出事件媒介列表
      */
-    @SaCheckPermission("dromara:eventsMedia:export")
     @Log(title = "事件媒介 ", businessType = BusinessType.EXPORT)
     @PostMapping("/export")
     public void export(SenEventsMediaBo bo, HttpServletResponse response) {
@@ -64,7 +62,6 @@ public class SenEventsMediaController extends BaseController {
      *
      * @param id 主键
      */
-    @SaCheckPermission("dromara:eventsMedia:query")
     @GetMapping("/{id}")
     public R<SenEventsMediaVo> getInfo(@NotNull(message = "主键不能为空")
                                      @PathVariable Long id) {
@@ -75,7 +72,6 @@ public class SenEventsMediaController extends BaseController {
      * 新增事件媒介
 
      */
-    @SaCheckPermission("dromara:eventsMedia:add")
     @Log(title = "事件媒介 ", businessType = BusinessType.INSERT)
     @RepeatSubmit()
     @PostMapping()
@@ -87,7 +83,6 @@ public class SenEventsMediaController extends BaseController {
      * 修改事件媒介
 
      */
-    @SaCheckPermission("dromara:eventsMedia:edit")
     @Log(title = "事件媒介 ", businessType = BusinessType.UPDATE)
     @RepeatSubmit()
     @PutMapping()
@@ -101,11 +96,19 @@ public class SenEventsMediaController extends BaseController {
      *
      * @param ids 主键串
      */
-    @SaCheckPermission("dromara:eventsMedia:remove")
     @Log(title = "事件媒介 ", businessType = BusinessType.DELETE)
     @DeleteMapping("/{ids}")
     public R<Void> remove(@NotEmpty(message = "主键不能为空")
                           @PathVariable Long[] ids) {
         return toAjax(senEventsMediaService.deleteWithValidByIds(List.of(ids), true));
     }
+    /**
+     * 获取事件媒介详细信息
+     *
+     * @param bo
+     */
+    @GetMapping("/getMediaByEventId")
+    public R<SenEventsMediaVo> getInfo(SenEventsMediaBo bo) {
+        return R.ok(senEventsMediaService.getMediaByEventId(bo));
+    }
 }

+ 12 - 0
ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/controller/SenEventsReportController.java

@@ -102,4 +102,16 @@ public class SenEventsReportController extends BaseController {
                           @PathVariable Long[] ids) {
         return toAjax(senEventsReportService.deleteWithValidByIds(List.of(ids), true));
     }
+
+    /**
+     * 批量重试失败推送
+     * @param ids 主键串
+     */
+    @Log(title = "批量重试失败推送")
+    @GetMapping("/againReport/{ids}")
+    public R<Void> againReport(@NotEmpty(message = "主键不能为空")
+                          @PathVariable Long[] ids) {
+        senEventsReportService.againReport(List.of(ids));
+        return toAjax(true);
+    }
 }

+ 10 - 0
ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/controller/SenMonitorPointController.java

@@ -7,6 +7,7 @@ import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.constraints.*;
 import cn.dev33.satoken.annotation.SaCheckPermission;
 import org.dromara.domain.SenMonitorPoint;
+import org.dromara.domain.vo.MonitorTreeVo;
 import org.dromara.system.domain.SysArea;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.validation.annotation.Validated;
@@ -112,4 +113,13 @@ public class SenMonitorPointController extends BaseController {
                           @PathVariable Long[] pointIds) {
         return toAjax(senMonitorPointService.deleteWithValidByIds(List.of(pointIds), true));
     }
+
+    /**
+     * 查询监控点树
+     */
+    @SaCheckPermission("manage:monitorPoint:list")
+    @GetMapping("/getPointsTree")
+    public R<MonitorTreeVo> getPointsTree(SenMonitorPointBo bo) {
+        return R.ok(senMonitorPointService.selectAreaTreeList(bo));
+    }
 }

+ 13 - 0
ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/controller/SenMonitorPointMediaController.java

@@ -6,6 +6,8 @@ import lombok.RequiredArgsConstructor;
 import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.constraints.*;
 import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.dromara.domain.bo.SenEventsMediaBo;
+import org.dromara.domain.vo.SenEventsMediaVo;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.validation.annotation.Validated;
 import org.dromara.common.idempotent.annotation.RepeatSubmit;
@@ -102,4 +104,15 @@ public class SenMonitorPointMediaController extends BaseController {
                           @PathVariable Long[] ids) {
         return toAjax(senMonitorPointMediaService.deleteWithValidByIds(List.of(ids), true));
     }
+
+    /**
+     * 获取事件媒介详细信息
+     *
+     * @param bo
+     */
+    @SaCheckPermission("manage:eventsMedia:query")
+    @GetMapping("/getPointMediaByPointId")
+    public R<SenMonitorPointMediaVo> getPointMediaByPointId(SenMonitorPointMediaBo bo) {
+        return R.ok(senMonitorPointMediaService.getPointMediaByPointId(bo));
+    }
 }

+ 11 - 0
ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/controller/SenMonitorTaskController.java

@@ -1,6 +1,7 @@
 package org.dromara.controller;
 
 import java.util.List;
+import java.util.Map;
 
 import lombok.RequiredArgsConstructor;
 import jakarta.servlet.http.HttpServletResponse;
@@ -102,4 +103,14 @@ public class SenMonitorTaskController extends BaseController {
                           @PathVariable Long[] ids) {
         return toAjax(senMonitorTaskService.deleteWithValidByIds(List.of(ids), true));
     }
+    /**
+     * 根据监控点id获取任务数据
+     *
+     * @param id 监控点ID
+     */
+    @SaCheckPermission("manage:monitorTask:query")
+    @GetMapping("/getTaskInfoByPointId/{id}")
+    public R<Map<String,Object>> getTaskInfoByPointId(@PathVariable Long id) {
+        return R.ok(senMonitorTaskService.getTaskInfoByPointId(id));
+    }
 }

+ 5 - 0
ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/domain/SenEvents.java

@@ -77,6 +77,11 @@ public class SenEvents implements Serializable {
     private Date createTime;
 
     /**
+     * 数据来源
+     */
+    private String datasource;
+
+    /**
      * 创建人
      */
     private Long createBy;

+ 5 - 1
ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/domain/SenEventsReport.java

@@ -33,6 +33,10 @@ public class SenEventsReport extends BaseEntity {
     private String reportPlatform;
 
     /**
+     * 事件id
+     */
+    private Long eventId;
+    /**
      * 监控点id
      */
     private Long pointId;
@@ -70,7 +74,7 @@ public class SenEventsReport extends BaseEntity {
     /**
      * 重试次数
      */
-    private Integer dealStatus;
+    private Integer retryNum;
 
 
 }

+ 6 - 6
ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/domain/bo/SenEventsReportBo.java

@@ -1,5 +1,6 @@
 package org.dromara.domain.bo;
 
+import com.alibaba.excel.annotation.ExcelProperty;
 import org.dromara.domain.SenEventsReport;
 import org.dromara.common.mybatis.core.domain.BaseEntity;
 import org.dromara.common.core.validate.AddGroup;
@@ -27,6 +28,10 @@ public class SenEventsReportBo extends BaseEntity {
     private Long id;
 
     /**
+     * 事件id
+     */
+    private Long eventId;
+    /**
      * 上报平台
      */
     @NotBlank(message = "上报平台不能为空", groups = { AddGroup.class, EditGroup.class })
@@ -53,32 +58,27 @@ public class SenEventsReportBo extends BaseEntity {
     /**
      * 数据详情
      */
-    @NotBlank(message = "数据详情不能为空", groups = { AddGroup.class, EditGroup.class })
     private String dataContent;
 
     /**
      * 数据类型
      */
-    @NotBlank(message = "数据类型不能为空", groups = { AddGroup.class, EditGroup.class })
     private String dataType;
 
     /**
      * 上报状态(0-发送成功1-发送失败)
      */
-    @NotBlank(message = "上报状态(0-发送成功1-发送失败)不能为空", groups = { AddGroup.class, EditGroup.class })
     private String reportStatus;
 
     /**
      * 描述
      */
-    @NotBlank(message = "描述不能为空", groups = { AddGroup.class, EditGroup.class })
     private String reportDesc;
 
     /**
      * 重试次数
      */
-    @NotNull(message = "重试次数不能为空", groups = { AddGroup.class, EditGroup.class })
-    private Integer dealStatus;
+    private Integer retryNum;
 
 
 }

+ 31 - 1
ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/domain/vo/SenEventsReportVo.java

@@ -1,5 +1,7 @@
 package org.dromara.domain.vo;
 
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
 import org.dromara.domain.SenEventsReport;
 import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
 import com.alibaba.excel.annotation.ExcelProperty;
@@ -10,6 +12,7 @@ import lombok.Data;
 
 import java.io.Serial;
 import java.io.Serializable;
+import java.util.Date;
 
 
 /**
@@ -39,6 +42,11 @@ public class SenEventsReportVo implements Serializable {
     private String reportPlatform;
 
     /**
+     * 事件id
+     */
+    @ExcelProperty(value = "事件id")
+    private Long eventId;
+    /**
      * 监控点id
      */
     @ExcelProperty(value = "监控点id")
@@ -85,7 +93,29 @@ public class SenEventsReportVo implements Serializable {
      * 重试次数
      */
     @ExcelProperty(value = "重试次数")
-    private Integer dealStatus;
+    private Integer retryNum;
+
+    /**
+     * 创建者
+     */
+    @ExcelProperty(value = "创建者")
+    private Long createBy;
+
+    /**
+     * 创建时间
+     */
+    @ExcelProperty(value = "创建时间")
+    private Date createTime;
 
+    /**
+     * 更新者
+     */
+    @ExcelProperty(value = "更新者")
+    private Long updateBy;
 
+    /**
+     * 更新时间
+     */
+    @ExcelProperty(value = "更新时间")
+    private Date updateTime;
 }

+ 11 - 3
ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/domain/vo/SenMonitorTaskVo.java

@@ -2,16 +2,16 @@ package org.dromara.domain.vo;
 
 import lombok.EqualsAndHashCode;
 import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.domain.SenMonitorPointMedia;
 import org.dromara.domain.SenMonitorTask;
 import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
-import com.alibaba.excel.annotation.ExcelProperty;
-import org.dromara.common.excel.annotation.ExcelDictFormat;
-import org.dromara.common.excel.convert.ExcelDictConvert;
 import io.github.linpeilie.annotations.AutoMapper;
 import lombok.Data;
 
 import java.io.Serial;
 import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
 
 
 /**
@@ -74,5 +74,13 @@ public class SenMonitorTaskVo extends BaseEntity {
      */
     private String status;
 
+    /**
+     * 离线任务关联的视频信息
+     */
+    private SenMonitorPointMedia mediaInfo;
 
+    /**
+     * 关联的算法信息
+     */
+    private List<Map<String,Object>> taskAlgorithms;
 }

+ 6 - 1
ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/mapper/SenMonitorTaskAlgorithmsMapper.java

@@ -1,9 +1,14 @@
 package org.dromara.mapper;
 
+import org.apache.ibatis.annotations.Param;
 import org.dromara.domain.SenMonitorTaskAlgorithms;
 import org.dromara.domain.vo.SenMonitorTaskAlgorithmsVo;
 import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
 
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
 /**
  * 监控点任务-算法配置Mapper接口
  *
@@ -11,5 +16,5 @@ import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
  * @date 2024-09-19
  */
 public interface SenMonitorTaskAlgorithmsMapper extends BaseMapperPlus<SenMonitorTaskAlgorithms, SenMonitorTaskAlgorithmsVo> {
-
+    List<Map<String,Object>> selectTaskAlgorithms(@Param("id") Long id);
 }

+ 2 - 0
ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/service/ISenEventsMediaService.java

@@ -77,4 +77,6 @@ public interface ISenEventsMediaService {
      * @return 是否删除成功
      */
     Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+    SenEventsMediaVo getMediaByEventId(SenEventsMediaBo bo);
 }

+ 2 - 0
ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/service/ISenEventsReportService.java

@@ -65,4 +65,6 @@ public interface ISenEventsReportService {
      * @return 是否删除成功
      */
     Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+    void againReport(Collection<Long> ids);
 }

+ 2 - 0
ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/service/ISenMonitorPointMediaService.java

@@ -65,4 +65,6 @@ public interface ISenMonitorPointMediaService {
      * @return 是否删除成功
      */
     Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+    SenMonitorPointMediaVo getPointMediaByPointId(SenMonitorPointMediaBo bo);
 }

+ 3 - 0
ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/service/ISenMonitorPointService.java

@@ -1,6 +1,7 @@
 package org.dromara.service;
 
 import org.dromara.domain.SenMonitorPoint;
+import org.dromara.domain.vo.MonitorTreeVo;
 import org.dromara.domain.vo.SenMonitorPointVo;
 import org.dromara.domain.bo.SenMonitorPointBo;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
@@ -69,4 +70,6 @@ public interface ISenMonitorPointService {
     Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
 
     List<SenMonitorPoint> getListByAreaId(SenMonitorPointBo bo);
+
+    MonitorTreeVo selectAreaTreeList(SenMonitorPointBo bo);
 }

+ 3 - 0
ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/service/ISenMonitorTaskService.java

@@ -7,6 +7,7 @@ import org.dromara.common.mybatis.core.page.PageQuery;
 
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 任务管理Service接口
@@ -65,4 +66,6 @@ public interface ISenMonitorTaskService {
      * @return 是否删除成功
      */
     Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+    Map<String, Object> getTaskInfoByPointId(Long id);
 }

+ 7 - 0
ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/service/impl/SenEventsMediaServiceImpl.java

@@ -142,4 +142,11 @@ public class SenEventsMediaServiceImpl implements ISenEventsMediaService {
         }
         return baseMapper.deleteByIds(ids) > 0;
     }
+
+    @Override
+    public SenEventsMediaVo getMediaByEventId(SenEventsMediaBo bo) {
+        LambdaQueryWrapper<SenEventsMedia> lqw = new LambdaQueryWrapper<>();
+        lqw.eq(SenEventsMedia::getEventId, bo.getEventId());
+        return baseMapper.selectVoOne(lqw);
+    }
 }

+ 61 - 4
ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/service/impl/SenEventsReportServiceImpl.java

@@ -8,6 +8,8 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import lombok.RequiredArgsConstructor;
+import org.dromara.domain.SenEvents;
+import org.dromara.mapper.SenEventsMapper;
 import org.springframework.stereotype.Service;
 import org.dromara.domain.bo.SenEventsReportBo;
 import org.dromara.domain.vo.SenEventsReportVo;
@@ -15,6 +17,10 @@ import org.dromara.domain.SenEventsReport;
 import org.dromara.mapper.SenEventsReportMapper;
 import org.dromara.service.ISenEventsReportService;
 
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.Date;
 import java.util.List;
 import java.util.Map;
 import java.util.Collection;
@@ -30,6 +36,7 @@ import java.util.Collection;
 public class SenEventsReportServiceImpl implements ISenEventsReportService {
 
     private final SenEventsReportMapper baseMapper;
+    private final SenEventsMapper senEventsMapper;
 
     /**
      * 查询推送记录
@@ -38,7 +45,7 @@ public class SenEventsReportServiceImpl implements ISenEventsReportService {
      * @return 推送记录
      */
     @Override
-    public SenEventsReportVo queryById(Long id){
+    public SenEventsReportVo queryById(Long id) {
         return baseMapper.selectVoById(id);
     }
 
@@ -79,7 +86,7 @@ public class SenEventsReportServiceImpl implements ISenEventsReportService {
         lqw.eq(StringUtils.isNotBlank(bo.getDataType()), SenEventsReport::getDataType, bo.getDataType());
         lqw.eq(StringUtils.isNotBlank(bo.getReportStatus()), SenEventsReport::getReportStatus, bo.getReportStatus());
         lqw.eq(StringUtils.isNotBlank(bo.getReportDesc()), SenEventsReport::getReportDesc, bo.getReportDesc());
-        lqw.eq(bo.getDealStatus() != null, SenEventsReport::getDealStatus, bo.getDealStatus());
+        lqw.eq(bo.getRetryNum() != null, SenEventsReport::getRetryNum, bo.getRetryNum());
         return lqw;
     }
 
@@ -116,7 +123,7 @@ public class SenEventsReportServiceImpl implements ISenEventsReportService {
     /**
      * 保存前的数据校验
      */
-    private void validEntityBeforeSave(SenEventsReport entity){
+    private void validEntityBeforeSave(SenEventsReport entity) {
         //TODO 做一些数据校验,如唯一约束
     }
 
@@ -129,9 +136,59 @@ public class SenEventsReportServiceImpl implements ISenEventsReportService {
      */
     @Override
     public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
-        if(isValid){
+        if (isValid) {
             //TODO 做一些业务上的校验,判断是否需要校验
         }
         return baseMapper.deleteByIds(ids) > 0;
     }
+    /**
+     * 批量重试失败推送记录
+     * @param ids 主键集合
+     */
+    @Override
+    public void againReport(Collection<Long> ids) {
+        for (Long id : ids) {
+            SenEventsReport senEventsReport = baseMapper.selectById(id);
+            senEventsReport.setRetryNum(senEventsReport.getRetryNum() + 1);
+            senEventsReport.setReportStatus("0");
+            baseMapper.updateById(senEventsReport);
+        }
+    }
+    public void reportEvents() {
+        // 查询未上报的数据
+        LambdaQueryWrapper<SenEvents> queryWrapper = new LambdaQueryWrapper<>();
+        // 获取当天的起始时间(00:00:00)
+        LocalDateTime dayStart = LocalDateTime.of(LocalDate.now(), LocalTime.MIN);
+        // 获取当天的结束时间(23:59:59)
+        LocalDateTime dayEnd = LocalDateTime.of(LocalDate.now(), LocalTime.MAX);
+        // 设置查询条件,查询当天的数据
+        queryWrapper.ge(SenEvents::getCreateTime, dayStart)
+                .le(SenEvents::getCreateTime, dayEnd);
+        List<SenEvents> unreportedEvents = senEventsMapper.selectList(queryWrapper);
+        for (SenEvents event : unreportedEvents) {
+            // 检查是否已经上报过
+            LambdaQueryWrapper<SenEventsReport> reportQueryWrapper = new LambdaQueryWrapper<>();
+            reportQueryWrapper.eq(SenEventsReport::getEventId, event.getId());
+            SenEventsReport existingReport = baseMapper.selectOne(reportQueryWrapper);
+            if (existingReport == null) {
+                // 上报数据
+                String reportPlatform = "examplePlatform"; // 示例平台名
+                String dataContent = "{}"; // 将SenEvent对象转换为JSON字符串
+                SenEventsReport newReport = new SenEventsReport();
+                newReport.setReportPlatform(reportPlatform);
+                newReport.setPointId(event.getPointId());
+                newReport.setPointName(event.getPointName());
+                newReport.setEventId(event.getId());
+                newReport.setEventName(event.getEventName());
+                newReport.setDataContent(dataContent);
+                newReport.setDataType("application/json");
+                newReport.setReportStatus("0"); // 假设0表示成功
+                newReport.setReportDesc("上报成功");
+                newReport.setRetryNum(0);
+                newReport.setCreateBy(1L);
+                newReport.setCreateTime(new Date());
+                baseMapper.insert(newReport);
+            }
+        }
+    }
 }

+ 10 - 2
ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/service/impl/SenEventsServiceImpl.java

@@ -320,10 +320,18 @@ public class SenEventsServiceImpl implements ISenEventsService {
     @Override
     public TableDataInfo<SenEventsVo> eventsMediaList(SenEventsBo bo, PageQuery pageQuery) {
         LambdaQueryWrapper<SenEvents> queryWrapper = new LambdaQueryWrapper<>();
+        String[] eventTypes = null;
+        if(StringUtils.isNotBlank(bo.getEventType())){
+            eventTypes= bo.getEventType().split(",");
+        }
+        String[] eventCategorys = null;
+        if(StringUtils.isNotBlank(bo.getEventCategory())){
+            eventCategorys= bo.getEventCategory().split(",");
+        }
         queryWrapper.eq(bo.getPointId() != null, SenEvents::getPointId, bo.getPointId());
-        queryWrapper.eq(StringUtils.isNotBlank(bo.getEventCategory()), SenEvents::getEventCategory, bo.getEventCategory());
+        queryWrapper.in(eventCategorys!=null, SenEvents::getEventCategory, (Object[]) eventCategorys);
         queryWrapper.eq(StringUtils.isNotBlank(bo.getDealStatus()), SenEvents::getDealStatus, bo.getDealStatus());
-        queryWrapper.in(StringUtils.isNotBlank(bo.getEventType()), SenEvents::getEventType, bo.getEventType());
+        queryWrapper.in(eventTypes!=null, SenEvents::getEventType, (Object[]) eventTypes);
         queryWrapper.ge(StringUtils.isNotBlank((CharSequence) bo.getParams().get("startDate")), SenEvents::getCreateTime, bo.getParams().get("startDate"))
                 .le(StringUtils.isNotBlank((CharSequence) bo.getParams().get("endDate")), SenEvents::getCreateTime, bo.getParams().get("endDate"));
         queryWrapper.orderByDesc(SenEvents::getCreateTime);

+ 6 - 0
ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/service/impl/SenMonitorPointMediaServiceImpl.java

@@ -131,4 +131,10 @@ public class SenMonitorPointMediaServiceImpl implements ISenMonitorPointMediaSer
         }
         return baseMapper.deleteByIds(ids) > 0;
     }
+
+    @Override
+    public SenMonitorPointMediaVo getPointMediaByPointId(SenMonitorPointMediaBo bo) {
+        LambdaQueryWrapper<SenMonitorPointMedia> lqw = new LambdaQueryWrapper<>();
+        return baseMapper.selectVoOne(lqw.eq(SenMonitorPointMedia::getPointId, bo.getPointId()));
+    }
 }

+ 71 - 73
ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/service/impl/SenMonitorPointServiceImpl.java

@@ -1,6 +1,5 @@
 package org.dromara.service.impl;
 
-import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import org.dromara.common.core.utils.MapstructUtils;
 import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
@@ -10,10 +9,10 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import lombok.RequiredArgsConstructor;
 import org.dromara.domain.SenMonitorPointMedia;
+import org.dromara.domain.vo.MonitorTreeVo;
 import org.dromara.mapper.SenMonitorPointMediaMapper;
 import org.dromara.system.domain.SysArea;
 import org.dromara.system.mapper.SysAreaMapper;
-import org.springframework.beans.BeanUtils;
 import org.springframework.stereotype.Service;
 import org.dromara.domain.bo.SenMonitorPointBo;
 import org.dromara.domain.vo.SenMonitorPointVo;
@@ -22,7 +21,6 @@ import org.dromara.mapper.SenMonitorPointMapper;
 import org.dromara.service.ISenMonitorPointService;
 
 import java.util.*;
-import java.util.stream.Collectors;
 
 /**
  * 监控点位信息Service业务层处理
@@ -171,75 +169,75 @@ public class SenMonitorPointServiceImpl implements ISenMonitorPointService {
     }
 
 
-//    @Override
-//    public MonitorTreeVo selectAreaTreeList(SenMonitorPointBo bo) {
-//        // 查询所有区域数据
-//        List<SysArea> allAreas = areaMapper.selectList(Wrappers.<SysArea>lambdaQuery().orderByAsc(SysArea::getOrderNum));
-//        // 构建节点映射表
-//        Map<Long, MonitorTreeVo> nodeMap = new HashMap<>();
-//        for (SysArea area : allAreas) {
-//            MonitorTreeVo node = new MonitorTreeVo(area);
-//            nodeMap.put(area.getAreaId(), node);
-//        }
-//
-//        // 构建树形结构
-//        for (SysArea area : allAreas) {
-//            Long parentId = area.getParentId();
-//            if (parentId != 0) {
-//                MonitorTreeVo parentNode = nodeMap.get(parentId);
-//                if (parentNode != null) {
-//                    parentNode.getChildren().add(nodeMap.get(area.getAreaId()));
-//                }
-//            }
-//        }
-//
-//        // 查找根节点
-//        MonitorTreeVo root = null;
-//        for (MonitorTreeVo node : nodeMap.values()) {
-//            if (node.getParentId() == 0) {
-//                if (root == null) {
-//                    root = node;
-//                }
-//            }
-//        }
-//
-//        // 关联监控点数据
-//        associateMonitorPoints(root, 3, bo);
-//        return root;
-//    }
-//
-//    private void associateMonitorPoints(MonitorTreeVo node, Integer level, SenMonitorPointBo bo) {
-//        if (node != null) {
-//            if (node.getAreaLevel().equals(level)) {
-//                List<SenMonitorPoint> points = baseMapper.selectList(
-//                        Wrappers.<SenMonitorPoint>lambdaQuery()
-//                                .eq(SenMonitorPoint::getAreaId, node.getAreaId())
-//                                .like(StringUtils.isNotBlank(bo.getPointName()),SenMonitorPoint::getPointName, bo.getPointName())
-//                                .orderByAsc(SenMonitorPoint::getPointId)
-//                );
-//                List<MonitorTreeVo> treePoints = getMonitorTreeVos(points);
-//                node.setChildren(treePoints);
-//
-//            }
-//            for (MonitorTreeVo child : node.getChildren()) {
-//                associateMonitorPoints(child, level, bo);
-//            }
-//        }
-//    }
-//
-//    @NotNull
-//    private static List<MonitorTreeVo> getMonitorTreeVos(List<SenMonitorPoint> points) {
-//        List<MonitorTreeVo> treePoints = new ArrayList<>();
-//        for (SenMonitorPoint point : points) {
-//            SysArea area = new SysArea();
-//            area.setAreaId(point.getPointId());
-//            area.setAreaName(point.getPointName());
-//            area.setParentId(point.getAreaId());
-//            area.setAreaLevel(4);
-//            MonitorTreeVo tree = new MonitorTreeVo(area);
-//            treePoints.add(tree);
-//        }
-//        return treePoints;
-//    }
+    @Override
+    public MonitorTreeVo selectAreaTreeList(SenMonitorPointBo bo) {
+        // 查询所有区域数据
+        List<SysArea> allAreas = areaMapper.selectList(Wrappers.<SysArea>lambdaQuery().orderByAsc(SysArea::getOrderNum));
+        // 构建节点映射表
+        Map<Long, MonitorTreeVo> nodeMap = new HashMap<>();
+        for (SysArea area : allAreas) {
+            MonitorTreeVo node = new MonitorTreeVo(area);
+            nodeMap.put(area.getAreaId(), node);
+        }
+
+        // 构建树形结构
+        for (SysArea area : allAreas) {
+            Long parentId = area.getParentId();
+            if (parentId != 0) {
+                MonitorTreeVo parentNode = nodeMap.get(parentId);
+                if (parentNode != null) {
+                    parentNode.getChildren().add(nodeMap.get(area.getAreaId()));
+                }
+            }
+        }
+
+        // 查找根节点
+        MonitorTreeVo root = null;
+        for (MonitorTreeVo node : nodeMap.values()) {
+            if (node.getParentId() == 0) {
+                if (root == null) {
+                    root = node;
+                }
+            }
+        }
+
+        // 关联监控点数据
+        associateMonitorPoints(root, 3, bo);
+        return root;
+    }
+
+    private void associateMonitorPoints(MonitorTreeVo node, Integer level, SenMonitorPointBo bo) {
+        if (node != null) {
+            if (node.getAreaLevel().equals(level)) {
+                List<SenMonitorPoint> points = baseMapper.selectList(
+                        Wrappers.<SenMonitorPoint>lambdaQuery()
+                                .eq(SenMonitorPoint::getAreaId, node.getAreaId())
+                                .eq(SenMonitorPoint::getVideoType,"1")
+                                .like(StringUtils.isNotBlank(bo.getPointName()),SenMonitorPoint::getPointName, bo.getPointName())
+                                .orderByAsc(SenMonitorPoint::getPointId)
+                );
+                List<MonitorTreeVo> treePoints = getMonitorTreeVos(points);
+                node.setChildren(treePoints);
+
+            }
+            for (MonitorTreeVo child : node.getChildren()) {
+                associateMonitorPoints(child, level, bo);
+            }
+        }
+    }
+
+    private static List<MonitorTreeVo> getMonitorTreeVos(List<SenMonitorPoint> points) {
+        List<MonitorTreeVo> treePoints = new ArrayList<>();
+        for (SenMonitorPoint point : points) {
+            SysArea area = new SysArea();
+            area.setAreaId(point.getPointId());
+            area.setAreaName(point.getPointName());
+            area.setParentId(point.getAreaId());
+            area.setAreaLevel(4);
+            MonitorTreeVo tree = new MonitorTreeVo(area);
+            treePoints.add(tree);
+        }
+        return treePoints;
+    }
 
 }

+ 28 - 3
ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/service/impl/SenMonitorTaskServiceImpl.java

@@ -8,6 +8,11 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import lombok.RequiredArgsConstructor;
+import org.dromara.domain.SenMonitorPointMedia;
+import org.dromara.domain.SenMonitorTaskAlgorithms;
+import org.dromara.domain.vo.SenMonitorPointMediaVo;
+import org.dromara.mapper.SenMonitorPointMediaMapper;
+import org.dromara.mapper.SenMonitorTaskAlgorithmsMapper;
 import org.springframework.stereotype.Service;
 import org.dromara.domain.bo.SenMonitorTaskBo;
 import org.dromara.domain.vo.SenMonitorTaskVo;
@@ -15,9 +20,7 @@ import org.dromara.domain.SenMonitorTask;
 import org.dromara.mapper.SenMonitorTaskMapper;
 import org.dromara.service.ISenMonitorTaskService;
 
-import java.util.List;
-import java.util.Map;
-import java.util.Collection;
+import java.util.*;
 
 /**
  * 任务管理Service业务层处理
@@ -30,6 +33,8 @@ import java.util.Collection;
 public class SenMonitorTaskServiceImpl implements ISenMonitorTaskService {
 
     private final SenMonitorTaskMapper baseMapper;
+    private final SenMonitorTaskAlgorithmsMapper taskAlgorithmsMapper;
+    private final SenMonitorPointMediaMapper mediaMapper;
 
     /**
      * 查询任务管理
@@ -130,4 +135,24 @@ public class SenMonitorTaskServiceImpl implements ISenMonitorTaskService {
         }
         return baseMapper.deleteByIds(ids) > 0;
     }
+
+    @Override
+    public Map<String, Object> getTaskInfoByPointId(Long id) {
+        Map<String,Object> map = new HashMap<>();
+        LambdaQueryWrapper<SenMonitorTask> lqw = Wrappers.lambdaQuery();
+        lqw.eq(SenMonitorTask::getPointId, id);
+         List<SenMonitorTaskVo> senMonitorTaskList = baseMapper.selectVoList(lqw);
+
+         if(senMonitorTaskList.size()>0){
+             for (SenMonitorTaskVo senMonitorTaskVo : senMonitorTaskList) {
+                 map.put("videoType",senMonitorTaskVo.getVideoType());
+                 LambdaQueryWrapper<SenMonitorPointMedia> media = Wrappers.lambdaQuery();
+                 media.eq(SenMonitorPointMedia::getId, senMonitorTaskVo.getMediaId());
+                 senMonitorTaskVo.setMediaInfo(mediaMapper.selectOne(media));
+                 senMonitorTaskVo.setTaskAlgorithms(taskAlgorithmsMapper.selectTaskAlgorithms(senMonitorTaskVo.getId()));
+             }
+         }
+        map.put("list",senMonitorTaskList);
+        return map;
+    }
 }

+ 17 - 0
ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/task/EventReportTask.java

@@ -0,0 +1,17 @@
+package org.dromara.task;
+import lombok.extern.slf4j.Slf4j;
+import lombok.RequiredArgsConstructor;
+import org.dromara.service.impl.SenEventsReportServiceImpl;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class EventReportTask {
+    private  final SenEventsReportServiceImpl senEventsReportService;
+    @Scheduled(cron = "0 0/10 * * * ?")
+    public void reportJob() {
+        senEventsReportService.reportEvents();
+    }
+
+}

+ 7 - 13
ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/task/DemoTask.java → ruoyi-modules/ruoyi-traffic/src/main/java/org/dromara/task/EventTask.java

@@ -1,5 +1,5 @@
 package org.dromara.task;
-
+import lombok.extern.slf4j.Slf4j;
 import org.dromara.domain.SenEvents;
 import org.dromara.domain.SenEventsMedia;
 import org.dromara.domain.SenFlowDetail;
@@ -7,29 +7,24 @@ import org.dromara.mapper.SenEventsMapper;
 import org.dromara.mapper.SenEventsMediaMapper;
 import org.dromara.mapper.SenFlowDetailMapper;
 import org.dromara.mapper.SenMonitorTaskMapper;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.scheduling.annotation.Scheduled;
-import org.springframework.stereotype.Component;
 import lombok.RequiredArgsConstructor;
-
 import java.util.Date;
 import java.util.List;
-
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+@Slf4j
 @Component
 @RequiredArgsConstructor
-public class DemoTask {
-    private static final Logger logger = LoggerFactory.getLogger(DemoTask.class);
-
+public class EventTask {
     private final SenMonitorTaskMapper taskMapper;
     private final SenEventsMapper eventMapper;
     private final SenFlowDetailMapper flowMapper;
     private final SenEventsMediaMapper eventsMediaMapper;
 
-            @Scheduled(cron = "0 0/30 * * * ?")
+    @Scheduled(cron = "0 0/30 * * * ?")
 //    @Scheduled(cron = "0 15 10 ? * *")
 //    @Scheduled(cron = "*/10 * * * * *")
-    public void executeHourlyTask() {
+    public void AiToEventJob() {
         List<SenEvents> taskList = taskMapper.selectScheduledTask();
         for (SenEvents event : taskList) {
             boolean flag = eventMapper.insert(event)>0;
@@ -47,7 +42,6 @@ public class DemoTask {
         for (SenFlowDetail flowDetail : flowList) {
             flowMapper.insert(flowDetail);
         }
-
     }
 
 }

+ 14 - 0
ruoyi-modules/ruoyi-traffic/src/main/resources/mapper/SenMonitorTaskAlgorithmsMapper.xml

@@ -4,4 +4,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="org.dromara.mapper.SenMonitorTaskAlgorithmsMapper">
 
+    <select id="selectTaskAlgorithms" resultType="java.util.Map">
+        SELECT
+        al.*,
+        config.algorithm_name,
+        config.algorithm_category,
+        dict.dict_label AS algorithm_category_name
+        FROM
+        sen_monitor_task_algorithms al
+        LEFT JOIN sen_algorithm_config config ON al.algorithm_id = config.id
+        LEFT JOIN sys_dict_data dict ON dict_type = 'algorithm_category'
+        AND config.algorithm_category = dict.dict_value
+        WHERE
+        task_id = ${id}
+    </select>
 </mapper>

+ 3 - 0
traffic-ui/.env.development

@@ -26,3 +26,6 @@ VITE_APP_CLIENT_ID = 'e5cd7e4891bf95d1d19206ce24a7b32e'
 
 # websocket 开关(开发环境默认关闭ws 因vite的bug导致如ws无法连接则会崩溃)
 VITE_APP_WEBSOCKET = false
+
+#文件服务器路径
+VITE_APP_FILEPATH='http://127.0.0.1:8080'

+ 3 - 0
traffic-ui/.env.production

@@ -26,3 +26,6 @@ VITE_APP_CLIENT_ID = 'e5cd7e4891bf95d1d19206ce24a7b32e'
 
 # websocket 开关
 VITE_APP_WEBSOCKET = true
+
+#文件服务器路径
+VITE_APP_FILEPATH='http://127.0.0.1:8080'

File diff suppressed because it is too large
+ 0 - 191
traffic-ui/html/ie.html


+ 1 - 0
traffic-ui/index.html

@@ -7,6 +7,7 @@
     <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
     <link rel="icon" href="/favicon.ico" />
     <script type="text/javascript" src="https://api.map.baidu.com/api?v=1.0&type=webgl&ak=KumIikHq4H2BQnbRrxMktUaS4peySWGD"></script>
+    <script type="text/javascript" src="/js/jessibuca/jessibuca.js"></script>
     <title>智慧交通智能认知系统</title>
 
     <!--[if lt IE 11

File diff suppressed because it is too large
+ 0 - 0
traffic-ui/public/js/jessibuca/decoder.js


BIN
traffic-ui/public/js/jessibuca/decoder.wasm


+ 637 - 0
traffic-ui/public/js/jessibuca/jessibuca.d.ts

@@ -0,0 +1,637 @@
+declare namespace Jessibuca {
+
+    /** 超时信息 */
+    enum TIMEOUT {
+        /** 当play()的时候,如果没有数据返回 */
+        loadingTimeout = 'loadingTimeout',
+        /** 当播放过程中,如果超过timeout之后没有数据渲染 */
+        delayTimeout = 'delayTimeout',
+    }
+
+    /** 错误信息 */
+    enum ERROR {
+        /** 播放错误,url 为空的时候,调用 play 方法 */
+        playError = 'playError',
+        /** http 请求失败 */
+        fetchError = 'fetchError',
+        /** websocket 请求失败 */
+        websocketError = 'websocketError',
+        /** webcodecs 解码 h265 失败 */
+        webcodecsH265NotSupport = 'webcodecsH265NotSupport',
+        /** mediaSource 解码 h265 失败 */
+        mediaSourceH265NotSupport = 'mediaSourceH265NotSupport',
+        /** wasm 解码失败 */
+        wasmDecodeError = 'wasmDecodeError',
+    }
+
+    interface Config {
+        /**
+         * 播放器容器
+         * *  若为 string ,则底层调用的是 document.getElementById('id')
+         * */
+        container: HTMLElement | string;
+        /**
+         * 设置最大缓冲时长,单位秒,播放器会自动消除延迟
+         */
+        videoBuffer?: number;
+        /**
+         * worker地址
+         * *  默认引用的是根目录下面的decoder.js文件 ,decoder.js 与 decoder.wasm文件必须是放在同一个目录下面。 */
+        decoder?: string;
+        /**
+         * 是否不使用离屏模式(提升渲染能力)
+         */
+        forceNoOffscreen?: boolean;
+        /**
+         * 是否开启当页面的'visibilityState'变为'hidden'的时候,自动暂停播放。
+         */
+        hiddenAutoPause?: boolean;
+        /**
+         * 是否有音频,如果设置`false`,则不对音频数据解码,提升性能。
+         */
+        hasAudio?: boolean;
+        /**
+         * 设置旋转角度,只支持,0(默认),180,270 三个值
+         */
+        rotate?: boolean;
+        /**
+         * 1. 当为`true`的时候:视频画面做等比缩放后,高或宽对齐canvas区域,画面不被拉伸,但有黑边。 等同于 `setScaleMode(1)`
+         * 2. 当为`false`的时候:视频画面完全填充canvas区域,画面会被拉伸。等同于 `setScaleMode(0)`
+         */
+        isResize?: boolean;
+        /**
+         * 1. 当为`true`的时候:视频画面做等比缩放后,完全填充canvas区域,画面不被拉伸,没有黑边,但画面显示不全。等同于 `setScaleMode(2)`
+         */
+        isFullResize?: boolean;
+        /**
+         * 1. 当为`true`的时候:ws协议不检验是否以.flv为依据,进行协议解析。
+         */
+        isFlv?: boolean;
+        /**
+         * 是否开启控制台调试打
+         */
+        debug?: boolean;
+        /**
+         * 1. 设置超时时长, 单位秒
+         * 2. 在连接成功之前(loading)和播放中途(heart),如果超过设定时长无数据返回,则回调timeout事件
+         */
+        timeout?: number;
+        /**
+         * 1. 设置超时时长, 单位秒
+         * 2. 在连接成功之前,如果超过设定时长无数据返回,则回调timeout事件
+         */
+        heartTimeout?: number;
+        /**
+         * 1. 设置超时时长, 单位秒
+         * 2. 在连接成功之前,如果超过设定时长无数据返回,则回调timeout事件
+         */
+        loadingTimeout?: number;
+        /**
+         * 是否支持屏幕的双击事件,触发全屏,取消全屏事件
+         */
+        supportDblclickFullscreen?: boolean;
+        /**
+         * 是否显示网
+         */
+        showBandwidth?: boolean;
+        /**
+         * 配置操作按钮
+         */
+        operateBtns?: {
+            /** 是否显示全屏按钮 */
+            fullscreen?: boolean;
+            /** 是否显示截图按钮 */
+            screenshot?: boolean;
+            /** 是否显示播放暂停按钮 */
+            play?: boolean;
+            /** 是否显示声音按钮 */
+            audio?: boolean;
+            /** 是否显示录制按 */
+            record?: boolean;
+        };
+        /**
+         * 开启屏幕常亮,在手机浏览器上, canvas标签渲染视频并不会像video标签那样保持屏幕常亮
+         */
+        keepScreenOn?: boolean;
+        /**
+         * 是否开启声音,默认是关闭声音播放的
+         */
+        isNotMute?: boolean;
+        /**
+         * 加载过程中文案
+         */
+        loadingText?: string;
+        /**
+         * 背景图片
+         */
+        background?: string;
+        /**
+         * 是否开启MediaSource硬解码
+         * * 视频编码只支持H.264视频(Safari on iOS不支持)
+         * * 不支持 forceNoOffscreen 为 false (开启离屏渲染)
+         */
+        useMSE?: boolean;
+        /**
+         * 是否开启Webcodecs硬解码
+         * *  视频编码只支持H.264视频 (需在chrome 94版本以上,需要https或者localhost环境)
+         * *  支持 forceNoOffscreen 为 false (开启离屏渲染)
+         * */
+        useWCS?: boolean;
+        /**
+         * 是否开启键盘快捷键
+         * 目前支持的键盘快捷键有:esc -> 退出全屏;arrowUp -> 声音增加;arrowDown -> 声音减少;
+         */
+        hotKey?: boolean;
+        /**
+         *  在使用MSE或者Webcodecs 播放H265的时候,是否自动降级到wasm模式。
+         *  设置为false 则直接关闭播放,抛出Error 异常,设置为true 则会自动切换成wasm模式播放。
+         */
+        autoWasm?: boolean;
+        /**
+         * heartTimeout 心跳超时之后自动再播放,不再抛出异常,而直接重新播放视频地址。
+         */
+        heartTimeoutReplay?: boolean,
+        /**
+         * heartTimeoutReplay 从试次数,超过之后,不再自动播放
+         */
+        heartTimeoutReplayTimes?: number,
+        /**
+         * loadingTimeout loading之后自动再播放,不再抛出异常,而直接重新播放视频地址。
+         */
+        loadingTimeoutReplay?: boolean,
+        /**
+         * heartTimeoutReplay 从试次数,超过之后,不再自动播放
+         */
+        loadingTimeoutReplayTimes?: number
+        /**
+         * wasm解码报错之后,不再抛出异常,而是直接重新播放视频地址。
+         */
+        wasmDecodeErrorReplay?: boolean,
+        /**
+         * https://github.com/langhuihui/jessibuca/issues/152 解决方案
+         * 例如:WebGL图像预处理默认每次取4字节的数据,但是540x960分辨率下的U、V分量宽度是540/2=270不能被4整除,导致绿屏。
+         */
+        openWebglAlignment?: boolean
+    }
+}
+
+
+declare class Jessibuca {
+
+    constructor(config?: Jessibuca.Config);
+
+    /**
+     * 是否开启控制台调试打印
+     @example
+     // 开启
+     jessibuca.setDebug(true)
+     // 关闭
+     jessibuca.setDebug(false)
+     */
+    setDebug(flag: boolean): void;
+
+    /**
+     * 静音
+     @example
+     jessibuca.mute()
+     */
+    mute(): void;
+
+    /**
+     * 取消静音
+     @example
+     jessibuca.cancelMute()
+     */
+    cancelMute(): void;
+
+    /**
+     * 留给上层用户操作来触发音频恢复的方法。
+     *
+     * iPhone,chrome等要求自动播放时,音频必须静音,需要由一个真实的用户交互操作来恢复,不能使用代码。
+     *
+     * https://developers.google.com/web/updates/2017/09/autoplay-policy-changes
+     */
+    audioResume(): void;
+
+    /**
+     *
+     * 设置超时时长, 单位秒
+     * 在连接成功之前和播放中途,如果超过设定时长无数据返回,则回调timeout事件
+
+     @example
+     jessibuca.setTimeout(10)
+
+     jessibuca.on('timeout',function(){
+        //
+    });
+     */
+    setTimeout(): void;
+
+    /**
+     * @param mode
+     *      0 视频画面完全填充canvas区域,画面会被拉伸  等同于参数 `isResize` 为false
+     *
+     *      1 视频画面做等比缩放后,高或宽对齐canvas区域,画面不被拉伸,但有黑边 等同于参数 `isResize` 为true
+     *
+     *      2 视频画面做等比缩放后,完全填充canvas区域,画面不被拉伸,没有黑边,但画面显示不全 等同于参数 `isFullResize` 为true
+     @example
+     jessibuca.setScaleMode(0)
+
+     jessibuca.setScaleMode(1)
+
+     jessibuca.setScaleMode(2)
+     */
+    setScaleMode(mode: number): void;
+
+    /**
+     * 暂停播放
+     *
+     * 可以在pause 之后,再调用 `play()`方法就继续播放之前的流。
+     @example
+     jessibuca.pause().then(()=>{
+        console.log('pause success')
+
+        jessibuca.play().then(()=>{
+
+        }).catch((e)=>{
+
+        })
+
+    }).catch((e)=>{
+        console.log('pause error',e);
+    })
+     */
+    pause(): Promise<void>;
+
+    /**
+     * 关闭视频,不释放底层资源
+     @example
+     jessibuca.close();
+     */
+    close(): void;
+
+    /**
+     * 关闭视频,释放底层资源
+     @example
+     jessibuca.destroy()
+     */
+    destroy(): void;
+
+    /**
+     * 清理画布为黑色背景
+     @example
+     jessibuca.clearView()
+     */
+    clearView(): void;
+
+    /**
+     * 播放视频
+     @example
+
+     jessibuca.play('url').then(()=>{
+        console.log('play success')
+    }).catch((e)=>{
+        console.log('play error',e)
+    })
+     //
+     jessibuca.play()
+     */
+    play(url?: string): Promise<void>;
+
+    /**
+     * 重新调整视图大小
+     */
+    resize(): void;
+
+    /**
+     * 设置最大缓冲时长,单位秒,播放器会自动消除延迟。
+     *
+     * 等同于 `videoBuffer` 参数。
+     *
+     @example
+     // 设置 200ms 缓冲
+     jessibuca.setBufferTime(0.2)
+     */
+    setBufferTime(time: number): void;
+
+    /**
+     * 设置旋转角度,只支持,0(默认) ,180,270 三个值。
+     *
+     * > 可用于实现监控画面小窗和全屏效果,由于iOS没有全屏API,此方法可以模拟页面内全屏效果而且多端效果一致。   *
+     @example
+     jessibuca.setRotate(0)
+
+     jessibuca.setRotate(90)
+
+     jessibuca.setRotate(270)
+     */
+    setRotate(deg: number): void;
+
+    /**
+     *
+     * 设置音量大小,取值0 — 1
+     *
+     * > 区别于 mute 和 cancelMute 方法,虽然设置setVolume(0) 也能达到 mute方法,但是mute 方法是不调用底层播放音频的,能提高性能。而setVolume(0)只是把声音设置为0 ,以达到效果。
+     * @param volume 当为0时,完全无声;当为1时,最大音量,默认值
+     @example
+     jessibuca.setVolume(0.2)
+
+     jessibuca.setVolume(0)
+
+     jessibuca.setVolume(1)
+     */
+    setVolume(volume: number): void;
+
+    /**
+     * 返回是否加载完毕
+     @example
+     var result = jessibuca.hasLoaded()
+     console.log(result) // true
+     */
+    hasLoaded(): boolean;
+
+    /**
+     * 开启屏幕常亮,在手机浏览器上, canvas标签渲染视频并不会像video标签那样保持屏幕常亮。
+     * H5目前在chrome\edge 84, android chrome 84及以上有原生亮屏API, 需要是https页面
+     * 其余平台为模拟实现,此时为兼容实现,并不保证所有浏览器都支持
+     @example
+     jessibuca.setKeepScreenOn()
+     */
+    setKeepScreenOn(): boolean;
+
+    /**
+     * 全屏(取消全屏)播放视频
+     @example
+     jessibuca.setFullscreen(true)
+     //
+     jessibuca.setFullscreen(false)
+     */
+    setFullscreen(flag: boolean): void;
+
+    /**
+     *
+     * 截图,调用后弹出下载框保存截图
+     * @param filename 可选参数, 保存的文件名, 默认 `时间戳`
+     * @param format   可选参数, 截图的格式,可选png或jpeg或者webp ,默认 `png`
+     * @param quality  可选参数, 当格式是jpeg或者webp时,压缩质量,取值0 ~ 1 ,默认 `0.92`
+     * @param type 可选参数, 可选download或者base64或者blob,默认`download`
+
+     @example
+
+     jessibuca.screenshot("test","png",0.5)
+
+     const base64 = jessibuca.screenshot("test","png",0.5,'base64')
+
+     const fileBlob = jessibuca.screenshot("test",'blob')
+     */
+    screenshot(filename?: string, format?: string, quality?: number, type?: string): void;
+
+    /**
+     * 开始录制。
+     * @param fileName 可选,默认时间戳
+     * @param fileType 可选,默认webm,支持webm 和mp4 格式
+
+     @example
+     jessibuca.startRecord('xxx','webm')
+     */
+    startRecord(fileName: string, fileType: string): void;
+
+    /**
+     * 暂停录制并下载。
+     @example
+     jessibuca.stopRecordAndSave()
+     */
+    stopRecordAndSave(): void;
+
+    /**
+     * 返回是否正在播放中状态。
+     @example
+     var result = jessibuca.isPlaying()
+     console.log(result) // true
+     */
+    isPlaying(): boolean;
+
+    /**
+     *   返回是否静音。
+     @example
+     var result = jessibuca.isMute()
+     console.log(result) // true
+     */
+    isMute(): boolean;
+
+    /**
+     * 返回是否正在录制。
+     @example
+     var result = jessibuca.isRecording()
+     console.log(result) // true
+     */
+    isRecording(): boolean;
+
+
+    /**
+     * 监听 jessibuca 初始化事件
+     * @example
+     * jessibuca.on("load",function(){console.log('load')})
+     */
+    on(event: 'load', callback: () => void): void;
+
+    /**
+     * 视频播放持续时间,单位ms
+     * @example
+     * jessibuca.on('timeUpdate',function (ts) {console.log('timeUpdate',ts);})
+     */
+    on(event: 'timeUpdate', callback: () => void): void;
+
+    /**
+     * 当解析出视频信息时回调,2个回调参数
+     * @example
+     * jessibuca.on("videoInfo",function(data){console.log('width:',data.width,'height:',data.width)})
+     */
+    on(event: 'videoInfo', callback: (data: {
+        /** 视频宽 */
+        width: number;
+        /** 视频高 */
+        height: number;
+    }) => void): void;
+
+    /**
+     * 当解析出音频信息时回调,2个回调参数
+     * @example
+     * jessibuca.on("audioInfo",function(data){console.log('numOfChannels:',data.numOfChannels,'sampleRate',data.sampleRate)})
+     */
+    on(event: 'audioInfo', callback: (data: {
+        /** 声频通道 */
+        numOfChannels: number;
+        /** 采样率 */
+        sampleRate: number;
+    }) => void): void;
+
+    /**
+     * 信息,包含错误信息
+     * @example
+     * jessibuca.on("log",function(data){console.log('data:',data)})
+     */
+    on(event: 'log', callback: () => void): void;
+
+    /**
+     * 错误信息
+     * @example
+     * jessibuca.on("error",function(error){
+        if(error === Jessibuca.ERROR.fetchError){
+            //
+        }
+        else if(error === Jessibuca.ERROR.webcodecsH265NotSupport){
+            //
+        }
+        console.log('error:',error)
+    })
+     */
+    on(event: 'error', callback: (err: Jessibuca.ERROR) => void): void;
+
+    /**
+     * 当前网速, 单位KB 每秒1次,
+     * @example
+     * jessibuca.on("kBps",function(data){console.log('kBps:',data)})
+     */
+    on(event: 'kBps', callback: (value: number) => void): void;
+
+    /**
+     * 渲染开始
+     * @example
+     * jessibuca.on("start",function(){console.log('start render')})
+     */
+    on(event: 'start', callback: () => void): void;
+
+    /**
+     * 当设定的超时时间内无数据返回,则回调
+     * @example
+     * jessibuca.on("timeout",function(error){console.log('timeout:',error)})
+     */
+    on(event: 'timeout', callback: (error: Jessibuca.TIMEOUT) => void): void;
+
+    /**
+     * 当play()的时候,如果没有数据返回,则回调
+     * @example
+     * jessibuca.on("loadingTimeout",function(){console.log('timeout')})
+     */
+    on(event: 'loadingTimeout', callback: () => void): void;
+
+    /**
+     * 当播放过程中,如果超过timeout之后没有数据渲染,则抛出异常。
+     * @example
+     * jessibuca.on("delayTimeout",function(){console.log('timeout')})
+     */
+    on(event: 'delayTimeout', callback: () => void): void;
+
+    /**
+     * 当前是否全屏
+     * @example
+     * jessibuca.on("fullscreen",function(flag){console.log('is fullscreen',flag)})
+     */
+    on(event: 'fullscreen', callback: () => void): void;
+
+    /**
+     * 触发播放事件
+     * @example
+     * jessibuca.on("play",function(flag){console.log('play')})
+     */
+    on(event: 'play', callback: () => void): void;
+
+    /**
+     * 触发暂停事件
+     * @example
+     * jessibuca.on("pause",function(flag){console.log('pause')})
+     */
+    on(event: 'pause', callback: () => void): void;
+
+    /**
+     * 触发声音事件,返回boolean值
+     * @example
+     * jessibuca.on("mute",function(flag){console.log('is mute',flag)})
+     */
+    on(event: 'mute', callback: () => void): void;
+
+    /**
+     * 流状态统计,流开始播放后回调,每秒1次。
+     * @example
+     * jessibuca.on("stats",function(s){console.log("stats is",s)})
+     */
+    on(event: 'stats', callback: (stats: {
+        /** 当前缓冲区时长,单位毫秒 */
+        buf: number;
+        /** 当前视频帧率 */
+        fps: number;
+        /** 当前音频码率,单位byte */
+        abps: number;
+        /** 当前视频码率,单位byte */
+        vbps: number;
+        /** 当前视频帧pts,单位毫秒 */
+        ts: number;
+    }) => void): void;
+
+    /**
+     * 渲染性能统计,流开始播放后回调,每秒1次。
+     * @param performance 0: 表示卡顿,1: 表示流畅,2: 表示非常流程
+     * @example
+     * jessibuca.on("performance",function(performance){console.log("performance is",performance)})
+     */
+    on(event: 'performance', callback: (performance: 0 | 1 | 2) => void): void;
+
+    /**
+     * 录制开始的事件
+
+     * @example
+     * jessibuca.on("recordStart",function(){console.log("record start")})
+     */
+    on(event: 'recordStart', callback: () => void): void;
+
+    /**
+     * 录制结束的事件
+
+     * @example
+     * jessibuca.on("recordEnd",function(){console.log("record end")})
+     */
+    on(event: 'recordEnd', callback: () => void): void;
+
+    /**
+     * 录制的时候,返回的录制时长,1s一次
+
+     * @example
+     * jessibuca.on("recordingTimestamp",function(timestamp){console.log("recordingTimestamp is",timestamp)})
+     */
+    on(event: 'recordingTimestamp', callback: (timestamp: number) => void): void;
+
+    /**
+     * 监听调用play方法 经过 初始化-> 网络请求-> 解封装 -> 解码 -> 渲染 一系列过程的时间消耗
+     * @param event
+     * @param callback
+     */
+    on(event: 'playToRenderTimes', callback: (times: {
+        playInitStart: number, // 1 初始化
+        playStart: number, // 2 初始化
+        streamStart: number, // 3 网络请求
+        streamResponse: number, // 4 网络请求
+        demuxStart: number, // 5 解封装
+        decodeStart: number, // 6 解码
+        videoStart: number, // 7 渲染
+        playTimestamp: number,// playStart- playInitStart
+        streamTimestamp: number,// streamStart - playStart
+        streamResponseTimestamp: number,// streamResponse - streamStart
+        demuxTimestamp: number, // demuxStart - streamResponse
+        decodeTimestamp: number, // decodeStart - demuxStart
+        videoTimestamp: number,// videoStart - decodeStart
+        allTimestamp: number // videoStart - playInitStart
+    }) => void): void
+
+    /**
+     * 监听方法
+     *
+     @example
+
+     jessibuca.on("load",function(){console.log('load')})
+     */
+    on(event: string, callback: Function): void;
+
+}
+
+export default Jessibuca;

File diff suppressed because it is too large
+ 0 - 0
traffic-ui/public/js/jessibuca/jessibuca.js


+ 19 - 2
traffic-ui/src/api/manage/monitorPoint/index.ts

@@ -31,7 +31,7 @@ export const getMonitorPoint = (pointId: string | number): AxiosPromise<MonitorP
  * 新增监控点位信息
  * @param data
  */
-export const addMonitorPoint = (data: MonitorPointForm) => {
+export const addMonitorPoint = (data) => {
   return request({
     url: '/manage/monitorPoint',
     method: 'post',
@@ -43,7 +43,7 @@ export const addMonitorPoint = (data: MonitorPointForm) => {
  * 修改监控点位信息
  * @param data
  */
-export const updateMonitorPoint = (data: MonitorPointForm) => {
+export const updateMonitorPoint = (data) => {
   return request({
     url: '/manage/monitorPoint',
     method: 'put',
@@ -73,3 +73,20 @@ export const getListByAreaTree = (params) => {
     params
   });
 };
+//获取监控点树形结构
+export const getPointsTree = (params) => {
+  return request({
+    url: '/manage/monitorPoint/getPointsTree',
+    method: 'get',
+    params
+  });
+};
+//根据监控点获取监控点媒介信息
+export const getPointMediaByPointId = (params) => {
+  return request({
+    url: '/manage/monitorPointMedia/getPointMediaByPointId',
+    method: 'get',
+    params
+  });
+};
+

+ 10 - 0
traffic-ui/src/api/manage/monitorTask/index.ts

@@ -61,3 +61,13 @@ export const delMonitorTask = (id: string | number | Array<string | number>) =>
     method: 'delete'
   });
 };
+/**
+ * 根据监控点id获取任务数据
+ * @param id
+ */
+export const getTaskInfoByPointId = (id) => {
+  return request({
+    url: '/manage/monitorTask/getTaskInfoByPointId/' + id,
+    method: 'get'
+  });
+};

+ 8 - 0
traffic-ui/src/api/sense/events/index.ts

@@ -225,3 +225,11 @@ export const eventsMediaList = (params) => {
     params
   });
 };
+//获取事件id获取媒介信息
+export const getMediaByEventId = (params) => {
+  return request({
+    url: '/manage/eventsMedia/getMediaByEventId',
+    method: 'get',
+    params
+  });
+};

+ 11 - 0
traffic-ui/src/api/sense/eventsReport/index.ts

@@ -61,3 +61,14 @@ export const delEventsReport = (id: string | number | Array<string | number>) =>
     method: 'delete'
   });
 };
+
+/**
+ * 批量重试失败推送
+ * @param id
+ */
+export const againReport = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/sense/eventsReport/againReport/' + id,
+    method: 'get'
+  });
+};

+ 6 - 6
traffic-ui/src/assets/styles/element-ui.scss

@@ -79,12 +79,12 @@
       .el-dialog__body {
         padding: 15px !important;
       }
-      .el-dialog__header {
-        padding: 16px 16px 8px 16px;
-        box-sizing: border-box;
-        border-bottom: 1px solid var(--brder-color);
-        margin-right: 0;
-      }
+      // .el-dialog__header {
+        // padding: 16px 16px 8px 16px;
+        // box-sizing: border-box;
+        // border-bottom: 1px solid var(--brder-color);
+        // margin-right: 0;
+      // }
     }
   }
 }

+ 266 - 0
traffic-ui/src/components/JessibucaPlayer/index.vue

@@ -0,0 +1,266 @@
+<template>
+  <div class="container-shell">
+    <div class="paint-tips" v-if="ifDraw">双击结束绘制</div>
+    <div class="title-name">
+      <div v-if="title"><el-icon>
+          <Location />
+        </el-icon>{{ title }}</div>
+    </div>
+    <div ref="container" id="container"></div>
+    <div class="operate-btns" v-if="showPaint">
+      <div v-if="!ifDraw" @click="paintPolygon">
+        <el-icon>
+          <Edit />
+        </el-icon>
+        <span>绘制</span>
+      </div>
+      <div v-else @click="paintPolygon">
+        <el-icon>
+          <CircleClose />
+        </el-icon>
+        <span>取消绘制</span>
+      </div>
+      <div @click="clearDraw">
+        <el-icon>
+          <Delete />
+        </el-icon>
+        <span>删除</span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup name="JessibucaPlayer">
+import useDraw from './useDraw';
+
+interface Props {
+  videoUrl?: string;
+  showPaint?: boolean;
+  title?: String;
+}
+
+const props = defineProps<Props>();
+
+const ifDraw = ref(false);
+const container = ref<HTMLDivElement | null>(null);
+const draw = useDraw();
+const jessibuca = ref(null)
+
+// 初始化绘制功能
+onMounted(() => {
+  // 创建播放器实例
+  createPlayer();
+  setTimeout(() => {
+    if (container.value && props.showPaint) {
+      draw.init(container.value, {
+        // 绘制完成时的回调
+        onComplete: (points) => {
+          console.log('points', points);
+        }
+      });
+
+    }
+  }, 1000);
+
+});
+
+// 清理函数,在组件卸载前调用
+onBeforeUnmount(async () => {
+  if (jessibuca.value) {
+    await jessibuca.value.destroy();
+  }
+  jessibuca.value = null
+});
+watch(() => props.videoUrl, (newValue, oldValue) => {
+  if (newValue != '') {
+    nextTick(() => {
+      restartPlay()
+    })
+  }
+}, { immediate: true });
+const createPlayer = () => {
+  jessibuca.value = new window.Jessibuca({
+    container: container.value!,
+    videoBuffer: 0.2, // 缓存时长
+    isResize: false,
+    useWCS: false,
+    useMSE: true,
+    decoder: "/js/jessibuca/decoder.js",
+    text: "",
+    loadingText: "疯狂加载中...",
+    debug: false,
+    supportDblclickFullscreen: false,
+    showBandwidth: false, // 显示网速
+    operateBtns: {
+      fullscreen: true,
+      screenshot: true,
+      play: true,
+      audio: true,
+      record: true,
+    },
+    record: "record",
+    recordType: 'mp4',
+    vod: false,
+    forceNoOffscreen: false,
+    isNotMute: true,
+    timeout: 10
+  });
+
+  // 添加事件监听器
+  jessibuca.value.on("load", () => { });
+  jessibuca.value.on("log", (msg) => { });
+  jessibuca.value.on("record", (msg) => { });
+  jessibuca.value.on("pause", () => { });
+  jessibuca.value.on("play", () => { });
+  jessibuca.value.on("fullscreen", (msg) => { });
+  jessibuca.value.on("mute", (msg) => {
+
+  });
+  jessibuca.value.on("audioInfo", (msg) => { });
+  jessibuca.value.on("videoInfo", (info) => { });
+  jessibuca.value.on("error", (error) => { });
+  jessibuca.value.on("timeout", () => { });
+  jessibuca.value.on('start', () => { });
+  jessibuca.value.on("performance", (performance) => { });
+  jessibuca.value.on('buffer', (buffer) => { });
+  jessibuca.value.on('stats', (stats) => { });
+  jessibuca.value.on('kBps', (kBps) => { });
+  jessibuca.value.on("play", () => { });
+
+}
+
+const playVideo = (videoUrl: string) => {
+  if (videoUrl && container.value) {
+    jessibuca.value.play(videoUrl);
+  }
+}
+
+const mute = () => {
+  jessibuca.value.mute();
+}
+
+const cancelMute = () => {
+  jessibuca.value.cancelMute();
+}
+
+const pause = () => {
+  jessibuca.value.pause();
+}
+
+const volumeChange = (volume: number) => {
+  jessibuca.value.setVolume(volume);
+}
+
+const rotateChange = (rotate: number) => {
+  jessibuca.value.setRotate(rotate);
+}
+
+const destroy = async () => {
+  if (jessibuca.value) {
+    await jessibuca.value.destroy();
+  }
+  createPlayer();
+}
+
+const fullscreen = () => {
+  jessibuca.value.setFullscreen(true);
+}
+
+const clearView = () => {
+  jessibuca.value.clearView();
+}
+
+const startRecord = (recordType: string) => {
+  const time = new Date().getTime();
+  jessibuca.value.startRecord(time, recordType);
+}
+
+const stopAndSaveRecord = () => {
+  jessibuca.value.stopRecordAndSave();
+}
+
+const screenShot = () => {
+  jessibuca.value.screenshot();
+}
+
+const restartPlay = () => {
+  destroy();
+  setTimeout(() => {
+    playVideo(props.videoUrl);
+  }, 100);
+}
+
+const changeBuffer = (buffer: number) => {
+  jessibuca.value.setBufferTime(buffer);
+}
+
+const scaleChange = (scale: number) => {
+  jessibuca.value.setScaleMode(scale);
+}
+
+function paintPolygon() {
+  if (ifDraw.value) {
+    clearDraw();
+    draw.endPaint();
+  } else {
+    draw.startPaint();
+  }
+  ifDraw.value = !ifDraw.value;
+}
+
+function clearDraw() {
+  draw.clearCanvas();
+}
+</script>
+
+<style lang="scss" scoped>
+.container-shell {
+  backdrop-filter: blur(5px);
+  background: hsla(0, 0%, 50%, 0.5);
+  width: 100%;
+  height: 100%;
+  position: relative;
+  border-radius: 5px;
+  box-shadow: 0 10px 20px;
+  padding: 10px 2px 2px 2px;
+  display: flex;
+  flex-direction: column;
+}
+
+#container {
+  background: rgba(13, 14, 27, 0.7);
+}
+
+
+.paint-tips {
+  position: absolute;
+  top: 5px;
+  left: 5px;
+}
+
+.title-name {
+  height: 25px;
+
+  >div {
+    display: flex;
+    align-items: center;
+  }
+}
+
+.operate-btns {
+  position: absolute;
+  bottom: 10px;
+  left: 20px;
+  color: #fff;
+  display: flex;
+  font-size: 13px;
+  z-index: 100;
+
+  div {
+    margin-right: 20px;
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+  }
+}
+</style>

+ 358 - 0
traffic-ui/src/components/JessibucaPlayer/index1.vue

@@ -0,0 +1,358 @@
+<template>
+  <div class="root">
+    <div class="container-shell">
+      <div class="paint-tips" v-if="ifDraw">双击结束绘制</div>
+      <div id="container" ref="container"></div>
+      <div class="operate-btns" v-if="showPaint">
+        <div v-if="!ifDraw" @click="paintPolygon">
+          <el-icon>
+            <Edit />
+          </el-icon>
+          <span>绘制</span>
+        </div>
+        <div v-else @click="paintPolygon">
+          <el-icon>
+            <CircleClose />
+          </el-icon>
+          <span>取消绘制</span>
+        </div>
+        <div @click="clearDraw">
+          <el-icon>
+            <Delete />
+          </el-icon>
+          <span>删除</span>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+import useDraw from './useDraw';
+export default {
+  name: "JessibucaPlayer",
+  props: ['videoUrl','showPaint'],
+  data () {
+    return {
+      jessibuca: null,
+      // videoUrl: 'http://200.200.19.126:81/rtp/34020000001320000009_34020000001320000001.live.flv',
+      // videoUrl: 'http://pull-demo.volcfcdnrd.com/live/st-4536523_yzmhde.flv',
+      version: '',
+      wasm: false,
+      vc: "ff",
+      playing: false,
+      quieting: true,
+      loaded: false, // mute
+      showOperateBtns: true,
+      showBandwidth: false,
+      err: "",
+      speed: 0,
+      performance: "",
+      volume: 1,
+      rotate: 0,
+      useWCS: false,
+      useMSE: true,
+      useOffscreen: false,
+      recording: false,
+      recordType: 'mp4',
+      scale: 0,
+      draw: null,
+      ifDraw: false
+    };
+  },
+  mounted () {
+    this.create();
+    this.draw = useDraw()
+    this.draw.init(this.$refs.container, {
+      // 绘制完成时的回调
+      onComplete: (points) => {
+        console.log('points', points);
+      }
+    });
+    window.onerror = (msg) => (this.err = msg);
+    this.play()
+    // this.draw.drawAreaByPoints([
+    //   {
+    //     "x": 241,
+    //     "y": 109
+    //   },
+    //   {
+    //     "x": 90,
+    //     "y": 261
+    //   },
+    //   {
+    //     "x": 546,
+    //     "y": 134
+    //   },
+    //   {
+    //     "x": 546,
+    //     "y": 134
+    //   }
+    // ])
+  },
+  async unmounted () {
+    if (this.jessibuca) {
+      await this.jessibuca.destroy();
+      this.jessibuca = null;
+    }
+  },
+  methods: {
+    create (options) {
+      options = options || {};
+      this.jessibuca = new window.Jessibuca(
+        Object.assign(
+          {
+            container: this.$refs.container,
+            videoBuffer: 0.2, // 缓存时长
+            isResize: false,
+            useWCS: this.useWCS,
+            useMSE: this.useMSE,
+            decoder: "/js/jessibuca/decoder.js",
+            text: "",
+            loadingText: "疯狂加载中...",
+            debug: false,
+            supportDblclickFullscreen: false,
+            showBandwidth: this.showBandwidth, // 显示网速
+            operateBtns: {
+              fullscreen: this.showOperateBtns,
+              screenshot: this.showOperateBtns,
+              play: this.showOperateBtns,
+              audio: this.showOperateBtns,
+              record: this.showOperateBtns,
+            },
+            record: "record",
+            recordType: 'mp4',
+            vod: this.vod,
+            forceNoOffscreen: !this.useOffscreen,
+            isNotMute: true,
+            timeout: 10
+          },
+          options
+        )
+      );
+      this.jessibuca.on("load", () => {
+      });
+
+      this.jessibuca.on("log", (msg) => {
+        // console.log("on log", msg);
+      });
+      this.jessibuca.on("record", (msg) => {
+        //console.log("on record:", msg);
+      });
+      this.jessibuca.on("pause", () => {
+        this.playing = false;
+      });
+      this.jessibuca.on("play", () => {
+        this.playing = true;
+      });
+      this.jessibuca.on("fullscreen", (msg) => {
+      });
+
+      this.jessibuca.on("mute", (msg) => {
+        this.quieting = msg;
+      });
+
+      this.jessibuca.on("mute", (msg) => {
+      });
+
+      this.jessibuca.on("audioInfo", (msg) => {
+        // console.log("audioInfo", msg);
+      });
+
+      this.jessibuca.on("videoInfo", (info) => {
+      });
+
+      this.jessibuca.on("error", (error) => {
+      });
+
+      this.jessibuca.on("timeout", () => {
+      });
+
+      this.jessibuca.on('start', () => {
+      })
+
+      this.jessibuca.on("performance", (performance) => {
+        var show = "卡顿";
+        if (performance === 2) {
+          show = "非常流畅";
+        } else if (performance === 1) {
+          show = "流畅";
+        }
+        this.performance = show;
+      });
+      this.jessibuca.on('buffer', (buffer) => {
+      })
+
+      this.jessibuca.on('stats', (stats) => {
+      })
+
+      this.jessibuca.on('kBps', (kBps) => {
+      });
+
+      this.jessibuca.on("play", () => {
+        this.playing = true;
+        this.loaded = true;
+        this.quieting = this.jessibuca.isMute();
+      });
+
+      this.jessibuca.on('recordingTimestamp', (ts) => {
+      })
+
+    },
+    play () {
+      if (this.videoUrl) {
+        this.jessibuca.play(this.videoUrl);
+      }
+
+    },
+    mute () {
+      this.jessibuca.mute();
+    },
+    cancelMute () {
+      this.jessibuca.cancelMute();
+    },
+
+    pause () {
+      this.jessibuca.pause();
+      this.playing = false;
+      this.err = "";
+      this.performance = "";
+    },
+    volumeChange () {
+      this.jessibuca.setVolume(this.volume);
+    },
+    rotateChange () {
+      this.jessibuca.setRotate(this.rotate);
+    },
+    async destroy () {
+      if (this.jessibuca) {
+        await this.jessibuca.destroy();
+      }
+      this.create();
+      this.playing = false;
+      this.loaded = false;
+      this.performance = "";
+    },
+
+    fullscreen () {
+      this.jessibuca.setFullscreen(true);
+    },
+
+    clearView () {
+      this.jessibuca.clearView();
+    },
+
+    startRecord () {
+      console.log(this.recordType)
+      const time = new Date().getTime();
+      this.jessibuca.startRecord(time, this.recordType);
+    },
+
+    stopAndSaveRecord () {
+      this.jessibuca.stopRecordAndSave();
+    },
+    screenShot () {
+      this.jessibuca.screenshot();
+    },
+
+
+    async restartPlay (type) {
+
+      if (type === 'mse') {
+        this.useWCS = false;
+        this.useOffscreen = false;
+      } else if (type === 'wcs') {
+        this.useMSE = false
+      } else if (type === 'offscreen') {
+        this.useMSE = false
+      }
+
+      await this.destroy();
+      setTimeout(() => {
+        this.play();
+      }, 100)
+    },
+
+    changeBuffer () {
+      this.jessibuca.setBufferTime(Number(this.$refs.buffer.value));
+    },
+
+    scaleChange () {
+      this.jessibuca.setScaleMode(this.scale);
+    },
+    paintPolygon () {
+      if (this.ifDraw) {
+        this.clearDraw()
+        this.draw.endPaint()
+      } else {
+        this.draw.startPaint()
+      }
+      this.ifDraw = !this.ifDraw
+    },
+    clearDraw () {
+      this.draw.clearCanvas()
+    },
+  },
+};
+</script>
+<style lang="scss" scoped>
+.root {
+  display: flex;
+  place-content: center;
+  margin-top: 3rem;
+}
+
+.container-shell {
+  position: relative;
+  backdrop-filter: blur(5px);
+  background: hsla(0, 0%, 50%, 0.5);
+  padding: 30px 4px 10px 4px;
+  /* border: 2px solid black; */
+  width: auto;
+  position: relative;
+  border-radius: 5px;
+  box-shadow: 0 10px 20px;
+}
+
+.container-shell-title {
+  position: absolute;
+  color: darkgray;
+  top: 4px;
+  left: 10px;
+  text-shadow: 1px 1px black;
+}
+
+#container {
+  background: rgba(13, 14, 27, 0.7);
+  width: 640px;
+  height: 398px;
+}
+
+@media (max-width: 720px) {
+  #container {
+    width: 90vw;
+    height: 52.7vw;
+  }
+}
+
+.paint-tips {
+  position: absolute;
+  top: 5px;
+  left: 5px;
+}
+
+.operate-btns {
+  position: absolute;
+  bottom: 20px;
+  left: 20px;
+  color: #fff;
+  display: flex;
+  font-size: 13px;
+
+  div {
+    margin-right: 20px;
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+  }
+}
+</style>

+ 216 - 0
traffic-ui/src/components/JessibucaPlayer/useDraw.js

@@ -0,0 +1,216 @@
+const useDraw = () => {
+  let Container = null;
+  let canvas = null; // canvas标签
+  let ctx = null; // canvas画布
+  let w, h; // 画布宽度
+  let isPaint = true; // 是否处于绘图模式
+  let points = []; // 已加入的点
+
+  // 默认样式
+  let defaultStyle = {
+    lineWidth: 4, // 画笔大小
+    strokeStyle: '#0073e6', // 画笔颜色
+    fillStyle: 'rgba(0, 115, 230,0.2)', // 填充颜色
+    backgroudColor: 'transparent' // 画布颜色
+  };
+
+  // 默认选项
+  let options = {
+    onComplete: undefined, // 绘图完成后的回调
+    beforeComplete: (points) => points.length >= 3, // 完成绘图前的验证
+    onClear: undefined, // 清空绘图后的回调
+    max: 4, // 限制最多点位数量
+    canvasStyle: { ...defaultStyle }
+  }; // 选项
+
+  // 画横线
+  const drawLine = (mouse) => {
+    ctx.clearRect(0, 0, w, h);
+    const firstPoint = points[0];
+    ctx.beginPath();
+    ctx.moveTo(firstPoint.x, firstPoint.y);
+    ctx.lineTo(mouse.x, mouse.y);
+    ctx.stroke();
+  };
+
+  // 画区域
+  const drawArea = (mouse) => {
+    ctx.clearRect(0, 0, w, h);
+    ctx.beginPath();
+    // 画已经加上的点
+    for (let i = 0; i < points.length; i++) {
+      if (i === 0) {
+        ctx.moveTo(points[i].x, points[i].y);
+      } else {
+        ctx.lineTo(points[i].x, points[i].y);
+      }
+    }
+    // 如果传入了鼠标点,将它作为最后一点,动态绘制区域。否则根据points直接封闭区域
+    if (mouse) {
+      ctx.lineTo(mouse.x, mouse.y);
+    }
+
+    // 回到第一个点,封闭区域
+    ctx.lineTo(points[0].x, points[0].y);
+
+    // 划线与填充
+    ctx.stroke();
+    ctx.fill();
+  };
+
+  // 根据情况绘制图形
+  const draw = (mouse) => {
+    const pointLength = points.length;
+    if (isPaint === false) return;
+    switch (pointLength) {
+      case 0:
+        return;
+      case 1:
+        drawLine(mouse); // 画直线
+        break;
+      default:
+        drawArea(mouse); // 画区域
+        break;
+    }
+  };
+
+  // 移动鼠标的事件:动态绘制图形
+  const onMouseMove = (e) => {
+    draw({ x: e.offsetX, y: e.offsetY });
+  };
+
+  // 点击事件:添加点
+  const onClick = (e) => {
+    if (getOpts().max && points.length > getOpts().max) {
+      return;
+    }
+    points.push({ x: e.offsetX, y: e.offsetY });
+  };
+
+  // 鼠标右键:撤销
+  const onRightClick = (e) => {
+    // 防止默认打开菜单s
+    e.stopPropagation();
+    e.preventDefault();
+
+    const pointLength = points.length;
+    if (pointLength === 0) return;
+    points.pop();
+    draw({ x: e.offsetX, y: e.offsetY });
+    return false;
+  };
+
+  //完成绘图
+  const onEnter = () => {
+    // const pointLength = points.length;
+    // 至少需要三个点才允许画区域
+    if (options.beforeComplete(points)) {
+      isPaint = false;
+      drawArea(); // 画封闭区域
+      options.onComplete && options.onComplete(getPoints());
+    }
+  };
+
+  // 清理画布
+  const clearCanvas = () => {
+    if (isPaint === false) {
+      isPaint = true;
+    }
+    points = [];
+    ctx.clearRect(0, 0, w, h);
+    options.onClear && options.onClear();
+  };
+
+  const startPaint = () => {
+    canvas.addEventListener('mousemove', onMouseMove);
+    canvas.addEventListener('click', onClick);
+    //  canvas.addEventListener('keydown', onEnter);
+    canvas.addEventListener('contextmenu', onRightClick);
+    canvas.addEventListener('dblclick', onEnter);
+  };
+
+  const endPaint = () => {
+    // if (canvas) {
+      canvas.removeEventListener('mousemove', onMouseMove);
+      canvas.removeEventListener('click', onClick);
+      // canvas.removeEventListener('keydown', onEnter);
+      canvas.removeEventListener('contextmenu', onRightClick);
+      canvas.removeEventListener('dblclick', onEnter);
+    // }
+    // canvas = null;
+    // Container = null;
+    // ctx = null;
+    // options = null;
+  };
+
+  // 根据点画区域
+  const drawAreaByPoints = (newPoints) => {
+    if (newPoints.length < 3) {
+      return;
+    }
+    points = newPoints;
+    drawArea();
+    isPaint = false;
+  };
+
+  const init = (dom, newOpts = {}) => {
+    // 初始化容器
+    Container = dom;
+    options = {
+      ...options,
+      ...newOpts,
+      canvasStyle: {
+        ...defaultStyle,
+        ...(newOpts.canvasStyle ? { ...newOpts.canvasStyle } : {})
+      }
+    };
+
+    w = Container.getBoundingClientRect().width;
+    h = Container.getBoundingClientRect().height;
+    if (Container.style.position === undefined) {
+      Container.style.position = 'relaive';
+    }
+
+    // 创建canvas标签
+    canvas = document.createElement('canvas');
+    const { backgroudColor } = options.canvasStyle;
+    canvas.style.backgroundColor = backgroudColor;
+    canvas.tabIndex = '0'; // 否则用不了回车
+
+    canvas.width = w;
+    canvas.height = h;
+    canvas.style.position = 'absolute';
+    canvas.style.left = 0;
+    canvas.style.top = 0;
+    canvas.style.zIndex = 39;
+    canvas.focus();
+    // 将canvas加入到dom中
+    Container.appendChild(canvas);
+
+    // 初始化画布
+    ctx = canvas.getContext('2d');
+    const { lineWidth, strokeStyle, fillStyle } = options.canvasStyle;
+    ctx.lineWidth = lineWidth;
+    ctx.strokeStyle = strokeStyle;
+    ctx.fillStyle = fillStyle;
+
+    // 判断有没有初始值,有的话关闭绘制状态
+    if (options.initPoints && options.initPoints.length !== 0) {
+      drawAreaByPoints(options.initPoints);
+    }
+  };
+
+  const getPoints = () => points;
+  const getOpts = () => options;
+
+  return {
+    init,
+    endPaint,
+    startPaint,
+    clearCanvas,
+    getPoints,
+    drawAreaByPoints
+  };
+};
+
+export default useDraw;

+ 3 - 0
traffic-ui/src/types/components.d.ts

@@ -77,6 +77,9 @@ declare module 'vue' {
     IFrame: typeof import('./../components/iFrame/index.vue')['default']
     ImagePreview: typeof import('./../components/ImagePreview/index.vue')['default']
     ImageUpload: typeof import('./../components/ImageUpload/index.vue')['default']
+    Index1: typeof import('./../components/JessibucaPlayer/index1.vue')['default']
+    Index2: typeof import('./../components/JessibucaPlayer/index2.vue')['default']
+    JessibucaPlayer: typeof import('./../components/JessibucaPlayer/index.vue')['default']
     LangSelect: typeof import('./../components/LangSelect/index.vue')['default']
     MultiInstanceUser: typeof import('./../components/Process/multiInstanceUser.vue')['default']
     Pagination: typeof import('./../components/Pagination/index.vue')['default']

+ 40 - 14
traffic-ui/src/views/index.vue

@@ -16,14 +16,12 @@
       </div>
     </div>
     <div class="event-image">
-      <div class="block">
-        <el-image :src="imgUrl" />
-      </div>
-      <div class="block">
-        <el-image :src="imgUrl" />
-      </div>
-      <div class="block">
-        <el-image :src="imgUrl" />
+      <div class="block" v-for="item, index in mediaList" :key="index">
+        <div class="image-tips">
+            <div>{{item.pointName}}</div>
+            <div>{{item.createTime}}</div>
+        </div>
+        <el-image :src="item.imgUrl" />
       </div>
     </div>
     <div class="left-part">
@@ -87,9 +85,8 @@
 </template>
 <script lang="ts" setup>
 import BaseChart from '@/components/BaseChart/index.vue'
-import imgUrl from '@/assets/images/event.png'
 import monitorIcon from '@/assets/images/monitor.svg'
-import { eventsCount, importantEventsStat, motorInLanesTop10 } from '@/api/sense/events/index'
+import { eventsCount, importantEventsStat, motorInLanesTop10, eventsMediaList } from '@/api/sense/events'
 import { lineCharts } from '@/api/sense/flowDetail/index'
 import { getListByAreaTree } from '@/api/manage/monitorPoint/index'
 const bdmap = ref(null);
@@ -102,6 +99,8 @@ const eventDateType = ref('1')
 const motorInLanesDate = ref('1')
 const importatEvents = ref([])
 const lineData = ref([])
+const mediaList = ref([])
+const imgSuffix = import.meta.env.VITE_APP_FILEPATH
 const pieOptions = computed(() => {
   let options = {
     tooltip: {
@@ -134,7 +133,7 @@ const pieOptions = computed(() => {
 const lineOptions = computed(() => {
   const dates = [];
   const pointNames = {};
-  lineData.value.forEach( (item)=>{
+  lineData.value.forEach((item) => {
     dates.push(item.date);
     if (!pointNames[item.point_name]) {
       pointNames[item.point_name] = [];
@@ -163,7 +162,7 @@ const lineOptions = computed(() => {
     yAxis: {
       type: 'value'
     },
-    series: Object.keys(pointNames).map( (pointName)=> {
+    series: Object.keys(pointNames).map((pointName) => {
       return {
         name: pointName,
         type: 'line',
@@ -188,6 +187,7 @@ onMounted(() => {
   getImportantEvents()
   getMotorInLanesTop10()
   getLineChart()
+  getLatestEventMeida()
 });
 const getAreaTree = (areaId) => {
   getListByAreaTree({ areaId }).then(({ code, data }) => {
@@ -243,7 +243,18 @@ const getLineChart = () => {
     if (code === 200) {
       lineData.value = (data || []).map(item => ({
         ...item,
-        date:item.date.substr(5)
+        date: item.date.substr(5)
+      }))
+    }
+  })
+}
+const getLatestEventMeida = () => {
+  eventsMediaList({ pageNum: 1, pageSize: 3 }).then(({ code, rows }) => {
+    if (code === 200) {
+      mediaList.value = (rows || []).map(item => ({
+        pointName: item.pointName,
+        createTime: item.createTime,
+        imgUrl: item.mediaList.length > 0 ? `${imgSuffix}${item.mediaList[0].filePath}` : ''
       }))
     }
   })
@@ -356,6 +367,7 @@ const getLineChart = () => {
   position: absolute;
   bottom: 20px;
   width: 40%;
+  height: 150px;
   z-index: 10;
   left: 50%;
   transform: translateX(-50%);
@@ -365,8 +377,22 @@ const getLineChart = () => {
   border-radius: 4px;
 
   .block {
+    flex:1;
+    position: relative;
     &:not(:first-of-type) {
-      margin-left: 15px;
+      margin-left: 10px;
+    }
+    .image-tips{
+      position: absolute;
+      left: 5px;
+      bottom: 2px;
+      color: #fff;
+      font-size: 10px;
+      z-index: 10;
+    }
+    .el-image{
+      height: 100%;
+      width: 100%;
     }
   }
 }

+ 148 - 4
traffic-ui/src/views/manage/monitorPoint/videoConfig.vue

@@ -6,7 +6,47 @@
         :props="{ label: 'label', children: 'children' }" :expand-on-click-node="false" :filter-node-method="filterNode"
         highlight-current default-expand-all @node-click="handleNodeClick" />
     </el-card>
+    <div class="map-tips">在地图中右键可以添加监控点</div>
     <div class="map" id="map"></div>
+    <el-dialog v-model="showMarkerDialog" :title="markerInfo.pointName" width="400">
+      <span v-if="taskInfo.list.length == 0">暂无已配置并检测的类型,请点击右下角的配置</span>
+      <div v-else-if="taskInfo.videoType == 1">
+        <span>正在检测:</span>
+        <span v-for="el, index in taskInfo.list[0].taskAlgorithms" :key="el.algorithm_name">{{ index === 0 ? '' : ','
+          }}{{ el.algorithm_name }}</span>
+      </div>
+      <div v-else-if="taskInfo.videoType == 2"></div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="" link @click="editMarker">修改</el-button>
+          <el-button type="" link @click="configClick">配置</el-button>
+          <el-button type="" link @click="deleteMarker">删除</el-button>
+        </div>
+      </template>
+    </el-dialog>
+    <el-dialog v-model="showConfigDialog" title="配置信息" width="800" :destroy-on-close="true">
+      <el-tabs v-model="activTab.first" class="custom-tabs" @tab-click="tabClick">
+        <el-tab-pane v-for="item in algorithmCategoryOptions" :key="item.value" :label="item.name" :name="item.value">
+          <el-row :gutter="20">
+            <el-col :lg="6" :xs="24">
+              <div class="algorithm-type">
+                <div :class="{ 'algorithm-type-selected': el.id === activTab.second }" v-for="el in item.children" @click="activTab.second=el.id">{{
+                  el.algorithmName }}</div>
+              </div>
+            </el-col>
+            <el-col :lg="18" :xs="24" v-if="activTab.first === item.value">
+              <JessibucaPlayer class="player" :videoUrl="`https://live.nodemedia.cn:8443/live/sony_4k_264.flv`"
+                :show-paint="true" />
+            </el-col>
+          </el-row>
+        </el-tab-pane>
+      </el-tabs>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" link @click="showConfigDialog = false">提交</el-button>
+        </div>
+      </template>
+    </el-dialog>
     <el-dialog ref="formDialogRef" v-model="dialog.visible" :title="dialog.title" width="450px" append-to-body>
       <el-form ref="monitorPointFormRef" :model="form" label-width="100px">
         <el-form-item label="行政区域" prop="areaId" :rules="[{ required: true, message: '行政区域不能为空' }]">
@@ -61,8 +101,11 @@
 </template>
 
 <script lang="ts" setup>
-import { getListByAreaTree, addMonitorPoint } from '@/api/manage/monitorPoint/index'
+import { getListByAreaTree, addMonitorPoint, updateMonitorPoint, delMonitorPoint } from '@/api/manage/monitorPoint/index'
+import { getTaskInfoByPointId } from '@/api/manage/monitorTask'
+import { listAlgorithmConfig } from '@/api/manage/algorithmConfig'
 import api from '@/api/system/user';
+import { getDicts } from '@/api/system/dict/data'
 import monitorIcon from '@/assets/images/monitor.svg'
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const areaTreeRef = ref<ElTreeInstance>();
@@ -76,7 +119,23 @@ const dialog = reactive<DialogOption>({
   visible: false,
   title: ''
 });
+const markerInfo = ref({
+  pointId: '',
+  pointName: ''
+})
+const taskInfo = ref<any>({
+  list: [],
+  videoType: ''
+})
+const showMarkerDialog = ref(false)
+const showConfigDialog = ref(false)
+const algorithmCategoryOptions = ref([])
+const activTab = reactive({
+  first: '',
+  second: '',
+})
 const form = ref({
+  pointId: '',
   areaId: '',
   pointName: '',
   sceneType: '1',
@@ -105,6 +164,7 @@ onMounted(() => {
   });
   getTreeSelect()
   getAreaTree(100)
+  getAlTabs()
 });
 const dialogClose = () => {
   dialog.visible = false
@@ -121,6 +181,28 @@ const getAreaTree = (areaId) => {
     }
   })
 }
+const getAlTabs = async () => {
+  await getDicts('algorithm_category').then(({ data }) => {
+    algorithmCategoryOptions.value = (data || []).map(item => ({
+      name: item.dictLabel,
+      value: Number(item.dictValue),
+      children: []
+    }))
+  })
+  const { code, rows } = await listAlgorithmConfig()
+  if (code == 200) {
+    (rows || []).forEach(item => {
+      algorithmCategoryOptions.value.forEach(el => {
+        if (item.algorithmCategory === el.value) {
+          el.children.push(item)
+        }
+      })
+    })
+  }
+  activTab.first = algorithmCategoryOptions.value[0].value
+  activTab.second = algorithmCategoryOptions.value[0].children[0].id
+}
+const tabClick = () => { }
 /** 通过条件过滤节点  */
 const filterNode = (value: string, data: any) => {
   if (!value) return true;
@@ -143,9 +225,14 @@ const submitForm = () => {
   monitorPointFormRef.value?.validate(async (valid: boolean) => {
     if (valid) {
       buttonLoading.value = true;
-      await addMonitorPoint(form.value)
+      if (form.value.pointId) {
+        await updateMonitorPoint(form.value)
+        proxy?.$modal.msgSuccess("修改成功");
+      } else {
+        await addMonitorPoint(form.value)
+        proxy?.$modal.msgSuccess("新增成功");
+      }
       buttonLoading.value = false;
-      proxy?.$modal.msgSuccess("新增成功");
       getAreaTree(100)
       dialog.visible = false;
     }
@@ -156,10 +243,40 @@ const showMarks = () => {
   marks.value.forEach(item => {
     var pt = new BMapGL.Point(Number(item.longitude), Number(item.latitude));
     let icon = new BMapGL.Icon(monitorIcon, new BMapGL.Size(40, 40))
-    var marker = new BMapGL.Marker(pt, { icon });
+    var marker = new BMapGL.Marker(pt, { icon, myCustomProperty: '111' });
     bdmap.value.addOverlay(marker);
+    marker.addEventListener("click", () => {
+      markerInfo.value = item
+      getTaskInfo(item.pointId)
+      showMarkerDialog.value = true
+    })
   })
 };
+const getTaskInfo = (pointId) => {
+  getTaskInfoByPointId(pointId).then(({ code, data }) => {
+    if (code === 200) {
+      taskInfo.value = data
+    }
+  })
+}
+const editMarker = () => {
+  showMarkerDialog.value = false
+  dialog.visible = true;
+  dialog.title = '修改监控点位';
+  Object.assign(form.value, markerInfo.value)
+}
+const deleteMarker = async () => {
+  await proxy?.$modal.confirm('您确定要删除该监控点位?');
+  showMarkerDialog.value = false
+  const { pointId } = markerInfo.value
+  await delMonitorPoint(pointId)
+  proxy?.$modal.msgSuccess("删除成功");
+  getAreaTree(100)
+}
+const configClick = () => {
+  showMarkerDialog.value = false
+  showConfigDialog.value = true
+}
 </script>
 
 <style lang="scss" scoped>
@@ -183,4 +300,31 @@ const showMarks = () => {
   width: 100%;
   height: 100%;
 }
+
+.map-tips {
+  position: absolute;
+  top: 20px;
+  font-size: 12px;
+  color: red;
+  left: 50%;
+  transform: translateX(-50%);
+  z-index: 99;
+}
+
+.player {
+  height: 400px;
+  width: 100%;
+}
+
+.algorithm-type {
+  >div {
+    margin-top: 10px;
+    cursor: pointer;
+  }
+
+  .algorithm-type-selected {
+    color: #409EFF;
+
+  }
+}
 </style>

+ 67 - 41
traffic-ui/src/views/sense/dealKeyEvents/index.vue

@@ -39,34 +39,21 @@
       </template>
       <div class="table-list">
         <div class="table-item" v-for="item in eventList">
-            <div class="table-item-content">
-              <img :src="`${imgSuffix}${item.mediaList[0].filePath}`" alt="">
+          <div class="table-item-content">
+            <img :src="`${imgSuffix}${item.mediaList[0].filePath}`" alt="">
+            <div class="table-event-time">
+              <div>监控点:{{item.pointName}}</div>
+              <div>{{ item.createTime }}</div>
             </div>
-            <div class="table-item-footer">
-              <div>
-                <span>{{item.eventName}}</span>
-                <span class="">{{item.createTime}}</span>
-              </div>
-              <el-button type="primary" size="small">忽略</el-button>
+          </div>
+          <div class="table-item-footer">
+            <div>
+              <span>{{ item.eventName }}</span>
             </div>
+            <el-button type="primary" size="small" @click="dealEvents(item)">忽略</el-button>
+          </div>
         </div>
-        <!-- <el-card v-for="item in eventList">
-          <template #header>
-            <div class="card-header">
-              <span>Card name</span>
-            </div>
-          </template>
-          <p v-for="o in 4" :key="o" class="text item">{{ 'List item ' + o }}</p>
-          <template #footer>
-            <div class="table-item-footer">
-              <div>车辆事故</div>
-              <div><el-button type="primary">忽略</el-button></div>
-            </div>
-          </template>
-        </el-card> -->
       </div>
-
-
       <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
         v-model:limit="queryParams.pageSize" @pagination="getList" />
     </el-card>
@@ -74,7 +61,7 @@
 </template>
 
 <script setup name="dealKeyEvents" lang="ts">
-import { eventsMediaList } from '@/api/sense/events';
+import { eventsMediaList, updateEvents } from '@/api/sense/events';
 import { listMonitorPoint } from '@/api/manage/monitorPoint'
 import { getDicts } from '@/api/system/dict/data'
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -86,7 +73,7 @@ const showSearch = ref(true);
 const total = ref(0);
 const monitorPoints = ref([])
 const queryFormRef = ref<ElFormInstance>();
-const imgSuffix = ref('http://127.0.0.1:8080')
+const imgSuffix = import.meta.env.VITE_APP_FILEPATH
 const shortcuts = [
   {
     text: '今天',
@@ -118,6 +105,7 @@ const queryParams = ref({
   pointId: undefined,
   eventType: [],
   eventCategory: '1',
+  dealStatus: '0',
   params: {
     date: [],
     startDate: '',
@@ -133,14 +121,27 @@ const getStartAndEndTimeForDate = (date) => {
 }
 const getList = async () => {
   loading.value = true;
-  queryParams.value.params.startDate = ''
-  queryParams.value.params.endDate = ''
-  if (queryParams.value.params.date && queryParams.value.params.date.length) {
-    const [startDate, endDate] = queryParams.value.params.date
-    queryParams.value.params.startDate = startDate
-    queryParams.value.params.endDate = endDate
+
+  const { pageNum, pageSize, pointId, eventType, eventCategory, dealStatus, params } = queryParams.value
+  let startDate = ''
+  let endDate = ''
+  if (params.date && params.date.length) {
+    startDate = params.date[0]
+    endDate = params.date[1]
   }
-  const res = await eventsMediaList(queryParams.value);
+  const paramObj = {
+    pageNum,
+    pageSize,
+    pointId,
+    eventType: eventType && eventType.join(),
+    eventCategory,
+    dealStatus,
+    params: {
+      startDate,
+      endDate
+    }
+  }
+  const res = await eventsMediaList(paramObj);
   eventList.value = res.rows;
   total.value = res.total;
   loading.value = false;
@@ -167,7 +168,14 @@ const getMonitorPoints = () => {
     }
   })
 }
-
+const dealEvents = (row) => {
+  updateEvents(Object.assign(row, { dealStatus: 2 })).then(({ code }) => {
+    if (code === 200) {
+      proxy?.$modal.msgSuccess('操作成功')
+      getList();
+    }
+  })
+}
 
 onMounted(() => {
   getList();
@@ -183,30 +191,48 @@ onMounted(() => {
 .table-list {
   display: flex;
   flex-wrap: wrap; // 换行
+
   .table-item {
     width: 24%;
     padding: 10px;
     margin: 0 10px 10px 0;
     border-radius: 2px;
-    box-shadow: 0px 0px 12px rgba(0,0,0,0.12);
+    box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.12);
   }
-  .table-item-content{
-    img{
+
+  .table-item-content {
+    position: relative;
+    img {
       height: 200px;
       width: 100%;
     }
+    .table-event-time{
+      position: absolute;
+      color: #fff;
+      font-size: 11px;
+      padding: 5px;
+      left: 0;
+      bottom: 0;
+      >div{
+        margin-bottom: 5px;
+      }
+    }
   }
-  .table-item-footer{
+
+  .table-item-footer {
     display: flex;
     align-items: center;
     justify-content: space-between;
-    >div:first-of-type{
+
+    >div:first-of-type {
       display: flex;
       align-items: center;
-      span:first-child{
+
+      span:first-child {
         font-size: 13px;
       }
-      span:last-child{
+
+      span:last-child {
         font-size: 12px;
         margin-left: 5px;
       }

+ 49 - 219
traffic-ui/src/views/sense/eventsReport/index.vue

@@ -1,36 +1,22 @@
 <template>
   <div class="p-2">
-    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+    <div class="custom-tabs">
+      <el-radio-group v-model="activeTab" size="large" @change="getList">
+        <el-radio-button label="事件推送记录" value="0" />
+        <el-radio-button label="事件推送失败记录" value="1" />
+      </el-radio-group>
+    </div>
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter"
+      :leave-active-class="proxy?.animate.searchAnimate.leave">
       <div v-show="showSearch" class="mb-[10px]">
         <el-card shadow="hover">
-          <el-form ref="queryFormRef" :model="queryParams" :inline="true">
-            <el-form-item label="上报平台" prop="reportPlatform">
-              <el-input v-model="queryParams.reportPlatform" placeholder="请输入上报平台" clearable @keyup.enter="handleQuery" />
-            </el-form-item>
-            <el-form-item label="监控点id" prop="pointId">
-              <el-input v-model="queryParams.pointId" placeholder="请输入监控点id" clearable @keyup.enter="handleQuery" />
-            </el-form-item>
+          <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="100px">
             <el-form-item label="监控点名称" prop="pointName">
               <el-input v-model="queryParams.pointName" placeholder="请输入监控点名称" clearable @keyup.enter="handleQuery" />
             </el-form-item>
             <el-form-item label="事件名称" prop="eventName">
               <el-input v-model="queryParams.eventName" placeholder="请输入事件名称" clearable @keyup.enter="handleQuery" />
             </el-form-item>
-            <el-form-item label="数据详情" prop="dataContent">
-              <el-input v-model="queryParams.dataContent" placeholder="请输入数据详情" clearable @keyup.enter="handleQuery" />
-            </el-form-item>
-            <el-form-item label="数据类型" prop="dataType">
-              <el-input v-model="queryParams.dataType" placeholder="请输入数据类型" clearable @keyup.enter="handleQuery" />
-            </el-form-item>
-            <el-form-item label="上报状态" prop="reportStatus">
-              <el-input v-model="queryParams.reportStatus" placeholder="请输入上报状态" clearable @keyup.enter="handleQuery" />
-            </el-form-item>
-            <el-form-item label="描述" prop="reportDesc">
-              <el-input v-model="queryParams.reportDesc" placeholder="请输入描述" clearable @keyup.enter="handleQuery" />
-            </el-form-item>
-            <el-form-item label="重试次数" prop="dealStatus">
-              <el-input v-model="queryParams.dealStatus" placeholder="请输入重试次数" clearable @keyup.enter="handleQuery" />
-            </el-form-item>
             <el-form-item>
               <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
               <el-button icon="Refresh" @click="resetQuery">重置</el-button>
@@ -39,101 +25,54 @@
         </el-card>
       </div>
     </transition>
-
     <el-card shadow="never">
       <template #header>
         <el-row :gutter="10" class="mb8">
-          <el-col :span="1.5">
-            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['sense:eventsReport:add']">新增</el-button>
-          </el-col>
-          <el-col :span="1.5">
-            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['sense:eventsReport:edit']">修改</el-button>
-          </el-col>
-          <el-col :span="1.5">
-            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['sense:eventsReport:remove']">删除</el-button>
-          </el-col>
-          <el-col :span="1.5">
-            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['sense:eventsReport:export']">导出</el-button>
+          <el-col :span="1.5" v-if="activeTab === '1'">
+            <el-button type="primary" plain @click="againClick" v-hasPermi="['sense:eventsReport:add']">批量重试</el-button>
           </el-col>
           <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
         </el-row>
       </template>
 
       <el-table v-loading="loading" :data="eventsReportList" @selection-change="handleSelectionChange">
-        <el-table-column type="selection" width="55" align="center" />
-        <el-table-column label="主键" align="center" prop="id" v-if="true" />
+        <el-table-column type="selection" width="55" align="center" v-if="activeTab === '1'" />
         <el-table-column label="上报平台" align="center" prop="reportPlatform" />
-        <el-table-column label="监控点id" align="center" prop="pointId" />
         <el-table-column label="监控点名称" align="center" prop="pointName" />
         <el-table-column label="事件名称" align="center" prop="eventName" />
-        <el-table-column label="数据详情" align="center" prop="dataContent" />
+        <!-- <el-table-column label="数据详情" align="center" prop="dataContent" /> -->
         <el-table-column label="数据类型" align="center" prop="dataType" />
-        <el-table-column label="上报状态" align="center" prop="reportStatus" />
-        <el-table-column label="描述" align="center" prop="reportDesc" />
-        <el-table-column label="重试次数" align="center" prop="dealStatus" />
-        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <el-table-column label="上报状态" align="center" prop="reportStatus">
           <template #default="scope">
-            <el-tooltip content="修改" placement="top">
-              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['sense:eventsReport:edit']"></el-button>
-            </el-tooltip>
-            <el-tooltip content="删除" placement="top">
-              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['sense:eventsReport:remove']"></el-button>
-            </el-tooltip>
+            {{ scope.row.reportStatus == 0 ? '发送成功' : '发送失败' }}
           </template>
         </el-table-column>
-      </el-table>
+        <el-table-column label="描述" align="center" prop="reportDesc" />
+        <el-table-column label="新增时间" align="center" prop="createTime" />
+        <template v-if="activeTab === '1'">
+          <el-table-column label="更新时间" align="center" prop="updateTime" />
+          <el-table-column label="重试次数" align="center" prop="retryNum" />
+          <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+            <template #default="scope">
+              <el-button link type="primary" @click="againClick(scope.row)">重试</el-button>
+            </template>
+          </el-table-column>
+        </template>
 
-      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
+      </el-table>
+      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize" @pagination="getList" />
     </el-card>
-    <!-- 添加或修改推送记录对话框 -->
-    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
-      <el-form ref="eventsReportFormRef" :model="form" :rules="rules" label-width="80px">
-        <el-form-item label="上报平台" prop="reportPlatform">
-          <el-input v-model="form.reportPlatform" placeholder="请输入上报平台" />
-        </el-form-item>
-        <el-form-item label="监控点id" prop="pointId">
-          <el-input v-model="form.pointId" placeholder="请输入监控点id" />
-        </el-form-item>
-        <el-form-item label="监控点名称" prop="pointName">
-          <el-input v-model="form.pointName" placeholder="请输入监控点名称" />
-        </el-form-item>
-        <el-form-item label="事件名称" prop="eventName">
-          <el-input v-model="form.eventName" placeholder="请输入事件名称" />
-        </el-form-item>
-        <el-form-item label="数据详情" prop="dataContent">
-          <el-input v-model="form.dataContent" placeholder="请输入数据详情" />
-        </el-form-item>
-        <el-form-item label="数据类型" prop="dataType">
-          <el-input v-model="form.dataType" placeholder="请输入数据类型" />
-        </el-form-item>
-        <el-form-item label="上报状态" prop="reportStatus">
-          <el-input v-model="form.reportStatus" placeholder="请输入上报状态" />
-        </el-form-item>
-        <el-form-item label="描述" prop="reportDesc">
-          <el-input v-model="form.reportDesc" placeholder="请输入描述" />
-        </el-form-item>
-        <el-form-item label="重试次数" prop="dealStatus">
-          <el-input v-model="form.dealStatus" placeholder="请输入重试次数" />
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <div class="dialog-footer">
-          <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
-          <el-button @click="cancel">取 消</el-button>
-        </div>
-      </template>
-    </el-dialog>
   </div>
 </template>
 
 <script setup name="EventsReport" lang="ts">
-import { listEventsReport, getEventsReport, delEventsReport, addEventsReport, updateEventsReport } from '@/api/sense/eventsReport';
-import { EventsReportVO, EventsReportQuery, EventsReportForm } from '@/api/sense/eventsReport/types';
+import { listEventsReport, againReport } from '@/api/sense/eventsReport';
+import { EventsReportVO } from '@/api/sense/eventsReport/types';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 
 const eventsReportList = ref<EventsReportVO[]>([]);
-const buttonLoading = ref(false);
 const loading = ref(true);
 const showSearch = ref(true);
 const ids = ref<Array<string | number>>([]);
@@ -142,98 +81,24 @@ const multiple = ref(true);
 const total = ref(0);
 
 const queryFormRef = ref<ElFormInstance>();
-const eventsReportFormRef = ref<ElFormInstance>();
-
-const dialog = reactive<DialogOption>({
-  visible: false,
-  title: ''
-});
-
-const initFormData: EventsReportForm = {
-  id: undefined,
-  reportPlatform: undefined,
-  pointId: undefined,
+const activeTab = ref('0')
+const queryParams = ref({
+  pageNum: 1,
+  pageSize: 10,
   pointName: undefined,
   eventName: undefined,
-  dataContent: undefined,
-  dataType: undefined,
-  reportStatus: undefined,
-  reportDesc: undefined,
-  dealStatus: undefined,
-}
-const data = reactive<PageData<EventsReportForm, EventsReportQuery>>({
-  form: {...initFormData},
-  queryParams: {
-    pageNum: 1,
-    pageSize: 10,
-    reportPlatform: undefined,
-    pointId: undefined,
-    pointName: undefined,
-    eventName: undefined,
-    dataContent: undefined,
-    dataType: undefined,
-    reportStatus: undefined,
-    reportDesc: undefined,
-    dealStatus: undefined,
-    params: {
-    }
-  },
-  rules: {
-    id: [
-      { required: true, message: "主键不能为空", trigger: "blur" }
-    ],
-    reportPlatform: [
-      { required: true, message: "上报平台不能为空", trigger: "blur" }
-    ],
-    pointId: [
-      { required: true, message: "监控点id不能为空", trigger: "blur" }
-    ],
-    pointName: [
-      { required: true, message: "监控点名称不能为空", trigger: "blur" }
-    ],
-    eventName: [
-      { required: true, message: "事件名称不能为空", trigger: "blur" }
-    ],
-    dataContent: [
-      { required: true, message: "数据详情不能为空", trigger: "blur" }
-    ],
-    dataType: [
-      { required: true, message: "数据类型不能为空", trigger: "blur" }
-    ],
-    reportStatus: [
-      { required: true, message: "上报状态不能为空", trigger: "blur" }
-    ],
-    reportDesc: [
-      { required: true, message: "描述不能为空", trigger: "blur" }
-    ],
-    dealStatus: [
-      { required: true, message: "重试次数不能为空", trigger: "blur" }
-    ],
-  }
-});
+})
 
-const { queryParams, form, rules } = toRefs(data);
 
 /** 查询推送记录列表 */
 const getList = async () => {
   loading.value = true;
-  const res = await listEventsReport(queryParams.value);
+  const res = await listEventsReport(Object.assign({}, queryParams.value, { reportStatus: activeTab.value }));
   eventsReportList.value = res.rows;
   total.value = res.total;
   loading.value = false;
 }
 
-/** 取消按钮 */
-const cancel = () => {
-  reset();
-  dialog.visible = false;
-}
-
-/** 表单重置 */
-const reset = () => {
-  form.value = {...initFormData};
-  eventsReportFormRef.value?.resetFields();
-}
 
 /** 搜索按钮操作 */
 const handleQuery = () => {
@@ -254,57 +119,22 @@ const handleSelectionChange = (selection: EventsReportVO[]) => {
   multiple.value = !selection.length;
 }
 
-/** 新增按钮操作 */
-const handleAdd = () => {
-  reset();
-  dialog.visible = true;
-  dialog.title = "添加推送记录";
-}
-
-/** 修改按钮操作 */
-const handleUpdate = async (row?: EventsReportVO) => {
-  reset();
-  const _id = row?.id || ids.value[0]
-  const res = await getEventsReport(_id);
-  Object.assign(form.value, res.data);
-  dialog.visible = true;
-  dialog.title = "修改推送记录";
-}
-
-/** 提交按钮 */
-const submitForm = () => {
-  eventsReportFormRef.value?.validate(async (valid: boolean) => {
-    if (valid) {
-      buttonLoading.value = true;
-      if (form.value.id) {
-        await updateEventsReport(form.value).finally(() =>  buttonLoading.value = false);
-      } else {
-        await addEventsReport(form.value).finally(() =>  buttonLoading.value = false);
-      }
-      proxy?.$modal.msgSuccess("操作成功");
-      dialog.visible = false;
-      await getList();
-    }
-  });
-}
-
-/** 删除按钮操作 */
-const handleDelete = async (row?: EventsReportVO) => {
-  const _ids = row?.id || ids.value;
-  await proxy?.$modal.confirm('是否确认删除推送记录编号为"' + _ids + '"的数据项?').finally(() => loading.value = false);
-  await delEventsReport(_ids);
-  proxy?.$modal.msgSuccess("删除成功");
+const againClick = async (row) => {
+  const id = row?.id || ids.value;
+  if(Array.isArray(id)&&id.length === 0)
+  return proxy?.$modal.msgError("请勾选记录");
+  await againReport(id)
+  proxy?.$modal.msgSuccess("重试成功");
   await getList();
 }
 
-/** 导出按钮操作 */
-const handleExport = () => {
-  proxy?.download('sense/eventsReport/export', {
-    ...queryParams.value
-  }, `eventsReport_${new Date().getTime()}.xlsx`)
-}
-
 onMounted(() => {
   getList();
 });
 </script>
+<style lang="scss" scoped>
+.custom-tabs {
+  text-align: center;
+  margin: 10px 0;
+}
+</style>

+ 1 - 1
traffic-ui/src/views/sense/flowDetail/index.vue

@@ -29,7 +29,7 @@
         </el-row>
       </template>
 
-      <el-table v-loading="loading" :data="flowDetailList" @selection-change="handleSelectionChange">
+      <el-table v-loading="loading" :data="flowDetailList" >
         <el-table-column label="任务编号" align="center" prop="taskId" />
         <el-table-column label="监控点名称" align="center" width="150" show-overflow-tooltip prop="pointName" />
         <el-table-column label="车道号" align="center" prop="laneNumber" />

+ 244 - 0
traffic-ui/src/views/sense/offence/index.vue

@@ -0,0 +1,244 @@
+<template>
+  <div class="p-2">
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter"
+      :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <div v-show="showSearch" class="mb-[10px]">
+        <el-card shadow="hover">
+          <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="90px">
+            <el-form-item label="监控点名称" prop="pointId">
+              <el-select v-model="queryParams.pointId">
+                <el-option v-for="item in monitorPoints" :key="item.pointId" :label="item.pointName"
+                  :value="item.pointId"></el-option>
+              </el-select>
+            </el-form-item>
+            <el-form-item label="事件类型" prop="eventType">
+              <el-select v-model="queryParams.eventType" multiple collapse-tags collapse-tags-tooltip>
+                <el-option v-for="item in eventTypeOptions" :key="item.dictValue" :label="item.dictLabel"
+                  :value="item.dictValue" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="处理状态" prop="dealStatus">
+              <el-select v-model="queryParams.dealStatus" >
+                <el-option label="未处理" value="0"></el-option>
+                <el-option label="已处理" value="1"></el-option>
+                <el-option label="已忽略" value="2"></el-option>
+              </el-select>
+            </el-form-item>
+            <el-form-item label="时间范围" prop="params">
+              <el-date-picker v-model="queryParams.params.date" value-format="YYYY-MM-DD HH:mm:ss" type="datetimerange"
+                range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" :shortcuts="shortcuts"
+                @change="handleQuery" />
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+            </el-form-item>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+
+    <el-card shadow="never">
+      <template #header>
+        <el-row :gutter="10" class="mb8">
+          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+        </el-row>
+      </template>
+      <div class="table-list">
+        <div class="table-item" v-for="item in eventList">
+          <div class="table-item-content">
+            <img :src="`${imgSuffix}${item.mediaList[0].filePath}`" alt="">
+            <div class="table-event-name">{{ item.eventName }}</div>
+            <div class="table-event-time">
+              <div>监控点:{{item.pointName}}</div>
+              <div>{{ item.createTime }}</div>
+            </div>
+          </div>
+          <div class="table-item-footer" v-if="item.dealStatus==0">
+            <el-button type="warning" size="small" @click="dealEvents(item,'1')">处理</el-button>
+            <el-button type="primary" size="small" @click="dealEvents(item,'2')">忽略</el-button>
+          </div>
+        </div>
+      </div>
+      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize" @pagination="getList" />
+    </el-card>
+  </div>
+</template>
+
+<script setup name="dealKeyEvents" lang="ts">
+import { eventsMediaList, updateEvents } from '@/api/sense/events';
+import { listMonitorPoint } from '@/api/manage/monitorPoint'
+import { getDicts } from '@/api/system/dict/data'
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+import { dateFormat } from '@/utils/index'
+const eventList = ref([]);
+const eventTypeOptions = ref([])
+const loading = ref(true);
+const showSearch = ref(true);
+const total = ref(0);
+const monitorPoints = ref([])
+const queryFormRef = ref<ElFormInstance>();
+const imgSuffix = import.meta.env.VITE_APP_FILEPATH
+const shortcuts = [
+  {
+    text: '今天',
+    value: () => {
+      const now = new Date();
+      return getStartAndEndTimeForDate(now)
+    }
+  },
+  {
+    text: '昨天',
+    value: () => {
+      const now = new Date();
+      now.setDate(now.getDate() - 1);
+      return getStartAndEndTimeForDate(now)
+    }
+  },
+  {
+    text: '前天',
+    value: () => {
+      const now = new Date();
+      now.setDate(now.getDate() - 2);
+      return getStartAndEndTimeForDate(now)
+    }
+  }
+]
+const queryParams = ref({
+  pageNum: 1,
+  pageSize: 12,
+  pointId: undefined,
+  eventType: [],
+  eventCategory: '2,3,4',
+  dealStatus: '0',
+  params: {
+    date: [],
+    startDate: '',
+    endDate: ''
+  }
+})
+const getStartAndEndTimeForDate = (date) => {
+  let startOfDay = new Date(date);
+  startOfDay.setHours(0, 0, 0, 0); // 设置为当天的开始时间
+  let endOfDay = new Date(date);
+  endOfDay.setHours(23, 59, 59, 999); // 设置为当天的最后一秒
+  return [startOfDay, endOfDay];
+}
+const getList = async () => {
+  loading.value = true;
+
+  const { pageNum, pageSize, pointId, eventType, eventCategory, dealStatus, params } = queryParams.value
+  let startDate = ''
+  let endDate = ''
+  if (params.date && params.date.length) {
+    startDate = params.date[0]
+    endDate = params.date[1]
+  }
+  const paramObj = {
+    pageNum,
+    pageSize,
+    pointId,
+    eventType: eventType && eventType.join(),
+    eventCategory,
+    dealStatus,
+    params: {
+      startDate,
+      endDate
+    }
+  }
+  const res = await eventsMediaList(paramObj);
+  eventList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+}
+
+
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  handleQuery();
+}
+
+const getMonitorPoints = () => {
+  listMonitorPoint().then(({ code, rows }) => {
+    if (code === 200) {
+      monitorPoints.value = rows
+    }
+  })
+}
+const dealEvents = (row,status) => {
+  updateEvents(Object.assign(row, { dealStatus: status })).then(({ code }) => {
+    if (code === 200) {
+      proxy?.$modal.msgSuccess('操作成功')
+      getList();
+    }
+  })
+}
+
+onMounted(() => {
+  getList();
+  getMonitorPoints();
+  getDicts('event_type').then(({ code, data }) => {
+    if (code === 200) {
+      eventTypeOptions.value = (data || []).filter((item, index) => index >= 4)
+    }
+  })
+});
+</script>
+<style lang="scss" scoped>
+.table-list {
+  display: flex;
+  flex-wrap: wrap; // 换行
+
+  .table-item {
+    width: 24%;
+    padding: 10px;
+    margin: 0 10px 10px 0;
+    border-radius: 2px;
+    box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.12);
+  }
+
+  .table-item-content {
+    position: relative;
+    img {
+      height: 200px;
+      width: 100%;
+    }
+    .table-event-name{
+      position: absolute;
+      background: red;
+      color: #fff;
+      font-size: 12px;
+      padding: 5px;
+      right: 0;
+      top: 0;
+    }
+    .table-event-time{
+      position: absolute;
+      color: #fff;
+      font-size: 11px;
+      padding: 5px;
+      left: 0;
+      bottom: 0;
+      >div{
+        margin-bottom: 5px;
+      }
+    }
+  }
+
+  .table-item-footer {
+    margin-top: 5px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+}
+</style>

+ 86 - 0
traffic-ui/src/views/sense/video/index.vue

@@ -0,0 +1,86 @@
+<template>
+  <div class="">
+    <el-row :gutter="20">
+      <!-- 区域树 -->
+      <el-col :lg="6" :xs="24" >
+        <el-card shadow="hover">
+          <el-input v-model="pointName" placeholder="请输入监控点名称" prefix-icon="Search" clearable />
+          <el-tree ref="areaTreeRef" class="mt-2" node-key="areaId" :data="areaOptions" :filter-node-method="filterNode"
+            :props="{ label: 'areaName', children: 'children' }" :expand-on-click-node="false" highlight-current
+            default-expand-all @node-click="handleNodeClick" />
+        </el-card>
+      </el-col>
+      <el-col :lg="18" :xs="24">
+          <JessibucaPlayer  class="player" :videoUrl="mediaInfo.streamUrl" :show-paint="false" :title="playerName" />
+      </el-col>
+    </el-row>
+  </div>
+</template>
+<script setup lang="ts">
+import { getPointsTree, getPointMediaByPointId } from '@/api/manage/monitorPoint'
+const pointName = ref('')
+const areaOptions = ref([])
+const areaTreeRef = ref<ElTreeInstance>();
+const playerName = ref('')
+const mediaInfo = ref({
+  streamUrl: ''
+})
+onMounted(() => {
+  getPointsTree({}).then(({ code, data }) => {
+    if (code === 200) {
+      areaOptions.value = [data || {}]
+      handleNodeClick(getFirstLeafAreaId(data))
+    }
+  })
+})
+const getFirstLeafAreaId=(data) =>{
+    let firstLeafAreaId = null;
+    function traverseTree(node) {
+        if (node.children && node.children.length > 0) {
+            for (let child of node.children) {
+                traverseTree(child);
+            }
+        } else if (!node.children || node.children.length === 0) { // This is a leaf node
+            if (!firstLeafAreaId) {
+                firstLeafAreaId = node;
+            }
+        }
+    }
+    traverseTree(data);
+    return firstLeafAreaId;
+}
+
+/** 通过条件过滤节点  */
+const filterNode = (value: string, data: any) => {
+  if (!value) return true;
+  return data.areaLevel === 4 && data.areaName.indexOf(value) !== -1;
+};
+watchEffect(
+  () => {
+    areaTreeRef.value?.filter(pointName.value);
+  },
+  {
+    flush: 'post' // watchEffect会在DOM挂载或者更新之前就会触发,此属性控制在DOM元素更新后运行
+  }
+);
+/** 节点单击事件 */
+const handleNodeClick = (val) => {
+  if (val.areaLevel === 4) {
+    playerName.value = val.areaName
+    getPointMediaByPointId({ pointId: val.areaId }).then(({ code, data }) => {
+      if (code === 200) {
+        mediaInfo.value = data
+      }
+    })
+  }
+};
+
+</script>
+<style lang="scss" scoped>
+.player{
+  margin-top: 10px;
+    height: 600px;
+    width: calc(100% - 20px);
+
+}
+</style>

+ 1 - 1
traffic-ui/src/views/system/user/index.vue

@@ -12,7 +12,7 @@
             :data="areaOptions"
             :props="{ label: 'label', children: 'children' }"
             :expand-on-click-node="false"
-            :filter-node-method="filterNode"
+
             highlight-current
             default-expand-all
             @node-click="handleNodeClick"

Some files were not shown because too many files changed in this diff