vue3/src/views/devices/Device_Data_Components/ECG_datas.vue

570 lines
15 KiB
Vue
Raw Normal View History

2025-06-09 15:39:06 +08:00
<template>
<el-dialog
v-model="dialogVisible"
:title="currentDeviceName"
width="80%"
:close-on-click-modal="false"
:close-on-press-escape="false"
:show-close="true"
class="ecg-dialog"
top="5vh"
>
<div class="ecg-container">
<!-- 左侧人员列表 -->
<div class="person-list">
<el-card class="box-card">
<el-input
v-model="searchQuery"
2025-06-10 14:20:09 +08:00
placeholder="请输入姓名&ID"
2025-06-09 15:39:06 +08:00
prefix-icon="el-icon-search"
clearable
/>
<el-scrollbar height="calc(100vh - 400px)">
<el-menu
:default-active="activePerson"
@select="handlePersonSelect"
>
<el-menu-item
v-for="person in filteredPersonList"
:key="person.id"
2025-06-10 14:20:09 +08:00
:index="person.id.toString()"
>
<span>{{ person.username }} (ID: {{ person.userid }})</span>
</el-menu-item>
</el-menu>
</el-scrollbar>
</el-card>
</div>
<!-- 中间时间列表 -->
<div class="time-list">
<el-card class="box-card">
<div class="time-list-header">
<el-date-picker
v-model="dateFilter"
type="date"
placeholder="请选择采集日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
:clearable="true"
@change="handleDateFilter"
style="width: 100%; margin-bottom: 10px;"
/>
</div>
<el-scrollbar height="400px">
<el-menu
:default-active="activeTime"
@select="handleTimeSelect"
>
<el-menu-item
v-for="(time, index) in filteredTimeList"
:key="index"
:index="index.toString()"
2025-06-09 15:39:06 +08:00
>
2025-06-10 14:20:09 +08:00
<span>{{ formatTime(time.collecttime) }}</span>
2025-06-09 15:39:06 +08:00
</el-menu-item>
2025-06-10 14:20:09 +08:00
<div v-if="filteredTimeList.length === 0" class="no-time-data">
暂无采集时间
</div>
2025-06-09 15:39:06 +08:00
</el-menu>
</el-scrollbar>
</el-card>
</div>
<!-- 右侧数据展示区域 -->
<div class="data-display">
<el-card class="box-card">
2025-06-10 14:20:09 +08:00
<div class="ecg-data-content">
<div class="no-selection" v-if="!selectedPerson">
请从左侧选择人员查看数据
2025-06-09 15:39:06 +08:00
</div>
<div v-else>
2025-06-10 14:20:09 +08:00
<div class="no-data" v-if="!hasData">
暂无数据
</div>
<div v-else>
<el-tabs v-model="activeTab" class="ecg-tabs">
<el-tab-pane label="基础数据" name="basic">
<div class="ecg-fields-container">
<div class="ecg-grid">
<div
v-for="field in basicFields"
:key="field.key"
class="ecg-grid-item">
<div class="ecg-field-content">
<span class="ecg-label">{{ field.label }}</span>
<span class="ecg-value">{{ selectedPersonData && selectedPersonData[field.key] !== undefined ? selectedPersonData[field.key] : '-' }}</span>
</div>
</div>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="心电图" name="ecg">
<div class="ecg-image-container">
<template v-if="selectedPersonData && selectedPersonData.ecgimageurl">
<el-image
:src="selectedPersonData.ecgimageurl"
:preview-src-list="[selectedPersonData.ecgimageurl]"
fit="contain"
class="ecg-image"
:preview-teleported="true"
/>
</template>
<template v-else>
<el-empty description="暂无心电图" />
</template>
</div>
<div v-if="selectedPersonData && selectedPersonData.diagnosis" style="margin-top: 20px;">
心电图诊断{{ selectedPersonData.diagnosis }}
</div>
</el-tab-pane>
</el-tabs>
</div>
2025-06-09 15:39:06 +08:00
</div>
</div>
2025-06-16 16:49:57 +08:00
<!-- 添加医生通知输入框和发送按钮 -->
<div class="doctor-notification">
<el-input
v-model="doctorMessage"
type="textarea"
:rows="3"
placeholder="请输入医生通知内容"
resize="none"
/>
<el-button
type="primary"
@click="sendDoctorNotification"
:disabled="!doctorMessage.trim()"
class="doctor-send-btn"
>
<span>发送</span>
<span>通知</span>
</el-button>
</div>
2025-06-09 15:39:06 +08:00
</el-card>
</div>
</div>
</el-dialog>
</template>
<script>
2025-06-10 14:20:09 +08:00
import { DeviceuserApi } from '@/api/deviceuser'
import { EcgdataApi } from '@/api/ecgdata'
2025-06-17 16:24:43 +08:00
import { PersonApi } from '@/api/person'
import { useMessage } from '@/hooks/web/useMessage'
import { DoctornoticeApi } from '@/api/doctornotice'
import { getUserProfile } from '@/api/system/user/profile'
2025-06-10 14:20:09 +08:00
2025-06-09 15:39:06 +08:00
export default {
name: 'ECGDatas',
2025-06-17 16:24:43 +08:00
setup() {
const message = useMessage()
return { message }
},
2025-06-09 15:39:06 +08:00
data() {
return {
searchQuery: '',
activePerson: '',
selectedPerson: null,
personList: [], // 这里需要从后端获取人员列表
hasData: false,
dialogVisible: false,
currentDeviceId: '',
2025-06-10 14:20:09 +08:00
currentDeviceName: '',
selectedPersonData: null, // 新增:存储选中人员的心电数据
activeTab: 'basic', // 新增:当前激活的标签页
2025-06-16 16:49:57 +08:00
doctorMessage: '', // 新增:医生通知内容
2025-06-17 16:24:43 +08:00
userProfile: null, // 新增:存储当前登录用户信息
2025-06-10 14:20:09 +08:00
basicFields: [
{ key: 'heartrate', label: '心率(次/分)' },
{ key: 'rhythm', label: '心律类型' },
{ key: 'printerval', label: 'PR间期(ms)' },
{ key: 'qrsduration', label: 'QRS时限(ms)' },
{ key: 'qtinterval', label: 'QT间期(ms)' },
{ key: 'qtcinterval', label: 'QTc间期(ms)' },
{ key: 'paxis', label: 'P电轴(度)' },
{ key: 'qrsaxis', label: 'QRS电轴(度)' },
{ key: 'taxis', label: 'T电轴(度)' },
{ key: 'rv5', label: 'RV5电压(mV)' },
{ key: 'sv1', label: 'SV1电压(mV)' },
{ key: 'rv5sv1', label: 'RV5+SV1电压(mV)' },
{ key: 'stsegment', label: 'ST段改变' },
{ key: 'twave', label: 'T波改变' }
],
ecgFields: [ // 修改:心电图字段映射
{ key: 'paxis', label: 'P电轴(度)' },
{ key: 'qrsaxis', label: 'QRS电轴(度)' },
{ key: 'taxis', label: 'T电轴(度)' },
{ key: 'rv5', label: 'RV5电压(mV)' },
{ key: 'sv1', label: 'SV1电压(mV)' },
{ key: 'rv5sv1', label: 'RV5+SV1电压(mV)' },
{ key: 'stsegment', label: 'ST段改变' },
{ key: 'twave', label: 'T波改变' },
{ key: 'diagnosis', label: '心电图诊断' }
],
timeList: [], // 新增:时间列表
activeTime: '', // 新增:当前激活的时间
dateFilter: null, // 新增:日期筛选值
2025-06-09 15:39:06 +08:00
}
},
computed: {
filteredPersonList() {
if (!this.searchQuery) return this.personList
return this.personList.filter(person =>
2025-06-10 14:20:09 +08:00
person.username.toLowerCase().includes(this.searchQuery.toLowerCase())
2025-06-09 15:39:06 +08:00
)
2025-06-10 14:20:09 +08:00
},
// 新增:根据日期筛选时间列表
filteredTimeList() {
if (!this.dateFilter) return this.timeList
return this.timeList.filter(time => {
const timeDate = new Date(time.collecttime).toISOString().split('T')[0]
return timeDate === this.dateFilter
})
2025-06-09 15:39:06 +08:00
}
},
methods: {
// 打开组件
2025-06-17 16:24:43 +08:00
async open(deviceId, deviceName) {
2025-06-10 14:20:09 +08:00
// 重置所有数据
this.searchQuery = ''
this.activePerson = ''
this.selectedPerson = null
this.personList = []
this.hasData = false
this.selectedPersonData = null
this.activeTab = 'basic'
this.activeTime = ''
this.timeList = []
this.dateFilter = null // 重置日期筛选
2025-06-16 16:49:57 +08:00
this.doctorMessage = '' // 重置医生通知内容
2025-06-10 14:20:09 +08:00
// 打开对话框并设置设备信息
2025-06-09 15:39:06 +08:00
this.dialogVisible = true
this.currentDeviceId = deviceId
this.currentDeviceName = deviceName
2025-06-17 16:24:43 +08:00
// 获取当前登录用户信息
this.userProfile = await getUserProfile()
2025-06-09 15:39:06 +08:00
this.loadPersonList()
},
// 加载人员列表
async loadPersonList() {
try {
2025-06-10 14:20:09 +08:00
const response = await DeviceuserApi.getDeviceuserByDeviceId(this.currentDeviceId)
this.personList = response
2025-06-09 15:39:06 +08:00
} catch (error) {
console.error('获取人员列表失败:', error)
this.$message.error('获取人员列表失败')
}
},
handlePersonSelect(index) {
this.activePerson = index
2025-06-10 14:20:09 +08:00
this.selectedPerson = this.personList.find(person => person.id.toString() === index)
// 重置时间列表
this.timeList = []
2025-06-09 15:39:06 +08:00
this.loadPersonData()
2025-06-10 14:20:09 +08:00
// 重置数据展示状态
this.selectedPersonData = null
this.hasData = false
this.activeTime = ''
2025-06-09 15:39:06 +08:00
},
async loadPersonData() {
try {
2025-06-10 14:20:09 +08:00
const response = await EcgdataApi.getEcgdataByUserId(this.selectedPerson.userid)
this.timeList = Array.isArray(response) ? response : [response]
2025-06-09 15:39:06 +08:00
} catch (error) {
console.error('获取心电数据失败:', error)
this.$message.error('获取数据失败')
}
2025-06-10 14:20:09 +08:00
},
async handleTimeSelect(index) {
this.activeTime = index
const selectedTime = this.timeList[parseInt(index)]
try {
const response = await EcgdataApi.getEcgdataByTime(selectedTime.collecttime, this.selectedPerson.userid)
this.selectedPersonData = response
this.hasData = !!this.selectedPersonData
} catch (error) {
console.error('获取指定时间的心电数据失败:', error)
this.$message.error('获取数据失败')
}
},
formatTime(timestamp) {
const date = new Date(timestamp)
return date.toLocaleString()
},
// 新增:处理日期筛选
handleDateFilter() {
this.activeTime = ''
this.selectedPersonData = null
this.hasData = false
},
2025-06-16 16:49:57 +08:00
// 新增:发送医生通知方法
async sendDoctorNotification() {
2025-06-17 16:24:43 +08:00
if (!this.selectedPerson) {
this.message.warning('请先选择人员')
return
}
2025-06-16 16:49:57 +08:00
if (!this.doctorMessage.trim()) {
2025-06-17 16:24:43 +08:00
this.message.warning('请输入通知内容')
2025-06-16 16:49:57 +08:00
return
}
2025-06-17 16:24:43 +08:00
2025-06-16 16:49:57 +08:00
try {
2025-06-17 16:24:43 +08:00
// 检查用户是否为会员
const userResponse = await PersonApi.getPerson(this.selectedPerson.userid)
if (!userResponse || userResponse.isvip === 0) {
this.message.warning('该用户不是会员,无法发送通知')
return
}
// 构建通知数据
const noticeData = {
id: 0, // 新增时后端会自动生成
deviceid: this.currentDeviceId,
userid: this.selectedPerson.userid,
username: this.selectedPerson.username,
orgid: this.userProfile.dept.orgid,
2025-06-17 16:24:43 +08:00
doctorid: this.userProfile.id,
datatime: new Date().getTime(),
noticetype: 2, // 建议提醒
noticetitle: '医生通知',
noticecontent: this.doctorMessage,
noticelevel: 1, // 普通级别
readstatus: 0, // 未读
readtime: null,
createtime: new Date().getTime(),
updatetime: new Date().getTime(),
createby: this.userProfile.nickname,
updateby: this.userProfile.nickname,
isdeleted: 0
}
// 调用创建通知接口
await DoctornoticeApi.createDoctornotice(noticeData)
this.message.success('通知发送成功')
2025-06-16 16:49:57 +08:00
this.doctorMessage = '' // 清空输入框
} catch (error) {
console.error('发送通知失败:', error)
2025-06-17 16:24:43 +08:00
this.message.error('发送通知失败')
2025-06-16 16:49:57 +08:00
}
},
2025-06-09 15:39:06 +08:00
}
}
</script>
<style scoped>
.ecg-dialog :deep(.el-dialog) {
margin-top: 5vh !important;
}
.ecg-dialog :deep(.el-dialog__body) {
padding: 0;
height: calc(85vh - 100px);
}
.ecg-container {
display: flex;
height: 100%;
gap: 20px;
padding: 15px;
}
.person-list {
width: 280px;
flex-shrink: 0;
}
2025-06-10 14:20:09 +08:00
.time-list {
2025-06-10 14:45:22 +08:00
width: 200px;
2025-06-10 14:20:09 +08:00
flex-shrink: 0;
}
.time-list :deep(.el-card__body) {
padding: 0;
height: 100%;
display: flex;
flex-direction: column;
}
.time-list-header {
padding: 15px;
border-bottom: 1px solid #EBEEF5;
}
.header-title {
font-weight: 500;
margin-bottom: 10px;
}
.time-list :deep(.el-scrollbar__wrap) {
overflow-x: hidden;
}
.time-list :deep(.el-menu) {
border-right: none;
}
.time-list :deep(.el-menu-item) {
height: 40px;
line-height: 40px;
}
2025-06-09 15:39:06 +08:00
.data-display {
flex: 1;
2025-06-16 16:49:57 +08:00
position: relative;
2025-06-09 15:39:06 +08:00
}
.box-card {
height: 100%;
2025-06-16 16:49:57 +08:00
display: flex;
flex-direction: column;
2025-06-09 15:39:06 +08:00
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 15px;
}
.no-selection,
.no-data {
display: flex;
justify-content: center;
align-items: center;
height: 200px;
color: #909399;
font-size: 16px;
}
.ecg-data-content {
min-height: 200px;
2025-06-16 16:49:57 +08:00
flex: 1;
overflow-y: auto;
padding-bottom: 120px;
2025-06-09 15:39:06 +08:00
}
.el-scrollbar {
height: calc(100vh - 400px) !important;
}
2025-06-10 14:20:09 +08:00
.ecg-fields-container {
padding: 20px;
}
.ecg-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
padding: 10px;
}
.ecg-grid-item {
background-color: #f5f7fa;
border-radius: 8px;
padding: 12px;
transition: all 0.3s ease;
2025-06-16 16:49:57 +08:00
min-width: 0;
2025-06-10 14:20:09 +08:00
}
.ecg-grid-item:hover {
transform: translateY(-2px);
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.ecg-field-content {
display: flex;
flex-direction: column;
gap: 6px;
}
.ecg-label {
color: #606266;
font-size: 13px;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.ecg-value {
color: #303133;
font-size: 15px;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.ecg-tabs {
padding: 0 20px;
}
.ecg-tabs :deep(.el-tabs__header) {
margin-bottom: 20px;
}
.ecg-tabs :deep(.el-tabs__nav-wrap::after) {
height: 1px;
}
.ecg-image-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 300px;
}
.ecg-image {
max-width: 100%;
max-height: 400px;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.08);
}
.no-ecg-image {
color: #909399;
font-size: 18px;
}
.no-time-data {
padding: 20px;
text-align: center;
color: #909399;
font-size: 14px;
}
2025-06-16 16:49:57 +08:00
.doctor-notification {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 20px;
background-color: #fff;
display: flex;
gap: 15px;
align-items: flex-start;
z-index: 1;
margin-top: 20px;
}
.doctor-notification :deep(.el-textarea__inner) {
flex: 1;
height: 80px !important;
}
.doctor-send-btn {
height: 80px !important;
min-width: 60px;
white-space: pre-line;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
line-height: 1.1;
padding: 0 16px;
}
2025-06-09 15:39:06 +08:00
</style>