<template>
|
<div class="room-screen-container">
|
<div class="search-bar">
|
<input
|
class="search-input"
|
type="text"
|
placeholder="请输入房间号查询"
|
v-model="searchRoomInput"
|
@keyup.enter="searchRoom"
|
/>
|
<button class="search-btn" @click="searchRoom"> 查询 </button>
|
</div>
|
|
<div class="header">
|
<div class="clinic-title">心电图诊间叫号系统</div>
|
<div class="clinic-info">
|
<div class="room-name">
|
{{ roomProfile.roomName || '诊间加载中...' }}
|
</div>
|
<div class="screen-type"> 模式:{{ screenTypeText }} </div>
|
</div>
|
</div>
|
|
<div class="main-content">
|
<div class="panel" v-if="showCheckPanel">
|
<div class="panel-header">检查队列</div>
|
<div class="patient-list">
|
<div v-if="checkRelatedPatientList.length === 0" class="empty-state">
|
暂无等待检查的患者
|
</div>
|
<div
|
v-for="(patient, index) in checkRelatedPatientList"
|
:key="'check-' + index"
|
class="patient-item"
|
:class="getStatusClass(patient.status)"
|
>
|
<div class="patient-info">
|
<div class="patient-number"> {{ getSeqPrefix(patient) }}{{ patient.bedNo }} </div>
|
<div class="patient-name">
|
{{ nameDesensitize(patient.patName) }}
|
</div>
|
<div class="patient-check-type">
|
{{ getCheckTypeName(patient.bookCheckType) }}
|
</div>
|
<div class="patient-status" :class="'status-' + getStatusClass(patient.status)">
|
{{ queueStatusConvert(patient.status) }}
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
|
<div
|
class="panel"
|
v-if="showInstallPanel"
|
>
|
<div class="panel-header">装机队列</div>
|
<div class="patient-list">
|
<div
|
v-if="installRelatedPatientList.length === 0"
|
class="empty-state"
|
>
|
暂无等待装机的患者
|
</div>
|
<div
|
v-for="(patient, index) in installRelatedPatientList"
|
:key="'install-'+index"
|
class="patient-item"
|
:class="getStatusClass(patient.status)"
|
>
|
<div class="patient-info">
|
<div class="patient-number">
|
{{ patient.bookSeqNum }}
|
</div>
|
<div class="patient-name">
|
{{ nameDesensitize(patient.patName) }}
|
</div>
|
<div class="patient-check-type">
|
{{ getCheckTypeName(patient.bookCheckType) }}
|
</div>
|
<div
|
class="patient-status"
|
:class="'status-'+getStatusClass(patient.status)"
|
>
|
{{ queueStatusConvert(patient.status) }}
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
|
<div class="footer">
|
<div class="announcement">
|
{{ announcementText }}
|
</div>
|
<div class="controls">
|
<button class="control-btn pulse" @click="initiateSpeak">
|
<i>📢</i>
|
<span>叫号</span>
|
</button>
|
<button class="flex-1 control-btn" @click="speak('欢迎使用诊间叫号系统')">
|
<i>🔊</i>
|
<span>测试播音</span>
|
</button>
|
<button class="flex-1 control-btn" @click="changeRoom">
|
<i>🔄</i>
|
<span>切换诊间</span>
|
</button>
|
</div>
|
</div>
|
</div>
|
</template>
|
|
<script>
|
import { ref, computed, onBeforeUnmount } from 'vue'
|
import { ScreenApi } from '@/api/ecg/screen'
|
|
import axios from 'axios'
|
|
export default {
|
name: 'RoomScreen',
|
setup() {
|
// 响应式数据
|
const roomProfile = ref({
|
roomName: '心电图诊室 01',
|
callingScreenType: 40
|
})
|
|
const checkRelatedPatientList = ref([])
|
const installRelatedPatientList = ref([])
|
const curSpeakPat = ref(null)
|
const announcementText = ref('系统运行中...')
|
const roomId = ref(1)
|
const timer = ref(null)
|
const speechSynthesis = ref(window.speechSynthesis || null)
|
const searchRoomInput = ref('')
|
|
// 计算属性
|
const screenTypeText = computed(() => {
|
const types = {
|
10: '仅检查队列',
|
20: '仅检查队列',
|
30: '仅装机队列',
|
40: '双队列模式',
|
50: '双队列模式'
|
}
|
return types[roomProfile.value.callingScreenType] || '未知模式'
|
})
|
|
const showCheckPanel = computed(() => {
|
return [10, 20, 40, 50].includes(roomProfile.value.callingScreenType)
|
})
|
|
const showInstallPanel = computed(() => {
|
return [30, 40, 50].includes(roomProfile.value.callingScreenType)
|
})
|
|
// 方法
|
const searchRoom = () => {
|
if (!searchRoomInput.value.trim()) {
|
announcementText.value = '请输入有效的房间号'
|
return
|
}
|
|
announcementText.value = `正在查询 ${searchRoomInput.value} 房间信息...`
|
|
setTimeout(() => {
|
const roomNum = parseInt(searchRoomInput.value) || 0
|
roomId.value = roomNum % 3
|
getRoomByIp()
|
announcementText.value = `已加载 ${roomProfile.value.roomName} 信息`
|
}, 500)
|
}
|
|
const getRoomByIp = () => {
|
setTimeout(() => {
|
ScreenApi.getRoomScreenData(searchRoomInput.value)
|
.then((response) => {
|
console.log(response, '991')
|
checkRelatedPatientList.value = response[1]
|
})
|
.catch((error) => {
|
console.error('获取患者失败:', error)
|
})
|
// axios
|
// .get(`http://localhost:48080/admin-api/ecg/screen/room-screen-data`, {
|
// params: {
|
// roomId: searchRoomInput.value
|
// }
|
// })
|
// .then((response) => {
|
// const patient = response.data
|
// console.log(response.data, '55')
|
|
// if (patient && patient.called === 0) {
|
// curSpeakPat.value = patient
|
// speak(`请${patient.patName}到${roomProfile.value.roomName}装机`)
|
// }
|
// })
|
// .catch((error) => {
|
// console.error('获取下一位患者失败:', error)
|
// })
|
|
const rooms = [
|
{ roomName: '心电图诊室 01', callingScreenType: 40 },
|
{ roomName: '动态心电图室', callingScreenType: 10 },
|
{ roomName: '运动试验室', callingScreenType: 30 }
|
]
|
roomProfile.value = rooms[roomId.value % 3]
|
announcementText.value = `已加载 ${roomProfile.value.roomName} 信息`
|
}, 300)
|
}
|
|
const getList = () => {
|
setTimeout(() => {
|
ScreenApi.getRoomScreenData(searchRoomInput.value)
|
.then((response) => {
|
console.log(response, '199')
|
checkRelatedPatientList.value = response[1]
|
})
|
.catch((error) => {
|
console.error('获取患者失败:', error)
|
})
|
// axios
|
// .get(`http://localhost:48080/admin-api/ecg/screen/room-screen-data`, {
|
// params: {
|
// roomId: searchRoomInput.value
|
// }
|
// })
|
// .then((response) => {
|
// checkRelatedPatientList.value = response.data.data[1]
|
// console.log(response.data.data[1], '66')
|
// })
|
// .catch((error) => {
|
// console.error('获取下一位患者失败:', error)
|
// })
|
|
if (!curSpeakPat.value && Math.random() > 0.7) {
|
initiateSpeak()
|
}
|
}, 500)
|
}
|
|
const startScrolling = () => {
|
getList()
|
timer.value = setInterval(() => {
|
getList()
|
}, 5000)
|
}
|
|
const nameDesensitize = (patName) => {
|
if (!patName) return ''
|
if (patName.length === 2) {
|
return patName.substring(0, 1) + '*'
|
} else if (patName.length === 3) {
|
return patName.substring(0, 1) + '*' + patName.substring(2, 3)
|
} else if (patName.length > 3) {
|
return patName.substring(0, 1) + '*' + '*' + patName.substring(3, patName.length)
|
}
|
return patName
|
}
|
|
const getStatusClass = (status) => {
|
if (status === 10 || status === 40 || status === 33 || status === 20 || status === 10)
|
return 'waiting'
|
if (status === 30) return 'in-progress'
|
if (status === 7 || status === 3 || status === 5) return 'completed'
|
return ''
|
}
|
|
const queueStatusConvert = (status) => {
|
const statusMap = {
|
3: '已过号-排队',
|
5: '已过号',
|
7: '已过号-安装',
|
10: '排队中',
|
12: '亲和',
|
13: '亲和-安装',
|
15: '已召回',
|
20: '候诊中',
|
30: '就诊中',
|
33: '已领用',
|
34: '已召回-安装',
|
36: '安装中',
|
40: '已就诊'
|
}
|
return statusMap[status] || '未知状态'
|
}
|
|
const getCheckTypeName = (type) => {
|
const types = {
|
1: '常规心电图',
|
2: '动态心电图',
|
3: '运动试验',
|
4: '心电监护'
|
}
|
return types[type] || '未知检查'
|
}
|
|
const getSeqPrefix = (patient) => {
|
const types = {
|
1: 'A001',
|
2: 'A002',
|
3: 'A003',
|
4: 'A004'
|
}
|
return types[patient.bookCheckType] || ''
|
}
|
|
const initiateSpeak = () => {
|
const waitingPatients = installRelatedPatientList.value.filter((p) => p.status === 5)
|
if (waitingPatients.length === 0) {
|
announcementText.value = '当前没有等待装机的患者'
|
return
|
}
|
|
const patient = waitingPatients[0]
|
curSpeakPat.value = {
|
patName: patient.patName,
|
roomName: roomProfile.value.roomName
|
}
|
|
speak('请' + patient.patName + '到' + roomProfile.value.roomName + '装机')
|
}
|
|
const speak = (msg) => {
|
announcementText.value = '正在呼叫: ' + msg
|
|
if (!speechSynthesis.value) {
|
console.warn('当前浏览器不支持语音合成')
|
return
|
}
|
|
speechSynthesis.value.cancel()
|
|
const speech = new SpeechSynthesisUtterance()
|
speech.text = msg + '。。。' + msg + '。。。' + msg
|
speech.pitch = 1
|
speech.rate = 0.9
|
speech.volume = 1
|
speech.lang = 'zh-CN'
|
|
speechSynthesis.value.speak(speech)
|
}
|
|
const onSpeachEndEvent = (event) => {
|
curSpeakPat.value = null
|
announcementText.value = '系统运行中...'
|
}
|
|
const changeRoom = () => {
|
roomId.value = (roomId.value + 1) % 3
|
getRoomByIp()
|
announcementText.value = '正在切换诊间...'
|
}
|
|
// 生命周期钩子
|
onBeforeUnmount(() => {
|
if (timer.value) {
|
clearInterval(timer.value)
|
}
|
})
|
|
// 初始化
|
getRoomByIp()
|
startScrolling()
|
|
// 初始化语音合成
|
if (speechSynthesis.value) {
|
speechSynthesis.value.onend = onSpeachEndEvent
|
}
|
|
return {
|
roomProfile,
|
checkRelatedPatientList,
|
installRelatedPatientList,
|
curSpeakPat,
|
announcementText,
|
roomId,
|
timer,
|
speechSynthesis,
|
searchRoomInput,
|
screenTypeText,
|
showCheckPanel,
|
showInstallPanel,
|
searchRoom,
|
getRoomByIp,
|
getList,
|
startScrolling,
|
nameDesensitize,
|
getStatusClass,
|
queueStatusConvert,
|
getCheckTypeName,
|
getSeqPrefix,
|
initiateSpeak,
|
speak,
|
onSpeachEndEvent,
|
changeRoom
|
}
|
}
|
}
|
</script>
|
|
<style scoped>
|
.room-screen-container {
|
height: 100vh;
|
display: flex;
|
flex-direction: column;
|
background: linear-gradient(135deg, #e6f0f8, #d9e4f0);
|
color: #333;
|
line-height: 1.5;
|
overflow: hidden;
|
padding: 10px;
|
}
|
|
.search-bar {
|
background: rgba(255, 255, 255, 0.8);
|
border-radius: 16px;
|
padding: 12px 15px;
|
margin-bottom: 10px;
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
display: flex;
|
align-items: center;
|
}
|
|
.search-input {
|
flex: 1;
|
border: none;
|
background: rgba(240, 244, 249, 0.7);
|
border-radius: 12px;
|
padding: 8px 12px;
|
font-size: 0.9rem;
|
outline: none;
|
color: #4a5568;
|
}
|
|
.search-btn {
|
margin-left: 8px;
|
background: #5b8cff;
|
color: white;
|
border: none;
|
border-radius: 12px;
|
padding: 8px 15px;
|
font-size: 0.9rem;
|
cursor: pointer;
|
transition: all 0.3s;
|
}
|
|
.search-btn:hover {
|
background: #3a7bff;
|
}
|
|
.header {
|
background: rgba(255, 255, 255, 0.8);
|
border-radius: 16px;
|
padding: 12px 15px;
|
margin-bottom: 10px;
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
}
|
|
.clinic-title {
|
font-size: 1.4rem;
|
font-weight: bold;
|
text-align: center;
|
margin-bottom: 5px;
|
color: #4a7dff;
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
}
|
|
.clinic-info {
|
display: flex;
|
justify-content: space-between;
|
width: 100%;
|
font-size: 0.9rem;
|
color: #555;
|
}
|
|
.room-name {
|
background: rgba(91, 140, 255, 0.1);
|
padding: 4px 10px;
|
border-radius: 20px;
|
min-width: 120px;
|
text-align: center;
|
color: #4a7dff;
|
}
|
|
.screen-type {
|
background: rgba(91, 140, 255, 0.1);
|
padding: 4px 10px;
|
border-radius: 20px;
|
color: #4a7dff;
|
}
|
|
.main-content {
|
flex: 1;
|
display: flex;
|
flex-direction: column;
|
gap: 12px;
|
overflow: hidden;
|
}
|
|
.panel {
|
background: rgba(255, 255, 255, 0.95);
|
border-radius: 14px;
|
padding: 12px;
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
display: flex;
|
flex-direction: column;
|
overflow: hidden;
|
flex: 1;
|
border: 1px solid rgba(0, 0, 0, 0.05);
|
}
|
|
.panel-header {
|
background: linear-gradient(90deg, #a8c4ff, #c0d3ff);
|
color: #2c3e50;
|
padding: 8px 12px;
|
border-radius: 8px;
|
margin-bottom: 10px;
|
font-size: 1rem;
|
font-weight: bold;
|
text-align: center;
|
}
|
|
.patient-list {
|
flex: 1;
|
overflow-y: auto;
|
-webkit-overflow-scrolling: touch;
|
}
|
|
.patient-item {
|
display: flex;
|
align-items: center;
|
padding: 10px 8px;
|
border-bottom: 1px solid #eee;
|
transition: all 0.3s;
|
}
|
|
.patient-item:last-child {
|
border-bottom: none;
|
}
|
|
.patient-item.warning {
|
background-color: #fdf6ec;
|
}
|
|
.patient-item.in-progress {
|
background-color: #f0f9eb;
|
}
|
|
.patient-item.completed {
|
background-color: #f4f4f5;
|
}
|
|
.patient-info {
|
display: flex;
|
flex: 1;
|
min-width: 0;
|
}
|
|
.patient-number {
|
width: 80px;
|
font-weight: bold;
|
color: #5b8cff;
|
font-size: 0.95rem;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
}
|
|
.patient-name {
|
width: 100px;
|
font-size: 0.95rem;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
}
|
|
.patient-check-type {
|
flex: 1;
|
font-size: 0.95rem;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
}
|
|
.patient-status {
|
width: 70px;
|
font-size: 0.8rem;
|
font-weight: bold;
|
text-align: center;
|
padding: 3px 8px;
|
border-radius: 10px;
|
}
|
|
.status-waiting {
|
background-color: #fdf6ec;
|
color: #e6a23c;
|
}
|
|
.status-in-progress {
|
background-color: #f0f9eb;
|
color: #67c23a;
|
}
|
|
.status-completed {
|
background-color: #f4f4f5;
|
color: #909399;
|
}
|
|
.patient-bed {
|
width: 60px;
|
font-size: 0.85rem;
|
text-align: right;
|
color: #666;
|
}
|
|
.footer {
|
background: rgba(255, 255, 255, 0.8);
|
border-radius: 16px;
|
padding: 12px 15px;
|
margin-top: 10px;
|
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
|
border: 1px solid rgba(0, 0, 0, 0.05);
|
}
|
|
.announcement {
|
background: rgba(91, 140, 255, 0.1);
|
padding: 8px 15px;
|
border-radius: 20px;
|
font-size: 0.9rem;
|
text-align: center;
|
margin-bottom: 12px;
|
min-height: 20px;
|
color: #4a7dff;
|
}
|
|
.controls {
|
display: grid;
|
grid-template-columns: repeat(3, 1fr);
|
gap: 8px;
|
}
|
|
.control-btn {
|
padding: 8px 5px;
|
background: rgba(91, 140, 255, 0.1);
|
border: none;
|
border-radius: 12px;
|
color: #4a7dff;
|
font-size: 0.85rem;
|
cursor: pointer;
|
transition: all 0.3s;
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
justify-content: center;
|
}
|
|
.control-btn i {
|
font-size: 1.2rem;
|
margin-bottom: 3px;
|
}
|
|
.control-btn:hover {
|
background: rgba(91, 140, 255, 0.2);
|
}
|
|
.control-btn:active {
|
transform: scale(0.95);
|
}
|
|
.empty-state {
|
text-align: center;
|
color: #999;
|
padding: 20px;
|
font-size: 0.9rem;
|
}
|
|
/* 滚动条样式 */
|
.patient-list::-webkit-scrollbar {
|
width: 5px;
|
}
|
|
.patient-list::-webkit-scrollbar-track {
|
background: #f1f1f1;
|
border-radius: 4px;
|
}
|
|
.patient-list::-webkit-scrollbar-thumb {
|
background: #c0c4cc;
|
border-radius: 4px;
|
}
|
|
.patient-list::-webkit-scrollbar-thumb:hover {
|
background: #909399;
|
}
|
|
/* 动画效果 */
|
@keyframes pulse {
|
0% {
|
transform: scale(1);
|
}
|
|
50% {
|
transform: scale(1.05);
|
}
|
|
100% {
|
transform: scale(1);
|
}
|
}
|
|
.pulse {
|
animation: pulse 2s infinite;
|
}
|
|
/* 响应式调整 */
|
@media (max-width: 480px) {
|
.header {
|
padding: 10px 12px;
|
}
|
|
.clinic-title {
|
font-size: 1.2rem;
|
}
|
|
.clinic-info {
|
font-size: 0.8rem;
|
}
|
|
.panel {
|
padding: 10px;
|
}
|
|
.panel-header {
|
font-size: 0.9rem;
|
padding: 6px 10px;
|
}
|
|
.patient-item {
|
padding: 8px 6px;
|
}
|
|
.patient-number,
|
.patient-name {
|
width: 60px;
|
font-size: 0.9rem;
|
}
|
|
.patient-status {
|
width: 60px;
|
font-size: 0.7rem;
|
}
|
|
.patient-bed {
|
width: 50px;
|
}
|
}
|
|
@media (max-height: 600px) {
|
.header {
|
padding: 8px 10px;
|
}
|
|
.panel {
|
padding: 8px;
|
}
|
|
.patient-item {
|
padding: 6px 4px;
|
}
|
}
|
</style>
|