vant封装图片上传组件,支持上传前压缩

<!-- 
  * 上传图片和预览组件
  *使用方式:
  <yh-uploadimage code="jxhd" placeholder="请上传图片</br>(最多6张)" :limit="6" :grid="12"
  	v-model="formData.Pictures"></yh-uploadimage>
	参数说明:
	grid://栅格占位 格数为0-24 用的是element自行联想
	code://文件服务器必带的参数,其他地方用可以不加
	uploadpath://图片在文件服务器上存放目录
	filetypes://文件上传的类型如.png,.jpg,.jpeg,.webp,.gif
	limit://图片上传限制数量
	placeholder://上传控件上的文案
	disabled://是否禁止上传或者是用于浏览已上传的时候用true
	single://是否单图上传,形同limit=1
	fileRename://文件重命名定制项可不传
	category: //上传文件分类(0=系统普通文件,1=特定的目录,2=超越code的上传)
  *
 -->
<template>
	<div class="yh-uploadimage">
		<el-row :gutter="6">
			<el-col v-if="!showUploadList" :span="gridNumber" v-for="(item, index) in fileList" class="col">
				<div class="pre-item" :key="index">
					<img @click="previewImage(index)" class="pre-item-image" :src="FormatDFSUrl(item.url)"
						mode="aspectFill" />
					<div class="del u-f-ac u-f-ajc" @click="removeFun(index)" v-if="!disabled">
						<van-icon name="cross" color="#FFFFFF" />
					</div>
				</div>
			</el-col>
			<el-col :span="gridNumber" v-if="fileList&&fileList.length<limitNumber&&!disabled" class="col">
				<van-uploader :multiple="limitNumber>1?true:false" :max-count="limitNumber" :after-read="afterRead"
					:accept="limitType">
					<template #default>
						<div class="slot-btn u-f-ac u-f-ajc" hover-class="slot-btn__hover" hover-stay-time="150">
							<div>
								<i class="el-icon-camera-solid"></i>
								<div class="placeholder" v-html="placeholderText"> </div>
							</div>
						</div>
					</template>
				</van-uploader>
			</el-col>
		</el-row>
	</div>
</template>

<script>
	import {
		ImagePreview
	} from 'vant';
	import {
		media_file_upload_url,
		FormatDFSUrl
	} from '@/api/learn.js'
	export default {
		name: "yh-uploadimage",
		data() {
			return {
				limitNumber: 2,
				showUploadList: false,
				placeholderText: "请上传图片",
				action: "", //上传地址
				limitType: '.png,.jpg,.jpeg,.webp,.gif',
				upload_size: "10",
				currentCode: "", //文件服务器Code
				currentPath: "/upload", //文件存放目录
				fileList: [],
				gridNumber: 4
			}
		},
		props: {
			value: {
				type: String
			}, //传入的图片集合,多个之间用逗号分隔
			grid: {
				type: Number
			}, //栅格占位 格数为0-24
			code: {
				type: String
			}, //文件服务器Code
			uploadpath: {
				type: String,
				default: '/upload'
			}, //文件存放目录
			filetypes: {
				type: String
			}, //文件类型,多个逗号分隔
			limit: {
				type: Number,
				default: 2,
			}, //上传文件数量限制
			placeholder: {
				type: String,
			}, //提示文字
			disabled: {
				type: Boolean,
				default: false
			}, //是否禁用
			single: {
				type: Boolean,
				default: false
			}, //是否单个图片,相当于limit=1,但是优先级大于limit
			fileRename: {
				type: String,
			}, //文件重命名
			category: {
				type: Number
			}, //上传文件分类(0=系统普通文件,1=特定的目录,2=超越code的上传)
		},
		created() {
			this.initFun();
		},
		onPullDownRefresh() {},
		watch: {
			value() {
				this.uploadInit()
			},
			fileList() {
				let res = ""
				if (this.fileList && this.fileList.length) {
					this.fileList.map(el => {
						res += el.url + ","
					})
					res = res.substring(0, res.length - 1)
				}
				this.$emit("input", res);
			},
		},
		methods: {
			afterRead(file) {
				let files = file;
				if (files instanceof Array) {
					files.map(el => {
						this.uploadSectionFile(el)
					})
				} else {
					this.uploadSectionFile(files)
				}
			},//上传前
			imgUpload(file) {
				const formData = new FormData()
				formData.append('file', file)

				media_file_upload_url(this.action, formData).then(res => {
					if (res.success) {
						if (this.fileList.length < this.limitNumber) {
							this.fileList.push({
								url: res.response.path
							})
						} else {
							this.$toast('上传数量已达上限')
						}
					} else {
						this.notify({
							type: 'warning',
							message: res.response.msg
						});
					}

				})
			},
			compress(img, Orientation) {
				const canvas = document.createElement('canvas')
				const ctx = canvas.getContext('2d')
				// 瓦片canvas
				const tCanvas = document.createElement('canvas')
				const tctx = tCanvas.getContext('2d')
				const initSize = img.src.length
				let width = img.width
				let height = img.height
				// 如果图片大于四百万像素,计算压缩比并将大小压至400万以下
				let ratio
				if ((ratio = width * height / 4000000) > 1) {
					console.log('大于400万像素')
					ratio = Math.sqrt(ratio)
					width /= ratio
					height /= ratio
				} else {
					ratio = 1
				}
				canvas.width = width
				canvas.height = height
				// 		铺底色
				ctx.fillStyle = '#fff'
				ctx.fillRect(0, 0, canvas.width, canvas.height)
				// 如果图片像素大于100万则使用瓦片绘制
				let count
				if ((count = width * height / 1000000) > 1) {
					console.log('超过100W像素')
					count = ~~(Math.sqrt(count) + 1) // 计算要分成多少块瓦片
					//            计算每块瓦片的宽和高
					const nw = ~~(width / count)
					const nh = ~~(height / count)
					tCanvas.width = nw
					tCanvas.height = nh
					for (let i = 0; i < count; i++) {
						for (let j = 0; j < count; j++) {
							tctx.drawImage(img, i * nw * ratio, j * nh * ratio, nw * ratio, nh * ratio, 0, 0, nw, nh)
							ctx.drawImage(tCanvas, i * nw, j * nh, nw, nh)
						}
					}
				} else {
					ctx.drawImage(img, 0, 0, width, height)
				}
				// 修复ios上传图片的时候 被旋转的问题
				if (Orientation != '' && Orientation != 1) {
					switch (Orientation) {
						case 6: // 需要顺时针(向左)90度旋转
							this.rotateImg(img, 'left', canvas)
							break
						case 8: // 需要逆时针(向右)90度旋转
							this.rotateImg(img, 'right', canvas)
							break
						case 3: // 需要180度旋转
							this.rotateImg(img, 'right', canvas) // 转两次
							this.rotateImg(img, 'right', canvas)
							break
					}
				}
				// 进行最小压缩
				const ndata = canvas.toDataURL('image/jpeg', 0.1)
				/* console.log('压缩前:' + initSize)
				console.log('压缩后:' + ndata.length)
				console.log('ndata:' + ndata)
				console.log('压缩率:' + ~~(100 * (initSize - ndata.length) / initSize) + '%') */
				tCanvas.width = tCanvas.height = canvas.width = canvas.height = 0
				return ndata
			},
			uploadSectionFile(f) { //	附件上传
				const self = this
				let Orientation
				let ndata
				if (f.file.size <= 1 * 1024 * 1024) {
					// 判断图片是否大于1M,是就直接上传
					ndata = f.file
					self.imgUpload(ndata)
				} else {
					// 反之压缩图片
					const reader = new FileReader()
					// 将图片2将转成 base64 格式
					reader.readAsDataURL(f.file)
					console.log(reader)
					// 读取成功后的回调
					reader.onloadend = function() {
						const result = this.result
						const img = new Image()
						img.src = result
						img.onload = function() {
							ndata = self.compress(img, Orientation)
							// BASE64转图片
							var arr = ndata.split(',');
							var mime = arr[0].match(/:(.*?);/)[1]
							var bstr = atob(arr[1]);
							var n = bstr.length;
							var u8arr = new Uint8Array(n)
							while (n--) {
								u8arr[n] = bstr.charCodeAt(n)
							}
							ndata = new File([u8arr], f.file.name, {
								type: mime
							})
							// self.ndata = ndata
							self.imgUpload(ndata)
						}
					}
				}
			},//获取到文件流进行上传前的处理
			FormatDFSUrl(path) {
				return FormatDFSUrl(path)
			},//文件路径转换
			uploadInit() {
				if (this.value) {
					let curFilePathList = this.value.split(",");
					if (curFilePathList) {
						let fileListArr = [];
						for (let i = 0; i < curFilePathList.length; i++) {
							let item = curFilePathList[i];
							fileListArr.push({
								url: item,
							});
						}
						this.fileList = fileListArr;
					}
				} else {
					this.fileList = [];
				}
			},//格式化图片数组
			initFun() {
				this.uploadInit();
				if (this.grid) {
					this.gridNumber = this.grid;
				}
				if (this.limit) {
					this.limitNumber = this.limit;
				}
				if (this.single) {
					this.limitNumber = 1
				}
				if (this.placeholder) {
					this.placeholderText = this.placeholder;
				}
				if (this.filetypes) {
					this.limitType = this.filetypes;
				}
				if (this.code) {
					this.currentCode = this.code;
				} else {
					this.$message({
						message: "[图片上传]未传入系统Code",
						type: "error"
					});
					return;
				}
				if (this.uploadpath) {
					this.currentPath = this.uploadpath;
				}
				let $fileReName = '';
				if (this.fileRename)
					$fileReName = `&fileRename=${this.fileRename}`;
				this.action =
					`?code=${this.currentCode}&path=${this.currentPath}&category=${this.category}${$fileReName}`;
			},//初始化方法
			removeFun(index) {
				this.$dialog.confirm({
						title: '提示',
						message: '是否要删除该图片'
					})
					.then(() => {
						this.fileList.splice(index, 1);
					})
					.catch(() => {});
			}, //删除图片
			previewImage(index) {
				let fileList = [];
				this.fileList.map(el => {
					fileList.push(this.FormatDFSUrl(el.url))
				})
				ImagePreview({
					images: fileList,
					startPosition: index,
				});

			}, //预览图片
		}
	}
</script>

<style lang="scss" scoped>
	.col {
		margin-top: 10px;
	}

	/deep/ .u-upload {
		height: 180px;
		overflow: hidden;
		width: 100%;
		margin-top: -12px;
	}

	/deep/ .u-upload>uni-view {
		width: 100%;
		height: 100%;
		text-align: center;
	}

	/deep/ .van-uploader {
		width: 100%;

		.van-uploader__wrapper {
			width: 100%;

			.van-uploader__input-wrapper {
				width: 100%;
			}
		}
	}

	.slot-btn {
		width: 100%;
		height: 180px;
		background: rgb(244, 245, 246);
		text-align: center;
		padding: 0 20px;

		.el-icon-camera-solid {
			font-size: 60px;
			font-family: PingFang-SC-Medium, PingFang-SC;
			font-weight: 500;
			color: #CCCCCC;
		}

		.placeholder {
			font-size: 24px;
			font-family: PingFang-SC-Medium, PingFang-SC;
			font-weight: 500;
			color: #CCCCCC;
		}
	}

	.slot-btn__hover {
		background-color: rgb(235, 236, 238);
	}

	.pre-box {
		display: flex;
		align-items: center;
		justify-content: space-between;
		flex-wrap: wrap;
	}

	.pre-item {
		height: 180px;
		position: relative;
	}

	.pre-item-image {
		width: 100%;
		height: 100%;
		object-fit: cover;
	}

	.del {
		position: absolute;
		top: -8px;
		right: -10px;
		z-index: 11;
		background: red;
		width: 32px;
		height: 32px;
		border-radius: 50%;
		text-align: center;
		font-size: 24px;
	}
</style>

猜你喜欢

转载自blog.csdn.net/wgb0409/article/details/122496907