当前位置:   article > 正文

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

vue 拍照上传插件

目录

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 模板规划

模板包含三部分:

  • 已经上传的图片列表展示,若只读,则不展示删除按钮,每个图片点击后都可以被放大
  • 添加按钮展示,若没有图片,并且非只读,则展示
  • 暂无图片提示
  1. <div class="t-upload">
  2. <ion-grid>
  3. <!-- {{ fileList }} -->
  4. <!-- 已经上传的图片 -->
  5. <template v-if="fileList?.length">
  6. <ion-col v-for="(img, index) in fileList" :key="img?.FILE_ID" size="4">
  7. <img
  8. class="file"
  9. :src="getImgUrl(img)"
  10. alt=""
  11. @click="goEnlargeImage(index)"
  12. />
  13. <img
  14. v-if="!readonly"
  15. class="delete"
  16. src="@/assets/image/common/upload-delete.png"
  17. @click.stop="removeFile(img?.FILE_ID)"
  18. />
  19. </ion-col>
  20. </template>
  21. <!-- 添加图片按钮 -->
  22. <template v-if="!readonly">
  23. <ion-col v-if="!fileList?.length || fileList?.length < 9" size="4">
  24. <img
  25. class="add-file"
  26. src="@/assets/image/common/upload-add.png"
  27. @click="addMediaFile()"
  28. />
  29. </ion-col>
  30. </template>
  31. <template v-if="!fileList?.length && readonly">
  32. <div class="fs-14">暂无附件</div>
  33. </template>
  34. </ion-grid>
  35. </div>

1.2 点击添加按钮

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

  • 拍照
  • 相册选择

1.2.1 实现询问弹框

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

  1. async addFile(max: number, callback: any) {
  2. const actionSheet = await actionSheetController.create({
  3. header: '附件类型选择',
  4. buttons: [
  5. {
  6. text: '拍照',
  7. handler: () => {
  8. this.camera({
  9. quality: 100,
  10. destinationType: 1,
  11. sourceType: 1,
  12. targetWidth: 1080,
  13. targetHeight: 1920,
  14. mediaType: 0,
  15. encodingType: 1,
  16. })
  17. .then(async (res) => {
  18. callback(res, 'photo');
  19. })
  20. .catch((err) => {
  21. publicService.toast('拍照失败,请重试');
  22. });
  23. },
  24. },
  25. {
  26. text: '相册',
  27. handler: () => {
  28. this.slectImagePicker({
  29. maximumImagesCount: max,
  30. quality: 50,
  31. })
  32. .then((res) => {
  33. callback(res, 'img');
  34. })
  35. .catch(() => {
  36. publicService.toast('相册打开失败');
  37. });
  38. },
  39. },
  40. {
  41. text: '取消',
  42. role: 'cancel',
  43. handler: () => {
  44. console.error('Cancel clicked');
  45. },
  46. },
  47. ],
  48. });
  49. await actionSheet.present();
  50. }

1.2.2 实现拍照 

安装 cordova 插件:

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

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

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

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

  1. // 用于拍照或从相册选择照片
  2. import { Camera, CameraOptions } from '@awesome-cordova-plugins/camera';
  3. /**
  4. * 拍照
  5. * @param opts 拍照配置
  6. * @returns
  7. */
  8. camera(opts: CameraOptions): Promise<any> {
  9. return new Promise((resolve, reject) => {
  10. Camera.getPicture(opts)
  11. .then((res: ChooserResult) => {
  12. resolve(res);
  13. })
  14. .then((error) => {
  15. reject('native camera error');
  16. });
  17. });
  18. }

1.2.3 实现相册选择

安装 cordova 插件:

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

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

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

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

  1. // 用于从相册中选择照片
  2. import { ImagePicker, ImagePickerOptions } from '@awesome-cordova-plugins/image-picker';
  3. /**
  4. * 照片选择
  5. * @param opts
  6. * @returns
  7. */
  8. slectImagePicker(opts: ImagePickerOptions): Promise<any> {
  9. // console.log('照片选择 ImagePicker ---', ImagePicker);
  10. return new Promise((resolve, reject) => {
  11. ImagePicker.getPictures(opts)
  12. .then((res) => {
  13. resolve(res);
  14. })
  15. .catch(() => {
  16. reject('slectImagePicker native error');
  17. });
  18. });
  19. }

 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,也可以和后端协商,文件流会存储在这个变量中

  1. // 文件上传下载
  2. import {
  3. FileTransfer,
  4. FileUploadOptions,
  5. } from '@awesome-cordova-plugins/file-transfer';
  6. /**
  7. * 文件上传
  8. * @param fileUrl 文件路径
  9. * @param url 服务器地址
  10. * @param opts 配置项
  11. * @returns
  12. */
  13. fileLoad(
  14. fileUrl: string,
  15. url: string,
  16. opts: FileUploadOptions
  17. ): Promise<any> {
  18. return new Promise((resolve, reject) => {
  19. // 创建文件上传实例
  20. const file = FileTransfer.create();
  21. file
  22. .upload(fileUrl, url, opts, false)
  23. .then((res) => {
  24. publicService.closeLoading(this.loading);
  25. this.loading = null;
  26. resolve(res);
  27. })
  28. .catch((err) => {
  29. console.log('文件上传错误 native ---', err);
  30. publicService.closeLoading(this.loading);
  31. this.loading = null;
  32. reject('文件上传发生错误');
  33. });
  34. });
  35. }

1.2.5 校验图片类型并上传

  1. /**
  2. * 文件上传
  3. * @param fileUrl 附件地址
  4. * @paramascriptionTypename 附件名称
  5. */
  6. function fileUpload(fileUrl: string, name?: string) {
  7. // 获取文件名称
  8. const pathArr = state.fileUrl.split('?')[0].split('/') || '';
  9. // 文件格式后缀
  10. const suffix = state.fileUrl.substring(
  11. state.fileUrl.lastIndexOf('.') + 1
  12. );
  13. // 文件格式后缀
  14. const suffixs = ['png', 'jpg', 'jpeg', 'svg'];
  15. // 文件类型验证 通过后再上传文件
  16. let fileTypeValidate = true;
  17. if (state.fileUrl && !suffix) {
  18. fileTypeValidate = false;
  19. toast('不支持的文件类型');
  20. } else if (!suffixs.includes(suffix)) {
  21. fileTypeValidate = false;
  22. toast(`不支持${suffix}文件类型`);
  23. }
  24. // 若文件格式不合法,则不进行上传
  25. if (!fileTypeValidate) {
  26. if (publicService.loading) {
  27. publicService.closeLoading(publicService.loading);
  28. publicService.loading = null;
  29. }
  30. return;
  31. }
  32. // 获取文件名称
  33. const fileName = new Date().getTime() + (name || `${pathArr[pathArr.length - 1]}`);
  34. nativeService
  35. .fileLoad(fileUrl, encodeURI(API.uploadFile.serviceApi), {
  36. fileName, // 将文件保存在服务器上时,要使用的文件名
  37. fileKey: 'form_file', // 表单元素名称,默认为 file,也可以和后端协商,文件流会存储在这个变量中
  38. httpMethod: 'POST', // 上传接口请求方式
  39. params: {
  40. // 业务数据ID(用于业务关联附件)
  41. businessKey: props.businessKey,
  42. // 表单ID(可为空)- 分组
  43. inputFileId: props.inputFileId,
  44. // 文件
  45. form_file: fileUrl,
  46. // 登录用户
  47. createUser: userInfos.userId,
  48. },
  49. })
  50. .then((res) => {
  51. console.log('upload.vue 上传接口响应 ---', res.response);
  52. const testJX = JSON.parse(res.response);
  53. console.log('尝试解析 ---', testJX);
  54. if (publicService.loading) {
  55. publicService.closeLoading(publicService.loading);
  56. publicService.loading = null;
  57. }
  58. // 获取文件列表
  59. getFiles();
  60. })
  61. .catch((err) => {
  62. console.error(err);
  63. });
  64. }

1.2.6 获取图片列表

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

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

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

 

1.2.7 在组件内 添加图片附件

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

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

  1. nativeService.addFile(
  2. 9 - state.fileList.length,
  3. (res: any, fileType: string, name?: string) => {
  4. if (fileType === 'photo') {
  5. publicService.loading = publicService.sloading('拍照上传中,请稍候...');
  6. state.fileUrl = res;
  7. // 文件上传
  8. fileUpload(res, name || '');
  9. } else if (Array.isArray(res)) {
  10. if (res.length) {
  11. publicService.loading = publicService.sloading('相册选择照片上传中,请稍候...');
  12. }
  13. res.forEach(async (item, index1) => {
  14. state.fileUrl = item;
  15. state.fileName = name || '';
  16. // 文件上传
  17. await fileUpload(item, name || '');
  18. });
  19. } else {
  20. publicService.loading = publicService.sloading('附件上传中,请稍候...');
  21. state.fileUrl = res;
  22. state.fileName = name || '';
  23. // 文件上传
  24. fileUpload(res, name || '');
  25. }
  26. }
  27. );

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

2.1 点击图片放大

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

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

  1. /**
  2. * 打开图片预览界面
  3. * @param index 当前点击的图片索引
  4. */
  5. async function goEnlargeImage(index: number) {
  6. // console.log('t-upload.vue 点击的图片索引 ---', index);
  7. const modal = await modalController.create({
  8. component: EnlargeImage as any,
  9. componentProps: {
  10. pictures: state.fileList,
  11. initialSlide: index,
  12. time: new Date().getTime() + '',
  13. },
  14. cssClass: 'enlarge-image-modal',
  15. });
  16. await modal.present();
  17. }

2.2 模板规划

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

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

组件接受俩参数:

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

模板如下:

  1. <ion-page>
  2. <ion-header>
  3. <ion-toolbar color="primary">
  4. <ion-buttons slot="end" @click="closePage()">
  5. <ion-icon class="close-icon" :icon="close"></ion-icon>
  6. </ion-buttons>
  7. </ion-toolbar>
  8. </ion-header>
  9. <ion-content>
  10. <swiper :initialSlide="initialSlide" @transitionStart="start($event)">
  11. <swiper-slide v-for="(item, index) in stateImageList" :key="index">
  12. <!-- <div class="img-title">
  13. {{ item.FILE_NAME }}
  14. </div> -->
  15. <div class="img-box" :style="{ 'max-height': maxHeight }">
  16. <img
  17. v-if="
  18. !item.FILE_SUFFIX.toLowerCase() ||
  19. (item.FILE_SUFFIX.toLowerCase() !== 'mp4' &&
  20. item.FILE_SUFFIX.toLowerCase() !== 'mp3')
  21. "
  22. :src="getImgUrl(item)"
  23. :style="{ 'max-height': maxHeight }"
  24. />
  25. </div>
  26. <!-- {{ getImgUrl(item) }} -->
  27. </swiper-slide>
  28. </swiper>
  29. </ion-content>
  30. </ion-page>

2.3 使用 swiper11 踩坑的过程

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

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

引入 swiper 相关内容:

  1. // swiper
  2. import { Swiper, SwiperSlide } from 'swiper/vue';
  3. import { Navigation, Pagination } from 'swiper/modules';
  4. import 'swiper/css';
  5. import 'swiper/css/navigation';
  6. import 'swiper/css/pagination';
  7. import '@ionic/vue/css/ionic-swiper.css';
  8. // 响应式变量
  9. const state = reactive({
  10. // swiper 扩展模块
  11. // modules: [Navigation, Pagination],
  12. // 最大高度
  13. maxHeight: window.innerHeight - 100,
  14. });
  15. // 图片列表
  16. const stateImageList = computed(() => {
  17. return JSON.parse(JSON.stringify(props.pictures));
  18. });
  19. function start(ev: any) {
  20. // console.log('ev ---', ev, stateImageList.value);
  21. }
  22. /**
  23. * 关闭当前页面
  24. */
  25. function closePage() {
  26. modalController.dismiss();
  27. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/煮酒与君饮/article/detail/748973
推荐阅读
相关标签
  

闽ICP备14008679号