图片裁剪
This commit is contained in:
parent
4470ca0774
commit
efefd1b348
@ -58,6 +58,7 @@
|
||||
"vue-router": "^4.3.2",
|
||||
"vue-types": "^4.2.1",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"vue-cropper": "0.5.8",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -38,7 +38,7 @@
|
||||
<div class="upload-empty">
|
||||
<slot name="empty">
|
||||
<el-icon v-if="!loading"><Plus /></el-icon>
|
||||
<span v-else>上传中{{progress}}%</span>
|
||||
<span v-else>上传中{{progress}}%</span>
|
||||
<!-- <span>请上传图片</span> -->
|
||||
</slot>
|
||||
</div>
|
||||
@ -51,9 +51,17 @@
|
||||
:teleported="true"
|
||||
v-if="imgViewVisible"
|
||||
@close="imgViewVisible = false"
|
||||
:url-list="[imageUrl.includes('http') ? imageUrl : baseURL + imageUrl]"
|
||||
:url-list="[imageUrl]"
|
||||
/>
|
||||
</div>
|
||||
<Cropper
|
||||
v-model:visible="cropperVisible"
|
||||
v-if="cropperVisible"
|
||||
:cropperSize="cropperSize"
|
||||
:oImg="oImg"
|
||||
@success="cropperSuccess"
|
||||
:fileName="fileName"
|
||||
></Cropper>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="UploadImg">
|
||||
@ -62,6 +70,7 @@ import { ElNotification, formContextKey, formItemContextKey } from 'element-plus
|
||||
import type { UploadProps, UploadRequestOptions } from 'element-plus';
|
||||
import { generateUUID } from '@/utils/auth';
|
||||
import {upload} from '@/api/common'
|
||||
import Cropper from '@/components/Upload/cropper.vue'
|
||||
|
||||
interface UploadFileProps {
|
||||
imageUrl?: string; // 图片地址 ==> 必传
|
||||
@ -75,9 +84,11 @@ interface UploadFileProps {
|
||||
borderRadius?: string; // 组件边框圆角 ==> 非必传(默认为 8px)
|
||||
iconSize?: number;
|
||||
name?: string;
|
||||
cropper:Boolean,
|
||||
cropperSize?:Object
|
||||
}
|
||||
|
||||
|
||||
const cropperVisible=ref(false)
|
||||
// 接受父组件参数
|
||||
const props = withDefaults(defineProps<UploadFileProps>(), {
|
||||
imageUrl: '',
|
||||
@ -89,7 +100,9 @@ const props = withDefaults(defineProps<UploadFileProps>(), {
|
||||
height: '150px',
|
||||
width: '150px',
|
||||
borderRadius: '8px',
|
||||
name:''
|
||||
name:'',
|
||||
cropper:false,
|
||||
cropperSize:null
|
||||
});
|
||||
|
||||
// 生成组件唯一id
|
||||
@ -122,10 +135,15 @@ interface UploadEmits {
|
||||
(e: 'update:imageUrl', value: string): void,
|
||||
(e: 'changeFileName', value: string): void;
|
||||
}
|
||||
|
||||
const fileName=ref('')
|
||||
const oImg=ref('')
|
||||
const emit = defineEmits<UploadEmits>();
|
||||
const loading=ref(false)
|
||||
const progress=ref(0)
|
||||
|
||||
const cropperSuccess=(fileChile:any)=>{
|
||||
actionFile(fileChile)
|
||||
}
|
||||
const actionFile=async (fileChild:any)=>{
|
||||
loading.value=true
|
||||
try {
|
||||
@ -133,7 +151,6 @@ const actionFile=async (fileChild:any)=>{
|
||||
formData.append('file',fileChild)
|
||||
formData.append('name',props.name)
|
||||
const res =await upload(formData)
|
||||
console.log(res)
|
||||
emit('update:imageUrl',res.fileUrl);
|
||||
emit('changeFileName',res.originalName)
|
||||
// 调用 el-form 内部的校验方法(可自动校验)
|
||||
@ -151,6 +168,22 @@ const handleHttpUploadOptions=ref({})
|
||||
|
||||
const handleHttpUpload = async (options: UploadRequestOptions) => {
|
||||
handleHttpUploadOptions.value=options
|
||||
if(props.cropper){
|
||||
fileName.value= options.file.name
|
||||
let reader = new FileReader();
|
||||
reader.readAsDataURL(options.file);
|
||||
let img = new Image();
|
||||
reader.onload = function (e) {
|
||||
img.src = this.result;
|
||||
img.onload = function () {
|
||||
let base = e.target.result;
|
||||
oImg.value= base;
|
||||
event.target.value = "";
|
||||
cropperVisible.value = true;
|
||||
};
|
||||
};
|
||||
return
|
||||
}
|
||||
actionFile(options.file)
|
||||
};
|
||||
|
||||
|
197
src/components/Upload/cropper.vue
Normal file
197
src/components/Upload/cropper.vue
Normal file
@ -0,0 +1,197 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="props.visible"
|
||||
title="图片裁剪"
|
||||
:append-to-body="true"
|
||||
class="cropper_model_dlg"
|
||||
width="500"
|
||||
:close-on-click-modal="false"
|
||||
:before-close="dialogClose"
|
||||
>
|
||||
<div class="cropper_content">
|
||||
<div class="cropper" style="text-align: center;">
|
||||
<vueCropper
|
||||
ref="cropperRef"
|
||||
:img="options.img"
|
||||
:outputSize="options.outputSize"
|
||||
:outputType="options.outputType"
|
||||
:info="options.info"
|
||||
:canScale="options.canScale"
|
||||
:autoCrop="options.autoCrop"
|
||||
:autoCropWidth="options.autoCropWidth"
|
||||
:autoCropHeight="options.autoCropHeight"
|
||||
:fixed="options.fixed"
|
||||
:maximgSize="options.maximgSize"
|
||||
:fixedBox="options.fixedBox"
|
||||
:enlarge="options.enlarge"
|
||||
:fixedNumber="options.fixedNumber"
|
||||
></vueCropper>
|
||||
</div>
|
||||
<div class="cropper_btns">
|
||||
<el-button @click="rotateLeft" icon="RefreshLeft" size="mini" title="左旋转"></el-button>
|
||||
<el-button @click="rotateRight" icon="RefreshRight" size="mini" title="右旋转"></el-button>
|
||||
<el-button @click="changeScale(1)" size="mini" title="放大">+</el-button>
|
||||
<el-button @click="changeScale(-1)" size="mini" title="缩小">-</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="dialogClose">取消</el-button>
|
||||
<el-button type="primary" @click="submit">确认</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import VueCropper from "vue-cropper/src/vue-cropper.vue";
|
||||
import { ref, reactive} from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
default: false
|
||||
},
|
||||
cropperSize: {
|
||||
type: Object,
|
||||
default() {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
oImg: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
fileName: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
folder: {
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
});
|
||||
|
||||
const dialogClose = () => {
|
||||
emit("update:visible", false);
|
||||
};
|
||||
const options = reactive({
|
||||
img: props.oImg, // 裁剪图片的地址
|
||||
outputSize: 1, // 裁剪生成图片的质量
|
||||
outputType: "png", // 裁剪生成图片的格式
|
||||
info: true, // 裁剪框的大小信息
|
||||
canScale: true, // 图片是否允许滚动缩放
|
||||
autoCrop: true, // 是否默认生成截图框
|
||||
autoCropWidth: props.cropperSize ? props.cropperSize.width : 200, // 默认生成截图框宽度
|
||||
autoCropHeight: props.cropperSize ? props.cropperSize.height : 200, // 默认生成截图框高度
|
||||
fixed: false, // 是否开启截图框宽高固定比例
|
||||
fixedNumber: [1, 1], // 截图框的宽高比例
|
||||
full: true, // 是否输出原图比例的截图
|
||||
fixedBox: true, // 固定截图框大小 不允许改变
|
||||
canMove: true, // 上传图片是否可以移动
|
||||
canMoveBox: true, // 截图框能否拖动
|
||||
original: true, // 上传图片按照原始比例渲染
|
||||
centerBox: false, // 截图框是否被限制在图片里面
|
||||
high: false, // 是否按照设备的dpr输出等比例图片
|
||||
infoTrue: true, // true为展示真实输出图片宽高false展示看到的截图框宽高
|
||||
maximgSize: 100, // 限制图片最大宽度和高度
|
||||
enlarge: 1, // 图片根据截图框输出比例倍数
|
||||
mode: "contain" // 图片默认渲染方式(contain, cover, 100px, 100% auto)
|
||||
});
|
||||
const cropperRef = ref();
|
||||
|
||||
const addImgLoading = ref(false);
|
||||
|
||||
// 左旋转
|
||||
const rotateLeft = () => {
|
||||
cropperRef.value.rotateLeft();
|
||||
};
|
||||
// 右旋转
|
||||
const rotateRight = () => {
|
||||
cropperRef.value.rotateRight();
|
||||
};
|
||||
// 放大缩小
|
||||
const changeScale = (num: any) => {
|
||||
num = num || 1;
|
||||
cropperRef.value.changeScale(num);
|
||||
};
|
||||
|
||||
const emit = defineEmits(["success", "update:visible"]);
|
||||
|
||||
// base64转图片文件
|
||||
const dataURLtoFile=(dataurl:any, filename:any)=> {
|
||||
let arr = dataurl.split(",");
|
||||
let mime = arr[0].match(/:(.*?);/)[1];
|
||||
let bstr = atob(arr[1]);
|
||||
let len = bstr.length;
|
||||
let u8arr = new Uint8Array(len);
|
||||
while (len--) {
|
||||
u8arr[len] = bstr.charCodeAt(len);
|
||||
}
|
||||
return new File([u8arr], filename, {type: mime});
|
||||
}
|
||||
|
||||
const submit=()=>{
|
||||
cropperRef.value.getCropData(fileData => {
|
||||
let file = dataURLtoFile(fileData, props.fileName);
|
||||
emit("update:visible", false);
|
||||
emit('success',file)
|
||||
})
|
||||
}
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.cropper_model_dlg {
|
||||
.el-dialog__body {
|
||||
padding: 0px 20px 60px !important
|
||||
}
|
||||
|
||||
.cropper_content {
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.add-img {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
width: 80px;
|
||||
height: 28px;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
.cropper {
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
background: yellow;
|
||||
}
|
||||
|
||||
.cropper500 {
|
||||
width: 600px;
|
||||
height: 600px;
|
||||
background: yellow;
|
||||
border: 1px solid #e3e3e3;
|
||||
}
|
||||
|
||||
.cropper_right {
|
||||
width: 520px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cropper_preview {
|
||||
margin: 0 auto;
|
||||
display: inline-block;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.cropper_btns {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -101,6 +101,7 @@
|
||||
:fileType=" ['image/jpeg', 'image/png', 'image/jpg', 'image/gif']"
|
||||
name="user"
|
||||
:fileSize="200"
|
||||
:cropper="true"
|
||||
v-model:image-url="formData.avatar">
|
||||
<template v-slot:tip>支持扩展名: jpg png jpeg;文件大小不超过200M</template>
|
||||
</UploadImg>
|
||||
@ -149,9 +150,6 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
const emit = defineEmits(["success", "update:visible"]);
|
||||
const uploadHeaders = reactive({
|
||||
authorization: useUserStore().getToken
|
||||
});
|
||||
const formData = reactive({
|
||||
id: 0,
|
||||
avatarName:'',
|
||||
|
Loading…
Reference in New Issue
Block a user