@@ -75,6 +75,9 @@ struct Index {
|
||||
// 时间轴视图(月/周/列表共用):按天预计算好色块布局,避免 build 中重算
|
||||
@State dayTimeline: DayTimeline = new DayTimeline(); // 选中日(月/周视图用)
|
||||
@State agendaTimelines: AgendaTimelineGroup[] = []; // 列表视图:每天一条时间轴
|
||||
// 日程显示方式:'timeline'(竖向时间轴,默认) | 'list'(列表)。
|
||||
// 列表模式无时间刻度 → 不绘制"当前时间红线",改用"进行中"文字标记。
|
||||
@State displayStyle: string = 'timeline';
|
||||
|
||||
// ---- 隐私政策与用户协议首启同意(未同意前不申请任何权限、不加载数据) ----
|
||||
@State policyChecked: boolean = false; // 是否已读取过同意状态(避免首帧闪烁)
|
||||
@@ -339,6 +342,8 @@ struct Index {
|
||||
}
|
||||
this.accounts = await AccountStore.loadAll(context);
|
||||
this.sources = await CalendarDataService.loadSources(context);
|
||||
// 显示方式(时间轴 / 列表)可能在设置页被改过,每次回到前台都重新读一次
|
||||
this.displayStyle = await AppSettings.getDisplayStyle(context);
|
||||
await this.reloadEvents();
|
||||
this.startAutoSync();
|
||||
}
|
||||
@@ -2009,120 +2014,320 @@ struct Index {
|
||||
)
|
||||
}
|
||||
|
||||
/** 单日竖向时间轴滚动区(周视图 / 月视图下方共用);DayTimelineView 自身带 Scroll 并自动定位到当前时刻 */
|
||||
/** 单日日程展示区(周视图 / 月视图下方共用)。
|
||||
* - timeline 模式:竖向时间轴(DayTimelineView,带"当前时间红线")。
|
||||
* - list 模式:按"行"平铺(eventRow),无时间刻度 → 不画红线,进行中改用文字标记。 */
|
||||
@Builder
|
||||
eventList() {
|
||||
Column() {
|
||||
DayTimelineView({
|
||||
timeline: this.dayTimeline,
|
||||
nowMs: this.nowMs,
|
||||
showNowLine: this.selectedDate === this.startOfDay(this.nowMs),
|
||||
scrollable: true,
|
||||
onPick: (e: DisplayEvent): void => this.openEvent(e)
|
||||
})
|
||||
if (!this.dayTimeline.hasTimed && this.dayTimeline.allDay.length === 0 && !this.loading) {
|
||||
Text('当天没有日程')
|
||||
.fontSize(13)
|
||||
.fontColor($r('app.color.text_hint'))
|
||||
.width('100%')
|
||||
.textAlign(TextAlign.Center)
|
||||
.padding(20)
|
||||
}
|
||||
}
|
||||
.layoutWeight(1)
|
||||
.width('100%')
|
||||
.padding({ left: 12, right: 16, top: 4, bottom: 24 })
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
// 下半区(日程展示区)左右滑 → 按天切换(一次 1 天),仅周视图生效
|
||||
.gesture(
|
||||
SwipeGesture({ direction: SwipeDirection.Horizontal })
|
||||
.onAction((event: GestureEvent) => {
|
||||
if (this.mode !== 'week') {
|
||||
return;
|
||||
if (this.displayStyle === 'list') {
|
||||
Scroll() {
|
||||
Column({ space: 8 }) {
|
||||
ForEach(this.eventsOfDate(this.selectedDate), (e: DisplayEvent) => {
|
||||
this.eventRow(e, this.isEventNow(e, this.selectedDate === this.startOfDay(this.nowMs)))
|
||||
}, (e: DisplayEvent) => `${e.isSystem ? 's' : 'l'}${e.id}_${e.startTime}_${e.title}`)
|
||||
if (this.eventsOfDate(this.selectedDate).length === 0 && !this.loading) {
|
||||
Text('当天没有日程')
|
||||
.fontSize(13)
|
||||
.fontColor($r('app.color.text_hint'))
|
||||
.width('100%')
|
||||
.textAlign(TextAlign.Center)
|
||||
.padding(20)
|
||||
}
|
||||
this.switchDay(this.isSwipeLeft(event) ? 1 : -1);
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: 20, right: 20, top: 4, bottom: 24 })
|
||||
.constraintSize({ minHeight: '100%' })
|
||||
}
|
||||
.layoutWeight(1)
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
.align(Alignment.Top)
|
||||
.gesture(
|
||||
SwipeGesture({ direction: SwipeDirection.Horizontal })
|
||||
.onAction((event: GestureEvent) => {
|
||||
if (this.mode !== 'week') {
|
||||
return;
|
||||
}
|
||||
this.switchDay(this.isSwipeLeft(event) ? 1 : -1);
|
||||
})
|
||||
)
|
||||
} else {
|
||||
Column() {
|
||||
DayTimelineView({
|
||||
timeline: this.dayTimeline,
|
||||
nowMs: this.nowMs,
|
||||
showNowLine: this.selectedDate === this.startOfDay(this.nowMs),
|
||||
scrollable: true,
|
||||
onPick: (e: DisplayEvent): void => this.openEvent(e)
|
||||
})
|
||||
)
|
||||
if (!this.dayTimeline.hasTimed && this.dayTimeline.allDay.length === 0 && !this.loading) {
|
||||
Text('当天没有日程')
|
||||
.fontSize(13)
|
||||
.fontColor($r('app.color.text_hint'))
|
||||
.width('100%')
|
||||
.textAlign(TextAlign.Center)
|
||||
.padding(20)
|
||||
}
|
||||
}
|
||||
.layoutWeight(1)
|
||||
.width('100%')
|
||||
.padding({ left: 12, right: 16, top: 4, bottom: 24 })
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
// 下半区(日程展示区)左右滑 → 按天切换(一次 1 天),仅周视图生效
|
||||
.gesture(
|
||||
SwipeGesture({ direction: SwipeDirection.Horizontal })
|
||||
.onAction((event: GestureEvent) => {
|
||||
if (this.mode !== 'week') {
|
||||
return;
|
||||
}
|
||||
this.switchDay(this.isSwipeLeft(event) ? 1 : -1);
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Builder
|
||||
agendaBody() {
|
||||
List({ space: 10, scroller: this.agendaScroller }) {
|
||||
ListItem() {
|
||||
Row({ space: 8 }) {
|
||||
if (this.loading) {
|
||||
LoadingProgress()
|
||||
.width(24)
|
||||
.height(24)
|
||||
.color($r('app.color.brand'))
|
||||
}
|
||||
Text(this.loading ? '正在加载全部日程…' : `共 ${this.agendaEvents.length} 条` +
|
||||
`,${this.agendaTimelines.length} 天`)
|
||||
.fontSize(12)
|
||||
.fontColor($r('app.color.text_hint'))
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ top: 2, bottom: 2 })
|
||||
}
|
||||
|
||||
// 每一天:日期头 + 该日一条竖向时间轴(0-23 格,色块按时间平铺,冲突平分宽度)
|
||||
ForEach(this.agendaTimelines, (day: AgendaTimelineGroup) => {
|
||||
if (this.displayStyle === 'list') {
|
||||
// 列表模式:按日分组,每天一条日期头 + 该日若干"行"卡片;不画红线,进行中改用文字标记
|
||||
List({ space: 6, scroller: this.agendaScroller }) {
|
||||
ListItem() {
|
||||
Column({ space: 6 }) {
|
||||
Row({ space: 8 }) {
|
||||
Text(day.label)
|
||||
.fontSize(14)
|
||||
.fontWeight(FontWeight.Bold)
|
||||
.fontColor(day.isToday ? $r('app.color.brand') : $r('app.color.text_primary'))
|
||||
if (day.isToday) {
|
||||
Text('今天')
|
||||
.fontSize(10)
|
||||
.fontColor($r('app.color.button_text'))
|
||||
.backgroundColor($r('app.color.brand'))
|
||||
.borderRadius(6)
|
||||
.padding({ left: 6, right: 6, top: 1, bottom: 1 })
|
||||
}
|
||||
Blank()
|
||||
Text(`${day.timeline.blocks.length + day.timeline.allDay.length} 条`)
|
||||
.fontSize(11)
|
||||
.fontColor($r('app.color.text_hint'))
|
||||
Row({ space: 8 }) {
|
||||
if (this.loading) {
|
||||
LoadingProgress()
|
||||
.width(24)
|
||||
.height(24)
|
||||
.color($r('app.color.brand'))
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ top: 4 })
|
||||
|
||||
DayTimelineView({
|
||||
timeline: day.timeline,
|
||||
nowMs: this.nowMs,
|
||||
showNowLine: day.isToday,
|
||||
scrollable: false,
|
||||
onPick: (e: DisplayEvent): void => this.openEvent(e)
|
||||
})
|
||||
Text(this.loading ? '正在加载全部日程…' : `共 ${this.agendaEvents.length} 条`)
|
||||
.fontSize(12)
|
||||
.fontColor($r('app.color.text_hint'))
|
||||
}
|
||||
.width('100%')
|
||||
.padding(10)
|
||||
.borderRadius(12)
|
||||
.backgroundColor($r('app.color.card_bg'))
|
||||
.border({ width: 1, color: $r('app.color.shadow_color') })
|
||||
.padding({ top: 2, bottom: 2 })
|
||||
}
|
||||
}, (day: AgendaTimelineGroup) => `day_${day.dateMs}`)
|
||||
|
||||
ForEach(this.agendaTimelines, (day: AgendaTimelineGroup) => {
|
||||
ListItem() {
|
||||
Text(day.label)
|
||||
.fontSize(13)
|
||||
.fontWeight(FontWeight.Bold)
|
||||
.fontColor(day.isToday ? $r('app.color.brand') : $r('app.color.text_secondary'))
|
||||
.width('100%')
|
||||
.padding({ top: 6, bottom: 2 })
|
||||
}
|
||||
ForEach(this.dayEvents(day), (e: DisplayEvent) => {
|
||||
ListItem() {
|
||||
this.eventRow(e, this.isEventNow(e, day.isToday))
|
||||
}
|
||||
.width('100%')
|
||||
}, (e: DisplayEvent) => `${e.isSystem ? 's' : 'l'}${e.id}_${e.startTime}_${e.title}`)
|
||||
}, (day: AgendaTimelineGroup) => `day_${day.dateMs}`)
|
||||
}
|
||||
.width('100%')
|
||||
.layoutWeight(1)
|
||||
.scrollBar(BarState.Auto)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
.padding({ left: 20, right: 20, top: 6, bottom: 24 })
|
||||
.cachedCount(8)
|
||||
.onAreaChange((oldVal: Area, newVal: Area): void => {
|
||||
const h: number = Number(newVal.height);
|
||||
if (h > 0 && (h - this.agendaViewportH > 1 || h - this.agendaViewportH < -1)) {
|
||||
this.agendaViewportH = h;
|
||||
if (this.agendaScrollPending) {
|
||||
this.doAgendaScroll();
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// 时间轴模式:每天一条竖向时间轴(0-23 格,色块按时间平铺,冲突平分宽度;今天画红线)
|
||||
List({ space: 10, scroller: this.agendaScroller }) {
|
||||
ListItem() {
|
||||
Row({ space: 8 }) {
|
||||
if (this.loading) {
|
||||
LoadingProgress()
|
||||
.width(24)
|
||||
.height(24)
|
||||
.color($r('app.color.brand'))
|
||||
}
|
||||
Text(this.loading ? '正在加载全部日程…' : `共 ${this.agendaEvents.length} 条` +
|
||||
`,${this.agendaTimelines.length} 天`)
|
||||
.fontSize(12)
|
||||
.fontColor($r('app.color.text_hint'))
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ top: 2, bottom: 2 })
|
||||
}
|
||||
|
||||
// 每一天:日期头 + 该日一条竖向时间轴(0-23 格,色块按时间平铺,冲突平分宽度)
|
||||
ForEach(this.agendaTimelines, (day: AgendaTimelineGroup) => {
|
||||
ListItem() {
|
||||
Column({ space: 6 }) {
|
||||
Row({ space: 8 }) {
|
||||
Text(day.label)
|
||||
.fontSize(14)
|
||||
.fontWeight(FontWeight.Bold)
|
||||
.fontColor(day.isToday ? $r('app.color.brand') : $r('app.color.text_primary'))
|
||||
if (day.isToday) {
|
||||
Text('今天')
|
||||
.fontSize(10)
|
||||
.fontColor($r('app.color.button_text'))
|
||||
.backgroundColor($r('app.color.brand'))
|
||||
.borderRadius(6)
|
||||
.padding({ left: 6, right: 6, top: 1, bottom: 1 })
|
||||
}
|
||||
Blank()
|
||||
Text(`${day.timeline.blocks.length + day.timeline.allDay.length} 条`)
|
||||
.fontSize(11)
|
||||
.fontColor($r('app.color.text_hint'))
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ top: 4 })
|
||||
|
||||
DayTimelineView({
|
||||
timeline: day.timeline,
|
||||
nowMs: this.nowMs,
|
||||
showNowLine: day.isToday,
|
||||
scrollable: false,
|
||||
onPick: (e: DisplayEvent): void => this.openEvent(e)
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.padding(10)
|
||||
.borderRadius(12)
|
||||
.backgroundColor($r('app.color.card_bg'))
|
||||
.border({ width: 1, color: $r('app.color.shadow_color') })
|
||||
}
|
||||
}, (day: AgendaTimelineGroup) => `day_${day.dateMs}`)
|
||||
}
|
||||
.width('100%')
|
||||
.layoutWeight(1)
|
||||
.scrollBar(BarState.Auto)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
.padding({ left: 12, right: 12, top: 6, bottom: 24 })
|
||||
.cachedCount(4)
|
||||
.onAreaChange((oldVal: Area, newVal: Area): void => {
|
||||
const h: number = Number(newVal.height);
|
||||
if (h > 0 && (h - this.agendaViewportH > 1 || h - this.agendaViewportH < -1)) {
|
||||
this.agendaViewportH = h;
|
||||
if (this.agendaScrollPending) {
|
||||
this.doAgendaScroll();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.layoutWeight(1)
|
||||
.scrollBar(BarState.Auto)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
.padding({ left: 12, right: 12, top: 6, bottom: 24 })
|
||||
.cachedCount(4)
|
||||
.onAreaChange((oldVal: Area, newVal: Area): void => {
|
||||
const h: number = Number(newVal.height);
|
||||
if (h > 0 && (h - this.agendaViewportH > 1 || h - this.agendaViewportH < -1)) {
|
||||
this.agendaViewportH = h;
|
||||
if (this.agendaScrollPending) {
|
||||
this.doAgendaScroll();
|
||||
}
|
||||
|
||||
/** 日程"行"卡片(列表模式 / 卡片共用):左侧 4px 颜色条 + 标题 + 时间/地点 + 所属日历本。
|
||||
* isNow=true 时以红色强调并打"● 进行中"标记(替代时间轴里的"当前时间红线")。 */
|
||||
@Builder
|
||||
eventRow(e: DisplayEvent, isNow: boolean = false) {
|
||||
Row({ space: 10 }) {
|
||||
Column()
|
||||
.width(4)
|
||||
.height(38)
|
||||
.borderRadius(2)
|
||||
.backgroundColor(isNow ? '#FF3B30' : e.color)
|
||||
Column({ space: 3 }) {
|
||||
Text(e.title === '' ? '(无标题)' : e.title)
|
||||
.fontSize(15)
|
||||
.fontColor(isNow ? '#FF3B30' : $r('app.color.text_primary'))
|
||||
.maxLines(1)
|
||||
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||
Row({ space: 6 }) {
|
||||
Text(e.isAllDay || this.spansDays(e)
|
||||
? '全天' : `${this.fmtTime(e.startTime)} - ${this.fmtTime(e.endTime)}`)
|
||||
.fontSize(12)
|
||||
.fontColor(isNow ? '#FF3B30' : $r('app.color.text_secondary'))
|
||||
if (e.recurring) {
|
||||
Text('↻ 重复')
|
||||
.fontSize(10)
|
||||
.fontColor($r('app.color.text_hint'))
|
||||
.padding({ left: 4, right: 4, top: 1, bottom: 1 })
|
||||
.borderRadius(4)
|
||||
.backgroundColor($r('app.color.chip_off_bg'))
|
||||
}
|
||||
if (isNow) {
|
||||
Text('● 进行中')
|
||||
.fontSize(10)
|
||||
.fontColor('#FF3B30')
|
||||
.padding({ left: 4, right: 4, top: 1, bottom: 1 })
|
||||
.borderRadius(4)
|
||||
.backgroundColor('#FFECEA')
|
||||
}
|
||||
}
|
||||
if (e.location !== '') {
|
||||
Row({ space: 4 }) {
|
||||
Text('📍')
|
||||
.fontSize(11)
|
||||
.fontColor($r('app.color.text_hint'))
|
||||
Text(e.location)
|
||||
.fontSize(12)
|
||||
.fontColor($r('app.color.text_hint'))
|
||||
.maxLines(1)
|
||||
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||
}
|
||||
.width('100%')
|
||||
}
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
if (e.isSystem) {
|
||||
Text('系统')
|
||||
.fontSize(10)
|
||||
.fontColor($r('app.color.text_hint'))
|
||||
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
|
||||
.borderRadius(6)
|
||||
.backgroundColor($r('app.color.chip_off_bg'))
|
||||
}
|
||||
// 所属日历本:最右侧、垂直居中,颜色同日历本;只读日历本加删除线标识
|
||||
if (e.calName !== '') {
|
||||
Text(e.calName)
|
||||
.fontSize(11)
|
||||
.fontColor(e.color)
|
||||
.maxLines(1)
|
||||
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||
.constraintSize({ maxWidth: '30%' })
|
||||
.decoration({ type: e.writable ? TextDecorationType.None : TextDecorationType.LineThrough })
|
||||
}
|
||||
}
|
||||
.alignItems(VerticalAlign.Center)
|
||||
.width('100%')
|
||||
.padding(10)
|
||||
.borderRadius(12)
|
||||
.backgroundColor($r('app.color.card_bg'))
|
||||
.border({ width: 1, color: $r('app.color.shadow_color') })
|
||||
.onClick(() => {
|
||||
this.openEvent(e);
|
||||
})
|
||||
}
|
||||
|
||||
/** 某条日程此刻是否"正在进行"。仅当天、且为按时段(非全天/非跨天)且 now 落在 [start,end) 才算。
|
||||
* 列表模式用这个替代时间轴的"当前时间红线":红条 + "● 进行中"文字标记。 */
|
||||
private isEventNow(e: DisplayEvent, isToday: boolean): boolean {
|
||||
if (!isToday) {
|
||||
return false;
|
||||
}
|
||||
if (e.isAllDay || this.spansDays(e)) {
|
||||
return false;
|
||||
}
|
||||
return e.startTime <= this.nowMs && this.nowMs < e.endTime;
|
||||
}
|
||||
|
||||
/** 取某天时间轴里要按"行"平铺展示的全部日程(全天 + 时段色块对应事件)。 */
|
||||
private dayEvents(day: AgendaTimelineGroup): DisplayEvent[] {
|
||||
const arr: DisplayEvent[] = [];
|
||||
for (const a of day.timeline.allDay) {
|
||||
arr.push(a);
|
||||
}
|
||||
for (const b of day.timeline.blocks) {
|
||||
if (b.ev !== null) {
|
||||
arr.push(b.ev);
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
/** 列表视图:把每天的分组转成"一天一条竖向时间轴"
|
||||
* 多日/跨天日程会**展开到它覆盖的每一天**(起始日记为跨天,中间日整天),
|
||||
* 确保列表里每天的数据完整,而不是只挂在第一天。 */
|
||||
|
||||
@@ -4,6 +4,7 @@ import { router } from '@kit.ArkUI';
|
||||
import { common } from '@kit.AbilityKit';
|
||||
import { notificationManager } from '@kit.NotificationKit';
|
||||
import { AppSettings } from '../common/AppSettings';
|
||||
import { CardDataService } from '../common/CardDataService';
|
||||
import { BackgroundSyncService } from '../common/BackgroundSyncService';
|
||||
import { AccountStore, DavAccount } from '../common/AccountStore';
|
||||
import { ReminderService } from '../common/ReminderService';
|
||||
@@ -39,6 +40,7 @@ struct SettingsPage {
|
||||
@State docUrl: string = '';
|
||||
@State showTips: boolean = false; // 功能引导卡片(底部弹出)
|
||||
@State defaultView: string = 'month'; // 打开 App 默认视图:month | week | agenda
|
||||
@State displayStyle: string = 'timeline'; // 日程显示方式:timeline(时间轴)| list(列表)
|
||||
private context?: common.Context;
|
||||
|
||||
aboutToAppear(): void {
|
||||
@@ -68,6 +70,9 @@ struct SettingsPage {
|
||||
AppSettings.getDefaultView(ctx).then((v: string): void => {
|
||||
this.defaultView = v;
|
||||
});
|
||||
AppSettings.getDisplayStyle(ctx).then((v: string): void => {
|
||||
this.displayStyle = v;
|
||||
});
|
||||
this.refreshNotifyState();
|
||||
this.loadBackupSettings();
|
||||
this.loadAllBooks();
|
||||
@@ -274,6 +279,25 @@ struct SettingsPage {
|
||||
: (this.defaultView === 'week' ? '周视图' : '列表视图');
|
||||
}
|
||||
|
||||
private async saveDisplayStyle(style: string): Promise<void> {
|
||||
if (this.context === undefined) {
|
||||
return;
|
||||
}
|
||||
this.displayStyle = style;
|
||||
await AppSettings.setDisplayStyle(this.context, style);
|
||||
// 同步刷桌面卡片:让 4×4 / 6×4 卡片立刻按新方式重绘(列表模式去掉红线)
|
||||
try {
|
||||
await CardDataService.pushToAllForms(this.context);
|
||||
} catch (err) {
|
||||
// 卡片刷新失败不影响设置保存,下次同步或卡片自刷新时会生效
|
||||
}
|
||||
this.getUIContext().getPromptAction().showToast({
|
||||
message: style === 'list'
|
||||
? '已切换为列表显示(不显示当前时间红线)'
|
||||
: '已切换为时间轴显示'
|
||||
});
|
||||
}
|
||||
|
||||
private intervalLabel(minutes: number): string {
|
||||
return minutes >= 60 ? `${minutes / 60} 小时` : `${minutes} 分钟`;
|
||||
}
|
||||
@@ -519,6 +543,43 @@ struct SettingsPage {
|
||||
.backgroundColor($r('app.color.card_bg'))
|
||||
.border({ width: 1, color: $r('app.color.shadow_color') })
|
||||
|
||||
// 日程显示方式:时间轴 / 列表(同时作用于主界面与桌面卡片)
|
||||
Column({ space: 8 }) {
|
||||
Row({ space: 10 }) {
|
||||
Column({ space: 2 }) {
|
||||
Text('日程显示方式')
|
||||
.fontSize(15)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor($r('app.color.text_primary'))
|
||||
Text('时间轴:按时间刻度看一天的占用与冲突;列表:快速扫读、适合长标题。列表模式不显示当前时间红线')
|
||||
.fontSize(12)
|
||||
.fontColor($r('app.color.text_secondary'))
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
Select([{ value: '时间轴' }, { value: '列表' }] as SelectOption[])
|
||||
.selected(this.displayStyle === 'list' ? 1 : 0)
|
||||
.value(this.displayStyle === 'list' ? '列表' : '时间轴')
|
||||
.fontColor($r('app.color.text_primary'))
|
||||
.font({ size: 14 })
|
||||
.optionFont({ size: 14 })
|
||||
.selectedOptionFont({ size: 14 })
|
||||
.onSelect((index: number) => {
|
||||
const v: string = index === 1 ? 'list' : 'timeline';
|
||||
if (v !== this.displayStyle) {
|
||||
this.saveDisplayStyle(v);
|
||||
}
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.width('100%')
|
||||
.padding(14)
|
||||
.borderRadius(12)
|
||||
.backgroundColor($r('app.color.card_bg'))
|
||||
.border({ width: 1, color: $r('app.color.shadow_color') })
|
||||
|
||||
// 数据修复:手动触发一次性全量重拉(重建提醒等本地残缺字段)
|
||||
Column({ space: 8 }) {
|
||||
Row({ space: 10 }) {
|
||||
|
||||
@@ -33,6 +33,16 @@ class TAllDay {
|
||||
isAllDay: boolean = true;
|
||||
}
|
||||
|
||||
/** 列表模式单条日程(解析 listJson) */
|
||||
class LItem4 {
|
||||
title: string = '';
|
||||
time: string = '';
|
||||
endTime: string = '';
|
||||
color: string = '#007DFF';
|
||||
calName: string = '';
|
||||
isNow: boolean = false;
|
||||
}
|
||||
|
||||
/** 冲突组:组内**贪心分列**,lanes[i] = 第 i 列(同列互不重叠) */
|
||||
class TGroup4 {
|
||||
startRatio: number = 0;
|
||||
@@ -79,6 +89,8 @@ struct Widget4x4Card {
|
||||
@LocalStorageProp('todayCount') todayCount: number = 0;
|
||||
@LocalStorageProp('isToday') isToday: boolean = true;
|
||||
@LocalStorageProp('dayCount') dayCount: number = 0;
|
||||
@LocalStorageProp('listJson') listJson: string = '[]';
|
||||
@LocalStorageProp('displayStyle') displayStyle: string = 'timeline';
|
||||
|
||||
private parseBlocks(): TBlock[] {
|
||||
try {
|
||||
@@ -459,6 +471,112 @@ struct Widget4x4Card {
|
||||
}
|
||||
}
|
||||
|
||||
/** 列表模式单条日程(解析 listJson) */
|
||||
private parseList(): LItem4[] {
|
||||
try {
|
||||
return JSON.parse(this.listJson) as LItem4[];
|
||||
} catch (err) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/** 列表模式单条行:左侧日历色条(进行中变红)+ 时间 + 标题 + 所属日历本;不画红线 */
|
||||
@Builder
|
||||
listRow(item: LItem4) {
|
||||
Row({ space: 8 }) {
|
||||
Column()
|
||||
.width(3)
|
||||
.height(item.time === '全天' ? 16 : 38)
|
||||
.borderRadius(2)
|
||||
.backgroundColor(item.isNow ? '#FF3B30' : item.color)
|
||||
if (item.time === '全天') {
|
||||
Text(item.title)
|
||||
.fontSize(12)
|
||||
.fontColor('#1A1A1A')
|
||||
.maxLines(1)
|
||||
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||
.layoutWeight(1)
|
||||
Text('全天')
|
||||
.fontSize(9)
|
||||
.fontColor('#FFFFFF')
|
||||
.backgroundColor(item.color)
|
||||
.borderRadius(6)
|
||||
.padding({ left: 5, right: 5, top: 1, bottom: 1 })
|
||||
} else {
|
||||
Column({ space: 2 }) {
|
||||
Text(item.time)
|
||||
.fontSize(10)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(item.isNow ? '#FF3B30' : '#333333')
|
||||
Text(item.endTime)
|
||||
.fontSize(10)
|
||||
.fontColor('#999999')
|
||||
}
|
||||
.width(38)
|
||||
.alignItems(HorizontalAlign.Center)
|
||||
Text(item.title)
|
||||
.fontSize(12)
|
||||
.fontColor(item.isNow ? '#FF3B30' : '#1A1A1A')
|
||||
.maxLines(2)
|
||||
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||
.layoutWeight(1)
|
||||
if (item.isNow) {
|
||||
Text('● 进行中')
|
||||
.fontSize(9)
|
||||
.fontColor('#FF3B30')
|
||||
.padding({ left: 4, right: 4, top: 1, bottom: 1 })
|
||||
.borderRadius(4)
|
||||
.backgroundColor('#FFECEA')
|
||||
}
|
||||
}
|
||||
if (item.calName !== '') {
|
||||
Text(item.calName)
|
||||
.fontSize(9)
|
||||
.fontColor(item.color)
|
||||
.maxLines(1)
|
||||
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||
.constraintSize({ maxWidth: '25%' })
|
||||
}
|
||||
}
|
||||
.alignItems(VerticalAlign.Center)
|
||||
.width('100%')
|
||||
.padding({ left: 8, right: 8, top: 5, bottom: 5 })
|
||||
.borderRadius(8)
|
||||
.backgroundColor(item.isNow ? '#FFF1F0' : '#F5F7FA')
|
||||
}
|
||||
|
||||
/** 列表模式主体:空态 / 可滚动的"行"列表(无时间轴、无红线) */
|
||||
@Builder
|
||||
listBody() {
|
||||
if (this.parseList().length === 0) {
|
||||
Column({ space: 6 }) {
|
||||
Text('📅')
|
||||
.fontSize(24)
|
||||
Text('暂无日程')
|
||||
.fontSize(13)
|
||||
.fontColor('#8A8A8A')
|
||||
}
|
||||
.width('100%')
|
||||
.layoutWeight(1)
|
||||
.justifyContent(FlexAlign.Center)
|
||||
.onClick(() => this.openApp())
|
||||
} else {
|
||||
List({ space: 4 }) {
|
||||
ForEach(this.parseList(), (item: LItem4) => {
|
||||
ListItem() {
|
||||
this.listRow(item)
|
||||
}
|
||||
.width('100%')
|
||||
}, (item: LItem4) => `${item.time}_${item.title}_${item.color}`)
|
||||
}
|
||||
.width('100%')
|
||||
.layoutWeight(1)
|
||||
.scrollBar(BarState.Auto)
|
||||
.cachedCount(8)
|
||||
.onClick(() => this.openApp())
|
||||
}
|
||||
}
|
||||
|
||||
build() {
|
||||
Column({ space: 6 }) {
|
||||
Row({ space: 4 }) {
|
||||
@@ -484,6 +602,9 @@ struct Widget4x4Card {
|
||||
|
||||
Divider().strokeWidth(0.5).color('#E5E5E5')
|
||||
|
||||
if (this.displayStyle === 'list') {
|
||||
this.listBody()
|
||||
} else {
|
||||
// 卡片不支持 Scroll,但支持 List / ListItem:
|
||||
// 把"整日时间轴"作为**一个很高的 ListItem**,超出卡片窗口的部分靠上下滑动查看
|
||||
List() {
|
||||
@@ -567,6 +688,7 @@ struct Widget4x4Card {
|
||||
}
|
||||
.layoutWeight(1)
|
||||
.width('100%')
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
|
||||
@@ -33,6 +33,16 @@ class TAllDay6 {
|
||||
isAllDay: boolean = true;
|
||||
}
|
||||
|
||||
/** 列表模式单条日程(解析 listJson) */
|
||||
class LItem6 {
|
||||
title: string = '';
|
||||
time: string = '';
|
||||
endTime: string = '';
|
||||
color: string = '#007DFF';
|
||||
calName: string = '';
|
||||
isNow: boolean = false;
|
||||
}
|
||||
|
||||
/** 冲突组:组内**贪心分列**,lanes[i] = 第 i 列(同列互不重叠) */
|
||||
class TGroup6 {
|
||||
startRatio: number = 0;
|
||||
@@ -78,6 +88,8 @@ struct Widget6x4Card {
|
||||
@LocalStorageProp('todayCount') todayCount: number = 0;
|
||||
@LocalStorageProp('isToday') isToday: boolean = true;
|
||||
@LocalStorageProp('dayCount') dayCount: number = 0;
|
||||
@LocalStorageProp('listJson') listJson: string = '[]';
|
||||
@LocalStorageProp('displayStyle') displayStyle: string = 'timeline';
|
||||
|
||||
private parseBlocks(): TBlock6[] {
|
||||
try {
|
||||
@@ -454,6 +466,112 @@ struct Widget6x4Card {
|
||||
}
|
||||
}
|
||||
|
||||
/** 列表模式单条日程(解析 listJson) */
|
||||
private parseList(): LItem6[] {
|
||||
try {
|
||||
return JSON.parse(this.listJson) as LItem6[];
|
||||
} catch (err) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/** 列表模式单条行:左侧日历色条(进行中变红)+ 时间 + 标题 + 所属日历本;不画红线 */
|
||||
@Builder
|
||||
listRow(item: LItem6) {
|
||||
Row({ space: 8 }) {
|
||||
Column()
|
||||
.width(4)
|
||||
.height(item.time === '全天' ? 18 : 42)
|
||||
.borderRadius(2)
|
||||
.backgroundColor(item.isNow ? '#FF3B30' : item.color)
|
||||
if (item.time === '全天') {
|
||||
Text(item.title)
|
||||
.fontSize(13)
|
||||
.fontColor('#1A1A1A')
|
||||
.maxLines(1)
|
||||
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||
.layoutWeight(1)
|
||||
Text('全天')
|
||||
.fontSize(9)
|
||||
.fontColor('#FFFFFF')
|
||||
.backgroundColor(item.color)
|
||||
.borderRadius(6)
|
||||
.padding({ left: 5, right: 5, top: 1, bottom: 1 })
|
||||
} else {
|
||||
Column({ space: 2 }) {
|
||||
Text(item.time)
|
||||
.fontSize(11)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(item.isNow ? '#FF3B30' : '#333333')
|
||||
Text(item.endTime)
|
||||
.fontSize(11)
|
||||
.fontColor('#999999')
|
||||
}
|
||||
.width(42)
|
||||
.alignItems(HorizontalAlign.Center)
|
||||
Text(item.title)
|
||||
.fontSize(13)
|
||||
.fontColor(item.isNow ? '#FF3B30' : '#1A1A1A')
|
||||
.maxLines(2)
|
||||
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||
.layoutWeight(1)
|
||||
if (item.isNow) {
|
||||
Text('● 进行中')
|
||||
.fontSize(9)
|
||||
.fontColor('#FF3B30')
|
||||
.padding({ left: 4, right: 4, top: 1, bottom: 1 })
|
||||
.borderRadius(4)
|
||||
.backgroundColor('#FFECEA')
|
||||
}
|
||||
}
|
||||
if (item.calName !== '') {
|
||||
Text(item.calName)
|
||||
.fontSize(9)
|
||||
.fontColor(item.color)
|
||||
.maxLines(1)
|
||||
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||
.constraintSize({ maxWidth: '25%' })
|
||||
}
|
||||
}
|
||||
.alignItems(VerticalAlign.Center)
|
||||
.width('100%')
|
||||
.padding({ left: 8, right: 8, top: 6, bottom: 6 })
|
||||
.borderRadius(8)
|
||||
.backgroundColor(item.isNow ? '#FFF1F0' : '#F5F7FA')
|
||||
}
|
||||
|
||||
/** 列表模式主体:空态 / 可滚动的"行"列表(无时间轴、无红线) */
|
||||
@Builder
|
||||
listBody() {
|
||||
if (this.parseList().length === 0) {
|
||||
Column({ space: 6 }) {
|
||||
Text('📅')
|
||||
.fontSize(24)
|
||||
Text('暂无日程')
|
||||
.fontSize(13)
|
||||
.fontColor('#8A8A8A')
|
||||
}
|
||||
.width('100%')
|
||||
.layoutWeight(1)
|
||||
.justifyContent(FlexAlign.Center)
|
||||
.onClick(() => this.openApp())
|
||||
} else {
|
||||
List({ space: 4 }) {
|
||||
ForEach(this.parseList(), (item: LItem6) => {
|
||||
ListItem() {
|
||||
this.listRow(item)
|
||||
}
|
||||
.width('100%')
|
||||
}, (item: LItem6) => `${item.time}_${item.title}_${item.color}`)
|
||||
}
|
||||
.width('100%')
|
||||
.layoutWeight(1)
|
||||
.scrollBar(BarState.Auto)
|
||||
.cachedCount(8)
|
||||
.onClick(() => this.openApp())
|
||||
}
|
||||
}
|
||||
|
||||
build() {
|
||||
Column({ space: 6 }) {
|
||||
Row({ space: 6 }) {
|
||||
@@ -483,6 +601,9 @@ struct Widget6x4Card {
|
||||
|
||||
Divider().strokeWidth(0.5).color('#E5E5E5')
|
||||
|
||||
if (this.displayStyle === 'list') {
|
||||
this.listBody()
|
||||
} else {
|
||||
// 卡片不支持 Scroll,但支持 List / ListItem:
|
||||
// 把"整日时间轴"作为**一个很高的 ListItem**,超出卡片窗口的部分靠上下滑动查看
|
||||
List() {
|
||||
@@ -566,6 +687,7 @@ struct Widget6x4Card {
|
||||
}
|
||||
.layoutWeight(1)
|
||||
.width('100%')
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
|
||||
Reference in New Issue
Block a user