uniapp 使用 webview 实现 app 端上传非图片或视频文件

2,189 阅读2分钟

前言

项目要做一个上传 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 两种使用方式

有两点要注意:

  1. App 平台同时支持网络网页和本地网页,但本地网页及相关资源(js、css等文件)必须放在 uni-app 项目根目录->hybrid->html 文件夹下或者 static 目录下

  2. 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 页面创建好后,可通过如下两种方式引用:

  1. 使用 web-view 组件
<web-view src="/hybrid/html/index.html"></web-view>
  1. 使用 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窗口不支持相互通讯 image.png

还好后面各种搜索,找到了通讯方法,详见此贴

实现

创建 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 实现文件上传

uniapp进行文件的选取和上传

const wv = plus.webview.open(url, 'id', styles) 创建打开webview 怎么监听postMessage?