系列第 5 篇。本文讲一个很容易被低估的工具能力不是只把文本复制出去而是把费用明细做成图片方便发到群里确认。一、真实问题背景羽毛球活动结束后经常要把场地费、球费、男女折扣、人均费用发到群里。纯文本能用但信息层级弱直接截图整个屏幕又容易带上多余导航。更好的方式是只截取费用结果卡片生成一张干净图片再走系统分享。项目里的费用结果页同时支持复制文本和分享截图。复制用于快速粘贴截图用于群内确认和留档。二、目标与边界本文要实现的是一个完整分享闭环1. 用componentSnapshot截取指定组件。2. 用ImageKit把 PixelMap 编码成 PNG。3. 写入应用缓存目录。4. 用fileUri生成可分享 URI。5. 用ShareKit拉起系统分享面板。边界是本文不讲图片美化模板也不讲跨设备传输。后续如果要做“平板展示 手机分享”或“跨设备数据流转”可以复用这里的截图和文件输出能力。三、结果页结构核心页面是features/src/main/ets/fee/FeeResultPage.ets。页面先从FeeCalcService读取最近一次计算结果再把结果区域包在一个带 id 的组件里。const CAPTURE_ID: string fee_result_capture; aboutToAppear(): void { this.result FeeCalcService.loadLastResult(); } Column() { // 费用结果卡片和计算明细 } .id(CAPTURE_ID) .width(100%) .backgroundColor($r(app.color.bg_page));给组件设置稳定 id 是截图的前提。不要截整个页面否则会把顶部导航、按钮和说明文字都带进去。这篇文章涉及的源码对象比较集中-features/src/main/ets/fee/FeeResultPage.ets结果页 UI、复制、截图和分享入口。-common/src/main/ets/service/FeeCalcService.ets费用输入、结果计算和最近一次结果缓存。-common/src/main/ets/feedback/Feedback.etsToast 反馈封装。-entry/src/main/ets/pages/FeeResult.ets路由壳页面。-common/src/main/ets/router/Routes.ets费用页与结果页的路由常量。把这些文件列清楚有一个实际好处以后如果分享失败可以快速判断问题在计算服务、页面渲染、截图组件、文件写入还是系统分享调用而不是把所有问题都归到“ShareKit 不稳定”。四、复制文本分享图片之前页面先提供了更轻量的复制能力。它使用kit.BasicServicesKit的 pasteboard。const pasteboardData pasteboard.createData( pasteboard.MIMETYPE_TEXT_PLAIN, this.result.detailText ); pasteboard.getSystemPasteboard().setData(pasteboardData).then(() { Feedback.toast(费用结果已复制); });复制文本是低成本兜底。即使截图或分享失败用户仍然能把费用结果发出去。五、截图、编码与写入缓存图片分享的核心逻辑在shareResult()。先截取组件得到PixelMap再用image.createImagePacker()编成 PNG。const pixelMap: image.PixelMap await componentSnapshot.get(CAPTURE_ID); const packer: image.ImagePacker image.createImagePacker(); const opts: image.PackingOption { format: image/png, quality: 100 }; const buffer: ArrayBuffer await packer.packing(pixelMap, opts);接下来把二进制写入缓存目录并转换成文件 URI。const ctx: common.UIAbilityContext getContext(this) as common.UIAbilityContext; const filePath: string ${ctx.cacheDir}/fee_result_${Date.now()}.png; const file fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE); fs.writeSync(file.fd, buffer); fs.closeSync(file); const uri: string fileUri.getUriFromPath(filePath);缓存目录适合这种临时分享文件。它不是用户长期相册也不是业务数据持久化目录。六、调用 ShareKit有了 URI 后就可以构造systemShare.SharedData再通过ShareController展示分享面板。const shared: systemShare.SharedData new systemShare.SharedData({ utd: utd.UniformDataType.PNG, uri }); const controller: systemShare.ShareController new systemShare.ShareController(shared); await controller.show(ctx, { previewMode: systemShare.SharePreviewMode.DETAIL, selectionMode: systemShare.SelectionMode.SINGLE });这里选择 PNG 类型是因为费用结果是文字和卡片组合清晰度比有损格式更重要。七、取舍与风险截图分享有几个风险点。第一组件必须已经渲染完成否则componentSnapshot.get可能失败。第二截图区域不能太大否则图片体积和编码耗时都会上升。第三分享文件必须用系统可识别的 URI不能直接把内部路径丢给其他应用。因此项目把截图范围控制在结果卡片区域失败时用 Toast 提示并保留文本复制能力。这是一种工具 App 比较稳的设计主流程可用增强能力失败不造成数据丢失。还有一个容易忽略的边界分享图只是一种输出格式不能反过来成为业务数据源。真正的费用结果仍然来自FeeCalcService中的结构化结果截图只是当前结果的表达。这样后续如果要增加“复制 Markdown 明细”“生成群公告文案”“跨设备发送费用结果”都不需要从图片里反解析数据。八、验证命令构建验证 D:\HuaweiDevelopFormalStudy\DevEco Studio\tools\hvigor\bin\hvigorw.bat assembleHap --mode module -p productdefault --no-daemon验证时间2026-06-28。当前构建结果为BUILD SUCCESSFUL。手工验收建议覆盖无结果时提示、正常复制、正常分享、取消分享、结果页返回再进入、深色模式下截图可读。九、官方参考截图、文件和分享涉及多个 Kit正式开发应分别核对 ArkUI、ImageKit、CoreFileKit 和 ShareKit 文档。可从 HarmonyOS 应用开发文档 进入对应 API 参考。十、工程验收清单- 被截图区域有稳定 id。- 截图范围不包含无关按钮和导航。- PNG 写入缓存目录分享后不污染业务数据。-fileUri由真实文件路径生成。- 分享失败有提示复制文本仍可用。- 截图内容与当前费用结果一致。十一、小结分享能力的价值不在于“能调起系统面板”而在于把用户要表达的信息整理好。费用结果页先形成结构化明细再截图为图片最后走系统分享这才是完整闭环。后续做跨设备展示、手表端计分或俱乐部群分享时也可以沿用这种“数据清楚、输出稳定、失败兜底”的思路。十二、下一篇衔接接下来可以继续写 App Linking从聚合链接直达费用页、对阵页或计分页为后续跨设备入口和活动分享做准备。