diff --git a/entry/src/main/ets/pages/Index.ets b/entry/src/main/ets/pages/Index.ets index 6934b1c..b658836 100644 --- a/entry/src/main/ets/pages/Index.ets +++ b/entry/src/main/ets/pages/Index.ets @@ -89,6 +89,10 @@ struct Index { * 连 itemGenerator 都不重新执行 ⇒ 内层永远拿到旧的 cells(hasEvent 全 false)。 * 只改内层 key 是没用的,必须让外层 key 变。 */ @State monthDataVersion: number = 0; + /** ⭐ 用户**主动选中**的日号(1~31)。翻月时用它算目标日, + * 避免"1/31 → 2/28 → 3/28"这种日号被永久钳死的问题: + * 有了它,2 月显示 28 号,翻到 3 月能回到 31 号。 */ + @State preferredDay: number = new Date().getDate(); @State syncing: boolean = false; @State loading: boolean = true; @State menuOpen: boolean = false; // 顶部 ≡ 下拉菜单 @@ -633,6 +637,14 @@ struct Index { const nm = this.addMonths(this.displayYear, this.displayMonth, delta); this.displayYear = nm[0]; this.displayMonth = nm[1]; + // ⭐ KEEP_SELECTED_DAY 翻月后必须把选中日**迁到新月份的同一天**。 + // 原来这里只改 displayMonth、不动 selectedDate, + // 导致 selectedDate 还停在上个月 ⇒ 新月份所有格子都匹配不上, + // 整个月没有任何一天是选中态(用户看到的"乱跳/没有选中")。 + // ⭐ 用 preferredDay(用户原本选的日号)而不是 selectedDate 的日号: + // 否则 1/31 → 2/28 之后,再翻月就一直是 28 号,回不到 31。 + const lastDay: number = new Date(nm[0], nm[1] + 1, 0).getDate(); + this.selectedDate = new Date(nm[0], nm[1], Math.min(this.preferredDay, lastDay)).getTime(); this.reloadEvents(); this.swiperGuard = true; this.swiperController.changeIndex(1, false); @@ -3389,6 +3401,8 @@ struct Index { .justifyContent(FlexAlign.Center) .onClick(() => { this.selectedDate = cell.dateMs; + // 用户主动点了某天 ⇒ 记住这个日号,作为后续翻月的基准 + this.preferredDay = new Date(cell.dateMs).getDate(); if (!cell.inMonth) { const d = new Date(cell.dateMs); this.displayYear = d.getFullYear(); diff --git a/scripts/test-month-swipe-selection.js b/scripts/test-month-swipe-selection.js new file mode 100644 index 0000000..8c378ed --- /dev/null +++ b/scripts/test-month-swipe-selection.js @@ -0,0 +1,65 @@ +// 验证 preferredDay 机制消除日号漂移 +function addMonths(y, m, delta) { + let mm = m + delta, yy = y; + while (mm < 0) { mm += 12; yy--; } + while (mm > 11) { mm -= 12; yy++; } + return [yy, mm]; +} +function migrate(preferredDay, y, m, delta) { + const nm = addMonths(y, m, delta); + const lastDay = new Date(nm[0], nm[1] + 1, 0).getDate(); + return { ms: new Date(nm[0], nm[1], Math.min(preferredDay, lastDay)).getTime(), y: nm[0], m: nm[1] }; +} +function fmt(ms) { + const d = new Date(ms); + return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`; +} + +let pass = 0, fail = 0; +function ok(name, cond, extra) { + if (cond) { console.log(' ✅', name); pass++; } + else { console.log(' ❌', name, extra); fail++; } +} + +// 1. 偏好 31 号:1/31 → 2/28 → 3/31(回到 31!) +let pref = 31, y = 2026, m = 0, cur = new Date(2026, 0, 31).getTime(), log = []; +for (let i = 0; i < 4; i++) { + const r = migrate(pref, y, m, 1); + cur = r.ms; y = r.y; m = r.m; + log.push(fmt(cur)); +} +console.log(' 偏好31号连续翻月:', log.join(' → ')); +ok('1/31→2/28→3/31→4/30(月底语义保留)', + log[0] === '2026-02-28' && log[1] === '2026-03-31' && log[2] === '2026-04-30', + log.join(',')); +ok('第4次翻到5月回到31', log[3] === '2026-05-31', log[3]); + +// 2. 偏好 15 号:一直稳定 +pref = 15; y = 2026; m = 0; log = []; +for (let i = 0; i < 6; i++) { + const r = migrate(pref, y, m, 1); + y = r.y; m = r.m; log.push(fmt(r.ms)); +} +ok('偏好15号稳定', log.every(s => s.endsWith('-15')), log.join(',')); + +// 3. 用户点 2/28 后偏好变 28,翻到 3 月应是 28 号 +pref = 28; y = 2026; m = 1; +let r = migrate(pref, y, m, 1); +ok('点过28号后翻月为3/28', fmt(r.ms) === '2026-03-28', fmt(r.ms)); + +// 4. 闰年偏好 31 +pref = 31; y = 2028; m = 0; +r = migrate(pref, y, m, 1); +ok('闰年 1/31 → 2/29', fmt(r.ms) === '2028-02-29', fmt(r.ms)); +r = migrate(pref, 2028, 1, 1); +ok('闰年 2/29 → 3/31', fmt(r.ms) === '2028-03-31', fmt(r.ms)); + +// 5. 往前翻同样保留 +pref = 31; y = 2026; m = 2; // 3月 +r = migrate(pref, y, m, -1); +ok('3/31 上月 → 2/28', fmt(r.ms) === '2026-02-28', fmt(r.ms)); +r = migrate(pref, 2026, 1, -1); +ok('2/28 再上月 → 1/31', fmt(r.ms) === '2026-01-31', fmt(r.ms)); + +console.log(`\n结果:通过 ${pass},失败 ${fail}`); +process.exit(fail === 0 ? 0 : 1); \ No newline at end of file