Sfoglia il codice sorgente

feat(health): 添加周视图支持并优化图表数据处理

- 为 blood-glucose 和 heart-rate 页面增加周视图模式
- 修改日期处理逻辑以支持跨月周计算
- 更新图表数据聚合方式,区分月视图与周视图
- 调整 UI 控件布局,新增视图切换按钮
- 改进 picker 组件使用 multiSelector 模式选择年月
- 优化图表 category 标签显示逻辑
- 修复记录添加与删除时的周期匹配判断
- 增加获取周开始/结束日期及周数的工具函数
mcbaiyun 2 mesi fa
parent
commit
9be2709880
2 ha cambiato i file con 338 aggiunte e 147 eliminazioni
  1. 77 41
      src/pages/health/details/blood-glucose.vue
  2. 261 106
      src/pages/health/details/heart-rate.vue

+ 77 - 41
src/pages/health/details/blood-glucose.vue

@@ -329,7 +329,8 @@ async function drawChart() {
   // 只为有记录的天生成categories和data,避免将无数据天设为0
   const data: number[] = []
   const filteredCategories: string[] = []
-  const dayMap = new Map<number, RecordItem>()
+  // dayMap 在月视图中存放 RecordItem,在周视图中存放 { rec: RecordItem, date: Date }
+  const dayMap = new Map<number, any>()
   if (viewMode.value === 'month') {
     for (const r of records.value) {
       const parts = r.date.split('-')
@@ -355,21 +356,22 @@ async function drawChart() {
       const parts = r.date.split('-')
       if (parts.length >= 3) {
         const recordDate = new Date(parseInt(parts[0], 10), parseInt(parts[1], 10) - 1, parseInt(parts[2], 10))
+        recordDate.setHours(0, 0, 0, 0)
         const recordWeekStart = getWeekStart(recordDate)
         if (recordWeekStart.getTime() === weekStart.getTime()) {
-          // 计算星期内的序号(1-7)
-          const dayIndex = recordDate.getDate() - weekStart.getDate() + 1
-          // 保留当天最新记录
-          dayMap.set(dayIndex, r)
+          // 基于时间差计算周内索引(支持跨月)
+          const dayIndex = Math.round((recordDate.getTime() - weekStart.getTime()) / 86400000) + 1
+          dayMap.set(dayIndex, { rec: r, date: recordDate })
         }
       }
     }
     // 按星期一到星期日输出
     const weekDays = ['一', '二', '三', '四', '五', '六', '日']
     for (let i = 1; i <= 7; i++) {
-      const rec = dayMap.get(i)
-      if (rec) {
-        const date = new Date(rec.date)
+      const entry = dayMap.get(i)
+      if (entry) {
+        const rec = entry.rec as RecordItem
+        const date = entry.date as Date
         filteredCategories.push(`${date.getDate()}日(${weekDays[i-1]})`)
         data.push(rec.value)
       }
@@ -545,47 +547,81 @@ async function updateChartData() {
 
   const year = current.value.getFullYear()
   const month = current.value.getMonth()
-  const days = daysInMonth(year, month)
-  
-  const categories: string[] = []
-  const showLabelDays = []
-  
-  if (days > 0) {
-    showLabelDays.push(1)
-    if (days > 1) showLabelDays.push(days)
-    if (days > 7) showLabelDays.push(Math.ceil(days / 3))
-    if (days > 14) showLabelDays.push(Math.ceil(days * 2 / 3))
-  }
-  
-  for (let d = 1; d <= days; d++) {
-    if (showLabelDays.includes(d)) {
-      categories.push(`${d}日`)
-    } else {
-      categories.push('')
+  let categories: string[] = []
+  let showLabelDays: number[] = []
+
+  if (viewMode.value === 'month') {
+    const days = daysInMonth(year, month)
+    if (days > 0) {
+      showLabelDays.push(1)
+      if (days > 1) showLabelDays.push(days)
+      if (days > 7) showLabelDays.push(Math.ceil(days / 3))
+      if (days > 14) showLabelDays.push(Math.ceil(days * 2 / 3))
+    }
+    for (let d = 1; d <= days; d++) {
+      categories.push(showLabelDays.includes(d) ? `${d}日` : '')
+    }
+  } else {
+    const weekStart = getWeekStart(current.value)
+    const weekDays = ['一', '二', '三', '四', '五', '六', '日']
+    for (let i = 0; i < 7; i++) {
+      const date = new Date(weekStart)
+      date.setDate(weekStart.getDate() + i)
+      categories.push(`${date.getDate()}日(${weekDays[i]})`)
     }
   }
 
-  // 只为有记录的天生成categories和data
+  // 只为有记录的天生成categories和data,周视图支持跨月计算
   const data: number[] = []
   const filteredCategories: string[] = []
-  const dayMap = new Map<number, RecordItem>()
-  for (const r of records.value) {
-    const parts = r.date.split('-')
-    if (parts.length >= 3) {
-      const y = parseInt(parts[0], 10)
-      const m = parseInt(parts[1], 10) - 1
-      const d = parseInt(parts[2], 10)
-      if (y === year && m === month) {
-        dayMap.set(d, r)
+  // dayMap 在月视图中存放 RecordItem,在周视图中存放 { rec: RecordItem, date: Date }
+  const dayMap = new Map<number, any>()
+
+  if (viewMode.value === 'month') {
+    for (const r of records.value) {
+      const parts = r.date.split('-')
+      if (parts.length >= 3) {
+        const y = parseInt(parts[0], 10)
+        const m = parseInt(parts[1], 10) - 1
+        const d = parseInt(parts[2], 10)
+        if (y === year && m === month) {
+          dayMap.set(d, r)
+        }
+      }
+    }
+    const sortedDays = Array.from(dayMap.keys()).sort((a, b) => a - b)
+    for (const d of sortedDays) {
+      const rec = dayMap.get(d)!
+      filteredCategories.push(`${d}日`)
+      data.push(rec.value)
+    }
+  } else {
+    const weekStart = getWeekStart(current.value)
+    for (const r of records.value) {
+      const parts = r.date.split('-')
+      if (parts.length >= 3) {
+        const recordDate = new Date(parseInt(parts[0], 10), parseInt(parts[1], 10) - 1, parseInt(parts[2], 10))
+        recordDate.setHours(0, 0, 0, 0)
+        const recordWeekStart = getWeekStart(recordDate)
+        if (recordWeekStart.getTime() === weekStart.getTime()) {
+          // 基于时间差计算周内索引,支持跨月
+          const dayIndex = Math.round((recordDate.getTime() - weekStart.getTime()) / 86400000) + 1
+          dayMap.set(dayIndex, { rec: r, date: recordDate })
+        }
+      }
+    }
+    const weekDays = ['一', '二', '三', '四', '五', '六', '日']
+    for (let i = 1; i <= 7; i++) {
+      const entry = dayMap.get(i)
+      if (entry) {
+        const rec = entry.rec as RecordItem
+        const date = entry.date as Date
+        filteredCategories.push(`${date.getDate()}日(${weekDays[i-1]})`)
+        data.push(rec.value)
       }
     }
   }
-  const sortedDays = Array.from(dayMap.keys()).sort((a, b) => a - b)
-  for (const d of sortedDays) {
-    const rec = dayMap.get(d)!
-    filteredCategories.push(`${d}日`)
-    data.push(rec.value)
-  }
+
   const categoriesToUse = filteredCategories.length ? filteredCategories : categories
 
   const validData = data.filter(v => v > 0)

+ 261 - 106
src/pages/health/details/heart-rate.vue

@@ -4,13 +4,18 @@
 
     <view class="header">
       <view class="month-selector">
-        <button class="btn" @click="prevMonth">‹</button>
-        <view class="month-label">{{ displayYear }}年 {{ displayMonth }}月</view>
-        <button class="btn" @click="nextMonth">›</button>
+        <button class="btn" @click="prevPeriod">‹</button>
+        <view class="period-controls">
+          <picker mode="multiSelector" :value="pickerValue" :range="pickerRange" @change="onPickerChange">
+            <view class="month-label">{{ displayPeriod }}</view>
+          </picker>
+          <view class="view-toggle">
+            <button :class="['toggle-btn', { active: viewMode === 'month' }]" @click="setViewMode('month')">月</button>
+            <button :class="['toggle-btn', { active: viewMode === 'week' }]" @click="setViewMode('week')">周</button>
+          </view>
+        </view>
+        <button class="btn" @click="nextPeriod">›</button>
       </view>
-      <picker mode="date" :value="pickerValue" @change="onPickerChange">
-        <view class="picker-display">切换月份</view>
-      </picker>
     </view>
 
     <!-- 趋势图 - 简化canvas设置 -->
@@ -86,7 +91,17 @@ type RecordItem = { id: string; date: string; hr: number }
 
 // 当前展示年月
 const current = ref(new Date())
-const pickerValue = ref(formatPickerDate(current.value))
+// picker 使用 multiSelector,两列:年份偏移(2000起),月份(0-11)
+const pickerValue = ref([current.value.getFullYear() - 2000, current.value.getMonth()])
+
+// 视图模式:'month' 或 'week'
+const viewMode = ref<'month' | 'week'>('month')
+
+// 年月选择器的选项范围
+const pickerRange = ref([
+  Array.from({ length: 50 }, (_, i) => `${2000 + i}年`),
+  Array.from({ length: 12 }, (_, i) => `${i + 1}月`)
+])
 
 // 明确的canvas尺寸(将由 getCanvasSize 初始化以匹配设备宽度)
 const canvasWidth = ref(700) // 初始值,会在 mounted 时覆盖
@@ -113,25 +128,69 @@ function formatPickerDate(d: Date) {
 const displayYear = computed(() => current.value.getFullYear())
 const displayMonth = computed(() => current.value.getMonth() + 1)
 
+const displayPeriod = computed(() => {
+  if (viewMode.value === 'month') {
+    return `${displayYear.value}年 ${displayMonth.value}月`
+  } else {
+    const weekStart = getWeekStart(current.value)
+    const weekEnd = getWeekEnd(current.value)
+    return `${formatDisplayDate(weekStart)} - ${formatDisplayDate(weekEnd)}`
+  }
+})
+
 const records = ref<RecordItem[]>(generateMockRecords(current.value))
 
 function generateMockRecords(d: Date): RecordItem[] {
-  const y = d.getFullYear()
-  const m = d.getMonth()
   const arr: RecordItem[] = []
-  const n = Math.floor(Math.random() * 7)
-  for (let i = 0; i < n; i++) {
-    const day = Math.max(1, Math.floor(Math.random() * 28) + 1)
-    const date = new Date(y, m, day)
-    arr.push({ 
-      id: `${y}${m}${i}${Date.now()}`, 
-      date: formatDisplayDate(date), 
-      hr: Math.floor(50 + Math.random() * 70)
-    })
+  if (viewMode.value === 'month') {
+    const y = d.getFullYear()
+    const m = d.getMonth()
+    const days = daysInMonth(y, m)
+    const n = Math.floor(Math.random() * Math.min(days, 7))
+    for (let i = 0; i < n; i++) {
+      const day = Math.max(1, Math.floor(Math.random() * days) + 1)
+      const date = new Date(y, m, day)
+      arr.push({ id: `${date.getTime()}${i}${Date.now()}`, date: formatDisplayDate(date), hr: Math.floor(50 + Math.random() * 70) })
+    }
+  } else {
+    const weekStart = getWeekStart(d)
+    const n = Math.floor(Math.random() * 7)
+    for (let i = 0; i < n; i++) {
+      const dayOffset = Math.floor(Math.random() * 7)
+      const date = new Date(weekStart)
+      date.setDate(weekStart.getDate() + dayOffset)
+      arr.push({ id: `${date.getTime()}${i}${Date.now()}`, date: formatDisplayDate(date), hr: Math.floor(50 + Math.random() * 70) })
+    }
   }
   return arr.sort((a, b) => (a.date < b.date ? 1 : -1))
 }
 
+// 获取指定日期所在周的开始日期(星期一),并规范化到本地午夜
+function getWeekStart(date: Date): Date {
+  const d = new Date(date)
+  d.setHours(0, 0, 0, 0)
+  const day = d.getDay()
+  const diff = day === 0 ? -6 : 1 - day
+  d.setDate(d.getDate() + diff)
+  d.setHours(0, 0, 0, 0)
+  return d
+}
+
+function getWeekEnd(date: Date): Date {
+  const d = getWeekStart(date)
+  d.setDate(d.getDate() + 6)
+  d.setHours(0, 0, 0, 0)
+  return d
+}
+
+function getWeekNumber(date: Date): number {
+  const d = new Date(date)
+  d.setHours(0, 0, 0, 0)
+  d.setDate(d.getDate() + 4 - (d.getDay() || 7))
+  const yearStart = new Date(d.getFullYear(), 0, 1)
+  return Math.ceil(((d.getTime() - yearStart.getTime()) / 86400000 + 1) / 7)
+}
+
 // 将 records 聚合为每天一个点(取最新记录)
 function aggregateDaily(recordsArr: RecordItem[], year: number, month: number) {
   const map = new Map<number, RecordItem>()
@@ -220,59 +279,81 @@ async function drawChart() {
 
   const year = current.value.getFullYear()
   const month = current.value.getMonth()
-  const days = daysInMonth(year, month)
-  
-  // 生成合理的categories - 只显示关键日期
-  const categories: string[] = []
-  const showLabelDays = []
-  
-  // 选择要显示的标签:1号、中间几天、最后一天
-  if (days > 0) {
-    showLabelDays.push(1) // 第1天
-    if (days > 1) showLabelDays.push(days) // 最后一天
-    // 中间添加2-3个关键点
-    if (days > 7) showLabelDays.push(Math.ceil(days / 3))
-    if (days > 14) showLabelDays.push(Math.ceil(days * 2 / 3))
-  }
-  
-  for (let d = 1; d <= days; d++) {
-    if (showLabelDays.includes(d)) {
-      categories.push(`${d}日`)
-    } else {
-      categories.push('')
+  let categories: string[] = []
+  let showLabelDays: number[] = []
+
+  if (viewMode.value === 'month') {
+    const days = daysInMonth(year, month)
+    if (days > 0) {
+      showLabelDays.push(1)
+      if (days > 1) showLabelDays.push(days)
+      if (days > 7) showLabelDays.push(Math.ceil(days / 3))
+      if (days > 14) showLabelDays.push(Math.ceil(days * 2 / 3))
+    }
+    for (let d = 1; d <= days; d++) categories.push(showLabelDays.includes(d) ? `${d}日` : '')
+  } else {
+    const weekStart = getWeekStart(current.value)
+    const weekDays = ['一', '二', '三', '四', '五', '六', '日']
+    for (let i = 0; i < 7; i++) {
+      const date = new Date(weekStart)
+      date.setDate(weekStart.getDate() + i)
+      categories.push(`${date.getDate()}日(${weekDays[i]})`)
     }
   }
 
   // 只为有记录的天生成categories和data,避免将无数据天设为0
   const data: number[] = []
   const filteredCategories: string[] = []
-  // 使用Map按日聚合,保留最新记录(records 数组头部为最新)
-  const dayMap = new Map<number, RecordItem>()
-  for (const r of records.value) {
-    const parts = r.date.split('-')
-    if (parts.length >= 3) {
-      const y = parseInt(parts[0], 10)
-      const m = parseInt(parts[1], 10) - 1
-      const d = parseInt(parts[2], 10)
-      if (y === year && m === month) {
-        // 以最后遍历到的(数组顺序保证头部为最新)作为最终值
-        dayMap.set(d, r)
+  // dayMap 在月视图中存放 RecordItem,在周视图中存放 { rec: RecordItem, date: Date }
+  const dayMap = new Map<number, any>()
+
+  if (viewMode.value === 'month') {
+    for (const r of records.value) {
+      const parts = r.date.split('-')
+      if (parts.length >= 3) {
+        const y = parseInt(parts[0], 10)
+        const m = parseInt(parts[1], 10) - 1
+        const d = parseInt(parts[2], 10)
+        if (y === year && m === month) dayMap.set(d, r)
+      }
+    }
+    const sortedDays = Array.from(dayMap.keys()).sort((a, b) => a - b)
+    for (const d of sortedDays) {
+      const rec = dayMap.get(d)!
+      filteredCategories.push(`${d}日`)
+      data.push(rec.hr)
+    }
+  } else {
+    const weekStart = getWeekStart(current.value)
+    for (const r of records.value) {
+      const parts = r.date.split('-')
+      if (parts.length >= 3) {
+        // 使用年/月/日构造本地日期,避免字符串解析带来时区差异
+        const recordDate = new Date(parseInt(parts[0], 10), parseInt(parts[1], 10) - 1, parseInt(parts[2], 10))
+        recordDate.setHours(0, 0, 0, 0)
+        const recordWeekStart = getWeekStart(recordDate)
+        if (recordWeekStart.getTime() === weekStart.getTime()) {
+          // 通过时间差计算当天在周内的索引,支持跨月情况
+          const dayIndex = Math.round((recordDate.getTime() - weekStart.getTime()) / 86400000) + 1
+          // 存入记录和对应的 Date 对象,后续使用更可靠的 Date
+          dayMap.set(dayIndex, { rec: r, date: recordDate })
+        }
+      }
+    }
+    const weekDays = ['一', '二', '三', '四', '五', '六', '日']
+    for (let i = 1; i <= 7; i++) {
+      const entry = dayMap.get(i)
+      if (entry) {
+        const rec = entry.rec as RecordItem
+        const date = entry.date as Date
+        filteredCategories.push(`${date.getDate()}日(${weekDays[i-1]})`)
+        data.push(rec.hr)
       }
     }
   }
 
-  // 将有数据的日期按日顺序输出
-  const sortedDays = Array.from(dayMap.keys()).sort((a, b) => a - b)
-  for (const d of sortedDays) {
-    const rec = dayMap.get(d)!
-    filteredCategories.push(`${d}日`)
-    data.push(rec.hr)
-  }
-
-  // 如果没有任何数据,则回退为整月空数据(避免 uCharts 抛错)
   const categoriesToUse = filteredCategories.length ? filteredCategories : categories
 
-  // 计算合理的Y轴范围
   const validData = data.filter(v => v > 0)
   const minVal = validData.length ? Math.floor(Math.min(...validData)) - 2 : 40
   const maxVal = validData.length ? Math.ceil(Math.max(...validData)) + 2 : 130
@@ -438,47 +519,75 @@ async function updateChartData() {
 
   const year = current.value.getFullYear()
   const month = current.value.getMonth()
-  const days = daysInMonth(year, month)
-  
-  const categories: string[] = []
-  const showLabelDays = []
-  
-  if (days > 0) {
-    showLabelDays.push(1)
-    if (days > 1) showLabelDays.push(days)
-    if (days > 7) showLabelDays.push(Math.ceil(days / 3))
-    if (days > 14) showLabelDays.push(Math.ceil(days * 2 / 3))
-  }
-  
-  for (let d = 1; d <= days; d++) {
-    if (showLabelDays.includes(d)) {
-      categories.push(`${d}日`)
-    } else {
-      categories.push('')
+  let categories: string[] = []
+  let showLabelDays: number[] = []
+
+  if (viewMode.value === 'month') {
+    const days = daysInMonth(year, month)
+    if (days > 0) {
+      showLabelDays.push(1)
+      if (days > 1) showLabelDays.push(days)
+      if (days > 7) showLabelDays.push(Math.ceil(days / 3))
+      if (days > 14) showLabelDays.push(Math.ceil(days * 2 / 3))
+    }
+    for (let d = 1; d <= days; d++) categories.push(showLabelDays.includes(d) ? `${d}日` : '')
+  } else {
+    const weekStart = getWeekStart(current.value)
+    const weekDays = ['一', '二', '三', '四', '五', '六', '日']
+    for (let i = 0; i < 7; i++) {
+      const date = new Date(weekStart)
+      date.setDate(weekStart.getDate() + i)
+      categories.push(`${date.getDate()}日(${weekDays[i]})`)
     }
   }
 
-  // 只为有记录的天生成categories和data
   const data: number[] = []
   const filteredCategories: string[] = []
-  const dayMap = new Map<number, RecordItem>()
-  for (const r of records.value) {
-    const parts = r.date.split('-')
-    if (parts.length >= 3) {
-      const y = parseInt(parts[0], 10)
-      const m = parseInt(parts[1], 10) - 1
-      const d = parseInt(parts[2], 10)
-      if (y === year && m === month) {
-        dayMap.set(d, r)
+  // dayMap 在月视图中存放 RecordItem,在周视图中存放 { rec: RecordItem, date: Date }
+  const dayMap = new Map<number, any>()
+
+  if (viewMode.value === 'month') {
+    for (const r of records.value) {
+      const parts = r.date.split('-')
+      if (parts.length >= 3) {
+        const y = parseInt(parts[0], 10)
+        const m = parseInt(parts[1], 10) - 1
+        const d = parseInt(parts[2], 10)
+        if (y === year && m === month) dayMap.set(d, r)
+      }
+    }
+    const sortedDays = Array.from(dayMap.keys()).sort((a, b) => a - b)
+    for (const d of sortedDays) {
+      const rec = dayMap.get(d)!
+      filteredCategories.push(`${d}日`)
+      data.push(rec.hr)
+    }
+  } else {
+    const weekStart = getWeekStart(current.value)
+    for (const r of records.value) {
+      const parts = r.date.split('-')
+      if (parts.length >= 3) {
+        const recordDate = new Date(parseInt(parts[0], 10), parseInt(parts[1], 10) - 1, parseInt(parts[2], 10))
+        recordDate.setHours(0, 0, 0, 0)
+        const recordWeekStart = getWeekStart(recordDate)
+        if (recordWeekStart.getTime() === weekStart.getTime()) {
+          const dayIndex = Math.round((recordDate.getTime() - weekStart.getTime()) / 86400000) + 1
+          dayMap.set(dayIndex, { rec: r, date: recordDate })
+        }
+      }
+    }
+    const weekDays = ['一', '二', '三', '四', '五', '六', '日']
+    for (let i = 1; i <= 7; i++) {
+      const entry = dayMap.get(i)
+      if (entry) {
+        const rec = entry.rec as RecordItem
+        const date = entry.date as Date
+        filteredCategories.push(`${date.getDate()}日(${weekDays[i-1]})`)
+        data.push(rec.hr)
       }
     }
   }
-  const sortedDays = Array.from(dayMap.keys()).sort((a, b) => a - b)
-  for (const d of sortedDays) {
-    const rec = dayMap.get(d)!
-    filteredCategories.push(`${d}日`)
-    data.push(rec.hr)
-  }
+
   const categoriesToUse = filteredCategories.length ? filteredCategories : categories
 
   const validData = data.filter(v => v > 0)
@@ -596,34 +705,43 @@ async function rebuildChart() {
   }
 }
 
-// 其他函数保持不变
-async function prevMonth() {
+// 周/月周期切换与 picker 处理
+async function prevPeriod() {
   const d = new Date(current.value)
-  d.setMonth(d.getMonth() - 1)
+  if (viewMode.value === 'month') d.setMonth(d.getMonth() - 1)
+  else d.setDate(d.getDate() - 7)
   current.value = d
-  pickerValue.value = formatPickerDate(d)
+  pickerValue.value = [d.getFullYear() - 2000, d.getMonth()]
   records.value = generateMockRecords(d)
   await rebuildChart()
 }
 
-async function nextMonth() {
+async function nextPeriod() {
   const d = new Date(current.value)
-  d.setMonth(d.getMonth() + 1)
+  if (viewMode.value === 'month') d.setMonth(d.getMonth() + 1)
+  else d.setDate(d.getDate() + 7)
   current.value = d
-  pickerValue.value = formatPickerDate(d)
+  pickerValue.value = [d.getFullYear() - 2000, d.getMonth()]
   records.value = generateMockRecords(d)
   await rebuildChart()
 }
 
+async function setViewMode(mode: 'month' | 'week') {
+  if (viewMode.value !== mode) {
+    viewMode.value = mode
+    records.value = generateMockRecords(current.value)
+    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 + Number(val[0])
+    const m = Number(val[1])
     const d = new Date(y, m, 1)
     current.value = d
-    pickerValue.value = formatPickerDate(d)
+    pickerValue.value = [Number(val[0]), Number(val[1])]
     records.value = generateMockRecords(d)
     await rebuildChart()
   }
@@ -669,9 +787,17 @@ async function confirmAdd() {
   const parts = addDate.value.split('-')
   const addY = parseInt(parts[0], 10)
   const addM = parseInt(parts[1], 10) - 1
-  if (addY === current.value.getFullYear() && addM === current.value.getMonth()) {
-    records.value = [item, ...records.value]
+  const addD = parseInt(parts[2], 10)
+  const addDateObj = new Date(addY, addM, addD)
+  let isInCurrentPeriod = false
+  if (viewMode.value === 'month') {
+    isInCurrentPeriod = addY === current.value.getFullYear() && addM === current.value.getMonth()
+  } else {
+    const weekStart = getWeekStart(current.value)
+    const recordWeekStart = getWeekStart(addDateObj)
+    isInCurrentPeriod = weekStart.getTime() === recordWeekStart.getTime()
   }
+  if (isInCurrentPeriod) records.value = [item, ...records.value]
   uni.showToast && uni.showToast({ title: '已添加', icon: 'success' })
   closeAdd()
   // 新增记录后彻底重建图表,确保像退出再进入一样刷新
@@ -720,6 +846,35 @@ async function confirmDeleteRecord(id: string) {
   gap: 12rpx 
 }
 
+.period-controls {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 8rpx;
+}
+
+.view-toggle {
+  display: flex;
+  gap: 4rpx;
+}
+
+.toggle-btn {
+  padding: 4rpx 12rpx;
+  border: 1rpx solid #ddd;
+  background: #f5f5f5;
+  color: #666;
+  border-radius: 6rpx;
+  font-size: 24rpx;
+  min-width: 60rpx;
+  text-align: center;
+}
+
+.toggle-btn.active {
+  background: #ff6a00;
+  color: #fff;
+  border-color: #ff6a00;
+}
+
 .month-label { 
   font-size: 34rpx; 
   color: #333