fix(待办): 补上截止时间选择器和父任务选择器弹层 (#49)

## 现象
新建待办时点「截止时间 - 修改」没反应;点「父任务 - 选择」也没反应。

## 根因
迁移到独立页面(#47)时,只搬了字段 UI 和状态变量,
**两个选择器弹层本身漏写了**:
  `pickShow` / `parentPickShow` 只被赋值为 true,
  但 build() 里没有任何 UI 消费它们 → 赋值等于空操作。

对比原弹层版本:时间选择器是叠在弹层之上的一个 if 块;
迁到页面后需要改成 bindSheet,这一步遗漏了。

## 修复
按 EventEditPage 的做法补两个弹层,**共用唯一一个 bindSheet**:

⚠️ 关键约束(EventEditPage:105 记载的坑):
ArkUI 同一组件挂多个 bindSheet 时,后一个会覆盖前一个,
实测表现就是"白框一闪而过、什么都没出来"。
所以用一个 `sheetShow` + `sheetMode`:
  sheetMode='due'    → DatePicker + TimePicker + 确定
  sheetMode='parent' → 父任务列表 + 无父任务 + 取消

## 改动点
- 加 `sheetShow` / `sheetMode` 状态
- 「修改」→ sheetMode='due'
- 「选择」/整行 → sheetMode='parent'
- 根容器挂唯一 bindSheet,`bottomSheet()` 按 mode 分发
- 新增 `confirmDue()`:把 DatePicker/TimePicker 的值合成毫秒并置 hasDue=true

## 编译
BUILD SUCCESSFUL

## 静态自检
- bindSheet 实际调用 1 处(861 行;79 行是注释)
- DatePicker ×1、TimePicker ×1
- confirmDue 定义 + 调用
- sheetMode 赋值 3 处(默认值 + 两个入口)

## 未验证
真机(安装时设备离线)。需验证:时间滚轮能弹、选完确定后截止时间更新。
This commit is contained in:
2026-11-05 09:47:45 +08:00
parent aa3187f7d9
commit 007c112af2
+178 -3
View File
@@ -75,6 +75,14 @@ struct TodoEditPage {
// ---------- 截止时间选择器 ---------- // ---------- 截止时间选择器 ----------
@State pickShow: boolean = false; @State pickShow: boolean = false;
/**
* ⭐ 页面唯一底部弹层(ArkUI 同一页面挂多个 bindSheet 会互相覆盖,
* 实测表现为"白框一闪而过")。用 sheetMode 切换内容:
* 'due' → 截止时间(日期 + 时间滚轮)
* 'parent' → 父任务选择
*/
@State sheetShow: boolean = false;
@State sheetMode: string = 'due';
@State pickDate: Date = new Date(); @State pickDate: Date = new Date();
@State pickHour: number = 9; @State pickHour: number = 9;
@State pickMin: number = 0; @State pickMin: number = 0;
@@ -615,7 +623,8 @@ struct TodoEditPage {
.borderRadius(10) .borderRadius(10)
.backgroundColor($r('app.color.card_bg')) .backgroundColor($r('app.color.card_bg'))
.onClick(() => { .onClick(() => {
this.pickShow = true; this.sheetMode = 'due';
this.sheetShow = true;
}) })
if (this.hasDue) { if (this.hasDue) {
Text('清除') Text('清除')
@@ -745,7 +754,8 @@ struct TodoEditPage {
.fontSize(13) .fontSize(13)
.fontColor($r('app.color.brand')) .fontColor($r('app.color.brand'))
.onClick(() => { .onClick(() => {
this.parentPickShow = true; this.sheetMode = 'parent';
this.sheetShow = true;
}) })
} }
} }
@@ -754,7 +764,8 @@ struct TodoEditPage {
.borderRadius(10) .borderRadius(10)
.backgroundColor($r('app.color.card_bg')) .backgroundColor($r('app.color.card_bg'))
.onClick(() => { .onClick(() => {
this.parentPickShow = true; this.sheetMode = 'parent';
this.sheetShow = true;
}) })
// 附件 // 附件
@@ -847,6 +858,170 @@ struct TodoEditPage {
// ⭐ 沉浸式:背景延伸到状态栏/导航条区域(与 EventEditPage 一致) // ⭐ 沉浸式:背景延伸到状态栏/导航条区域(与 EventEditPage 一致)
// 只扩绘制范围、不移动子组件 → 状态栏区域同色,不再是割裂的白条 // 只扩绘制范围、不移动子组件 → 状态栏区域同色,不再是割裂的白条
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM]) .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
.bindSheet($$this.sheetShow, this.bottomSheet(), {
height: this.sheetMode === 'parent' ? 400 : 380,
showClose: false,
dragBar: true,
backgroundColor: $r('app.color.card_bg')
})
}
/** 唯一底部弹层:按 sheetMode 切换内容 */
@Builder
bottomSheet() {
if (this.sheetMode === 'parent') {
this.parentSheet()
} else {
this.dueSheet()
}
}
/** 截止时间选择弹层(日期 + 时间) */
@Builder
dueSheet() {
Column({ space: 14 }) {
Text('选择截止时间')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor($r('app.color.text_primary'))
.width('100%')
.textAlign(TextAlign.Center)
.padding({ top: 12 })
Row({ space: 6 }) {
DatePicker({
start: new Date(2000, 0, 1),
end: new Date(2050, 11, 31),
selected: this.pickDate
})
.onDateChange((value: Date) => {
this.pickDate = value;
})
// 滚轮文字显式指定颜色:默认取系统主题色,深色模式下会与弹层底色撞色
.textStyle({ color: $r('app.color.text_secondary'), font: { size: 14 } })
.selectedTextStyle({
color: $r('app.color.text_primary'),
font: { size: 16, weight: FontWeight.Medium }
})
.disappearTextStyle({ color: $r('app.color.text_hint'), font: { size: 14 } })
.layoutWeight(1)
.height(210)
TimePicker({
selected: new Date(2000, 0, 1, this.pickHour, this.pickMin)
})
.onChange((value: TimePickerResult) => {
this.pickHour = value.hour;
this.pickMin = value.minute;
})
.textStyle({ color: $r('app.color.text_secondary'), font: { size: 14 } })
.selectedTextStyle({
color: $r('app.color.text_primary'),
font: { size: 16, weight: FontWeight.Medium }
})
.disappearTextStyle({ color: $r('app.color.text_hint'), font: { size: 14 } })
.layoutWeight(1)
.height(210)
}
.width('100%')
Button('确定')
.width('100%')
.height(44)
.borderRadius(12)
.fontSize(15)
.fontColor($r('app.color.button_text'))
.backgroundColor($r('app.color.brand'))
.onClick(() => {
this.confirmDue();
this.sheetShow = false;
})
}
.width('100%')
.padding({ left: 16, right: 16, bottom: 24 })
}
/** 父任务选择弹层 */
@Builder
parentSheet() {
Column({ space: 10 }) {
Row({ space: 8 }) {
Text('选择父任务')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor($r('app.color.text_primary'))
Blank()
Text('无父任务')
.fontSize(14)
.fontColor($r('app.color.brand'))
.onClick(() => {
this.parentUid = '';
this.sheetShow = false;
})
}
.width('100%')
.padding({ top: 12 })
if (this.parentCandidates().length === 0) {
Text('没有可选的顶层任务')
.fontSize(13)
.fontColor($r('app.color.text_hint'))
.padding(20)
} else {
List({ space: 6 }) {
ForEach(this.parentCandidates(), (c: DisplayEvent) => {
ListItem() {
Row({ space: 8 }) {
Circle().width(8).height(8).fill(c.color)
Text(c.title === '' ? '(无标题待办)' : c.title)
.fontSize(14)
.fontColor($r('app.color.text_primary'))
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.layoutWeight(1)
if (this.parentUid === c.uid) {
Text('✓')
.fontSize(14)
.fontColor($r('app.color.brand'))
}
}
.width('100%')
.padding(12)
.borderRadius(10)
.backgroundColor($r('app.color.chip_off_bg'))
}
.width('100%')
.onClick(() => {
this.parentUid = c.uid;
this.sheetShow = false;
})
}, (c: DisplayEvent) => `pc_${c.uid}`)
}
.width('100%')
.layoutWeight(1)
.scrollBar(BarState.Off)
}
Button('取消')
.width('100%')
.height(44)
.borderRadius(12)
.fontSize(15)
.fontColor($r('app.color.text_primary'))
.backgroundColor($r('app.color.chip_off_bg'))
.onClick(() => {
this.sheetShow = false;
})
}
.width('100%')
.padding({ left: 16, right: 16, bottom: 24 })
.layoutWeight(1)
}
/** 确认截止时间:把滚轮值合成毫秒 */
private confirmDue(): void {
const d: Date = this.pickDate;
this.dueMs = new Date(d.getFullYear(), d.getMonth(), d.getDate(),
this.pickHour, this.pickMin).getTime();
this.hasDue = true;
} }
/** 字段小标题 */ /** 字段小标题 */