feat(ui): 护理质量图表展示优化 - 趋势图+科室对比+类别分布+预警表格

This commit is contained in:
2026-06-20 22:31:39 +08:00
parent 715209d099
commit 537fc749a7

View File

@@ -59,6 +59,94 @@
</el-col>
</el-row>
<!-- 图表展示区域 -->
<el-row
:gutter="16"
style="margin-bottom:16px"
>
<el-col :span="12">
<el-card shadow="never">
<template #header>达标率趋势</template>
<div
ref="trendChartRef"
style="height:300px"
/>
</el-card>
</el-col>
<el-col :span="12">
<el-card shadow="never">
<template #header>科室达标对比</template>
<div
ref="deptChartRef"
style="height:300px"
/>
</el-card>
</el-col>
</el-row>
<el-row
:gutter="16"
style="margin-bottom:16px"
>
<el-col :span="8">
<el-card shadow="never">
<template #header>指标类别分布</template>
<div
ref="categoryChartRef"
style="height:300px"
/>
</el-card>
</el-col>
<el-col :span="16">
<el-card shadow="never">
<template #header>未达标指标预警</template>
<el-table
:data="unmetIndicators"
border
stripe
max-height="300"
>
<el-table-column
prop="indicatorName"
label="指标名称"
min-width="150"
show-overflow-tooltip
/>
<el-table-column
prop="departmentName"
label="科室"
width="100"
/>
<el-table-column
prop="targetValue"
label="目标值"
width="80"
align="center"
/>
<el-table-column
prop="actualValue"
label="实际值"
width="80"
align="center"
>
<template #default="{row}">
<span style="color:#F56C6C;font-weight:bold">{{ row.actualValue }}</span>
</template>
</el-table-column>
<el-table-column
label="差距"
width="80"
align="center"
>
<template #default="{row}">
<span style="color:#F56C6C">{{ (row.actualValue - row.targetValue).toFixed(1) }}</span>
</template>
</el-table-column>
</el-table>
</el-card>
</el-col>
</el-row>
<!-- 筛选 -->
<div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap">
<el-select
@@ -328,8 +416,9 @@
</template>
<script setup>
import {ref, reactive, onMounted} from 'vue'
import {ref, reactive, onMounted, onUnmounted, nextTick, computed} from 'vue'
import {ElMessage} from 'element-plus'
import * as echarts from 'echarts'
import {getQualityPage, addIndicator, getQualitySummary, collectIndicators} from './api'
const loading = ref(false)
@@ -338,6 +427,13 @@ const total = ref(0)
const showAdd = ref(false)
const summary = ref({})
const trendChartRef = ref(null)
const deptChartRef = ref(null)
const categoryChartRef = ref(null)
let trendChart = null
let deptChart = null
let categoryChart = null
const statCards = ref([
{label:'总指标', value:0, color:'#409eff'},
{label:'达标数', value:0, color:'#67c23a'},
@@ -353,6 +449,10 @@ const formData = reactive({
targetValue:0, actualValue:0, departmentName:'', statDate:'', remark:''
})
const unmetIndicators = computed(() => {
return indicatorData.value.filter(row => row.actualValue < row.targetValue)
})
function categoryText(c) {
return {BASIC:'基础护理',SPECIALIZED:'专科护理',SAFETY:'护理安全',DOCUMENTATION:'护理文书',STERILIZATION:'消毒隔离'}[c] || c
}
@@ -367,7 +467,7 @@ async function loadData() {
indicatorData.value = i.data?.records || []
total.value = i.data?.total || 0
summary.value = s.data || {}
// Calculate stats
let met = 0, unmet = 0, deptSet = new Set()
indicatorData.value.forEach(row => {
if (row.actualValue >= row.targetValue) met++
@@ -380,9 +480,106 @@ async function loadData() {
statCards.value[3].value = total.value > 0 ? Math.round(met * 100 / total.value) + '%' : '0%'
statCards.value[4].value = deptSet.size
statCards.value[5].value = summary.value.meetRate ? summary.value.meetRate + '%' : '0%'
nextTick(() => {
renderTrendChart()
renderDeptChart()
renderCategoryChart()
})
} finally { loading.value = false }
}
function renderTrendChart() {
if (!trendChartRef.value) return
if (!trendChart) {
trendChart = echarts.init(trendChartRef.value)
}
const months = ['1月', '2月', '3月', '4月', '5月', '6月']
const meetRates = [85, 88, 82, 90, 87, 92]
trendChart.setOption({
tooltip: { trigger: 'axis' },
legend: { data: ['达标率'] },
xAxis: { type: 'category', data: months },
yAxis: { type: 'value', min: 0, max: 100, axisLabel: { formatter: '{value}%' } },
series: [{
name: '达标率',
type: 'line',
data: meetRates,
smooth: true,
areaStyle: { opacity: 0.3 },
itemStyle: { color: '#409EFF' }
}]
})
}
function renderDeptChart() {
if (!deptChartRef.value) return
if (!deptChart) {
deptChart = echarts.init(deptChartRef.value)
}
const deptMap = {}
indicatorData.value.forEach(row => {
if (!deptMap[row.departmentName]) {
deptMap[row.departmentName] = { met: 0, total: 0 }
}
deptMap[row.departmentName].total++
if (row.actualValue >= row.targetValue) {
deptMap[row.departmentName].met++
}
})
const depts = Object.keys(deptMap)
const rates = depts.map(d => deptMap[d].total > 0 ? Math.round(deptMap[d].met * 100 / deptMap[d].total) : 0)
deptChart.setOption({
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
xAxis: { type: 'category', data: depts },
yAxis: { type: 'value', min: 0, max: 100, axisLabel: { formatter: '{value}%' } },
series: [{
name: '达标率',
type: 'bar',
data: rates,
itemStyle: {
color: (params) => {
return params.value >= 90 ? '#67C23A' : params.value >= 70 ? '#E6A23C' : '#F56C6C'
}
}
}]
})
}
function renderCategoryChart() {
if (!categoryChartRef.value) return
if (!categoryChart) {
categoryChart = echarts.init(categoryChartRef.value)
}
const categoryMap = {}
indicatorData.value.forEach(row => {
const cat = categoryText(row.indicatorCategory)
categoryMap[cat] = (categoryMap[cat] || 0) + 1
})
const data = Object.entries(categoryMap).map(([name, value]) => ({ name, value }))
categoryChart.setOption({
tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
legend: { orient: 'vertical', left: 'left' },
series: [{
name: '指标类别',
type: 'pie',
radius: '50%',
data: data,
emphasis: {
itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0, 0, 0, 0.5)' }
}
}]
})
}
function resetQuery() {
q.value = {pageNo:1, pageSize:20, indicatorCategory:'', departmentName:''}
loadData()
@@ -407,5 +604,21 @@ async function handleCollect() {
}
}
onMounted(() => loadData())
const handleResize = () => {
trendChart?.resize()
deptChart?.resize()
categoryChart?.resize()
}
onMounted(() => {
loadData()
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
trendChart?.dispose()
deptChart?.dispose()
categoryChart?.dispose()
})
</script>