From 566131267924622cf9ba9dce141cb6e29f683d53 Mon Sep 17 00:00:00 2001 From: laoyang Date: Sun, 1 Nov 2026 09:38:14 +0800 Subject: [PATCH] =?UTF-8?q?=E6=97=A5=E7=A8=8B=E7=BC=96=E8=BE=91=EF=BC=9A?= =?UTF-8?q?=E7=A1=AE=E8=AE=A4=E3=80=8C=E5=BC=80=E5=A7=8B=E3=80=8D=E5=90=8E?= =?UTF-8?q?=E7=BB=93=E6=9D=9F=E6=97=B6=E9=97=B4=E8=87=AA=E5=8A=A8=20=3D=20?= =?UTF-8?q?=E5=BC=80=E5=A7=8B=20+=201=20=E5=B0=8F=E6=97=B6=20(laoyang/Fami?= =?UTF-8?q?lyBook#34)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户 2026-11-01: 「当我选择了开始时间并确定之后,结束时间要跟着开始时间变成开始时间加1个小时。 也就是说,所有时间我们都默认它是一个小时。如果不是一个小时,再让他自己去改。」 ## 改前 `applyPicker()` 只在**会变成负时长**时才动结束时间: if (this.fEndDate < iso || (this.fEndDate === iso && this.fEndTime < hm)) { ... } ⇒ 用户改了开始、结束还挂在老位置,实际时长变成随机的。 ## 改后 点「开始」确定 ⇒ 结束**无条件**重置为「开始 + 1 小时」。 改「结束」那条路一个字不动(改结束不影响开始)—— 这是「默认一小时、 不像一小时再自己改」的另一半。 全天日程跳过这段:时刻部分本来就不生效,改了不会落到服务端, 但会让"切回非全天"时看到一个莫名的时间。 ## 新增 fbPlusHours() `FbDateTimeField.ets` 里加 `fbPlusHours(date, time, hours)`,返回 `{date, time}`。 **不复用现成的 `fbAddHour()`**,因为那个有两个问题: return fbPad2(Math.min(hh + 1, 23)) + ':' + mm; ① `Math.min(hh+1, 23)` **封顶 23 点** ⇒ 23:30 加一小时还是 23:30; ② 只返回时间、不带日期 ⇒ 表达不了「次日 00:30」。 跨天/跨月/跨年全部交给 `Date` 运算,不自己算天数 (自己 `if (hh>=24) dd+=1` 在"11 月 30 日 + 1 天"上就会错成 11 月 31 日)。 ## 验证 **纯函数真值表 13 项**(从真实 `.ets` 抽函数、只擦类型标注后 node 直接跑, 不手抄替身;擦除后断言行数不变、无类型残留、没引入新字符): 普通 / 跨天整点 / 跨天半点 / 跨月 / 跨年 / 非闰年 2 月末 / **闰年 2 月末 ⇒ 2/29** / 午夜 / 带分钟 —— 全过。其中 23:30+1h ⇒ **次日 00:30**、12/31 ⇒ 次年 01/01、2028-02-28 ⇒ 2028-02-29。 **反向验证 3 项**(证明判据不是恒真): 坏A(封顶 23 点 / 不带日期)被抓到、坏B(丢分钟)被抓到、坏C(固定 28 天)被抓到。 **接线断言 12 项 + 反向验证 5 项**(静态,因为"函数对 ≠ 接进去了", 上一轮 #34 主体 bug 就是参数名对不上、编译运行都不报错): - 开始分支调用 `fbPlusHours(iso, hm, 1)`、结果**同时**写 `fEndDate` 与 `fEndTime` - 结束分支**没有任何** `fStart*` 赋值 - 全天日程有 `!this.fAllDay` 判断 - `pickIsEnd = !isStart`(#34 主体防回归) - 不再残留 `this.fEndDate < iso` 那种条件顺延 - 反向:改回老写法 / 只写日期不写时间 / 改结束顺手改开始 / `pickIsEnd` 再反过来 → 逐条转红;**对照组**(只改一句注释)仍绿 —— 证明断言不会"见到改动就红" > ⚠️ 接线判据自己第一版有 bug,是反向验证抓出来的:坏1 用正则 > `if \(!this\.fAllDay\) \{[\s\S]*?\n \}` 找替换目标,但渲染层还有一个 > `if (!this.fAllDay)`(控制「结束」那一行显示),正则先吃到那个 ⇒ 注入落在无关位置 > ⇒ 断言仍绿(假阴性)。改成"先把 applyPicker 的函数体切出来再替换"。 **编译**:BUILD SUCCESSFUL,33 tasks(17 executed)。验签通过。 **⚠️ 已知存量失败(与本次无关)**:`_fb_arkts_guard.js` 跑出 1321 通过 / 3 失败, 三条都在工作区里**别的会话未提交的改动**上(`AGENDA_BACK_DAYS` 被换成 `CAL_FULL_FROM/TO` 导致"列表视图显式传日期范围"判据失配、`canEdit` 相关两条属 #31)。 本次只改 `FbDateTimeField.ets` 与 `FbEventEditSheet.ets`,不碰那些文件; 用 `git show HEAD:` 核实过 HEAD 版本里 `AGENDA_BACK_DAYS` 还在(判据本来通过)。 --- .../main/ets/components/FbDateTimeField.ets | 32 +++++++++++++++++ .../main/ets/components/FbEventEditSheet.ets | 34 +++++++++++++++---- 2 files changed, 60 insertions(+), 6 deletions(-) diff --git a/entry/src/main/ets/components/FbDateTimeField.ets b/entry/src/main/ets/components/FbDateTimeField.ets index 7b0b63d..d84f371 100644 --- a/entry/src/main/ets/components/FbDateTimeField.ets +++ b/entry/src/main/ets/components/FbDateTimeField.ets @@ -105,6 +105,38 @@ export function fbAddHour(time: string): string { return fbPad2(Math.min(hh + 1, 23)) + ':' + mm; } +/** `fbPlusHours()` 的返回形状(ArkTS 不允许匿名对象字面量作为返回值类型书写,单独给个接口) */ +export interface FbDateTime { + date: string; + time: string; +} + +/** + * `YYYY-MM-DD` + `HH:mm` 往后推 N 小时 ⇒ `{ date, time }`(**跨天自动进位**)。 + * + * 起源:工单 #34 追加需求(2026-11-01)—— + * 「当我选择了开始时间并确定之后,结束时间要跟着开始时间变成开始时间加1个小时。 + * 也就是说,所有时间我们都默认它是一个小时。如果不是一个小时,再让他自己去改。」 + * + * ⚠️ 为什么不复用上面的 `fbAddHour()`:那只返回时间、不带日期, + * 而且用 `Math.min(hh+1, 23)` **封顶 23 点** ⇒ 23:30 加一小时还是 23:30。 + * 日程跨天是常态(20:00 → 次日 02:00 是真实用例),必须有日期。 + * + * ⚠️ 用 `Date` 做进位而不是自己算:跨月、跨年、闰年、月末天数全交给运行时。 + * 自己 `if (hh >= 24) { dd += 1 }` 看着简单,但"11 月 30 日 + 1 天"就会错成 11 月 31 日。 + */ +export function fbPlusHours(date: string, time: string, hours: number): FbDateTime { + const d: Date = fbParseDate(date); + const h: number = time.length >= 5 ? parseInt(time.substring(0, 2), 10) : 9; + const m: number = time.length >= 5 ? parseInt(time.substring(3, 5), 10) : 0; + const out: Date = new Date(d.getFullYear(), d.getMonth(), d.getDate()); + out.setHours(h + hours, m, 0, 0); + return { + date: fbIsoOf(out), + time: fbPad2(out.getHours()) + ':' + fbPad2(out.getMinutes()) + }; +} + /** 日期 + 时分拼成服务端认的 `YYYY-MM-DD HH:MM`(秒由服务端补) */ export function fbJoinDateTime(date: string, time: string): string { return date + ' ' + time; diff --git a/entry/src/main/ets/components/FbEventEditSheet.ets b/entry/src/main/ets/components/FbEventEditSheet.ets index 77d8044..34d8f76 100644 --- a/entry/src/main/ets/components/FbEventEditSheet.ets +++ b/entry/src/main/ets/components/FbEventEditSheet.ets @@ -63,7 +63,10 @@ import { fbDateOf, fbTimeOf, fbIsoOf, - fbAddHour + fbAddHour, + // 工单 #34 追加需求:点「开始」确定后,结束 = 开始 + 1 小时(跨天自动进位) + fbPlusHours, + FbDateTime } from './FbDateTimeField'; const DOMAIN: number = 0x0000; @@ -671,7 +674,25 @@ export struct FbEventEditSheet { this.pickOpen = true; } - /** 「确定」:把草稿写回;开始改了还会把结束顺延(避免"负时长") */ + /** + * 「确定」:把草稿写回。 + * + * 🔵 工单 #34 追加需求(用户 2026-11-01): + * 「当我选择了开始时间并确定之后,结束时间要跟着开始时间变成开始时间加1个小时。 + * 也就是说,所有时间我们都默认它是一个小时。如果不是一个小时,再让他自己去改。」 + * + * ⇒ **点「开始」确定后,结束无条件重置为「开始 + 1 小时」**。 + * + * 改动前只在"会变成负时长"时才动结束(`if (结束 < 开始)`), + * 于是用户改了开始、结束还挂在老位置,实际时长变成随机的 —— 就是要灭掉这个。 + * + * ⚠️ 改「结束」那条路**一个字不动**:改结束不能影响开始。 + * 这正是"默认一小时、不像一小时再自己改"的另一半。 + * ⚠️ 跨天用 `fbPlusHours()` 的 `Date` 运算:23:30 + 1h ⇒ **次日 00:30**, + * 日期一起进一天。不要图省事写成 `fbAddHour()` —— 那个封顶 23 点且不带日期。 + * ⚠️ 全天日程不参与:时刻部分本来就不生效(`save()` 里全天走 `00:00:00`), + * 这里改了也不会落到服务端,但会让"切回非全天"时看到一个莫名的时间,所以跳过。 + */ private applyPicker(iso: string, hm: string): void { if (this.pickIsEnd) { this.fEndDate = iso; @@ -679,10 +700,11 @@ export struct FbEventEditSheet { } else { this.fStartDate = iso; this.fStartTime = hm; - // ⚠️ 开始改到"结束之后" ⇒ 把结束顺延到同一时刻(服务端不校验,但负时长是坏的) - if (this.fEndDate < iso || (this.fEndDate === iso && this.fEndTime < hm)) { - this.fEndDate = iso; - this.fEndTime = hm; + if (!this.fAllDay) { + // 结束 = 开始 + 1 小时(默认时长一律一小时;要别的时长用户自己去点「结束」改) + const plus: FbDateTime = fbPlusHours(iso, hm, 1); + this.fEndDate = plus.date; + this.fEndTime = plus.time; } } this.pickOpen = false;