Files
akmon/pages/ec/caregiver/elder-details.uvue
2026-01-20 08:04:15 +08:00

754 lines
18 KiB
Plaintext

<template>
<scroll-view class="elder-details-container">
<!-- Header with Elder Basic Info -->
<view class="elder-header">
<view class="elder-avatar-section">
<image class="elder-avatar" :src="elderInfo.avatar || '/static/default-avatar.png'" mode="aspectFill"></image>
<view class="elder-status-badge" :class="getStatusClass(elderInfo.health_status)">
<text class="status-text">{{ getHealthStatusText(elderInfo.health_status) }}</text>
</view>
</view>
<view class="elder-basic-info">
<text class="elder-name">{{ elderInfo.name }}</text>
<text class="elder-details">{{ elderInfo.age }}岁 | {{ elderInfo.gender === 'male' ? '男' : '女' }}</text>
<text class="elder-room">房间: {{ elderInfo.room_number }}</text>
<text class="care-level">护理等级: {{ getCareLevelText(elderInfo.care_level) }}</text>
</view>
<view class="action-buttons">
<button class="btn-edit" @click="editElder">编辑</button>
<button class="btn-record" @click="recordVitals">记录体征</button>
</view>
</view>
<!-- Quick Vital Signs -->
<view class="section-card">
<view class="section-header">
<text class="section-title">生命体征</text>
<text class="last-update">最后更新: {{ formatTime(latestVitals.recorded_at) }}</text>
</view>
<view class="vitals-grid">
<view class="vital-item">
<text class="vital-label">血压</text>
<text class="vital-value" :class="getVitalStatusClass('blood_pressure', latestVitals.blood_pressure)">
{{ latestVitals.blood_pressure || '--' }}
</text>
</view>
<view class="vital-item">
<text class="vital-label">心率</text>
<text class="vital-value" :class="getVitalStatusClass('heart_rate', latestVitals.heart_rate)">
{{ latestVitals.heart_rate ? latestVitals.heart_rate + ' bpm' : '--' }}
</text>
</view>
<view class="vital-item">
<text class="vital-label">体温</text>
<text class="vital-value" :class="getVitalStatusClass('temperature', latestVitals.temperature)">
{{ latestVitals.temperature ? latestVitals.temperature + '°C' : '--' }}
</text>
</view>
<view class="vital-item">
<text class="vital-label">血氧</text>
<text class="vital-value" :class="getVitalStatusClass('oxygen_saturation', latestVitals.oxygen_saturation)">
{{ latestVitals.oxygen_saturation ? latestVitals.oxygen_saturation + '%' : '--' }}
</text>
</view>
</view>
</view>
<!-- Today's Tasks -->
<view class="section-card">
<view class="section-header">
<text class="section-title">今日护理任务</text>
<text class="task-count">{{ todayTasks.length }} 项任务</text>
</view>
<view v-if="todayTasks.length === 0" class="empty-state">
<text class="empty-text">今日暂无护理任务</text>
</view>
<view v-else class="tasks-list">
<view v-for="task in todayTasks" :key="task.id" class="task-item" @click="completeTask(task)">
<view class="task-info">
<text class="task-title">{{ task.title }}</text>
<text class="task-time">{{ formatTime(task.scheduled_time) }}</text>
</view>
<view class="task-status" :class="getTaskStatusClass(task.status)">
<text class="status-text">{{ getTaskStatusText(task.status) }}</text>
</view>
</view>
</view>
</view>
<!-- Health Records -->
<view class="section-card">
<view class="section-header">
<text class="section-title">健康记录</text>
<text class="view-all" @click="viewAllRecords">查看全部</text>
</view>
<view v-if="healthRecords.length === 0" class="empty-state">
<text class="empty-text">暂无健康记录</text>
</view>
<view v-else class="records-list">
<view v-for="record in healthRecords.slice(0, 5)" :key="record.id" class="record-item">
<view class="record-info">
<text class="record-type">{{ getRecordTypeText(record.record_type) }}</text>
<text class="record-content">{{ record.content }}</text>
<text class="record-time">{{ formatTime(record.recorded_at) }}</text>
</view>
<view class="record-priority" :class="getPriorityClass(record.priority)">
<text class="priority-text">{{ getPriorityText(record.priority) }}</text>
</view>
</view>
</view>
</view>
<!-- Medications -->
<view class="section-card">
<view class="section-header">
<text class="section-title">用药信息</text>
<text class="medication-count">{{ medications.length }} 种药物</text>
</view>
<view v-if="medications.length === 0" class="empty-state">
<text class="empty-text">暂无用药记录</text>
</view>
<view v-else class="medications-list">
<view v-for="medication in medications" :key="medication.id" class="medication-item">
<view class="medication-info">
<text class="medication-name">{{ medication.medication_name }}</text>
<text class="medication-dosage">{{ medication.dosage }} | {{ medication.frequency }}</text>
<text class="medication-time">下次用药: {{ formatTime(medication.next_dose_time) }}</text>
</view>
<view class="medication-status" :class="getMedicationStatusClass(medication.status)">
<text class="status-text">{{ getMedicationStatusText(medication.status) }}</text>
</view>
</view>
</view>
</view>
<!-- Emergency Contact -->
<view class="section-card">
<view class="section-header">
<text class="section-title">紧急联系人</text>
</view>
<view class="contact-info">
<view class="contact-item">
<text class="contact-label">姓名:</text>
<text class="contact-value">{{ elderInfo.emergency_contact_name || '--' }}</text>
</view>
<view class="contact-item">
<text class="contact-label">关系:</text>
<text class="contact-value">{{ elderInfo.emergency_contact_relationship || '--' }}</text>
</view>
<view class="contact-item">
<text class="contact-label">电话:</text>
<text class="contact-value phone-number" @click="callEmergencyContact">
{{ elderInfo.emergency_contact_phone || '--' }}
</text>
</view>
</view>
</view>
<!-- Quick Actions -->
<view class="quick-actions">
<button class="action-btn emergency" @click="reportEmergency">
<text class="btn-icon">🚨</text>
<text class="btn-text">紧急报告</text>
</button>
<button class="action-btn medication" @click="recordMedication">
<text class="btn-icon">💊</text>
<text class="btn-text">用药记录</text>
</button>
<button class="action-btn activity" @click="recordActivity">
<text class="btn-icon">🏃</text>
<text class="btn-text">活动记录</text>
</button>
</view>
</scroll-view>
</template>
<script lang="uts">
import { ElderInfo, HealthRecord, Medication, CareTask, VitalSigns } from '../types.uts'
export default {
data() {
return {
elderId: '',
elderInfo: {} as ElderInfo,
latestVitals: {} as VitalSigns,
todayTasks: [] as CareTask[],
healthRecords: [] as HealthRecord[],
medications: [] as Medication[]
}
},
onLoad(options) {
if (options.id) {
this.elderId = options.id
this.loadElderDetails()
}
},
methods: {
async loadElderDetails() {
try {
await Promise.all([
this.loadElderInfo(),
this.loadVitalSigns(),
this.loadTodayTasks(),
this.loadHealthRecords(),
this.loadMedications()
])
} catch (error) {
console.error('加载老人详情失败:', error)
uni.showToast({
title: '加载失败',
icon: 'error'
})
}
},
async loadElderInfo() {
const result = await supa.executeAs('elder_by_id', {
elder_id: this.elderId
})
if (result.success && result.data.length > 0) {
this.elderInfo = result.data[0] as ElderInfo
}
},
async loadVitalSigns() {
const result = await supa.executeAs('latest_vital_signs', {
elder_id: this.elderId
})
if (result.success && result.data.length > 0) {
this.latestVitals = result.data[0] as VitalSigns
}
},
async loadTodayTasks() {
const today = new Date().toISOString().split('T')[0]
const result = await supa.executeAs('elder_tasks_by_date', {
elder_id: this.elderId,
date: today
})
if (result.success) {
this.todayTasks = result.data as CareTask[]
}
},
async loadHealthRecords() {
const result = await supa.executeAs('elder_health_records', {
elder_id: this.elderId,
limit: 10
})
if (result.success) {
this.healthRecords = result.data as HealthRecord[]
}
},
async loadMedications() {
const result = await supa.executeAs('elder_medications', {
elder_id: this.elderId
})
if (result.success) {
this.medications = result.data as Medication[]
}
},
getStatusClass(status: string): string {
const statusMap = {
'good': 'status-good',
'fair': 'status-fair',
'poor': 'status-poor',
'critical': 'status-critical'
}
return statusMap[status] || 'status-good'
},
getHealthStatusText(status: string): string {
const statusMap = {
'good': '良好',
'fair': '一般',
'poor': '较差',
'critical': '危重'
}
return statusMap[status] || '未知'
},
getCareLevelText(level: string): string {
const levelMap = {
'level1': '一级护理',
'level2': '二级护理',
'level3': '三级护理',
'special': '特级护理'
}
return levelMap[level] || '未设置'
},
getVitalStatusClass(type: string, value: string): string {
// 根据生命体征类型和值判断状态
if (!value) return 'vital-normal'
// 这里可以根据具体的医学标准来判断
return 'vital-normal'
},
getTaskStatusClass(status: string): string {
const statusMap = {
'pending': 'task-pending',
'in_progress': 'task-progress',
'completed': 'task-completed',
'overdue': 'task-overdue'
}
return statusMap[status] || 'task-pending'
},
getTaskStatusText(status: string): string {
const statusMap = {
'pending': '待执行',
'in_progress': '进行中',
'completed': '已完成',
'overdue': '已逾期'
}
return statusMap[status] || '未知'
},
getRecordTypeText(type: string): string {
const typeMap = {
'vital_signs': '生命体征',
'medication': '用药记录',
'activity': '活动记录',
'incident': '事件记录',
'observation': '观察记录'
}
return typeMap[type] || type
},
getPriorityClass(priority: string): string {
const priorityMap = {
'low': 'priority-low',
'normal': 'priority-normal',
'high': 'priority-high',
'urgent': 'priority-urgent'
}
return priorityMap[priority] || 'priority-normal'
},
getPriorityText(priority: string): string {
const priorityMap = {
'low': '低',
'normal': '普通',
'high': '高',
'urgent': '紧急'
}
return priorityMap[priority] || '普通'
},
getMedicationStatusClass(status: string): string {
const statusMap = {
'active': 'med-active',
'paused': 'med-paused',
'completed': 'med-completed'
}
return statusMap[status] || 'med-active'
},
getMedicationStatusText(status: string): string {
const statusMap = {
'active': '正在服用',
'paused': '暂停',
'completed': '已完成'
}
return statusMap[status] || '未知'
},
formatTime(timestamp: string): string {
if (!timestamp) return '--'
const date = new Date(timestamp)
const now = new Date()
const diff = now.getTime() - date.getTime()
const minutes = Math.floor(diff / (1000 * 60))
const hours = Math.floor(minutes / 60)
const days = Math.floor(hours / 24)
if (days > 0) {
return `${days}天前`
} else if (hours > 0) {
return `${hours}小时前`
} else if (minutes > 0) {
return `${minutes}分钟前`
} else {
return '刚刚'
}
},
editElder() {
uni.navigateTo({
url: `/pages/ec/admin/elder-form?id=${this.elderId}`
})
},
recordVitals() {
uni.navigateTo({
url: `/pages/ec/health/vital-signs-form?elder_id=${this.elderId}`
})
},
async completeTask(task: CareTask) {
try {
const result = await supa.executeAs('update_task_status', {
task_id: task.id,
status: 'completed',
completed_at: new Date().toISOString()
})
if (result.success) {
uni.showToast({
title: '任务已完成',
icon: 'success'
})
this.loadTodayTasks()
}
} catch (error) {
console.error('完成任务失败:', error)
uni.showToast({
title: '操作失败',
icon: 'error'
})
}
},
viewAllRecords() {
uni.navigateTo({
url: `/pages/ec/health/records?elder_id=${this.elderId}`
})
},
callEmergencyContact() {
if (this.elderInfo.emergency_contact_phone) {
uni.makePhoneCall({
phoneNumber: this.elderInfo.emergency_contact_phone
})
}
},
reportEmergency() {
uni.navigateTo({
url: `/pages/ec/incident/report-form?elder_id=${this.elderId}&type=emergency`
})
},
recordMedication() {
uni.navigateTo({
url: `/pages/ec/medication/record-form?elder_id=${this.elderId}`
})
},
recordActivity() {
uni.navigateTo({
url: `/pages/ec/activity/record-form?elder_id=${this.elderId}`
})
}
}
}
</script>
<style lang="scss">
.elder-details-container {
background-color: #f5f5f5;
min-height: 100vh;
}
.elder-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 40rpx 30rpx;
color: white;
display: flex;
align-items: center;
gap: 20rpx;
}
.elder-avatar-section {
position: relative;
.elder-avatar {
width: 120rpx;
height: 120rpx;
border-radius: 60rpx;
border: 4rpx solid rgba(255,255,255,0.3);
}
.elder-status-badge {
position: absolute;
bottom: 0;
right: 0;
padding: 5rpx 10rpx;
border-radius: 20rpx;
font-size: 20rpx;
&.status-good {
background-color: #4CAF50;
}
&.status-fair {
background-color: #FF9800;
}
&.status-poor {
background-color: #f44336;
}
&.status-critical {
background-color: #9C27B0;
}
}
}
.elder-basic-info {
flex: 1;
.elder-name {
font-size: 36rpx;
font-weight: bold;
margin-bottom: 10rpx;
}
.elder-details, .elder-room, .care-level {
font-size: 26rpx;
opacity: 0.9;
margin-bottom: 5rpx;
}
}
.action-buttons {
display: flex;
flex-direction: column;
gap: 10rpx;
button {
padding: 15rpx 20rpx;
border-radius: 10rpx;
font-size: 24rpx;
background-color: rgba(255,255,255,0.2);
color: white;
border: 2rpx solid rgba(255,255,255,0.3);
}
}
.section-card {
background: white;
margin: 20rpx 30rpx;
border-radius: 20rpx;
padding: 30rpx;
box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.1);
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30rpx;
padding-bottom: 20rpx;
border-bottom: 2rpx solid #f0f0f0;
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.last-update, .task-count, .medication-count {
font-size: 24rpx;
color: #999;
}
.view-all {
font-size: 26rpx;
color: #667eea;
}
}
.vitals-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20rpx;
}
.vital-item {
background-color: #f8f9ff;
padding: 20rpx;
border-radius: 15rpx;
text-align: center;
.vital-label {
font-size: 24rpx;
color: #666;
margin-bottom: 10rpx;
}
.vital-value {
font-size: 28rpx;
font-weight: bold;
&.vital-normal {
color: #4CAF50;
}
&.vital-warning {
color: #FF9800;
}
&.vital-danger {
color: #f44336;
}
}
}
.empty-state {
text-align: center;
padding: 60rpx 0;
.empty-text {
font-size: 28rpx;
color: #999;
}
}
.tasks-list, .records-list, .medications-list {
.task-item, .record-item, .medication-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 0;
border-bottom: 2rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
}
}
.task-info, .record-info, .medication-info {
flex: 1;
.task-title, .record-type, .medication-name {
font-size: 28rpx;
color: #333;
margin-bottom: 5rpx;
}
.task-time, .record-content, .medication-dosage {
font-size: 24rpx;
color: #666;
margin-bottom: 5rpx;
}
.record-time, .medication-time {
font-size: 22rpx;
color: #999;
}
}
.task-status, .record-priority, .medication-status {
padding: 10rpx 15rpx;
border-radius: 20rpx;
font-size: 22rpx;
&.task-pending, &.priority-low, &.med-paused {
background-color: #e3f2fd;
color: #1976d2;
}
&.task-progress, &.priority-normal, &.med-active {
background-color: #e8f5e8;
color: #388e3c;
}
&.task-completed, &.med-completed {
background-color: #f3e5f5;
color: #7b1fa2;
}
&.task-overdue, &.priority-urgent {
background-color: #ffebee;
color: #d32f2f;
}
&.priority-high {
background-color: #fff3e0;
color: #f57c00;
}
}
.contact-info {
.contact-item {
display: flex;
align-items: center;
padding: 15rpx 0;
border-bottom: 2rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.contact-label {
width: 120rpx;
font-size: 26rpx;
color: #666;
}
.contact-value {
flex: 1;
font-size: 28rpx;
color: #333;
&.phone-number {
color: #667eea;
}
}
}
}
.quick-actions {
padding: 30rpx;
display: flex;
gap: 20rpx;
}
.action-btn {
flex: 1;
height: 120rpx;
border-radius: 15rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10rpx;
border: none;
&.emergency {
background: linear-gradient(135deg, #ff6b6b 0%, #ff5252 100%);
color: white;
}
&.medication {
background: linear-gradient(135deg, #4ecdc4 0%, #26a69a 100%);
color: white;
}
&.activity {
background: linear-gradient(135deg, #45b7d1 0%, #2196f3 100%);
color: white;
}
.btn-icon {
font-size: 36rpx;
}
.btn-text {
font-size: 24rpx;
}
}
</style>