
🚀🚀🚀🚀
鸿蒙生态为开发者提供海量的 HarmonyOS 模板/组件,助力开发效率原地起飞
★一键直达生态市场组件&模板市场 , 快速应用DevEco Studio插件市场集成组件&模板 ★
在之前第三期为大家介绍了“教育备考案例”(^_^点这里立即回顾)
本期带来新上模板“课堂应用模板”的案例解析
👉覆盖 20+行业,点击查看往期案例汇总贴,持续更新,点击收藏!一键三连!常看常新!
【第 10 期】教育行业 ·课堂应用
一、 概述
1. 行业洞察
1) 行业诉求:
精准分发:面对不同的受众教育类应用有不同的业务场景,产出适配内容、精准题库等,并以高效且契合场景的方式进行分发,最终实现优质教育资源的有效传递。
高效流畅、操作敏捷是教育类应用不同场景重要诉求,在线学习、考试等场景出现卡顿会严重影响学习、考试的。
需具备智能刷题与精准辅导能力:基于大数据和算法,依据答题情况判断用户知识掌握状况,动态调整出题难度,推送契合的学习内容。
多端协同能力:实现手机、平板、PC 端数据实时互通,提供离线功能,便于用户利用碎片化时间用于刷题或知识点复习,并可在线后同步学习进度。
2) 行业常用三方 SDK
腾讯浏览服务SDK 高德地图定位 百度地图SDK 极光Push SDK 火山引擎SDK 同盾SDK 友盟SDK 微信支付SDK 支付宝SDK 听云SDK 福昕 PDF SDK 网易云信 IM SDK 保利威视频直播webSDK 支付宝分享SDK
2. 案例概览(下载模板)
基于以上行业分析,本期将介绍鸿蒙生态市场教育类行业模板——课堂应用模板,为行业提供常用功能的开发案例,模板主要分首页、学习、我的三大模块。
Stage 开发模型 + 声明式 UI 开发范式。
分层架构设计+ 组件化拆分,支持开发者在开发时既可以选择完整使用模板,也可以根据需求单独选用其中的业务组件。
本模板已集成华为账号、广告等服务,只需做少量配置和定制即可快速实现华为账号的登录、媒体播放等功能。
本模板主要页面及核心功能如下所示:
教育学习应用模板 ├──首页 │ ├──顶部栏-搜索 │ │ ├── 搜索 │ │ └── 职级分类选择 │ │ │ ├──课堂列表 │ │ ├── 动态布局 │ │ ├── 信息流 │ │ └── 功能区 │ └──课堂详情 │ ├── 图文 │ ├── 收藏、评论 │ ├── 分享(微信、朋友圈、QQ、生成海报、复制链接等) │ └── 视频 ├──学习 │ ├──顶部栏 │ │ └── 搜索 │ ├──课堂列表 │ └──视频详情页 │ ├── 竖屏播放 │ ├── 横屏播放 │ ├── 暂停、播放、进度调节、倍速、选集、画中画 │ └── 收藏、评论、分享 │ └──我的 ├──登录 │ ├── 华为账号一键登录 │ ├── 微信登录 │ ├── 账密登录 │ └── 用户隐私协议同意 │ ├──个人主页 │ └── 头像、昵称、简介 │ ├──功能栏 │ ├── 我的课程 │ ├── 我的收藏 │ ├── 我的下载 │ └── 我的错题 │ └──常用服务 ├── 订单 ├── 消息 ├── 评论 ├── 观看记录 ├── 意见反馈 └── 设置 ├── 编辑个人信息 ├── 隐私设置 ├── 播放设置 ├── 清理缓存 ├── 检测版本 ├── 关于我们 └── 退出登录 二、 应用架构设计
1. 分层化模块设计
产品定制层:专注于满足不同设备或使用场景的个性化需求,作为应用的入口,是用户直接互动的界面。
o 本实践支持直板机、折叠机,为单 HAP 包形式,包含路由根节点、底部导航栏等。
基础特性层:用于存放相对独立的功能 UI 和业务逻辑实现。
o 本实践的基础特性层将应用底部导航栏的每个选项拆分成一个独立的业务功能模块。
o 每个功能模块都具备高内聚、低耦合、可定制的特点,支持产品的灵活部署。
公共能力层:存放公共能力,包括公共 UI 组件、数据管理、外部交互和工具库等共享功能。
o 本实践的公共能力层分为公共基础能力和可分可合组件,均打包为 HAR 包被上层业务组件引用。
o 公共基础能力包含日志、文件处理等工具类,公共类型定义,网络库,以及弹窗、加载等公共组件。
o 可分可合组件将包含行业特点、可完全自闭环的能力抽出独立的组件模块,支持开发者在开发中单独集成使用,详见业务组件设计章节。
本模板详细工程结构可见工程结构章节。
2. 业务组件设计
为支持开发者单独获取特定场景的页面和功能,本模板将功能完全自闭环的部分能力抽离出独立的行业组件模块,不依赖公共基础能力包,开发者可以单独集成,开箱即用,降低使用难度。
三、 行业场景技术方案
1. 视频播放控制层定制化
1)场景说明
用户可播放网络视频,视频播放支持加解锁、画中画、倍速、等功能。
2)技术方案
· 基于AVPlayer实现视频播放能力。
· 使用XComponent渲染视频画面。
· ArkUI 搭建控制层操作能力。
2. 画中画
1)场景说明
用户可启动画中画功能同时操作其他界面不影响视频观看。
2)技术方案
· 采用XComponent实现画中画能力。
· 封装 PipManager 工具类封装画中画相关 API。
四、 模板代码
1. 工程结构(下载模板)
详细代码结构如下所示:
Course├──commons│ ├──commonlib/src/main/ets // 基础模块 │ │ ├──components│ │ │ ├──BottomEditBar.ets // 底部编辑组件│ │ │ ├──CourseItemComponent.ets // 课程列表组件│ │ │ ├──DeleteDialog.ets // 删除按钮组件│ │ │ ├──DialogComponent.ets // 自适应弹框组件│ │ │ ├──GlobalAttributeModifier.ets // 自定义动态属性│ │ │ ├──LeftSwipeComponent.ets // 编辑组件│ │ │ ├──NoData.ets // 空数据页面│ │ │ └──TopBar.ets // 标题栏 │ │ ├──constants │ │ │ ├──CommonConstants.ets // 常量│ │ │ └──CommonEnums.ets // 路由页面 │ │ ├──models │ │ │ ├──CollectionModel.ets // 收藏│ │ │ ├──DownloadModel.ets // 下载│ │ │ ├──MyCourseModel.ets // 我的课程│ │ │ ├──OrderInfo.ets // 订单数据模型│ │ │ ├──TopicItemModel.ets // 题目数据模型│ │ │ ├──UserInfo.ets // 用户信息│ │ │ ├──WindowSize.ets // 窗口数据模型│ │ │ └──WrongQuestionModel.ets // 错题数据模型 │ │ └──utils │ │ ├──DialogUtil.ets // 弹框│ │ ├──Logger.ets // 日志│ │ ├──NumberUtil.ets // 数据格式化│ │ ├──PermissionUtil.ets // 权限│ │ ├──PreferenceUtil.ets // 首选项│ │ └──RouterModule.ets // 路由 │ ││ │ │ └──http/src/main/ets // 网络模块 │ ├──apis│ │ ├──Apis.ets // 请求接口│ │ └──HttpRequest.ets // 请求对象│ ├──mocks │ │ ├──AxiosMock.ets // mock│ │ ├──RequestMock.ets // 请求mock数据│ │ └──Data│ │ └──CourseData.ets // 数据源│ ├──model │ │ └──CourseModel.ets // 课程数据模型 │ ├──tools │ │ └──ConvertTool.ets // 模型转换工具 │ └──types │ └──Course.ets // 课程数据协议 ├──components│ ├──aggregated_payment // 支付组件 │ ├──aggregated_share // 分享组件│ ├──classification // 分类组件 │ ├──feed_back // 意见反馈组件│ ├──login_info // 登录组件│ ├──open_ads // 广告组件│ ├──search // 搜索组件│ └──recorded_player // 视频播放组件 ├──features │ ├──course/src/main/ets // 学习课程模块 │ │ ├──component│ │ │ └──CourseStudyComponent.ets // 课程数据源 │ │ ├──viewModels│ │ │ └──CourseVM.ets // 课程数据源 │ │ └──views │ │ └──MyLearnPage.ets // 学习页面 │ ││ ├──home/src/main/ets // 首页模块 │ │ ├──components│ │ │ ├──AnswerQuestionsComponent.ets // 答题view│ │ │ ├──CommentPopup.ets // 评分弹框│ │ │ ├──CourseCatalogComponents.ets // 课程章节弹框│ │ │ ├──LiveBroadcastSign.ets // 直播标签view│ │ │ ├──LiveNumberOverlay.ets // 数量view│ │ │ ├──LiveStreamingItem.ets // 直播列表item│ │ │ ├──AnswerSheetDialog.ets // 答题卡view│ │ │ └──HomeTop.ets // 首页职能类型选择页面 │ │ ├──models│ │ │ ├──Practice.ets // 模块数据│ │ │ ├──HomeMenuModel.ets // 课程数据│ │ │ ├──SelectRoleModel.ets // 职称级别数据│ │ │ └──Score.ets // 打分数据 │ │ ├──viewModels│ │ │ ├──CourseCatalogVM.ets // 章节数据刷新│ │ │ └──HomeVM.ets // 首页数据刷新 │ │ └──pages│ │ ├──AnswerQuestionsExamPage.ets // 答题页面│ │ ├──AnswerQuestionsPage.ets // 答题view│ │ ├──AnswerSheetPage.ets // 答题卡页面│ │ ├──ChapterDetail.ets // 章节详情页面│ │ ├──CourseListPage.ets // 课程页页面│ │ ├──ExamResultPage.ets // 答题结果页面│ │ ├──HomePage.ets // 首页│ │ ├──HomeSearch.ets // 搜索页面│ │ ├──LiveStreamingListPage.ets // 直播页面│ │ ├──MaterialPage.ets // 资料页面│ │ ├──TestReportPage.ets // 结果页面│ │ └──RecordedDetail.ets // 课程详情页面 │ └──mine/src/main/ets // 个人模块 │ ├──viewModel│ │ └──MyVM.ets // 数据处理层 │ ├──models│ │ ├──MessageModel.ets // 消息数据│ │ ├──SystemMessageModel.ets // 系统消息数据│ │ └──ServiceModel.ets // 模块服务数据 │ ├──component│ │ ├──CourseHistoryComponent.ets // 观看记录item│ │ └──TopicItemComponent.ets // 题目标题 │ └──views│ ├──AboutAndVersion.ets // 关于页面│ ├──AboutPage.ets // 设置页面│ ├──Authentication.ets // 用户协议页面│ ├──CourseUpdateMessagePage.ets // 课程动态消息页面│ ├──DataSharingPage.ets // 三方信息页面│ ├──FeedbackPage.ets // 意见反馈页面│ ├──FeedbackRecordsPage.ets // 反馈记录│ ├──MessageListCard.ets // 消息列表组件页面│ ├──MessagePage.ets // 消息页面│ ├──MinePage.ets // 个人页面│ ├──MultiLineText.ets // 消息组件页面│ ├──MyCollectionPage.ets // 收藏页面│ ├──MyCourseComments.ets // 课程评论页面│ ├──MyCoursePage.ets // 课程页面│ ├──MyDownloadPage.ets // 下载页面│ ├──MyOrderPage.ets // 订单页面│ ├──MyWrongQuestionPage.ets // 错题页面│ ├──OrderListPage.ets // 订单列表页面│ ├──OrderDetailPage.ets // 订单列表页面│ ├──PersonalInfoPage.ets // 个人信息页面│ ├──PersonalInformationCollectionPage.ets // 个人信息收集页面│ ├──PrivacyAgreement.ets // 隐私协议页面│ ├──PrivacySetPage.ets // 隐私设置页面│ ├──PrivacyStatement.ets // 隐私声明页面│ ├──SetUpPage.ets // 设置页面│ ├──SystemMessage.ets // 系统消息页面│ ├──UpdateVersionContentCard.ets // 版本更新页面│ ├──WatchHistoryPage.ets // 观看记录页面│ └──UserAgreement.ets // 服务协议页面 │└──product └──entry/src/main/ets ├──viewmodels // 首页数据 │ └──MainEntryVM.ets ├──entryability // 入口 │ └──EntryAbility.ets ├──entrybackupability // 后台 │ └──EntryBackupAbility.ets ├──models │ ├──RouterTable.ets // 路由表 │ └──Types.ets // tab数据类型 └──pages ├──Index.ets // 入口页面 ├──Launch.ets // 启动页面 ├──LaunchAd.ets // 广告页面 ├──Login.ets // 登录页面 ├──MainEntry.ets // 主页面 ├──PrivacyPolicy.ets // 隐私页面 └──PrivacyPolicyAlert.ets // 隐私弹框页面2. 关键代码解读
本篇代码非应用的全量代码,只包括应用的部分能力的关键代码。
1) 播放器工具封装
@ObservedV2export class AVPlayer { callbackComplete: () => void = () => { }; callbackTimeUpdate: (nol: number, total: number) => void = (nol: number) => { }; callbackBitrateUpdate: (bitrateList: number[]) => void = (bitrateList: number[]) => { }; callbackErrorUpdate: (error: string) => void = (error: string) => { }; @Trace currentTime: number = 0; @Trace totalDuration: number = 0; @Trace isPlaying: boolean = false; @Trace isLoading: boolean = true; @Trace isLoadError: boolean = false; // surfaceID用于播放画面显示,具体的值需要通过XComponent接口获取,相关文档链接见上面XComponent创建方法 surfaceID: string = ''; // 画中画是否开启 isPipOpen: boolean = false; // 复原 isPiPWindowRestore: boolean = false; avPlayer: media.AVPlayer = {} as media.AVPlayer; isCreate: boolean = false; seekTime: number = 0; // 碰到特殊场景进入其他页时,即便加载成功也不需要播放 isResetPause: boolean = false; videoUrl: string = '';
async init(url: string, seekTime: number): Promise<void> { await this.release(); // 创建avPlayer实例对象 this.avPlayer = await media.createAVPlayer(); // 创建状态机变化回调函数 await this.setStateChangeCallback(); await this.setSourceInfoCallback(); this.isCreate = true; this.isResetPause = false; this.videoUrl = url this.avPlayer.url = url; this.isLoading = true; this.seekTime = seekTime; }
getPlay() { if (this.avPlayer) { this.avPlayer.play(); } }
getPause() { if (this.avPlayer) { this.avPlayer.pause(); } }
getStop() { if (this.avPlayer) { this.avPlayer.stop(); } }
changeVolume(volume: number) { if (this.avPlayer) { this.avPlayer.setVolume(volume) } }
changeSpeed(speed: number) { if (this.avPlayer) { this.avPlayer.setSpeed(speed) } }
changeSeek(seekTime: number, mode: SliderChangeMode) { if (this.avPlayer) { this.avPlayer.seek(seekTime, 2); } }
setSurfaceId(surfaceId: string): void { if (this.avPlayer.surfaceId === surfaceId) { return } this.surfaceID = surfaceId this.avPlayer.surfaceId = this.surfaceID; }
// 切换播放 async changePlay(url: string, seekTime: number) { if (this.avPlayer) { setTimeout(() => { this.isLoadError = false }, 1000) this.isPlaying = false this.isLoading = true this.isResetPause = false; await this.avPlayer.reset() this.totalDuration = 0 this.seekTime = seekTime this.videoUrl = url this.avPlayer.url = url } }
setCompleteCallback(func: () => void) { this.callbackComplete = func; this.isPlaying = false }
setTimeUpdateCallback(func: (nol: number, total: number) => void) { this.callbackTimeUpdate = func; }
setBitrateUpdateCallback(func: (bitrateList: number[]) => void) { this.callbackBitrateUpdate = func; }
setErrorCallback(func: (error: string) => void) { this.callbackErrorUpdate = func; }
// 资源释放 async release() { if (this.isCreate) { this.isCreate = false; this.isPlaying = false; this.isLoading = false; this.isLoadError = false; this.isResetPause = false; await this.avPlayer.release(); } }
// 视频信息上报函数 async setSourceInfoCallback(): Promise<void> { // 时间上报函数 this.avPlayer.on('timeUpdate', (time: number) => { this.currentTime = time this.callbackTimeUpdate(time, this.totalDuration); }); // 音量变化回调函数 this.avPlayer.on('volumeChange', (vol: number) => { }); // 视频播放结束触发回调 this.avPlayer.on('endOfStream', () => { }); // seek操作回调函数 this.avPlayer.on('seekDone', (seekDoneTime: number) => { }); // 视频总时长上报函数 this.avPlayer.on('durationUpdate', (duration: number) => { }); // 设置倍速播放回调函数 this.avPlayer.on('speedDone', (speed: number) => { }); // bitrate设置成功回调函数 this.avPlayer.on('bitrateDone', (bitrate: number) => { }); // 缓冲上报回调函数 this.avPlayer.on('bufferingUpdate', (infoType: media.BufferingInfoType, value: number) => { }); // 首帧上报回调函数 this.avPlayer.on('startRenderFrame', () => { }); // 视频宽高上报回调函数 this.avPlayer.on('videoSizeChange', (width: number, height: number) => { }); // 焦点上报回调函数 this.avPlayer.on('audioInterrupt', (info: audio.InterruptEvent) => { }); // HLS上报所有支持的比特率 this.avPlayer.on('availableBitrates', (bitrates: number[]) => { this.callbackBitrateUpdate(bitrates); }); // 设置错误监听 this.avPlayer.on('error', (error) => { this.callbackErrorUpdate('Error ' + error.code + ' - ' + error.message); }); }
// 状态机 async setStateChangeCallback(): Promise<void> { this.avPlayer.on('stateChange', async (state: string, reason: media.StateChangeReason) => { if (this.avPlayer == null) { return; } switch (state) { // 闲置状态,成功调用reset接口后触发该状态机上报 case AVPlayerState.IDLE: this.isLoadError = false break; // 资源初始化,avplayer设置播放源后触发该状态上报 case AVPlayerState.INITIALIZED: if (this.surfaceID) { this.setSurfaceId(this.surfaceID); // 设置显示画面,当播放的资源为纯音频时无需设置 this.avPlayer.prepare(); // 进入准备状态 } break; // 已准备状态 case AVPlayerState.PREPARED: this.avPlayer.audioInterruptMode = audio.InterruptMode.INDEPENDENT_MODE; this.totalDuration = this.avPlayer.duration; this.currentTime = this.totalDuration * this.seekTime / 100 this.avPlayer.seek(this.currentTime, 2); if (this.isResetPause) { this.isPlaying = false; this.avPlayer.pause() }else { this.isPlaying = true; this.avPlayer.play(); } this.changeSpeed(media.PlaybackSpeed.SPEED_FORWARD_1_00_X) this.isLoadError = false; break; // 正在播放状态 case AVPlayerState.PLAYING: this.isLoading = false; this.isPlaying = true; break; // 暂停状态 case AVPlayerState.PAUSED: this.isLoading = false; this.isPlaying = false; break; // 播放至结尾状态 case AVPlayerState.COMPLETED: this.isPlaying = false this.callbackComplete(); break; // 停止状态 case AVPlayerState.STOPPED: break; // 销毁状态,销毁与当前AVPlayer关联的播放引擎,无法再进行状态转换 case AVPlayerState.RELEASED: this.isLoading = true; break; // 错误状态,当播放引擎发生不可逆的错误,则会转换至当前状态 case AVPlayerState.ERROR: setTimeout(() => { this.isPlaying = false; this.isLoading = false; this.isLoadError = true; }, 1000) break; default: break; } }) }}2) 画中画工具类封装
const CONTEXT_WIDTH: number = 1600;const CONTEXT_HEIGHT: number = 900;
export class CustomXComponentController extends XComponentController { onSurfaceCreated(surfaceId: string): void { if (PipManager.getInstance().player.surfaceID === surfaceId) { return; } PipManager.getInstance().player.setSurfaceId(surfaceId); }}
// 画中画控制器export class PipManager { private static instance: PipManager = new PipManager(); private pipController?: PiPWindow.PiPController; private mXComponentController: XComponentController; player: AVPlayer;
constructor() { this.player = new AVPlayer(); this.mXComponentController = new CustomXComponentController(); }
public static getInstance(): PipManager { return PipManager.instance; }
getXComponentController(): CustomXComponentController { return this.mXComponentController; }
init(ctx: Context, navigationId: string) { // 当前设备如若不支持画中画则退出 if (!PiPWindow.isPiPEnabled()) { return; } let config: PiPWindow.PiPConfiguration = { context: ctx, // XComponent组件绑定同一个 componentController: this.mXComponentController, navigationId: navigationId, // 画中画媒体类型枚举,当前使用的视频播放模版 templateType: PiPWindow.PiPTemplateType.VIDEO_PLAY, contentWidth: CONTEXT_WIDTH, contentHeight: CONTEXT_HEIGHT, }; PiPWindow.create(config).then((controller: PiPWindow.PiPController) => { this.pipController = controller; this.pipController?.setAutoStartEnabled(false); this.pipController.on('stateChange', (state: PiPWindow.PiPState, reason: string) => { this.onStateChange(state, reason); }); this.pipController.on('controlEvent', (control: PiPWindow.ControlEventParam) => { this.onActionEvent(control); }); }).catch((err: BusinessError) => { }) }
startPip() { this.pipController?.startPiP().then(() => { }).catch((err: BusinessError) => { }); }
stopPip() { this.pipController?.stopPiP().then(() => { }).catch((err: BusinessError) => { }); }
setAutoStart(autoStart: boolean): void { this.pipController?.setAutoStartEnabled(autoStart); }
updateContentSize(width: number, height: number) { if (this.pipController) { this.pipController.updateContentSize(width, height); } }
updatePiPControlStatus() { let controlType: PiPWindow.PiPControlType = PiPWindow.PiPControlType.VIDEO_PLAY_PAUSE; let status: PiPWindow.PiPControlStatus = PiPWindow.PiPControlStatus.PLAY; this.pipController?.updatePiPControlStatus(controlType, status); }
unregisterPipStateChangeListener() { this.pipController?.off('stateChange'); this.pipController?.off('controlEvent'); }
// 监听画中画生命周期 onStateChange(state: PiPWindow.PiPState, _reason: string) { switch (state) { // 表示画中画将要启动 case PiPWindow.PiPState.ABOUT_TO_START: break; // 表示画中画已经启动 case PiPWindow.PiPState.STARTED: // 启动画中画 PipManager.getInstance().player.isPipOpen = true; break; // 表示画中画将要停止 case PiPWindow.PiPState.ABOUT_TO_STOP: // 画中画关闭 PipManager.getInstance().player.isPipOpen = false; break; // 表示画中画已经停止 case PiPWindow.PiPState.STOPPED: // 画中画关闭 PipManager.getInstance().player.isPipOpen = false; // // 小窗口点击关闭画中画,视频暂停播放 PipManager.getInstance().player.isPiPWindowRestore = false; break; // 表示画中画将从小窗播放恢复到原始播放界面 case PiPWindow.PiPState.ABOUT_TO_RESTORE: // 小窗口复原关闭画中画 PipManager.getInstance().player.isPipOpen = false; // 从小窗口复原 PipManager.getInstance().player.isPiPWindowRestore = true; break; // 表示画中画生命周期执行过程出现了异常 case PiPWindow.PiPState.ERROR: break; default: break; } }
// 监听画中画控制面板控件动作事件 onActionEvent(control: PiPWindow.ControlEventParam) { switch (control.controlType) { // 视频播放、停止 case PiPWindow.PiPControlType.VIDEO_PLAY_PAUSE: if (control.status === PiPWindow.PiPControlStatus.PAUSE) { // 停止视频 PipManager.getInstance().player.isPlaying = false; } else if (control.status === PiPWindow.PiPControlStatus.PLAY) { // 播放视频 PipManager.getInstance().player.isPlaying = true; } break; // 切换到下一个视频 case PiPWindow.PiPControlType.VIDEO_NEXT: break; // 切换到上一个视频 case PiPWindow.PiPControlType.VIDEO_PREVIOUS: break; // 视频进度快进 case PiPWindow.PiPControlType.FAST_FORWARD: break; // 视频进度后退 case PiPWindow.PiPControlType.FAST_BACKWARD: break; default: break; } }}3. 模板集成
本模板提供了两种代码集成方式,供开发者自由使用。
1) 整体集成
开发者可以选择直接基于模板工程开发自己的应用工程。
模板代码获取:
① 通过 IDE 插件创建模板工程,开发指导。
② 通过生态市场下载源码, 下载模板。
③ 通过开源仓访问源码,仓库地址。
打开模板工程,根据 README 说明中的快速入门章节,将自己的应用信息配置在模板工程内,即可运行并查看模板效果。
对接开发者自己的服务器接口,转换数据结构,展示真实的云侧数据。
将 commons/http/src/main/ets/apis/Apis.ets 文件中的 mock 接口替换为真实的服务器接口。
在 commons/http/src/main/ets/types/Course.ets 文件中将云侧开发者自定义的数据结构转换为端侧数据结构。
根据自己的业务内容修改模板,进行定制化开发。
2) 按需集成
若开发者已搭建好自己的应用工程,但暂未实现其中的部分场景能力,可以选择取用其中的业务组件,集成在自己的工程中。
组件代码获取:
① 通过 IDE 插件下载组件源码。开发指导
② 通过生态市场下载组件源码。 下载地址
下载组件源码,根据 README 中的说明,将组件包配置在自己的工程中。
根据 API 参考和示例代码,将组件集成在自己的对应场景中。
以上是第 10 期“课堂应用”行业优秀案例的内容,更多行业敬请期待~
欢迎下载使用行业模板“点击下载”,若您有体验和开发问题,或者迫不及待想了解 XX 行业的优秀案例,欢迎在评论区留言,小编会快马加鞭为您解答~
同时诚邀您添加下方二维码加入“组件模板活动社群”,精彩上新 &活动不错过!
👉 HarmonyOS 官方模板优秀案例系列持续更新, 点击查看往期案例汇总贴,点击收藏,方便查找!
👉【集成有礼】HarmonyOS 官方模板集成创新活动,挥洒创意,赢精美大礼!点击参加
👉【HarmonyOS 行业解决方案】为各行业鸿蒙应用提供全流程技术方案。点击查看
推荐阅读
电子书

大厂实战PPT下载
换一换 
张雁飞 | Datafuse Labs 联合创始人
杨海银 | 美的 IoT 软件开发移动总架构师
周建华 | 太保寿险 首席架构师






评论