feat: first commit
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user