HarmonyOS Next 系列之从手机选择图片或拍照上传功能实现(五)

系列文章目录

HarmonyOS Next 系列之省市区弹窗选择器实现(一)
HarmonyOS Next 系列之验证码输入组件实现(二)
HarmonyOS Next 系列之底部标签栏TabBar实现(三)
HarmonyOS Next 系列之HTTP请求封装和Token持久化存储(四)
HarmonyOS Next 系列之从手机选择图片或拍照上传功能实现(五)
HarmonyOS Next 系列之可移动悬浮按钮实现(六)
HarmonyOS Next 系列之沉浸式状态实现的多种方式(七)
HarmonyOS Next系列之Echarts图表组件(折线图、柱状图、饼图等)实现(八)
HarmonyOS Next系列之地图组件(Map Kit)使用(九)
HarmonyOS Next系列之半圆环进度条实现(十)
HarmonyOS Next 系列之列表下拉刷新和触底加载更多数据实现(十一)
HarmonyOS Next系列之实现一个左右露出中间大两边小带缩放动画的轮播图(十二)


系列文章目录2

【鸿蒙】HarmonyOS NEXT开发快速入门教程之ArkTS语法装饰器(上)
【鸿蒙】HarmonyOS NEXT开发快速入门教程之ArkTS语法装饰器(下)
【鸿蒙】HarmonyOS NEXT应用开发快速入门教程之布局篇(上)
【鸿蒙】HarmonyOS NEXT应用开发快速入门教程之布局篇(下)
【鸿蒙】HarmonyOS Next 组件或页面之间的所有通信(传参)方法总结



前言

HarmonyOS Next(基于API11)实现从手机选择图片或拍照上传功能,常用于头像上传等操作


一、实现步骤总结

1、媒体读写权限检查和申请
2、从手机存储选择图片或拍照
3、把图片复制到缓存目录
4、接口请求上传图片

分析说明:
图片上传使用API request.uploadFile 而该api上传文件的本地路径只支持internal协议

在这里插入图片描述

所以选择完图片/或拍照后需要把图片从内部存储复制到cache目录下,该操作需要外部存储设备媒体读写权限,且是用户级别的权限,因此每次复制图片前需要检查权限如果没权限需弹窗口让用户授权,最后在通过该api实现上传

在这里插入图片描述

二、代码实现

1.媒体读写权限检查和申请

(1)检查权限

工具类文件:

import bundleManager from '@ohos.bundle.bundleManager';
import abilityAccessCtrl, { Context, Permissions } from '@ohos.abilityAccessCtrl';

//校验应用是否授予权限
//@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)
  }
}

调用:

      const READ_MEDIA_PERMISSION: Permissions = 'ohos.permission.READ_MEDIA' //媒体读取权限
      const WRITE_MEDIA_PERMISSION: Permissions = 'ohos.permission.WRITE_MEDIA' //媒体写入权限
      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)

(2)申请权限
工具类文件:

import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';
import common from '@ohos.app.ability.common'

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)
    })

  })
}

调用:

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

2.从手机存储选择图片或拍照

(1)从手机存储选择图片

    import picker from '@ohos.file.picker';
    ....
    ....

       //从相册选择
        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) {
              console.log(`图片本地路径:${PhotoSelectResult.photoUris[0]}`)
            } 
        })

(2)拍照

    import camera from '@ohos.multimedia.camera';
    import camerapicker from '@ohos.multimedia.cameraPicker';
    import { BusinessError } from '@ohos.base';
    ....
    ....

      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);
        } catch (error) {
          let err = error as BusinessError;
          console.error(`the pick call failed. error code: ${err.code}`);
       }   

3.复制图片到缓存目录下

默认复制图片到缓存目录cache根路径下,移动后文件名前面加上当前时间戳区分:timestamep+原name.格式

import fs from '@ohos.file.fs';

/**
 * 复制文件到缓存目录下
 * @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_ONLY)
    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('')
  }
}

4. 接口请求上传图片

  //开始上传图片 path:图片路径后缀(图片名称)
  async uploadImage(path: string) {
    let uri=`internal://cache/${path}` //上传图片全路径
    let uploadConfig: request.UploadConfig = {
      url:"http://xxxxxxx",
      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) => {
         console.info("upOnComplete success data" + JSON.stringify(data));
         })
      //所有图片上传完成回调
      uploadTask.on('complete', (taskStates: request.TaskState[]) => {
        console.info("upOnComplete complete taskState:" + JSON.stringify(taskStates));
      })
      //上传失败回调
      uploadTask.on('fail', (taskStates: request.TaskState[]) => {
        console.info("upOnComplete fail taskState:" + JSON.stringify(taskStates));
      })
    }catch (e){
      console.log( JSON.stringify(e),'e')
    }

  }

需要注意的是我们在复制图片步骤中通过context.cacheDir获取到的缓存目录路径如下所示:

"path":"/data/storage/el2/base/haps/entry/cache/1717854801890_IMG_20240603_170235.jpg"

需转换成internal协议路径,
前面 “/data/storage/el2/base/haps/entry/cache"实际等价于"internal://cache”
所以在上传接口拼接uri参数时候只需要知道图片名称+格式即可,最终上传参数拼接后路径为internal://cache/1717854801890_IMG_20240603_170235.jpg


三、完整代码

完整代码将封装一个完整的组件,自定义底部弹窗菜单选择拍照或从手机相册选择,选完自动上传。
在这里插入图片描述

代码目录结构
在这里插入图片描述

utils/index.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)
    })

  })
}

ImageUploadDialog.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' //媒体写入权限
      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)

      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()
        //无权限返回
        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'
    })
  }
}

组件入参 说明:

uploadURL:上传接口url
success:(res: ReceiveRes)=>void 上传成功回调函数
fail:(res: request.TaskState[])=>void=()=>{} //上传失败回调
complete: (res: request.TaskState[]) => void = () => {} //上传完成回调

完成或失败回调参数说明

 request.TaskState[]: {
  path:string //图片在本地路径
  message:string //上传结果信息
  responseCode //上传结果状态码 0:成功,其他值失败
 }[]

成功回调参数说明

interface ReceiveRes {
  body: string //接口返回数据,字符串类型需要通过JSON.Parse转对象来取值
  headers: object //请求头数据,对象类型
}

获取上传成功接口返回图片url可通过uploadTask.on(‘headerReceive’,callback)获取

页面调用:

pages/Index

import ImageUploadDialog from '../components/ImageUploadDialog/ImageUploadDialog'
import { promptAction } from '@kit.ArkUI'

@Entry
@Component
struct Index {
  @State dialogController: CustomDialogController | null = null //选择上传类型弹窗控制器
  aboutToAppear(): void {
   this.dialogController= new CustomDialogController({
     builder: ImageUploadDialog({
       uploadURL: 'http://xxxxxxxxx',//上传地址
       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,//自定义样式
    })
  }
  build() {
    Column(){
      Button('上传').onClick(()=>{
          this.dialogController?.open()
      })

    }.width('100%')
  }
}

最后不要忘记添加权限
三个:

  "ohos.permission.INTERNET":网访问权限
 "ohos.permission.READ_MEDIA":外部存储设备媒体读取权限
 "ohos.permission.WRITE_MEDIA":外部存储设备媒体写入权限

module.json5:

 //权限
    requestPermissions: [{
      "name": "ohos.permission.INTERNET",
    },{
      "name": "ohos.permission.READ_MEDIA",
      "reason": "$string:reasonReadWriteMedia",//使用权限原因
      "usedScene": {
        "abilities": [//使用的该权限的EntryAbility名称数组
          "EntryAbility"
        ],
        "when": "inuse"
      }
    },{
      "name": "ohos.permission.WRITE_MEDIA",
      "reason": "$string:reasonReadWriteMedia",
      "usedScene": {
        "abilities": [
          "EntryAbility"
        ],
        "when": "inuse"
      }
    }]

entry\src\main\resources\base\element\string.json

{
  "string": [
 ,{
      "name":"reasonReadWriteMedia",
      "value": "上传头像"
    }
  ]
}

效果:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述


2024-09-09更新:

最近上架被提示

应用申请ohos.permission.READ_MEDIA和ohos.permission.WRITE_MEDIA已下线,请使用参考@ohos.file.photoAccessHelper替换

也就是上文使用 @ohos.file.picker从手机相册选择图片api需要换成@ohos.file.photoAccessHelper (基于api12),其他都不变

 //从手机相册选择
      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)
            }
          }
        })
      })

换成:

import { photoAccessHelper } from '@kit.MediaLibraryKit';
....
....
....

 //从手机相册选择
      Text('从手机相册选择').custText().onClick(async () => {
        const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
        photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; // 过滤选择媒体文件类型为IMAGE
        photoSelectOptions.maxSelectNumber = 1; // 选择媒体文件的最大数目
        const photoViewPicker = new photoAccessHelper.PhotoViewPicker();
        photoViewPicker.select(photoSelectOptions).then(async(photoSelectResult: photoAccessHelper.PhotoSelectResult) => {
          if (photoSelectResult.photoUris.length) {
            //复制图片到缓存目录(缓存目录才有读写权限)
            let filePath = await copyFileToCache(photoSelectResult.photoUris[0])
            if (filePath) {
              this.uploadImage(filePath)
            }
          }
        }).catch((err: BusinessError) => {
         
        })
      })

使用@ohos.file.photoAccessHelper后无须再去检查和申请任何权限

完整代码:

utils/index.ets(工具类):


import fs from '@ohos.file.fs';
import  { Context } from '@ohos.abilityAccessCtrl';


/**
 * 复制文件到缓存目录下
 * @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_ONLY)//只读
    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('')
  }
}



ImageUploadDialog.ets(图片上传弹窗菜单选择组件):


import { copyFileToCache } from '../utils/index'
import { request } from '@kit.BasicServicesKit';
import camera from '@ohos.multimedia.camera';
import camerapicker from '@ohos.multimedia.cameraPicker';
import { BusinessError } from '@ohos.base';
import { common } from '@kit.AbilityKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit';

//上传回调数据类型
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 = () => {} //上传完成回调


  //开始上传图片 path:图片路径后缀(图片名称)
  async uploadImage(path: string) {
    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 () => {

        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 () => {
        const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
        photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; // 过滤选择媒体文件类型为IMAGE
        photoSelectOptions.maxSelectNumber = 1; // 选择媒体文件的最大数目
        const photoViewPicker = new photoAccessHelper.PhotoViewPicker();
        photoViewPicker.select(photoSelectOptions).then(async(photoSelectResult: photoAccessHelper.PhotoSelectResult) => {
          if (photoSelectResult.photoUris.length) {
            //复制图片到缓存目录(缓存目录才有读写权限)
            let filePath = await copyFileToCache(photoSelectResult.photoUris[0],this.context)
            if (filePath) {
              this.uploadImage(filePath)
            }
          }
        }).catch((err: BusinessError) => {

        })
      })
      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'
    })
  }
}

页面调用:
pages/Index

import ImageUploadDialog from '../components/ImageUploadDialog'
import { promptAction } from '@kit.ArkUI'

@Entry
@Component
struct Index {
  @State dialogController: CustomDialogController | null = null //选择上传类型弹窗控制器
  aboutToAppear(): void {
    this.dialogController= new CustomDialogController({
      builder: ImageUploadDialog({
        uploadURL: 'http://xxxxxxxxx',//上传地址
        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,//自定义样式
    })
  }
  build() {
    Column(){
      Button('上传').onClick(()=>{
        this.dialogController?.open()
      })

    }.width('100%')
  }
}
实现图片按按钮切换并具有平移切换动画的功能,可以使用HTML、CSS和JavaScript来完成。以下是一种实现方式: 首先,在HTML中需要定义一个容器,用来显示图片和按钮。可以使用`<div>`标签来定义容器,例如: ```html <div id="image-container"> <img src="image1.jpg" alt="Image 1"> <img src="image2.jpg" alt="Image 2"> <img src="image3.jpg" alt="Image 3"> <img src="image4.jpg" alt="Image 4"> <img src="image5.jpg" alt="Image 5"> <button id="prev-button">上一张</button> <button id="next-button">下一张</button> </div> ``` 在容器中使用`<img>`标签来显示图片,使用`<button>`标签来定义切换按钮。为了方便控制样式,我们给每个图片元素添加一个类名`image`,给上一张和下一张按钮分别添加类名`prev-button`和`next-button`。 接下来,需要使用CSS来设置容器和图片的样式。可以使用`position: relative`来让容器相对定位,以便后面实现图片的平移。在图片的样式中,使用`position: absolute`来让图片绝对定位,以便实现平移和叠加。图片的`left`属性可以设置为0、100%、200%等来控制图片的位置。 ```css #image-container { position: relative; width: 500px; height: 300px; overflow: hidden; } .image { position: absolute; top: 0; width: 500px; height: 300px; } .image:nth-child(1) { left: 0; } .image:nth-child(2) { left: 100%; } .image:nth-child(3) { left: 200%; } .image:nth-child(4) { left: 300%; } .image:nth-child(5) { left: 400%; } .prev-button, .next-button { position: absolute; top: 50%; transform: translateY(-50%); } .prev-button { left: 0; } .next-button { right: 0; } ``` 最后,实现JavaScript来切换图片实现平移切换动画。在这个例子中,我们使用了jQuery库来简化代码。具体实现如下: ```javascript $(function() { var $imageContainer = $('#image-container'); var $images = $imageContainer.find('.image'); var $prevButton = $('#prev-button'); var $nextButton = $('#next-button'); var currentImage = 1; // 初始化图片位置 $images.each(function(index) { var left = index * 100 + '%'; $(this).css('left', left); }); // 上一张按钮点击事件 $prevButton.on('click', function() { if (currentImage === 1) { return; } currentImage--; $images.animate({ left: '+=' + $imageContainer.width() }, 'slow'); }); // 下一张按钮点击事件 $nextButton.on('click', function() { if (currentImage === $images.length) { return; } currentImage++; $images.animate({ left: '-=' + $imageContainer.width() }, 'slow'); }); }); ``` 在这个JavaScript代码中,我们首先定义了一些变量来保存容器、图片和按钮的jQuery对象以及当前显示的图片编号。在初始化时,根据图片的位置设置每个图片的`left`属性。 在按钮的点击事件中,根据当前显示的图片编号和按钮的类型来计算应该显示的图片编号。然后,使用`animate()`方法来实现平移切换动画。在动画中,可以设置图片的`left`属性来实现平移。动画完成后,可以更新当前显示的图片编号。 完整的HTML、CSS和JavaScript代码如下: ```html <!DOCTYPE html> <html> <head> <title>图片切换</title> <style> #image-container { position: relative; width: 500px; height: 300px; overflow: hidden; } .image { position: absolute; top: 0; width: 500px; height: 300px; } .image:nth-child(1) { left: 0; } .image:nth-child(2) { left: 100%; } .image:nth-child(3) { left: 200%; } .image:nth-child(4) { left: 300%; } .image:nth-child(5) { left: 400%; } .prev-button, .next-button { position: absolute; top: 50%; transform: translateY(-50%); } .prev-button { left: 0; } .next-button { right: 0; } </style> </head> <body> <div id="image-container"> <img src="image1.jpg" alt="Image 1" class="image"> <img src="image2.jpg" alt="Image 2" class="image"> <img src="image3.jpg" alt="Image 3" class="image"> <img src="image4.jpg" alt="Image 4" class="image"> <img src="image5.jpg" alt="Image 5" class="image"> <button id="prev-button">上一张</button> <button id="next-button">下一张</button> </div> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <script> $(function() { var $imageContainer = $('#image-container'); var $images = $imageContainer.find('.image'); var $prevButton = $('#prev-button'); var $nextButton = $('#next-button'); var currentImage = 1; // 初始化图片位置 $images.each(function(index) { var left = index * 100 + '%'; $(this).css('left', left); }); // 上一张按钮点击事件 $prevButton.on('click', function() { if (currentImage === 1) { return; } currentImage--; $images.animate({ left: '+=' + $imageContainer.width() }, 'slow'); }); // 下一张按钮点击事件 $nextButton.on('click', function() { if (currentImage === $images.length) { return; } currentImage++; $images.animate({ left: '-=' + $imageContainer.width() }, 'slow'); }); }); </script> </body> </html> ```
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

pixle0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值