| | |
| | | /> |
| | | <AtImagePicker |
| | | ref="picker" |
| | | multiple |
| | | mode="aspectFit" |
| | | :count="9" |
| | | :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)" |
| | | :onChange="(files,operationType,index) => handleChange(files,operationType,index)" |
| | | :onFail="evt => handleFail(evt)" |
| | | :onImageClick="(index, file) => handleImgView(index,file)" |
| | | /> |
| | | <AtCurtain |
| | | class="c-image-picker-view" |
| | | closeBtnPosition="top-right" |
| | | :isOpened="showImg" |
| | | :onClose="evt=>handleImgClose()" |
| | | > |
| | | <image |
| | | class="img" |
| | | mode="aspectFit" |
| | | :src="curtainImg" |
| | | /> |
| | | </AtCurtain> |
| | | <CImageCompressor ref="compressor" /> |
| | | <CImagePreview ref="imgPreview" /> |
| | | </view> |
| | | </template> |
| | | |
| | |
| | | import Taro from '@tarojs/taro'; |
| | | import { $ } from '@tarojs/extend'; |
| | | import { AtInput, AtImagePicker, AtCurtain } from 'taro-ui-vue'; |
| | | import { $fetcherCommon } from '@fetchers/FCommon'; |
| | | 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: () => {}, |
| | | }, |
| | | // 是否开启缩略图 |
| | | needThumb: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | // 上传地址来源 |
| | | uploadUrlSource: { |
| | | type: String, |
| | | default: '', |
| | | }, |
| | | }, |
| | | data() { |
| | | return { |
| | | showImg: false, |
| | | curtainImg: '', |
| | | fileNames: {}, |
| | | }; |
| | | }, |
| | | computed: { |
| | |
| | | } else if (Object.prototype.toString.call(value) === '[object Array]') { |
| | | files = value.map(url => ({ url })); |
| | | } |
| | | files.push({ type: 'btn' }); |
| | | return files; |
| | | }, |
| | | selectedFull() { |
| | | return this.files.length >= this.count; |
| | | }, |
| | | }, |
| | | methods: { |
| | | triggerChange(files) { |
| | | _triggerChange(files) { |
| | | const value = []; |
| | | files.forEach(file => { |
| | | if (file.type === 'btn') { |
| | | return; |
| | | } |
| | | 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') { |
| | | const needs = files |
| | | .map((file, index) => { |
| | | const fileInfo = file.file; |
| | | // 没有 file 信息对象,或者不是 blob 类型 |
| | | if (!fileInfo || fileInfo.path.indexOf('blob') < 0) { |
| | | // 屏蔽其他格式 |
| | | 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; |
| | | } |
| | | // 尺寸小于 2M 的图片 |
| | | } |
| | | // 非本次添加的图片 |
| | | 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、wxfile、tmp 类型 |
| | | if ( |
| | | !fileInfo || |
| | | (fileInfo.path.indexOf('blob') < 0 && |
| | | fileInfo.path.indexOf('wxfile') < 0 && |
| | | fileInfo.path.indexOf('http://tmp/') < 0) |
| | | ) { |
| | | return false; |
| | | } |
| | | // 尺寸小于 1M 的图片 |
| | | if (fileInfo.size < 1 * 1024 * 1024) { |
| | | return false; |
| | | } |
| | | return [fileInfo, index]; |
| | | return [fileInfo, needIndex]; |
| | | }) |
| | | .filter(Boolean); |
| | | // 存在需要压缩的图片,一次性压缩 |
| | | if (needs.length > 0) { |
| | | const files2 = [...files]; |
| | | const needPath = needs.map(need => need[0].path); |
| | | this.$refs.compressor.$compress(needPath, compressedFiles => { |
| | | compressedFiles.forEach((cpFile, index) => { |
| | | const filesIndex = needs[index][1]; |
| | | 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: cpFile.path, |
| | | file: cpFile, |
| | | url: cpPath.compress, |
| | | }; |
| | | }); |
| | | this.triggerChange(files2); |
| | | this._triggerChange(files2); |
| | | }); |
| | | } |
| | | // 不存在,直接显示 |
| | | else { |
| | | this.triggerChange(files); |
| | | this._triggerChange(nextFiles); |
| | | } |
| | | } |
| | | // 删除图片,直接显示 |
| | | else { |
| | | this.triggerChange(files); |
| | | this._triggerChange(files); |
| | | } |
| | | }, |
| | | handleImgView(index, file) { |
| | | this.curtainImg = file.url; |
| | | this.showImg = true; |
| | | 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) { |
| | | console.log(msg); |
| | | if (typeof msg === 'object') { |
| | | msg = msg.message; |
| | | } |
| | | Taro.showToast({ |
| | | title: msg, |
| | | icon: 'none', |
| | |
| | | }); |
| | | }, |
| | | $uploadImage(callback) { |
| | | const url = $fetcherCommon.getUploadImgURL(); |
| | | const uploadTeam = []; |
| | | const imgs = []; |
| | | const files = []; |
| | | this.files.forEach(file => { |
| | | if (file.type === 'btn') { |
| | | return; |
| | | } |
| | | // blob 临时文件才上传 |
| | | if (file.url.indexOf('blob') >= 0) { |
| | | uploadTeam.push( |
| | | new Promise((resolve, reject) => { |
| | | Taro.uploadFile({ |
| | | url, |
| | | filePath: file.url, |
| | | name: 'file', |
| | | formData: {}, |
| | | success(res) { |
| | | const res2 = |
| | | typeof res.data === 'string' |
| | | ? JSON.parse(res.data) |
| | | : res.data; |
| | | if (res2.state.code === 2000) { |
| | | resolve( |
| | | $fetcherCommon.transImgPath( |
| | | 'fix', |
| | | res2.data.src |
| | | ) |
| | | ); |
| | | } else { |
| | | reject({ message: res2.state.msg }); |
| | | } |
| | | }, |
| | | cancel() { |
| | | reject({ message: '上传图片已取消!' }); |
| | | }, |
| | | fail() { |
| | | reject({ message: '上传图片失败!' }); |
| | | }, |
| | | }); |
| | | }) |
| | | ); |
| | | } |
| | | // 其他类型视为 url,忽略 |
| | | else { |
| | | uploadTeam.push(Promise.resolve(file.url)); |
| | | } |
| | | file.fileName = this.fileNames[file.url]; |
| | | files.push(file); |
| | | }); |
| | | Promise.all(uploadTeam) |
| | | .then(res => { |
| | | this.itemRes.onChange(res); |
| | | setTimeout(() => { |
| | | callback('success'); |
| | | }, 0); |
| | | }) |
| | | .catch(err => { |
| | | callback('error', err); |
| | | }); |
| | | uploadImage( |
| | | files, |
| | | (state, res) => { |
| | | if (state === 'success') { |
| | | this.itemRes.onChange(res); |
| | | setTimeout(() => { |
| | | callback(state); |
| | | }, 10); |
| | | } else if (state === 'error') { |
| | | callback(state, res); |
| | | } |
| | | }, |
| | | $fetchCommon.transKeyName('underline', { |
| | | ...this.params, |
| | | needThumb: this.needThumb ? 1 : 0, |
| | | }), |
| | | this.uploadUrlSource, |
| | | ); |
| | | }, |
| | | }, |
| | | mounted() { |
| | |
| | | } |
| | | }, |
| | | }; |
| | | |
| | | // 图片上传节流 |
| | | const _readyUpload = {}; |
| | | |
| | | export const uploadImage = (files, callback, formData = {}, uploadUrlSource) => { |
| | | if (!files || files.length === 0) { |
| | | callback('success', []); |
| | | return; |
| | | } |
| | | let url = ''; |
| | | if (uploadUrlSource === 'LPG') { |
| | | url = $fetchCommon.getLPGUploadImgURL(); |
| | | } else { |
| | | 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, |
| | | 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> |