HarmonyOS元服务开发实践:桌面卡片字典 原创
一、项目说明
1.DEMO创意为卡片字典。
2.不同卡片显示不同内容:微卡、小卡、中卡、大卡,根据不同卡片特征显示同一个字的不同内容,基于用户习惯可选择喜欢的卡片。
3.万能卡片刷新:用户点击卡片刷新按钮查看新内容,同时卡片设置了定时刷新,让用户每天看到的卡片都是新的文字,便于用户学习和查阅。
4.元服务内具有搜索功能,用户可以通过搜索查询相应的字和解释,采用了类似现在用户习惯的上下滑动方式来进行逐字详细阐述。
5.基于API9、ArkTS语言开发,通过serverless云服务实现注册、登录等功能。
二、元服务效果
1.万能卡片效果
2.元服务内页
三、项目开发
1.环境搭建
软件要求:
DevEco Studio版本:DevEco Studio 3.1 Release及以上版本。
HarmonyOS SDK版本:API version 9及以上版本。
硬件要求:
设备类型:华为手机或运行在DevEco Studio上的华为手机设备模拟器。
HarmonyOS系统:3.1.0 Developer Release及以上版本。
2.主要代码结构解读
entry/src/main/ets: 文件入口
common:公共资源文件
images:公共图片资源
Constants.ts:公共常量
CountryViewModel.ts:国家号码类
LazyFE_Class.ets:懒数据加载类
Log.ts:日志类
components:封装组件文件
database:数据库封装类
data_cyhz.ets:数据文件
entryability:应用/服务入口
entryformability:卡片服务
pages:应用/服务页面
Auth.ets:认证授权
CloudFunction.ets:云函数
CloudProject.ets:云项目
CloudStorage.ets:云存储
Index.ets:主页
User_Login.ets:登录页
User_SignUp.ets:注册页
User_VerifyCodeLogin.ets:验证码登录页
services:后台操作类
widget:服务卡片
resources:资源文件目录
3.进入应用说明
由于创建的是云模板项目,所以无需额外配置SDK依赖,只需要注意的是,云模板的初始集成sdk位置不一样,所以我们还是在应用初始化阶段使用context初始化SDK,推荐在entryability的onCreate中进行。
4.首页
我们需要给应用添加底部菜单栏,用于切换不同的应用模块,由于各个模块之间属于完全独立的情况,并且不需要每次切换都进行界面的刷新,所以我们用到了Tabs,TabContent组件。
本应用一共有首页、我的两个模块,分别对应Tabs组件的两个子组件TabContent。
首页包含搜索文字和滑动浏览信息两个模块,具体代码实现我们将在下边分模块进行说明。
搜索文字:主要用到Search组件,通过搜索文字来跳转到相应的文字展示信息,主要代码如下:
...
//常用汉字搜索栏
Column() {
Search({ value: this.submitValue, placeholder: '汉字搜索', controller: this.search })
.searchButton('搜索')
.placeholderColor(Color.Grey)
.textFont({ size: 14, weight: 400 })
.margin({ left: 20, right: 20 })
.onSubmit((value: string) => {
this.submitValue = value
for (let i = 0; i < wz.length; i++) {
const element: any = wz[i];
if (this.submitValue == element.zi) {
this.swiperIndex = i
this.submitValue = ''
}
}
})
.onChange((value: string) => {
this.changeValue = value
})
}.width("100%").margin({ top: 20, bottom: 20 })
......
浏览信息模块:主要用到swiper组件,通过数据的懒加载行为,来预缓存数据,通过滑动页面来展示文字信息,主要代码如下:
...
//常用汉字轮播部分
Column() {
Swiper(this.swiperController) {
LazyForEach(this.data_wz, (item: any) => {
Column() {
...
}.width("100%")
.height("100%")
.justifyContent(FlexAlign.Start)
.alignItems(HorizontalAlign.Start)
}, item => item)
}
.vertical(true)
.cachedCount(2)
.autoPlay(false)
.indicator(false)
.loop(false)
.duration(400)
.itemSpace(0)
.curve(Curve.Linear)
.cachedCount(3)
.index(this.swiperIndex)
.disableSwipe(this.disableSwipe)
.onChange((index: number) => {
console.info("swiper:" + index.toString())
this.swiperIndex = index
})
}.width("100%")
...
5.我的
我的页包含游客登陆、用户登录两个模块。
其中游客登陆不显示登录信息以及应用部分功能,仅能使用部分应用能力;
用户登录显示用户部分信息,并展开应用所有功能,需要用户注册登录;
具体代码实现我们将在下边分模块进行说明。
游客登录:
...
//游客登陆状态
if (this.isVisitor) {
//头像信息
Column() {
Image($r('app.media.icon'))
.width(90)
.objectFit(ImageFit.Contain)
.borderRadius(50)
Text(this.isVisitor ? "游客_" : this.userName).fontSize(16).margin(20)
Button(this.isLogin ? "退出" : "登录", { type: ButtonType.Capsule })
.fontSize(14)
.width('120')
.height('30')
.backgroundColor(0xf48fb1)
.onClick(() => {
router.replaceUrl({
url: "pages/User_Login"
})
})
}
.width('90%')
.height('240')
.borderRadius(12)
.margin({ top: 20 })
.backgroundColor(0xFFFFFF)
.shadow({ radius: 12, color: 0xCECECE, offsetX: 4, offsetY: 6 })
.justifyContent(FlexAlign.Center)
}
...
用户登录:
...
//已经登陆状态
if (!this.isVisitor) {
//头像信息
Column() {
...
}
.width('90%')
.height('240')
.borderRadius(12)
.margin({ top: 20 })
.backgroundColor(0xFFFFFF)
.shadow({ radius: 12, color: 0xCECECE, offsetX: 4, offsetY: 6 })
.justifyContent(FlexAlign.Center)
//选择项
Column() {
...
}.width('100%')
.height("100%")
.backgroundColor(0xF5F5F5)
.justifyContent(FlexAlign.Start)
6.注册登录页
让用户进行账号注册,能够完全使用应用。
核心代码:
...
.onClick(() => {
if (this.phoneNumber !== '' && this.password !== '') {
let verifyCodeSettings = new VerifyCodeSettingBuilder()
.setAction(VerifyCodeAction.REGISTER_LOGIN)
.setLang('zh_CN')
.setSendInterval(60)
.build();
agconnect.auth().requestPhoneVerifyCode(this.countryCode, this.phoneNumber, verifyCodeSettings)
.then(verifyCodeResult => {
this.startTimer()
//验证码申请成功
}).catch(error => {
//验证码申请失败
Prompt.showToast({ message: "请输入正确的手机号和密码" + JSON.stringify(error) })
});
}else {
Prompt.showToast({ message: "手机号和密码不能为空" })
}
})
......
......
.onClick(() => {
if (this.phoneNumber !== '' && this.password !== '') {
let user = new PhoneUserBuilder()
.setCountryCode(this.countryCode)
.setPhoneNumber(this.phoneNumber)
.setPassword(this.password) //可以给用户设置初始密码。填写后后续可以用密码来登录
.setVerifyCode(this.VerifyCode)
.build();
agconnect.auth().createPhoneUser(user)
.then(result => {
// 创建用户成功
AppStorage.Set('phoneNumber', user.phoneNumber)
AppStorage.Set('password', user.password)
AppStorage.Set('isVisitor', false)
AppStorage.Set('isLogin', true)
AppStorage.Set('userName', user.phoneNumber)
router.pushUrl({
url: "pages/Index"
})
})
.catch(error => {
// 创建用户失败
Prompt.showToast({ message: "注册失败," + JSON.stringify(error),duration:4 })
})
} else {
Prompt.showToast({ message: "手机号和密码不能为空" })
}
})
7.其他云服务
说明:这是云模板初始业务,如有其他业务需求,可自行添加。
四、卡片开发
按需求添加卡片,也可以只用初始创建卡片,修改相关卡片参数即可。
1.创建卡片
2.初始卡片修改相关参数
打开resources/base/profile目录下的form_config.json文件,按需修改参数
3.卡片加载与获取数据
卡片加载更新部分由EntryFormAbility.ts文件完成,这里可参考官方文档操作即可。
由于没有连接到后台数据部分,所以我们这里采用自定义模拟数据,然后在每次卡片挂载到桌面时,随机选取卡片内容,代码如下:
...
aboutToAppear() {
let idx = Math.floor((Math.random() * wz_arr.length))
this.zi = wz_arr[idx].zi
this.pinYin = wz_arr[idx].pinYin
this.buShou = wz_arr[idx].buShou
this.biHua = wz_arr[idx].biHua
this.fanTi = wz_arr[idx].fanTi
this.near_words = wz_arr[idx].near_words
this.reverse_words = wz_arr[idx].reverse_words
this.explain = wz_arr[idx].explain.toString()
}
...
4.卡片主要代码
...
Column() {
//微卡
Stack() {
Text(this.zi)
.width("100%")
.textAlign(TextAlign.Center)
.fontSize(30)
.fontColor('#1f1f1f')
.fontWeight(600)
.margin({right:20})
Row(){
Image("/common/images/R2.png")
.width(18)
.height(18)
.margin({right:"15%"})
.objectRepeat(ImageRepeat.NoRepeat)
.onClick(()=>{
this.rotateAngle = 180
this.aboutToAppear()
})
.rotate({ angle: this.rotateAngle })
.animation({
duration:300,
curve: Curve.Linear,
playMode: PlayMode.Normal
})
}.width("100%").justifyContent(FlexAlign.End)
}
.width("100%")
.height(72)
//小卡、中卡
Flex({direction:FlexDirection.Column,wrap:FlexWrap.Wrap,justifyContent:FlexAlign.Start}){
Column(){
Text("拼音:"+this.pinYin).fontSize(14).margin({left:15})
Text("部首:"+this.buShou).fontSize(14).margin({top:4,left:15})
Text("笔画:"+this.biHua).fontSize(14).margin({top:4,left:15})
Text("繁体:"+this.fanTi).fontSize(14).margin({top:4,left:15})
}.width(208)
.justifyContent(FlexAlign.Start)
.alignItems(HorizontalAlign.Start)
Column(){
Text("近义词:"+this.near_words).fontSize(12).margin({right:15})
Text("反义词:"+this.reverse_words).fontSize(12).margin({top:4,right:15})
}
.justifyContent(FlexAlign.Start)
.alignItems(HorizontalAlign.Start)
}
.width("100%")
.height(102)
//大卡
Column(){
Text("解释:").width("100%").textAlign(TextAlign.Start).fontSize(12).margin({left:15,right:15})
Text(this.explain).fontSize(14).margin({top:4,left:15,right:15})
}.width("100%")
.height("100%")
.justifyContent(FlexAlign.Start)
.alignItems(HorizontalAlign.Start)
}
.width("100%")
.alignItems(HorizontalAlign.Center)
.backgroundImage("/common/images/cywz.jpg")
.backgroundImageSize(ImageSize.Cover)
.onClick(() => {
postCardAction(this, {
"action": this.ACTION_TYPE,
"abilityName": this.ABILITY_NAME,
"params": {
"message": this.MESSAGE,
}
});
})
...
五、项目运行
六、结语
各位感兴趣的开发者可以点击进入元服务官网,详细了解元服务、万能卡片相关信息。大家还可以在华为手机的负一屏、华为应用市场元服务专区体验本文作者及团队已经上架运营的元服务-成语心情,用万能卡片按照自己的心情来刷刷成语吧。
基于传统文化文学的不断探索和尝试