/* * AgentMail 鸿蒙客户端 — 日历 API * GET /calendar/events?from=&to= * * 与 WebUI 同源同参(`client/electron/src/api/client.ts` 的 listCalendarEvents): * from/to 是 **RFC3339 区间**,服务端按 `event_time` 落在区间内筛。 * * 区间必须按**网格**算,不是按月首月末算 —— 月视图首尾会显示上/下月的格子, * 只查当月会让那几个格子永远空着(看着像"那天没事件",其实是没查)。 */ import { ApiClient } from './ApiClient'; import { CalendarListResponse, CalendarEvent, CalendarEventInput, CalendarDeleteResponse, LunarRangeResponse, CalendarImportResponse } from '../model/Models'; export class CalendarApi { private client: ApiClient; constructor(client: ApiClient) { this.client = client; } /** * 列区间内的事件。 * * `from`/`to` 走 `encodeURIComponent`:RFC3339 里的 `+08:00` 若不编码, * 查询串里的 `+` 会被服务端解成空格,`time.Parse` 直接失败 ⇒ 静默返回空列表 * (界面表现为"这个月没有日程",而实际是参数被吃掉了)。 */ async listEvents(from: string, to: string): Promise { const query: string = 'from=' + encodeURIComponent(from) + '&to=' + encodeURIComponent(to); return this.client.get('/calendar/events', query); } /** * 新建日程。 * * 服务端在**建事件时**就校验收件地址("地址在这里就校验而不是等到触发时:建事件时报错 * 人能立刻改,而触发时报错只会进 journalctl")。所以这里的 400 必须**显示给用户**, * 不许吞成"保存失败"——错误文案("收件地址无法解析:…")是唯一能让人立刻改的东西。 */ async createEvent(input: CalendarEventInput): Promise { return this.client.post('/calendar/events', input); } /** 编辑日程(PUT 与 POST 同形,见 `CalendarEventInput` 的说明)。 */ async updateEvent(id: string, input: CalendarEventInput): Promise { return this.client.put('/calendar/events/' + encodeURIComponent(id), input); } /** 删除日程。 */ async deleteEvent(id: string): Promise { return this.client.del('/calendar/events/' + encodeURIComponent(id)); } /** * 一段日期区间的农历标签(按 `YYYY-MM-DD` 索引)。 * * ★ 入参是**日期键**(`YYYY-MM-DD`),不是 RFC3339 —— 与 `listEvents` 的区别就在这里: * 事件查询是"瞬间落在区间内",而农历是"这一天是农历几号",是纯日期语义。 * 传时间戳进去会被服务端拒(它只收 `YYYY-MM-DD`),这比"悄悄按 UTC 挪一天"好。 * * ★ 区间是**闭区间**且必须给全:服务端**不设默认值**(默认范围会让"我要 3 月" * 与"服务端以为我要这个月"悄悄不一致)。上限 400 天。 */ async listLunar(fromIso: string, toIso: string): Promise { const query: string = 'from=' + encodeURIComponent(fromIso) + '&to=' + encodeURIComponent(toIso); return this.client.get('/calendar/lunar', query); } /** * 导出 .ics 文本(`GET /calendar/export.ics`)。 * * ★ 区间要用**屏幕上正在看的那段**,不是写死 ±1 年 —— * 服务端注释里写着同一条理由:「写死 ±1 年会让人点导出后得到一堆与屏幕上不符的事件」。 * 入参是 RFC3339 时间戳(服务端 `time.Parse(RFC3339)`),**不是**日期键 —— * 与 `listLunar` 的日期键口径不同,因为服务端这两个端点收的形状本来就不同。 */ async exportIcs(fromTimestamp: string, toTimestamp: string): Promise { const query: string = 'from=' + encodeURIComponent(fromTimestamp) + '&to=' + encodeURIComponent(toTimestamp); return this.client.getText('/calendar/export.ics', query); } /** * 导入 .ics(`POST /calendar/import.ics`,raw `text/calendar`)。 * * 返回 `{imported, skipped}` —— **两个数都要显示**:只说"导入成功" * 会把"20 条里跳过了 18 条"读成一切正常,而跳过的原因(重复 / 缺 DTSTART) * 正是用户需要知道的那部分。 */ async importIcs(icsText: string): Promise { return this.client.postText('/calendar/import.ics', icsText, 'text/calendar'); } }