feat(待办): 投射到日历视图 —— 日时间轴顶部显示"当天截止"待办区 (#45)
待办只有 DUE 没有 DTSTART,无法画时间轴色块;历史上 buildEventDaySet() 明确"待办不计入月视图"。本次按方案 A:在日视图时间轴顶部加「当天截止」待办区。 ## 显示 - 位置:日时间轴(月/周视图选中日下方 + 列表视图每天卡片)顶部、 全天/跨天区之上,随内容一起滚动 - 每行:勾选圈(颜色跟随日历本)+ 标题(完成态划线) - 只有「当天截止 + 未完成 + 有截止时间」的待办才显示,按截止时间升序 ## 实现 - `DayTimeline` 加 `todos` 字段 - `Index.todosDueOn(dateMs)` 筛选当天截止待办 - `rebuildDayTimeline()` / `buildAgendaTimelines()` 两处填充 - `DayTimelineView` 加 `onToggleTodo` 回调 + 待办区渲染 - 勾选直接复用 `Index.toggleTodo`(级联完成、提醒重排等既有逻辑全部生效) ## 刷新 toggleTodo 成功后除 reload this.todos,还重建 dayTimeline / agendaTimelines —— 否则待办区是快照,勾选后不消失/不划线。 ## 验证 - 编译 BUILD SUCCESSFUL - 筛选逻辑等价验证:只显示(当天截止+未完成+有截止),按时间升序,边界正确 - 真机未验(设备离线) ## 未验证 真机:勾选圈交互、待办区随数据刷新、列表视图卡片里的待办区显示。
This commit is contained in:
@@ -36,6 +36,8 @@ export class DayTimeline {
|
||||
allDay: DisplayEvent[] = []; // 全天 / 跨天(画在 0 点之前)
|
||||
blocks: TimelineBlock[] = []; // 时间轴色块
|
||||
hasTimed: boolean = false; // 当天是否有有时间日程
|
||||
/** 当天截止、尚未完成的待办(画在时间轴顶部、全天区之上,带勾选框) */
|
||||
todos: DisplayEvent[] = [];
|
||||
}
|
||||
|
||||
/** 冲突分组:一组时间上互相重叠的日程(用于横向平分宽度) */
|
||||
|
||||
@@ -654,13 +654,27 @@ struct Index {
|
||||
this.rebuildDayTimeline();
|
||||
}
|
||||
|
||||
/**
|
||||
* 当天截止、尚未完成的待办(#45 待办投射到日历)。
|
||||
* 待办的 DUE 存在 startTime;只有未完成且有截止时间的才显示。
|
||||
*/
|
||||
private todosDueOn(dateMs: number): DisplayEvent[] {
|
||||
const day: number = this.startOfDay(dateMs);
|
||||
const out: DisplayEvent[] = this.todos.filter(
|
||||
(t: DisplayEvent): boolean => !t.completed && t.startTime > 0
|
||||
&& this.startOfDay(t.startTime) === day);
|
||||
out.sort((a: DisplayEvent, b: DisplayEvent): number => a.startTime - b.startTime);
|
||||
return out;
|
||||
}
|
||||
|
||||
/** 重建"选中日"的时间轴布局(月/周视图下方那条) */
|
||||
private rebuildDayTimeline(): void {
|
||||
const dayEvents: DisplayEvent[] = this.eventsOfDate(this.selectedDate);
|
||||
const tl: DayTimeline = TimelineUtil.build(dayEvents, this.selectedDate);
|
||||
tl.todos = this.todosDueOn(this.selectedDate);
|
||||
TimelineUtil.markNow(tl.blocks, this.nowMs);
|
||||
this.dayTimeline = tl;
|
||||
LogUtil.write(`时间轴(${this.fmtDateCn(this.selectedDate)}):输入 ${dayEvents.length} 条 → 色块 ${tl.blocks.length} 块,全天/跨天 ${tl.allDay.length} 条`);
|
||||
LogUtil.write(`时间轴(${this.fmtDateCn(this.selectedDate)}):输入 ${dayEvents.length} 条 → 色块 ${tl.blocks.length} 块,全天/跨天 ${tl.allDay.length} 条,待办 ${tl.todos.length} 条`);
|
||||
}
|
||||
|
||||
/** 选中日期变化(点月格/周条)→ 重算该日时间轴 */
|
||||
@@ -680,6 +694,7 @@ struct Index {
|
||||
d.allDay = this.dayTimeline.allDay;
|
||||
d.blocks = this.dayTimeline.blocks;
|
||||
d.hasTimed = this.dayTimeline.hasTimed;
|
||||
d.todos = this.dayTimeline.todos;
|
||||
this.dayTimeline = d;
|
||||
this.agendaTimelines = this.agendaTimelines.slice();
|
||||
}
|
||||
@@ -2382,7 +2397,10 @@ struct Index {
|
||||
nowMs: this.nowMs,
|
||||
showNowLine: this.selectedDate === this.startOfDay(this.nowMs),
|
||||
scrollable: true,
|
||||
onPick: (e: DisplayEvent): void => this.openEvent(e)
|
||||
onPick: (e: DisplayEvent): void => this.openEvent(e),
|
||||
onToggleTodo: (t: DisplayEvent): void => {
|
||||
this.toggleTodo(t);
|
||||
}
|
||||
})
|
||||
if (!this.dayTimeline.hasTimed && this.dayTimeline.allDay.length === 0 && !this.loading) {
|
||||
Text('当天没有日程')
|
||||
@@ -2513,7 +2531,10 @@ struct Index {
|
||||
nowMs: this.nowMs,
|
||||
showNowLine: day.isToday,
|
||||
scrollable: false,
|
||||
onPick: (e: DisplayEvent): void => this.openEvent(e)
|
||||
onPick: (e: DisplayEvent): void => this.openEvent(e),
|
||||
onToggleTodo: (t: DisplayEvent): void => {
|
||||
this.toggleTodo(t);
|
||||
}
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
@@ -2698,6 +2719,7 @@ struct Index {
|
||||
g.label = key === today ? '今天' : this.fmtDateCn(key);
|
||||
g.isToday = key === today;
|
||||
const tl: DayTimeline = TimelineUtil.build(map.get(key) ?? [], key);
|
||||
tl.todos = this.todosDueOn(key);
|
||||
TimelineUtil.markNow(tl.blocks, this.nowMs);
|
||||
g.timeline = tl;
|
||||
out.push(g);
|
||||
@@ -4132,6 +4154,11 @@ struct Index {
|
||||
}
|
||||
// 重新读库刷新列表(服务层已更新 completed)
|
||||
this.todos = await CalendarDataService.loadTodos(ctx, this.sources);
|
||||
// ⭐ 时间轴上的待办区是快照,勾选后必须重建,否则不消失/不划线(#45)
|
||||
this.rebuildDayTimeline();
|
||||
if (this.mode === 'agenda') {
|
||||
this.agendaTimelines = this.buildAgendaTimelines(this.agendaEvents);
|
||||
}
|
||||
// ⭐ 桌面卡片是独立实例,必须显式推送,否则停在旧数据
|
||||
await CardDataService.pushToAllForms(ctx);
|
||||
// 完成/取消完成会改变"该不该提醒",需要重排
|
||||
@@ -4353,6 +4380,7 @@ struct DayTimelineView {
|
||||
private twoLineVp: number = 30; // 能放下"标题 + 时间/地点"两行的高度门槛
|
||||
private threeLineVp: number = 46; // 能放下"标题 / 时间 / 地址"三行的高度门槛
|
||||
onPick: (e: DisplayEvent) => void = (e: DisplayEvent): void => {};
|
||||
onToggleTodo: (t: DisplayEvent) => void = (t: DisplayEvent): void => {}; // 勾选待办回调
|
||||
private scroller: Scroller = new Scroller();
|
||||
@State private viewportH: number = 0; // 滚动视口高度(由 Scroll 实测,用于把"当前时刻"居中)
|
||||
@State private allDayH: number = 0; // 顶部"全天/跨天"区高度(它现在随内容滚动,居中定位要加上)
|
||||
@@ -4629,6 +4657,45 @@ struct DayTimelineView {
|
||||
@Builder
|
||||
private timelineBody() {
|
||||
Column() {
|
||||
// ⭐ 当天截止待办区(#45):画在最顶,全天/跨天之上,随内容一起滚动。
|
||||
if (this.timeline.todos.length > 0) {
|
||||
Column({ space: 4 }) {
|
||||
ForEach(this.timeline.todos, (td: DisplayEvent) => {
|
||||
Row({ space: 8 }) {
|
||||
// 勾选圈:点击切换完成(与待办列表同一回调)
|
||||
Text(td.completed ? '✓' : '')
|
||||
.fontSize(12)
|
||||
.fontColor(td.completed ? '#FFFFFF' : 'rgba(0,0,0,0)')
|
||||
.width(18)
|
||||
.height(18)
|
||||
.textAlign(TextAlign.Center)
|
||||
.borderRadius(9)
|
||||
.backgroundColor(td.completed ? td.color : 'rgba(0,0,0,0)')
|
||||
.border({ width: 1.5, color: td.color })
|
||||
.onClick(() => {
|
||||
this.onToggleTodo(td);
|
||||
})
|
||||
Text(td.title === '' ? '(无标题待办)' : td.title)
|
||||
.fontSize(12)
|
||||
.fontColor($r('app.color.text_primary'))
|
||||
.decoration({ type: td.completed ? TextDecorationType.LineThrough : TextDecorationType.None })
|
||||
.maxLines(1)
|
||||
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||
.layoutWeight(1)
|
||||
}
|
||||
.alignItems(VerticalAlign.Center)
|
||||
.width('100%')
|
||||
.height(24)
|
||||
.padding({ left: 8, right: 8 })
|
||||
.borderRadius(6)
|
||||
.backgroundColor($r('app.color.card_bg'))
|
||||
.border({ width: 0.5, color: $r('app.color.shadow_color') })
|
||||
}, (td: DisplayEvent) => `todo_${this.timeline.dateKey}_${td.id}_${td.completed}`)
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ bottom: 6 })
|
||||
}
|
||||
|
||||
// 全天 / 跨天日程:同样用**色块**展示(与普通日程保持一致的视觉),
|
||||
// 画在 0 点之前并**随内容一起滚动**(向下滑时跟 0 点一起上移,不再常驻顶部)
|
||||
if (this.timeline.allDay.length > 0) {
|
||||
|
||||
Reference in New Issue
Block a user