Răsfoiți Sursa

feat(health): 添加健康提醒功能并优化月份选择器

- 在健康数据页面添加健康提醒入口按钮
- 新增健康提醒页面路由配置
- 将身高详情页的月份选择器改为年月双列选择
- 优化月份选择器的显示逻辑和数据处理方式
- 更新相关样式以支持新的提醒功能界面
mcbaiyun 2 luni în urmă
părinte
comite
cb10129bc5

+ 4 - 0
src/pages.json

@@ -42,6 +42,10 @@
 			"path": "pages/health/details/heart-rate",
 			"style": { "navigationBarTitleText": "心率" }
 		},
+		{
+			"path": "pages/health/reminder",
+			"style": { "navigationBarTitleText": "健康提醒" }
+		},
 		{
 			"path": "pages/profile/index",
 			"style": {

+ 16 - 12
src/pages/health/details/height.vue

@@ -4,12 +4,11 @@
     <view class="header">
       <view class="month-selector">
         <button class="btn" @click="prevMonth">‹</button>
-        <view class="month-label">{{ displayYear }}年 {{ displayMonth }}月</view>
+        <picker mode="multiSelector" :value="pickerValue" :range="pickerRange" @change="onPickerChange">
+          <view class="month-label">{{ displayYear }}年 {{ displayMonth }}月</view>
+        </picker>
         <button class="btn" @click="nextMonth">›</button>
       </view>
-      <picker mode="date" :value="pickerValue" @change="onPickerChange">
-        <view class="picker-display">切换月份</view>
-      </picker>
     </view>
 
     <!-- 趋势图 - 简化canvas设置 -->
@@ -87,7 +86,13 @@ type RecordItem = { id: string; date: string; height: number }
 
 // 当前展示年月
 const current = ref(new Date())
-const pickerValue = ref(formatPickerDate(current.value))
+const pickerValue = ref([current.value.getFullYear() - 2000, current.value.getMonth()]) // 年从2000年开始,月0-11
+
+// 年月选择器的选项范围
+const pickerRange = ref([
+  Array.from({ length: 50 }, (_, i) => `${2000 + i}年`), // 2000-2049年
+  Array.from({ length: 12 }, (_, i) => `${i + 1}月`) // 1-12月
+])
 
 // 明确的canvas尺寸(将由 getCanvasSize 初始化以匹配设备宽度)
 const canvasWidth = ref(700) // 初始值,会在 mounted 时覆盖
@@ -602,7 +607,7 @@ async function prevMonth() {
   const d = new Date(current.value)
   d.setMonth(d.getMonth() - 1)
   current.value = d
-  pickerValue.value = formatPickerDate(d)
+  pickerValue.value = [d.getFullYear() - 2000, d.getMonth()]
   records.value = generateMockRecords(d)
   await rebuildChart()
 }
@@ -611,20 +616,19 @@ async function nextMonth() {
   const d = new Date(current.value)
   d.setMonth(d.getMonth() + 1)
   current.value = d
-  pickerValue.value = formatPickerDate(d)
+  pickerValue.value = [d.getFullYear() - 2000, d.getMonth()]
   records.value = generateMockRecords(d)
   await rebuildChart()
 }
 
 async function onPickerChange(e: any) {
   const val = e?.detail?.value || e
-  const parts = (val as string).split('-')
-  if (parts.length >= 2) {
-    const y = parseInt(parts[0], 10)
-    const m = parseInt(parts[1], 10) - 1
+  if (Array.isArray(val) && val.length >= 2) {
+    const y = 2000 + val[0]
+    const m = val[1]
     const d = new Date(y, m, 1)
     current.value = d
-    pickerValue.value = formatPickerDate(d)
+    pickerValue.value = [val[0], val[1]]
     records.value = generateMockRecords(d)
     await rebuildChart()
   }

+ 48 - 0
src/pages/health/index.vue

@@ -39,6 +39,14 @@
         </view>
       </view>
     </view>
+
+    <view class="reminder-card">
+      <view class="reminder-button" @click="openReminder">
+        <image src="/static/icons/remixicon/alarm-line.svg" class="reminder-icon" mode="widthFix" />
+        <text class="reminder-text">健康提醒</text>
+        <uni-icons class="reminder-arrow" type="arrowright" size="20" color="#c0c0c0" />
+      </view>
+    </view>
   </view>
   <TabBar />
 </template>
@@ -54,6 +62,10 @@ const title = ref('健康数据')
 const openDetail = (type: string) => {
   uni.navigateTo({ url: `/pages/health/details/${type}` })
 }
+
+const openReminder = () => {
+  uni.navigateTo({ url: '/pages/health/reminder' })
+}
 </script>
 
 <style scoped>
@@ -124,4 +136,40 @@ const openDetail = (type: string) => {
   margin-right: 30rpx;
   display: inline-block;
 }
+
+.reminder-card {
+  margin-top: 20rpx;
+  padding: 50rpx 0rpx;
+}
+
+.reminder-button {
+  display: flex;
+  justify-content: flex-start;
+  align-items: center;
+  padding: 30rpx 40rpx;
+  background-color: #fff;
+  border-radius: 12rpx;
+}
+
+.reminder-text {
+  font-size: 32rpx;
+  color: #000000;
+  flex: 1;
+  letter-spacing: 1rpx;
+}
+
+.reminder-arrow {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  width: 44rpx;
+  height: 44rpx;
+}
+
+.reminder-icon {
+  width: 40rpx;
+  height: 40rpx;
+  margin-right: 30rpx;
+  display: inline-block;
+}
 </style>

+ 84 - 0
src/pages/health/reminder.vue

@@ -0,0 +1,84 @@
+<template>
+  <CustomNav title="健康提醒" leftType="back" />
+  <view class="content">
+    <view class="reminder-list">
+      <view class="reminder-item" v-for="(reminder, index) in reminders" :key="index">
+        <view class="reminder-info">
+          <text class="reminder-title">{{ reminder.title }}</text>
+          <text class="reminder-time">{{ reminder.time }}</text>
+        </view>
+        <switch :checked="reminder.enabled" @change="toggleReminder(index)" />
+      </view>
+    </view>
+  </view>
+  <TabBar />
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+
+import CustomNav from '@/components/custom-nav.vue'
+import TabBar from '@/components/tab-bar.vue'
+
+interface Reminder {
+  title: string
+  time: string
+  enabled: boolean
+}
+
+const reminders = ref<Reminder[]>([
+  { title: '喝水提醒', time: '08:00', enabled: true },
+  { title: '运动提醒', time: '18:00', enabled: false },
+  { title: '测量血压', time: '07:00', enabled: true },
+  { title: '测量血糖', time: '12:00', enabled: false },
+  { title: '服药提醒', time: '09:00', enabled: true }
+])
+
+const toggleReminder = (index: number) => {
+  reminders.value[index].enabled = !reminders.value[index].enabled
+}
+</script>
+
+<style scoped>
+.content {
+  padding: calc(var(--status-bar-height) + 44px) 50rpx 50rpx 50rpx;
+  min-height: 100vh;
+  background-color: #f5f5f5;
+  box-sizing: border-box;
+}
+
+.reminder-list {
+  background-color: #fff;
+  border-radius: 12rpx;
+  overflow: hidden;
+  margin-top: 50rpx;
+}
+
+.reminder-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 30rpx 40rpx;
+  border-bottom: 1rpx solid #eee;
+}
+
+.reminder-item:last-child {
+  border-bottom: none;
+}
+
+.reminder-info {
+  flex: 1;
+}
+
+.reminder-title {
+  font-size: 32rpx;
+  color: #000000;
+  display: block;
+  margin-bottom: 10rpx;
+}
+
+.reminder-time {
+  font-size: 28rpx;
+  color: #5a5a5a;
+}
+</style>

+ 1 - 0
src/static/icons/remixicon/alarm-line.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="32" height="32" fill="rgba(149,149,149,1)"><path d="M12.0001 22.0001C7.02956 22.0001 3.00012 17.9707 3.00012 13.0001C3.00012 8.02956 7.02956 4.00012 12.0001 4.00012C16.9707 4.00012 21.0001 8.02956 21.0001 13.0001C21.0001 17.9707 16.9707 22.0001 12.0001 22.0001ZM12.0001 20.0001C15.8661 20.0001 19.0001 16.8661 19.0001 13.0001C19.0001 9.13412 15.8661 6.00012 12.0001 6.00012C8.13412 6.00012 5.00012 9.13412 5.00012 13.0001C5.00012 16.8661 8.13412 20.0001 12.0001 20.0001ZM13.0001 13.0001H16.0001V15.0001H11.0001V8.00012H13.0001V13.0001ZM1.74707 6.2826L5.2826 2.74707L6.69682 4.16128L3.16128 7.69682L1.74707 6.2826ZM18.7176 2.74707L22.2532 6.2826L20.839 7.69682L17.3034 4.16128L18.7176 2.74707Z"></path></svg>

+ 1 - 0
src/static/icons/remixicon/notification-line.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="rgba(149,149,149,1)"><path d="M5 18H19V11.0314C19 7.14806 15.866 4 12 4C8.13401 4 5 7.14806 5 11.0314V18ZM12 2C16.9706 2 21 6.04348 21 11.0314V20H3V11.0314C3 6.04348 7.02944 2 12 2ZM9.5 21H14.5C14.5 22.3807 13.3807 23.5 12 23.5C10.6193 23.5 9.5 22.3807 9.5 21Z"></path></svg>