uniapp開發(fā)H5使用formData上傳文件解決方案
由于uniapp 的 uni.uploadFile 上傳的限制,無法實(shí)現(xiàn)上傳excel、world、ppt等類型的文件,不支持下圖的上傳方式,故只能調(diào)用原生的方法來解決。

解決方案
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)隔開)
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) {
// 通過正則表達(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)勿超過${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(`打開失敗`);
}
})
}
_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ù)端已正確開啟跨域,并且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開發(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-07
JS實(shí)現(xiàn)的簡(jiǎn)單拖拽功能示例
這篇文章主要介紹了JS實(shí)現(xiàn)的簡(jiǎn)單拖拽功能,涉及javascript鼠標(biāo)事件響應(yīng)及頁面元素屬性動(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-06
JavaScript使用Promise控制并發(fā)請(qǐng)求
當(dāng)我們需要同時(shí)處理多個(gè)請(qǐng)求時(shí),如何避免請(qǐng)求之間的沖突和混亂呢,這就是今天我們要探討的話題——如何使用Promise控制并發(fā)請(qǐng)求,感興趣的可以了解一下2023-06-06
Chrome調(diào)試折騰記之JS斷點(diǎn)調(diào)試技巧
這篇文章主要介紹了Chrome調(diào)試折騰記之JS斷點(diǎn)調(diào)試技巧,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-09-09
JavaScript使用SpreadJS創(chuàng)建Excel查看器
在現(xiàn)代的Web應(yīng)用開發(fā)中,Excel文件的處理和展示是一項(xiàng)常見的需求,小編今天將為大家展示如何借助SpreadJS來創(chuàng)建一個(gè)Excel查看器,感興趣的小伙伴可以了解下2023-12-12
d3.js 地鐵軌道交通項(xiàng)目實(shí)戰(zhàn)
這篇文章主要介紹了d3.js 地鐵軌道交通項(xiàng)目實(shí)戰(zhàn),本文通過實(shí)例代碼項(xiàng)目截圖給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-11-11

