🚀🚀🚀🚀
鸿蒙生态为开发者提供海量的 HarmonyOS 模板/组件,助力开发效率原地起飞
★一键直达生态市场组件&模板市场 , 快速应用DevEco Studio插件市场集成组件&模板 ★
碎片化阅读时代,好用的读书 APP 能够带来“高质量阅读”体验
本期介绍阅读行业·电子书应用案例
👉覆盖 20+行业,点击查看往期案例汇总贴,持续更新,点击收藏!一键三连!常看常新!
【第 8 期】阅读行业· 电子书
一、 概述
1. 行业洞察
1) 行业诉求:
个性化不足:缺乏根据用户阅读偏好(题材/作者/难度)的智能推荐系统。
购买体验差:电子书与纸质书价格差异不合理,跨平台购买无法同步。
数据价值未挖掘:用户阅读习惯(速度/时间段/划线笔记)未形成深度分析报告。
社交功能薄弱:读书笔记分享渠道有限,缺乏作者/书友的互动交流平台。
2) 行业常用三方 SDK
说明:“以上三方库及链接仅为示例,三方库由三方开发者独立提供,以其官方内容为准”
岳鹰全景监控SDk 支付宝支付SDK 穿山甲广告SDK 快手联盟广告SDK 友盟SDK 腾讯微信SDK 腾讯优量汇 极光SDK
2. 案例概览(下载模板)
基于以上行业分析,本期将介绍鸿蒙生态市场阅读类行业模板——电子书阅读应用模板,为行业提供常用功能的开发案例,涵盖了从书籍获取、阅读体验到个性化管理的核心环节。
Stage 开发模型 + 声明式 UI 开发范式。
分层架构设计+ 组件化拆分,支持开发者在开发时既可以选择完整使用模板,也可以根据需求单独选用其中的业务组件。
本模板已集成华为账号、推送服务、支付、分享、广告等服务,只需做少量配置和定制即可快速实现相关功能。
本模板主要页面及核心功能如下所示:
阅读听书模板
|-- 阅读
| |-- 阅读界面
| |-- 章节目录
| |-- 亮度调整
| |-- 深色模式
| └-- 阅读设置
|-- 书架
| |-- 书籍列表
| |-- 浏览历史
| |-- 阅读时长记录
| |-- 搜索
| └-- 书籍管理
|-- 书城
| |-- 今日力荐
| └-- 猜你喜欢
|-- 分类
| |-- 男频/女频分类
| └-- 热门、都市、玄幻等分类
└-- 我的
|-- 用户登录
|-- 会员中心
| └-- 选择套餐并开通会员
|-- 我的账户
| └-- 选择套餐并充值书币
|-- 福利中心
|-- 活动中心
|-- 阅读记录
|-- 阅读偏好设置
| |-- 男频/女频偏好
| └-- 热门、都市、玄幻等偏好
|-- 联系客服
|-- 问题与反馈
| |-- 反馈问题
| └-- 反馈记录
└-- 设置
|-- 个人信息
| └-- 修改头像/昵称/电话
|-- 阅读设置
|-- 隐私设置
|-- 关于
|-- 清理缓存
└-- 退出登录
复制代码
二、 应用架构设计
1. 分层模块化设计
① 本实践暂时只支持直板机,为单 HAP 包形式,包含路由根节点、底部导航栏等。
① 本实践的基础特性层将应用底部导航栏的每个选项拆分成一个独立的业务功能模块。
② 每个功能模块都具备高内聚、低耦合、可定制的特点,支持产品的灵活部署。
① 本实践的公共能力层分为公共基础能力和可分可合组件,均打包为 HAR 包被上层业务组件引用。
② 公共基础能力包含日志、文件处理等工具类,公共类型定义,网络库,以及弹窗、加载等公共组件。
③ 可分可合组件将包含行业特点、可完全自闭环的能力抽出独立的组件模块,支持开发者在开发中单独集成使用,详见业务组件设计章节。
2. 业务组件设计
为支持开发者单独获取特定场景的页面和功能,本模板将功能完全自闭环的部分能力抽离出独立的行业组件模块,不依赖公共基础能力包,开发者可以单独集成,开箱即用,降低使用难度。
三、 行业场景技术方案
1. 阅读电子书
1) 场景说明
2) 技术方案
① 阅读控制器通过监听pageShow事件,增加章节购买,判断是否阅读完毕等功能
② 阅读中应用前后台切换。
③ 通过 navigation 的生命周期事件,控制阅读器实例。
2. 书城及分类搜索
1) 场景说明
2) 技术方案
3. 账号管理
1) 场景说明
2) 技术方案
① 通过Account Kit实现华为账号一键登录,并获取用户手机号,关联应用已有用户。
① 通过Ads Kit实现广告接入。
四、 模板代码
1. 工程结构(下载模板)
详细代码结构如下所示:
BookRead
├─BookRead/entry/src/main/ets
│ ├─entryability
│ │ └─EntryAbility.ets // 应用主入口能力
│ ├─entrybackupability
│ │ └─EntryBackupAbility.ets // 应用备份恢复能力配置
│ └─pages
│ ├─BookHomePage.ets // 应用主页框架
│ └─Index.ets // 应用入口默认首页
│
├─BookRead/commons/common/src/main/ets
│ ├─api
│ │ └─BookApi.ets // 图书API调用
│ ├─comp
│ │ ├─AggregatedPaymentPicker.ets // 集成支付选择器组件
│ │ ├─GlobalContext.ets // 全局上下文管理类
│ │ ├─TCInvoke.ets // 终端云服务调用封装
│ │ ├─TCLogger.ets // 终端云日志记录工具
│ │ ├─TCRouter.ets // 终端云路由导航管理
│ │ └─Toast.ets // 提示消息显示
│ ├─constant
│ │ └─Constants.ets // 常量定义文件
│ ├─model
│ │ ├─Book.ets // 图书数据模型
│ │ ├─BookSortInfo.ets // 图书排序信息模型
│ │ ├─BorrowInfo.ets // 借阅信息模型
│ │ ├─FeedbackRecordModel.ets // 反馈记录模型
│ │ ├─Member.ets // 会员信息模型
│ │ ├─PaymentModels.ets // 支付相关信息模型
│ │ ├─PreferenceInfo.ets // 用户偏好设置模型
│ │ ├─ReadSet.ets // 阅读模型
│ │ └─UserInfoModel.ets // 用户信息模型
│ ├─ui
│ │ ├─BookCard.ets // 图书卡片组件
│ │ ├─CommonUI.ets // 基础UI组件
│ │ └─TSearch.ets // 搜索功能相关组件
│ ├─utils
│ │ ├─DialogUtil.ets // 全局弹窗工具类
│ │ ├─EpubUtils.ets // Epub格式相关工具方法
│ │ ├─FileUtils.ets // 文件操作工具类
│ │ ├─MathUtil.ets // 数学计算工具类
│ │ ├─TimeUtils.ets // 时间处理工具类
│ │ ├─UserInfoUtil.ets // 用户信息处理工具类
│ │ └─WindowUtils.ets // 窗口或界面尺寸相关工具
│ └─viewmodel
│ ├─MemberCenterVM.ets // 会员中心逻辑模型
│ ├─RechargeRecordVM.ets // 充值记录逻辑模型
│ └─RechargeVM.ets // 充值功能业务逻辑模型
│
├─BookRead/feature/book_home/src/main/ets
│ ├─comps
│ │ ├─BookListCard.ets // 图书列表卡片式展示组件
│ │ ├─BookSwiperCard.ets // 图书轮播图展示卡片
│ │ ├─BookViewCard.ets // 图书详情视图卡片
│ │ ├─BookWaterFlowCard.ets // 图书瀑布流布局组件
│ │ └─HotRankCard.ets // 热门排行榜卡片
│ ├─viewModels
│ │ └─SearchViewModels.ets // 搜索功能逻辑模型
│ └─views
│ ├─BookListPage.ets // 图书列表页面
│ ├─BookViewListPage.ets // 带分类的图书列表视图页
│ └─SearchPage.ets // 全站搜索功能主页面
│
├─BookRead/feature/book_person/src/main/ets
│ ├─comp
│ │ ├─AccountCard.ets // 用户账户余额信息卡片
│ │ ├─ActivityCard.ets // 活动信息展示卡片
│ │ ├─CenterToolCard.ets // 个人中心工具卡片集
│ │ ├─FileSelect.ets // 文件选择器组件
│ │ ├─MembershipCard.ets // 会员身份展示卡片
│ │ ├─MyBalanceCard.ets // 余额信息展示卡片
│ │ ├─MyInfoCard.ets // 个人基本信息展示卡片
│ │ ├─OptionView.ets // 选项卡片组件
│ │ ├─SelectAvatarCard.ets // 头像选择器组件
│ │ ├─ServiceCard.ets // 服务入口卡片
│ │ ├─SignComp.ets // 签到卡片
│ │ └─UsrMsgCard.ets // 用户信息卡片
│ └─views
│ ├─AboutPage.ets // 关于我们信息页
│ ├─AccountPage.ets // 账户管理主页面
│ ├─ActivityPage.ets // 活动信息列表页
│ ├─DataCollectionPage.ets // 用户数据采集页
│ ├─DataSharingPage.ets // 数据共享协议页
│ ├─FeedbackPage.ets // 意见反馈表单页
│ ├─FeedbackRecordPage.ets // 反馈记录列表页
│ ├─IssueAndFeedbackPage.ets // 问题反馈页面
│ ├─LibraryPage.ets // 我的书馆页面
│ ├─LoginPage.ets // 用户登录页
│ ├─MemberAgreementPage.ets // 会员协议展示页
│ ├─MemberCenterPage.ets // 会员中心主界面
│ ├─PersonPage.ets // 个人中心主页
│ ├─PreferencePage.ets // 偏好设置页面
│ ├─PrivacyPage.ets // 隐私设置界面
│ ├─PrivacyPolicyPage.ets // 隐私政策声明页
│ ├─ReadSettingPage.ets // 阅读设置页
│ ├─RechargePage.ets // 充值中心主界面
│ ├─RechargeRecordPage.ets // 充值记录查询页
│ ├─WelfareCenter.ets // 福利中心页面
│ └─SettingPage.ets // 系统设置综合页面
│
├─BookRead/feature/book_read_kit/src/main/ets
│ ├─components
│ │ ├─BookCoverCard.ets // 图书封面展示组件
│ │ ├─ExpandableText.ets // 书评展示组件
│ │ ├─ReaderPage.ets // 阅读器核心页面
│ │ ├─ReaderTopCard.ets // 阅读器顶部控制栏
│ │ └─ShareBookCard.ets // 图书分享功能组件
│ ├─model
│ │ └─ReviewModels.ets // 图书数据模型
│ ├─pages
│ │ ├─MyReviewPage.ets // 我的评价页面
│ │ ├─WonderfulReviewPage.ets // 精彩书评页面
│ │ ├─WriteCommentPage.ets // 写评论页面
│ │ └─WriteReviewPage.ets // 全部书评页面
│ ├─utils
│ │ └─ReviewdataUtil.ets // 书籍评论工具类
│ └─viewmodel
│ └─WriteReviewVM.ets // 书籍评论数据模型
│
├─BookRead/feature/book_shelf/src/main/ets
│ ├─comps
│ │ ├─DeleteBottomCard.ets // 书籍删除确认底部弹窗
│ │ └─ManageGroupBottomCard.ets // 管理分组底部卡片
│ └─views
│ ├─BookGroupContent.ets // 书籍分组页
│ └─BookShelfPage.ets // 我的书架主界面
│
├─BookRead/feature/book_sort/src/main/ets
│ └─pages
│ └─BookSortPage.ets // 图书分类导航页面
│
├─BookRead/components/aggregated_payment/src/main/ets
│ ├─components
│ │ └─AggregatedPaymentPicker.ets // 集成支付选择器组件
│ ├─model
│ │ └─PaymentModels.ets // 支付相关信息模型
│ └─views
│ ├─AggregatedPaymentVM.ets // 支付组件数据模型
│ ├─MockApi.ets // 支付mock数据
│ ├─OrderInfoUtil.ets // 支付mock参数工具
│ └─SignUtils.ets // 支付mock签名工具
│
├─BookRead/components/base_common/src/main/ets
│ └─components
│ ├─ActionSheet.ets // 阅读器底部工具栏
│ └─UserInfo.ets // 阅读器用户信息
│
├─BookRead/components/incentive_ad/src/main/ets
│ ├─components
│ │ └─IncentiveAdvertising.ets // 广告位广告类
│ └─viewmodel
│ └─RewardAdStatusHandler.ets // 事件订阅
│
├─BookRead/components/reader_tool_bar/src/main/ets
│ └─components
│ └─PushKit.ets // 推送组件
│
├─BookRead/components/reader_tool_bar/src/main/ets
│ └─components
│ ├─ReaderToolBar.ets // 阅读器底部工具栏
│ └─SelectState.ets // 文本选择状态组件
│
└─BookRead/components/swiper_card/src/main/ets
├─components
│ ├─BookSingleSwiper.ets // 单图书滑动展示组件
│ ├─example.ets // 组件使用示例
│ └─TUISwiper.ets // 通用滑动卡片组件
└─model
├─Book.ets // 图书数据模型定义
└─LazyDataVM.ets // 数据逻辑模型
复制代码
2. 关键代码解读
本篇代码非应用的全量代码,只包括应用的部分能力的关键代码。
若需获取全量代码,请查看模板集成章节。
1) 定义翻页时触发的函数
① 判断章节是否购买,如果没有购买,跳转回上一页面。
② 判断是否阅读完成,如果阅读完成,跳转到评论界面并解锁写评论。
③ 记录阅读进度,并支持在目录中显示阅读的当前章节。
① 注册阅读器的 pageShow 回调函数。
this.readerComponentController.on('pageShow', (data: readerCore.PageDataInfo): void => {
hilog.info(0x0000, 'testTag', 'pageshow: data is: ' + JSON.stringify(data));
if (data.state === readerCore.PageState.PAGE_ON_SHOW) {
this.isLoading = false;
let spineItem = this.spineList[data.resourceIndex]
let catalogItem = this.catalogItemList.filter(item => item.resourceFile === spineItem.href)
this.curChapterId = catalogItem[0].catalogId
this.pageData = data;
if (this.lockedStatus[this.curChapterId]) {
if (this.userInfo!.continuousBuy && this.userInfo!.bookCoins > this.price) {
// 自动购买
this.updateBookCoinsAndSave(this.price)
this.lockedStatus[this.curChapterId] = false
} else {
if (!this.isShowBuySheet) {
this.isShowBuySheet = true
baseActionSheet.show({
id: 'buyChapter',
title: { title: '购买' },
height: SheetSize.FIT_CONTENT,
preferType: SheetType.CENTER,
customContent: () => {
this.buildBuyChapter(this.curChapterId)
},
onWillDismiss: (dismissSheetAction: DismissSheetAction) => {
this.isShowBuySheet = false
if (this.lockedStatus[this.curChapterId]) {
this.readerComponentController.startPlay(this.pageData!.resourceIndex, this.pageData!.startDomPos)
dismissSheetAction.dismiss();
}
}
})
}
}
} else {
this.pageData = data
}
} else if (data.state === readerCore.PageState.OPEN_BOOK_FAIL) {
hilog.info(0x0000, 'testTag', '检测到PAGE_OUT_OF_RANGE状态,跳转到精彩书评页面');
this.handleSwipeToReview();
}
});
复制代码
2) 消息通知
① 接收推送消息,并记录。
① 在 EntryAbility 中处理收到的消息,可以根据 action 的不同,实现不同的逻辑。
async onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): Promise<void> {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
// 获取Push Token
try {
const pushToken = await pushService.getToken();
AppStorage.setOrCreate('PushToken', pushToken)
hilog.info(0x0000, 'testTag', 'Succeeded in getting push token');
} catch (err) {
let e: BusinessError = err as BusinessError;
hilog.error(0x0000, 'testTag', 'Failed to get push token: %{public}d %{public}s', e.code, e.message);
}
if (want.action === 'com.test.action') {
AppStorage.setOrCreate('takeMessage', 1)
const pushData: PushData = {
pushContent: want.parameters?.pushContent as string,
time: want.parameters?.time as string,
read: false
}
AppStorage.setOrCreate('pushData', pushData)
}
// 上报Push Token到服务端
// axios.post("http://localhost:8080/upload", pushToken, {
// headers: {
// "Content-Type": "application/json"
// }
// })
// .then((response:AxiosResponse) => {
// console.log("服务器响应:", response.data);
// })
// .catch((err:BusinessError) => {
// console.error("请求失败:", err);
// });
}
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
if (want.action === 'com.test.action') {
AppStorage.setOrCreate('takeMessage', 1)
AppStorage.setOrCreate('coldBoot', 1)
const pushData: PushData = {
pushContent: want.parameters?.pushContent as string,
time: want.parameters?.time as string,
read: false
}
AppStorage.setOrCreate('pushData', pushData)
}
}
复制代码
3) 添加激励广告
① 接入华为激励广告,完成观看,以换取相应奖励。
① 创建订阅者,并添加回调。
// 订阅方法,需要在每次展示广告前调用
public registerPPSReceiver(): void {
if (this.subscriber) {
this.unRegisterPPSReceiver();
}
// 订阅者信息
const subscribeInfo: commonEventManager.CommonEventSubscribeInfo = {
events: ['com.huawei.hms.pps.action.PPS_REWARD_STATUS_CHANGED'],
// publisherBundleName被设置为"com.huawei.hms.adsservice",这意味着只有来自该包名的事件才会被订阅者接受和处理。
// 如果没有明确声明publisherBundleName,那么订阅者可能会收到来自其它包名的伪造事件,从而导致安全性问题或误导。
publisherBundleName: 'com.huawei.hms.adsservice'
};
// 创建订阅者回调
commonEventManager.createSubscriber(subscribeInfo, (err: BusinessError, commonEventSubscriber:
commonEventManager.CommonEventSubscriber) => {
if (err) {
hilog.error(0x0000, 'testTag', `Failed to create subscriber. Code is ${err.code}, message is ${err.message}`);
return;
}
hilog.info(0x0000, 'testTag', 'Succeeded in creating subscriber');
this.subscriber = commonEventSubscriber;
// 订阅公共事件回调
if (!this.subscriber) {
hilog.warn(0x0000, 'testTag', 'Need create subscriber');
return;
}
commonEventManager.subscribe(this.subscriber, (err: BusinessError, commonEventSubscriber:
commonEventManager.CommonEventData) => {
if (err) {
hilog.error(0x0000, 'testTag', `Failed to subscribe. Code is ${err.code}, message is ${err.message}`);
} else {
hilog.info(0x0000, 'testTag', 'Succeeded in subscribing data');
const status: string = commonEventSubscriber?.parameters?.[KEY_REWARD_STATUS];
switch (status) {
case AdStatus.AD_OPEN:
hilog.info(0x0000, 'testTag', 'Status is onAdOpen');
break;
case AdStatus.AD_CLICKED:
hilog.info(0x0000, 'testTag', 'Status is onAdClick');
break;
case AdStatus.AD_CLOSED:
hilog.info(0x0000, 'testTag', 'Status is onAdClose');
this.unRegisterPPSReceiver();
break;
case AdStatus.AD_REWARDED:
const rewardData: Record<string, string | number> = commonEventSubscriber?.parameters?.[KEY_REWARD_DATA];
const rewardType: string = rewardData?.rewardType as string;
const rewardAmount: number = rewardData?.rewardAmount as number;
this.userInfoFromStorage.bookCoins += rewardAmount;
AppStorage.setOrCreate('bookCoins', this.userInfoFromStorage.bookCoins);
this.bookFinish++
AppStorage.set('bookFinished', this.bookFinish);
emitter.emit({ eventId:15 })
hilog.info(0x0000, 'testTag', `Status is onAdReward. Type is ${rewardType}, Amount is ${rewardAmount}`);
hilog.info(0x0000, 'testTag',
`Status is userInfoFromStorage. Type is ${this.userInfoFromStorage.bookCoins}`);
break;
case AdStatus.AD_VIDEO_START:
hilog.info(0x0000, 'testTag', 'Status is onAdVideoStart');
break;
case AdStatus.AD_COMPLETED:
hilog.info(0x0000, 'testTag', 'Status is onAdCompleted');
break;
default:
break;
}
}
});
});
}
复制代码
3. 模板集成
本模板提供了两种代码集成方式,供开发者自由选用。
1) 整体集成(下载模板)
开发者可以选择直接基于模板工程开发自己的应用工程。
① 通过 IDE 插件创建模板工程,开发指导。
② 通过生态市场下载源码, 下载模板。
③ 通过开源仓访问源码,仓库地址。
将 commons/lib_common/src/main/ets/httprequest/HttpRequestApi.ets 文件中的 mock 接口替换为真实的服务器接口。
在 commons/lib_common/src/main/ets/httprequest/HttpRequest.ets 文件中将云侧开发者自定义的数据结构转换为端侧数据结构。
根据自己的业务内容修改模板,进行定制化开发。
2) 按需集成
若开发者已搭建好自己的应用工程,但暂未实现其中的部分场景能力,可以选择取用其中的业务组件,集成在自己的工程中。
① 通过 IDE 插件下载组件源码。开发指导
② 通过生态市场下载组件源码。 下载地址
以上是第 8 期“电子书阅读”行业优秀案例的内容,更多行业敬请期待~
欢迎下载使用行业模板“点击下载”,若您有体验和开发问题,或者迫不及待想了解 XX 行业的优秀案例,欢迎在评论区留言,小编会快马加鞭为您解答~
同时诚邀您添加下方二维码加入“组件模板活动社群”,精彩上新 &活动不错过!
👉 HarmonyOS 官方模板优秀案例系列持续更新, 点击查看往期案例汇总贴,方便查找!
👉【互动有礼】邀请你成为 HarmonyOS 官方模板产品经理,优化方案由你制定!点击参加
👉【集成有礼】HarmonyOS 官方模板集成创新活动,挥洒创意,赢精美大礼!点击参加
👉【HarmonyOS 行业解决方案】为各行业鸿蒙应用提供全流程技术方案。点击查看
评论