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

    模板包含三部分:

    • 已经上传的图片列表展示,若只读,则不展示删除按钮,每个图片点击后都可以被放大
    • 添加按钮展示,若没有图片,并且非只读,则展示
    • 暂无图片提示
    1. class="t-upload">
    2. <ion-grid>
    3. <template v-if="fileList?.length">
    4. <ion-col v-for="(img, index) in fileList" :key="img?.FILE_ID" size="4">
    5. <img
    6. class="file"
    7. :src="getImgUrl(img)"
    8. alt=""
    9. @click="goEnlargeImage(index)"
    10. />
    11. <img
    12. v-if="!readonly"
    13. class="delete"
    14. src="@/assets/image/common/upload-delete.png"
    15. @click.stop="removeFile(img?.FILE_ID)"
    16. />
    17. ion-col>
    18. template>
    19. <template v-if="!readonly">
    20. <ion-col v-if="!fileList?.length || fileList?.length < 9" size="4">
    21. <img
    22. class="add-file"
    23. src="@/assets/image/common/upload-add.png"
    24. @click="addMediaFile()"
    25. />
    26. ion-col>
    27. template>
    28. <template v-if="!fileList?.length && readonly">
    29. <div class="fs-14">暂无附件div>
    30. template>
    31. ion-grid>
  • 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-header>
    2. <ion-toolbar color="primary">
    3. <ion-buttons slot="end" @click="closePage()">
    4. <ion-icon class="close-icon" :icon="close">ion-icon>
    5. ion-buttons>
    6. ion-toolbar>
    7. ion-header>
    8. <ion-content>
    9. <swiper :initialSlide="initialSlide" @transitionStart="start($event)">
    10. <swiper-slide v-for="(item, index) in stateImageList" :key="index">
    11. <div class="img-box" :style="{ 'max-height': maxHeight }">
    12. <img
    13. v-if="
    14. !item.FILE_SUFFIX.toLowerCase() ||
    15. (item.FILE_SUFFIX.toLowerCase() !== 'mp4' &&
    16. item.FILE_SUFFIX.toLowerCase() !== 'mp3')
    17. "
    18. :src="getImgUrl(item)"
    19. :style="{ 'max-height': maxHeight }"
    20. />
    21. div>
    22. swiper-slide>
    23. swiper>
    24. ion-content>

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

  • 相关阅读:
    如何编译linux驱动ko
    【JavaScript】WebAPI详解
    SpringSecurity初识及使用
    快速集成 HelpLook ChatBot,无人值守AI机器人提升网站留资转化!
    CSS - 移动端布局(二)移动端适配
    Java并发面试题:(六)悲观锁和乐观锁和Java内存模型和CAS原理
    css实现一个温度计图表
    微信小程序--》小程序—组件通信和behavior使用
    sql表的相关操作
    如何计算一个结构体的大小?(C语言)
  • 原文地址:https://blog.csdn.net/Lyrelion/article/details/134049262