Files
funjia-admin-viewmodel-temp…/src/components/ImgPreview.vue
T
2025-03-27 19:33:39 +08:00

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>