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:
@@ -1,18 +1,18 @@
|
||||
/**
|
||||
* 家书 FamilyBook · 顶栏(鸿蒙端)
|
||||
*
|
||||
* 形态照用户指的本机另一应用(SyncCalendar):
|
||||
* **应用图标 + 标题贴左上,右侧放通知/头像** —— 就是一个普通 Row,
|
||||
* **自己不做任何状态栏留白**。
|
||||
* 用户 2026-10-12 定的形态:
|
||||
* · **应用图标 + 「家书」标题**贴左上(下行小字显示当前模块);
|
||||
* · 顶栏底色**白色**(与下面的个人信息卡同色,连成一片);
|
||||
* · 顶栏**并入页面滚动流**,向上滑动时跟着内容一起滑走
|
||||
* (所以它本身不固定、也不自己处理状态栏留白)。
|
||||
*
|
||||
* 🔴 沉浸式靠两层配合,顶栏本身不参与:
|
||||
* ① EntryAbility:系统栏涂成全透明(`#00000000`);
|
||||
* ② 根容器:`expandSafeArea` 让背景铺到状态栏区域。
|
||||
* 于是「顶栏上方那一块」(其实是根容器背景)与通知栏完全融为一体,
|
||||
* 而顶栏内容本身就在安全区内、位置不动 —— 不用算状态栏高度,也就不会算错。
|
||||
* 🔴 与状态栏的关系:顶栏是白色,而系统栏被涂成全透明、透出的是**根容器底色**。
|
||||
* 所以要让"通知栏与顶栏融为一体",根容器在顶栏这一段必须是白色 ——
|
||||
* 由 `Index.ets` 在顶部铺一层与顶栏同高的白色块来实现(页面滚动时它不动,
|
||||
* 保证通知栏区域永远是白的,不会滚出内容)。
|
||||
*
|
||||
* ⚠️ 顶栏**不放**「设置」—— 与 Web 端一致:设置收在抽屉底部的账号区里,
|
||||
* 顶栏只留日常入口,再把管理入口顶出来会把顶栏挤满。
|
||||
* ⚠️ 顶栏**不放**「设置」—— 与 Web 端一致:设置收在抽屉底部的账号区里。
|
||||
*/
|
||||
|
||||
import { FbColors, FbIcon, FbMetric } from '../common/Theme';
|
||||
@@ -46,11 +46,15 @@ export struct FbTopBar {
|
||||
})
|
||||
}
|
||||
|
||||
// ---- 应用图标 + 标题(贴左,与抽屉里的品牌区同款,一眼认出是"家书")----
|
||||
// ---- 应用图标 ----
|
||||
// ⚠️ 必须给描边:logo 是「米色底(74%) + 砖红图案(24%)」的实心方图,
|
||||
// 放在白色/米色上时底板与背景几乎同色,只剩砖红图案发飘、看不清。
|
||||
// 一层极浅的描边就能把底板"托"出来(本机范本 SyncCalendar 也给了描边)。
|
||||
Image($r('app.media.fb_logo'))
|
||||
.width(30)
|
||||
.height(30)
|
||||
.borderRadius(8)
|
||||
.border({ width: 1, color: FbColors.line })
|
||||
.flexShrink(0)
|
||||
|
||||
Column({ space: 0 }) {
|
||||
@@ -59,11 +63,10 @@ export struct FbTopBar {
|
||||
.fontWeight(FontWeight.Bold)
|
||||
.fontColor(FbColors.ink)
|
||||
.maxLines(1)
|
||||
// 副标题显示当前模块名 —— 这样"品牌 + 当前位置"一行说清,
|
||||
// 也省掉原来那个只写模块名的孤零零标题
|
||||
// 副标题显示当前模块名 —— "品牌 + 当前位置"一行说清
|
||||
Text(this.moduleName())
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
.fontColor(FbColors.muted)
|
||||
.maxLines(1)
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
@@ -111,17 +114,12 @@ export struct FbTopBar {
|
||||
}
|
||||
.width('100%')
|
||||
.height(FbMetric.topBarH)
|
||||
// 左右留 14;上下尽量薄(顶栏默认贴状态栏下方,不留额外空隙)
|
||||
.padding({ left: 14, right: 10, top: 4, bottom: 4 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
// 🔴 **不设 backgroundColor** —— 这是"融为一体"的最后一块拼图。
|
||||
// 顶栏没设背景时,它透出的是根容器的底色(page_bg,宣纸米);
|
||||
// 而状态栏被涂成全透明、透出的也是根容器底色 ⇒ 两者**天然同色**。
|
||||
//
|
||||
// ⚠️ 之前给顶栏设了 `FbColors.surface`(白色),于是变成
|
||||
// 「状态栏(米色)+ 顶栏(白色)」上下拼接 —— 正是用户说的"没融为一体"。
|
||||
// 本机范本 SyncCalendar 的 header() 也**没有 backgroundColor**,就是这个道理。
|
||||
// 教训:想与系统栏同色,就别给紧邻元素单独上色,让它透出共同底色。
|
||||
// 顶栏底色 = 白色(与下面的个人信息卡同色)
|
||||
.backgroundColor(FbColors.surface)
|
||||
// 底线:与下方内容(同是白色卡)之间给一条极浅的分隔,避免"两块白糊在一起"
|
||||
.border({ width: { bottom: 1 }, color: FbColors.line2 })
|
||||
}
|
||||
|
||||
private moduleName(): string {
|
||||
|
||||
@@ -128,32 +128,29 @@ export default class EntryAbility extends UIAbility {
|
||||
/**
|
||||
* 系统栏配色。
|
||||
*
|
||||
* 🔴 核心手法(照 SyncCalendar 那套,实测最干净):
|
||||
* **系统栏涂成全透明 `#00000000`**,再让窗口底色 = 页面背景色透出来。
|
||||
* 这样顶部通知栏的颜色天然等于"它下面那一块"的颜色 —— 就是用户要的
|
||||
* 「上面那一块和通知栏完全融为一体」。
|
||||
* 用户 2026-10-12 定的要求:
|
||||
* ① 家书标题栏用**浅色(白)**;
|
||||
* ② 上面通知栏也是**浅色**,两者融为一体。
|
||||
*
|
||||
* ⚠️ 曾经的做法是把状态栏设成某个具体色(先设页面底色、又改成顶栏底色),
|
||||
* 结果都不对:状态栏是**独立于页面**的一层,给它写死颜色就必然和它下面的
|
||||
* 内容割裂(顶栏是白的、状态栏是米色的,看着就是"没对齐")。
|
||||
* 透明才是正解 —— 它不需要知道下面是白还是纸色。
|
||||
* ⇒ 做法:把状态栏设成与顶栏**同一个颜色**(`surface` = 白)。
|
||||
* 这是一个"配置出来的"同色,比"让系统栏透明、再指望紧邻元素恰好同色"稳得多 ——
|
||||
* 后者一旦顶栏改了底色就会立刻出现色带(已经踩过一次)。
|
||||
*
|
||||
* ⚠️ `setWindowBackgroundColor` 对 phone/tablet 会报 unavailable(编译器告警),
|
||||
* 但**这里必须调**:透明系统栏要透出的就是它。告警可忽略(SyncCalendar 同样这么用)。
|
||||
* ⚠️ 底部导航条取**页面底色**(宣纸米),与页面下部同色,不突兀。
|
||||
* ⚠️ 取色走 resourceManager(经 dark/ 限定符解析)—— 深色模式一并生效。
|
||||
*/
|
||||
private applySystemBars(win: window.Window): void {
|
||||
const pageBg: string = this.themeColor($r('app.color.page_bg').id, '#F7F3EA');
|
||||
// 前景(状态栏里的时间/电量)按页面底色亮度取反,深色模式自动变浅色字
|
||||
const fg: string = this.isDarkBg(pageBg) ? '#FFFFFF' : '#2C231D';
|
||||
const surfaceBg: string = this.themeColor($r('app.color.surface').id, '#FFFFFF');
|
||||
const paperBg: string = this.themeColor($r('app.color.page_bg').id, '#F7F3EA');
|
||||
try {
|
||||
win.setWindowSystemBarProperties({
|
||||
statusBarColor: '#00000000',
|
||||
statusBarContentColor: fg,
|
||||
navigationBarColor: '#00000000',
|
||||
navigationBarContentColor: fg
|
||||
// 顶部:与顶栏同色(白)⇒ 通知栏与标题栏融为一体
|
||||
statusBarColor: surfaceBg,
|
||||
statusBarContentColor: this.isDarkBg(surfaceBg) ? '#FFFFFF' : '#2C231D',
|
||||
// 底部:与页面底色同色
|
||||
navigationBarColor: paperBg,
|
||||
navigationBarContentColor: this.isDarkBg(paperBg) ? '#FFFFFF' : '#2C231D'
|
||||
});
|
||||
// 窗口底色 = 页面背景色,供透明系统栏透出
|
||||
win.setWindowBackgroundColor(pageBg);
|
||||
} catch (err) {
|
||||
hilog.error(DOMAIN, 'FamilyBook', 'setSystemBarProperties failed: %{public}s',
|
||||
JSON.stringify(err));
|
||||
|
||||
@@ -82,58 +82,64 @@ struct Index {
|
||||
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
|
||||
}
|
||||
|
||||
/** 内容区:顶栏 + 当前模块页 */
|
||||
/** 内容区:当前模块页(含顶栏 —— 顶栏由页面放进自己的滚动流,见 topBarSlot) */
|
||||
@Builder
|
||||
mainArea() {
|
||||
Column() {
|
||||
// 顶栏:不再需要自己做状态栏留白(背景已由 expandSafeArea 铺到顶,
|
||||
// 内容本身就在安全区内)
|
||||
FbTopBar({ app: this.app }) {
|
||||
}
|
||||
|
||||
// ---- 模块页 ----
|
||||
Column() {
|
||||
this.moduleContent()
|
||||
}
|
||||
.layoutWeight(1)
|
||||
.width('100%')
|
||||
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 }) {
|
||||
}
|
||||
FbHomePage({ app: this.app, topBar: this.topBarSlot })
|
||||
} 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') {
|
||||
FbMoviesPage({ app: this.app })
|
||||
FbMoviesPage({ app: this.app, topBar: this.topBarSlot })
|
||||
} else if (this.app.activeModule === 'trips') {
|
||||
FbTripsPage({ app: this.app })
|
||||
FbTripsPage({ app: this.app, topBar: this.topBarSlot })
|
||||
} else if (this.app.activeModule === 'calendar') {
|
||||
FbCalendarPage({ app: this.app })
|
||||
FbCalendarPage({ app: this.app, topBar: this.topBarSlot })
|
||||
} else if (this.app.activeModule === 'tasks') {
|
||||
FbTasksPage({ app: this.app })
|
||||
FbTasksPage({ app: this.app, topBar: this.topBarSlot })
|
||||
} else if (this.app.activeModule === 'resources') {
|
||||
FbResourcesPage({ app: this.app })
|
||||
FbResourcesPage({ app: this.app, topBar: this.topBarSlot })
|
||||
} else if (this.app.activeModule === 'days') {
|
||||
FbDaysPage({ app: this.app })
|
||||
FbDaysPage({ app: this.app, topBar: this.topBarSlot })
|
||||
} else if (this.app.activeModule === 'ledger') {
|
||||
FbLedgerPage({ app: this.app })
|
||||
FbLedgerPage({ app: this.app, topBar: this.topBarSlot })
|
||||
} else if (this.app.activeModule === 'books') {
|
||||
FbBooksPage({ app: this.app })
|
||||
FbBooksPage({ app: this.app, topBar: this.topBarSlot })
|
||||
} else if (this.app.activeModule === 'notes') {
|
||||
FbNotesPage({ app: this.app })
|
||||
FbNotesPage({ app: this.app, topBar: this.topBarSlot })
|
||||
} else if (this.app.activeModule === '__notifications') {
|
||||
FbNotificationsPage({ app: this.app })
|
||||
FbNotificationsPage({ app: this.app, topBar: this.topBarSlot })
|
||||
} else if (this.app.activeModule === '__settings') {
|
||||
FbSettingsPage({ app: this.app })
|
||||
FbSettingsPage({ app: this.app, topBar: this.topBarSlot })
|
||||
} else {
|
||||
// 兜底:模块还没做页面时给一句提示,而不是白屏
|
||||
Column({ space: 8 }) {
|
||||
|
||||
@@ -16,6 +16,12 @@ import { FbEmptyBox, FbIconButton, FbProgressLine, FbRating, FbTag } from '../..
|
||||
|
||||
@ComponentV2
|
||||
export struct FbBooksPage {
|
||||
|
||||
/**
|
||||
* 顶栏插槽:由 Index.ets 传入,放在本页滚动流最前面 ⇒ 随页面一起滚走。
|
||||
* 🔴 用户要求「向上滑动时标题栏跟着一起滑走」。
|
||||
*/
|
||||
@BuilderParam topBar: () => void;
|
||||
@Param app: FbAppState = fbAppState();
|
||||
|
||||
/** 模块主题色(读书 = 藕紫) */
|
||||
@@ -23,61 +29,66 @@ export struct FbBooksPage {
|
||||
private all: FbBookItem[] = FB_DEMO_BOOKS;
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
// ---- 页头 ----
|
||||
Row({ space: 8 }) {
|
||||
Column({ space: 2 }) {
|
||||
Text('书架')
|
||||
.fontSize(FbMetric.fontTitle)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
Text(this.all.length.toString() + ' 本 · '
|
||||
+ this.readingCount().toString() + ' 本在读 · '
|
||||
+ this.noteCount().toString() + ' 条笔记')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
Scroll() {
|
||||
Column() {
|
||||
this.topBar()
|
||||
|
||||
// 上传 / 统计(Web 端在设置菜单里)
|
||||
FbIconButton({ icon: $r('app.media.fb_ic_chart'), tint: FbColors.muted })
|
||||
FbIconButton({ icon: $r('app.media.fb_ic_gear'), tint: FbColors.muted })
|
||||
// ---- 页头 ----
|
||||
Row({ space: 8 }) {
|
||||
Column({ space: 2 }) {
|
||||
Text('书架')
|
||||
.fontSize(FbMetric.fontTitle)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
Text(this.all.length.toString() + ' 本 · '
|
||||
+ this.readingCount().toString() + ' 本在读 · '
|
||||
+ this.noteCount().toString() + ' 条笔记')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
|
||||
// 上传 / 统计(Web 端在设置菜单里)
|
||||
FbIconButton({ icon: $r('app.media.fb_ic_chart'), tint: FbColors.muted })
|
||||
FbIconButton({ icon: $r('app.media.fb_ic_gear'), tint: FbColors.muted })
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12, bottom: 8 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
// ---- 分组列表 ----
|
||||
if (this.all.length === 0) {
|
||||
FbEmptyBox({
|
||||
title: '书架还是空的',
|
||||
hint: '传一本进来,或先记下想读的书',
|
||||
icon: $r('app.media.fb_ic_books')
|
||||
})
|
||||
} else {
|
||||
List({ space: 0 }) {
|
||||
ForEach(this.groups(), (g: FbGroup<FbBookItem>) => {
|
||||
ListItemGroup({ header: this.groupHeader(g.title, g.count) }) {
|
||||
ForEach(g.items, (b: FbBookItem) => {
|
||||
ListItem() {
|
||||
this.bookRow(b)
|
||||
}
|
||||
}, (b: FbBookItem): string => 'bk_' + b.id.toString())
|
||||
}
|
||||
}, (g: FbGroup<FbBookItem>): string => 'bg_' + g.key)
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 24 })
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12, bottom: 8 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
// ---- 分组列表 ----
|
||||
if (this.all.length === 0) {
|
||||
FbEmptyBox({
|
||||
title: '书架还是空的',
|
||||
hint: '传一本进来,或先记下想读的书',
|
||||
icon: $r('app.media.fb_ic_books')
|
||||
})
|
||||
} else {
|
||||
List({ space: 0 }) {
|
||||
ForEach(this.groups(), (g: FbGroup<FbBookItem>) => {
|
||||
ListItemGroup({ header: this.groupHeader(g.title, g.count) }) {
|
||||
ForEach(g.items, (b: FbBookItem) => {
|
||||
ListItem() {
|
||||
this.bookRow(b)
|
||||
}
|
||||
}, (b: FbBookItem): string => 'bk_' + b.id.toString())
|
||||
}
|
||||
}, (g: FbGroup<FbBookItem>): string => 'bg_' + g.key)
|
||||
}
|
||||
.layoutWeight(1)
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 24 })
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor(FbColors.paper)
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
.align(Alignment.TopStart)
|
||||
}
|
||||
|
||||
/** 一本书:书脊色块(代替封面)+ 书名/作者 + 评分/笔记/进度 */
|
||||
|
||||
@@ -39,6 +39,12 @@ interface FbCalCell {
|
||||
|
||||
@ComponentV2
|
||||
export struct FbCalendarPage {
|
||||
|
||||
/**
|
||||
* 顶栏插槽:由 Index.ets 传入,放在本页滚动流最前面 ⇒ 随页面一起滚走。
|
||||
* 🔴 用户要求「向上滑动时标题栏跟着一起滑走」。
|
||||
*/
|
||||
@BuilderParam topBar: () => void;
|
||||
@Param app: FbAppState = fbAppState();
|
||||
|
||||
/** 模块主题色(日程 = 琥珀) */
|
||||
@@ -56,191 +62,196 @@ export struct FbCalendarPage {
|
||||
private weekLabels: string[] = ['一', '二', '三', '四', '五', '六', '日'];
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
// ---- 页头:月份 + 视图切换 + 新建 + 设置 ----
|
||||
Row({ space: 8 }) {
|
||||
Column({ space: 2 }) {
|
||||
Text(this.showYear.toString() + ' 年 ' + this.showMonth.toString() + ' 月')
|
||||
.fontSize(FbMetric.fontTitle)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
Text(this.visibleBooks().length.toString() + ' 本日历可见 · 本月 '
|
||||
+ this.monthEventCount().toString() + ' 条')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
Scroll() {
|
||||
Column() {
|
||||
this.topBar()
|
||||
|
||||
FbPrimaryButton({
|
||||
label: '新建',
|
||||
bg: this.accent,
|
||||
icon: $r('app.media.fb_ic_plus')
|
||||
})
|
||||
FbIconButton({ icon: $r('app.media.fb_ic_gear'), tint: FbColors.muted })
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12, bottom: 8 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
Scroll() {
|
||||
Column({ space: 0 }) {
|
||||
|
||||
// ---- 月历卡 ----
|
||||
Column({ space: 0 }) {
|
||||
// 月份切换条
|
||||
Row() {
|
||||
this.navBtn('上一月', -1)
|
||||
Blank()
|
||||
Text('今天')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(this.accent)
|
||||
.padding({ left: 12, right: 12, top: 5, bottom: 5 })
|
||||
.backgroundColor(fbPalette(this.accent).brand + '14')
|
||||
.borderRadius(FbMetric.radiusPill)
|
||||
.onClick(() => {
|
||||
const d: Date = new Date();
|
||||
this.showYear = d.getFullYear();
|
||||
this.showMonth = d.getMonth() + 1;
|
||||
this.pickedDay = d.getDate();
|
||||
})
|
||||
Blank()
|
||||
this.navBtn('下一月', 1)
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ bottom: 10 })
|
||||
|
||||
// 星期表头
|
||||
Row() {
|
||||
ForEach(this.weekLabels, (w: string) => {
|
||||
Text(w)
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
.layoutWeight(1)
|
||||
.textAlign(TextAlign.Center)
|
||||
}, (w: string): string => 'wk_' + w)
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ bottom: 6 })
|
||||
|
||||
// 日期网格(7 列)
|
||||
Grid() {
|
||||
ForEach(this.cells(), (c: FbCalCell) => {
|
||||
GridItem() {
|
||||
this.dayCell(c)
|
||||
}
|
||||
}, (c: FbCalCell): string => 'cell_' + c.day.toString() + '_'
|
||||
+ (c.inMonth ? '1' : '0'))
|
||||
}
|
||||
.columnsTemplate('1fr 1fr 1fr 1fr 1fr 1fr 1fr')
|
||||
.rowsGap(2)
|
||||
.width('100%')
|
||||
.height(this.gridHeight())
|
||||
.scrollBar(BarState.Off)
|
||||
// ---- 页头:月份 + 视图切换 + 新建 + 设置 ----
|
||||
Row({ space: 8 }) {
|
||||
Column({ space: 2 }) {
|
||||
Text(this.showYear.toString() + ' 年 ' + this.showMonth.toString() + ' 月')
|
||||
.fontSize(FbMetric.fontTitle)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
Text(this.visibleBooks().length.toString() + ' 本日历可见 · 本月 '
|
||||
+ this.monthEventCount().toString() + ' 条')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
}
|
||||
.width('100%')
|
||||
.padding(FbMetric.cardPad)
|
||||
.backgroundColor(FbColors.surface)
|
||||
.borderRadius(FbMetric.radius)
|
||||
.margin({ left: FbMetric.pagePad, right: FbMetric.pagePad })
|
||||
|
||||
// ---- 日历本(可见的几本,横向排列当作图例)----
|
||||
Column({ space: 10 }) {
|
||||
Row({ space: 8 }) {
|
||||
ForEach(this.visibleBooks(), (b: FbCalBook) => {
|
||||
Row({ space: 5 }) {
|
||||
FbDot({ dia: 7, fillC: b.color })
|
||||
Text(b.name)
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.muted)
|
||||
.maxLines(1)
|
||||
}
|
||||
.padding({ right: 10 })
|
||||
}, (b: FbCalBook): string => 'book_' + b.id.toString())
|
||||
}
|
||||
.width('100%')
|
||||
.alignItems(VerticalAlign.Center)
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad + 4, right: FbMetric.pagePad + 4, top: 12 })
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
|
||||
// ---- 选中日的日程 ----
|
||||
Column({ space: 0 }) {
|
||||
Row() {
|
||||
Text(this.pickedDay.toString() + ' 日')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
Blank()
|
||||
Text(this.todayEvents.length.toString() + ' 条日程')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: 4, right: 4, top: 12, bottom: 8 })
|
||||
|
||||
Column({ space: 0 }) {
|
||||
if (this.todayEvents.length === 0) {
|
||||
FbEmptyBox({
|
||||
title: '这天没有安排',
|
||||
hint: '在月历里点一下某天,就能在那天加日程',
|
||||
icon: $r('app.media.fb_ic_calendar')
|
||||
})
|
||||
} else {
|
||||
ForEach(this.todayEvents, (ev: FbCalEvent, idx: number) => {
|
||||
this.eventRow(ev, idx === this.todayEvents.length - 1)
|
||||
}, (ev: FbCalEvent): string => 'cev_' + ev.id.toString())
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.cardPad, right: FbMetric.cardPad, bottom: 6 })
|
||||
}
|
||||
.width('100%')
|
||||
.backgroundColor(FbColors.surface)
|
||||
.borderRadius(FbMetric.radius)
|
||||
.margin({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12 })
|
||||
|
||||
// ---- 未来七天 ----
|
||||
Column({ space: 0 }) {
|
||||
Row() {
|
||||
Text('未来七天')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
Blank()
|
||||
Text('共 ' + this.soonEvents.length.toString() + ' 条')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: 4, right: 4, top: 12, bottom: 8 })
|
||||
|
||||
Column({ space: 0 }) {
|
||||
ForEach(this.soonEvents, (ev: FbCalEvent, idx: number) => {
|
||||
this.eventRow(ev, idx === this.soonEvents.length - 1)
|
||||
}, (ev: FbCalEvent): string => 'sev_' + ev.id.toString())
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.cardPad, right: FbMetric.cardPad, bottom: 6 })
|
||||
}
|
||||
.width('100%')
|
||||
.backgroundColor(FbColors.surface)
|
||||
.borderRadius(FbMetric.radius)
|
||||
.margin({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12, bottom: 28 })
|
||||
FbPrimaryButton({
|
||||
label: '新建',
|
||||
bg: this.accent,
|
||||
icon: $r('app.media.fb_ic_plus')
|
||||
})
|
||||
FbIconButton({ icon: $r('app.media.fb_ic_gear'), tint: FbColors.muted })
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12, bottom: 8 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
Scroll() {
|
||||
Column({ space: 0 }) {
|
||||
|
||||
// ---- 月历卡 ----
|
||||
Column({ space: 0 }) {
|
||||
// 月份切换条
|
||||
Row() {
|
||||
this.navBtn('上一月', -1)
|
||||
Blank()
|
||||
Text('今天')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(this.accent)
|
||||
.padding({ left: 12, right: 12, top: 5, bottom: 5 })
|
||||
.backgroundColor(fbPalette(this.accent).brand + '14')
|
||||
.borderRadius(FbMetric.radiusPill)
|
||||
.onClick(() => {
|
||||
const d: Date = new Date();
|
||||
this.showYear = d.getFullYear();
|
||||
this.showMonth = d.getMonth() + 1;
|
||||
this.pickedDay = d.getDate();
|
||||
})
|
||||
Blank()
|
||||
this.navBtn('下一月', 1)
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ bottom: 10 })
|
||||
|
||||
// 星期表头
|
||||
Row() {
|
||||
ForEach(this.weekLabels, (w: string) => {
|
||||
Text(w)
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
.layoutWeight(1)
|
||||
.textAlign(TextAlign.Center)
|
||||
}, (w: string): string => 'wk_' + w)
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ bottom: 6 })
|
||||
|
||||
// 日期网格(7 列)
|
||||
Grid() {
|
||||
ForEach(this.cells(), (c: FbCalCell) => {
|
||||
GridItem() {
|
||||
this.dayCell(c)
|
||||
}
|
||||
}, (c: FbCalCell): string => 'cell_' + c.day.toString() + '_'
|
||||
+ (c.inMonth ? '1' : '0'))
|
||||
}
|
||||
.columnsTemplate('1fr 1fr 1fr 1fr 1fr 1fr 1fr')
|
||||
.rowsGap(2)
|
||||
.width('100%')
|
||||
.height(this.gridHeight())
|
||||
.scrollBar(BarState.Off)
|
||||
}
|
||||
.width('100%')
|
||||
.padding(FbMetric.cardPad)
|
||||
.backgroundColor(FbColors.surface)
|
||||
.borderRadius(FbMetric.radius)
|
||||
.margin({ left: FbMetric.pagePad, right: FbMetric.pagePad })
|
||||
|
||||
// ---- 日历本(可见的几本,横向排列当作图例)----
|
||||
Column({ space: 10 }) {
|
||||
Row({ space: 8 }) {
|
||||
ForEach(this.visibleBooks(), (b: FbCalBook) => {
|
||||
Row({ space: 5 }) {
|
||||
FbDot({ dia: 7, fillC: b.color })
|
||||
Text(b.name)
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.muted)
|
||||
.maxLines(1)
|
||||
}
|
||||
.padding({ right: 10 })
|
||||
}, (b: FbCalBook): string => 'book_' + b.id.toString())
|
||||
}
|
||||
.width('100%')
|
||||
.alignItems(VerticalAlign.Center)
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad + 4, right: FbMetric.pagePad + 4, top: 12 })
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
|
||||
// ---- 选中日的日程 ----
|
||||
Column({ space: 0 }) {
|
||||
Row() {
|
||||
Text(this.pickedDay.toString() + ' 日')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
Blank()
|
||||
Text(this.todayEvents.length.toString() + ' 条日程')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: 4, right: 4, top: 12, bottom: 8 })
|
||||
|
||||
Column({ space: 0 }) {
|
||||
if (this.todayEvents.length === 0) {
|
||||
FbEmptyBox({
|
||||
title: '这天没有安排',
|
||||
hint: '在月历里点一下某天,就能在那天加日程',
|
||||
icon: $r('app.media.fb_ic_calendar')
|
||||
})
|
||||
} else {
|
||||
ForEach(this.todayEvents, (ev: FbCalEvent, idx: number) => {
|
||||
this.eventRow(ev, idx === this.todayEvents.length - 1)
|
||||
}, (ev: FbCalEvent): string => 'cev_' + ev.id.toString())
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.cardPad, right: FbMetric.cardPad, bottom: 6 })
|
||||
}
|
||||
.width('100%')
|
||||
.backgroundColor(FbColors.surface)
|
||||
.borderRadius(FbMetric.radius)
|
||||
.margin({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12 })
|
||||
|
||||
// ---- 未来七天 ----
|
||||
Column({ space: 0 }) {
|
||||
Row() {
|
||||
Text('未来七天')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
Blank()
|
||||
Text('共 ' + this.soonEvents.length.toString() + ' 条')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: 4, right: 4, top: 12, bottom: 8 })
|
||||
|
||||
Column({ space: 0 }) {
|
||||
ForEach(this.soonEvents, (ev: FbCalEvent, idx: number) => {
|
||||
this.eventRow(ev, idx === this.soonEvents.length - 1)
|
||||
}, (ev: FbCalEvent): string => 'sev_' + ev.id.toString())
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.cardPad, right: FbMetric.cardPad, bottom: 6 })
|
||||
}
|
||||
.width('100%')
|
||||
.backgroundColor(FbColors.surface)
|
||||
.borderRadius(FbMetric.radius)
|
||||
.margin({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12, bottom: 28 })
|
||||
}
|
||||
.width('100%')
|
||||
}
|
||||
.width('100%')
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
.align(Alignment.TopStart)
|
||||
}
|
||||
.layoutWeight(1)
|
||||
.width('100%')
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
.align(Alignment.TopStart)
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor(FbColors.paper)
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
.align(Alignment.TopStart)
|
||||
}
|
||||
|
||||
/** 上一月 / 下一月按钮 */
|
||||
|
||||
@@ -18,6 +18,12 @@ import { FbAvatar, FbEmptyBox, FbIconButton, FbPrimaryButton, FbTag } from '../.
|
||||
|
||||
@ComponentV2
|
||||
export struct FbDaysPage {
|
||||
|
||||
/**
|
||||
* 顶栏插槽:由 Index.ets 传入,放在本页滚动流最前面 ⇒ 随页面一起滚走。
|
||||
* 🔴 用户要求「向上滑动时标题栏跟着一起滑走」。
|
||||
*/
|
||||
@BuilderParam topBar: () => void;
|
||||
@Param app: FbAppState = fbAppState();
|
||||
|
||||
/** 模块主题色(纪念日 = 陶土橙) */
|
||||
@@ -27,116 +33,121 @@ export struct FbDaysPage {
|
||||
@Local kindFilter: string = '';
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
// ---- 页头 ----
|
||||
Row({ space: 8 }) {
|
||||
Column({ space: 2 }) {
|
||||
Text('纪念日')
|
||||
.fontSize(FbMetric.fontTitle)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
Text('节日 / 纪念日 / 生日,都在这一条时间线上')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
Scroll() {
|
||||
Column() {
|
||||
this.topBar()
|
||||
|
||||
FbPrimaryButton({
|
||||
label: '添加',
|
||||
bg: this.accent,
|
||||
icon: $r('app.media.fb_ic_plus')
|
||||
})
|
||||
FbIconButton({ icon: $r('app.media.fb_ic_gear'), tint: FbColors.muted })
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12, bottom: 8 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
Scroll() {
|
||||
Column({ space: 0 }) {
|
||||
|
||||
// ---- 即将到来(30 天内)----
|
||||
Column({ space: 0 }) {
|
||||
Row({ space: 8 }) {
|
||||
Text('即将到来')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
Blank()
|
||||
Text('30 天内')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: 4, right: 4, bottom: 10 })
|
||||
|
||||
if (this.upcoming().length === 0) {
|
||||
Text('三十天内没有纪念日')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(FbColors.faint)
|
||||
} else {
|
||||
// 横向排列的「大数字」卡
|
||||
Scroll() {
|
||||
Row({ space: 10 }) {
|
||||
ForEach(this.upcoming(), (a: FbAnniversaryItem) => {
|
||||
this.upcomingCard(a)
|
||||
}, (a: FbAnniversaryItem): string => 'up_' + a.id.toString())
|
||||
}
|
||||
}
|
||||
.scrollable(ScrollDirection.Horizontal)
|
||||
.scrollBar(BarState.Off)
|
||||
.width('100%')
|
||||
}
|
||||
// ---- 页头 ----
|
||||
Row({ space: 8 }) {
|
||||
Column({ space: 2 }) {
|
||||
Text('纪念日')
|
||||
.fontSize(FbMetric.fontTitle)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
Text('节日 / 纪念日 / 生日,都在这一条时间线上')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
}
|
||||
.width('100%')
|
||||
.padding(FbMetric.cardPad)
|
||||
.backgroundColor(FbColors.surface)
|
||||
.borderRadius(FbMetric.radius)
|
||||
.margin({ left: FbMetric.pagePad, right: FbMetric.pagePad })
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
|
||||
// ---- 全部纪念日 ----
|
||||
Row({ space: 8 }) {
|
||||
this.kindChip('全部', '')
|
||||
this.kindChip('生日', 'birthday')
|
||||
this.kindChip('纪念日', 'anniversary')
|
||||
this.kindChip('节日', 'holiday')
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 14, bottom: 8 })
|
||||
|
||||
if (this.filtered().length === 0) {
|
||||
FbEmptyBox({
|
||||
title: '这里还没有纪念日',
|
||||
hint: '名册里填上生日,它就会自动出现在这儿',
|
||||
icon: $r('app.media.fb_ic_days')
|
||||
})
|
||||
} else {
|
||||
Column({ space: 0 }) {
|
||||
ForEach(this.filtered(), (a: FbAnniversaryItem, idx: number) => {
|
||||
this.annRow(a, idx === this.filtered().length - 1)
|
||||
}, (a: FbAnniversaryItem): string => 'ann_' + a.id.toString())
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.cardPad, right: FbMetric.cardPad, top: 4, bottom: 8 })
|
||||
.backgroundColor(FbColors.surface)
|
||||
.borderRadius(FbMetric.radius)
|
||||
.margin({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 28 })
|
||||
}
|
||||
FbPrimaryButton({
|
||||
label: '添加',
|
||||
bg: this.accent,
|
||||
icon: $r('app.media.fb_ic_plus')
|
||||
})
|
||||
FbIconButton({ icon: $r('app.media.fb_ic_gear'), tint: FbColors.muted })
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12, bottom: 8 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
Scroll() {
|
||||
Column({ space: 0 }) {
|
||||
|
||||
// ---- 即将到来(30 天内)----
|
||||
Column({ space: 0 }) {
|
||||
Row({ space: 8 }) {
|
||||
Text('即将到来')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
Blank()
|
||||
Text('30 天内')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: 4, right: 4, bottom: 10 })
|
||||
|
||||
if (this.upcoming().length === 0) {
|
||||
Text('三十天内没有纪念日')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(FbColors.faint)
|
||||
} else {
|
||||
// 横向排列的「大数字」卡
|
||||
Scroll() {
|
||||
Row({ space: 10 }) {
|
||||
ForEach(this.upcoming(), (a: FbAnniversaryItem) => {
|
||||
this.upcomingCard(a)
|
||||
}, (a: FbAnniversaryItem): string => 'up_' + a.id.toString())
|
||||
}
|
||||
}
|
||||
.scrollable(ScrollDirection.Horizontal)
|
||||
.scrollBar(BarState.Off)
|
||||
.width('100%')
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
.padding(FbMetric.cardPad)
|
||||
.backgroundColor(FbColors.surface)
|
||||
.borderRadius(FbMetric.radius)
|
||||
.margin({ left: FbMetric.pagePad, right: FbMetric.pagePad })
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
|
||||
// ---- 全部纪念日 ----
|
||||
Row({ space: 8 }) {
|
||||
this.kindChip('全部', '')
|
||||
this.kindChip('生日', 'birthday')
|
||||
this.kindChip('纪念日', 'anniversary')
|
||||
this.kindChip('节日', 'holiday')
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 14, bottom: 8 })
|
||||
|
||||
if (this.filtered().length === 0) {
|
||||
FbEmptyBox({
|
||||
title: '这里还没有纪念日',
|
||||
hint: '名册里填上生日,它就会自动出现在这儿',
|
||||
icon: $r('app.media.fb_ic_days')
|
||||
})
|
||||
} else {
|
||||
Column({ space: 0 }) {
|
||||
ForEach(this.filtered(), (a: FbAnniversaryItem, idx: number) => {
|
||||
this.annRow(a, idx === this.filtered().length - 1)
|
||||
}, (a: FbAnniversaryItem): string => 'ann_' + a.id.toString())
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.cardPad, right: FbMetric.cardPad, top: 4, bottom: 8 })
|
||||
.backgroundColor(FbColors.surface)
|
||||
.borderRadius(FbMetric.radius)
|
||||
.margin({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 28 })
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
}
|
||||
.width('100%')
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
.align(Alignment.TopStart)
|
||||
}
|
||||
.layoutWeight(1)
|
||||
.width('100%')
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
.align(Alignment.TopStart)
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor(FbColors.paper)
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
.align(Alignment.TopStart)
|
||||
}
|
||||
|
||||
/** 即将到来的大数字卡 */
|
||||
|
||||
@@ -22,9 +22,13 @@ import { FbAvatar, FbDot, FbDotLine, FbSectionTitle, FbStatTriple, FbTag } from
|
||||
|
||||
@ComponentV2
|
||||
export struct FbHomePage {
|
||||
|
||||
/**
|
||||
* 顶栏插槽:由 Index.ets 传入,放在本页滚动流最前面 ⇒ 随页面一起滚走。
|
||||
* 🔴 用户要求「向上滑动时标题栏跟着一起滑走」。
|
||||
*/
|
||||
@BuilderParam topBar: () => void;
|
||||
@Param app: FbAppState = fbAppState();
|
||||
/** 顶栏右侧插槽(由外层传入,本页不用,留空占位以保持签名统一) */
|
||||
@BuilderParam topActions: () => void;
|
||||
|
||||
private cards: FbModuleCard[] = FB_DEMO_MODULE_CARDS;
|
||||
private todayEvents: FbCalEvent[] = FB_DEMO_EVENTS_TODAY;
|
||||
@@ -72,6 +76,8 @@ export struct FbHomePage {
|
||||
build() {
|
||||
Scroll() {
|
||||
Column({ space: 0 }) {
|
||||
this.topBar()
|
||||
|
||||
|
||||
// ==================== ① 个人信息卡(问候 + 今天 + 天气)====================
|
||||
// 🔴 用户 2026-10-12 定的:这一块**要白色卡片**("这样显得好看")。
|
||||
|
||||
@@ -24,6 +24,12 @@ import { FbEmptyBox, FbIconButton, FbPrimaryButton, FbTag } from '../../componen
|
||||
|
||||
@ComponentV2
|
||||
export struct FbLedgerPage {
|
||||
|
||||
/**
|
||||
* 顶栏插槽:由 Index.ets 传入,放在本页滚动流最前面 ⇒ 随页面一起滚走。
|
||||
* 🔴 用户要求「向上滑动时标题栏跟着一起滑走」。
|
||||
*/
|
||||
@BuilderParam topBar: () => void;
|
||||
@Param app: FbAppState = fbAppState();
|
||||
|
||||
/** 模块主题色(财务 = 橄榄绿) */
|
||||
@@ -34,207 +40,210 @@ export struct FbLedgerPage {
|
||||
@Local bookId: number = 0;
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
// ---- 页头 ----
|
||||
Row({ space: 8 }) {
|
||||
Column({ space: 2 }) {
|
||||
Text(this.currentBook().name)
|
||||
.fontSize(FbMetric.fontTitle)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
Text(this.books.length.toString() + ' 本账 · '
|
||||
+ this.currentBook().entryCount.toString() + ' 条账目')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
Scroll() {
|
||||
Column() {
|
||||
this.topBar()
|
||||
|
||||
FbPrimaryButton({
|
||||
label: '记一笔',
|
||||
bg: this.accent,
|
||||
icon: $r('app.media.fb_ic_plus')
|
||||
})
|
||||
FbIconButton({ icon: $r('app.media.fb_ic_gear'), tint: FbColors.muted })
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12, bottom: 8 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
// ---- 账本切换(横向)----
|
||||
Scroll() {
|
||||
// ---- 页头 ----
|
||||
Row({ space: 8 }) {
|
||||
ForEach(this.books, (b: FbLedgerBook) => {
|
||||
Row({ space: 6 }) {
|
||||
Text(b.name)
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(this.isCurrent(b.id) ? Color.White : FbColors.muted)
|
||||
.maxLines(1)
|
||||
if (b.myPerm !== 'owner') {
|
||||
FbTag({
|
||||
text: b.myPerm === 'write' ? '可写' : '只读',
|
||||
tone: this.isCurrent(b.id) ? 'brand' : 'muted'
|
||||
})
|
||||
}
|
||||
}
|
||||
.padding({ left: 12, right: 12, top: 7, bottom: 7 })
|
||||
.backgroundColor(this.isCurrent(b.id) ? this.accent : FbColors.surface)
|
||||
.borderRadius(FbMetric.radiusPill)
|
||||
.alignItems(VerticalAlign.Center)
|
||||
.onClick(() => {
|
||||
this.bookId = b.id;
|
||||
})
|
||||
}, (b: FbLedgerBook): string => 'bk_' + b.id.toString())
|
||||
}
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad })
|
||||
}
|
||||
.scrollable(ScrollDirection.Horizontal)
|
||||
.scrollBar(BarState.Off)
|
||||
.width('100%')
|
||||
.padding({ bottom: 10 })
|
||||
|
||||
Scroll() {
|
||||
Column({ space: 0 }) {
|
||||
|
||||
// ---- 概览卡 ----
|
||||
Column({ space: 12 }) {
|
||||
Column({ space: 4 }) {
|
||||
Text('净资产')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.muted)
|
||||
Text(fbMoney(this.netCent()))
|
||||
.fontSize(30)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.width('100%')
|
||||
|
||||
// 资产 / 负债分列
|
||||
Row({ space: 0 }) {
|
||||
Column({ space: 3 }) {
|
||||
Text('资产')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
Text(fbMoney(this.assetCent()))
|
||||
.fontSize(FbMetric.fontBody)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.green)
|
||||
}
|
||||
.layoutWeight(1)
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
|
||||
Column({ space: 3 }) {
|
||||
Text('负债')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
Text(fbMoney(this.debtCent()))
|
||||
.fontSize(FbMetric.fontBody)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.red)
|
||||
}
|
||||
.layoutWeight(1)
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
}
|
||||
.width('100%')
|
||||
|
||||
// 折线图占位(第二轮接图表能力后填)
|
||||
Column({ space: 4 }) {
|
||||
Row({ space: 6 }) {
|
||||
Image($r('app.media.fb_ic_chart'))
|
||||
.width(13)
|
||||
.height(13)
|
||||
.renderMode(ImageRenderMode.Template)
|
||||
.fillColor(FbColors.faint)
|
||||
Text('净资产变化(盘点成线)')
|
||||
.fontSize(10)
|
||||
.fontColor(FbColors.faint)
|
||||
}
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
Column()
|
||||
.width('100%')
|
||||
.height(56)
|
||||
.borderRadius(FbMetric.radiusS)
|
||||
.backgroundColor(FbColors.inputBg)
|
||||
}
|
||||
.width('100%')
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
}
|
||||
.width('100%')
|
||||
.padding(FbMetric.cardPad)
|
||||
.backgroundColor(FbColors.surface)
|
||||
.borderRadius(FbMetric.radius)
|
||||
.margin({ left: FbMetric.pagePad, right: FbMetric.pagePad })
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
|
||||
// ---- 账目(按类别分组)----
|
||||
Row({ space: 8 }) {
|
||||
Text('账目')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
Column({ space: 2 }) {
|
||||
Text(this.currentBook().name)
|
||||
.fontSize(FbMetric.fontTitle)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
Blank()
|
||||
Text(this.entries.length.toString() + ' 条 · 按类别')
|
||||
Text(this.books.length.toString() + ' 本账 · '
|
||||
+ this.currentBook().entryCount.toString() + ' 条账目')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad + 4, right: FbMetric.pagePad + 4, top: 14, bottom: 8 })
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
|
||||
if (this.entries.length === 0) {
|
||||
FbEmptyBox({
|
||||
title: '这本账还没有账目',
|
||||
hint: '存款、基金、保险、房子、车子都可以记进来',
|
||||
icon: $r('app.media.fb_ic_ledger')
|
||||
})
|
||||
} else {
|
||||
ForEach(this.groups(), (g: FbGroup<FbLedgerEntry>) => {
|
||||
Column({ space: 0 }) {
|
||||
// 类别小标题 + 该类别小计
|
||||
Row({ space: 8 }) {
|
||||
Text(g.title)
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
Text(fbMoney(this.sumOf(g.items)))
|
||||
.fontSize(10)
|
||||
.fontColor(FbColors.faint)
|
||||
Blank()
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ top: 10, bottom: 6 })
|
||||
|
||||
Column({ space: 0 }) {
|
||||
ForEach(g.items, (e: FbLedgerEntry, idx: number) => {
|
||||
this.entryRow(e, idx === g.items.length - 1)
|
||||
}, (e: FbLedgerEntry): string => 'le_' + e.id.toString())
|
||||
}
|
||||
.width('100%')
|
||||
}
|
||||
.width('100%')
|
||||
.padding(FbMetric.cardPad)
|
||||
.backgroundColor(FbColors.surface)
|
||||
.borderRadius(FbMetric.radius)
|
||||
.margin({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 12 })
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
}, (g: FbGroup<FbLedgerEntry>): string => 'lg_' + g.key)
|
||||
|
||||
Blank().height(16)
|
||||
}
|
||||
FbPrimaryButton({
|
||||
label: '记一笔',
|
||||
bg: this.accent,
|
||||
icon: $r('app.media.fb_ic_plus')
|
||||
})
|
||||
FbIconButton({ icon: $r('app.media.fb_ic_gear'), tint: FbColors.muted })
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12, bottom: 8 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
// ---- 账本切换(横向)----
|
||||
Scroll() {
|
||||
Row({ space: 8 }) {
|
||||
ForEach(this.books, (b: FbLedgerBook) => {
|
||||
Row({ space: 6 }) {
|
||||
Text(b.name)
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(this.isCurrent(b.id) ? Color.White : FbColors.muted)
|
||||
.maxLines(1)
|
||||
if (b.myPerm !== 'owner') {
|
||||
FbTag({
|
||||
text: b.myPerm === 'write' ? '可写' : '只读',
|
||||
tone: this.isCurrent(b.id) ? 'brand' : 'muted'
|
||||
})
|
||||
}
|
||||
}
|
||||
.padding({ left: 12, right: 12, top: 7, bottom: 7 })
|
||||
.backgroundColor(this.isCurrent(b.id) ? this.accent : FbColors.surface)
|
||||
.borderRadius(FbMetric.radiusPill)
|
||||
.alignItems(VerticalAlign.Center)
|
||||
.onClick(() => {
|
||||
this.bookId = b.id;
|
||||
})
|
||||
}, (b: FbLedgerBook): string => 'bk_' + b.id.toString())
|
||||
}
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad })
|
||||
}
|
||||
.scrollable(ScrollDirection.Horizontal)
|
||||
.scrollBar(BarState.Off)
|
||||
.width('100%')
|
||||
.padding({ bottom: 10 })
|
||||
|
||||
Scroll() {
|
||||
Column({ space: 0 }) {
|
||||
|
||||
// ---- 概览卡 ----
|
||||
Column({ space: 12 }) {
|
||||
Column({ space: 4 }) {
|
||||
Text('净资产')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.muted)
|
||||
Text(fbMoney(this.netCent()))
|
||||
.fontSize(30)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.width('100%')
|
||||
|
||||
// 资产 / 负债分列
|
||||
Row({ space: 0 }) {
|
||||
Column({ space: 3 }) {
|
||||
Text('资产')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
Text(fbMoney(this.assetCent()))
|
||||
.fontSize(FbMetric.fontBody)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.green)
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
|
||||
Column({ space: 3 }) {
|
||||
Text('负债')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
Text(fbMoney(this.debtCent()))
|
||||
.fontSize(FbMetric.fontBody)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.red)
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
}
|
||||
.width('100%')
|
||||
|
||||
// 折线图占位(第二轮接图表能力后填)
|
||||
Column({ space: 4 }) {
|
||||
Row({ space: 6 }) {
|
||||
Image($r('app.media.fb_ic_chart'))
|
||||
.width(13)
|
||||
.height(13)
|
||||
.renderMode(ImageRenderMode.Template)
|
||||
.fillColor(FbColors.faint)
|
||||
Text('净资产变化(盘点成线)')
|
||||
.fontSize(10)
|
||||
.fontColor(FbColors.faint)
|
||||
}
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
Column()
|
||||
.width('100%')
|
||||
.height(56)
|
||||
.borderRadius(FbMetric.radiusS)
|
||||
.backgroundColor(FbColors.inputBg)
|
||||
}
|
||||
.width('100%')
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
}
|
||||
.width('100%')
|
||||
.padding(FbMetric.cardPad)
|
||||
.backgroundColor(FbColors.surface)
|
||||
.borderRadius(FbMetric.radius)
|
||||
.margin({ left: FbMetric.pagePad, right: FbMetric.pagePad })
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
|
||||
// ---- 账目(按类别分组)----
|
||||
Row({ space: 8 }) {
|
||||
Text('账目')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
Blank()
|
||||
Text(this.entries.length.toString() + ' 条 · 按类别')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad + 4, right: FbMetric.pagePad + 4, top: 14, bottom: 8 })
|
||||
|
||||
if (this.entries.length === 0) {
|
||||
FbEmptyBox({
|
||||
title: '这本账还没有账目',
|
||||
hint: '存款、基金、保险、房子、车子都可以记进来',
|
||||
icon: $r('app.media.fb_ic_ledger')
|
||||
})
|
||||
} else {
|
||||
ForEach(this.groups(), (g: FbGroup<FbLedgerEntry>) => {
|
||||
Column({ space: 0 }) {
|
||||
// 类别小标题 + 该类别小计
|
||||
Row({ space: 8 }) {
|
||||
Text(g.title)
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
Text(fbMoney(this.sumOf(g.items)))
|
||||
.fontSize(10)
|
||||
.fontColor(FbColors.faint)
|
||||
Blank()
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ top: 10, bottom: 6 })
|
||||
|
||||
Column({ space: 0 }) {
|
||||
ForEach(g.items, (e: FbLedgerEntry, idx: number) => {
|
||||
this.entryRow(e, idx === g.items.length - 1)
|
||||
}, (e: FbLedgerEntry): string => 'le_' + e.id.toString())
|
||||
}
|
||||
.width('100%')
|
||||
}
|
||||
.width('100%')
|
||||
.padding(FbMetric.cardPad)
|
||||
.backgroundColor(FbColors.surface)
|
||||
.borderRadius(FbMetric.radius)
|
||||
.margin({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 12 })
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
}, (g: FbGroup<FbLedgerEntry>): string => 'lg_' + g.key)
|
||||
|
||||
Blank().height(16)
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
}
|
||||
.width('100%')
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
.align(Alignment.TopStart)
|
||||
}
|
||||
.layoutWeight(1)
|
||||
.width('100%')
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
.align(Alignment.TopStart)
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor(FbColors.paper)
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
.align(Alignment.TopStart)
|
||||
}
|
||||
|
||||
/** 一条账目:名字 + 归属人 + 金额 + 盘点入口 */
|
||||
|
||||
@@ -23,6 +23,12 @@ import { FbAvatar, FbDot, FbEmptyBox, FbPrimaryButton, FbTag } from '../../compo
|
||||
|
||||
@ComponentV2
|
||||
export struct FbNotesPage {
|
||||
|
||||
/**
|
||||
* 顶栏插槽:由 Index.ets 传入,放在本页滚动流最前面 ⇒ 随页面一起滚走。
|
||||
* 🔴 用户要求「向上滑动时标题栏跟着一起滑走」。
|
||||
*/
|
||||
@BuilderParam topBar: () => void;
|
||||
@Param app: FbAppState = fbAppState();
|
||||
|
||||
/** 模块主题色(笔记 = 褐灰) */
|
||||
@@ -30,61 +36,67 @@ export struct FbNotesPage {
|
||||
private all: FbNoteItem[] = FB_DEMO_NOTES;
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
Row({ space: 8 }) {
|
||||
Column({ space: 2 }) {
|
||||
Text('笔记')
|
||||
.fontSize(FbMetric.fontTitle)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
Text(this.all.length.toString() + ' 条 · 分块 + 标签')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
Scroll() {
|
||||
Column() {
|
||||
this.topBar()
|
||||
|
||||
FbPrimaryButton({
|
||||
label: '记一条',
|
||||
bg: this.accent,
|
||||
icon: $r('app.media.fb_ic_plus')
|
||||
})
|
||||
Row({ space: 8 }) {
|
||||
Column({ space: 2 }) {
|
||||
Text('笔记')
|
||||
.fontSize(FbMetric.fontTitle)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
Text(this.all.length.toString() + ' 条 · 分块 + 标签')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
|
||||
FbPrimaryButton({
|
||||
label: '记一条',
|
||||
bg: this.accent,
|
||||
icon: $r('app.media.fb_ic_plus')
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12, bottom: 10 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
if (this.all.length === 0) {
|
||||
FbEmptyBox({
|
||||
title: '还没有笔记',
|
||||
hint: '随手记点什么,按标签归类和检索',
|
||||
icon: $r('app.media.fb_ic_notes')
|
||||
})
|
||||
} else {
|
||||
// ⚠️ 用**两列定尺寸网格**而不是一列长卡:Web 端用户明确要求过
|
||||
// 「笔记列表要么按列表展示,要么按块展示,一行几个块,但块大小要确定,
|
||||
// 显示不下就显示简要信息」—— 一列长卡一条占大半屏,很难扫。
|
||||
Grid() {
|
||||
ForEach(this.all, (n: FbNoteItem) => {
|
||||
GridItem() {
|
||||
this.noteCard(n)
|
||||
}
|
||||
}, (n: FbNoteItem): string => 'nt_' + n.id.toString())
|
||||
}
|
||||
.columnsTemplate('1fr 1fr')
|
||||
.columnsGap(10)
|
||||
.rowsGap(10)
|
||||
.layoutWeight(1)
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 24 })
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12, bottom: 10 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
if (this.all.length === 0) {
|
||||
FbEmptyBox({
|
||||
title: '还没有笔记',
|
||||
hint: '随手记点什么,按标签归类和检索',
|
||||
icon: $r('app.media.fb_ic_notes')
|
||||
})
|
||||
} else {
|
||||
// ⚠️ 用**两列定尺寸网格**而不是一列长卡:Web 端用户明确要求过
|
||||
// 「笔记列表要么按列表展示,要么按块展示,一行几个块,但块大小要确定,
|
||||
// 显示不下就显示简要信息」—— 一列长卡一条占大半屏,很难扫。
|
||||
Grid() {
|
||||
ForEach(this.all, (n: FbNoteItem) => {
|
||||
GridItem() {
|
||||
this.noteCard(n)
|
||||
}
|
||||
}, (n: FbNoteItem): string => 'nt_' + n.id.toString())
|
||||
}
|
||||
.columnsTemplate('1fr 1fr')
|
||||
.columnsGap(10)
|
||||
.rowsGap(10)
|
||||
.layoutWeight(1)
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 24 })
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor(FbColors.paper)
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
.align(Alignment.TopStart)
|
||||
}
|
||||
|
||||
@Builder
|
||||
@@ -148,66 +160,77 @@ export struct FbNotesPage {
|
||||
|
||||
@ComponentV2
|
||||
export struct FbNotificationsPage {
|
||||
|
||||
/**
|
||||
* 顶栏插槽:由 Index.ets 传入,放在本页滚动流最前面 ⇒ 随页面一起滚走。
|
||||
* 🔴 用户要求「向上滑动时标题栏跟着一起滑走」。
|
||||
*/
|
||||
@BuilderParam topBar: () => void;
|
||||
@Param app: FbAppState = fbAppState();
|
||||
|
||||
private all: FbNotificationItem[] = FB_DEMO_NOTIFICATIONS;
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
Row({ space: 8 }) {
|
||||
Column({ space: 2 }) {
|
||||
Text('通知消息')
|
||||
.fontSize(FbMetric.fontTitle)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
Text(this.unreadCount().toString() + ' 条未读 · 共 '
|
||||
+ this.all.length.toString() + ' 条')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(this.unreadCount() > 0 ? FbColors.red : FbColors.faint)
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
Scroll() {
|
||||
Column() {
|
||||
this.topBar()
|
||||
|
||||
Text('全部已读')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(FbColors.brand)
|
||||
.padding({ left: 12, right: 12, top: 7, bottom: 7 })
|
||||
.backgroundColor(FbColors.brand + '14')
|
||||
.borderRadius(FbMetric.radiusPill)
|
||||
.onClick(() => {
|
||||
// 本轮只做界面:把本地未读标记清掉即可
|
||||
this.app.unread = 0;
|
||||
Row({ space: 8 }) {
|
||||
Column({ space: 2 }) {
|
||||
Text('通知消息')
|
||||
.fontSize(FbMetric.fontTitle)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
Text(this.unreadCount().toString() + ' 条未读 · 共 '
|
||||
+ this.all.length.toString() + ' 条')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(this.unreadCount() > 0 ? FbColors.red : FbColors.faint)
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
|
||||
Text('全部已读')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(FbColors.brand)
|
||||
.padding({ left: 12, right: 12, top: 7, bottom: 7 })
|
||||
.backgroundColor(FbColors.brand + '14')
|
||||
.borderRadius(FbMetric.radiusPill)
|
||||
.onClick(() => {
|
||||
// 本轮只做界面:把本地未读标记清掉即可
|
||||
this.app.unread = 0;
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12, bottom: 10 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
if (this.all.length === 0) {
|
||||
FbEmptyBox({
|
||||
title: '没有新消息',
|
||||
hint: '别人共享给你的资源、日程与任务都会出现在这儿',
|
||||
icon: $r('app.media.fb_ic_bell')
|
||||
})
|
||||
} else {
|
||||
List({ space: 10 }) {
|
||||
ForEach(this.all, (n: FbNotificationItem) => {
|
||||
ListItem() {
|
||||
this.notifCard(n)
|
||||
}
|
||||
}, (n: FbNotificationItem): string => 'nf_' + n.id.toString())
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 24 })
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12, bottom: 10 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
if (this.all.length === 0) {
|
||||
FbEmptyBox({
|
||||
title: '没有新消息',
|
||||
hint: '别人共享给你的资源、日程与任务都会出现在这儿',
|
||||
icon: $r('app.media.fb_ic_bell')
|
||||
})
|
||||
} else {
|
||||
List({ space: 10 }) {
|
||||
ForEach(this.all, (n: FbNotificationItem) => {
|
||||
ListItem() {
|
||||
this.notifCard(n)
|
||||
}
|
||||
}, (n: FbNotificationItem): string => 'nf_' + n.id.toString())
|
||||
}
|
||||
.layoutWeight(1)
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 24 })
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor(FbColors.paper)
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
.align(Alignment.TopStart)
|
||||
}
|
||||
|
||||
@Builder
|
||||
|
||||
@@ -18,6 +18,11 @@ import { FbDot, FbEmptyBox, FbIconButton, FbPersonChip, FbPrimaryButton, FbRatin
|
||||
@ComponentV2
|
||||
export struct FbMoviesPage {
|
||||
@Param app: FbAppState = fbAppState();
|
||||
/**
|
||||
* 顶栏插槽:由 Index.ets 传入,放在本页滚动流最前面 ⇒ 随页面一起滚走。
|
||||
* 🔴 用户要求「向上滑动时标题栏跟着一起滑走」。
|
||||
*/
|
||||
@BuilderParam topBar: () => void;
|
||||
|
||||
private movies: FbMovieItem[] = FB_DEMO_MOVIES;
|
||||
private cinemas: string[] = FB_DEMO_CINEMAS;
|
||||
@@ -25,97 +30,106 @@ export struct FbMoviesPage {
|
||||
private accent: string = '#7A4E8C';
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
// ---- 页头:标题 + 记一场电影 + 设置 ----
|
||||
Row({ space: 8 }) {
|
||||
Column({ space: 2 }) {
|
||||
Text('观影记录')
|
||||
.fontSize(FbMetric.fontTitle)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
Text('共 ' + this.movies.length.toString() + ' 部 · 按观影时间排序')
|
||||
Scroll() {
|
||||
Column() {
|
||||
this.topBar()
|
||||
|
||||
// ---- 页头:标题 + 记一场电影 + 设置 ----
|
||||
Row({ space: 8 }) {
|
||||
Column({ space: 2 }) {
|
||||
Text('观影记录')
|
||||
.fontSize(FbMetric.fontTitle)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
Text('共 ' + this.movies.length.toString() + ' 部 · 按观影时间排序')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
|
||||
FbPrimaryButton({
|
||||
label: '记一场',
|
||||
bg: this.accent,
|
||||
icon: $r('app.media.fb_ic_plus')
|
||||
})
|
||||
// 「影院」不单独占抽屉位置 —— 它是观影模块的一部分,从这里进
|
||||
FbIconButton({
|
||||
icon: $r('app.media.fb_ic_gear'),
|
||||
tint: FbColors.muted,
|
||||
dia: 36
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12, bottom: 8 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
// ---- 搜索条 ----
|
||||
Row({ space: 6 }) {
|
||||
Image($r('app.media.fb_ic_search'))
|
||||
.width(15)
|
||||
.height(15)
|
||||
.renderMode(ImageRenderMode.Template)
|
||||
.fillColor(FbColors.faint)
|
||||
Text('搜索片名、影院、观影人…')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(FbColors.faint)
|
||||
.layoutWeight(1)
|
||||
.maxLines(1)
|
||||
}
|
||||
.width('100%')
|
||||
.height(36)
|
||||
.padding({ left: 12, right: 12 })
|
||||
.backgroundColor(FbColors.inputBg)
|
||||
.borderRadius(FbMetric.radiusS)
|
||||
.margin({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 4 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
// ---- 影院数提示 ----
|
||||
Row({ space: 6 }) {
|
||||
Blank()
|
||||
Text('影院 ' + this.cinemas.length.toString() + ' 家')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
|
||||
FbPrimaryButton({
|
||||
label: '记一场',
|
||||
bg: this.accent,
|
||||
icon: $r('app.media.fb_ic_plus')
|
||||
})
|
||||
// 「影院」不单独占抽屉位置 —— 它是观影模块的一部分,从这里进
|
||||
FbIconButton({
|
||||
icon: $r('app.media.fb_ic_gear'),
|
||||
tint: FbColors.muted,
|
||||
dia: 36
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12, bottom: 8 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
// ---- 搜索条 ----
|
||||
Row({ space: 6 }) {
|
||||
Image($r('app.media.fb_ic_search'))
|
||||
.width(15)
|
||||
.height(15)
|
||||
.renderMode(ImageRenderMode.Template)
|
||||
.fillColor(FbColors.faint)
|
||||
Text('搜索片名、影院、观影人…')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(FbColors.faint)
|
||||
.layoutWeight(1)
|
||||
.maxLines(1)
|
||||
}
|
||||
.width('100%')
|
||||
.height(36)
|
||||
.padding({ left: 12, right: 12 })
|
||||
.backgroundColor(FbColors.inputBg)
|
||||
.borderRadius(FbMetric.radiusS)
|
||||
.margin({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 4 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
// ---- 影院数提示 ----
|
||||
Row({ space: 6 }) {
|
||||
Blank()
|
||||
Text('影院 ' + this.cinemas.length.toString() + ' 家')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad + 4, right: FbMetric.pagePad + 4, top: 6, bottom: 6 })
|
||||
|
||||
// ---- 海报网格 ----
|
||||
if (this.movies.length === 0) {
|
||||
FbEmptyBox({
|
||||
title: '还没有观影记录',
|
||||
hint: '一家人一起看过的电影,都会以海报墙的方式排在这里',
|
||||
icon: $r('app.media.fb_ic_movies')
|
||||
})
|
||||
} else {
|
||||
Grid() {
|
||||
ForEach(this.movies, (mv: FbMovieItem) => {
|
||||
GridItem() {
|
||||
this.movieCard(mv)
|
||||
}
|
||||
}, (mv: FbMovieItem): string => 'mv_' + mv.id.toString())
|
||||
}
|
||||
.columnsTemplate('1fr 1fr')
|
||||
.columnsGap(12)
|
||||
.rowsGap(12)
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 24 })
|
||||
.layoutWeight(1)
|
||||
.width('100%')
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
.padding({ left: FbMetric.pagePad + 4, right: FbMetric.pagePad + 4, top: 6, bottom: 6 })
|
||||
|
||||
// ---- 海报网格 ----
|
||||
if (this.movies.length === 0) {
|
||||
FbEmptyBox({
|
||||
title: '还没有观影记录',
|
||||
hint: '一家人一起看过的电影,都会以海报墙的方式排在这里',
|
||||
icon: $r('app.media.fb_ic_movies')
|
||||
})
|
||||
} else {
|
||||
Grid() {
|
||||
ForEach(this.movies, (mv: FbMovieItem) => {
|
||||
GridItem() {
|
||||
this.movieCard(mv)
|
||||
}
|
||||
}, (mv: FbMovieItem): string => 'mv_' + mv.id.toString())
|
||||
}
|
||||
.columnsTemplate('1fr 1fr')
|
||||
.columnsGap(12)
|
||||
.rowsGap(12)
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 24 })
|
||||
// ⚠️ 不能给 layoutWeight:本页已整体放进 Scroll,
|
||||
// Column 的高度由内容决定,layoutWeight(1) 会去分"剩余空间"(此时为 0)
|
||||
// ⇒ Grid 高度塌成 0、整页只有顶栏和页头。
|
||||
.width('100%')
|
||||
.width('100%')
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor(FbColors.paper)
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
.align(Alignment.TopStart)
|
||||
}
|
||||
|
||||
/** 一张电影卡:海报位(2:3)+ 片名 + 元信息 + 同看的人 */
|
||||
|
||||
@@ -30,6 +30,12 @@ interface FbKinRing {
|
||||
|
||||
@ComponentV2
|
||||
export struct FbPeoplePage {
|
||||
|
||||
/**
|
||||
* 顶栏插槽:由 Index.ets 传入,放在本页滚动流最前面 ⇒ 随页面一起滚走。
|
||||
* 🔴 用户要求「向上滑动时标题栏跟着一起滑走」。
|
||||
*/
|
||||
@BuilderParam topBar: () => void;
|
||||
@Param app: FbAppState = fbAppState();
|
||||
|
||||
/** 子视图:名册(roster)/ 家谱(kin) */
|
||||
@@ -43,27 +49,33 @@ export struct FbPeoplePage {
|
||||
private rels: FbKinRelation[] = FB_DEMO_KIN_RELATIONS;
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
// ---- 分段切换(名册 / 家谱)----
|
||||
Row({ space: 4 }) {
|
||||
this.segBtn('名册', 'roster', '#A84A28')
|
||||
this.segBtn('家谱', 'kin', '#A84A28')
|
||||
}
|
||||
.padding(3)
|
||||
.backgroundColor(FbColors.inputBg)
|
||||
.borderRadius(FbMetric.radiusPill)
|
||||
.margin({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12 })
|
||||
Scroll() {
|
||||
Column() {
|
||||
this.topBar()
|
||||
|
||||
if (this.subView === 'roster') {
|
||||
this.rosterView()
|
||||
} else {
|
||||
this.kinView()
|
||||
// ---- 分段切换(名册 / 家谱)----
|
||||
Row({ space: 4 }) {
|
||||
this.segBtn('名册', 'roster', '#A84A28')
|
||||
this.segBtn('家谱', 'kin', '#A84A28')
|
||||
}
|
||||
.padding(3)
|
||||
.backgroundColor(FbColors.inputBg)
|
||||
.borderRadius(FbMetric.radiusPill)
|
||||
.margin({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12 })
|
||||
|
||||
if (this.subView === 'roster') {
|
||||
this.rosterView()
|
||||
} else {
|
||||
this.kinView()
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor(FbColors.paper)
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
.align(Alignment.TopStart)
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
|
||||
@@ -16,6 +16,12 @@ import { FbEmptyBox, FbIconButton, FbPrimaryButton, FbTag } from '../../componen
|
||||
|
||||
@ComponentV2
|
||||
export struct FbResourcesPage {
|
||||
|
||||
/**
|
||||
* 顶栏插槽:由 Index.ets 传入,放在本页滚动流最前面 ⇒ 随页面一起滚走。
|
||||
* 🔴 用户要求「向上滑动时标题栏跟着一起滑走」。
|
||||
*/
|
||||
@BuilderParam topBar: () => void;
|
||||
@Param app: FbAppState = fbAppState();
|
||||
|
||||
/** 模块主题色(资源 = 靛蓝) */
|
||||
@@ -25,76 +31,81 @@ export struct FbResourcesPage {
|
||||
@Local kindFilter: string = '';
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
// ---- 页头 ----
|
||||
Row({ space: 8 }) {
|
||||
Column({ space: 2 }) {
|
||||
Text(this.all.length.toString() + ' 项资源')
|
||||
.fontSize(FbMetric.fontTitle)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
Text('证件 / 资料 / 附件,可共享给家庭成员')
|
||||
Scroll() {
|
||||
Column() {
|
||||
this.topBar()
|
||||
|
||||
// ---- 页头 ----
|
||||
Row({ space: 8 }) {
|
||||
Column({ space: 2 }) {
|
||||
Text(this.all.length.toString() + ' 项资源')
|
||||
.fontSize(FbMetric.fontTitle)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
Text('证件 / 资料 / 附件,可共享给家庭成员')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
|
||||
FbPrimaryButton({
|
||||
label: '添加',
|
||||
bg: this.accent,
|
||||
icon: $r('app.media.fb_ic_plus')
|
||||
})
|
||||
FbIconButton({ icon: $r('app.media.fb_ic_gear'), tint: FbColors.muted })
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12, bottom: 8 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
// ---- 类别筛选 ----
|
||||
Row({ space: 8 }) {
|
||||
this.kindChip('全部', '')
|
||||
this.kindChip('证件', 'document')
|
||||
this.kindChip('资料', 'material')
|
||||
Blank()
|
||||
Text('共享给我 ' + this.sharedCount().toString() + ' 项')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
|
||||
FbPrimaryButton({
|
||||
label: '添加',
|
||||
bg: this.accent,
|
||||
icon: $r('app.media.fb_ic_plus')
|
||||
})
|
||||
FbIconButton({ icon: $r('app.media.fb_ic_gear'), tint: FbColors.muted })
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12, bottom: 8 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
// ---- 类别筛选 ----
|
||||
Row({ space: 8 }) {
|
||||
this.kindChip('全部', '')
|
||||
this.kindChip('证件', 'document')
|
||||
this.kindChip('资料', 'material')
|
||||
Blank()
|
||||
Text('共享给我 ' + this.sharedCount().toString() + ' 项')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 8 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
// ---- 列表 ----
|
||||
if (this.filtered().length === 0) {
|
||||
FbEmptyBox({
|
||||
title: '这里还没有资源',
|
||||
hint: '家里的证件、资料和附件都可以收进来,需要时一键共享',
|
||||
icon: $r('app.media.fb_ic_resources')
|
||||
})
|
||||
} else {
|
||||
List({ space: 0 }) {
|
||||
ForEach(this.groups(), (g: FbGroup<FbResourceItem>) => {
|
||||
ListItemGroup({ header: this.groupHeader(g.title, g.items.length.toString() + ' 项') }) {
|
||||
ForEach(g.items, (r: FbResourceItem) => {
|
||||
ListItem() {
|
||||
this.resourceCard(r)
|
||||
}
|
||||
}, (r: FbResourceItem): string => 'res_' + r.id.toString())
|
||||
}
|
||||
}, (g: FbGroup<FbResourceItem>): string => 'rsg_' + g.key)
|
||||
}
|
||||
.layoutWeight(1)
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 24 })
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 8 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
// ---- 列表 ----
|
||||
if (this.filtered().length === 0) {
|
||||
FbEmptyBox({
|
||||
title: '这里还没有资源',
|
||||
hint: '家里的证件、资料和附件都可以收进来,需要时一键共享',
|
||||
icon: $r('app.media.fb_ic_resources')
|
||||
})
|
||||
} else {
|
||||
List({ space: 0 }) {
|
||||
ForEach(this.groups(), (g: FbGroup<FbResourceItem>) => {
|
||||
ListItemGroup({ header: this.groupHeader(g.title, g.items.length.toString() + ' 项') }) {
|
||||
ForEach(g.items, (r: FbResourceItem) => {
|
||||
ListItem() {
|
||||
this.resourceCard(r)
|
||||
}
|
||||
}, (r: FbResourceItem): string => 'res_' + r.id.toString())
|
||||
}
|
||||
}, (g: FbGroup<FbResourceItem>): string => 'rsg_' + g.key)
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 24 })
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor(FbColors.paper)
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
.align(Alignment.TopStart)
|
||||
}
|
||||
|
||||
/** 一项资源卡 */
|
||||
|
||||
@@ -17,6 +17,12 @@ import { FbAvatar, FbDot, FbTag } from '../../components/Widgets';
|
||||
|
||||
@ComponentV2
|
||||
export struct FbSettingsPage {
|
||||
|
||||
/**
|
||||
* 顶栏插槽:由 Index.ets 传入,放在本页滚动流最前面 ⇒ 随页面一起滚走。
|
||||
* 🔴 用户要求「向上滑动时标题栏跟着一起滑走」。
|
||||
*/
|
||||
@BuilderParam topBar: () => void;
|
||||
@Param app: FbAppState = fbAppState();
|
||||
|
||||
/** 抽屉里勾选着的模块 key(与 FB_DEFAULT_NAV 一致,不含 nav) */
|
||||
@@ -28,6 +34,8 @@ export struct FbSettingsPage {
|
||||
build() {
|
||||
Scroll() {
|
||||
Column({ space: 0 }) {
|
||||
this.topBar()
|
||||
|
||||
|
||||
// ==================== 我的资料 ====================
|
||||
Row({ space: 14 }) {
|
||||
|
||||
@@ -17,6 +17,12 @@ import { FbCheckCircle, FbDot, FbEmptyBox, FbIconButton, FbPrimaryButton, FbTag
|
||||
|
||||
@ComponentV2
|
||||
export struct FbTasksPage {
|
||||
|
||||
/**
|
||||
* 顶栏插槽:由 Index.ets 传入,放在本页滚动流最前面 ⇒ 随页面一起滚走。
|
||||
* 🔴 用户要求「向上滑动时标题栏跟着一起滑走」。
|
||||
*/
|
||||
@BuilderParam topBar: () => void;
|
||||
@Param app: FbAppState = fbAppState();
|
||||
|
||||
private boards: string[] = FB_DEMO_TASK_BOARDS;
|
||||
@@ -27,78 +33,83 @@ export struct FbTasksPage {
|
||||
@Local boardFilter: string = '';
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
// ---- 页头 ----
|
||||
Row({ space: 8 }) {
|
||||
Column({ space: 2 }) {
|
||||
Text(this.doingCount().toString() + ' 项在做')
|
||||
.fontSize(FbMetric.fontTitle)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
Text(this.todoCount().toString() + ' 项待办'
|
||||
+ (this.overdueCount() > 0 ? ' · ' + this.overdueCount().toString() + ' 项逾期' : ''))
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(this.overdueCount() > 0 ? FbColors.red : FbColors.faint)
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
Scroll() {
|
||||
Column() {
|
||||
this.topBar()
|
||||
|
||||
FbPrimaryButton({
|
||||
label: '新建任务',
|
||||
bg: this.accent,
|
||||
icon: $r('app.media.fb_ic_plus')
|
||||
})
|
||||
FbIconButton({ icon: $r('app.media.fb_ic_gear'), tint: FbColors.muted })
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12, bottom: 8 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
// ---- 看板筛选条(横向滚动)----
|
||||
Scroll() {
|
||||
// ---- 页头 ----
|
||||
Row({ space: 8 }) {
|
||||
this.boardChip('全部', '')
|
||||
ForEach(this.boards, (b: string) => {
|
||||
this.boardChip(b, b)
|
||||
}, (b: string): string => 'bd_' + b)
|
||||
}
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad })
|
||||
}
|
||||
.scrollable(ScrollDirection.Horizontal)
|
||||
.scrollBar(BarState.Off)
|
||||
.width('100%')
|
||||
.padding({ bottom: 10 })
|
||||
Column({ space: 2 }) {
|
||||
Text(this.doingCount().toString() + ' 项在做')
|
||||
.fontSize(FbMetric.fontTitle)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor(FbColors.ink)
|
||||
Text(this.todoCount().toString() + ' 项待办'
|
||||
+ (this.overdueCount() > 0 ? ' · ' + this.overdueCount().toString() + ' 项逾期' : ''))
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(this.overdueCount() > 0 ? FbColors.red : FbColors.faint)
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
|
||||
// ---- 按状态分组的列表 ----
|
||||
if (this.filtered().length === 0) {
|
||||
FbEmptyBox({
|
||||
title: '这里没有任务',
|
||||
hint: '新建时会顺带指定它属于哪个看板、做到哪一步',
|
||||
icon: $r('app.media.fb_ic_tasks')
|
||||
})
|
||||
} else {
|
||||
List({ space: 0 }) {
|
||||
ForEach(this.groups(), (g: FbGroup<FbTaskItem>) => {
|
||||
ListItemGroup({ header: this.groupHeader(g) }) {
|
||||
ForEach(g.items, (t: FbTaskItem) => {
|
||||
ListItem() {
|
||||
this.taskCard(t)
|
||||
}
|
||||
}, (t: FbTaskItem): string => 'tk_' + t.id.toString())
|
||||
}
|
||||
}, (g: FbGroup<FbTaskItem>): string => 'grp_' + g.key)
|
||||
FbPrimaryButton({
|
||||
label: '新建任务',
|
||||
bg: this.accent,
|
||||
icon: $r('app.media.fb_ic_plus')
|
||||
})
|
||||
FbIconButton({ icon: $r('app.media.fb_ic_gear'), tint: FbColors.muted })
|
||||
}
|
||||
.layoutWeight(1)
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 24 })
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12, bottom: 8 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
// ---- 看板筛选条(横向滚动)----
|
||||
Scroll() {
|
||||
Row({ space: 8 }) {
|
||||
this.boardChip('全部', '')
|
||||
ForEach(this.boards, (b: string) => {
|
||||
this.boardChip(b, b)
|
||||
}, (b: string): string => 'bd_' + b)
|
||||
}
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad })
|
||||
}
|
||||
.scrollable(ScrollDirection.Horizontal)
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
.width('100%')
|
||||
.padding({ bottom: 10 })
|
||||
|
||||
// ---- 按状态分组的列表 ----
|
||||
if (this.filtered().length === 0) {
|
||||
FbEmptyBox({
|
||||
title: '这里没有任务',
|
||||
hint: '新建时会顺带指定它属于哪个看板、做到哪一步',
|
||||
icon: $r('app.media.fb_ic_tasks')
|
||||
})
|
||||
} else {
|
||||
List({ space: 0 }) {
|
||||
ForEach(this.groups(), (g: FbGroup<FbTaskItem>) => {
|
||||
ListItemGroup({ header: this.groupHeader(g) }) {
|
||||
ForEach(g.items, (t: FbTaskItem) => {
|
||||
ListItem() {
|
||||
this.taskCard(t)
|
||||
}
|
||||
}, (t: FbTaskItem): string => 'tk_' + t.id.toString())
|
||||
}
|
||||
}, (g: FbGroup<FbTaskItem>): string => 'grp_' + g.key)
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 24 })
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor(FbColors.paper)
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
.align(Alignment.TopStart)
|
||||
}
|
||||
|
||||
/** 看板筛选胶囊 */
|
||||
|
||||
@@ -20,6 +20,12 @@ import { FbDot, FbEmptyBox, FbIconButton, FbPrimaryButton, FbTag } from '../../c
|
||||
|
||||
@ComponentV2
|
||||
export struct FbTripsPage {
|
||||
|
||||
/**
|
||||
* 顶栏插槽:由 Index.ets 传入,放在本页滚动流最前面 ⇒ 随页面一起滚走。
|
||||
* 🔴 用户要求「向上滑动时标题栏跟着一起滑走」。
|
||||
*/
|
||||
@BuilderParam topBar: () => void;
|
||||
@Param app: FbAppState = fbAppState();
|
||||
|
||||
private trips: FbTripItem[] = FB_DEMO_TRIPS;
|
||||
@@ -30,56 +36,61 @@ export struct FbTripsPage {
|
||||
@Local expandedId: number = 0;
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
// ---- 页头操作条 ----
|
||||
Row({ space: 8 }) {
|
||||
Column({ space: 1 }) {
|
||||
Text('共 ' + this.trips.length.toString() + ' 个旅程')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(FbColors.ink)
|
||||
Text('每个旅程里可以有若干段行程')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
Scroll() {
|
||||
Column() {
|
||||
this.topBar()
|
||||
|
||||
FbPrimaryButton({
|
||||
label: '新建旅程',
|
||||
bg: this.accent,
|
||||
icon: $r('app.media.fb_ic_plus')
|
||||
})
|
||||
FbIconButton({ icon: $r('app.media.fb_ic_gear'), tint: FbColors.muted })
|
||||
// ---- 页头操作条 ----
|
||||
Row({ space: 8 }) {
|
||||
Column({ space: 1 }) {
|
||||
Text('共 ' + this.trips.length.toString() + ' 个旅程')
|
||||
.fontSize(FbMetric.fontSmall)
|
||||
.fontColor(FbColors.ink)
|
||||
Text('每个旅程里可以有若干段行程')
|
||||
.fontSize(FbMetric.fontTiny)
|
||||
.fontColor(FbColors.faint)
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
|
||||
FbPrimaryButton({
|
||||
label: '新建旅程',
|
||||
bg: this.accent,
|
||||
icon: $r('app.media.fb_ic_plus')
|
||||
})
|
||||
FbIconButton({ icon: $r('app.media.fb_ic_gear'), tint: FbColors.muted })
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12, bottom: 10 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
if (this.trips.length === 0) {
|
||||
FbEmptyBox({
|
||||
title: '还没有旅程',
|
||||
hint: '去过的地方会按时间排在这里,点开能看到每一段行程',
|
||||
icon: $r('app.media.fb_ic_trips')
|
||||
})
|
||||
} else {
|
||||
List({ space: 12 }) {
|
||||
ForEach(this.trips, (t: FbTripItem) => {
|
||||
ListItem() {
|
||||
this.tripCard(t)
|
||||
}
|
||||
}, (t: FbTripItem): string => 'trip_' + t.id.toString())
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 24 })
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12, bottom: 10 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
|
||||
if (this.trips.length === 0) {
|
||||
FbEmptyBox({
|
||||
title: '还没有旅程',
|
||||
hint: '去过的地方会按时间排在这里,点开能看到每一段行程',
|
||||
icon: $r('app.media.fb_ic_trips')
|
||||
})
|
||||
} else {
|
||||
List({ space: 12 }) {
|
||||
ForEach(this.trips, (t: FbTripItem) => {
|
||||
ListItem() {
|
||||
this.tripCard(t)
|
||||
}
|
||||
}, (t: FbTripItem): string => 'trip_' + t.id.toString())
|
||||
}
|
||||
.layoutWeight(1)
|
||||
.width('100%')
|
||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, bottom: 24 })
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor(FbColors.paper)
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
.align(Alignment.TopStart)
|
||||
}
|
||||
|
||||
/** 一张旅程卡:标题 + 时间 + 进度;展开后接行程时间轴 */
|
||||
|
||||
Reference in New Issue
Block a user