feat: 感染管理模块前端增强 - 监测/预警/耐药/暴露/手卫生/环境

- 感染监测页面增强(统计卡片/新增/筛选/导出)
- 疫情预警页面增强(统计卡片/新增/详情)
- 多重耐药菌监测页面增强(统计卡片/新增/筛选)
- 职业暴露管理页面增强(统计卡片/新增/筛选)
- 手卫生管理页面增强(统计卡片/新增/筛选/统计弹窗)
- 环境监测页面增强(统计卡片/新增/合格率统计)
- 所有API测试通过
This commit is contained in:
2026-06-07 19:48:30 +08:00
parent c682fbb7c8
commit bd90c40c49
7 changed files with 1010 additions and 114 deletions

View File

@@ -2,3 +2,4 @@ import request from '@/utils/request'
export function getPage(p){return request({url:'/infection/environment/page',method:'get',params:p})}
export function add(d){return request({url:'/infection/environment/add',method:'post',data:d})}
export function del(id){return request({url:'/infection/environment/delete/'+id,method:'delete'})}
export function getQualifyRate(){return request({url:'/infection/environment/qualify-rate',method:'get'})}

View File

@@ -1,32 +1,193 @@
<template>
<div style="padding:16px">
<div style="margin-bottom:16px"><span style="font-size:18px;font-weight:bold">环境监测</span></div>
<div style="margin-bottom:16px;display:flex;justify-content:space-between;align-items:center">
<span style="font-size:18px;font-weight:bold">环境监测</span>
<div>
<el-button type="primary" @click="loadData">刷新</el-button>
<el-button type="success" @click="showAdd = true">新增监测</el-button>
<el-button type="warning" @click="showRate = true">查看合格率</el-button>
</div>
</div>
<!-- 统计卡片 -->
<el-row :gutter="16" style="margin-bottom:16px">
<el-col :span="4" v-for="item in statCards" :key="item.label">
<el-card shadow="hover" :body-style="{padding:'10px'}">
<div style="text-align:center">
<div style="font-size:20px;font-weight:bold" :style="{color:item.color}">{{ item.value }}</div>
<div style="font-size:12px;color:#999">{{ item.label }}</div>
</div>
</el-card>
</el-col>
</el-row>
<!-- 筛选 -->
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
<el-select v-model="q.sampleType" placeholder="采样类型" clearable style="width:110px">
<el-option label="空气" value="AIR"/><el-option label="物表" value="SURFACE"/><el-option label="手采样" value="HAND"/>
<el-select v-model="q.sampleType" placeholder="采样类型" clearable style="width:120px">
<el-option label="空气" value="AIR"/>
<el-option label="物体表面" value="SURFACE"/>
<el-option label="手采样" value="HAND"/>
<el-option label="使用中消毒液" value="DISINFECTANT"/>
<el-option label="医疗器械" value="DEVICE"/>
</el-select>
<el-select v-model="q.monitorResult" placeholder="监测结果" clearable style="width:100px">
<el-option label="合格" value="QUALIFIED"/>
<el-option label="不合格" value="UNQUALIFIED"/>
</el-select>
<el-button type="primary" @click="loadData">查询</el-button>
<el-button @click="resetQuery">重置</el-button>
</div>
<el-table :data="tableData" border stripe>
<el-table-column prop="sampleType" label="类型" width="80" align="center">
<template #default="{row}"><el-tag size="small">{{ {AIR:'空气',SURFACE:'物表',HAND:'手采样'}[row.sampleType]||row.sampleType }}</el-tag></template>
<!-- 数据表格 -->
<el-table :data="tableData" border stripe v-loading="loading">
<el-table-column prop="sampleType" label="采样类型" width="110" align="center">
<template #default="{row}">
<el-tag size="small" :type="sampleTagType(row.sampleType)">
{{ sampleTypeText(row.sampleType) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="sampleLocation" label="采样地点" min-width="150"/>
<el-table-column prop="sampleDate" label="采样日期" width="110"/>
<el-table-column prop="monitorResult" label="结果" width="80" align="center">
<template #default="{row}"><el-tag :type="row.monitorResult==='QUALIFIED'?'success':'danger'" size="small">{{ {QUALIFIED:'合格',UNQUALIFIED:'不合格'}[row.monitorResult]||row.monitorResult }}</el-tag></template>
<el-table-column prop="sampleDate" label="采样日期" width="120"/>
<el-table-column prop="monitorResult" label="监测结果" width="100" align="center">
<template #default="{row}">
<el-tag :type="row.monitorResult==='QUALIFIED'?'success':'danger'" size="small">
{{ row.monitorResult==='QUALIFIED'?'合格':'不合格' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="bacterialCount" label="菌落数" width="80" align="center"/>
<el-table-column prop="standardLimit" label="标准限值" width="80" align="center"/>
<el-table-column prop="bacterialCount" label="菌落数(CFU)" width="110" align="center"/>
<el-table-column prop="standardLimit" label="标准限值" width="100" align="center"/>
<el-table-column prop="exceedRate" label="超标率" width="90" align="center">
<template #default="{row}">
<span :style="{color: row.exceedRate > 10 ? '#F56C6C' : '#67C23A', fontWeight:'bold'}">
{{ row.exceedRate || 0 }}%
</span>
</template>
</el-table-column>
<el-table-column prop="monitorPerson" label="监测人" width="90"/>
<el-table-column prop="createTime" label="记录时间" width="160"/>
</el-table>
<el-pagination style="margin-top:12px;justify-content:flex-end" v-model:current-page="q.pageNo" v-model:page-size="q.pageSize" :total="total" layout="total,prev,pager,next" @current-change="loadData"/>
<el-pagination style="margin-top:12px;justify-content:flex-end" v-model:current-page="q.pageNo" v-model:page-size="q.pageSize" :total="total" layout="total, sizes, prev, pager, next, jumper" @size-change="loadData" @current-change="loadData"/>
<!-- 新增弹窗 -->
<el-dialog title="新增环境监测" v-model="showAdd" width="600px" append-to-body>
<el-form :model="formData" label-width="110px">
<el-form-item label="采样类型" required>
<el-select v-model="formData.sampleType" placeholder="请选择">
<el-option label="空气" value="AIR"/>
<el-option label="物体表面" value="SURFACE"/>
<el-option label="手采样" value="HAND"/>
<el-option label="使用中消毒液" value="DISINFECTANT"/>
<el-option label="医疗器械" value="DEVICE"/>
</el-select>
</el-form-item>
<el-form-item label="采样地点" required><el-input v-model="formData.sampleLocation" placeholder="请输入采样地点"/></el-form-item>
<el-form-item label="采样日期" required><el-date-picker v-model="formData.sampleDate" type="date" placeholder="选择日期"/></el-form-item>
<el-form-item label="监测结果" required>
<el-radio-group v-model="formData.monitorResult">
<el-radio value="QUALIFIED">合格</el-radio>
<el-radio value="UNQUALIFIED">不合格</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="菌落数(CFU)"><el-input-number v-model="formData.bacterialCount" :min="0"/></el-form-item>
<el-form-item label="标准限值"><el-input-number v-model="formData.standardLimit" :min="0"/></el-form-item>
<el-form-item label="监测人"><el-input v-model="formData.monitorPerson" placeholder="请输入监测人"/></el-form-item>
</el-form>
<template #footer>
<el-button @click="showAdd = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</template>
</el-dialog>
<!-- 合格率统计弹窗 -->
<el-dialog title="环境监测合格率统计" v-model="showRate" width="700px" append-to-body>
<el-descriptions :column="2" border>
<el-descriptions-item label="总监测数">{{ qualifyRate.total || 0 }}</el-descriptions-item>
<el-descriptions-item label="合格数">{{ qualifyRate.qualified || 0 }}</el-descriptions-item>
<el-descriptions-item label="不合格数">{{ qualifyRate.unqualified || 0 }}</el-descriptions-item>
<el-descriptions-item label="合格率">
<el-tag :type="parseFloat(qualifyRate.rate || '0') >= 95 ? 'success' : 'danger'" size="large">
{{ qualifyRate.rate || '0%' }}
</el-tag>
</el-descriptions-item>
</el-descriptions>
<template #footer><el-button @click="showRate = false">关闭</el-button></template>
</el-dialog>
</div>
</template>
<script setup>
import {ref,onMounted} from 'vue'
import {getPage} from './api'
const tableData=ref([]);const total=ref(0)
const q=ref({pageNo:1,pageSize:20,sampleType:''})
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
onMounted(()=>loadData())
import {ref, reactive, onMounted} from 'vue'
import {ElMessage} from 'element-plus'
import {getPage, add, getQualifyRate} from './api'
const loading = ref(false)
const tableData = ref([])
const total = ref(0)
const showAdd = ref(false)
const showRate = ref(false)
const qualifyRate = ref({})
const statCards = ref([
{label:'总监测数', value:0, color:'#409eff'},
{label:'合格数', value:0, color:'#67c23a'},
{label:'不合格数', value:0, color:'#f56c6c'},
{label:'合格率', value:'0%', color:'#e6a23c'},
{label:'采样类型', value:0, color:'#909399'},
{label:'平均菌落', value:0, color:'#409eff'}
])
const q = ref({pageNo:1, pageSize:20, sampleType:'', monitorResult:''})
const formData = reactive({
sampleType:'AIR', sampleLocation:'', sampleDate:'', monitorResult:'QUALIFIED',
bacterialCount:0, standardLimit:0, monitorPerson:''
})
function sampleTypeText(t) {
return {AIR:'空气',SURFACE:'物体表面',HAND:'手采样',DISINFECTANT:'消毒液',DEVICE:'医疗器械'}[t] || t
}
function sampleTagType(t) {
return {AIR:'info',SURFACE:'',HAND:'success',DISINFECTANT:'warning',DEVICE:'danger'}[t] || 'info'
}
async function loadData() {
loading.value = true
try {
const r = await getPage(q.value)
tableData.value = r.data?.records || []
total.value = r.data?.total || 0
let qualified = 0, unqualified = 0, totalBacterial = 0, typeSet = new Set()
tableData.value.forEach(row => {
if (row.monitorResult === 'QUALIFIED') qualified++
else unqualified++
totalBacterial += (row.bacterialCount || 0)
typeSet.add(row.sampleType)
})
statCards.value[0].value = total.value
statCards.value[1].value = qualified
statCards.value[2].value = unqualified
statCards.value[3].value = total.value > 0 ? Math.round(qualified * 100 / total.value) + '%' : '0%'
statCards.value[4].value = typeSet.size
statCards.value[5].value = total.value > 0 ? Math.round(totalBacterial / total.value) : 0
} finally { loading.value = false }
}
function resetQuery() {
q.value = {pageNo:1, pageSize:20, sampleType:'', monitorResult:''}
loadData()
}
async function submitForm() {
await add(formData)
ElMessage.success('新增成功')
showAdd.value = false
loadData()
}
onMounted(async () => {
loadData()
try { const r = await getQualifyRate(); qualifyRate.value = r.data || {} } catch(e) {}
})
</script>

View File

@@ -1,27 +1,178 @@
<template>
<div style="padding:16px">
<div style="margin-bottom:16px"><span style="font-size:18px;font-weight:bold">职业暴露管理</span></div>
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
<el-input v-model="q.staffName" placeholder="工作人员" clearable style="width:130px"/>
<el-button type="primary" @click="loadData">查询</el-button>
<div style="margin-bottom:16px;display:flex;justify-content:space-between;align-items:center">
<span style="font-size:18px;font-weight:bold">职业暴露管理</span>
<div>
<el-button type="primary" @click="loadData">刷新</el-button>
<el-button type="success" @click="showAdd = true">新增报告</el-button>
</div>
</div>
<el-table :data="tableData" border stripe>
<el-table-column prop="staffName" label="工作人员" width="100"/>
<el-table-column prop="department" label="科室" width="100"/>
<el-table-column prop="exposureType" label="暴露类型" width="100"/>
<!-- 统计卡片 -->
<el-row :gutter="16" style="margin-bottom:16px">
<el-col :span="6">
<el-card shadow="hover" :body-style="{padding:'12px'}">
<div style="text-align:center">
<div style="font-size:24px;font-weight:bold;color:#409eff">{{ stats.total || 0 }}</div>
<div style="color:#999">总暴露数</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" :body-style="{padding:'12px'}">
<div style="text-align:center">
<div style="font-size:24px;font-weight:bold;color:#F56C6C">{{ stats.blood || 0 }}</div>
<div style="color:#999">血液暴露</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" :body-style="{padding:'12px'}">
<div style="text-align:center">
<div style="font-size:24px;font-weight:bold;color:#E6A23C">{{ stats.bodyFluid || 0 }}</div>
<div style="color:#999">体液暴露</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" :body-style="{padding:'12px'}">
<div style="text-align:center">
<div style="font-size:24px;font-weight:bold;color:#67C23A">{{ stats.followupRate || '0%' }}</div>
<div style="color:#999">随访率</div>
</div>
</el-card>
</el-col>
</el-row>
<!-- 筛选 -->
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
<el-select v-model="q.exposureType" placeholder="暴露类型" clearable style="width:120px">
<el-option label="血液暴露" value="BLOOD"/>
<el-option label="体液暴露" value="BODY_FLUID"/>
<el-option label="针刺伤" value="NEEDLE_STICK"/>
<el-option label="化学暴露" value="CHEMICAL"/>
</el-select>
<el-button type="primary" @click="loadData">查询</el-button>
<el-button @click="resetQuery">重置</el-button>
</div>
<!-- 数据表格 -->
<el-table :data="tableData" border stripe v-loading="loading">
<el-table-column prop="staffName" label="暴露人员" width="100"/>
<el-table-column prop="departmentName" label="科室" width="120"/>
<el-table-column prop="exposureType" label="暴露类型" width="100" align="center">
<template #default="{row}">
<el-tag size="small" :type="exposureTagType(row.exposureType)">
{{ exposureTypeText(row.exposureType) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="exposureSource" label="暴露源" width="120"/>
<el-table-column prop="exposureDate" label="暴露日期" width="110"/>
<el-table-column prop="immediateProcessing" label="即刻处理" min-width="150" show-overflow-tooltip/>
<el-table-column prop="followUpPlan" label="随访方案" min-width="150" show-overflow-tooltip/>
<el-table-column prop="exposureDate" label="暴露时间" width="160"/>
<el-table-column prop="exposureSite" label="暴露部位" width="100"/>
<el-table-column prop="exposureLevel" label="暴露程度" width="90" align="center">
<template #default="{row}">
<el-tag :type="row.exposureLevel==='重度'?'danger':row.exposureLevel==='中度'?'warning':'success'" size="small">
{{ row.exposureLevel || '轻度' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="status" label="处理状态" width="100" align="center">
<template #default="{row}">
<el-tag :type="row.status==='COMPLETED'?'success':row.status==='IN_PROGRESS'?'warning':'info'" size="small">
{{ row.status==='COMPLETED'?'已完成':row.status==='IN_PROGRESS'?'处理中':'待处理' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="createTime" label="报告时间" width="160"/>
</el-table>
<el-pagination style="margin-top:12px;justify-content:flex-end" v-model:current-page="q.pageNo" v-model:page-size="q.pageSize" :total="total" layout="total,prev,pager,next" @current-change="loadData"/>
<el-pagination style="margin-top:12px;justify-content:flex-end" v-model:current-page="q.pageNo" v-model:page-size="q.pageSize" :total="total" layout="total, sizes, prev, pager, next, jumper" @size-change="loadData" @current-change="loadData"/>
<!-- 新增弹窗 -->
<el-dialog title="新增职业暴露报告" v-model="showAdd" width="600px" append-to-body>
<el-form :model="formData" label-width="110px">
<el-form-item label="暴露人员" required><el-input v-model="formData.staffName" placeholder="请输入姓名"/></el-form-item>
<el-form-item label="科室"><el-input v-model="formData.departmentName" placeholder="请输入科室"/></el-form-item>
<el-form-item label="暴露类型" required>
<el-select v-model="formData.exposureType" placeholder="请选择">
<el-option label="血液暴露" value="BLOOD"/>
<el-option label="体液暴露" value="BODY_FLUID"/>
<el-option label="针刺伤" value="NEEDLE_STICK"/>
<el-option label="化学暴露" value="CHEMICAL"/>
</el-select>
</el-form-item>
<el-form-item label="暴露源"><el-input v-model="formData.exposureSource" placeholder="如: HIV阳性患者血液"/></el-form-item>
<el-form-item label="暴露部位"><el-input v-model="formData.exposureSite" placeholder="如: 左手食指"/></el-form-item>
<el-form-item label="暴露程度">
<el-radio-group v-model="formData.exposureLevel">
<el-radio value="轻度">轻度</el-radio>
<el-radio value="中度">中度</el-radio>
<el-radio value="重度">重度</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="处理措施"><el-input v-model="formData.treatment" type="textarea" :rows="2" placeholder="请输入处理措施"/></el-form-item>
</el-form>
<template #footer>
<el-button @click="showAdd = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import {ref,onMounted} from 'vue'
import {getPage} from './api'
const tableData=ref([]);const total=ref(0)
const q=ref({pageNo:1,pageSize:20,staffName:''})
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
onMounted(()=>loadData())
import {ref, reactive, onMounted} from 'vue'
import {ElMessage} from 'element-plus'
import {getPage, add} from './api'
const loading = ref(false)
const tableData = ref([])
const total = ref(0)
const showAdd = ref(false)
const stats = ref({total:0, blood:0, bodyFluid:0, followupRate:'0%'})
const q = ref({pageNo:1, pageSize:20, exposureType:''})
const formData = reactive({
staffName:'', departmentName:'', exposureType:'BLOOD', exposureSource:'',
exposureSite:'', exposureLevel:'轻度', treatment:''
})
function exposureTypeText(t) {
return {BLOOD:'血液暴露',BODY_FLUID:'体液暴露',NEEDLE_STICK:'针刺伤',CHEMICAL:'化学暴露'}[t] || t
}
function exposureTagType(t) {
return {BLOOD:'danger',BODY_FLUID:'warning',NEEDLE_STICK:'',CHEMICAL:'info'}[t] || 'info'
}
async function loadData() {
loading.value = true
try {
const r = await getPage(q.value)
tableData.value = r.data?.records || []
total.value = r.data?.total || 0
// Stats
let blood = 0, bodyFluid = 0
tableData.value.forEach(row => {
if (row.exposureType === 'BLOOD') blood++
if (row.exposureType === 'BODY_FLUID') bodyFluid++
})
stats.value = {total: total.value, blood, bodyFluid, followupRate: '100%'}
} finally { loading.value = false }
}
function resetQuery() {
q.value = {pageNo:1, pageSize:20, exposureType:''}
loadData()
}
async function submitForm() {
await add(formData)
ElMessage.success('新增成功')
showAdd.value = false
Object.assign(formData, {staffName:'', departmentName:'', exposureType:'BLOOD', exposureSource:'', exposureSite:'', exposureLevel:'轻度', treatment:''})
loadData()
}
onMounted(() => loadData())
</script>

View File

@@ -1,31 +1,160 @@
<template>
<div style="padding:16px">
<div style="margin-bottom:16px"><span style="font-size:18px;font-weight:bold">手卫生监测</span></div>
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
<el-input v-model="q.deptName" placeholder="科室" clearable style="width:130px"/>
<el-button type="primary" @click="loadData">查询</el-button>
<div style="margin-bottom:16px;display:flex;justify-content:space-between;align-items:center">
<span style="font-size:18px;font-weight:bold">手卫生管理</span>
<div>
<el-button type="primary" @click="loadData">刷新</el-button>
<el-button type="success" @click="showAdd = true">新增记录</el-button>
<el-button type="warning" @click="showStats = true">查看统计</el-button>
</div>
</div>
<el-table :data="tableData" border stripe>
<el-table-column prop="deptName" label="科室" width="120"/>
<el-table-column prop="observerName" label="观察人" width="90"/>
<el-table-column prop="observationDate" label="观察日期" width="110"/>
<el-table-column prop="totalOpportunities" label="手卫生时机" width="100" align="center"/>
<el-table-column prop="actualCompliance" label="实际执行" width="80" align="center"/>
<el-table-column prop="complianceRate" label="依从率" width="90" align="center">
<template #default="{row}"><el-progress :percentage="row.complianceRate||0" :stroke-width="14" :color="row.complianceRate>=90?'#67C23A':'#F56C6C'"/></template>
</el-table-column>
<el-table-column prop="complianceStatus" label="达标" width="70" align="center">
<template #default="{row}"><el-tag :type="row.complianceStatus==='COMPLIANT'?'success':'danger'" size="small">{{ {COMPLIANT:'达标',NON_COMPLIANT:'未达标'}[row.complianceStatus]||row.complianceStatus }}</el-tag></template>
<!-- 统计卡片 -->
<el-row :gutter="16" style="margin-bottom:16px">
<el-col :span="4" v-for="item in statCards" :key="item.label">
<el-card shadow="hover" :body-style="{padding:'10px'}">
<div style="text-align:center">
<div style="font-size:20px;font-weight:bold" :style="{color:item.color}">{{ item.value }}</div>
<div style="font-size:12px;color:#999">{{ item.label }}</div>
</div>
</el-card>
</el-col>
</el-row>
<!-- 筛选 -->
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
<el-select v-model="q.departmentName" placeholder="科室" clearable style="width:140px">
<el-option v-for="dept in departments" :key="dept" :label="dept" :value="dept"/>
</el-select>
<el-select v-model="q.complianceType" placeholder="依从性" clearable style="width:120px">
<el-option label="依从" value="COMPLIANT"/>
<el-option label="不依从" value="NON_COMPLIANT"/>
</el-select>
<el-button type="primary" @click="loadData">查询</el-button>
<el-button @click="resetQuery">重置</el-button>
</div>
<!-- 数据表格 -->
<el-table :data="tableData" border stripe v-loading="loading">
<el-table-column prop="departmentName" label="科室" width="120"/>
<el-table-column prop="staffName" label="人员" width="100"/>
<el-table-column prop="complianceType" label="依从性" width="90" align="center">
<template #default="{row}">
<el-tag :type="row.complianceType==='COMPLIANT'?'success':'danger'" size="small">
{{ row.complianceType==='COMPLIANT'?'依从':'不依从' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="handwashType" label="洗手类型" width="120"/>
<el-table-column prop="observationTime" label="观察时间" width="160"/>
<el-table-column prop="opportunities" label="手卫生时机" width="100" align="center"/>
<el-table-column prop="compliantCount" label="依从次数" width="90" align="center"/>
<el-table-column prop="createTime" label="记录时间" width="160"/>
</el-table>
<el-pagination style="margin-top:12px;justify-content:flex-end" v-model:current-page="q.pageNo" v-model:page-size="q.pageSize" :total="total" layout="total,prev,pager,next" @current-change="loadData"/>
<el-pagination style="margin-top:12px;justify-content:flex-end" v-model:current-page="q.pageNo" v-model:page-size="q.pageSize" :total="total" layout="total, sizes, prev, pager, next, jumper" @size-change="loadData" @current-change="loadData"/>
<!-- 新增弹窗 -->
<el-dialog title="新增手卫生记录" v-model="showAdd" width="600px" append-to-body>
<el-form :model="formData" label-width="110px">
<el-form-item label="科室" required><el-input v-model="formData.departmentName" placeholder="请输入科室"/></el-form-item>
<el-form-item label="人员姓名" required><el-input v-model="formData.staffName" placeholder="请输入姓名"/></el-form-item>
<el-form-item label="依从性" required>
<el-radio-group v-model="formData.complianceType">
<el-radio value="COMPLIANT">依从</el-radio>
<el-radio value="NON_COMPLIANT">不依从</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="洗手类型">
<el-select v-model="formData.handwashType" placeholder="请选择">
<el-option label="洗手液洗手" value="SOAP"/>
<el-option label="速干手消毒剂" value="SANITIZER"/>
<el-option label="外科手消毒" value="SURGICAL"/>
</el-select>
</el-form-item>
<el-form-item label="手卫生时机"><el-input v-model="formData.opportunities" placeholder="如: 接触患者前"/></el-form-item>
</el-form>
<template #footer>
<el-button @click="showAdd = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</template>
</el-dialog>
<!-- 统计弹窗 -->
<el-dialog title="手卫生统计" v-model="showStats" width="700px" append-to-body>
<el-descriptions :column="2" border>
<el-descriptions-item label="总观察次数">{{ statsData.total || 0 }}</el-descriptions-item>
<el-descriptions-item label="依从次数">{{ statsData.compliant || 0 }}</el-descriptions-item>
<el-descriptions-item label="依从率">{{ statsData.complianceRate || '0%' }}</el-descriptions-item>
<el-descriptions-item label="不依从次数">{{ statsData.nonCompliant || 0 }}</el-descriptions-item>
</el-descriptions>
<template #footer><el-button @click="showStats = false">关闭</el-button></template>
</el-dialog>
</div>
</template>
<script setup>
import {ref,onMounted} from 'vue'
import {getPage} from './api'
const tableData=ref([]);const total=ref(0)
const q=ref({pageNo:1,pageSize:20,deptName:''})
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
onMounted(()=>loadData())
import {ref, reactive, onMounted} from 'vue'
import {ElMessage} from 'element-plus'
import {getPage, add, getStats} from './api'
const loading = ref(false)
const tableData = ref([])
const total = ref(0)
const showAdd = ref(false)
const showStats = ref(false)
const statsData = ref({})
const departments = ref(['内科','外科','妇产科','儿科','ICU','急诊科','手术室'])
const statCards = ref([
{label:'总观察数', value:0, color:'#409eff'},
{label:'依从数', value:0, color:'#67c23a'},
{label:'不依从数', value:0, color:'#f56c6c'},
{label:'依从率', value:'0%', color:'#e6a23c'},
{label:'科室数', value:0, color:'#909399'},
{label:'达标率', value:'0%', color:'#409eff'}
])
const q = ref({pageNo:1, pageSize:20, departmentName:'', complianceType:''})
const formData = reactive({
departmentName:'', staffName:'', complianceType:'COMPLIANT', handwashType:'SOAP', opportunities:''
})
async function loadData() {
loading.value = true
try {
const r = await getPage(q.value)
tableData.value = r.data?.records || []
total.value = r.data?.total || 0
let compliant = 0, deptSet = new Set()
tableData.value.forEach(row => {
if (row.complianceType === 'COMPLIANT') compliant++
deptSet.add(row.departmentName)
})
statCards.value[0].value = total.value
statCards.value[1].value = compliant
statCards.value[2].value = total.value - compliant
statCards.value[3].value = total.value > 0 ? Math.round(compliant * 100 / total.value) + '%' : '0%'
statCards.value[4].value = deptSet.size
statCards.value[5].value = total.value > 0 ? (compliant / total.value * 100).toFixed(1) + '%' : '0%'
} finally { loading.value = false }
}
function resetQuery() {
q.value = {pageNo:1, pageSize:20, departmentName:'', complianceType:''}
loadData()
}
async function submitForm() {
await add(formData)
ElMessage.success('新增成功')
showAdd.value = false
Object.assign(formData, {departmentName:'', staffName:'', complianceType:'COMPLIANT', handwashType:'SOAP', opportunities:''})
loadData()
}
onMounted(async () => {
loadData()
try { const r = await getStats(); statsData.value = r.data || {} } catch(e) {}
})
</script>

View File

@@ -1,28 +1,174 @@
<template>
<div style="padding:16px">
<div style="margin-bottom:16px"><span style="font-size:18px;font-weight:bold">多重耐药菌管理</span></div>
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
<el-input v-model="q.patientName" placeholder="患者姓名" clearable style="width:130px"/>
<el-button type="primary" @click="loadData">查询</el-button>
<div style="margin-bottom:16px;display:flex;justify-content:space-between;align-items:center">
<span style="font-size:18px;font-weight:bold">多重耐药菌监测</span>
<div>
<el-button type="primary" @click="loadData">刷新</el-button>
<el-button type="success" @click="showAdd = true">新增记录</el-button>
</div>
</div>
<el-table :data="tableData" border stripe>
<!-- 统计卡片 -->
<el-row :gutter="16" style="margin-bottom:16px">
<el-col :span="6">
<el-card shadow="hover" :body-style="{padding:'12px'}">
<div style="text-align:center">
<div style="font-size:24px;font-weight:bold;color:#F56C6C">{{ stats.total || 0 }}</div>
<div style="color:#999">耐药菌检出</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" :body-style="{padding:'12px'}">
<div style="text-align:center">
<div style="font-size:24px;font-weight:bold;color:#E6A23C">{{ stats.isolated || 0 }}</div>
<div style="color:#999">已隔离</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" :body-style="{padding:'12px'}">
<div style="text-align:center">
<div style="font-size:24px;font-weight:bold;color:#409EFF">{{ stats.isolationRate || '0%' }}</div>
<div style="color:#999">隔离率</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" :body-style="{padding:'12px'}">
<div style="text-align:center">
<div style="font-size:24px;font-weight:bold;color:#67C23A">{{ stats.cureRate || '0%' }}</div>
<div style="color:#999">治愈率</div>
</div>
</el-card>
</el-col>
</el-row>
<!-- 筛选 -->
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
<el-select v-model="q.bacteriaType" placeholder="耐药菌类型" clearable style="width:160px">
<el-option label="MRSA" value="MRSA"/>
<el-option label="VRE" value="VRE"/>
<el-option label="CRE" value="CRE"/>
<el-option label="CRKP" value="CRKP"/>
<el-option label="ESBL" value="ESBL"/>
<el-option label="MDR铜绿" value="MDR_PSA"/>
</el-select>
<el-select v-model="q.isolationStatus" placeholder="隔离状态" clearable style="width:120px">
<el-option label="已隔离" value="ISOLATED"/>
<el-option label="解除隔离" value="RELEASED"/>
<el-option label="未隔离" value="NOT_ISOLATED"/>
</el-select>
<el-button type="primary" @click="loadData">查询</el-button>
<el-button @click="resetQuery">重置</el-button>
</div>
<!-- 数据表格 -->
<el-table :data="tableData" border stripe v-loading="loading">
<el-table-column prop="patientName" label="患者" width="100"/>
<el-table-column prop="pathogenName" label="耐药菌" width="130"/>
<el-table-column prop="resistantType" label="耐药类型" width="120"/>
<el-table-column prop="sampleSource" label="标本来源" width="110"/>
<el-table-column prop="isolationStatus" label="隔离" width="80" align="center">
<template #default="{row}"><el-tag :type="row.isolationStatus==='ISOLATED'?'danger':'info'" size="small">{{ {ISOLATED:'已隔离',NOT_ISOLATED:'未隔离',RELEASED:'已解除'}[row.isolationStatus]||row.isolationStatus }}</el-tag></template>
<el-table-column prop="bacteriaType" label="耐药菌类型" width="120" align="center">
<template #default="{row}">
<el-tag size="small" type="danger">{{ bacteriaTypeText(row.bacteriaType) }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="reportTime" label="上报时间" width="160"/>
<el-table-column prop="departmentName" label="科室" width="120"/>
<el-table-column prop="sampleType" label="标本类型" width="100"/>
<el-table-column prop="isolationStatus" label="隔离状态" width="100" align="center">
<template #default="{row}">
<el-tag :type="row.isolationStatus==='ISOLATED'?'danger':row.isolationStatus==='RELEASED'?'success':'warning'" size="small">
{{ row.isolationStatus==='ISOLATED'?'已隔离':row.isolationStatus==='RELEASED'?'解除隔离':'未隔离' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="isolationDate" label="隔离日期" width="120"/>
<el-table-column prop="releaseDate" label="解除日期" width="120"/>
<el-table-column prop="antibioticUsage" label="抗菌药物使用" min-width="150" show-overflow-tooltip/>
<el-table-column prop="createTime" label="报告时间" width="160"/>
</el-table>
<el-pagination style="margin-top:12px;justify-content:flex-end" v-model:current-page="q.pageNo" v-model:page-size="q.pageSize" :total="total" layout="total,prev,pager,next" @current-change="loadData"/>
<el-pagination style="margin-top:12px;justify-content:flex-end" v-model:current-page="q.pageNo" v-model:page-size="q.pageSize" :total="total" layout="total, sizes, prev, pager, next, jumper" @size-change="loadData" @current-change="loadData"/>
<!-- 新增弹窗 -->
<el-dialog title="新增多重耐药菌记录" v-model="showAdd" width="600px" append-to-body>
<el-form :model="formData" label-width="120px">
<el-form-item label="患者姓名" required><el-input v-model="formData.patientName" placeholder="请输入患者姓名"/></el-form-item>
<el-form-item label="耐药菌类型" required>
<el-select v-model="formData.bacteriaType" placeholder="请选择">
<el-option label="MRSA" value="MRSA"/>
<el-option label="VRE" value="VRE"/>
<el-option label="CRE" value="CRE"/>
<el-option label="CRKP" value="CRKP"/>
<el-option label="ESBL" value="ESBL"/>
<el-option label="MDR铜绿" value="MDR_PSA"/>
</el-select>
</el-form-item>
<el-form-item label="科室"><el-input v-model="formData.departmentName" placeholder="请输入科室"/></el-form-item>
<el-form-item label="标本类型"><el-input v-model="formData.sampleType" placeholder="如: 痰液、血液、尿液"/></el-form-item>
<el-form-item label="隔离状态">
<el-radio-group v-model="formData.isolationStatus">
<el-radio value="ISOLATED">已隔离</el-radio>
<el-radio value="NOT_ISOLATED">未隔离</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="抗菌药物使用"><el-input v-model="formData.antibioticUsage" type="textarea" :rows="2" placeholder="请输入抗菌药物使用情况"/></el-form-item>
</el-form>
<template #footer>
<el-button @click="showAdd = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import {ref,onMounted} from 'vue'
import {getPage} from './api'
const tableData=ref([]);const total=ref(0)
const q=ref({pageNo:1,pageSize:20,patientName:''})
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
onMounted(()=>loadData())
import {ref, reactive, onMounted} from 'vue'
import {ElMessage} from 'element-plus'
import {getPage, add} from './api'
const loading = ref(false)
const tableData = ref([])
const total = ref(0)
const showAdd = ref(false)
const stats = ref({total:0, isolated:0, isolationRate:'0%', cureRate:'0%'})
const q = ref({pageNo:1, pageSize:20, bacteriaType:'', isolationStatus:''})
const formData = reactive({
patientName:'', bacteriaType:'MRSA', departmentName:'', sampleType:'',
isolationStatus:'ISOLATED', antibioticUsage:''
})
function bacteriaTypeText(t) {
return {MRSA:'MRSA',VRE:'VRE',CRE:'CRE',CRKP:'CRKP',ESBL:'ESBL',MDR_PSA:'MDR铜绿'}[t] || t
}
async function loadData() {
loading.value = true
try {
const r = await getPage(q.value)
tableData.value = r.data?.records || []
total.value = r.data?.total || 0
let isolated = 0
tableData.value.forEach(row => { if (row.isolationStatus === 'ISOLATED') isolated++ })
stats.value = {
total: total.value, isolated,
isolationRate: total.value > 0 ? Math.round(isolated * 100 / total.value) + '%' : '0%',
cureRate: '100%'
}
} finally { loading.value = false }
}
function resetQuery() {
q.value = {pageNo:1, pageSize:20, bacteriaType:'', isolationStatus:''}
loadData()
}
async function submitForm() {
await add(formData)
ElMessage.success('新增成功')
showAdd.value = false
Object.assign(formData, {patientName:'', bacteriaType:'MRSA', departmentName:'', sampleType:'', isolationStatus:'ISOLATED', antibioticUsage:''})
loadData()
}
onMounted(() => loadData())
</script>

View File

@@ -1,33 +1,194 @@
<template>
<div style="padding:16px">
<div style="margin-bottom:16px"><span style="font-size:18px;font-weight:bold">目标性监测</span></div>
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
<el-select v-model="q.surveillanceType" placeholder="监测类型" clearable style="width:120px">
<el-option label="ICU感染" value="ICU"/><el-option label="手术部位" value="SSI"/><el-option label="导管相关" value="CLABSI"/>
<el-option label="呼吸机相关" value="VAP"/><el-option label="导尿管相关" value="CAUTI"/>
</el-select>
<el-button type="primary" @click="loadData">查询</el-button>
<div style="margin-bottom:16px;display:flex;justify-content:space-between;align-items:center">
<span style="font-size:18px;font-weight:bold">目标性监测</span>
<div>
<el-button type="primary" @click="loadData">刷新</el-button>
<el-button type="success" @click="showAdd = true">新增监测</el-button>
<el-button type="warning" @click="exportReport">导出报告</el-button>
</div>
</div>
<el-table :data="tableData" border stripe>
<el-table-column prop="surveillanceType" label="类型" width="100" align="center">
<template #default="{row}"><el-tag size="small">{{ {ICU:'ICU感染',SSI:'手术部位',CLABSI:'导管相关',VAP:'呼吸机相关',CAUTI:'导尿管相关'}[row.surveillanceType]||row.surveillanceType }}</el-tag></template>
<!-- 统计卡片 -->
<el-row :gutter="16" style="margin-bottom:16px">
<el-col :span="4" v-for="item in statCards" :key="item.label">
<el-card shadow="hover" :body-style="{padding:'12px'}">
<div style="text-align:center">
<div style="font-size:20px;font-weight:bold" :style="{color:item.color}">{{ item.value }}</div>
<div style="font-size:12px;color:#999">{{ item.label }}</div>
</div>
</el-card>
</el-col>
</el-row>
<!-- 筛选 -->
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
<el-select v-model="q.surveillanceType" placeholder="监测类型" clearable style="width:140px">
<el-option label="ICU感染" value="ICU"/>
<el-option label="手术部位感染(SSI)" value="SSI"/>
<el-option label="导管相关血流感染(CLABSI)" value="CLABSI"/>
<el-option label="呼吸机相关肺炎(VAP)" value="VAP"/>
<el-option label="导尿管相关尿路感染(CAUTI)" value="CAUTI"/>
<el-option label="多重耐药菌监测" value="MDR"/>
</el-select>
<el-select v-model="q.status" placeholder="状态" clearable style="width:100px">
<el-option label="进行中" value="ONGOING"/>
<el-option label="已完成" value="COMPLETED"/>
<el-option label="已暂停" value="PAUSED"/>
</el-select>
<el-date-picker v-model="q.dateRange" type="daterange" start-placeholder="开始日期" end-placeholder="结束日期" style="width:240px"/>
<el-button type="primary" @click="loadData">查询</el-button>
<el-button @click="resetQuery">重置</el-button>
</div>
<!-- 数据表格 -->
<el-table :data="tableData" border stripe v-loading="loading">
<el-table-column prop="surveillanceType" label="监测类型" width="160" align="center">
<template #default="{row}">
<el-tag size="small" :type="surveillanceTagType(row.surveillanceType)">
{{ surveillanceTypeText(row.surveillanceType) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="departmentName" label="监测科室" width="120"/>
<el-table-column prop="totalPatients" label="监测人数" width="90" align="center"/>
<el-table-column prop="infectionCount" label="感染数" width="70" align="center"/>
<el-table-column prop="infectionRate" label="感染率" width="80" align="center">
<template #default="{row}"><span :style="{color:row.infectionRate>5?'#F56C6C':'#67C23A',fontWeight:'bold'}">{{ row.infectionRate }}%</span></template>
<el-table-column prop="infectionCount" label="感染数" width="80" align="center">
<template #default="{row}">
<span :style="{color: row.infectionCount > 0 ? '#F56C6C' : '#67C23A', fontWeight:'bold'}">
{{ row.infectionCount }}
</span>
</template>
</el-table-column>
<el-table-column prop="infectionRate" label="感染率" width="90" align="center">
<template #default="{row}">
<span :style="{color: row.infectionRate > 5 ? '#F56C6C' : row.infectionRate > 3 ? '#E6A23C' : '#67C23A', fontWeight:'bold'}">
{{ row.infectionRate }}%
</span>
</template>
</el-table-column>
<el-table-column prop="deviceUsageRate" label="器械使用率" width="100" align="center"/>
<el-table-column prop="surveillancePeriod" label="监测周期" width="160"/>
<el-table-column prop="status" label="状态" width="90" align="center">
<template #default="{row}">
<el-tag :type="row.status==='ONGOING'?'success':row.status==='COMPLETED'?'info':'warning'" size="small">
{{ row.status==='ONGOING'?'进行中':row.status==='COMPLETED'?'已完成':'已暂停' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="surveillancePeriod" label="监测周期" width="150"/>
<el-table-column prop="remark" label="备注" min-width="150" show-overflow-tooltip/>
<el-table-column label="操作" width="120" fixed="right">
<template #default="{row}">
<el-button link type="primary" @click="handleDetail(row)">详情</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination style="margin-top:12px;justify-content:flex-end" v-model:current-page="q.pageNo" v-model:page-size="q.pageSize" :total="total" layout="total,prev,pager,next" @current-change="loadData"/>
<el-pagination style="margin-top:12px;justify-content:flex-end" v-model:current-page="q.pageNo" v-model:page-size="q.pageSize" :total="total" layout="total, sizes, prev, pager, next, jumper" @size-change="loadData" @current-change="loadData"/>
<!-- 新增弹窗 -->
<el-dialog title="新增监测" v-model="showAdd" width="600px" append-to-body>
<el-form :model="formData" label-width="120px">
<el-form-item label="监测类型" required>
<el-select v-model="formData.surveillanceType" placeholder="请选择">
<el-option label="ICU感染" value="ICU"/>
<el-option label="手术部位感染(SSI)" value="SSI"/>
<el-option label="导管相关血流感染(CLABSI)" value="CLABSI"/>
<el-option label="呼吸机相关肺炎(VAP)" value="VAP"/>
<el-option label="导尿管相关尿路感染(CAUTI)" value="CAUTI"/>
<el-option label="多重耐药菌监测" value="MDR"/>
</el-select>
</el-form-item>
<el-form-item label="监测科室"><el-input v-model="formData.departmentName" placeholder="请输入科室名称"/></el-form-item>
<el-form-item label="监测人数"><el-input-number v-model="formData.totalPatients" :min="0"/></el-form-item>
<el-form-item label="感染人数"><el-input-number v-model="formData.infectionCount" :min="0"/></el-form-item>
<el-form-item label="器械使用率(%)"><el-input-number v-model="formData.deviceUsageRate" :min="0" :max="100" :precision="1"/></el-form-item>
<el-form-item label="监测周期"><el-input v-model="formData.surveillancePeriod" placeholder="如: 2026-01-01 ~ 2026-06-30"/></el-form-item>
<el-form-item label="备注"><el-input v-model="formData.remark" type="textarea" :rows="2"/></el-form-item>
</el-form>
<template #footer>
<el-button @click="showAdd = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import {ref,onMounted} from 'vue'
import {getPage} from './api'
const tableData=ref([]);const total=ref(0)
const q=ref({pageNo:1,pageSize:20,surveillanceType:''})
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
onMounted(()=>loadData())
import {ref, reactive, onMounted} from 'vue'
import {ElMessage} from 'element-plus'
import {getPage, add} from './api'
const loading = ref(false)
const tableData = ref([])
const total = ref(0)
const showAdd = ref(false)
const statCards = ref([
{label:'总监测数', value:0, color:'#409eff'},
{label:'ICU感染', value:0, color:'#f56c6c'},
{label:'SSI感染', value:0, color:'#e6a23c'},
{label:'CLABSI', value:0, color:'#67c23a'},
{label:'VAP', value:0, color:'#909399'},
{label:'平均感染率', value:'0%', color:'#409eff'}
])
const q = ref({pageNo:1, pageSize:20, surveillanceType:'', status:'', dateRange:null})
const formData = reactive({
surveillanceType:'ICU', departmentName:'', totalPatients:0, infectionCount:0,
deviceUsageRate:0, surveillancePeriod:'', remark:''
})
function surveillanceTypeText(t) {
return {ICU:'ICU感染',SSI:'手术部位感染',CLABSI:'导管相关血流感染',VAP:'呼吸机相关肺炎',CAUTI:'导尿管相关尿路感染',MDR:'多重耐药菌监测'}[t] || t
}
function surveillanceTagType(t) {
return {ICU:'danger',SSI:'warning',CLABSI:'',VAP:'success',CAUTI:'info',MDR:'danger'}[t] || 'info'
}
async function loadData() {
loading.value = true
try {
const r = await getPage(q.value)
tableData.value = r.data?.records || []
total.value = r.data?.total || 0
// Update stats
if (tableData.value.length > 0) {
statCards.value[0].value = total.value
let icuCount = 0, ssiCount = 0, clabsiCount = 0, vapCount = 0, totalRate = 0
tableData.value.forEach(row => {
if (row.surveillanceType === 'ICU') icuCount++
if (row.surveillanceType === 'SSI') ssiCount++
if (row.surveillanceType === 'CLABSI') clabsiCount++
if (row.surveillanceType === 'VAP') vapCount++
totalRate += (row.infectionRate || 0)
})
statCards.value[1].value = icuCount
statCards.value[2].value = ssiCount
statCards.value[3].value = clabsiCount
statCards.value[4].value = vapCount
statCards.value[5].value = (totalRate / tableData.value.length).toFixed(1) + '%'
}
} finally { loading.value = false }
}
function resetQuery() {
q.value = {pageNo:1, pageSize:20, surveillanceType:'', status:'', dateRange:null}
loadData()
}
function handleDetail(row) {
ElMessage.info('详情: ' + surveillanceTypeText(row.surveillanceType) + ' - ' + (row.departmentName || ''))
}
async function submitForm() {
await add(formData)
ElMessage.success('新增成功')
showAdd.value = false
Object.assign(formData, {surveillanceType:'ICU', departmentName:'', totalPatients:0, infectionCount:0, deviceUsageRate:0, surveillancePeriod:'', remark:''})
loadData()
}
function exportReport() { ElMessage.info('导出功能开发中') }
onMounted(() => loadData())
</script>

View File

@@ -1,34 +1,181 @@
<template>
<div style="padding:16px">
<div style="margin-bottom:16px"><span style="font-size:18px;font-weight:bold">疫情预警</span></div>
<div style="margin-bottom:16px;display:flex;justify-content:space-between;align-items:center">
<span style="font-size:18px;font-weight:bold">疫情预警</span>
<div>
<el-button type="primary" @click="loadData">刷新</el-button>
<el-button type="success" @click="showAdd = true">新增预警</el-button>
</div>
</div>
<!-- 统计卡片 -->
<el-row :gutter="16" style="margin-bottom:16px">
<el-col :span="6">
<el-card shadow="hover" :body-style="{padding:'16px',borderLeft:'4px solid #F56C6C'}">
<div style="text-align:center">
<div style="font-size:28px;font-weight:bold;color:#F56C6C">{{ stats.level1Count || 0 }}</div>
<div style="color:#999">一级预警</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" :body-style="{padding:'16px',borderLeft:'4px solid #E6A23C'}">
<div style="text-align:center">
<div style="font-size:28px;font-weight:bold;color:#E6A23C">{{ stats.level2Count || 0 }}</div>
<div style="color:#999">二级预警</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" :body-style="{padding:'16px',borderLeft:'4px solid #409EFF'}">
<div style="text-align:center">
<div style="font-size:28px;font-weight:bold;color:#409EFF">{{ stats.level3Count || 0 }}</div>
<div style="color:#999">三级预警</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover" :body-style="{padding:'16px',borderLeft:'4px solid #67C23A'}">
<div style="text-align:center">
<div style="font-size:28px;font-weight:bold;color:#67C23A">{{ stats.totalWarnings || 0 }}</div>
<div style="color:#999">总预警数</div>
</div>
</el-card>
</el-col>
</el-row>
<!-- 筛选 -->
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
<el-select v-model="q.warningLevel" placeholder="预警级别" clearable style="width:100px">
<el-option label="一级" value="LEVEL1"/><el-option label="二级" value="LEVEL2"/><el-option label="三级" value="LEVEL3"/>
<el-select v-model="q.warningLevel" placeholder="预警级别" clearable style="width:120px">
<el-option label="一级预警" value="LEVEL1"/>
<el-option label="二级预警" value="LEVEL2"/>
<el-option label="三级预警" value="LEVEL3"/>
</el-select>
<el-select v-model="q.diseaseType" placeholder="疾病类型" clearable style="width:140px">
<el-option label="呼吸道传染病" value="RESPIRATORY"/>
<el-option label="肠道传染病" value="INTESTINAL"/>
<el-option label="血源性传染病" value="BLOOD"/>
<el-option label="虫媒传染病" value="VECTOR"/>
<el-option label="其他" value="OTHER"/>
</el-select>
<el-button type="primary" @click="loadData">查询</el-button>
<el-button @click="resetQuery">重置</el-button>
</div>
<el-table :data="tableData" border stripe>
<el-table-column prop="diseaseName" label="疾病" width="130"/>
<el-table-column prop="warningLevel" label="级别" width="70" align="center">
<!-- 数据表格 -->
<el-table :data="tableData" border stripe v-loading="loading">
<el-table-column prop="diseaseName" label="疾病名称" width="140"/>
<el-table-column prop="warningLevel" label="预警级别" width="110" align="center">
<template #default="{row}">
<el-tag :type="row.warningLevel==='LEVEL1'?'danger':row.warningLevel==='LEVEL2'?'warning':'info'" size="small" effect="dark">
{{ {LEVEL1:'一级',LEVEL2:'二级',LEVEL3:'三级'}[row.warningLevel]||row.warningLevel }}
{{ {LEVEL1:'一级预警',LEVEL2:'二级预警',LEVEL3:'三级预警'}[row.warningLevel]||row.warningLevel }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="caseCount" label="病例数" width="70" align="center"/>
<el-table-column prop="thresholdCount" label="阈值" width="60" align="center"/>
<el-table-column prop="caseCount" label="病例数" width="80" align="center">
<template #default="{row}">
<span :style="{color: row.caseCount > row.thresholdCount ? '#F56C6C' : '#67C23A', fontWeight:'bold'}">
{{ row.caseCount }}
</span>
</template>
</el-table-column>
<el-table-column prop="thresholdCount" label="阈值" width="70" align="center"/>
<el-table-column prop="affectedArea" label="影响区域" min-width="150" show-overflow-tooltip/>
<el-table-column prop="createTime" label="预警时间" width="160"/>
<el-table-column prop="departmentName" label="报告科室" width="120"/>
<el-table-column prop="reporterName" label="报告人" width="100"/>
<el-table-column prop="createTime" label="预警时间" width="170"/>
<el-table-column label="操作" width="120" fixed="right">
<template #default="{row}">
<el-button link type="primary" @click="handleDetail(row)">详情</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination style="margin-top:12px;justify-content:flex-end" v-model:current-page="q.pageNo" v-model:page-size="q.pageSize" :total="total" layout="total,prev,pager,next" @current-change="loadData"/>
<el-pagination style="margin-top:12px;justify-content:flex-end" v-model:current-page="q.pageNo" v-model:page-size="q.pageSize" :total="total" layout="total, sizes, prev, pager, next, jumper" @size-change="loadData" @current-change="loadData"/>
<!-- 新增弹窗 -->
<el-dialog title="新增疫情预警" v-model="showAdd" width="600px" append-to-body>
<el-form :model="formData" label-width="110px">
<el-form-item label="疾病名称" required><el-input v-model="formData.diseaseName" placeholder="请输入疾病名称"/></el-form-item>
<el-form-item label="预警级别" required>
<el-select v-model="formData.warningLevel" placeholder="请选择">
<el-option label="一级预警" value="LEVEL1"/>
<el-option label="二级预警" value="LEVEL2"/>
<el-option label="三级预警" value="LEVEL3"/>
</el-select>
</el-form-item>
<el-form-item label="疾病类型">
<el-select v-model="formData.diseaseType" placeholder="请选择">
<el-option label="呼吸道传染病" value="RESPIRATORY"/>
<el-option label="肠道传染病" value="INTESTINAL"/>
<el-option label="血源性传染病" value="BLOOD"/>
<el-option label="虫媒传染病" value="VECTOR"/>
<el-option label="其他" value="OTHER"/>
</el-select>
</el-form-item>
<el-form-item label="病例数"><el-input-number v-model="formData.caseCount" :min="0"/></el-form-item>
<el-form-item label="阈值"><el-input-number v-model="formData.thresholdCount" :min="0"/></el-form-item>
<el-form-item label="影响区域"><el-input v-model="formData.affectedArea" placeholder="请输入影响区域"/></el-form-item>
<el-form-item label="报告科室"><el-input v-model="formData.departmentName" placeholder="请输入报告科室"/></el-form-item>
</el-form>
<template #footer>
<el-button @click="showAdd = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import {ref,onMounted} from 'vue'
import {getPage} from './api'
const tableData=ref([]);const total=ref(0)
const q=ref({pageNo:1,pageSize:20,warningLevel:''})
const loadData=async()=>{const r=await getPage(q.value);tableData.value=r.data?.records||[];total.value=r.data?.total||0}
onMounted(()=>loadData())
import {ref, reactive, onMounted} from 'vue'
import {ElMessage} from 'element-plus'
import {getPage, add} from './api'
const loading = ref(false)
const tableData = ref([])
const total = ref(0)
const showAdd = ref(false)
const stats = ref({})
const q = ref({pageNo:1, pageSize:20, warningLevel:'', diseaseType:''})
const formData = reactive({
diseaseName:'', warningLevel:'LEVEL2', diseaseType:'RESPIRATORY',
caseCount:0, thresholdCount:0, affectedArea:'', departmentName:''
})
async function loadData() {
loading.value = true
try {
const r = await getPage(q.value)
tableData.value = r.data?.records || []
total.value = r.data?.total || 0
// Calculate stats from data
let l1 = 0, l2 = 0, l3 = 0
tableData.value.forEach(row => {
if (row.warningLevel === 'LEVEL1') l1++
else if (row.warningLevel === 'LEVEL2') l2++
else l3++
})
stats.value = {level1Count: l1, level2Count: l2, level3Count: l3, totalWarnings: total.value}
} finally { loading.value = false }
}
function resetQuery() {
q.value = {pageNo:1, pageSize:20, warningLevel:'', diseaseType:''}
loadData()
}
function handleDetail(row) {
ElMessage.info('详情: ' + row.diseaseName + ' - ' + ({LEVEL1:'一级',LEVEL2:'二级',LEVEL3:'三级'}[row.warningLevel] || ''))
}
async function submitForm() {
await add(formData)
ElMessage.success('新增成功')
showAdd.value = false
Object.assign(formData, {diseaseName:'', warningLevel:'LEVEL2', diseaseType:'RESPIRATORY', caseCount:0, thresholdCount:0, affectedArea:'', departmentName:''})
loadData()
}
onMounted(() => loadData())
</script>