一、效果展示
二、技术栈
技术栈:
编程语言:使用 TypeScript 进行开发,借助其类型系统提升代码的可读性与稳定性。
框架与库:基于鸿蒙系统相关框架(如
@kit.ArkWeb
、@hadss/hmrouter
)进行开发,同时结合自定义的Index
模块(其中包含auth
、cameraPlugin
等)来实现特定业务功能。
核心点:
组件化开发:通过
@Component
装饰器定义MKWeb
组件,组件内封装了各种状态(如src
、title
等)和方法(如webBack
、webClose
等),使得代码结构清晰,易于维护。WebView 集成:利用
webview.WebviewController
控制 WebView,实现页面加载、刷新、导航等功能,并通过一系列on
事件(如onPageBegin
、onProgressChange
等)监听 WebView 的状态变化,实时更新组件状态,为用户提供良好的交互体验。H5 与原生交互:通过
controller.registerJavaScriptProxy
方法注册 JavaScript 代理,实现 H5 调用原生的用户信息查询、移除、更新以及相机相册调用等功能,打通了 H5 与原生应用的通信桥梁。UI 构建:运用
@Builder
装饰器构建菜单和整体 UI 布局,使用Column
、Row
等布局组件实现页面的合理排版,同时通过Image
、Text
等组件展示界面元素,并且对Image
组件进行扩展定义通用的图标样式。
三、详细源码及注解
// 导入所需的模块和类
import { auth, cameraPlugin, MkUser, SafeConstants } from "../../../../Index"
import { webview } from "@kit.ArkWeb"
import { HMRouterMgr } from "@hadss/hmrouter"
// 使用 @Component 装饰器定义一个组件
@Component
export struct MKWeb {
// 定义加载的页面地址,初始值为空字符串
src: ResourceStr = '' // 加载的页面地址
// 定义当前网页的标题,初始值为 '美寇商城'
@State title: string = 'XX商城'
// 从本地存储中获取顶部安全距离,初始值为 0
@StorageProp(SafeConstants.TOP_HEIGHT) safeTop: number = 0
// 定义是否正在加载的状态,初始值为 true
@State isLoading: boolean = true
// 定义加载进度,初始值为 0
@State Progress:number = 0
// 定义当前页面在历史记录中的索引,初始值为 0
@State historyCurrIndex: number = 0
// 定义当前页面在历史记录中的总长度,初始值为 0
@State historySize: number = 0
// 创建一个 WebviewController 实例,用于控制 WebView
controller = new webview.WebviewController()
/**
* 回到web容器的上一个页面
*/
webBack(){
// 如果当前页面在历史记录中有前一个页面,则返回上一个页面
if(this.historyCurrIndex > 0){
this.controller.backward()
}else{
// 否则,关闭当前页面
HMRouterMgr.pop()
}
}
/**
* 回到上一个页面
*/
webClose(){
// 关闭当前页面
HMRouterMgr.pop()
}
/*
*
* h5调用原生程序功能
* */
webInit(){
// 注册 JavaScript 代理,允许 H5 调用原生功能
this.controller.registerJavaScriptProxy({
// 查询当前用户信息
queryUser:():MkUser =>auth.getUser(),
// 移除当前用户信息
removeUser:():void =>auth.removeUser(),
// 更新当前用户信息
updateUser:(u:MkUser):void => auth.saveUser(u),
// 调用相机拍照并返回照片路径
pickerCamera:():Promise<string>=>cameraPlugin.pickerCamera(),
// 调用相册选择照片并返回照片路径
pickerPhoto:():Promise<string>=>cameraPlugin.pickerCamera()
},'mk',[
'queryUser',
'removeUser',
'updateUser',
'pickerCamera',
'pickerPhoto'
])
}
// 使用 @Builder 装饰器定义一个菜单构建器
@Builder
MenuBuilder() {
Menu() {
// 添加一个菜单项,点击时刷新页面
MenuItem({ content: '刷新一下' })
.onClick(() => {
this.controller.refresh()
})
}
.width(100)
.fontColor($r('app.color.text'))
.font({ size: 14 })
.radius(4)
}
// 构建组件的 UI
build() {
Column(){
/*----------------------------------导航条--------------------------------------*/
Row() {
Row() {
// 添加返回按钮,点击时调用 webBack 方法
Image($r("app.media.ic_public_left"))
.iconStyle()
.onClick(() => {
this.webBack()
})
// 添加关闭按钮,点击时调用 webClose 方法
Image($r('app.media.ic_public_close'))
.iconStyle()
.onClick(() => {
this.webClose()
})
}
.width(100)
// 显示当前网页的标题
Text(this.title)
.fontSize(16)
.fontWeight(500)
.fontColor($r('app.color.black'))
.layoutWeight(1)
.maxLines(1)
.textAlign(TextAlign.Center)
.textOverflow({ overflow: TextOverflow.MARQUEE })
Row() {
Blank()
// 添加更多操作按钮,绑定菜单
Image($r('app.media.ic_public_more'))
.iconStyle()
.bindMenu(this.MenuBuilder)
}
.width(100)
}
.height(50 + this.safeTop)
.backgroundColor($r('app.color.white'))
.padding({ top: this.safeTop })
/*---------------------------------堆叠布局-------------------------------------*/
Stack({alignContent: Alignment.Top}) {
// 如果正在加载,显示进度条
if(this.isLoading){
Progress({total: 100, value:this.Progress, type: ProgressType.Linear})
.style({strokeWidth: 2,enableSmoothEffect: true})
.color($r('app.color.red'))
.zIndex(1)
}
// 添加 WebView 组件,加载指定页面
Web({ src:this.src, controller:this.controller })
// 页面开始加载时,设置 isLoading 为 true
.onPageBegin(()=>{
this.isLoading = true
})
// 页面加载进度变化时,更新 Progress 状态
.onProgressChange((res)=>{
this.Progress = res.newProgress
// 如果加载完成,延迟 300 毫秒后设置 isLoading 为 false
if(res.newProgress == 100){
animateTo({duration: 300,delay:100}, ()=>{
this.isLoading = false
})
}
})
// 页面加载完成时,不执行任何操作
.onPageEnd(()=>{})
// 刷新历史记录时,更新当前页面的历史记录索引和总长度
.onRefreshAccessedHistory(()=>{
const history = this.controller.getBackForwardEntries()
this.historyCurrIndex = history.currentIndex
this.historySize = history.size
})
// 接收到页面标题时,更新 title 状态
.onTitleReceive((res)=>{
this.title = res.title
})
// 页面显示时,初始化 WebView
.onAppear(()=>{
this.webInit()
})
}
.width('100%')
.layoutWeight(1)
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.under'))
}
}
// 扩展 Image 组件,定义图标样式
@Extend(Image)
function iconStyle() {
.width(24)
.aspectRatio(1)
.fillColor($r('app.color.text'))
.margin(13)
}
Web组件主要完成混合开发单页面业务,在其中集成了Loading加载动画,以及注入了鸿蒙原生的功能(图库、调用相机,省市区,注:省市区是一个JSON文件通常存放在Rawfile文件下,我这里的JSON未放上啦,需要可以自己手动添加一下)
import { camera, cameraPicker } from "@kit.CameraKit";
import { fileIo } from "@kit.CoreFileKit";
import { util } from "@kit.ArkTS";
class CameraPlugin {
async pickerCamera(){
// 1. 打开相机后置摄像头得到拍照结果集
const pickerProfile: cameraPicker.PickerProfile = {
// 后置摄像头
cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK
};
// 打开相机
const pickerResult: cameraPicker.PickerResult = await cameraPicker.pick(getContext(),
// 只允许选择图片
[cameraPicker.PickerMediaType.PHOTO], pickerProfile);
// 2. 根据结果集的URI属性同步打开文件
const file = fileIo.openSync(pickerResult.resultUri)
// 3. 同步读取文件的详情信息
const stat = fileIo.statSync(file.fd)
// 4. 定义缓冲区用于保存读取的文件
const buffer = new ArrayBuffer(stat.size)
// 5. 开始同步读取内容到缓冲区
fileIo.readSync(file.fd, buffer)
// 6. 读取完毕后关闭文件流
fileIo.closeSync(file)
// 7. 借助util工具方法把读取的文件流转成base64编码的字符串
const helper = new util.Base64Helper()
// 8. 把base64编码的字符串打印出来
const str = helper.encodeToStringSync(new Uint8Array(buffer))
// 9. 打印日志
console.log('mk-logger', 'pickerCamera', str)
return str
}
}
export const cameraPlugin = new CameraPlugin()
import { photoAccessHelper } from "@kit.MediaLibraryKit"
import { fileIo } from "@kit.CoreFileKit"
import { util } from "@kit.ArkTS"
class PhotoPlugin{
async pickPhoto(){
// 1. 打开相册选择图片
let PhotoSelectOptions = new photoAccessHelper.PhotoSelectOptions()
// 设置图片类型
PhotoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE
// 设置最多可选图片数量
PhotoSelectOptions.maxSelectNumber = 1
// 设置是否显示原图
let photoPicker = new photoAccessHelper.PhotoViewPicker()
// 调用相册选择图片
const res = await photoPicker.select(PhotoSelectOptions)
// 2. 文件操作
// 2.1 获取照片的uri地址
const uri = res.photoUris[0]
// 2.2 根据uri同步打开文件
const file = fileIo.openSync(uri)
// 2.3 同步获取文件的详细信息
const stat = fileIo.statSync(file.fd)
// 2.4 创建缓冲区存储读取的文件流
const buffer = new ArrayBuffer(stat.size)
// 2.5 开始同步读取文件流到缓冲区
fileIo.readSync(file.fd, buffer)
// 2.6 关闭文件流
fileIo.closeSync(file)
// 3. 转成base64编码的字符串
const helper = new util.Base64Helper()
const str = helper.encodeToStringSync(new Uint8Array(buffer))
console.log('mk-logger', 'photoPlugin-str', str)
return str
}
}
export const photoPlugin = new PhotoPlugin()
import { util } from '@kit.ArkTS'
// 1. 定义读取的本地数据的数据类型(AreaDataItem)
export interface AreaDataItem {
code: string
name: string
areaList: AreaDataItem[]
}
// 2. 定义输出数据的数据类型(AreaColumns)
export interface AreaColumns {
province_list: Record<number, string>
city_list: Record<number, string>
county_list: Record<number, string>
}
class LocationPlugin {
async getAreaColumns(){
// 1. 定义对象用于存储转换后的数据
const areaColumns: AreaColumns = {
province_list: {},
city_list: {},
county_list: {}
}
try {
// 2. 读取rawfile目录下的本地文件
const unit8Array = getContext().resourceManager.getRawFileContentSync('area.json')
// 3. 将读取的字节数组转成字符串
const decoder = new util.TextDecoder()
const resStr = decoder.decodeToString(unit8Array)
// 4. 将读取的Json字符串转成对象数组
const areaData = JSON.parse(resStr) as AreaDataItem[]
// 5. 遍历处理数据
// 5.1 省转换
areaData.forEach((province)=>{
areaColumns.province_list[Number(province.code)] = province.name
// 5.2 市转换
province.areaList.forEach((city)=>{
areaColumns.city_list[Number(city.code)] = province.name
// 5.3 区转换
city.areaList.forEach((county)=>{
areaColumns.county_list[Number(county.code)] = county.name
})
})
})
// 6. 返回数据
AlertDialog.show({message:JSON.stringify(areaColumns,null,4)})
return areaColumns
} catch (e) {
return areaColumns
}
}
}
export const locationPlugin = new LocationPlugin()
四、总结
本篇代码主要围绕MKWeb
组件展开,综合运用多种技术实现了 Web 相关的丰富功能。从技术栈的选型到各功能模块的核心实现,都展示了在鸿蒙系统下开发 Web 交互界面的思路。然而,代码在一些细节上存在不足,如代码重复、错误处理缺失以及配置灵活性问题。通过对这些要点的分析,开发者可以在类似项目中优化代码结构,增强应用的健壮性和可维护性,从而打造出更优质的 Web 相关应用功能。有需要的可自行改造,这里这里只封装了原生的功能,但是调用相机需要真机,有条件可以自行尝试,这里的源码仅供参考,有其他的需求可以参考改造。