Files
FamilyBook_harmonyos/entry/src/main/ets/pages/Index.ets
T
laoyang 0816e25988 v0.4.0 顶栏改白色 + 通知栏同色 + 顶栏随页面滚动
用户 08:56 的三条要求:
  1. 家书标题栏用浅色(下面色块的背景色);
  2. 上面通知栏也是浅色,两者融为一体;
  3. 向上滑动时标题栏跟着向上滑,不固定在原位。
(追问确认:顶栏底色 = 白;「品牌条 + 页面标题条」一起随滚动消失。)

## ① 系统栏改「不透明 + 与顶栏同色」,弃用透明方案
原来走「系统栏全透明 + 让紧邻元素透出同底色」。这版顶栏要用白色,
若仍走透明方案就得再造一层"白色压条"去刷白状态栏,太绕。
⇒ 直接 `statusBarColor = surface`(白)、`navigationBarColor = page_bg`(纸色)。
这是**配置出来的同色**,比"指望紧邻元素恰好同色"稳得多(后者顶栏一改底色就出色带)。
副产品:不再需要 `setWindowBackgroundColor` ⇒ 连那条 unavailable 告警也消失,
编译回到 **0 error / 0 warning**。

## ② 顶栏随滚动:整页包进 Scroll,顶栏作为内容第一个元素
原来 `Column { 页头; List/Grid(layoutWeight 1) }` —— 顶栏在滚动区**外面**,
所以固定不动。改为 `Scroll { Column { this.topBar(); 页头; 列表 } }`。
顶栏由 Index 以 `@BuilderParam topBar` 插槽下发(`topBarSlot`),13 个页面全部接入。

⚠️ 同步必须处理的两点:
· 列表的 `.layoutWeight(1)` 要删 —— Scroll>Column 里高度由内容决定,
  layoutWeight 会去分"剩余空间"(=0) ⇒ 列表塌成 0 高、整页只剩顶栏和页头;
· List/Grid 不能自己再滚动(去 scrollBar/edgeEffect)—— 两层滚动容器会抢手势。
⚠️ ArkUI 限制:带尾随 lambda 的组件只能有 1 个 `@BuilderParam`
(首页原有闲置的 topActions 已删)。

## ③ 顶栏白色 + logo 可辨认
logo 是「米色底(74%) + 砖红图案(24%)」的实心方图,放在米色上底板隐形、
只剩图案发飘(用户说"看不清")。今加一层极浅描边把底板"托"出来。
顶栏高度 56→48vp。

## 验证(真机,全部通过)
· 沉浸式:状态栏色 `#FFFFFF` == 顶栏色 `#FFFFFF` ✓
· 顶栏随滚动:「家书」标题 y=140 → 滑动后**已滚出可视区** ✓
· 顶栏滚走后状态栏仍为 `#FFFFFF`(未露出页面内容)✓
· 14 个页面遍历 **14/14**;深验(每页特征文本)**12/12**
· 编译 **0 error / 0 warning**

## 过程记录:批量改 12 个文件失败两次(已回滚重做)
第 1 版按缩进正则切分 ⇒ 闭合括号错乱;第 2 版跳字符时**没消费 Column 的
修饰符链** ⇒ 吃掉 `@Builder` 声明、**636 个报错**。
第 3 版改为**按行 + 括号深度解析**,且**改完立刻做结构断言**
(括号配平、`this.topBar()` 恰好 +1),不满足就不写入;先单页试跑再推广。
教训:批量改多文件时正则不可靠,必须配结构自检 + 单页试点。

配套修掉 `_tooling/familybook_harmonyos_run.sh` 一个"假成功":
`hdc list targets` 无设备时输出字面量 `[Empty]`,原来"非空即通过"
⇒ 拿 `[Empty]` 当序列号去装、还打印「✓ 设备:[Empty]」。已显式排除并 die。
2026-10-12 10:06:55 +08:00

160 lines
6.9 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 · 主界面(鸿蒙端)
*
* 骨架:**左侧抽屉 + 右侧内容**,两套形态由窗口短边决定(见 FbAppState.wideLayout):
* · 宽屏(平板 / 折叠屏展开 / 横屏手机):抽屉常驻,内容区并排;
* · 窄屏:内容铺满,抽屉从左侧滑出(点遮罩或选中模块即收起)。
*
* 内容区 = 顶栏 + 当前模块页。此处只做**模块分发**,各模块的版面在各自文件里。
*
* ⚠️ 当前全部为**演示数据 / 静态界面**:还没有接口,所有「新建 / 编辑 / 删除」
* 只做入口与样式,不落任何数据。接口到位后只要替换各页面里的数据源即可。
*/
import { display, window } from '@kit.ArkUI';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { FbColors, FbMetric } from '../common/Theme';
import { FbAppState, FB_WIDE_BREAKPOINT, fbAppState } from '../common/Store';
import { FbDrawerShell } from '../components/Drawer';
import { FbTopBar } from '../components/TopBar';
import { FbHomePage } from './home/FbHomePage';
import { FbMoviesPage } from './movies/FbMoviesPage';
import { FbTripsPage } from './trips/FbTripsPage';
import { FbCalendarPage } from './calendar/FbCalendarPage';
import { FbTasksPage } from './tasks/FbTasksPage';
import { FbPeoplePage } from './people/FbPeoplePage';
import { FbResourcesPage } from './resources/FbResourcesPage';
import { FbDaysPage } from './days/FbDaysPage';
import { FbLedgerPage } from './ledger/FbLedgerPage';
import { FbBooksPage } from './books/FbBooksPage';
import { FbNotesPage, FbNotificationsPage } from './misc/FbMiscPages';
import { FbSettingsPage } from './settings/FbSettingsPage';
@Entry
@ComponentV2
struct Index {
/** 全局状态(抽屉 / 当前模块 / 宽窄屏) */
@Local app: FbAppState = fbAppState();
aboutToAppear(): void {
this.measureScreen();
}
/**
* 量屏幕断点(同步,首帧就能用)。
*
* ⚠️ 这里**只测宽窄屏**。状态栏/手势条高度**不再自己算** ——
* 沉浸式改用「系统栏透明 + expandSafeArea」方案后,
* 窗口布局不动、系统已把内容排在安全区内,自己再算一遍必然错(踩过)。
*/
private measureScreen(): void {
try {
const d: display.Display = display.getDefaultDisplaySync();
const density: number = d.densityPixels;
if (density <= 0) {
return;
}
// 宽窄屏:按窗口短边(短边旋转时不变,故比例接近 1:1 的设备横竖屏布局天然一致)
const shortSide: number = Math.min(d.width, d.height) / density;
this.app.wideLayout = shortSide >= FB_WIDE_BREAKPOINT;
} catch (err) {
hilog.warn(0x0000, 'FamilyBook', 'measureScreen failed: %{public}s', JSON.stringify(err));
}
}
build() {
Column() {
FbDrawerShell({ app: this.app }) {
this.mainArea()
}
}
.width('100%')
.height('100%')
// 🔴 沉浸式的全部秘诀在这一句(照 SyncCalendar 的写法):
// `expandSafeArea` 让**根容器的背景**延伸到状态栏/手势条区域,
// 但**不移动任何子组件** —— 内容仍在安全区内正常排版,
// 只有背景色一路铺到屏幕边缘。
// 再配合 EntryAbility 里"系统栏全透明",顶部通知栏透出的就是这层背景色,
// 于是「上面那一块和通知栏完全融为一体」。
// ⇒ 不需要手算状态栏高度、不需要给顶栏加 padding,也就不会算错。
.backgroundColor(FbColors.paper)
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
}
/** 内容区:当前模块页(含顶栏 —— 顶栏由页面放进自己的滚动流,见 topBarSlot) */
@Builder
mainArea() {
Column() {
this.moduleContent()
}
.width('100%')
.height('100%')
.backgroundColor(FbColors.paper)
}
/**
* 顶栏内容(交给各模块页放进**自己的滚动流最前面** ⇒ 跟着内容一起滚走)。
*
* 🔴 用户要求:「向上滑动的时候,标题栏也跟着向上滑动,不需要固定在当前位置」
* ⇒ 顶栏不能再像以前那样摆在滚动容器**外面**(那样它是固定的)。
* 交给页面承载、页面把它放在 Scroll/List 的内容最前面,才会跟着滚。
*
* ⚠️ 状态栏由系统直接涂成白色(EntryAbility 里 `statusBarColor = surface`,
* 与顶栏同色)。所以顶栏滚走之后,通知栏区域仍由系统保持白色,
* 不会露出页面底色 —— 不需要在页面上再铺"白色压条"。
*/
@Builder
topBarSlot() {
FbTopBar({ app: this.app }) {
}
}
/** 按当前模块渲染对应页面(用 if/else 分发:ArkUI 的 build 里不允许 switch) */
@Builder
moduleContent() {
if (this.app.activeModule === 'home') {
FbHomePage({ app: this.app, topBar: this.topBarSlot })
} else if (this.app.activeModule === 'people' || this.app.activeModule === 'kin') {
FbPeoplePage({ app: this.app, topBar: this.topBarSlot })
} else if (this.app.activeModule === 'movies') {
FbMoviesPage({ app: this.app, topBar: this.topBarSlot })
} else if (this.app.activeModule === 'trips') {
FbTripsPage({ app: this.app, topBar: this.topBarSlot })
} else if (this.app.activeModule === 'calendar') {
FbCalendarPage({ app: this.app, topBar: this.topBarSlot })
} else if (this.app.activeModule === 'tasks') {
FbTasksPage({ app: this.app, topBar: this.topBarSlot })
} else if (this.app.activeModule === 'resources') {
FbResourcesPage({ app: this.app, topBar: this.topBarSlot })
} else if (this.app.activeModule === 'days') {
FbDaysPage({ app: this.app, topBar: this.topBarSlot })
} else if (this.app.activeModule === 'ledger') {
FbLedgerPage({ app: this.app, topBar: this.topBarSlot })
} else if (this.app.activeModule === 'books') {
FbBooksPage({ app: this.app, topBar: this.topBarSlot })
} else if (this.app.activeModule === 'notes') {
FbNotesPage({ app: this.app, topBar: this.topBarSlot })
} else if (this.app.activeModule === '__notifications') {
FbNotificationsPage({ app: this.app, topBar: this.topBarSlot })
} else if (this.app.activeModule === '__settings') {
FbSettingsPage({ app: this.app, topBar: this.topBarSlot })
} else {
// 兜底:模块还没做页面时给一句提示,而不是白屏
Column({ space: 8 }) {
Text('这个模块还没有页面')
.fontSize(FbMetric.fontBody)
.fontColor(FbColors.muted)
Text('当前模块:' + this.app.activeModule)
.fontSize(FbMetric.fontTiny)
.fontColor(FbColors.faint)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
}
}