Parcourir la source

[26110201] 事件查询优化: alarm_event_record新增alarm_category列(幂等, 数据后续外部补充)+sys_dict_type/sys_dict_data新增告警类别/告警等级字典(字典值占位待替换)+统计分析下新增事件查询菜单; list/process/export改为POST+JSONObject入参(告警类别/告警等级/是否确认(已确认=process_flag 2, 未确认≠2)/告警时间按当天00:00:00~23:59:59过滤, 默认告警时间倒序); 确认告警批量处理仅process_flag=0数据置为已处理; 导出走AlarmEventRecordExportStat VO(告警等级/告警类别转字典标签, 处理状态转4态文案, 缺值空串); 前端新增eventQuery.vue(复选框+确认告警Modal+Excel导出), api/eventRecord.js改POST

taosj il y a 1 jour
Parent
commit
36af769a96

+ 99 - 12
forecast-backend-server/src/main/java/com/ufo/project/alarm/controller/AlarmEventRecordController.java

@@ -1,7 +1,11 @@
 package com.ufo.project.alarm.controller;
 
+import java.util.ArrayList;
 import java.util.List;
 import javax.servlet.http.HttpServletResponse;
+import com.alibaba.fastjson2.JSONArray;
+import com.alibaba.fastjson2.JSONObject;
+import com.github.pagehelper.PageHelper;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -15,15 +19,17 @@ import org.springframework.web.bind.annotation.RestController;
 import com.ufo.base.framework.aspectj.lang.annotation.Log;
 import com.ufo.base.framework.aspectj.lang.enums.BusinessType;
 import com.ufo.project.alarm.domain.AlarmEventRecord;
+import com.ufo.project.alarm.domain.vo.AlarmEventRecordExportStat;
 import com.ufo.project.alarm.service.IAlarmEventRecordService;
 import com.ufo.base.framework.web.controller.BaseController;
 import com.ufo.base.framework.web.domain.AjaxResult;
+import com.ufo.base.common.utils.DictUtils;
 import com.ufo.base.common.utils.poi.ExcelUtil;
 import com.ufo.base.framework.web.page.TableDataInfo;
 
 /**
  * 报警信息统计Controller
- * 
+ *
  * @author ufo
  * @date 2026-05-05
  */
@@ -35,28 +41,64 @@ public class AlarmEventRecordController extends BaseController
     private IAlarmEventRecordService alarmEventRecordService;
 
     /**
-     * 查询报警信息统计列表
+     * 查询事件列表
+     *
+     * 参数: alarmCategory 告警类别, alarmLevel 告警等级, confirmed 是否确认(true=已确认, false=未确认),
+     * alarmDate 告警时间(yyyy-MM-dd), pageNum, pageSize
      */
     @PreAuthorize("@ss.hasPermi('alarm:eventRecord:list')")
-    @GetMapping("/list")
-    public TableDataInfo list(AlarmEventRecord alarmEventRecord)
+    @PostMapping("/list")
+    public TableDataInfo list(@RequestBody JSONObject params)
     {
-        startPage();
-        List<AlarmEventRecord> list = alarmEventRecordService.selectAlarmEventRecordList(alarmEventRecord);
+        AlarmEventRecord query = buildQuery(params);
+        PageHelper.startPage(params.getIntValue("pageNum", 1), params.getIntValue("pageSize", 10));
+        List<AlarmEventRecord> list = alarmEventRecordService.selectAlarmEventRecordList(query);
         return getDataTable(list);
     }
 
     /**
-     * 导出报警信息统计列表
+     * 确认告警(批量处理, 仅未处理数据可处理)
+     *
+     * 参数: ids 告警主键集合
+     */
+    @PreAuthorize("@ss.hasPermi('alarm:eventRecord:edit')")
+    @Log(title = "事件查询", businessType = BusinessType.UPDATE)
+    @PostMapping("/process")
+    public AjaxResult process(@RequestBody JSONObject params)
+    {
+        JSONArray idsArr = params.getJSONArray("ids");
+        List<String> ids = idsArr == null ? null : idsArr.toJavaList(String.class);
+        if (ids == null || ids.isEmpty())
+        {
+            return error("请选择要处理的告警数据");
+        }
+        return toAjax(alarmEventRecordService.processAlarmEventRecord(ids));
+    }
+
+    /**
+     * 导出事件列表
+     *
+     * 参数: alarmCategory 告警类别, alarmLevel 告警等级, confirmed 是否确认, alarmDate 告警时间
      */
     @PreAuthorize("@ss.hasPermi('alarm:eventRecord:export')")
-    @Log(title = "报警信息统计", businessType = BusinessType.EXPORT)
+    @Log(title = "事件查询", businessType = BusinessType.EXPORT)
     @PostMapping("/export")
-    public void export(HttpServletResponse response, AlarmEventRecord alarmEventRecord)
+    public void export(HttpServletResponse response, @RequestBody JSONObject params)
     {
-        List<AlarmEventRecord> list = alarmEventRecordService.selectAlarmEventRecordList(alarmEventRecord);
-        ExcelUtil<AlarmEventRecord> util = new ExcelUtil<AlarmEventRecord>(AlarmEventRecord.class);
-        util.exportExcel(response, list, "报警信息统计数据");
+        AlarmEventRecord query = buildQuery(params);
+        List<AlarmEventRecord> list = alarmEventRecordService.selectAlarmEventRecordList(query);
+        List<AlarmEventRecordExportStat> exportList = new ArrayList<>();
+        for (AlarmEventRecord record : list)
+        {
+            exportList.add(new AlarmEventRecordExportStat(
+                    dictLabel("alarm_level", record.getAlarmLevel() == null ? null : String.valueOf(record.getAlarmLevel())),
+                    dictLabel("alarm_category", record.getAlarmCategory()),
+                    record.getAlarmData(),
+                    processFlagLabel(record.getProcessFlag()),
+                    record.getAlarmCreateTime()));
+        }
+        ExcelUtil<AlarmEventRecordExportStat> util = new ExcelUtil<AlarmEventRecordExportStat>(AlarmEventRecordExportStat.class);
+        util.exportExcel(response, exportList, "事件查询数据");
     }
 
     /**
@@ -101,4 +143,49 @@ public class AlarmEventRecordController extends BaseController
     {
         return toAjax(alarmEventRecordService.deleteAlarmEventRecordByIds(ids));
     }
+
+    /**
+     * 构建查询条件
+     */
+    private AlarmEventRecord buildQuery(JSONObject params)
+    {
+        AlarmEventRecord query = new AlarmEventRecord();
+        query.setAlarmCategory(params.getString("alarmCategory"));
+        query.setAlarmLevel(params.getLong("alarmLevel"));
+        query.setConfirmed(params.getBoolean("confirmed"));
+        query.setAlarmDate(params.getString("alarmDate"));
+        return query;
+    }
+
+    /**
+     * 字典值转标签(无匹配时返回空串)
+     */
+    private String dictLabel(String dictType, String dictValue)
+    {
+        if (dictValue == null || dictValue.isEmpty())
+        {
+            return "";
+        }
+        String label = DictUtils.getDictLabel(dictType, dictValue);
+        return label == null ? "" : label;
+    }
+
+    /**
+     * 处理状态转文案: 0未处理/1处理中/2已处理/3误报
+     */
+    private String processFlagLabel(Long processFlag)
+    {
+        if (processFlag == null)
+        {
+            return "";
+        }
+        switch (processFlag.intValue())
+        {
+            case 0: return "未处理";
+            case 1: return "处理中";
+            case 2: return "已处理";
+            case 3: return "误报";
+            default: return String.valueOf(processFlag);
+        }
+    }
 }

+ 44 - 6
forecast-backend-server/src/main/java/com/ufo/project/alarm/domain/AlarmEventRecord.java

@@ -34,6 +34,10 @@ public class AlarmEventRecord extends BaseEntity
     @Excel(name = "监控端")
     private String monitoringTerminal;
 
+    /** 告警类别 */
+    @Excel(name = "告警类别")
+    private String alarmCategory;
+
     /** 报警级别 */
     @Excel(name = "报警级别")
     private Long alarmLevel;
@@ -62,7 +66,13 @@ public class AlarmEventRecord extends BaseEntity
     @Excel(name = "处理人ids")
     private String processPersonId;
 
-    public void setId(String id) 
+    /** 是否确认(查询条件: true=已确认即已处理, false=未确认) */
+    private Boolean confirmed;
+
+    /** 告警日期(查询条件: yyyy-MM-dd, 按当天 00:00:00~23:59:59 过滤) */
+    private String alarmDate;
+
+    public void setId(String id)
     {
         this.id = id;
     }
@@ -98,16 +108,25 @@ public class AlarmEventRecord extends BaseEntity
     {
         return alarmData;
     }
-    public void setMonitoringTerminal(String monitoringTerminal) 
+    public void setMonitoringTerminal(String monitoringTerminal)
     {
         this.monitoringTerminal = monitoringTerminal;
     }
 
-    public String getMonitoringTerminal() 
+    public String getMonitoringTerminal()
     {
         return monitoringTerminal;
     }
-    public void setAlarmLevel(Long alarmLevel) 
+    public void setAlarmCategory(String alarmCategory)
+    {
+        this.alarmCategory = alarmCategory;
+    }
+
+    public String getAlarmCategory()
+    {
+        return alarmCategory;
+    }
+    public void setAlarmLevel(Long alarmLevel)
     {
         this.alarmLevel = alarmLevel;
     }
@@ -161,15 +180,33 @@ public class AlarmEventRecord extends BaseEntity
     {
         return sendStatus;
     }
-    public void setProcessPersonId(String processPersonId) 
+    public void setProcessPersonId(String processPersonId)
     {
         this.processPersonId = processPersonId;
     }
 
-    public String getProcessPersonId() 
+    public String getProcessPersonId()
     {
         return processPersonId;
     }
+    public void setConfirmed(Boolean confirmed)
+    {
+        this.confirmed = confirmed;
+    }
+
+    public Boolean getConfirmed()
+    {
+        return confirmed;
+    }
+    public void setAlarmDate(String alarmDate)
+    {
+        this.alarmDate = alarmDate;
+    }
+
+    public String getAlarmDate()
+    {
+        return alarmDate;
+    }
 
     @Override
     public String toString() {
@@ -179,6 +216,7 @@ public class AlarmEventRecord extends BaseEntity
             .append("alarmRuleId", getAlarmRuleId())
             .append("alarmData", getAlarmData())
             .append("monitoringTerminal", getMonitoringTerminal())
+            .append("alarmCategory", getAlarmCategory())
             .append("createTime", getCreateTime())
             .append("createBy", getCreateBy())
             .append("updateTime", getUpdateTime())

+ 95 - 0
forecast-backend-server/src/main/java/com/ufo/project/alarm/domain/vo/AlarmEventRecordExportStat.java

@@ -0,0 +1,95 @@
+package com.ufo.project.alarm.domain.vo;
+
+import com.ufo.base.framework.aspectj.lang.annotation.Excel;
+
+/**
+ * 事件查询导出行数据
+ *
+ * @author ufo
+ * @date 2026-11-02
+ */
+public class AlarmEventRecordExportStat
+{
+    /** 告警等级 */
+    @Excel(name = "告警等级")
+    private String alarmLevel;
+
+    /** 告警类别 */
+    @Excel(name = "告警类别")
+    private String alarmCategory;
+
+    /** 告警内容 */
+    @Excel(name = "告警内容")
+    private String alarmData;
+
+    /** 处理状态 */
+    @Excel(name = "处理状态")
+    private String processFlag;
+
+    /** 告警日期 */
+    @Excel(name = "告警日期")
+    private String alarmDate;
+
+    public AlarmEventRecordExportStat()
+    {
+    }
+
+    public AlarmEventRecordExportStat(String alarmLevel, String alarmCategory, String alarmData, String processFlag, String alarmDate)
+    {
+        this.alarmLevel = alarmLevel;
+        this.alarmCategory = alarmCategory;
+        this.alarmData = alarmData;
+        this.processFlag = processFlag;
+        this.alarmDate = alarmDate;
+    }
+
+    public String getAlarmLevel()
+    {
+        return alarmLevel;
+    }
+
+    public void setAlarmLevel(String alarmLevel)
+    {
+        this.alarmLevel = alarmLevel;
+    }
+
+    public String getAlarmCategory()
+    {
+        return alarmCategory;
+    }
+
+    public void setAlarmCategory(String alarmCategory)
+    {
+        this.alarmCategory = alarmCategory;
+    }
+
+    public String getAlarmData()
+    {
+        return alarmData;
+    }
+
+    public void setAlarmData(String alarmData)
+    {
+        this.alarmData = alarmData;
+    }
+
+    public String getProcessFlag()
+    {
+        return processFlag;
+    }
+
+    public void setProcessFlag(String processFlag)
+    {
+        this.processFlag = processFlag;
+    }
+
+    public String getAlarmDate()
+    {
+        return alarmDate;
+    }
+
+    public void setAlarmDate(String alarmDate)
+    {
+        this.alarmDate = alarmDate;
+    }
+}

+ 12 - 1
forecast-backend-server/src/main/java/com/ufo/project/alarm/mapper/AlarmEventRecordMapper.java

@@ -1,6 +1,8 @@
 package com.ufo.project.alarm.mapper;
 
+import java.util.Date;
 import java.util.List;
+import org.apache.ibatis.annotations.Param;
 import com.ufo.project.alarm.domain.AlarmEventRecord;
 
 /**
@@ -53,9 +55,18 @@ public interface AlarmEventRecordMapper
 
     /**
      * 批量删除报警信息统计
-     * 
+     *
      * @param ids 需要删除的数据主键集合
      * @return 结果
      */
     public int deleteAlarmEventRecordByIds(String[] ids);
+
+    /**
+     * 批量处理报警信息(仅未处理数据可处理, 置为已处理)
+     *
+     * @param ids 需要处理的报警信息主键集合
+     * @param updateTime 更新时间
+     * @return 结果
+     */
+    public int processAlarmEventRecord(@Param("ids") List<String> ids, @Param("updateTime") Date updateTime);
 }

+ 9 - 1
forecast-backend-server/src/main/java/com/ufo/project/alarm/service/IAlarmEventRecordService.java

@@ -53,9 +53,17 @@ public interface IAlarmEventRecordService
 
     /**
      * 删除报警信息统计信息
-     * 
+     *
      * @param id 报警信息统计主键
      * @return 结果
      */
     public int deleteAlarmEventRecordById(String id);
+
+    /**
+     * 批量处理报警信息(仅未处理数据可处理, 置为已处理)
+     *
+     * @param ids 需要处理的报警信息主键集合
+     * @return 结果
+     */
+    public int processAlarmEventRecord(List<String> ids);
 }

+ 13 - 1
forecast-backend-server/src/main/java/com/ufo/project/alarm/service/impl/AlarmEventRecordServiceImpl.java

@@ -84,7 +84,7 @@ public class AlarmEventRecordServiceImpl implements IAlarmEventRecordService
 
     /**
      * 删除报警信息统计信息
-     * 
+     *
      * @param id 报警信息统计主键
      * @return 结果
      */
@@ -93,4 +93,16 @@ public class AlarmEventRecordServiceImpl implements IAlarmEventRecordService
     {
         return alarmEventRecordMapper.deleteAlarmEventRecordById(id);
     }
+
+    /**
+     * 批量处理报警信息(仅未处理数据可处理, 置为已处理)
+     *
+     * @param ids 需要处理的报警信息主键集合
+     * @return 结果
+     */
+    @Override
+    public int processAlarmEventRecord(List<String> ids)
+    {
+        return alarmEventRecordMapper.processAlarmEventRecord(ids, DateUtils.getNowDate());
+    }
 }

+ 20 - 2
forecast-backend-server/src/main/resources/mybatis/alarm/AlarmEventRecordMapper.xml

@@ -10,6 +10,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="alarmRuleId"    column="alarm_rule_id"    />
         <result property="alarmData"    column="alarm_data"    />
         <result property="monitoringTerminal"    column="monitoring_terminal"    />
+        <result property="alarmCategory"    column="alarm_category"    />
         <result property="createTime"    column="create_time"    />
         <result property="createBy"    column="create_by"    />
         <result property="updateTime"    column="update_time"    />
@@ -24,7 +25,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </resultMap>
 
     <sql id="selectAlarmEventRecordVo">
-        select id, station_id, alarm_rule_id, alarm_data, monitoring_terminal, create_time, create_by, update_time, update_by, alarm_level, alarm_create_time, process_flag, process_person, process_result, send_status, process_person_id from alarm_event_record
+        select id, station_id, alarm_rule_id, alarm_data, monitoring_terminal, alarm_category, create_time, create_by, update_time, update_by, alarm_level, alarm_create_time, process_flag, process_person, process_result, send_status, process_person_id from alarm_event_record
     </sql>
 
     <select id="selectAlarmEventRecordList" parameterType="AlarmEventRecord" resultMap="AlarmEventRecordResult">
@@ -34,14 +35,19 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="alarmRuleId != null  and alarmRuleId != ''"> and alarm_rule_id = #{alarmRuleId}</if>
             <if test="alarmData != null  and alarmData != ''"> and alarm_data = #{alarmData}</if>
             <if test="monitoringTerminal != null  and monitoringTerminal != ''"> and monitoring_terminal = #{monitoringTerminal}</if>
+            <if test="alarmCategory != null  and alarmCategory != ''"> and alarm_category = #{alarmCategory}</if>
             <if test="alarmLevel != null "> and alarm_level = #{alarmLevel}</if>
             <if test="alarmCreateTime != null  and alarmCreateTime != ''"> and alarm_create_time = #{alarmCreateTime}</if>
+            <if test="confirmed != null and confirmed"> and process_flag = 2</if>
+            <if test="confirmed != null and !confirmed"> and process_flag != 2</if>
+            <if test="alarmDate != null  and alarmDate != ''"> and alarm_create_time &gt;= concat(#{alarmDate}, ' 00:00:00') and alarm_create_time &lt;= concat(#{alarmDate}, ' 23:59:59')</if>
             <if test="processFlag != null "> and process_flag = #{processFlag}</if>
             <if test="processPerson != null  and processPerson != ''"> and process_person = #{processPerson}</if>
             <if test="processResult != null  and processResult != ''"> and process_result = #{processResult}</if>
             <if test="sendStatus != null "> and send_status = #{sendStatus}</if>
             <if test="processPersonId != null  and processPersonId != ''"> and process_person_id = #{processPersonId}</if>
         </where>
+        order by alarm_create_time desc
     </select>
     
     <select id="selectAlarmEventRecordById" parameterType="String" resultMap="AlarmEventRecordResult">
@@ -57,6 +63,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="alarmRuleId != null and alarmRuleId != ''">alarm_rule_id,</if>
             <if test="alarmData != null">alarm_data,</if>
             <if test="monitoringTerminal != null">monitoring_terminal,</if>
+            <if test="alarmCategory != null and alarmCategory != ''">alarm_category,</if>
             <if test="createTime != null">create_time,</if>
             <if test="createBy != null">create_by,</if>
             <if test="updateTime != null">update_time,</if>
@@ -75,6 +82,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="alarmRuleId != null and alarmRuleId != ''">#{alarmRuleId},</if>
             <if test="alarmData != null">#{alarmData},</if>
             <if test="monitoringTerminal != null">#{monitoringTerminal},</if>
+            <if test="alarmCategory != null and alarmCategory != ''">#{alarmCategory},</if>
             <if test="createTime != null">#{createTime},</if>
             <if test="createBy != null">#{createBy},</if>
             <if test="updateTime != null">#{updateTime},</if>
@@ -96,6 +104,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="alarmRuleId != null and alarmRuleId != ''">alarm_rule_id = #{alarmRuleId},</if>
             <if test="alarmData != null">alarm_data = #{alarmData},</if>
             <if test="monitoringTerminal != null">monitoring_terminal = #{monitoringTerminal},</if>
+            <if test="alarmCategory != null and alarmCategory != ''">alarm_category = #{alarmCategory},</if>
             <if test="createTime != null">create_time = #{createTime},</if>
             <if test="createBy != null">create_by = #{createBy},</if>
             <if test="updateTime != null">update_time = #{updateTime},</if>
@@ -116,9 +125,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </delete>
 
     <delete id="deleteAlarmEventRecordByIds" parameterType="String">
-        delete from alarm_event_record where id in 
+        delete from alarm_event_record where id in
         <foreach item="id" collection="array" open="(" separator="," close=")">
             #{id}
         </foreach>
     </delete>
+
+    <update id="processAlarmEventRecord">
+        update alarm_event_record
+        set process_flag = 2, update_time = #{updateTime}
+        where process_flag = 0 and id in
+        <foreach item="id" collection="ids" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </update>
 </mapper>

+ 14 - 28
forecast-backend-vue/src/api/alarm/eventRecord.js

@@ -1,44 +1,30 @@
 import request from '@/utils/request'
 
-// 查询报警信息统计列表
-export function listEventRecord(query) {
+// 查询事件列表
+export function listEventRecord(data) {
   return request({
     url: '/alarm/eventRecord/list',
-    method: 'get',
-    params: query
-  })
-}
-
-// 查询报警信息统计详细
-export function getEventRecord(id) {
-  return request({
-    url: '/alarm/eventRecord/' + id,
-    method: 'get'
-  })
-}
-
-// 新增报警信息统计
-export function addEventRecord(data) {
-  return request({
-    url: '/alarm/eventRecord',
     method: 'post',
     data: data
   })
 }
 
-// 修改报警信息统计
-export function updateEventRecord(data) {
+// 确认告警(批量处理)
+export function processEventRecord(ids) {
   return request({
-    url: '/alarm/eventRecord',
-    method: 'put',
-    data: data
+    url: '/alarm/eventRecord/process',
+    method: 'post',
+    data: { ids: ids }
   })
 }
 
-// 删除报警信息统计
-export function delEventRecord(id) {
+// 导出事件列表
+export function exportEventRecord(data) {
   return request({
-    url: '/alarm/eventRecord/' + id,
-    method: 'delete'
+    url: '/alarm/eventRecord/export',
+    method: 'post',
+    data: data,
+    responseType: 'blob',
+    timeout: 120000
   })
 }

+ 255 - 0
forecast-backend-vue/src/views/ufo/electricity/statanalysis/eventQuery.vue

@@ -0,0 +1,255 @@
+<template>
+  <div class="event-query-page">
+    <a-card :bordered="false">
+      <a-row :gutter="[12, 12]" align="middle">
+        <a-col :xs="24" :md="5">
+          <a-select
+            v-model:value="query.alarmCategory"
+            :options="categoryOptions"
+            placeholder="告警类别"
+            allow-clear
+            class="full-control"
+          />
+        </a-col>
+        <a-col :xs="24" :md="5">
+          <a-select
+            v-model:value="query.alarmLevel"
+            :options="levelOptions"
+            placeholder="告警等级"
+            allow-clear
+            class="full-control"
+          />
+        </a-col>
+        <a-col :xs="24" :md="4">
+          <a-select
+            v-model:value="query.confirmed"
+            :options="confirmedOptions"
+            placeholder="是否确认"
+            allow-clear
+            class="full-control"
+          />
+        </a-col>
+        <a-col :xs="24" :md="5">
+          <a-date-picker
+            v-model:value="query.alarmDate"
+            format="YYYY-MM-DD"
+            value-format="YYYY-MM-DD"
+            placeholder="告警时间"
+            class="full-control"
+          />
+        </a-col>
+        <a-col :xs="24" :md="5">
+          <a-space wrap>
+            <a-button type="primary" :loading="loading" @click="handleQuery">查询</a-button>
+            <a-button :loading="processLoading" @click="handleProcess">确认告警</a-button>
+            <a-button :loading="exportLoading" @click="handleExport">导出</a-button>
+          </a-space>
+        </a-col>
+      </a-row>
+    </a-card>
+
+    <a-card :bordered="false" class="content-card">
+      <a-table
+        :columns="columns"
+        :data-source="rows"
+        :loading="loading"
+        :pagination="pagination"
+        :row-selection="rowSelection"
+        row-key="id"
+        size="middle"
+        @change="handleTableChange"
+      >
+        <template #bodyCell="{ column, record }">
+          <template v-if="column.dataIndex === 'alarmLevel'">
+            {{ getDictLabel(record.alarmLevel, alarmLevel.value) }}
+          </template>
+          <template v-else-if="column.dataIndex === 'alarmCategory'">
+            {{ getDictLabel(record.alarmCategory, alarmCategory.value) }}
+          </template>
+          <template v-else-if="column.dataIndex === 'processFlag'">
+            <a-tag :color="processColorMap[record.processFlag] || 'default'">
+              {{ processLabelMap[record.processFlag] ?? record.processFlag }}
+            </a-tag>
+          </template>
+        </template>
+      </a-table>
+    </a-card>
+  </div>
+</template>
+
+<script setup name="EventQuery">
+import {computed, onMounted, reactive, ref} from 'vue';
+import {message, Modal} from 'ant-design-vue';
+import dayjs from 'dayjs';
+import {saveAs} from 'file-saver';
+import {blobValidate} from '@/utils/bearjia';
+import {useDict, getDictLabel} from '@/composables/useDict';
+import {listEventRecord, processEventRecord, exportEventRecord} from '@/api/alarm/eventRecord';
+
+const {alarm_category, alarm_level} = useDict('alarm_category', 'alarm_level');
+const alarmCategory = alarm_category;
+const alarmLevel = alarm_level;
+
+const categoryOptions = computed(() =>
+  alarmCategory.value.map((o) => ({label: o.dictLabel, value: o.dictValue}))
+);
+const levelOptions = computed(() =>
+  alarmLevel.value.map((o) => ({label: o.dictLabel, value: o.dictValue}))
+);
+
+const confirmedOptions = [
+  {label: '已确认', value: true},
+  {label: '未确认', value: false}
+];
+
+const processLabelMap = {
+  0: '未处理',
+  1: '处理中',
+  2: '已处理',
+  3: '误报'
+};
+
+const processColorMap = {
+  0: 'default',
+  1: 'processing',
+  2: 'success',
+  3: 'warning'
+};
+
+const columns = [
+  {title: '告警等级', dataIndex: 'alarmLevel', width: 120},
+  {title: '告警类别', dataIndex: 'alarmCategory', width: 140},
+  {title: '告警内容', dataIndex: 'alarmData', ellipsis: true},
+  {title: '处理状态', dataIndex: 'processFlag', width: 110},
+  {title: '告警日期', dataIndex: 'alarmCreateTime', width: 180}
+];
+
+const loading = ref(false);
+const processLoading = ref(false);
+const exportLoading = ref(false);
+const rows = ref([]);
+const selectedRowKeys = ref([]);
+const rowSelection = reactive({
+  columnWidth: 48,
+  selectedRowKeys: selectedRowKeys,
+  onChange: (keys) => {
+    selectedRowKeys.value = keys;
+  }
+});
+const pagination = reactive({
+  current: 1,
+  pageSize: 10,
+  total: 0,
+  showSizeChanger: true,
+  showTotal: (total) => `共 ${total} 条`
+});
+
+const query = reactive({
+  alarmCategory: undefined,
+  alarmLevel: undefined,
+  confirmed: undefined,
+  alarmDate: dayjs().format('YYYY-MM-DD')
+});
+
+const buildParams = () => ({
+  alarmCategory: query.alarmCategory,
+  alarmLevel: query.alarmLevel,
+  confirmed: query.confirmed,
+  alarmDate: query.alarmDate
+});
+
+const fetchList = async (pageNum, pageSize) => {
+  loading.value = true;
+  try {
+    const res = await listEventRecord({
+      ...buildParams(),
+      pageNum,
+      pageSize
+    });
+    rows.value = res.rows || [];
+    pagination.total = res.total || 0;
+    pagination.current = pageNum;
+    pagination.pageSize = pageSize;
+  } catch (e) {
+    // request.js 已统一提示错误
+  } finally {
+    loading.value = false;
+  }
+};
+
+const handleQuery = () => {
+  fetchList(1, pagination.pageSize);
+};
+
+const handleTableChange = (pag) => {
+  fetchList(pag.current, pag.pageSize);
+};
+
+const handleProcess = () => {
+  if (selectedRowKeys.value.length === 0) {
+    message.warning('请先勾选要确认的告警数据');
+    return;
+  }
+  Modal.confirm({
+    title: '确认告警',
+    content: `确定将选中的 ${selectedRowKeys.value.length} 条告警标记为已处理吗?仅未处理的告警会被处理。`,
+    okText: '确认',
+    cancelText: '取消',
+    onOk: async () => {
+      processLoading.value = true;
+      try {
+        await processEventRecord(selectedRowKeys.value);
+        message.success('确认成功');
+        selectedRowKeys.value = [];
+        fetchList(pagination.current, pagination.pageSize);
+      } catch (e) {
+        // request.js 已统一提示错误
+      } finally {
+        processLoading.value = false;
+      }
+    }
+  });
+};
+
+const handleExport = async () => {
+  exportLoading.value = true;
+  try {
+    const res = await exportEventRecord(buildParams());
+    const isFile = await blobValidate(res);
+    if (isFile) {
+      const name = `事件查询_${dayjs().format('YYYYMMDDHHmmss')}.xlsx`;
+      saveAs(new Blob([res]), name);
+      message.success('导出成功');
+    } else {
+      const text = await res.text();
+      let msg = '导出失败';
+      try {
+        msg = JSON.parse(text)?.msg || msg;
+      } catch (e) {
+        /* ignore */
+      }
+      message.error(msg);
+    }
+  } catch (e) {
+    // request.js 已统一提示错误
+  } finally {
+    exportLoading.value = false;
+  }
+};
+
+onMounted(() => {
+  fetchList(1, pagination.pageSize);
+});
+</script>
+
+<style lang="less" scoped>
+.event-query-page {
+  padding: 16px;
+}
+.full-control {
+  width: 100%;
+}
+.content-card {
+  margin-top: 16px;
+}
+</style>

+ 72 - 0
release_note/20261028/20261028_alarm_event_query.sql

@@ -0,0 +1,72 @@
+-- =====================================================
+-- [26110201] 事件查询优化(幂等,可重复执行)
+-- 1. alarm_event_record 增加告警类别字段 alarm_category(数据后续由外部补充)
+-- 2. 字典配置:告警类别(alarm_category)、告警等级(alarm_level)
+--    注意: 字典项暂为占位值,后续替换为真实 code/name
+-- 3. 统计分析 下增加 事件查询 菜单
+-- =====================================================
+
+-- 1. 增加告警类别字段(幂等)
+ALTER TABLE alarm_event_record ADD COLUMN IF NOT EXISTS alarm_category VARCHAR(50);
+COMMENT ON COLUMN alarm_event_record.alarm_category IS '告警类别';
+
+-- 2.1 字典类型: 告警类别
+INSERT INTO sys_dict_type (dict_id, dict_name, dict_type, status, create_by, create_time, remark)
+SELECT
+nextval(pg_get_serial_sequence('sys_dict_type', 'dict_id')),
+'告警类别', 'alarm_category', '0', 'admin', now(), '事件查询-告警类别'
+WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type t WHERE t.dict_type = 'alarm_category');
+
+-- 2.2 字典类型: 告警等级
+INSERT INTO sys_dict_type (dict_id, dict_name, dict_type, status, create_by, create_time, remark)
+SELECT
+nextval(pg_get_serial_sequence('sys_dict_type', 'dict_id')),
+'告警等级', 'alarm_level', '0', 'admin', now(), '事件查询-告警等级'
+WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type t WHERE t.dict_type = 'alarm_level');
+
+-- 2.3 告警类别字典数据(占位,待替换为真实值)
+INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark)
+SELECT
+nextval(pg_get_serial_sequence('sys_dict_data', 'dict_code')),
+1, '占位-类别一', '1', 'alarm_category', '', '', 'N', '0', 'admin', now(), '占位, 待替换为真实告警类别'
+WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data t WHERE t.dict_type = 'alarm_category' AND t.dict_value = '1');
+
+INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark)
+SELECT
+nextval(pg_get_serial_sequence('sys_dict_data', 'dict_code')),
+2, '占位-类别二', '2', 'alarm_category', '', '', 'N', '0', 'admin', now(), '占位, 待替换为真实告警类别'
+WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data t WHERE t.dict_type = 'alarm_category' AND t.dict_value = '2');
+
+INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark)
+SELECT
+nextval(pg_get_serial_sequence('sys_dict_data', 'dict_code')),
+3, '占位-类别三', '3', 'alarm_category', '', '', 'N', '0', 'admin', now(), '占位, 待替换为真实告警类别'
+WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data t WHERE t.dict_type = 'alarm_category' AND t.dict_value = '3');
+
+-- 2.4 告警等级字典数据(占位,待替换为真实值)
+INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark)
+SELECT
+nextval(pg_get_serial_sequence('sys_dict_data', 'dict_code')),
+1, '占位-等级一', '1', 'alarm_level', '', '', 'N', '0', 'admin', now(), '占位, 待替换为真实告警等级'
+WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data t WHERE t.dict_type = 'alarm_level' AND t.dict_value = '1');
+
+INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark)
+SELECT
+nextval(pg_get_serial_sequence('sys_dict_data', 'dict_code')),
+2, '占位-等级二', '2', 'alarm_level', '', '', 'N', '0', 'admin', now(), '占位, 待替换为真实告警等级'
+WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data t WHERE t.dict_type = 'alarm_level' AND t.dict_value = '2');
+
+INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark)
+SELECT
+nextval(pg_get_serial_sequence('sys_dict_data', 'dict_code')),
+3, '占位-等级三', '3', 'alarm_level', '', '', 'N', '0', 'admin', now(), '占位, 待替换为真实告警等级'
+WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data t WHERE t.dict_type = 'alarm_level' AND t.dict_value = '3');
+
+-- 3. 增加 事件查询 二级菜单(挂在 统计分析 下)
+INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+SELECT
+(SELECT COALESCE(MAX(menu_id), 0) + 1 FROM sys_menu),
+'事件查询',
+(SELECT menu_id FROM sys_menu t WHERE t.menu_name = '统计分析' AND t.parent_id = 0),
+50, 'event-query', 'ufo/electricity/statanalysis/eventQuery', '', 'EventQuery', 1, 0, 'C', '0', '0', 'alarm:eventRecord:list', 'AlertOutlined', 'admin', now(), '', NULL, '事件查询页面'
+WHERE NOT EXISTS (SELECT 1 FROM sys_menu t WHERE t.menu_name = '事件查询');