Initial commit of akmon project

This commit is contained in:
2026-01-20 08:04:15 +08:00
commit 77a2bab985
1309 changed files with 343305 additions and 0 deletions

View File

@@ -0,0 +1,855 @@
<!-- 养老管理系统 - 护理员仪表板 (简化版) -->
<template>
<view class="caregiver-dashboard">
<view class="header">
<text class="title">护理工作台</text>
<text class="welcome">{{ caregiverName }}{{ currentTime }}</text>
</view>
<!-- 今日工作概览 -->
<view class="today-overview">
<view class="overview-card">
<view class="card-icon">📋</view>
<view class="card-content">
<text class="card-number">{{ todayStats.total_tasks }}</text>
<text class="card-label">今日任务</text>
<view class="card-status">
<text class="status-text">{{ todayStats.completed_tasks }}/{{ todayStats.total_tasks }}</text>
</view>
</view>
</view>
<view class="overview-card">
<view class="card-icon">👥</view>
<view class="card-content">
<text class="card-number">{{ todayStats.assigned_elders }}</text>
<text class="card-label">负责老人</text>
</view>
</view>
<view class="overview-card">
<view class="card-icon">⚠️</view>
<view class="card-content">
<text class="card-number">{{ todayStats.urgent_tasks }}</text>
<text class="card-label">紧急任务</text>
</view>
<view class="card-alert" v-if="todayStats.urgent_tasks > 0">
<text class="alert-text">需处理</text>
</view>
</view>
</view>
<!-- 待处理任务 -->
<view class="pending-tasks-section">
<view class="section-header">
<text class="section-title">待处理任务</text>
<text class="section-more" @click="viewAllTasks">查看全部</text>
</view>
<view class="tasks-list">
<view v-for="task in pendingTasks" :key="task.id" class="task-item" :class="getTaskPriorityClass(task.priority)" @click="startTask(task)">
<view class="task-main">
<view class="task-info">
<text class="task-name">{{ task.task_name }}</text>
<text class="task-elder">{{ task.elder_name }}</text>
<text class="task-time">{{ formatTime(task.scheduled_time) }}</text>
</view>
<view class="task-priority">
<view class="priority-badge" :class="task.priority">
<text class="priority-text">{{ getPriorityText(task.priority) }}</text>
</view>
</view>
</view>
<view class="task-actions">
<button class="task-btn start" @click.stop="startTask(task)">开始</button>
<button class="task-btn detail" @click.stop="viewTaskDetail(task)">详情</button>
</view>
</view>
</view>
</view>
<!-- 负责的老人 -->
<view class="assigned-elders-section">
<view class="section-header">
<text class="section-title">我负责的老人</text>
<text class="section-more" @click="viewAllElders">查看全部</text>
</view>
<view class="elders-list">
<view v-for="elder in assignedElders" :key="elder.id" class="elder-item" @click="viewElderDetail(elder)">
<view class="elder-avatar">
<image class="avatar-image" :src="elder.profile_picture ?? ''" mode="aspectFill"
v-if="elder.profile_picture !== null" />
<text class="avatar-fallback" v-else>{{ elder.name.charAt(0) }}</text>
</view>
<view class="elder-info">
<text class="elder-name">{{ elder.name }}</text>
<text class="elder-room">{{ elder.room_number }}{{ elder.bed_number }}</text>
<text class="elder-care-level">{{ getCareLevelText(elder.care_level) }}</text>
</view>
<view class="elder-status">
<view class="health-indicator" :class="getHealthStatusClass(elder.health_status)">
<text class="health-text">{{ getHealthStatusText(elder.health_status) }}</text>
</view>
<view class="alert-count" v-if="getElderAlertCount(elder.id) > 0">
<text class="alert-number">{{ getElderAlertCount(elder.id) }}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 最近完成的任务 -->
<view class="completed-tasks-section">
<view class="section-header">
<text class="section-title">最近完成</text>
<text class="section-more" @click="viewCompletedTasks">查看更多</text>
</view>
<view class="completed-list">
<view v-for="task in completedTasks" :key="task.id" class="completed-item">
<view class="completed-icon">✅</view>
<view class="completed-info">
<text class="completed-name">{{ task.task_name }}</text>
<text class="completed-elder">{{ task.elder_name }}</text>
<text class="completed-time">{{ formatDateTime(task.scheduled_time) }}</text>
</view>
<view class="completed-status">
<text class="status-text">已完成</text>
</view>
</view>
</view>
</view>
<!-- 快速操作 -->
<view class="quick-actions">
<view class="action-item" @click="quickReport">
<view class="action-icon">📝</view>
<text class="action-text">快速记录</text>
</view>
<view class="action-item" @click="emergencyCall">
<view class="action-icon">🚨</view>
<text class="action-text">紧急呼叫</text>
</view>
<view class="action-item" @click="healthCheck">
<view class="action-icon">💊</view>
<text class="action-text">健康检查</text>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref, onMounted } from 'vue'
import supa from '@/components/supadb/aksupainstance.uts'
import type { Elder, CareTask } from '../types.uts'
import { formatDateTime, formatTime,getPriorityText, getCareLevelText, getHealthStatusText, getHealthStatusClass, getTaskPriorityClass } from '../types.uts'
import { state, getCurrentUserId } from '@/utils/store.uts'
import type { UserProfile, UserStats } from '@/pages/user/types.uts'
const profile =ref<UserProfile>(state.userProfile)
// 响应式数据
const caregiverName = ref<string>('护理员')
const currentTime = ref<string>('')
// 今日统计
const todayStats = ref({
total_tasks: 0,
completed_tasks: 0,
assigned_elders: 0,
urgent_tasks: 0
})
// 数据列表
const pendingTasks = ref<Array<CareTask>>([])
const assignedElders = ref<Array<Elder>>([])
const completedTasks = ref<Array<CareTask>>([])
const elderAlerts = ref<Map<string, number>>(new Map())
// 更新当前时间
const updateCurrentTime = () => {
const now = new Date()
const hours = now.getHours().toString().padStart(2, '0')
const minutes = now.getMinutes().toString().padStart(2, '0')
currentTime.value = `今天 ${hours}:${minutes}`
}
// 获取今天的时间范围
const getTodayRange = () => {
const today = new Date()
const start = new Date(today.getFullYear(), today.getMonth(), today.getDate())
const end = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1)
return {
start: start.toISOString(),
end: end.toISOString()
}
}
// 加载今日统计
const loadTodayStats = async () => {
try {
const { start, end } = getTodayRange()
const currentUserId = profile.value.id
// 加载今日任务统计
const tasksResult = await supa
.from('ec_care_tasks')
.select('*', { count: 'exact' })
.eq('assigned_to', currentUserId)
.gte('scheduled_time', start)
.lt('scheduled_time', end)
.executeAs<Array<CareTask>>()
if (tasksResult.error === null) {
todayStats.value.total_tasks = tasksResult.count ?? 0
// 计算已完成任务数
const completedCount = tasksResult.data?.filter(task => task.status === 'completed').length ?? 0
todayStats.value.completed_tasks = completedCount
// 计算紧急任务数
const urgentCount = tasksResult.data?.filter(task => task.priority === 'urgent' && task.status !== 'completed').length ?? 0
todayStats.value.urgent_tasks = urgentCount
}
// 通过任务表统计负责老人数量
const eldersResult = await supa
.from('ec_care_tasks')
.select('elder_id')
.eq('assigned_to', currentUserId)
.executeAs<Array<{ elder_id: string }>>()
if (eldersResult.error === null && eldersResult.data !== null) {
const uniqueElderIds = Array.from(new Set(eldersResult.data.map(e => e.elder_id)))
todayStats.value.assigned_elders = uniqueElderIds.length
}
} catch (error) {
console.error('加载统计数据失败:', error)
}
}
// 加载待处理任务
const loadPendingTasks = async () => {
try {
const currentUserId = profile.value.id
const result = await supa
.from('ec_care_tasks')
.select(`
id,
task_name,
elder_name,
scheduled_time,
status,
priority
`)
.eq('assigned_to', currentUserId)
.eq('status', 'pending')
.order('scheduled_time', { ascending: true })
.limit(5)
.executeAs<Array<CareTask>>()
if (result.error === null && result.data !== null) {
pendingTasks.value = result.data
}
} catch (error) {
console.error('加载待处理任务失败:', error)
}
}
// 加载负责的老人
const loadAssignedElders = async () => {
try {
const currentUserId = profile.value.id
// 先查找当前护理员负责的所有老人ID
const taskResult = await supa
.from('ec_care_tasks')
.select('elder_id')
.eq('assigned_to', currentUserId)
.executeAs<Array<{ elder_id: string }>>()
if (taskResult.error === null && taskResult.data !== null) {
const uniqueElderIds = Array.from(new Set(taskResult.data.map(e => e.elder_id)))
if (uniqueElderIds.length === 0) {
assignedElders.value = []
return
}
// 查询老人信息
const eldersResult = await supa
.from('ec_elders')
.select(`
id,
name,
room_number,
bed_number,
health_status,
care_level,
profile_picture
`)
.in('id', uniqueElderIds)
.eq('status', 'active')
.limit(6)
.executeAs<Array<Elder>>()
if (eldersResult.error === null && eldersResult.data !== null) {
assignedElders.value = eldersResult.data
// 加载每个老人的告警数量
loadElderAlerts()
}
}
} catch (error) {
console.error('加载负责老人失败:', error)
}
}
// 加载最近完成的任务
const loadCompletedTasks = async () => {
try {
const currentUserId = profile.value.id
const result = await supa
.from('ec_care_tasks')
.select(`
id,
task_name,
elder_name,
scheduled_time,
status
`)
.eq('assigned_to', currentUserId)
.eq('status', 'completed')
.order('updated_at', { ascending: false })
.limit(3)
.executeAs<Array<CareTask>>()
if (result.error === null && result.data !== null) {
completedTasks.value = result.data
}
} catch (error) {
console.error('加载完成任务失败:', error)
}
}
// 加载老人告警数量
const loadElderAlerts = async () => {
try {
for (let i: Int = 0; i < assignedElders.value.length; i++) {
const elder = assignedElders.value[i]
const alertsResult = await supa
.from('ec_health_alerts')
.select('*', { count: 'exact' })
.eq('elder_id', elder.id)
.eq('status', 'active')
.executeAs<Array<any>>()
if (alertsResult.error === null) {
elderAlerts.value.set(elder.id, alertsResult.count ?? 0)
}
}
} catch (error) {
console.error('加载告警数据失败:', error)
}
}
// 获取老人告警数量
const getElderAlertCount = (elderId: string): number => {
return elderAlerts.value.get(elderId) ?? 0
}
// 开始任务
const startTask = async (task: CareTask) => {
try {
await supa
.from('ec_care_tasks')
.update({
status: 'in_progress',
start_time: new Date().toISOString()
})
.eq('id', task.id)
.executeAs<any>()
uni.navigateTo({
url: `/pages/ec/tasks/execute?id=${task.id}`
})
} catch (error) {
console.error('开始任务失败:', error)
uni.showToast({
title: '操作失败',
icon: 'error'
})
}
}
// 查看任务详情
const viewTaskDetail = (task: CareTask) => {
uni.navigateTo({
url: `/pages/ec/tasks/detail?id=${task.id}`
})
}
// 查看老人详情
const viewElderDetail = (elder: Elder) => {
uni.navigateTo({
url: `/pages/ec/elders/detail?id=${elder.id}`
})
}
// 导航函数
const viewAllTasks = () => {
uni.navigateTo({
url: '/pages/ec/tasks/my-tasks'
})
}
const viewAllElders = () => {
uni.navigateTo({
url: '/pages/ec/caregiver/my-elders'
})
}
const viewCompletedTasks = () => {
uni.navigateTo({
url: '/pages/ec/tasks/completed'
})
}
// 快速操作
const quickReport = () => {
uni.navigateTo({
url: '/pages/ec/reports/quick-add'
})
}
const emergencyCall = () => {
uni.makePhoneCall({
phoneNumber: '120'
})
}
const healthCheck = () => {
uni.navigateTo({
url: '/pages/ec/health/quick-check'
})
}
onLoad(async(options: OnLoadOptions) => {
profile.value.id = options['id'] ?? getCurrentUserId()
if (profile.value.id !='' ) {
loadTodayStats()
loadPendingTasks()
loadAssignedElders()
loadCompletedTasks()
// 定时更新时间
setInterval(() => {
updateCurrentTime()
}, 60000)
}
})
</script>
<style scoped>
.caregiver-dashboard {
display: flex;
flex-direction: column;
min-height: 100vh;
background-color: #f5f5f5;
padding: 20px;
}
.header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.title {
font-size: 24px;
font-weight: bold;
color: #333;
}
.welcome {
font-size: 14px;
color: #666;
}
/* 今日概览 */
.today-overview {
display: flex;
flex-direction: row;
margin-bottom: 20px;
}
.overview-card {
flex: 1;
background-color: #fff;
border-radius: 8px;
padding: 15px;
margin-right: 10px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
display: flex;
flex-direction: row;
align-items: center;
}
.overview-card:last-child {
margin-right: 0;
}
.card-icon {
font-size: 24px;
margin-right: 10px;
}
.card-content {
flex: 1;
display: flex;
flex-direction: column;
}
.card-number {
font-size: 20px;
font-weight: bold;
color: #333;
}
.card-label {
font-size: 12px;
color: #666;
}
.card-status {
display: flex;
align-items: center;
}
.status-text {
font-size: 12px;
color: #1890ff;
}
.card-alert {
display: flex;
align-items: center;
}
.alert-text {
font-size: 12px;
color: #ff4d4f;
background-color: #fff2f0;
padding: 2px 6px;
border-radius: 4px;
}
/* 通用列表样式 */
.pending-tasks-section,
.assigned-elders-section,
.completed-tasks-section {
background-color: #fff;
border-radius: 8px;
padding: 15px;
margin-bottom: 15px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.section-header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.section-title {
font-size: 16px;
font-weight: bold;
color: #333;
}
.section-more {
font-size: 14px;
color: #1890ff;
}
/* 任务列表 */
.task-item {
display: flex;
flex-direction: column;
padding: 12px;
border-radius: 6px;
margin-bottom: 8px;
border-left: 3px solid #d9d9d9;
}
.task-item.priority-urgent {
border-left-color: #ff4d4f;
background-color: #fff2f0;
}
.task-item.priority-high {
border-left-color: #fa8c16;
background-color: #fff7e6;
}
.task-item.priority-normal {
border-left-color: #1890ff;
background-color: #f0f9ff;
}
.task-main {
display: flex;
flex-direction: row;
align-items: center;
margin-bottom: 8px;
}
.task-info {
flex: 1;
display: flex;
flex-direction: column;
}
.task-name {
font-size: 14px;
font-weight: bold;
color: #333;
margin-bottom: 3px;
}
.task-elder {
font-size: 12px;
color: #666;
margin-bottom: 2px;
}
.task-time {
font-size: 12px;
color: #999;
}
.priority-badge {
padding: 2px 6px;
border-radius: 4px;
font-size: 10px;
}
.priority-badge.urgent {
background-color: #ff4d4f;
color: #fff;
}
.priority-badge.high {
background-color: #fa8c16;
color: #fff;
}
.priority-badge.normal {
background-color: #1890ff;
color: #fff;
}
.task-actions {
display: flex;
flex-direction: row;
justify-content: flex-end;
}
.task-btn {
padding: 4px 12px;
border-radius: 4px;
border: none;
font-size: 12px;
margin-left: 8px;
}
.task-btn.start {
background-color: #52c41a;
color: #fff;
}
.task-btn.detail {
background-color: #1890ff;
color: #fff;
}
/* 老人列表 */
.elder-item {
display: flex;
flex-direction: row;
align-items: center;
padding: 12px;
border-bottom: 1px solid #f0f0f0;
}
.elder-item:last-child {
border-bottom: none;
}
.elder-avatar {
width: 40px;
height: 40px;
border-radius: 20px;
margin-right: 12px;
overflow: hidden;
background-color: #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
}
.avatar-image {
width: 100%;
height: 100%;
}
.avatar-fallback {
font-size: 16px;
color: #666;
}
.elder-info {
flex: 1;
display: flex;
flex-direction: column;
}
.elder-name {
font-size: 14px;
font-weight: bold;
color: #333;
margin-bottom: 3px;
}
.elder-room {
font-size: 12px;
color: #666;
margin-bottom: 2px;
}
.elder-care-level {
font-size: 12px;
color: #1890ff;
}
.elder-status {
display: flex;
flex-direction: column;
align-items: flex-end;
}
.health-indicator {
padding: 2px 6px;
border-radius: 4px;
font-size: 10px;
margin-bottom: 4px;
}
.health-excellent {
background-color: #f6ffed;
color: #52c41a;
}
.health-good {
background-color: #e6f7ff;
color: #1890ff;
}
.health-fair {
background-color: #fff7e6;
color: #d48806;
}
.health-poor {
background-color: #fff2f0;
color: #ff4d4f;
}
.alert-count {
background-color: #ff4d4f;
color: #fff;
border-radius: 10px;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
}
.alert-number {
font-size: 10px;
}
/* 完成列表 */
.completed-item {
display: flex;
flex-direction: row;
align-items: center;
padding: 10px;
border-bottom: 1px solid #f0f0f0;
}
.completed-item:last-child {
border-bottom: none;
}
.completed-icon {
margin-right: 12px;
font-size: 16px;
}
.completed-info {
flex: 1;
display: flex;
flex-direction: column;
}
.completed-name {
font-size: 14px;
color: #333;
margin-bottom: 3px;
}
.completed-elder {
font-size: 12px;
color: #666;
margin-bottom: 2px;
}
.completed-time {
font-size: 12px;
color: #999;
}
.completed-status .status-text {
font-size: 12px;
color: #52c41a;
}
/* 快速操作 */
.quick-actions {
display: flex;
flex-direction: row;
justify-content: space-around;
background-color: #fff;
border-radius: 8px;
padding: 15px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.action-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 10px;
}
.action-icon {
font-size: 24px;
margin-bottom: 5px;
}
.action-text {
font-size: 12px;
color: #666;
}
</style>

View File

@@ -0,0 +1,753 @@
<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>

View File

@@ -0,0 +1,170 @@
<!-- 养老管理系统 - 我负责的老人列表 -->
<template>
<view class="my-elders-page">
<view class="header">
<text class="title">我负责的老人</text>
</view>
<view class="elders-list">
<view v-if="elders.length === 0" class="empty-text">暂无负责老人</view>
<view v-for="elder in elders" :key="elder.id" class="elder-item" @click="viewElderDetail(elder)">
<view class="elder-avatar">
<image class="avatar-image" :src="elder.profile_picture ?? ''" mode="aspectFill" v-if="elder.profile_picture !== null" />
<text class="avatar-fallback" v-else>{{ elder.name.charAt(0) }}</text>
</view>
<view class="elder-info">
<text class="elder-name">{{ elder.name }}</text>
<text class="elder-room">{{ elder.room_number }}{{ elder.bed_number }}</text>
<text class="elder-care-level">{{ getCareLevelText(elder.care_level) }}</text>
</view>
<view class="elder-status">
<view class="health-indicator" :class="getHealthStatusClass(elder.health_status)">
<text class="health-text">{{ getHealthStatusText(elder.health_status) }}</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref, onMounted } from 'vue'
import supa from '@/components/supadb/aksupainstance.uts'
import type { Elder } from '../types.uts'
import { getCareLevelText, getHealthStatusText, getHealthStatusClass } from '../types.uts'
import { state, getCurrentUserId } from '@/utils/store.uts'
const elders = ref<Array<Elder>>([])
const profile = ref(state.userProfile)
const loadMyElders = async (currentUserId) => {
try {
// 查找当前护理员负责的所有老人ID
const taskResult = await supa
.from('ec_care_tasks')
.select('*',{count:'exact'})
.eq('assigned_to', currentUserId)
.executeAs<Elder>()
if (taskResult.error === null && taskResult.data !== null) {
elders.value = taskResult.data
}
} catch (error) {
console.error('加载负责老人失败:', error)
}
}
const viewElderDetail = (elder: Elder) => {
uni.navigateTo({
url: `/pages/ec/elders/detail?id=${elder.id}`
})
}
onLoad((options: OnLoadOptions) => {
const currentUserId = options['id'] ?? getCurrentUserId()
loadMyElders(currentUserId)
})
</script>
<style scoped>
.my-elders-page {
min-height: 100vh;
background: #f5f5f5;
padding: 20px;
}
.header {
margin-bottom: 20px;
}
.title {
font-size: 22px;
font-weight: bold;
color: #333;
}
.elders-list {
background: #fff;
border-radius: 8px;
padding: 15px;
}
.elder-item {
display: flex;
flex-direction: row;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid #f0f0f0;
}
.elder-item:last-child {
border-bottom: none;
}
.elder-avatar {
width: 40px;
height: 40px;
border-radius: 20px;
margin-right: 12px;
overflow: hidden;
background-color: #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
}
.avatar-image {
width: 100%;
height: 100%;
}
.avatar-fallback {
font-size: 16px;
color: #666;
}
.elder-info {
flex: 1;
display: flex;
flex-direction: column;
}
.elder-name {
font-size: 14px;
font-weight: bold;
color: #333;
margin-bottom: 3px;
}
.elder-room {
font-size: 12px;
color: #666;
margin-bottom: 2px;
}
.elder-care-level {
font-size: 12px;
color: #1890ff;
}
.elder-status {
display: flex;
flex-direction: column;
align-items: flex-end;
}
.health-indicator {
padding: 2px 6px;
border-radius: 4px;
font-size: 10px;
margin-bottom: 4px;
}
.health-excellent {
background-color: #f6ffed;
color: #52c41a;
}
.health-good {
background-color: #e6f7ff;
color: #1890ff;
}
.health-fair {
background-color: #fff7e6;
color: #d48806;
}
.health-poor {
background-color: #fff2f0;
color: #ff4d4f;
}
.empty-text {
text-align: center;
color: #999;
padding: 30px 0;
}
</style>

File diff suppressed because it is too large Load Diff