UniApp 调用原生能力:扫码、定位与文件系统的跨端封装

在跨端开发中,UniApp 的核心优势在于“一次编写,多端运行”。但真正决定用户体验的,往往是那些需要调用原生能力的场景,比如扫码、定位和文件操作。这些能力在 App、小程序、H5 等平台上的实现差异巨大,如果直接调用各端原生 API,代码会迅速变得难以维护。

本文将围绕扫码、定位与文件系统三个高频功能,分享如何在 UniApp 中做一层优雅的跨端封装,让业务代码不再关心平台差异。

为什么需要跨端封装

UniApp 已经提供了 uni.scanCodeuni.getLocationuni.getFileSystemManager 等统一 API,但实际开发中仍会遇到几个问题:

  • 平台支持不一致:例如 uni.getFileSystemManager 在 H5 端不可用,小程序与 App 的路径规则也不同。
  • 权限处理差异:App 端需要动态申请权限,小程序端需要在 manifest.json 中声明,H5 端则依赖浏览器授权。
  • 返回值格式不统一:定位返回的坐标系、扫码返回的码类型,在不同端存在细微差别。
  • 错误处理分散:用户拒绝授权、设备不支持、网络异常等场景,需要统一兜底。

因此,封装的目标不是替代 UniApp API,而是在其之上建立一层“业务友好”的适配层。

扫码能力的跨端封装

扫码在 App 和小程序中可以直接使用 uni.scanCode,H5 端则通常需要借助摄像头和第三方库(如 html5-qrcode)。封装时建议统一返回结构:

// utils/scan.js
export async function scanCode(options = {}) {
  const { onlyFromCamera = false, scanType = ['barCode', 'qrCode'] } = options
  
  // #ifdef H5
  return scanByH5(options)
  // #endif
  
  // #ifndef H5
  try {
    const res = await uni.scanCode({ onlyFromCamera, scanType })
    return {
      success: true,
      result: res.result,
      scanType: res.scanType,
      charSet: res.charSet
    }
  } catch (err) {
    return { success: false, error: err.errMsg || '扫码失败' }
  }
  // #endif
}

对于 H5 端,可以动态加载 html5-qrcode,用 video 元素渲染扫码区域,识别成功后 resolve 相同结构。这样业务层只需判断 success 字段,无需关心当前平台。

需要注意两点:一是 App 端扫码页面是原生全屏,无法自定义 UI;二是小程序端 scanType 部分类型不支持,建议做降级处理。

定位能力的跨端封装

定位的复杂性主要来自坐标系和权限。UniApp 的 uni.getLocation 支持 type: 'wgs84' | 'gcj02',但各端默认值不同:

  • App 端默认 wgs84,可指定 gcj02
  • 小程序端默认 wgs84,但微信小程序推荐 gcj02
  • H5 端依赖浏览器,通常返回 wgs84

封装时建议统一使用 gcj02(国内地图服务通用),并在 H5 端做坐标转换或直接提示用户。

// utils/location.js
export async function getLocation(options = {}) {
  const { type = 'gcj02', geocode = false } = options
  
  // #ifdef H5
  if (!navigator.geolocation) {
    return { success: false, error: '当前浏览器不支持定位' }
  }
  // #endif
  
  try {
    const res = await uni.getLocation({ type, geocode })
    return {
      success: true,
      latitude: res.latitude,
      longitude: res.longitude,
      address: res.address || null,
      raw: res
    }
  } catch (err) {
    const msg = err.errMsg || ''
    if (msg.includes('auth deny') || msg.includes('authorize')) {
      return { success: false, error: '用户拒绝了定位权限', needAuth: true }
    }
    return { success: false, error: msg || '定位失败' }
  }
}

在 App 端,还需要在 manifest.json 中配置定位模块,并在首次调用前动态申请权限。可以使用 plus.android.requestPermissions 或 UniApp 的 uni.authorize 做统一处理。

文件系统的跨端封装

文件系统是三者中平台差异最大的。小程序提供 uni.getFileSystemManager(),App 端有 plus.io,H5 端则只能用 BlobFileReader。封装时应聚焦几个高频操作:保存文件、读取文件、删除文件、获取文件信息。

// utils/fs.js
export async function saveFile(tempFilePath, targetName) {
  // #ifdef MP-WEIXIN
  const fs = uni.getFileSystemManager()
  const targetPath = `${wx.env.USER_DATA_PATH}/${targetName}`
  return new Promise((resolve) => {
    fs.saveFile({
      tempFilePath,
      filePath: targetPath,
      success: (res) => resolve({ success: true, path: res.savedFilePath }),
      fail: (err) => resolve({ success: false, error: err.errMsg })
    })
  })
  // #endif
  
  // #ifdef APP-PLUS
  return new Promise((resolve) => {
    plus.io.resolveLocalFileSystemURL(tempFilePath, (entry) => {
      plus.io.resolveLocalFileSystemURL('_doc/', (root) => {
        entry.copyTo(root, targetName, (newEntry) => {
          resolve({ success: true, path: newEntry.fullPath })
        }, (err) => resolve({ success: false, error: err.message }))
      })
    })
  })
  // #endif
  
  // #ifdef H5
  // H5 端通常直接使用 Blob 下载,不持久化
  return { success: false, error: 'H5 端不支持持久化保存' }
  // #endif
}

对于读取文件,小程序用 fs.readFile,App 用 plus.io.FileReader,H5 用 FileReader。建议统一返回 ArrayBufferbase64,由业务层决定如何解析。

统一错误处理与权限管理

跨端封装最容易忽略的是错误码的统一。建议定义一套内部错误码:

  • PERMISSION_DENIED:用户拒绝授权
  • NOT_SUPPORTED:当前平台不支持
  • SYSTEM_ERROR:系统或网络异常
  • USER_CANCEL:用户主动取消

在封装层捕获原生错误后,映射为上述错误码返回。业务层只需根据错误码决定是提示用户、跳转设置页,还是降级处理。

权限方面,可以封装一个 ensurePermission 方法,在调用扫码、定位前统一检查:

export async function ensurePermission(scope) {
  // #ifdef APP-PLUS
  // 使用 plus.android.requestPermissions
  // #endif
  // #ifdef MP-WEIXIN
  const setting = await uni.getSetting()
  if (!setting.authSetting[scope]) {
    await uni.authorize({ scope })
  }
  // #endif
  // #ifdef H5
  // 浏览器权限在调用时自动触发
  // #endif
}

小结

UniApp 的跨端能力已经覆盖了大部分场景,但扫码、定位、文件系统这三个领域由于平台底层差异大,仍然需要一层适配封装。封装的核心思路是:统一入参、统一返回结构、统一错误码、按平台条件编译。这样业务代码只需调用 scanCode()getLocation()saveFile(),而不必写满 #ifdef

随着 UniApp 对鸿蒙、纯血鸿蒙等新平台的支持,这层封装的价值会进一步放大。建议将上述工具方法放在 utils/native/ 目录下,配合 TypeScript 类型定义,形成团队内部的跨端原生能力规范。

未经允许不得转载:任鹏个人博客 » UniApp 调用原生能力:扫码、定位与文件系统的跨端封装

赞 (0) 打赏

评论 0

取消
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏