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.txt
if (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 + "/" + resFile
let rawFd = await context.resourceManager.getRawFd(resFile)
//复制资源文件到沙箱cache文件夹
try {
fs.copyFileSync(rawFd.fd, realUri)
this.sandboxFilePath = realUri
return 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 = realUri
return 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 + "/" + resFile
let rawFd = await context.resourceManager.getRawFd(resFile)
//复制资源文件到沙箱cache文件夹
try {
fs.copyFileSync(rawFd.fd, realUri)
this.sandboxFilePath = realUri
return 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
评论