/**
|
* CImagePicker
|
* @author Tevin
|
*/
|
|
<template>
|
<view class="c-image-picker">
|
<AtInput
|
ref="input"
|
:name="itemRes.name"
|
:title="itemRes.label"
|
:required="itemRes.required"
|
:error="itemRes.error"
|
/>
|
<AtImagePicker
|
ref="picker"
|
mode="aspectFit"
|
:sourceType="sourceType"
|
:multiple="count > 1"
|
:count="count"
|
:showAddBtn="!selectedFull"
|
:length="3"
|
:files="files"
|
:onChange="(files,operationType,index) => handleChange(files,operationType,index)"
|
:onFail="evt => handleFail(evt)"
|
:onImageClick="(index, file) => handleImgView(index,file)"
|
/>
|
<CImageCompressor ref="compressor" />
|
<CImagePreview ref="imgPreview" />
|
</view>
|
</template>
|
|
<script>
|
import Taro from '@tarojs/taro';
|
import { $ } from '@tarojs/extend';
|
import { AtInput, AtImagePicker, AtCurtain } from 'taro-ui-vue';
|
import { $hostBoot } from '@components/bases/HostBoot';
|
import { $fetchCommon } from '@fetchers/FCommon';
|
import { Tools } from '@components/common/Tools';
|
import CImageCompressor from './CImageCompressor.vue';
|
import CImagePreview from './CImagePreview.vue';
|
import './cImagePicker.scss';
|
|
export default {
|
name: 'CImagePicker',
|
components: {
|
CImageCompressor,
|
CImagePreview,
|
AtInput,
|
AtImagePicker,
|
AtCurtain,
|
},
|
props: {
|
// 表单数据资源(表单组件内部机制专用)
|
itemRes: Object,
|
// 最大图片张数
|
count: {
|
type: Number,
|
default: 1,
|
},
|
// 图片来源
|
sourceType: {
|
type: Array,
|
default: () => ['album', 'camera'],
|
},
|
// 上传图片参数
|
params: {
|
type: Object,
|
default: () => {},
|
},
|
},
|
data() {
|
return {
|
fileNames: {},
|
};
|
},
|
computed: {
|
files() {
|
const value = this.itemRes.formData[this.itemRes.name];
|
let files = [];
|
if (Object.prototype.toString.call(value) === '[object String]') {
|
files = value.split(',').map(url => ({ url }));
|
} else if (Object.prototype.toString.call(value) === '[object Array]') {
|
files = value.map(url => ({ url }));
|
}
|
return files;
|
},
|
selectedFull() {
|
return this.files.length >= this.count;
|
},
|
},
|
methods: {
|
_triggerChange(files) {
|
const value = [];
|
files.forEach(file => {
|
value.push(file.url);
|
// 记录原文件名
|
if (file.file && file.file.originalFileObj) {
|
this.fileNames[file.url] = file.file.originalFileObj.name;
|
}
|
});
|
this.itemRes.onChange(value);
|
},
|
handleChange(files, operationType, index) {
|
// 添加图片
|
if (operationType === 'add') {
|
// 屏蔽其他格式
|
let nextFiles = files.filter(file => {
|
// 新添加
|
if (file.file && file.file.originalFileObj) {
|
if (
|
/image\/(gif|png|jpg|jpeg)/.test(
|
file.file.originalFileObj.type
|
)
|
) {
|
return true;
|
} else {
|
Tools.toast('不支持的格式,请选择其他图片!');
|
return false;
|
}
|
}
|
// 非本次添加的图片
|
else {
|
return true;
|
}
|
});
|
// 限制数量
|
if (nextFiles.length > this.count) {
|
Tools.toast('最多只能选 ' + this.count + ' 张图片!');
|
nextFiles = nextFiles.splice(0, this.count);
|
}
|
// 检查是否需要压缩
|
const needs = nextFiles
|
.map((file, needIndex) => {
|
const fileInfo = file.file;
|
// 没有 file 信息对象,或者不是 blob 类型
|
if (!fileInfo || fileInfo.path.indexOf('blob') < 0) {
|
return false;
|
}
|
// 尺寸小于 1M 的图片
|
if (fileInfo.size < 1 * 1024 * 1024) {
|
return false;
|
}
|
return [fileInfo, needIndex];
|
})
|
.filter(Boolean);
|
// 存在需要压缩的图片,一次性压缩
|
if (needs.length > 0) {
|
const files2 = [...nextFiles];
|
const needPaths = needs.map(need => need[0].path);
|
this.$refs.compressor.$compress(needPaths, compressedFiles => {
|
compressedFiles.forEach((cpPath, cpIndex) => {
|
const filesIndex = needs[cpIndex][1];
|
files2[filesIndex] = {
|
url: cpPath.compress,
|
};
|
});
|
this._triggerChange(files2);
|
});
|
}
|
// 不存在,直接显示
|
else {
|
this._triggerChange(nextFiles);
|
}
|
}
|
// 删除图片,直接显示
|
else {
|
this._triggerChange(files);
|
}
|
},
|
handleImgView(index, file) {
|
const urls = this.files
|
.map(file => (file.type === 'btn' ? false : file.url))
|
.filter(Boolean);
|
// 图片预览,支持H5模式下的双指缩放
|
this.$refs.imgPreview.$preview({
|
current: file.url, // 当前显示图片的http链接
|
urls, // 需要预览的图片http链接列表
|
});
|
},
|
handleImgClose() {
|
this.showImg = false;
|
},
|
handleFail(msg) {
|
if (typeof msg === 'object') {
|
msg = msg.message;
|
}
|
Taro.showToast({
|
title: msg,
|
icon: 'none',
|
mask: true,
|
duration: 2000,
|
});
|
},
|
$uploadImage(callback) {
|
const files = [];
|
this.files.forEach(file => {
|
if (file.type === 'btn') {
|
return;
|
}
|
file.fileName = this.fileNames[file.url];
|
files.push(file);
|
});
|
uploadImage(
|
files,
|
(state, res) => {
|
if (state === 'success') {
|
this.itemRes.onChange(res);
|
setTimeout(() => {
|
callback(state);
|
}, 10);
|
} else if (state === 'error') {
|
callback(state, res);
|
}
|
},
|
this.params
|
);
|
},
|
},
|
mounted() {
|
if (process.env.TARO_ENV === 'h5') {
|
$(this.$refs.input.$el)
|
.find('.at-input__input')
|
.prepend(this.$refs.picker.$el);
|
} else if (process.env.TARO_ENV === 'weapp') {
|
$(this.$refs.input.$el)
|
.find('.at-input__container')
|
.append(this.$refs.picker.$el);
|
}
|
},
|
};
|
|
// 图片上传节流
|
const _readyUpload = {};
|
|
export const uploadImage = (files, callback, params) => {
|
if (!files || files.length === 0) {
|
callback('success', []);
|
return;
|
}
|
let url = $fetchCommon.getUploadImgURL();
|
if (url.indexOf('http') < 0) {
|
url = $hostBoot.getHost() + url;
|
}
|
const uploadTeam = [];
|
const imgs = [];
|
files.forEach(file => {
|
// 临时文件才上传
|
if (
|
file.url.indexOf('blob') >= 0 ||
|
file.url.indexOf('wxfile') >= 0 ||
|
file.url.indexOf('http://tmp/') >= 0
|
) {
|
const header = {};
|
if (process.env.TARO_ENV === 'weapp') {
|
const localCookies = JSON.parse(Taro.getStorageSync('cookies') || '{}');
|
const cookiesArr = [];
|
Object.keys(localCookies).forEach(key => {
|
cookiesArr.push(key + '=' + localCookies[key]);
|
});
|
header['Cookie'] = cookiesArr.join('; ');
|
}
|
const requestFile = {
|
url,
|
filePath: file.url,
|
fileName: file.fileName || '',
|
};
|
uploadTeam.push(
|
new Promise((resolve, reject) => {
|
// 如果本次已上传过,直接返回地址
|
if (_readyUpload[file.url] && _readyUpload[file.url].length > 5) {
|
resolve(_readyUpload[file.url]);
|
return;
|
}
|
// 上传
|
Taro.uploadFile({
|
...requestFile,
|
header,
|
name: 'file',
|
formData: { ...params },
|
timeout: 30 * 1000,
|
success(res) {
|
let res2;
|
try {
|
res2 =
|
typeof res.data === 'string'
|
? JSON.parse(res.data)
|
: res.data;
|
} catch (err) {
|
reject({
|
...requestFile,
|
response: res,
|
message: '上传图片异常!',
|
});
|
return;
|
}
|
// 上传成功
|
if (res2.state.code === 2000) {
|
const imgUrl = $fetchCommon.transImgPath(
|
'fix',
|
res2.data.src || res2.data.file || res2.data.url
|
);
|
_readyUpload[file.url] = imgUrl;
|
resolve(imgUrl);
|
}
|
// 上传失败
|
else {
|
reject({
|
...requestFile,
|
response: res2,
|
message: res2.state.msg,
|
});
|
}
|
},
|
cancel(err) {
|
reject({
|
...requestFile,
|
message: '上传图片已取消!',
|
});
|
},
|
fail() {
|
reject({
|
...requestFile,
|
message: '上传图片失败!',
|
});
|
},
|
});
|
})
|
);
|
}
|
// 其他类型视为 url,忽略
|
else {
|
uploadTeam.push(Promise.resolve(file.url));
|
}
|
});
|
Promise.all(uploadTeam)
|
.then(res => {
|
callback('success', res);
|
})
|
.catch(err => {
|
callback('error', err);
|
});
|
};
|
</script>
|