Преглед на файлове

feat(patient): 添加健康打卡功能

- 在首页添加打卡任务卡片UI组件
- 实现基于提醒设置的打卡任务生成功能
- 添加打卡状态切换和本地存储逻辑
- 实现打卡记录的过期清理机制
- 添加提醒设置更新事件监听
- 完善打卡任务按时间排序显示
- 添加打卡记录本地存储管理
- 实现组件挂载时的任务初始化加载
mcbaiyun преди 1 месец
родител
ревизия
40a990180e
променени са 2 файла, в които са добавени 268 реда и са изтрити 1 реда
  1. 3 0
      src/pages/patient/health/reminder.vue
  2. 265 1
      src/pages/patient/index/index.vue

+ 3 - 0
src/pages/patient/health/reminder.vue

@@ -568,6 +568,9 @@ const saveReminders = async () => {
       }
       ;(uni as any).setStorageSync('patientReminders', remindersToSave)
       ;(uni as any).setStorageSync('notificationsEnabled', notificationsEnabled.value)
+      
+      // 通知首页更新打卡任务
+      uni.$emit('reminderSettingsUpdated')
     } catch (e) {
       console.error('保存提醒设置到本地存储失败:', e)
     }

+ 265 - 1
src/pages/patient/index/index.vue

@@ -63,13 +63,41 @@
         </view>
       </view>
 
+      <!-- 打卡功能区域 -->
+      <view class="checkin-card" v-if="checkinTasks.length > 0">
+        <view class="card-header">
+          <text class="card-title">今日打卡</text>
+          <text class="card-subtitle">{{ currentDate }}</text>
+        </view>
+        <view class="card-content">
+          <view 
+            class="checkin-item" 
+            v-for="(task, index) in checkinTasks" 
+            :key="index"
+            @click="toggleCheckin(task)"
+          >
+            <view class="checkin-info">
+              <text class="checkin-time">{{ task.time }}</text>
+              <text class="checkin-type">{{ task.type }}</text>
+            </view>
+            <view class="checkin-status" :class="{ checked: task.checked }">
+              <uni-icons 
+                :type="task.checked ? 'checkbox-filled' : 'circle'" 
+                :color="task.checked ? '#07C160' : '#ddd'" 
+                size="24" 
+              />
+            </view>
+          </view>
+        </view>
+      </view>
+
     </view>
   </view>
   <TabBar />
 </template>
 
 <script setup lang="ts">
-import { ref, computed } from 'vue'
+import { ref, computed, onMounted } from 'vue'
 import { onShow } from '@dcloudio/uni-app'
 import CustomNav from '@/components/custom-nav.vue'
 import TabBar from '@/components/tab-bar.vue'
@@ -96,6 +124,19 @@ const avatarSrc = computed(() => {
   return defaultAvatarUrl
 })
 
+// 打卡任务相关数据
+const checkinTasks = ref<Array<{
+  time: string
+  type: string
+  checked: boolean
+}>>([])
+
+// 当前日期
+const currentDate = computed(() => {
+  const now = new Date()
+  return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`
+})
+
 const loadUser = () => {
   try {
     const u = uni.getStorageSync('user_info')
@@ -158,6 +199,137 @@ const fetchUserInfo = async () => {
   }
 }
 
+// 生成打卡任务
+const generateCheckinTasks = () => {
+  try {
+    // 获取本地存储的提醒设置
+    const patientReminders = uni.getStorageSync('patientReminders')
+    if (!patientReminders) {
+      console.log('未找到本地提醒设置')
+      return
+    }
+
+    // 获取今天的打卡记录
+    const today = currentDate.value
+    const checkinRecords = uni.getStorageSync('checkinRecords') || {}
+    const todayRecords = checkinRecords[today] || {}
+
+    const tasks: Array<{ time: string; type: string; checked: boolean }> = []
+
+    // 处理血压测量任务
+    if (patientReminders.bloodPressureReminder?.enabled) {
+      patientReminders.bloodPressureReminder.times.forEach((time: string) => {
+        tasks.push({
+          time,
+          type: '测量血压',
+          checked: !!todayRecords[`bloodPressure_${time}`]
+        })
+      })
+    }
+
+    // 处理血糖测量任务
+    if (patientReminders.bloodSugarReminder?.enabled) {
+      patientReminders.bloodSugarReminder.times.forEach((time: string) => {
+        tasks.push({
+          time,
+          type: '测量血糖',
+          checked: !!todayRecords[`bloodSugar_${time}`]
+        })
+      })
+    }
+
+    // 处理心率测量任务
+    if (patientReminders.heartRateReminder?.enabled) {
+      patientReminders.heartRateReminder.times.forEach((time: string) => {
+        tasks.push({
+          time,
+          type: '测量心率',
+          checked: !!todayRecords[`heartRate_${time}`]
+        })
+      })
+    }
+
+    // 处理用药提醒任务
+    if (patientReminders.medicationReminder?.enabled) {
+      patientReminders.medicationReminder.times.forEach((time: string) => {
+        tasks.push({
+          time,
+          type: '用药提醒',
+          checked: !!todayRecords[`medication_${time}`]
+        })
+      })
+    }
+
+    // 按时间排序
+    tasks.sort((a, b) => a.time.localeCompare(b.time))
+    
+    checkinTasks.value = tasks
+  } catch (e) {
+    console.error('生成打卡任务失败:', e)
+  }
+}
+
+// 切换打卡状态
+const toggleCheckin = (task: { time: string; type: string }) => {
+  try {
+    const today = currentDate.value
+    const checkinRecords = uni.getStorageSync('checkinRecords') || {}
+    
+    // 初始化今天的记录
+    if (!checkinRecords[today]) {
+      checkinRecords[today] = {}
+    }
+    
+    // 生成任务键名
+    let taskKey = ''
+    if (task.type === '测量血压') {
+      taskKey = `bloodPressure_${task.time}`
+    } else if (task.type === '测量血糖') {
+      taskKey = `bloodSugar_${task.time}`
+    } else if (task.type === '测量心率') {
+      taskKey = `heartRate_${task.time}`
+    } else if (task.type === '用药提醒') {
+      taskKey = `medication_${task.time}`
+    }
+    
+    // 切换打卡状态
+    checkinRecords[today][taskKey] = !checkinRecords[today][taskKey]
+    
+    // 保存到本地存储
+    uni.setStorageSync('checkinRecords', checkinRecords)
+    
+    // 更新界面显示
+    generateCheckinTasks()
+  } catch (e) {
+    console.error('切换打卡状态失败:', e)
+  }
+}
+
+// 清理过期的打卡记录
+const cleanExpiredCheckinRecords = () => {
+  try {
+    const checkinRecords = uni.getStorageSync('checkinRecords') || {}
+    const today = currentDate.value
+    const keys = Object.keys(checkinRecords)
+    
+    // 只保留最近7天的记录
+    const sevenDaysAgo = new Date()
+    sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7)
+    
+    const cleanedRecords: Record<string, any> = {}
+    keys.forEach(key => {
+      // 简单的日期比较(格式:YYYY-MM-DD)
+      if (key >= sevenDaysAgo.toISOString().split('T')[0] || key === today) {
+        cleanedRecords[key] = checkinRecords[key]
+      }
+    })
+    
+    uni.setStorageSync('checkinRecords', cleanedRecords)
+  } catch (e) {
+    console.error('清理过期打卡记录失败:', e)
+  }
+}
+
 // 如果在微信小程序端且未登录,自动跳转到登录页
 onShow(() => {
   const token = uni.getStorageSync('token')
@@ -166,9 +338,27 @@ onShow(() => {
     uni.reLaunch({ url: '/pages/public/login/index' })
   } else {
     fetchUserInfo()
+    // 生成打卡任务
+    generateCheckinTasks()
+    // 清理过期记录
+    cleanExpiredCheckinRecords()
   }
 })
 
+// 监听提醒设置更新事件
+onMounted(() => {
+  uni.$on('reminderSettingsUpdated', () => {
+    // 延迟一点时间确保本地存储已更新
+    setTimeout(() => {
+      generateCheckinTasks()
+    }, 100)
+  })
+})
+
+// 在组件卸载前移除事件监听
+// 注意:在实际的uni-app项目中,可能需要根据具体生命周期进行调整
+// 如果存在onUnload钩子,应该在那里移除事件监听器
+
 function handleScan(res: any) {
   return handleQrScanResult(res)
 }
@@ -358,4 +548,78 @@ function onQrClick() {
   font-size: 28rpx;
   color: #666;
 }
+
+.checkin-card {
+  background-color: #fff;
+  border-radius: 20rpx;
+  margin: 20rpx;
+  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
+  overflow: hidden;
+}
+
+.card-header {
+  padding: 20rpx;
+  border-bottom: 1rpx solid #eee;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.card-title {
+  font-size: 36rpx;
+  font-weight: bold;
+  color: #333;
+}
+
+.card-subtitle {
+  font-size: 28rpx;
+  color: #666;
+}
+
+.card-content {
+  padding: 20rpx;
+}
+
+.checkin-item {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 20rpx;
+  border-radius: 10rpx;
+  background-color: #f9f9f9;
+  margin-bottom: 20rpx;
+}
+
+.checkin-item:last-child {
+  margin-bottom: 0;
+}
+
+.checkin-info {
+  display: flex;
+  flex-direction: column;
+}
+
+.checkin-time {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 10rpx;
+}
+
+.checkin-type {
+  font-size: 28rpx;
+  color: #666;
+}
+
+.checkin-status {
+  width: 40rpx;
+  height: 40rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.checkin-status.checked {
+  color: #07C160;
+}
 </style>