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

@@ -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>