鸿蒙端日程编辑:点「开始」时间选择器改的却是结束时间 #34

Open
opened 2026-10-30 07:40:44 +00:00 by laoyang · 2 comments
Owner

现象

用户 2026-10-30 报:

鸿蒙端,日程添加时,选择时间,点上面,修改的反而是结束时间,这个是 bug。

在日程编辑表单里点**「开始」那一行(上面那行)弹出时间选择器,
选完确认后
实际被改的是「结束」时间**。

根因

entry/src/main/ets/components/FbEventEditSheet.ets 里,
dateTimeRow 与 openPicker 的参数语义相反:

// 调用方传的是 isStart
dateTimeRow(label: string, isStart: boolean) {
  FbDateTimeRow({
    ...
    onTap: (): void => {
      this.openPicker(isStart);      // ← isStart
    }
  })
}

// 被调用方收的是 isEnd
private openPicker(isEnd: boolean): void {
  this.pickIsEnd = isEnd;            // ← 名字换了,语义反了
  ...
}

isStart=true(点「开始」)→ isEnd=true → 整条链路按"结束"处理。

pickIsEnd 有三处消费者,全部跟着错:

位置 错误表现
openPicker 读初值 点「开始」时弹层显示的是结束时间
applyPicker 写回 点「开始」确认后写进 fEndDate/fEndTime
弹层标题 点「开始」时标题显示「选择结束时间」

为什么没被发现

两个控件长得一样、只有标签不同;且初值与写回同时错,
所以"弹出来的时间"和"改完的时间"是自洽的(都是结束时间),
看着不像坏,只是改错了对象。

对照:观影表单是对的

FbMovieNewSheet.ets 用同一个 FbDateTimePicker 组件,但 pickIsEnd 是显式赋值:

this.pickIsEnd = false;   // 开始
this.pickIsEnd = true;    // 结束

没有正反颠倒的空间。日程这边用布尔参数透传,才让"参数名"和"语义"有机会不一致。

修复

把 openPicker 参数语义统一成 isStart(与调用方一致),内部自行翻转:

private openPicker(isStart: boolean): void {
  this.pickIsEnd = !isStart;
  ...
}

不选"调用处传 !isStart"是因为那会造出 openPicker(!isStart) 的双重否定,
更容易再错一次。让函数名与参数指向同一件事,最不容易复发。

验收标准

  1. 点「开始」→ 标题「选择开始时间」,初值 = 当前开始时间
  2. 确认后只有开始时间变,结束时间不动
  3. 点「结束」→ 标题「选择结束时间」,初值 = 当前结束时间
  4. 改结束不影响开始
  5. 「开始改到结束之后 ⇒ 结束顺延」的既有逻辑仍生效

为什么单独建单

鸿蒙端日程编辑表单 openPicker 参数语义写反(isStart 传给 isEnd),导致改开始时间却改了结束时间。与 #31(只读日历编辑入口权限)、#33(地址联想)改的文件和验证方式完全不同。

## 现象 用户 2026-10-30 报: > 鸿蒙端,日程添加时,选择时间,点上面,修改的反而是结束时间,这个是 bug。 在日程编辑表单里点**「开始」**那一行(上面那行)弹出时间选择器, 选完确认后**实际被改的是「结束」时间**。 ## 根因 `entry/src/main/ets/components/FbEventEditSheet.ets` 里, `dateTimeRow` 与 `openPicker` 的参数**语义相反**: ```ts // 调用方传的是 isStart dateTimeRow(label: string, isStart: boolean) { FbDateTimeRow({ ... onTap: (): void => { this.openPicker(isStart); // ← isStart } }) } // 被调用方收的是 isEnd private openPicker(isEnd: boolean): void { this.pickIsEnd = isEnd; // ← 名字换了,语义反了 ... } ``` **`isStart=true`(点「开始」)→ `isEnd=true` → 整条链路按"结束"处理。** `pickIsEnd` 有三处消费者,全部跟着错: | 位置 | 错误表现 | |---|---| | `openPicker` 读初值 | 点「开始」时弹层显示的是**结束**时间 | | `applyPicker` 写回 | 点「开始」确认后写进 `fEndDate`/`fEndTime` | | 弹层标题 | 点「开始」时标题显示「选择**结束**时间」 | ## 为什么没被发现 两个控件长得一样、只有标签不同;且初值与写回**同时**错, 所以"弹出来的时间"和"改完的时间"是自洽的(都是结束时间), 看着不像坏,只是改错了对象。 ## 对照:观影表单是对的 `FbMovieNewSheet.ets` 用同一个 `FbDateTimePicker` 组件,但 `pickIsEnd` 是**显式赋值**: ```ts this.pickIsEnd = false; // 开始 this.pickIsEnd = true; // 结束 ``` 没有正反颠倒的空间。日程这边用布尔参数透传,才让"参数名"和"语义"有机会不一致。 ## 修复 把 `openPicker` 参数语义统一成 `isStart`(与调用方一致),内部自行翻转: ```ts private openPicker(isStart: boolean): void { this.pickIsEnd = !isStart; ... } ``` > 不选"调用处传 `!isStart`"是因为那会造出 `openPicker(!isStart)` 的双重否定, > 更容易再错一次。让函数名与参数指向同一件事,最不容易复发。 ## 验收标准 1. 点「开始」→ 标题「选择开始时间」,初值 = 当前开始时间 2. 确认后**只有**开始时间变,结束时间不动 3. 点「结束」→ 标题「选择结束时间」,初值 = 当前结束时间 4. 改结束不影响开始 5. 「开始改到结束之后 ⇒ 结束顺延」的既有逻辑仍生效 --- ### 为什么单独建单 鸿蒙端日程编辑表单 openPicker 参数语义写反(isStart 传给 isEnd),导致改开始时间却改了结束时间。与 #31(只读日历编辑入口权限)、#33(地址联想)改的文件和验证方式完全不同。
Author
Owner

已修复(commit d2d0a43,鸿蒙端仓库)

根因

FbEventEditSheet.ets 里 dateTimeRow 与 openPicker 的参数语义相反:

dateTimeRow(label: string, isStart: boolean) {
  ...
  this.openPicker(isStart);      // 传 isStart
}

private openPicker(isEnd: boolean): void {   // 收 isEnd
  this.pickIsEnd = isEnd;
  ...
}

isStart=true(点「开始」)→ isEnd=true → 整条链路按"结束"处理。

为什么一直没被发现

pickIsEnd 有三处消费者,全部一起错,所以表现是"自洽"的:

位置 错误表现
读初值 点「开始」时弹层显示的是结束时间
写回 点「开始」确认后写进 fEndDate/fEndTime
标题 点「开始」时显示「选择结束时间」

弹出来的值和改完的值都是结束时间,一致 ⇒ 看着不像坏,只是改错了对象。

修复

参数统一为 isStart,与调用方同名同义,内部自行取反:

private openPicker(isStart: boolean): void {
  this.pickIsEnd = !isStart;
  this.pickDate = fbParseDate(isStart ? this.fStartDate : this.fEndDate);
  const tm: Date = fbParseTime(isStart ? this.fStartTime : this.fEndTime);
  ...
}

没有选择在调用处写 openPicker(!isStart) —— 那会造成双重否定,更容易再错一次。

影响面

只有日程编辑受影响。 观影表单 FbMovieNewSheet.ets 用同一个
FbDateTimePicker 组件,但 pickIsEnd 是显式赋 false/true,没有颠倒空间。

全仓库 grep isStart/isEnd,除本处外无其它使用点。

验证

项 结果
编译 ✅ BUILD SUCCESSFUL,33 tasks(17 executed)
源码映射断言 ✅ 从真实源码提取 pickIsEnd=!isStart、读日期/读时间三条映射,全部符合预期
真值表 ✅ 点开始→读fStart*→写fStart*→标题「选择开始时间」;点结束→读fEnd*→写fEnd*→标题「选择结束时间」
反向验证 ✅ 用修复前代码跑同一判据正确转红(旧版点开始时读写都是 fEnd*),证明判据非恒真
真机 ⏸ 手机未连接(hdc list targets = [Empty]),待插上后验证

真机待验步骤(手机连上后我做):

  1. 新建日程 → 点「开始」→ 标题应是「选择开始时间」、初值为当前开始时间
  2. 改成别的时刻 → 确认 → 结束时间不变
  3. 点「结束」→ 标题「选择结束时间」→ 改 → 确认 → 开始时间不变
  4. 把开始改到结束之后 → 结束应自动顺延(原有逻辑)
## 已修复(commit `d2d0a43`,鸿蒙端仓库) ### 根因 `FbEventEditSheet.ets` 里 `dateTimeRow` 与 `openPicker` 的参数**语义相反**: ```ts dateTimeRow(label: string, isStart: boolean) { ... this.openPicker(isStart); // 传 isStart } private openPicker(isEnd: boolean): void { // 收 isEnd this.pickIsEnd = isEnd; ... } ``` `isStart=true`(点「开始」)→ `isEnd=true` → 整条链路按"结束"处理。 ### 为什么一直没被发现 `pickIsEnd` 有三处消费者,**全部一起错**,所以表现是"自洽"的: | 位置 | 错误表现 | |---|---| | 读初值 | 点「开始」时弹层显示的是结束时间 | | 写回 | 点「开始」确认后写进 `fEndDate`/`fEndTime` | | 标题 | 点「开始」时显示「选择**结束**时间」 | 弹出来的值和改完的值都是结束时间,一致 ⇒ 看着不像坏,只是改错了对象。 ### 修复 参数统一为 `isStart`,与调用方同名同义,内部自行取反: ```ts private openPicker(isStart: boolean): void { this.pickIsEnd = !isStart; this.pickDate = fbParseDate(isStart ? this.fStartDate : this.fEndDate); const tm: Date = fbParseTime(isStart ? this.fStartTime : this.fEndTime); ... } ``` 没有选择在调用处写 `openPicker(!isStart)` —— 那会造成双重否定,更容易再错一次。 ### 影响面 **只有日程编辑受影响。** 观影表单 `FbMovieNewSheet.ets` 用同一个 `FbDateTimePicker` 组件,但 `pickIsEnd` 是显式赋 `false`/`true`,没有颠倒空间。 全仓库 grep `isStart`/`isEnd`,除本处外无其它使用点。 ### 验证 | 项 | 结果 | |---|---| | 编译 | ✅ `BUILD SUCCESSFUL`,33 tasks(17 executed) | | 源码映射断言 | ✅ 从真实源码提取 `pickIsEnd=!isStart`、读日期/读时间三条映射,全部符合预期 | | 真值表 | ✅ 点开始→读`fStart*`→写`fStart*`→标题「选择开始时间」;点结束→读`fEnd*`→写`fEnd*`→标题「选择结束时间」 | | 反向验证 | ✅ 用修复前代码跑同一判据**正确转红**(旧版点开始时读写都是 `fEnd*`),证明判据非恒真 | | 真机 | ⏸ 手机未连接(`hdc list targets` = `[Empty]`),待插上后验证 | 真机待验步骤(手机连上后我做): 1. 新建日程 → 点「开始」→ 标题应是「选择开始时间」、初值为当前开始时间 2. 改成别的时刻 → 确认 → **结束时间不变** 3. 点「结束」→ 标题「选择结束时间」→ 改 → 确认 → **开始时间不变** 4. 把开始改到结束之后 → 结束应自动顺延(原有逻辑)
Author
Owner

追加需求(2026-11-01):确认「开始」后,结束时间自动 = 开始 + 1 小时

用户原话:

34,需要修改。当我选择了开始时间并确定之后,结束时间要跟着开始时间变成
开始时间加1个小时。也就是说,所有时间我们都默认它是一个小时。
如果不是一个小时,再让他自己去改。

现状

FbEventEditSheet.ets::applyPicker() 现在只在会变成负时长时才动结束时间:

} else {
  this.fStartDate = iso;
  this.fStartTime = hm;
  // ⚠️ 开始改到"结束之后" ⇒ 把结束顺延到同一时刻(服务端不校验,但负时长是坏的)
  if (this.fEndDate < iso || (this.fEndDate === iso && this.fEndTime < hm)) {
    this.fEndDate = iso;
    this.fEndTime = hm;
  }
}

⇒ 改开始时间时,只要结束还在后面,结束时间一点不动。
用户改了开始、结束还挂在老位置,实际时长变成随机的 —— 这正是他要改的。

要改成

点「开始」确定后:结束 = 开始 + 1 小时,无条件(不是"只在负时长时")。

  • 跨天:23:30 + 1h ⇒ 次日 00:30,结束日期跟着进一天
  • 跨月 / 跨年:交给 Date 运算,不自己算
  • 用户再自己去点「结束」改成别的时长 —— 那条路不变(改结束不动开始)

验收标准

  1. 新建日程,点「开始」选 14:00 → 确定 → 结束变成 15:00(同一天)
  2. 改成 23:30 → 确定 → 结束变成次日 00:30(日期+1,不是 23:30/23:30)
  3. 再点「结束」改成 16:00 → 开始不变(仍 14:00)
  4. 编辑一条已有日程(原本 2 小时),只改开始 → 结束也按 +1h 重置
  5. 全天日程:勾「全天」时不起作用(时刻部分本来就不参与)
  6. 原有缺陷回归(#34 的主体):点「开始」改的是开始、点「结束」改的是结束

为什么不再用现成的 fbAddHour()

FbDateTimeField.ets::fbAddHour() 是:

return fbPad2(Math.min(hh + 1, 23)) + ':' + mm;

两个问题:23:xx 加一小时仍是 23:xx(封顶),而且它只返回时间、不带日期,
没法表达「次日」。所以另写一个带日期的 fbPlusHour()(放在同一个文件,
与其它换算函数同一处,全应用唯一一份)。

## 追加需求(2026-11-01):确认「开始」后,结束时间自动 = 开始 + 1 小时 用户原话: > 34,需要修改。当我选择了开始时间并确定之后,结束时间要跟着开始时间变成 > 开始时间加1个小时。也就是说,所有时间我们都默认它是一个小时。 > 如果不是一个小时,再让他自己去改。 ### 现状 `FbEventEditSheet.ets::applyPicker()` 现在**只在会变成负时长时才动结束时间**: ```ts } else { this.fStartDate = iso; this.fStartTime = hm; // ⚠️ 开始改到"结束之后" ⇒ 把结束顺延到同一时刻(服务端不校验,但负时长是坏的) if (this.fEndDate < iso || (this.fEndDate === iso && this.fEndTime < hm)) { this.fEndDate = iso; this.fEndTime = hm; } } ``` ⇒ 改开始时间时,只要结束还在后面,**结束时间一点不动**。 用户改了开始、结束还挂在老位置,实际时长变成随机的 —— 这正是他要改的。 ### 要改成 点「开始」确定后:**结束 = 开始 + 1 小时**,无条件(不是"只在负时长时")。 - 跨天:23:30 + 1h ⇒ **次日 00:30**,结束日期跟着进一天 - 跨月 / 跨年:交给 `Date` 运算,不自己算 - 用户再自己去点「结束」改成别的时长 —— 那条路不变(改结束不动开始) ### 验收标准 1. 新建日程,点「开始」选 14:00 → 确定 → 结束变成 **15:00**(同一天) 2. 改成 23:30 → 确定 → 结束变成**次日 00:30**(日期+1,不是 23:30/23:30) 3. 再点「结束」改成 16:00 → 开始**不变**(仍 14:00) 4. 编辑一条**已有**日程(原本 2 小时),只改开始 → 结束也按 +1h 重置 5. 全天日程:勾「全天」时不起作用(时刻部分本来就不参与) 6. 原有缺陷回归(#34 的主体):点「开始」改的是开始、点「结束」改的是结束 ### 为什么不再用现成的 `fbAddHour()` `FbDateTimeField.ets::fbAddHour()` 是: ```ts return fbPad2(Math.min(hh + 1, 23)) + ':' + mm; ``` 两个问题:**23:xx 加一小时仍是 23:xx**(封顶),而且它只返回时间、不带日期, 没法表达「次日」。所以另写一个带日期的 `fbPlusHour()`(放在同一个文件, 与其它换算函数同一处,全应用唯一一份)。
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: laoyang/FamilyBook#34