v73:桌面卡片刷新修复 + 首页日程/任务就地弹详情卡 + 一键导航

用户 2026-10-21 三条:
  「卡片里面的数据并不刷新……应用里面的日程已经有了,但卡片上的日程却并不刷新」
  「首页上的那个日程和任务,点击之后都应该像日程和任务里面的卡片一样弹出来,
    不要进入到日程里面……有地址的还可以直接一键导航」
  「任务也是一样,点击任务之后弹出来详情,可以标记它是不是完成了」

① 卡片不刷新(根因)
   卡片进程(FormExtensionAbility)与应用进程靠 preferences 传 formIds / 翻页偏移。
   SDK 明文禁止跨进程用 preferences,且 getPreferences() 在**进程内**缓存实例、
   同名再取不读盘 ⇒ 应用进程可能永久持有空注册表 ⇒ pushAll() 首行
   `if (list.length === 0) return` 直接返回 ⇒ 卡片只靠系统 30 分钟自刷。
   修:store() 每次先 removePreferencesFromCacheSync 再取;并写死单写者纪律
   (register/unregister/setOffset/clearOffset 只由卡片进程调,应用进程只读)。

② 首页点日程 → 弹 FbEventSheet(canEdit: false,首页不给编辑入口)
   有地址的行整行可点 + 右侧「➤ 导航」胶囊;「编辑日程」才跳日历模块
   (编辑是日历页的职责,不在首页复制第二份表单)。

③ 首页点任务 → 新的 FbTaskDetailSheet
   状态圆可点(勾完成)、标题带删除线、底部「标记为已完成/未完成」+「改一改」。
   状态**必须走 POST /tasks/<id>/status**(PATCH 会忽略 status)。

④ 一键导航 FbNav.ets:startAbility 隐式 Want(不是 openLink,那是 App Linking);
   canOpenLink 需 module.json5 声明 querySchemes(未声明恒 false 且不抛错);
   高德/百度/花瓣/腾讯按序探测,一个地图都没装则复制地点到剪贴板 + Toast。

⑤ 首页登记 FB_BACK(与其余 10 个模块一致),根容器 Stack(浮层是兄弟节点,
   不能当 Column 子项排,否则卡片被挤出屏幕)。

静态守卫 905/0、v73 反向验证 17/0、assembleHap 通过、已装机启动。
This commit is contained in:
2026-10-21 20:01:37 +08:00
parent 15bc3c8599
commit 48a0507455
7 changed files with 1282 additions and 32 deletions
+128 -16
View File
@@ -21,6 +21,7 @@ import { FbCalBook } from '../model/Types';
import { FbDot } from './Widgets';
import { FbSheetRing } from './FbSheetRing';
import { FbSheetClose } from './FbSheetClose';
import { fbCanNavigate } from '../common/FbNav';
const FB_SHEET_BOTTOM_GAP: number = 16;
@@ -44,6 +45,19 @@ export struct FbEventSheet {
@Param accent: string = '';
/** 能不能编辑(不能写的日历本不给"编辑"按钮) */
@Param canEdit: boolean = false;
/**
* 点「导航」。
*
* 🔴 v73(用户 2026-10-21「有地址的可以一键导航」):
* 地点那一行从"纯文字"变成"可点"。
* ⚠️ 为什么把动作**抛给宿主**而不是在这里直接拉地图:
* 拉起应用要 `UIAbilityContext`,而组件里 `getUIContext().getHostContext()`
* 拿到的是 `common.Context`,还得强转 —— 那个强转在**卡片/浮层重建**时可能拿到
* undefined。宿主页本来就有可靠的上下文来源,且"要不要拉地图"属于导航决策
* (跟"当前在哪个模块"有关),不是这张卡的职责。
*/
@Event onNavigate: () => void = (): void => {
};
@Event onClose: () => void = (): void => {
};
@Event onEdit: () => void = (): void => {
@@ -58,6 +72,21 @@ export struct FbEventSheet {
return m !== undefined ? fbThemeVars(m.color).brand : FbTint.brand;
}
/**
* 取主题色的**半透明版本**(八位色 `#AARRGGBB`)。
*
* 🔴 本项目踩过:ArkUI 的八位色是 **alpha 在前**(`#AARRGGBB`),
* 与 CSS 的 `#RRGGBBAA` **相反** ⇒ 直接把 CSS 那套搬过来会得到
* "几乎透明的怪色"或者干脆渲染成黑色。
* ⚠️ 还要兼容"传进来的本身就是八位色"(模块色可能带 alpha):
* 先剥掉已有的 alpha,再拼新的,否则会得到 10 位非法色值。
*/
private accentAlpha(aa: string): string {
const c: string = this.accentColor().replace('#', '');
const rgb: string = c.length >= 8 ? c.substring(2) : c;
return '#' + aa + rgb;
}
/**
* 标题文字。
*
@@ -195,6 +224,57 @@ export struct FbEventSheet {
.alignItems(VerticalAlign.Top)
}
/**
* 「地点」这一行 —— 比普通 `row()` 多一颗「导航」标签,整行可点。
*
* ⚠️ 为什么单独抽一个 Builder 而不是给 `row()` 加参数:
* `row()` 有 2 个参数,加个 `tappable: boolean` 就变成 3 个 ——
* 3 个参数的 @Builder 更危险(多值参数不参与状态观察,本项目已踩 6+ 次)。
* 这里的数据是**每次打开浮层才建一次**(详情卡整体由 `if (detailOpen())`
* 重建),不存在"数据变了行不刷新"的场景,所以用 Builder 是安全的;
* 但仍保持**单参数**,把风险面压到最小。
*/
@Builder
navRow(place: string) {
Row({ space: 10 }) {
Text('地点')
.fontSize(FbMetric.fontTiny)
.fontColor(FbColors.faint)
.width(56)
Text(place)
.fontSize(FbMetric.fontSmall)
.fontColor(FbColors.ink)
.layoutWeight(1)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
/* 标签用模块色(与「编辑日程」按钮同色系),一眼看出"这是这颗票能做的事"。
⚠️ 底色用**极浅的主题色**(`fbAlpha` 那套思路:八位色 `#AARRGGBB`,
alpha 在前)—— 深底白字会把这一行压得比标题还重。 */
if (fbCanNavigate(place)) {
Row({ space: 3 }) {
Text('➤')
.fontSize(10)
.fontColor(this.accentColor())
Text('导航')
.fontSize(FbMetric.fontTiny)
.fontColor(this.accentColor())
}
.padding({ left: 8, right: 8, top: 3, bottom: 3 })
.borderRadius(FbMetric.radiusPill)
.backgroundColor(this.accentAlpha('1A'))
.border({ width: 1, color: this.accentAlpha('33') })
}
}
.width('100%')
.padding({ top: 8, bottom: 8 })
.alignItems(VerticalAlign.Center)
.onClick((): void => {
this.onNavigate();
})
}
build() {
// FB_WRAP_CENTER:把卡片**垂直居中**(用户 2026-10-16 要求:浮在屏幕中间)。
// ⚠️ 居中放在**组件内部**做,而不是改宿主的 Stack 对齐 ——
@@ -280,7 +360,15 @@ export struct FbEventSheet {
this.row('提醒', this.remindText())
}
if (this.event.location !== '') {
this.row('地点', this.event.location)
/* 🔴 v73(用户 2026-10-21「有地址的还可以直接一键导航」):
地点不再是纯文字 —— **整行可点**,右侧带一颗「导航」小标签。
⚠️ 为什么整行可点而不是只让那颗小标签可点:
小标签只有 40vp 宽,手指点不中会以为"没反应";
整行(含地点文字)都是热区,命中率高得多。
⚠️ 但**视觉上**仍要有那颗「导航」标签 —— 只有"整行可点"
而没有任何标记的话,用户根本不知道地点能点
(本项目踩过:卡尾"查看行程"是纯文本,用户以为按钮坏了)。 */
this.navRow(this.event.location)
}
if (this.event.note !== '') {
this.row('备注', this.event.note)
@@ -312,22 +400,46 @@ export struct FbEventSheet {
}
// ---- 底部按钮(只有动作,关闭统一到右上角 ✕)----
if (this.canEdit && this.event !== null) {
/* 🔴 v73(用户 2026-10-21「有地址的还可以直接一键导航」):
有地点时底部多一颗「导航」—— 与上面那行可点的地点**并存**,
是有意的:地点行是"看的时候顺手点",底部按钮是"看完了决定要去"。
两个入口做同一件事,比只留一个更好用(日期详情卡不长,
地点行可能已经滚出视野)。
⚠️ 判据用 `event.location`,不用别的字段 —— 那是唯一权威的地点来源。
⚠️ 两个按钮用 `layoutWeight(1)` 均分宽度;只有一个时它自己占满。 */
if (this.event !== null
&& (fbCanNavigate(this.event.location) || this.canEdit)) {
Row({ space: 10 }) {
/* 🔴 用户 2026-10-19:「点击日程弹出的框,编辑按钮也颜色也要修改。」
⇒ 原来写死名册青 #4A7C82,在日程页里跟模块色(棕)不一致。
改成 `accentColor()`(= 日程模块主题色,与卡片外环同色)。 */
Text('编辑日程')
.fontSize(FbMetric.fontSmall)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.layoutWeight(1)
.height(42)
.backgroundColor(this.accentColor())
.borderRadius(FbMetric.radiusS)
.onClick((): void => {
this.onEdit();
})
if (fbCanNavigate(this.event.location)) {
Text('导航')
.fontSize(FbMetric.fontSmall)
.fontColor(this.accentColor())
.textAlign(TextAlign.Center)
.layoutWeight(1)
.height(42)
.backgroundColor(this.accentAlpha('14'))
.borderRadius(FbMetric.radiusS)
.border({ width: 1, color: this.accentAlpha('33') })
.onClick((): void => {
this.onNavigate();
})
}
if (this.canEdit) {
/* 🔴 用户 2026-10-19:「点击日程弹出的框,编辑按钮也颜色也要修改。」
⇒ 原来写死名册青 #4A7C82,在日程页里跟模块色(棕)不一致。
改成 `accentColor()`(= 日程模块主题色,与卡片外环同色)。 */
Text('编辑日程')
.fontSize(FbMetric.fontSmall)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.layoutWeight(1)
.height(42)
.backgroundColor(this.accentColor())
.borderRadius(FbMetric.radiusS)
.onClick((): void => {
this.onEdit();
})
}
}
.width('100%')
.margin({ top: 14 })