前言
项目要做一个上传 mp3 的功能
在 uniapp 中上传文件可以使用 uni.chooseFile(OBJECT) 选择文件,再通过 uni.uploadFile(OBJECT) 上传,但是这个很常用的 choosefile 并不兼容 app 端😅
想要在 app 端选择非图片或视频文件上传,就要使用 webview 了(对于图片可以使用 uni.choiseImage(OBJECT),视频可以使用uni.choiseVideo(OBJECT) )
webview
通过 webview 可以在 uniapp 应用中嵌入一个页面,可以简单理解为 iframe,简单描述 webview 的使用,详情可查看 文档 和 webview api(文档有坑,详情可往下看)
webview 两种使用方式
有两点要注意:
-
App 平台同时支持网络网页和本地网页,但本地网页及相关资源(js、css等文件)必须放在
uni-app 项目根目录->hybrid->html
文件夹下或者static
目录下 -
webview html 需要引入 uni.webview
<script type="text/javascript" src="https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js"></script>
html 页面创建好后,可通过如下两种方式引用:
- 使用 web-view 组件
<web-view src="/hybrid/html/index.html"></web-view>
- 使用
plus.webview.create
api 创建 webview
this.wv = plus.webview.create('/hybrid/html/index.html', '', {
'uni-app': 'none',
top: '50%',
left: '80%',
background: 'transparent'
});
// 当前页面其实也是一个 webview , 往当前页面 append 刚刚创建的 webview
var currentWebview = this.$scope.$getAppWebview();
currentWebview.append(this.wv);
webview 与组件的双向通讯
组件通过 查询字符串
向 webview 传参
// 组件方式:
<web-view src="/hybrid/html/index.html?data1=a&data2=b"></web-view>
// api 方式:
this.wv = plus.webview.create('/hybrid/html/index.html?data1=a&data2=b', '', {});
通过 url 查询字符串传递后,在 webview 中,可以读取并解析 location.search
获取参数
webview 通过 uni.postMessage
向组件传参
webview 通过 postMessage 向组件传参
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件上传</title>
<style>
</style>
</head>
<body>
<script type="text/javascript" src="https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js"></script>
<script>
document.addEventListener('UniAppJSBridgeReady', function () {
uni.postMessage({
data: {
number: 1,
str: '我是来自 webview 的字符串'
}
})
})
</script>
</body>
</html>
webview 组件通过监听 message 接收
<template>
<web-view src="/hybrid/html/index.html?data1=a&data2=b" @message="handleMessage"></web-view>
</template>
<script>
export default {
methods: {
handleMessage(data) {
console.log('接收到 webview 传来的数据:', data)
}
}
}
</script>
webview api 通过监听 plusMessage 接收
// webview api
this.wv = plus.webview.create(`/hybrid/html/index.html?token=${token}`, '', {
'uni-app': 'none',
top: '50%',
left: '80%',
background: 'transparent'
});
var currentWebview = this.$scope.$getAppWebview();
currentWebview.append(this.wv);
this.wv.hide()
plus.globalEvent.addEventListener('plusMessage', msg => {
if (msg.data.args.data.name == 'postMessage') {
const data = msg.data.args.data.arg
console.log('接收到 webview 传来的数据 :',data)
}
})
踩坑
一开始我采用 webview 组件的方式实现上传,因为可以很方便的双向通讯,然而却踩坑了,只要页面引入了 web-view 组件,那么这个 web-view 就会全屏显示,换句话说如果页面上有其他内容,就会被这个全屏的 web-view 遮挡住,配置其他内容的 z-index
无用
看了文档,尝试配置 fullscreen
无效,再尝试配置 webview-styles
,把宽高设置小一点,也是无效
后来发现可以通过 setStyle
修改到样式,但是要在一个定时器中执行,也就是说页面至少有一段时间是被全屏 webview 遮挡的,如下代码,至少有一秒遮挡
onReady() {
// #ifdef APP-PLUS
var currentWebview = this.$scope.$getAppWebview()
setTimeout(function() {
wv = currentWebview.children()[0]
wv.setStyle({ top:150, height:300, width:'50px' })
}, 1000); //如果是页面初始化调用时,需要延时一下
// #endif
}
于是只好改用 plus.webview.create
api 创建 webview,然而写到后面,发现文档上竟然没有 api 方式接收 webview 数据的描述,而且官方还回答说使用 plus.webview.create
创建的Webview窗口不支持相互通讯
还好后面各种搜索,找到了通讯方法,详见此贴
实现
创建 webview html
首先在 /hybrid/html 中创建 upload.html
逻辑很简单,只需要一个上传按钮和一个隐藏的 file 类型 input,点击按钮时触发上传
监听 input 的 onchange
事件,拿到上传的文件,通过 xhr 上传到服务器,拿到结果,通过 uni.postMessage
传到页面去
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件上传</title>
<style>
#upload {
}
</style>
</head>
<body>
<input type="file" id="file" style="display: none;">
<div id="upload">
本地上传
</div>
<script type="text/javascript" src="https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js"></script>
<script>
document.addEventListener('UniAppJSBridgeReady', function () {
const $fileInput = document.getElementById('file')
const $uploadBtn = document.getElementById('upload')
$uploadBtn.onclick = () => {
$fileInput.click();
}
$fileInput.onchange = (e) => {
const file = $fileInput.files[0]
console.log(file)
var formData = new FormData();
formData.append('file', file);
const query = location.search.substr(1)
const queryArr = query.split('=')
console.log(queryArr[1])
var xhr = new XMLHttpRequest();
xhr.open('POST', 'xx.xx.xx.xx:8080/file/upload');
xhr.setRequestHeader('token', queryArr[1]);
xhr.send(formData);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
const resObj = JSON.parse(xhr.responseText)
uni.postMessage({
data: {
fileName: resObj.data.fileName,
fullPath: resObj.data.fullPath
},
})
} else {
console.log(xhr.statusText);
}
}
}
})
</script>
</body>
</html>
引入 webview html
在需要上传功能的页面引入 upload.html webview,并且监听 webview 传出的数据,对数据进行处理
onReady () {
// #ifdef APP-PLUS
// 引入 upload.html 并通过 url 传入 token 参数
const token = uni.getStorageSync('Authorization')
this.wv = plus.webview.create(`/hybrid/html/upload.html?token=${token}`, '', {
'uni-app': 'none',
top: '50%',
left: '80%',
background: 'transparent'
});
var currentWebview = this.$scope.$getAppWebview();
currentWebview.append(this.wv);
// 监听 webview 传出的上传结果进行处理
plus.globalEvent.addEventListener('plusMessage', msg => {
if (msg.data.args.data.name === 'postMessage') {
const data = msg.data.args.data.arg
console.log('接收到 webview 传来的 data :',data)
// 对上传好的文件进行处理
// ...
}
})
// #endif
},
参考
Uni-app App 使用 web-view 实现文件上传
const wv = plus.webview.open(url, 'id', styles) 创建打开webview 怎么监听postMessage?