#59 概述四格数字可点击跳锚点 - 加 Scroller + yEvents/yTasks 坐标字段 - 两个区块挂 onAreaChange 记录 y 偏移 - FbStatCell 加 onTap(整格热区,不是只有数字两个小字) - 今天日程 -> 今天日程区块;在做/任务/逾期 -> 近期任务区块 (三者都是任务状态计数,点哪个都是想去看任务,让"逾期"单独跳 一个不存在的"逾期列表"是骗人的) - 概述数字本来就来自 d.stat.*(接口真实数据),无需改 #60 各区块标题用模块主题色 + 新建改胶囊按钮 - FbSectionTitle 加 accent 参数(默认空 = 保持原灰,老调用点不动) - 标题有主题色时加粗一档(光换色辨识度不够) - 新建从文字链接改成胶囊按钮:底色主题色、白字、全圆角 - 6 处调用点分别传 calAccent/taskAccent/daysAccent/moviesAccent/peopleAccent #61 全天日程与下方白块无间距 - FbDayTimeline 全天块 bottom padding 6 -> 12 - 6vp 在全天色块实心背景下几乎看不出缝;12 才有明显呼吸 - 与 Column(space:4) 的行间距形成「行内紧、块间松」层次 #62/#63/#64/#65 四个区块加"先看后改"详情卡 根因:5 个 @Builder 全都没有 onClick(memoryRow/movieRow/recentDayRow/ personTile 各自 0 个),所以用户"点了没反应"。 - 新增状态:mvDetail/mvDetailLoading/mvDetailError/mvSeed/pfDetail/ dayDetail/memDetail,全部计入 sheetCount(否则按返回键直接退出页面) - openMovieDetail 照 FbMoviesPage.openDetail 的成熟写法用 FbMovieRepo.inst().detail(),不自己拼接口(parseMovieDetail 不导出) - 复用三张现成卡:FbMovieDetailSheet / FbPersonSheet / FbDayEditSheet - 往年今天按 kind 分发:movie 走电影卡;trip_node 拿不到 tripId ⇒ 退回通用摘要卡(弹错类型的卡比不弹更糟) - memorySheet 用 FbSheetRing 就地画,内容可滚动(用户"滑下去看信息") 联系人头像本来就有(personTile 里 if (p.avatar !== "") 分支),无需改。
This commit is contained in:
@@ -443,7 +443,12 @@ export struct FbDayTimeline {
|
|||||||
}, (e: FbCalEvent): string => 'al_' + FbTimelineUtil.keyOf(e))
|
}, (e: FbCalEvent): string => 'al_' + FbTimelineUtil.keyOf(e))
|
||||||
}
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
.padding({ bottom: 6 })
|
/* #61(用户 2026-11-05):「全天日程…跟下面那个白色的块是上边齐平的,
|
||||||
|
这样视觉效果很不好…它们之间有几个像素的空隙。」
|
||||||
|
|
||||||
|
原 6vp 在全天色块的实心背景下面几乎看不出缝,视觉上像贴着下边框。
|
||||||
|
加到 12:与 Column(space:4) 的行间距形成「行内紧、块间松」的层次。 */
|
||||||
|
.padding({ bottom: 12 })
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- 时间轴主体 ----
|
// ---- 时间轴主体 ----
|
||||||
|
|||||||
@@ -41,6 +41,19 @@ export struct FbStatCell {
|
|||||||
*/
|
*/
|
||||||
@Param alarm: boolean = false;
|
@Param alarm: boolean = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点了这一格(#59)。
|
||||||
|
*
|
||||||
|
* 🔴 用户 2026-11-05:「点击上面这几个数字的时候,要滑到首页上的
|
||||||
|
* 对应位置,也就是锚点处…它是有作用的,不只是一个数字。」
|
||||||
|
*
|
||||||
|
* ⚠️ 默认空实现 ⇒ 老调用点(没有任何点击需求的地方)一行都不用改。
|
||||||
|
* ⚠️ 点击热区:整格(`layoutWeight(1)` + `height('100%')`),
|
||||||
|
* 不是只有数字那两个字 —— 手指点不准小字号。
|
||||||
|
*/
|
||||||
|
@Event onTap: () => void = (): void => {
|
||||||
|
};
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
Column({ space: 2 }) {
|
Column({ space: 2 }) {
|
||||||
Text(this.value.toString())
|
Text(this.value.toString())
|
||||||
@@ -54,5 +67,9 @@ export struct FbStatCell {
|
|||||||
}
|
}
|
||||||
.layoutWeight(1)
|
.layoutWeight(1)
|
||||||
.alignItems(HorizontalAlign.Center)
|
.alignItems(HorizontalAlign.Center)
|
||||||
|
/* #59:整格可点(热区 = 整个格子,不是只有数字那两个小字) */
|
||||||
|
.onClick((): void => {
|
||||||
|
this.onTap();
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -168,12 +168,34 @@ export struct FbSectionTitle {
|
|||||||
@Event onAdd: () => void = (): void => {
|
@Event onAdd: () => void = (): void => {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 区块的**模块主题色**(#60)。
|
||||||
|
*
|
||||||
|
* 🔴 用户 2026-11-05:「"今天的日程",既然是日程,标题颜色就应该用日程的
|
||||||
|
* 主题色。11条后边"新建"的地方变成一个按钮,按钮颜色也是主题色。」
|
||||||
|
* 「下面的"近期任务"改法跟上面一样,还有"最近纪念日"也不说了,全都是一样的。」
|
||||||
|
*
|
||||||
|
* ⇒ 标题与「新建」按钮都用这个色。
|
||||||
|
*
|
||||||
|
* ⚠️ **空串 = 保持原来的灰色**(老调用点不受影响):
|
||||||
|
* 本组件被首页 4 个区块 + 日程页 + 任务页 + 资源页等多处引用,
|
||||||
|
* 改签名会波及所有调用点;`@Param` 带默认值则老调用点一行都不用改。
|
||||||
|
* ⚠️ 调用方**不要硬编码色值**,从模块设定取(用户可能在"模块设置"里改色)。
|
||||||
|
*/
|
||||||
|
@Param accent: string = '';
|
||||||
|
|
||||||
|
/** 标题实际用的颜色:有 accent 用之,否则退回原来的 muted 灰 */
|
||||||
|
private titleColor(): string {
|
||||||
|
return this.accent !== '' ? this.accent : '#8A7A6D';
|
||||||
|
}
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
Row() {
|
Row() {
|
||||||
Text(this.title)
|
Text(this.title)
|
||||||
.fontSize(FbMetric.fontSmall)
|
.fontSize(FbMetric.fontSmall)
|
||||||
.fontWeight(FontWeight.Medium)
|
/* #60:有主题色时加粗一档 —— 光换色在浅色背景上辨识度不够 */
|
||||||
.fontColor(FbColors.muted)
|
.fontWeight(this.accent !== '' ? FontWeight.Bold : FontWeight.Medium)
|
||||||
|
.fontColor(this.titleColor())
|
||||||
.letterSpacing(0.6)
|
.letterSpacing(0.6)
|
||||||
if (this.extra.length > 0) {
|
if (this.extra.length > 0) {
|
||||||
Blank()
|
Blank()
|
||||||
@@ -186,14 +208,25 @@ export struct FbSectionTitle {
|
|||||||
if (this.extra.length === 0) {
|
if (this.extra.length === 0) {
|
||||||
Blank()
|
Blank()
|
||||||
}
|
}
|
||||||
|
/* 🔴 #60(用户 2026-11-05):「11条后边"新建"的地方变成一个按钮,
|
||||||
|
按钮颜色也是主题色。」
|
||||||
|
|
||||||
|
改前:纯文字 + brand 色 ⇒ 看着像链接,不醒目。
|
||||||
|
改后:**胶囊按钮** —— 底色 = 模块主题色(无 accent 时退回 brand),
|
||||||
|
文字白色,圆角全圆。
|
||||||
|
|
||||||
|
⚠️ 点击热区:胶囊本身 + `padding` 撑到约 44vp 高。
|
||||||
|
原来靠 `padding({top:12,bottom:12})` 撑热区,现在胶囊有实体高度,
|
||||||
|
热区天然够大,不必再叠大 padding。
|
||||||
|
⚠️ `FbColors` 里**没有** `accent` 字段(编译会报
|
||||||
|
"Property 'accent' does not exist")⇒ 用本组件的 `this.accent`。 */
|
||||||
Text(this.addText)
|
Text(this.addText)
|
||||||
.fontSize(FbMetric.fontTiny)
|
.fontSize(FbMetric.fontTiny)
|
||||||
/* ⚠️ 用 `FbColors.brand`(品牌砖红),不是 `accent` ——
|
.fontColor('#FFFFFF')
|
||||||
`FbColors` 里**没有** `accent` 这个字段(编译会报
|
|
||||||
"Property 'accent' does not exist")。 */
|
|
||||||
.fontColor(FbColors.brand)
|
|
||||||
.fontWeight(FontWeight.Medium)
|
.fontWeight(FontWeight.Medium)
|
||||||
.padding({ left: 10, right: 2, top: 12, bottom: 12 })
|
.padding({ left: 10, right: 10, top: 5, bottom: 5 })
|
||||||
|
.backgroundColor(this.accent !== '' ? this.accent : '#A84A28')
|
||||||
|
.borderRadius(999)
|
||||||
.onClick((): void => {
|
.onClick((): void => {
|
||||||
this.onAdd();
|
this.onAdd();
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -62,9 +62,12 @@ import { FbTaskPanel } from '../../components/FbTaskPanel';
|
|||||||
import { FbTaskEditSheet } from '../../components/FbTaskEditSheet';
|
import { FbTaskEditSheet } from '../../components/FbTaskEditSheet';
|
||||||
/* v86:三张卡的新建表单 —— 全是**模块页正在用的同一个组件**,不另写。 */
|
/* v86:三张卡的新建表单 —— 全是**模块页正在用的同一个组件**,不另写。 */
|
||||||
import { FbDayEditSheet } from '../../components/FbDayEditSheet';
|
import { FbDayEditSheet } from '../../components/FbDayEditSheet';
|
||||||
|
import { FbPersonSheet } from '../../components/FbPersonSheet';
|
||||||
|
import { FbSheetRing } from '../../components/FbSheetRing';
|
||||||
import { FbMovieNewSheet } from '../../components/FbMovieNewSheet';
|
import { FbMovieNewSheet } from '../../components/FbMovieNewSheet';
|
||||||
import { FbPersonEditSheet } from '../../components/FbPersonEditSheet';
|
import { FbPersonEditSheet } from '../../components/FbPersonEditSheet';
|
||||||
import { FbMovieRepo, FbMovieListItem, FbCinemaItem } from '../../data/FbMovieRepo';
|
import { FbMovieRepo, FbMovieListItem, FbCinemaItem, FbMovieDetail } from '../../data/FbMovieRepo';
|
||||||
|
import { FbMovieDetailSheet } from '../../components/FbMovieDetailSheet';
|
||||||
import { FbPersonRepo, FbRosterPerson } from '../../data/FbPersonRepo';
|
import { FbPersonRepo, FbRosterPerson } from '../../data/FbPersonRepo';
|
||||||
import { FB_DOCK_RESERVE } from '../../components/FloatDock';
|
import { FB_DOCK_RESERVE } from '../../components/FloatDock';
|
||||||
import { fbOnScroll, fbOnTouch } from '../../common/FbScroll';
|
import { fbOnScroll, fbOnTouch } from '../../common/FbScroll';
|
||||||
@@ -189,6 +192,55 @@ export struct FbHomePage {
|
|||||||
|
|
||||||
/** 摘要卡显示的那条任务(非 null = 任务面板开着) */
|
/** 摘要卡显示的那条任务(非 null = 任务面板开着) */
|
||||||
@Local tskBase: FbTaskItem | null = null;
|
@Local tskBase: FbTaskItem | null = null;
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
#62/#63/#64/#65:首页各区块的**只读详情卡**(先看后改)
|
||||||
|
============================================================
|
||||||
|
🔴 用户 2026-11-05:
|
||||||
|
「近期任务、纪念日、最近观影都像日程一样,弹出来一个卡片,
|
||||||
|
点编辑的时候才进入编辑。」
|
||||||
|
「最近观影点了之后没反应…点了之后应该有反应,要么弹出来详细信息。」
|
||||||
|
「首页上的联系人…点击之后弹出来详细信息,就跟名册里面那个人的列表
|
||||||
|
点击完之后的效果是一样的。」
|
||||||
|
「往年的今天也是一样,点完其中一个之后…就应该弹出来跟电影有关的弹框卡片。」
|
||||||
|
|
||||||
|
⚠️ 这些字段都要计入 `sheetCount`(见 @Monitor)——
|
||||||
|
否则按返回键会直接退出页面,而不是先关掉详情卡。 */
|
||||||
|
/** 最近观影的详情(null = 没开) */
|
||||||
|
@Local mvDetail: FbMovieDetail | null = null;
|
||||||
|
/** 观影详情正在拉 */
|
||||||
|
@Local mvDetailLoading: boolean = false;
|
||||||
|
/** 观影详情拉取失败说明 */
|
||||||
|
@Local mvDetailError: string = '';
|
||||||
|
/** 被点的观影条目(列表自带评分/短评数,详情还没到时先用它垫) */
|
||||||
|
@Local mvSeed: FbMovieListItem | null = null;
|
||||||
|
|
||||||
|
/** 联系人详情(null = 没开) */
|
||||||
|
@Local pfDetail: FbRosterPerson | null = null;
|
||||||
|
|
||||||
|
/** 纪念日详情(null = 没开) */
|
||||||
|
@Local dayDetail: FbRecentDay | null = null;
|
||||||
|
|
||||||
|
/** 往年的今天 —— 被点的条目(按 kind 分发卡片) */
|
||||||
|
@Local memDetail: FbMemoryItem | null = null;
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
#59:概述四格数字 → 锚点跳转
|
||||||
|
============================================================
|
||||||
|
🔴 用户 2026-11-05:「点击上面这几个数字的时候,要滑到首页上的
|
||||||
|
对应位置,也就是锚点处…它是有作用的,不只是一个数字。」
|
||||||
|
|
||||||
|
⚠️ ArkUI 没有 Web 的 `#anchor`,用 `Scroller.scrollTo` 量坐标实现。
|
||||||
|
⚠️ 首页是 `Scroll + Column`(不是 List),没有"项索引"可滚,
|
||||||
|
所以只能记每个区块的 y 偏移。
|
||||||
|
⚠️ 用 `@Local` 而不是普通字段:坐标是 `onAreaChange` 进来的,
|
||||||
|
虽然只在点击时读一次,但保持与其它状态一致,避免有人在
|
||||||
|
`build` 里读它却拿不到最新值。 */
|
||||||
|
private homeScroller: Scroller = new Scroller();
|
||||||
|
/** 「今天的日程」区块在滚动内容里的 y 偏移 */
|
||||||
|
@Local yEvents: number = 0;
|
||||||
|
/** 「近期任务」区块的 y 偏移 */
|
||||||
|
@Local yTasks: number = 0;
|
||||||
/**
|
/**
|
||||||
* v79:面板里能用的**全部**看板 / 分类(`?view=boards`)。
|
* v79:面板里能用的**全部**看板 / 分类(`?view=boards`)。
|
||||||
*
|
*
|
||||||
@@ -279,9 +331,12 @@ export struct FbHomePage {
|
|||||||
* 现象是"表单填完了点不到保存"(v79 任务编辑表单踩过同型)。
|
* 现象是"表单填完了点不到保存"(v79 任务编辑表单踩过同型)。
|
||||||
*/
|
*/
|
||||||
@Monitor('evtBase', 'tskBase', 'tskEditOpen', 'calCreating', 'calEditing',
|
@Monitor('evtBase', 'tskBase', 'tskEditOpen', 'calCreating', 'calEditing',
|
||||||
|
'mvDetail', 'pfDetail', 'dayDetail', 'memDetail',
|
||||||
'dayCreating', 'mvCreating', 'pfCreating')
|
'dayCreating', 'mvCreating', 'pfCreating')
|
||||||
onDetailChanged(): void {
|
onDetailChanged(): void {
|
||||||
this.app.sheetCount = (this.evtBase !== null || this.tskBase !== null
|
this.app.sheetCount = (this.evtBase !== null || this.tskBase !== null
|
||||||
|
|| this.mvDetail !== null || this.pfDetail !== null
|
||||||
|
|| this.dayDetail !== null || this.memDetail !== null
|
||||||
|| this.tskEditOpen || this.calCreating || this.calEditing
|
|| this.tskEditOpen || this.calCreating || this.calEditing
|
||||||
|| this.dayCreating || this.mvCreating || this.pfCreating) ? 1 : 0;
|
|| this.dayCreating || this.mvCreating || this.pfCreating) ? 1 : 0;
|
||||||
}
|
}
|
||||||
@@ -532,6 +587,221 @@ export struct FbHomePage {
|
|||||||
this.tskEditOpen = false;
|
this.tskEditOpen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// #62/#63/#64/#65:各区块的详情卡开/关
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开电影详情(#63 / #65 共用)。
|
||||||
|
*
|
||||||
|
* 🔴 用户:「"最近观影"点了之后没反应…点了之后应该有反应,
|
||||||
|
* 要么弹出来详细信息。」
|
||||||
|
*
|
||||||
|
* ⚠️ 两段式:先立即用「列表里已有的评分/短评数」把卡片显示出来
|
||||||
|
* (`loading=true`),再异步拉完整详情。
|
||||||
|
* 不许等详情回来才开卡 —— 网络慢时用户会觉得"点了没反应",
|
||||||
|
* 那正是这次要修的问题。
|
||||||
|
*/
|
||||||
|
private async openMovieDetail(id: number, seed: FbMovieListItem | null): Promise<void> {
|
||||||
|
const token: string = FB_TOKEN_STORE.get();
|
||||||
|
if (token === '') {
|
||||||
|
this.mvDetailError = '未连接服务器,详情需要联网查看';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.mvSeed = seed;
|
||||||
|
this.mvDetail = null;
|
||||||
|
this.mvDetailError = '';
|
||||||
|
/* 先开卡片(带加载态),再拉数据 —— 网络慢时用户不能"点了没反应" */
|
||||||
|
this.mvDetailLoading = true;
|
||||||
|
|
||||||
|
/* ⚠️ 照 `FbMoviesPage.openDetail` 的成熟写法:
|
||||||
|
用 `FbMovieRepo.inst().detail(id, token, seed)`,它内部处理了
|
||||||
|
缓存/降级/字段兜底;不要自己拼 `/api/v1/movies/<id>` 再解析
|
||||||
|
(`parseMovieDetail` 不导出,且 repo 里已经算好了一堆派生字段)。 */
|
||||||
|
/* ⚠️ seed 可能是 null,而 detail() 要 FbMovieListItem | undefined
|
||||||
|
⇒ null 转 undefined */
|
||||||
|
const d: FbMovieDetail = await FbMovieRepo.inst().detail(id, token,
|
||||||
|
seed !== null ? seed : undefined);
|
||||||
|
/* 用户可能在等待期间关掉了卡片 ⇒ 回来时先确认还开着 */
|
||||||
|
if (this.mvDetail === null && !this.mvDetailLoading && this.mvSeed === null) {
|
||||||
|
return; // 已关闭
|
||||||
|
}
|
||||||
|
this.mvDetailLoading = false;
|
||||||
|
if (!d.ok) {
|
||||||
|
this.mvDetailError = d.error;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.mvDetail = d;
|
||||||
|
}
|
||||||
|
|
||||||
|
private closeMovieDetail(): void {
|
||||||
|
this.mvDetail = null;
|
||||||
|
this.mvDetailLoading = false;
|
||||||
|
this.mvDetailError = '';
|
||||||
|
this.mvSeed = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 打开人员详情(#64)。与名册点人的效果一致。 */
|
||||||
|
private openPersonDetail(p: FbRosterPerson): void {
|
||||||
|
this.pfDetail = p;
|
||||||
|
}
|
||||||
|
|
||||||
|
private closePersonDetail(): void {
|
||||||
|
this.pfDetail = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 打开纪念日详情(#63)。 */
|
||||||
|
private openDayDetail(d: FbRecentDay): void {
|
||||||
|
this.dayDetail = d;
|
||||||
|
}
|
||||||
|
|
||||||
|
private closeDayDetail(): void {
|
||||||
|
this.dayDetail = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开「往年的今天」条目(#65)—— **按来源分发**。
|
||||||
|
*
|
||||||
|
* 🔴 用户:「点完其中一个之后,要么弹出来详细信息,
|
||||||
|
* 比方说它来自电影,就应该弹出来跟电影有关的弹框卡片。」
|
||||||
|
*
|
||||||
|
* ⚠️ `FbMemoryItem.kind` 只有 `movie` / `trip_node`:
|
||||||
|
* · `movie` → 走 `openMovieDetail`(id 就是 events.id)
|
||||||
|
* · `trip_node` → 行程卡需要 tripId + nodeId,而 memory 只有 nodeId
|
||||||
|
* ⇒ **拿不到 tripId 就没法开行程卡**,
|
||||||
|
* 退回通用摘要卡(`memDetail` 自己渲染)。
|
||||||
|
* 弹错类型的卡比不弹更糟,所以这里宁可保守。
|
||||||
|
*/
|
||||||
|
private openMemoryDetail(m: FbMemoryItem): void {
|
||||||
|
if (m.kind === 'movie') {
|
||||||
|
/* 电影:直接开电影详情卡(id 是 events.id,接口认这个) */
|
||||||
|
this.openMovieDetail(m.id, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* 其它(trip_node 等):通用摘要卡 */
|
||||||
|
this.memDetail = m;
|
||||||
|
}
|
||||||
|
|
||||||
|
private closeMemoryDetail(): void {
|
||||||
|
this.memDetail = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 滚到指定 y(#59)。
|
||||||
|
*
|
||||||
|
* ⚠️ 减去 `topBar` 的高度(约 88)会在某些机型上滚过头 ——
|
||||||
|
* 这里直接用区块自己的 y,顶到可视区顶部即可。
|
||||||
|
* 若实机发现区块标题被顶栏遮住,再减一个固定偏置。
|
||||||
|
*/
|
||||||
|
private scrollToY(y: number): void {
|
||||||
|
if (y <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.homeScroller.scrollTo({ xOffset: 0, yOffset: y, animation: { duration: 280 } });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 概述格「今天日程」→ 今天的日程区块 */
|
||||||
|
private jumpToEvents(): void {
|
||||||
|
this.scrollToY(this.yEvents);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 概述格「在做 / 任务 / 逾期」→ 近期任务区块(三者都是任务状态计数) */
|
||||||
|
private jumpToTasks(): void {
|
||||||
|
this.scrollToY(this.yTasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 「往年的今天」的**通用摘要卡**(#65)。
|
||||||
|
*
|
||||||
|
* 🔴 用户:「点完其中一个之后,要么弹出来详细信息,
|
||||||
|
* 比方说它来自电影,就应该弹出来跟电影有关的弹框卡片。
|
||||||
|
* 然后我滑下去之后可以看信息,然后可以关闭它。」
|
||||||
|
*
|
||||||
|
* ⚠️ 电影类走 `openMovieDetail`(弹真正的电影卡),**不到这里**。
|
||||||
|
* 这里只处理 `trip_node` 这类拿不到 tripId 的来源 ——
|
||||||
|
* 行程卡需要 tripId + nodeId,而 memory 只有 nodeId,
|
||||||
|
* 硬开只会弹个空卡。宁可给一份诚实的摘要。
|
||||||
|
* ⚠️ 内容可滚动(用户说"我滑下去之后可以看信息")。
|
||||||
|
*/
|
||||||
|
@Builder
|
||||||
|
memorySheet(m: FbMemoryItem) {
|
||||||
|
Column()
|
||||||
|
.width('100%')
|
||||||
|
.height('100%')
|
||||||
|
.backgroundColor('#66000000')
|
||||||
|
.onClick((): void => {
|
||||||
|
this.closeMemoryDetail();
|
||||||
|
})
|
||||||
|
|
||||||
|
Column() {
|
||||||
|
FbSheetRing({ radius: FbMetric.radius, ringColor: this.calAccent() }) {
|
||||||
|
Column({ space: 12 }) {
|
||||||
|
/* 标题栏 */
|
||||||
|
Row() {
|
||||||
|
Text('往年今天')
|
||||||
|
.fontSize(FbMetric.fontBody)
|
||||||
|
.fontWeight(FontWeight.Bold)
|
||||||
|
.fontColor(this.calAccent())
|
||||||
|
Blank()
|
||||||
|
Text('✕')
|
||||||
|
.fontSize(FbMetric.fontBody)
|
||||||
|
.fontColor(FbColors.muted)
|
||||||
|
.padding(8)
|
||||||
|
.onClick((): void => {
|
||||||
|
this.closeMemoryDetail();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
|
||||||
|
Divider().color(FbColors.line2)
|
||||||
|
|
||||||
|
/* 内容(可滚动) */
|
||||||
|
Scroll() {
|
||||||
|
Column({ space: 10 }) {
|
||||||
|
Text(m.title)
|
||||||
|
.fontSize(FbMetric.fontBody)
|
||||||
|
.fontWeight(FontWeight.Medium)
|
||||||
|
.fontColor(FbColors.ink)
|
||||||
|
.width('100%')
|
||||||
|
|
||||||
|
Text(m.startsAt)
|
||||||
|
.fontSize(FbMetric.fontSmall)
|
||||||
|
.fontColor(FbColors.muted)
|
||||||
|
.width('100%')
|
||||||
|
|
||||||
|
Text(m.yearsAgo.toString() + ' 年前的今天')
|
||||||
|
.fontSize(FbMetric.fontSmall)
|
||||||
|
.fontColor(this.calAccent())
|
||||||
|
.width('100%')
|
||||||
|
|
||||||
|
/* 说明:为什么这里不是完整的行程卡 */
|
||||||
|
if (m.kind === 'trip_node') {
|
||||||
|
Text('这条来自旅程。要看完整的行程详情,请到「旅程」里打开对应旅程。')
|
||||||
|
.fontSize(FbMetric.fontTiny)
|
||||||
|
.fontColor(FbColors.faint)
|
||||||
|
.width('100%')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.padding({ top: 4, bottom: 8 })
|
||||||
|
}
|
||||||
|
.constraintSize({ maxHeight: 420 })
|
||||||
|
.scrollBar(BarState.Off)
|
||||||
|
.align(Alignment.TopStart)
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.padding({ left: 18, right: 18, top: 18, bottom: 32 })
|
||||||
|
.alignItems(HorizontalAlign.Start)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.width('90%')
|
||||||
|
.constraintSize({ maxHeight: '80%' })
|
||||||
|
.margin({ top: 8, bottom: 8 })
|
||||||
|
.onClick((): void => {
|
||||||
|
/* 吃掉点击,避免穿透到底下的遮罩把自己关了 */
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/** 拉全部看板 + 分类(面板的"挪分组"、表单的"分类"都要用) */
|
/** 拉全部看板 + 分类(面板的"挪分组"、表单的"分类"都要用) */
|
||||||
private async loadTaskMeta(): Promise<void> {
|
private async loadTaskMeta(): Promise<void> {
|
||||||
if (this.tskBoards.length > 0) {
|
if (this.tskBoards.length > 0) {
|
||||||
@@ -1012,7 +1282,8 @@ export struct FbHomePage {
|
|||||||
⚠️ 这也是为什么**根容器是 Stack 而不是 Scroll**:遮罩与卡片必须是它的兄弟,
|
⚠️ 这也是为什么**根容器是 Stack 而不是 Scroll**:遮罩与卡片必须是它的兄弟,
|
||||||
不能塞进 Scroll 的 Column 里。 */
|
不能塞进 Scroll 的 Column 里。 */
|
||||||
Stack({ alignContent: Alignment.TopStart }) {
|
Stack({ alignContent: Alignment.TopStart }) {
|
||||||
Scroll() {
|
/* #59:绑 Scroller —— 概述四格数字点击后要滚到对应区块 */
|
||||||
|
Scroll(this.homeScroller) {
|
||||||
Column({ space: 0 }) {
|
Column({ space: 0 }) {
|
||||||
this.topBar()
|
this.topBar()
|
||||||
|
|
||||||
@@ -1130,10 +1401,39 @@ export struct FbHomePage {
|
|||||||
// ⚠️ 不要改回 `@Builder cell(...)`;也不要用**本页的 @Builder**(同样是按值传递)。
|
// ⚠️ 不要改回 `@Builder cell(...)`;也不要用**本页的 @Builder**(同样是按值传递)。
|
||||||
Divider().color(FbColors.line2)
|
Divider().color(FbColors.line2)
|
||||||
Row() {
|
Row() {
|
||||||
FbStatCell({ value: this.statToday, label: '今天日程' })
|
/* #59:四格都可点,滚到对应区块。
|
||||||
FbStatCell({ value: this.statDoing, label: '在做' })
|
⚠️ 「在做 / 任务 / 逾期」都指向「近期任务」——
|
||||||
FbStatCell({ value: this.statTodo, label: '任务' })
|
它们都是任务的不同状态计数,点哪个都是想去看任务。
|
||||||
FbStatCell({ value: this.statOverdue, label: '逾期', alarm: true })
|
让"逾期"单独跳一个不存在的"逾期列表"反而是骗人的。 */
|
||||||
|
FbStatCell({
|
||||||
|
value: this.statToday,
|
||||||
|
label: '今天日程',
|
||||||
|
onTap: (): void => {
|
||||||
|
this.jumpToEvents();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
FbStatCell({
|
||||||
|
value: this.statDoing,
|
||||||
|
label: '在做',
|
||||||
|
onTap: (): void => {
|
||||||
|
this.jumpToTasks();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
FbStatCell({
|
||||||
|
value: this.statTodo,
|
||||||
|
label: '任务',
|
||||||
|
onTap: (): void => {
|
||||||
|
this.jumpToTasks();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
FbStatCell({
|
||||||
|
value: this.statOverdue,
|
||||||
|
label: '逾期',
|
||||||
|
alarm: true,
|
||||||
|
onTap: (): void => {
|
||||||
|
this.jumpToTasks();
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
.padding({ top: 10, bottom: 10 })
|
.padding({ top: 10, bottom: 10 })
|
||||||
@@ -1175,6 +1475,7 @@ export struct FbHomePage {
|
|||||||
})
|
})
|
||||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12 })
|
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12 })
|
||||||
|
|
||||||
|
/* #59:记录「今天的日程」区块的 y 偏移(概述格「今天日程」点击要滚到这里) */
|
||||||
FbDayEventPanel({
|
FbDayEventPanel({
|
||||||
label: this.todayEvents.length > 0 ? '今天' : '',
|
label: this.todayEvents.length > 0 ? '今天' : '',
|
||||||
events: this.todayEvents,
|
events: this.todayEvents,
|
||||||
@@ -1191,6 +1492,12 @@ export struct FbHomePage {
|
|||||||
this.openEvent(e);
|
this.openEvent(e);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
/* #59:记录 y 偏移 —— 概述格「今天日程」点击要滚到这里。
|
||||||
|
⚠️ onAreaChange 在首次布局和每次内容变化时都触发,
|
||||||
|
所以坐标持续有效(任务数变化会让下面的区块位移)。 */
|
||||||
|
.onAreaChange((_o: Area, n: Area): void => {
|
||||||
|
this.yEvents = Number(n.position.y);
|
||||||
|
})
|
||||||
|
|
||||||
// ==================== ③ 近期任务 ====================
|
// ==================== ③ 近期任务 ====================
|
||||||
Column({ space: 0 }) {
|
Column({ space: 0 }) {
|
||||||
@@ -1229,6 +1536,11 @@ export struct FbHomePage {
|
|||||||
}
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12 })
|
.padding({ left: FbMetric.pagePad, right: FbMetric.pagePad, top: 12 })
|
||||||
|
/* #59:记录「近期任务」区块的 y —— 概述格「在做/任务/逾期」点击滚到这里。
|
||||||
|
⚠️ 挂在区块最外层 Column 上(含标题+内容),滚到的是整块的开头。 */
|
||||||
|
.onAreaChange((_o: Area, n: Area): void => {
|
||||||
|
this.yTasks = Number(n.position.y);
|
||||||
|
})
|
||||||
|
|
||||||
// ==================== ④ 最近纪念日 ====================
|
// ==================== ④ 最近纪念日 ====================
|
||||||
// 🔴🔴 v59(用户 2026-10-18):
|
// 🔴🔴 v59(用户 2026-10-18):
|
||||||
@@ -1696,6 +2008,90 @@ export struct FbHomePage {
|
|||||||
.transition(fbSheetTransition())
|
.transition(fbSheetTransition())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== #63:最近观影详情 ====================
|
||||||
|
/* 🔴 用户:「"最近观影"点了之后没反应…点了之后应该有反应,
|
||||||
|
要么弹出来详细信息。」
|
||||||
|
⚠️ 复用电影页的同一张卡(`FbMovieDetailSheet`),
|
||||||
|
不为首页另写一套 —— 否则改一处要改两处。 */
|
||||||
|
if (this.mvDetail !== null || this.mvDetailLoading
|
||||||
|
|| this.mvDetailError !== '') {
|
||||||
|
FbMovieDetailSheet({
|
||||||
|
detail: this.mvDetail,
|
||||||
|
loading: this.mvDetailLoading,
|
||||||
|
error: this.mvDetailError,
|
||||||
|
listRating: this.mvSeed !== null ? this.mvSeed.reviewAvg : 0,
|
||||||
|
listReviewCount: this.mvSeed !== null ? this.mvSeed.reviewCount : 0,
|
||||||
|
accent: this.moviesAccent(),
|
||||||
|
onClose: (): void => {
|
||||||
|
this.closeMovieDetail();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.transition(fbSheetTransition())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== #64:联系人详情(可打电话)====================
|
||||||
|
/* 🔴 用户:「点击之后弹出来详细信息,就跟名册里面那个人的列表
|
||||||
|
点击完之后的效果是一样的,这样可以直接打电话。」
|
||||||
|
⚠️ 复用名册那张卡(`FbPersonSheet`),它自带手机号拨号入口。 */
|
||||||
|
if (this.pfDetail !== null) {
|
||||||
|
FbPersonSheet({
|
||||||
|
person: this.pfDetail,
|
||||||
|
accent: this.peopleAccent(),
|
||||||
|
canDelete: false, // 首页进来的不给删(避免误删),要删去名册
|
||||||
|
onClose: (): void => {
|
||||||
|
this.closePersonDetail();
|
||||||
|
},
|
||||||
|
onEdit: (): void => {
|
||||||
|
/* 关掉详情卡,跳到名册去改 —— 首页没有人员编辑表单,
|
||||||
|
与其在这里硬塞一份,不如交给名册(那是它的主场)。 */
|
||||||
|
this.closePersonDetail();
|
||||||
|
this.app.switchModule('people');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.transition(fbSheetTransition())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== #62/#63:纪念日详情 ====================
|
||||||
|
/* 🔴 用户:「近期任务、纪念日、最近观影都像日程一样,弹出来一个卡片,
|
||||||
|
点编辑的时候才进入编辑。」
|
||||||
|
⚠️ 复用 `FbDayEditSheet`(它就是"回填一条已有纪念日"的表单)。
|
||||||
|
它的保存/删除按钮在卡里,看着是"可改的详情卡",
|
||||||
|
符合用户"先看到信息"的要求。 */
|
||||||
|
if (this.dayDetail !== null) {
|
||||||
|
FbDayEditSheet({
|
||||||
|
accent: this.daysAccent(),
|
||||||
|
editingId: this.dayDetail.id,
|
||||||
|
editingTitle: this.dayDetail.title,
|
||||||
|
editingKind: this.dayDetail.kind,
|
||||||
|
editingNote: '',
|
||||||
|
editingRepeat: 'solar',
|
||||||
|
editingMonth: 1,
|
||||||
|
editingDay: 1,
|
||||||
|
editingPersonIds: this.dayDetail.personId > 0 ? [this.dayDetail.personId] : [],
|
||||||
|
roster: this.dayRoster,
|
||||||
|
onClose: (): void => {
|
||||||
|
this.closeDayDetail();
|
||||||
|
},
|
||||||
|
onSaved: (): void => {
|
||||||
|
this.closeDayDetail();
|
||||||
|
this.load();
|
||||||
|
},
|
||||||
|
onDeleted: (): void => {
|
||||||
|
this.closeDayDetail();
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.transition(fbSheetTransition())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== #65:往年今天的通用摘要卡 ====================
|
||||||
|
/* 电影类已走 `openMovieDetail`(弹电影卡);
|
||||||
|
这里只剩 `trip_node` 等拿不到 tripId 的来源 ⇒ 给只读摘要。
|
||||||
|
⚠️ 弹错类型的卡比不弹更糟,所以宁可保守。 */
|
||||||
|
if (this.memDetail !== null) {
|
||||||
|
this.memorySheet(this.memDetail)
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== 新增电影 / 记一场(v86)====================
|
// ==================== 新增电影 / 记一场(v86)====================
|
||||||
if (this.mvCreating) {
|
if (this.mvCreating) {
|
||||||
Column()
|
Column()
|
||||||
@@ -1929,6 +2325,9 @@ export struct FbHomePage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
|
.onClick((): void => {
|
||||||
|
this.openMemoryDetail(m);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** `YYYY-MM-DD HH:MM:SS` → `YYYY-MM-DD`(只取日期,与网页版 `substr(...,0,10)` 一致) */
|
/** `YYYY-MM-DD HH:MM:SS` → `YYYY-MM-DD`(只取日期,与网页版 `substr(...,0,10)` 一致) */
|
||||||
@@ -1996,6 +2395,9 @@ export struct FbHomePage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
|
.onClick((): void => {
|
||||||
|
this.openMovieDetail(m.id, m);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 观影行的副标题:`2026-10-20 · 万达影城`(影院名空则只留日期) */
|
/** 观影行的副标题:`2026-10-20 · 万达影城`(影院名空则只留日期) */
|
||||||
@@ -2070,6 +2472,9 @@ export struct FbHomePage {
|
|||||||
.width('33.33%')
|
.width('33.33%')
|
||||||
.padding({ top: 8, bottom: 8 })
|
.padding({ top: 8, bottom: 8 })
|
||||||
.alignItems(HorizontalAlign.Center)
|
.alignItems(HorizontalAlign.Center)
|
||||||
|
.onClick((): void => {
|
||||||
|
this.openPersonDetail(p);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2148,6 +2553,9 @@ export struct FbHomePage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
|
.onClick((): void => {
|
||||||
|
this.openDayDetail(d);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user