v0.8.4 简要卡观感 / 分享 / 名册搜索 / 全局返回手势(v59e)

八条需求的落地与真因:

1. 浮层与白底"糊在一起"、看不清 —— 根因不是配色,是**遮罩从来没画出来过**:
   ArkUI 的八位色值是 #AARRGGBB(alpha 在前),而全应用写的是 '#00000066'
   (本意"黑 40%")⇒ alpha = 0x00 ⇒ 全透明。同型还有 brand + '1A'(拼到了蓝通道)
   与 shadow('#00000033')。⇒ 17 个文件统一改走 fbAlpha() / FB_MASK / FB_SHADOW /
   FB_HAIRLINE;真机像素采样复验:遮罩 (148,146,140)、卡片白、浅底 (237,242,243)。
2. 简要卡外圈加**主题色描边环**(外层铺色 + padding,不是 border —— border 会被
   内层圆角切出白缝)。
3. 简要卡**去掉删除按钮**,删除挪到完整详情卡最下面(Divider + 红字块)。
   顺手修一处静默失败:confirmDelete() 原来只取 quickSource(),
   从家谱直接开详情时 quickPerson 为 null ⇒ 点删除毫无反应。
4. 信息行间距 10 → 16(每行再各加 3vp 上下内边距,实测行距 134/118px)。
5. 主电话整行可点**直接拨号**(call.makeCall,落到系统拨号盘)。
6. 按钮改为 详情 / 编辑 / 分享 / 关闭;分享走系统分享面板,
   内容为纯文本「姓名 / 电话 / 邮箱」三项(用户点名的就是这三项,
   不多带单位),面板拉不起来时兜底复制到剪贴板并提示。
7. 名册搜索"完全不能用"有两个独立真因:
   ① 搜索框是 List 的第一个 item,无结果时整个 List 被空态替换 ⇒ 输入框被卸载、
      焦点丢失 ⇒ 输不进第二个字。改:List 无条件渲染,空态自身做成 ListItem。
   ② rowToPerson 把 initials 写死成空串,服务端下发的拼音首字母从未落库 ⇒
      拼音搜索恒 0 结果。改:FB_DB_VERSION 6→7 + PRAGMA 探测补列 + 同步时落库;
      匹配同时遍历全部电话(原来只比主号)。
   另加搜索框「清空」(24×24 命中区)。
8. 全局返回手势:应用是单页 + 模块分发、没有路由栈 ⇒ 自建 FB_BACK 登记表
   (倒序询问)+ moduleHistory + applyModule()(不记历史,否则 A/B 两页来回弹)。
   9 个页面登记/注销;Index.onBackPress 先关浮层 → 回上一模块 → 才 return false
   交给系统退桌面(onBackPress 必须声明 boolean,返回 void 会被当成没接管)。

验证:
- 静态守卫 417 / 0(新增 ⑤阴影·描边·色环 ⑥透明度色 ⑦返回手势 ⑧搜索 ⑨分享)
- 反向验证 10 / 0(9 条注入全部能让守卫变红;其中"遮罩换纯黑"那条暴露了
  一条存在型假绿,已改成数个数 + 禁止 Color.Black)
- 建表/迁移列一致性守卫 12 / 0
- 真机全模块遍历 14 / 14;真机逐项复核 12 / 0 与 7 / 0

工具侧顺带修好两个过期的探针:
- 遍历探针仍用"应用内横滑"打开抽屉,而该手势早已按用户要求去掉、入口改为顶栏图标
  ⇒ 一次全量遍历里 12 个模块全报"抽屉里没找到"(像应用坏了,其实是探针过期)。
- 资源/笔记两页写死了早已不存在的条目(户口本/房产证/阳台改造清单)⇒ 改成
  结构性文案(副标题 + 常驻筛选胶囊),不随用户数据变化而恒红。
This commit is contained in:
2026-10-18 23:12:57 +08:00
parent 1c1a859464
commit 7858de27e3
26 changed files with 1213 additions and 257 deletions
+43
View File
@@ -43,6 +43,7 @@ import { FbDb } from '../data/FbDb';
import { FB_ATTACH } from '../data/FbAttachQueue';
import { FB_KEY_STORE } from '../data/FbKeyStore';
import { fbSyncState } from '../common/FbSyncStore';
import { FB_BACK } from '../common/FbBack';
import { common } from '@kit.AbilityKit';
/**
@@ -97,6 +98,48 @@ struct Index {
FB_GRIP.stop();
}
/**
* 🔴🔴 v59e(用户 2026-10-18):接管**系统返回手势**。
*
* 用户原话:
* 「加入全局返回按键手势,现在在界面中使用系统的返回手势,是返回到桌面了,
* 这样不好,如果有弹框,返回应该是关闭弹窗,或者返回上个页面,
* 如果要退出 app,用户可以选择返回桌面的手势。在应用中给,
* 应该是返回上一层才对。」
*
* 为什么以前会"直接回桌面":本应用**没有实现 `onBackPress`** ⇒
* 系统认为这一页没有消费返回 ⇒ 按默认行为**把应用退到后台**。
* 而本应用是"单页 + 模块分发",系统并不知道"用户其实还在应用里"。
*
* 处理顺序(从**最上面一层**往下):
* ① 抽屉开着 ⇒ 收抽屉(它是最外层的浮层);
* ② 页面内浮层(详情 / 编辑 / 简要卡 / 设置 …)⇒ 由页面自己关,
* 见 `common/FbBack.ets` 的登记表;
* ③ 不在首页 ⇒ 回上一个模块(没有历史就回首页);
* ④ 首页且没有任何浮层 ⇒ **返回 false**,交还给系统
* (用户想退到桌面时,退回桌面仍是他的手势说了算)。
*
* ⚠️ 返回值语义:`true` = **已消费**(不再往下传);`false` = 交给系统处理。
* 这里必须显式写 `: boolean`,返回 `void` 会被当成 false ⇒ 等于没接管。
*/
onBackPress(): boolean {
// ① 抽屉
if (this.app.drawerOpen) {
this.app.drawerOpen = false;
return true;
}
// ② 页面内浮层(登记表里最后登记的先问 —— 见 common/FbBack.ets)
if (FB_BACK.back()) {
return true;
}
// ③ 回上一个模块 / 回首页
if (this.entered && !this.app.needLogin && this.app.backToPrevModule()) {
return true;
}
// ④ 真的在最外层了:让系统退到桌面
return false;
}
/**
* 读状态栏高度(由 `EntryAbility` 在全屏生效后量好、放进 AppStorage)。
*
+22 -2
View File
@@ -8,7 +8,7 @@
* 「在线读」「上传 EPUB 解析」「笔记导入」这些属于交互+文件能力,留给第二轮。
*/
import { FbColors, FbMetric, fbTint, FbTint } from '../../common/Theme';
import { FbColors, FbMetric, fbTint, FbTint, FB_MASK } from '../../common/Theme';
import { FbAppState, fbAppState } from '../../common/Store';
import { FbBookItem, FbGroup } from '../../model/Types';
import { fbGroupBook } from '../../model/DemoData';
@@ -18,6 +18,7 @@ import { FbEmptyBox, FbIconButton, FbProgressLine, FbRating, FbTag } from '../..
import { FB_DOCK_RESERVE } from '../../components/FloatDock';
import { fbOnScroll, fbOnTouch } from '../../common/FbScroll';
import { FbModuleSettingsSheet } from '../../components/FbModuleSettingsSheet';
import { FB_BACK } from '../../common/FbBack';
import { FB_SET_BOOKS } from '../../model/FbSettingsMenus';
@ComponentV2
@@ -48,7 +49,26 @@ export struct FbBooksPage {
*
* ⚠️ 不在这里打开本地库 —— `EntryAbility.onCreate` 已统一打开。
*/
/**
* 🔴 v59e(用户 2026-10-18):系统返回手势 —— 先关本页的浮层,再交给 `Index` 换模块。
*
* 本页只有「模块设置」一个浮层,所以规则很短:开着就关掉(返回 true = 已消费)。
* ⚠️ 登记/注销成对出现,见 `common/FbBack.ets` 与 `Index.onBackPress`。
*/
aboutToDisappear(): void {
FB_BACK.unregister('books');
}
private backOnce(): boolean {
if (this.settingsOpen) {
this.settingsOpen = false;
return true;
}
return false;
}
aboutToAppear(): void {
FB_BACK.register('books', (): boolean => this.backOnce());
this.load();
}
@@ -163,7 +183,7 @@ export struct FbBooksPage {
Column()
.width('100%')
.height('100%')
.backgroundColor('#00000066')
.backgroundColor(FB_MASK)
.onClick((): void => {
this.settingsOpen = false;
})
@@ -23,7 +23,7 @@
* 旧代码从条目里臆造 id,导致新建日程根本落不到正确的本上(见 `FbCalRepo.fetchBooks`)。
*/
import { FbColors, FbMetric, fbPalette, FbTint } from '../../common/Theme';
import { FbColors, FbMetric, fbPalette, FbTint, FB_MASK } from '../../common/Theme';
import { FbAppState, fbAppState } from '../../common/Store';
import { FbCalBook, FbCalEvent, isEditableEvent } from '../../model/Types';
import { parseCalendar } from '../../data/FbParse';
@@ -38,6 +38,7 @@ import { FbCalSettingsSheet } from '../../components/FbCalSettingsSheet';
import { FbDayEventPanel } from '../../components/FbDayEventPanel';
import { FB_DOCK_RESERVE } from '../../components/FloatDock';
import { fbOnScroll, fbOnTouch } from '../../common/FbScroll';
import { FB_BACK } from '../../common/FbBack';
/** 月历上「非本月」日期点的灰(hex 常量:格子数据结构里存的是字符串色,不能用 Resource) */
/**
@@ -139,7 +140,41 @@ export struct FbCalendarPage {
/** 详情请求序号(防"连点两条,前一条的响应覆盖后一条") */
private detailToken: number = 0;
/**
* 🔴 v59e(用户 2026-10-18):系统返回手势 —— 只关**最上面一层**浮层。
*
* 四个浮层:编辑 / 新建 / 设置 / 详情。
* ⚠️ 「详情」放最后问:编辑是从详情里点出去的(此时详情已经关了),
* 所以编辑一定比详情更"上"。
*/
aboutToDisappear(): void {
FB_BACK.unregister('calendar');
}
private backOnce(): boolean {
if (this.editing) {
// 与 `FbEventEditSheet` 的 onCancel 一致
this.editing = false;
this.editEvent = null;
return true;
}
if (this.creating) {
this.creating = false;
return true;
}
if (this.settingsOpen) {
this.settingsOpen = false;
return true;
}
if (this.detailOpen()) {
this.closeDetail();
return true;
}
return false;
}
aboutToAppear(): void {
FB_BACK.register('calendar', (): boolean => this.backOnce());
// 先按设置里的默认视图展示(用户上次选的)
this.mode = FB_CAL_SETTINGS.getView();
this.style = FB_CAL_SETTINGS.getStyle();
@@ -596,7 +631,7 @@ export struct FbCalendarPage {
Column()
.width('100%')
.height('100%')
.backgroundColor('#00000066')
.backgroundColor(FB_MASK)
.onClick((): void => {
this.closeDetail();
})
@@ -621,7 +656,7 @@ export struct FbCalendarPage {
Column()
.width('100%')
.height('100%')
.backgroundColor('#00000066')
.backgroundColor(FB_MASK)
.onClick((): void => {
this.editing = false;
this.editEvent = null;
@@ -648,7 +683,7 @@ export struct FbCalendarPage {
Column()
.width('100%')
.height('100%')
.backgroundColor('#00000066')
.backgroundColor(FB_MASK)
.onClick((): void => {
this.creating = false;
})
@@ -674,7 +709,7 @@ export struct FbCalendarPage {
Column()
.width('100%')
.height('100%')
.backgroundColor('#00000066')
.backgroundColor(FB_MASK)
.onClick((): void => {
this.settingsOpen = false;
})
+22 -2
View File
@@ -10,7 +10,7 @@
* 因为「还有几天」是这个模块唯一真正要一眼看到的信息。
*/
import { FbColors, FbMetric, FbTint } from '../../common/Theme';
import { FbColors, FbMetric, FbTint, FB_MASK } from '../../common/Theme';
import { FbAppState, fbAppState } from '../../common/Store';
import { FbAnniversaryItem, FbKinChip } from '../../model/Types';
import { parseDays } from '../../data/FbParse';
@@ -19,6 +19,7 @@ import { FbAvatar, FbEmptyBox, FbIconButton, FbPrimaryButton, FbTag } from '../.
import { FB_DOCK_RESERVE } from '../../components/FloatDock';
import { fbOnScroll, fbOnTouch, fbOnHScroll } from '../../common/FbScroll';
import { FbModuleSettingsSheet } from '../../components/FbModuleSettingsSheet';
import { FB_BACK } from '../../common/FbBack';
import { FB_SET_DAYS } from '../../model/FbSettingsMenus';
@ComponentV2
@@ -51,7 +52,26 @@ export struct FbDaysPage {
*
* ⚠️ 不在这里打开本地库 —— `EntryAbility.onCreate` 已统一打开。
*/
/**
* 🔴 v59e(用户 2026-10-18):系统返回手势 —— 先关本页的浮层,再交给 `Index` 换模块。
*
* 本页只有「模块设置」一个浮层,所以规则很短:开着就关掉(返回 true = 已消费)。
* ⚠️ 登记/注销成对出现,见 `common/FbBack.ets` 与 `Index.onBackPress`。
*/
aboutToDisappear(): void {
FB_BACK.unregister('days');
}
private backOnce(): boolean {
if (this.settingsOpen) {
this.settingsOpen = false;
return true;
}
return false;
}
aboutToAppear(): void {
FB_BACK.register('days', (): boolean => this.backOnce());
this.load();
}
@@ -215,7 +235,7 @@ export struct FbDaysPage {
Column()
.width('100%')
.height('100%')
.backgroundColor('#00000066')
.backgroundColor(FB_MASK)
.onClick((): void => {
this.settingsOpen = false;
})
@@ -14,7 +14,7 @@
* 任何地方都不要拿「元」去加总 —— 浮点误差会越滚越大。
*/
import { FbColors, FbMetric, fbTint, FbTint } from '../../common/Theme';
import { FbColors, FbMetric, fbTint, FbTint, FB_MASK, fbAlpha } from '../../common/Theme';
import { FbAppState, fbAppState } from '../../common/Store';
import { FbGroup, FbLedgerBook, FbLedgerEntry, FbLedgerDetail} from '../../model/Types';
import { fbLedgerKindName, fbMoney } from '../../model/DemoData';
@@ -24,6 +24,7 @@ import { FbEmptyBox, FbIconButton, FbPrimaryButton, FbTag } from '../../componen
import { FB_DOCK_RESERVE } from '../../components/FloatDock';
import { fbOnScroll, fbOnTouch, fbOnHScroll } from '../../common/FbScroll';
import { FbModuleSettingsSheet } from '../../components/FbModuleSettingsSheet';
import { FB_BACK } from '../../common/FbBack';
import { FB_SET_LEDGER } from '../../model/FbSettingsMenus';
@ComponentV2
@@ -65,7 +66,26 @@ export struct FbLedgerPage {
* ⚠️ 账本必须走两级:`/ledger`(有哪些本)→ `/ledger/<id>/entries`(明细 + 汇总)。
* 净资产只在这第二级里 ⇒ 拿到第一本之后要立刻拉一次明细。
*/
/**
* 🔴 v59e(用户 2026-10-18):系统返回手势 —— 先关本页的浮层,再交给 `Index` 换模块。
*
* 本页只有「模块设置」一个浮层,所以规则很短:开着就关掉(返回 true = 已消费)。
* ⚠️ 登记/注销成对出现,见 `common/FbBack.ets` 与 `Index.onBackPress`。
*/
aboutToDisappear(): void {
FB_BACK.unregister('ledger');
}
private backOnce(): boolean {
if (this.settingsOpen) {
this.settingsOpen = false;
return true;
}
return false;
}
aboutToAppear(): void {
FB_BACK.register('ledger', (): boolean => this.backOnce());
this.load();
}
@@ -349,7 +369,7 @@ export struct FbLedgerPage {
Column()
.width('100%')
.height('100%')
.backgroundColor('#00000066')
.backgroundColor(FB_MASK)
.onClick((): void => {
this.settingsOpen = false;
})
@@ -406,7 +426,7 @@ export struct FbLedgerPage {
.fontSize(10)
.fontColor(this.accent)
.padding({ left: 8, right: 8, top: 4, bottom: 4 })
.backgroundColor(this.accent + '14')
.backgroundColor(fbAlpha(this.accent, 0.08))
.borderRadius(FbMetric.radiusPill)
}
.width('100%')
@@ -11,7 +11,7 @@
* 没有使用场景。所以本文件现在只承载「体量小、形态相近」的页面。
*/
import { FbColors, FbMetric } from '../../common/Theme';
import { FbColors, FbMetric, FbTint, fbAlpha } from '../../common/Theme';
import { FbAppState, fbAppState } from '../../common/Store';
import { FbNoteItem, FbNotificationItem } from '../../model/Types';
import { parseNotes, parseNotifications } from '../../data/FbParse';
@@ -242,7 +242,7 @@ export struct FbNotificationsPage {
.fontSize(FbMetric.fontSmall)
.fontColor(FbColors.brand)
.padding({ left: 12, right: 12, top: 7, bottom: 7 })
.backgroundColor(FbColors.brand + '14')
.backgroundColor(fbAlpha(FbTint.brand, 0.08))
.borderRadius(FbMetric.radiusPill)
.onClick(() => {
// 本轮只做界面:把本地未读标记清掉即可
@@ -21,7 +21,7 @@
* 断网时更是完全空白。**先渲染本地、再后台同步**是这套架构的要点。
*/
import { FbColors, FbMetric, fbPalette, fbTint, FbTint } from '../../common/Theme';
import { FbColors, FbMetric, fbPalette, fbTint, FbTint, FB_MASK } from '../../common/Theme';
import { FbAppState, fbAppState } from '../../common/Store';
import { FbKinChip } from '../../model/Types';
import { FbCinemaItem } from '../../data/FbMovieRepo';
@@ -32,6 +32,7 @@ import { FbMovieDetail, FbMovieListItem, FbMovieRepo, FbSyncResult } from '../..
import { FbMovieDetailSheet } from '../../components/FbMovieDetailSheet';
import { FbCinemaSheet } from '../../components/FbCinemaSheet';
import { FbModuleSettingsSheet } from '../../components/FbModuleSettingsSheet';
import { FB_BACK } from '../../common/FbBack';
import { FB_SET_MOVIES } from '../../model/FbSettingsMenus';
import { FB_TOKEN_STORE } from '../../data/FbTokenStore';
import { hilog } from '@kit.PerformanceAnalysisKit';
@@ -151,7 +152,35 @@ export struct FbMoviesPage {
* ⚠️ 不能声明成 async 让框架 await —— 生命周期钩子返回 void 即可,
* 异步逻辑在内部自己跑(否则会影响页面首帧时机)。
*/
/**
* 🔴 v59e(用户 2026-10-18):系统返回手势 —— 只关**最上面一层**浮层。
*
* 本页三个浮层:详情(`sheetOpen`)/ 影院(`cinemaSheetOpen`)/ 设置(`settingsOpen`)。
* ⚠️ 顺序按"后出现的在上层":设置里点「影院管理」会把设置关掉、开影院
* (两者不会同时开),但反过来"影院开着时又点设置"是可能的 ⇒ 先收设置。
*/
aboutToDisappear(): void {
FB_BACK.unregister('movies');
}
private backOnce(): boolean {
if (this.settingsOpen) {
this.settingsOpen = false;
return true;
}
if (this.cinemaSheetOpen) {
this.cinemaSheetOpen = false;
return true;
}
if (this.sheetOpen) {
this.sheetOpen = false;
return true;
}
return false;
}
aboutToAppear(): void {
FB_BACK.register('movies', (): boolean => this.backOnce());
this.bootstrap();
}
@@ -329,7 +358,7 @@ export struct FbMoviesPage {
Column()
.width('100%')
.height('100%')
.backgroundColor('#00000066')
.backgroundColor(FB_MASK)
.onClick((): void => {
this.sheetOpen = false;
})
@@ -354,7 +383,7 @@ export struct FbMoviesPage {
Column()
.width('100%')
.height('100%')
.backgroundColor('#00000066')
.backgroundColor(FB_MASK)
.onClick((): void => {
this.cinemaSheetOpen = false;
})
@@ -372,7 +401,7 @@ export struct FbMoviesPage {
Column()
.width('100%')
.height('100%')
.backgroundColor('#00000066')
.backgroundColor(FB_MASK)
.onClick((): void => {
this.settingsOpen = false;
})
+260 -88
View File
@@ -12,7 +12,7 @@
* 每圈一张卡,卡里按人列。关系表达上等价,宽度可控。
*/
import { FbColors, FbMetric, fbTint, FbTint } from '../../common/Theme';
import { FbColors, FbMetric, fbTint, FbTint, FB_MASK } from '../../common/Theme';
import { FbAppState, fbAppState } from '../../common/Store';
import { FbKinGraph, FbKinPerson, FbRosterItem } from '../../model/Types';
import { FbModuleDef, fbModuleOf, fbThemeVars } from '../../model/Modules';
@@ -35,6 +35,8 @@ import { common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { fbOnScroll, fbOnTouch, fbOnHScroll } from '../../common/FbScroll';
import { fbImageSrc } from '../../data/FbAttachQueue';
import { FB_BACK } from '../../common/FbBack';
import { FbSharePerson, fbPersonShareText, fbShareText } from '../../common/FbShare';
const DOMAIN: number = 0x0000;
const TAG: string = 'FbPeoplePage';
@@ -101,6 +103,19 @@ function matchesKeyword(it: FbRosterItem, p: FbRosterPerson | undefined, q: stri
return true;
}
}
// 🔴 v59e:**所有**号码都要能搜到(原来只比 `p.phone` = 主号,
// 第 2、3 个号码根本搜不出来,用户会以为"这人不在名册里")。
// 并且按**纯数字**再比一次:号码里常带空格/横杠(`138 0000 0000`),
// 用户手打的通常不带分隔符。
const digits: string = k.replace(/[^0-9]/g, '');
for (const ph of it.phones) {
if (ph.indexOf(k) >= 0) {
return true;
}
if (digits !== '' && ph.replace(/[^0-9]/g, '').indexOf(digits) >= 0) {
return true;
}
}
// ---- ② 拼音首字母**子序列**匹配 ----
// 🔴 用户 2026-10-13:"增加按照名字的拼音首字母进行搜索,
@@ -266,12 +281,15 @@ export struct FbPeoplePage {
* ⇒ 用 `@Monitor` 一处声明、自动跟随派生,赋值的写法一概不用改。
*
* ⚠️ 用 `AppStorageV2.connect` 拿到的**同一个单例**(`fbAppState()` 返回的就是它)。
* ⚠️ v59e 补上 `quickPerson`:简要卡也是"页面内浮层",它开着时
* 悬浮栏同样会压住卡片底部的按钮(本卡片底下就是四个动作按钮)。
*/
@Monitor('detailPerson', 'editPerson', 'creating')
@Monitor('detailPerson', 'editPerson', 'creating', 'quickPerson')
onSheetChanged(): void {
const app: FbAppState = fbAppState();
// 只要有一个浮层开着,就让悬浮栏让位(计数 0/1 即可,本页不会同时开两个)
const open: boolean = this.detailPerson !== null || this.editPerson !== null || this.creating;
const open: boolean = this.detailPerson !== null || this.editPerson !== null
|| this.creating || this.quickPerson !== null;
if (open) {
app.sheetCount = 1;
} else {
@@ -315,9 +333,67 @@ export struct FbPeoplePage {
* 理由见 FbMoviesPage 的说明:等网络回来才渲染 = 转圈好几秒、断网全白。
*/
aboutToAppear(): void {
/* 🔴 v59e(用户 2026-10-18):把「关掉本页最上层浮层」的能力登记给
系统返回手势(见 `common/FbBack.ets`)。
⚠️ 在 `aboutToAppear` 里登记、`aboutToDisappear` 里注销 ——
切模块时本页会被销毁,不注销的话登记表里会留下一个"已经死掉的 this",
下一次返回手势会调到旧组件上(改了状态也没人渲染,看起来就是"返回没反应")。 */
FB_BACK.register('people', (): boolean => this.backOnce());
this.bootstrap();
}
aboutToDisappear(): void {
FB_BACK.unregister('people');
}
/**
* 返回手势:**只关一层**,关了就说"我消费了"。
*
* 🔴 用户 2026-10-18 原话:「如果有弹框,返回应该是关闭弹窗,或者返回上个页面,
* 如果要退出 app,用户可以选择返回桌面的手势。在应用中给,应该是返回上一层才对。」
*
* 层级顺序 = 屏幕上"最上面"的那个:
* 编辑 / 新建 > 详情 > 简要卡 > 家谱子视图 > (交还给 `Index` 换模块)
*
* ⚠️ 与各浮层的 `onClose` / `onCancel` **走同一条路**(不是另写一套收尾):
* 否则"点关闭"和"按返回"会出现两种不同的落点,用户会觉得其中一种坏了。
*/
private backOnce(): boolean {
// ① 编辑浮层(含"新建"复用同一个组件,但两者状态是分开的)
if (this.editPerson !== null) {
// 与 `onCancel` 一致:取消编辑 ⇒ 回到详情(刚才就是从详情进来的)
this.detailPerson = this.editPerson;
this.editPerson = null;
return true;
}
if (this.creating) {
this.creating = false;
return true;
}
// ② 详情 ⇒ 回到简要卡(它还在的话)
if (this.detailPerson !== null) {
const back: FbRosterPerson | null = this.detailPerson;
this.detailPerson = null;
if (back !== null && this.quickPerson !== null) {
this.quickFromDetail = back;
}
return true;
}
// ③ 简要卡 ⇒ 回列表
if (this.quickPerson !== null || this.quickFromDetail !== null) {
this.quickPerson = null;
this.quickFromDetail = null;
return true;
}
// ④ 家谱 ⇒ 回名册(同一页的两个子视图,"返回上一层"就是回到默认那个)
if (this.subView === 'kin') {
this.subView = 'roster';
return true;
}
// ⑤ 本页没有可关的东西 ⇒ 交给 `Index`(换回上一个模块)
return false;
}
private async bootstrap(): Promise<void> {
const ctx: common.Context | undefined = this.getUIContext().getHostContext();
if (ctx === undefined) {
@@ -463,7 +539,13 @@ export struct FbPeoplePage {
* 用户能立刻看出"是不是点错了人"(这正是误触最需要的信息)。
*/
private confirmDelete(): void {
const who: FbRosterPerson | null = this.quickSource();
/* 🔴 v59e:删除入口从"简要卡"挪到了"详情卡底部" ⇒ 目标人先取 `detailPerson`。
⚠️ 不能只取 `quickSource()`:详情有可能是**从家谱那行直接打开**的
(`kinRow` 的 onClick 直接写 `detailPerson`,`quickPerson` 一直是 null)
⇒ 那样点删除会**静默什么都不发生**(`who === null` 就 return 了)。
这正是"按钮点了没反应"最难查的一类:没有报错、没有日志。 */
const who: FbRosterPerson | null =
this.detailPerson !== null ? this.detailPerson : this.quickSource();
if (who === null) {
return;
}
@@ -546,7 +628,7 @@ export struct FbPeoplePage {
Column()
.width('100%')
.height('100%')
.backgroundColor('#00000066')
.backgroundColor(FB_MASK)
.onClick((): void => {
this.detailPerson = null;
})
@@ -567,6 +649,12 @@ export struct FbPeoplePage {
// 关详情、开编辑(把同一个人带过去)
this.editPerson = this.detailPerson;
this.detailPerson = null;
},
/* 🔴 v59e(用户 2026-10-18):「应该在详情页面的最下面加一个删除按钮。」
⇒ 删除的入口从简要卡挪到了这里(简要卡上那个太容易误触)。
⚠️ 走的还是同一个 `confirmDelete()`(二次确认 + 调接口 + 刷新名册)。 */
onDelete: (): void => {
this.confirmDelete();
}
})
}
@@ -580,7 +668,7 @@ export struct FbPeoplePage {
Column()
.width('100%')
.height('100%')
.backgroundColor('#00000066')
.backgroundColor(FB_MASK)
.onClick((): void => {
this.quickPerson = null;
this.quickFromDetail = null;
@@ -608,8 +696,21 @@ export struct FbPeoplePage {
this.editPerson = who;
}
},
onDelete: (): void => {
this.confirmDelete();
/* 🔴 v59e(用户 2026-10-18):「上面的电话点击之后就可以直接拨打。」
⚠️ 卡片**不自己拨号**(那要 AbilityContext),只把号码发出来,
真正的 `call.makeCall()` 在本页 —— 与本页别处的拨号同一个实现。 */
onCall: (phone: string): void => {
const who: FbRosterPerson | null = this.quickSource();
this.dial(phone, who !== null ? who.shown : '');
},
/* 🔴 v59e:「下面有一个按钮,详情、编辑、关闭,还有分享。
分享这个可以选择,比如说微信或什么的,以文本的形式,姓名、电话、邮箱。」
⇒ 拉起系统分享面板(见 `common/FbShare.ets` 为什么不用自己拉微信)。 */
onShare: (): void => {
const who: FbRosterPerson | null = this.quickSource();
if (who !== null) {
this.sharePerson(who);
}
}
})
}
@@ -619,7 +720,7 @@ export struct FbPeoplePage {
Column()
.width('100%')
.height('100%')
.backgroundColor('#00000066')
.backgroundColor(FB_MASK)
.onClick((): void => {
this.editPerson = null;
})
@@ -668,7 +769,7 @@ export struct FbPeoplePage {
Column()
.width('100%')
.height('100%')
.backgroundColor('#00000066')
.backgroundColor(FB_MASK)
.onClick((): void => {
this.creating = false;
})
@@ -798,6 +899,23 @@ export struct FbPeoplePage {
.onChange((v: string) => {
this.keyword = v;
})
/* 🔴 v59e:搜索框右侧补一个「清空」——
名册上千人,搜错了要逐字删掉很烦(而且手机上退格容易连删)。
⚠️ 命中区给 24×24(图标只有 12):手机上 12vp 的点击区域点不中。 */
if (this.keyword !== '') {
Row() {
Image($r('app.media.fb_ic_close'))
.width(12)
.height(12)
.colorFilter(fbTint(FbTint.faint))
}
.width(24)
.height(24)
.justifyContent(FlexAlign.Center)
.onClick((): void => {
this.keyword = '';
})
}
}
.layoutWeight(1)
.height(36)
@@ -898,34 +1016,58 @@ export struct FbPeoplePage {
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 6 })
}
if (this.loading) {
Text('正在读取本地数据…')
.width('100%')
.padding({ top: 40, bottom: 40 })
.textAlign(TextAlign.Center)
.fontSize(FbMetric.fontSmall)
.fontColor(FbColors.faint)
} else if (this.visiblePeople().length === 0) {
Text(this.emptyHint())
.width('100%')
.padding({ top: 40, bottom: 40, left: 24, right: 24 })
.textAlign(TextAlign.Center)
.fontSize(FbMetric.fontSmall)
.fontColor(FbColors.faint)
} else {
// 🔴 **平铺列表**(不再按分组分堆)。
// 用户 2026-10-12:"名册不需要分组,是按照标签来的",
// ⇒ 分组筛选已移到顶部的标签条(见 tagBar),列表只负责呈现"筛完的人"。
// ⚠️ 这也让 `List` 的 `ListItemGroup` 整个消失 —— 少一层嵌套,
// 而且不用再纠结"分组头的背景色"。
List({ space: 0 }) {
// 🔴 **顶栏 + 页头作为第一个 item**(用户 2026-10-15 要求本页也跟滚)。
// 放进 List 内容 ⇒ 上滑时跟着滚出屏幕,与首页观感一致;
// 同时 List 仍按需渲染 ⇒ 上千人也不卡(见 pageHeaderItem 的说明)。
ListItem() {
this.pageHeaderItem()
}
/* 🔴🔴 **`List` 必须无条件渲染**(用户 2026-10-18:「名册的搜索完全不能用」)。
*
* 真凶(不是"过滤逻辑没接上",而是**搜索框自己会被卸载**):
* 顶栏 + 页头(含搜索框)原本是 `List` 的**第一个 item**,
* 而"没有结果"时走的是 `else` 分支 —— 整块 `List` 被换成一行空状态文字
* ⇒ **搜索框跟着一起消失**:
* · 输第一个字(中文输入法下先出的是拼音字母)没有匹配 ⇒ 框没了;
* · 框没了 ⇒ 焦点丢了、键盘收起 ⇒ **再也输入不了第二个字**;
* · 用户看到的正是"搜索完全不能用",而代码里过滤逻辑其实是对的。
*
* ⇒ 正确形态:`List` 常驻,**空状态也做成列表里的一项**
* (顶栏/搜索框永远是 item[0],不会被任何分支替换掉)。
* ⚠️ 这条经验对所有"搜索框放在列表头"的页面都成立:
* 条件渲染包裹 `List` ⇒ 搜索中间态会把输入框一起卸载。
*/
List({ space: 0 }) {
// 🔴 **顶栏 + 页头作为第一个 item**(用户 2026-10-15 要求本页也跟滚)。
// 放进 List 内容 ⇒ 上滑时跟着滚出屏幕,与首页观感一致;
// 同时 List 仍按需渲染 ⇒ 上千人也不卡(见 pageHeaderItem 的说明)。
ListItem() {
this.pageHeaderItem()
}
if (this.loading) {
ListItem() {
/* ⚠️ 提示行**直接写在这里**,不抽 `@Builder(text)`:
`@Builder` 的简单类型参数是**按值传**的,参数变了不会触发重建
(本项目在日程/首页踩过两次)。`this.emptyHint()` 必须跟着
`keyword` 变化重算,所以只能让它留在**带状态的 UI 描述里**。 */
Row() {
Text('正在读取本地数据…')
.fontSize(FbMetric.fontSmall)
.fontColor(FbColors.faint)
.textAlign(TextAlign.Center)
.width('100%')
}
.width('100%')
.padding({ top: 40, bottom: 40, left: 24, right: 24 })
}
} else if (this.visiblePeople().length === 0) {
ListItem() {
Row() {
Text(this.emptyHint())
.fontSize(FbMetric.fontSmall)
.fontColor(FbColors.faint)
.textAlign(TextAlign.Center)
.width('100%')
}
.width('100%')
.padding({ top: 40, bottom: 40, left: 24, right: 24 })
}
} else {
ForEach(this.visiblePeople(), (p: FbRosterItem, idx: number) => {
ListItem() {
// 🔴 每条**独立圆角卡**(用户 2026-10-16)⇒ 卡与卡之间留空隙,
@@ -934,59 +1076,59 @@ export struct FbPeoplePage {
this.personRow(p)
}
}, (p: FbRosterItem): string => 'pr_' + p.id.toString())
// 🔴 **末尾占位**:给底部悬浮栏留出高度。
// 为什么要做成**列表的最后一个 item**、而不是 List 的 `padding(bottom)`:
// · `padding(bottom)` 是**容器内边距** ⇒ 它把列表的**可视底边往上收**
// ⇒ 内容在那条线就停住/被裁,看着就是"没滚到屏幕最底、下面空一块"
// (用户 2026-10-15:「它的内容是从悬浮框上面滚出来的」);
// · 做成 item ⇒ 它是**内容的一部分**,跟着一起滚,
// 平时在屏幕外、只有滚到底时才出现在悬浮栏后面 ——
// 与观影页 `.padding` 挂在**内容 Column** 上的效果一致。
ListItem() {
this.dockSpacerItem()
}
}
// 🔴 **`layoutWeight(1)` 让列表吃掉页头之后的全部剩余高度**
// ⇒ 列表白底一直铺到屏幕底部,下面只留一条极窄的页面底色
// (与首页/观影页观感一致;用户明确要求这个形态)。
// ⚠️ 这里能用 `layoutWeight` 的**前提是外层不再是 Scroll** ——
// 在 Scroll 里子组件高度由内容决定,`layoutWeight` 会拿到 0。
// 见 `build()` 的说明。
.layoutWeight(1)
.width('100%')
/* 🔴🔴 v59b(用户 2026-10-18):「名册页面,不好看,太丑了,应该是
* 每个人都是圆角矩形,往中间收一点,距离左右都有点距离,
* 每个人中间都有一点空隙。这样是很多个圆角矩形平铺下来的。」
*
* 🔴 真凶:卡片**早就**是圆角矩形了(v51 就做了 `borderRadius` +
* `margin`),但**容器也是纯白** ⇒ **白卡压在白底上**,
* 圆角和左右收边**全都看不见** —— 看上去就是一整块通栏白。
* (这解释了为什么"改过一轮还是丑":改的是卡片的形状,
* 而问题在于它没有可对比的背景。)
*
* ⇒ 容器改成**页面底色(宣纸米色)**,白卡才"浮"得出来;
* 外层那个兜底白(见 build 末尾)也一并改掉,否则滚到底
* 会从米色突然切回白色。
*/
.backgroundColor(FbColors.paper)
// 🔴🔴 **这里绝对不能再加 `padding({bottom})`**
// (用户 2026-10-15:「它的内容是从悬浮框上面滚出来的」)。
//
// ⚠️ 容器上的 `padding(bottom)` 是**内边距**:它会把 List 的**可视底边**
// 往上收一截 ⇒ 列表内容在那条线就被裁掉/停住,
// 看上去就是"内容没能滚到屏幕最底、下面空出一块"。
// ⇒ 正确做法与观影页一致:预留改成**列表内容末尾的一个占位 item**
// (它是"内容",会跟着一起滚,只在滚到底时才占位)。
// 见下面的 `dockSpacerItem`。
.scrollBar(BarState.Off)
.edgeEffect(EdgeEffect.Spring)
// 🔴 滚动方向上报(驱动底部悬浮栏随滚收起 / 唤回)—— 见 common/FbScroll.ets
.onDidScroll(fbOnScroll)
// 🔴 触摸方向兜底(内容不足一屏的页面滚不动,只能靠手指位移判断)
// 与上面的 `onDidScroll` 互补、互不冲突 —— 见 common/FbScroll.ets
.onTouch(fbOnTouch)
// 🔴 **末尾占位**:给底部悬浮栏留出高度。
// 为什么要做成**列表的最后一个 item**、而不是 List 的 `padding(bottom)`:
// · `padding(bottom)` 是**容器内边距** ⇒ 它把列表的**可视底边往上收**
// ⇒ 内容在那条线就停住/被裁,看着就是"没滚到屏幕最底、下面空一块"
// (用户 2026-10-15:「它的内容是从悬浮框上面滚出来的」);
// · 做成 item ⇒ 它是**内容的一部分**,跟着一起滚,
// 平时在屏幕外、只有滚到底时才出现在悬浮栏后面 ——
// 与观影页 `.padding` 挂在**内容 Column** 上的效果一致。
ListItem() {
this.dockSpacerItem()
}
}
// 🔴 **`layoutWeight(1)` 让列表吃掉页头之后的全部剩余高度**
// ⇒ 列表白底一直铺到屏幕底部,下面只留一条极窄的页面底色
// (与首页/观影页观感一致;用户明确要求这个形态)。
// ⚠️ 这里能用 `layoutWeight` 的**前提是外层不再是 Scroll** ——
// 在 Scroll 里子组件高度由内容决定,`layoutWeight` 会拿到 0。
// 见 `build()` 的说明。
.layoutWeight(1)
.width('100%')
/* 🔴🔴 v59b(用户 2026-10-18):「名册页面,不好看,太丑了,应该是
* 每个人都是圆角矩形,往中间收一点,距离左右都有点距离,
* 每个人中间都有一点空隙。这样是很多个圆角矩形平铺下来的。」
*
* 🔴 真凶:卡片**早就**是圆角矩形了(v51 就做了 `borderRadius` +
* `margin`),但**容器也是纯白** ⇒ **白卡压在白底上**,
* 圆角和左右收边**全都看不见** —— 看上去就是一整块通栏白。
* (这解释了为什么"改过一轮还是丑":改的是卡片的形状,
* 而问题在于它没有可对比的背景。)
*
* ⇒ 容器改成**页面底色(宣纸米色)**,白卡才"浮"得出来;
* 外层那个兜底白(见 build 末尾)也一并改掉,否则滚到底
* 会从米色突然切回白色。
*/
.backgroundColor(FbColors.paper)
// 🔴🔴 **这里绝对不能再加 `padding({bottom})`**
// (用户 2026-10-15:「它的内容是从悬浮框上面滚出来的」)。
//
// ⚠️ 容器上的 `padding(bottom)` 是**内边距**:它会把 List 的**可视底边**
// 往上收一截 ⇒ 列表内容在那条线就被裁掉/停住,
// 看上去就是"内容没能滚到屏幕最底、下面空出一块"。
// ⇒ 正确做法与观影页一致:预留改成**列表内容末尾的一个占位 item**
// (它是"内容",会跟着一起滚,只在滚到底时才占位)。
// 见下面的 `dockSpacerItem`。
.scrollBar(BarState.Off)
.edgeEffect(EdgeEffect.Spring)
// 🔴 滚动方向上报(驱动底部悬浮栏随滚收起 / 唤回)—— 见 common/FbScroll.ets
.onDidScroll(fbOnScroll)
// 🔴 触摸方向兜底(内容不足一屏的页面滚不动,只能靠手指位移判断)
// 与上面的 `onDidScroll` 互补、互不冲突 —— 见 common/FbScroll.ets
.onTouch(fbOnTouch)
}
.width('100%')
.layoutWeight(1)
@@ -1202,6 +1344,36 @@ export struct FbPeoplePage {
});
}
/**
* 分享一个人(**纯文本**:姓名 / 电话 / 邮箱,见 `common/FbShare.ets`)。
*
* 🔴 用户 2026-10-18:「分享这个可以选择,比如说微信或什么的,以文本的形式,
* 姓名、电话、邮箱,这几个以文本的形式分享给别人。」
*
* ⚠️ 用**系统分享面板**:微信 / 短信 / 邮件 / 备忘录都在里面,用户自己选。
* ⚠️ 分享面板拉不起来时,`fbShareText` 会把同一段文本**复制到剪贴板**并回一句话
* ⇒ 这里用 Toast 告诉用户"内容去哪了",而不是静默失败。
*/
private sharePerson(p: FbRosterPerson): void {
const ctx: common.UIAbilityContext | undefined =
this.getUIContext().getHostContext() as common.UIAbilityContext | undefined;
if (ctx === undefined) {
return;
}
const data: FbSharePerson = {
name: p.shown !== '' ? p.shown : p.name,
phones: p.phones,
email: p.email,
};
fbShareText(ctx, fbPersonShareText(data), data.name).then((note: string): void => {
if (note !== '') {
this.getUIContext().getPromptAction().showToast({ message: note, duration: 2600 });
}
}).catch((err: BusinessError): void => {
hilog.warn(DOMAIN, TAG, '分享失败:%{public}s', JSON.stringify(err));
});
}
/**
* 拨号(跳到系统拨号盘,**不直接呼出**)。
*
@@ -8,7 +8,7 @@
* 那一排「类别筛选胶囊」对应 Web 端页头的分类入口。
*/
import { FbColors, FbMetric, fbTint, FbTint } from '../../common/Theme';
import { FbColors, FbMetric, fbTint, FbTint, FB_MASK, fbAlpha } from '../../common/Theme';
import { FbAppState, fbAppState } from '../../common/Store';
import { FbGroup, FbResourceItem } from '../../model/Types';
import { fbGroupResource } from '../../model/DemoData';
@@ -18,6 +18,7 @@ import { FbEmptyBox, FbIconButton, FbPrimaryButton, FbTag } from '../../componen
import { FB_DOCK_RESERVE } from '../../components/FloatDock';
import { fbOnScroll, fbOnTouch } from '../../common/FbScroll';
import { FbModuleSettingsSheet } from '../../components/FbModuleSettingsSheet';
import { FB_BACK } from '../../common/FbBack';
import { FB_SET_RESOURCES } from '../../model/FbSettingsMenus';
@ComponentV2
@@ -50,7 +51,26 @@ export struct FbResourcesPage {
*
* ⚠️ 不在这里打开本地库 —— `EntryAbility.onCreate` 已统一打开。
*/
/**
* 🔴 v59e(用户 2026-10-18):系统返回手势 —— 先关本页的浮层,再交给 `Index` 换模块。
*
* 本页只有「模块设置」一个浮层,所以规则很短:开着就关掉(返回 true = 已消费)。
* ⚠️ 登记/注销成对出现,见 `common/FbBack.ets` 与 `Index.onBackPress`。
*/
aboutToDisappear(): void {
FB_BACK.unregister('resources');
}
private backOnce(): boolean {
if (this.settingsOpen) {
this.settingsOpen = false;
return true;
}
return false;
}
aboutToAppear(): void {
FB_BACK.register('resources', (): boolean => this.backOnce());
this.load();
}
@@ -180,7 +200,7 @@ export struct FbResourcesPage {
Column()
.width('100%')
.height('100%')
.backgroundColor('#00000066')
.backgroundColor(FB_MASK)
.onClick((): void => {
this.settingsOpen = false;
})
@@ -217,7 +237,7 @@ export struct FbResourcesPage {
.width(42)
.height(42)
.borderRadius(FbMetric.radiusS)
.backgroundColor(this.accent + '14')
.backgroundColor(fbAlpha(this.accent, 0.08))
Column({ space: 4 }) {
Row({ space: 6 }) {
@@ -10,7 +10,7 @@
* 抽屉里列什么,正是由这里决定的。
*/
import { FbColors, FbMetric, fbPalette, fbTint, FbTint } from '../../common/Theme';
import { FbColors, FbMetric, fbPalette, fbTint, FbTint, fbAlpha } from '../../common/Theme';
import { FbAppState, fbAppState } from '../../common/Store';
import { FbModuleDef, FB_MODULES, FB_PRESET_COLORS } from '../../model/Modules';
import { FbAvatar, FbDot, FbPrimaryButton, FbTag } from '../../components/Widgets';
@@ -534,7 +534,7 @@ export struct FbSettingsPage {
.width(32)
.height(32)
.borderRadius(9)
.backgroundColor(fbPalette(m.color).brand + '14')
.backgroundColor(fbAlpha(fbPalette(m.color).brand, 0.08))
Column({ space: 2 }) {
Text(m.name)
+22 -2
View File
@@ -9,7 +9,7 @@
* 看板名仍保留(卡片上那个小色块标签),点它可以切到别的看板。
*/
import { FbColors, FbMetric, fbTint, FbTint } from '../../common/Theme';
import { FbColors, FbMetric, fbTint, FbTint, FB_MASK } from '../../common/Theme';
import { FbAppState, fbAppState } from '../../common/Store';
import { FbGroup, FbTaskItem } from '../../model/Types';
import { fbGroupTask } from '../../model/DemoData';
@@ -19,6 +19,7 @@ import { FbCheckCircle, FbDot, FbEmptyBox, FbIconButton, FbPrimaryButton, FbTag
import { FB_DOCK_RESERVE } from '../../components/FloatDock';
import { fbOnScroll, fbOnTouch, fbOnHScroll } from '../../common/FbScroll';
import { FbModuleSettingsSheet } from '../../components/FbModuleSettingsSheet';
import { FB_BACK } from '../../common/FbBack';
import { FB_SET_TASKS } from '../../model/FbSettingsMenus';
@ComponentV2
@@ -52,7 +53,26 @@ export struct FbTasksPage {
*
* ⚠️ 不在这里打开本地库 —— `EntryAbility.onCreate` 已统一打开。
*/
/**
* 🔴 v59e(用户 2026-10-18):系统返回手势 —— 先关本页的浮层,再交给 `Index` 换模块。
*
* 本页只有「模块设置」一个浮层,所以规则很短:开着就关掉(返回 true = 已消费)。
* ⚠️ 登记/注销成对出现,见 `common/FbBack.ets` 与 `Index.onBackPress`。
*/
aboutToDisappear(): void {
FB_BACK.unregister('tasks');
}
private backOnce(): boolean {
if (this.settingsOpen) {
this.settingsOpen = false;
return true;
}
return false;
}
aboutToAppear(): void {
FB_BACK.register('tasks', (): boolean => this.backOnce());
this.load();
}
@@ -186,7 +206,7 @@ export struct FbTasksPage {
Column()
.width('100%')
.height('100%')
.backgroundColor('#00000066')
.backgroundColor(FB_MASK)
.onClick((): void => {
this.settingsOpen = false;
})
+22 -2
View File
@@ -12,7 +12,7 @@
* 时间轴节点分「计划 / 实际」两阶段 —— 与 Web 端一致(v41 加的四种动作按钮)。
*/
import { FbColors, FbMetric, fbTint, FbTint } from '../../common/Theme';
import { FbColors, FbMetric, fbTint, FbTint, FB_MASK } from '../../common/Theme';
import { FbAppState, fbAppState } from '../../common/Store';
import { FbTripItem, FbTripNode } from '../../model/Types';
import { fbMoney } from '../../model/DemoData';
@@ -22,6 +22,7 @@ import { FbDot, FbEmptyBox, FbIconButton, FbPrimaryButton, FbTag } from '../../c
import { FB_DOCK_RESERVE } from '../../components/FloatDock';
import { fbOnScroll, fbOnTouch } from '../../common/FbScroll';
import { FbModuleSettingsSheet } from '../../components/FbModuleSettingsSheet';
import { FB_BACK } from '../../common/FbBack';
import { FB_SET_TRIPS } from '../../model/FbSettingsMenus';
@ComponentV2
@@ -71,7 +72,26 @@ export struct FbTripsPage {
* ⚠️ 本页**不自己打开本地库** —— 已在 `EntryAbility.onCreate` 统一打开了
* (见那里的说明)。页面只管用。
*/
/**
* 🔴 v59e(用户 2026-10-18):系统返回手势 —— 先关本页的浮层,再交给 `Index` 换模块。
*
* 本页只有「模块设置」一个浮层,所以规则很短:开着就关掉(返回 true = 已消费)。
* ⚠️ 登记/注销成对出现,见 `common/FbBack.ets` 与 `Index.onBackPress`。
*/
aboutToDisappear(): void {
FB_BACK.unregister('trips');
}
private backOnce(): boolean {
if (this.settingsOpen) {
this.settingsOpen = false;
return true;
}
return false;
}
aboutToAppear(): void {
FB_BACK.register('trips', (): boolean => this.backOnce());
this.load();
}
@@ -179,7 +199,7 @@ export struct FbTripsPage {
Column()
.width('100%')
.height('100%')
.backgroundColor('#00000066')
.backgroundColor(FB_MASK)
.onClick((): void => {
this.settingsOpen = false;
})