feat(cdss): upgrade rule engine with priority, category, execution history and stats

This commit is contained in:
2026-06-19 06:55:48 +08:00
parent 18e3c06b1a
commit 8b2b47b71c
15 changed files with 464 additions and 16 deletions

View File

@@ -2,6 +2,9 @@ package com.healthlink.his.web.cdss.appservice;
import com.core.common.core.domain.R;
import java.util.List;
import java.util.Map;
public interface ICdssAppService {
R<?> evaluateRules(Long encounterId, Long patientId, String triggerType, Long departmentId);
@@ -11,4 +14,10 @@ public interface ICdssAppService {
R<?> acknowledgeAlert(Long id, String remark);
R<?> getRules(String ruleType, String severity, String keyword);
R<?> getRuleStats();
R<?> getExecutionHistory(Long ruleId, Long encounterId, Integer page, Integer size);
R<?> getRulesEnhanced(String ruleType, String severity, String keyword, String category, Integer priority);
}

View File

@@ -1,10 +1,13 @@
package com.healthlink.his.web.cdss.appservice.impl;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.core.common.core.domain.R;
import com.healthlink.his.cdss.domain.CdssAlert;
import com.healthlink.his.cdss.domain.CdssRule;
import com.healthlink.his.cdss.domain.CdssRuleExecution;
import com.healthlink.his.cdss.service.ICdssAlertService;
import com.healthlink.his.cdss.service.ICdssRuleExecutionService;
import com.healthlink.his.cdss.service.ICdssRuleService;
import com.healthlink.his.web.cdss.appservice.ICdssAppService;
import org.slf4j.Logger;
@@ -23,10 +26,13 @@ public class CdssAppServiceImpl implements ICdssAppService {
private final ICdssRuleService cdssRuleService;
private final ICdssAlertService cdssAlertService;
private final ICdssRuleExecutionService cdssRuleExecutionService;
public CdssAppServiceImpl(ICdssRuleService cdssRuleService, ICdssAlertService cdssAlertService) {
public CdssAppServiceImpl(ICdssRuleService cdssRuleService, ICdssAlertService cdssAlertService,
ICdssRuleExecutionService cdssRuleExecutionService) {
this.cdssRuleService = cdssRuleService;
this.cdssAlertService = cdssAlertService;
this.cdssRuleExecutionService = cdssRuleExecutionService;
}
@Override
@@ -38,12 +44,36 @@ public class CdssAppServiceImpl implements ICdssAppService {
List<CdssAlert> triggeredAlerts = new ArrayList<>();
for (CdssRule rule : activeRules) {
if (matchRule(rule, encounterId, patientId)) {
CdssAlert alert = buildAlert(rule, encounterId, patientId);
cdssAlertService.save(alert);
triggeredAlerts.add(alert);
log.info("CDSS rule triggered: ruleCode={}, encounterId={}", rule.getRuleCode(), encounterId);
long startTime = System.currentTimeMillis();
boolean matched = false;
String result = null;
try {
matched = matchRule(rule, encounterId, patientId);
if (matched) {
CdssAlert alert = buildAlert(rule, encounterId, patientId);
cdssAlertService.save(alert);
triggeredAlerts.add(alert);
result = "MATCHED";
log.info("CDSS rule triggered: ruleCode={}, encounterId={}", rule.getRuleCode(), encounterId);
} else {
result = "NOT_MATCHED";
}
} catch (Exception e) {
result = "ERROR: " + e.getMessage();
log.warn("CDSS rule execution error: ruleCode={}, error={}", rule.getRuleCode(), e.getMessage());
}
long duration = System.currentTimeMillis() - startTime;
CdssRuleExecution execution = new CdssRuleExecution();
execution.setRuleId(rule.getId());
execution.setRuleCode(rule.getRuleCode());
execution.setEncounterId(encounterId);
execution.setPatientId(patientId);
execution.setMatched(matched);
execution.setExecutionTime(new Date());
execution.setExecutionResult(result);
execution.setDurationMs((int) duration);
cdssRuleExecutionService.save(execution);
}
return R.ok(Map.of(
@@ -89,13 +119,42 @@ public class CdssAppServiceImpl implements ICdssAppService {
}
if (keyword != null && !keyword.isEmpty()) {
rules = rules.stream()
.filter(r -> r.getRuleName().contains(keyword) ||
.filter(r -> r.getRuleName().contains(keyword) ||
(r.getRuleCode() != null && r.getRuleCode().contains(keyword)))
.toList();
}
return R.ok(rules);
}
@Override
public R<?> getRuleStats() {
return R.ok(cdssRuleService.getRuleStats());
}
@Override
public R<?> getExecutionHistory(Long ruleId, Long encounterId, Integer page, Integer size) {
LambdaQueryWrapper<CdssRuleExecution> wrapper = new LambdaQueryWrapper<>();
if (ruleId != null) {
wrapper.eq(CdssRuleExecution::getRuleId, ruleId);
}
if (encounterId != null) {
wrapper.eq(CdssRuleExecution::getEncounterId, encounterId);
}
wrapper.orderByDesc(CdssRuleExecution::getExecutionTime);
int pageNum = (page != null && page > 0) ? page : 1;
int pageSize = (size != null && size > 0) ? size : 20;
wrapper.last("LIMIT " + pageSize + " OFFSET " + (pageNum - 1) * pageSize);
List<CdssRuleExecution> history = cdssRuleExecutionService.list(wrapper);
return R.ok(history);
}
@Override
public R<?> getRulesEnhanced(String ruleType, String severity, String keyword,
String category, Integer priority) {
List<CdssRule> rules = cdssRuleService.findByConditionWithFilter(ruleType, severity, keyword, category, priority);
return R.ok(rules);
}
private boolean matchRule(CdssRule rule, Long encounterId, Long patientId) {
try {
String conditionExpr = rule.getConditionExpr();

View File

@@ -54,4 +54,34 @@ public class CdssController {
@RequestParam(value = "keyword", required = false) String keyword) {
return cdssAppService.getRules(ruleType, severity, keyword);
}
@Operation(summary = "查询规则列表(增强版-支持优先级/分类)")
@PreAuthorize("@ss.hasPermi('infection:cdss:list')")
@GetMapping("/rules/enhanced")
public R<?> getRulesEnhanced(
@RequestParam(value = "ruleType", required = false) String ruleType,
@RequestParam(value = "severity", required = false) String severity,
@RequestParam(value = "keyword", required = false) String keyword,
@RequestParam(value = "category", required = false) String category,
@RequestParam(value = "priority", required = false) Integer priority) {
return cdssAppService.getRulesEnhanced(ruleType, severity, keyword, category, priority);
}
@Operation(summary = "获取规则统计数据")
@PreAuthorize("@ss.hasPermi('infection:cdss:list')")
@GetMapping("/rules/stats")
public R<?> getRuleStats() {
return cdssAppService.getRuleStats();
}
@Operation(summary = "获取规则执行历史")
@PreAuthorize("@ss.hasPermi('infection:cdss:list')")
@GetMapping("/rules/history")
public R<?> getExecutionHistory(
@RequestParam(value = "ruleId", required = false) Long ruleId,
@RequestParam(value = "encounterId", required = false) Long encounterId,
@RequestParam(value = "page", required = false, defaultValue = "1") Integer page,
@RequestParam(value = "size", required = false, defaultValue = "20") Integer size) {
return cdssAppService.getExecutionHistory(ruleId, encounterId, page, size);
}
}

View File

@@ -0,0 +1,39 @@
-- V86: CDSS规则引擎升级 - 添加优先级/分类字段 + 规则执行历史
ALTER TABLE cdss_rule ADD COLUMN IF NOT EXISTS priority INT NOT NULL DEFAULT 0;
ALTER TABLE cdss_rule ADD COLUMN IF NOT EXISTS category VARCHAR(64);
COMMENT ON COLUMN cdss_rule.priority IS '规则优先级(0普通 1紧急 2最高)';
COMMENT ON COLUMN cdss_rule.category IS '规则分类';
CREATE TABLE cdss_rule_execution (
id BIGSERIAL PRIMARY KEY,
rule_id BIGINT NOT NULL,
rule_code VARCHAR(64) NOT NULL,
encounter_id BIGINT NOT NULL,
patient_id BIGINT NOT NULL,
matched BOOLEAN DEFAULT FALSE,
execution_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
execution_result TEXT,
duration_ms INT,
tenant_id BIGINT DEFAULT 0,
create_by VARCHAR(64),
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
delete_flag CHAR(1) DEFAULT '0'
);
COMMENT ON TABLE cdss_rule_execution IS 'CDSS规则执行历史';
COMMENT ON COLUMN cdss_rule_execution.id IS '执行记录ID';
COMMENT ON COLUMN cdss_rule_execution.rule_id IS '规则ID';
COMMENT ON COLUMN cdss_rule_execution.rule_code IS '规则编码';
COMMENT ON COLUMN cdss_rule_execution.encounter_id IS '就诊ID';
COMMENT ON COLUMN cdss_rule_execution.patient_id IS '患者ID';
COMMENT ON COLUMN cdss_rule_execution.matched IS '是否命中';
COMMENT ON COLUMN cdss_rule_execution.execution_time IS '执行时间';
COMMENT ON COLUMN cdss_rule_execution.execution_result IS '执行结果';
COMMENT ON COLUMN cdss_rule_execution.duration_ms IS '执行耗时(毫秒)';
CREATE INDEX idx_cdss_exec_rule ON cdss_rule_execution(rule_id);
CREATE INDEX idx_cdss_exec_encounter ON cdss_rule_execution(encounter_id);
CREATE INDEX idx_cdss_exec_patient ON cdss_rule_execution(patient_id);
CREATE INDEX idx_cdss_exec_time ON cdss_rule_execution(execution_time);

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.healthlink.his.cdss.mapper.CdssRuleExecutionMapper">
<resultMap type="com.healthlink.his.cdss.domain.CdssRuleExecution" id="CdssRuleExecutionResult">
<id column="id" property="id"/>
<result column="rule_id" property="ruleId"/>
<result column="rule_code" property="ruleCode"/>
<result column="encounter_id" property="encounterId"/>
<result column="patient_id" property="patientId"/>
<result column="matched" property="matched"/>
<result column="execution_time" property="executionTime"/>
<result column="execution_result" property="executionResult"/>
<result column="duration_ms" property="durationMs"/>
<result column="tenant_id" property="tenantId"/>
<result column="create_by" property="createBy"/>
<result column="create_time" property="createTime"/>
<result column="delete_flag" property="deleteFlag"/>
</resultMap>
<sql id="Base_Column_List">
id, rule_id, rule_code, encounter_id, patient_id,
matched, execution_time, execution_result, duration_ms,
tenant_id, create_by, create_time, delete_flag
</sql>
</mapper>

View File

@@ -15,6 +15,8 @@
<result column="department_id" property="departmentId"/>
<result column="status" property="status"/>
<result column="sort_order" property="sortOrder"/>
<result column="priority" property="priority"/>
<result column="category" property="category"/>
<result column="tenant_id" property="tenantId"/>
<result column="create_by" property="createBy"/>
<result column="create_time" property="createTime"/>
@@ -26,7 +28,8 @@
<sql id="Base_Column_List">
id, rule_code, rule_name, rule_type, severity, trigger_type,
condition_expr, action_expr, description, department_id,
status, sort_order, tenant_id, create_by, create_time,
status, sort_order, priority, category,
tenant_id, create_by, create_time,
update_by, update_time, delete_flag
</sql>

View File

@@ -42,4 +42,8 @@ public class CdssRule extends HisBaseEntity {
private Integer status;
private Integer sortOrder;
private Integer priority;
private String category;
}

View File

@@ -0,0 +1,43 @@
package com.healthlink.his.cdss.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.core.common.core.domain.HisBaseEntity;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.util.Date;
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("cdss_rule_execution")
public class CdssRuleExecution extends HisBaseEntity {
@TableId(type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
@JsonSerialize(using = ToStringSerializer.class)
private Long ruleId;
private String ruleCode;
@JsonSerialize(using = ToStringSerializer.class)
private Long encounterId;
@JsonSerialize(using = ToStringSerializer.class)
private Long patientId;
private Boolean matched;
private Date executionTime;
private String executionResult;
private Integer durationMs;
}

View File

@@ -0,0 +1,9 @@
package com.healthlink.his.cdss.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.healthlink.his.cdss.domain.CdssRuleExecution;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface CdssRuleExecutionMapper extends BaseMapper<CdssRuleExecution> {
}

View File

@@ -0,0 +1,7 @@
package com.healthlink.his.cdss.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.healthlink.his.cdss.domain.CdssRuleExecution;
public interface ICdssRuleExecutionService extends IService<CdssRuleExecution> {
}

View File

@@ -4,10 +4,16 @@ import com.baomidou.mybatisplus.extension.service.IService;
import com.healthlink.his.cdss.domain.CdssRule;
import java.util.List;
import java.util.Map;
public interface ICdssRuleService extends IService<CdssRule> {
List<CdssRule> findActiveRules(String triggerType, Long departmentId);
List<CdssRule> findByCondition(String ruleType, String severity, Integer status);
Map<String, Object> getRuleStats();
List<CdssRule> findByConditionWithFilter(String ruleType, String severity, String keyword,
String category, Integer priority);
}

View File

@@ -0,0 +1,11 @@
package com.healthlink.his.cdss.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.cdss.domain.CdssRuleExecution;
import com.healthlink.his.cdss.mapper.CdssRuleExecutionMapper;
import com.healthlink.his.cdss.service.ICdssRuleExecutionService;
import org.springframework.stereotype.Service;
@Service
public class CdssRuleExecutionServiceImpl extends ServiceImpl<CdssRuleExecutionMapper, CdssRuleExecution> implements ICdssRuleExecutionService {
}

View File

@@ -3,15 +3,25 @@ package com.healthlink.his.cdss.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.healthlink.his.cdss.domain.CdssRule;
import com.healthlink.his.cdss.domain.CdssRuleExecution;
import com.healthlink.his.cdss.mapper.CdssRuleMapper;
import com.healthlink.his.cdss.service.ICdssRuleExecutionService;
import com.healthlink.his.cdss.service.ICdssRuleService;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class CdssRuleServiceImpl extends ServiceImpl<CdssRuleMapper, CdssRule> implements ICdssRuleService {
private final ICdssRuleExecutionService executionService;
public CdssRuleServiceImpl(ICdssRuleExecutionService executionService) {
this.executionService = executionService;
}
@Override
public List<CdssRule> findActiveRules(String triggerType, Long departmentId) {
LambdaQueryWrapper<CdssRule> wrapper = new LambdaQueryWrapper<>();
@@ -22,12 +32,38 @@ public class CdssRuleServiceImpl extends ServiceImpl<CdssRuleMapper, CdssRule> i
if (departmentId != null) {
wrapper.and(w -> w.isNull(CdssRule::getDepartmentId).or().eq(CdssRule::getDepartmentId, departmentId));
}
wrapper.orderByAsc(CdssRule::getSortOrder);
wrapper.orderByDesc(CdssRule::getPriority).orderByAsc(CdssRule::getSortOrder);
return baseMapper.selectList(wrapper);
}
@Override
public List<CdssRule> findByCondition(String ruleType, String severity, Integer status) {
return findByConditionWithFilter(ruleType, severity, null, null, null);
}
@Override
public Map<String, Object> getRuleStats() {
long totalCount = baseMapper.selectCount(null);
long activeCount = baseMapper.selectCount(
new LambdaQueryWrapper<CdssRule>().eq(CdssRule::getStatus, 1));
long totalExecutions = executionService.count();
long matchedExecutions = executionService.count(
new LambdaQueryWrapper<CdssRuleExecution>().eq(CdssRuleExecution::getMatched, true));
Map<String, Object> stats = new HashMap<>();
stats.put("totalRules", totalCount);
stats.put("activeRules", activeCount);
stats.put("inactiveRules", totalCount - activeCount);
stats.put("totalExecutions", totalExecutions);
stats.put("matchedExecutions", matchedExecutions);
stats.put("hitRate", totalExecutions > 0
? Math.round(matchedExecutions * 10000.0 / totalExecutions) / 100.0 : 0.0);
return stats;
}
@Override
public List<CdssRule> findByConditionWithFilter(String ruleType, String severity, String keyword,
String category, Integer priority) {
LambdaQueryWrapper<CdssRule> wrapper = new LambdaQueryWrapper<>();
if (ruleType != null && !ruleType.isEmpty()) {
wrapper.eq(CdssRule::getRuleType, ruleType);
@@ -35,10 +71,17 @@ public class CdssRuleServiceImpl extends ServiceImpl<CdssRuleMapper, CdssRule> i
if (severity != null && !severity.isEmpty()) {
wrapper.eq(CdssRule::getSeverity, severity);
}
if (status != null) {
wrapper.eq(CdssRule::getStatus, status);
if (category != null && !category.isEmpty()) {
wrapper.eq(CdssRule::getCategory, category);
}
wrapper.orderByAsc(CdssRule::getSortOrder);
if (priority != null) {
wrapper.eq(CdssRule::getPriority, priority);
}
if (keyword != null && !keyword.isEmpty()) {
wrapper.and(w -> w.like(CdssRule::getRuleName, keyword)
.or().like(CdssRule::getRuleCode, keyword));
}
wrapper.orderByDesc(CdssRule::getPriority).orderByAsc(CdssRule::getSortOrder);
return baseMapper.selectList(wrapper);
}
}

View File

@@ -8,6 +8,29 @@ export function getCdssRuleList(query) {
})
}
export function getCdssRuleListEnhanced(query) {
return request({
url: '/infection/cdss/rules/enhanced',
method: 'get',
params: query
})
}
export function getCdssRuleStats() {
return request({
url: '/infection/cdss/rules/stats',
method: 'get'
})
}
export function getCdssRuleHistory(query) {
return request({
url: '/infection/cdss/rules/history',
method: 'get',
params: query
})
}
export function addCdssRule(data) {
return request({
url: '/infection/cdss/rules',

View File

@@ -18,16 +18,71 @@
<el-option label="INFO" value="INFO" />
</el-select>
</el-form-item>
<el-form-item label="优先级" prop="priority">
<el-select v-model="queryParams.priority" placeholder="请选择" clearable style="width: 120px">
<el-option label="最高" :value="2" />
<el-option label="紧急" :value="1" />
<el-option label="普通" :value="0" />
</el-select>
</el-form-item>
<el-form-item label="分类" prop="category">
<el-input v-model="queryParams.category" placeholder="规则分类" clearable style="width: 140px" />
</el-form-item>
<el-form-item label="规则名称" prop="keyword">
<el-input v-model="queryParams.keyword" placeholder="搜索规则名称" clearable style="width: 180px" @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>
<el-button type="info" icon="DataAnalysis" @click="handleToggleStats">{{ showStats ? '返回列表' : '统计概览' }}</el-button>
</el-form-item>
</el-form>
<vxe-table :data="ruleList" :loading="loading" border stripe height="auto">
<div v-if="showStats" class="stats-panel">
<el-row :gutter="16">
<el-col :span="6">
<el-card shadow="hover" class="stat-card">
<div class="stat-value">{{ ruleStats.totalRules || 0 }}</div>
<div class="stat-label">规则总数</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" class="stat-card stat-active">
<div class="stat-value">{{ ruleStats.activeRules || 0 }}</div>
<div class="stat-label">启用规则</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" class="stat-card stat-exec">
<div class="stat-value">{{ ruleStats.totalExecutions || 0 }}</div>
<div class="stat-label">执行总次数</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" class="stat-card stat-hit">
<div class="stat-value">{{ ruleStats.hitRate || 0 }}%</div>
<div class="stat-label">命中率</div>
</el-card>
</el-col>
</el-row>
<el-divider content-position="left">执行历史</el-divider>
<vxe-table :data="executionHistory" :loading="historyLoading" border stripe height="auto" size="small">
<vxe-column type="seq" title="序号" width="60" />
<vxe-column field="ruleCode" title="规则编码" width="120" />
<vxe-column field="encounterId" title="就诊ID" width="100" />
<vxe-column field="matched" title="是否命中" width="90" align="center">
<template #default="{ row }">
<el-tag :type="row.matched ? 'success' : 'info'" size="small">{{ row.matched ? '命中' : '未命中' }}</el-tag>
</template>
</vxe-column>
<vxe-column field="executionResult" title="执行结果" min-width="150" show-overflow />
<vxe-column field="durationMs" title="耗时(ms)" width="100" align="center" />
<vxe-column field="executionTime" title="执行时间" width="170" />
</vxe-table>
</div>
<vxe-table v-else :data="ruleList" :loading="loading" border stripe height="auto">
<vxe-column type="seq" title="序号" width="70" />
<vxe-column field="ruleCode" title="规则编码" width="120" />
<vxe-column field="ruleName" title="规则名称" min-width="180" show-overflow />
@@ -41,6 +96,12 @@
<el-tag :type="severityTagType(row.severity)" effect="dark">{{ row.severity }}</el-tag>
</template>
</vxe-column>
<vxe-column field="priority" title="优先级" width="90" align="center">
<template #default="{ row }">
<el-tag :type="priorityTagType(row.priority)" size="small">{{ priorityLabel(row.priority) }}</el-tag>
</template>
</vxe-column>
<vxe-column field="category" title="分类" width="110" show-overflow />
<vxe-column field="conditionExpr" title="条件表达式" min-width="200" show-overflow />
<vxe-column field="actionExpr" title="执行动作" min-width="200" show-overflow />
<vxe-column field="status" title="状态" width="80" align="center">
@@ -55,16 +116,22 @@
<script setup name="CdssRules">
import { ref, reactive, onMounted } from 'vue'
import { getCdssRuleList } from '@/api/cdss/cdssRule'
import { getCdssRuleListEnhanced, getCdssRuleStats, getCdssRuleHistory } from '@/api/cdss/cdssRule'
const ruleList = ref([])
const loading = ref(false)
const showSearch = ref(true)
const showStats = ref(false)
const ruleStats = ref({})
const executionHistory = ref([])
const historyLoading = ref(false)
const queryParams = reactive({
ruleType: '',
severity: '',
keyword: ''
keyword: '',
category: '',
priority: undefined
})
const severityTagType = (severity) => {
@@ -72,6 +139,16 @@ const severityTagType = (severity) => {
return map[severity] || 'info'
}
const priorityTagType = (priority) => {
const map = { 2: 'danger', 1: 'warning', 0: 'info' }
return map[priority] || 'info'
}
const priorityLabel = (priority) => {
const map = { 2: '最高', 1: '紧急', 0: '普通' }
return map[priority] || '普通'
}
const getList = async () => {
loading.value = true
try {
@@ -79,7 +156,9 @@ const getList = async () => {
if (queryParams.ruleType) params.ruleType = queryParams.ruleType
if (queryParams.severity) params.severity = queryParams.severity
if (queryParams.keyword) params.keyword = queryParams.keyword
const res = await getCdssRuleList(params)
if (queryParams.category) params.category = queryParams.category
if (queryParams.priority !== undefined && queryParams.priority !== '') params.priority = queryParams.priority
const res = await getCdssRuleListEnhanced(params)
if (res.code === 200) {
ruleList.value = res.data || []
}
@@ -88,6 +167,29 @@ const getList = async () => {
}
}
const getStats = async () => {
try {
const res = await getCdssRuleStats()
if (res.code === 200) {
ruleStats.value = res.data || {}
}
} catch (e) {
ruleStats.value = {}
}
}
const getHistory = async () => {
historyLoading.value = true
try {
const res = await getCdssRuleHistory({ page: 1, size: 50 })
if (res.code === 200) {
executionHistory.value = res.data || []
}
} finally {
historyLoading.value = false
}
}
const handleQuery = () => {
getList()
}
@@ -96,10 +198,43 @@ const resetQuery = () => {
queryParams.ruleType = ''
queryParams.severity = ''
queryParams.keyword = ''
queryParams.category = ''
queryParams.priority = undefined
handleQuery()
}
const handleToggleStats = () => {
showStats.value = !showStats.value
if (showStats.value) {
getStats()
getHistory()
}
}
onMounted(() => {
getList()
})
</script>
<style scoped>
.stats-panel {
padding: 16px 0;
}
.stat-card {
text-align: center;
}
.stat-card .stat-value {
font-size: 28px;
font-weight: bold;
color: #303133;
line-height: 1.2;
}
.stat-card .stat-label {
font-size: 13px;
color: #909399;
margin-top: 8px;
}
.stat-active .stat-value { color: #67c23a; }
.stat-exec .stat-value { color: #409eff; }
.stat-hit .stat-value { color: #e6a23c; }
</style>