Use ionic + cordova + vue3 to select albums, take pictures, upload and preview pictures

Table of Contents

1. Upload component upload.vue

1.1 Template planning

1.2 Click the Add button

1.2.1 Implement inquiry pop-up box

1.2.2 Realize taking pictures

1.2.3 Implement album selection

1.2.4 Implement file upload

1.2.5 Verify image type and upload

1.2.6 Get image list

1.2.7 Add image attachments within the component

2. Image enlargement component enlarge-image.vue

2.1 Click on the image to enlarge

2.2 Template planning

2.3 The process of using swiper11 to avoid pitfalls


1. Upload component upload.vue

1.1 Template Planning

The template contains three parts:

  • A list of uploaded images is displayed. If it is read-only, the delete button will not be displayed. Each image can be enlarged after being clicked.
  • Add button display. If there is no picture and it is not read-only, it will be displayed.
  • No picture prompt yet
 <div class="t-upload">
    <ion-grid>
      <!-- {<!-- -->{ fileList }} -->

      <!-- Uploaded pictures -->
      <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>

      <!-- Add image button -->
      <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 & amp; & amp; readonly">
        <div class="fs-14">No attachments</div>
      </template>
    </ion-grid>
  </div>

1.2 Click the add button

After clicking the Add button, a pop-up box will appear allowing the user to select the image source:

  • Photograph
  • Album selection

1.2.1 Implement inquiry pop-up box

This is very simple. You can use ionic’s actionSheetController to implement it. The execution method is determined based on the user’s choice.

 async addFile(max: number, callback: any) {
    const actionSheet = await actionSheetController.create({
      header: 'Attachment type selection',
      buttons: [
        {
          text: 'take a photo',
          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('Photography failed, please try again');
              });
          },
        },
        {
          text: 'Album',
          handler: () => {
            this.slectImagePicker({
              maximumImagesCount: max,
              quality: 50,
            })
              .then((res) => {
                callback(res, 'img');
              })
              .catch(() => {
                publicService.toast('Album opening failed');
              });
          },
        },
        {
          text: 'Cancel',
          role: 'cancel',
          handler: () => {
            console.error('Cancel clicked');
          },
        },
      ],
    });
    await actionSheet.present();
  }

1.2.2 Realize taking photos

Install the cordova plugin:

Common problems: When debugging on a real machine, when I click to take a photo, I am prompted that I have passed in illegal parameters.

Solution: Upgrade the camera plug-in version. The original version was 4.1.4. After upgrading to 7.0.0, the problem will be automatically solved.

This method ultimately returns a picture object information

// Used to take photos or select photos from the album
import { Camera, CameraOptions } from '@awesome-cordova-plugins/camera';

  /**
   * Photograph
   * @param opts photo configuration
   * @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 Realize photo album selection

Install the cordova plugin:

Common problems: In the album selection interface, the confirmation button is in English.

Solution: In node_modules, find the xml file in the plug-in source code, search for relevant English words, and change it to Chinese

This method ultimately returns a set of image object information

// Used to select photos from the album
import { ImagePicker, ImagePickerOptions } from '@awesome-cordova-plugins/image-picker';

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

1.2.4 Implement file upload

Install the cordova plugin:

Common problems:

  • If the data returned by the interface is not a JSON object but a map object, the parsing may fail.
  • The file name on the server needs to be unique
  • The server address passed to the file-transfer plug-in needs to be encoded using encodeURI.
  • The file-transfer plug-in will save the file stream parameters given to the interface into file by default. You can specify the parameter name through fileKey.

solution:

  • The data returned by the interface will eventually be stored in res.response. This is a layer encapsulated by the plug-in for us, allowing the backend to write the returned data into the format of a JSON object.
  • Use timestamp to ensure name uniqueness, new Date().getTime() + (name || `${pathArr[pathArr.length – 1]}`)
  • Encoding server address: encodeURI(API.uploadFile.serviceApi)
  • fileKey: ‘form_file’, //The name of the form element, the default is file, you can also negotiate with the backend, the file stream will be stored in this variable
//File upload and download
import {
  FileTransfer,
  FileUploadOptions,
} from '@awesome-cordova-plugins/file-transfer';

  /**
   * File Upload
   * @param fileUrl file path
   * @param url server address
   * @param opts configuration items
   * @returns
   */
  fileLoad(
    fileUrl: string,
    url: string,
    opts: FileUploadOptions
  ): Promise<any> {
    return new Promise((resolve, reject) => {
      //Create file upload instance
      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('File upload error native ---', err);
          publicService.closeLoading(this.loading);
          this.loading = null;
          reject('An error occurred while uploading the file');
        });
    });
  }

1.2.5 Verify image type and upload

 /**
     * File Upload
     * @param fileUrl attachment address
     * @paramascriptionTypename attachment name
     */
    function fileUpload(fileUrl: string, name?: string) {
      // Get file name
      const pathArr = state.fileUrl.split('?')[0].split('/') || '';
      //File format suffix
      const suffix = state.fileUrl.substring(
        state.fileUrl.lastIndexOf('.') + 1
      );
      //File format suffix
      const suffixs = ['png', 'jpg', 'jpeg', 'svg'];

      //Upload the file after passing the file type verification
      let fileTypeValidate = true;

      if (state.fileUrl & amp; & amp; !suffix) {
        fileTypeValidate = false;
        toast('Unsupported file type');
      } else if (!suffixs.includes(suffix)) {
        fileTypeValidate = false;
        toast(`${suffix} file type not supported`);
      }

      // If the file format is illegal, it will not be uploaded.
      if (!fileTypeValidate) {
        if (publicService.loading) {
          publicService.closeLoading(publicService.loading);
          publicService.loading = null;
        }
        return;
      }

      // Get file name
      const fileName = new Date().getTime() + (name || `${pathArr[pathArr.length - 1]}`);
      nativeService
        .fileLoad(fileUrl, encodeURI(API.uploadFile.serviceApi), {
          fileName, // The file name to use when saving the file on the server
          fileKey: 'form_file', //The name of the form element, the default is file, you can also negotiate with the backend, the file stream will be stored in this variable
          httpMethod: 'POST', //Upload interface request method
          params: {
            //Business data ID (used for business-related attachments)
            businessKey: props.businessKey,
            // Form ID (can be empty) - grouping
            inputFileId: props.inputFileId,
            // document
            form_file: fileUrl,
            //Login user
            createUser: userInfos.userId,
          },
        })
        .then((res) => {
          console.log('upload.vue upload interface response ---', res.response);
          const testJX = JSON.parse(res.response);
          console.log('Try to parse ---', testJX);

          if (publicService.loading) {
            publicService.closeLoading(publicService.loading);
            publicService.loading = null;
          }
          // Get file list
          getFiles();
        })
        .catch((err) => {
          console.error(err);
        });
    }

1.2.6 Get picture list

Several scenarios for getting the picture list getFiles:

  • After uploading successfully
  • After successful deletion
  • When the component is initialized
  • watch monitors changes in the business parameters passed to the interface, such as businessKey

Each time you obtain the picture list, you should emit the latest picture list information.

1.2.7 Add image attachments within the component

As mentioned above, taking a photo returns a picture information, and selecting an album returns a set of picture information.

According to the type of returned information, you can get the local path and name of the image on the mobile phone, and call the file upload in sequence.

nativeService.addFile(
  9 - state.fileList.length,
  (res: any, fileType: string, name?: string) => {
    if (fileType === 'photo') {
      publicService.loading = publicService.sloading('Photos are being uploaded, please wait...');
      state.fileUrl = res;
      // File Upload
      fileUpload(res, name || '');
    } else if (Array.isArray(res)) {
      if (res. length) {
        publicService.loading = publicService.sloading('Photo album selection is being uploaded, please wait...');
      }
      res.forEach(async (item, index1) => {
        state.fileUrl = item;
        state.fileName = name || '';
        // File Upload
        await fileUpload(item, name || '');
      });
    } else {
      publicService.loading = publicService.sloading('Attachment is being uploaded, please wait...');
      state.fileUrl = res;
      state.fileName = name || '';
      // File Upload
      fileUpload(res, name || '');
    }
  }
);

2. Image enlargement component enlarge-image.vue

2.1 Click on the image to enlarge

Use ionic modalController to create a pop-up page

Inside the method, pass in information such as the image amplification component, various parameters required by the component, and the default displayed image index.

 /**
     * Open the picture preview interface
     * @param index currently clicked image index
     */
    async function goEnlargeImage(index: number) {
      // console.log('t-upload.vue clicked image index ---', 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 Template Planning

Because the blogger is using ionic7, components such as ion-slide no longer exist.

Through the official website prompt Vue Slides Guide: How to Get Swiper for Vue on Ionic Apps, I decided to use the swiper plug-in to meet the requirements.

The component accepts two parameters:

  • The picture list pictures is associated with the calculated attribute stateImageList in the component to ensure single data flow.
  • The currently activated image index initialSlide, which defaults to 0, is associated with the initialSlide property of swiper.

The template is as follows:

 <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' & amp; & amp;
                  item.FILE_SUFFIX.toLowerCase() !== 'mp3')
              "
              :src="getImgUrl(item)"
              :style="{ 'max-height': maxHeight }"
            />
          </div>
          <!-- {<!-- -->{ getImgUrl(item) }} -->
        </swiper-slide>
      </swiper>
    </ion-content>
  </ion-page>

2.3 The process of using swiper11 to overcome pitfalls

The ionic official website says to install swiper: npm install swiper@latest

After executing this command, npm will only install swiper/vue for you, but not swiper/modules. This is a very big problem, because modules such as Navigation and Pagination must install swiper/modules before they can be imported. We need to install them manually.

Introduce swiper related content:

// 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';

    //Responsive variables
    const state = reactive({
      // swiper extension module
      // modules: [Navigation, Pagination],
      // maximum height
      maxHeight: window.innerHeight - 100,
    });

    // Picture list
    const stateImageList = computed(() => {
      return JSON.parse(JSON.stringify(props.pictures));
    });

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

    /**
     * Close the current page
     */
    function closePage() {
      modalController.dismiss();
    }