From 8f38cfa98ba51a4d3513c6d8243ea22c10011b09 Mon Sep 17 00:00:00 2001 From: laoyang Date: Fri, 6 Nov 2026 11:47:47 +0800 Subject: [PATCH] =?UTF-8?q?=E8=81=94=E7=B3=BB=E4=BA=BA=E8=AF=A6=E6=83=85?= =?UTF-8?q?=E5=8D=A1=EF=BC=9A=E7=82=B9=E6=89=8B=E6=9C=BA=E5=8F=B7=E7=9B=B4?= =?UTF-8?q?=E6=8E=A5=E6=8B=A8=E5=8F=B7=20(#89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户:点开以后的弹窗,点击手机号可以直接拨打电话。 事实核查:详情卡一直没有这个能力。手机号是一行纯 Text (this.infoRow("phone", ...) 无 onClick)。而 FbPersonSheet 文件头 注释里写着它"自带手机号拨号入口"—— 那句是错的,实测点了没反应。 对照:名册的简要卡(FbPersonQuickSheet)v59e 就做了可点即拨, 详情卡一直缺 —— 同一份数据两处体验不一致。 ① FbPersonSheet 加 @Event onCall,默认实现为空 (TopBar.myDetailPanel 等宿主没接时仍可点、只是不拨,不崩不误触发)。 卡片**不自己拨号**:call.makeCall 需要 UIAbilityContext, 组件自己拿容易在浮层销毁瞬间拿到 undefined。 ② 手机号那一行改成 phoneRow(不复用 infoRow) 纯展示行加 onClick 会出现"整行看着一样、只有手机号能点", 用户不知道点哪。新行给明确交互外观:整行浅主题底 + 号码主题色加粗 + 右侧「拨打」胶囊,与 FbPersonQuickSheet.phoneRow 同一套语言。 补充电话(逗号拼接的多个号)仍是普通展示行 —— 没法一次拨、也不该猜拨哪个。 ③ 两个宿主接上:FbHomePage 新增 dial()、FbPeoplePage 复用已有 dial()。 实现刻意与名册页一致(不另写一套),并抄上关键约束: 必须 call.makeCall 而非 startAbility(tel:) —— 后者是隐式 Intent 会弹 「选择打开方式」(2026-10-15 真机误点进"同步日历");上下文必须是 UIAbilityContext;call.makeCall 不需要权限、只填号不代拨。 验证(真机 5NC0225B27004718,详情卡「爸爸/杨培振」): - UI 树:手机号[208,772]、号码 17706392406[208,818]、拨打胶囊[837,795]; - 点号码文字(342,842) → 拉起拨号盘 177 0639 2406 / 山东青岛 电信 / 老爸; - 点「拨打」胶囊(893,819) → 同样拉到该号码; - 全程**没有**「选择打开方式」框,证明走的是 call.makeCall 而非 tel: 隐式 Intent; - 返回 App 后详情卡状态保持。 hvigorw assembleHap BUILD SUCCESSFUL。 --- .../src/main/ets/components/FbPersonSheet.ets | 86 ++++++++++++++++++- entry/src/main/ets/pages/home/FbHomePage.ets | 38 ++++++++ .../main/ets/pages/people/FbPeoplePage.ets | 8 ++ 3 files changed, 130 insertions(+), 2 deletions(-) diff --git a/entry/src/main/ets/components/FbPersonSheet.ets b/entry/src/main/ets/components/FbPersonSheet.ets index ca6ff28..97e887b 100644 --- a/entry/src/main/ets/components/FbPersonSheet.ets +++ b/entry/src/main/ets/components/FbPersonSheet.ets @@ -90,7 +90,27 @@ export struct FbPersonSheet { }; /** - * 是否显示「删除」按钮。 + * 点手机号 → 请宿主去拨号。 + * + * 🔴 用户 2026-11-06:「点开以后的弹窗,点击手机号可以直接拨打电话。」 + * ⇒ 首页「联系人」点开的就是这张卡,而它的手机号原来只是一行 `Text`, + * 点了没任何反应。名册页的简要卡(`FbPersonQuickSheet`)早有这个能力, + * 详情卡一直缺 —— 两处体验不一致。 + * + * ⚠️ 与 `onCall` 同口径:卡片**不自己拨号**。`call.makeCall()` 需要 + * `UIAbilityContext`,而 `getUIContext().getHostContext()` 只有页面侧 + * 才拿得稳;组件自己拿容易在浮层销毁的瞬间拿到 undefined。 + * ⇒ 只把号码发出来,真正的拨号由宿主调它自己的 `dial()`。 + * (名册页 `FbPeoplePage.dial()` 里有完整说明:必须用 `call.makeCall` + * 而不是 `tel:` 隐式 Intent,否则弹「选择打开方式」。) + * + * ⚠️ 默认实现是**空**:`TopBar.myDetailPanel` 等宿主没接这个事件时, + * 号码仍然显示、仍然可点,只是点了不拨 —— 不会崩、也不会误触发。 + */ + @Event onCall: (phone: string) => void = (phone: string): void => { + }; + + /** 是否显示「删除」按钮。 * * ⚠️ 顶栏那个「我的详情」(`TopBar.myDetailPanel`)要传 `false`: * 删除自己既没意义、又很危险(服务端是物理删除,删完连账号都进不去)。 @@ -332,6 +352,63 @@ export struct FbPersonSheet { return start + ' ~ ' + end; } + /** + * 手机号那一行(**可点即拨**)。 + * + * 🔴 用户 2026-11-06:「点开以后的弹窗,点击手机号可以直接拨打电话。」 + * + * ⚠️ 为什么不复用 `infoRow`:那是个纯展示行,加上 `onClick` 以后会出现 + * "整行看着一样、只有手机号这一行能点"的情况,用户不知道该点哪。 + * 这里给它**明确的交互外观**:号码用主题色、右侧一枚「拨打」胶囊、 + * 整行浅色底 —— 与名册页简要卡(`FbPersonQuickSheet.phoneRow`)同一套语言, + * 两处点到同一个号码时观感一致。 + * ⚠️ 补充电话(`phones[1..]`)是**逗号拼接**的一串,没法一次拨多个号, + * 所以那一行仍用普通 `infoRow`,只把主号做成可点。 + */ + @Builder + phoneRow(phone: string) { + Row({ space: 10 }) { + Image(this.iconOf('phone')) + .width(15) + .height(15) + .renderMode(ImageRenderMode.Template) + .colorFilter(fbTint(FbTint.faint)) + Column({ space: 2 }) { + Text('手机号') + .fontSize(FbMetric.fontTiny) + .fontColor(FbColors.faint) + Text(phone) + .fontSize(FbMetric.fontSmall) + .fontColor(this.accentColor()) + .fontWeight(FontWeight.Medium) + .maxLines(1) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + } + .alignItems(HorizontalAlign.Start) + .layoutWeight(1) + + /* ⚠️ 不挂图标:本工程 `media/` 里没有电话图标(只有 user/notes/people/days/share + 那一批)。为一枚小提示去加新图(还得跟主题染两份色)不划算, + 文字胶囊更直白:"这里能拨"。 */ + Text('拨打') + .fontSize(10) + .fontColor(FbTint.white) + .backgroundColor(this.accentColor()) + .borderRadius(FbMetric.radiusPill) + .padding({ left: 8, right: 8, top: 2, bottom: 2 }) + } + .width('100%') + .padding({ left: 8, right: 8, top: 8, bottom: 8 }) + .backgroundColor(fbAlpha(this.accentColor(), 0.08)) + .borderRadius(FbMetric.radiusS) + .alignItems(VerticalAlign.Center) + .margin({ top: 4, bottom: 4 }) + .onClick((): void => { + /* ⚠️ 只发事件,不自己拨 —— 理由见 `onCall` 的说明 */ + this.onCall(phone); + }) + } + /** * 信息行(图标 + 标签 + 值)。 * @@ -487,10 +564,15 @@ export struct FbPersonSheet { // ⚠️ 每条都**只在有值时显示** —— 空行会让浮层显得"很多没填", // 而实际上大部分人的手机/邮箱/地址本来就是空的。 if (this.person !== null && this.person.phone !== '') { - this.infoRow('phone', '手机号', this.person.phone) + /* 🔴 2026-11-06:**主号改成可点即拨**(用户要求)。 + 原来这里是 `this.infoRow('phone', '手机号', this.person.phone)` —— + 一行纯文字,点了没有任何反应。 */ + this.phoneRow(this.person.phone) } // 🔴 补充电话(服务端下发的 `phones`,主号已在上面单独显示) if (this.person !== null && this.person.phones.length > 1) { + /* ⚠️ 这一行是**逗号拼接的多个号**,没法一次拨(也不该猜用户想拨哪个), + 所以保持普通展示行、不做成可点。 */ this.infoRow('phone', '其他电话', this.otherPhones()) } if (this.person !== null && this.person.org !== '') { diff --git a/entry/src/main/ets/pages/home/FbHomePage.ets b/entry/src/main/ets/pages/home/FbHomePage.ets index c021bc7..3768bd5 100644 --- a/entry/src/main/ets/pages/home/FbHomePage.ets +++ b/entry/src/main/ets/pages/home/FbHomePage.ets @@ -43,6 +43,9 @@ import { FbCalEvent, FbCalBook, FbCatOpt, FbHomeData, FbMark, FbMemoryItem, FbRe FbTaskItem, FbBoardOpt, FbTaskDetail, FbTaskMeta, taskKey, FbWeather, isEditableEvent, isEventWritable } from '../../model/Types'; import { common } from '@kit.AbilityKit'; +/* 🔴 2026-11-06:首页联系人详情要能**点号码直接拨**(用户要求), + 与名册页 `FbPeoplePage` 同一个系统 API。 */ +import { call } from '@kit.TelephonyKit'; import { BusinessError } from '@kit.BasicServicesKit'; import { hilog } from '@kit.PerformanceAnalysisKit'; import { parseHome } from '../../data/FbParse'; @@ -734,6 +737,35 @@ export struct FbHomePage { this.pfDetail = null; } + /** + * 拨号(点联系人详情里的手机号时)。 + * + * 🔴 用户 2026-11-06:「点开以后的弹窗,点击手机号可以直接拨打电话。」 + * + * ⚠️ 与名册页 `FbPeoplePage.dial()` **同一实现**,别在这里另写一套: + * 两处拨同一个号码,行为必须一致(弹不弹选择框、是否填到拨号盘)。 + * + * ⚠️ 必须用 `call.makeCall(ctx, 号码)`,**不是** `startAbility(dial + tel: URI)`: + * 后者是隐式 Intent —— 系统里凡声明该 action 的应用都会成为候选, + * 于是弹「选择打开方式」框(2026-10-15 真机实测就误点进"同步日历"); + * 而且各厂商对 `tel:` 处理不一致(有的拉拨号盘、有的直接拨)。 + * `call.makeCall` 是系统直达 API,无应用选择框、跨机型一致、**不需要权限**。 + * ⚠️ 它仍只是"打开拨号盘并填好号码",最后一下由用户按 —— 合规要求满足。 + * + * ⚠️ 上下文必须是 **`UIAbilityContext`**(`startAbility`/`makeCall` 只在这个类型上); + * 用 `common.Context` 会编译报 "Property ... does not exist"。 + */ + private dial(phone: string, who: string): void { + const ctx: common.UIAbilityContext | undefined = + this.getUIContext().getHostContext() as common.UIAbilityContext | undefined; + if (ctx === undefined) { + return; + } + call.makeCall(ctx, phone).catch((err: BusinessError): void => { + hilog.warn(DOMAIN, TAG, '拨号失败(%{public}s):%{public}s', who, JSON.stringify(err)); + }); + } + /** * 这条纪念日是不是「派生生日」(不能编辑的那种)。 * @@ -2293,6 +2325,12 @@ export struct FbHomePage { 与其在这里硬塞一份,不如交给名册(那是它的主场)。 */ this.closePersonDetail(); this.app.switchModule('people'); + }, + /* 🔴 用户 2026-11-06:「点开以后的弹窗,点击手机号可以直接拨打电话。」 + ⚠️ 卡片只发号码,真正拨号走本页 `dial()`(理由见那里的注释)。 */ + onCall: (phone: string): void => { + const who: string = this.pfDetail !== null ? this.pfDetail.shown : ''; + this.dial(phone, who); } }) .transition(fbSheetTransition()) diff --git a/entry/src/main/ets/pages/people/FbPeoplePage.ets b/entry/src/main/ets/pages/people/FbPeoplePage.ets index 8bbad84..bfb2c80 100644 --- a/entry/src/main/ets/pages/people/FbPeoplePage.ets +++ b/entry/src/main/ets/pages/people/FbPeoplePage.ets @@ -618,6 +618,14 @@ export struct FbPeoplePage { ⚠️ 走的还是同一个 `confirmDelete()`(二次确认 + 调接口 + 刷新名册)。 */ onDelete: (): void => { this.confirmDelete(); + }, + /* 🔴 用户 2026-11-06:「点开以后的弹窗,点击手机号可以直接拨打电话。」 + ⇒ 详情卡补上 `onCall`。这张卡原来只有纯文字,点了没反应; + 简要卡(`FbPersonQuickSheet`)早就能拨 —— 两处不一致。 + ⚠️ 复用本页已有的 `dial()`(`call.makeCall`,见那里的长注释)。 */ + onCall: (phone: string): void => { + const who: FbRosterPerson | null = this.detailPerson; + this.dial(phone, who !== null ? who.shown : ''); } }) /* 🔴 用户 2026-10-19:「点击详情,框变大了,也应该有一个过渡动画。」