v73:桌面卡片刷新修复 + 首页日程/任务就地弹详情卡 + 一键导航
用户 2026-10-21 三条:
「卡片里面的数据并不刷新……应用里面的日程已经有了,但卡片上的日程却并不刷新」
「首页上的那个日程和任务,点击之后都应该像日程和任务里面的卡片一样弹出来,
不要进入到日程里面……有地址的还可以直接一键导航」
「任务也是一样,点击任务之后弹出来详情,可以标记它是不是完成了」
① 卡片不刷新(根因)
卡片进程(FormExtensionAbility)与应用进程靠 preferences 传 formIds / 翻页偏移。
SDK 明文禁止跨进程用 preferences,且 getPreferences() 在**进程内**缓存实例、
同名再取不读盘 ⇒ 应用进程可能永久持有空注册表 ⇒ pushAll() 首行
`if (list.length === 0) return` 直接返回 ⇒ 卡片只靠系统 30 分钟自刷。
修:store() 每次先 removePreferencesFromCacheSync 再取;并写死单写者纪律
(register/unregister/setOffset/clearOffset 只由卡片进程调,应用进程只读)。
② 首页点日程 → 弹 FbEventSheet(canEdit: false,首页不给编辑入口)
有地址的行整行可点 + 右侧「➤ 导航」胶囊;「编辑日程」才跳日历模块
(编辑是日历页的职责,不在首页复制第二份表单)。
③ 首页点任务 → 新的 FbTaskDetailSheet
状态圆可点(勾完成)、标题带删除线、底部「标记为已完成/未完成」+「改一改」。
状态**必须走 POST /tasks/<id>/status**(PATCH 会忽略 status)。
④ 一键导航 FbNav.ets:startAbility 隐式 Want(不是 openLink,那是 App Linking);
canOpenLink 需 module.json5 声明 querySchemes(未声明恒 false 且不抛错);
高德/百度/花瓣/腾讯按序探测,一个地图都没装则复制地点到剪贴板 + Toast。
⑤ 首页登记 FB_BACK(与其余 10 个模块一致),根容器 Stack(浮层是兄弟节点,
不能当 Column 子项排,否则卡片被挤出屏幕)。
静态守卫 905/0、v73 反向验证 17/0、assembleHap 通过、已装机启动。
This commit is contained in:
@@ -44,6 +44,7 @@ import { FB_DOCK_RESERVE } from '../../components/FloatDock';
|
||||
import { fbOnScroll, fbOnTouch } from '../../common/FbScroll';
|
||||
import { fbMaskTransition, fbSheetTransition, fbViewTransition } from '../../common/FbAnim';
|
||||
import { FB_BACK } from '../../common/FbBack';
|
||||
import { fbOpenNavigation } from '../../common/FbNav';
|
||||
|
||||
/** 视图粗细顺序(决定切视图时过渡的上下方向,见 `switchModeAnimated`) */
|
||||
const FB_CAL_VIEW_ORDER: string[] = ['month', 'week', 'agenda'];
|
||||
@@ -744,6 +745,29 @@ export struct FbCalendarPage {
|
||||
this.detailToken += 1; // 丢弃在途请求
|
||||
}
|
||||
|
||||
/**
|
||||
* 详情卡上点「导航」/ 点地点行 → 拉起地图。
|
||||
*
|
||||
* 🔴 v73(用户 2026-10-21「有地址的还可以直接一键导航」)。
|
||||
*
|
||||
* ⚠️ 地点取 `detailFull.location`(**优先**),退到列表那份 `detailBase.place`:
|
||||
* 详情是 `/events/<id>` 拉回来的权威值;列表那份是 `/calendar` 的精简条目。
|
||||
* 两者一般一致,但"刚在网页端改了地点、端上还没刷新"时会在详情里更新 ——
|
||||
* 所以详情有值就一定用详情。
|
||||
* ⚠️ 拿 `UIAbilityContext`(`startAbility` 只在这个上下文上有,
|
||||
* 用 `common.Context` 编译就报错 —— 名册拨号那次实测过)。
|
||||
*/
|
||||
private navigateDetail(): void {
|
||||
const placeOfFull: string = this.detailFull !== null ? this.detailFull.location : '';
|
||||
const placeOfBase: string = this.detailBase !== null ? this.detailBase.place : '';
|
||||
const place: string = placeOfFull !== '' ? placeOfFull : placeOfBase;
|
||||
const title: string = this.detailFull !== null ? this.detailFull.title
|
||||
: (this.detailBase !== null ? this.detailBase.title : '');
|
||||
const ctx: common.UIAbilityContext | undefined =
|
||||
this.getUIContext().getHostContext() as common.UIAbilityContext | undefined;
|
||||
fbOpenNavigation(ctx, place, title);
|
||||
}
|
||||
|
||||
/** 日程系列变了之后刷新数据 */
|
||||
private refresh(): void {
|
||||
this.detailBase = null;
|
||||
@@ -876,6 +900,9 @@ export struct FbCalendarPage {
|
||||
bookName: this.detailBase !== null ? this.detailBase.calName : '',
|
||||
bookColor: this.detailBase !== null ? this.detailBase.color : '#9AA0A6',
|
||||
canEdit: this.detailBase !== null && isEditableEvent(this.detailBase),
|
||||
onNavigate: (): void => {
|
||||
this.navigateDetail();
|
||||
},
|
||||
onClose: (): void => {
|
||||
this.closeDetail();
|
||||
},
|
||||
|
||||
@@ -37,18 +37,32 @@
|
||||
* `/home` 给 `memories` 字段,这里加第 ④ 块。
|
||||
*/
|
||||
|
||||
import { FbColors, FbMetric } from '../../common/Theme';
|
||||
import { FbColors, FbMetric, FB_MASK } from '../../common/Theme';
|
||||
import { FbAppState, fbAppState } from '../../common/Store';
|
||||
import { FbCalEvent, FbHomeData, FbMark, FbMemoryItem, FbRecentDay, FbTaskItem,
|
||||
FbWeather } from '../../model/Types';
|
||||
FbWeather, isEditableEvent } from '../../model/Types';
|
||||
import { common } from '@kit.AbilityKit';
|
||||
import { BusinessError } from '@kit.BasicServicesKit';
|
||||
import { hilog } from '@kit.PerformanceAnalysisKit';
|
||||
import { parseHome } from '../../data/FbParse';
|
||||
import { loadModule } from '../../data/FbModuleLoad';
|
||||
import { FB_CAL_SETTINGS, FbCalStyle } from '../../data/FbCalSettings';
|
||||
import { FbCalRepo, FbCalEventFull } from '../../data/FbCalRepo';
|
||||
import { FB_TOKEN_STORE } from '../../data/FbTokenStore';
|
||||
import { setTaskStatus } from '../../data/FbTaskRepo';
|
||||
import { FB_CARD_FORMS } from '../../data/FbCardForms';
|
||||
import { fbApi, FbApiResult } from '../../data/FbHttp';
|
||||
import { FbSectionTitle } from '../../components/Widgets';
|
||||
import { FbStatCell } from '../../components/FbStatCell';
|
||||
import { FbDayEventPanel } from '../../components/FbDayEventPanel';
|
||||
import { FbEventSheet } from '../../components/FbEventSheet';
|
||||
import { FbTaskDetailSheet } from '../../components/FbTaskDetailSheet';
|
||||
import { FB_DOCK_RESERVE } from '../../components/FloatDock';
|
||||
import { fbOnScroll, fbOnTouch } from '../../common/FbScroll';
|
||||
import { fbMaskTransition, fbSheetTransition } from '../../common/FbAnim';
|
||||
import { FB_BACK } from '../../common/FbBack';
|
||||
import { fbOpenNavigation } from '../../common/FbNav';
|
||||
import { fbModuleOf, FbModuleDef, fbThemeVars } from '../../model/Modules';
|
||||
|
||||
@ComponentV2
|
||||
export struct FbHomePage {
|
||||
@@ -103,12 +117,81 @@ export struct FbHomePage {
|
||||
*/
|
||||
@Local calStyle: FbCalStyle = 'timeline';
|
||||
|
||||
// ============================================================
|
||||
// v73(用户 2026-10-21):首页点日程 / 点任务 → **就地弹详情卡**
|
||||
// ============================================================
|
||||
/* 🔴 用户原话:
|
||||
「应用里面啊,就是首页上的那个日程和这个任务,点击之后都应该像那个日程和任务
|
||||
里面的卡片一样弹出来,不要进入到日程里面。比如说一个日程,我在首页上点击之后
|
||||
就能弹出和日程里面的卡片是一样的,看它里面的详情啊。呃,如果是有这个地址的,
|
||||
还可以直接一键导航。任务也是一样,点击任务之后弹出来详情,可以标记可以标记
|
||||
它是不是完成了什么之类的。」
|
||||
⇒ 下面这八组状态就是这件事的全部:日程详情卡 / 任务详情卡 / 各自的开关。
|
||||
⚠️ **不要**改回"点一下 `switchModule('calendar')`"—— 那是用户明确否掉的旧行为
|
||||
(原来 `onPick` 只有一行 `this.app.switchModule('calendar')`,
|
||||
点哪一条都跳到日程页,用户还得自己再找一遍那条)。 */
|
||||
|
||||
/** 摘要卡显示的那条日程(列表那份;非 null = 日程详情卡开着) */
|
||||
@Local evtBase: FbCalEvent | null = null;
|
||||
/** 日程详情(`/events/<id>` 的完整字段;null = 还在拉 / 拉不到) */
|
||||
@Local evtFull: FbCalEventFull | null = null;
|
||||
/** 日程详情加载中 */
|
||||
@Local evtLoading: boolean = false;
|
||||
/** 日程详情加载失败说明(空 = 没失败) */
|
||||
@Local evtError: string = '';
|
||||
/** 丢弃在途请求的令牌(连点两条日程时,先回来的那条不该覆盖后点的) */
|
||||
@Local evtToken: number = 0;
|
||||
|
||||
/** 摘要卡显示的那条任务(非 null = 任务详情卡开着) */
|
||||
@Local tskBase: FbTaskItem | null = null;
|
||||
/** 任务卡上显示的当前状态(勾完**立刻**变,不等网络) */
|
||||
@Local tskStatus: string = 'todo';
|
||||
/** 任务状态正在提交(防连点) */
|
||||
@Local tskBusy: boolean = false;
|
||||
/** 任务卡的提示(失败原因 / 成功说明) */
|
||||
@Local tskNote: string = '';
|
||||
|
||||
/**
|
||||
* 详情卡开着 ⇒ 底部悬浮快捷栏让位。
|
||||
*
|
||||
* ⚠️ 与任务页/日程页同一套机制(`sheetCount` 计数):悬浮栏在**更外层
|
||||
* `Index` 的 Stack** 里,层级比本页高 ⇒ 不让位就会盖住卡片底部的按钮、
|
||||
* 还把点击抢走(名册/日程页都踩过)。
|
||||
*/
|
||||
@Monitor('evtBase', 'tskBase')
|
||||
onDetailChanged(): void {
|
||||
this.app.sheetCount = (this.evtBase !== null || this.tskBase !== null) ? 1 : 0;
|
||||
}
|
||||
|
||||
aboutToAppear(): void {
|
||||
// 显示方式来自日程设置(启动时已预载进内存,可同步读)
|
||||
this.calStyle = FB_CAL_SETTINGS.getStyle();
|
||||
/* 🔴 v73:**首页也要登记返回手势**。
|
||||
原来首页根本没登记过 —— 因为以前它没有浮层,"返回"就该由 `Index` 处理
|
||||
(回上一个模块 / 退到桌面)。现在多了两张详情卡,不登记的话:
|
||||
卡开着按返回 ⇒ **卡片留在屏幕上**、模块却被切走了,回来还是那张卡。
|
||||
⚠️ 登记/注销必须成对(见 `common/FbBack.ets` 的纪律)。 */
|
||||
FB_BACK.register('home', (): boolean => this.backOnce());
|
||||
this.load();
|
||||
}
|
||||
|
||||
aboutToDisappear(): void {
|
||||
FB_BACK.unregister('home');
|
||||
}
|
||||
|
||||
/** 返回手势:先关详情卡,再交给 `Index`(返回 true = 已消费) */
|
||||
private backOnce(): boolean {
|
||||
if (this.evtBase !== null) {
|
||||
this.closeEvent();
|
||||
return true;
|
||||
}
|
||||
if (this.tskBase !== null) {
|
||||
this.closeTask();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 拉首页数据。
|
||||
*
|
||||
@@ -154,6 +237,192 @@ export struct FbHomePage {
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// v73:日程详情卡
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* 点一条日程 → 弹详情卡(**不跳模块**)。
|
||||
*
|
||||
* 逻辑与日程页的 `openEvent` 保持同一套(那是最成熟的一份,别另写一套):
|
||||
* ① 先用列表那条垫一份"壳"(标题/时间/地点/备注够画,用户立刻看到内容);
|
||||
* ② 可编辑条目再拉 `/events/<id>` 补 `rrule` / `reminder` / 精确 `ends_at`;
|
||||
* ③ 拉不到就保留壳 + 一行小字说明(**不要**把整张卡变成"读不到")。
|
||||
*
|
||||
* ⚠️ 衍生条目(农历/节气/节假日)**没有 `/events/<id>` 可拉**
|
||||
* (服务端 `ref_id` 是 0,它们不是 events 表里的行)⇒ 不要发那个请求。
|
||||
*/
|
||||
private openEvent(e: FbCalEvent): void {
|
||||
this.evtBase = e;
|
||||
this.evtFull = null;
|
||||
this.evtError = '';
|
||||
if (!isEditableEvent(e)) {
|
||||
this.evtLoading = false;
|
||||
this.evtFull = this.shellOf(e);
|
||||
return;
|
||||
}
|
||||
const token: string = FB_TOKEN_STORE.get();
|
||||
if (token === '') {
|
||||
this.evtLoading = false;
|
||||
this.evtFull = this.shellOf(e);
|
||||
return;
|
||||
}
|
||||
this.evtLoading = true;
|
||||
this.evtToken += 1;
|
||||
const my: number = this.evtToken;
|
||||
FbCalRepo.inst().fetchEvent(e.id, token).then((full: FbCalEventFull | null): void => {
|
||||
if (my !== this.evtToken) {
|
||||
return; // 已经有更新的一次点击了,这份丢弃
|
||||
}
|
||||
this.evtLoading = false;
|
||||
if (full === null) {
|
||||
this.evtError = '读不到详情';
|
||||
this.evtFull = this.shellOf(e);
|
||||
return;
|
||||
}
|
||||
this.evtFull = full;
|
||||
});
|
||||
}
|
||||
|
||||
/** 列表那条 → 完整字段的壳(只垫列表**已知**的字段,别编) */
|
||||
private shellOf(b: FbCalEvent): FbCalEventFull {
|
||||
const shell: FbCalEventFull = {
|
||||
id: b.id, calendar_id: b.calId, title: b.title, location: b.place,
|
||||
starts_at: b.date + ' ' + (b.allDay ? '00:00:00' : (b.time + ':00')),
|
||||
ends_at: b.endTime !== '' ? (b.date + ' ' + b.endTime + ':00') : '',
|
||||
all_day: b.allDay, note: b.note, rrule: '', reminder_minutes: '',
|
||||
status: 'confirmed', transp: false, created_by: 0
|
||||
};
|
||||
return shell;
|
||||
}
|
||||
|
||||
private closeEvent(): void {
|
||||
this.evtBase = null;
|
||||
this.evtFull = null;
|
||||
this.evtError = '';
|
||||
this.evtLoading = false;
|
||||
this.evtToken += 1; // 丢弃在途请求
|
||||
}
|
||||
|
||||
/** 详情卡上点「导航」/ 点地点行 → 拉起地图 */
|
||||
private navigateEvent(): void {
|
||||
const placeOfFull: string = this.evtFull !== null ? this.evtFull.location : '';
|
||||
const placeOfBase: string = this.evtBase !== null ? this.evtBase.place : '';
|
||||
const place: string = placeOfFull !== '' ? placeOfFull : placeOfBase;
|
||||
const title: string = this.evtFull !== null ? this.evtFull.title
|
||||
: (this.evtBase !== null ? this.evtBase.title : '');
|
||||
/* ⚠️ 必须是 `UIAbilityContext`(`startAbility` 只在这个上下文上有;
|
||||
用 `common.Context` 编译就报 "Property 'startAbility' does not exist",
|
||||
名册拨号那次实测过)。 */
|
||||
const ctx: common.UIAbilityContext | undefined =
|
||||
this.getUIContext().getHostContext() as common.UIAbilityContext | undefined;
|
||||
fbOpenNavigation(ctx, place, title);
|
||||
}
|
||||
|
||||
/**
|
||||
* 日程详情卡上点「编辑」→ 切到日程模块。
|
||||
*
|
||||
* ⚠️ 这里**仍然要跳模块**,而且是唯一该跳的地方:编辑是日程页的职责
|
||||
* (它有日历本选择、重复规则、提醒、地点联想那一整套表单)。
|
||||
* 在首页再复制一份编辑表单 = 同一件事两个实现,必然漂移。
|
||||
* 用户否掉的是"**看点详情就跳走**",不是"编辑也不许跳"。
|
||||
*/
|
||||
private editEventFromDetail(): void {
|
||||
this.closeEvent();
|
||||
this.app.switchModule('calendar');
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// v73:任务详情卡
|
||||
// ============================================================
|
||||
|
||||
private openTask(t: FbTaskItem): void {
|
||||
this.tskBase = t;
|
||||
this.tskStatus = t.status;
|
||||
this.tskBusy = false;
|
||||
this.tskNote = '';
|
||||
}
|
||||
|
||||
private closeTask(): void {
|
||||
this.tskBase = null;
|
||||
this.tskBusy = false;
|
||||
this.tskNote = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 勾完成 / 取消完成。
|
||||
*
|
||||
* 🔴🔴 **必须走 `/tasks/<id>/status`**(`setTaskStatus`)——
|
||||
* 服务端 `PATCH /tasks/<id>` 会把请求里的 status **原样忽略**
|
||||
* (见 `FbTaskRepo` 文件头),打 PATCH 会"看起来成功、状态没变"。
|
||||
* ⚠️ 这是本项目已经踩过一次的坑,别改成 updateTask。
|
||||
*
|
||||
* ⚠️ 已完成 → 取消完成统一回 `todo`(**不回 `doing`**):
|
||||
* 用户只想撤销一次误勾,把它标成"进行中"是替他做了他没表达的决定。
|
||||
* (与任务页 `toggleDone` 同一套规则。)
|
||||
*/
|
||||
private async toggleTask(): Promise<void> {
|
||||
const t: FbTaskItem | null = this.tskBase;
|
||||
if (t === null || this.tskBusy) {
|
||||
return;
|
||||
}
|
||||
this.tskBusy = true;
|
||||
this.tskNote = '';
|
||||
const next: string = this.tskStatus === 'done' ? 'todo' : 'done';
|
||||
const res: FbApiResult = await setTaskStatus(FB_TOKEN_STORE.get(), t.id, next);
|
||||
this.tskBusy = false;
|
||||
if (!res.ok) {
|
||||
this.tskNote = res.error !== '' ? res.error : '改状态失败,请稍后再试。';
|
||||
return;
|
||||
}
|
||||
/* 本地先把状态改掉(卡上立刻有反馈),再重拉首页数据对齐派生字段
|
||||
(逾期、看板计数、"近期任务"里这条还在不在,都是服务端算的)。
|
||||
⚠️ 顺序不能反 —— 等网络回来再改,用户会以为"没点上"又点一次。 */
|
||||
this.tskStatus = next;
|
||||
this.tskNote = next === 'done' ? '已标记完成' : '已改回未完成';
|
||||
this.load();
|
||||
// 桌面卡片上的数字(今天日程/在做/待办)也可能受影响 —— 顺手推一次
|
||||
this.pushCards();
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务卡上点「改一改」→ 切到任务模块。
|
||||
*
|
||||
* ⚠️ 与日程同理:改标题/改截止/看板/负责人是一整套表单,属于任务页的职责。
|
||||
* 用户要的"弹详情、能勾完成"这里已经给了;"改内容"跳过去是对的。
|
||||
*/
|
||||
private editTaskFromDetail(): void {
|
||||
this.closeTask();
|
||||
this.app.switchModule('tasks');
|
||||
}
|
||||
|
||||
/**
|
||||
* 把最新数据推给桌面上所有日程卡片。
|
||||
*
|
||||
* ⚠️ 不 await、失败只记日志:这是"顺手做的一件好事",绝不能影响勾选动作。
|
||||
*/
|
||||
private pushCards(): void {
|
||||
const ctx: common.Context | undefined = this.getUIContext().getHostContext();
|
||||
if (ctx === undefined) {
|
||||
return;
|
||||
}
|
||||
FB_CARD_FORMS.pushAll(ctx).catch((err: BusinessError): void => {
|
||||
hilog.warn(0x0000, 'FbHome', '刷新桌面卡片失败:%{public}s', err.message);
|
||||
});
|
||||
}
|
||||
|
||||
/** 日程模块主题色(详情卡环色 / 编辑按钮色) */
|
||||
private calAccent(): string {
|
||||
const m: FbModuleDef | undefined = fbModuleOf('calendar');
|
||||
return m !== undefined ? fbThemeVars(m.color).brand : '#A84A28';
|
||||
}
|
||||
|
||||
/** 任务模块主题色 */
|
||||
private taskAccent(): string {
|
||||
const m: FbModuleDef | undefined = fbModuleOf('tasks');
|
||||
return m !== undefined ? fbThemeVars(m.color).brand : '#A8574F';
|
||||
}
|
||||
|
||||
/** 问候语时段(与 Web 端 home.php 里那段 PHP 同口径) */
|
||||
private hello(): string {
|
||||
const h: number = new Date().getHours();
|
||||
@@ -250,9 +519,17 @@ export struct FbHomePage {
|
||||
}
|
||||
|
||||
build() {
|
||||
Scroll() {
|
||||
Column({ space: 0 }) {
|
||||
this.topBar()
|
||||
/* 🔴 v73:外层必须是 **Stack**(叠放),不能是 Column(排列)。
|
||||
浮层要"盖在页面上",而不是"排在页面后面" —— 后者会把 Scroll 挤出屏幕,
|
||||
表现是"点了日程详情打不开 / 打开了但整页空了"。
|
||||
(本项目 2026-10-20 真机事故就是这个:`Column { 整高遮罩; 卡片 }`
|
||||
把卡片挤出可视区,编译通过、`aboutToAppear` 照常跑、`dumpLayout` 里一个节点都没有。)
|
||||
⚠️ 这也是为什么**根容器是 Stack 而不是 Scroll**:遮罩与卡片必须是它的兄弟,
|
||||
不能塞进 Scroll 的 Column 里。 */
|
||||
Stack({ alignContent: Alignment.TopStart }) {
|
||||
Scroll() {
|
||||
Column({ space: 0 }) {
|
||||
this.topBar()
|
||||
|
||||
// ==================== ① 个人信息卡(问候 + 今天 + 天气)====================
|
||||
// 🔴 用户 2026-10-12 定的:这一块**要白色卡片**("这样显得好看")。
|
||||
@@ -413,8 +690,10 @@ export struct FbHomePage {
|
||||
emptyTitle: '今天没有安排',
|
||||
emptyHint: '过得轻松一点',
|
||||
onPick: (e: FbCalEvent): void => {
|
||||
// 点条目 ⇒ 切到日程模块(首页不做就地编辑:那是日程页的职责)
|
||||
this.app.switchModule('calendar');
|
||||
/* 🔴 v73(用户 2026-10-21):**就地弹详情卡**,不跳模块。
|
||||
旧实现是 `this.app.switchModule('calendar')` —— 点哪条都跳到日程页,
|
||||
用户还得自己再找一遍那条(用户明确否掉了这个行为)。 */
|
||||
this.openEvent(e);
|
||||
}
|
||||
})
|
||||
|
||||
@@ -541,18 +820,92 @@ export struct FbHomePage {
|
||||
Blank().height(FB_DOCK_RESERVE)
|
||||
}
|
||||
.width('100%')
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
// 🔴 滚动方向上报(驱动底部悬浮栏随滚收起 / 唤回)—— 见 common/FbScroll.ets
|
||||
.onDidScroll(fbOnScroll)
|
||||
|
||||
// 🔴 触摸方向兜底(内容不足一屏的页面滚不动,只能靠手指位移判断)
|
||||
// 与上面的 `onDidScroll` 互补、互不冲突 —— 见 common/FbScroll.ets
|
||||
.onTouch(fbOnTouch)
|
||||
.align(Alignment.TopStart)
|
||||
|
||||
// ==================== 日程详情卡(v73)====================
|
||||
// ⚠️ 遮罩与卡片是 Stack 的**兄弟**,不是 Column 的子项(见 build 开头那段说明)。
|
||||
if (this.evtBase !== null) {
|
||||
Column()
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor(FB_MASK)
|
||||
.transition(fbMaskTransition())
|
||||
.onClick((): void => {
|
||||
this.closeEvent();
|
||||
})
|
||||
|
||||
FbEventSheet({
|
||||
event: this.evtFull,
|
||||
loading: this.evtLoading,
|
||||
error: this.evtError,
|
||||
accent: this.calAccent(),
|
||||
bookName: this.evtBase !== null ? this.evtBase.calName : '',
|
||||
bookColor: this.evtBase !== null ? this.evtBase.color : '#9AA0A6',
|
||||
/* 🔴 首页**不给编辑入口**(`canEdit: false` 是有意的):
|
||||
用户要的是"看详情、能导航",而"编辑"要跳去日程模块 ——
|
||||
首页这颗按钮点下去会先关掉卡、再切模块,用户会看到"卡闪一下没了、
|
||||
页面换成了日程",那正是他说"不要进入到日程里面"的那种突兀感。
|
||||
⇒ 编辑请从日程模块进(那里点开同一条就带「编辑日程」)。
|
||||
⚠️ 也别把这条改成"跳过去并自动打开这条的编辑"—— 那要传一串参数穿两个模块,
|
||||
复杂度远大于收益,而且用户没提这个需求。 */
|
||||
canEdit: false,
|
||||
onNavigate: (): void => {
|
||||
this.navigateEvent();
|
||||
},
|
||||
onClose: (): void => {
|
||||
this.closeEvent();
|
||||
},
|
||||
onEdit: (): void => {
|
||||
this.editEventFromDetail();
|
||||
}
|
||||
})
|
||||
.transition(fbSheetTransition())
|
||||
}
|
||||
|
||||
// ==================== 任务详情卡(v73)====================
|
||||
if (this.tskBase !== null) {
|
||||
Column()
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor(FB_MASK)
|
||||
.transition(fbMaskTransition())
|
||||
.onClick((): void => {
|
||||
this.closeTask();
|
||||
})
|
||||
|
||||
FbTaskDetailSheet({
|
||||
task: this.tskBase,
|
||||
status: this.tskStatus,
|
||||
busy: this.tskBusy,
|
||||
note: this.tskNote,
|
||||
accent: this.taskAccent(),
|
||||
onToggle: (): void => {
|
||||
this.toggleTask();
|
||||
},
|
||||
onEdit: (): void => {
|
||||
this.editTaskFromDetail();
|
||||
},
|
||||
onClose: (): void => {
|
||||
this.closeTask();
|
||||
}
|
||||
})
|
||||
.transition(fbSheetTransition())
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.scrollBar(BarState.Off)
|
||||
.edgeEffect(EdgeEffect.Spring)
|
||||
// 🔴 滚动方向上报(驱动底部悬浮栏随滚收起 / 唤回)—— 见 common/FbScroll.ets
|
||||
.onDidScroll(fbOnScroll)
|
||||
|
||||
// 🔴 触摸方向兜底(内容不足一屏的页面滚不动,只能靠手指位移判断)
|
||||
// 与上面的 `onDidScroll` 互补、互不冲突 —— 见 common/FbScroll.ets
|
||||
.onTouch(fbOnTouch)
|
||||
.align(Alignment.TopStart)
|
||||
.alignContent(Alignment.TopStart)
|
||||
}
|
||||
|
||||
/** 天气地点行:`青岛市 · 崂山区 · 东南风 12 km/h` */
|
||||
@@ -599,6 +952,15 @@ export struct FbHomePage {
|
||||
* + 子任务进度 + 截止时间。
|
||||
* ⚠️ 看板色**必须**用真实派生色(`boardColor`)—— 用户 2026-10-08 报过
|
||||
* 「首页近期任务……颜色并没有跟看板颜色一致」。
|
||||
*
|
||||
* 🔴 v73(用户 2026-10-21):「任务也是一样,点击任务之后弹出来详情,
|
||||
* 可以标记它是不是完成了什么之类的。」
|
||||
* ⇒ 整行可点,弹 `FbTaskDetailSheet`(**不再跳任务模块**)。
|
||||
* ⚠️ 这是 `@Builder taskRow(t, isLast)` —— **两个参数**,本项目的著名反模式。
|
||||
* 它在这里是安全的,理由与 `memoryRow` 完全相同:
|
||||
* 列表由 `ForEach` 按 `t.id` 重建(数据变化 ⇒ 增删列表项 ⇒ 走重建路径,
|
||||
* 不依赖 Builder 参数刷新)。⚠️ 如果哪天出现"数据变了、这一行文字没变",
|
||||
* **先改成组件**(参照 `FbStatCell`),别在这个 Builder 上打补丁。
|
||||
*/
|
||||
@Builder
|
||||
taskRow(t: FbTaskItem, isLast: boolean) {
|
||||
@@ -640,6 +1002,15 @@ export struct FbHomePage {
|
||||
.width('100%')
|
||||
.padding({ top: 11, bottom: 11 })
|
||||
.alignItems(VerticalAlign.Center)
|
||||
/* 🔴 v73:整行点开任务详情卡。
|
||||
⚠️ 热区放在这个 `Row` 上(它含圆点/标题/状态/截止,一行都算),
|
||||
不是放在 `Column` 上 —— Column 还包着分隔线,点分隔线也会触发,
|
||||
两者视觉上不是"同一件东西"。
|
||||
⚠️ 手指反馈:`stateStyles` 用不到(这里不是 Button),
|
||||
所以靠"点开后有一张卡弹出来"作为反馈 —— 那已经足够强。 */
|
||||
.onClick((): void => {
|
||||
this.openTask(t);
|
||||
})
|
||||
|
||||
if (!isLast) {
|
||||
Divider()
|
||||
|
||||
Reference in New Issue
Block a user