fix(月视图): 首屏农历不变红,切视图才生效 #6

用户反馈:刚打开应用时,月视图里有事件的日期农历没变红;
切到周视图再切回月视图,才变红。

## 根因:ForEach 的 key 不完整
月视图的 key 是:
    (cell) => `${cell.dateMs}_${cell.dateMs === this.selectedDate}`
**没有包含 hasEvent。**

首屏时序:
  1. aboutToAppear → rebuildPages(),此时 events 为空,hasEvent 全 false
  2. reloadEvents() 拉回数据 → rebuildPages() 再次重建,hasEvent 变 true
  3. 但每个格子的 key 完全没变(dateMs 和选中态都没变)
  ⇒ ArkUI 判定"这些项没有变化",**复用旧组件、跳过重绘**,颜色停在旧值

切视图能恢复,是因为切回来时整棵子树被重新创建,绕过了 diff。

## 修法
把影响渲染的**全部**状态纳入 key:hasEvent / isToday / 是否选中。
key 变了 → ArkUI 视为新项 → 重建该格子 → 颜色正确。

## 顺带加固
中间层"每周"的 key 原来是 `String(idx)`(0..5),跨月翻页时序号永远重复,
会让 ArkUI 误判为同一周而复用旧节点。改为用该周首日的 dateMs 做 key。

## 为什么不改成 @Observed / @ObjectLink
monthPages 是二维数组(MonthCell[][]),buildMonthCells 返回新数组、
chunkCells 又一次切片,改成可观察对象要动整条构建链路,
牵涉面大且容易引入其它 diff 问题。用 key 是最小、最稳的改法。

⚠️ 通用教训:ArkUI 的 ForEach 用自定义 key 时,**key 必须覆盖所有影响渲染的
   字段**。漏一个字段,该字段更新后界面就不刷新 —— 而且这类 bug 只在
   "同一项的数据变了、key 没变"时出现,首次渲染永远是正常的,很容易漏测。
This commit is contained in:
2026-11-02 17:10:38 +08:00
parent fa3dbc6157
commit d49b041a2c
+11 -3
View File
@@ -1706,7 +1706,8 @@ struct Index {
.onClick(() => { .onClick(() => {
this.selectedDate = cell.dateMs; this.selectedDate = cell.dateMs;
}) })
}, (cell: MonthCell) => `${cell.dateMs}_${cell.dateMs === this.selectedDate}_${cell.hasEvent}`) }, (cell: MonthCell) => `${cell.dateMs}_${cell.dateMs === this.selectedDate}`
+ `_he${cell.hasEvent ? 1 : 0}_td${cell.isToday ? 1 : 0}`)
} }
.width('100%') .width('100%')
.padding({ left: 12, right: 12 }) .padding({ left: 12, right: 12 })
@@ -1808,13 +1809,20 @@ struct Index {
Column({ space: 2 }) { Column({ space: 2 }) {
ForEach(this.chunkCells(cells), (week: MonthCell[], idx: number) => { ForEach(this.chunkCells(cells), (week: MonthCell[], idx: number) => {
Row({ space: 2 }) { Row({ space: 2 }) {
// ⭐ key 必须包含影响渲染的**全部**状态:hasEvent / isToday /
// 是否选中。只放 dateMs+选中态时,数据加载完成后 hasEvent 从
// false 变 true 但 key 没变 ⇒ ArkUI 复用旧组件、跳过重绘,
// 表现为"首屏农历不变红,切一次视图才正常"。
ForEach(week, (cell: MonthCell) => { ForEach(week, (cell: MonthCell) => {
this.dayCell(cell) this.dayCell(cell)
}, (cell: MonthCell) => `${cell.dateMs}_${cell.dateMs === this.selectedDate}`) }, (cell: MonthCell) => `${cell.dateMs}_${cell.dateMs === this.selectedDate}`
+ `_he${cell.hasEvent ? 1 : 0}_td${cell.isToday ? 1 : 0}`)
} }
.width('100%') .width('100%')
.layoutWeight(this.wideLayout ? 1 : 0) .layoutWeight(this.wideLayout ? 1 : 0)
}, (week: MonthCell[], idx: number) => String(idx)) // 用"这一周首日的 dateMs"当 key,而不是序号:跨月翻页时序号会重复,
// 会让 ArkUI 误以为是同一周而复用旧节点。
}, (week: MonthCell[], idx: number) => `wk${week.length > 0 ? week[0].dateMs : idx}`)
} }
.width('100%') .width('100%')
// 注意:不要给页面设百分比高度——竖屏时 Swiper 是自然高度, // 注意:不要给页面设百分比高度——竖屏时 Swiper 是自然高度,