From 3c9b4dcfdd941e144b131001a3bbb5c471aa3e02 Mon Sep 17 00:00:00 2001 From: Yang Yongquan Date: Mon, 14 Sep 2026 09:07:47 +0800 Subject: [PATCH] =?UTF-8?q?1.=E4=BF=AE=E6=94=B9=E4=BA=862x2=E7=9A=84?= =?UTF-8?q?=E5=8D=A1=E7=89=87=E6=A0=B7=E5=BC=8F=E3=80=82=202.=E4=B8=BA?= =?UTF-8?q?=E5=8D=A1=E7=89=87=E5=A2=9E=E5=8A=A0=E4=BA=86=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=8C=89=E9=92=AE=EF=BC=8C=E5=8F=AF=E4=BB=A5=E9=80=9A=E8=BF=87?= =?UTF-8?q?=E5=8D=A1=E7=89=87=E7=9B=B4=E6=8E=A5=E6=B7=BB=E5=8A=A0=E6=97=A5?= =?UTF-8?q?=E7=A8=8B=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Yang Yongquan --- entry/src/main/ets/common/CardDataService.ets | 26 ++- .../main/ets/entryability/EntryAbility.ets | 20 +++ entry/src/main/ets/pages/Index.ets | 18 +++ entry/src/main/ets/pages/widget/Widget2x2.ets | 150 ++++++++---------- entry/src/main/ets/pages/widget/Widget4x2.ets | 64 ++++++-- entry/src/main/ets/pages/widget/Widget4x4.ets | 59 +++++-- entry/src/main/ets/pages/widget/Widget6x4.ets | 59 +++++-- 7 files changed, 263 insertions(+), 133 deletions(-) diff --git a/entry/src/main/ets/common/CardDataService.ets b/entry/src/main/ets/common/CardDataService.ets index bf634dd..14fd6db 100644 --- a/entry/src/main/ets/common/CardDataService.ets +++ b/entry/src/main/ets/common/CardDataService.ets @@ -24,6 +24,10 @@ export class CardData { eventsJson: string = '[]'; dateText: string = ''; lunarText: string = ''; + // 2x2 卡片强化:月-日(x月x日,年份冗余故省略)/ 星期 / 今日日程条数 + dateMd: string = ''; + weekday: string = ''; + todayCount: number = 0; } export class CardDataService { @@ -110,13 +114,17 @@ export class CardDataService { /** 组装卡片数据(异步:查询本地库) */ static async buildCardData(context: common.Context): Promise { - const data = new CardData(); - try { - LogUtil.init(context); - const now = new Date(); - const weekCn: string[] = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']; - data.dateText = `${now.getMonth() + 1}月${now.getDate()}日 ${weekCn[now.getDay()]}`; - data.lunarText = LunarUtil.lunarDayText(now.getTime()); + const data = new CardData(); + try { + LogUtil.init(context); + const now = new Date(); + const weekCn: string[] = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']; + const fullWeekCn: string[] = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']; + data.dateText = `${now.getMonth() + 1}月${now.getDate()}日 ${weekCn[now.getDay()]}`; + data.lunarText = LunarUtil.lunarDayText(now.getTime()); + // 2x2 卡片强化字段:月-日(x月x日,年份冗余省略)、星期全名、今日日程条数 + data.dateMd = `${now.getMonth() + 1}月${now.getDate()}日`; + data.weekday = fullWeekCn[now.getDay()]; const start: number = CardDataService.startOfDay(now.getTime()); const end: number = start + 60 * 86400000; const sources = await CalendarDataService.loadSources(context); @@ -188,7 +196,9 @@ export class CardDataService { } } data.eventsJson = JSON.stringify(items); - LogUtil.write(`卡片数据刷新:${items.length} 条`); + // 今日日程条数:item.date === '今天' 的均为今天分组(含全天/跨天进行中) + data.todayCount = items.filter((i: CardItem): boolean => i.date === '今天').length; + LogUtil.write(`卡片数据刷新:${items.length} 条,今日 ${data.todayCount} 条`); } catch (err) { LogUtil.write(`卡片数据刷新失败: ${JSON.stringify(err)}`); } diff --git a/entry/src/main/ets/entryability/EntryAbility.ets b/entry/src/main/ets/entryability/EntryAbility.ets index b4aa187..6845c2e 100644 --- a/entry/src/main/ets/entryability/EntryAbility.ets +++ b/entry/src/main/ets/entryability/EntryAbility.ets @@ -16,6 +16,8 @@ export default class EntryAbility extends UIAbility { LogUtil.write('---- App 启动 ----'); // 后台同步:注册延迟任务兜底 + 按设置恢复长时任务 BackgroundSyncService.initOnLaunch(this.context); + // 服务卡片"添加"按钮跳转:把 widgetAction=add 透传给主页面(在 AppStorage 中暂存) + this.handleWidgetParam(want); // 申请通知权限:日程提醒、后台同步常驻通知都依赖它(系统只弹一次授权框) notificationManager.isNotificationEnabled() .then((enabled: boolean): Promise => { @@ -32,6 +34,24 @@ export default class EntryAbility extends UIAbility { hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy'); } + /** 卡片"添加"按钮已在后台运行时再次点击:同样把 widgetAction 透传给主页面 */ + onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void { + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onNewWant'); + this.handleWidgetParam(want); + } + + /** 读取卡片深链参数:widgetAction=add 时写入 AppStorage,供 Index 页面拉起新建日程页 */ + private handleWidgetParam(want: Want): void { + const params = want.parameters; + if (params !== undefined) { + const raw = params['widgetAction']; + if (typeof raw === 'string' && raw === 'add') { + AppStorage.setOrCreate('widgetAction', 'add'); + LogUtil.write('卡片添加入口:已设置 widgetAction=add'); + } + } + } + onWindowStageCreate(windowStage: window.WindowStage): void { hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); windowStage.loadContent('pages/Index', (err) => { diff --git a/entry/src/main/ets/pages/Index.ets b/entry/src/main/ets/pages/Index.ets index 079bc27..2730b80 100644 --- a/entry/src/main/ets/pages/Index.ets +++ b/entry/src/main/ets/pages/Index.ets @@ -58,6 +58,8 @@ struct Index { private permissionAsked: boolean = false; @State detailShow: boolean = false; // 只读日程详情半屏弹层 @State detailEvent: DisplayEvent | null = null; + // 服务卡片"添加"按钮深链:EntryAbility 写入,本页读取后拉起新建日程页 + @Watch('onWidgetActionChange') @StorageLink('widgetAction') widgetAction: string = ''; aboutToAppear(): void { const ctx = this.getUIContext().getHostContext(); @@ -97,11 +99,27 @@ struct Index { /** 从设置页/账号页返回时刷新(同步间隔、系统日历开关立即生效),并处理编辑账号后的待同步 */ onPageShow(): void { + // 卡片"添加"按钮深链:首次冷启动经 onCreate→AppStorage 落到这里消费 + this.consumeWidgetAction(); // 无条件刷新:之前"列表为空就跳过"的条件会导致账号列表卡死在空状态, // 同步时误报"没有可同步的账号" this.reloadAll().then((): Promise => this.handlePendingSync()); } + /** 卡片深链监听(App 已在后台运行时,onNewWant 改写 AppStorage 触发) */ + private onWidgetActionChange(): void { + this.consumeWidgetAction(); + } + + /** 消费卡片"添加"深链:拉起新建日程页并立即清除标记,避免重复触发 */ + private consumeWidgetAction(): void { + const action: string = AppStorage.get('widgetAction') ?? ''; + if (action === 'add') { + AppStorage.setOrCreate('widgetAction', ''); + this.addEvent(); + } + } + private async handlePendingSync(): Promise { const pendingId: string | undefined = AppStorage.get('pendingSyncAccountId'); if (pendingId !== undefined && pendingId !== '') { diff --git a/entry/src/main/ets/pages/widget/Widget2x2.ets b/entry/src/main/ets/pages/widget/Widget2x2.ets index fa5db64..6a5138d 100644 --- a/entry/src/main/ets/pages/widget/Widget2x2.ets +++ b/entry/src/main/ets/pages/widget/Widget2x2.ets @@ -1,44 +1,69 @@ // entry/src/main/ets/pages/widget/Widget2x2.ets -// 2x2 服务卡片:日期 + 农历 + 下一条日程 +// 2x2 服务卡片:突出今天的日期(年月日 / 星期 / 农历)+ 今日日程条数;点击卡片打开 App,右上角 + 直接添加日程 let storage2x2 = new LocalStorage(); -class CardItem2x2 { - title: string = ''; - time: string = ''; - endTime: string = ''; - date: string = ''; - showDate: boolean = false; - calName: string = ''; - color: string = '#007DFF'; -} - @Entry(storage2x2) @Component struct Widget2x2Card { @LocalStorageProp('eventsJson') eventsJson: string = '[]'; @LocalStorageProp('dateText') dateText: string = ''; @LocalStorageProp('lunarText') lunarText: string = ''; + @LocalStorageProp('dateMd') dateMd: string = ''; + @LocalStorageProp('weekday') weekday: string = ''; + @LocalStorageProp('todayCount') todayCount: number = 0; - private parseItems(): CardItem2x2[] { - try { - return JSON.parse(this.eventsJson) as CardItem2x2[]; - } catch (err) { - return []; + /** 右上角添加按钮:拉起 App 直接进入新建日程页(阻止冒泡,避免同时打开 App 首页) */ + @Builder + addButton() { + Button() { + Text('+') + .fontSize(20) + .fontWeight(FontWeight.Medium) + .fontColor('#FFFFFF') } + .width(30) + .height(30) + .borderRadius(15) + .padding(0) + .backgroundColor('#007DFF') + .onClick(() => { + postCardAction(this, { + action: 'router', + abilityName: 'EntryAbility', + params: { widgetAction: 'add' } + }); + }) + } + + /** 卡片"打开 App":点击日期区或今日日程数区域触发;添加按钮是 header 行的兄弟节点,其点击不会冒泡到这里 */ + private openApp(): void { + postCardAction(this, { + action: 'router', + abilityName: 'EntryAbility', + params: {} + }); } build() { - Column({ space: 4 }) { + Column({ space: 6 }) { + // 顶部:大日期 + 添加按钮 Row({ space: 6 }) { - Text(this.dateText) - .fontSize(14) - .fontWeight(FontWeight.Bold) - .fontColor('#1A1A1A') - .maxLines(1) - Blank() - Text(this.lunarText) - .fontSize(11) - .fontColor('#8A8A8A') + Column({ space: 2 }) { + Text(this.dateMd === '' ? this.dateText : this.dateMd) + .fontSize(15) + .fontWeight(FontWeight.Bold) + .fontColor('#1A1A1A') + .maxLines(1) + Text(`${this.weekday === '' ? '' : this.weekday + ' '}${this.lunarText}`) + .fontSize(11) + .fontColor('#8A8A8A') + .maxLines(1) + } + .layoutWeight(1) + .alignItems(HorizontalAlign.Start) + .onClick(() => this.openApp()) + + this.addButton() } .width('100%') @@ -46,68 +71,31 @@ struct Widget2x2Card { .strokeWidth(0.5) .color('#E5E5E5') - if (this.parseItems().length === 0) { - Column({ space: 4 }) { - Text('暂无日程') - .fontSize(13) + // 今日日程条数(占据剩余空间,垂直居中) + Row({ space: 8 }) { + Text(`${this.todayCount}`) + .fontSize(40) + .fontWeight(FontWeight.Bold) + .fontColor('#007DFF') + Column({ space: 2 }) { + Text('今日日程') + .fontSize(12) + .fontColor('#1A1A1A') + Text(this.todayCount === 0 ? '点击添加' : '点击查看') + .fontSize(10) .fontColor('#8A8A8A') } - .width('100%') - .layoutWeight(1) - .justifyContent(FlexAlign.Center) - } else { - Column({ space: 4 }) { - Row({ space: 6 }) { - Circle().width(6).height(6).fill(this.parseItems()[0].color) - Text(this.parseItems()[0].title) - .fontSize(13) - .fontWeight(FontWeight.Medium) - .fontColor('#1A1A1A') - .maxLines(2) - .textOverflow({ overflow: TextOverflow.Ellipsis }) - .layoutWeight(1) - if (this.parseItems()[0].calName !== '') { - Text(this.parseItems()[0].calName) - .fontSize(9) - .fontColor(this.parseItems()[0].color) - .maxLines(1) - .textOverflow({ overflow: TextOverflow.Ellipsis }) - .constraintSize({ maxWidth: '30%' }) - } - } - .width('100%') - .alignItems(VerticalAlign.Center) - - Blank() - Row({ space: 6 }) { - Text(this.parseItems()[0].date) - .fontSize(10) - .fontColor('#8A8A8A') - Text(this.parseItems()[0].time === '全天' - ? '全天' - : `${this.parseItems()[0].time} - ${this.parseItems()[0].endTime}`) - .fontSize(11) - .fontWeight(FontWeight.Medium) - .fontColor('#007DFF') - } - .width('100%') - .justifyContent(FlexAlign.End) - } - .width('100%') - .layoutWeight(1) + .alignItems(HorizontalAlign.Start) } + .width('100%') + .layoutWeight(1) + .alignItems(VerticalAlign.Center) + .onClick(() => this.openApp()) } .width('100%') .height('100%') - .padding(12) + .padding(14) .backgroundColor('#FFFFFF') .borderRadius(16) - .onClick(() => { - postCardAction(this, { - action: 'router', - abilityName: 'EntryAbility', - params: {} - }); - }) } } diff --git a/entry/src/main/ets/pages/widget/Widget4x2.ets b/entry/src/main/ets/pages/widget/Widget4x2.ets index 31e3b3a..feae9f7 100644 --- a/entry/src/main/ets/pages/widget/Widget4x2.ets +++ b/entry/src/main/ets/pages/widget/Widget4x2.ets @@ -28,6 +28,29 @@ struct Widget4x2Card { } } + /** 右上角添加按钮:拉起 App 直接进入新建日程页(阻止冒泡,避免同时打开 App 首页) */ + @Builder + addButton() { + Button() { + Text('+') + .fontSize(16) + .fontWeight(FontWeight.Medium) + .fontColor('#FFFFFF') + } + .width(26) + .height(26) + .borderRadius(13) + .padding(0) + .backgroundColor('#007DFF') + .onClick(() => { + postCardAction(this, { + action: 'router', + abilityName: 'EntryAbility', + params: { widgetAction: 'add' } + }); + }) + } + /** 全天事件行:圆角矩形 + 左侧日历色竖条 + 标题 + “全天”标记(无时间轴) */ @Builder buildAllDayRow(item: CardItem2x4) { @@ -114,18 +137,32 @@ struct Widget4x2Card { .backgroundColor('#F5F7FA') } + /** 卡片"打开 App":点击日期区或日程列表触发;添加按钮是 header 行的兄弟节点,其点击不会冒泡到这里 */ + private openApp(): void { + postCardAction(this, { + action: 'router', + abilityName: 'EntryAbility', + params: {} + }); + } + build() { Column({ space: 4 }) { Row({ space: 6 }) { - Text(this.dateText) - .fontSize(14) - .fontWeight(FontWeight.Bold) - .fontColor('#1A1A1A') - .maxLines(1) - Text(this.lunarText) - .fontSize(11) - .fontColor('#8A8A8A') - .maxLines(1) + Row({ space: 6 }) { + Text(this.dateText) + .fontSize(14) + .fontWeight(FontWeight.Bold) + .fontColor('#1A1A1A') + .maxLines(1) + Text(this.lunarText) + .fontSize(11) + .fontColor('#8A8A8A') + .maxLines(1) + } + .onClick(() => this.openApp()) + Blank() + this.addButton() } .width('100%') @@ -140,6 +177,7 @@ struct Widget4x2Card { .width('100%') .layoutWeight(1) .justifyContent(FlexAlign.Center) + .onClick(() => this.openApp()) } else { List({ space: 4 }) { ForEach(this.parseItems(), (item: CardItem2x4, idx: number) => { @@ -166,6 +204,7 @@ struct Widget4x2Card { .layoutWeight(1) .scrollBar(BarState.Off) .cachedCount(4) + .onClick(() => this.openApp()) } } .width('100%') @@ -173,12 +212,5 @@ struct Widget4x2Card { .padding(12) .backgroundColor('#FFFFFF') .borderRadius(16) - .onClick(() => { - postCardAction(this, { - action: 'router', - abilityName: 'EntryAbility', - params: {} - }); - }) } } diff --git a/entry/src/main/ets/pages/widget/Widget4x4.ets b/entry/src/main/ets/pages/widget/Widget4x4.ets index 414b893..55d184a 100644 --- a/entry/src/main/ets/pages/widget/Widget4x4.ets +++ b/entry/src/main/ets/pages/widget/Widget4x4.ets @@ -27,6 +27,29 @@ struct Widget4x4Card { } } + /** 右上角添加按钮:拉起 App 直接进入新建日程页(阻止冒泡,避免同时打开 App 首页) */ + @Builder + addButton() { + Button() { + Text('+') + .fontSize(16) + .fontWeight(FontWeight.Medium) + .fontColor('#FFFFFF') + } + .width(26) + .height(26) + .borderRadius(13) + .padding(0) + .backgroundColor('#007DFF') + .onClick(() => { + postCardAction(this, { + action: 'router', + abilityName: 'EntryAbility', + params: { widgetAction: 'add' } + }); + }) + } + /** 全天事件行:圆角矩形 + 左侧日历色竖条 + 标题 + “全天”标记(无时间轴) */ @Builder buildAllDayRow(item: CardItem4x4) { @@ -113,20 +136,33 @@ struct Widget4x4Card { .backgroundColor('#F5F7FA') } + /** 卡片"打开 App":点击日期区或日程列表触发;添加按钮是 header 行的兄弟节点,其点击不会冒泡到这里 */ + private openApp(): void { + postCardAction(this, { + action: 'router', + abilityName: 'EntryAbility', + params: {} + }); + } + build() { Column({ space: 6 }) { Row({ space: 6 }) { - Text(this.dateText) - .fontSize(16) - .fontWeight(FontWeight.Bold) - .fontColor('#1A1A1A') - Text(this.lunarText) - .fontSize(12) - .fontColor('#8A8A8A') + Row({ space: 6 }) { + Text(this.dateText) + .fontSize(16) + .fontWeight(FontWeight.Bold) + .fontColor('#1A1A1A') + Text(this.lunarText) + .fontSize(12) + .fontColor('#8A8A8A') + } + .onClick(() => this.openApp()) Blank() Text('同步日历') .fontSize(10) .fontColor('#B0B0B0') + this.addButton() } .width('100%') @@ -143,6 +179,7 @@ struct Widget4x4Card { .width('100%') .layoutWeight(1) .justifyContent(FlexAlign.Center) + .onClick(() => this.openApp()) } else { List({ space: 4 }) { ForEach(this.parseItems(), (item: CardItem4x4, idx: number) => { @@ -169,6 +206,7 @@ struct Widget4x4Card { .layoutWeight(1) .scrollBar(BarState.Auto) .cachedCount(8) + .onClick(() => this.openApp()) } } .width('100%') @@ -176,12 +214,5 @@ struct Widget4x4Card { .padding(14) .backgroundColor('#FFFFFF') .borderRadius(16) - .onClick(() => { - postCardAction(this, { - action: 'router', - abilityName: 'EntryAbility', - params: {} - }); - }) } } diff --git a/entry/src/main/ets/pages/widget/Widget6x4.ets b/entry/src/main/ets/pages/widget/Widget6x4.ets index 55361bd..cbd806a 100644 --- a/entry/src/main/ets/pages/widget/Widget6x4.ets +++ b/entry/src/main/ets/pages/widget/Widget6x4.ets @@ -27,6 +27,29 @@ struct Widget6x4Card { } } + /** 右上角添加按钮:拉起 App 直接进入新建日程页(阻止冒泡,避免同时打开 App 首页) */ + @Builder + addButton() { + Button() { + Text('+') + .fontSize(16) + .fontWeight(FontWeight.Medium) + .fontColor('#FFFFFF') + } + .width(26) + .height(26) + .borderRadius(13) + .padding(0) + .backgroundColor('#007DFF') + .onClick(() => { + postCardAction(this, { + action: 'router', + abilityName: 'EntryAbility', + params: { widgetAction: 'add' } + }); + }) + } + /** 全天事件行:圆角矩形 + 左侧日历色竖条 + 标题 + “全天”标记(无时间轴) */ @Builder buildAllDayRow(item: CardItem6x4) { @@ -113,20 +136,33 @@ struct Widget6x4Card { .backgroundColor('#F5F7FA') } + /** 卡片"打开 App":点击日期区或日程列表触发;添加按钮是 header 行的兄弟节点,其点击不会冒泡到这里 */ + private openApp(): void { + postCardAction(this, { + action: 'router', + abilityName: 'EntryAbility', + params: {} + }); + } + build() { Column({ space: 6 }) { Row({ space: 6 }) { - Text(this.dateText) - .fontSize(16) - .fontWeight(FontWeight.Bold) - .fontColor('#1A1A1A') - Text(this.lunarText) - .fontSize(12) - .fontColor('#8A8A8A') + Row({ space: 6 }) { + Text(this.dateText) + .fontSize(16) + .fontWeight(FontWeight.Bold) + .fontColor('#1A1A1A') + Text(this.lunarText) + .fontSize(12) + .fontColor('#8A8A8A') + } + .onClick(() => this.openApp()) Blank() Text('同步日历') .fontSize(10) .fontColor('#B0B0B0') + this.addButton() } .width('100%') @@ -143,6 +179,7 @@ struct Widget6x4Card { .width('100%') .layoutWeight(1) .justifyContent(FlexAlign.Center) + .onClick(() => this.openApp()) } else { List({ space: 4 }) { ForEach(this.parseItems(), (item: CardItem6x4, idx: number) => { @@ -169,6 +206,7 @@ struct Widget6x4Card { .layoutWeight(1) .scrollBar(BarState.Auto) .cachedCount(12) + .onClick(() => this.openApp()) } } .width('100%') @@ -176,12 +214,5 @@ struct Widget6x4Card { .padding(14) .backgroundColor('#FFFFFF') .borderRadius(16) - .onClick(() => { - postCardAction(this, { - action: 'router', - abilityName: 'EntryAbility', - params: {} - }); - }) } }