Harmony行酒令开发核心代码
·
一、背景说明
行酒令APP是为了练习鸿蒙开发,熟悉相关组件以及上架审核流程而开发的。本文将分享练习的相关核心代码,老手略过。
功能比较简单,先给几张首页截图。



二、项目及代码
2.1 项目代码结构

2.2 代码
-
主Ability
import { AbilityConstant, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
import EmbeddableUIAbility from '@ohos.app.ability.EmbeddableUIAbility';
import userLocal from '../base/utils/UserLocal';
export default class EntryAbility extends EmbeddableUIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
userLocal.init(this.context)
}
onDestroy(): void {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
}
onWindowStageCreate(windowStage: window.WindowStage): void {
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.');
});
const win = windowStage.getMainWindowSync()
win.setWindowLayoutFullScreen(true)
}
onWindowStageDestroy(): void {
// Main window is destroyed, release UI related resources
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
}
onForeground(): void {
// Ability has brought to foreground
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
}
onBackground(): void {
// Ability has back to background
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
}
}
-
首页pages/Index
import userLocal from '../base/utils/UserLocal';
import { APP_NAME, KEY_PRIVACY_SHOW } from '../data/Constants';
import UserPrivacyDialog from '../base/widget/UserPrivacyDialog';
import { common } from '@kit.AbilityKit';
import { TabBar } from './component/TabBar';
/**
* Created by ZhaoHongBo on 2024/12/16.
* Copyright (c) 2024 ZHB. All rights reserved.
*/
@Entry
@Component
struct Index {
context = getContext(this) as common.UIAbilityContext;
dialogController: CustomDialogController = new CustomDialogController({
builder: UserPrivacyDialog({
confirm: () => {
userLocal.save(KEY_PRIVACY_SHOW, '1')
},
cancel: () => {
this.context.getApplicationContext().killAllProcesses()
}
}),
autoCancel: false
})
onPageShow(): void {
this.showPrivacyDialog()
}
onPageHide(): void {
this.dialogController.close()
}
showPrivacyDialog() {
let showFlag = userLocal.get(KEY_PRIVACY_SHOW, '0')
if (showFlag == '0') {
this.dialogController.open()
}
}
build() {
Navigation() {
TabBar()
}.title(APP_NAME).titleMode(NavigationTitleMode.Full)
}
}
-
显示H5页面的自定义WebView
import { AtomicServiceWeb, AtomicServiceWebController, router } from '@kit.ArkUI'
@Entry
@Component
struct HBWebPage {
@State params: object = router.getParams()
@State url: string | Resource = ''
@State titleStr: string = ''
@State controller: AtomicServiceWebController = new AtomicServiceWebController()
navPathStack: NavPathStack = new NavPathStack();
aboutToAppear(): void {
let urlPath: string = this.params['url']
if (urlPath.startsWith('http')) {
this.url = urlPath
} else {
this.url = $rawfile(urlPath)
}
}
onBackPress(): boolean | void {
if (this.controller.accessBackward()) {
this.controller.backward()
} else {
router.back()
}
return true
}
build() {
NavDestination() {
AtomicServiceWeb({
src: this.url,
navPathStack: this.navPathStack,
controller: this.controller,
onControllerAttached: () => {
this.controller.setCustomUserAgent('')
}
})
}
.onReady((context: NavDestinationContext) => {
this.navPathStack = context.pathStack
})
.title(this.titleStr)
.onClick(event => {
if (event.target) {
this.onBackPress()
}
})
.margin({ top: 48 })
}
}
-
首页
import fileHelper from '../../base/utils/FileHelper'
import { common } from '@kit.AbilityKit'
import { GameBean, GameRootBean } from '../../data/bean/GameRootBean'
import { HTabBarTitle } from './HTabBarTitle'
import router from '@ohos.router'
/**
* Created by ZhaoHongBo(zzf_soft@163.com) on 2024/12/23.
* Copyright (c) 2024 ZHB. All rights reserved.
*/
@Component
export struct HomeView {
context = getContext() as common.UIAbilityContext
@State data1: GameRootBean = new GameRootBean()
@State data2: GameRootBean = new GameRootBean()
@State data3: GameRootBean = new GameRootBean()
@State selectedIndex: number = 0
tabController: TabsController = new TabsController()
aboutToAppear(): void {
this.data1 = JSON.parse(fileHelper.getRawFile(this.context, 'data/directive1.json')) as GameRootBean
this.data2 = JSON.parse(fileHelper.getRawFile(this.context, 'data/directive2.json')) as GameRootBean
this.data3 = JSON.parse(fileHelper.getRawFile(this.context, 'data/directive3.json')) as GameRootBean
}
build() {
Stack() {
Image($r('app.media.bg_game_wan')).opacity(0.1)
Tabs({ controller: this.tabController }) {
TabContent() {
this.buildTxt1()
}.tabBar(this.tabBarCustom('顺口溜', 0))
TabContent() {
this.buildTxt2()
}.tabBar(this.tabBarCustom('划拳', 1))
TabContent() {
this.buildTxt3()
}.tabBar(this.tabBarCustom('哥俩好', 2))
TabContent() {
this.buildDev()
}.tabBar(this.tabBarCustom('开发者', 3))
}.barBackgroundColor(Color.White).onChange(index => {
this.selectedIndex = index
}).onAppear(() => {
this.tabController.changeIndex(3)
})
}
}
@Builder
tabBarCustom(title: string, targetIndex: number) {
HTabBarTitle({ title, targetIndex, currentTabIndex: this.selectedIndex })
}
@Builder
buildTxt(item: GameBean) {
Stack() {
Stack() {
Text(item.content.replaceAll(',', '\n')
.replaceAll('。', '\n')
.replaceAll(';', '\n')
.replaceAll(';', '\n')
.replaceAll('、', '\n'))
.fontColor(Color.Black)
.fontWeight(FontWeight.Bold)
.fontSize(28)
.lineHeight(50)
.letterSpacing(12)
}
.backgroundColor(Color.White)
.opacity(0.8)
.borderRadius(8)
.padding(16)
.width('100%')
.height('90%')
}.width('90%').height('90%')
}
@Builder
buildTxt1() {
Swiper() {
ForEach(this.data1.data, (item: GameBean, index) => {
this.buildTxt(item)
})
}.vertical(true).height('100%')
}
@Builder
buildTxt2() {
Swiper() {
ForEach(this.data2.data, (item: GameBean, index) => {
this.buildTxt(item)
})
}.vertical(true).height('100%')
}
@Builder
buildTxt3() {
Swiper() {
ForEach(this.data3.data, (item: GameBean, index) => {
this.buildTxt(item)
})
}.vertical(true).height('100%')
}
@Builder
buildDev() {
Column() {
Button('打开地图').onClick(() => {
router.pushUrl({
url: 'pages/MapPage'
})
})
}
}
}
-
关于
import { SettingItemView } from '../../base/widget/SettingItemView'
import { APP_NAME, URL_PROTOCOL_PRI, URL_PROTOCOL_USE } from '../../data/Constants'
import { router } from '@kit.ArkUI'
/**
* Created by ZhaoHongBo(zzf_soft@163.com) on 2024/12/23.
* Copyright (c) 2024 ZHB. All rights reserved.
*/
@Component
export struct AboutView {
build() {
RelativeContainer() {
Column() {
Image($r('app.media.startIcon'))
.width(120)
.height(120)
.id('login_btn')
.clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.7 })
Text(APP_NAME)
.height(48)
.width('100%')
.textAlign(TextAlign.Center)
.fontWeight(FontWeight.Bold)
.fontSize(18)
.margin({ bottom: 8 })
SettingItemView({
attributes: {
title: '当前版本',
tips: 'v1.0.0'
}
}).margin({ top: 24 })
SettingItemView({
attributes: {
title: '隐私政策',
showArrow: true
}
}).margin({ top: 12 }).onClick(() => {
router.pushUrl({
url: 'pages/HBWebPage',
params: {
url: URL_PROTOCOL_PRI
}
})
})
SettingItemView({
attributes: {
title: '用户协议',
showArrow: true
}
}).margin({ top: 12 }).onClick(() => {
router.pushUrl({
url: 'pages/HBWebPage',
params: {
url: URL_PROTOCOL_USE
}
})
})
SettingItemView({
attributes: {
title: '联系邮箱',
tips: 'zzf_soft@163.com',
showArrow: true
}
}).margin({ top: 12 })
}.alignRules({
top: { anchor: '__container__', align: VerticalAlign.Top },
bottom: { anchor: 'copyright', align: VerticalAlign.Top }
})
Column() {
Text($r('app.string.copyright')).fontSize(14).fontWeight(FontWeight.Medium).alignSelf(ItemAlign.Center)
Text($r('app.string.icp_info')).fontSize(13).fontWeight(FontWeight.Regular)
}.id('copyright').alignRules({
bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
}).align(Alignment.Center).width('100%')
}.height('85%')
}
}
-
知识库
import { JSON } from '@kit.ArkTS'
import { GameBean, GameRootBean } from '../../data/bean/GameRootBean'
import { common } from '@kit.AbilityKit'
import fileHelper from '../../base/utils/FileHelper'
import { router } from '@kit.ArkUI'
/**
* Created by ZhaoHongBo(zzf_soft@163.com) on 2024/12/23.
* Copyright (c) 2024 ZHB. All rights reserved.
*/
@Component
export struct KnowledgeView {
context = getContext() as common.UIAbilityContext
@State data: GameRootBean = new GameRootBean()
aboutToAppear(): void {
this.data = JSON.parse(fileHelper.getRawFile(this.context, 'data/game.json')) as GameRootBean
}
build() {
Stack({ alignContent: Alignment.BottomEnd }) {
List() {
ForEach(this.data.data, (item: GameBean, index: number) => {
ListItem() {
Stack() {
Column({ space: 8 }) {
Text(`${item.id}`)
.fontSize(21)
.padding({ top: 4, bottom: 4, left: 16 })
.fontWeight(FontWeight.Bold)
.backgroundColor($r('app.color.list_title_head_color'))
.borderRadius({ topLeft: 4, topRight: 4 })
.width('100%')
.textAlign(TextAlign.Start)
Text(item.content).padding(8).fontSize(16).fontStyle(FontStyle.Italic)
}
}.width('100%')
}
.borderWidth(3)
.borderColor($r('app.color.default_theme_color11'))
.borderRadius(8)
.margin(8)
})
}.margin({ left: 16, right: 16 }).scrollBar(BarState.Off)
Image($r('app.media.ic_random_packages'))
.width(48)
.height(48)
.borderColor($r('app.color.hb_theme_color_main'))
.borderWidth(2)
.borderRadius(100)
.useEffect(true).margin(16)
.onClick(() => {
router.pushUrl({
url: 'pages/GamePage'
})
})
}
}
}
-
TabBar
import { AboutView } from './AboutView'
import { HomeView } from './HomeView'
import { KnowledgeView } from './KnowledgeView'
interface BuilderParams {
index: number //标签索引
label: string //标签名称
normalIcon: Resource //未选中状态图标
selectIcon: Resource //选中状态图标
}
@Component
export struct TabBar {
controller: TabsController = new TabsController() //tabs控制器
@State current: number = 0 //当前tab选中项的索引
@Builder
tabBuilder($$: BuilderParams) {
Column() {
//图标
Image(this.current === $$.index ? $$.selectIcon : $$.normalIcon).height(24).width(24).objectFit(ImageFit.Contain)
//文字
Text($$.label)
.fontSize('12fp')
.fontColor($r(this.current === $$.index ? 'app.color.hb_theme_color_main' : 'app.color.hb_theme_color_default'))
.fontWeight(this.current === $$.index ? FontWeight.Bold : FontWeight.Normal)
.margin({ top: 3 })
}.width('100%').onClick(() => {
this.current = $$.index
this.controller.changeIndex(this.current) //切换到当前页
})
}
build() {
Column() {
Tabs({ barPosition: BarPosition.End, controller: this.controller }) {
TabContent() {
HomeView()
}.tabBar(this.tabBuilder({
index: 0,
label: '顺口溜',
normalIcon: $r('app.media.bottom_tab_home_nor'),
selectIcon: $r('app.media.bottom_tab_home_sel')
}))
TabContent() {
KnowledgeView()
}.tabBar(this.tabBuilder({
index: 1,
label: '小游戏',
normalIcon: $r('app.media.bottom_tab_game_nor'),
selectIcon: $r('app.media.bottom_tab_game_sel')
}))
TabContent() {
AboutView()
}.tabBar(this.tabBuilder({
index: 2,
label: '关于',
normalIcon: $r('app.media.bottom_tab_my_nor'),
selectIcon: $r('app.media.bottom_tab_my_sel')
}))
}
.width('100%')
.margin({ bottom: 12 })
.barMode(BarMode.Fixed)
.scrollable(true)
.divider({
color: '#dedede',
strokeWidth: 1
})
.barBackgroundColor(Color.White)
.onChange((index: number) => {
this.current = index
})
}.width('100%')
}
}
-
通用loading
import hilog from '@ohos.hilog'
@Preview
@CustomDialog
export struct LoadingDialog {
@Prop loadingTips: string
@State rotateAngle: number = 0
controller: CustomDialogController
private tag: string = 'LoadingDialog'
aboutToAppear() {
hilog.debug(0xFFFF, this.tag, "Loading展示》》》》》")
}
build() {
Stack() {
Column() {
Image($r('app.media.hb_loading_icon'))
.height(50)
.width(50)
.rotate({ angle: this.rotateAngle })
.animation({
duration: 500,
iterations: -1,
curve: Curve.Friction
})
.onAppear(() => {
this.rotateAngle = 360
})
Text(this.loadingTips)
.fontSize('15fp')
.margin({ top: 10 })
}
.justifyContent(FlexAlign.Center)
.height(150)
.width(150)
.border({ radius: 5 })
}
}
}
-
设置项
@Entry
@Component
export struct SettingItemView {
@State attributes: SettingItemAttributes = new SettingItemAttributes()
build() {
Column() {
Row() {
Text(this.attributes.title).fontSize(16).margin({ left: 2 })
Blank()
Text(this.attributes.tips).fontSize(16).margin({ left: 8 })
Image($r('app.media.hb_arrow_right'))
.width(21)
.height(21)
.visibility(this.attributes.showArrow ? Visibility.Visible : Visibility.None)
}
.width('90%')
.padding({
left: 16,
right: 16,
top: 12,
bottom: 12
})
.margin({ left: 8, right: 8 })
.borderRadius(8)
.borderStyle(BorderStyle.Solid)
.borderColor($r('app.color.hb_item_board_color'))
.borderWidth(1)
}.width('100%')
}
}
class SettingItemAttributes {
title: string = '当前版本'
tips?: string = 'v1.0.0'
showArrow?: boolean = true
}
-
隐私协议弹框
import { LengthMetrics, router } from '@kit.ArkUI'
import { URL_PROTOCOL_PRI, URL_PROTOCOL_USE } from '../../data/Constants'
/**
* Created by ZhaoHongBo on 2024/12/9.
* Copyright (c) 2024 ZHB. All rights reserved.
*/
@CustomDialog
export default struct UserPrivacyDialog {
controller: CustomDialogController
confirm: Function = () => {
}
cancel: Function = () => {
}
build() {
Column({ space: 10 }) {
Text() {
Span('我已阅读并同意')
Span($r('app.string.user_privacy_content2'))
.fontColor($r('app.color.htitle_font_color'))
.fontWeight(FontWeight.Bold)
.onClick(() => {
this.controller.close()
router.pushUrl({
url: 'pages/HBWebPage',
params: {
url: URL_PROTOCOL_PRI
}
})
})
Span($r('app.string.user_privacy_content3'))
Span($r('app.string.user_privacy_content4'))
.fontColor($r('app.color.htitle_font_color'))
.fontWeight(FontWeight.Bold)
.onClick(() => {
this.controller.close()
router.pushUrl({
url: 'pages/HBWebPage',
params: {
url: URL_PROTOCOL_USE
}
})
})
Span(',点击以下“确定”按钮即表示同意。')
}.fontColor('#111111').lineSpacing(LengthMetrics.vp(10)).fontSize('18fp').margin({ top: 16, bottom: 8 })
Button('确定')
.backgroundColor($r('app.color.hb_theme_color_main'))
.onClick(() => {
this.confirm()
this.controller.close()
}).width('90%').margin({ bottom: 8 })
}.width('90%')
.padding(12)
}
}
-
用户协议
import { router } from '@kit.ArkUI'
import { URL_PROTOCOL_PRI, URL_PROTOCOL_USE } from '../../data/Constants'
@CustomDialog
export default struct UserPrivacyDialog {
controller: CustomDialogController
confirm: Function = () => {
}
cancel: Function = () => {
}
build() {
Column({ space: 10 }) {
Text($r('app.string.user_privacy_title'))
.fontSize(20)
.fontWeight(FontWeight.Bold).margin({ top: 12 })
Text() {
Span($r('app.string.user_privacy_content1'))
Span($r('app.string.user_privacy_content2')).fontColor($r('app.color.hb_default_hint')).onClick(() => {
this.controller.close()
router.pushUrl({
url: 'pages/HBWebPage',
params: {
url: URL_PROTOCOL_PRI
}
})
})
Span($r('app.string.user_privacy_content3'))
Span($r('app.string.user_privacy_content4')).fontColor($r('app.color.hb_default_hint')).onClick(() => {
this.controller.close()
router.pushUrl({
url: 'pages/HBWebPage',
params: {
url: URL_PROTOCOL_USE
}
})
})
Span($r('app.string.user_privacy_content5'))
}.width('100%').lineHeight(28)
Flex({ justifyContent: FlexAlign.SpaceAround }) {
Button($r('app.string.refuse_label'))
.backgroundColor($r('app.color.lightest_primary_color'))
.fontColor($r('app.color.light_gray'))
.onClick(() => {
this.cancel()
this.controller.close()
}).layoutWeight(1)
Blank().width(18)
Button($r('app.string.agree_label'))
.backgroundColor($r('app.color.primary_color'))
.fontColor($r('app.color.hb_default_hint'))
.onClick(() => {
this.confirm()
this.controller.close()
}).layoutWeight(1)
}.margin({ bottom: 10 })
}.width('90%')
.padding(12)
}
}
-
网络请求(axios二次封装)
import { promptAction } from '@kit.ArkUI';
import axios, { AxiosError, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from '@ohos/axios';
import { APP_NAME } from '../../data/Constants';
const baseUrl: string = "https://aihongbo.cn/japanapi/"
export interface HttpResponse<T> {
result: string,
data: T,
message: string
}
export interface APIErrorType {
result: string
message: string,
data: object
}
const instance = axios.create({
baseURL: baseUrl,
timeout: 30_000,
readTimeout: 30_000,
connectTimeout: 30_000
})
instance.interceptors.request.use((config: InternalAxiosRequestConfig) => {
config.headers['auth'] = APP_NAME
return config
}, (error: AxiosError) => {
return Promise.reject(error)
})
instance.interceptors.response.use((response: AxiosResponse) => {
return response
}, (error: AxiosError<APIErrorType>) => {
if (error.response?.status === 401) {
// 删除用户信息 去登录页
// auth.removeUser()
// router.pushUrl({
// url: 'pages/LoginPage'
// })
promptAction.showToast({ message: '请求资源出错' })
} else {
promptAction.showToast({ message: `${error.response?.data || error.response?.status || '请求出错'}` })
}
return Promise.reject(error)
})
export type ResponseType<T> = AxiosResponse<T>
export class HNet {
static get<T>(url: string, params?: AxiosRequestConfig): Promise<T> {
return new Promise((resolve, reject) => {
instance.get<T, ResponseType<T>>(url, params).then(response => {
let result: string = String(response.data['result'])
if (result === '200' || result === '101' || result === '1' || result === '111') {
resolve(response.data)
} else {
let apiErr: APIErrorType = {
result: result,
message: response.data['message'],
data: response
}
reject(apiErr)
}
}).catch((error: AxiosError) => {
let apiErr: APIErrorType = {
result: '000',
message: error.message,
data: error.toJSON()
}
reject(apiErr)
})
})
}
static post<T, D>(url: string, data?: D): Promise<T> {
return new Promise((resolve, reject) => {
instance.post<null, ResponseType<T>, D>(url, data).then(response => {
resolve(response.data)
}).catch((error: AxiosError) => {
reject(error)
})
})
}
static delete<T>(url: string, data?: AxiosRequestConfig): Promise<ResponseType<T>> {
return instance.delete<null, ResponseType<T>>(url, data)
}
static put<T>(url: string, data?: object): Promise<ResponseType<T>> {
return instance.put<null, ResponseType<T>>(url, data)
}
}
更多推荐


所有评论(0)