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。
This commit is contained in:
2026-10-12 10:06:55 +08:00
parent d6bda758d8
commit 0816e25988
15 changed files with 1081 additions and 942 deletions
+21 -23
View File
@@ -1,18 +1,18 @@
/** /**
* 家书 FamilyBook · 顶栏(鸿蒙端) * 家书 FamilyBook · 顶栏(鸿蒙端)
* *
* 形态照用户指的本机另一应用(SyncCalendar): * 用户 2026-10-12 定的形态:
* **应用图标 + 标题贴左上,右侧放通知/头像** —— 就是一个普通 Row, * · **应用图标 + 「家书」标题**贴左上(下行小字显示当前模块);
* **自己不做任何状态栏留白**。 * · 顶栏底色**白色**(与下面的个人信息卡同色,连成一片);
* · 顶栏**并入页面滚动流**,向上滑动时跟着内容一起滑走
* (所以它本身不固定、也不自己处理状态栏留白)。
* *
* 🔴 沉浸式靠两层配合,顶栏本身不参与: * 🔴 与状态栏的关系:顶栏是白色,而系统栏被涂成全透明、透出的是**根容器底色**。
* ① EntryAbility:系统栏涂成全透明(`#00000000`); * 所以要让"通知栏与顶栏融为一体",根容器在顶栏这一段必须是白色 ——
* ② 根容器:`expandSafeArea` 让背景铺到状态栏区域。 * 由 `Index.ets` 在顶部铺一层与顶栏同高的白色块来实现(页面滚动时它不动,
* 于是「顶栏上方那一块」(其实是根容器背景)与通知栏完全融为一体, * 保证通知栏区域永远是白的,不会滚出内容)。
* 而顶栏内容本身就在安全区内、位置不动 —— 不用算状态栏高度,也就不会算错。
* *
* ⚠️ 顶栏**不放**「设置」—— 与 Web 端一致:设置收在抽屉底部的账号区里, * ⚠️ 顶栏**不放**「设置」—— 与 Web 端一致:设置收在抽屉底部的账号区里。
* 顶栏只留日常入口,再把管理入口顶出来会把顶栏挤满。
*/ */
import { FbColors, FbIcon, FbMetric } from '../common/Theme'; import { FbColors, FbIcon, FbMetric } from '../common/Theme';
@@ -46,11 +46,15 @@ export struct FbTopBar {
}) })
} }
// ---- 应用图标 + 标题(贴左,与抽屉里的品牌区同款,一眼认出是"家书")---- // ---- 应用图标 ----
// ⚠️ 必须给描边:logo 是「米色底(74%) + 砖红图案(24%)」的实心方图,
// 放在白色/米色上时底板与背景几乎同色,只剩砖红图案发飘、看不清。
// 一层极浅的描边就能把底板"托"出来(本机范本 SyncCalendar 也给了描边)。
Image($r('app.media.fb_logo')) Image($r('app.media.fb_logo'))
.width(30) .width(30)
.height(30) .height(30)
.borderRadius(8) .borderRadius(8)
.border({ width: 1, color: FbColors.line })
.flexShrink(0) .flexShrink(0)
Column({ space: 0 }) { Column({ space: 0 }) {
@@ -59,11 +63,10 @@ export struct FbTopBar {
.fontWeight(FontWeight.Bold) .fontWeight(FontWeight.Bold)
.fontColor(FbColors.ink) .fontColor(FbColors.ink)
.maxLines(1) .maxLines(1)
// 副标题显示当前模块名 —— 这样"品牌 + 当前位置"一行说清, // 副标题显示当前模块名 —— "品牌 + 当前位置"一行说清
// 也省掉原来那个只写模块名的孤零零标题
Text(this.moduleName()) Text(this.moduleName())
.fontSize(FbMetric.fontTiny) .fontSize(FbMetric.fontTiny)
.fontColor(FbColors.faint) .fontColor(FbColors.muted)
.maxLines(1) .maxLines(1)
} }
.alignItems(HorizontalAlign.Start) .alignItems(HorizontalAlign.Start)
@@ -111,17 +114,12 @@ export struct FbTopBar {
} }
.width('100%') .width('100%')
.height(FbMetric.topBarH) .height(FbMetric.topBarH)
// 左右留 14;上下尽量薄(顶栏默认贴状态栏下方,不留额外空隙)
.padding({ left: 14, right: 10, top: 4, bottom: 4 }) .padding({ left: 14, right: 10, top: 4, bottom: 4 })
.alignItems(VerticalAlign.Center) .alignItems(VerticalAlign.Center)
// 🔴 **不设 backgroundColor** —— 这是"融为一体"的最后一块拼图。 // 顶栏底色 = 白色(与下面的个人信息卡同色)
// 顶栏没设背景时,它透出的是根容器的底色(page_bg,宣纸米); .backgroundColor(FbColors.surface)
// 而状态栏被涂成全透明、透出的也是根容器底色 ⇒ 两者**天然同色**。 // 底线:与下方内容(同是白色卡)之间给一条极浅的分隔,避免"两块白糊在一起"
// .border({ width: { bottom: 1 }, color: FbColors.line2 })
// ⚠️ 之前给顶栏设了 `FbColors.surface`(白色),于是变成
// 「状态栏(米色)+ 顶栏(白色)」上下拼接 —— 正是用户说的"没融为一体"。
// 本机范本 SyncCalendar 的 header() 也**没有 backgroundColor**,就是这个道理。
// 教训:想与系统栏同色,就别给紧邻元素单独上色,让它透出共同底色。
} }
private moduleName(): string { private moduleName(): string {
@@ -128,32 +128,29 @@ export default class EntryAbility extends UIAbility {
/** /**
* 系统栏配色。 * 系统栏配色。
* *
* 🔴 核心手法(照 SyncCalendar 那套,实测最干净): * 用户 2026-10-12 定的要求:
* **系统栏涂成全透明 `#00000000`**,再让窗口底色 = 页面背景色透出来。 * ① 家书标题栏用**浅色(白)**;
* 这样顶部通知栏的颜色天然等于"它下面那一块"的颜色 —— 就是用户要的 * ② 上面通知栏也是**浅色**,两者融为一体。
* 「上面那一块和通知栏完全融为一体」。
* *
* ⚠️ 曾经的做法是把状态栏设成某个具体色(先设页面底色、又改成顶栏底色), * ⇒ 做法:把状态栏设成与顶栏**同一个颜色**(`surface` = 白)。
* 结果都不对:状态栏是**独立于页面**的一层,给它写死颜色就必然和它下面的 * 这是一个"配置出来的"同色,比"让系统栏透明、再指望紧邻元素恰好同色"稳得多 ——
* 内容割裂(顶栏是白的、状态栏是米色的,看着就是"没对齐")。 * 后者一旦顶栏改了底色就会立刻出现色带(已经踩过一次)。
* 透明才是正解 —— 它不需要知道下面是白还是纸色。
* *
* ⚠️ `setWindowBackgroundColor` 对 phone/tablet 会报 unavailable(编译器告警), * ⚠️ 底部导航条取**页面底色**(宣纸米),与页面下部同色,不突兀。
* 但**这里必须调**:透明系统栏要透出的就是它。告警可忽略(SyncCalendar 同样这么用)。 * ⚠️ 取色走 resourceManager(经 dark/ 限定符解析)—— 深色模式一并生效。
*/ */
private applySystemBars(win: window.Window): void { private applySystemBars(win: window.Window): void {
const pageBg: string = this.themeColor($r('app.color.page_bg').id, '#F7F3EA'); const surfaceBg: string = this.themeColor($r('app.color.surface').id, '#FFFFFF');
// 前景(状态栏里的时间/电量)按页面底色亮度取反,深色模式自动变浅色字 const paperBg: string = this.themeColor($r('app.color.page_bg').id, '#F7F3EA');
const fg: string = this.isDarkBg(pageBg) ? '#FFFFFF' : '#2C231D';
try { try {
win.setWindowSystemBarProperties({ win.setWindowSystemBarProperties({
statusBarColor: '#00000000', // 顶部:与顶栏同色(白)⇒ 通知栏与标题栏融为一体
statusBarContentColor: fg, statusBarColor: surfaceBg,
navigationBarColor: '#00000000', statusBarContentColor: this.isDarkBg(surfaceBg) ? '#FFFFFF' : '#2C231D',
navigationBarContentColor: fg // 底部:与页面底色同色
navigationBarColor: paperBg,
navigationBarContentColor: this.isDarkBg(paperBg) ? '#FFFFFF' : '#2C231D'
}); });
// 窗口底色 = 页面背景色,供透明系统栏透出
win.setWindowBackgroundColor(pageBg);
} catch (err) { } catch (err) {
hilog.error(DOMAIN, 'FamilyBook', 'setSystemBarProperties failed: %{public}s', hilog.error(DOMAIN, 'FamilyBook', 'setSystemBarProperties failed: %{public}s',
JSON.stringify(err)); JSON.stringify(err));
+31 -25
View File
@@ -82,58 +82,64 @@ struct Index {
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM]) .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
} }
/** 内容区:顶栏 + 当前模块页 */ /** 内容区:当前模块页(含顶栏 —— 顶栏由页面放进自己的滚动流,见 topBarSlot) */
@Builder @Builder
mainArea() { mainArea() {
Column() {
// 顶栏:不再需要自己做状态栏留白(背景已由 expandSafeArea 铺到顶,
// 内容本身就在安全区内)
FbTopBar({ app: this.app }) {
}
// ---- 模块页 ----
Column() { Column() {
this.moduleContent() this.moduleContent()
} }
.layoutWeight(1)
.width('100%')
}
.width('100%') .width('100%')
.height('100%') .height('100%')
.backgroundColor(FbColors.paper) .backgroundColor(FbColors.paper)
} }
/**
* 顶栏内容(交给各模块页放进**自己的滚动流最前面** ⇒ 跟着内容一起滚走)。
*
* 🔴 用户要求:「向上滑动的时候,标题栏也跟着向上滑动,不需要固定在当前位置」
* ⇒ 顶栏不能再像以前那样摆在滚动容器**外面**(那样它是固定的)。
* 交给页面承载、页面把它放在 Scroll/List 的内容最前面,才会跟着滚。
*
* ⚠️ 状态栏由系统直接涂成白色(EntryAbility 里 `statusBarColor = surface`,
* 与顶栏同色)。所以顶栏滚走之后,通知栏区域仍由系统保持白色,
* 不会露出页面底色 —— 不需要在页面上再铺"白色压条"。
*/
@Builder
topBarSlot() {
FbTopBar({ app: this.app }) {
}
}
/** 按当前模块渲染对应页面(用 if/else 分发:ArkUI 的 build 里不允许 switch) */ /** 按当前模块渲染对应页面(用 if/else 分发:ArkUI 的 build 里不允许 switch) */
@Builder @Builder
moduleContent() { moduleContent() {
if (this.app.activeModule === 'home') { if (this.app.activeModule === 'home') {
FbHomePage({ app: this.app }) { FbHomePage({ app: this.app, topBar: this.topBarSlot })
}
} else if (this.app.activeModule === 'people' || this.app.activeModule === 'kin') { } else if (this.app.activeModule === 'people' || this.app.activeModule === 'kin') {
FbPeoplePage({ app: this.app }) FbPeoplePage({ app: this.app, topBar: this.topBarSlot })
} else if (this.app.activeModule === 'movies') { } else if (this.app.activeModule === 'movies') {
FbMoviesPage({ app: this.app }) FbMoviesPage({ app: this.app, topBar: this.topBarSlot })
} else if (this.app.activeModule === 'trips') { } else if (this.app.activeModule === 'trips') {
FbTripsPage({ app: this.app }) FbTripsPage({ app: this.app, topBar: this.topBarSlot })
} else if (this.app.activeModule === 'calendar') { } else if (this.app.activeModule === 'calendar') {
FbCalendarPage({ app: this.app }) FbCalendarPage({ app: this.app, topBar: this.topBarSlot })
} else if (this.app.activeModule === 'tasks') { } else if (this.app.activeModule === 'tasks') {
FbTasksPage({ app: this.app }) FbTasksPage({ app: this.app, topBar: this.topBarSlot })
} else if (this.app.activeModule === 'resources') { } else if (this.app.activeModule === 'resources') {
FbResourcesPage({ app: this.app }) FbResourcesPage({ app: this.app, topBar: this.topBarSlot })
} else if (this.app.activeModule === 'days') { } else if (this.app.activeModule === 'days') {
FbDaysPage({ app: this.app }) FbDaysPage({ app: this.app, topBar: this.topBarSlot })
} else if (this.app.activeModule === 'ledger') { } else if (this.app.activeModule === 'ledger') {
FbLedgerPage({ app: this.app }) FbLedgerPage({ app: this.app, topBar: this.topBarSlot })
} else if (this.app.activeModule === 'books') { } else if (this.app.activeModule === 'books') {
FbBooksPage({ app: this.app }) FbBooksPage({ app: this.app, topBar: this.topBarSlot })
} else if (this.app.activeModule === 'notes') { } else if (this.app.activeModule === 'notes') {
FbNotesPage({ app: this.app }) FbNotesPage({ app: this.app, topBar: this.topBarSlot })
} else if (this.app.activeModule === '__notifications') { } else if (this.app.activeModule === '__notifications') {
FbNotificationsPage({ app: this.app }) FbNotificationsPage({ app: this.app, topBar: this.topBarSlot })
} else if (this.app.activeModule === '__settings') { } else if (this.app.activeModule === '__settings') {
FbSettingsPage({ app: this.app }) FbSettingsPage({ app: this.app, topBar: this.topBarSlot })
} else { } else {
// 兜底:模块还没做页面时给一句提示,而不是白屏 // 兜底:模块还没做页面时给一句提示,而不是白屏
Column({ space: 8 }) { Column({ space: 8 }) {
+14 -3
View File
@@ -16,6 +16,12 @@ import { FbEmptyBox, FbIconButton, FbProgressLine, FbRating, FbTag } from '../..
@ComponentV2 @ComponentV2
export struct FbBooksPage { export struct FbBooksPage {
/**
* 顶栏插槽:由 Index.ets 传入,放在本页滚动流最前面 ⇒ 随页面一起滚走。
* 🔴 用户要求「向上滑动时标题栏跟着一起滑走」。
*/
@BuilderParam topBar: () => void;
@Param app: FbAppState = fbAppState(); @Param app: FbAppState = fbAppState();
/** 模块主题色(读书 = 藕紫) */ /** 模块主题色(读书 = 藕紫) */
@@ -23,7 +29,10 @@ export struct FbBooksPage {
private all: FbBookItem[] = FB_DEMO_BOOKS; private all: FbBookItem[] = FB_DEMO_BOOKS;
build() { build() {
Scroll() {
Column() { Column() {
this.topBar()
// ---- 页头 ---- // ---- 页头 ----
Row({ space: 8 }) { Row({ space: 8 }) {
Column({ space: 2 }) { Column({ space: 2 }) {
@@ -67,7 +76,6 @@ export struct FbBooksPage {
} }
}, (g: FbGroup<FbBookItem>): string => 'bg_' + g.key) }, (g: FbGroup<FbBookItem>): string => 'bg_' + g.key)
} }
.layoutWeight(1)
.width('100%') .width('100%')
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 24 }) .padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 24 })
.scrollBar(BarState.Off) .scrollBar(BarState.Off)
@@ -75,9 +83,12 @@ export struct FbBooksPage {
} }
} }
.width('100%') .width('100%')
}
.width('100%')
.height('100%') .height('100%')
.backgroundColor(FbColors.paper) .scrollBar(BarState.Off)
.alignItems(HorizontalAlign.Start) .edgeEffect(EdgeEffect.Spring)
.align(Alignment.TopStart)
} }
/** 一本书:书脊色块(代替封面)+ 书名/作者 + 评分/笔记/进度 */ /** 一本书:书脊色块(代替封面)+ 书名/作者 + 评分/笔记/进度 */
@@ -39,6 +39,12 @@ interface FbCalCell {
@ComponentV2 @ComponentV2
export struct FbCalendarPage { export struct FbCalendarPage {
/**
* 顶栏插槽:由 Index.ets 传入,放在本页滚动流最前面 ⇒ 随页面一起滚走。
* 🔴 用户要求「向上滑动时标题栏跟着一起滑走」。
*/
@BuilderParam topBar: () => void;
@Param app: FbAppState = fbAppState(); @Param app: FbAppState = fbAppState();
/** 模块主题色(日程 = 琥珀) */ /** 模块主题色(日程 = 琥珀) */
@@ -56,7 +62,10 @@ export struct FbCalendarPage {
private weekLabels: string[] = ['一', '二', '三', '四', '五', '六', '日']; private weekLabels: string[] = ['一', '二', '三', '四', '五', '六', '日'];
build() { build() {
Scroll() {
Column() { Column() {
this.topBar()
// ---- 页头:月份 + 视图切换 + 新建 + 设置 ---- // ---- 页头:月份 + 视图切换 + 新建 + 设置 ----
Row({ space: 8 }) { Row({ space: 8 }) {
Column({ space: 2 }) { Column({ space: 2 }) {
@@ -231,16 +240,18 @@ export struct FbCalendarPage {
} }
.width('100%') .width('100%')
} }
.layoutWeight(1)
.width('100%') .width('100%')
.scrollBar(BarState.Off) .scrollBar(BarState.Off)
.edgeEffect(EdgeEffect.Spring) .edgeEffect(EdgeEffect.Spring)
.align(Alignment.TopStart) .align(Alignment.TopStart)
} }
.width('100%') .width('100%')
}
.width('100%')
.height('100%') .height('100%')
.backgroundColor(FbColors.paper) .scrollBar(BarState.Off)
.alignItems(HorizontalAlign.Start) .edgeEffect(EdgeEffect.Spring)
.align(Alignment.TopStart)
} }
/** 上一月 / 下一月按钮 */ /** 上一月 / 下一月按钮 */
+14 -3
View File
@@ -18,6 +18,12 @@ import { FbAvatar, FbEmptyBox, FbIconButton, FbPrimaryButton, FbTag } from '../.
@ComponentV2 @ComponentV2
export struct FbDaysPage { export struct FbDaysPage {
/**
* 顶栏插槽:由 Index.ets 传入,放在本页滚动流最前面 ⇒ 随页面一起滚走。
* 🔴 用户要求「向上滑动时标题栏跟着一起滑走」。
*/
@BuilderParam topBar: () => void;
@Param app: FbAppState = fbAppState(); @Param app: FbAppState = fbAppState();
/** 模块主题色(纪念日 = 陶土橙) */ /** 模块主题色(纪念日 = 陶土橙) */
@@ -27,7 +33,10 @@ export struct FbDaysPage {
@Local kindFilter: string = ''; @Local kindFilter: string = '';
build() { build() {
Scroll() {
Column() { Column() {
this.topBar()
// ---- 页头 ---- // ---- 页头 ----
Row({ space: 8 }) { Row({ space: 8 }) {
Column({ space: 2 }) { Column({ space: 2 }) {
@@ -127,16 +136,18 @@ export struct FbDaysPage {
} }
.width('100%') .width('100%')
} }
.layoutWeight(1)
.width('100%') .width('100%')
.scrollBar(BarState.Off) .scrollBar(BarState.Off)
.edgeEffect(EdgeEffect.Spring) .edgeEffect(EdgeEffect.Spring)
.align(Alignment.TopStart) .align(Alignment.TopStart)
} }
.width('100%') .width('100%')
}
.width('100%')
.height('100%') .height('100%')
.backgroundColor(FbColors.paper) .scrollBar(BarState.Off)
.alignItems(HorizontalAlign.Start) .edgeEffect(EdgeEffect.Spring)
.align(Alignment.TopStart)
} }
/** 即将到来的大数字卡 */ /** 即将到来的大数字卡 */
+8 -2
View File
@@ -22,9 +22,13 @@ import { FbAvatar, FbDot, FbDotLine, FbSectionTitle, FbStatTriple, FbTag } from
@ComponentV2 @ComponentV2
export struct FbHomePage { export struct FbHomePage {
/**
* 顶栏插槽:由 Index.ets 传入,放在本页滚动流最前面 ⇒ 随页面一起滚走。
* 🔴 用户要求「向上滑动时标题栏跟着一起滑走」。
*/
@BuilderParam topBar: () => void;
@Param app: FbAppState = fbAppState(); @Param app: FbAppState = fbAppState();
/** 顶栏右侧插槽(由外层传入,本页不用,留空占位以保持签名统一) */
@BuilderParam topActions: () => void;
private cards: FbModuleCard[] = FB_DEMO_MODULE_CARDS; private cards: FbModuleCard[] = FB_DEMO_MODULE_CARDS;
private todayEvents: FbCalEvent[] = FB_DEMO_EVENTS_TODAY; private todayEvents: FbCalEvent[] = FB_DEMO_EVENTS_TODAY;
@@ -72,6 +76,8 @@ export struct FbHomePage {
build() { build() {
Scroll() { Scroll() {
Column({ space: 0 }) { Column({ space: 0 }) {
this.topBar()
// ==================== ① 个人信息卡(问候 + 今天 + 天气)==================== // ==================== ① 个人信息卡(问候 + 今天 + 天气)====================
// 🔴 用户 2026-10-12 定的:这一块**要白色卡片**("这样显得好看")。 // 🔴 用户 2026-10-12 定的:这一块**要白色卡片**("这样显得好看")。
@@ -24,6 +24,12 @@ import { FbEmptyBox, FbIconButton, FbPrimaryButton, FbTag } from '../../componen
@ComponentV2 @ComponentV2
export struct FbLedgerPage { export struct FbLedgerPage {
/**
* 顶栏插槽:由 Index.ets 传入,放在本页滚动流最前面 ⇒ 随页面一起滚走。
* 🔴 用户要求「向上滑动时标题栏跟着一起滑走」。
*/
@BuilderParam topBar: () => void;
@Param app: FbAppState = fbAppState(); @Param app: FbAppState = fbAppState();
/** 模块主题色(财务 = 橄榄绿) */ /** 模块主题色(财务 = 橄榄绿) */
@@ -34,7 +40,10 @@ export struct FbLedgerPage {
@Local bookId: number = 0; @Local bookId: number = 0;
build() { build() {
Scroll() {
Column() { Column() {
this.topBar()
// ---- 页头 ---- // ---- 页头 ----
Row({ space: 8 }) { Row({ space: 8 }) {
Column({ space: 2 }) { Column({ space: 2 }) {
@@ -121,7 +130,6 @@ export struct FbLedgerPage {
.fontWeight(FontWeight.Medium) .fontWeight(FontWeight.Medium)
.fontColor(FbColors.green) .fontColor(FbColors.green)
} }
.layoutWeight(1)
.alignItems(HorizontalAlign.Start) .alignItems(HorizontalAlign.Start)
Column({ space: 3 }) { Column({ space: 3 }) {
@@ -133,7 +141,6 @@ export struct FbLedgerPage {
.fontWeight(FontWeight.Medium) .fontWeight(FontWeight.Medium)
.fontColor(FbColors.red) .fontColor(FbColors.red)
} }
.layoutWeight(1)
.alignItems(HorizontalAlign.Start) .alignItems(HorizontalAlign.Start)
} }
.width('100%') .width('100%')
@@ -225,16 +232,18 @@ export struct FbLedgerPage {
} }
.width('100%') .width('100%')
} }
.layoutWeight(1)
.width('100%') .width('100%')
.scrollBar(BarState.Off) .scrollBar(BarState.Off)
.edgeEffect(EdgeEffect.Spring) .edgeEffect(EdgeEffect.Spring)
.align(Alignment.TopStart) .align(Alignment.TopStart)
} }
.width('100%') .width('100%')
}
.width('100%')
.height('100%') .height('100%')
.backgroundColor(FbColors.paper) .scrollBar(BarState.Off)
.alignItems(HorizontalAlign.Start) .edgeEffect(EdgeEffect.Spring)
.align(Alignment.TopStart)
} }
/** 一条账目:名字 + 归属人 + 金额 + 盘点入口 */ /** 一条账目:名字 + 归属人 + 金额 + 盘点入口 */
+28 -5
View File
@@ -23,6 +23,12 @@ import { FbAvatar, FbDot, FbEmptyBox, FbPrimaryButton, FbTag } from '../../compo
@ComponentV2 @ComponentV2
export struct FbNotesPage { export struct FbNotesPage {
/**
* 顶栏插槽:由 Index.ets 传入,放在本页滚动流最前面 ⇒ 随页面一起滚走。
* 🔴 用户要求「向上滑动时标题栏跟着一起滑走」。
*/
@BuilderParam topBar: () => void;
@Param app: FbAppState = fbAppState(); @Param app: FbAppState = fbAppState();
/** 模块主题色(笔记 = 褐灰) */ /** 模块主题色(笔记 = 褐灰) */
@@ -30,7 +36,10 @@ export struct FbNotesPage {
private all: FbNoteItem[] = FB_DEMO_NOTES; private all: FbNoteItem[] = FB_DEMO_NOTES;
build() { build() {
Scroll() {
Column() { Column() {
this.topBar()
Row({ space: 8 }) { Row({ space: 8 }) {
Column({ space: 2 }) { Column({ space: 2 }) {
Text('笔记') Text('笔记')
@@ -82,9 +91,12 @@ export struct FbNotesPage {
} }
} }
.width('100%') .width('100%')
}
.width('100%')
.height('100%') .height('100%')
.backgroundColor(FbColors.paper) .scrollBar(BarState.Off)
.alignItems(HorizontalAlign.Start) .edgeEffect(EdgeEffect.Spring)
.align(Alignment.TopStart)
} }
@Builder @Builder
@@ -148,12 +160,21 @@ export struct FbNotesPage {
@ComponentV2 @ComponentV2
export struct FbNotificationsPage { export struct FbNotificationsPage {
/**
* 顶栏插槽:由 Index.ets 传入,放在本页滚动流最前面 ⇒ 随页面一起滚走。
* 🔴 用户要求「向上滑动时标题栏跟着一起滑走」。
*/
@BuilderParam topBar: () => void;
@Param app: FbAppState = fbAppState(); @Param app: FbAppState = fbAppState();
private all: FbNotificationItem[] = FB_DEMO_NOTIFICATIONS; private all: FbNotificationItem[] = FB_DEMO_NOTIFICATIONS;
build() { build() {
Scroll() {
Column() { Column() {
this.topBar()
Row({ space: 8 }) { Row({ space: 8 }) {
Column({ space: 2 }) { Column({ space: 2 }) {
Text('通知消息') Text('通知消息')
@@ -197,7 +218,6 @@ export struct FbNotificationsPage {
} }
}, (n: FbNotificationItem): string => 'nf_' + n.id.toString()) }, (n: FbNotificationItem): string => 'nf_' + n.id.toString())
} }
.layoutWeight(1)
.width('100%') .width('100%')
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 24 }) .padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 24 })
.scrollBar(BarState.Off) .scrollBar(BarState.Off)
@@ -205,9 +225,12 @@ export struct FbNotificationsPage {
} }
} }
.width('100%') .width('100%')
}
.width('100%')
.height('100%') .height('100%')
.backgroundColor(FbColors.paper) .scrollBar(BarState.Off)
.alignItems(HorizontalAlign.Start) .edgeEffect(EdgeEffect.Spring)
.align(Alignment.TopStart)
} }
@Builder @Builder
@@ -18,6 +18,11 @@ import { FbDot, FbEmptyBox, FbIconButton, FbPersonChip, FbPrimaryButton, FbRatin
@ComponentV2 @ComponentV2
export struct FbMoviesPage { export struct FbMoviesPage {
@Param app: FbAppState = fbAppState(); @Param app: FbAppState = fbAppState();
/**
* 顶栏插槽:由 Index.ets 传入,放在本页滚动流最前面 ⇒ 随页面一起滚走。
* 🔴 用户要求「向上滑动时标题栏跟着一起滑走」。
*/
@BuilderParam topBar: () => void;
private movies: FbMovieItem[] = FB_DEMO_MOVIES; private movies: FbMovieItem[] = FB_DEMO_MOVIES;
private cinemas: string[] = FB_DEMO_CINEMAS; private cinemas: string[] = FB_DEMO_CINEMAS;
@@ -25,7 +30,10 @@ export struct FbMoviesPage {
private accent: string = '#7A4E8C'; private accent: string = '#7A4E8C';
build() { build() {
Scroll() {
Column() { Column() {
this.topBar()
// ---- 页头:标题 + 记一场电影 + 设置 ---- // ---- 页头:标题 + 记一场电影 + 设置 ----
Row({ space: 8 }) { Row({ space: 8 }) {
Column({ space: 2 }) { Column({ space: 2 }) {
@@ -106,16 +114,22 @@ export struct FbMoviesPage {
.columnsGap(12) .columnsGap(12)
.rowsGap(12) .rowsGap(12)
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 24 }) .padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 24 })
.layoutWeight(1) // ⚠️ 不能给 layoutWeight:本页已整体放进 Scroll,
// Column 的高度由内容决定,layoutWeight(1) 会去分"剩余空间"(此时为 0)
// ⇒ Grid 高度塌成 0、整页只有顶栏和页头。
.width('100%')
.width('100%') .width('100%')
.scrollBar(BarState.Off) .scrollBar(BarState.Off)
.edgeEffect(EdgeEffect.Spring) .edgeEffect(EdgeEffect.Spring)
} }
} }
.width('100%') .width('100%')
}
.width('100%')
.height('100%') .height('100%')
.backgroundColor(FbColors.paper) .scrollBar(BarState.Off)
.alignItems(HorizontalAlign.Start) .edgeEffect(EdgeEffect.Spring)
.align(Alignment.TopStart)
} }
/** 一张电影卡:海报位(2:3)+ 片名 + 元信息 + 同看的人 */ /** 一张电影卡:海报位(2:3)+ 片名 + 元信息 + 同看的人 */
@@ -30,6 +30,12 @@ interface FbKinRing {
@ComponentV2 @ComponentV2
export struct FbPeoplePage { export struct FbPeoplePage {
/**
* 顶栏插槽:由 Index.ets 传入,放在本页滚动流最前面 ⇒ 随页面一起滚走。
* 🔴 用户要求「向上滑动时标题栏跟着一起滑走」。
*/
@BuilderParam topBar: () => void;
@Param app: FbAppState = fbAppState(); @Param app: FbAppState = fbAppState();
/** 子视图:名册(roster)/ 家谱(kin) */ /** 子视图:名册(roster)/ 家谱(kin) */
@@ -43,7 +49,10 @@ export struct FbPeoplePage {
private rels: FbKinRelation[] = FB_DEMO_KIN_RELATIONS; private rels: FbKinRelation[] = FB_DEMO_KIN_RELATIONS;
build() { build() {
Scroll() {
Column() { Column() {
this.topBar()
// ---- 分段切换(名册 / 家谱)---- // ---- 分段切换(名册 / 家谱)----
Row({ space: 4 }) { Row({ space: 4 }) {
this.segBtn('名册', 'roster', '#A84A28') this.segBtn('名册', 'roster', '#A84A28')
@@ -61,9 +70,12 @@ export struct FbPeoplePage {
} }
} }
.width('100%') .width('100%')
}
.width('100%')
.height('100%') .height('100%')
.backgroundColor(FbColors.paper) .scrollBar(BarState.Off)
.alignItems(HorizontalAlign.Start) .edgeEffect(EdgeEffect.Spring)
.align(Alignment.TopStart)
} }
// ============================================================ // ============================================================
@@ -16,6 +16,12 @@ import { FbEmptyBox, FbIconButton, FbPrimaryButton, FbTag } from '../../componen
@ComponentV2 @ComponentV2
export struct FbResourcesPage { export struct FbResourcesPage {
/**
* 顶栏插槽:由 Index.ets 传入,放在本页滚动流最前面 ⇒ 随页面一起滚走。
* 🔴 用户要求「向上滑动时标题栏跟着一起滑走」。
*/
@BuilderParam topBar: () => void;
@Param app: FbAppState = fbAppState(); @Param app: FbAppState = fbAppState();
/** 模块主题色(资源 = 靛蓝) */ /** 模块主题色(资源 = 靛蓝) */
@@ -25,7 +31,10 @@ export struct FbResourcesPage {
@Local kindFilter: string = ''; @Local kindFilter: string = '';
build() { build() {
Scroll() {
Column() { Column() {
this.topBar()
// ---- 页头 ---- // ---- 页头 ----
Row({ space: 8 }) { Row({ space: 8 }) {
Column({ space: 2 }) { Column({ space: 2 }) {
@@ -84,7 +93,6 @@ export struct FbResourcesPage {
} }
}, (g: FbGroup<FbResourceItem>): string => 'rsg_' + g.key) }, (g: FbGroup<FbResourceItem>): string => 'rsg_' + g.key)
} }
.layoutWeight(1)
.width('100%') .width('100%')
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 24 }) .padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 24 })
.scrollBar(BarState.Off) .scrollBar(BarState.Off)
@@ -92,9 +100,12 @@ export struct FbResourcesPage {
} }
} }
.width('100%') .width('100%')
}
.width('100%')
.height('100%') .height('100%')
.backgroundColor(FbColors.paper) .scrollBar(BarState.Off)
.alignItems(HorizontalAlign.Start) .edgeEffect(EdgeEffect.Spring)
.align(Alignment.TopStart)
} }
/** 一项资源卡 */ /** 一项资源卡 */
@@ -17,6 +17,12 @@ import { FbAvatar, FbDot, FbTag } from '../../components/Widgets';
@ComponentV2 @ComponentV2
export struct FbSettingsPage { export struct FbSettingsPage {
/**
* 顶栏插槽:由 Index.ets 传入,放在本页滚动流最前面 ⇒ 随页面一起滚走。
* 🔴 用户要求「向上滑动时标题栏跟着一起滑走」。
*/
@BuilderParam topBar: () => void;
@Param app: FbAppState = fbAppState(); @Param app: FbAppState = fbAppState();
/** 抽屉里勾选着的模块 key(与 FB_DEFAULT_NAV 一致,不含 nav) */ /** 抽屉里勾选着的模块 key(与 FB_DEFAULT_NAV 一致,不含 nav) */
@@ -28,6 +34,8 @@ export struct FbSettingsPage {
build() { build() {
Scroll() { Scroll() {
Column({ space: 0 }) { Column({ space: 0 }) {
this.topBar()
// ==================== 我的资料 ==================== // ==================== 我的资料 ====================
Row({ space: 14 }) { Row({ space: 14 }) {
+14 -3
View File
@@ -17,6 +17,12 @@ import { FbCheckCircle, FbDot, FbEmptyBox, FbIconButton, FbPrimaryButton, FbTag
@ComponentV2 @ComponentV2
export struct FbTasksPage { export struct FbTasksPage {
/**
* 顶栏插槽:由 Index.ets 传入,放在本页滚动流最前面 ⇒ 随页面一起滚走。
* 🔴 用户要求「向上滑动时标题栏跟着一起滑走」。
*/
@BuilderParam topBar: () => void;
@Param app: FbAppState = fbAppState(); @Param app: FbAppState = fbAppState();
private boards: string[] = FB_DEMO_TASK_BOARDS; private boards: string[] = FB_DEMO_TASK_BOARDS;
@@ -27,7 +33,10 @@ export struct FbTasksPage {
@Local boardFilter: string = ''; @Local boardFilter: string = '';
build() { build() {
Scroll() {
Column() { Column() {
this.topBar()
// ---- 页头 ---- // ---- 页头 ----
Row({ space: 8 }) { Row({ space: 8 }) {
Column({ space: 2 }) { Column({ space: 2 }) {
@@ -88,7 +97,6 @@ export struct FbTasksPage {
} }
}, (g: FbGroup<FbTaskItem>): string => 'grp_' + g.key) }, (g: FbGroup<FbTaskItem>): string => 'grp_' + g.key)
} }
.layoutWeight(1)
.width('100%') .width('100%')
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 24 }) .padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 24 })
.scrollBar(BarState.Off) .scrollBar(BarState.Off)
@@ -96,9 +104,12 @@ export struct FbTasksPage {
} }
} }
.width('100%') .width('100%')
}
.width('100%')
.height('100%') .height('100%')
.backgroundColor(FbColors.paper) .scrollBar(BarState.Off)
.alignItems(HorizontalAlign.Start) .edgeEffect(EdgeEffect.Spring)
.align(Alignment.TopStart)
} }
/** 看板筛选胶囊 */ /** 看板筛选胶囊 */
+14 -3
View File
@@ -20,6 +20,12 @@ import { FbDot, FbEmptyBox, FbIconButton, FbPrimaryButton, FbTag } from '../../c
@ComponentV2 @ComponentV2
export struct FbTripsPage { export struct FbTripsPage {
/**
* 顶栏插槽:由 Index.ets 传入,放在本页滚动流最前面 ⇒ 随页面一起滚走。
* 🔴 用户要求「向上滑动时标题栏跟着一起滑走」。
*/
@BuilderParam topBar: () => void;
@Param app: FbAppState = fbAppState(); @Param app: FbAppState = fbAppState();
private trips: FbTripItem[] = FB_DEMO_TRIPS; private trips: FbTripItem[] = FB_DEMO_TRIPS;
@@ -30,7 +36,10 @@ export struct FbTripsPage {
@Local expandedId: number = 0; @Local expandedId: number = 0;
build() { build() {
Scroll() {
Column() { Column() {
this.topBar()
// ---- 页头操作条 ---- // ---- 页头操作条 ----
Row({ space: 8 }) { Row({ space: 8 }) {
Column({ space: 1 }) { Column({ space: 1 }) {
@@ -69,7 +78,6 @@ export struct FbTripsPage {
} }
}, (t: FbTripItem): string => 'trip_' + t.id.toString()) }, (t: FbTripItem): string => 'trip_' + t.id.toString())
} }
.layoutWeight(1)
.width('100%') .width('100%')
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 24 }) .padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 24 })
.scrollBar(BarState.Off) .scrollBar(BarState.Off)
@@ -77,9 +85,12 @@ export struct FbTripsPage {
} }
} }
.width('100%') .width('100%')
}
.width('100%')
.height('100%') .height('100%')
.backgroundColor(FbColors.paper) .scrollBar(BarState.Off)
.alignItems(HorizontalAlign.Start) .edgeEffect(EdgeEffect.Spring)
.align(Alignment.TopStart)
} }
/** 一张旅程卡:标题 + 时间 + 进度;展开后接行程时间轴 */ /** 一张旅程卡:标题 + 时间 + 进度;展开后接行程时间轴 */