鸿蒙NEXT-图片上传

685 阅读6分钟

大致思路

利用图片选择器打开相册将选中的图片存(复制)到沙箱中,将这个图片(以类型,文件名,uri定义图片的唯一性)上传(request/axios)

一,选择图片

步骤

  1. 用photoAccessHelper创建一个图片选择器的行为PhotoSelectOptions
  2. 约束选择图片的条件,选择图片的最大数量,及图片类型
  3. 在用photoAccessHelper创建一个图片选择器PhotoViewPicker
  4. 用异步方法photoPicker.select()获取用户的选择结果
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { BusinessError } from '@kit.BasicServicesKit';

@Entry
@Component
struct Index {
  // 图片的uri
  uri: string = ""
  // 自定义生成的文件名
  fileName: string = ""
  // 图片类型
  fileType: string = "jpg"

  build() {
    Column() {
      Button("选择图片")
        .onClick(async () => {

          try {
            // 创建一个新的 PhotoSelectOptions 实例来配置图片选择器的行为
            let PhotoSelectOptions = new photoAccessHelper.PhotoSelectOptions();

            // 设置 MIME 类型为图像类型,这样用户只能选择图像文件
            PhotoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;

            // 设置用户可以选择的最大图片数量为 1 张
            PhotoSelectOptions.maxSelectNumber = 1;


            // PhotoSelectOptions.isSearchSupported = false

            // 创建一个新的 PhotoViewPicker 实例,用于打开图片选择器
            let photoPicker = new photoAccessHelper.PhotoViewPicker();

            // 使用前面配置好的选项打开图片选择器,并等待用户完成选择
            // 注意这里的 select 方法是一个异步方法,所以需要使用 await 关键字等待其结果
            const PhotoSelectResult = await photoPicker.select(PhotoSelectOptions);

            // 获取用户选择的第一张图片的 URI(统一资源标识符)
            // 假设这里只关心用户选择的第一张图片
            this.uri = PhotoSelectResult.photoUris[0];
          } catch (error) {
            // 如果在 try 块中发生了错误,则会在这里捕获到这个错误
            // 将捕获到的错误转换成 BusinessError 类型(假设 error 是一个继承自 Error 的自定义错误类型)


            // 在控制台中打印错误信息,包括错误码和错误消息
            console.error(`PhotoViewPicker failed with err: ${error.code}, ${error.message}`);
          }
        })
    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
  }
}

拷贝图片到缓存目录

为了只能上传存在缓存目录的文件

预览模拟器中的文件

/data/app/el2/100/base/你的项目的boundle Name 即可 /haps/entry/cache

  1. 获取上下文对象
  2. 拼接生成唯一的图片名称 例如:时间戳.jpg
  3. 用filo.openSync()来获取指定图片的读取权限
  4. 生成图片储存的路径
  5. 用fileIo.copyFileSync()将待复制的文件复制到指定路径
{

  try {
    // 三.将文件保存到缓存目录(只能上传在缓存目录中的文件)
    const context = getContext(this) // 上下文对象 程序运行过程中

    // 生成一个新的文件名   234342343243.jpg
    this.fileName = Date.now() + '.' + this.fileType


    // 获取这个相册图片文件的权限 读取权限
    const file = fileIo.openSync(this.uri, fileIo.OpenMode.READ_ONLY)


    // 将文件 拷贝到 临时目录
    // 通过缓存路径+文件名 拼接出完整的路径
    // /data/storage/el2/base/haps/entry/cache/    1726038971043.jpg
    const copyFilePath = context.cacheDir + '/' + this.fileName

    // 拷贝文件
    // file.fd  待复制的文件 文件描述符 数字
    // copyFilePath  文件复制到哪去
    fileIo.copyFileSync(file.fd, copyFilePath)
  } catch (e) {
    console.log("e", JSON.stringify(e))

  }
}

三,文件上传

  1. 用数组储存设置文件上传的信息(文件名,类型,路径......等等等)
  2. 用请求接口的方式发送文件信息
  3. 上传成功会返回图片上传的地址

  // 获取上下文,通常用于获取应用运行时环境或上下文信息
  const context = getContext(this);

  // 定义一个文件数组,其中包含要上传的文件信息
  let files: Array<request.File> = [
    {
      // 文件名 234342343243.jpg
      filename: this.fileName,
      // 文件类型
      type: this.fileType,
      // 看接口文档来确定
      name: 'img',
      // 文件的 URI,内部缓存路径加上文件名
      uri: `internal://cache/${this.fileName}`
    }
  ];

  // 发起文件上传请求
  request.uploadFile(context, {
    // 上传接口的 URL 地址
    url: 'https://hmajax.itheima.net/api/uploadimg',
    // HTTP 请求方法
    method: http.RequestMethod.POST,
    // 请求头,设置内容类型为 multipart/form-data,适用于文件上传
    header: {
      // multipart/form-data  当前的参数 是一个文件!!!
      contentType: 'multipart/form-data',
    },
    // 文件信息数组
    files,
    // 额外提交的数据,虽然这里为空数组,但根据接口要求不能省略
    data: []
  })
    .then(res => {
      // 监听 headerReceive 事件,当接收到响应头部时触发
      res.on('headerReceive', (value) => {
        // 解析并处理响应数据
        // 定义一个接口描述返回的数据结构
        interface I {
          url: string;
        }

        interface II2 {
          // code: number
          data: I;
          message: string;
        }

        interface II3 {
          body: string;
        }

        // 解析响应体中的数据
        const parsedBody = JSON.parse((value as II3).body) as II2;
        // 输出上传成功的文件 URL
        console.log("上传的地址为:", parsedBody.data.url);

        // 可以在此处添加其他逻辑,如显示对话框等
        // AlertDialog.show({
        //   message: parsedBody.data.url
        // });
      });
    })
    .catch((err: Error) => {
      console.log("err", JSON.stringify(err))
    })

四,归总流程

详细

import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { BusinessError, request } from '@kit.BasicServicesKit';
import { fileIo } from '@kit.CoreFileKit';
import { http } from '@kit.NetworkKit';


// fs.copyFileSync
@Entry
@Component
struct Index {
  // 图片的uri
  uri: string = ""
  // 自定义生成的文件名
  fileName: string = ""
  // 图片类型
  fileType: string = "jpg"

  build() {
    Column() {
      Button("1 选择图片")
        .onClick(async () => {

          try {
            // 创建一个新的 PhotoSelectOptions 实例来配置图片选择器的行为
            let PhotoSelectOptions = new photoAccessHelper.PhotoSelectOptions();

            // 设置 MIME 类型为图像类型,这样用户只能选择图像文件
            PhotoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;

            // 设置用户可以选择的最大图片数量为 1 张
            PhotoSelectOptions.maxSelectNumber = 1;


            // PhotoSelectOptions.isSearchSupported = false

            // 创建一个新的 PhotoViewPicker 实例,用于打开图片选择器
            let photoPicker = new photoAccessHelper.PhotoViewPicker();

            // 使用前面配置好的选项打开图片选择器,并等待用户完成选择
            // 注意这里的 select 方法是一个异步方法,所以需要使用 await 关键字等待其结果
            const PhotoSelectResult = await photoPicker.select(PhotoSelectOptions);

            // `file://media/Photo/1/IMG_1725939294_000/screenshot_20240910_113314.jpg`
            // 假设这里只关心用户选择的第一张图片
            this.uri = PhotoSelectResult.photoUris[0];
          } catch (error) {
            // 如果在 try 块中发生了错误,则会在这里捕获到这个错误
            // 将捕获到的错误转换成 BusinessError 类型(假设 error 是一个继承自 Error 的自定义错误类型)


            // 在控制台中打印错误信息,包括错误码和错误消息
            console.error(`PhotoViewPicker failed with err: ${error.code}, ${error.message}`);
          }
        })

      Button(`2 拷贝图片到缓存目录中`)
        .onClick(() => {

          try {
            // 三.将文件保存到缓存目录(只能上传在缓存目录中的文件)
            const context = getContext(this) // 上下文对象 程序运行过程中

            // 生成一个新的文件名   234342343243.jpg
            this.fileName = Date.now() + '.' + this.fileType


            // 获取这个相册图片文件的权限 读取权限
            const file = fileIo.openSync(this.uri, fileIo.OpenMode.READ_ONLY)


            // 将文件 拷贝到 临时目录
            // 通过缓存路径+文件名 拼接出完整的路径
            // /data/storage/el2/base/haps/entry/cache/    1726038971043.jpg
            const copyFilePath = context.cacheDir + '/' + this.fileName

            // 拷贝文件
            // file.fd  待复制的文件 文件描述符 数字
            // copyFilePath  文件复制到哪去
            fileIo.copyFileSync(file.fd, copyFilePath)
          } catch (e) {
            console.log("e", JSON.stringify(e))

          }
        })

      Button(`3 上传图片`)
        .onClick(() => {
          // 获取上下文,通常用于获取应用运行时环境或上下文信息
          const context = getContext(this);

          // 定义一个文件数组,其中包含要上传的文件信息
          let files: Array<request.File> = [
            {
              // 文件名 234342343243.jpg
              filename: this.fileName,
              // 文件类型
              type: this.fileType,
              // 看接口文档来确定
              name: 'img',
              // 文件的 URI,内部缓存路径加上文件名
              uri: `internal://cache/${this.fileName}`
            }
          ];

          // 发起文件上传请求
          request.uploadFile(context, {
            // 上传接口的 URL 地址
            url: 'https://hmajax.itheima.net/api/uploadimg',
            // HTTP 请求方法
            method: http.RequestMethod.POST,
            // 请求头,设置内容类型为 multipart/form-data,适用于文件上传
            header: {
              // multipart/form-data  当前的参数 是一个文件!!!
              contentType: 'multipart/form-data',
            },
            // 文件信息数组
            files,
            // 额外提交的数据,虽然这里为空数组,但根据接口要求不能省略
            data: []
          })
            .then(res => {
              // 监听 headerReceive 事件,当接收到响应头部时触发
              res.on('headerReceive', (value) => {
                // 解析并处理响应数据
                // 定义一个接口描述返回的数据结构
                interface I {
                  url: string;
                }

                interface II2 {
                  // code: number
                  data: I;
                  message: string;
                }

                interface II3 {
                  body: string;
                }

                // 解析响应体中的数据
                const parsedBody = JSON.parse((value as II3).body) as II2;
                // 输出上传成功的文件 URL
                console.log("上传的地址为:", parsedBody.data.url);

                // 可以在此处添加其他逻辑,如显示对话框等
                // AlertDialog.show({
                //   message: parsedBody.data.url
                // });
              });
            })
            .catch((err: Error) => {
              console.log("err", JSON.stringify(err))
            })
        })
    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
  }
}

// let str = `file://media/Photo/1/IMG_1725939294_000/screenshot_20240910_113314.jpg`
// // 获取 . 所在字符串内的下标 位置
//
// // let index=str.indexOf('.') // 左->右
// let index = str.lastIndexOf('.') // 右 -> 左
//
// // 从index位置开始裁剪,一直到结束  裁剪后的字符串给你返回
// const newStr = str.slice(index + 1)
// console.log("newStr", newStr)
// // console.log('index', index)

封装后的

import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { fileIo } from '@kit.CoreFileKit';
import { request } from '@kit.BasicServicesKit';
import { http } from '@kit.NetworkKit';

@Entry
@Component
struct Index {
  async aaa() {


    const promise = new Promise<string>(async (resolve) => {
      // 创建一个新的 PhotoSelectOptions 实例来配置图片选择器的行为
      let PhotoSelectOptions = new photoAccessHelper.PhotoSelectOptions();

      // 设置 MIME 类型为图像类型,这样用户只能选择图像文件
      PhotoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;

      // 设置用户可以选择的最大图片数量为 1 张
      PhotoSelectOptions.maxSelectNumber = 1;


      // PhotoSelectOptions.isSearchSupported = false

      // 创建一个新的 PhotoViewPicker 实例,用于打开图片选择器
      let photoPicker = new photoAccessHelper.PhotoViewPicker();

      // 使用前面配置好的选项打开图片选择器,并等待用户完成选择
      // 注意这里的 select 方法是一个异步方法,所以需要使用 await 关键字等待其结果
      const PhotoSelectResult = await photoPicker.select(PhotoSelectOptions);

      // `file://media/Photo/1/IMG_1725939294_000/screenshot_20240910_113314.jpg`
      // 假设这里只关心用户选择的第一张图片
      let uri = PhotoSelectResult.photoUris[0];

      //   ======================================

      // 三.将文件保存到缓存目录(只能上传在缓存目录中的文件)
      let fileType = "jpg"
      const context = getContext(this) // 上下文对象 程序运行过程中

      // 生成一个新的文件名   234342343243.jpg
      let fileName = Date.now() + '.' + fileType


      // 获取这个相册图片文件的权限 读取权限
      const file = fileIo.openSync(uri, fileIo.OpenMode.READ_ONLY)


      // 将文件 拷贝到 临时目录
      // 通过缓存路径+文件名 拼接出完整的路径
      // /data/storage/el2/base/haps/entry/cache/    1726038971043.jpg
      const copyFilePath = context.cacheDir + '/' + fileName

      // 拷贝文件
      // file.fd  待复制的文件 文件描述符 数字
      // copyFilePath  文件复制到哪去
      fileIo.copyFileSync(file.fd, copyFilePath)

      //   =====================================

      // 获取上下文,通常用于获取应用运行时环境或上下文信息


      // 定义一个文件数组,其中包含要上传的文件信息
      let files: Array<request.File> = [
        {
          // 文件名 234342343243.jpg
          filename: fileName,
          // 文件类型
          type: fileType,
          // 看接口文档来确定
          name: 'img',
          // 文件的 URI,内部缓存路径加上文件名
          uri: `internal://cache/${fileName}`
        }
      ];

      // 发起文件上传请求
      const res = await request.uploadFile(context, {
        // 上传接口的 URL 地址
        url: 'https://hmajax.itheima.net/api/uploadimg',
        // HTTP 请求方法
        method: http.RequestMethod.POST,
        // 请求头,设置内容类型为 multipart/form-data,适用于文件上传
        header: {
          // multipart/form-data  当前的参数 是一个文件!!!
          contentType: 'multipart/form-data',
        },
        // 文件信息数组
        files,
        // 额外提交的数据,虽然这里为空数组,但根据接口要求不能省略
        data: []
      })

      // 监听 headerReceive 事件,当接收到响应头部时触发
      res.on('headerReceive', (value) => {
        // 解析并处理响应数据
        // 定义一个接口描述返回的数据结构
        interface I {
          url: string;
        }

        interface II2 {
          // code: number
          data: I;
          message: string;
        }

        interface II3 {
          body: string;
        }

        // 解析响应体中的数据
        const parsedBody = JSON.parse((value as II3).body) as II2;
        // 输出上传成功的文件 URL
        // console.log("上传的地址为:", parsedBody.data.url);
        resolve(parsedBody.data.url)

      });


      // resolve("xxx")
    })
    return promise


  }

  @State
  imgSrc: string = ''

  build() {
    Column() {

      Image(this.imgSrc)
        .width(100)

      Button("直接图片上传")
        .onClick(async () => {
          //   返回 上传后的url给我即可
          const result = await  this.aaa()
          //   result 包含上传成功的url
          console.log("result", result)
          this.imgSrc = result
        })

    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
  }
}

暂时无法在飞书文档外展示此内容