138 lines
5.5 KiB
Plaintext
138 lines
5.5 KiB
Plaintext
/**
|
||||
|
|
* 家书 FamilyBook · 「把一个人分享出去」(鸿蒙端)
|
|||
|
|
*
|
|||
|
|
* 🔴 用户 2026-10-18 原话:
|
|||
|
|
* 「再增加一个功能,就是下面有一个按钮,详情、编辑、关闭,还有分享。
|
|||
|
|
* 分享这个可以选择,比如说微信或什么的,以文本的形式,姓名、电话、邮箱,
|
|||
|
|
* 这几个以文本的形式分享给别人。」
|
|||
|
|
*
|
|||
|
|
* 为什么用**系统分享面板**(Share Kit)而不是自己做「复制 / 发微信」:
|
|||
|
|
* · 用户要的是"可以选择,比如说微信或什么的" —— 那正是系统分享面板的职责,
|
|||
|
|
* 它会把手机上**所有认领了纯文本**的应用都列出来(微信 / 短信 / 邮件 / 备忘录…)。
|
|||
|
|
* · 自己去拉起微信 = 依赖微信的包名与应用跳转白名单,换台手机就失效;
|
|||
|
|
* 而且要维护"哪些应用在装"的判断,纯属自找。
|
|||
|
|
*
|
|||
|
|
* ⚠️ 分享内容是**纯文本**(`general.plain-text`),不是 vCard:
|
|||
|
|
* 用户说得很清楚"以文本的形式,姓名、电话、邮箱"。vCard(.vcf)虽然
|
|||
|
|
* "更像通讯录",但很多聊天窗口里发出去只是一张卡片,反而看不见内容。
|
|||
|
|
* ⚠️ Share Kit 在**没有 Huawei 分享服务**的设备上可能不可用(`systemShare`
|
|||
|
|
* 的 syscap 是 `SystemCapability.Collaboration.SystemShare`)
|
|||
|
|
* ⇒ 必须兜一层**剪贴板**:弹不出面板时把同一段文本复制走并告诉用户,
|
|||
|
|
* "分享没反应"是最让人恼火的一种失败。
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
import { systemShare } from '@kit.ShareKit';
|
|||
|
|
import { uniformTypeDescriptor } from '@kit.ArkData';
|
|||
|
|
import { pasteboard } from '@kit.BasicServicesKit';
|
|||
|
|
import { common } from '@kit.AbilityKit';
|
|||
|
|
import { BusinessError } from '@kit.BasicServicesKit';
|
|||
|
|
import { hilog } from '@kit.PerformanceAnalysisKit';
|
|||
|
|
|
|||
|
|
const DOMAIN: number = 0x0000;
|
|||
|
|
const TAG: string = 'FbShare';
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 分享一个人需要的字段。
|
|||
|
|
*
|
|||
|
|
* ⚠️ 只收**纯字符串**(不收 `FbRosterPerson`):
|
|||
|
|
* 这个工具被"简要卡"和"详情卡"两处用,两处的数据类型不同(一个来自列表、
|
|||
|
|
* 一个来自 `/people/<id>`),让它们各自拼好再传进来最省事,
|
|||
|
|
* 也避免 `common/` 反向依赖 `data/`(本项目踩过循环 import)。
|
|||
|
|
*/
|
|||
|
|
export interface FbSharePerson {
|
|||
|
|
/** 显示名(昵称优先) */
|
|||
|
|
name: string;
|
|||
|
|
/** 电话(主号在前) */
|
|||
|
|
phones: string[];
|
|||
|
|
/** 邮箱('' = 不显示) */
|
|||
|
|
email: string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 拼分享文本:**姓名 / 电话 / 邮箱** —— 用户点名的就是这三项。
|
|||
|
|
*
|
|||
|
|
* 例:
|
|||
|
|
* ```
|
|||
|
|
* 杨永全
|
|||
|
|
* 电话:13800000000 / 0532-88888888
|
|||
|
|
* 邮箱:yyq@example.com
|
|||
|
|
* ```
|
|||
|
|
* ⚠️ 第一行**只放名字**(不带"姓名:"前缀):在微信里发出去,第一行就是
|
|||
|
|
* "这个人是谁",比"姓名:杨永全"更像一条通讯录信息。
|
|||
|
|
* ⚠️ 空项**整行不出现**:`邮箱:` 后面空着会让对方以为漏了。
|
|||
|
|
* ⚠️ **不要"顺手多给两行"**(单位 / 职务 / 生日…):用户原话只列了
|
|||
|
|
* 「姓名、电话、邮箱,这几个」。这是一段会被发到第三方应用里的文本,
|
|||
|
|
* 多写的每一个字段都是用户没想到会外发的信息。
|
|||
|
|
*/
|
|||
|
|
export function fbPersonShareText(p: FbSharePerson): string {
|
|||
|
|
const lines: string[] = [];
|
|||
|
|
lines.push(p.name);
|
|||
|
|
if (p.phones.length > 0) {
|
|||
|
|
const out: string[] = [];
|
|||
|
|
for (const s of p.phones) {
|
|||
|
|
if (s !== '') {
|
|||
|
|
out.push(s);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (out.length > 0) {
|
|||
|
|
lines.push('电话:' + out.join(' / '));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (p.email !== '') {
|
|||
|
|
lines.push('邮箱:' + p.email);
|
|||
|
|
}
|
|||
|
|
return lines.join('\n');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 拉起系统分享面板。
|
|||
|
|
*
|
|||
|
|
* @returns `''` = 已拉起(用户看到面板了);非空 = **没能拉起**,
|
|||
|
|
* 返回的是给用户看的一句话(此时内容已复制到剪贴板)。
|
|||
|
|
*
|
|||
|
|
* ⚠️ 为什么要返回"一句话"而不是布尔:失败时用户必须知道"内容去哪了",
|
|||
|
|
* 只说"失败"他会以为白点了。
|
|||
|
|
*/
|
|||
|
|
export async function fbShareText(ctx: common.UIAbilityContext,
|
|||
|
|
text: string, title: string): Promise<string> {
|
|||
|
|
try {
|
|||
|
|
const record: systemShare.SharedRecord = {
|
|||
|
|
utd: uniformTypeDescriptor.UniformDataType.PLAIN_TEXT,
|
|||
|
|
content: text,
|
|||
|
|
title: title,
|
|||
|
|
label: 'text',
|
|||
|
|
description: '',
|
|||
|
|
};
|
|||
|
|
const data: systemShare.SharedData = new systemShare.SharedData(record);
|
|||
|
|
const controller: systemShare.ShareController = new systemShare.ShareController(data);
|
|||
|
|
await controller.show(ctx, {
|
|||
|
|
selectionMode: systemShare.SelectionMode.SINGLE,
|
|||
|
|
// 纯文本推荐用缩略卡(DETAIL 那张大预览图是给图片/视频的)
|
|||
|
|
previewMode: systemShare.SharePreviewMode.DEFAULT,
|
|||
|
|
});
|
|||
|
|
return '';
|
|||
|
|
} catch (err) {
|
|||
|
|
const e: BusinessError = err as BusinessError;
|
|||
|
|
hilog.warn(DOMAIN, TAG, '系统分享不可用,退回剪贴板:%{public}s',
|
|||
|
|
e.message !== undefined ? e.message : JSON.stringify(err));
|
|||
|
|
return await fbCopyText(text);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 兜底:复制到剪贴板。
|
|||
|
|
*
|
|||
|
|
* @returns `''` = 复制成功;非空 = 失败提示
|
|||
|
|
*/
|
|||
|
|
export async function fbCopyText(text: string): Promise<string> {
|
|||
|
|
try {
|
|||
|
|
const pd: pasteboard.PasteData =
|
|||
|
|
pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, text);
|
|||
|
|
await pasteboard.getSystemPasteboard().setData(pd);
|
|||
|
|
return '已复制到剪贴板,可以粘贴到任何聊天窗口';
|
|||
|
|
} catch (err) {
|
|||
|
|
hilog.error(DOMAIN, TAG, '复制到剪贴板失败:%{public}s', JSON.stringify(err));
|
|||
|
|
return '分享失败(本机没有可用的分享服务)';
|
|||
|
|
}
|
|||
|
|
}
|