Initial commit of akmon project

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

868
pages/ec/tasks/list.uvue Normal file
View File

@@ -0,0 +1,868 @@
<template>
<view class="task-management">
<view class="header">
<text class="title">护理任务</text>
<button class="add-btn" @click="addNewTask">
<text class="btn-text"> 新建</text>
</button>
</view>
<!-- 任务统计 -->
<view class="task-stats">
<view class="stat-item">
<text class="stat-number">{{ taskStats.pending }}</text>
<text class="stat-label">待处理</text>
</view>
<view class="stat-item">
<text class="stat-number">{{ taskStats.in_progress }}</text>
<text class="stat-label">进行中</text>
</view>
<view class="stat-item">
<text class="stat-number">{{ taskStats.completed }}</text>
<text class="stat-label">已完成</text>
</view>
<view class="stat-item">
<text class="stat-number">{{ taskStats.overdue }}</text>
<text class="stat-label">已逾期</text>
</view>
</view>
<!-- 筛选器 -->
<view class="filter-section">
<scroll-view class="filter-scroll" direction="horizontal" :show-scrollbar="false">
<view class="filter-item" :class="{ active: selectedStatus === 'all' }" @click="filterByStatus('all')">
全部
</view>
<view class="filter-item" :class="{ active: selectedStatus === 'pending' }" @click="filterByStatus('pending')">
待处理
</view>
<view class="filter-item" :class="{ active: selectedStatus === 'in_progress' }" @click="filterByStatus('in_progress')">
进行中
</view>
<view class="filter-item" :class="{ active: selectedStatus === 'completed' }" @click="filterByStatus('completed')">
已完成
</view>
<view class="filter-item" :class="{ active: selectedPriority === 'urgent' }" @click="filterByPriority('urgent')">
紧急任务
</view>
</scroll-view>
</view>
<!-- 任务列表 -->
<view class="tasks-section">
<scroll-view class="tasks-list" direction="vertical" :refresher-enabled="true"
:refresher-triggered="isRefreshing" @refresherrefresh="refreshTasks">
<view class="task-card" v-for="task in filteredTasks" :key="task.id"
:class="getTaskCardClass(task)" @click="viewTaskDetail(task)">
<view class="task-header">
<view class="task-priority" :class="task.priority">
<text class="priority-text">{{ getPriorityText(task.priority) }}</text>
</view>
<view class="task-status" :class="task.status">
<text class="status-text">{{ getTaskStatusText(task.status) }}</text>
</view>
</view>
<view class="task-content">
<text class="task-title">{{ task.title }}</text>
<view class="task-info">
<view class="info-item">
<text class="info-icon">👤</text>
<text class="info-text">{{ task.elder_name || '未分配' }}</text>
</view>
<view class="info-item">
<text class="info-icon">🕐</text>
<text class="info-text">{{ formatDateTime(task.scheduled_time) }}</text>
</view>
<view class="info-item" v-if="task.assigned_to_name">
<text class="info-icon">👩‍⚕️</text>
<text class="info-text">{{ task.assigned_to_name }}</text>
</view>
</view>
</view>
<view class="task-actions">
<button class="action-btn" v-if="task.status === 'pending'" @click.stop="startTask(task)">
<text class="btn-text">开始</text>
</button>
<button class="action-btn" v-if="task.status === 'in_progress'" @click.stop="completeTask(task)">
<text class="btn-text">完成</text>
</button>
<button class="action-btn secondary" @click.stop="editTask(task)">
<text class="btn-text">编辑</text>
</button>
</view>
</view>
<!-- 空状态 -->
<view class="empty-state" v-if="filteredTasks.length === 0 && !isLoading">
<text class="empty-icon">📋</text>
<text class="empty-title">暂无任务</text>
<text class="empty-description">{{ getEmptyStateText() }}</text>
<button class="empty-action" @click="addNewTask">创建第一个任务</button>
</view>
<!-- 加载状态 -->
<view class="loading-state" v-if="isLoading">
<text class="loading-text">加载中...</text>
</view>
</scroll-view>
</view>
</view>
</template>
<script lang="uts">
import { CareTask, TaskStats } from '../types.uts'
export default {
data() {
return {
tasks: [] as CareTask[],
taskStats: {
pending: 0,
in_progress: 0,
completed: 0,
overdue: 0
} as TaskStats,
selectedStatus: 'all',
selectedPriority: '',
isLoading: false,
isRefreshing: false
}
},
computed: {
filteredTasks(): CareTask[] {
let filtered = this.tasks
// 按状态筛选
if (this.selectedStatus !== 'all') {
filtered = filtered.filter(task => task.status === this.selectedStatus)
}
// 按优先级筛选
if (this.selectedPriority === 'urgent') {
filtered = filtered.filter(task => task.priority === 'urgent' || task.priority === 'high')
}
// 按时间排序,逾期的优先显示
return filtered.sort((a, b) => {
const now = new Date()
const aScheduled = new Date(a.scheduled_time)
const bScheduled = new Date(b.scheduled_time)
// 逾期任务优先
const aOverdue = aScheduled < now && a.status !== 'completed'
const bOverdue = bScheduled < now && b.status !== 'completed'
if (aOverdue && !bOverdue) return -1
if (!aOverdue && bOverdue) return 1
// 按计划时间排序
return aScheduled.getTime() - bScheduled.getTime()
})
}
},
onLoad() {
this.loadTasks()
},
onShow() {
this.loadTasks()
},
methods: {
async loadTasks() {
this.isLoading = true
try {
const [tasksResult, statsResult] = await Promise.all([
supa.executeAs('care_tasks', {}),
supa.executeAs('task_stats', {})
])
if (tasksResult.success) {
this.tasks = tasksResult.data as CareTask[]
}
if (statsResult.success && statsResult.data.length > 0) {
this.taskStats = statsResult.data[0] as TaskStats
}
} catch (error) {
console.error('加载任务失败:', error)
uni.showToast({
title: '加载失败',
icon: 'error'
})
} finally {
this.isLoading = false
}
},
async refreshTasks() {
this.isRefreshing = true
await this.loadTasks()
this.isRefreshing = false
},
filterByStatus(status: string) {
this.selectedStatus = status
this.selectedPriority = ''
},
filterByPriority(priority: string) {
this.selectedPriority = priority
this.selectedStatus = 'all'
},
async startTask(task: CareTask) {
try {
const result = await supa.executeAs('update_task_status', {
task_id: task.id,
status: 'in_progress',
started_at: new Date().toISOString()
})
if (result.success) {
uni.showToast({
title: '任务已开始',
icon: 'success'
})
this.loadTasks()
}
} catch (error) {
console.error('开始任务失败:', error)
uni.showToast({
title: '操作失败',
icon: 'error'
})
}
},
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.loadTasks()
}
} catch (error) {
console.error('完成任务失败:', error)
uni.showToast({
title: '操作失败',
icon: 'error'
})
}
},
addNewTask() {
uni.navigateTo({
url: '/pages/ec/tasks/form'
})
},
editTask(task: CareTask) {
uni.navigateTo({
url: `/pages/ec/tasks/form?id=${task.id}`
})
},
viewTaskDetail(task: CareTask) {
uni.navigateTo({
url: `/pages/ec/tasks/detail?id=${task.id}`
})
},
getTaskCardClass(task: CareTask): string {
const classes = ['task-card']
if (task.status === 'overdue' || this.isTaskOverdue(task)) {
classes.push('overdue')
}
if (task.priority === 'urgent') {
classes.push('urgent')
}
return classes.join(' ')
},
isTaskOverdue(task: CareTask): boolean {
const now = new Date()
const scheduled = new Date(task.scheduled_time)
return scheduled < now && task.status !== 'completed'
},
getPriorityText(priority: string): string {
const priorityMap = {
'low': '低',
'normal': '普通',
'high': '高',
'urgent': '紧急'
}
return priorityMap[priority] || '普通'
},
getTaskStatusText(status: string): string {
const statusMap = {
'pending': '待处理',
'in_progress': '进行中',
'completed': '已完成',
'cancelled': '已取消',
'overdue': '已逾期'
}
return statusMap[status] || '未知'
},
formatDateTime(timestamp: string): string {
if (!timestamp) return ''
const date = new Date(timestamp)
const now = new Date()
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
const taskDate = new Date(date.getFullYear(), date.getMonth(), date.getDate())
const time = `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`
if (taskDate.getTime() === today.getTime()) {
return `今天 ${time}`
} else if (taskDate.getTime() === today.getTime() - 24 * 60 * 60 * 1000) {
return `昨天 ${time}`
} else if (taskDate.getTime() === today.getTime() + 24 * 60 * 60 * 1000) {
return `明天 ${time}`
} else {
return `${date.getMonth() + 1}/${date.getDate()} ${time}`
}
},
getEmptyStateText(): string {
if (this.selectedStatus !== 'all') {
const statusMap = {
'pending': '没有待处理的任务',
'in_progress': '没有进行中的任务',
'completed': '没有已完成的任务'
}
return statusMap[this.selectedStatus] || '没有相关任务'
}
if (this.selectedPriority === 'urgent') {
return '没有紧急任务'
}
return '还没有创建任何任务'
}
}
}
const tasks = ref<Array<CareTask>>([])
const filteredTasks = ref<Array<CareTask>>([])
const selectedStatus = ref<string>('all')
const selectedPriority = ref<string>('all')
const isLoading = ref<boolean>(false)
const isRefreshing = ref<boolean>(false)
// 任务统计
const taskStats = ref({
pending: 0,
in_progress: 0,
completed: 0,
overdue: 0
})
// 加载任务数据
const loadTasks = async () => {
try {
isLoading.value = true
const result = await supa
.from('ec_care_tasks')
.select(`
id,
task_name,
elder_name,
scheduled_time,
status,
priority,
caregiver_name,
due_date,
created_at
`)
.order('scheduled_time', { ascending: true })
.executeAs<Array<CareTask>>()
if (result.error === null && result.data !== null) {
tasks.value = result.data
applyFilters()
updateTaskStats()
}
} catch (error) {
console.error('加载任务数据失败:', error)
} finally {
isLoading.value = false
}
}
// 更新任务统计
const updateTaskStats = () => {
const now = new Date()
taskStats.value = {
pending: tasks.value.filter(task => task.status === 'pending').length,
in_progress: tasks.value.filter(task => task.status === 'in_progress').length,
completed: tasks.value.filter(task => task.status === 'completed').length,
overdue: tasks.value.filter(task => {
const dueDate = new Date(task.scheduled_time)
return task.status !== 'completed' && dueDate < now
}).length
}
}
// 应用筛选
const applyFilters = () => {
let filtered = tasks.value
// 状态筛选
if (selectedStatus.value !== 'all') {
filtered = filtered.filter(task => task.status === selectedStatus.value)
}
// 优先级筛选
if (selectedPriority.value === 'urgent') {
filtered = filtered.filter(task => task.priority === 'urgent')
}
filteredTasks.value = filtered
}
// 状态筛选
const filterByStatus = (status: string) => {
selectedStatus.value = status
selectedPriority.value = 'all' // 重置优先级筛选
applyFilters()
}
// 优先级筛选
const filterByPriority = (priority: string) => {
selectedPriority.value = priority
selectedStatus.value = 'all' // 重置状态筛选
applyFilters()
}
// 刷新任务
const refreshTasks = async () => {
isRefreshing.value = true
await loadTasks()
isRefreshing.value = false
}
// 获取优先级文本
const getPriorityText = (priority: string): string => {
switch (priority) {
case 'urgent': return '紧急'
case 'high': return '高'
case 'normal': return '普通'
case 'low': return '低'
default: return priority
}
}
// 获取任务卡片样式类
const getTaskCardClass = (task: CareTask): string => {
const classes = ['task-card']
// 优先级样式
if (task.priority === 'urgent') {
classes.push('urgent')
} else if (task.priority === 'high') {
classes.push('high-priority')
}
// 逾期样式
const now = new Date()
const dueDate = new Date(task.scheduled_time)
if (task.status !== 'completed' && dueDate < now) {
classes.push('overdue')
}
return classes.join(' ')
}
// 开始任务
const startTask = async (task: CareTask) => {
try {
await supa
.from('ec_care_tasks')
.update({
status: 'in_progress',
updated_at: new Date().toISOString()
})
.eq('id', task.id)
.executeAs<any>()
// 更新本地数据
task.status = 'in_progress'
updateTaskStats()
uni.showToast({
title: '任务已开始',
icon: 'success'
})
// 导航到任务执行页面
uni.navigateTo({
url: `/pages/ec/tasks/execute?id=${task.id}`
})
} 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',
updated_at: new Date().toISOString()
})
.eq('id', task.id)
.executeAs<any>()
// 更新本地数据
task.status = 'completed'
updateTaskStats()
uni.showToast({
title: '任务已完成',
icon: 'success'
})
} 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 editTask = (task: CareTask) => {
uni.navigateTo({
url: `/pages/ec/tasks/edit?id=${task.id}`
})
}
// 新建任务
const addNewTask = () => {
uni.navigateTo({
url: '/pages/ec/tasks/add'
})
}
// 获取空状态文本
const getEmptyStateText = (): string => {
if (selectedStatus.value !== 'all') {
return `没有${getTaskStatusText(selectedStatus.value)}的任务`
}
if (selectedPriority.value === 'urgent') {
return '没有紧急任务'
}
return '还没有创建任何任务,点击下方按钮创建第一个任务'
}
// 生命周期
onMounted(() => {
loadTasks()
})
</script>
<style scoped>
.task-management {
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;
}
.add-btn {
background-color: #1890ff;
color: #fff;
border: none;
border-radius: 6px;
padding: 10px 16px;
font-size: 14px;
}
/* 任务统计 */
.task-stats {
display: flex;
flex-direction: row;
background-color: #fff;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.stat-item {
flex: 1;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
}
.stat-number {
font-size: 24px;
font-weight: bold;
color: #1890ff;
margin-bottom: 5px;
}
.stat-label {
font-size: 12px;
color: #666;
}
/* 筛选器 */
.filter-section {
margin-bottom: 20px;
}
.filter-scroll {
height: 50px;
}
.filter-item {
display: inline-block;
padding: 8px 16px;
margin-right: 10px;
background-color: #fff;
border: 1px solid #d9d9d9;
border-radius: 20px;
font-size: 14px;
color: #666;
white-space: nowrap;
}
.filter-item.active {
background-color: #1890ff;
color: #fff;
border-color: #1890ff;
}
/* 任务列表 */
.tasks-section {
flex: 1;
}
.tasks-list {
height: 100%;
}
.task-card {
background-color: #fff;
border-radius: 8px;
padding: 16px;
margin-bottom: 12px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
border-left: 4px solid #d9d9d9;
}
.task-card.urgent {
border-left-color: #ff4d4f;
}
.task-card.high-priority {
border-left-color: #fa8c16;
}
.task-card.overdue {
background-color: #fff2f0;
border-left-color: #ff4d4f;
}
.task-header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.task-priority {
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
}
.task-priority.urgent {
background-color: #ff4d4f;
color: #fff;
}
.task-priority.high {
background-color: #fa8c16;
color: #fff;
}
.task-priority.normal {
background-color: #1890ff;
color: #fff;
}
.task-priority.low {
background-color: #52c41a;
color: #fff;
}
.task-status {
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
}
.task-status.pending {
background-color: #fff7e6;
color: #d48806;
}
.task-status.in_progress {
background-color: #e6f7ff;
color: #1890ff;
}
.task-status.completed {
background-color: #f6ffed;
color: #52c41a;
}
.task-content {
margin-bottom: 12px;
}
.task-title {
font-size: 16px;
font-weight: bold;
color: #333;
margin-bottom: 8px;
}
.task-info {
display: flex;
flex-direction: column;
}
.info-item {
display: flex;
flex-direction: row;
align-items: center;
margin-bottom: 4px;
}
.info-icon {
margin-right: 8px;
font-size: 14px;
}
.info-text {
font-size: 14px;
color: #666;
}
.task-actions {
display: flex;
flex-direction: row;
justify-content: flex-end;
}
.action-btn {
padding: 6px 12px;
border-radius: 4px;
border: none;
font-size: 12px;
margin-left: 8px;
background-color: #1890ff;
color: #fff;
}
.action-btn.secondary {
background-color: #fff;
color: #666;
border: 1px solid #d9d9d9;
}
/* 空状态 */
.empty-state {
text-align: center;
padding: 60px 20px;
}
.empty-icon {
font-size: 64px;
margin-bottom: 20px;
}
.empty-title {
font-size: 18px;
color: #333;
margin-bottom: 10px;
}
.empty-description {
font-size: 14px;
color: #666;
margin-bottom: 30px;
}
.empty-action {
background-color: #1890ff;
color: #fff;
border: none;
border-radius: 6px;
padding: 12px 24px;
font-size: 14px;
}
/* 加载状态 */
.loading-state {
text-align: center;
padding: 40px 20px;
}
.loading-text {
font-size: 14px;
color: #666;
}
</style>