|
|
|
|
@@ -0,0 +1,315 @@
|
|
|
|
|
<template>
|
|
|
|
|
<div class="intraop-vital-sign-container">
|
|
|
|
|
<el-card v-loading="loading">
|
|
|
|
|
<template #header>
|
|
|
|
|
<div class="card-header">
|
|
|
|
|
<span>术中生命体征监测</span>
|
|
|
|
|
<div class="header-actions">
|
|
|
|
|
<el-select v-model="currentRecordId" placeholder="选择麻醉记录" style="width: 220px" @change="loadTimeline">
|
|
|
|
|
<el-option v-for="item in recordOptions" :key="item.id" :label="item.id + ' - ' + (item.patientName || '')" :value="item.id" />
|
|
|
|
|
</el-select>
|
|
|
|
|
<el-button type="primary" size="small" :disabled="!currentRecordId" @click="handleRecord">录入体征</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<div v-if="!currentRecordId" class="empty-tip">
|
|
|
|
|
<el-empty description="请先选择一条麻醉记录" />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div v-else>
|
|
|
|
|
<el-table :data="timelineData" border style="width: 100%" size="small">
|
|
|
|
|
<el-table-column type="index" label="序号" width="55" align="center" />
|
|
|
|
|
<el-table-column prop="recordTime" label="记录时间" width="170" />
|
|
|
|
|
<el-table-column prop="heartRate" label="心率(bpm)" width="95" align="center" />
|
|
|
|
|
<el-table-column label="血压(mmHg)" width="120" align="center">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
{{ scope.row.bloodPressureSys }}/{{ scope.row.bloodPressureDia }}
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column prop="spo2" label="SpO2(%)" width="90" align="center" />
|
|
|
|
|
<el-table-column prop="etco2" label="EtCO2(mmHg)" width="105" align="center" />
|
|
|
|
|
<el-table-column prop="temperature" label="体温(℃)" width="90" align="center" />
|
|
|
|
|
<el-table-column prop="respiratoryRate" label="呼吸(次/分)" width="100" align="center" />
|
|
|
|
|
<el-table-column prop="remark" label="备注" min-width="120" show-overflow-tooltip />
|
|
|
|
|
</el-table>
|
|
|
|
|
|
|
|
|
|
<div v-if="timelineData.length > 0" class="trend-section">
|
|
|
|
|
<div class="trend-title">趋势概览</div>
|
|
|
|
|
<div class="trend-cards">
|
|
|
|
|
<div class="trend-card">
|
|
|
|
|
<div class="trend-label">心率</div>
|
|
|
|
|
<div class="trend-range">
|
|
|
|
|
<span class="trend-min">{{ minVal('heartRate') }}</span>
|
|
|
|
|
<span class="trend-sep">~</span>
|
|
|
|
|
<span class="trend-max">{{ maxVal('heartRate') }}</span>
|
|
|
|
|
<span class="trend-unit">bpm</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="trend-card">
|
|
|
|
|
<div class="trend-label">血压</div>
|
|
|
|
|
<div class="trend-range">
|
|
|
|
|
<span class="trend-min">{{ minVal('bloodPressureSys') }}</span>
|
|
|
|
|
<span class="trend-sep">/</span>
|
|
|
|
|
<span class="trend-max">{{ maxVal('bloodPressureDia') }}</span>
|
|
|
|
|
<span class="trend-unit">mmHg</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="trend-card">
|
|
|
|
|
<div class="trend-label">SpO2</div>
|
|
|
|
|
<div class="trend-range">
|
|
|
|
|
<span class="trend-min">{{ minVal('spo2') }}</span>
|
|
|
|
|
<span class="trend-sep">~</span>
|
|
|
|
|
<span class="trend-max">{{ maxVal('spo2') }}</span>
|
|
|
|
|
<span class="trend-unit">%</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="trend-card">
|
|
|
|
|
<div class="trend-label">体温</div>
|
|
|
|
|
<div class="trend-range">
|
|
|
|
|
<span class="trend-min">{{ minVal('temperature') }}</span>
|
|
|
|
|
<span class="trend-sep">~</span>
|
|
|
|
|
<span class="trend-max">{{ maxVal('temperature') }}</span>
|
|
|
|
|
<span class="trend-unit">℃</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="trend-card">
|
|
|
|
|
<div class="trend-label">呼吸</div>
|
|
|
|
|
<div class="trend-range">
|
|
|
|
|
<span class="trend-min">{{ minVal('respiratoryRate') }}</span>
|
|
|
|
|
<span class="trend-sep">~</span>
|
|
|
|
|
<span class="trend-max">{{ maxVal('respiratoryRate') }}</span>
|
|
|
|
|
<span class="trend-unit">次/分</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
|
|
<el-dialog v-model="recordDialogVisible" title="录入术中生命体征" width="520px" destroy-on-close :close-on-click-modal="false">
|
|
|
|
|
<el-form ref="recordFormRef" :model="recordForm" :rules="recordRules" label-width="110px">
|
|
|
|
|
<el-row :gutter="16">
|
|
|
|
|
<el-col :span="12">
|
|
|
|
|
<el-form-item label="心率(bpm)" prop="heartRate">
|
|
|
|
|
<el-input-number v-model="recordForm.heartRate" :min="30" :max="250" style="width: 100%" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="12">
|
|
|
|
|
<el-form-item label="收缩压(mmHg)" prop="bloodPressureSys">
|
|
|
|
|
<el-input-number v-model="recordForm.bloodPressureSys" :min="50" :max="300" style="width: 100%" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="12">
|
|
|
|
|
<el-form-item label="舒张压(mmHg)" prop="bloodPressureDia">
|
|
|
|
|
<el-input-number v-model="recordForm.bloodPressureDia" :min="20" :max="200" style="width: 100%" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="12">
|
|
|
|
|
<el-form-item label="SpO2(%)" prop="spo2">
|
|
|
|
|
<el-input-number v-model="recordForm.spo2" :min="50" :max="100" :step="0.1" :precision="1" style="width: 100%" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="12">
|
|
|
|
|
<el-form-item label="EtCO2(mmHg)">
|
|
|
|
|
<el-input-number v-model="recordForm.etco2" :min="0" :max="100" :step="0.1" :precision="1" style="width: 100%" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="12">
|
|
|
|
|
<el-form-item label="体温(℃)" prop="temperature">
|
|
|
|
|
<el-input-number v-model="recordForm.temperature" :min="34" :max="42" :step="0.1" :precision="1" style="width: 100%" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="12">
|
|
|
|
|
<el-form-item label="呼吸(次/分)" prop="respiratoryRate">
|
|
|
|
|
<el-input-number v-model="recordForm.respiratoryRate" :min="5" :max="60" style="width: 100%" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="12">
|
|
|
|
|
<el-form-item label="记录时间" prop="recordTime">
|
|
|
|
|
<el-date-picker v-model="recordForm.recordTime" type="datetime" placeholder="选择时间" value-format="YYYY-MM-DD HH:mm:ss" style="width: 100%" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="24">
|
|
|
|
|
<el-form-item label="备注">
|
|
|
|
|
<el-input v-model="recordForm.remark" type="textarea" :rows="2" placeholder="请输入备注" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-col>
|
|
|
|
|
</el-row>
|
|
|
|
|
</el-form>
|
|
|
|
|
<template #footer>
|
|
|
|
|
<el-button @click="recordDialogVisible = false">取消</el-button>
|
|
|
|
|
<el-button type="primary" :loading="submitLoading" @click="submitRecord">保存</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
import { ref, reactive, onMounted } from 'vue'
|
|
|
|
|
import { ElMessage } from 'element-plus'
|
|
|
|
|
import { getByEncounter, getVitalSignTimeline, recordVitalSign } from '@/api/anesthesia'
|
|
|
|
|
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
const submitLoading = ref(false)
|
|
|
|
|
const currentRecordId = ref(null)
|
|
|
|
|
const recordOptions = ref([])
|
|
|
|
|
const timelineData = ref([])
|
|
|
|
|
const recordDialogVisible = ref(false)
|
|
|
|
|
const recordFormRef = ref(null)
|
|
|
|
|
|
|
|
|
|
const recordForm = reactive({
|
|
|
|
|
heartRate: null,
|
|
|
|
|
bloodPressureSys: null,
|
|
|
|
|
bloodPressureDia: null,
|
|
|
|
|
spo2: null,
|
|
|
|
|
etco2: null,
|
|
|
|
|
temperature: null,
|
|
|
|
|
respiratoryRate: null,
|
|
|
|
|
recordTime: '',
|
|
|
|
|
remark: ''
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const recordRules = {
|
|
|
|
|
heartRate: [{ required: true, message: '请输入心率', trigger: 'blur' }],
|
|
|
|
|
bloodPressureSys: [{ required: true, message: '请输入收缩压', trigger: 'blur' }],
|
|
|
|
|
bloodPressureDia: [{ required: true, message: '请输入舒张压', trigger: 'blur' }],
|
|
|
|
|
spo2: [{ required: true, message: '请输入血氧饱和度', trigger: 'blur' }],
|
|
|
|
|
temperature: [{ required: true, message: '请输入体温', trigger: 'blur' }],
|
|
|
|
|
respiratoryRate: [{ required: true, message: '请输入呼吸频率', trigger: 'blur' }],
|
|
|
|
|
recordTime: [{ required: true, message: '请选择记录时间', trigger: 'change' }]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function minVal(field) {
|
|
|
|
|
if (timelineData.value.length === 0) return '-'
|
|
|
|
|
const vals = timelineData.value.map(item => item[field]).filter(v => v != null)
|
|
|
|
|
return vals.length > 0 ? Math.min(...vals) : '-'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function maxVal(field) {
|
|
|
|
|
if (timelineData.value.length === 0) return '-'
|
|
|
|
|
const vals = timelineData.value.map(item => item[field]).filter(v => v != null)
|
|
|
|
|
return vals.length > 0 ? Math.max(...vals) : '-'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function loadRecordOptions() {
|
|
|
|
|
getByEncounter(1).then(res => {
|
|
|
|
|
recordOptions.value = res.data || []
|
|
|
|
|
}).catch(() => {})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function loadTimeline() {
|
|
|
|
|
if (!currentRecordId.value) return
|
|
|
|
|
loading.value = true
|
|
|
|
|
getVitalSignTimeline(currentRecordId.value).then(res => {
|
|
|
|
|
timelineData.value = res.data || []
|
|
|
|
|
}).catch(() => {
|
|
|
|
|
ElMessage.error('查询时间线失败')
|
|
|
|
|
}).finally(() => {
|
|
|
|
|
loading.value = false
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleRecord() {
|
|
|
|
|
recordForm.heartRate = null
|
|
|
|
|
recordForm.bloodPressureSys = null
|
|
|
|
|
recordForm.bloodPressureDia = null
|
|
|
|
|
recordForm.spo2 = null
|
|
|
|
|
recordForm.etco2 = null
|
|
|
|
|
recordForm.temperature = null
|
|
|
|
|
recordForm.respiratoryRate = null
|
|
|
|
|
recordForm.recordTime = ''
|
|
|
|
|
recordForm.remark = ''
|
|
|
|
|
recordDialogVisible.value = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function submitRecord() {
|
|
|
|
|
recordFormRef.value?.validate(valid => {
|
|
|
|
|
if (!valid) return
|
|
|
|
|
submitLoading.value = true
|
|
|
|
|
const payload = {
|
|
|
|
|
...recordForm,
|
|
|
|
|
recordId: currentRecordId.value
|
|
|
|
|
}
|
|
|
|
|
recordVitalSign(payload).then(res => {
|
|
|
|
|
if (res.code === 200) {
|
|
|
|
|
ElMessage.success('生命体征已记录')
|
|
|
|
|
recordDialogVisible.value = false
|
|
|
|
|
loadTimeline()
|
|
|
|
|
} else {
|
|
|
|
|
ElMessage.error(res.msg || '记录失败')
|
|
|
|
|
}
|
|
|
|
|
}).catch(() => {
|
|
|
|
|
ElMessage.error('记录失败')
|
|
|
|
|
}).finally(() => {
|
|
|
|
|
submitLoading.value = false
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
loadRecordOptions()
|
|
|
|
|
})
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.intraop-vital-sign-container {
|
|
|
|
|
padding: 12px;
|
|
|
|
|
}
|
|
|
|
|
.card-header {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
}
|
|
|
|
|
.header-actions {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
align-items: center;
|
|
|
|
|
}
|
|
|
|
|
.empty-tip {
|
|
|
|
|
padding: 40px 0;
|
|
|
|
|
}
|
|
|
|
|
.trend-section {
|
|
|
|
|
margin-top: 20px;
|
|
|
|
|
border-top: 1px solid #ebeef5;
|
|
|
|
|
padding-top: 16px;
|
|
|
|
|
}
|
|
|
|
|
.trend-title {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
color: #303133;
|
|
|
|
|
}
|
|
|
|
|
.trend-cards {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 16px;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
}
|
|
|
|
|
.trend-card {
|
|
|
|
|
flex: 1;
|
|
|
|
|
min-width: 120px;
|
|
|
|
|
background: #f5f7fa;
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
padding: 12px;
|
|
|
|
|
text-align: center;
|
|
|
|
|
}
|
|
|
|
|
.trend-label {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
color: #909399;
|
|
|
|
|
margin-bottom: 6px;
|
|
|
|
|
}
|
|
|
|
|
.trend-range {
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: #303133;
|
|
|
|
|
}
|
|
|
|
|
.trend-sep {
|
|
|
|
|
color: #c0c4cc;
|
|
|
|
|
margin: 0 2px;
|
|
|
|
|
}
|
|
|
|
|
.trend-unit {
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
color: #909399;
|
|
|
|
|
margin-left: 2px;
|
|
|
|
|
}
|
|
|
|
|
</style>
|