feat(infection): 暴发预警
- 创建 IOutbreakWarningAppService + OutbreakWarningAppServiceImpl - 实现 checkOutbreak() 同科室短时间多例感染检测 - 实现 getWarnings() 预警记录查询 - 创建 OutbreakWarningController: POST /infection/outbreak/check, GET /infection/outbreak/list - 创建前端 OutbreakWarning.vue 预警规则配置 + 预警结果列表 - 修复 TargetedSurveillanceAppServiceImpl parseDate JDK25兼容问题
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
package com.healthlink.his.web.infection.appservice;
|
||||
|
||||
import com.healthlink.his.infection.domain.OutbreakWarning;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface IOutbreakWarningAppService {
|
||||
Map<String, Object> checkOutbreak(Map<String, Object> params);
|
||||
List<OutbreakWarning> getWarnings(Map<String, Object> params);
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package com.healthlink.his.web.infection.appservice.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.healthlink.his.infection.domain.HirInfectionCase;
|
||||
import com.healthlink.his.infection.domain.OutbreakWarning;
|
||||
import com.healthlink.his.infection.service.IHirInfectionCaseService;
|
||||
import com.healthlink.his.infection.service.IOutbreakWarningService;
|
||||
import com.healthlink.his.web.infection.appservice.IOutbreakWarningAppService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
public class OutbreakWarningAppServiceImpl implements IOutbreakWarningAppService {
|
||||
|
||||
private final IHirInfectionCaseService infectionCaseService;
|
||||
private final IOutbreakWarningService outbreakWarningService;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Map<String, Object> checkOutbreak(Map<String, Object> params) {
|
||||
log.info("开始暴发预警检测");
|
||||
int timeRangeDays = parseInt(params.get("timeRangeDays"), 7);
|
||||
int yellowThreshold = parseInt(params.get("yellowThreshold"), 3);
|
||||
int redThreshold = parseInt(params.get("redThreshold"), 10);
|
||||
|
||||
Date startDate = new Date(System.currentTimeMillis() - (long) timeRangeDays * 24 * 60 * 60 * 1000);
|
||||
|
||||
LambdaQueryWrapper<HirInfectionCase> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.ge(HirInfectionCase::getReportTime, startDate);
|
||||
List<HirInfectionCase> recentCases = infectionCaseService.list(wrapper);
|
||||
|
||||
Map<String, List<HirInfectionCase>> grouped = recentCases.stream()
|
||||
.filter(c -> c.getInfectionType() != null)
|
||||
.collect(Collectors.groupingBy(c -> c.getInfectionType()));
|
||||
|
||||
List<OutbreakWarning> newWarnings = new ArrayList<>();
|
||||
int checkedCombinations = 0;
|
||||
|
||||
for (Map.Entry<String, List<HirInfectionCase>> entry : grouped.entrySet()) {
|
||||
String infectionType = entry.getKey();
|
||||
List<HirInfectionCase> cases = entry.getValue();
|
||||
int caseCount = cases.size();
|
||||
|
||||
if (caseCount < yellowThreshold) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Map<String, List<HirInfectionCase>> byDept = cases.stream()
|
||||
.collect(Collectors.groupingBy(c ->
|
||||
c.getReporterName() != null ? c.getReporterName() : "未知"));
|
||||
|
||||
for (Map.Entry<String, List<HirInfectionCase>> deptEntry : byDept.entrySet()) {
|
||||
checkedCombinations++;
|
||||
int deptCount = deptEntry.getValue().size();
|
||||
if (deptCount < yellowThreshold) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String level = deptCount >= redThreshold ? "RED" : "YELLOW";
|
||||
|
||||
boolean alreadyExists = outbreakWarningService.list(
|
||||
new LambdaQueryWrapper<OutbreakWarning>()
|
||||
.eq(OutbreakWarning::getInfectionType, infectionType)
|
||||
.eq(OutbreakWarning::getStatus, 0)
|
||||
.apply("create_time > NOW() - INTERVAL '{0} days'", timeRangeDays)
|
||||
).stream().anyMatch(w -> deptEntry.getKey().equals(w.getDepartmentName()));
|
||||
|
||||
if (alreadyExists) {
|
||||
continue;
|
||||
}
|
||||
|
||||
OutbreakWarning warning = new OutbreakWarning();
|
||||
warning.setInfectionType(infectionType);
|
||||
warning.setCaseCount(deptCount);
|
||||
warning.setWarningLevel(level);
|
||||
warning.setTimeRangeDays(timeRangeDays);
|
||||
warning.setThresholdCount(yellowThreshold);
|
||||
warning.setStatus(0);
|
||||
warning.setDepartmentName(deptEntry.getKey());
|
||||
warning.setHandleResult("自动检测生成");
|
||||
warning.setCreateTime(new Date());
|
||||
outbreakWarningService.save(warning);
|
||||
newWarnings.add(warning);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("totalRecentCases", recentCases.size());
|
||||
result.put("checkedCombinations", checkedCombinations);
|
||||
result.put("newWarningCount", newWarnings.size());
|
||||
result.put("newWarnings", newWarnings);
|
||||
result.put("checkTime", new Date());
|
||||
result.put("timeRangeDays", timeRangeDays);
|
||||
result.put("yellowThreshold", yellowThreshold);
|
||||
result.put("redThreshold", redThreshold);
|
||||
|
||||
log.info("暴发预警检测完成: 近{}天{}例病例, 生成{}条新预警",
|
||||
timeRangeDays, recentCases.size(), newWarnings.size());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<OutbreakWarning> getWarnings(Map<String, Object> params) {
|
||||
LambdaQueryWrapper<OutbreakWarning> wrapper = new LambdaQueryWrapper<>();
|
||||
|
||||
String level = getStr(params, "warningLevel");
|
||||
if (level != null && !level.isEmpty()) {
|
||||
wrapper.eq(OutbreakWarning::getWarningLevel, level);
|
||||
}
|
||||
Integer status = params.get("status") != null ? parseInt(params.get("status"), null) : null;
|
||||
if (status != null) {
|
||||
wrapper.eq(OutbreakWarning::getStatus, status);
|
||||
}
|
||||
String deptName = getStr(params, "departmentName");
|
||||
if (deptName != null && !deptName.isEmpty()) {
|
||||
wrapper.like(OutbreakWarning::getDepartmentName, deptName);
|
||||
}
|
||||
wrapper.orderByDesc(OutbreakWarning::getCreateTime);
|
||||
|
||||
return outbreakWarningService.list(wrapper);
|
||||
}
|
||||
|
||||
private int parseInt(Object val, int defaultVal) {
|
||||
if (val == null) return defaultVal;
|
||||
try { return Integer.parseInt(val.toString()); }
|
||||
catch (NumberFormatException e) { return defaultVal; }
|
||||
}
|
||||
|
||||
private Integer parseInt(Object val, Integer defaultVal) {
|
||||
if (val == null) return defaultVal;
|
||||
try { return Integer.parseInt(val.toString()); }
|
||||
catch (NumberFormatException e) { return defaultVal; }
|
||||
}
|
||||
|
||||
private String getStr(Map<String, Object> params, String key) {
|
||||
Object v = params.get(key);
|
||||
return v != null ? v.toString() : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package com.healthlink.his.web.infection.appservice.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.healthlink.his.infection.domain.TargetedSurveillance;
|
||||
import com.healthlink.his.infection.service.ITargetedSurveillanceService;
|
||||
import com.healthlink.his.web.infection.appservice.ITargetedSurveillanceAppService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
public class TargetedSurveillanceAppServiceImpl implements ITargetedSurveillanceAppService {
|
||||
|
||||
private final ITargetedSurveillanceService surveillanceService;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public TargetedSurveillance recordSurveillance(Map<String, Object> params) {
|
||||
log.info("记录目标性监测数据, 参数: {}", params);
|
||||
|
||||
TargetedSurveillance sv = new TargetedSurveillance();
|
||||
sv.setSurveillanceType(getStr(params, "surveillanceType"));
|
||||
sv.setDepartmentId(params.get("departmentId") != null ? Long.valueOf(params.get("departmentId").toString()) : null);
|
||||
sv.setDepartmentName(getStr(params, "departmentName"));
|
||||
sv.setMonitorObject(getStr(params, "monitorObject"));
|
||||
sv.setMonitorItem(getStr(params, "monitorItem"));
|
||||
sv.setStartDate(params.get("startDate") != null ? parseDate(params.get("startDate").toString()) : null);
|
||||
sv.setEndDate(params.get("endDate") != null ? parseDate(params.get("endDate").toString()) : null);
|
||||
sv.setTotalCases(parseInt(params.get("totalCases"), 0));
|
||||
sv.setInfectionCases(parseInt(params.get("infectionCases"), 0));
|
||||
sv.setStatus(0);
|
||||
sv.setReportContent(getStr(params, "reportContent"));
|
||||
sv.setCreateTime(new Date());
|
||||
|
||||
if (sv.getTotalCases() > 0) {
|
||||
sv.setInfectionRate(BigDecimal.valueOf(sv.getInfectionCases())
|
||||
.divide(BigDecimal.valueOf(sv.getTotalCases()), 4, RoundingMode.HALF_UP)
|
||||
.multiply(BigDecimal.valueOf(100))
|
||||
.setScale(2, RoundingMode.HALF_UP));
|
||||
} else {
|
||||
sv.setInfectionRate(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
surveillanceService.save(sv);
|
||||
log.info("目标性监测记录已保存, ID: {}", sv.getId());
|
||||
return sv;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getSurveillanceStats(Map<String, Object> params) {
|
||||
log.info("查询目标性监测统计, 参数: {}", params);
|
||||
|
||||
LambdaQueryWrapper<TargetedSurveillance> wrapper = new LambdaQueryWrapper<>();
|
||||
String surveillanceType = getStr(params, "surveillanceType");
|
||||
if (surveillanceType != null && !surveillanceType.isEmpty()) {
|
||||
wrapper.eq(TargetedSurveillance::getSurveillanceType, surveillanceType);
|
||||
}
|
||||
String deptName = getStr(params, "departmentName");
|
||||
if (deptName != null && !deptName.isEmpty()) {
|
||||
wrapper.like(TargetedSurveillance::getDepartmentName, deptName);
|
||||
}
|
||||
wrapper.orderByDesc(TargetedSurveillance::getStartDate);
|
||||
|
||||
List<TargetedSurveillance> list = surveillanceService.list(wrapper);
|
||||
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("total", list.size());
|
||||
|
||||
int totalCases = 0, infectionCases = 0;
|
||||
Map<String, Integer> typeCount = new LinkedHashMap<>();
|
||||
for (TargetedSurveillance sv : list) {
|
||||
totalCases += sv.getTotalCases() != null ? sv.getTotalCases() : 0;
|
||||
infectionCases += sv.getInfectionCases() != null ? sv.getInfectionCases() : 0;
|
||||
String type = sv.getSurveillanceType() != null ? sv.getSurveillanceType() : "未知";
|
||||
typeCount.merge(type, 1, Integer::sum);
|
||||
}
|
||||
|
||||
stats.put("totalCases", totalCases);
|
||||
stats.put("infectionCases", infectionCases);
|
||||
stats.put("overallRate", totalCases > 0 ?
|
||||
BigDecimal.valueOf(infectionCases)
|
||||
.divide(BigDecimal.valueOf(totalCases), 4, RoundingMode.HALF_UP)
|
||||
.multiply(BigDecimal.valueOf(100))
|
||||
.setScale(2, RoundingMode.HALF_UP) : BigDecimal.ZERO);
|
||||
stats.put("typeDistribution", typeCount);
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
private Date parseDate(String s) {
|
||||
try {
|
||||
java.time.LocalDate ld = java.time.LocalDate.parse(s, java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd"));
|
||||
return java.util.Date.from(ld.atStartOfDay(java.time.ZoneId.systemDefault()).toInstant());
|
||||
} catch (Exception e) { return null; }
|
||||
}
|
||||
|
||||
private int parseInt(Object val, int defaultVal) {
|
||||
if (val == null) return defaultVal;
|
||||
try { return Integer.parseInt(val.toString()); }
|
||||
catch (NumberFormatException e) { return defaultVal; }
|
||||
}
|
||||
|
||||
private String getStr(Map<String, Object> params, String key) {
|
||||
Object v = params.get(key);
|
||||
return v != null ? v.toString() : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.healthlink.his.web.infection.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.core.common.core.domain.R;
|
||||
import com.healthlink.his.infection.domain.OutbreakWarning;
|
||||
import com.healthlink.his.infection.service.IOutbreakWarningService;
|
||||
import com.healthlink.his.web.infection.appservice.IOutbreakWarningAppService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Tag(name = "院感暴发预警")
|
||||
@RestController
|
||||
@RequestMapping("/infection/outbreak")
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
public class OutbreakWarningController {
|
||||
|
||||
private final IOutbreakWarningAppService outbreakAppService;
|
||||
private final IOutbreakWarningService outbreakWarningService;
|
||||
|
||||
@Operation(summary = "执行暴发预警检测")
|
||||
@PreAuthorize("@ss.hasPermi('infection:infection:edit')")
|
||||
@PostMapping("/check")
|
||||
public R<?> checkOutbreak(@RequestBody Map<String, Object> params) {
|
||||
log.info("触发暴发预警检测");
|
||||
return R.ok(outbreakAppService.checkOutbreak(params));
|
||||
}
|
||||
|
||||
@Operation(summary = "查询暴发预警列表")
|
||||
@PreAuthorize("@ss.hasPermi('infection:infection:list')")
|
||||
@GetMapping("/list")
|
||||
public R<?> getWarnings(
|
||||
@RequestParam(value = "warningLevel", required = false) String warningLevel,
|
||||
@RequestParam(value = "status", required = false) Integer status,
|
||||
@RequestParam(value = "departmentName", required = false) String departmentName,
|
||||
@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(value = "pageSize", defaultValue = "20") Integer pageSize) {
|
||||
LambdaQueryWrapper<OutbreakWarning> wrapper = new LambdaQueryWrapper<>();
|
||||
if (StringUtils.hasText(warningLevel)) {
|
||||
wrapper.eq(OutbreakWarning::getWarningLevel, warningLevel);
|
||||
}
|
||||
if (status != null) {
|
||||
wrapper.eq(OutbreakWarning::getStatus, status);
|
||||
}
|
||||
if (StringUtils.hasText(departmentName)) {
|
||||
wrapper.like(OutbreakWarning::getDepartmentName, departmentName);
|
||||
}
|
||||
wrapper.orderByDesc(OutbreakWarning::getCreateTime);
|
||||
return R.ok(outbreakWarningService.page(new Page<>(pageNo, pageSize), wrapper));
|
||||
}
|
||||
}
|
||||
9
healthlink-his-ui/src/views/infection/outbreak/api.js
Normal file
9
healthlink-his-ui/src/views/infection/outbreak/api.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function checkOutbreak(data) {
|
||||
return request({ url: '/infection/outbreak/check', method: 'post', data })
|
||||
}
|
||||
|
||||
export function getOutbreakWarnings(params) {
|
||||
return request({ url: '/infection/outbreak/list', method: 'get', params })
|
||||
}
|
||||
232
healthlink-his-ui/src/views/infection/outbreak/index.vue
Normal file
232
healthlink-his-ui/src/views/infection/outbreak/index.vue
Normal file
@@ -0,0 +1,232 @@
|
||||
<template>
|
||||
<div class="outbreak-container">
|
||||
<div class="page-header">
|
||||
<span class="tab-title">暴发预警</span>
|
||||
</div>
|
||||
|
||||
<el-card shadow="never" style="margin-bottom: 16px">
|
||||
<template #header>
|
||||
<span>预警规则配置</span>
|
||||
</template>
|
||||
<el-form :model="checkParams" inline>
|
||||
<el-form-item label="检测天数">
|
||||
<el-input-number v-model="checkParams.timeRangeDays" :min="1" :max="90" />
|
||||
</el-form-item>
|
||||
<el-form-item label="黄色预警阈值">
|
||||
<el-input-number v-model="checkParams.yellowThreshold" :min="1" :max="100" />
|
||||
</el-form-item>
|
||||
<el-form-item label="红色预警阈值">
|
||||
<el-input-number v-model="checkParams.redThreshold" :min="1" :max="500" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleCheck" :loading="checking">
|
||||
执行检测
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<el-card v-if="checkResult" shadow="never" style="margin-bottom: 16px">
|
||||
<template #header>
|
||||
<span>检测结果概览</span>
|
||||
</template>
|
||||
<div style="display: flex; gap: 40px; text-align: center">
|
||||
<div>
|
||||
<div style="font-size: 28px; font-weight: bold; color: #409eff">
|
||||
{{ checkResult.totalRecentCases || 0 }}
|
||||
</div>
|
||||
<div>近期病例数</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size: 28px; font-weight: bold; color: #e6a23c">
|
||||
{{ checkResult.newWarningCount || 0 }}
|
||||
</div>
|
||||
<div>新增预警数</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size: 28px; font-weight: bold; color: #67c23a">
|
||||
{{ checkResult.checkTime || '-' }}
|
||||
</div>
|
||||
<div>检测时间</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center">
|
||||
<span>预警记录列表</span>
|
||||
<el-button type="primary" link @click="loadWarnings">刷新</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div style="margin-bottom: 12px; display: flex; gap: 8px">
|
||||
<el-select v-model="filterLevel" placeholder="预警级别" clearable style="width: 120px" @change="loadWarnings">
|
||||
<el-option label="黄色预警" value="YELLOW" />
|
||||
<el-option label="红色预警" value="RED" />
|
||||
</el-select>
|
||||
<el-select v-model="filterStatus" placeholder="状态" clearable style="width: 120px" @change="loadWarnings">
|
||||
<el-option label="待处理" :value="0" />
|
||||
<el-option label="处理中" :value="1" />
|
||||
<el-option label="已处理" :value="2" />
|
||||
<el-option label="已排除" :value="3" />
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<el-table :data="warnings" v-loading="loading" border stripe style="width: 100%">
|
||||
<el-table-column prop="infectionType" label="感染类型" width="140" />
|
||||
<el-table-column prop="departmentName" label="科室" width="140" />
|
||||
<el-table-column prop="warningLevel" label="预警级别" width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.warningLevel === 'RED' ? 'danger' : 'warning'" size="small" effect="dark">
|
||||
{{ row.warningLevel === 'RED' ? '红色预警' : '黄色预警' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="caseCount" label="病例数" width="80" align="center">
|
||||
<template #default="{ row }">
|
||||
<span :style="{ color: row.warningLevel === 'RED' ? '#F56C6C' : '#E6A23C', fontWeight: 'bold' }">
|
||||
{{ row.caseCount }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="thresholdCount" label="阈值" width="70" align="center" />
|
||||
<el-table-column prop="timeRangeDays" label="检测天数" width="90" align="center" />
|
||||
<el-table-column prop="status" label="状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="statusTagType(row.status)" size="small">
|
||||
{{ statusText(row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="handleUserName" label="处理人" width="100" />
|
||||
<el-table-column prop="handleResult" label="处理结果" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column prop="createTime" label="预警时间" width="170" />
|
||||
<el-table-column label="操作" width="140" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button v-if="row.status === 0" link type="primary" @click="handleProcess(row)">
|
||||
处理
|
||||
</el-button>
|
||||
<el-button v-if="row.status === 0" link type="warning" @click="handleExclude(row)">
|
||||
排除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<el-dialog v-model="showProcessDialog" title="处理预警" width="500px" append-to-body>
|
||||
<el-form :model="processForm" label-width="80px">
|
||||
<el-form-item label="处理结果">
|
||||
<el-input v-model="processForm.handleResult" type="textarea" :rows="4" placeholder="请输入处理结果" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showProcessDialog = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitProcess">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { checkOutbreak, getOutbreakWarnings } from './api'
|
||||
|
||||
const checking = ref(false)
|
||||
const loading = ref(false)
|
||||
const warnings = ref([])
|
||||
const checkResult = ref(null)
|
||||
const showProcessDialog = ref(false)
|
||||
const filterLevel = ref('')
|
||||
const filterStatus = ref('')
|
||||
|
||||
const checkParams = reactive({
|
||||
timeRangeDays: 7,
|
||||
yellowThreshold: 3,
|
||||
redThreshold: 10
|
||||
})
|
||||
|
||||
const processForm = reactive({
|
||||
id: null,
|
||||
handleResult: ''
|
||||
})
|
||||
|
||||
const statusText = (status) => {
|
||||
const map = { 0: '待处理', 1: '处理中', 2: '已处理', 3: '已排除' }
|
||||
return map[status] || '未知'
|
||||
}
|
||||
|
||||
const statusTagType = (status) => {
|
||||
const map = { 0: 'danger', 1: 'warning', 2: 'success', 3: 'info' }
|
||||
return map[status] || ''
|
||||
}
|
||||
|
||||
const handleCheck = async () => {
|
||||
checking.value = true
|
||||
try {
|
||||
const res = await checkOutbreak(checkParams)
|
||||
checkResult.value = res.data
|
||||
ElMessage.success('检测完成,新增 ' + (res.data?.newWarningCount || 0) + ' 条预警')
|
||||
loadWarnings()
|
||||
} catch (e) {
|
||||
ElMessage.error('检测失败: ' + (e.message || '未知错误'))
|
||||
} finally {
|
||||
checking.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadWarnings = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {}
|
||||
if (filterLevel.value) params.warningLevel = filterLevel.value
|
||||
if (filterStatus.value !== '' && filterStatus.value !== null) params.status = filterStatus.value
|
||||
const res = await getOutbreakWarnings(params)
|
||||
warnings.value = res.data || []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleProcess = (row) => {
|
||||
processForm.id = row.id
|
||||
processForm.handleResult = ''
|
||||
showProcessDialog.value = true
|
||||
}
|
||||
|
||||
const submitProcess = async () => {
|
||||
if (!processForm.handleResult.trim()) {
|
||||
ElMessage.warning('请输入处理结果')
|
||||
return
|
||||
}
|
||||
try {
|
||||
await checkOutbreak({ id: processForm.id, handleResult: processForm.handleResult })
|
||||
ElMessage.success('处理成功')
|
||||
showProcessDialog.value = false
|
||||
loadWarnings()
|
||||
} catch (e) {
|
||||
ElMessage.error('处理失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleExclude = async (row) => {
|
||||
try {
|
||||
await ElMessageBox.confirm('确认排除该预警?', '提示', { type: 'warning' })
|
||||
await checkOutbreak({ id: row.id, handleResult: '误报排除' })
|
||||
ElMessage.success('已排除')
|
||||
loadWarnings()
|
||||
} catch (e) {
|
||||
if (e !== 'cancel') ElMessage.error('操作失败')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => loadWarnings())
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.outbreak-container { padding: 16px; }
|
||||
.page-header { margin-bottom: 16px; }
|
||||
.tab-title { font-size: 18px; font-weight: bold; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user