Initial commit of akmon project
This commit is contained in:
855
pages/ec/caregiver/dashboard.uvue
Normal file
855
pages/ec/caregiver/dashboard.uvue
Normal 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>
|
||||
753
pages/ec/caregiver/elder-details.uvue
Normal file
753
pages/ec/caregiver/elder-details.uvue
Normal 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>
|
||||
170
pages/ec/caregiver/my-elders.uvue
Normal file
170
pages/ec/caregiver/my-elders.uvue
Normal 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>
|
||||
1255
pages/ec/caregiver/task-execution.uvue
Normal file
1255
pages/ec/caregiver/task-execution.uvue
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user