230 lines
5.8 KiB
Vue
230 lines
5.8 KiB
Vue
<template>
|
|
<div :class="$style['img-preview']">
|
|
<vue-cropper :key="id" ref="cropper" :src="imgUrl" alt="" :autoCrop="false" dragMode="move" :background="false"
|
|
:viewMode="2" :toggleDragModeOnDblclick="false" @ready="onReady" @crop="onCrop" @zoom="onZoom" />
|
|
<div :class="$style['mask']">
|
|
<calc-rect-dock :top="rectPosition.top" :right="rectPosition.right" :bottom="rectPosition.bottom"
|
|
:left="rectPosition.left"></calc-rect-dock>
|
|
</div>
|
|
<div :class="$style['prev']" @click="$emit('imgIdxChange', -1)">
|
|
<el-icon>
|
|
<d-arrow-left />
|
|
</el-icon>
|
|
</div>
|
|
<div :class="$style['next']" @click="$emit('imgIdxChange', 1)">
|
|
<el-icon>
|
|
<d-arrow-right />
|
|
</el-icon>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<script setup lang="ts">
|
|
import VueCropper from 'vue-cropperjs';
|
|
import 'cropperjs/dist/cropper.css';
|
|
import { reactive, ref, onBeforeMount, watch } from "vue";
|
|
import { computed } from '@vue/reactivity';
|
|
import { merge, cloneDeep } from "lodash-es";
|
|
import {
|
|
DArrowLeft,
|
|
DArrowRight,
|
|
} from "@element-plus/icons-vue";
|
|
|
|
const props = defineProps<{
|
|
id: "",
|
|
imgUrl: "",
|
|
position: "",
|
|
}>();
|
|
|
|
defineEmits<{
|
|
(n: 'imgIdxChange', value: TImgPreviewIdx): void
|
|
}>();
|
|
|
|
const cropper = ref();
|
|
const cropOption = reactive({
|
|
ratio: 1,
|
|
delta: { deltaX: 0, deltaY: 0 }
|
|
});
|
|
const rectPosition = reactive({
|
|
top: 0,
|
|
right: 0,
|
|
bottom: 0,
|
|
left: 0
|
|
});
|
|
const defRatio = ref(0);
|
|
|
|
onBeforeMount(() => {
|
|
initRectPosition();
|
|
})
|
|
|
|
watch([cropOption, () => props.id], () => {
|
|
initRectPosition();
|
|
})
|
|
|
|
const initRectPosition = () => {
|
|
const rectOption: any = formatRectPosition(cloneDeep(props.position)) || {};
|
|
merge(rectPosition, getStyle(rectOption));
|
|
}
|
|
|
|
const initCropOption = (e) => {
|
|
const canvasData: any = e.target?.cropper?.canvasData;
|
|
if (canvasData) {
|
|
if (e?.detail?.ratio && e.detail.ratio >= defRatio.value) {
|
|
merge(cropOption, {
|
|
ratio: e.detail.ratio,
|
|
delta: {
|
|
deltaX: canvasData.left,
|
|
deltaY: canvasData.top
|
|
}
|
|
});
|
|
}
|
|
else {
|
|
merge(cropOption, {
|
|
delta: {
|
|
deltaX: canvasData.left,
|
|
deltaY: canvasData.top
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 图片渲染完成事件
|
|
* @param e
|
|
*/
|
|
const onReady = (e: any) => {
|
|
const canvasData: any = e.target?.cropper?.canvasData;
|
|
if (canvasData) {
|
|
defRatio.value = canvasData.width / e.target.width;
|
|
merge(cropOption, {
|
|
ratio: defRatio.value,
|
|
delta: { deltaX: 0, deltaY: 0 }
|
|
})
|
|
}
|
|
};
|
|
|
|
const onCrop = (e) => {
|
|
initCropOption(e);
|
|
}
|
|
|
|
const onZoom = (e) => {
|
|
initCropOption(e);
|
|
}
|
|
|
|
/**
|
|
* 格式化矩形框位置信息
|
|
* @param position json字符串
|
|
*/
|
|
const formatRectPosition = (position: any) => {
|
|
const fpm: any = {};
|
|
let rectInfo: any = {};
|
|
if (typeof (position) === "string") {
|
|
try {
|
|
rectInfo = JSON.parse(position);
|
|
const { face_rect, object_rect } = rectInfo;
|
|
if (face_rect) {
|
|
fpm.top = face_rect.top;
|
|
fpm.right = face_rect.right;
|
|
fpm.bottom = face_rect.bottom;
|
|
fpm.left = face_rect.left;
|
|
}
|
|
else if (object_rect) {
|
|
fpm.top = object_rect.top;
|
|
fpm.right = object_rect.right;
|
|
fpm.bottom = object_rect.bottom;
|
|
fpm.left = object_rect.left;
|
|
fpm.type = 2;//ERectType.Body;
|
|
}
|
|
return fpm;
|
|
} catch (error) {
|
|
console.warn("小图位置信息有误", position);
|
|
return null;
|
|
}
|
|
}
|
|
else {
|
|
if (position.face_rect) {
|
|
return position.face_rect;
|
|
}
|
|
else if (position.object_rect) {
|
|
return position.object_rect;
|
|
}
|
|
}
|
|
console.warn("小图位置信息有误", position);
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* 定位矩形框
|
|
*/
|
|
const getStyle = (position: any) => {
|
|
const faceRect = position;
|
|
const { ratio, delta } = cropOption;
|
|
if (ratio != -1) {
|
|
for (const item in faceRect) {
|
|
if (["top", "right", "bottom", "left"].includes(item))
|
|
faceRect[item] = parseFloat(faceRect[item]) * ratio
|
|
}
|
|
}
|
|
return {
|
|
top: faceRect.top + delta.deltaY,
|
|
left: faceRect.left + delta.deltaX,
|
|
bottom: faceRect.bottom + delta.deltaY,
|
|
right: faceRect.right + delta.deltaX,
|
|
};
|
|
}
|
|
|
|
</script>
|
|
<style module lang="scss">
|
|
:global {
|
|
:local(.img-preview) {
|
|
position: relative;
|
|
display: flex;
|
|
flex-direction: column;
|
|
|
|
:local(.mask) {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
overflow: hidden;
|
|
pointer-events: none;
|
|
}
|
|
|
|
&:hover {
|
|
|
|
:local(.prev),
|
|
:local(.next) {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
}
|
|
|
|
:local(.prev),
|
|
:local(.next) {
|
|
position: absolute;
|
|
height: 100px;
|
|
width: 100px;
|
|
top: 50%;
|
|
transform: translate(0, -50%);
|
|
background-color: rgb(10 10 10 / 20%);
|
|
line-height: 100px;
|
|
text-align: center;
|
|
cursor: pointer;
|
|
display: none;
|
|
pointer-events: painted;
|
|
font-size: 30px;
|
|
}
|
|
|
|
:local(.prev) {
|
|
left: 0;
|
|
}
|
|
|
|
:local(.next) {
|
|
right: 0;
|
|
transform: translate(0, -50%);
|
|
}
|
|
}
|
|
}
|
|
</style> |