使用 ionic + cordova + vue3 实现相册选择、拍照,并上传、预览图片

目录

1.上传组件 upload.vue

1.1 模板规划

1.2 点击添加按钮

1.2.1 实现询问弹框

1.2.2 实现拍照 

1.2.3 实现相册选择

 1.2.4 实现文件上传

1.2.5 校验图片类型并上传

1.2.6 获取图片列表

1.2.7 在组件内 添加图片附件

2.图片放大组件 enlarge-image.vue

2.1 点击图片放大

2.2 模板规划

2.3 使用 swiper11 踩坑的过程


1.上传组件 upload.vue

1.1 模板规划

模板包含三部分:

  • 已经上传的图片列表展示,若只读,则不展示删除按钮,每个图片点击后都可以被放大
  • 添加按钮展示,若没有图片,并且非只读,则展示
  • 暂无图片提示
  <div class="t-upload">
    <ion-grid>
      <!-- {{ fileList }} -->

      <!-- 已经上传的图片 -->
      <template v-if="fileList?.length">
        <ion-col v-for="(img, index) in fileList" :key="img?.FILE_ID" size="4">
          <img
            class="file"
            :src="getImgUrl(img)"
            alt=""
            @click="goEnlargeImage(index)"
          />
          <img
            v-if="!readonly"
            class="delete"
            src="@/assets/image/common/upload-delete.png"
            @click.stop="removeFile(img?.FILE_ID)"
          />
        </ion-col>
      </template>

      <!-- 添加图片按钮 -->
      <template v-if="!readonly">
        <ion-col v-if="!fileList?.length || fileList?.length < 9" size="4">
          <img
            class="add-file"
            src="@/assets/image/common/upload-add.png"
            @click="addMediaFile()"
          />
        </ion-col>
      </template>

      <template v-if="!fileList?.length && readonly">
        <div class="fs-14">暂无附件</div>
      </template>
    </ion-grid>
  </div>

1.2 点击添加按钮

点击添加按钮后,会出现一个弹框,让用户选择图片来源:

  • 拍照
  • 相册选择

1.2.1 实现询问弹框

这个很简单,使用 ionic 的 actionSheetController 即可实现,根据用户的选择,决定执行的方法

  async addFile(max: number, callback: any) {
    const actionSheet = await actionSheetController.create({
      header: '附件类型选择',
      buttons: [
        {
          text: '拍照',
          handler: () => {
            this.camera({
              quality: 100,
              destinationType: 1,
              sourceType: 1,
              targetWidth: 1080,
              targetHeight: 1920,
              mediaType: 0,
              encodingType: 1,
            })
              .then(async (res) => {
                callback(res, 'photo');
              })
              .catch((err) => {
                publicService.toast('拍照失败,请重试');
              });
          },
        },
        {
          text: '相册',
          handler: () => {
            this.slectImagePicker({
              maximumImagesCount: max,
              quality: 50,
            })
              .then((res) => {
                callback(res, 'img');
              })
              .catch(() => {
                publicService.toast('相册打开失败');
              });
          },
        },
        {
          text: '取消',
          role: 'cancel',
          handler: () => {
            console.error('Cancel clicked');
          },
        },
      ],
    });
    await actionSheet.present();
  }

1.2.2 实现拍照 

安装 cordova 插件:

  • @awesome-cordova-plugins/camera@6.4.0
  • cordova-plugin-camera@7.0.0

容易出现的问题:在真机调试时,点击拍照,提示我传入了非法参数

解决方案:升级 camera 插件版本,原来用的版本是 4.1.4,升级到 7.0.0 后 自动解决问题 

此方法最终返回一个图片对象信息

// 用于拍照或从相册选择照片
import { Camera, CameraOptions } from '@awesome-cordova-plugins/camera';

  /**
   * 拍照
   * @param opts 拍照配置
   * @returns
   */
  camera(opts: CameraOptions): Promise<any> {
    return new Promise((resolve, reject) => {
      Camera.getPicture(opts)
        .then((res: ChooserResult) => {
          resolve(res);
        })
        .then((error) => {
          reject('native camera error');
        });
    });
  }

1.2.3 实现相册选择

安装 cordova 插件:

  • @awesome-cordova-plugins/image-picker@6.4.0
  • cordova-plugin-telerik-imagepicker@2.3.6

容易出现的问题:在相册选择界面中,确认取消按钮是英文

解决方案:在 node_modules 里,找插件源码中的 xml 文件,搜索相关英文单词,改成中文

此方法最终返回一组图片对象信息

// 用于从相册中选择照片
import { ImagePicker, ImagePickerOptions } from '@awesome-cordova-plugins/image-picker';

  /**
   * 照片选择
   * @param opts
   * @returns
   */
  slectImagePicker(opts: ImagePickerOptions): Promise<any> {
    // console.log('照片选择 ImagePicker ---', ImagePicker);
    return new Promise((resolve, reject) => {
      ImagePicker.getPictures(opts)
        .then((res) => {
          resolve(res);
        })
        .catch(() => {
          reject('slectImagePicker native error');
        });
    });
  }

 1.2.4 实现文件上传

安装 cordova 插件:

  • @awesome-cordova-plugins/file-transfer@6.4.0
  • cordova-plugin-file-transfer@1.7.1

容易出现的问题:

  • 若接口返回的数据不是 JSON 对象,而是 map 对象,则容易解析失败
  • 服务器上的文件名字,需要保证唯一性
  • 给 file-transfer 插件传递的服务器地址,需要使用 encodeURI 进行编码
  • file-transfer 插件会把 给接口的文件流参数,默认存到 file 里,可以通过 fileKey 指定参数名

解决方案:

  • 接口返回的数据 最终会在 res.response 中存储,这是插件替我们封装了一层,让后端把返回的数据写成 JSON 对象的格式
  • 使用时间戳保证名字唯一性,new Date().getTime() + (name || `${pathArr[pathArr.length - 1]}`)
  • 编码服务器地址:encodeURI(API.uploadFile.serviceApi)
  • fileKey: 'form_file', // 表单元素名称,默认为 file,也可以和后端协商,文件流会存储在这个变量中

// 文件上传下载
import {
  FileTransfer,
  FileUploadOptions,
} from '@awesome-cordova-plugins/file-transfer';

  /**
   * 文件上传
   * @param fileUrl 文件路径
   * @param url 服务器地址
   * @param opts 配置项
   * @returns
   */
  fileLoad(
    fileUrl: string,
    url: string,
    opts: FileUploadOptions
  ): Promise<any> {
    return new Promise((resolve, reject) => {
      // 创建文件上传实例
      const file = FileTransfer.create();
      file
        .upload(fileUrl, url, opts, false)
        .then((res) => {
          publicService.closeLoading(this.loading);
          this.loading = null;
          resolve(res);
        })
        .catch((err) => {
          console.log('文件上传错误 native ---', err);
          publicService.closeLoading(this.loading);
          this.loading = null;
          reject('文件上传发生错误');
        });
    });
  }

1.2.5 校验图片类型并上传

    /**
     * 文件上传
     * @param fileUrl 附件地址
     * @paramascriptionTypename 附件名称
     */
    function fileUpload(fileUrl: string, name?: string) {
      // 获取文件名称
      const pathArr = state.fileUrl.split('?')[0].split('/') || '';
      // 文件格式后缀
      const suffix = state.fileUrl.substring(
        state.fileUrl.lastIndexOf('.') + 1
      );
      // 文件格式后缀
      const suffixs = ['png', 'jpg', 'jpeg', 'svg'];

      // 文件类型验证  通过后再上传文件
      let fileTypeValidate = true;

      if (state.fileUrl && !suffix) {
        fileTypeValidate = false;
        toast('不支持的文件类型');
      } else if (!suffixs.includes(suffix)) {
        fileTypeValidate = false;
        toast(`不支持${suffix}文件类型`);
      }

      // 若文件格式不合法,则不进行上传
      if (!fileTypeValidate) {
        if (publicService.loading) {
          publicService.closeLoading(publicService.loading);
          publicService.loading = null;
        }
        return;
      }

      // 获取文件名称
      const fileName = new Date().getTime() + (name || `${pathArr[pathArr.length - 1]}`);
      nativeService
        .fileLoad(fileUrl, encodeURI(API.uploadFile.serviceApi), {
          fileName, // 将文件保存在服务器上时,要使用的文件名
          fileKey: 'form_file', // 表单元素名称,默认为 file,也可以和后端协商,文件流会存储在这个变量中
          httpMethod: 'POST', // 上传接口请求方式
          params: {
            // 业务数据ID(用于业务关联附件)
            businessKey: props.businessKey,
            // 表单ID(可为空)- 分组
            inputFileId: props.inputFileId,
            // 文件
            form_file: fileUrl,
            // 登录用户
            createUser: userInfos.userId,
          },
        })
        .then((res) => {
          console.log('upload.vue 上传接口响应 ---', res.response);
          const testJX = JSON.parse(res.response);
          console.log('尝试解析 ---', testJX);

          if (publicService.loading) {
            publicService.closeLoading(publicService.loading);
            publicService.loading = null;
          }
          // 获取文件列表
          getFiles();
        })
        .catch((err) => {
          console.error(err);
        });
    }

1.2.6 获取图片列表

获取图片列表 getFiles 的几个场景:

  • 上传成功后
  • 删除成功后
  • 组件初始化的时候
  • watch 监听到 给接口传递的业务参数 发生变化后,比如 businessKey

每次获取了图片列表后,都应该 emit 最新的图片列表信息

 

1.2.7 在组件内 添加图片附件

上面说过,拍照返回的是一个图片信息,相册选择返回的是一组图片信息

根据返回信息的类型,可以拿到图片在手机本地的路径、名称,并依次调用文件上传

nativeService.addFile(
  9 - state.fileList.length,
  (res: any, fileType: string, name?: string) => {
    if (fileType === 'photo') {
      publicService.loading = publicService.sloading('拍照上传中,请稍候...');
      state.fileUrl = res;
      // 文件上传
      fileUpload(res, name || '');
    } else if (Array.isArray(res)) {
      if (res.length) {
        publicService.loading = publicService.sloading('相册选择照片上传中,请稍候...');
      }
      res.forEach(async (item, index1) => {
        state.fileUrl = item;
        state.fileName = name || '';
        // 文件上传
        await fileUpload(item, name || '');
      });
    } else {
      publicService.loading = publicService.sloading('附件上传中,请稍候...');
      state.fileUrl = res;
      state.fileName = name || '';
      // 文件上传
      fileUpload(res, name || '');
    }
  }
);

2.图片放大组件 enlarge-image.vue

2.1 点击图片放大

使用 ionic modalController 创建一个弹框页面

在方法内部传入 图片放大组件、组件需要的各种参数、默认展示的图片索引 等信息

    /**
     * 打开图片预览界面
     * @param index 当前点击的图片索引
     */
    async function goEnlargeImage(index: number) {
      // console.log('t-upload.vue 点击的图片索引 ---', index);
      const modal = await modalController.create({
        component: EnlargeImage as any,
        componentProps: {
          pictures: state.fileList,
          initialSlide: index,
          time: new Date().getTime() + '',
        },
        cssClass: 'enlarge-image-modal',
      });
      await modal.present();
    }

2.2 模板规划

因为博主使用的是 ionic7,已经不存在 ion-slide 等组件了

通过官网提示 Vue Slides Guide: How to Get Swiper for Vue on Ionic Apps,决定使用 swiper 插件实现需求

组件接受俩参数:

  • 图片列表 pictures,在组件里用计算属性 stateImageList 进行关联,保证单项数据流
  • 当前激活的图片索引 initialSlide,默认为 0,和 swiper 的 initialSlide 属性关联绑定

模板如下:

  <ion-page>
    <ion-header>
      <ion-toolbar color="primary">
        <ion-buttons slot="end" @click="closePage()">
          <ion-icon class="close-icon" :icon="close"></ion-icon>
        </ion-buttons>
      </ion-toolbar>
    </ion-header>

    <ion-content>
      <swiper :initialSlide="initialSlide" @transitionStart="start($event)">
        <swiper-slide v-for="(item, index) in stateImageList" :key="index">
          <!-- <div class="img-title">
            {{ item.FILE_NAME }}
          </div> -->
          <div class="img-box" :style="{ 'max-height': maxHeight }">
            <img
              v-if="
                !item.FILE_SUFFIX.toLowerCase() ||
                (item.FILE_SUFFIX.toLowerCase() !== 'mp4' &&
                  item.FILE_SUFFIX.toLowerCase() !== 'mp3')
              "
              :src="getImgUrl(item)"
              :style="{ 'max-height': maxHeight }"
            />
          </div>
          <!-- {{ getImgUrl(item) }} -->
        </swiper-slide>
      </swiper>
    </ion-content>
  </ion-page>

2.3 使用 swiper11 踩坑的过程

ionic 官网说的安装 swiper:npm install swiper@latest

执行了这个命令,npm 只会给你装上 swiper/vue,没给你装 swiper/modules,这样问题非常大,因为 Navigation, Pagination 等模块必须安装 swiper/modules,才能进行引入,咱需要手动安装哦

引入 swiper 相关内容:

// swiper
import { Swiper, SwiperSlide } from 'swiper/vue';
import { Navigation, Pagination } from 'swiper/modules';
import 'swiper/css';
import 'swiper/css/navigation';
import 'swiper/css/pagination';
import '@ionic/vue/css/ionic-swiper.css';

    // 响应式变量
    const state = reactive({
      // swiper 扩展模块
      // modules: [Navigation, Pagination],
      // 最大高度
      maxHeight: window.innerHeight - 100,
    });

    // 图片列表
    const stateImageList = computed(() => {
      return JSON.parse(JSON.stringify(props.pictures));
    });

    function start(ev: any) {
      // console.log('ev ---', ev, stateImageList.value);
    }

    /**
     * 关闭当前页面
     */
    function closePage() {
      modalController.dismiss();
    }

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Lyrelion

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

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

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

打赏作者

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

抵扣说明:

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

余额充值