1256 lines
32 KiB
Plaintext
1256 lines
32 KiB
Plaintext
<!-- 护理任务执行 - 重构版本 -->
|
|
<template>
|
|
<view class="task-execution">
|
|
<!-- Header -->
|
|
<view class="header">
|
|
<text class="header-title">护理任务</text>
|
|
<view class="header-info">
|
|
<text class="current-shift">{{ currentShift }}</text>
|
|
<text class="current-time">{{ currentTime }}</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- Task Summary -->
|
|
<view class="summary-section">
|
|
<view class="summary-card pending">
|
|
<view class="summary-icon">⏳</view>
|
|
<view class="summary-content">
|
|
<text class="summary-number">{{ stats.pending_tasks }}</text>
|
|
<text class="summary-label">待处理</text>
|
|
</view>
|
|
</view>
|
|
<view class="summary-card progress">
|
|
<view class="summary-icon">🔄</view>
|
|
<view class="summary-content">
|
|
<text class="summary-number">{{ stats.in_progress_tasks }}</text>
|
|
<text class="summary-label">进行中</text>
|
|
</view>
|
|
</view>
|
|
<view class="summary-card completed">
|
|
<view class="summary-icon">✅</view>
|
|
<view class="summary-content">
|
|
<text class="summary-number">{{ stats.completed_tasks }}</text>
|
|
<text class="summary-label">已完成</text>
|
|
</view>
|
|
</view>
|
|
<view class="summary-card overdue">
|
|
<view class="summary-icon">⚠️</view>
|
|
<view class="summary-content">
|
|
<text class="summary-number">{{ stats.overdue_tasks }}</text>
|
|
<text class="summary-label">已逾期</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- Quick Actions -->
|
|
<view class="quick-actions">
|
|
<button class="quick-btn" @click="showEmergencyReport">
|
|
<text class="quick-icon">🚨</text>
|
|
<text class="quick-text">紧急上报</text>
|
|
</button>
|
|
<button class="quick-btn" @click="showQuickRecord">
|
|
<text class="quick-icon">📝</text>
|
|
<text class="quick-text">快速记录</text>
|
|
</button>
|
|
<button class="quick-btn" @click="showElderList">
|
|
<text class="quick-icon">👥</text>
|
|
<text class="quick-text">负责老人</text>
|
|
</button>
|
|
<button class="quick-btn" @click="refreshTasks">
|
|
<text class="quick-icon">🔄</text>
|
|
<text class="quick-text">刷新任务</text>
|
|
</button>
|
|
</view>
|
|
|
|
<!-- Filter Tabs -->
|
|
<view class="filter-tabs">
|
|
<button
|
|
class="filter-tab"
|
|
:class="{ active: activeTab === 'urgent' }"
|
|
@click="setActiveTab('urgent')"
|
|
>
|
|
<text class="tab-text">紧急任务</text>
|
|
<text class="tab-count">{{ urgentTasks.length }}</text>
|
|
</button>
|
|
<button
|
|
class="filter-tab"
|
|
:class="{ active: activeTab === 'scheduled' }"
|
|
@click="setActiveTab('scheduled')"
|
|
>
|
|
<text class="tab-text">计划任务</text>
|
|
<text class="tab-count">{{ scheduledTasks.length }}</text>
|
|
</button>
|
|
<button
|
|
class="filter-tab"
|
|
:class="{ active: activeTab === 'completed' }"
|
|
@click="setActiveTab('completed')"
|
|
>
|
|
<text class="tab-text">已完成</text>
|
|
<text class="tab-count">{{ completedTasks.length }}</text>
|
|
</button>
|
|
</view>
|
|
|
|
<!-- Task List -->
|
|
<view class="tasks-section">
|
|
<scroll-view class="tasks-list" scroll-y="true" :style="{ height: '500px' }">
|
|
<view
|
|
v-for="task in filteredTasks"
|
|
:key="task.id"
|
|
class="task-item"
|
|
:class="getTaskStatusClass(task)"
|
|
@click="viewTaskDetail(task)"
|
|
>
|
|
<view class="task-header">
|
|
<view class="task-priority">
|
|
<text class="priority-icon">{{ getPriorityIcon(task.priority) }}</text>
|
|
</view>
|
|
<view class="task-info">
|
|
<text class="task-name">{{ task.task_name }}</text>
|
|
<text class="elder-name">{{ task.elder_name }}</text>
|
|
</view>
|
|
<view class="task-time">
|
|
<text class="scheduled-time">{{ formatTime(task.scheduled_time) }}</text>
|
|
<text class="task-status">{{ getTaskStatusText(task.status) }}</text>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="task-details">
|
|
<view class="detail-row">
|
|
<text class="detail-label">位置:</text>
|
|
<text class="detail-value">{{ task.room_number || '未指定' }}</text>
|
|
</view>
|
|
<view class="detail-row" v-if="task.estimated_duration">
|
|
<text class="detail-label">预计时长:</text>
|
|
<text class="detail-value">{{ task.estimated_duration }}分钟</text>
|
|
</view>
|
|
<view class="detail-row" v-if="task.description">
|
|
<text class="detail-label">备注:</text>
|
|
<text class="detail-value">{{ task.description }}</text>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="task-actions">
|
|
<button
|
|
v-if="task.status === 'pending'"
|
|
class="action-btn primary"
|
|
@click.stop="startTask(task)"
|
|
>
|
|
<text class="btn-text">开始</text>
|
|
</button>
|
|
<button
|
|
v-if="task.status === 'in_progress'"
|
|
class="action-btn success"
|
|
@click.stop="completeTask(task)"
|
|
>
|
|
<text class="btn-text">完成</text>
|
|
</button>
|
|
<button
|
|
v-if="task.status === 'in_progress'"
|
|
class="action-btn warning"
|
|
@click.stop="pauseTask(task)"
|
|
>
|
|
<text class="btn-text">暂停</text>
|
|
</button>
|
|
<button
|
|
class="action-btn info"
|
|
@click.stop="addNote(task)"
|
|
>
|
|
<text class="btn-text">记录</text>
|
|
</button>
|
|
</view>
|
|
</view>
|
|
</scroll-view>
|
|
</view>
|
|
|
|
<!-- Task Detail Modal -->
|
|
<view v-if="showDetailModal" class="modal-overlay" @click="hideDetailModal">
|
|
<view class="modal-content" @click.stop>
|
|
<view class="modal-header">
|
|
<text class="modal-title">{{ currentTask?.task_name }}</text>
|
|
<button class="close-btn" @click="hideDetailModal">
|
|
<text class="close-text">✕</text>
|
|
</button>
|
|
</view>
|
|
|
|
<view class="modal-body">
|
|
<view class="task-summary">
|
|
<view class="summary-row">
|
|
<text class="summary-label">老人:</text>
|
|
<text class="summary-value">{{ currentTask?.elder_name }}</text>
|
|
</view>
|
|
<view class="summary-row">
|
|
<text class="summary-label">房间:</text>
|
|
<text class="summary-value">{{ currentTask?.room_number }}</text>
|
|
</view>
|
|
<view class="summary-row">
|
|
<text class="summary-label">计划时间:</text>
|
|
<text class="summary-value">{{ formatDateTime(currentTask?.scheduled_time) }}</text>
|
|
</view>
|
|
<view class="summary-row">
|
|
<text class="summary-label">优先级:</text>
|
|
<text class="summary-value">{{ getTaskPriorityText(currentTask?.priority) }}</text>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="task-description" v-if="currentTask?.description">
|
|
<text class="desc-label">任务描述:</text>
|
|
<text class="desc-content">{{ currentTask.description }}</text>
|
|
</view>
|
|
|
|
<view class="execution-form" v-if="currentTask?.status === 'in_progress'">
|
|
<view class="form-group">
|
|
<text class="form-label">执行状态</text>
|
|
<picker
|
|
:value="executionStatusIndex"
|
|
:range="executionStatusOptions"
|
|
range-key="label"
|
|
@change="onExecutionStatusChange"
|
|
>
|
|
<text class="picker-text">{{ executionStatus?.label || '选择状态' }}</text>
|
|
</picker>
|
|
</view>
|
|
|
|
<view class="form-group">
|
|
<text class="form-label">执行记录</text>
|
|
<textarea
|
|
class="form-textarea"
|
|
placeholder="请记录执行情况、老人状态等"
|
|
v-model="executionRecord.notes"
|
|
/>
|
|
</view>
|
|
|
|
<view class="form-group">
|
|
<text class="form-label">老人状态</text>
|
|
<picker
|
|
:value="elderConditionIndex"
|
|
:range="elderConditionOptions"
|
|
range-key="label"
|
|
@change="onElderConditionChange"
|
|
>
|
|
<text class="picker-text">{{ elderCondition?.label || '选择状态' }}</text>
|
|
</picker>
|
|
</view>
|
|
|
|
<view class="form-group">
|
|
<text class="form-label">异常情况</text>
|
|
<textarea
|
|
class="form-textarea"
|
|
placeholder="如有异常情况请详细描述"
|
|
v-model="executionRecord.issues_notes"
|
|
/>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="modal-footer">
|
|
<button class="cancel-btn" @click="hideDetailModal">
|
|
<text class="btn-text">关闭</text>
|
|
</button>
|
|
<button
|
|
v-if="currentTask?.status === 'pending'"
|
|
class="confirm-btn"
|
|
@click="startTaskFromModal"
|
|
>
|
|
<text class="btn-text">开始任务</text>
|
|
</button>
|
|
<button
|
|
v-if="currentTask?.status === 'in_progress'"
|
|
class="confirm-btn"
|
|
@click="completeTaskFromModal"
|
|
>
|
|
<text class="btn-text">完成任务</text>
|
|
</button>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- Quick Record Modal -->
|
|
<view v-if="showQuickRecordModal" class="modal-overlay" @click="hideQuickRecordModal">
|
|
<view class="modal-content" @click.stop>
|
|
<view class="modal-header">
|
|
<text class="modal-title">快速记录</text>
|
|
<button class="close-btn" @click="hideQuickRecordModal">
|
|
<text class="close-text">✕</text>
|
|
</button>
|
|
</view>
|
|
|
|
<view class="modal-body">
|
|
<view class="form-group">
|
|
<text class="form-label">选择老人</text>
|
|
<picker
|
|
:value="quickRecordElderIndex"
|
|
:range="elderOptions"
|
|
range-key="name"
|
|
@change="onQuickRecordElderChange"
|
|
>
|
|
<text class="picker-text">{{ quickRecordElder?.name || '选择老人' }}</text>
|
|
</picker>
|
|
</view>
|
|
|
|
<view class="form-group">
|
|
<text class="form-label">记录类型</text>
|
|
<picker
|
|
:value="quickRecordTypeIndex"
|
|
:range="recordTypeOptions"
|
|
range-key="label"
|
|
@change="onQuickRecordTypeChange"
|
|
>
|
|
<text class="picker-text">{{ quickRecordType?.label || '选择类型' }}</text>
|
|
</picker>
|
|
</view>
|
|
|
|
<view class="form-group">
|
|
<text class="form-label">记录内容</text>
|
|
<textarea
|
|
class="form-textarea"
|
|
placeholder="请输入记录内容"
|
|
v-model="quickRecord.content"
|
|
/>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="modal-footer">
|
|
<button class="cancel-btn" @click="hideQuickRecordModal">
|
|
<text class="btn-text">取消</text>
|
|
</button>
|
|
<button class="confirm-btn" @click="saveQuickRecord">
|
|
<text class="btn-text">保存</text>
|
|
</button>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup lang="uts">
|
|
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
|
import { formatDateTime, formatTime, getTaskStatusText, getTaskPriorityText, getPriorityIcon } from '../types.uts'
|
|
import type { CareTask, Elder, CareRecord } from '../types.uts'
|
|
|
|
// Task stats type
|
|
type TaskStats = {
|
|
pending_tasks: number
|
|
in_progress_tasks: number
|
|
completed_tasks: number
|
|
overdue_tasks: number
|
|
}
|
|
|
|
// Execution record type
|
|
type ExecutionRecord = {
|
|
notes: string
|
|
issues_notes: string
|
|
elder_condition: string
|
|
status: string
|
|
}
|
|
|
|
// Quick record type
|
|
type QuickRecord = {
|
|
elder_id: string
|
|
type: string
|
|
content: string
|
|
}
|
|
|
|
// Data
|
|
const tasks = ref<CareTask[]>([])
|
|
const stats = ref<TaskStats>({
|
|
pending_tasks: 0,
|
|
in_progress_tasks: 0,
|
|
completed_tasks: 0,
|
|
overdue_tasks: 0
|
|
})
|
|
|
|
const currentTime = ref('')
|
|
const currentShift = ref('白班')
|
|
const activeTab = ref('urgent')
|
|
|
|
// Modal states
|
|
const showDetailModal = ref(false)
|
|
const showQuickRecordModal = ref(false)
|
|
const currentTask = ref<CareTask | null>(null)
|
|
|
|
// Form data
|
|
const executionRecord = ref<ExecutionRecord>({
|
|
notes: '',
|
|
issues_notes: '',
|
|
elder_condition: '',
|
|
status: ''
|
|
})
|
|
|
|
const quickRecord = ref<QuickRecord>({
|
|
elder_id: '',
|
|
type: '',
|
|
content: ''
|
|
})
|
|
|
|
// Options
|
|
const executionStatusOptions = ref([
|
|
{ value: 'normal', label: '正常执行' },
|
|
{ value: 'partial', label: '部分完成' },
|
|
{ value: 'delayed', label: '延期执行' },
|
|
{ value: 'cancelled', label: '取消执行' }
|
|
])
|
|
|
|
const elderConditionOptions = ref([
|
|
{ value: 'good', label: '状态良好' },
|
|
{ value: 'fair', label: '状态一般' },
|
|
{ value: 'poor', label: '状态较差' },
|
|
{ value: 'emergency', label: '紧急情况' }
|
|
])
|
|
|
|
const recordTypeOptions = ref([
|
|
{ value: 'health', label: '健康状况' },
|
|
{ value: 'behavior', label: '行为记录' },
|
|
{ value: 'mood', label: '情绪状态' },
|
|
{ value: 'diet', label: '饮食情况' },
|
|
{ value: 'sleep', label: '睡眠情况' },
|
|
{ value: 'medication', label: '用药情况' },
|
|
{ value: 'activity', label: '活动参与' },
|
|
{ value: 'other', label: '其他记录' }
|
|
])
|
|
|
|
const elderOptions = ref<Elder[]>([])
|
|
|
|
// Form indexes
|
|
const executionStatusIndex = ref(-1)
|
|
const elderConditionIndex = ref(-1)
|
|
const quickRecordElderIndex = ref(-1)
|
|
const quickRecordTypeIndex = ref(-1)
|
|
|
|
// Timer
|
|
let timeTimer: number | null = null
|
|
|
|
// Computed
|
|
const executionStatus = computed<any>(() => {
|
|
return executionStatusIndex.value >= 0 ? executionStatusOptions.value[executionStatusIndex.value] : null
|
|
})
|
|
|
|
const elderCondition = computed<any>(() => {
|
|
return elderConditionIndex.value >= 0 ? elderConditionOptions.value[elderConditionIndex.value] : null
|
|
})
|
|
|
|
const quickRecordElder = computed<Elder | null>(() => {
|
|
return quickRecordElderIndex.value >= 0 ? elderOptions.value[quickRecordElderIndex.value] : null
|
|
})
|
|
|
|
const quickRecordType = computed<any>(() => {
|
|
return quickRecordTypeIndex.value >= 0 ? recordTypeOptions.value[quickRecordTypeIndex.value] : null
|
|
})
|
|
|
|
const urgentTasks = computed<CareTask[]>(() => {
|
|
return tasks.value.filter(task =>
|
|
task.priority === 'urgent' || task.priority === 'high' || isTaskOverdue(task)
|
|
)
|
|
})
|
|
|
|
const scheduledTasks = computed<CareTask[]>(() => {
|
|
return tasks.value.filter(task =>
|
|
task.status === 'pending' || task.status === 'in_progress'
|
|
)
|
|
})
|
|
|
|
const completedTasks = computed<CareTask[]>(() => {
|
|
return tasks.value.filter(task => task.status === 'completed')
|
|
})
|
|
|
|
const filteredTasks = computed<CareTask[]>(() => {
|
|
switch (activeTab.value) {
|
|
case 'urgent':
|
|
return urgentTasks.value
|
|
case 'scheduled':
|
|
return scheduledTasks.value
|
|
case 'completed':
|
|
return completedTasks.value
|
|
default:
|
|
return tasks.value
|
|
}
|
|
})
|
|
|
|
// Methods
|
|
const loadTasks = async (): Promise<void> => {
|
|
try {
|
|
const response = await supa.executeAs('rpc/get_caregiver_tasks', {
|
|
caregiver_id: getCurrentCaregiver()
|
|
})
|
|
if (response.success && response.data) {
|
|
tasks.value = response.data as CareTask[]
|
|
}
|
|
} catch (error) {
|
|
console.error('加载任务列表失败:', error)
|
|
}
|
|
}
|
|
|
|
const loadStats = async (): Promise<void> => {
|
|
try {
|
|
const response = await supa.executeAs('rpc/get_task_stats', {
|
|
caregiver_id: getCurrentCaregiver()
|
|
})
|
|
if (response.success && response.data && response.data.length > 0) {
|
|
stats.value = response.data[0] as TaskStats
|
|
}
|
|
} catch (error) {
|
|
console.error('加载统计数据失败:', error)
|
|
}
|
|
}
|
|
|
|
const loadElders = async (): Promise<void> => {
|
|
try {
|
|
const response = await supa.executeAs('rpc/get_caregiver_elders', {
|
|
caregiver_id: getCurrentCaregiver()
|
|
})
|
|
if (response.success && response.data) {
|
|
elderOptions.value = response.data as Elder[]
|
|
}
|
|
} catch (error) {
|
|
console.error('加载老人列表失败:', error)
|
|
}
|
|
}
|
|
|
|
const getCurrentCaregiver = (): string => {
|
|
// 这里应该从用户状态或本地存储获取当前护理员ID
|
|
return 'current_caregiver_id'
|
|
}
|
|
|
|
const isTaskOverdue = (task: CareTask): boolean => {
|
|
if (!task.scheduled_time) return false
|
|
const scheduledTime = new Date(task.scheduled_time)
|
|
const now = new Date()
|
|
return scheduledTime < now && task.status !== 'completed'
|
|
}
|
|
|
|
const getTaskStatusClass = (task: CareTask): string => {
|
|
const classes = []
|
|
|
|
if (isTaskOverdue(task)) {
|
|
classes.push('overdue')
|
|
}
|
|
|
|
switch (task.priority) {
|
|
case 'urgent':
|
|
classes.push('priority-urgent')
|
|
break
|
|
case 'high':
|
|
classes.push('priority-high')
|
|
break
|
|
}
|
|
|
|
switch (task.status) {
|
|
case 'pending':
|
|
classes.push('status-pending')
|
|
break
|
|
case 'in_progress':
|
|
classes.push('status-progress')
|
|
break
|
|
case 'completed':
|
|
classes.push('status-completed')
|
|
break
|
|
}
|
|
|
|
return classes.join(' ')
|
|
}
|
|
|
|
const updateCurrentTime = (): void => {
|
|
const now = new Date()
|
|
currentTime.value = now.toLocaleTimeString('zh-CN', {
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
})
|
|
|
|
const hour = now.getHours()
|
|
if (hour >= 6 && hour < 14) {
|
|
currentShift.value = '白班'
|
|
} else if (hour >= 14 && hour < 22) {
|
|
currentShift.value = '夜班'
|
|
} else {
|
|
currentShift.value = '夜班'
|
|
}
|
|
}
|
|
|
|
// Event handlers
|
|
const setActiveTab = (tab: string): void => {
|
|
activeTab.value = tab
|
|
}
|
|
|
|
const refreshTasks = async (): Promise<void> => {
|
|
await Promise.all([
|
|
loadTasks(),
|
|
loadStats()
|
|
])
|
|
}
|
|
|
|
const viewTaskDetail = (task: CareTask): void => {
|
|
currentTask.value = task
|
|
executionRecord.value = {
|
|
notes: '',
|
|
issues_notes: '',
|
|
elder_condition: '',
|
|
status: ''
|
|
}
|
|
executionStatusIndex.value = -1
|
|
elderConditionIndex.value = -1
|
|
showDetailModal.value = true
|
|
}
|
|
|
|
const startTask = async (task: CareTask): Promise<void> => {
|
|
try {
|
|
const response = await supa.executeAs('update', {
|
|
table: 'care_tasks',
|
|
data: {
|
|
status: 'in_progress',
|
|
start_time: new Date().toISOString()
|
|
},
|
|
match: { id: task.id }
|
|
})
|
|
if (response.success) {
|
|
await refreshTasks()
|
|
}
|
|
} catch (error) {
|
|
console.error('开始任务失败:', error)
|
|
}
|
|
}
|
|
|
|
const completeTask = async (task: CareTask): Promise<void> => {
|
|
try {
|
|
const response = await supa.executeAs('update', {
|
|
table: 'care_tasks',
|
|
data: {
|
|
status: 'completed',
|
|
end_time: new Date().toISOString()
|
|
},
|
|
match: { id: task.id }
|
|
})
|
|
if (response.success) {
|
|
await refreshTasks()
|
|
}
|
|
} catch (error) {
|
|
console.error('完成任务失败:', error)
|
|
}
|
|
}
|
|
|
|
const pauseTask = async (task: CareTask): Promise<void> => {
|
|
try {
|
|
const response = await supa.executeAs('update', {
|
|
table: 'care_tasks',
|
|
data: { status: 'pending' },
|
|
match: { id: task.id }
|
|
})
|
|
if (response.success) {
|
|
await refreshTasks()
|
|
}
|
|
} catch (error) {
|
|
console.error('暂停任务失败:', error)
|
|
}
|
|
}
|
|
|
|
const addNote = (task: CareTask): void => {
|
|
currentTask.value = task
|
|
showDetailModal.value = true
|
|
}
|
|
|
|
// Modal methods
|
|
const hideDetailModal = (): void => {
|
|
showDetailModal.value = false
|
|
currentTask.value = null
|
|
}
|
|
|
|
const startTaskFromModal = async (): Promise<void> => {
|
|
if (currentTask.value) {
|
|
await startTask(currentTask.value)
|
|
hideDetailModal()
|
|
}
|
|
}
|
|
|
|
const completeTaskFromModal = async (): Promise<void> => {
|
|
if (!currentTask.value) return
|
|
|
|
try {
|
|
// 更新任务状态
|
|
const taskResponse = await supa.executeAs('update', {
|
|
table: 'care_tasks',
|
|
data: {
|
|
status: 'completed',
|
|
end_time: new Date().toISOString()
|
|
},
|
|
match: { id: currentTask.value.id }
|
|
})
|
|
|
|
// 保存护理记录
|
|
const recordResponse = await supa.executeAs('insert', {
|
|
table: 'care_records',
|
|
data: {
|
|
task_id: currentTask.value.id,
|
|
elder_id: currentTask.value.elder_id,
|
|
caregiver_id: getCurrentCaregiver(),
|
|
care_content: executionRecord.value.notes,
|
|
elder_condition: executionRecord.value.elder_condition,
|
|
issues_notes: executionRecord.value.issues_notes,
|
|
status: 'completed'
|
|
}
|
|
})
|
|
|
|
if (taskResponse.success && recordResponse.success) {
|
|
hideDetailModal()
|
|
await refreshTasks()
|
|
}
|
|
} catch (error) {
|
|
console.error('完成任务失败:', error)
|
|
}
|
|
}
|
|
|
|
const showEmergencyReport = (): void => {
|
|
console.log('显示紧急上报')
|
|
}
|
|
|
|
const showQuickRecord = (): void => {
|
|
quickRecord.value = {
|
|
elder_id: '',
|
|
type: '',
|
|
content: ''
|
|
}
|
|
quickRecordElderIndex.value = -1
|
|
quickRecordTypeIndex.value = -1
|
|
showQuickRecordModal.value = true
|
|
}
|
|
|
|
const hideQuickRecordModal = (): void => {
|
|
showQuickRecordModal.value = false
|
|
}
|
|
|
|
const showElderList = (): void => {
|
|
console.log('显示负责老人列表')
|
|
}
|
|
|
|
const onExecutionStatusChange = (e: any): void => {
|
|
executionStatusIndex.value = e.detail.value
|
|
if (executionStatus.value) {
|
|
executionRecord.value.status = executionStatus.value.value
|
|
}
|
|
}
|
|
|
|
const onElderConditionChange = (e: any): void => {
|
|
elderConditionIndex.value = e.detail.value
|
|
if (elderCondition.value) {
|
|
executionRecord.value.elder_condition = elderCondition.value.value
|
|
}
|
|
}
|
|
|
|
const onQuickRecordElderChange = (e: any): void => {
|
|
quickRecordElderIndex.value = e.detail.value
|
|
if (quickRecordElder.value) {
|
|
quickRecord.value.elder_id = quickRecordElder.value.id
|
|
}
|
|
}
|
|
|
|
const onQuickRecordTypeChange = (e: any): void => {
|
|
quickRecordTypeIndex.value = e.detail.value
|
|
if (quickRecordType.value) {
|
|
quickRecord.value.type = quickRecordType.value.value
|
|
}
|
|
}
|
|
|
|
const saveQuickRecord = async (): Promise<void> => {
|
|
if (!quickRecord.value.elder_id || !quickRecord.value.content.trim()) return
|
|
|
|
try {
|
|
const response = await supa.executeAs('insert', {
|
|
table: 'care_records',
|
|
data: {
|
|
elder_id: quickRecord.value.elder_id,
|
|
caregiver_id: getCurrentCaregiver(),
|
|
care_content: quickRecord.value.content,
|
|
status: 'completed'
|
|
}
|
|
})
|
|
|
|
if (response.success) {
|
|
hideQuickRecordModal()
|
|
}
|
|
} catch (error) {
|
|
console.error('保存快速记录失败:', error)
|
|
}
|
|
}
|
|
|
|
// Lifecycle
|
|
onMounted(async () => {
|
|
updateCurrentTime()
|
|
timeTimer = setInterval(updateCurrentTime, 60000) // 每分钟更新一次
|
|
|
|
await Promise.all([
|
|
loadTasks(),
|
|
loadStats(),
|
|
loadElders()
|
|
])
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
if (timeTimer) {
|
|
clearInterval(timeTimer)
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
.task-execution {
|
|
padding: 20px;
|
|
background: #f5f7fa;
|
|
min-height: 100vh;
|
|
|
|
.header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 24px;
|
|
padding: 20px;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
border-radius: 12px;
|
|
color: white;
|
|
|
|
.header-title {
|
|
font-size: 24px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.header-info {
|
|
text-align: right;
|
|
|
|
.current-shift {
|
|
display: block;
|
|
font-size: 16px;
|
|
font-weight: 500;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.current-time {
|
|
font-size: 14px;
|
|
opacity: 0.9;
|
|
}
|
|
}
|
|
}
|
|
|
|
.summary-section {
|
|
display: flex;
|
|
gap: 16px;
|
|
margin-bottom: 24px;
|
|
flex-wrap: wrap;
|
|
|
|
.summary-card {
|
|
flex: 1;
|
|
min-width: 120px;
|
|
padding: 16px;
|
|
background: white;
|
|
border-radius: 12px;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
|
|
&.pending {
|
|
border-left: 4px solid #f59e0b;
|
|
}
|
|
|
|
&.progress {
|
|
border-left: 4px solid #3b82f6;
|
|
}
|
|
|
|
&.completed {
|
|
border-left: 4px solid #10b981;
|
|
}
|
|
|
|
&.overdue {
|
|
border-left: 4px solid #ef4444;
|
|
}
|
|
|
|
.summary-icon {
|
|
font-size: 24px;
|
|
}
|
|
|
|
.summary-content {
|
|
.summary-number {
|
|
display: block;
|
|
font-size: 24px;
|
|
font-weight: 700;
|
|
color: #1a202c;
|
|
margin-bottom: 2px;
|
|
}
|
|
|
|
.summary-label {
|
|
font-size: 12px;
|
|
color: #64748b;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.quick-actions {
|
|
display: flex;
|
|
gap: 12px;
|
|
margin-bottom: 24px;
|
|
flex-wrap: wrap;
|
|
|
|
.quick-btn {
|
|
flex: 1;
|
|
min-width: 100px;
|
|
padding: 16px;
|
|
background: white;
|
|
border: 2px solid #e5e7eb;
|
|
border-radius: 12px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 8px;
|
|
|
|
&:hover {
|
|
border-color: #4a90e2;
|
|
background: #f8fafc;
|
|
}
|
|
|
|
.quick-icon {
|
|
font-size: 24px;
|
|
}
|
|
|
|
.quick-text {
|
|
font-size: 12px;
|
|
color: #374151;
|
|
font-weight: 500;
|
|
}
|
|
}
|
|
}
|
|
|
|
.filter-tabs {
|
|
display: flex;
|
|
gap: 12px;
|
|
margin-bottom: 24px;
|
|
background: white;
|
|
padding: 8px;
|
|
border-radius: 12px;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
|
.filter-tab {
|
|
flex: 1;
|
|
padding: 12px 16px;
|
|
background: transparent;
|
|
border: none;
|
|
border-radius: 8px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 4px;
|
|
|
|
&.active {
|
|
background: #4a90e2;
|
|
|
|
.tab-text, .tab-count {
|
|
color: white;
|
|
}
|
|
}
|
|
|
|
.tab-text {
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
color: #374151;
|
|
}
|
|
|
|
.tab-count {
|
|
font-size: 12px;
|
|
color: #6b7280;
|
|
font-weight: 600;
|
|
}
|
|
}
|
|
}
|
|
|
|
.tasks-section {
|
|
background: white;
|
|
border-radius: 12px;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
|
.tasks-list {
|
|
padding: 16px;
|
|
|
|
.task-item {
|
|
padding: 16px;
|
|
border: 1px solid #e5e7eb;
|
|
border-radius: 12px;
|
|
margin-bottom: 12px;
|
|
background: white;
|
|
|
|
&.overdue {
|
|
border-left: 4px solid #ef4444;
|
|
background: #fef2f2;
|
|
}
|
|
|
|
&.priority-urgent {
|
|
border-left: 4px solid #dc2626;
|
|
}
|
|
|
|
&.priority-high {
|
|
border-left: 4px solid #f59e0b;
|
|
}
|
|
|
|
&.status-progress {
|
|
background: #f0f9ff;
|
|
border-color: #3b82f6;
|
|
}
|
|
|
|
&.status-completed {
|
|
background: #f6f6f6;
|
|
opacity: 0.8;
|
|
}
|
|
|
|
.task-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
margin-bottom: 12px;
|
|
|
|
.task-priority {
|
|
.priority-icon {
|
|
font-size: 20px;
|
|
}
|
|
}
|
|
|
|
.task-info {
|
|
flex: 1;
|
|
|
|
.task-name {
|
|
display: block;
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
color: #1a202c;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.elder-name {
|
|
font-size: 14px;
|
|
color: #6b7280;
|
|
}
|
|
}
|
|
|
|
.task-time {
|
|
text-align: right;
|
|
|
|
.scheduled-time {
|
|
display: block;
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
color: #374151;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.task-status {
|
|
font-size: 12px;
|
|
color: #6b7280;
|
|
}
|
|
}
|
|
}
|
|
|
|
.task-details {
|
|
margin-bottom: 12px;
|
|
|
|
.detail-row {
|
|
display: flex;
|
|
gap: 8px;
|
|
margin-bottom: 4px;
|
|
font-size: 14px;
|
|
|
|
.detail-label {
|
|
color: #6b7280;
|
|
min-width: 60px;
|
|
}
|
|
|
|
.detail-value {
|
|
color: #374151;
|
|
flex: 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
.task-actions {
|
|
display: flex;
|
|
gap: 8px;
|
|
justify-content: flex-end;
|
|
|
|
.action-btn {
|
|
padding: 6px 12px;
|
|
border: none;
|
|
border-radius: 6px;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
|
|
&.primary {
|
|
background: #3b82f6;
|
|
color: white;
|
|
}
|
|
|
|
&.success {
|
|
background: #10b981;
|
|
color: white;
|
|
}
|
|
|
|
&.warning {
|
|
background: #f59e0b;
|
|
color: white;
|
|
}
|
|
|
|
&.info {
|
|
background: #6b7280;
|
|
color: white;
|
|
}
|
|
|
|
.btn-text {
|
|
color: inherit;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.modal-overlay {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: rgba(0, 0, 0, 0.5);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 1000;
|
|
|
|
.modal-content {
|
|
background: white;
|
|
border-radius: 12px;
|
|
width: 90%;
|
|
max-width: 500px;
|
|
max-height: 80vh;
|
|
overflow-y: auto;
|
|
|
|
.modal-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 20px;
|
|
border-bottom: 1px solid #e5e7eb;
|
|
|
|
.modal-title {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
color: #1a202c;
|
|
}
|
|
|
|
.close-btn {
|
|
padding: 4px;
|
|
background: none;
|
|
border: none;
|
|
font-size: 18px;
|
|
color: #6b7280;
|
|
}
|
|
}
|
|
|
|
.modal-body {
|
|
padding: 20px;
|
|
|
|
.task-summary {
|
|
padding: 16px;
|
|
background: #f8fafc;
|
|
border-radius: 8px;
|
|
margin-bottom: 20px;
|
|
|
|
.summary-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin-bottom: 8px;
|
|
|
|
.summary-label {
|
|
font-weight: 500;
|
|
color: #374151;
|
|
}
|
|
|
|
.summary-value {
|
|
color: #1a202c;
|
|
}
|
|
}
|
|
}
|
|
|
|
.task-description {
|
|
margin-bottom: 20px;
|
|
|
|
.desc-label {
|
|
display: block;
|
|
font-weight: 500;
|
|
color: #374151;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.desc-content {
|
|
color: #6b7280;
|
|
line-height: 1.5;
|
|
}
|
|
}
|
|
|
|
.execution-form, .form-group {
|
|
margin-bottom: 16px;
|
|
|
|
.form-label {
|
|
display: block;
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
color: #374151;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.form-textarea {
|
|
width: 100%;
|
|
padding: 10px 16px;
|
|
border: 1px solid #d1d5db;
|
|
border-radius: 8px;
|
|
font-size: 14px;
|
|
color: #374151;
|
|
height: 80px;
|
|
resize: vertical;
|
|
}
|
|
|
|
.picker-text {
|
|
padding: 10px 16px;
|
|
border: 1px solid #d1d5db;
|
|
border-radius: 8px;
|
|
background: white;
|
|
color: #374151;
|
|
width: 100%;
|
|
display: block;
|
|
}
|
|
}
|
|
}
|
|
|
|
.modal-footer {
|
|
display: flex;
|
|
gap: 12px;
|
|
justify-content: flex-end;
|
|
padding: 20px;
|
|
border-top: 1px solid #e5e7eb;
|
|
|
|
.cancel-btn, .confirm-btn {
|
|
padding: 10px 20px;
|
|
border: none;
|
|
border-radius: 8px;
|
|
font-size: 14px;
|
|
|
|
.btn-text {
|
|
color: inherit;
|
|
}
|
|
}
|
|
|
|
.cancel-btn {
|
|
background: #f3f4f6;
|
|
color: #374151;
|
|
}
|
|
|
|
.confirm-btn {
|
|
background: #4a90e2;
|
|
color: white;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</style>
|