目录
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();
}