Files
FamilyBook_harmonyos/entry/src/main/ets/pages/days/FbDaysPage.ets
T

323 lines
10 KiB
Plaintext
Raw Normal View History

/**
* 家书 FamilyBook · 纪念日模块(鸿蒙端)
*
* 对应 Web 端 `templates/days/index.php`。
*
* Web 端的结构:**「即将到来」横条在顶**(30 天内的纪念日,含名册里每个人的生日),
* 下面是全部纪念日的列表(可按类别筛)。
*
* 端上照此,但把「即将到来」做成**大数字卡片**(还剩几天用大字),
* 因为「还有几天」是这个模块唯一真正要一眼看到的信息。
*/
import { FbColors, FbMetric } from '../../common/Theme';
import { FbAppState, fbAppState } from '../../common/Store';
import { FbAnniversaryItem, FbKinChip } from '../../model/Types';
import { FB_DEMO_ANNIVERSARIES } from '../../model/DemoData';
import { FbAvatar, FbEmptyBox, FbIconButton, FbPrimaryButton, FbTag } from '../../components/Widgets';
@ComponentV2
export struct FbDaysPage {
/**
* 顶栏插槽:由 Index.ets 传入,放在本页滚动流最前面 ⇒ 随页面一起滚走。
* 🔴 用户要求「向上滑动时标题栏跟着一起滑走」。
*/
@BuilderParam topBar: () => void;
@Param app: FbAppState = fbAppState();
/** 模块主题色(纪念日 = 陶土橙) */
private accent: string = '#B4633C';
private all: FbAnniversaryItem[] = FB_DEMO_ANNIVERSARIES;
/** 类别筛选('' = 全部) */
@Local kindFilter: string = '';
build() {
Scroll() {
Column() {
this.topBar()
// ---- 页头 ----
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)
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%')
}
}
.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())
}
.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)
}
.width('100%')
}
.width('100%')
.height('100%')
.scrollBar(BarState.Off)
.edgeEffect(EdgeEffect.Spring)
.align(Alignment.TopStart)
}
/** 即将到来的大数字卡 */
@Builder
upcomingCard(a: FbAnniversaryItem) {
Column({ space: 4 }) {
if (a.daysLeft === 0) {
Text('今天')
.fontSize(24)
.fontWeight(FontWeight.Medium)
.fontColor(Color.White)
} else {
Row({ space: 1 }) {
Text(a.daysLeft.toString())
.fontSize(26)
.fontWeight(FontWeight.Medium)
.fontColor(Color.White)
Text('天')
.fontSize(FbMetric.fontTiny)
.fontColor('#FFFFFFCC')
.margin({ bottom: 4 })
}
.alignItems(VerticalAlign.Bottom)
}
Text(a.title)
.fontSize(FbMetric.fontSmall)
.fontColor(Color.White)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text(a.date)
.fontSize(10)
.fontColor('#FFFFFFB3')
}
.width(96)
.padding(14)
.backgroundColor(this.accent)
.borderRadius(FbMetric.radius)
.alignItems(HorizontalAlign.Start)
}
/** 一行纪念日:日期 + 标题 + 相关人 + 天数 */
@Builder
annRow(a: FbAnniversaryItem, isLast: boolean) {
Column({ space: 0 }) {
Row({ space: 12 }) {
// 日期块
Column({ space: 0 }) {
Text(this.monthDay(a.date))
.fontSize(FbMetric.fontSmall)
.fontWeight(FontWeight.Medium)
.fontColor(FbColors.ink)
Text(this.yearText(a.date))
.fontSize(9)
.fontColor(FbColors.faint)
}
.width(52)
.alignItems(HorizontalAlign.Start)
Column({ space: 4 }) {
Row({ space: 6 }) {
Text(a.title)
.fontSize(FbMetric.fontBody)
.fontColor(FbColors.ink)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.layoutWeight(1)
FbTag({ text: this.kindName(a.kind), tone: this.kindTone(a.kind) })
}
.width('100%')
.alignItems(VerticalAlign.Center)
// 相关人(生日才有)
if (a.people.length > 0) {
Row({ space: 6 }) {
ForEach(a.people, (p: FbKinChip) => {
Row({ space: 3 }) {
FbAvatar({ name: p.shown, color: p.avatarColor, dia: 16, fontSizeVp: 9 })
Text(p.shown)
.fontSize(10)
.fontColor(FbColors.muted)
}
.alignItems(VerticalAlign.Center)
}, (p: FbKinChip): string => 'ap_' + a.id.toString() + '_' + p.id.toString())
}
}
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
// 剩余天数
Text(a.daysLeft === 0 ? '今天' : a.daysLeft.toString() + ' 天')
.fontSize(FbMetric.fontTiny)
.fontWeight(a.daysLeft <= 7 ? FontWeight.Medium : FontWeight.Normal)
.fontColor(a.daysLeft <= 7 ? this.accent : FbColors.faint)
}
.width('100%')
.padding({ top: 12, bottom: 12 })
.alignItems(VerticalAlign.Center)
if (!isLast) {
Divider().color(FbColors.line2).margin({ left: 64 })
}
}
.width('100%')
}
@Builder
kindChip(label: string, key: string) {
Text(label)
.fontSize(FbMetric.fontSmall)
.fontColor(this.kindFilter === key ? Color.White : FbColors.muted)
.backgroundColor(this.kindFilter === key ? this.accent : FbColors.inputBg)
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.borderRadius(FbMetric.radiusPill)
.onClick(() => {
this.kindFilter = key;
})
}
/** 'MM-DD' → '10-17';'YYYY-MM-DD' → '11-08' */
private monthDay(date: string): string {
const parts: string[] = date.split('-');
if (parts.length === 3) {
return parts[1] + '-' + parts[2];
}
return date;
}
/** 带年份的日期显示年份(如 '2019-11-08' → '2019'),不带则显示「每年」 */
private yearText(date: string): string {
const parts: string[] = date.split('-');
if (parts.length === 3) {
return parts[0];
}
return '每年';
}
private kindName(kind: string): string {
if (kind === 'birthday') {
return '生日';
}
if (kind === 'holiday') {
return '节日';
}
if (kind === 'solar_term') {
return '节气';
}
return '纪念日';
}
private kindTone(kind: string): string {
if (kind === 'birthday') {
return 'brand';
}
if (kind === 'holiday') {
return 'red';
}
if (kind === 'solar_term') {
return 'green';
}
return 'blue';
}
private upcoming(): FbAnniversaryItem[] {
return this.all.filter((a: FbAnniversaryItem): boolean => a.daysLeft <= 30);
}
private filtered(): FbAnniversaryItem[] {
if (this.kindFilter.length === 0) {
return this.all;
}
return this.all.filter((a: FbAnniversaryItem): boolean => a.kind === this.kindFilter);
}
}