Houjie
2025-06-03 2fda34643bc22e25f6c569415da5f955c81536bf
uni_modules/uni-file-picker/components/uni-file-picker/uni-file-picker.vue
@@ -16,7 +16,7 @@
      </upload-image>
      <upload-file v-if="fileMediatype !== 'image' || showType !== 'grid'" :readonly="readonly"
         :list-styles="listStyles" :files-list="filesList" :showType="showType" :delIcon="delIcon"
         @uploadFiles="uploadFiles" @choose="choose" @delFile="delFile">
         @uploadFiles="uploadFiles" @choose="choose" @delFile="delFile" @downloadFile="downloadFile">
         <slot><button type="primary" size="mini">选择文件</button></slot>
      </upload-file>
   </view>
@@ -87,7 +87,7 @@
      options: {
         virtualHost: true
      },
      emits: ['select', 'success', 'fail', 'progress', 'delete', 'update:modelValue', 'input'],
      emits: ['select', 'success', 'fail', 'progress', 'delete', 'update:modelValue', 'input', 'downloadFile'],
      props: {
         modelValue: {
            type: [Array, Object],
@@ -183,7 +183,7 @@
         sourceType: {
            type: Array,
            default () {
               return  ['album', 'camera']
               return ['album', 'camera']
            }
         },
         provider: {
@@ -254,366 +254,417 @@
         }
      },
      methods: {
         /**
          * 公开用户使用,清空文件
          * @param {Object} index
          */
         clearFiles(index) {
            if (index !== 0 && !index) {
               this.files = []
               this.$nextTick(() => {
                  this.setEmit()
               })
            } else {
               this.files.splice(index, 1)
            }
            this.$nextTick(() => {
               this.setEmit()
            })
         },
         /**
          * 公开用户使用,继续上传
          */
         upload() {
            let files = []
            this.files.forEach((v, index) => {
               if (v.status === 'ready' || v.status === 'error') {
                  files.push(Object.assign({}, v))
               }
            })
            return this.uploadFiles(files)
         },
         async setValue(newVal, oldVal) {
            const newData =  async (v) => {
               const reg = /cloud:\/\/([\w.]+\/?)\S*/
               let url = ''
               if(v.fileID){
                  url = v.fileID
               }else{
                  url = v.url
               }
               if (reg.test(url)) {
                  v.fileID = url
                  v.url = await this.getTempFileURL(url)
               }
               if(v.url) v.path = v.url
               return v
            }
            if (this.returnType === 'object') {
               if (newVal) {
                  await newData(newVal)
               } else {
                  newVal = {}
               }
            } else {
               if (!newVal) newVal = []
               for(let i =0 ;i < newVal.length ;i++){
                  let v = newVal[i]
                  await newData(v)
               }
            }
            this.localValue = newVal
            if (this.form && this.formItem &&!this.is_reset) {
               this.is_reset = false
               this.formItem.setValue(this.localValue)
            }
            let filesData = Object.keys(newVal).length > 0 ? newVal : [];
            this.files = [].concat(filesData)
         },
         /**
          * 选择文件
          */
         choose() {
            if (this.disabled) return
            if (this.files.length >= Number(this.limitLength) && this.showType !== 'grid' && this.returnType ===
               'array') {
         downloadFile(item) {
            console.log('准备下载:', item);
            if (!item.url) {
               uni.showToast({
                  title: `您最多选择 ${this.limitLength} 个文件`,
                  title: '文件不可下载',
                  icon: 'none'
               })
               return
               });
               return;
            }
            this.chooseFiles()
         },
         /**
          * 选择文件并上传
          */
         chooseFiles() {
            const _extname = get_extname(this.fileExtname)
            // 获取后缀
            uniCloud
               .chooseAndUploadFile({
                  type: this.fileMediatype,
                  compressed: false,
                  sizeType: this.sizeType,
                  sourceType: this.sourceType,
                  // TODO 如果为空,video 有问题
                  extension: _extname.length > 0 ? _extname : undefined,
                  count: this.limitLength - this.files.length, //默认9
                  onChooseFile: this.chooseFileCallback,
                  onUploadProgress: progressEvent => {
                     this.setProgress(progressEvent, progressEvent.index)
            // #ifdef H5
            const a = document.createElement('a');
            a.href = item.url;
            a.download = item.name || 'file';
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            // #endif
            // #ifndef H5
            uni.downloadFile({
               url: item.url,
               success: (res) => {
                  if (res.statusCode === 200) {
                     uni.openDocument({
                        filePath: res.tempFilePath,
                        success: () => {
                           console.log('打开文档成功');
                        },
                        fail: () => {
                           uni.showToast({
                              title: '无法打开此文档类型',
                              icon: 'none'
                           });
                        }
                     });
                  }
               })
               .then(result => {
                  this.setSuccessAndError(result.tempFiles)
               })
               .catch(err => {
                  console.log('选择失败', err)
               })
         },
         /**
          * 选择文件回调
          * @param {Object} res
          */
         async chooseFileCallback(res) {
            const _extname = get_extname(this.fileExtname)
            const is_one = (Number(this.limitLength) === 1 &&
                  this.disablePreview &&
                  !this.disabled) ||
               this.returnType === 'object'
            // 如果这有一个文件 ,需要清空本地缓存数据
            if (is_one) {
               this.files = []
            }
            let {
               filePaths,
               files
            } = get_files_and_is_max(res, _extname)
            if (!(_extname && _extname.length > 0)) {
               filePaths = res.tempFilePaths
               files = res.tempFiles
            }
            let currentData = []
            for (let i = 0; i < files.length; i++) {
               if (this.limitLength - this.files.length <= 0) break
               files[i].uuid = Date.now()
               let filedata = await get_file_data(files[i], this.fileMediatype)
               filedata.progress = 0
               filedata.status = 'ready'
               this.files.push(filedata)
               currentData.push({
                  ...filedata,
                  file: files[i]
               })
            }
            this.$emit('select', {
               tempFiles: currentData,
               tempFilePaths: filePaths
            })
            res.tempFiles = files
            // 停止自动上传
            if (!this.autoUpload || this.noSpace) {
               res.tempFiles = []
            }
            res.tempFiles.forEach((fileItem, index) => {
               this.provider && (fileItem.provider = this.provider);
               const fileNameSplit = fileItem.name.split('.')
               const ext = fileNameSplit.pop()
               const fileName = fileNameSplit.join('.').replace(/[\s\/\?<>\\:\*\|":]/g, '_')
               fileItem.cloudPath = fileName + '_' + Date.now() + '_' + index + '.' + ext
            })
         },
         /**
          * 批传
          * @param {Object} e
          */
         uploadFiles(files) {
            files = [].concat(files)
            return uploadCloudFiles.call(this, files, 5, res => {
                  this.setProgress(res, res.index, true)
               })
               .then(result => {
                  this.setSuccessAndError(result)
                  return result;
               })
               .catch(err => {
                  console.log(err)
               })
         },
         /**
          * 成功或失败
          */
         async setSuccessAndError(res, fn) {
            let successData = []
            let errorData = []
            let tempFilePath = []
            let errorTempFilePath = []
            for (let i = 0; i < res.length; i++) {
               const item = res[i]
               const index = item.uuid ? this.files.findIndex(p => p.uuid === item.uuid) : item.index
               if (index === -1 || !this.files) break
               if (item.errMsg === 'request:fail') {
                  this.files[index].url = item.path
                  this.files[index].status = 'error'
                  this.files[index].errMsg = item.errMsg
                  // this.files[index].progress = -1
                  errorData.push(this.files[index])
                  errorTempFilePath.push(this.files[index].url)
               } else {
                  this.files[index].errMsg = ''
                  this.files[index].fileID = item.url
                  const reg = /cloud:\/\/([\w.]+\/?)\S*/
                  if (reg.test(item.url)) {
                     this.files[index].url = await this.getTempFileURL(item.url)
                  }else{
                     this.files[index].url = item.url
                  }
                  this.files[index].status = 'success'
                  this.files[index].progress += 1
                  successData.push(this.files[index])
                  tempFilePath.push(this.files[index].fileID)
               },
               fail: () => {
                  uni.showToast({
                     title: '下载失败',
                     icon: 'none'
                  });
               }
            }
            if (successData.length > 0) {
               this.setEmit()
               // 状态改变返回
               this.$emit('success', {
                  tempFiles: this.backObject(successData),
                  tempFilePaths: tempFilePath
               })
            }
            if (errorData.length > 0) {
               this.$emit('fail', {
                  tempFiles: this.backObject(errorData),
                  tempFilePaths: errorTempFilePath
               })
            }
            });
            // #endif
         },
         /**
          * 获取进度
          * @param {Object} progressEvent
          * @param {Object} index
          * @param {Object} type
          */
         setProgress(progressEvent, index, type) {
            const fileLenth = this.files.length
            const percentNum = (index / fileLenth) * 100
            const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total)
            let idx = index
            if (!type) {
               idx = this.files.findIndex(p => p.uuid === progressEvent.tempFile.uuid)
            }
            if (idx === -1 || !this.files[idx]) return
            // fix by mehaotian 100 就会消失,-1 是为了让进度条消失
            this.files[idx].progress = percentCompleted - 1
            // 上传中
            this.$emit('progress', {
               index: idx,
               progress: parseInt(percentCompleted),
               tempFile: this.files[idx]
            })
         },
         /**
          * 删除文件
          * @param {Object} index
          */
         delFile(index) {
            this.$emit('delete', {
               index,
               tempFile: this.files[index],
               tempFilePath: this.files[index].url
            })
            this.files.splice(index, 1)
      /**
       * 公开用户使用,清空文件
       * @param {Object} index
       */
      clearFiles(index) {
         if (index !== 0 && !index) {
            this.files = []
            this.$nextTick(() => {
               this.setEmit()
            })
         },
         /**
          * 获取文件名和后缀
          * @param {Object} name
          */
         getFileExt(name) {
            const last_len = name.lastIndexOf('.')
            const len = name.length
            return {
               name: name.substring(0, last_len),
               ext: name.substring(last_len + 1, len)
            }
         },
         /**
          * 处理返回事件
          */
         setEmit() {
            let data = []
            if (this.returnType === 'object') {
               data = this.backObject(this.files)[0]
               this.localValue = data?data:null
            } else {
               data = this.backObject(this.files)
               if (!this.localValue) {
                  this.localValue = []
               }
               this.localValue = [...data]
            }
            // #ifdef VUE3
            this.$emit('update:modelValue', this.localValue)
            // #endif
            // #ifndef VUE3
            this.$emit('input', this.localValue)
            // #endif
         },
         /**
          * 处理返回参数
          * @param {Object} files
          */
         backObject(files) {
            let newFilesData = []
            files.forEach(v => {
               newFilesData.push({
                  extname: v.extname,
                  fileType: v.fileType,
                  image: v.image,
                  name: v.name,
                  path: v.path,
                  size: v.size,
                  fileID:v.fileID,
                  url: v.url,
                  // 修改删除一个文件后不能再上传的bug, #694
            uuid: v.uuid,
            status: v.status,
            cloudPath: v.cloudPath
               })
            })
            return newFilesData
         },
         async getTempFileURL(fileList) {
            fileList = {
               fileList: [].concat(fileList)
            }
            const urls = await uniCloud.getTempFileURL(fileList)
            return urls.fileList[0].tempFileURL || ''
         },
         /**
          * 获取父元素实例
          */
         getForm(name = 'uniForms') {
            let parent = this.$parent;
            let parentName = parent.$options.name;
            while (parentName !== name) {
               parent = parent.$parent;
               if (!parent) return false;
               parentName = parent.$options.name;
            }
            return parent;
         } else {
            this.files.splice(index, 1)
         }
         this.$nextTick(() => {
            this.setEmit()
         })
      },
      /**
       * 公开用户使用,继续上传
       */
      upload() {
         let files = []
         this.files.forEach((v, index) => {
            if (v.status === 'ready' || v.status === 'error') {
               files.push(Object.assign({}, v))
            }
         })
         return this.uploadFiles(files)
      },
      async setValue(newVal, oldVal) {
         const newData = async (v) => {
            const reg = /cloud:\/\/([\w.]+\/?)\S*/
            let url = ''
            if (v.fileID) {
               url = v.fileID
            } else {
               url = v.url
            }
            if (reg.test(url)) {
               v.fileID = url
               v.url = await this.getTempFileURL(url)
            }
            if (v.url) v.path = v.url
            return v
         }
         if (this.returnType === 'object') {
            if (newVal) {
               await newData(newVal)
            } else {
               newVal = {}
            }
         } else {
            if (!newVal) newVal = []
            for (let i = 0; i < newVal.length; i++) {
               let v = newVal[i]
               await newData(v)
            }
         }
         this.localValue = newVal
         if (this.form && this.formItem && !this.is_reset) {
            this.is_reset = false
            this.formItem.setValue(this.localValue)
         }
         let filesData = Object.keys(newVal).length > 0 ? newVal : [];
         this.files = [].concat(filesData)
      },
      /**
       * 选择文件
       */
      choose() {
         if (this.disabled) return
         if (this.files.length >= Number(this.limitLength) && this.showType !== 'grid' && this.returnType ===
            'array') {
            uni.showToast({
               title: `您最多选择 ${this.limitLength} 个文件`,
               icon: 'none'
            })
            return
         }
         this.chooseFiles()
      },
      /**
       * 选择文件并上传
       */
      chooseFiles() {
         const _extname = get_extname(this.fileExtname)
         // 获取后缀
         uniCloud
            .chooseAndUploadFile({
               type: this.fileMediatype,
               compressed: false,
               sizeType: this.sizeType,
               sourceType: this.sourceType,
               // TODO 如果为空,video 有问题
               extension: _extname.length > 0 ? _extname : undefined,
               count: this.limitLength - this.files.length, //默认9
               onChooseFile: this.chooseFileCallback,
               onUploadProgress: progressEvent => {
                  this.setProgress(progressEvent, progressEvent.index)
               }
            })
            .then(result => {
               this.setSuccessAndError(result.tempFiles)
            })
            .catch(err => {
               console.log('选择失败', err)
            })
      },
      /**
       * 选择文件回调
       * @param {Object} res
       */
      async chooseFileCallback(res) {
         const _extname = get_extname(this.fileExtname)
         const is_one = (Number(this.limitLength) === 1 &&
               this.disablePreview &&
               !this.disabled) ||
            this.returnType === 'object'
         // 如果这有一个文件 ,需要清空本地缓存数据
         if (is_one) {
            this.files = []
         }
         let {
            filePaths,
            files
         } = get_files_and_is_max(res, _extname)
         if (!(_extname && _extname.length > 0)) {
            filePaths = res.tempFilePaths
            files = res.tempFiles
         }
         let currentData = []
         for (let i = 0; i < files.length; i++) {
            if (this.limitLength - this.files.length <= 0) break
            files[i].uuid = Date.now()
            let filedata = await get_file_data(files[i], this.fileMediatype)
            filedata.progress = 0
            filedata.status = 'ready'
            this.files.push(filedata)
            currentData.push({
               ...filedata,
               file: files[i]
            })
         }
         this.$emit('select', {
            tempFiles: currentData,
            tempFilePaths: filePaths
         })
         res.tempFiles = files
         // 停止自动上传
         if (!this.autoUpload || this.noSpace) {
            res.tempFiles = []
         }
         res.tempFiles.forEach((fileItem, index) => {
            this.provider && (fileItem.provider = this.provider);
            const fileNameSplit = fileItem.name.split('.')
            const ext = fileNameSplit.pop()
            const fileName = fileNameSplit.join('.').replace(/[\s\/\?<>\\:\*\|":]/g, '_')
            fileItem.cloudPath = fileName + '_' + Date.now() + '_' + index + '.' + ext
         })
      },
      /**
       * 批传
       * @param {Object} e
       */
      uploadFiles(files) {
         files = [].concat(files)
         return uploadCloudFiles.call(this, files, 5, res => {
               this.setProgress(res, res.index, true)
            })
            .then(result => {
               this.setSuccessAndError(result)
               return result;
            })
            .catch(err => {
               console.log(err)
            })
      },
      /**
       * 成功或失败
       */
      async setSuccessAndError(res, fn) {
         let successData = []
         let errorData = []
         let tempFilePath = []
         let errorTempFilePath = []
         for (let i = 0; i < res.length; i++) {
            const item = res[i]
            const index = item.uuid ? this.files.findIndex(p => p.uuid === item.uuid) : item.index
            if (index === -1 || !this.files) break
            if (item.errMsg === 'request:fail') {
               this.files[index].url = item.path
               this.files[index].status = 'error'
               this.files[index].errMsg = item.errMsg
               // this.files[index].progress = -1
               errorData.push(this.files[index])
               errorTempFilePath.push(this.files[index].url)
            } else {
               this.files[index].errMsg = ''
               this.files[index].fileID = item.url
               const reg = /cloud:\/\/([\w.]+\/?)\S*/
               if (reg.test(item.url)) {
                  this.files[index].url = await this.getTempFileURL(item.url)
               } else {
                  this.files[index].url = item.url
               }
               this.files[index].status = 'success'
               this.files[index].progress += 1
               successData.push(this.files[index])
               tempFilePath.push(this.files[index].fileID)
            }
         }
         if (successData.length > 0) {
            this.setEmit()
            // 状态改变返回
            this.$emit('success', {
               tempFiles: this.backObject(successData),
               tempFilePaths: tempFilePath
            })
         }
         if (errorData.length > 0) {
            this.$emit('fail', {
               tempFiles: this.backObject(errorData),
               tempFilePaths: errorTempFilePath
            })
         }
      },
      /**
       * 获取进度
       * @param {Object} progressEvent
       * @param {Object} index
       * @param {Object} type
       */
      setProgress(progressEvent, index, type) {
         const fileLenth = this.files.length
         const percentNum = (index / fileLenth) * 100
         const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total)
         let idx = index
         if (!type) {
            idx = this.files.findIndex(p => p.uuid === progressEvent.tempFile.uuid)
         }
         if (idx === -1 || !this.files[idx]) return
         // fix by mehaotian 100 就会消失,-1 是为了让进度条消失
         this.files[idx].progress = percentCompleted - 1
         // 上传中
         this.$emit('progress', {
            index: idx,
            progress: parseInt(percentCompleted),
            tempFile: this.files[idx]
         })
      },
      /**
       * 删除文件
       * @param {Object} index
       */
      delFile(index) {
         this.$emit('delete', {
            index,
            tempFile: this.files[index],
            tempFilePath: this.files[index].url
         })
         this.files.splice(index, 1)
         this.$nextTick(() => {
            this.setEmit()
         })
      },
      /**
       * 获取文件名和后缀
       * @param {Object} name
       */
      getFileExt(name) {
         const last_len = name.lastIndexOf('.')
         const len = name.length
         return {
            name: name.substring(0, last_len),
            ext: name.substring(last_len + 1, len)
         }
      },
      /**
       * 处理返回事件
       */
      setEmit() {
         let data = []
         if (this.returnType === 'object') {
            data = this.backObject(this.files)[0]
            this.localValue = data ? data : null
         } else {
            data = this.backObject(this.files)
            if (!this.localValue) {
               this.localValue = []
            }
            this.localValue = [...data]
         }
         // #ifdef VUE3
         this.$emit('update:modelValue', this.localValue)
         // #endif
         // #ifndef VUE3
         this.$emit('input', this.localValue)
         // #endif
      },
      /**
       * 处理返回参数
       * @param {Object} files
       */
      backObject(files) {
         let newFilesData = []
         files.forEach(v => {
            newFilesData.push({
               extname: v.extname,
               fileType: v.fileType,
               image: v.image,
               name: v.name,
               path: v.path,
               size: v.size,
               fileID: v.fileID,
               url: v.url,
               // 修改删除一个文件后不能再上传的bug, #694
               uuid: v.uuid,
               status: v.status,
               cloudPath: v.cloudPath
            })
         })
         return newFilesData
      },
      async getTempFileURL(fileList) {
         fileList = {
            fileList: [].concat(fileList)
         }
         const urls = await uniCloud.getTempFileURL(fileList)
         return urls.fileList[0].tempFileURL || ''
      },
      /**
       * 获取父元素实例
       */
      getForm(name = 'uniForms') {
         let parent = this.$parent;
         let parentName = parent.$options.name;
         while (parentName !== name) {
            parent = parent.$parent;
            if (!parent) return false;
            parentName = parent.$options.name;
         }
         return parent;
      }
   }
   }
</script>
@@ -665,4 +716,4 @@
      position: absolute;
      transform: rotate(90deg);
   }
</style>
</style>