Bootstrap

Uni-App笔记 App端文件上传

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)
 })

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;