feat(ui): 护理质量图表展示优化 - 趋势图+科室对比+类别分布+预警表格
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user