fix(mobile): 修复患者详情页 - 完整展示姓名/床位/医嘱/体征/评估数据
This commit is contained in:
@@ -1,31 +1,54 @@
|
||||
<template>
|
||||
<div class="patient-detail">
|
||||
<div class="patient-header">
|
||||
<div class="avatar">{{ patient.name?.charAt(0) }}</div>
|
||||
<div class="info"><div class="name">{{ patient.name }} <span class="bed">{{ patient.bedNo }}床</span></div><div class="diag">{{ patient.diagnosis }}</div></div>
|
||||
<div class="avatar">{{ (patient.patientName || patient.name || '?').charAt(0) }}</div>
|
||||
<div class="info">
|
||||
<div class="name">{{ patient.patientName || patient.name || '未知患者' }}</div>
|
||||
<div class="meta">{{ patient.bedNo || patient.locationName || '' }} | {{ patient.gender || '' }} {{ patient.age ? patient.age + '岁' : '' }}</div>
|
||||
<div class="diag">{{ patient.primaryDiagnosisName || patient.diagnosis || '暂无诊断' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tabs">
|
||||
<div v-for="tab in tabs" :key="tab.key" class="tab" :class="{ active: activeTab === tab.key }" @click="activeTab = tab.key">{{ tab.label }}</div>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div v-if="activeTab === 'orders'">
|
||||
<div v-for="order in orders" :key="order.id" class="order-item">
|
||||
<div class="order-main"><div class="order-name">{{ order.orderName || order.adviceName }}</div><div class="order-dose">{{ order.dosage }} {{ order.frequency }}</div></div>
|
||||
<button v-if="order.status === 'PENDING' || order.executeStatus === '待执行'" class="exec-btn" @click="executeOrder(order)">执行</button>
|
||||
<div v-for="order in orders" :key="order.id || order.adviceId" class="order-item">
|
||||
<div class="order-main">
|
||||
<div class="order-name">{{ order.adviceName || order.orderName || '医嘱' }}</div>
|
||||
<div class="order-dose">{{ order.dosage || '' }} {{ order.frequency || '' }}</div>
|
||||
</div>
|
||||
<button v-if="order.executeStatus === '待执行' || order.status === 'PENDING'" class="exec-btn" @click="executeOrder(order)">执行</button>
|
||||
<span v-else class="done-tag">已执行</span>
|
||||
</div>
|
||||
<div v-if="orders.length === 0" class="empty">暂无医嘱</div>
|
||||
</div>
|
||||
<div v-if="activeTab === 'vitals'">
|
||||
<div class="vital-grid"><div class="vital-item" v-for="v in latestVitals" :key="v.key"><div class="vital-value">{{ v.value || '--' }}</div><div class="vital-label">{{ v.label }}</div></div></div>
|
||||
<button class="action-btn" @click="$router.push(`/mobile/vital-entry/${$route.params.id}`)">录入体征</button>
|
||||
<div class="vital-grid">
|
||||
<div class="vital-item"><div class="vital-value">{{ latestTemp || '--' }}</div><div class="vital-label">体温°C</div></div>
|
||||
<div class="vital-item"><div class="vital-value">{{ latestPulse || '--' }}</div><div class="vital-label">脉搏</div></div>
|
||||
<div class="vital-item"><div class="vital-value">{{ latestBP || '--' }}</div><div class="vital-label">血压</div></div>
|
||||
<div class="vital-item"><div class="vital-value">{{ latestSpo2 || '--' }}</div><div class="vital-label">血氧%</div></div>
|
||||
<div class="vital-item"><div class="vital-value">{{ latestResp || '--' }}</div><div class="vital-label">呼吸</div></div>
|
||||
<div class="vital-item"><div class="vital-value">{{ latestPain || '--' }}</div><div class="vital-label">疼痛</div></div>
|
||||
</div>
|
||||
<button class="action-btn" @click="goVitalEntry">录入体征</button>
|
||||
<div v-if="vitals.length > 0" class="vital-history">
|
||||
<div class="section-title">体征记录</div>
|
||||
<div v-for="v in vitals.slice(0, 5)" :key="v.id" class="vital-record">
|
||||
<span class="vital-time">{{ formatTime(v.recordTime) }}</span>
|
||||
<span>T:{{ v.temperature }} P:{{ v.pulse }}</span>
|
||||
<span>BP:{{ v.bloodPressureHigh }}/{{ v.bloodPressureLow }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="vitals.length === 0" class="empty">暂无体征记录</div>
|
||||
</div>
|
||||
<div v-if="activeTab === 'assessments'">
|
||||
<div v-for="a in assessments" :key="a.id" class="assess-item">
|
||||
<div class="assess-type">{{ a.assessmentType }}</div>
|
||||
<div class="assess-score">评分: {{ a.totalScore }} <span :class="'risk-' + a.riskLevel">{{ a.riskLevel }}</span></div>
|
||||
<div class="assess-type">{{ a.assessmentType || '护理评估' }}</div>
|
||||
<div class="assess-score">评分: {{ a.totalScore || '--' }} <span :class="'risk-' + (a.riskLevel || 'LOW')">{{ a.riskLevel || '未知' }}</span></div>
|
||||
</div>
|
||||
<button class="action-btn" @click="$router.push(`/mobile/assessment/${$route.params.id}`)">新建评估</button>
|
||||
<button class="action-btn" @click="goAssessment">新建评估</button>
|
||||
<div v-if="assessments.length === 0" class="empty">暂无评估记录</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -33,43 +56,60 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { nursingApi } from '../api'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const patient = ref({})
|
||||
const orders = ref([])
|
||||
const latestVitals = ref([])
|
||||
const vitals = ref([])
|
||||
const assessments = ref([])
|
||||
const activeTab = ref('orders')
|
||||
const tabs = [{ key: 'orders', label: '医嘱' }, { key: 'vitals', label: '体征' }, { key: 'assessments', label: '评估' }]
|
||||
|
||||
const latestTemp = computed(() => vitals.value[0]?.temperature || '--')
|
||||
const latestPulse = computed(() => vitals.value[0]?.pulse || '--')
|
||||
const latestBP = computed(() => vitals.value[0] ? `${vitals.value[0].bloodPressureHigh}/${vitals.value[0].bloodPressureLow}` : '--')
|
||||
const latestSpo2 = computed(() => vitals.value[0]?.spo2 || '--')
|
||||
const latestResp = computed(() => vitals.value[0]?.respiration || '--')
|
||||
const latestPain = computed(() => vitals.value[0]?.painScore || '--')
|
||||
|
||||
const formatTime = (t) => { if (!t) return ''; const d = new Date(t); return `${d.getMonth()+1}/${d.getDate()} ${d.getHours()}:${String(d.getMinutes()).padStart(2,'0')}` }
|
||||
|
||||
onMounted(async () => {
|
||||
const id = route.params.id
|
||||
try {
|
||||
const [pRes, oRes, vRes, aRes] = await Promise.all([
|
||||
nursingApi.getPatientInfo(id), nursingApi.getOrders(id),
|
||||
nursingApi.getVitalSigns(id), nursingApi.getAssessments(id)
|
||||
const [pRes, oRes, vRes, aRes] = await Promise.allSettled([
|
||||
nursingApi.getPatientInfo(id),
|
||||
nursingApi.getOrders(id),
|
||||
nursingApi.getVitalSigns(id),
|
||||
nursingApi.getAssessments(id)
|
||||
])
|
||||
patient.value = pRes.data || {}; orders.value = oRes.data?.records || oRes.data || []; latestVitals.value = vRes.data?.records || vRes.data || []; assessments.value = aRes.data?.records || aRes.data || []
|
||||
} catch (e) { ElMessage.error('加载失败') }
|
||||
if (pRes.status === 'fulfilled') patient.value = pRes.value?.data || {}
|
||||
if (oRes.status === 'fulfilled') orders.value = oRes.value?.data?.records || oRes.value?.data || []
|
||||
if (vRes.status === 'fulfilled') vitals.value = vRes.value?.data?.records || vRes.value?.data || []
|
||||
if (aRes.status === 'fulfilled') assessments.value = aRes.value?.data?.records || aRes.value?.data || []
|
||||
} catch (e) { console.error('加载失败:', e) }
|
||||
})
|
||||
|
||||
const executeOrder = async (order) => {
|
||||
try { await nursingApi.completeTask(order.id, { result: '执行完成' }); order.status = 'COMPLETED'; ElMessage.success('医嘱已执行') } catch (e) { ElMessage.error('执行失败') }
|
||||
try { await nursingApi.completeTask(order.id || order.adviceId, { result: '执行完成' }); ElMessage.success('医嘱已执行'); order.executeStatus = '已执行' } catch (e) { ElMessage.error('执行失败') }
|
||||
}
|
||||
const goVitalEntry = () => router.push(`/mobile/vital-entry/${route.params.id}`)
|
||||
const goAssessment = () => router.push(`/mobile/assessment/${route.params.id}`)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.patient-header { background: linear-gradient(135deg, #1890ff, #096dd9); color: #fff; padding: 16px; display: flex; align-items: center; gap: 12px; }
|
||||
.avatar { width: 48px; height: 48px; border-radius: 50%; background: rgba(255,255,255,0.2); display: flex; align-items: center; justify-content: center; font-size: 20px; }
|
||||
.avatar { width: 48px; height: 48px; border-radius: 50%; background: rgba(255,255,255,0.2); display: flex; align-items: center; justify-content: center; font-size: 20px; flex-shrink: 0; }
|
||||
.name { font-size: 18px; font-weight: 600; }
|
||||
.bed { font-size: 14px; opacity: 0.8; }
|
||||
.diag { font-size: 13px; opacity: 0.8; }
|
||||
.meta { font-size: 13px; opacity: 0.8; margin-top: 2px; }
|
||||
.diag { font-size: 12px; opacity: 0.8; margin-top: 2px; }
|
||||
.tabs { display: flex; background: #fff; border-bottom: 1px solid #eee; position: sticky; top: 48px; z-index: 5; }
|
||||
.tab { flex: 1; text-align: center; padding: 12px; font-size: 14px; color: #666; }
|
||||
.tab { flex: 1; text-align: center; padding: 12px; font-size: 14px; color: #666; cursor: pointer; }
|
||||
.tab.active { color: #1890ff; border-bottom: 2px solid #1890ff; font-weight: 600; }
|
||||
.tab-content { padding: 12px; }
|
||||
.order-item { background: #fff; border-radius: 8px; padding: 12px; margin-bottom: 8px; display: flex; justify-content: space-between; align-items: center; }
|
||||
@@ -82,8 +122,12 @@ const executeOrder = async (order) => {
|
||||
.vital-value { font-size: 18px; font-weight: 600; color: #1890ff; }
|
||||
.vital-label { font-size: 11px; color: #999; margin-top: 4px; }
|
||||
.action-btn { width: 100%; padding: 12px; background: #1890ff; color: #fff; border: none; border-radius: 8px; font-size: 15px; margin-top: 12px; }
|
||||
.vital-history { margin-top: 12px; }
|
||||
.section-title { font-size: 14px; font-weight: 600; margin-bottom: 8px; }
|
||||
.vital-record { font-size: 12px; color: #666; padding: 6px 0; border-bottom: 1px solid #f5f5f5; display: flex; gap: 8px; }
|
||||
.vital-time { color: #999; min-width: 80px; }
|
||||
.assess-item { background: #fff; border-radius: 8px; padding: 12px; margin-bottom: 8px; display: flex; justify-content: space-between; }
|
||||
.assess-type { font-weight: 600; }
|
||||
.risk-HIGH { color: #f5222d; } .risk-MEDIUM { color: #fa8c16; } .risk-LOW { color: #52c41a; }
|
||||
.risk-HIGH, .risk-高 { color: #f5222d; } .risk-MEDIUM, .risk-中 { color: #fa8c16; } .risk-LOW, .risk-低 { color: #52c41a; }
|
||||
.empty { text-align: center; padding: 20px; color: #999; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user