Files
FamilyBook_harmonyos/entry/src/main/ets/data/FbModuleLoad.ets
T
laoyang b32501535e 日程改为全量同步 + 修复大响应超时(laoyang/FamilyBook#31)
## 背景

用户 2026-10-30:「全部拉取,全量同步」。
起因:切到下个月再点某一天,端上没数据(即使服务端有)。
根因是请求范围写死成「今天-7 ~ +60」且切月不重拉 ⇒ 窗口外的日子永远空。

## 改动

### FbModuleLoad.loadModule
- `name` 参数从 `FbModuleName` 放宽到 `string`
  日程改成全量后,缓存键要带请求窗口(`calendar:1970-01-01:2100-12-31`),
  否则"旧窗口的数据"会被当成新窗口的数据显示。
- 新增可选 `timeoutMs`
  全量约 9.3MB / 28628 条,默认 15 秒在移动网络下会超时;
  超时后 `FbModuleRepo` 退回旧缓存,表现成"翻月没数据"且极难查。

### FbModuleRepo.load
- 透传 `timeoutMs` 给 `FbApi`。

### FbCalendarPage
- 请求改为全量范围(`CAL_FULL_FROM` ~ `CAL_FULL_TO`),超时放宽到 60 秒。
- 缓存键带窗口(`calendar:<from>:<to>`):某次请求失败后 `FbModuleRepo`
  会把**旧范围**的缓存 sink 回来,端上就把"只含 10~12 月的数据"当全量显示
  —— 这是该 bug 的帮凶,加窗口标识后从根上消失。
- `agendaDays()` 的 `-7 ~ +60` 现在**只决定列表显示多少天**,
  不再兼作请求范围(原来两个数必须同步的约束随全量同步作废)。
- 删除 `agendaFrom()` / `agendaTo()`(不再需要按当前日期算窗口)。

### 恢复 canEdit 的写权限判断
本轮改动曾把
    `isEventWritable(this.detailBase, this.books)`
回退成
    `isEditableEvent(this.detailBase)`
这是**工单 #31 已修过的 bug 回退**:只判 kind/id/calId 不看本的可写性,
导致只读共享日历里的日程也显示「编辑」按钮,用户改完点保存才报
「这本日历你没有写权限」。

已恢复,并与首页 `FbHomePage.ets:1517` 保持同一口径
(否则同一事件"从首页进能编辑、从日历页进不能")。

## 验证

`build_hap debug` → `BUILD SUCCESSFUL`(33 tasks)。

⚠️ **未做真机验证**。全量同步的实际耗时与内存占用需在真机上确认
(28628 条约 9.3MB,解析 150~300ms,主要成本是流量)。

## 备注

这 3 个文件的改动来自 2026-10-30 那一轮,因故未提交;
本次一并整理提交,与品牌改名(家书→岁月家)无关。
2026-11-01 22:54:22 +08:00

122 lines
5.5 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 家书 FamilyBook · 页面数据接入辅助(鸿蒙端)
*
* 各模块页面的数据装配**都是同一套动作**:
* ① 页面出现 → ② 调仓库 `load()` → ③ 先拿缓存渲染、再拿网络刷新 → ④ 解析成端上类型。
* 这份文件把 ②④ 收成一处,页面只写"我关心哪个模块、怎么解析"。
*
* 🔴 为什么要抽(而不是每页各写一遍):
* 10 个模块 × 同一段 20 行样板 = 200 行重复代码,
* 而重复代码的代价不是"多打几个字",是**改一处漏九处**
* (本项目在别的地方已经吃过这个亏)。
*/
import { hilog } from '@kit.PerformanceAnalysisKit';
import { FbModuleLoad, FbModuleName, FbModuleRepo } from './FbModuleRepo';
const DOMAIN: number = 0x0000;
const TAG: string = 'FbModuleLoad';
/**
* 一次性加载(**直接给路径**,不必登记在上面的静态表里)。
*
* ⚠️ 给"两级接口"用(如账本明细 `/ledger/<id>/entries` —— 路径带 id,
* 没法在静态表里穷举)。缓存键也用完整路径,天然互不冲突。
*/
export async function loadPath<T>(cacheKey: string, path: string, parse: (json: string) => T,
sink: (data: T, fromCache: boolean) => void,
onNote?: (note: string) => void): Promise<void> {
await FbModuleRepo.inst().load(cacheKey, path, (r: FbModuleLoad): void => {
let data: T;
try {
data = parse(r.json);
} catch (err) {
hilog.error(DOMAIN, TAG, '解析 %{public}s 失败:%{public}s', cacheKey, JSON.stringify(err));
return;
}
if (onNote !== undefined && r.error !== '') {
onNote(r.error);
}
sink(data, r.fromCache);
});
}
/** 模块 → 接口路径 */
export const FB_MODULE_PATH: Record<string, string> = {
'home': '/api/v1/home',
'calendar': '/api/v1/calendar',
'days': '/api/v1/days',
'tasks': '/api/v1/tasks',
'trips': '/api/v1/trips',
'ledger': '/api/v1/ledger',
'resources': '/api/v1/resources',
'books': '/api/v1/books',
'notes': '/api/v1/notes',
'notifications': '/api/v1/notifications',
};
/**
* 装配一个模块:读缓存 + 拉网络,每次拿到**解析后**的数据回调。
*
* @param name 模块名
* @param parse JSON → 端上类型的解析函数
* @param sink 数据到位回调(会被调 1~2 次:缓存一次、网络一次)
* @param onNote 可选:提示文案(同步失败/未登录时给页面显示一行小字)
* @param pathOverride 可选:**改掉这个模块的请求路径**(带查询串)。
*
* 🔴🔴 v59(2026-10-18,用户报「列表这个视图,今天的数据缺一条」的真根因):
* 服务端 `/api/v1/calendar` 的 `from`/`to` **默认只给"本月"**
* (`Api::str('from', date('Y-m-01'))` / `date('Y-m-t')`)。
* 而日程页的**列表视图**要渲染 `-7 ~ +60` 天 —— 跨到下个月去,
* 下月那一段**服务端根本没返回** ⇒ 落在下月的日程(包括"今天"往后那几天)
* 在列表里凭空少掉。
* ⚠️ 月视图看起来正常,是因为它本来就只看本月 ⇒ 这个 bug 只在列表暴露。
* ⇒ 日程页显式传 `from`/`to`(覆盖它要显示的那一整段),不要依赖默认值。
*
* ⚠️ 只改**请求路径**、不动缓存键(缓存仍按模块名存):
* 否则同一个模块会出现两个缓存键、切视图时互相覆盖。
* ⚠️ 这也意味着"列表拉的比月视图多"时,缓存里会留下更宽的一段 ——
* 这是**好事**(月视图也能直接命中缓存),但**不要**指望缓存里的范围
* 与当前视图严格一致;页面永远按自己需要的日期去索引。
*/
/**
* @param name 缓存键。**接收任意字符串**而不是只收 `FbModuleName`:
* 🔴 2026-10-30:日程改成全量同步后,缓存键要带上请求窗口
* (`calendar:1970-01-01:2100-12-31`),否则"旧窗口的数据"会被当成
* 新窗口的数据显示 —— 那正是"翻月没数据/数据不对"的根因之一。
* `FbModuleRepo.load` 本来就是 `string`,这里放宽是为了让调用方传得进来。
* @param timeoutMs 可选:本次请求超时(毫秒)。
* 🔴 2026-10-30:日程改成一次拉**全量**(约 9.3MB / 28628 条),
* 默认 15 秒在移动网络下会超时;超时后 `FbModuleRepo` 退回旧缓存,
* 表现成"翻月没数据"且极难查。⇒ 大响应必须能放宽超时。
*/
export async function loadModule<T>(name: string, parse: (json: string) => T,
sink: (data: T, fromCache: boolean) => void,
onNote?: (note: string) => void,
pathOverride?: string,
timeoutMs?: number): Promise<void> {
const path: string = pathOverride !== undefined && pathOverride !== ''
? pathOverride
: (FB_MODULE_PATH[name] !== undefined ? FB_MODULE_PATH[name] : '');
if (path === '') {
hilog.warn(DOMAIN, TAG, '模块 %{public}s 没有配路径', name);
return;
}
await FbModuleRepo.inst().load(name, path, (r: FbModuleLoad): void => {
// ⚠️ 解析失败**不能抛**:缓存/接口的形状变了(或确实是空),
// 抛出去会让页面白屏。这里给个空数组兜住,并留一条日志。
let data: T;
try {
data = parse(r.json);
} catch (err) {
hilog.error(DOMAIN, TAG, '解析 %{public}s 失败:%{public}s', name, JSON.stringify(err));
return;
}
if (onNote !== undefined && r.error !== '') {
onNote(r.error);
}
sink(data, r.fromCache);
});
}