跨端: 鸿蒙日历补「月/周/日」三档 —— 原来只有月视图
用户列的缺失之一(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:
@ -385,3 +385,95 @@ test('★ 判据自检:写侧的错误写法必须判红', () => {
|
||||
assert.ok(!/put<CalendarEvent>/.test("post<CalendarEvent>('/calendar/events/' + id, input)"),
|
||||
'编辑用 POST 能被"必须 PUT"那条命中');
|
||||
});
|
||||
|
||||
/* ───────────────────────── 视图档:月 / 周 / 日 ───────────────────────── */
|
||||
|
||||
/*
|
||||
* 2026-09-18:鸿蒙的日历原来**只有月视图**(`CalendarPage.ets` 的注释列过"没做"清单)。
|
||||
* 对齐 WebUI `CalendarView.tsx` 的 `type Scale = 'month' | 'week' | 'day'`。
|
||||
*
|
||||
* 档位切错**不会报错**,只会静静地不对 —— 所以下面每一条都对着一个具体的错法。
|
||||
*/
|
||||
|
||||
test('★ 翻页步长按档走:月=按天 0(按月翻)、周=7、日=1 —— 认错档位就是静默跳过/原地不动', () => {
|
||||
assert.equal(C.stepDaysOf('month'), 0, '月档按**月**翻,不按天 —— 返回 0 表示走 addMonths');
|
||||
assert.equal(C.stepDaysOf('week'), 7, '周档必须按 7 天翻(按 1 天翻看起来像日档)');
|
||||
assert.equal(C.stepDaysOf('day'), 1, '日档按 1 天翻(按 7 天翻会跳过一周且毫无提示)');
|
||||
// 自检:三个档的步长必须**互不相同**,否则"分档"本身没意义
|
||||
const steps = C.SCALE_ORDER.map(s => C.stepDaysOf(s));
|
||||
assert.equal(new Set(steps).size, 3, `三档步长必须两两不同,实际 ${steps.join('/')}`);
|
||||
});
|
||||
|
||||
test('★ 周档跨月时标题两头都写月份(只写"9月28日-4日"看不出 4 日是十月)', () => {
|
||||
// 2026-09-30 是周三 → 周一是 09-28,周日是 10-04 ⇒ 跨月
|
||||
const t = C.rangeTitleOf('week', '2026-09-30', 1);
|
||||
assert.match(t, /9\.28/, `跨月周标题要写左端月日,实际「${t}」`);
|
||||
assert.match(t, /10\.4/, `跨月周标题要写右端月日(含月份),实际「${t}」`);
|
||||
// 不跨月时反过来:不该出现"两个月份"
|
||||
const same = C.rangeTitleOf('week', '2026-09-18', 1);
|
||||
assert.ok(!same.includes('–') || (same.match(/月/g) ?? []).length === 1,
|
||||
`同一月内的周不该写两个月份,实际「${same}」`);
|
||||
});
|
||||
|
||||
test('★ 日档标题带星期;月档标题不带(三档各一种写法,别互相串)', () => {
|
||||
const d = C.rangeTitleOf('day', '2026-09-18', 1);
|
||||
assert.match(d, /^2026年9月18日 周./, `日档要「年月日 + 周X」,实际「${d}」`);
|
||||
assert.equal(C.rangeTitleOf('month', '2026-09-18', 1), '2026年9月', '月档只有年月');
|
||||
});
|
||||
|
||||
test('★ 周/日档的 7 天必须与月网格用**同一个** weekStart(否则同一日期两档列位置不同)', () => {
|
||||
/*
|
||||
* 这是"看起来只是排版不同"的那类错:月视图第一列是周一、周视图第一列是周日,
|
||||
* 同一个日期在两档下会落在不同的列 —— 用户会觉得"切个视图日期就跳位了"。
|
||||
* 两处都必须从**同一个** `weekStart` 参数出发。
|
||||
*/
|
||||
for (const ws of [0, 1]) {
|
||||
const days = C.weekDaysOf('2026-09-18', ws);
|
||||
assert.equal(days.length, 7, '一周必须恰好 7 天');
|
||||
// 这 7 天的星期序列必须等于 weekdayLabels(ws) 的顺序
|
||||
const names = C.weekdayLabels(ws);
|
||||
const got = days.map(iso => {
|
||||
const p = iso.split('-').map(Number);
|
||||
return C.weekdayNameOf(p[0], p[1], p[2]);
|
||||
});
|
||||
assert.deepEqual(got, names, `weekStart=${ws} 时两处星期序必须一致`);
|
||||
}
|
||||
});
|
||||
|
||||
test('★ 周/日档日期推进:跨月、跨年、闰年都对(与月档同一个 Date.UTC 口径)', () => {
|
||||
assert.equal(C.addDaysIso('2026-09-30', 1), '2026-10-01', '跨月');
|
||||
assert.equal(C.addDaysIso('2026-01-01', -1), '2025-12-31', '跨年(往回)');
|
||||
assert.equal(C.addDaysIso('2024-02-28', 1), '2024-02-29', '闰年二月');
|
||||
assert.equal(C.addDaysIso('2025-02-28', 1), '2025-03-01', '平年二月');
|
||||
assert.equal(C.addDaysIso('2026-09-18', 7), '2026-09-25', '周档一整步(前)');
|
||||
assert.equal(C.addDaysIso('2026-09-18', -7), '2026-09-11', '周档一整步(后)');
|
||||
// 非法输入不许猜一个日子
|
||||
assert.deepEqual(C.weekDaysOf('not-a-date', 1), [], '坏日期键要给空数组,不能猜');
|
||||
});
|
||||
|
||||
test('★ 页面:档位必须驱动「标题/步长/格子」三处,且三处都来自 Calendar.ts', () => {
|
||||
const page = code(join(ETS, 'pages', 'CalendarPage.ets'));
|
||||
/*
|
||||
* 三处里任一处漏改 ⇒ "看起来正常但翻错量/标题不对"。
|
||||
* 所以判据不只要求"有档位这个状态",而是要求**三个消费者**都在。
|
||||
*/
|
||||
assert.match(page, /calScale:\s*CalendarScale/, '要有档位状态(类型来自 Calendar.ts)');
|
||||
assert.match(page, /stepDaysOf\(\s*this\.calScale\s*\)/, '翻页步长要按档走(不许在页面里写 if/else)');
|
||||
assert.match(page, /rangeTitleOf\(\s*this\.calScale/, '标题要按档走');
|
||||
assert.match(page, /this\.calScale === 'month'/, '格子形状要按档走(月=整月网格,其余=一行)');
|
||||
/*
|
||||
* ★ 档位状态名**不能**叫 `scale`:ArkUI 的 `CustomComponent` 已经有一个 `scale`
|
||||
* 属性(通用修饰符 `scale(value: ScaleOptions)`),同名会编译失败
|
||||
* (实测:`Property 'scale' ... is not assignable to ... '{ (value: ScaleOptions)... }'`)。
|
||||
* 这条钉住它,免得后人图省事改回去。
|
||||
*/
|
||||
assert.ok(!/@State\s+scale\s*:/.test(page),
|
||||
'档位状态不许叫 `scale` —— 与 ArkUI 内置的 scale 修饰符同名,编译不过');
|
||||
});
|
||||
|
||||
test('★ 页面:周/日档下空态要占满剩余高度(否则下半屏是一大片空壁纸)', () => {
|
||||
const page = code(join(ETS, 'pages', 'CalendarPage.ets'));
|
||||
const empty = /这一天没有日程[\s\S]{0,200}?layoutWeight\(1\)/.exec(page);
|
||||
assert.ok(empty,
|
||||
'空态必须 layoutWeight(1):实测 09-18 周视图下"这一天没有日程"贴顶、下半屏全空');
|
||||
});
|
||||
|
||||
@ -102,7 +102,7 @@ const SUITE = [
|
||||
// 明文只对公网告警 / 404 说清“少了 /api/v1” / 网络错误码分类 / 归一化只有一份实现。
|
||||
// 值判据跑真逻辑(model/ApiBase.ts);`.ets` 那几条是**静态**接线判据(无设备)。
|
||||
['test/harmony-apibase.test.mjs', ['--experimental-strip-types', '--no-warnings'], 13],
|
||||
['test/harmony-calendar.test.mjs', ['--experimental-strip-types', '--no-warnings'], 23],
|
||||
['test/harmony-calendar.test.mjs', ['--experimental-strip-types', '--no-warnings'], 30],
|
||||
['test/debt-visibility.test.mjs', [], 1],
|
||||
['test/commit-hygiene.test.mjs', ['--experimental-strip-types', '--no-warnings'], 4],
|
||||
// 判据目录自身的卫生:读文本必须走 test/lib/read.mjs 的具名入口
|
||||
|
||||
Reference in New Issue
Block a user