跨端: 鸿蒙日历写侧(新建/编辑/删除/暂停恢复)+ 表单不再是一面滚轮墙

用户:「是的去吧」(批准上一封列出的下一步)。

- CalendarApi:createEvent / updateEvent / deleteEvent(POST /calendar/events、
  PUT/DELETE /calendar/events/{id});新建与编辑发**同一份** CalendarEventInput
  —— 服务端两个端点同形(CreateCalendarEvent 里那个 Status 字段的注释就写着这句),
  拆两份会在严格解码下 400
- CalendarPage:写侧表单(新建与编辑共用一份)、二次确认删除、暂停/恢复走 update
  (调度器只触发 active;没有暂停就只能删掉重加,而那会丢 event_id 与历史)
- 服务端 400 文案原样显示(收件地址无法解析:…)—— 建事件时就校验地址,
  吞成保存失败等于让人猜,而猜的代价是以为设好了、实际永远发不出去
- 空标题/空收件人在本地就挡住,不发注定 400 的请求
- 提交的是**时间戳**(isoTimestampOfLocal),不是日期键;编辑回填走 localDateOf
- ★ 表单形状:日期/时间选择器合起来 ~1400px,展开着放会把标题与收件人挤出屏幕 ——
  改成一行摘要 + 点开展开(默认收起)。这不是美观问题:展开态下保存在屏幕外,
  要跨过一整面滚轮才够得着
- 判据 +7(端点/载荷/前置校验/文案/二次确认/时间戳往返/变异自检),23 条全绿
- 真机实测(hvigorw + hdc + uitest 逐字段输入 + 逐键点击,每一步都回库核对):
  建 → 库里 event_time 是 2026-09-28T01:00Z(= 本地 09:00 整,默认值正确)、
  暂停 → status=paused、删除第一下只变确认删除(库里还在)、第二下才真删(库里 0 条)
This commit is contained in:
pi
2026-09-14 18:57:53 +08:00
parent 94ba4b9c58
commit d3f6f3baf6
5 changed files with 659 additions and 78 deletions

View File

@ -182,3 +182,27 @@ export function hhmmAtOffset(timestamp: string, offsetMinutes: number): string {
const shifted = new Date(ms + offsetMinutes * 60000);
return `${pad2(shifted.getUTCHours())}:${pad2(shifted.getUTCMinutes())}`;
}
/**
* 本地年月日 + 时:分 → RFC3339 时间戳(**时间戳,不是日期键**)。
*
* 为什么不用字符串拼 + 时区偏移:设备时区可能是 +08:00 / -05:00 / UTC,手拼偏移
* 就要自己处理夏令时与半小时时区(+05:30)。`Date` 的本地构造 + `toISOString()`
* 拿到的是同一个**瞬间**的 UTC 表示,服务端 `time.Parse(RFC3339)` 直接吃得下。
*
* 与"取日期键"的区别(那条禁令针对的是 `.toISOString().slice(0,10)`):
* 日期键必须是**本地**年月日,时间戳必须是**绝对瞬间** —— 两者混用的那一天,
* 事件会被存到错误的小时上,而界面看起来完全正常。
*/
export function isoTimestampOfLocal(year: number, month: number, day: number, hour: number, minute: number): string {
return new Date(year, month - 1, day, hour, minute, 0, 0).toISOString();
}
/** RFC3339 时间戳 → 本地 `Date`(编辑表单回填用:日期与时间选择器都要 `Date`)。 */
export function localDateOf(timestamp: string): Date {
const ms: number = Date.parse(timestamp);
if (Number.isNaN(ms)) {
return new Date();
}
return new Date(ms);
}

View File

@ -266,3 +266,33 @@ export class CalendarEvent {
export class CalendarListResponse {
events: CalendarEvent[] = [];
}
/**
* 新建/编辑日程的请求体。
*
* **一份类型给两个端点用**(`POST /calendar/events` 与 `PUT /calendar/events/{id}`)——
* 服务端也是这么设计的(`CreateCalendarEvent` 里那个 `Status` 字段的注释写着:
* "前端的 CalendarEventInput 是**一份**类型,新建与编辑发的是同一个对象")。
* 拆成两份的后果在严格解码下是新建直接 400。
*
* 服务端必填:`title`、`event_time`、以及至少一个收件人(`recipients` 之一)。
* `reminder_text` 空则由服务端填默认模板(**变量形式**,触发时才渲染成本次时间)。
*/
export class CalendarEventInput {
title: string = '';
description: string = '';
reminder_text: string = '';
/** 完整三维地址串;这一版只发一个(多收件人与 separate/together 是下一步) */
recipients: string[] = [];
delivery_mode: string = 'separate';
/** RFC3339 时间戳(不是日期键)—— 由 `Calendar.ts` 的 isoTimestampOfLocal 生成 */
event_time: string = '';
remind_before: number = 0;
recurrence: string = 'none';
status: string = 'active';
}
/** 删除日程的响应(服务端给 `{status}`) */
export class CalendarDeleteResponse {
status: string = '';
}