首页联系人:点编辑就地弹框,不再跳名册 (#90)

用户:现在在首页上,弹出名册信息之后,点击编辑,怎么跳转到名册去了?
点击编辑应该直接弹框编辑。

改前 onEdit = closePersonDetail() + switchModule(people) —— 用户被甩到
名册模块,还得自己重新找到那个人再点编辑,绕了一圈。
当时注释的理由"首页没有人员编辑表单"站不住:首页**已经**在用
FbPersonEditSheet 做新增成员,同一个组件换参数就是编辑模式。

① onEdit 改为就地弹表单(同一个 FbPersonEditSheet,不另写一份,
   否则两处字段/校验/保存逻辑必分叉);
② 新增 @Local pfEditing: FbRosterPerson | null —— 用对象不用 boolean
   (表单要拿它回填,只存 true/false 还得再查一次库)。
   与 pfCreating 分开两个状态:新建时 person = null 是合法值,
   合并成一个 boolean + person? 会让两种情形撞车;
③ 取消 ⇒ 回详情卡(不是直接回首页)。用户刚才是从详情进来的,
   取消只是"我不改了",不该连"我在看谁"一起丢掉(与名册页同口径);
④ 保存 ⇒ 关表单 + reloadSideCards + refreshPersonDetail(id)。
   这里有个必须处理的坑:编辑表单 save() **只调接口、不写回本地库**,
   直接把编辑前快照塞回 pfDetail 会让用户看到"改完还是旧名字"。
   ⇒ 新增 refreshPersonDetail(),两级刷新(与 FbPeoplePage.openDetail 同一思路):
     ① byId() 读本地库(快,可能是旧值)
     ② fetchDetail() 打 GET /people/<id>(服务端权威值,回来原地替换)
     断网保持①、不弹错;读不到就什么都不做,不拿 null 覆盖正常的详情卡。

验证(真机 5NC0225B27004718):
- 首页点爸爸→详情卡→点编辑 → **就地弹出编辑表单**,页面仍是首页
  (下方最近观影/联系人/往年的今天都在);
- 表单标题「编辑资料」、姓名回填「杨培振」、生日(阳历)1955-03-30;
- 点取消 → 回到详情卡(含手机号/拨打/编辑),不是直接回首页。
hvigorw assembleHap BUILD SUCCESSFUL。

⚠️ 刻意没执行"保存":这是用户真实联系人数据;保存路径与名册页完全
   同一份代码(同组件同 onSaved),无需在首页重复踩。refreshPersonDetail
   的两级刷新未走真机保存验证 —— 若日后发现"保存后详情显示旧值"先查这里。
This commit is contained in:
2026-11-06 11:56:05 +08:00
parent 8f38cfa98b
commit 19e1333958
+125 -3
View File
@@ -363,6 +363,21 @@ export struct FbHomePage {
/** 「新增成员」表单开着 */ /** 「新增成员」表单开着 */
@Local pfCreating: boolean = false; @Local pfCreating: boolean = false;
/**
* 正在**编辑**的那个人(null = 编辑浮层没开)。
*
* 🔴 用户 2026-11-06:「现在在首页上,弹出名册信息之后,点击编辑,
* 怎么跳转到名册去了?点击编辑应该直接弹框编辑。」
* ⇒ 原来 `onEdit` 是"关掉详情卡 + `switchModule('people')`",
* 用户被整个甩到另一个模块,还得自己再找一遍那个人 —— 很难用。
* 现在改成就地打开同一张表单(与名册页同一个组件 `FbPersonEditSheet`)。
*
* ⚠️ 用**对象**而不是 `boolean`:编辑表单要拿它回填(姓名/电话/生日/工作经历…),
* 只存 true/false 还得再去库里查一次。
* ⚠️ 与 `pfCreating` 分开两个状态,不要合并成一个 `boolean + person?`:
* 新建时 `person = null` 是合法值(不是"没开"),合并后这两种情形会撞车。
*/
@Local pfEditing: FbRosterPerson | null = null;
/** 联系人卡要显示的人(本地库前 N 个) */ /** 联系人卡要显示的人(本地库前 N 个) */
@Local pfRecent: FbRosterPerson[] = []; @Local pfRecent: FbRosterPerson[] = [];
/** 名册总人数(卡片底部"共 N 人"用) */ /** 名册总人数(卡片底部"共 N 人"用) */
@@ -737,6 +752,48 @@ export struct FbHomePage {
this.pfDetail = null; this.pfDetail = null;
} }
/**
* 按 id 重读一个人,并(若该人正是当前详情卡)把详情卡换成最新值。
*
* 🔴 2026-11-06:首页支持就地编辑后必须补这一步。
* 编辑表单 `FbPersonEditSheet.save()` **只调接口、不写回本地库**
* (见那里的实现:PATCH 完直接 `onSaved()`)。所以:
* 把编辑前那份快照塞回 `pfDetail` 会让用户看到"改完还是旧名字"。
*
* ⇒ **两级刷新**,与名册页 `FbPeoplePage.openDetail()` 同一思路:
* ① `byId()` 先读本地库 —— 快,但可能是旧值(编辑不一定同步了库);
* ② `fetchDetail()` 再打 `GET /people/<id>` —— 服务端权威值,
* 回来就原地替换。断网时保持 ① 的显示,不弹错误。
*
* ⚠️ 读不到(那人被删了、或令牌为空)就**什么都不做**,
* 不要拿 null 去覆盖一个正常打开的详情卡。
* ⚠️ 用 `pfDetailId` 防"改 A 期间又去改 B"——响应回来时若详情卡
* 已经换成别人,这次结果就该丢弃(名册页用 detailToken 干这件事)。
*/
private async refreshPersonDetail(id: number): Promise<void> {
if (id <= 0) {
return;
}
try {
/* ① 本地库(快) */
const local: FbRosterPerson | null = await FbPersonRepo.inst().byId(id);
if (local !== null && this.pfDetail !== null && this.pfDetail.id === id) {
this.pfDetail = local;
}
/* ② 服务端权威值(准) */
const token: string = FB_TOKEN_STORE.get();
if (token === '') {
return;
}
const full: FbRosterPerson | null = await FbPersonRepo.inst().fetchDetail(id, token);
if (full !== null && this.pfDetail !== null && this.pfDetail.id === id) {
this.pfDetail = full;
}
} catch (err) {
hilog.warn(DOMAIN, TAG, '重读联系人失败(id=%{public}d):%{public}s', id, JSON.stringify(err));
}
}
/** /**
* 拨号(点联系人详情里的手机号时)。 * 拨号(点联系人详情里的手机号时)。
* *
@@ -2321,10 +2378,15 @@ export struct FbHomePage {
this.closePersonDetail(); this.closePersonDetail();
}, },
onEdit: (): void => { onEdit: (): void => {
/* 关掉详情卡,跳到名册去改 —— 首页没有人员编辑表单, /* 🔴 用户 2026-11-06:「点击编辑应该直接弹框编辑。」
与其在这里硬塞一份,不如交给名册(那是它的主场)。 */ 改前这里是「关掉详情卡 + `switchModule('people')`」——
把用户整个甩到名册模块,他还得自己再找一遍那个人。
⇒ 现在就地打开同一张表单(`FbPersonEditSheet`,与名册页同一个组件)。
⚠️ 先把详情卡收起来:两层浮层叠着会互相抢点击,
而且用户改完最想看到的是"改对了没"(见 `onSaved`)。 */
const who: FbRosterPerson | null = this.pfDetail;
this.closePersonDetail(); this.closePersonDetail();
this.app.switchModule('people'); this.pfEditing = who;
}, },
/* 🔴 用户 2026-11-06:「点开以后的弹窗,点击手机号可以直接拨打电话。」 /* 🔴 用户 2026-11-06:「点开以后的弹窗,点击手机号可以直接拨打电话。」
⚠️ 卡片只发号码,真正拨号走本页 `dial()`(理由见那里的注释)。 */ ⚠️ 卡片只发号码,真正拨号走本页 `dial()`(理由见那里的注释)。 */
@@ -2482,6 +2544,66 @@ export struct FbHomePage {
}) })
.transition(fbSheetTransition()) .transition(fbSheetTransition())
} }
// ==================== 联系人**编辑**(#89 后续)====================
/* 🔴 用户 2026-11-06:「现在在首页上,弹出名册信息之后,点击编辑,
怎么跳转到名册去了?点击编辑应该直接弹框编辑。」
改前 `onEdit` 是 `switchModule('people')` —— 把用户甩到名册模块。
现在就地弹表单,用**同一个组件** `FbPersonEditSheet`(与名册页一致):
不另写一份,否则两处字段/校验/保存逻辑必然分叉。
⚠️ 遮罩 + 卡片成对写(本项目所有浮层的约定):
遮罩在前、卡片在后,点遮罩等于取消。
⚠️ `isCreate` 不传(默认 false)⇒ 编辑模式,表单会用 `person` 回填。 */
if (this.pfEditing !== null) {
Column()
.width('100%')
.height('100%')
.backgroundColor(FB_MASK)
.transition(fbMaskTransition())
.onClick((): void => {
this.pfEditing = null;
})
FbPersonEditSheet({
person: this.pfEditing,
/* 已有标签供点选(与新建同一个来源;不传的话用户只能手打,
很容易出现"同事"和"同 事"两个标签) */
knownTags: this.personKnownTags(),
accent: this.peopleAccent(),
onCancel: (): void => {
/* 🔴 取消 ⇒ **回到详情卡**(不是直接回首页)。
用户刚才是从详情进来的,取消只是"我不改了",
不该连"我在看谁"一起丢掉 —— 与名册页 `onCancel` 同口径。 */
const who: FbRosterPerson | null = this.pfEditing;
this.pfEditing = null;
this.pfDetail = who;
},
onSaved: (): void => {
/* 保存成功 ⇒ 关表单、重读卡片数据,并把详情卡重新打开。
⚠️ 重新打开用的是**编辑前那份快照**(`pfEditing`),
它可能是旧值(比如刚改了昵称)。所以紧接着要按 id
从本地库重读一次,拿服务端权威值盖掉 —— 否则用户会看到
"改完还是旧名字"(服务端已入库,界面却是旧的)。 */
const who: FbRosterPerson | null = this.pfEditing;
this.pfEditing = null;
this.reloadSideCards();
if (who !== null) {
/* ⚠️ 不能 await:`onSaved` 是同步回调(`() => void`)。
用 `.catch()` 显式兜住,别让一个失败的读库变成未捕获的 Promise 拒绝。 */
this.refreshPersonDetail(who.id).catch((): void => {
});
}
},
onDeleted: (): void => {
/* 首页这张卡**不给删**(`canDelete: false`,避免误删),
走不到这里;置 null 只是兜底,别留一个打不开的浮层。 */
this.pfEditing = null;
}
})
.transition(fbSheetTransition())
}
} }
.width('100%') .width('100%')
.height('100%') .height('100%')