Uniapp+vue3 APP上传图片到oss

1,469 阅读2分钟

开发工具: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)
      })
  }

结束!!!!!!