跨端: 鸿蒙日历补「月/周/日」三档 —— 原来只有月视图
用户列的缺失之一(WebUI `CalendarView.tsx` 的 `type Scale = 'month'|'week'|'day'`)。 鸿蒙这边 `CalendarPage.ets` 自己的注释里就写着"没做"。 ## 三处必须一起改,所以档位进 model 而不是页面里一串 if 档位切换同时改变三件事,任一漏改都**不报错、只是静静地不对**: | | 月 | 周 | 日 | |---|---|---|---| | 标题 | `2026年9月` | 跨月时两头写月份 `9.28 – 10.4` | `2026年9月18日 周五` | | 翻页步长 | ±1 **月** | ±7 天 | ±1 天 | | 显示格子 | 整月网格 | 一行 7 天 | 一行、只亮一格 | 所以 `model/Calendar.ts`(纯逻辑,node 直接跑)新增: `CalendarScale` / `addDaysIso` / `weekDaysOf` / `rangeTitleOf` / `stepDaysOf` / `scaleLabel`。 页面只调用,不在渲染里重写 —— 重写就是"三处里漏改一处"。 ★ 步长按档走是**必须**的:周档按 1 天翻看起来像日档、日档按 7 天翻会跳过一周, 两者都不报错。判据钉"三档步长两两不同"。 ★ 周/日档的 7 天必须与月网格用**同一个** `weekStart`:不一致的话同一日期在两档下列位置 不同,用户看到的是"切个视图日期就跳位了"。 ★ 日档的格子仍摆在一行的**星期列**里(不是居中大字):上下翻日时格子不会在屏幕上跳。 ## 实现要点 - 档位状态叫 `calScale` 而**不是** `scale` —— ArkUI 的 `CustomComponent` 已有 `scale` 修饰符,同名直接编译失败(实测报 `Property 'scale' ... is not assignable to ...`)。 判据钉住这个命名。 - 周/日档下 `year/month` 由锚点 `selectedIso` 推出来(`syncYearMonthFrom`), 不允许两处各自保存"当前是几月" —— 否则会出现"标题写 9 月、周视图显示 10 月那周"。 - `monthKey()` 加上**档位**前缀:切档时那 7 个 iso 就是月网格里的 7 个, 不带档位的话键完全重叠 ⇒ 一个节点都不被替换 ⇒ 过渡静默不播、旧 `inMonth` 残留。 - 空态补 `layoutWeight(1)`:实测周视图下"这一天没有日程"贴顶、**下半屏是一大片空壁纸**。 ## 判据(harmony-calendar 23 → 30 条) 7 条新增,每条对着一个具体错法。**变异自检跑过两个方向**: - 周档步长改成 1(看起来像日档)⇒ 判红 ✓ - 跨月周标题漏掉结束月份(`9.28 – 4`)⇒ 判红 ✓ ## 实测(模拟器,逐档截图) 月/周/日三档切换后:周档标题 `2026年9月14–20日` + 一行 14~20,日档标题 `2026年9月18日 周五` + 只亮 18,月档回到整月网格。三张截图逐一核过。
This commit is contained in:
@ -206,3 +206,117 @@ export function localDateOf(timestamp: string): Date {
|
||||
}
|
||||
return new Date(ms);
|
||||
}
|
||||
|
||||
/*
|
||||
* ── 视图档(月 / 周 / 日)──
|
||||
*
|
||||
* 对齐 WebUI `CalendarView.tsx` 的 `type Scale = 'month' | 'week' | 'day'`。
|
||||
* 鸿蒙这边原来**只有月视图**(`CalendarPage.ets:8` 的注释列过"没做"的清单)。
|
||||
*
|
||||
* 档位切换会同时改变三件事,所以它必须是**数据类型**而不是页面上的一串 if:
|
||||
* ① 标题怎么写(月:`2026年9月`;周:跨月时两头都写月份;日:带星期)
|
||||
* ② 前后翻动的**步长**(月:±1 月;周:±7 天;日:±1 天)
|
||||
* ③ 要显示哪些格子(月:整月网格;周:一行 7 天;日:单天)
|
||||
* 这三处任一漏改都会"看起来正常但翻错量",所以三处都由这里同一组函数算。
|
||||
*/
|
||||
|
||||
/** 视图档。与 WebUI 的 `Scale` 同值(字符串也一致,便于跨端对照)。 */
|
||||
export type CalendarScale = 'month' | 'week' | 'day';
|
||||
|
||||
/** 档位的中文短名(给切换按钮)。顺序与 WebUI 的 `['month','week','day']` 一致。 */
|
||||
export const SCALE_ORDER: CalendarScale[] = ['month', 'week', 'day'];
|
||||
|
||||
export function scaleLabel(scale: CalendarScale): string {
|
||||
if (scale === 'month') {
|
||||
return '月';
|
||||
}
|
||||
return scale === 'week' ? '周' : '日';
|
||||
}
|
||||
|
||||
/**
|
||||
* 把 `YYYY-MM-DD` 位移若干天。
|
||||
*
|
||||
* 用 `Date.UTC` 构造再位移:与 `firstWeekday` 同一口径(UTC 取数、不受设备时区影响)。
|
||||
* 日期键是"本地年月日"的**字面量**,不承载瞬间语义 —— 见文件顶部关于
|
||||
* "日期键 vs 时间戳"的那条说明。
|
||||
*/
|
||||
export function addDaysIso(iso: string, delta: number): string {
|
||||
const parts: number[] = iso.split('-').map((s: string) => parseInt(s, 10));
|
||||
const d: Date = new Date(Date.UTC(parts[0], parts[1] - 1, parts[2]));
|
||||
d.setUTCDate(d.getUTCDate() + delta);
|
||||
const y: number = d.getUTCFullYear();
|
||||
const m: number = d.getUTCMonth() + 1;
|
||||
const dd: number = d.getUTCDate();
|
||||
return isoOf(y, m, dd);
|
||||
}
|
||||
|
||||
/**
|
||||
* 某一天所在周的 7 天(按 `weekStart` 起始),返回 `YYYY-MM-DD` 数组。
|
||||
*
|
||||
* ★ 必须与月网格用**同一个** `weekStart` —— 两处不一致的话,月视图第一列是周日、
|
||||
* 周视图第一列是周一,同一个日期在两个档位下的列位置不同,看起来像错位。
|
||||
* 判据钉"两处用同一个常量"。
|
||||
*
|
||||
* 返回空数组表示入参不是合法日期键(调用方据此跳过,不猜一个日子)。
|
||||
*/
|
||||
export function weekDaysOf(iso: string, weekStart: number): string[] {
|
||||
const parts: number[] = iso.split('-').map((s: string) => parseInt(s, 10));
|
||||
if (parts.length !== 3 || parts.some((n: number) => Number.isNaN(n))) {
|
||||
return [];
|
||||
}
|
||||
const d: Date = new Date(Date.UTC(parts[0], parts[1] - 1, parts[2]));
|
||||
const w: number = d.getUTCDay();
|
||||
const back: number = (w - weekStart + 7) % 7;
|
||||
const out: string[] = [];
|
||||
for (let i = 0; i < 7; i++) {
|
||||
out.push(addDaysIso(iso, i - back));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标题文案 —— 三档各一种写法(与 WebUI `CalendarView.tsx:242-258` 同口径)。
|
||||
*
|
||||
* ★ 周视图**跨月时两头都写月**(`9.28 – 10.4`):
|
||||
* 只写 `9月28日 - 4日` 的话,"4日"看不出是十月 —— 这是 WebUI 注释里明说的坑。
|
||||
* ★ 日视图带星期:单天视图上"今天是周几"比"几号"更常用。
|
||||
*/
|
||||
export function rangeTitleOf(scale: CalendarScale, iso: string, weekStart: number): string {
|
||||
const parts: number[] = iso.split('-').map((s: string) => parseInt(s, 10));
|
||||
if (parts.length !== 3 || parts.some((n: number) => Number.isNaN(n))) {
|
||||
return '';
|
||||
}
|
||||
const year: number = parts[0];
|
||||
const month: number = parts[1];
|
||||
const day: number = parts[2];
|
||||
if (scale === 'month') {
|
||||
return `${year}年${month}月`;
|
||||
}
|
||||
if (scale === 'week') {
|
||||
const days: string[] = weekDaysOf(iso, weekStart);
|
||||
if (days.length === 0) {
|
||||
return '';
|
||||
}
|
||||
const a: number[] = days[0].split('-').map((s: string) => parseInt(s, 10));
|
||||
const b: number[] = days[6].split('-').map((s: string) => parseInt(s, 10));
|
||||
if (a[1] === b[1]) {
|
||||
return `${a[0]}年${a[1]}月${a[2]}–${b[2]}日`;
|
||||
}
|
||||
return `${a[1]}.${a[2]} – ${b[1]}.${b[2]}`;
|
||||
}
|
||||
/* 日视图:星期名走 weekdayNameOf(与月网格同一处算法,不另写一份) */
|
||||
return `${year}年${month}月${day}日 周${weekdayNameOf(year, month, day)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 前后翻动的步长(天)—— 月档返回 0,因为月档按**月**翻而不是按天。
|
||||
*
|
||||
* 单独抽出来是防"档位改了但步长没改":日档按 7 天翻会跳过一周,
|
||||
* 周档按 1 天翻看起来像日档 —— 两者都不会报错,只会静静地不对。
|
||||
*/
|
||||
export function stepDaysOf(scale: CalendarScale): number {
|
||||
if (scale === 'week') {
|
||||
return 7;
|
||||
}
|
||||
return scale === 'day' ? 1 : 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user