1. web组件文件上传功能简介
鸿蒙的web组件可以加载网页,如果网页本身具备文件上传功能的话就比较尴尬了,因为html上传文件时,允许用户选择本地文件,但是鸿蒙因为安全性的考虑,只允许操作沙箱中的文件,所以在web组件中的上传功能本身无法直接使用。如果一定要使用的话,就要另辟蹊径,既然不允许选择本地文件,那么,我们给它提供沙箱中的文件就好了,web组件提供了onShowFileSelector事件,在处理具有“文件”输入类型的HTML表单时,如果用户按下“选择文件”按钮,会触发该事件,该事件的定义如下:
onShowFileSelector(callback: (event?: { result: FileSelectorResult, fileSelector: FileSelectorParam }) => boolean)
其中,参数result为FileSelectorResult类型,是重点要处理的对象,它提供了handleFileList方法,可以通知web组件选择的沙箱文件,定义如下:
handleFileList(fileList: Array<string>): void
参数fileList就是需要进行操作的文件列表。
onShowFileSelector其他的参数说明可以参考相关文档。
2. web组件文件上传文件示例
本示例运行后的界面如下所示:
该示例允许上传三种文件类型,第一种是资源文件,也就是在开发期间通过rawfile添加的文件,第二种是图片文件,会打开图片选择器让用户选择图片,第三种是普通文件,允许用户选择任意类型的文件。
下面详细介绍创建该应用的步骤。
步骤1:创建Empty Ability项目。
步骤2:在module.json5配置文件加上对权限的声明:
"requestPermissions": [{"name": "ohos.permission.INTERNET"}]
这里添加了获取互联网信息的权限。
步骤3:资源目录添加demo.txt文件,示意图如下:
步骤4:在Index.ets文件里添加如下的代码:
import fs from '@ohos.file.fs'; import picker from '@ohos.file.picker'; import web_webview from '@ohos.web.webview' @Entry @Component struct Index {//要加载的网址@State webUrl: string = "http://192.168.100.102:8081/index"//文件的沙箱路径sandboxFilePath: string = "" //上传文件的类型,0:资源文件;1:图像文件;2:普通文件,默认图像文件uploadFileType = 1 scroller: Scroller = new Scroller()controller: web_webview.WebviewController = new web_webview.WebviewController() build() {Row() {Column() {Text("Web组件文件上传示例").fontSize(14).fontWeight(FontWeight.Bold).width('100%').textAlign(TextAlign.Center).padding(10) Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {Text("网址:").fontSize(14).width(50).flexGrow(0) TextInput({ text: this.webUrl }).onChange((value) => {this.webUrl = value}).width(110).fontSize(11).flexGrow(1) Button("加载").onClick(() => {this.controller.loadUrl(this.webUrl);}).width(60).fontSize(14).flexGrow(0)}.width('100%').padding(5) Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {Text("上传文件类型:").fontSize(14).width(150).flexGrow(0)}.width('100%').padding(5) Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {Column() {Radio({ value: '', group: 'radioGroup' }).checked(this.uploadFileType === 0).height(30).width(100).onChange((isChecked: boolean) => {if (isChecked) {this.uploadFileType = 0}})Text('资源文件')} Column() {Radio({ value: '', group: 'radioGroup' }).checked(this.uploadFileType === 1).height(30).width(100).onChange((isChecked: boolean) => {if (isChecked) {this.uploadFileType = 1}})Text('图片文件')} Column() {Radio({ value: '', group: 'radioGroup' }).checked(this.uploadFileType === 2).height(30).width(100).onChange((isChecked: boolean) => {if (isChecked) {this.uploadFileType = 2}})Text('普通文件')}}.width('100%').padding(5) Scroll(this.scroller) {Web({ src: this.webUrl, controller: this.controller }).padding(10).width('100%').textZoomRatio(150).backgroundColor(0xeeeeee).onShowFileSelector((event) => {this.selectFile().then((selected) => {if (selected) {let fileList: Array<string> = [this.sandboxFilePath,]event.result.handleFileList(fileList)}})return true})}.align(Alignment.Top).backgroundColor(0xeeeeee).height(300).flexGrow(1).scrollable(ScrollDirection.Vertical).scrollBar(BarState.On).scrollBarWidth(20)}.width('100%').justifyContent(FlexAlign.Start).height('100%')}.height('100%')} //选择上传的文件async selectFile(): Promise<boolean> {//资源文件中的demo.txtif (this.uploadFileType == 0) {return this.copyResFile2Sandbox("demo.txt")} else if (this.uploadFileType == 1) {//选择图像文件let imgPicker = new picker.PhotoViewPicker();let result = await imgPicker.select();if (result.photoUris.length > 0) {return this.copySelFile2Sandbox(result.photoUris[0])}return false} else {//选择任意文件let filePicker = new picker.DocumentViewPicker();let result = await filePicker.select();if (result.length > 0) {return this.copySelFile2Sandbox(result[0])}return false}} //复制资源文件到沙箱async copyResFile2Sandbox(resFile: string): Promise<boolean> {let context = getContext(this)//计划复制到的目标路径let realUri = context.cacheDir + "/" + resFilelet rawFd = await context.resourceManager.getRawFd(resFile) //复制资源文件到沙箱cache文件夹try {fs.copyFileSync(rawFd.fd, realUri)this.sandboxFilePath = realUrireturn true} catch (err) {console.error(err.message)}return false} //复制选中文件到沙箱copySelFile2Sandbox(selectFile: string): boolean {let context = getContext(this)let segments = selectFile.split('/')//文件名称let fileName = segments[segments.length-1]//计划复制到的目标路径let realUri = context.cacheDir + "/" + fileName//复制选择的文件到沙箱cache文件夹try {let file = fs.openSync(selectFile);fs.copyFileSync(file.fd, realUri)this.sandboxFilePath = realUrireturn true} catch (err) {console.error(err.message)}return false} }
步骤5:编译运行,可以使用模拟器或者真机。
步骤6:输入包含上传功能的网页,然后单击“加载”按钮,加载网页。
步骤7:选择“资源文件”类型,然后单击web组件中的“选择文件”按钮,会选择资源文件中的demo.txt文件:
步骤8:单击“上传文件”按钮,会上传到服务端,在服务端可以看到上传的文件。
步骤9:选择“图片文件”类型,然后单击web组件中的“选择文件”按钮,会弹出图片选择器,然后选择其中一张图片:
步骤10:单击“完成”按钮,然后单击“上传文件”按钮,会上传到服务端。
步骤11:选择“普通文件”类型,然后单击web组件中的“选择文件”按钮,会弹出文件选择器,然后选择任意文件:
步骤12:同样,单击“上传文件”按钮,会上传到服务端。
这样就完成了多种文件类型的web组件上传。
3. 上传功能分析
在这三种方式中,本质上都是把文件复制到沙箱,然后把沙箱文件路径给web组件,其中比较复杂的是第一种,就是资源文件。资源文件和其他文件不太一样,不能直接复制到沙箱,而是先通过resourceManager得到资源文件RawFd,然后得到fd,最后使用该fd进行复制,代码如下:
//复制资源文件到沙箱async copyResFile2Sandbox(resFile: string): Promise<boolean> {let context = getContext(this)//计划复制到的目标路径let realUri = context.cacheDir + "/" + resFilelet rawFd = await context.resourceManager.getRawFd(resFile) //复制资源文件到沙箱cache文件夹try {fs.copyFileSync(rawFd.fd, realUri)this.sandboxFilePath = realUrireturn true} catch (err) {console.error(err.message)}return false}
另外两种都是复制普通文件到沙箱的方式,在前述文章中都多次使用,就不赘述了。
(本文作者原创,除非明确授权禁止转载)
本文源码地址:
https://gitee.com/zl3624/harmonyos_network_samples/tree/master/code/web/UploadInWeb
本系列源码地址:
https://gitee.com/zl3624/harmonyos_network_samples