v65/v66:六个空壳模块接线 + 桌面服务卡片
用户 2026-10-20:
「做几个和 syncCalendar 差不多的卡片。另外其他的功能也逐步完成……旅程的
新建旅程啊,车辆管理、出行方式、节点设置……资源也点不进去……任务也点不
进去,也没法完成……纪念日、新增纪念日……财务的显示也有问题……笔记现在
也点不进去,记笔记也记不了。」
v65 —— 六个模块(任务 / 资源 / 纪念日 / 笔记 / 账本 / 旅程)原本是**同一套
空壳模板**复制出来的:`FbPrimaryButton` 没有 `onTap`、`ListItem` 没有
`onClick`、设置菜单的 `action` 全是空串。这一版把它们全部接上真实接口:
· 任务:新建 / 编辑(`FbTaskEditSheet` + `FbTaskRepo`)、列表可点、勾完成
(走 `/tasks/<id>/status` —— 服务端 `PATCH` **显式忽略** status,走 Patch
会得到"看起来成功的假成功")、按看板筛选
· 资源:新增 / 查看 / 改 / 删(`FbResourceSheet` + `FbResourceRepo`);
过滤维度从**并不存在的**「证件 / 资料」改成按权限与归属
(`resources` 表没有 category 列,那个筛选一直是筛空的)
· 纪念日:新增 / 编辑(`FbDayEditSheet` + `FbDayRepo`);派生生日
(id=0、来自名册)只给提示不给编辑 —— 点开 `/days/0` 必然 404
· 笔记:记一条 / 打开 / 改(`FbNoteSheet` + `FbNoteRepo`),正文按 blocks 存
· 账本:记一笔(`FbLedgerEntrySheet` + `FbLedgerRepo`);修掉财务显示自相
矛盾 —— 资产/负债改回**服务端 summary**(原来端上按金额正负号重算,与
服务端"负债取绝对值"的口径打架,会算出 净资产 ≠ 资产 − 负债)
· 旅程:题图卡片(封面 + 标题 + 城市)+ 行程 + 新建旅程 + 新建节点
服务端同时修掉两处**静默数据丢失**(网页端因提交格式凑巧没暴露,只有走
API 的客户端会中招):
· 新建笔记传的是 `body`,而 `Note::create()` 只读 `blocks` ⇒ 新笔记只有标题
· 改笔记时 `blocks` 恒为空,而 `Note::update()` 是整组替换 ⇒ 只改标题就把
正文全删光;另修 `annivPeopleOf()`(`find()` 的 SQL 不含 people,原来那个
兜底恒为空 ⇒ 只改纪念日标题会清空关联人)
v66 —— 桌面服务卡片(用户选了「桌面服务卡片」这一项):
· `FbFormAbility`(FormExtensionAbility)+ `form_config.json` 三块尺寸:
2×2 今日概览 / 2×4 左右分栏 / 4×4 今日全景
· `FbCardData` 只打 `/home` 一个接口(与网页首页同源),数据**扁平化**后
由 `formBindingData` 推给卡片;断网时用上次成功的缓存并注明"离线"
· 卡片是后台进程 ⇒ 请求带 `authProbe: true`:401 **不能**触发全局"踢回
登录页"(那会顺手把正在用应用的人踢下线,v62 那次事故同一个坑)
· 点卡片 → `postCardAction(router, fbModule)` → `EntryAbility` 读 Want 参数
直接落到对应模块;补了 `onNewWant`(没有它,第二次点卡片"什么也没发生")
· 4×4 右上角 ↻ 走 `postAction(message)` → `onFormEvent` 重取数再推回
验证:`hvigorw assembleHap` BUILD SUCCESSFUL;打包后的 hap 里确认
`FbFormAbility`(type=form)与 `$profile:form_config` 都在,卡片代码进了
`ets/widgets.abc`。静态守卫 730 条全绿,反向验证 110 条注入全部能让守卫变红。
⚠️ 设备仍断连(`hdc list targets` 为空),真机尚未验收。
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* 家书 FamilyBook · 服务卡片提供方(鸿蒙端 FormExtensionAbility)
|
||||
*
|
||||
* 用户 2026-10-20:「做几个和 syncCalendar 差不多的卡片。」
|
||||
* 参照 `SyncCalendar` 的 `EntryFormAbility`:
|
||||
* · `onAddForm` —— 添加到桌面时先塞一份"空壳",再异步推真实数据;
|
||||
* · `onUpdateForm` —— 系统定时刷新(`form_config.json` 里 `updateDuration: 1` ⇒ 30 分钟);
|
||||
* · `onFormEvent` —— 卡片上的「刷新」按钮(`action: 'message'`);
|
||||
* · `onRemoveForm` —— 移出桌面。
|
||||
*
|
||||
* 🔴 三个必须记住的约束:
|
||||
* ① **生命周期回调返回后进程只活约 10 秒**(官方文档),所以回调里**不能做长任务**。
|
||||
* 这里的做法与 SyncCalendar 一致:立刻返回/用本地缓存,真实的网络请求
|
||||
* 放在 `.then()` 里异步推 —— 就算被回收,也只是"这次没刷成",
|
||||
* 下一次系统刷新(30 分钟)还会重试。**卡片数据不是关键数据,允许慢一拍。**
|
||||
* ② `onAddForm` **必须同步返回**一个 `FormBindingData`(不能是 Promise),
|
||||
* 否则桌面直接显示"卡片加载失败"。
|
||||
* ③ 数据构建失败**绝不能抛**:抛出去的表现是桌面上一张白卡,
|
||||
* 用户完全不知道发生了什么。⇒ 失败也返回"带一句人话"的壳(见 `FbCardData.hint`)。
|
||||
*/
|
||||
|
||||
import { FormExtensionAbility, formBindingData, formProvider } from '@kit.FormKit';
|
||||
import { Want } from '@kit.AbilityKit';
|
||||
import { BusinessError } from '@kit.BasicServicesKit';
|
||||
import { hilog } from '@kit.PerformanceAnalysisKit';
|
||||
import { FbCardData, fbCardBuild } from '../data/FbCardData';
|
||||
|
||||
const DOMAIN: number = 0x0000;
|
||||
const TAG: string = 'FbForm';
|
||||
|
||||
export default class FbFormAbility extends FormExtensionAbility {
|
||||
/**
|
||||
* 重新算一遍并推给指定卡片。
|
||||
*
|
||||
* ⚠️ 三种尺寸(2×2 / 2×4 / 4×4)共用这一份数据:多算一次就多打一次接口,
|
||||
* 没有必要 —— 它们只是**排版不同**,数字必须是同一个。
|
||||
*/
|
||||
private push(formId: string): void {
|
||||
fbCardBuild(this.context).then((data: FbCardData): void => {
|
||||
const binding: formBindingData.FormBindingData = formBindingData.createFormBindingData(data);
|
||||
formProvider.updateForm(formId, binding).then((): void => {
|
||||
hilog.info(DOMAIN, TAG, '卡片 %{public}s 已更新(%{public}s)', formId, data.state);
|
||||
}).catch((err: BusinessError): void => {
|
||||
// 卡片可能已经被用户删了 —— 打一条日志就够,不要重试
|
||||
hilog.warn(DOMAIN, TAG, '卡片 %{public}s 更新失败:%{public}s', formId, err.message);
|
||||
});
|
||||
}).catch((err: BusinessError): void => {
|
||||
hilog.error(DOMAIN, TAG, '卡片数据构建失败:%{public}s', err.message);
|
||||
});
|
||||
}
|
||||
|
||||
/** 从 Want 里取卡片 id(`ohos.extra.param.key.form_identity`) */
|
||||
private formIdOf(want: Want): string {
|
||||
if (want.parameters === undefined) {
|
||||
return '';
|
||||
}
|
||||
const raw: Object | undefined = want.parameters['ohos.extra.param.key.form_identity'];
|
||||
return typeof raw === 'string' ? raw : '';
|
||||
}
|
||||
|
||||
onAddForm(want: Want): formBindingData.FormBindingData {
|
||||
const formId: string = this.formIdOf(want);
|
||||
hilog.info(DOMAIN, TAG, '添加卡片:%{public}s', formId);
|
||||
if (formId !== '') {
|
||||
// 异步补真实数据(不 await:见文件头约束 ①)
|
||||
this.push(formId);
|
||||
}
|
||||
// 先给一份"正在取"的壳 —— 卡片上会显示这天/这周的空状态,而不是白屏
|
||||
const shell: FbCardData = new FbCardData();
|
||||
shell.state = 'loading';
|
||||
shell.hint = '正在取数据…';
|
||||
return formBindingData.createFormBindingData(shell);
|
||||
}
|
||||
|
||||
onUpdateForm(formId: string): void {
|
||||
hilog.info(DOMAIN, TAG, '系统定时刷新:%{public}s', formId);
|
||||
this.push(formId);
|
||||
}
|
||||
|
||||
onFormEvent(formId: string, message: string): void {
|
||||
/* 卡片上的按钮都走 `action: 'message'`,`message` 是 JSON 字符串。
|
||||
⚠️ 不解析 JSON:不同版本系统给的形态可能是 `{params:{...}}` 或直接 `{...}`,
|
||||
子串匹配更稳(与 SyncCalendar 同一个取舍)。 */
|
||||
hilog.info(DOMAIN, TAG, '卡片事件:%{public}s %{public}s', formId, message);
|
||||
this.push(formId);
|
||||
}
|
||||
|
||||
onRemoveForm(formId: string): void {
|
||||
// 本卡片不落任何"每张卡"的状态(没有翻页偏移之类),所以只需记一条日志
|
||||
hilog.info(DOMAIN, TAG, '移除卡片:%{public}s', formId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user