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

1011 lines
21 KiB
Plaintext

<template>
<view class="elder-status">
<!-- 老人基本信息卡片 -->
<view class="elder-info-card">
<view class="elder-header">
<view class="elder-avatar">
<image class="avatar-image" :src="elderInfo.profile_picture" mode="aspectFill"
@error="handleAvatarError" v-if="elderInfo.profile_picture" />
<text class="avatar-fallback" v-else>{{ elderInfo.name ? elderInfo.name.charAt(0) : '?' }}</text>
</view>
<view class="elder-basic">
<text class="elder-name">{{ elderInfo.name }}</text>
<text class="elder-info">{{ elderInfo.age }}岁 · {{ elderInfo.gender === 'male' ? '男' : '女' }}</text>
<text class="elder-room">{{ elderInfo.room_number }}房 {{ elderInfo.bed_number }}床</text>
</view>
<view class="elder-status">
<view class="health-indicator" :class="healthStatusClass">
<view class="indicator-dot"></view>
<text class="status-text">{{ healthStatusText }}</text>
</view>
<view class="care-level">
<text class="level-text">{{ careLevelText }}</text>
</view>
</view>
</view>
<view class="elder-details">
<view class="detail-row">
<view class="detail-item">
<text class="detail-label">入住日期:</text>
<text class="detail-value">{{ formatDate(elderInfo.admission_date) }}</text>
</view>
<view class="detail-item">
<text class="detail-label">护理员:</text>
<text class="detail-value">{{ assignedCaregiver }}</text>
</view>
</view>
<view class="detail-row">
<view class="detail-item">
<text class="detail-label">主治医生:</text>
<text class="detail-value">{{ assignedDoctor }}</text>
</view>
<view class="detail-item">
<text class="detail-label">紧急联系人:</text>
<text class="detail-value">{{ elderInfo.emergency_contact }}</text>
</view>
</view>
</view>
</view>
<!-- 健康状态统计 -->
<view class="health-stats-section">
<view class="section-title">
<text class="title-text">健康状态</text>
<button class="view-history-btn" @tap="viewHealthHistory">
<text class="btn-text">查看历史</text>
</button>
</view>
<view class="health-stats-grid">
<view class="health-stat-card" v-for="vital in recentVitals" :key="vital.type">
<text class="stat-icon">{{ getVitalIcon(vital.type) }}</text>
<text class="stat-label">{{ getVitalLabel(vital.type) }}</text>
<text class="stat-value">{{ vital.value }}{{ getVitalUnit(vital.type) }}</text>
<text class="stat-time">{{ formatTime(vital.recorded_time) }}</text>
<view class="stat-trend" :class="vital.trend">
<text class="trend-icon">{{ getTrendIcon(vital.trend) }}</text>
</view>
</view>
</view>
</view>
<!-- 今日活动状态 -->
<view class="activity-status-section">
<view class="section-title">
<text class="title-text">今日活动</text>
<text class="activity-count">{{ todaysActivities.length }}项</text>
</view>
<view class="activity-timeline" v-if="todaysActivities.length > 0">
<view class="activity-item" v-for="activity in todaysActivities" :key="activity.id">
<view class="activity-time">
<text class="time-text">{{ formatTime(activity.scheduled_time) }}</text>
</view>
<view class="activity-content">
<text class="activity-title">{{ activity.title }}</text>
<text class="activity-location">{{ activity.location }}</text>
</view>
<view class="activity-status" :class="activity.status">
<text class="status-text">{{ getActivityStatusText(activity.status) }}</text>
</view>
</view>
</view>
<view class="empty-state" v-else>
<text class="empty-text">今天没有安排活动</text>
</view>
</view>
<!-- 用药状态 -->
<view class="medication-status-section">
<view class="section-title">
<text class="title-text">用药状态</text>
<view class="medication-summary">
<text class="summary-text">已服用 {{ takenMedications }}/{{ totalMedications }}</text>
</view>
</view>
<view class="medication-list" v-if="todaysMedications.length > 0">
<view class="medication-item" v-for="medication in todaysMedications" :key="medication.id">
<view class="medication-time">
<text class="time-text">{{ formatTime(medication.scheduled_time) }}</text>
</view>
<view class="medication-info">
<text class="medication-name">{{ medication.medication_name }}</text>
<text class="medication-dosage">{{ medication.dosage }}</text>
</view>
<view class="medication-status" :class="medication.status">
<text class="status-icon">{{ getMedicationStatusIcon(medication.status) }}</text>
<text class="status-text">{{ getMedicationStatusText(medication.status) }}</text>
</view>
</view>
</view>
<view class="empty-state" v-else>
<text class="empty-text">今天没有用药安排</text>
</view>
</view>
<!-- 护理记录摘要 -->
<view class="care-summary-section">
<view class="section-title">
<text class="title-text">护理记录</text>
<button class="view-all-btn" @tap="viewAllCareRecords">
<text class="btn-text">查看全部</text>
</button>
</view>
<view class="care-records-preview" v-if="recentCareRecords.length > 0">
<view class="care-record-item" v-for="record in recentCareRecords" :key="record.id">
<view class="record-time">
<text class="time-text">{{ formatDateTime(record.created_at) }}</text>
</view>
<view class="record-content">
<text class="record-type">{{ getCareRecordTypeText(record.type) }}</text>
<text class="record-notes">{{ record.notes }}</text>
<text class="record-caregiver">护理员:{{ record.caregiver_name }}</text>
</view>
</view>
</view>
<view class="empty-state" v-else>
<text class="empty-text">暂无护理记录</text>
</view>
</view>
<!-- 联系方式 -->
<view class="contact-section">
<view class="section-title">
<text class="title-text">联系方式</text>
</view>
<view class="contact-list">
<view class="contact-item" @tap="callCaregiver" v-if="caregiverInfo.phone">
<text class="contact-icon">👩‍⚕️</text>
<view class="contact-info">
<text class="contact-name">护理员 {{ caregiverInfo.name }}</text>
<text class="contact-phone">{{ caregiverInfo.phone }}</text>
</view>
<text class="contact-action">📞</text>
</view>
<view class="contact-item" @tap="callDoctor" v-if="doctorInfo.phone">
<text class="contact-icon">👨‍⚕️</text>
<view class="contact-info">
<text class="contact-name">医生 {{ doctorInfo.name }}</text>
<text class="contact-phone">{{ doctorInfo.phone }}</text>
</view>
<text class="contact-action">📞</text>
</view>
<view class="contact-item" @tap="callFacility">
<text class="contact-icon">🏥</text>
<view class="contact-info">
<text class="contact-name">机构总机</text>
<text class="contact-phone">400-123-4567</text>
</view>
<text class="contact-action">📞</text>
</view>
</view>
</view>
<!-- 快捷操作 -->
<view class="quick-actions-section">
<view class="section-title">
<text class="title-text">快捷操作</text>
</view>
<view class="action-buttons">
<button class="action-btn primary" @tap="sendMessage">
<text class="btn-text">发送消息</text>
</button>
<button class="action-btn secondary" @tap="scheduleVisit">
<text class="btn-text">预约探访</text>
</button>
<button class="action-btn secondary" @tap="viewFinancialRecords">
<text class="btn-text">费用记录</text>
</button>
</view>
</view>
</view>
</template>
<style scoped>
.elder-status {
padding: 40rpx;
background-color: #f8f9fa;
min-height: 100vh;
}
.elder-info-card {
background: white;
border-radius: 24rpx;
padding: 40rpx;
margin-bottom: 40rpx;
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.05);
}
.elder-header {
display: flex;
align-items: flex-start;
margin-bottom: 30rpx;
}
.elder-avatar {
width: 120rpx;
height: 120rpx;
border-radius: 60rpx;
margin-right: 30rpx;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
background: #f0f0f0;
}
.avatar-image {
width: 100%;
height: 100%;
}
.avatar-fallback {
font-size: 48rpx;
color: #666;
font-weight: bold;
}
.elder-basic {
flex: 1;
}
.elder-name {
font-size: 48rpx;
font-weight: bold;
color: #333;
display: block;
margin-bottom: 8rpx;
}
.elder-info {
font-size: 30rpx;
color: #666;
display: block;
margin-bottom: 6rpx;
}
.elder-room {
font-size: 28rpx;
color: #007AFF;
display: block;
}
.elder-status {
text-align: right;
}
.health-indicator {
display: flex;
align-items: center;
justify-content: flex-end;
margin-bottom: 12rpx;
}
.indicator-dot {
width: 20rpx;
height: 20rpx;
border-radius: 10rpx;
margin-right: 12rpx;
}
.status-text {
font-size: 28rpx;
font-weight: 600;
}
.health-indicator.stable .indicator-dot {
background: #4caf50;
}
.health-indicator.stable .status-text {
color: #4caf50;
}
.health-indicator.attention .indicator-dot {
background: #ff9800;
}
.health-indicator.attention .status-text {
color: #ff9800;
}
.health-indicator.critical .indicator-dot {
background: #f44336;
}
.health-indicator.critical .status-text {
color: #f44336;
}
.care-level {
background: #e3f2fd;
padding: 8rpx 16rpx;
border-radius: 16rpx;
}
.level-text {
font-size: 24rpx;
color: #1976d2;
font-weight: 600;
}
.elder-details {
border-top: 1rpx solid #f0f0f0;
padding-top: 30rpx;
}
.detail-row {
display: flex;
margin-bottom: 20rpx;
}
.detail-row:last-child {
margin-bottom: 0;
}
.detail-item {
flex: 1;
}
.detail-label {
font-size: 26rpx;
color: #666;
}
.detail-value {
font-size: 28rpx;
color: #333;
font-weight: 500;
}
.section-title {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24rpx;
}
.title-text {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.view-history-btn,
.view-all-btn {
background: #007AFF;
color: white;
padding: 16rpx 24rpx;
border-radius: 20rpx;
border: none;
font-size: 24rpx;
}
.activity-count {
background: #ff6b6b;
color: white;
padding: 8rpx 16rpx;
border-radius: 16rpx;
font-size: 24rpx;
}
.medication-summary {
background: #4caf50;
color: white;
padding: 8rpx 16rpx;
border-radius: 16rpx;
font-size: 24rpx;
}
.health-stats-section,
.activity-status-section,
.medication-status-section,
.care-summary-section,
.contact-section,
.quick-actions-section {
margin-bottom: 40rpx;
}
.health-stats-grid {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
}
.health-stat-card {
flex: 1;
min-width: 280rpx;
background: white;
padding: 30rpx;
border-radius: 16rpx;
position: relative;
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.05);
}
.stat-icon {
font-size: 48rpx;
display: block;
margin-bottom: 12rpx;
}
.stat-label {
font-size: 28rpx;
color: #666;
display: block;
margin-bottom: 8rpx;
}
.stat-value {
font-size: 36rpx;
font-weight: bold;
color: #333;
display: block;
margin-bottom: 8rpx;
}
.stat-time {
font-size: 24rpx;
color: #999;
display: block;
}
.stat-trend {
position: absolute;
top: 20rpx;
right: 20rpx;
width: 40rpx;
height: 40rpx;
border-radius: 20rpx;
display: flex;
align-items: center;
justify-content: center;
}
.stat-trend.up {
background: #e8f5e8;
}
.stat-trend.down {
background: #ffebee;
}
.stat-trend.stable {
background: #f5f5f5;
}
.trend-icon {
font-size: 24rpx;
}
.activity-timeline,
.medication-list,
.care-records-preview {
background: white;
border-radius: 16rpx;
overflow: hidden;
}
.activity-item,
.medication-item,
.care-record-item {
display: flex;
align-items: center;
padding: 24rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.activity-item:last-child,
.medication-item:last-child,
.care-record-item:last-child {
border-bottom: none;
}
.activity-time,
.medication-time,
.record-time {
width: 140rpx;
flex-shrink: 0;
}
.time-text {
font-size: 28rpx;
color: #007AFF;
font-weight: 600;
}
.activity-content,
.medication-info,
.record-content {
flex: 1;
margin-left: 20rpx;
}
.activity-title,
.medication-name,
.record-type {
font-size: 32rpx;
color: #333;
display: block;
margin-bottom: 4rpx;
font-weight: 500;
}
.activity-location,
.medication-dosage,
.record-notes {
font-size: 26rpx;
color: #666;
display: block;
margin-bottom: 4rpx;
}
.record-caregiver {
font-size: 24rpx;
color: #999;
display: block;
}
.activity-status,
.medication-status {
flex-shrink: 0;
display: flex;
align-items: center;
gap: 8rpx;
padding: 8rpx 16rpx;
border-radius: 12rpx;
font-size: 24rpx;
}
.activity-status.pending,
.medication-status.scheduled {
background: #fff3e0;
color: #ff9800;
}
.activity-status.completed,
.medication-status.taken {
background: #e8f5e8;
color: #4caf50;
}
.medication-status.missed {
background: #ffebee;
color: #f44336;
}
.contact-list {
background: white;
border-radius: 16rpx;
overflow: hidden;
}
.contact-item {
display: flex;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.contact-item:last-child {
border-bottom: none;
}
.contact-icon {
font-size: 48rpx;
margin-right: 20rpx;
}
.contact-info {
flex: 1;
}
.contact-name {
font-size: 32rpx;
color: #333;
display: block;
margin-bottom: 4rpx;
font-weight: 500;
}
.contact-phone {
font-size: 28rpx;
color: #666;
display: block;
}
.contact-action {
font-size: 36rpx;
color: #007AFF;
}
.action-buttons {
display: flex;
gap: 20rpx;
}
.action-btn {
flex: 1;
padding: 30rpx;
border-radius: 16rpx;
border: none;
font-size: 30rpx;
font-weight: 600;
}
.action-btn.primary {
background: #007AFF;
color: white;
}
.action-btn.secondary {
background: white;
color: #007AFF;
border: 1rpx solid #007AFF;
}
.empty-state {
background: white;
padding: 60rpx;
border-radius: 16rpx;
text-align: center;
}
.empty-text {
font-size: 30rpx;
color: #999;
}
</style>
<script setup lang="uts">
import { ref, computed, onMounted } from 'vue'
import { formatDate, formatTime, formatDateTime, getActivityStatusText, getStatusText } from '../types.uts'
import type { ElderInfo, HealthVitals, Activity, MedicationSchedule, CareRecord, CaregiverInfo, DoctorInfo } from '../types.uts'
// 数据状态
const elderInfo = ref<ElderInfo>({
id: '',
name: '',
age: 0,
gender: 'male',
room_number: '',
bed_number: '',
admission_date: '',
health_status: 'stable',
care_level: 1,
emergency_contact: '',
profile_picture: '',
family_contact: ''
})
const recentVitals = ref<HealthVitals[]>([])
const todaysActivities = ref<Activity[]>([])
const todaysMedications = ref<MedicationSchedule[]>([])
const recentCareRecords = ref<CareRecord[]>([])
const caregiverInfo = ref<CaregiverInfo>({
id: '',
name: '',
phone: '',
department: '',
specialization: '',
shift: 'day'
})
const doctorInfo = ref<DoctorInfo>({
id: '',
name: '',
phone: '',
department: '',
specialization: '',
title: ''
})
// 计算属性
const healthStatusClass = computed(() => {
return elderInfo.value.health_status || 'stable'
})
const healthStatusText = computed(() => {
const statusMap = {
'stable': '稳定',
'attention': '需关注',
'critical': '危险'
}
return statusMap[elderInfo.value.health_status] || '未知'
})
const careLevelText = computed(() => {
const levelMap = {
1: '一级护理',
2: '二级护理',
3: '三级护理',
4: '特级护理'
}
return levelMap[elderInfo.value.care_level] || '未设置'
})
const assignedCaregiver = computed(() => {
return caregiverInfo.value.name || '未分配'
})
const assignedDoctor = computed(() => {
return doctorInfo.value.name || '未分配'
})
const takenMedications = computed(() => {
return todaysMedications.value.filter(med => med.status === 'taken').length
})
const totalMedications = computed(() => {
return todaysMedications.value.length
})
// 辅助函数
function getVitalIcon(type: string): string {
const icons = {
'heart_rate': '❤️',
'blood_pressure': '🩸',
'temperature': '🌡️',
'blood_sugar': '🍯',
'oxygen_saturation': '🫁'
}
return icons[type] || '📊'
}
function getVitalLabel(type: string): string {
const labels = {
'heart_rate': '心率',
'blood_pressure': '血压',
'temperature': '体温',
'blood_sugar': '血糖',
'oxygen_saturation': '血氧'
}
return labels[type] || type
}
function getVitalUnit(type: string): string {
const units = {
'heart_rate': 'bpm',
'blood_pressure': 'mmHg',
'temperature': '°C',
'blood_sugar': 'mmol/L',
'oxygen_saturation': '%'
}
return units[type] || ''
}
function getTrendIcon(trend: string): string {
const icons = {
'up': '↗️',
'down': '↘️',
'stable': '→'
}
return icons[trend] || '→'
}
function getMedicationStatusIcon(status: string): string {
const icons = {
'scheduled': '⏰',
'taken': '✅',
'missed': '❌',
'skipped': '⏭️'
}
return icons[status] || '⏰'
}
function getMedicationStatusText(status: string): string {
const statusMap = {
'scheduled': '待服用',
'taken': '已服用',
'missed': '已错过',
'skipped': '已跳过'
}
return statusMap[status] || '未知'
}
function getCareRecordTypeText(type: string): string {
const typeMap = {
'vital_signs': '生命体征',
'medication': '用药记录',
'nursing': '护理服务',
'meal': '用餐记录',
'activity': '活动记录',
'incident': '事件记录'
}
return typeMap[type] || type
}
function handleAvatarError() {
elderInfo.value.profile_picture = ''
}
// 导航函数
function viewHealthHistory() {
uni.navigateTo({
url: `/pages/ec/family/health-history?elder_id=${elderInfo.value.id}`
})
}
function viewAllCareRecords() {
uni.navigateTo({
url: `/pages/ec/family/care-records?elder_id=${elderInfo.value.id}`
})
}
function sendMessage() {
uni.navigateTo({
url: `/pages/ec/family/send-message?elder_id=${elderInfo.value.id}`
})
}
function scheduleVisit() {
uni.navigateTo({
url: `/pages/ec/family/schedule-visit?elder_id=${elderInfo.value.id}`
})
}
function viewFinancialRecords() {
uni.navigateTo({
url: `/pages/ec/family/financial-records?elder_id=${elderInfo.value.id}`
})
}
// 通话功能
function callCaregiver() {
if (caregiverInfo.value.phone) {
uni.makePhoneCall({
phoneNumber: caregiverInfo.value.phone
})
}
}
function callDoctor() {
if (doctorInfo.value.phone) {
uni.makePhoneCall({
phoneNumber: doctorInfo.value.phone
})
}
}
function callFacility() {
uni.makePhoneCall({
phoneNumber: '400-123-4567'
})
}
// 数据加载
async function loadElderInfo() {
try {
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1]
const elderId = currentPage.$route.query?.elder_id as string
if (!elderId) {
uni.showToast({
title: '缺少老人ID',
icon: 'error'
})
return
}
const supa = (globalThis as any).supa
const result = await supa.executeAs('get_elder_info', {
elder_id: elderId
})
if (result && result.length > 0) {
elderInfo.value = result[0]
}
} catch (error) {
console.error('加载老人信息失败:', error)
uni.showToast({
title: '加载失败',
icon: 'error'
})
}
}
async function loadRecentVitals() {
try {
const supa = (globalThis as any).supa
const result = await supa.executeAs('get_recent_vitals', {
elder_id: elderInfo.value.id,
limit: 4
})
if (result && result.length > 0) {
recentVitals.value = result
}
} catch (error) {
console.error('加载健康数据失败:', error)
}
}
async function loadTodaysActivities() {
try {
const supa = (globalThis as any).supa
const today = new Date().toISOString().split('T')[0]
const result = await supa.executeAs('get_elder_activities', {
elder_id: elderInfo.value.id,
date: today
})
if (result && result.length > 0) {
todaysActivities.value = result
}
} catch (error) {
console.error('加载今日活动失败:', error)
}
}
async function loadTodaysMedications() {
try {
const supa = (globalThis as any).supa
const today = new Date().toISOString().split('T')[0]
const result = await supa.executeAs('get_elder_medications', {
elder_id: elderInfo.value.id,
date: today
})
if (result && result.length > 0) {
todaysMedications.value = result
}
} catch (error) {
console.error('加载用药信息失败:', error)
}
}
async function loadRecentCareRecords() {
try {
const supa = (globalThis as any).supa
const result = await supa.executeAs('get_recent_care_records', {
elder_id: elderInfo.value.id,
limit: 3
})
if (result && result.length > 0) {
recentCareRecords.value = result
}
} catch (error) {
console.error('加载护理记录失败:', error)
}
}
async function loadCaregiverInfo() {
try {
const supa = (globalThis as any).supa
const result = await supa.executeAs('get_assigned_caregiver', {
elder_id: elderInfo.value.id
})
if (result && result.length > 0) {
caregiverInfo.value = result[0]
}
} catch (error) {
console.error('加载护理员信息失败:', error)
}
}
async function loadDoctorInfo() {
try {
const supa = (globalThis as any).supa
const result = await supa.executeAs('get_assigned_doctor', {
elder_id: elderInfo.value.id
})
if (result && result.length > 0) {
doctorInfo.value = result[0]
}
} catch (error) {
console.error('加载医生信息失败:', error)
}
}
// 初始化
onMounted(async () => {
await loadElderInfo()
if (elderInfo.value.id) {
await Promise.all([
loadRecentVitals(),
loadTodaysActivities(),
loadTodaysMedications(),
loadRecentCareRecords(),
loadCaregiverInfo(),
loadDoctorInfo()
])
}
})
</script>