大多数app项目基本上都设计到拍照和图片上传,尤其是用户编辑个人头像或者发布动态,

下面,我以最简单的方式封装了一个ImgUploadDialog.ets

        效果如下:

好的,那我们来分析一下,拍照是不是需要权限呢?选取本地文件(图片)是不是也要权限呢?

所以我查阅了官方api,总结我们需要使用

@ohos.abilityAccessCtrl:权限管理能力,包括鉴权、授权等

@ohos.bundle.bundleManager:获取BundleInfo,然后拿到appInfo,就可以拿accessTokenId

@ohos.file.fs:文件控制

分析好了之后就开干,先新建个工具类,utils.ets

import fs from '@ohos.file.fs';
import bundleManager from '@ohos.bundle.bundleManager';
import abilityAccessCtrl, { Context, Permissions } from '@ohos.abilityAccessCtrl';
import common from '@ohos.app.ability.common'

/**
 * 复制文件到缓存目录下
 * @param path :文件路径
 * @param context :Context
 * @returns Promise<string> 移动后文件路径
 */
export async function copyFileToCache(path: string,context:Context): Promise<string> {
  try {

    let file =  fs.openSync(path, fs.OpenMode.READ_WRITE)
    if (file) {
      let fileDir: string = `${context.cacheDir}` //临时文件目录
      //时间戳生成随机文件名
      let newPath: string =  `${new Date().getTime()}_${path.split("/")[path.split("/").length-1]}`
      let targetPath: string = `${fileDir}/${newPath}`
      fs.copyFileSync(file.fd, targetPath)
      return  newPath
    }
    else {
      return ''
    }

  } catch (e) {
    return Promise.resolve('')
  }
}

//校验应用是否授予权限
//@params permissions:权限名称数组
//@return permissionabilityAccessCtrl:权限名称
async function checkAccessToken(permission: Permissions): Promise<abilityAccessCtrl.GrantStatus> {
  let atManager = abilityAccessCtrl.createAtManager();
  let grantStatus: abilityAccessCtrl.GrantStatus = 0;

  // 获取应用程序的accessTokenID
  let tokenId: number = 0;
  try {
    let bundleInfo: bundleManager.BundleInfo = await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
    let appInfo: bundleManager.ApplicationInfo = bundleInfo.appInfo;
    tokenId = appInfo.accessTokenId;
  } catch (err) {
    console.error(`getBundleInfoForSelf failed, code is ${err.code}, message is ${err.message}`);
  }

  // 校验应用是否被授予权限
  try {
    grantStatus = await atManager.checkAccessToken(tokenId, permission);
  } catch (err) {
    console.error(`checkAccessToken failed, code is ${err.code}, message is ${err.message}`);
  }

  return grantStatus;
}


//检查用户权限
//@params permissions:权限名称数组
export  async function checkPermissions(permissions: Permissions): Promise<boolean> {
  try {
    let grantStatus: abilityAccessCtrl.GrantStatus = await checkAccessToken(permissions);
    return grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED
  }
  catch (e) {
    return Promise.reject(e)
  }
}

interface rejectObj {
  code: number
  message: string
}
/**
 * 申请权限
 * @params context:AblitiyContext
 * @params permissions:权限名称数组
 * @returns  Promise<boolean>:是否授权成功
 */
export async function applyPermission(context: common.UIAbilityContext, permissions: Array<Permissions>): Promise<boolean> {
  let atManager = abilityAccessCtrl.createAtManager();
  return new Promise((resolve: (res: boolean) => void, reject: (e: rejectObj) => void) => {
    atManager.requestPermissionsFromUser(context, permissions).then((data) => {
      let grantStatus: Array<number> = data.authResults;
      resolve(grantStatus.every(item => item === 0))
    }).catch((err: rejectObj) => {
      reject(err)
    })

  })
}
@Concurrent
export function printArgs(type: number): Promise<string> {
  return new Promise((rs,rj) =>{
    console.log('任务内容')
    if (type % 2 === 0) {
      rj('FAIL FULL')
    }
    setTimeout(() =>{
      rs('SUCCESS FULL')
    }, 2000)
  })
}

方法都写好了,在去新建个组件:ImgUploadDialog.ets

import picker from '@ohos.file.picker';
import { checkPermissions, applyPermission, copyFileToCache } from '../utils/index'
import { request } from '@kit.BasicServicesKit';
import { Permissions } from '@ohos.abilityAccessCtrl';
import camera from '@ohos.multimedia.camera';
import camerapicker from '@ohos.multimedia.cameraPicker';
import { BusinessError } from '@ohos.base';
import { common } from '@kit.AbilityKit';

//上传回调数据类型
interface ReceiveRes {
  body: string
  headers: object
}


@Extend(Text)
function custText() {
  .width('100%')
  .height('48')
  .fontColor('#39364D')
  .textAlign(TextAlign.Center)
}

@CustomDialog
export default struct ImageUploadDialog {
  dialogController: CustomDialogController
  @Prop uploadURL:string='';//上传接口地址
  private context = getContext(this) as common.UIAbilityContext; //UIAbilityContext
  private success: (res: ReceiveRes) => void = () => {} //上传成功回调
  private fail: (res: request.TaskState[]) => void = () => {} //上传失败回调
  private complete: (res: request.TaskState[]) => void = () => {} //上传完成回调

  //检查权限
  async checkAppPermission(): Promise<boolean> {
    try {
      const READ_MEDIA_PERMISSION: Permissions = 'ohos.permission.READ_MEDIA' //媒体读取权限
      const WRITE_MEDIA_PERMISSION: Permissions = 'ohos.permission.WRITE_MEDIA' //媒体写入权限
      const CAMERA_PERMISSION: Permissions = 'ohos.permission.CAMERA' //媒体写入权限
      let permissionList: Permissions[] = []; //需要申请选项列表
      let readPermission = await checkPermissions(READ_MEDIA_PERMISSION)//检查是否有媒体读取权限
      !readPermission && permissionList.push(READ_MEDIA_PERMISSION)
      let writePermission = await checkPermissions(WRITE_MEDIA_PERMISSION)//检查是否有媒体写入权限
      !writePermission && permissionList.push(READ_MEDIA_PERMISSION)
      let cameraPermission = await checkPermissions(CAMERA_PERMISSION)//检查是否有媒体写入权限
      !cameraPermission && permissionList.push(CAMERA_PERMISSION)

      if (permissionList.length) {
        //申请权限
        let res: boolean = await applyPermission(this.context, permissionList)
        if (!res) {//用户未同意授权
          AlertDialog.show({
            title: "提示",
            message: "无权限读写用户外部存储中的媒体文件信息,请前往系统设置开启",
            alignment: DialogAlignment.Center,
            secondaryButton: {
              value: '关闭',
              action: () => {
              }
            }
          })
        }
        return res
      }
      return true

    }

    catch (e) {
      return Promise.reject(e)
    }
  }

  //开始上传图片 path:图片路径后缀(图片名称)
  async uploadImage(path: string) {
    console.log(path, 'path')
    let uri=`internal://cache/${path}` //上传图片全路径
    let uploadConfig: request.UploadConfig = {
      url:this.uploadURL,
      header:{},
      method: "POST",
      files: [{ filename: path, name: "file", uri, type: path.split('.')[path.split('.').length-1] }],
      data: [],
    };
    try {
      let uploadTask:request.UploadTask=await request.uploadFile(this.context, uploadConfig)
      //上传中回调
      uploadTask.on('progress', (size,total) => {
        console.log(size.toString(),total.toString(),'上传进度')
      })

      //每上传一张图片成功回调
      uploadTask.on('headerReceive', (data: object) => {
        let res = data as ReceiveRes
        this.success && this.success(res)
      })

      //所有上传完成回调
      uploadTask.on('complete', (taskStates: request.TaskState[]) => {
        console.info("upOnComplete complete taskState:" + JSON.stringify(taskStates));
        this.complete && this.complete(taskStates)
      })
      //上传失败回调
      uploadTask.on('fail', (taskStates: request.TaskState[]) => {
        console.info("upOnComplete fail taskState:" + JSON.stringify(taskStates));
        this.fail&&this.fail(taskStates)
      })
    }catch (e){
      console.log( JSON.stringify(e),'e')
    }

  }

  build() {
    Column() {
      //拍照
      Text('拍照').custText().onClick(async()=>{
        //检查是否有读写外部媒体权限
        let res: boolean = await this.checkAppPermission()
        //无权限返回
        console.log(JSON.stringify(res), '拍照是否有权限')
        if (!res) {
          return
        }
        try {
          let pickerProfile: camerapicker.PickerProfile = {
            cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK
          };
          let pickerResult: camerapicker.PickerResult = await camerapicker.pick(this.context,
            [camerapicker.PickerMediaType.PHOTO, camerapicker.PickerMediaType.PHOTO], pickerProfile);
          if(pickerResult?.resultUri){
            //关闭弹窗
            this.dialogController.close()
            //复制图片到缓存目录(缓存目录才有读写权限)
            let filePath = await copyFileToCache(pickerResult.resultUri, this.context)
            if (filePath) {
              //上传头像并设置
              this.uploadImage(filePath)
            }

          }
        } catch (error) {
          let err = error as BusinessError;
          console.error(`the pick call failed. error code: ${err.code}`);
        }

      })
      Divider().color('#F7F9FA').width('100%').strokeWidth(1)
      //从手机相册选择
      Text('从手机相册选择').custText().onClick(async () => {
        //检查是否有读写外部媒体权限
        let res: boolean = await this.checkAppPermission()
        //无权限返回
        if (!res) return
        //关闭弹窗
        this.dialogController.close()
        //从相册选择
        let PhotoSelectOptions = new picker.PhotoSelectOptions();
        PhotoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
        PhotoSelectOptions.maxSelectNumber = 1;
        let photoPicker = new picker.PhotoViewPicker();
        photoPicker.select(PhotoSelectOptions).then(async (PhotoSelectResult) => {
          if (PhotoSelectResult.photoUris.length) {
            //复制图片到缓存目录(缓存目录才有读写权限)
            let filePath = await copyFileToCache(PhotoSelectResult.photoUris[0],this.context)
            if (filePath) {
              this.uploadImage(filePath)
            }
          }
        })
      })
      Button('取消', { type: ButtonType.Capsule })
        .backgroundColor('#F7F7F7')
        .fontSize('16fp')
        .fontColor('#333333')
        .width('100%')
        .margin({ top: '30' })
        .onClick(() => {
          this.dialogController.close()
        })
    }.width('100%').padding({ left: '16', top: '11', right: '16', bottom: '16' })
    .backgroundColor(Color.White)
    .borderRadius({
      topLeft: '24',
      topRight: '24'
    })
  }
}

在你需要的地方去使用:

先引入:

import ImgUploadDialog from '../../components/ImgUploadDialog'

在给它初始化一下:

@State dialogController: CustomDialogController | null = null //选择上传类型弹窗控制器
this.dialogController = new CustomDialogController({
  builder: ImgUploadDialog({
    uploadURL: 'https://xxx/', //上传地址
    success: e => { //上传成功回调,e上传成功接口返回数据
      let res = JSON.parse(e.body) as object //接口上传成功返回数据
      console.log(JSON.stringify(res), '上传成功')
      //根据实际接口返回字段获取图片url
      //url=res['data']
    },
    fail: e => { //上传失败回调
      console.log(JSON.stringify(e))
      promptAction.showToast({ message: '上传失败' })
    },
    complete: e => { //上传完成回调
      console.log(JSON.stringify(e), 'complete')
    }
  }),
  alignment: DialogAlignment.Bottom, //弹窗居于底部
  customStyle: true, //自定义样式
})

最后在你想要的方法中掉用:

this.dialogController?.open()
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐