uniapp开发H5使用formData上传文件解决方案
更新时间:2023年12月06日 14:47:07 作者:QQ帝国
我们很多时候上传文件就是使用FormData,然而uniapp默认不支持FormData类型数据的上传,下面这篇文章主要给大家介绍了关于uniapp开发H5使用formData上传文件的相关资料,需要的朋友可以参考下
由于uniapp 的 uni.uploadFile 上传的限制,无法实现上传excel、world、ppt等类型的文件,不支持下图的上传方式,故只能调用原生的方法来解决。
解决方案
1、先创建一个原生上传的组件
<template> <view class="lsj-file" :style="[getStyles]"> <view ref="lsj" class="hFile" :style="[getStyles]" @click="onClick"> <slot><view class="defview" :style="[getStyles]">上传</view></slot> </view> </view> </template> <script> import {LsjFile} from './LsjFile.js' export default { name: 'Lsj-upload', props: { // 打印日志 debug: {type: Boolean,default: false}, // 自动上传 instantly: {type: Boolean,default: false}, // 上传接口参数设置 option: {type: Object,default: ()=>{}}, // 文件大小上限 size: { type: Number, default: 10 }, // 文件选择个数上限,超出后不触发点击 count: { type: Number, default: 9 }, // 允许上传的文件格式(多个以逗号隔开) formats: { type: String, default:''}, // input file选择限制 accept: {type: String,default: ''}, // 微信选择文件类型 //all=从所有文件选择, //video=只能选择视频文件, //image=只能选择图片文件, //file=可以选择除了图片和视频之外的其它的文件 wxFileType: { type: String, default: 'all' }, // webviewID需唯一,不同窗口也不要同Id childId: { type: String, default: 'lsjUpload' }, // 文件选择触发面宽度 width: { type: String, default: '100%' }, // 文件选择触发面高度 height: { type: String, default: '80rpx' }, // top,left,bottom,right仅position=absolute时才需要传入 top: { type: [String, Number], default: '' }, left: { type: [String, Number], default: '' }, bottom: { type: [String, Number], default: '' }, right: { type: [String, Number], default: '' }, // nvue不支持跟随窗口滚动 position: { type: String, // #ifdef APP-NVUE default: 'absolute', // #endif // #ifndef APP-NVUE default: 'static', // #endif }, }, data() { return { } }, watch: { option(v) { // #ifdef APP-PLUS this.lsjFile&&this.show(); // #endif } }, updated() { // #ifdef APP-PLUS if (this.isShow) { this.lsjFile&&this.show(); } // #endif }, computed: { getStyles() { let styles = { width: this.width, height: this.height } if (this.position == 'absolute') { styles['top'] = this.top styles['bottom'] = this.bottom styles['left'] = this.left styles['right'] = this.right styles['position'] = 'fixed' } return styles } }, mounted() { this._size = 0; let WEBID = this.childId + new Date().getTime(); this.lsjFile = new LsjFile({ id: WEBID, debug: this.debug, width: this.width, height: this.height, option: this.option, instantly: this.instantly, // 限制条件 prohibited: { // 大小 size: this.size, // 允许上传的格式 formats: this.formats, // 限制选择的格式 accept: this.accept, count: this.count }, onchange: this.onchange, onprogress: this.onprogress, }); this.create(); }, beforeDestroy() { // #ifdef APP-PLUS this.lsjFile.dom.close(); // #endif }, methods: { setFiles(array) { if (array instanceof Map) { for (let [key, item] of array) { item['progress'] = 100; item['type'] = 'success'; this.lsjFile.files.set(key,item); } } else if (Array.isArray(array)) { array.forEach(item=>{ if (item.name) { item['progress'] = 100; item['type'] = 'success'; this.lsjFile.files.set(item.name,item); } }); } this.onchange(this.lsjFile.files); }, setData() { this.lsjFile&&this.lsjFile.setData(...arguments); }, getDomStyles(callback) { // #ifndef APP-NVUE let view = uni .createSelectorQuery() .in(this) .select('.lsj-file') view.fields( { size: true, rect: true }, ({ height, width, top, left, right, bottom }) => { uni.createSelectorQuery() .selectViewport() .scrollOffset(({ scrollTop }) => { return callback({ top: parseInt(top) + parseInt(scrollTop) + 'px', left: parseInt(left) + 'px', width: parseInt(width) + 'px', height: parseInt(height) + 'px' }) }) .exec() } ).exec() // #endif // #ifdef APP-NVUE const dom = weex.requireModule('dom') dom.getComponentRect(this.$refs.lsj, ({ size: { height, width, top, left, right, bottom } }) => { return callback({ top: parseInt(top) + 'px', left: parseInt(left) + 'px', width: parseInt(width) + 'px', height: parseInt(height) + 'px', right: parseInt(right) + 'px', bottom: parseInt(bottom) + 'px' }) }) // #endif }, show() { this.isShow = true; // #ifdef APP-PLUS this.lsjFile&&this.getDomStyles(styles => { this.lsjFile.dom.setStyle(styles) }); // #endif // #ifdef H5 this.lsjFile.dom.style.display = 'inline' // #endif }, hide() { this.isShow = false; // #ifdef APP-PLUS this.lsjFile&&this.lsjFile.dom.setStyle({ top: '-100px', left:'0px', width: '1px', height: '100px', }); // #endif // #ifdef H5 this.lsjFile.dom.style.display = 'none' // #endif }, /** * 手动提交上传 * @param {string}name 文件名称,不传则上传所有type等于waiting和fail的文件 */ upload(name) { this.lsjFile&&this.lsjFile.upload(name); }, /** * @returns {Map} 已选择的文件Map集 */ onchange(files) { this.$emit('change',files); this._size = files.size; return files.size >= this.count ? this.hide() : this.show(); }, /** * @returns {object} 当前上传中的对象 */ onprogress(item,end=false) { this.$emit('progress',item); if (end) { setTimeout(()=>{ this.$emit('uploadEnd',item); },0); } }, /** * 移除组件内缓存的某条数据 * @param {string}name 文件名称,不指定默认清除所有文件 */ clear(name) { this.lsjFile.clear(name); }, // 创建选择器 create() { // 若iOS端服务端处理不了跨域就将hybrid目录内的html放到服务端去,并将此处path改成服务器上的地址 let path = '/uni_modules/lsj-upload/hybrid/html/uploadFile.html'; let dom = this.lsjFile.create(path); // #ifdef H5 this.$refs.lsj.$el.appendChild(dom); // #endif // #ifndef APP-PLUS this.show(); // #endif // #ifdef APP-PLUS dom.setStyle({position: this.position}); dom.loadURL(path); setTimeout(()=>{ // #ifdef APP-NVUE plus.webview.currentWebview().append(dom); // #endif // #ifndef APP-NVUE this.$root.$scope.$getAppWebview().append(dom); // #endif this.show(); },300) // #endif }, // 点击选择附件 onClick() { if (this._size >= this.count) { this.toast(`只允许上传${this.count}个文件`); return; } // #ifdef MP-WEIXIN if (!this.isShow) {return;} let count = this.count - this._size; this.lsjFile.chooseMessageFile(this.wxFileType,count); // #endif }, toast(msg) { uni.showToast({ title: msg, icon: 'none' }); } } } </script> <style scoped> .lsj-file { /* display: inline-block; */ } .defview { /* background-color: #007aff; color: #fff; border-radius: 10rpx; display: flex; align-items: center; justify-content: center; font-size: 28rpx; */ } .hFile { /* position: relative; overflow: hidden; */ } </style>
LsjFile.js
export class LsjFile { constructor(data) { this.dom = null; // files.type = waiting;//(等待上传)|| loading(上传中)|| success(成功) || fail(失败) this.files = new Map(); this.debug = data.debug || false; this.id = data.id; this.width = data.width; this.height = data.height; this.option = data.option; this.instantly = data.instantly; this.prohibited = data.prohibited; this.onchange = data.onchange; this.onprogress = data.onprogress; this.uploadHandle = this._uploadHandle; // #ifdef MP-WEIXIN this.uploadHandle = this._uploadHandleWX; // #endif } /** * 创建File节点 * @param {string}path webview地址 */ create(path) { if (!this.dom) { // #ifdef H5 let dom = document.createElement('input'); dom.type = 'file' dom.value = '' dom.style.height = this.height dom.style.width = this.width // dom.style.visibility = 'hidden' dom.style.position = 'absolute' dom.style.top = 0 dom.style.left = 0 dom.style.right = 0 dom.style.bottom = 0 dom.style.opacity = 0 dom.style.zIndex = 0 dom.accept = this.prohibited.accept; if (this.prohibited.count > 1) { dom.multiple = 'multiple'; } dom.onchange = event => { for (let file of event.target.files) { this.addFile(file); } this.dom.value = ''; }; this.dom = dom; // #endif // #ifdef APP-PLUS let styles = { top: '-100px', left: 0, width: '1px', height: '1px', background: 'transparent' }; let extras = { debug: this.debug, instantly: this.instantly, prohibited: this.prohibited, } this.dom = plus.webview.create(path, this.id, styles,extras); this.setData(this.option); this._overrideUrlLoading(); // #endif return this.dom; } } copyObject(obj) { if (typeof obj !== "undefined") { return JSON.parse(JSON.stringify(obj)); } else { return obj; } } /** * 自动根据字符串路径设置对象中的值 支持.和[] * @param {Object} dataObj 数据源 * @param {String} name 支持a.b 和 a[b] * @param {String} value 值 * setValue(dataObj, name, value); */ setValue(dataObj, name, value) { // 通过正则表达式 查找路径数据 let dataValue; if (typeof value === "object") { dataValue = this.copyObject(value); } else { dataValue = value; } let regExp = new RegExp("([\\w$]+)|\\[(:\\d)\\]", "g"); const patten = name.match(regExp); // 遍历路径 逐级查找 最后一级用于直接赋值 for (let i = 0; i < patten.length - 1; i++) { let keyName = patten[i]; if (typeof dataObj[keyName] !== "object") dataObj[keyName] = {}; dataObj = dataObj[keyName]; } // 最后一级 dataObj[patten[patten.length - 1]] = dataValue; this.debug&&console.log('参数更新后',JSON.stringify(this.option)); } /** * 设置上传参数 * @param {object|string}name 上传参数,支持a.b 和 a[b] */ setData() { let [name,value = ''] = arguments; if (typeof name === 'object') { Object.assign(this.option,name); } else { this.setValue(this.option,name,value); } this.debug&&console.log(JSON.stringify(this.option)); // #ifdef APP-PLUS this.dom.evalJS(`vm.setData('${JSON.stringify(this.option)}')`); // #endif } /** * 上传 * @param {string}name 文件名称 */ async upload(name='') { if (!this.option.url) { throw Error('未设置上传地址'); } // #ifndef APP-PLUS console.log('has',this.files.has(name)) if (name && this.files.has(name)) { await this.uploadHandle(this.files.get(name)); } else { for (let item of this.files.values()) { if (item.type === 'waiting' || item.type === 'fail') { await this.uploadHandle(item); } } } // #endif // #ifdef APP-PLUS this.dom&&this.dom.evalJS(`vm.upload('${name}')`); // #endif } // 选择文件change addFile(file) { let name = file.name; this.debug&&console.log('文件名称',name,'大小',file.size); if (file) { // 限制文件格式 let path = ''; let suffix = name.substring(name.lastIndexOf(".")+1).toLowerCase(); let formats = this.prohibited.formats.toLowerCase(); if (formats&&!formats.includes(suffix)) { this.toast(`不支持上传${suffix.toUpperCase()}格式文件`); return false; } // 限制文件大小 if (file.size > 1024 * 1024 * Math.abs(this.prohibited.size)) { this.toast(`附件大小请勿超过${this.prohibited.size}M`) return false; } // #ifndef MP-WEIXIN path = URL.createObjectURL(file); // #endif // #ifdef MP-WEIXIN path = file.path; // #endif this.files.set(file.name,{file,path,name: file.name,size: file.size,progress: 0,type: 'waiting'}); // #ifndef MP-WEIXIN this.onchange(this.files); this.instantly&&this.upload(); // #endif // #ifdef MP-WEIXIN return true; // #endif } } /** * 移除文件 * @param {string}name 不传name默认移除所有文件,传入name移除指定name的文件 */ clear(name='') { // #ifdef APP-PLUS this.dom&&this.dom.evalJS(`vm.clear('${name}')`); // #endif if (!name) { this.files.clear(); } else { this.files.delete(name); } return this.onchange(this.files); } /** * 提示框 * @param {string}msg 轻提示内容 */ toast(msg) { uni.showToast({ title: msg, icon: 'none' }); } /** * 微信小程序选择文件 * @param {number}count 可选择文件数量 */ chooseMessageFile(type,count) { wx.chooseMessageFile({ count: count, type: type, success: ({ tempFiles }) => { for (let file of tempFiles) { let next = this.addFile(file); if (!next) {return} } this.onchange(this.files); this.instantly&&this.upload(); }, fail: () => { this.toast(`打开失败`); } }) } _overrideUrlLoading() { this.dom.overrideUrlLoading({ mode: 'reject' }, e => { let {retype,item,files,end} = this._getRequest( e.url ); let _this = this; switch (retype) { case 'updateOption': this.dom.evalJS(`vm.setData('${JSON.stringify(_this.option)}')`); break case 'change': try { _this.files = new Map([..._this.files,...JSON.parse(unescape(files))]); } catch (e) { return console.error('出错了,请检查代码') } _this.onchange(_this.files); break case 'progress': try { item = JSON.parse(unescape(item)); } catch (e) { return console.error('出错了,请检查代码') } _this._changeFilesItem(item,end); break default: break } }) } _getRequest(url) { let theRequest = new Object() 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 } _changeFilesItem(item,end=false) { this.debug&&console.log('onprogress',JSON.stringify(item)); this.onprogress(item,end); this.files.set(item.name,item); } // 上传接口 _uploadHandle(item) { item.type = 'loading'; delete item.responseText; return new Promise((resolve,reject)=>{ this.debug&&console.log('option',JSON.stringify(this.option)); let {url,name,method='POST',header,formData} = this.option; let form = new FormData(); for (let keys in formData) { form.append(keys, formData[keys]) } form.append(name, item.file); let xmlRequest = new XMLHttpRequest(); xmlRequest.open(method, url, true); for (let keys in header) { xmlRequest.setRequestHeader(keys, header[keys]) } xmlRequest.upload.addEventListener( 'progress', event => { if (event.lengthComputable) { let progress = Math.ceil((event.loaded * 100) / event.total) if (progress <= 100) { item.progress = progress; this._changeFilesItem(item); } } }, false ); xmlRequest.ontimeout = () => { console.error('请求超时') item.type = 'fail'; this._changeFilesItem(item,true); return resolve(false); } xmlRequest.onreadystatechange = ev => { if (xmlRequest.readyState == 4) { if (xmlRequest.status == 200) { this.debug&&console.log('上传完成1:' + xmlRequest.responseText) item['responseText'] = xmlRequest.responseText; item.type = 'success'; this._changeFilesItem(item,true); return resolve(true); } else if (xmlRequest.status == 0) { console.error('status = 0 :请检查请求头Content-Type与服务端是否匹配,服务端已正确开启跨域,并且nginx未拦截阻止请求') } console.error('--ERROR--:status = ' + xmlRequest.status) item.type = 'fail'; this._changeFilesItem(item,true); return resolve(false); } } xmlRequest.send(form) }); } _uploadHandleWX(item) { item.type = 'loading'; delete item.responseText; return new Promise((resolve,reject)=>{ this.debug&&console.log('option',JSON.stringify(this.option)); let form = {filePath: item.file.path,...this.option }; form['fail'] = ({ errMsg = '' }) => { console.error('--ERROR--:' + errMsg) item.type = 'fail'; this._changeFilesItem(item,true); return resolve(false); } form['success'] = res => { if (res.statusCode == 200) { this.debug&&console.log('上传完成,微信端返回不一定是字符串,根据接口返回格式判断是否需要JSON.parse:' + res.data) item['responseText'] = res.data; item.type = 'success'; this._changeFilesItem(item,true); return resolve(true); } item.type = 'fail'; this._changeFilesItem(item,true); return resolve(false); } let xmlRequest = uni.uploadFile(form); xmlRequest.onProgressUpdate(({ progress = 0 }) => { if (progress <= 100) { item.progress = progress; this._changeFilesItem(item); } }) }); } }
2、使用组件
引入组件后使用
<uploadFile @getUploadSuccess="getUploadSuccess" :uploadUrl="uploadbeforeCreatedUrl" :formData="{}" formatType="png,jpg,jpeg,pdf,docx,doc,xlsx,xls,ppt,pptx"> <image :src="addIcon" mode="scaleToFill" /> </uploadFile> // uploadUrl为上传的路径 // 文件上传成功回调 getUploadSuccess(fileData){ let resdata = JSON.parse(fileData.responseText); this.form.activityResource.push(resdata.data); this.$forceUpdate(); },
效果图
总结
到此这篇关于uniapp开发H5使用formData上传文件的文章就介绍到这了,更多相关uniapp用formData上传文件内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
js 开发之autocomplete="off"在chrom中失效的解决办法
这篇文章主要介绍了js 开发之autocomplete="off"在chrom中失效的解决办法的相关资料,希望通过本文能帮助到大家,解决遇到这样的问题,需要的朋友可以参考下2017-09-09
最新评论