fix(mobile): 修复患者详情页 - 完整展示姓名/床位/医嘱/体征/评估数据

This commit is contained in:
2026-06-20 15:28:03 +08:00
parent 5050366f50
commit 37a0c1885e

View File

@@ -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>