Uni-App笔记 App端文件上传
由于 uni.chooseFile(OBJECT) api 不支持 App端,故使用 plus.webview 导入 html 来进行文件的选择
Web-View 使用注意事项
小程序仅支持加载网络网页,不支持本地的html
补充说明:app-vue下web-view组件不支持自定义样式,而v-show的本质是改变组件的样式。即组件支持v-if而不是支持v-show。
小程序端 web-view 组件一定有原生导航栏,下面一定是全屏的 web-view 组件,navigationStyle: custom 对 web-view 组件无效
App 端使用 自定义组件模式 时,uni.web-view.js 的最低版为 uni.webview.1.5.2.js
App 平台同时支持网络网页和本地网页,但本地网页及相关资源(js、css等文件)必须放在 uni-app 项目根目录->hybrid->html 文件夹下或者 static 目录下
<!-- src/hybrid/html/upload.html -->
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title class="title">文件上传</title>
<meta name="viewport"
content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<style type="text/css">
* {
padding: 0;
margin: 0;
border: 0;
}
.content {
background: transparent;
}
.mask {
width: 100%;
height: 100vh;
background-color: rgba(0, 0, 0, .3);
position: relative;
}
.loading {
position: absolute;
left: 50%;
top: 50%;
margin-left: -100px;
margin-top: -25px;
width: 200px;
height: 90px;
background-color: #fff;
border-radius: 15px;
color: #a8a8a8;
text-align: center;
line-height: 50px;
font-size: 20px;
display: none;
}
.cancel {
height: 40px;
background-color: #c2c2c2;
border-radius: 0 0 15px 15px;
line-height: 40px;
color: #fff;
}
.btn {
position: fixed;
bottom: 0;
width: 100%;
height: 60px;
text-align: center;
line-height: 60px;
background-color: #001529;
color: #fff;
font-size: 20px;
}
.file {
position: absolute;
width: 100%;
height: 60px;
top: 0;
left: 0;
opacity: 0;
}
</style>
</head>
<body>
<div class="content">
<div class="mask">
</div>
<div class="loading">
<div class="progress">上传中...</div>
<div class="cancel">取消</div>
</div>
<div class="btn">
点击选择文件
<input class="file" type="file" />
</div>
</div>
<!-- 导入官方js -->
<script type="text/javascript" src="https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js"></script>
<!-- 上传js -->
<script src="js/upload.js" type="text/javascript" charset="utf-8"></script>
</body>
</html>
let mask = document.querySelector(".mask");
let fileInput = document.querySelector(".file");
let loading = document.querySelector(".loading");
let progress = document.querySelector(".progress");
let cancel = document.querySelector(".cancel");
/**
* 上传
* @param {*} file 二进制文件流
* @param {*} url 上传接口url
* @param {*} key 二进制文件流名称
* @param {*} header 请求头
* @param {*} data form数据
* @returns
*/
let upload = (file, url, key = 'file', header = {}, data = {}) => {
// 没有url取消上传
if (!url) {
return;
}
// 显示上传
loading.style.display = 'block';
// 创建 FormData 表单对象
let formData = new FormData();
// 导入表单数据
for (let keys in data) {
formData.append(keys, data[keys]);
}
// 导入文件流
formData.append(key, file);
// 创建 Ajax 请求对象
let xhr = new XMLHttpRequest();
xhr.open("POST", url, true);
// 导入header
for (let keys in header) {
xhr.setRequestHeader(keys, header[keys]);
}
// 设置进度
xhr.upload.addEventListener("progress", function (event) {
if (event.lengthComputable) {
// event.loaded 已上传 event.total 总数
let percent = Math.ceil(event.loaded * 100 / event.total) + "%";
progress.innerText = `上传中... ${percent}`;
}
}, false);
// 请求超时事件处理
xhr.ontimeout = function () {
progress.innerText = 'request timeout';
setTimeout(() => {
// 关闭 Loading
loading.style.display = 'none';
// 关闭 WebView 页面
plus.webview.currentWebview().close();
}, 1000);
};
xhr.onreadystatechange = (ev) => {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
progress.innerText = '上传成功';
// 上传成功
// escape 进行路由编码
// location.href 用于将数据传输到应用上通过 WebView 的 overrideUrlLoading 方法进行接受
location.href = `callback?fileName=${escape(file.name)}&responseText=${escape(xhr.responseText)}`;
} else {
progress.innerText = '上传失败';
}
}
setTimeout(() => {
// 关闭 Loading
loading.style.display = 'none';
// 关闭 WebView 页面
plus.webview.currentWebview().close();
}, 1000);
}
};
// 发送
xhr.send(formData);
// 取消上传
cancel.addEventListener("click", () => {
xhr.abort();
// 关闭 WebView 页面
plus.webview.currentWebview().close();
});
}
// 点击遮罩关闭 WebView 页面
mask.addEventListener("click", () => {
plus.webview.currentWebview().close();
});
// 页面加载完成触发 UniAppJSBridgeReady
document.addEventListener('UniAppJSBridgeReady', () => {
// 从当前页面获取参数
let { url, key, header, formData } = plus.webview.currentWebview();
// 清空文件输入框
fileInput.value = '';
// 选择文件触发
fileInput.addEventListener('change', (event) => {
let file = fileInput.files[0];
// 默认限制文件大小,10M
if (file.size > (1024 * 1024 * 10)) {
plus.nativeUI.alert('上传文件大小不得超过10M');
return;
}
// 上传文件
upload(file, url, key, header, formData);
}, false);
});
H5+规范:WebView
// 触发函数
export function uploadFileHandle({
currentWebview,
action,
name = 'file',
header = {},
formData = {}
}: {
currentWebview: any,
action: string,
name: string,
header?: any,
formData?: any
}) {
return new Promise((resolve, reject) => {
let wvPath = '/hybrid/html/upload.html';
// 创建WebView页面
let wv = plus.webview.create("", wvPath, {
top: '0',
height: '100%',
background: 'transparent'
}, {
url: action,
header: header,
formData: formData,
key: name,
});
// 载入upload.html
wv.loadURL(wvPath);
// 加入当前页
currentWebview.append(wv);
// 接收响应的值 overrideUrlLoading 会组织跳转
wv.overrideUrlLoading({ mode: 'reject' }, (e) => {
// 解析出 fileName, str
let { fileName, responseText } = getRequest(e.url);
// unescape 解码
fileName = unescape(fileName);
responseText = unescape(responseText);
responseText = JSON.parse(responseText);
// 返回数据
resolve({ fileName, ...responseText })
});
})
}
// 路由参数处理
function getRequest(url: string) {
let theRequest = {} as any;
let index = url.indexOf("?");
if (index != -1) {
let str = url.substring(index + 1);
let strs = str.split("&");
for (let i = 0; i < strs.length; i++) {
theRequest[`${strs[i].split("=")[0]}`] = unescape(strs[i].split("=")[1]);
}
}
return theRequest;
}
// 调用
uploadFileHandle({
// #ifdef APP-PLUS
currentWebview: (this.$mp as any).page.$getAppWebview(), // Vue 中获取当前WebView页面
// #endif
action: this.action, // 上传api
name: 'file', // 文件流名称
header: this.header, // 请求头
formData: { // 请求中其他数据
...
}
}).then(result => {
// 成功
this.success(result)
})