1. 前言
在做订单评论的时候,需要图片上传,开始使用的 FormData 上传,但是后端一直获取不到上传的 file ,由于之前是使用 taro 开发的H5和微信小程序,所以对 uniapp 开发不是很熟悉,使用了创建 input 标签,然后设置属性,来获取图片,最后提交上传。结果发现还是提交不成功,最后发现在 taro 那边使用的将图片转换为 base64 ,然后再提交上传,就按照这个思路实现。
2. H5 将选择图片转 base64
2.1 使用 XMLHttpRequest 和 FileReader 实现图片转 base64
ini
// H5端获取图片的base64
function getWebBase64 (filePath) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open('GET', filePath, true);
xhr.responseType = 'blob';
xhr.onload = function() {
if (this.status === 200) {
let fileReader = new FileReader();
fileReader.onload = function(e) {
resolve(e.target.result);
}
fileReader.onerror = reject;
fileReader.readAsDataURL(this.response);
}
}
xhr.onerror = reject;
xhr.send();
})
}
2.2 使用 fetch 和 FileReader 实现图片转 base64
javascript
// H5端获取图片的base64
function getWebBase64 (filePath) {
return new Promise((resolve, reject) => {
fetch(filePath).then(fetchRes => {
return fetchRes.blob()
}).then(data => {
let fr = new FileReader()
fr.onload = function(){
resolve(fr.result)
}
fr.readAsDataURL(data)
})
})
}
2.3 使用 canvas 实现图片转 base64
ini
// H5端获取图片的base64
function getWebBase64 (filePath) {
return new Promise((resolve, reject) => {
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
let img = new Image;
img.onload = function() {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
resolve(canvas.toDataURL());
canvas.height = canvas.width = 0;
}
img.onerror = reject;
img.src = filePath;
})
}
3. 微信小程序 将选择图片转 base64
javascript
// 微信小程序端获取图片的base64
function getWeappBase64 (filePath) {
return new Promise((resolve, reject)=>{
let fileManage = uni.getFileSystemManager();
fileManage.readFile({
filePath,
encoding:'base64',
success:(e)=>{
resolve(`data:image/jpg;base64,${e.data}`)
},
fail: err => {
reject(err)
}
})
})
}
4. 判断环境调用不同的方法
scss
//上传的图片转为base64
export function fileToBase64 (filePath) {
// #ifdef MP-WEIXIN
return getWeappBase64(filePath)
// #endif
// #ifdef H5
return getWebBase64(filePath)
// #endif
}
5. 组件实现
- HTML 实现上传图片的盒子;
- 引入文件转 base64 的方法,上边我们实现了方法的具体实现,直接复制就可以使用;
- 引入 axios 请求的上传接口, ev 发布订阅实现的仅当前次上传成功,才能进行下次上传;
- handleUploadImage 上传方法的实现:
- 定义一个上传事件 RUI_HANDLE_UPLOAD_IMAGE_EVENT,只有提交成功,事件才会清除,允许下次提交;
- 事件回调函数会返回一个 done 函数,用于在上传成功或者失败的时候告诉RUI_HANDLE_UPLOAD_IMAGE_EVENT提交完成,清除当前次订阅;
- uni.chooseImage 选择一张图片,限制一张一张上传,是防止上传图片过大,转换 base64 导致提交数据过大,处理慢的问题;
- 获取选择图片的图片信息 uni.getImageInfo;
- 通过 fileToBase64 将图片转换为 base64;
- 调用 axios.uploadOssUploadString 上传图片,注意此处使用自己的上传接口;
- 通过 success 返回上传成功的结果;
- 最后执行 done 函数,可以进行下次提交订阅。
xml
<template>
<view
@click="handleUploadImage"
class="rui-upload-image-component-content">
<slot></slot>
</view>
</template>
<script>
import { fileToBase64 } from './index';
import { axios, ev } from '@/utils';
export default {
name: 'RuiUploadImage',
methods: {
handleUploadImage(){
ev.once('RUI_HANDLE_UPLOAD_IMAGE_EVENT', done => {
new Promise((resolve, reject) => {
// 选择图片
uni.chooseImage({
count: 1,
success: ({ tempFilePaths, tempFiles }) => {
return resolve(tempFilePaths)
},
fail: reject
})
}).then(tempFilePaths => {
return new Promise((resolve, reject) => {
// 获取图片信息,获取图片base64
uni.getImageInfo({
src: tempFilePaths[0],
success: ({ path }) => {
fileToBase64(path).then(base64 => {
resolve(base64)
}).catch(reject)
},
fail: reject
})
})
}).then(file => {
// 上传图片到oss
return axios.uploadOssUploadString({ file, type: 0, level: 1})
}).then(res => {
// 返回id获取完整地址
this.$emit('success', { ...res })
}).finally(done);
})
}
}
}
</script>
6. 使用
6.1 script 实现
javascript
import RuiUploadImage from '@/components/RuiUploadImage/index.vue';
export default {
components: {
RuiUploadImage
},
data() {
return {
evaluateInfo: {
order_id: '',
order_service_score: 5,
content: '',
images: []
},
imageMap: new Map()
}
},
methods: {
// 选择上传的图片
success(res){
this.imageMap.set(res.file_id, res)
this.evaluateInfo.images = [...this.imageMap.values()]
},
// 删除图片
deleteImage(file_id){
this.imageMap.delete(file_id)
this.evaluateInfo.images = [...this.imageMap.values()]
}
}
}
6.2 HTML 实现
ini
<view class="upload-box-list rui-flex-ac">
<view class="rui-flex-ac">
<view class="rui-upload-handle-box rui-pr rui-mr15"
v-for="item in evaluateInfo.images"
:key="item.file_id">
<image :src="icon.paySuccessClose" @click="deleteImage(item.file_id)" class="rui-delete-image rui-icon32"/>
<image :src="item.url" class="rui-upload-handle-box"/>
</view>
</view>
<RuiUploadImage @success="success" v-if="evaluateInfo.images.length < 3">
<view class="rui-upload-handle-box rui-flex-cc">
<view class="rui-fa">
<view class="rui-flex-cc">
<image :src="icon.evaluateUpload" class="rui-icon60"/>
</view>
<view class="rui-fs24 rui-color7 rui-mt10">添加图片</view>
</view>
</view>
</RuiUploadImage>
</view>
7. 实现结果

8. 注意
- 由于该项目只需要H5和微信小程序,所以只做了H5和微信小程序的兼容,如果需要更加全面的转换 base64 建议使用插件市场的插件;
- 此处可能有人注意到我保存图片使用的是 Map 对象,然后展示的时候使用的数组,为什么要分开操作呢?是操作数组的删除会比较麻烦,我这里不管添加还是删除,直接操作 Map 对象,然后将 Map 的最新 value 列表给渲染列表,实现一样的操作,但是简化了我遍历的步骤,不用查找位置再删除,直接删除 Map 对应的 key ,然后获取最新的 value 列表就好。