feat(ui): 危急值实时通知优化 - WebSocket推送+声音提醒+快捷处理

This commit is contained in:
2026-06-20 22:15:04 +08:00
parent da6f03961c
commit 109abc122a
2 changed files with 133 additions and 0 deletions

View File

@@ -0,0 +1,129 @@
<template>
<div class="critical-value-notify">
<el-badge :value="pendingCount" :hidden="pendingCount === 0" :max="99">
<el-button @click="showDrawer = true" type="danger" plain>
危急值 ({{ pendingCount }})
</el-button>
</el-badge>
<el-drawer v-model="showDrawer" title="危急值处理" size="450px">
<div v-if="pendingList.length === 0" style="text-align:center;padding:40px;color:#999">
暂无待处理危急值
</div>
<div v-for="item in pendingList" :key="item.id" class="notify-item">
<el-alert
:title="item.patientName + ' - ' + item.itemName"
type="error"
show-icon
:closable="false"
>
<div style="margin: 8px 0">
<div><strong>结果:</strong> <span style="color:#F56C6C;font-weight:bold">{{ item.resultValue }}</span></div>
<div><strong>参考范围:</strong> {{ item.referenceRange }}</div>
<div><strong>报告时间:</strong> {{ item.reportTime }}</div>
</div>
<el-button-group style="margin-top: 8px">
<el-button size="small" type="primary" @click="quickConfirm(item)">
确认接收
</el-button>
<el-button size="small" type="warning" @click="quickProcess(item)">
处理
</el-button>
</el-button-group>
</el-alert>
</div>
</el-drawer>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getPendingList, confirmCriticalValue, processCriticalValue } from '../api'
const pendingCount = ref(0)
const pendingList = ref([])
const showDrawer = ref(false)
let ws = null
let audio = null
const loadPending = async () => {
const res = await getPendingList()
pendingList.value = res.data || []
pendingCount.value = pendingList.value.length
}
const quickConfirm = async (row) => {
await ElMessageBox.confirm('确认接收此危急值?', '确认', { type: 'warning' })
await confirmCriticalValue(row.id, { receiverId: 1, receiverName: '当前用户' })
ElMessage.success('已确认接收')
loadPending()
}
const quickProcess = async (row) => {
await ElMessageBox.prompt('请输入处理结果', '处理危急值', { type: 'info' })
.then(async ({ value }) => {
await processCriticalValue(row.id, { handlerId: 1, handlerName: '当前用户', result: value || '已处理' })
ElMessage.success('处理完成')
loadPending()
})
}
const playAlertSound = () => {
try {
if (!audio) {
audio = new Audio('data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQoGAACBhYqFbF1fdJivrJBhNjVggoKIeGBGPoKQmJhyaTg3ZIKJi3ldREB/kJyaamA0NGOCi418W0ZAf4+YnmpgMzRjgoqNfFtGQH+QmJ5qYDM0Y4KKjXxbRkB/kJyeamAzNGOCio18W0ZAf5CcnmphMzRjgoqNfFtGQH+QnJ5qYTM0Y4KKjXxbRkB/kJyeamEzNGOCio18W0ZAf5CcnmphMzRjgoqNfFtGQH+QnJ5qYTM0Y4KKjXxbRkB/kJyeamEzNGOCio18W0ZAf5CcnmphMzRjgA==')
audio.loop = false
}
audio.play().catch(() => {})
} catch (e) {}
}
const initWebSocket = () => {
try {
const wsUrl = `ws://${window.location.hostname}:18082/ws/critical-value`
ws = new WebSocket(wsUrl)
ws.onmessage = (event) => {
const data = JSON.parse(event.data)
if (data.type === 'CRITICAL_VALUE') {
pendingCount.value++
pendingList.value.unshift(data)
playAlertSound()
ElMessage.error(`新危急值: ${data.patientName} - ${data.itemName}`)
if (Notification.permission === 'granted') {
new Notification('危急值提醒', {
body: `${data.patientName} - ${data.itemName}: ${data.resultValue}`,
icon: '/favicon.ico'
})
}
}
}
ws.onclose = () => {
setTimeout(initWebSocket, 5000)
}
} catch (e) {
console.error('WebSocket连接失败:', e)
}
}
onMounted(() => {
loadPending()
initWebSocket()
if (Notification.permission === 'default') {
Notification.requestPermission()
}
})
onUnmounted(() => {
if (ws) ws.close()
})
</script>
<style scoped>
.notify-item {
margin-bottom: 12px;
}
</style>

View File

@@ -1,5 +1,8 @@
<template>
<div class="app-container">
<div style="position:fixed;top:60px;right:20px;z-index:1000">
<CriticalValueNotify />
</div>
<el-row
:gutter="20"
class="mb8"
@@ -178,6 +181,7 @@
<script setup>
import { ref, onMounted } from 'vue'
import { getPendingList, getOverdueList, confirmCriticalValue, processCriticalValue, closeCriticalValue, getStatistics } from '../api'
import CriticalValueNotify from '../components/CriticalValueNotify.vue'
import { ElMessage, ElMessageBox } from 'element-plus'
const activeTab = ref('pending')