【上新啦】HarmonyOS 官方模板优秀案例 (第 2 期:新闻行业 · 综合新闻)

  • 2025-08-19
    北京
  • 本文字数:23112 字

    阅读完需:约 76 分钟

🚀🚀🚀🚀

鸿蒙生态为开发者提供海量的 HarmonyOS 模板/组件,助力开发效率原地起飞

★一键直达生态市场组件&模板市场 , 快速应用DevEco Studio插件市场集成组件&模板 ★

HarmonyOS 官方模板优秀案例第1期-便捷生活行业收到大家点赞

第 2 期-新闻行业小编加急上新啦!

本期案例内容更丰富,增加“预加载”、“AI 朗读”等行业创新特性介绍

👉覆盖 20+行业,本帖下方以汇总形式持续更新中,点击收藏!一键三连!常看常新!

【第 2 期】新闻行业· 综合新闻

一、 概述

1. 行业洞察

1) 行业诉求:

  • 新闻类应用中,核心关键是内容产出及内容分发效率,当前行业主要痛点是如何提升接收内容效率以及广告分发效率;

    跨端场景使用是新闻类应用的重要场景诉求,尤其是 PC、PAD、及车机端等全场景的使用及流转分发;

    如何借助新技术实现精准内容分发,做到精准生产、精准传播、精准服务,是新闻类应用开发者在思考的问题;

    新闻类应用需要为用户带来,自主可控、坚持贴近群众服务群众、易用、流畅、即时的应用使用体验。

2) 行业常用三方 SDK

SDK 链接:

支付宝 SDK 微信支付 SDK 银联 SDK 腾讯QQ SDK 新浪微博 SDK 极光PUSH SDK 友盟移动统计SDK 腾讯微信 SDK 高德地图 SDK 个推 Bugly ShareSDK 听云SDK

2. 优秀案例概览(下载模板

综合新闻行业模板是基于以上行业分析实现的参考,为综合新闻类应用提供了常用功能的开发样例,涵盖新闻分类、列表、详情,精选视频,互动评论,内容分享等多个实用场景。

  • Stage 开发模型 + 声明式 UI 开发范式

  • 分层架构设计+ 组件化拆分,支持开发者在开发时既可以选择完整使用模板,也可以根据需求单独选用其中的业务组件。

  • 集成华为账号、分享、扫码等一方服务和微信登录等三方 SDK,开发者仅需做少量配置和定制即可快速体验相关能力。

  • 集成预加载、推送服务、广告服务、AI 朗读等创新特性,构建更丰富更全面的模板体验。

本模板主要页面及核心功能如下所示:

 

二、 应用架构设计

1. 分层模块化设计

  • 产品定制层:专注于满足不同设备或使用场景的个性化需求,作为应用的入口,是用户直接互动的界面。

① 本实践暂时只支持直板机,为单 HAP 包形式,包含路由根节点、底部导航栏等。

  • 基础特性层:用于存放相对独立的功能 UI 和业务逻辑实现。

① 本实践的基础特性层将应用功能拆分成 6 个相对独立的业务功能模块。

② 每个功能模块都具备高内聚、低耦合、可定制的特点,支持产品的灵活部署。

  • 公共能力层:存放公共能力,包括公共 UI 组件、数据管理、外部交互和工具库等共享功能。

① 本实践的公共能力层分为公共基础能力和行业组件,均打包为 HAR 包被基础特性层的业务模块引用。

② 公共基础能力包含账号管理、动态布局等工具,公共类型定义,网络库,以及弹窗、加载等公共组件。

③ 行业组件将包含行业特点、可完全自闭环的能力抽出独立的组件模块,支持开发者在开发中单独集成使用,详见业务组件设计章节

本模板整体工程分包及介绍如下所示:

  • 详细工程结构可见工程结构章节。

ComprehensiveNews├──commons                                   //公共能力层-公共基础能力│  ├──lib_account                            //账号登录模块             │  ├──lib_common                             //基础模块             │  ├──lib_flex_layout                        //动态布局模块             │  ├──lib_native_components                  //动态布局-原生模块                  │  ├──lib_news_api                           //服务端api模块             │  ├──lib_news_feed_details                  //新闻详情模块               │  └──lib_widget                             //通用UI模块             ├──components                                //公共能力层-行业组件│  ├──module_advertisement                   //广告组件                     │  ├──module_channeledit                     //频道编辑组件│  ├──module_feedback                        //意见反馈组件│  ├──module_feedcomment                     //评论组件│  ├──module_highlight                       //高亮组件│  ├──module_imagepreview                    //图片预览组件│  ├──module_newsfeed                        //动态卡片组件│  ├──module_post                            //发帖组件│  ├──module_setfontsize                     //字体大小调节组件│  ├──module_share                           //分享组件│  ├──module_swipeplayer                     //视频组件│  └──module_text_reader                     //朗读组件            │      ├──features                                  //基础特性层│  ├──business_home                          //首页模块             │  ├──business_interaction                   //互动模块                           │  ├──business_mine                          //我的模块                        │  ├──business_profile                       //个人主页模块             │  ├──business_setting                       //设置模块             │  └──business_video                         //视频模块             └──products                                  //产品定制层   └──phone                                  //手机设备的主入口模块
复制代码

2.业务组件设计

为支持开发者单独获取特定场景的页面和功能,本模板将功能完全自闭环的部分能力抽离出独立的行业组件模块,不依赖公共基础能力包,开发者可以单独集成,开箱即用,降低使用难度。

例如开发者已搭建了一个自己的新闻工程,只想单独取用本模板中的广告功能,可根据本模板中 module_advertisement 模块的 README 文件中的快速入门步骤,将该模块单独集成在自己的工程中。

三、行业场景技术方案

1. 账号管理

1) 场景说明

支持华为账号一键登录及其他方式(账号密码登录、微信登录)。

用户登录后展示昵称和头像,点击用户信息栏可进入用户主页,查看并编辑个人信息和历史动态。

2) 技术方案

  • 华为账号一键登录

① 通过 Account Kit 实现华为账号一键登录,并获取用户手机号,关联应用已有用户。

  • 微信登录

① 根据鸿蒙接入指南接入微信 SDK,可通过跳转微信并获取微信用户授权进行登录。

  • 头像修改

① 通过 Scenario Fusion Kit 提供的选择头像Button快速拉起头像选择页面,供用户完成华为账号头像或其他头像的选择与展示。

3) 代码参考

  • 部分核心代码参见账号管理实现章节

2.内容分享

1) 场景说明

  • 用户可在首页-新闻详情,视频,互动等多个页面点击分享按钮,拉起分享面板。

  • 用户可通过以下方式实现内容分享。

① 点击微信、朋友圈、QQ 按钮拉起对应应用进行分享操作;

② 点击生成海报按钮将对应内容生成为携带二维码的海报,分享给上述应用或保存在本地;

③ 点击复制链接将 url 存入剪切板;

④ 点击系统分享拉起系统分享,支持华为分享,传入小艺智能空间等多种分享方式。

 

2) 技术方案

  • 微信分享

根据微信鸿蒙SDK接入指南接入微信 SDK 后,可通过分享功能开发手册调用指定 API 拉起微信并实现内容分享。

根据QQ鸿蒙SDK接入指南接入QQ SDK 后,可通过调用分享接口拉起 QQ 并实现内容分享。

  • 生成二维码海报并保存

通过系统组件QRCode将分享内容转换为字符串并写入,直接生成对应二维码。

通过弹窗授权保存媒体库资源的方式获取用户授权,并将海报存入相册,无需申请相册管理模块权限 ohos.permission.WRITE_IMAGEVIDEO。

  • 复制链接

通过 Basic Services Kit 中的pasteboard进行内容复制。

  • 系统分享

通过 Share Kit 接入系统分享面板并发起分享。

3) 代码参考

部分核心代码参见内容分享实现章节

3.扫码识别

1) 场景说明

用户可通过首页右上角的扫码按钮拉起扫码界面,通过摄像头或相册图片完成扫码操作。

应用识别出有效二维码信息可跳转并访问对应内容。

 

2) 技术方案

  • 使用 Scan Kit 提供的默认界面扫码能力,可实现系统级体验一致的扫码界面以及相册扫码入口。

  • 开发者也可以通过自定义界面扫码实现更定制化的界面样式和功能。

3) 代码参考

  • 部分核心代码参见扫码识别实现章节

4.视频播放

1) 场景说明

支持竖屏、全屏、长按倍速视频播放,支持当前视频播放完毕后自动续播下一个。

支持用户上下滑动查看上一个或下一个视频。

 

2) 技术方案

  • 使用Swiper组件+LazyForEach懒加载实现上下滑动视频翻页的效果,并最大限度的降低应用内存占用,提升滑动帧率。

  • 使用AVPlayer进行视频播放,实现播放事件的监听,及全屏、倍速等播放形式的设置。

  • 使用· gesture手势响应实现长按倍速等手势事件

3) 代码参考

  • 部分核心代码参见视频播放实现章节。

5. 应用设置

1) 场景说明

支持应用通用设置。

① 隐私设置:支持打开或关闭个性化推荐,关闭后首页顶部导航栏不展示推荐栏。

③ 播放与网络设置:支持修改非 WLAN 网络下的图片和视频播放设置,支持修改视频完播操作和自动播放设置。

④ 清理缓存:支持一键清理应用内缓存。

⑤ 夜间模式:支持手动开启或关闭应用内夜间模式。

⑥ 字体大小:支持手动调节应用内字体大小。

⑦ 检测版本:检测当前安装的应用版本是否为最新的应用市场在架版本。

 

2) 技术方案

  • 播放与网络设置

① 使用 Network Kit管理网络连接,获取并监控设备的网络状态,进行全局存储。

② 在图片展示或视频播放页面获取网络状态变量,在流量状态下进行提示或省流处理。

  • 清理缓存

① 通过 Core File Kit查询应用缓存大小并展示。

② 使用基础文件操作接口获取缓存目录后遍历目录内的文件并删除。

  • 夜间模式和字体大小

① 通过 Ability Kit 中环境变量的查询与设置实现应用深浅色模式和字体大小的切换。

  • 检测版本

① 通过 AppGallery Kit主动检查应用是否存在新版本,实现版本检测、显示更新提醒功能。

3) 代码参考

  • 部分核心代码参见应用设置实现章节。

四、 行业场景创新特性

1. 预加载

在应用启动前或初始化阶段,为避免出现首页内容加载慢、白屏等情况,可以通过接入预加载提升应用首开速度,提升用户体验。

本模板使用将首页推荐的新闻资源,在安装应用时通过预加载提前缓存到本地。用户首次访问应用时,直接从缓存中获取数据。减少了从服务器重新下载资源的时间,大大提升了应用首开速度。

同时集成了 H5 预加载组件FastWeb,对【我的-消息】中的私信对话 H5 界面进行预渲染、预编译,有效提升应用内 H5 的加载性能。

部分核心代码参见预加载实现章节

 

2. 广告

为实现流量变现,应用可以通过接入 Ads Kit 的流量变现服务实现横幅广告、开屏广告、贴片广告等多种形式的广告服务,向用户提供个性化的营销活动或商业广告。

本模板构建了开屏广告原生广告,多样化呈现广告信息。

部分核心代码参见广告实现章节

 

3. 内容推送

应用可以通过接入 Push Kit 实现从云端到终端的消息推送通道,向应用实时推送消息,提升用户的感知度和活跃度。

本模板集成推送通知消息能力,向用户推送指定新闻内容,可在终端设备的通知中心、锁屏、横幅等展示,用户点击后拉起应用。

部分核心代码参见内容推送实现章节

 

4. AI 朗读

用户在接收新闻资讯时,除了常规的文本浏览,还可以通过 AI 朗读来获取信息。

本模板通过接入 Speech Kit 的朗读控件,提供了一键播放新闻朗读,并实现朗读内容滚动展示的功能,在用户不方便查看屏幕文字或想通过音频收听内容的时候获取新闻信息。

部分核心代码参见 AI 朗读实现章节

 

五、 模板代码

1. 工程结构(下载模板

详细代码结构如下所示:

ComprehensiveNews├──commons│  ├──lib_account/src/main/ets                            // 账号登录模块             │  │    ├──components│  │    │   └──AgreePrivacyBox.ets                        // 隐私同意勾选                  │  │    ├──pages  │  │    │   ├──HuaweiLoginPage.ets                        // 华为账号登录页面│  │    │   ├──OtherLoginPage.ets                         // 其他方式登录页面│  │    │   └──ProtocolWebView.ets                        // 协议H5                  │  │    └──utils  │  │        ├──HuaweiAuthUtils.ets                        // 华为认证工具类│  │        ├──LoginSheetUtils.ets                        // 统一登录半模态弹窗│  │        └──WXApiUtils.ets                             // 微信登录事件处理类 │  ││  ├──lib_common/src/main/ets                             // 基础模块             │  │    ├──constants                                      // 通用常量 │  │    ├──datasource                                     // 懒加载数据模型│  │    ├──dialogs                                        // 通用弹窗 │  │    ├──models                                         // 状态观测模型│  │    ├──push                                           // 推送│  │    └──utils                                          // 通用方法     │  ││  ├──lib_flex_layout/src/main/ets                        // 动态布局模块             │  │    ├──components│  │    │   ├──AddPlateComp.ets                           // 添加车牌组件│  │    │   └──GuideListComp.ets                          // 指南列表组件                  │  │    ├──sdk                                            // 动态布局核心sdk        │  │    └──views│  │        └──FlexLayout.ets                             // 动态布局列表页 │  ││  ├──lib_native_components/src/main/ets                  // 动态布局-原生模块             │  │    ├──components│  │    │   ├──AdvertisementCard.ets                      // 广告卡片│  │    │   ├──FeedDetailsCard.ets                        // 动态卡片│  │    │   ├──HotListServiceSwitchCard.ets               // 热榜切换组件│  │    │   ├──HotNewsServiceCard.ets                     // 热榜新闻│  │    │   ├──LeftTextRightImageCard.ets                 // 左文右图卡片│  │    │   ├──TopTextBottomBigImageCard.ets              // 上文下图大卡│  │    │   ├──TopTextBottomImageCard.ets                 // 上文下图卡片│  │    │   ├──TopTextBottomVideoCard.ets                 // 上文下视频卡片│  │    │   └──VerticalBigImageCard.ets                   // 上下布局大图│  │    └─utils │  │        ├──Modifier.ets                               // 样式modifier│  │        ├──NodeBuilderConfig.ets                      // node配置类│  │        └──Utils.ets                                  // 工具方法             │  ││  ├──lib_news_api/src/main/ets                           // 服务端api模块             │  │    ├──constants                                      // 常量文件    │  │    ├──database                                       // 数据库 │  │    ├──observedmodels                                 // 状态模型  │  │    ├──params                                         // 请求响应参数 │  │    ├──services                                       // 服务api  │  │    └──utils                                          // 工具utils │  ││  ├──lib_news_feed_details/src/main/ets                  // 新闻详情模块             │  │    ├──components│  │    │   ├──ArticleDetailsFooter.ets                   // 文章底部区域│  │    │   ├──NewsContent.ets                            // 新闻主体内容│  │    │   └──RecommendArea.ets                          // 相关推荐                  │  │    └──views  │  │        └──ArticleFeedDetails.ets                     // 新闻详情页      │  │ │  └──lib_widget/src/main/ets                             // 通用UI模块             │       └──components│           ├──ButtonGroup.ets                            // 组合按钮│           ├──CustomBadge.ets                            // 自定义信息标记组件│           ├──EmptyBuilder.ets                           // 空白组件│           └──NavHeaderBar.ets                           // 自定义标题栏├──components│  ├──module_advertisement                                // 广告组件                     │  ├──module_channeledit                                  // 频道编辑组件│  ├──module_feedback                                     // 意见反馈组件 │  ├──module_feedcomment                                  // 评论组件│  ├──module_highlight                                    // 高亮组件│  ├──module_imagepreview                                 // 图片预览组件│  ├──module_newsfeed                                     // 动态卡片组件│  ├──module_post                                         // 发帖组件│  ├──module_setfontsize                                  // 字体大小调节组件│  ├──module_share                                        // 分享组件│  ├──module_swipeplayer                                  // 视频组件│  └──module_text_reader                                  // 朗读组件            ├──features│  ├──business_home/src/main/ets                          // 首页模块             │  │    ├──components│  │    │   └──NewsSearch.ets                             // 搜索页面                  │  │    └──pages│  │        └──HomePage.ets                               // 首页页面│  ││  ├──business_interaction/src/main/ets                   // 互动模块             │  │    ├─components│  │    │   ├──InteractionFeedCard.ets                    // 动态卡片│  │    │   ├──InterActionTabContent.ets                  // 动态列表│  │    │   ├──NoWatcher.ets                              // 暂无关注│  │    │   └──TopBar.ets                                 // 顶部Tab                  │  │    └──pages │  │        ├──InteractionPage.ets                        // 互动主页面│  │        └──PublishPostPage.ets                        // 发帖页面                  │  ││  ├──business_mine/src/main/ets                          // 我的模块             │  │    ├──components│  │    │   ├──BaseMarkLikePage.ets                       // 收藏点赞基础页面│  │    │   ├──CancelDialogBuilder.ets                    // 取消收藏点赞弹窗│  │    │   ├──CommentRoot.ets                            // 主评论│  │    │   ├──CommentSub.ets                             // 从属评论│  │    │   ├──FanItem.ets                                // 粉丝单元│  │    │   ├──IMItem.ets                                 // 私信单元│  │    │   ├──MessageItem.ets                            // 消息单元│  │    │   ├──SetReadIcon.ets                            // 标记已读│  │    │   └──UniformNewsCard.ets                        // 统一新闻卡片                │  │    └──pages │  │        ├──CommentPage.ets                            // 评论页面│  │        ├──HistoryPage.ets                            // 我的历史│  │        ├──LikePage.ets                               // 我的点赞│  │        ├──MarkPage.ets                               // 我的收藏│  │        ├──MessageCommentReplyPage.ets                // 评论与回复│  │        ├──MessageFansPage.ets                        // 新增粉丝│  │        ├──MessageIMChatPage.ets                      // 聊天页面│  │        ├──MessageIMListPage.ets                      // 私信列表│  │        ├──MessagePage.ets                            // 消息页面│  │        ├──MessageSingleCommentList.ets               // 全部回复页面│  │        ├──MessageSystemPage.ets                      // 系统消息│  │        └──MinePage.ets                               // 我的页面              │  ││  ├──business_profile/src/main/ets                       // 个人主页模块             │  │    ├──components│  │    │   ├──AuthorItem.ets                             // 作者单元│  │    │   ├──BaseFollowWatchPage.ets                    // 关注粉丝基础页面│  │    │   ├──DialogLikeNum.ets                          // 获赞弹窗│  │    │   ├──TabBar.ets                                 // 顶部Tab│  │    │   ├──UniformNews.ets                            // 统一新闻卡片│  │    │   ├──UserIntro.ets                              // 用户信息│  │    │   └──WatchButton.ets                            // 关注按钮                │  │    └──pages│  │        ├──FollowerPage.ets                           // 粉丝页面│  │        ├──PersonalHomePage.ets                       // 个人主页│  │        └──WatchPage.ets                              // 关注页面  │  ││  ├──business_setting/src/main/ets                       // 设置模块             │  │    ├──components│  │    │   ├──SettingCard.ets                            // 设置卡片│  │    │   └──SettingSelectDialog.ets                    // 设置选项弹窗               │  │    └──pages│  │        ├──SettingAbout.ets                           // 关于页面│  │        ├──SettingFont.ets                            // 字体大小设置页面│  │        ├──SettingH5.ets                              // H5页面│  │        ├──SettingNetwork.ets                         // 播放与网络设置页面│  │        ├──SettingPage.ets                            // 设置页面│  │        ├──SettingPersonal.ets                        // 编辑个人信息页面│  │        └──SettingPrivacy.ets                         // 隐私设置页面   │  │ │  └──business_video/src/main/ets                         // 视频模块             │       ├──components│       │   ├──CommentView.ets                            // 评论视图│       │   ├──Sidebar.ets                                // 侧边栏视图│       │   ├──TabHeaderView.ets                          // 顶部Tab视图│       │   └──VideoLayerView.ets                         // 视频外层操作层视图│       ├──pages│       │   ├──CommentViewPage.ets                        // 评论页面│       │   ├──VideoDetailPage.ets                        // 视频详情页│       │   └──VideoPage.ets                              // 视频首页│       └──views│           ├──FeaturedPage.ets                           // 精选页面               │           ├──FollowPage.ets                             // 关注页面│           ├──RecommendPage.ets                          // 推荐页面│           └──VideoSwiperPage.ets                        // 短视频轮播页面└──products   └──phone/src/main/ets                                  // phone模块        ├──common                                │   ├──AppTheme.ets                               // 应用主题色        │   ├──Constants.ets                              // 业务常量        │   ├──FormUtils.ets                              // 卡片Utils        │   └──Types.ets                                  // 数据模型        ├──components                            │   └──CustomTabBar.ets                           // 应用底部Tab        ├──pages           │   ├──AgreeDialogPage.ets                        // 隐私同意弹窗        │   ├──Index.ets                                  // 入口页面        │   ├──IndexPage.ets                              // 应用主页面        │   ├──PrivacyPage.ets                            // 查看隐私协议页面        │   ├──SafePage.ets                               // 隐私同意页面        │   ├──SplashPage.ets                             // 开屏广告页面        │   └──StartPage.ets                              // 应用启动页面        └──widget                                         // 服务卡片    
复制代码

2.关键代码解读

本篇代码非应用的全量代码,只包括应用的部分能力的关键代码。

若需获取全量代码,请查看模板集成章节。

1) 账号管理实现

  • 华为账号一键登录

​// ComprehensiveNews/commons/lib_account/src/main/ets/viewmodels/LoginVM.ets@ObservedV2export class LoginVM extends BaseViewModel {  // 封装登录按钮controller  controller: loginComponentManager.LoginWithHuaweiIDButtonController =    new loginComponentManager.LoginWithHuaweiIDButtonController()      .setAgreementStatus(loginComponentManager.AgreementStatus.NOT_ACCEPTED)      .onClickLoginWithHuaweiIDButton((error: BusinessError, response: loginComponentManager.HuaweiIDCredential) => {        if (response) {          Logger.info(TAG, 'onClickLoginWithHuaweiIDButton success, response: ' + JSON.stringify(response));        }        if (error) {          this.handleHuaweiLoginFail(error);          return;        }        this.huaweiLogin();      })    // ...}
复制代码

​// ComprehensiveNews/commons/lib_account/src/main/ets/pages/HuaweiLoginPage.ets@ComponentV2struct HuaweiLoginPage {  @Local vm: LoginVM = LoginVM.getInstance(false);  build() {    NavDestination() {	  // ...      Column() {        // 集成华为账号一键登录按钮        LoginWithHuaweiIDButton({          params: {            style: loginComponentManager.Style.BUTTON_CUSTOM,            extraStyle: {              buttonStyle: new loginComponentManager.ButtonStyle().loadingStyle({                show: true              }).backgroundColor($r('app.color.app_theme'))            },            loginType: loginComponentManager.LoginType.QUICK_LOGIN,            supportDarkMode: false,          },          controller: this.vm.controller,        }).id('login_with_huaweiId_button')      }    }  }}
复制代码

  • 微信登录

​// ComprehensiveNews/commons/lib_account/src/main/ets/viewmodels/LoginVM.ets@ObservedV2export class LoginVM extends BaseViewModel {  // 封装微信登录请求  async jumpWX() {    const req = new wxopensdk.SendAuthReq;    /**     * 填写实际的请求信息     */    req.isOption1 = false;    req.nonAutomatic = true;    req.scope = 'snsapi_userinfo';    req.state = 'none';    req.transaction = 'test123';    const isJumpSuccess = await wxApi.sendReq(getContext() as common.UIAbilityContext, req);    Logger.info(TAG, 'jump wechat result: ' + isJumpSuccess);    if (!isJumpSuccess) {      promptAction.showToast({ message: '未安装微信' });    }  }  // ...}
复制代码

2) 内容分享实现

  • 微信分享

​// ComprehensiveNews/components/module_share/src/main/ets/viewModel/WxShareViewModel.etsimport * as wXOpenSdk from '@tencent/wechat_open_sdk';// ...export class WxShareViewModel {  // 此处换成真实appid  wXApi = wXOpenSdk.WXAPIFactory.createWXAPI('wxc92d9d6570127a32')  private static _instance: WxShareViewModel  isWXApp: boolean = this.wXApi.isWXAppInstalled()  isWXAPPCallback(callback: Function) {    if (!this.isWXApp) {      AlertDialog.show({ message: JSON.stringify('请先安装微信') })    } else {      callback()    }  }  /**   * 分享网页   * @param text 文本内容   */  newsWebShare(qrCodeInfo: ShareOptions) {    this.isWXAPPCallback(async () => {      const webpageObject = new wXOpenSdk.WXWebpageObject()      /*       * 填写真实web地址       * */      webpageObject.webpageUrl = ''      const mediaMessage = new wXOpenSdk.WXMediaMessage()      mediaMessage.mediaObject = webpageObject      mediaMessage.title = qrCodeInfo.title      mediaMessage.description = `${qrCodeInfo.createTime}`      const thumbData = await getContext(this).resourceManager.getMediaContent($r('app.media.startIcon'))      const thumbPixel = image.createImageSource(thumbData.buffer).createPixelMapSync()      const thumbBuffer = await image.createImagePacker().packToData(thumbPixel, { format: 'image/png', quality: 100 })      mediaMessage.thumbData = new Uint8Array(thumbBuffer)      const req = new wXOpenSdk.SendMessageToWXReq()      req.scene = wXOpenSdk.SendMessageToWXReq.WXSceneSession      req.message = mediaMessage      this.wXApi.sendReq(getContext(this) as common.UIAbilityContext, req)    })  }  // ...}
复制代码

  • QQ 分享

​// ComprehensiveNews/components/module_share/src/main/ets/viewModel/QqShareViewModel.etsimport { IQQOpenApi, OpenApiConfig, QQOpenApiFactory, ShareResultType } from '@tencent/qq-open-sdk';// .../** * QQ分享 * 实际业务请填写实际appid */export class QqShareViewModel {  static appId = 102803516  private static _instance: QqShareViewModel  private static qqOpenApi: IQQOpenApi  shareData: QqShareData = new QqShareData()  public initQqShare(): IQQOpenApi {    if (!QqShareViewModel.qqOpenApi) {      let openApiOption: OpenApiConfig = {        forceEnableWeb: false,        autoHandleAuthResult: true,      }      QqShareViewModel.qqOpenApi =        QQOpenApiFactory.createApi(QqShareViewModel.appId, openApiOption)    }    return QqShareViewModel.qqOpenApi  }  /*   * 需要根据业务获取实际签名   * */  public genSign(appId: string | number, shareDataStr: string, timestamp: number, nonce: number) {    let totalStr = `POSTconnect.qq.com/share?appid=${appId}&nonce=${nonce}&ts=${timestamp}&${shareDataStr}`    return '业务签名'  }  public handleShareInfo(qrCodeInfo:ShareOptions) {    /*     * 生成时间戳并计算签名     * */    let timeStamp = Date.parse(new Date().toString()) / 1000    let nonce = Math.floor(Math.random() * 100000000 + 100)    let shareContent: QqShareContent = {      msg_style: 0,      title: qrCodeInfo.title,      url: '',      picture_url: qrCodeInfo.coverUrl as string,    }    let shareDataSign = this.genSign(QqShareViewModel.appId, qrCodeInfo.title, timeStamp, nonce)    this.shareData.timestamp = timeStamp    this.shareData.shareJson = JSON.stringify(shareContent)    this.shareData.nonce = nonce    this.shareData.shareJsonSign = shareDataSign  }  public async share(qrCodeInfo:ShareOptions) {    this.handleShareInfo(qrCodeInfo)    let result = await QqShareViewModel.qqOpenApi.share(2, this.shareData);    // ...  }}
复制代码

  • 系统分享

​// ComprehensiveNews/components/module_share/src/main/ets/utils/SystemShare.etsexport class PosterShare {  // ...  public async share(qrCodeInfo:ShareOptions) {    let shareData: systemShare.SharedData = new systemShare.SharedData({      utd: utd.UniformDataType.TEXT,      content: 'www.vmall.com/index.html?cid=128688',      title: qrCodeInfo.title,      description: `${qrCodeInfo.createTime}`,    });    let controller: systemShare.ShareController = new systemShare.ShareController(shareData);    let context = getContext(this) as common.UIAbilityContext;    controller.show(context, {      selectionMode: systemShare.SelectionMode.SINGLE,      previewMode: systemShare.SharePreviewMode.DETAIL,    }).then(() => {      console.info('ShareController show success.');    }).catch((error: BusinessError) => {      console.error(`ShareController show error. code: ${error.code}, message: ${error.message}`);    });    controller.on('dismiss',() => {      ShareEventDispatcher.dispatchToClose()    })  }}
复制代码

3)扫码识别实现

  • 二维码信息识别

​// ComprehensiveNews/commons/lib_common/src/main/ets/utils/NewsScan.etsexport class NewsScan {  static TAG: string = 'NewsScan'  public scan() {    // 定义扫码参数options    let options: scanBarcode.ScanOptions = {      scanTypes: [scanCore.ScanType.ALL],      enableMultiMode: true,      enableAlbum: true,    };    try {      // 可调用getContext接口获取当前页面关联的UIAbilityContext      scanBarcode.startScanForResult(getContext(this), options).then((result: scanBarcode.ScanResult) => {        // 解析码值结果跳转应用服务页        let resultParams: Record<string, string | NewsEnum> = JSON.parse(result.originalValue)        if(!resultParams?.articleId) {          promptAction.showToast({ message: '扫码错误,仅支持模板内文章海报生成的二维码' })          return        }        let routerParams = {          'id': resultParams?.articleId,        } as Record<string, string>        RouterToNews.routerToNewsById(routerParams.id)      }).catch((error: BusinessError) => {        promptAction.showToast({ message: '扫码错误' })        Logger.error(NewsScan.TAG, '[Scan CPSample]',          `Failed to get ScanResult by promise with options. Code:${error.code}, message: ${error.message}`);      });    } catch (error) {      promptAction.showToast({ message: '扫码错误' })      Logger.error(NewsScan.TAG, '[Scan CPSample]',        `Failed to start the scanning service. Code:${error.code}, message: ${error.message}`);    }  }}
复制代码

4) 视频播放实现

  • 视频跟手上下滑动效果实现

​// ComprehensiveNews/components/module_swipeplayer/src/main/ets/views/VideoSwipePlayer.ets@ComponentV2({ freezeWhenInactive: true })export struct VideoSwipePlayer {  // ...  build() {    Stack({ alignContent: Alignment.TopStart }) {      Swiper(this.swiperController) {        LazyForEach(this.datasource, (item: VideoPlayerData, index: number) => {          VideoPlayerView({            videoData: item,            index: index,            autoPlay: this.options?.autoPlay,            currentIndex: this.currentIndex,            totalCount: this.options?.totalCount,            swipePlayerController: this.swipePlayerController,            videoItemPlayerSession: this.videoItemPlayerSession,            videoNetwork: this.videoNetwork,            videoNetworkSetting: this.videoNetworkSetting,            videoLayerBuilder: this.videoLayerBuilder,            fullBtnBuilder: this.fullBtnBuilder,            pathStack: this.pathStack,          })        }, (item: VideoPlayerData, index: number) => item.getVideoId() + index);      }      .cachedCount(this.options?.cachedCount ?? 2)      .width('100%')      .height('100%')      .vertical(true)      .loop(false)      .curve(Curve.Ease)      .duration(300)      .indicator(false)      .backgroundColor(Color.Black)      .effectMode(this.currentIndex === 0 ? EdgeEffect.None : EdgeEffect.Spring)      .onChange((index: number) => {        this.currentIndex = index;        if (this.options?.swiperCallback?.onChange) {          this.options.swiperCallback?.onChange(index);        }      })      .onAnimationStart((index: number, targetIndex: number, extraInfo: SwiperAnimationEvent) => {        this.currentIndex = targetIndex;        if (this.options?.swiperCallback?.onAnimationStart) {          this.options.swiperCallback?.onAnimationStart(index, targetIndex, extraInfo);        }      })    }    .width('100%')    .height('100%')    .id('player')  }}
复制代码

5)应用设置实现

  • 清理缓存

​// ComprehensiveNews/commons/lib_common/src/main/ets/utils/FileUtils.etsexport class FileUtils {  /**   * 读取缓存大小,单位Byte   */  static getCache() {    return storageStatistics.getCurrentBundleStats().then((bundleStats: storageStatistics.BundleStats) => {      Logger.info(TAG, 'getCurrentBundleStats successfully:' + JSON.stringify(bundleStats));      Logger.info(TAG, 'appSize :' + bundleStats.appSize);      Logger.info(TAG, 'cacheSize :' + bundleStats.cacheSize);      Logger.info(TAG, 'dataSize :' + bundleStats.dataSize);      return bundleStats.cacheSize;    });  }  /**   * 清除缓存   * @param cacheDir   */  static clearCache() {    const cacheDir = getContext().cacheDir;    Logger.info(TAG, 'clear cache dir:' + cacheDir);    fs.listFile(cacheDir).then((filenames) => {      for (let i = 0; i < filenames.length; i++) {        let dirPath = cacheDir + '/' + filenames[i];        Logger.info(TAG, 'dir path:' + dirPath);        let isDirectory: boolean = false;        try {          isDirectory = fs.statSync(dirPath).isDirectory();        } catch (e) {          Logger.error(TAG, 'statSync error:' + JSON.stringify(e));        }        if (isDirectory) {          fs.rmdirSync(dirPath);        } else {          fs.unlink(dirPath).then(() => {            Logger.info(TAG, 'remove file succeed');          }).catch((err: Error) => {            Logger.error(TAG, 'remove file failed with error message: ' + err.message);          });        }      }    })  }  // ...}
复制代码

  • 检测版本

export class AppGalleryUtils {  /**   * 是否有更新版本   * @param context   * @returns   */  public static checkAppUpdate(context: common.UIAbilityContext): Promise<boolean> {    try {      return updateManager.checkAppUpdate(context)        .then((checkResult: updateManager.CheckUpdateResult) => {          Logger.info(TAG, 'Succeeded in checking Result updateAvailable:' + checkResult.updateAvailable);          return checkResult.updateAvailable === updateManager.UpdateAvailableCode.LATER_VERSION_EXIST;        }).catch((error: BusinessError) => {          Logger.error(TAG, `checkAppUpdate onError.code is ${error.code}, message is ${error.message}`);          return false;        });    } catch (error) {      Logger.error(TAG, `checkAppUpdate onError.code is ${error.code}, message is ${error.message}`);      return Promise.resolve(false);    }  }  /**   * 显示升级弹窗   * @param context   */  public static showUpdateDialog(context: common.UIAbilityContext) {    try {      updateManager.showUpdateDialog(context)        .then((resultCode: updateManager.ShowUpdateResultCode) => {          Logger.info(TAG, 'Succeeded in showing UpdateDialog resultCode:' + resultCode);        })        .catch((error: BusinessError) => {          Logger.error(TAG, `showUpdateDialog onError.code is ${error.code}, message is ${error.message}`);        });    } catch (error) {      Logger.error(TAG, `showUpdateDialog onError.code is ${error.code}, message is ${error.message}`);    }  }}
复制代码

6)预加载实现

  • 推荐新闻列表云函数

// ComprehensiveNews/preload/handler.jslet myHandler = async function (event, context, callback, logger) {  logger.info(`Input event: ${JSON.stringify(event)}`);  let hmSystem = {    status: 1,    msg: "success",  };  logger.info(`hmSystem: ${hmSystem}`);  let recommendList = [       // ...  ];  logger.info(`tableTitleArr: ${recommendList}`);  let result = { hmSystem, recommendList };  let res = new context.HTTPResponse(    result,    {      "faas-content-type": "json",    },    "application/json",    "200"  );  callback(res);};module.exports.myHandler = myHandler;
复制代码

  • 端侧集成安装预加载

​// ComprehensiveNews/products/phone/src/main/ets/phoneability/PhoneAbility.etsexport default class PhoneAbility extends UIAbility {  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {    // ...    this.functionPreload();  }  private functionPreload() {    // 获取安装预加载数据    try {      cloudResPrefetch.getPrefetchResult(cloudResPrefetch.PrefetchMode.INSTALL_PREFETCH)        .then((data: cloudResPrefetch.PrefetchResult) => {          let result: RequestListData[] =            (data.result as CloudRecommendListResult<RequestListData[]>).recommendList as RequestListData[]          AppStorage.setOrCreate('recommendList', result)          Logger.info('preloadTag',            `Succeeded in getting install prefetch data, result: ${JSON.stringify(data.result)}`);        }).catch(async (err: BusinessError) => {        Logger.error('preloadTag', `Failed to get install prefetch data, code: ${err.code}, message: ${err.message}`);        // 使用原有方式获取应用数据        let recommendList = await HomeServiceApi.queryHomeRecommendList('recommend');        AppStorage.setOrCreate('recommendList', recommendList)      })    } catch (err) {      Logger.error('preloadTag', `Failed to get install prefetch data, code: ${err.code}, message: ${err.message}`);    }  }  // ...}
复制代码

7)广告实现

  • 请求广告

​// ComprehensiveNews/components/module_advertisement/src/main/ets/components/Advertisement.ets@ComponentV2export struct AdvertisementComponent {  // 请求广告  private async loadAd(adId: string): Promise<void> {    // 1为全屏广告,8为横幅广告    if (this.adType === 1) {      // 广告请求参数      this.adRequestParams = {        // 广告位ID        adId: adId,        // 开屏广告类型        adType: this.adType,        // 请求的广告数量        adCount: 1,        // 开放匿名设备标识符        oaid: this.oaId,      };      this.adDisplayOptions = {        mute: true,      };      this.adOptions = {        allowMobileTraffic: 0,        adContentClassification: 'A',        tagForUnderAgeOfPromise: -1,        tagForChildProtection: -1,      };      // 广告请求回调监听      const adLoadListener: advertising.AdLoadListener = {        // 广告请求失败回调        onAdLoadFailure: (errorCode: number, errorMsg: string) => {          hilog.error(0x0000, 'testTag', `Failed to load ad. Code is ${errorCode}, message is ${errorMsg}`);        },        // 广告请求成功回调        onAdLoadSuccess: (ads: Array<advertising.Advertisement>) => {          clearTimeout(this.timeOutIndex);          if (this.isTimeOut) {            return;          }          hilog.info(0x0000, 'testTag', 'Succeeded in loading ad');          this.ad = ads[0];        },      };      // 创建AdLoader广告对象      const adLoader: advertising.AdLoader = new advertising.AdLoader(this.context);      this.timeOutHandler();      // 调用广告请求接口      adLoader.loadAd(this.adRequestParams, this.adOptions, adLoadListener);    } else {      this.adRequestParams = {        adId: adId,        adType: this.adType,        // 宽和高当前只支持360*57和360*144两种尺寸        adWidth: 360,        adHeight: 57,        oaid: this.oaId,      };      this.adDisplayOptions = {        refreshTime: 30000,      };      this.adOptions = {        allowMobileTraffic: 0,        tagForChildProtection: -1,        tagForUnderAgeOfPromise: -1,        adContentClassification: 'A',      };    }  }}
复制代码

8)内容推送实现

  • 获取云端推送

​// ComprehensiveNews/commons/lib_common/src/main/ets/push/PushUtils.etsexport class PushUtils {  /**   * 获取push推送token   **/  public static async getTokenSyn(): Promise<string> {    try {      const pushToken = await pushService.getToken();      return pushToken;    } catch (err) {      let e: BusinessError = err as BusinessError;      Logger.error('get token', 'Get push token catch error: %{public}d %{public}s');      return '';    }  }  /**   * 本地使用push rest api mock云端推送   **/  public static async pushMessage(articleList: ESObject) {    /*     * 这里的Authorization需要开发者自行获取,详情见readme     * */    axios.post('https://push-api.cloud.huawei.com/v3/388421841222475582/messages:send',      PushUtils.pushMessageParams(articleList), {        headers: {          'Content-Type': 'application/json',          'push-type': 0,          'Authorization': 'Bearer ',        },      })      .then((resp: AxiosResponse) => {        Logger.info('push success', JSON.stringify(resp))      })      .catch((err: AxiosError) => {        Logger.info('push err' + err)      })  }}
复制代码

9)AI 朗读实现

  • 朗读控件

​// ComprehensiveNews/components/module_text_reader/src/main/ets/views/ReadNewsComponent.ets// 当前受部分API接入限制,仅支持状态管理V1版本@Componentstruct ReadIcon {  /**   * 包名   */  @Prop bundleName: string = ''  /**   * 播放状态   */  @State readState: ReadStateCode = ReadStateCode.WAITING;  /**   * 用于显示当前页的按钮状态   */  @State isInit: boolean = false;  /**   * 播放加载数据,传入的格式id,title的text,author的text,date的text,bodyInfo内容,封面的PixelMap   */  @State readInfo: TextReader.ReadInfo | undefined = undefined  /**   * 用于显示播放的id   */  @Prop readInfoListString: string  @Prop localCoverImage:PixelMap  @State readInfoList: TextReader.ReadInfo[] | undefined = undefined  /**   * 用于显示播放的id   */  @Prop currentId: string  /**   * 初始化   */  async init() {    const readerParam: TextReader.ReaderParam = {      isVoiceBrandVisible: true,      businessBrandInfo: {        panelName: this.bundleName,        panelIcon: $r('app.media.startIcon'),      },    }    try {      let context: Context | undefined = this.getUIContext().getHostContext()      if (context) {        await TextReader.init(context, readerParam);        this.isInit = true;      }    } catch (err) {      console.error(`TextReader failed to init. Code: ${err.code}, message: ${err.message}`);    }  }  async aboutToAppear() {    this.readInfoList = JSON.parse(this.readInfoListString) as TextReader.ReadInfo[]    this.readInfoList.forEach((item) => {      if (item.id === this.currentId) {        this.readInfo = item      }    })    this.init();  }  onStateChanged = (state: TextReader.ReadState) => {    if (this.readInfo?.id === state.id) {      this.readState = state.state;    } else {      this.readState = ReadStateCode.WAITING;    }  }  // 设置操作监听  setActionListener() {    TextReader.on('stateChange', (state: TextReader.ReadState) => {      this.onStateChanged(state)    });    TextReader.on('requestMore', () => this.onStateChanged);    TextReader.on('eventPanel', (pe: TextReader.PanelEvent) => {      // 监听点击上一条或者下一条音频      if (pe.click === 'BPC_03' || pe.click === 'BPC_04') {        let event: emitter.InnerEvent = {          eventId: 1,          priority: emitter.EventPriority.IMMEDIATE,        };        let eventData: emitter.EventData = {          data: {            state: pe.id,          },        };        // Send an event with eventId 1 and the event content is eventData        emitter.emit(event, eventData);      }    });  }  build() {    Column() {      TextReaderIcon({ readState: this.readState })        .margin({ right: 20 })        .width(32)        .height(32)        .onClick(async () => {          try {            this.setActionListener();            if (this.readInfoList !== undefined) {              await TextReader.start(this.readInfoList, this.readInfo?.id);            }          } catch (err) {            console.error(`TextReader failed to start. Code: ${err.code}, message: ${err.message}`);          }        })    }    .justifyContent(FlexAlign.Center)    .height('100%')    .width('70%')    .alignItems(HorizontalAlign.End)  }}
复制代码

3.模板集成

本模板提供了两种代码集成方式,供开发者自由选用。

1)整体集成(下载模板

开发者可以选择直接基于模板工程开发自己的应用工程。

  • 模板代码获取:

① 通过 IDE 插件创建模板工程,开发指导

② 通过生态市场下载源码, 下载模板

③ 通过开源仓访问源码,仓库地址

  • 打开模板工程,根据 README 说明中的快速入门章节,将自己的应用信息配置在模板工程内,即可运行并查看模板效果。

 

  • 对接开发者自己的服务器接口,转换数据结构,展示真实的云侧数据。

① ComprehensiveNews/commons/lib_news_api/src/main/ets/services 文件中的接口当前为本地 mock 数据,开发者可根据业务需要替换为真实的服务器接口,并进行云侧数据结构与端侧数据结构的对接转换。

 

根据自己的业务内容修改模板,进行定制化开发。

2) 按需集成

若开发者已搭建好自己的应用工程,但暂未实现其中的部分场景能力,可以选择取用其中的业务组件,集成在自己的工程中。

  • 组件代码获取:

① 通过 IDE 插件下载组件源码。开发指导

② 通过生态市场下载组件源码。 下载地址

  • 下载组件源码,根据 README 中的说明,将组件包配置在自己的工程中。

 

  • 根据 API 参考和示例代码,将组件集成在自己的对应场景中。

以上是第二期“新闻行业-综合新闻”行业优秀案例的内容,更多行业敬请期待~

欢迎下载使用行业模板“点击下载”,若您有体验和开发问题,或者迫不及待想了解 XX 行业的优秀案例,欢迎在评论区留言,小编会快马加鞭为您解答~

同时诚邀您添加下方二维码加入“组件模板活动社群”,精彩上新 &活动不错过!

👉本系列持续更新,欢迎收藏本帖!

第一期:HarmonyOS官方模板优秀案例 | 便捷生活行业· 购物中心

第二期:HarmonyOS官方模板优秀案例 | 新闻行业· 综合新闻

👉HarmonyOS 组件模板相关推荐

  • 【活动 ing】HarmonyOS 组件/模板集成创新活动,报名时间截止 2025 年 8 月 30 日,点击查看

  • ·鸿蒙应用开发者激励计划 2025,点击查看