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

856 lines
19 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 养老管理系统 - 护理员仪表板 (简化版) -->
<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>