安裝
npm install vue-cropper
yarn add vue-cropper
注冊
元件内使用
import { VueCropper } from 'vue-cropper'
components: {
VueCropper,
},
main.js裡面使用
import VueCropper from 'vue-cropper'
Vue.use(VueCropper)
cdn方式使用
<script src="vuecropper.js"></script>
Vue.use(window['vue-cropper'])
nuxt 使用方式
if(process.browser) {
vueCropper = require('vue-cropper')
Vue.use(vueCropper.default)
}
使用
<template>
<div>
<!-- 多圖檔上傳 -->
<el-upload v-if="multiple" action='string' list-type="picture-card" accept="image/*" :on-preview="handlePreview" :auto-upload="false" :on-remove="handleRemove" :http-request="upload" :on-change="consoleFL" :file-list="uploadList">
<i class="el-icon-plus"></i>
</el-upload>
<!-- 單圖檔上傳 -->
<el-upload v-else class="avatar-uploader" action="'string'" :auto-upload="false" :show-file-list="false" :on-change="handleCrop" :http-request="upload">
<img v-if="imageUrl" :src="imageUrl" class="avatar" ref="singleImg" @mouseenter="mouseEnter" @mouseleave="mouseLeave" :style="{width:width+'px',height:height+'px'}">
<i v-else class="el-icon-plus avatar-uploader-icon" :style="{width:width+'px',height:height+'px','line-height':height+'px','font-size':height/6+'px'}"></i>
<!-- 單圖檔上傳狀态顯示 -->
<!-- <div v-if="imageUrl" class="reupload" ref="reupload" @click.stop="handlePreviewSingle" @mouseenter="mouseEnter" @mouseleave="mouseLeave" :style="{width:reuploadWidth+'px',height:reuploadWidth+'px','line-height':reuploadWidth+'px','font-size':reuploadWidth/5+'px'}">重新上傳</div> -->
<div id="uploadIcon" v-if="imageUrl" ref="reupload" @mouseenter="mouseEnter" @mouseleave="mouseLeave" :style="{width:'100%'}">
<i class="el-icon-zoom-in" @click.stop="handlePreviewSingle" :style="{color:'#2E2E2E',fontSize:'25px',display:'inline-block',paddingRight:'15px'}"></i>
<i class="el-icon-upload" :style="{color:'#2E2E2E',fontSize:'25px',display:'inline-block'}"></i>
</div>
<div class="reupload" ref="uploading" :style="{width:reuploadWidth+'px',height:reuploadWidth+'px','line-height':reuploadWidth+'px','font-size':reuploadWidth/5+'px'}">上傳中..</div>
<div class="reupload" ref="failUpload" :style="{width:reuploadWidth+'px',height:reuploadWidth+'px','line-height':reuploadWidth+'px','font-size':reuploadWidth/5+'px'}">上傳失敗</div>
</el-upload>
<!-- 多圖檔預覽彈窗 -->
<el-dialog :visible.sync="dialogVisible">
<img width="100%" :src="dialogImageUrl" alt="">
</el-dialog>
<!-- 剪裁元件彈窗 -->
<el-dialog :visible.sync="cropperModel" width="1100px" :before-close="beforeClose">
<Cropper :img-file="file" ref="vueCropper" :fixedNumber="fixedNumber" @upload="upload">
</Cropper>
</el-dialog>
</div>
</template>
<script>
import Cropper from './cropper';
// import axios from '@/assets/js/axios'
export default {
name: 'uploader',
props: { // 這裡幾個屬性可以寫在data中,這是原部落客寫法,應該是又套了一層
targetUrl: {
// 上傳位址
type: String,
// default: '/storage/upload'
default: `${process.env.API_ROOT}/sys/oss/upload`
},
multiple: {
// 多圖開關
type: Boolean,
default: false
},
initUrl: {
// 初始圖檔連結
default: ''
},
fixedNumber: {
// 剪裁框比例設定
default: function () {
return [1.5, 1];
}
},
width: {
// 單圖剪裁框寬度
type: Number,
default: 178
},
height: {
// 單圖剪裁框高度
type: Number,
default: 178
}
},
data () {
return {
file: '', // 目前被選擇的圖檔檔案
imageUrl: '', // 單圖情況框内圖檔連結
dialogImageUrl: '', // 多圖情況彈窗内圖檔連結
uploadList: [], // 上傳圖檔清單
reupload: true, // 控制"重新上傳"開關
dialogVisible: false, // 展示彈窗開關
cropperModel: false, // 剪裁元件彈窗開關
reuploadWidth: this.height * 0.7, // 動态改變”重新上傳“大小
};
},
updated () {
if (this.$refs.vueCropper) {
this.$refs.vueCropper.Update();
}
},
watch: {
initUrl: function (val) {
// 監聽傳入初始化圖檔
// console.info('watch');
if (val) {
if (typeof this.initUrl === 'string') {
this.imageUrl = val;
} else {
this.uploadList = this.formatImgArr(val);
// this.$emit('imgupload', this.uploadList);
}
}
}
},
mounted () {
if (typeof this.initUrl === 'string') {
this.imageUrl = this.initUrl;
} else {
this.uploadList = this.formatImgArr(this.initUrl);
}
},
methods: {
/** **************************** multiple多圖情況 **************************************/
handlePreview (file) {
// 點選進行圖檔展示
this.dialogImageUrl = file.url;
this.dialogVisible = true;
},
handleRemove (file, fileList) {
// 删除圖檔後更新圖檔檔案清單并通知父級變化
this.uploadList = fileList;
this.$emit('imgupload', this.uploadList);
// this.$emit('imgupload', this.formatImgArr(this.uploadList));
},
consoleFL (file, fileList) {
// 彈出剪裁框,将目前檔案設定為檔案
this.cropperModel = true;
this.file = file;
// this.uploadList = fileList;
},
/************************************************************************************/
/** **************************** single單圖情況 **************************************/
handlePreviewSingle (file) { // 點選進行圖檔展示
this.dialogImageUrl = this.file.url;
this.dialogVisible = true;
},
mouseEnter () { // 滑鼠劃入顯示“重新上傳”
this.$refs.reupload.style.display = 'block';
if (this.$refs.failUpload.style.display === 'block') {
this.$refs.failUpload.style.display = 'none';
}
this.$refs.singleImg.style.opacity = '0.6';
},
mouseLeave () {
// 滑鼠劃出隐藏“重新上傳”
this.$refs.reupload.style.display = 'none';
this.$refs.singleImg.style.opacity = '1';
},
handleCrop (file, files) {
// console.log(file);
// 點選彈出剪裁框
this.cropperModel = true;
this.file = file;
// this.imageUrl = file.url
},
/************************************************************************************/
async upload (data) {
// 自定義upload事件
if (!this.multiple) {
// 如果單圖,則顯示正在上傳
this.$refs.uploading.style.display = 'block';
}
let img = new Image();
img.src = data;
img.onload = async () => {
// let _data = this.compress(img);
let blob = this.dataURItoBlob(data);
let formData = new FormData();
formData.append('file', blob, this.file.name); // 有的背景需要傳檔案名,不然會報錯
this.imgUpload(formData);
};
},
async imgUpload(formData) {
const res = await this.$http({
url: 'sys/oss/upload',
method: 'post',
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
});
if (!this.multiple) {
// 上傳完成後隐藏正在上傳
this.$refs.uploading.style.display = 'none';
}
if (res.data.code === 0) {
// 上傳成功将照片傳回父元件
const currentPic = res.data.url;
if (this.multiple) {
this.uploadList.push({
url: currentPic,
uid: '111'
});
this.$emit('imgupload', this.uploadList);// 根據自己實際項目需要将照片傳回給父元件
// this.uploadList.pop();
// this.$emit('imgupload', this.formatImgArr(this.uploadList));
} else {
this.$emit('imgupload', currentPic);
}
this.$refs.vueCropper.isDisabled = false;
} else {
// 上傳失敗則顯示上傳失敗,如多圖則從圖檔清單删除圖檔
if (!this.multiple) {
this.$refs.failUpload.style.display = 'block';
} else {
this.uploadList.pop();
}
this.$refs.vueCropper.isDisabled = false;
}
this.cropperModel = false;
},
formatImgArr (arr) {
const result = arr.map((item, index) => {
if (typeof item === 'string') {
return {
url: item,
uid: `index${index}`
};
} else {
return item.url;
}
});
return result;
},
beforeClose () {
// this.uploadList.pop();
console.log(this.uploadList);
this.cropperModel = false;
},
// 壓縮圖檔
compress(img) {
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
// let initSize = img.src.length;
let width = img.width;
let height = img.height;
canvas.width = width;
canvas.height = height;
// 鋪底色
ctx.fillStyle = '#fff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, width, height);
// 進行壓縮
let ndata = canvas.toDataURL('image/jpeg', 0.8);
return ndata;
},
// base64轉成bolb對象
dataURItoBlob(base64Data) {
let byteString;
if (base64Data.split(',')[0].indexOf('base64') >= 0) { byteString = atob(base64Data.split(',')[1]); } else { byteString = unescape(base64Data.split(',')[1]); }
let mimeString = base64Data.split(',')[0].split(':')[1].split(';')[0];
let ia = new Uint8Array(byteString.length);
for (let i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
return new Blob([ia], { type: mimeString });
}
},
components: {
Cropper
}
};
</script>
<style>
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
border-color: #409eff;
}
.avatar-uploader-icon {
color: #8c939d;
text-align: center;
}
.avatar {
display: block;
}
.reupload {
border-radius: 50%;
position: absolute;
color: #fff;
background-color: #000000;
opacity: 0.6;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: none;
}
#uploadIcon{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: none;
}
</style>