1124 lines
27 KiB
Plaintext
1124 lines
27 KiB
Plaintext
<!-- 护士工作台 - 重构版本 -->
|
||
<template>
|
||
<view class="nurse-dashboard">
|
||
<!-- Header -->
|
||
<view class="header">
|
||
|
||
<text class="header-title">护士工作台</text>
|
||
<text class="header-subtitle">{{ currentTime }}</text>
|
||
<view class="header-actions">
|
||
<button class="action-btn urgent" @click="showUrgentTasks">
|
||
<text class="btn-text">🚨 紧急任务</text>
|
||
</button>
|
||
<button class="action-btn" @click="showVitalSignsEntry">
|
||
<text class="btn-text">➕ 记录体征</text>
|
||
</button>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- Stats Cards -->
|
||
<view class="stats-section">
|
||
<view class="stat-card">
|
||
<view class="stat-icon">👥</view>
|
||
<view class="stat-content">
|
||
<text class="stat-number">{{ stats.assigned_patients }}</text>
|
||
<text class="stat-label">负责患者</text>
|
||
</view>
|
||
</view>
|
||
<view class="stat-card">
|
||
<view class="stat-icon">✅</view>
|
||
<view class="stat-content">
|
||
<text class="stat-number">{{ stats.completed_tasks }}</text>
|
||
<text class="stat-label">今日完成</text>
|
||
</view>
|
||
</view>
|
||
<view class="stat-card">
|
||
<view class="stat-icon">⏰</view>
|
||
<view class="stat-content">
|
||
<text class="stat-number">{{ stats.pending_tasks }}</text>
|
||
<text class="stat-label">待处理</text>
|
||
</view>
|
||
</view>
|
||
<view class="stat-card urgent">
|
||
<view class="stat-icon">⚠️</view>
|
||
<view class="stat-content">
|
||
<text class="stat-number">{{ stats.urgent_tasks }}</text>
|
||
<text class="stat-label">紧急处理</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- Quick Actions -->
|
||
<view class="quick-actions">
|
||
<text class="section-title">快速操作</text>
|
||
<view class="actions-grid">
|
||
<button class="quick-action-btn" @click="showPatientList">
|
||
<text class="action-icon">👥</text>
|
||
<text class="action-text">患者列表</text>
|
||
</button>
|
||
<button class="quick-action-btn" @click="showVitalSigns">
|
||
<text class="action-icon">❤️</text>
|
||
<text class="action-text">生命体征</text>
|
||
</button>
|
||
<button class="quick-action-btn" @click="showMedicationTasks">
|
||
<text class="action-icon">💊</text>
|
||
<text class="action-text">用药管理</text>
|
||
</button>
|
||
<button class="quick-action-btn" @click="showCareRecords">
|
||
<text class="action-icon">📋</text>
|
||
<text class="action-text">护理记录</text>
|
||
</button>
|
||
<button class="quick-action-btn" @click="showNursingPlans">
|
||
<text class="action-icon">📅</text>
|
||
<text class="action-text">护理计划</text>
|
||
</button>
|
||
<button class="quick-action-btn" @click="showIncidentReports">
|
||
<text class="action-icon">⚠️</text>
|
||
<text class="action-text">事件报告</text>
|
||
</button>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- Today's Tasks -->
|
||
<view class="tasks-section">
|
||
<view class="section-header">
|
||
<text class="section-title">今日任务 ({{ todayTasks.length }})</text>
|
||
<view class="task-filters">
|
||
<button
|
||
class="filter-btn"
|
||
:class="{ active: taskFilter === 'all' }"
|
||
@click="setTaskFilter('all')"
|
||
>
|
||
<text class="filter-text">全部</text>
|
||
</button>
|
||
<button
|
||
class="filter-btn"
|
||
:class="{ active: taskFilter === 'pending' }"
|
||
@click="setTaskFilter('pending')"
|
||
>
|
||
<text class="filter-text">待处理</text>
|
||
</button>
|
||
<button
|
||
class="filter-btn"
|
||
:class="{ active: taskFilter === 'urgent' }"
|
||
@click="setTaskFilter('urgent')"
|
||
>
|
||
<text class="filter-text">紧急</text>
|
||
</button>
|
||
</view>
|
||
</view>
|
||
<scroll-view class="tasks-list" scroll-y="true" :style="{ height: '400px' }">
|
||
<view
|
||
v-for="task in filteredTasks"
|
||
:key="task.id"
|
||
class="task-item"
|
||
:class="{
|
||
'urgent': task.priority === 'urgent',
|
||
'high': task.priority === 'high',
|
||
'completed': task.status === 'completed',
|
||
'overdue': isTaskOverdue(task)
|
||
}"
|
||
@click="openTask(task)"
|
||
>
|
||
<view class="task-header">
|
||
<view class="task-info">
|
||
<text class="task-title">{{ task.task_name }}</text>
|
||
<text class="task-patient">{{ task.elder_name }}</text>
|
||
</view>
|
||
<view class="task-meta">
|
||
<text class="task-time">{{ formatTime(task.scheduled_time) }}</text>
|
||
<text class="task-priority" :class="task.priority">{{ getPriorityText(task.priority) }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="task-content">
|
||
<text class="task-description">{{ task.description }}</text>
|
||
<text class="task-type">{{ getTaskTypeText(task.task_type) }}</text>
|
||
</view>
|
||
<view class="task-actions">
|
||
<button
|
||
v-if="task.status === 'pending'"
|
||
class="start-btn"
|
||
@click.stop="startTask(task)"
|
||
>
|
||
<text class="btn-text">开始</text>
|
||
</button>
|
||
<button
|
||
v-if="task.status === 'in_progress'"
|
||
class="complete-btn"
|
||
@click.stop="completeTask(task)"
|
||
>
|
||
<text class="btn-text">完成</text>
|
||
</button>
|
||
<button class="detail-btn" @click.stop="viewTaskDetail(task)">
|
||
<text class="btn-text">详情</text>
|
||
</button>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="filteredTasks.length === 0" class="empty-state">
|
||
<text class="empty-text">暂无任务</text>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
|
||
<!-- Patient Status Overview -->
|
||
<view class="patients-section">
|
||
<view class="section-header">
|
||
<text class="section-title">患者状态概览</text>
|
||
<button class="view-all-btn" @click="showAllPatients">
|
||
<text class="btn-text">查看全部</text>
|
||
</button>
|
||
</view>
|
||
<scroll-view class="patients-list" scroll-y="true" :style="{ height: '300px' }">
|
||
<view
|
||
v-for="patient in assignedPatients"
|
||
:key="patient.id"
|
||
class="patient-item"
|
||
:class="{ 'alert': hasHealthAlert(patient) }"
|
||
@click="viewPatientDetail(patient)"
|
||
>
|
||
<view class="patient-avatar">
|
||
<image
|
||
v-if="patient.profile_picture"
|
||
:src="patient.profile_picture"
|
||
class="avatar-image"
|
||
/>
|
||
<text v-else class="avatar-text">{{ getPatientInitial(patient.name) }}</text>
|
||
</view>
|
||
<view class="patient-info">
|
||
<text class="patient-name">{{ patient.name }}</text>
|
||
<text class="patient-room">{{ patient.room_number }}床</text>
|
||
<text class="patient-status">{{ getHealthStatusText(patient.health_status) }}</text>
|
||
</view>
|
||
<view class="patient-indicators">
|
||
<view v-if="hasVitalSignsAlert(patient)" class="indicator vital-alert">
|
||
<text class="indicator-text">⚠️</text>
|
||
</view>
|
||
<view v-if="hasMedicationDue(patient)" class="indicator medication-due">
|
||
<text class="indicator-text">💊</text>
|
||
</view>
|
||
<view v-if="hasHealthAlert(patient)" class="indicator health-alert">
|
||
<text class="indicator-text">🚨</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="assignedPatients.length === 0" class="empty-state">
|
||
<text class="empty-text">暂无分配的患者</text>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
|
||
<!-- Recent Activities -->
|
||
<view class="activities-section">
|
||
<view class="section-header">
|
||
<text class="section-title">最近活动</text>
|
||
<button class="view-all-btn" @click="showAllActivities">
|
||
<text class="btn-text">查看全部</text>
|
||
</button>
|
||
</view>
|
||
<scroll-view class="activities-list" scroll-y="true" :style="{ height: '250px' }">
|
||
<view
|
||
v-for="activity in recentActivities"
|
||
:key="activity.id"
|
||
class="activity-item"
|
||
@click="viewActivityDetail(activity)"
|
||
>
|
||
<view class="activity-icon">
|
||
<text class="icon-text">{{ getActivityIcon(activity.record_type) }}</text>
|
||
</view>
|
||
<view class="activity-content">
|
||
<text class="activity-title">{{ activity.description }}</text>
|
||
<text class="activity-patient">{{ activity.elder_name }}</text>
|
||
<text class="activity-time">{{ formatDateTime(activity.created_at) }}</text>
|
||
</view>
|
||
<view class="activity-type">
|
||
<text class="type-text">{{ getRecordTypeText(activity.record_type) }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="recentActivities.length === 0" class="empty-state">
|
||
<text class="empty-text">暂无活动记录</text>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="uts">
|
||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||
import supa from '@/components/supadb/aksupainstance.uts'
|
||
|
||
import {
|
||
formatTime,
|
||
formatDate,
|
||
formatDateTime,
|
||
getCurrentTimeString,
|
||
getTodayStart,
|
||
getTodayEnd,
|
||
getRecentDate,
|
||
getPriorityText,
|
||
getTaskTypeText,
|
||
getRecordTypeText,
|
||
getHealthStatusText
|
||
} from '../types_new.uts'
|
||
|
||
// 数据类型定义
|
||
type NurseStats = {
|
||
assigned_patients: number
|
||
completed_tasks: number
|
||
pending_tasks: number
|
||
urgent_tasks: number
|
||
}
|
||
|
||
type CareTask = {
|
||
id: string
|
||
elder_id: string
|
||
elder_name: string
|
||
caregiver_id: string
|
||
task_type: string
|
||
task_name: string
|
||
description: string
|
||
scheduled_time: string
|
||
estimated_duration: number
|
||
status: string
|
||
priority: string
|
||
completion_notes: string
|
||
completed_at: string
|
||
created_at: string
|
||
}
|
||
|
||
type Elder = {
|
||
id: string
|
||
name: string
|
||
room_number: string
|
||
bed_number: string
|
||
health_status: string
|
||
care_level: string
|
||
profile_picture: string
|
||
status: string
|
||
created_at: string
|
||
}
|
||
|
||
type CareRecord = {
|
||
id: string
|
||
elder_id: string
|
||
elder_name: string
|
||
caregiver_id: string
|
||
record_type: string
|
||
description: string
|
||
timestamp: string
|
||
duration: number
|
||
notes: string
|
||
rating: number
|
||
created_at: string
|
||
}
|
||
|
||
// 响应式数据
|
||
const currentTime = ref<string>('')
|
||
const stats = ref<NurseStats>({
|
||
assigned_patients: 0,
|
||
completed_tasks: 0,
|
||
pending_tasks: 0,
|
||
urgent_tasks: 0
|
||
})
|
||
const todayTasks = ref<Array<CareTask>>([])
|
||
const assignedPatients = ref<Array<Elder>>([])
|
||
const recentActivities = ref<Array<CareRecord>>([])
|
||
const taskFilter = ref<string>('all')
|
||
|
||
let timeInterval: number = 0
|
||
|
||
// 计算属性
|
||
const filteredTasks = computed<Array<CareTask>>(() => {
|
||
switch (taskFilter.value) {
|
||
case 'pending':
|
||
return todayTasks.value.filter(task => task.status === 'pending')
|
||
case 'urgent':
|
||
return todayTasks.value.filter(task => task.priority === 'urgent')
|
||
default:
|
||
return todayTasks.value
|
||
}
|
||
})
|
||
|
||
// 生命周期
|
||
onMounted(() => {
|
||
loadData()
|
||
updateCurrentTime()
|
||
timeInterval = setInterval(updateCurrentTime, 60000)
|
||
})
|
||
|
||
onUnmounted(() => {
|
||
if (timeInterval) {
|
||
clearInterval(timeInterval)
|
||
}
|
||
})
|
||
|
||
// 更新当前时间
|
||
const updateCurrentTime = () => {
|
||
currentTime.value = getCurrentTimeString()
|
||
}
|
||
|
||
// 加载数据
|
||
const loadData = async () => {
|
||
await Promise.all([
|
||
loadStats(),
|
||
loadTodayTasks(),
|
||
loadAssignedPatients(),
|
||
loadRecentActivities()
|
||
])
|
||
}
|
||
|
||
// 加载统计数据
|
||
const loadStats = async () => {
|
||
try {
|
||
// 分配的患者数量
|
||
const patientsResult = await supa
|
||
.from('ec_elders')
|
||
.select('*', { count: 'exact' })
|
||
.eq('assigned_nurse', getUserId())
|
||
.eq('status', 'active')
|
||
.executeAs<Elder[]>()
|
||
|
||
// 今日完成任务数
|
||
const completedResult = await supa
|
||
.from('ec_care_tasks')
|
||
.select('*', { count: 'exact' })
|
||
.eq('caregiver_id', getUserId())
|
||
.eq('status', 'completed')
|
||
.gte('completed_at', getTodayStart())
|
||
.lte('completed_at', getTodayEnd())
|
||
.executeAs<CareTask[]>()
|
||
|
||
// 待处理任务数
|
||
const pendingResult = await supa
|
||
.from('ec_care_tasks')
|
||
.select('*', { count: 'exact' })
|
||
.eq('caregiver_id', getUserId())
|
||
.eq('status', 'pending')
|
||
.executeAs<CareTask[]>()
|
||
|
||
// 紧急任务数
|
||
const urgentResult = await supa
|
||
.from('ec_care_tasks')
|
||
.select('*', { count: 'exact' })
|
||
.eq('caregiver_id', getUserId())
|
||
.eq('priority', 'urgent')
|
||
.in('status', ['pending', 'in_progress'])
|
||
.executeAs<CareTask[]>()
|
||
|
||
stats.value = {
|
||
assigned_patients: patientsResult.count ?? 0,
|
||
completed_tasks: completedResult.count ?? 0,
|
||
pending_tasks: pendingResult.count ?? 0,
|
||
urgent_tasks: urgentResult.count ?? 0
|
||
}
|
||
} catch (error) {
|
||
console.error('加载统计数据失败:', error)
|
||
}
|
||
}
|
||
|
||
// 加载今日任务
|
||
const loadTodayTasks = async () => {
|
||
try {
|
||
const result = await supa
|
||
.from('ec_care_tasks')
|
||
.select(`
|
||
id,
|
||
elder_id,
|
||
elder_name,
|
||
caregiver_id,
|
||
task_type,
|
||
task_name,
|
||
description,
|
||
scheduled_time,
|
||
estimated_duration,
|
||
status,
|
||
priority,
|
||
completion_notes,
|
||
completed_at,
|
||
created_at
|
||
`)
|
||
.eq('caregiver_id', getUserId())
|
||
.gte('scheduled_time', getTodayStart())
|
||
.lte('scheduled_time', getTodayEnd())
|
||
.order('scheduled_time', { ascending: true })
|
||
.executeAs<CareTask[]>()
|
||
|
||
if (result.error === null && result.data !== null) {
|
||
todayTasks.value = result.data
|
||
}
|
||
} catch (error) {
|
||
console.error('加载今日任务失败:', error)
|
||
}
|
||
}
|
||
|
||
// 加载分配的患者
|
||
const loadAssignedPatients = async () => {
|
||
try {
|
||
const result = await supa
|
||
.from('ec_elders')
|
||
.select(`
|
||
id,
|
||
name,
|
||
room_number,
|
||
bed_number,
|
||
health_status,
|
||
care_level,
|
||
profile_picture,
|
||
status,
|
||
created_at
|
||
`)
|
||
.eq('assigned_nurse', getUserId())
|
||
.eq('status', 'active')
|
||
.order('room_number', { ascending: true })
|
||
.executeAs<Elder[]>()
|
||
|
||
if (result.error === null && result.data !== null) {
|
||
assignedPatients.value = result.data
|
||
}
|
||
} catch (error) {
|
||
console.error('加载分配患者失败:', error)
|
||
}
|
||
}
|
||
|
||
// 加载最近活动
|
||
const loadRecentActivities = async () => {
|
||
try {
|
||
const result = await supa
|
||
.from('ec_care_records')
|
||
.select(`
|
||
id,
|
||
elder_id,
|
||
elder_name,
|
||
caregiver_id,
|
||
record_type,
|
||
description,
|
||
timestamp,
|
||
duration,
|
||
notes,
|
||
rating,
|
||
created_at
|
||
`)
|
||
.eq('caregiver_id', getUserId())
|
||
.gte('created_at', getRecentDate(3))
|
||
.order('created_at', { ascending: false })
|
||
.limit(10)
|
||
.executeAs<CareRecord[]>()
|
||
|
||
if (result.error === null && result.data !== null) {
|
||
recentActivities.value = result.data
|
||
}
|
||
} catch (error) {
|
||
console.error('加载最近活动失败:', error)
|
||
}
|
||
}
|
||
|
||
// 辅助函数
|
||
const getUserId = (): string => {
|
||
// 从全局状态或存储中获取当前用户ID
|
||
return 'current_nurse_id'
|
||
}
|
||
|
||
const isTaskOverdue = (task: CareTask): boolean => {
|
||
if (task.status === 'completed') return false
|
||
const now = new Date()
|
||
const scheduledTime = new Date(task.scheduled_time)
|
||
return now > scheduledTime
|
||
}
|
||
|
||
const getPatientInitial = (name: string): string => {
|
||
return name.charAt(0).toUpperCase()
|
||
}
|
||
|
||
const hasHealthAlert = (patient: Elder): boolean => {
|
||
// 这里应该查询患者是否有活跃的健康提醒
|
||
return Math.random() > 0.8 // 示例
|
||
}
|
||
|
||
const hasVitalSignsAlert = (patient: Elder): boolean => {
|
||
// 这里应该查询患者是否有异常的生命体征
|
||
return Math.random() > 0.9 // 示例
|
||
}
|
||
|
||
const hasMedicationDue = (patient: Elder): boolean => {
|
||
// 这里应该查询患者是否有即将到期的用药
|
||
return Math.random() > 0.7 // 示例
|
||
}
|
||
|
||
const getActivityIcon = (recordType: string): string => {
|
||
const iconMap = new Map([
|
||
['medication', '💊'],
|
||
['hygiene', '🛁'],
|
||
['mobility', '🚶'],
|
||
['nutrition', '🍽️'],
|
||
['social', '👥'],
|
||
['medical', '🏥'],
|
||
['vital_signs', '❤️'],
|
||
['activity', '🎯']
|
||
])
|
||
return iconMap.get(recordType) ?? '📋'
|
||
}
|
||
|
||
// 事件处理
|
||
const setTaskFilter = (filter: string) => {
|
||
taskFilter.value = filter
|
||
}
|
||
|
||
const showUrgentTasks = () => {
|
||
taskFilter.value = 'urgent'
|
||
}
|
||
|
||
const showVitalSignsEntry = () => {
|
||
uni.navigateTo({ url: '/pages/ec/nurse/vital-signs-entry' })
|
||
}
|
||
|
||
const showPatientList = () => {
|
||
uni.navigateTo({ url: '/pages/ec/nurse/patient-list' })
|
||
}
|
||
|
||
const showVitalSigns = () => {
|
||
uni.navigateTo({ url: '/pages/ec/nurse/vital-signs' })
|
||
}
|
||
|
||
const showMedicationTasks = () => {
|
||
uni.navigateTo({ url: '/pages/ec/nurse/medication-tasks' })
|
||
}
|
||
|
||
const showCareRecords = () => {
|
||
uni.navigateTo({ url: '/pages/ec/nurse/care-records' })
|
||
}
|
||
|
||
const showNursingPlans = () => {
|
||
uni.navigateTo({ url: '/pages/ec/nurse/nursing-plans' })
|
||
}
|
||
|
||
const showIncidentReports = () => {
|
||
uni.navigateTo({ url: '/pages/ec/nurse/incident-reports' })
|
||
}
|
||
|
||
const showAllPatients = () => {
|
||
uni.navigateTo({ url: '/pages/ec/nurse/patient-list' })
|
||
}
|
||
|
||
const showAllActivities = () => {
|
||
uni.navigateTo({ url: '/pages/ec/nurse/activities' })
|
||
}
|
||
|
||
const openTask = (task: CareTask) => {
|
||
uni.navigateTo({
|
||
url: `/pages/ec/nurse/task-detail?id=${task.id}`
|
||
})
|
||
}
|
||
|
||
const startTask = async (task: CareTask) => {
|
||
try {
|
||
await supa
|
||
.from('ec_care_tasks')
|
||
.update({ status: 'in_progress' })
|
||
.eq('id', task.id)
|
||
.executeAs<any>()
|
||
|
||
// 更新本地状态
|
||
let taskIndex = -1
|
||
for (let i: Int = 0; i < todayTasks.value.length; i++) {
|
||
if (todayTasks.value[i].id === task.id) {
|
||
taskIndex = i
|
||
break
|
||
}
|
||
}
|
||
if (taskIndex >= 0) {
|
||
todayTasks.value[taskIndex].status = 'in_progress'
|
||
}
|
||
|
||
uni.showToast({ title: '任务已开始', icon: 'success' })
|
||
} catch (error) {
|
||
console.error('开始任务失败:', error)
|
||
uni.showToast({ title: '操作失败', icon: 'error' })
|
||
}
|
||
}
|
||
|
||
const completeTask = async (task: CareTask) => {
|
||
try {
|
||
await supa
|
||
.from('ec_care_tasks')
|
||
.update({
|
||
status: 'completed',
|
||
completed_at: new Date().toISOString()
|
||
})
|
||
.eq('id', task.id)
|
||
.executeAs<any>()
|
||
|
||
// 更新本地状态
|
||
let taskIndex = -1
|
||
for (let i: Int = 0; i < todayTasks.value.length; i++) {
|
||
if (todayTasks.value[i].id === task.id) {
|
||
taskIndex = i
|
||
break
|
||
}
|
||
}
|
||
if (taskIndex >= 0) {
|
||
todayTasks.value[taskIndex].status = 'completed'
|
||
todayTasks.value[taskIndex].completed_at = new Date().toISOString()
|
||
}
|
||
|
||
// 更新统计
|
||
stats.value.completed_tasks++
|
||
stats.value.pending_tasks = Math.max(0, stats.value.pending_tasks - 1)
|
||
|
||
uni.showToast({ title: '任务已完成', icon: 'success' })
|
||
} catch (error) {
|
||
console.error('完成任务失败:', error)
|
||
uni.showToast({ title: '操作失败', icon: 'error' })
|
||
}
|
||
}
|
||
|
||
const viewTaskDetail = (task: CareTask) => {
|
||
uni.navigateTo({
|
||
url: `/pages/ec/nurse/task-detail?id=${task.id}`
|
||
})
|
||
}
|
||
|
||
const viewPatientDetail = (patient: Elder) => {
|
||
uni.navigateTo({
|
||
url: `/pages/ec/nurse/patient-detail?id=${patient.id}`
|
||
})
|
||
}
|
||
|
||
const viewActivityDetail = (activity: CareRecord) => {
|
||
uni.navigateTo({
|
||
url: `/pages/ec/nurse/activity-detail?id=${activity.id}`
|
||
})
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
/* uts-android 兼容性重构:
|
||
1. 移除所有嵌套选择器、伪类(如 :last-child、&.xxx),全部 class 扁平化。
|
||
2. 所有间距用 margin-right/margin-bottom 控制,禁止 gap、flex-wrap、嵌套。
|
||
3. 所有布局 display: flex,禁止 grid、gap、伪类。
|
||
4. 组件间距、分隔线全部用 border/margin 控制。
|
||
5. 新增.is-last、.is-active、.is-urgent、.is-high、.is-completed、.is-overdue 等辅助 class。
|
||
*/
|
||
.nurse-dashboard {
|
||
padding: 20px;
|
||
background-color: #f5f5f5;
|
||
min-height: 600px;
|
||
}
|
||
.header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 30px;
|
||
padding: 20px;
|
||
background: linear-gradient(135deg, #56ccf2 0%, #2f80ed 100%);
|
||
border-radius: 15px;
|
||
color: white;
|
||
}
|
||
.header-title {
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
}
|
||
.header-subtitle {
|
||
font-size: 14px;
|
||
opacity: 0.8;
|
||
margin-top: 5px;
|
||
}
|
||
.header-actions {
|
||
display: flex;
|
||
flex-direction: row;
|
||
}
|
||
.action-btn {
|
||
padding: 8px 16px;
|
||
border-radius: 20px;
|
||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||
background-color: rgba(255, 255, 255, 0.1);
|
||
color: white;
|
||
margin-right: 10px;
|
||
}
|
||
.action-btn.is-last {
|
||
margin-right: 0;
|
||
}
|
||
.action-btn.is-urgent {
|
||
background-color: #ff4757;
|
||
border-color: #ff4757;
|
||
}
|
||
.btn-text {
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
}
|
||
.stats-section {
|
||
display: flex;
|
||
flex-direction: row;
|
||
flex-wrap: wrap;
|
||
margin-bottom: 30px;
|
||
}
|
||
.stat-card {
|
||
flex: 1 1 200rpx;
|
||
min-width: 120px;
|
||
padding: 20px;
|
||
background-color: white;
|
||
border-radius: 12px;
|
||
margin-right: 15px;
|
||
margin-bottom: 15px;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
.stat-card.is-last {
|
||
margin-right: 0;
|
||
}
|
||
.stat-card.is-urgent {
|
||
background: linear-gradient(135deg, #ff6b6b 0%, #ee5a6f 100%);
|
||
color: white;
|
||
}
|
||
.stat-icon {
|
||
font-size: 24px;
|
||
width: 40px;
|
||
height: 40px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background-color: rgba(0, 0, 0, 0.1);
|
||
border-radius: 50%;
|
||
margin-right: 15px;
|
||
}
|
||
.stat-content {
|
||
flex: 1;
|
||
}
|
||
.stat-number {
|
||
font-size: 28px;
|
||
font-weight: bold;
|
||
display: block;
|
||
}
|
||
.stat-label {
|
||
font-size: 14px;
|
||
opacity: 0.7;
|
||
margin-top: 5px;
|
||
}
|
||
.quick-actions {
|
||
margin-bottom: 30px;
|
||
}
|
||
.section-title {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
margin-bottom: 15px;
|
||
color: #333;
|
||
}
|
||
.actions-grid {
|
||
display: flex;
|
||
flex-direction: row;
|
||
flex-wrap: wrap;
|
||
}
|
||
.quick-action-btn {
|
||
flex: 1;
|
||
min-width: 120px;
|
||
padding: 20px;
|
||
background-color: white;
|
||
border-radius: 12px;
|
||
margin-right: 15px;
|
||
margin-bottom: 15px;
|
||
border: none;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
.quick-action-btn.is-last {
|
||
margin-right: 0;
|
||
}
|
||
.action-icon {
|
||
font-size: 24px;
|
||
}
|
||
.action-text {
|
||
font-size: 14px;
|
||
color: #666;
|
||
}
|
||
.tasks-section, .patients-section, .activities-section {
|
||
margin-bottom: 30px;
|
||
}
|
||
.section-header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 15px;
|
||
}
|
||
.task-filters {
|
||
display: flex;
|
||
flex-direction: row;
|
||
}
|
||
.filter-btn {
|
||
padding: 6px 12px;
|
||
border-radius: 15px;
|
||
border: 1px solid #ddd;
|
||
background-color: white;
|
||
color: #666;
|
||
margin-right: 8px;
|
||
}
|
||
.filter-btn.is-active {
|
||
background-color: #2f80ed;
|
||
color: white;
|
||
border-color: #2f80ed;
|
||
}
|
||
.filter-btn.is-last {
|
||
margin-right: 0;
|
||
}
|
||
.filter-text {
|
||
font-size: 12px;
|
||
}
|
||
.view-all-btn {
|
||
padding: 6px 12px;
|
||
border-radius: 15px;
|
||
border: 1px solid #ddd;
|
||
background-color: white;
|
||
color: #666;
|
||
}
|
||
.tasks-list, .patients-list, .activities-list {
|
||
background-color: white;
|
||
border-radius: 12px;
|
||
}
|
||
.task-item {
|
||
padding: 20px;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
}
|
||
.task-item.is-last {
|
||
border-bottom: none;
|
||
}
|
||
.task-item.is-urgent {
|
||
background-color: #fff2f0;
|
||
border-left: 4px solid #ff4d4f;
|
||
}
|
||
.task-item.is-high {
|
||
background-color: #fffbf0;
|
||
border-left: 4px solid #faad14;
|
||
}
|
||
.task-item.is-completed {
|
||
opacity: 0.6;
|
||
background-color: #f6ffed;
|
||
}
|
||
.task-item.is-overdue {
|
||
background-color: #fff1f0;
|
||
border-left: 4px solid #f5222d;
|
||
}
|
||
.task-header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
margin-bottom: 10px;
|
||
}
|
||
.task-info {
|
||
flex: 1;
|
||
}
|
||
.task-title {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #333;
|
||
display: block;
|
||
}
|
||
.task-patient {
|
||
font-size: 14px;
|
||
color: #666;
|
||
margin-top: 5px;
|
||
display: block;
|
||
}
|
||
.task-meta {
|
||
text-align: right;
|
||
}
|
||
.task-time {
|
||
font-size: 14px;
|
||
color: #666;
|
||
display: block;
|
||
}
|
||
.task-priority {
|
||
font-size: 12px;
|
||
margin-top: 5px;
|
||
display: block;
|
||
}
|
||
.task-priority.is-urgent { color: #ff4d4f; }
|
||
.task-priority.is-high { color: #faad14; }
|
||
.task-priority.is-medium { color: #1890ff; }
|
||
.task-priority.is-low { color: #52c41a; }
|
||
.task-content {
|
||
margin-bottom: 15px;
|
||
}
|
||
.task-description {
|
||
font-size: 14px;
|
||
color: #555;
|
||
display: block;
|
||
margin-bottom: 5px;
|
||
}
|
||
.task-type {
|
||
font-size: 12px;
|
||
color: #888;
|
||
}
|
||
.task-actions {
|
||
display: flex;
|
||
flex-direction: row;
|
||
margin-bottom: 10px;
|
||
}
|
||
.start-btn, .complete-btn, .detail-btn {
|
||
padding: 6px 12px;
|
||
border-radius: 15px;
|
||
border: none;
|
||
font-size: 12px;
|
||
color: white;
|
||
margin-right: 10px;
|
||
}
|
||
.start-btn {
|
||
background-color: #52c41a;
|
||
}
|
||
.complete-btn {
|
||
background-color: #1890ff;
|
||
}
|
||
.detail-btn {
|
||
background-color: #d9d9d9;
|
||
color: #666;
|
||
}
|
||
.patient-item {
|
||
padding: 15px;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
margin-bottom: 10px;
|
||
}
|
||
.patient-item.is-last {
|
||
border-bottom: none;
|
||
}
|
||
.patient-item.is-alert {
|
||
background-color: #fff7e6;
|
||
border-left: 4px solid #faad14;
|
||
}
|
||
.patient-avatar {
|
||
width: 50px;
|
||
height: 50px;
|
||
border-radius: 50%;
|
||
background-color: #f0f0f0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
overflow: hidden;
|
||
margin-right: 15px;
|
||
}
|
||
.avatar-image {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
}
|
||
.avatar-text {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
color: #666;
|
||
}
|
||
.patient-info {
|
||
flex: 1;
|
||
}
|
||
.patient-name {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #333;
|
||
display: block;
|
||
}
|
||
.patient-room {
|
||
font-size: 14px;
|
||
color: #666;
|
||
margin-top: 5px;
|
||
display: block;
|
||
}
|
||
.patient-status {
|
||
font-size: 12px;
|
||
color: #888;
|
||
margin-top: 5px;
|
||
display: block;
|
||
}
|
||
.patient-indicators {
|
||
display: flex;
|
||
flex-direction: row;
|
||
margin-left: 10px;
|
||
}
|
||
.indicator {
|
||
width: 24px;
|
||
height: 24px;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-right: 5px;
|
||
}
|
||
.indicator.is-last {
|
||
margin-right: 0;
|
||
}
|
||
.indicator.is-vital-alert {
|
||
background-color: #fff7e6;
|
||
}
|
||
.indicator.is-medication-due {
|
||
background-color: #e6f7ff;
|
||
}
|
||
.indicator.is-health-alert {
|
||
background-color: #fff2f0;
|
||
}
|
||
.activity-item {
|
||
padding: 15px;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
margin-bottom: 10px;
|
||
}
|
||
.activity-item.is-last {
|
||
border-bottom: none;
|
||
}
|
||
.activity-icon {
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 50%;
|
||
background-color: #f0f0f0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-right: 15px;
|
||
}
|
||
.icon-text {
|
||
font-size: 18px;
|
||
}
|
||
.activity-content {
|
||
flex: 1;
|
||
}
|
||
.activity-title {
|
||
font-size: 14px;
|
||
color: #333;
|
||
display: block;
|
||
}
|
||
.activity-patient {
|
||
font-size: 13px;
|
||
color: #666;
|
||
margin-top: 3px;
|
||
display: block;
|
||
}
|
||
.activity-time {
|
||
font-size: 12px;
|
||
color: #888;
|
||
margin-top: 3px;
|
||
display: block;
|
||
}
|
||
.activity-type {
|
||
margin-left: 10px;
|
||
}
|
||
.type-text {
|
||
font-size: 12px;
|
||
color: #888;
|
||
background-color: #f5f5f5;
|
||
padding: 2px 8px;
|
||
border-radius: 10px;
|
||
}
|
||
.empty-state {
|
||
padding: 40px;
|
||
text-align: center;
|
||
}
|
||
.empty-text {
|
||
font-size: 14px;
|
||
color: #999;
|
||
}
|
||
</style>
|