rkljw 2 zile în urmă
părinte
comite
fbfec7a738
3 a modificat fișierele cu 739 adăugiri și 1 ștergeri
  1. 238 0
      src/utils/fileHelper.js
  2. 500 0
      src/utils/wechatFileHelper.js
  3. 1 1
      src/views/file/index.vue

+ 238 - 0
src/utils/fileHelper.js

@@ -0,0 +1,238 @@
+/**
+ * 文件访问助手工具
+ * 专门处理安卓系统的文件访问问题
+ */
+
+// 检测运行环境
+export function detectEnvironment() {
+  const userAgent = navigator.userAgent.toLowerCase()
+  
+  return {
+    isWechat: /micromessenger/.test(userAgent),
+    isAndroid: /android/.test(userAgent),
+    isIOS: /iphone|ipad|ipod/.test(userAgent),
+    isChrome: /chrome/.test(userAgent),
+    isSafari: /safari/.test(userAgent) && !/chrome/.test(userAgent),
+    isFirefox: /firefox/.test(userAgent),
+    isEdge: /edge/.test(userAgent)
+  }
+}
+
+// 检查文件访问权限
+export function checkFileAccessPermission() {
+  return new Promise((resolve, reject) => {
+    const env = detectEnvironment()
+    
+    if (env.isAndroid) {
+      // 安卓系统特殊处理
+      if (env.isWechat) {
+        // 微信环境,权限相对较好
+        resolve({ hasPermission: true, message: '微信环境,文件访问权限正常' })
+      } else {
+        // 非微信环境,需要检查权限
+        try {
+          // 尝试创建一个临时的文件输入框来测试权限
+          const testInput = document.createElement('input')
+          testInput.type = 'file'
+          testInput.accept = 'image/*'
+          testInput.style.display = 'none'
+          
+          document.body.appendChild(testInput)
+          
+          const timeout = setTimeout(() => {
+            document.body.removeChild(testInput)
+            resolve({ 
+              hasPermission: false, 
+              message: '安卓系统:请确保已授予文件访问权限,建议使用微信打开' 
+            })
+          }, 3000)
+          
+          testInput.onchange = () => {
+            clearTimeout(timeout)
+            document.body.removeChild(testInput)
+            resolve({ hasPermission: true, message: '文件访问权限正常' })
+          }
+          
+          testInput.click()
+        } catch (error) {
+          resolve({ 
+            hasPermission: false, 
+            message: '无法检测文件访问权限,请手动尝试选择文件' 
+          })
+        }
+      }
+    } else {
+      // 其他系统
+      resolve({ hasPermission: true, message: '文件访问权限正常' })
+    }
+  })
+}
+
+// 创建优化的文件选择器
+export function createOptimizedFileInput(options = {}) {
+  const {
+    multiple = true,
+    accept = 'image/*,.pdf,.doc,.docx,.xls,.xlsx,.txt,.mp4,.mp3,.zip,.rar',
+    capture = '',
+    onSelect = () => {},
+    onError = () => {}
+  } = options
+  
+  const env = detectEnvironment()
+  
+  // 创建文件输入框
+  const input = document.createElement('input')
+  input.type = 'file'
+  input.multiple = multiple
+  input.accept = accept
+  input.capture = capture
+  input.style.display = 'none'
+  
+  // 添加事件监听
+  input.addEventListener('change', (event) => {
+    const files = Array.from(event.target.files)
+    if (files.length > 0) {
+      onSelect(files)
+    } else {
+      onError('未选择任何文件')
+    }
+    
+    // 清理
+    document.body.removeChild(input)
+  })
+  
+  // 添加错误处理
+  input.addEventListener('error', (error) => {
+    onError('文件选择失败:' + error.message)
+    document.body.removeChild(input)
+  })
+  
+  // 安卓系统特殊处理
+  if (env.isAndroid && !env.isWechat) {
+    // 添加额外的提示
+    console.log('安卓系统文件选择提示:请确保已授予文件访问权限')
+  }
+  
+  return input
+}
+
+// 触发文件选择
+export function triggerFileSelection(options = {}) {
+  return new Promise((resolve, reject) => {
+    const input = createOptimizedFileInput({
+      ...options,
+      onSelect: (files) => resolve(files),
+      onError: (error) => reject(new Error(error))
+    })
+    
+    document.body.appendChild(input)
+    input.click()
+  })
+}
+
+// 验证文件类型和大小
+export function validateFile(file, options = {}) {
+  const {
+    maxSize = 50 * 1024 * 1024, // 50MB
+    allowedTypes = ['image/*', '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.txt', '.mp4', '.mp3', '.zip', '.rar'],
+    allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'txt', 'mp4', 'mp3', 'zip', 'rar']
+  } = options
+  
+  const errors = []
+  
+  // 检查文件大小
+  if (file.size > maxSize) {
+    errors.push(`文件大小超过限制 (${formatFileSize(maxSize)})`)
+  }
+  
+  // 检查文件类型
+  const fileExtension = file.name.split('.').pop().toLowerCase()
+  const isValidType = allowedTypes.some(type => {
+    if (type.includes('*')) {
+      return file.type.startsWith(type.replace('*', ''))
+    }
+    return file.type === type || fileExtension === type.replace('.', '')
+  })
+  
+  if (!isValidType) {
+    errors.push('不支持的文件类型')
+  }
+  
+  return {
+    isValid: errors.length === 0,
+    errors
+  }
+}
+
+// 格式化文件大小
+export function formatFileSize(bytes) {
+  if (bytes === 0) return '0 B'
+  const k = 1024
+  const sizes = ['B', 'KB', 'MB', 'GB']
+  const i = Math.floor(Math.log(bytes) / Math.log(k))
+  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
+}
+
+// 获取文件图标
+export function getFileIcon(fileType, fileName = '') {
+  if (fileType.startsWith('image/')) {
+    return 'el-icon-picture'
+  } else if (fileType.includes('pdf')) {
+    return 'el-icon-document'
+  } else if (fileType.includes('word') || fileType.includes('document')) {
+    return 'el-icon-document'
+  } else if (fileType.includes('excel') || fileType.includes('spreadsheet')) {
+    return 'el-icon-document'
+  } else if (fileType.includes('video')) {
+    return 'el-icon-video-camera'
+  } else if (fileType.includes('audio')) {
+    return 'el-icon-headset'
+  } else if (fileName.toLowerCase().includes('.zip') || fileName.toLowerCase().includes('.rar')) {
+    return 'el-icon-folder'
+  } else {
+    return 'el-icon-document'
+  }
+}
+
+// 生成环境提示信息
+export function getEnvironmentTips() {
+  const env = detectEnvironment()
+  const tips = []
+  
+  if (env.isAndroid) {
+    tips.push({
+      type: 'info',
+      title: '安卓系统提示',
+      content: '请确保已授予浏览器文件访问权限,如果无法选择文件,建议使用微信打开'
+    })
+  }
+  
+  if (!env.isWechat) {
+    tips.push({
+      type: 'warning',
+      title: '非微信环境',
+      content: '建议在微信中打开此页面以获得最佳体验'
+    })
+  }
+  
+  if (env.isIOS) {
+    tips.push({
+      type: 'info',
+      title: 'iOS系统提示',
+      content: 'iOS系统文件访问相对稳定,如遇问题请检查浏览器权限设置'
+    })
+  }
+  
+  return tips
+}
+
+export default {
+  detectEnvironment,
+  checkFileAccessPermission,
+  createOptimizedFileInput,
+  triggerFileSelection,
+  validateFile,
+  formatFileSize,
+  getFileIcon,
+  getEnvironmentTips
+} 

+ 500 - 0
src/utils/wechatFileHelper.js

@@ -0,0 +1,500 @@
+/**
+ * 微信小程序web-view文件访问助手
+ * 专门处理微信小程序web-view中的文件访问问题
+ */
+
+// 检测是否在微信小程序web-view中
+export function isWechatMiniProgram() {
+  return typeof wx !== 'undefined' && wx.miniProgram
+}
+
+// 检测是否在微信环境中
+export function isWechatEnvironment() {
+  return /micromessenger/i.test(navigator.userAgent)
+}
+
+// 检测安卓系统
+export function isAndroid() {
+  return /android/i.test(navigator.userAgent)
+}
+
+// 获取微信小程序环境信息
+export function getWechatEnvironment() {
+  return new Promise((resolve) => {
+    if (typeof wx !== 'undefined' && wx.miniProgram) {
+      wx.miniProgram.getEnv((res) => {
+        resolve({
+          isMiniProgram: true,
+          env: res.miniprogram ? 'miniprogram' : 'webview',
+          version: res.version || 'unknown'
+        })
+      })
+    } else {
+      resolve({
+        isMiniProgram: false,
+        env: 'browser',
+        version: 'unknown'
+      })
+    }
+  })
+}
+
+// 微信小程序文件选择器
+export function createWechatFileSelector(options = {}) {
+  const {
+    count = 9, // 最多可以选择的文件数量
+    type = 'all', // 文件类型:all, image, video, file
+    sizeType = ['original', 'compressed'], // 所选的图片的尺寸:original, compressed
+    sourceType = ['album', 'camera'], // 选择图片的来源:album, camera
+    onSuccess = () => {},
+    onFail = () => {},
+    onCancel = () => {}
+  } = options
+
+  return new Promise((resolve, reject) => {
+    if (!isWechatMiniProgram()) {
+      reject(new Error('不在微信小程序环境中'))
+      return
+    }
+
+    // 根据文件类型选择不同的API
+    if (type === 'image') {
+      // 选择图片
+      wx.chooseImage({
+        count,
+        sizeType,
+        sourceType,
+        success: (res) => {
+          const files = res.tempFilePaths.map((path, index) => ({
+            path,
+            name: `image_${Date.now()}_${index}.jpg`,
+            type: 'image/jpeg',
+            size: 0 // 微信小程序中无法直接获取文件大小
+          }))
+          onSuccess(files)
+          resolve(files)
+        },
+        fail: (err) => {
+          onFail(err)
+          reject(err)
+        },
+        cancel: () => {
+          onCancel()
+          reject(new Error('用户取消选择'))
+        }
+      })
+    } else if (type === 'video') {
+      // 选择视频
+      wx.chooseVideo({
+        sourceType,
+        maxDuration: 60,
+        camera: 'back',
+        success: (res) => {
+          const file = {
+            path: res.tempFilePath,
+            name: `video_${Date.now()}.mp4`,
+            type: 'video/mp4',
+            size: 0,
+            duration: res.duration,
+            width: res.width,
+            height: res.height
+          }
+          onSuccess([file])
+          resolve([file])
+        },
+        fail: (err) => {
+          onFail(err)
+          reject(err)
+        },
+        cancel: () => {
+          onCancel()
+          reject(new Error('用户取消选择'))
+        }
+      })
+    } else {
+      // 选择文件(微信小程序基础库2.16.0+支持)
+      if (wx.chooseMessageFile) {
+        wx.chooseMessageFile({
+          count,
+          type: 'file',
+          success: (res) => {
+            const files = res.tempFiles.map((file) => ({
+              path: file.path,
+              name: file.name,
+              type: file.type || 'application/octet-stream',
+              size: file.size
+            }))
+            onSuccess(files)
+            resolve(files)
+          },
+          fail: (err) => {
+            onFail(err)
+            reject(err)
+          },
+          cancel: () => {
+            onCancel()
+            reject(new Error('用户取消选择'))
+          }
+        })
+      } else {
+        // 降级到选择图片
+        wx.chooseImage({
+          count,
+          sizeType,
+          sourceType,
+          success: (res) => {
+            const files = res.tempFilePaths.map((path, index) => ({
+              path,
+              name: `file_${Date.now()}_${index}.jpg`,
+              type: 'image/jpeg',
+              size: 0
+            }))
+            onSuccess(files)
+            resolve(files)
+          },
+          fail: (err) => {
+            onFail(err)
+            reject(err)
+          },
+          cancel: () => {
+            onCancel()
+            reject(new Error('用户取消选择'))
+          }
+        })
+      }
+    }
+  })
+}
+
+// 上传文件到服务器
+export function uploadFileToServer(filePath, options = {}) {
+  const {
+    url = '/api/upload',
+    name = 'file',
+    header = {},
+    formData = {},
+    onProgress = () => {},
+    onSuccess = () => {},
+    onFail = () => {}
+  } = options
+
+  return new Promise((resolve, reject) => {
+    if (!isWechatMiniProgram()) {
+      reject(new Error('不在微信小程序环境中'))
+      return
+    }
+
+    const uploadTask = wx.uploadFile({
+      url,
+      filePath,
+      name,
+      header,
+      formData,
+      success: (res) => {
+        try {
+          const data = JSON.parse(res.data)
+          onSuccess(data)
+          resolve(data)
+        } catch (error) {
+          onFail(error)
+          reject(error)
+        }
+      },
+      fail: (err) => {
+        onFail(err)
+        reject(err)
+      }
+    })
+
+    // 监听上传进度
+    uploadTask.onProgressUpdate((res) => {
+      onProgress({
+        progress: res.progress,
+        totalBytesSent: res.totalBytesSent,
+        totalBytesExpectedToSend: res.totalBytesExpectedToSend
+      })
+    })
+  })
+}
+
+// 获取文件信息
+export function getFileInfo(filePath) {
+  return new Promise((resolve, reject) => {
+    if (!isWechatMiniProgram()) {
+      reject(new Error('不在微信小程序环境中'))
+      return
+    }
+
+    wx.getFileInfo({
+      filePath,
+      success: (res) => {
+        resolve({
+          size: res.size,
+          digest: res.digest
+        })
+      },
+      fail: (err) => {
+        reject(err)
+      }
+    })
+  })
+}
+
+// 保存文件到相册
+export function saveFileToAlbum(filePath) {
+  return new Promise((resolve, reject) => {
+    if (!isWechatMiniProgram()) {
+      reject(new Error('不在微信小程序环境中'))
+      return
+    }
+
+    wx.saveImageToPhotosAlbum({
+      filePath,
+      success: () => {
+        resolve()
+      },
+      fail: (err) => {
+        reject(err)
+      }
+    })
+  })
+}
+
+// 预览文件
+export function previewFile(filePath, fileType = 'image') {
+  return new Promise((resolve, reject) => {
+    if (!isWechatMiniProgram()) {
+      reject(new Error('不在微信小程序环境中'))
+      return
+    }
+
+    if (fileType === 'image') {
+      wx.previewImage({
+        urls: [filePath],
+        success: () => {
+          resolve()
+        },
+        fail: (err) => {
+          reject(err)
+        }
+      })
+    } else {
+      // 对于非图片文件,可以尝试打开文档
+      wx.openDocument({
+        filePath,
+        success: () => {
+          resolve()
+        },
+        fail: (err) => {
+          reject(err)
+        }
+      })
+    }
+  })
+}
+
+// 检查文件访问权限
+export function checkFilePermission() {
+  return new Promise((resolve) => {
+    if (!isWechatMiniProgram()) {
+      resolve({
+        hasPermission: false,
+        message: '不在微信小程序环境中'
+      })
+      return
+    }
+
+    // 尝试获取相册权限
+    wx.getSetting({
+      success: (res) => {
+        const hasAlbumPermission = res.authSetting['scope.writePhotosAlbum']
+        const hasCameraPermission = res.authSetting['scope.camera']
+        
+        resolve({
+          hasPermission: hasAlbumPermission && hasCameraPermission,
+          albumPermission: hasAlbumPermission,
+          cameraPermission: hasCameraPermission,
+          message: hasAlbumPermission && hasCameraPermission 
+            ? '文件访问权限正常' 
+            : '需要授权相册和相机权限'
+        })
+      },
+      fail: () => {
+        resolve({
+          hasPermission: false,
+          message: '无法检测权限状态'
+        })
+      }
+    })
+  })
+}
+
+// 请求文件访问权限
+export function requestFilePermission() {
+  return new Promise((resolve, reject) => {
+    if (!isWechatMiniProgram()) {
+      reject(new Error('不在微信小程序环境中'))
+      return
+    }
+
+    wx.authorize({
+      scope: 'scope.writePhotosAlbum',
+      success: () => {
+        resolve({
+          success: true,
+          message: '权限授权成功'
+        })
+      },
+      fail: (err) => {
+        reject({
+          success: false,
+          message: '权限授权失败',
+          error: err
+        })
+      }
+    })
+  })
+}
+
+// 显示权限设置引导
+export function showPermissionGuide() {
+  return new Promise((resolve) => {
+    if (!isWechatMiniProgram()) {
+      resolve()
+      return
+    }
+
+    wx.showModal({
+      title: '需要权限',
+      content: '为了正常使用文件功能,请在设置中开启相册和相机权限',
+      confirmText: '去设置',
+      cancelText: '取消',
+      success: (res) => {
+        if (res.confirm) {
+          wx.openSetting({
+            success: (settingRes) => {
+              resolve(settingRes)
+            },
+            fail: () => {
+              resolve()
+            }
+          })
+        } else {
+          resolve()
+        }
+      }
+    })
+  })
+}
+
+// 创建文件选择器组件
+export function createFileSelectorComponent() {
+  return {
+    data() {
+      return {
+        isWechat: false,
+        isMiniProgram: false,
+        hasPermission: false,
+        selectedFiles: [],
+        uploading: false
+      }
+    },
+    
+    mounted() {
+      this.checkEnvironment()
+    },
+    
+    methods: {
+      async checkEnvironment() {
+        this.isWechat = isWechatEnvironment()
+        this.isMiniProgram = isWechatMiniProgram()
+        
+        if (this.isMiniProgram) {
+          const env = await getWechatEnvironment()
+          console.log('微信环境信息:', env)
+          
+          const permission = await checkFilePermission()
+          this.hasPermission = permission.hasPermission
+          
+          if (!permission.hasPermission) {
+            this.$message.warning(permission.message)
+          }
+        }
+      },
+      
+      async selectFiles(type = 'all') {
+        try {
+          const files = await createWechatFileSelector({
+            count: 9,
+            type,
+            onSuccess: (files) => {
+              this.selectedFiles = files
+              this.$message.success(`已选择 ${files.length} 个文件`)
+            },
+            onFail: (error) => {
+              this.$message.error(`选择文件失败: ${error.errMsg || error.message}`)
+            },
+            onCancel: () => {
+              this.$message.info('用户取消选择')
+            }
+          })
+          
+          return files
+        } catch (error) {
+          this.$message.error(`选择文件失败: ${error.message}`)
+          throw error
+        }
+      },
+      
+      async uploadFiles(files) {
+        this.uploading = true
+        
+        try {
+          const uploadPromises = files.map(file => 
+            uploadFileToServer(file.path, {
+              url: '/api/upload',
+              onProgress: (progress) => {
+                console.log(`文件 ${file.name} 上传进度: ${progress.progress}%`)
+              }
+            })
+          )
+          
+          const results = await Promise.all(uploadPromises)
+          this.$message.success('所有文件上传成功')
+          return results
+        } catch (error) {
+          this.$message.error(`上传失败: ${error.message}`)
+          throw error
+        } finally {
+          this.uploading = false
+        }
+      },
+      
+      async requestPermission() {
+        try {
+          await requestFilePermission()
+          this.hasPermission = true
+          this.$message.success('权限授权成功')
+        } catch (error) {
+          await showPermissionGuide()
+        }
+      }
+    }
+  }
+}
+
+export default {
+  isWechatMiniProgram,
+  isWechatEnvironment,
+  isAndroid,
+  getWechatEnvironment,
+  createWechatFileSelector,
+  uploadFileToServer,
+  getFileInfo,
+  saveFileToAlbum,
+  previewFile,
+  checkFilePermission,
+  requestFilePermission,
+  showPermissionGuide,
+  createFileSelectorComponent
+} 

+ 1 - 1
src/views/file/index.vue

@@ -19,7 +19,7 @@
         ref="fileInput"
         type="file"
         multiple
-        accept="image/*,.pdf,.doc,.docx,.xls,.xlsx,.txt,.mp4,.mp3"
+        accept="*/*"
         @change="handleFileSelect"
         style="display: none"
       />