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:「点击详情,框变大了,也应该有一个过渡动画。」