开发工具:HBuilder(v3.8.7.20230703); 框架:uni-app(vue3);
由于第一次使用uniapp开发APP,开发中遇到app上传图片的问题,这个问题是困扰我时间最长的,查找了各种文章,所以记录一下。
大家肯定和我一样一开始都没有考虑,以为
uni.uploadFile()可以直接上传到OSS,我替大家试了,不行。
原因:APP上传拿到的不是一个file格式数据,是一个app的临时链接,无法上传到oss,要转换为file格式才可以, 但是转file要使用nodejs中的内置模块,可能是uniapp没有加载nodejs内置模块无法使用,然后可以使用renderjs实现。
renderjs是一个运行在视图层的js, 它只支持app-vue和h5, 主要服务于APP。
基础使用
<template>
<view>
<uni-file-picker
v-if="!slots.default"
v-model="fileList"
:limit="limit"
:title="title"
:readonly="disabled"
@select="handleSelectImage"
/>
// info:传到renderjs中的参数
// change:info="uploadFile.tofile" 监听info的变化 。
<view :info="uploadData" :change:info="uploadFile.tofile"></view>
</view>
</template>
// service 层
<script setup>
</srcipt>
// module:renderjs模块名称
<script module="uploadFile" lang="renderjs">
// data:info的新数据
// oldValue: 旧数据
// ownerInstance 可用来向service通信
tofile (data, oldValue, ownerInstance) {}
</srcipt>
以下是全部代码
<template>
<view>
<uni-file-picker
v-if="!slots.default"
v-model="fileList"
:limit="limit"
:title="title"
:readonly="disabled"
@select="handleSelectImage"
/>
<view :info="uploadData" :change:info="uploadFile.tofile"></view>
</view>
</template>
<script setup>
import { watchEffect, ref, useSlots, reactive } from 'vue'
import { signatureKey } from '@/utils/ali-oss'
import { getUploadPolicy } from '@/request/oss'
const fileList = ref([])
const uploadData = reactive({})
const slots = useSlots()
const isUpload = ref(false)
const props = defineProps({
modelValue: {
type: String,
default() {
return ''
}
},
fileType: {
type: String,
default() {
return ''
}
},
limit: {
type: String,
default() {
return '1'
}
},
title: {
type: String,
default() {
return ''
}
},
disabled: {
type: Boolean,
default() {
return false
}
}
})
const emit = defineEmits(['update:modelValue', 'success'])
const formatFiles = (modelValue) => {
if (!modelValue) {
return []
}
const fileList = modelValue.split(',')
return fileList.map(item => {
return {
name: '',
key: item,
extname: 'png',
url: signatureKey(item)
}
})
}
const handleSuccess = (data, url) => {
fileList.value.push({
name: '',
url,
key: data.key
})
const keyList = []
fileList.value.forEach(item => {
keyList.push(item.key)
})
emit('update:modelValue', keyList.join(','))
emit('success', keyList.join(','))
}
const handleUpload = (fileData) => {
const file = fileData.file
const ext = file.name?.split('.')[1]
const suffix = ext ? `.${ext}` : ''
const fileType = props.fileType || 'other_file'
getUploadPolicy({ suffix, fileType })
.then(({ code, data }) => {
if(code === 200) {
// #ifdef APP-PLUS
Object.assign(uploadData, { ...data, ...file })
handleSuccess(data, file.path)
// #endif
// #ifdef H5
uni.uploadFile({
url: data.host,
file: file,
formData: {
'key': data.key,
'policy': data.policy,
'OSSAccessKeyId': data.ossAccessKeyId,
'success_action_status': '200',
'signature': data.signature
},
success: ({ statusCode }) => {
if (statusCode === 200) {
handleSuccess(data, fileData.url)
}
}
})
// #endif
}
})
.catch((err) => {
console.error(err)
})
}
const handleSelectImage = ({ tempFiles }) => {
tempFiles.forEach(item => {
handleUpload(item)
})
}
watchEffect(() => {
if (!isUpload.value) {
fileList.value = formatFiles(props.modelValue)
}
})
</script>
<script module="uploadFile" lang="renderjs">
export default {
methods: {
tofile (data, oldValue, ownerInstance, instance) {
if (data && data.path) {
plus.io.resolveLocalFileSystemURL(data.path, (entry) => {
entry.file((file) => {
var fileReader = new plus.io.FileReader()
fileReader.readAsDataURL(file)
fileReader.onloadend = (evt) => {
const base64Data = evt.target.result
var arr = base64Data.split(',')
var mime = arr[0].match(/:(.*?);/)[1]
var bstr = atob(arr[1])
var n = bstr.length
var u8arr = new Uint8Array(n)
while(n--) {
u8arr[n] = bstr.charCodeAt(n)
}
const file = new File([u8arr], data.name, {type: mime})
this.handleUpload(data, file)
}
})
})
}
},
handleUpload(data, file) {
const formData = new FormData()
formData.append('key', data.key)
formData.append('policy', data.policy)
formData.append('OSSAccessKeyId', data.ossAccessKeyId)
formData.append('success_action_status', '200')
formData.append('signature', data.signature)
formData.append('file', file)
// 无法使用uniapp中的上传方法,所以使用原生的上传
const xhr = new XMLHttpRequest()
xhr.open('POST', data.host, true)
xhr.onload = (e) => {
console.log(e)
}
xhr.send(formData)
xhr.onreadystatechange = () => {
if(xhr.readyState == 4 && xhr.status == 200) {
console.log('上传成功!')
}
}
}
}
}
</script>
<style lang="scss" scoped>
</style>
app上传后的回调
const handleUpload = (fileData) => {
const file = fileData.file
const ext = file.name?.split('.')[1]
const suffix = ext ? `.${ext}` : ''
const fileType = props.fileType || 'other_file'
getUploadPolicy({ suffix, fileType })
.then(({ code, data }) => {
if(code === 200) {
// #ifdef APP-PLUS
Object.assign(uploadData, { ...data, ...file })
// 强调以下这段代码其实取了巧,它有更好的实现方式是在renderjs xhr上传成功后在回调,用到了
// renderjs的ownerInstance.callMethod('')实现,具体可以查文档。我为什么没用呢,是因为,
// 不知道是不是用了vue3的原因,还是uniapp有bug,用不了,不触发service层数据。
// ------------------------
handleSuccess(data, file.path)
// --------------------------
// #endif
}
})
.catch((err) => {
console.error(err)
})
}
结束!!!!!!