跨端: 鸿蒙端功能同步第一步——日历(只读月视图)上架,入口进底部导航
用户:「要给鸿蒙端做功能同步」。按 API 面盘点(WebUI 62 个 API 函数 vs 鸿蒙 38 个), 最大的用户面缺口是**日历**:纯逻辑(model/Calendar.ts)与判据早就在,一直没页面。 新增: - api/CalendarApi.ets:GET /calendar/events?from=&to=(与 WebUI 同参;区间按**网格**取, 不是月首月末 —— 首尾格子会显示邻月,只查当月会让那些格子永远空着) - pages/CalendarPage.ets:月网格(翻月/回今天)、点某天看当天日程、事件点、今天/选中两态、 加载失败说出来。**没做**:写侧(增删改)、农历重复、.ics、滑动翻页 —— 逐条写在文件头 - model/Calendar.ts:补 localIsoOf / hhmmAtOffset / deviceOffsetMinutes(偏移是入参 ⇒ 三时区可真跑) - model/Models.ets:CalendarEvent / CalendarListResponse(字段对齐服务端 JSON) - NavItems:加「日历」,底部成为 通信/日历/联系人 三项(与 WebUI 同序) - MainPage:日历是**常驻 pane**(visibility 控制),首次可见才拉数据;today 走 @Prop @Watch(visible) 在 pane 变可见时重算 ⇒ 结算欠账 calendar-today-recompute (DEBTS 15 笔 → 14 笔,余额里不再计这一笔) 判据:harmony-calendar 新增 6 条(网格/表头同源、事件归日走 localIsoOf、三时区钟点、 today 重算路径、翻月走 addMonths、变异自检);harmony-nav ② 分派与 ④ 让位跟着改成结构性判据 (④ 原来那个 400 字符窗口一加 pane 就红 —— 窗口式判据的又一次现身);harmony-logic 两处 「只剩两个平级页签」跟着改成三项。 真机实测(harmony-emu + hvigorw assembleHap + hdc install + uitest click + dumpLayout): 9 月网格星期对齐(周一起始,2026-09-01 落在「二」列)、事件点恰好在有日程的那 6 天 (11/17/18/24/25/30)、点 09-17 列出当天两条日程且钟点是本地时间(DB 里 02:20Z/08:30Z → 界面 10:20/16:30)。
This commit is contained in:
@ -144,3 +144,41 @@ export function isoAtOffset(now: Date, offsetMinutes: number): string {
|
||||
const shifted = new Date(now.getTime() + offsetMinutes * 60000);
|
||||
return `${shifted.getUTCFullYear()}-${pad2(shifted.getUTCMonth() + 1)}-${pad2(shifted.getUTCDate())}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 把服务端的 RFC3339 时间戳归到**本地**那一天 —— 网格 `iso` 键的口径。
|
||||
*
|
||||
* 为什么不能 `.slice(0, 10)`:那是**时间戳的前 10 个字符**,也就是 UTC 日期。
|
||||
* UTC+8 的 09-15 00:30 在 UTC 里是 09-14T16:30Z,切字符串会把事件画到**前一天的格子**里
|
||||
* —— 而网格、表头、"今天"标记全都正常,是肉眼最难发现的一类错。
|
||||
*
|
||||
* 解析失败(服务端不会给,但别让一个坏值把整个月画崩)返回空串,
|
||||
* 调用侧对空串一律当"没有日期"处理。
|
||||
*/
|
||||
export function localIsoOf(timestamp: string): string {
|
||||
const ms: number = Date.parse(timestamp);
|
||||
if (Number.isNaN(ms)) {
|
||||
return '';
|
||||
}
|
||||
return isoOfLocal(new Date(ms));
|
||||
}
|
||||
|
||||
/** 设备本地时区相对 UTC 的偏移(分钟,东为正)。页面用它把时间戳显示成本地钟点。 */
|
||||
export function deviceOffsetMinutes(now: Date): number {
|
||||
return -now.getTimezoneOffset();
|
||||
}
|
||||
|
||||
/**
|
||||
* 时间戳在**给定偏移**下的 `HH:MM`。
|
||||
*
|
||||
* 偏移做成入参而不是读进程时区:这样"UTC+8 / UTC-8 / UTC 三种下显示都对"这条
|
||||
* 判据能**真跑**,不必依赖跑判据的机器正好在哪个时区(与 `isoAtOffset` 同一个理由)。
|
||||
*/
|
||||
export function hhmmAtOffset(timestamp: string, offsetMinutes: number): string {
|
||||
const ms: number = Date.parse(timestamp);
|
||||
if (Number.isNaN(ms)) {
|
||||
return '';
|
||||
}
|
||||
const shifted = new Date(ms + offsetMinutes * 60000);
|
||||
return `${pad2(shifted.getUTCHours())}:${pad2(shifted.getUTCMinutes())}`;
|
||||
}
|
||||
|
||||
@ -235,3 +235,34 @@ export class DecideResponse {
|
||||
expires_at: string = '';
|
||||
warning: string = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 日历事件(`GET /calendar/events` 的一项)。
|
||||
*
|
||||
* 字段名与类型**严格对齐服务端 JSON**(`server/internal/models/calendar.go` 的
|
||||
* `CalendarEvent`)—— 鸿蒙侧模型层对齐 docs/API.md 是既定纪律,不是"照着 WebUI 抄"。
|
||||
* 只声明这一版日历用得上的字段:写全了却没人读,等于给下一个人一份会过期的副本。
|
||||
*
|
||||
* `event_time` 是 RFC3339 时间戳(不是日期)—— 归到**本地哪一天**由
|
||||
* `model/Calendar.ts` 的 `localIsoOf()` 决定,页面不自己切字符串。
|
||||
*/
|
||||
export class CalendarEvent {
|
||||
event_id: string = '';
|
||||
title: string = '';
|
||||
description: string = '';
|
||||
/** 提醒正文(触发时发出去的那封信的开头) */
|
||||
reminder_text: string = '';
|
||||
/** 完整三维地址串;空时服务端会退回 agent_name/to_address(本版不展示) */
|
||||
recipients: string[] = [];
|
||||
delivery_mode: string = 'separate'; // separate | together
|
||||
event_time: string = '';
|
||||
remind_before: number = 0; // 提前多少分钟
|
||||
recurrence: string = 'none'; // none|daily|weekly|monthly|yearly|lunar_monthly|lunar_yearly
|
||||
status: string = 'active'; // active | paused | cancelled
|
||||
permission_mode: string = '';
|
||||
}
|
||||
|
||||
/** `GET /calendar/events` 的响应 */
|
||||
export class CalendarListResponse {
|
||||
events: CalendarEvent[] = [];
|
||||
}
|
||||
|
||||
@ -19,14 +19,19 @@ export interface NavItem {
|
||||
}
|
||||
|
||||
/**
|
||||
* 平级导航项。**只有两项**:通信(内部三栏:收件箱/发件箱/授权)与联系人。
|
||||
* 平级导航项。**三项**:通信(内部三栏:收件箱/发件箱/授权)、日历、联系人。
|
||||
*
|
||||
* 「会话」原先是个平级 tab,已撤 —— 它不是第三个地方,而是"同一批数据的另一种看法"
|
||||
* (收件箱按会话折叠、联系人卡片视图就是会话的进度视角)。理由与时机见 `MainPage.build()` 的注释。
|
||||
* 「日历」等 P6 的内容做完再上入口 —— 不留点进去空着的页签(§7.15)。
|
||||
* (收件箱按会话折叠、联系人卡片视图就是会话的进度视角)。理由见 `MainPage.build()` 的注释。
|
||||
*
|
||||
* 「日历」原来写着"等 P6 内容做完再上入口 —— 不留点进去空着的页签"。P6 第一步
|
||||
* (只读月视图:`pages/CalendarPage.ets`)已经做完,所以入口**现在**上:
|
||||
* 空页签那条理由不再成立,而日历的纯逻辑(`model/Calendar.ts`)本来就是照着
|
||||
* 「顶层是 通信/日历/联系人 三个平级 pane」写判据的。
|
||||
*/
|
||||
export const NAV_ITEMS: NavItem[] = [
|
||||
{ key: 'comm', label: '通信', icon: '✉️' },
|
||||
{ key: 'calendar', label: '日历', icon: '📅' },
|
||||
{ key: 'contacts', label: '联系人', icon: '👤' }
|
||||
];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user