uniapp開(kāi)發(fā)H5使用formData上傳文件解決方案
由于uniapp 的 uni.uploadFile 上傳的限制,無(wú)法實(shí)現(xiàn)上傳excel、world、ppt等類型的文件,不支持下圖的上傳方式,故只能調(diào)用原生的方法來(lái)解決。
解決方案
1、先創(chuàng)建一個(gè)原生上傳的組件
<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}, // 自動(dòng)上傳 instantly: {type: Boolean,default: false}, // 上傳接口參數(shù)設(shè)置 option: {type: Object,default: ()=>{}}, // 文件大小上限 size: { type: Number, default: 10 }, // 文件選擇個(gè)數(shù)上限,超出后不觸發(fā)點(diǎn)擊 count: { type: Number, default: 9 }, // 允許上傳的文件格式(多個(gè)以逗號(hào)隔開(kāi)) 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' }, // 文件選擇觸發(fā)面寬度 width: { type: String, default: '100%' }, // 文件選擇觸發(fā)面高度 height: { type: String, default: '80rpx' }, // top,left,bottom,right僅position=absolute時(shí)才需要傳入 top: { type: [String, Number], default: '' }, left: { type: [String, Number], default: '' }, bottom: { type: [String, Number], default: '' }, right: { type: [String, Number], default: '' }, // nvue不支持跟隨窗口滾動(dòng) 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 }, /** * 手動(dòng)提交上傳 * @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} 當(dāng)前上傳中的對(duì)象 */ onprogress(item,end=false) { this.$emit('progress',item); if (end) { setTimeout(()=>{ this.$emit('uploadEnd',item); },0); } }, /** * 移除組件內(nèi)緩存的某條數(shù)據(jù) * @param {string}name 文件名稱,不指定默認(rèn)清除所有文件 */ clear(name) { this.lsjFile.clear(name); }, // 創(chuàng)建選擇器 create() { // 若iOS端服務(wù)端處理不了跨域就將hybrid目錄內(nèi)的html放到服務(wù)端去,并將此處path改成服務(wù)器上的地址 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 }, // 點(diǎn)擊選擇附件 onClick() { if (this._size >= this.count) { this.toast(`只允許上傳${this.count}個(gè)文件`); 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 } /** * 創(chuàng)建File節(jié)點(diǎn) * @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; } } /** * 自動(dòng)根據(jù)字符串路徑設(shè)置對(duì)象中的值 支持.和[] * @param {Object} dataObj 數(shù)據(jù)源 * @param {String} name 支持a.b 和 a[b] * @param {String} value 值 * setValue(dataObj, name, value); */ setValue(dataObj, name, value) { // 通過(guò)正則表達(dá)式 查找路徑數(shù)據(jù) 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); // 遍歷路徑 逐級(jí)查找 最后一級(jí)用于直接賦值 for (let i = 0; i < patten.length - 1; i++) { let keyName = patten[i]; if (typeof dataObj[keyName] !== "object") dataObj[keyName] = {}; dataObj = dataObj[keyName]; } // 最后一級(jí) dataObj[patten[patten.length - 1]] = dataValue; this.debug&&console.log('參數(shù)更新后',JSON.stringify(this.option)); } /** * 設(shè)置上傳參數(shù) * @param {object|string}name 上傳參數(shù),支持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('未設(shè)置上傳地址'); } // #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(`附件大小請(qǐng)勿超過(guò)${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默認(rèn)移除所有文件,傳入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 輕提示內(nèi)容 */ toast(msg) { uni.showToast({ title: msg, icon: 'none' }); } /** * 微信小程序選擇文件 * @param {number}count 可選擇文件數(shù)量 */ 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(`打開(kāi)失敗`); } }) } _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('出錯(cuò)了,請(qǐng)檢查代碼') } _this.onchange(_this.files); break case 'progress': try { item = JSON.parse(unescape(item)); } catch (e) { return console.error('出錯(cuò)了,請(qǐng)檢查代碼') } _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('請(qǐng)求超時(shí)') 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 :請(qǐng)檢查請(qǐng)求頭Content-Type與服務(wù)端是否匹配,服務(wù)端已正確開(kāi)啟跨域,并且nginx未攔截阻止請(qǐng)求') } 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('上傳完成,微信端返回不一定是字符串,根據(jù)接口返回格式判斷是否需要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為上傳的路徑 // 文件上傳成功回調(diào) getUploadSuccess(fileData){ let resdata = JSON.parse(fileData.responseText); this.form.activityResource.push(resdata.data); this.$forceUpdate(); },
效果圖
總結(jié)
到此這篇關(guān)于uniapp開(kāi)發(fā)H5使用formData上傳文件的文章就介紹到這了,更多相關(guān)uniapp用formData上傳文件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JS實(shí)現(xiàn)點(diǎn)擊button按鈕切換圖片
這篇文章主要為大家詳細(xì)介紹了JS實(shí)現(xiàn)點(diǎn)擊button按鈕切換圖片,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07JS實(shí)現(xiàn)的簡(jiǎn)單拖拽功能示例
這篇文章主要介紹了JS實(shí)現(xiàn)的簡(jiǎn)單拖拽功能,涉及javascript鼠標(biāo)事件響應(yīng)及頁(yè)面元素屬性動(dòng)態(tài)操作相關(guān)技巧,需要的朋友可以參考下2017-03-03基于javascript html5實(shí)現(xiàn)多文件上傳
這篇文章主要為大家詳細(xì)介紹了基于javascript html5實(shí)現(xiàn)多文件上傳的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-03-03一個(gè)判斷搶購(gòu)時(shí)間是否到達(dá)的簡(jiǎn)單的js函數(shù)
這篇文章主要介紹了一個(gè)簡(jiǎn)單的判斷搶購(gòu)時(shí)間是否到達(dá)的js函數(shù),原理很簡(jiǎn)單,找到時(shí)鐘的id,計(jì)算數(shù)值,到達(dá)搶購(gòu)時(shí)間時(shí)執(zhí)行任務(wù),需要的朋友可以參考下2014-06-06JavaScript使用Promise控制并發(fā)請(qǐng)求
當(dāng)我們需要同時(shí)處理多個(gè)請(qǐng)求時(shí),如何避免請(qǐng)求之間的沖突和混亂呢,這就是今天我們要探討的話題——如何使用Promise控制并發(fā)請(qǐng)求,感興趣的可以了解一下2023-06-06Chrome調(diào)試折騰記之JS斷點(diǎn)調(diào)試技巧
這篇文章主要介紹了Chrome調(diào)試折騰記之JS斷點(diǎn)調(diào)試技巧,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-09-09JavaScript使用SpreadJS創(chuàng)建Excel查看器
在現(xiàn)代的Web應(yīng)用開(kāi)發(fā)中,Excel文件的處理和展示是一項(xiàng)常見(jiàn)的需求,小編今天將為大家展示如何借助SpreadJS來(lái)創(chuàng)建一個(gè)Excel查看器,感興趣的小伙伴可以了解下2023-12-12d3.js 地鐵軌道交通項(xiàng)目實(shí)戰(zhàn)
這篇文章主要介紹了d3.js 地鐵軌道交通項(xiàng)目實(shí)戰(zhàn),本文通過(guò)實(shí)例代碼項(xiàng)目截圖給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-11-11