You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1587 lines
48 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div class="imageInfo">
<!-- 标题 -->
<div class="titleDiv">
<div class="titleName">
<span class="infovalue_name" v-if="editNameFlag">
{{ props.nowShowImageData.name }}
<EditOutlined
style="margin-left: 5px; font-size: 20px; color: #07aaed"
@click="editNameChange"
/>
</span>
<span class="infovalue_name" v-if="!editNameFlag">
<a-input
ref="focusInputRef"
v-model:value="editName"
style="width: 60%"
size="small"
@keypress.enter="pressEnterNameFunction"
/>
<CheckOutlined style="margin-left: 10px; color: green" @click="pressEnterNameFunction" />
<CloseOutlined style="margin-left: 10px; color: red" @click="editNameBlur" />
</span>
</div>
<div class="titleTime">
<ClockCircleOutlined />
&nbsp;&nbsp;
<span>
{{ dayjs(props.nowShowImageData.createTime).format('YYYY-MM-DD HH:mm:ss (UTCZ)') }}
</span>
&nbsp;&nbsp;&nbsp;
<span>
{{
props.nowShowImageData.size
? props.nowShowImageData.size > 1024 * 1024
? parseFloat((props.nowShowImageData.size / (1024 * 1024)).toFixed(2)) + 'MB'
: props.nowShowImageData.size > 1024
? parseFloat((props.nowShowImageData.size / 1024).toFixed(2)) + 'KB'
: parseFloat(props.nowShowImageData.size) + 'B'
: imageSize
}}
</span>
&nbsp;&nbsp;&nbsp;
<span>
{{ props.nowShowImageData.width + ' x ' + props.nowShowImageData.height }}
</span>
</div>
<div class="titleCoordinate">
<span>
{{ props.nowShowImageData.lat + '° E' }}
</span>
&nbsp;&nbsp;&nbsp;&nbsp;
<span>
{{ props.nowShowImageData.lng + '° N' }}
</span>
&nbsp;&nbsp;&nbsp;&nbsp;
<span>
{{ '拍摄高度' + props.nowShowImageData.relativeAltitude + 'm' }}
</span>
&nbsp;&nbsp;&nbsp;&nbsp;
</div>
</div>
<!-- 标签 -->
<div class="tagsDiv">
<a-tag color="success" v-for="tag in props.nowShowImageData.fileTags" :key="tag">
{{ tag }}
</a-tag>
<PlusSquareOutlined style="font-size: 20px; color: #07aaed" @click="addFileTagsChange" />
</div>
<!-- 关闭按钮 -->
<div class="closeButton">
<CloseOutlined @click="closePathImageInfo" style="font-size: 20px; color: white" />
</div>
<!-- 上一张下一张图片 -->
<div class="leftButton" @click="clickLeftOrRightButton('left')">
<LeftOutlined style="color: #ffffff; font-size: 20px" />
</div>
<div class="rightButton" @click="clickLeftOrRightButton('right')">
<RightOutlined style="color: #ffffff; font-size: 20px" />
</div>
<!-- 涂鸦颜色选择-提示 -->
<div @click="setGraffiti" :class="graffitiFlag ? 'graffitiButton_choose' : 'graffitiButton'">
<a-popover placement="left">
<template #content>
<div style="display: flex; gap: 5px">
<div
class="popoverClass"
v-for="color in ['#2D8CF0', '#19BE6B', '#FFBB00', '#E23C39', '#B620E0']"
:key="color"
:style="{ background: color }"
@click="graffitiColor = color"
>
<CheckOutlined v-if="graffitiColor == color" style="color: white" />
</div>
</div>
</template>
<EditOutlined
:style="{
color: graffitiColor,
fontSize: '20px',
}"
/>
</a-popover>
</div>
<!-- 隐藏or显示涂鸦和标签 -->
<div class="showTextboxClass">
<div class="button">
<a-tooltip placement="left">
<template #title>
<span>{{ hideOrShowGraffitiFlag ? '点击隐藏涂鸦信息' : '点击显示涂鸦信息' }}</span>
</template>
<EyeOutlined
@click="hideOrShowGraffiti(false)"
v-if="hideOrShowGraffitiFlag"
style="color: #2d8cf0"
/>
<EyeInvisibleOutlined
@click="hideOrShowGraffiti(true)"
v-if="!hideOrShowGraffitiFlag"
style="color: #595959"
/>
<div style="position: absolute; bottom: -10px; right: -5px; pointer-events: none">
<div
v-html="
`${
hideOrShowGraffitiFlag
? graffiti_svg.replaceAll('currentColor', '#2d8cf0')
: graffiti_svg.replaceAll('currentColor', '#595959')
}`
"
/>
</div>
</a-tooltip>
</div>
</div>
<!-- 退出涂鸦 -->
<div class="escTip" v-if="graffitiFlag">
<div class="whiteEsc">Esc</div>
<div class="blackTip">退出涂鸦</div>
</div>
<!-- 图片部分 -->
<div id="imageDiv" class="imageDiv" style="overflow: hidden">
<div
ref="mouseCanvasRef"
class="dragModal"
@mousedown="onMouseDown"
@wheel="onWheel"
:style="{
position: 'relative',
transform: `scale(${scale}) rotate(${rotationAngle}deg)`,
transition: 'transform 0.2s',
width: `${imageWidth}px`,
height: `${imageHeight}px`,
background: `url(${VITE_GLOB_MEDIALIBRARY_IMAGE_URL + props.nowShowImageData.objectKey}) no-repeat center center`,
backgroundSize: 'contain',
backgroundPosition: 'center',
}"
>
<!-- 创建的矩形 -->
<div
v-if="hideOrShowGraffitiFlag"
v-for="(rect, index) in graffitis"
:key="index"
:style="{
position: 'absolute',
left: rect.x + 'px',
top: rect.y + 'px',
width: rect.width + 'px',
height: rect.height + 'px',
zIndex: rect.status == 'edit' ? 1000 : 201,
}"
>
<!-- 文字标签 -->
<div
v-if="nowGraffiti == index || rect.text"
:style="{
position: 'absolute',
left: '0px',
top: rect.height + 'px',
width: '220px',
height: 30 + 'px',
background: nowGraffiti == index ? '#ffffff' : '#ffffff00',
}"
>
<div style="display: block" v-if="nowGraffiti == index">
<a-input
v-model:value="rect.text"
style="width: 110px; height: 30px; margin-right: 10px"
@keypress.enter="
nowGraffiti = -1;
rect.status = 'success';
addGraffiti();
"
/>
<CheckOutlined
style="margin-right: 10px; color: green"
@click="
nowGraffiti = -1;
rect.status = 'success';
addGraffiti();
"
/>
<CloseOutlined
style="margin-right: 10px; color: red"
@click="
rect.text
? ((rect.x = graffitisClone[index].x),
(rect.y = graffitisClone[index].y),
(rect.width = graffitisClone[index].width),
(rect.height = graffitisClone[index].height),
(rect.color = graffitisClone[index].color),
(rect.text = graffitisClone[index].text),
(rect.status = 'success'))
: graffitis.splice(index, 1);
nowGraffiti = -1;
"
/>
<DeleteOutlined
style="margin-right: 10px"
@click="
deleteGraffiti(index, rect.text);
nowGraffiti = -1;
"
/>
<a-popover placement="top">
<template #content>
<div style="display: flex; gap: 5px">
<div
class="popoverClass"
v-for="color in ['#2D8CF0', '#19BE6B', '#FFBB00', '#E23C39', '#B620E0']"
:key="color"
:style="{ background: color }"
@click="rect.color = color"
>
<CheckOutlined v-if="rect.color == color" style="color: white" />
</div>
</div>
</template>
<FontColorsOutlined />
</a-popover>
</div>
<div
v-if="nowGraffiti != index"
style="
font-size: 20px;
font-weight: bold;
-webkit-text-stroke: 1px white;
width: fit-content;
"
:style="{
textDecoration: rect.status == 'mouse' ? 'underline' : '',
}"
@mouseenter="graffitiFlag && rect.status != 'edit' ? (rect.status = 'mouse') : ''"
@mouseleave="graffitiFlag && rect.status == 'mouse' ? (rect.status = 'success') : ''"
@click="
graffitiFlag
? ((rect.status = 'edit'),
(graffitisClone = cloneDeep(graffitis)),
(nowGraffiti = index))
: ''
"
>
{{ rect.text }}
</div>
</div>
<!-- 上 -->
<div
:style="{
position: 'absolute',
left: rect.status == 'edit' ? '6px' : '0px',
top: '0px',
width: rect.status == 'edit' ? `${rect.width - 14}px` : `${rect.width}px`,
height: '4px',
background: `${rect.color}`,
outline: rect.status != 'success' ? `2px solid #ffffff` : '',
}"
@mouseenter="mouseenter(rect, 'top')"
@mouseleave="mouseleave(rect)"
@click="graffitiFlag ? ((rect.status = 'edit'), (nowGraffiti = index)) : ''"
@mousedown="funMouseDownEdit($event, index, 'top')"
/>
<!-- 下 -->
<div
:style="{
position: 'absolute',
left: rect.status == 'edit' ? '6px' : '0px',
bottom: `0px`,
width: rect.status == 'edit' ? `${rect.width - 14}px` : `${rect.width}px`,
height: '4px',
background: `${rect.color}`,
outline: rect.status != 'success' ? `2px solid #ffffff` : '',
}"
@mouseenter="mouseenter(rect, 'bottom')"
@mouseleave="mouseleave(rect)"
@click="graffitiFlag ? ((rect.status = 'edit'), (nowGraffiti = index)) : ''"
@mousedown="funMouseDownEdit($event, index, 'bottom')"
/>
<!-- 右 -->
<div
:style="{
position: 'absolute',
right: '0px',
top: rect.status == 'edit' ? '6px' : '0px',
width: '4px',
height: rect.status == 'edit' ? `${rect.height - 14}px` : `${rect.height}px`,
background: `${rect.color}`,
outline: rect.status != 'success' ? `2px solid #ffffff` : '',
}"
@mouseenter="mouseenter(rect, 'right')"
@mouseleave="mouseleave(rect)"
@click="graffitiFlag ? ((rect.status = 'edit'), (nowGraffiti = index)) : ''"
@mousedown="funMouseDownEdit($event, index, 'right')"
/>
<!-- 左 -->
<div
:style="{
position: 'absolute',
left: '0px',
top: rect.status == 'edit' ? '6px' : '0px',
width: '4px',
height: rect.status == 'edit' ? `${rect.height - 14}px` : `${rect.height}px`,
background: `${rect.color}`,
outline: rect.status != 'success' ? `2px solid #ffffff` : '',
}"
@mouseenter="mouseenter(rect, 'left')"
@mouseleave="mouseleave(rect)"
@click="graffitiFlag ? ((rect.status = 'edit'), (nowGraffiti = index)) : ''"
@mousedown="funMouseDownEdit($event, index, 'left')"
/>
<!-- 左上 -->
<div
v-if="rect.status == 'edit'"
:style="{
position: 'absolute',
left: '-4px',
top: '-4px',
width: '12px',
height: '12px',
background: `#ffffff`,
}"
@mouseenter="mouseenter(rect, 'leftTop')"
@mouseleave="mouseleave(rect)"
@mousedown="funMouseDownEdit($event, index, 'leftTop')"
/>
<!-- 右下 -->
<div
v-if="rect.status == 'edit'"
:style="{
position: 'absolute',
right: '-4px',
bottom: '-4px',
width: '12px',
height: '12px',
background: `#ffffff`,
}"
@mouseenter="mouseenter(rect, 'rightBottom')"
@mouseleave="mouseleave(rect)"
@mousedown="funMouseDownEdit($event, index, 'rightBottom')"
/>
<!-- 右上 -->
<div
v-if="rect.status == 'edit'"
:style="{
position: 'absolute',
right: '-4px',
top: '-4px',
width: '12px',
height: '12px',
background: `#ffffff`,
}"
@mouseenter="mouseenter(rect, 'rightTop')"
@mouseleave="mouseleave(rect)"
@mousedown="funMouseDownEdit($event, index, 'rightTop')"
/>
<!-- 左下 -->
<div
v-if="rect.status == 'edit'"
:style="{
position: 'absolute',
left: '-4px',
bottom: '-4px',
width: '12px',
height: '12px',
background: `#ffffff`,
}"
@mouseenter="mouseenter(rect, 'leftBottom')"
@mouseleave="mouseleave(rect)"
@mousedown="funMouseDownEdit($event, index, 'leftBottom')"
/>
</div>
</div>
<!-- 云台偏航角查看图片 -->
<div class="rimbalGawDegreeButton" @click="rotateGimbalYawDegree">
<a-tooltip placement="left">
<template #title>
<span> {{ rimbalGawDegreeFlag ? '正常查看图片' : '云台偏航角查看图片' }}</span>
</template>
<InstagramOutlined v-if="!rimbalGawDegreeFlag" style="color: #3c3c3c; font-size: 25px" />
<FileImageOutlined v-if="rimbalGawDegreeFlag" style="color: #3c3c3c; font-size: 25px" />
</a-tooltip>
</div>
<!-- 全屏 -->
<div class="expandButton" @click="clickExpandButton">
<ExpandOutlined v-if="!fullscreenFlag" style="color: #3c3c3c; font-size: 25px" />
<CompressOutlined v-if="fullscreenFlag" style="color: #3c3c3c; font-size: 25px" />
</div>
</div>
<!-- 底部按钮和图片列表 -->
<div class="bottomDiv">
<div class="buttonList">
<!-- 放大 -->
<div class="button">
<a-tooltip placement="top">
<template #title>
<span>放大</span>
</template>
<ZoomInOutlined @click="zoomIn" />
</a-tooltip>
</div>
<!-- 缩小 -->
<div class="button">
<a-tooltip placement="top">
<template #title>
<span>缩小</span>
</template>
<ZoomOutOutlined @click="zoomOut" />
</a-tooltip>
</div>
<!-- 100%比例 -->
<div class="button">
<a-tooltip placement="top">
<template #title>
<span>100%比例</span>
</template>
<OneToOneOutlined @click="setProportion" />
</a-tooltip>
</div>
<!-- 顺时针旋转 -->
<div class="button">
<a-tooltip placement="top">
<template #title>
<span>顺时针旋转</span>
</template>
<RotateRightOutlined @click="rotateClockwise" />
</a-tooltip>
</div>
<!-- 逆时针旋转 -->
<div class="button">
<a-tooltip placement="top">
<template #title>
<span>逆时针旋转</span>
</template>
<RotateLeftOutlined @click="rotateCounterClockwise" />
</a-tooltip>
</div>
<!-- 刷新 -->
<div class="button">
<a-tooltip placement="top">
<template #title>
<span>复位刷新</span>
</template>
<RedoOutlined @click="refresh" />
</a-tooltip>
</div>
<!-- 回中 -->
<div class="button">
<a-tooltip placement="top">
<template #title>
<span>回中</span>
</template>
<AimOutlined @click="handlerLocation" />
</a-tooltip>
</div>
</div>
<div class="imageChooseList">
<div
v-for="li in props.allImageDataList.filter((item) => item.display == 1)"
:key="li.id"
@click="setNowShowImageData(li)"
>
<a-tooltip placement="top">
<template #title>
<span>{{ li.name }}</span>
</template>
<div :class="li.id == props.nowShowImageData.id ? 'bottom_div_choose' : 'bottom_div'">
<img
:src="VITE_GLOB_MEDIALIBRARY_IMAGE_URL + li.minipic"
loading="lazy"
width="75"
height="50"
/>
</div>
</a-tooltip>
</div>
</div>
</div>
<!-- 标签弹窗 -->
<a-modal
title="标签设置"
:open="addFileTagsFlag"
:mask="false"
:maskClosable="false"
:closable="false"
:footer="null"
>
<div style="display: block">
<div
style="display: flex; flex-wrap: wrap; gap: 5px; width: 96%; margin: 10px"
v-if="props.nowShowImageData.fileTags && props.nowShowImageData.fileTags.length > 0"
>
已添加的标签:
<div
v-for="la in props.nowShowImageData.fileTags"
:key="la"
style="border: 1px solid #595959; border-radius: 3px"
>
<span style="margin-left: 5px">{{ la }}</span>
<CloseOutlined
style="margin-left: 5px; margin-right: 5px"
@click="deleteFileTags(la)"
/>
</div>
</div>
<div style="display: inline-flex; gap: 5px; width: 96%; margin: 10px">
<a-input v-model:value="newFileTagsName" size="small" placeholder="请输入标签" />
<a-button type="primary" @click="pressEnterFileTagsFunction"></a-button>
</div>
<div
style="
display: inline-flex;
gap: 5px;
width: 96%;
margin: 5px 10px 20px 10px;
justify-content: flex-end;
"
>
<a-button
@click="
addFileTagsFlag = false;
newFileTagsName = '';
"
>
关闭
</a-button>
</div>
</div>
</a-modal>
</div>
</template>
<script lang="ts" setup>
import { ref, watch, onMounted, onBeforeUnmount } from 'vue';
import {
CloseOutlined,
RightOutlined,
LeftOutlined,
ZoomOutOutlined,
ZoomInOutlined,
RotateLeftOutlined,
RotateRightOutlined,
DeleteOutlined,
CheckOutlined,
EyeOutlined,
EyeInvisibleOutlined,
EditOutlined,
FontColorsOutlined,
RedoOutlined,
ExpandOutlined,
CompressOutlined,
OneToOneOutlined,
AimOutlined,
ClockCircleOutlined,
PlusSquareOutlined,
InstagramOutlined,
FileImageOutlined,
} from '@ant-design/icons-vue';
import dayjs from 'dayjs';
import { UpdatePicStatus, UpdatePicName } from '@/api/demo/mediaLibrary';
import { graffiti_svg } from './svg';
import { getAppEnvConfig } from '@/utils/env';
import { cloneDeep } from 'lodash-es';
import { useMessage } from '@/hooks/web/useMessage';
const { createMessage, createConfirm } = useMessage();
const { VITE_GLOB_MEDIALIBRARY_IMAGE_URL } = getAppEnvConfig();
const props = defineProps(['nowShowImageData', 'allImageDataList']);
const emits = defineEmits([
'setNowShowImageData',
'closePathImageInfo',
'handleSuccessPath',
'handlerLocation',
'funUpdateDisplayOrShowOnMapData',
]);
// 修改名称--------------------------------
const focusInputRef = ref();
const editNameFlag = ref(true);
const editName = ref();
function editNameChange() {
if (props.nowShowImageData.name.split('.').length <= 1) {
editName.value = props.nowShowImageData.name;
} else {
editName.value = props.nowShowImageData.name.split('.').slice(0, -1).join('.');
}
setTimeout(() => {
if (focusInputRef.value && focusInputRef.value.focus) {
focusInputRef.value.focus(); // 调用 focus 方法
}
}, 300);
editNameFlag.value = false;
}
// 修改名称方法
async function pressEnterNameFunction() {
let newName: any = null;
if (props.nowShowImageData.name.split('.').length <= 1) {
newName = editName.value;
} else {
newName = editName.value + '.' + props.nowShowImageData.name.split('.').pop();
}
let query = {
id: props.nowShowImageData.id,
name: newName,
};
UpdatePicName(query).then((res) => {
props.nowShowImageData.name = newName;
editNameFlag.value = true;
createMessage.success(res);
emits('funUpdateDisplayOrShowOnMapData', props.nowShowImageData);
emits('handleSuccessPath', props.nowShowImageData);
});
}
function editNameBlur() {
editNameFlag.value = true;
editName.value = props.nowShowImageData.name.split('.').slice(0, -1).join('.');
}
// 关闭图片抽屉--------------------------------------------------------------
function closePathImageInfo() {
emits('setNowShowImageData', {});
}
// 上一张、下一张图片--------------------------------------------------------------
function clickLeftOrRightButton(direction) {
const list = props.allImageDataList.filter((item) => item.display == 1);
for (let index = 0; index < list.length; index++) {
if (list[index].id == props.nowShowImageData.id) {
if (direction == 'left') {
if (index == 0) {
setNowShowImageData(list[list.length - 1]);
} else {
setNowShowImageData(list[index - 1]);
}
}
if (direction == 'right') {
if (index == list.length - 1) {
setNowShowImageData(list[0]);
} else {
setNowShowImageData(list[index + 1]);
}
}
}
}
}
// 全屏
const fullscreenFlag = ref(false);
function clickExpandButton() {
const mapContainer = document.getElementById('imageDiv');
if (mapContainer) {
if (!document.fullscreenElement) {
mapContainer.requestFullscreen();
fullscreenFlag.value = true;
} else {
document.exitFullscreen();
fullscreenFlag.value = false;
}
}
}
// ----------------------------------------------------------------------
// 缩放比例
const scale = ref(0.9);
// 放大函数
function zoomIn() {
if (scale.value < 4) {
// 设置最大缩放倍数为3倍
scale.value += 0.1;
}
}
// 缩小函数
function zoomOut() {
if (scale.value > 0.5) {
// 设置最小缩放倍数为0.5倍
scale.value -= 0.1;
}
}
// 旋转
const rotationAngle = ref(0);
// 顺时针旋转函数
function rotateClockwise() {
rotationAngle.value += 90; // 每次旋转90度
}
// 逆时针旋转
function rotateCounterClockwise() {
rotationAngle.value -= 90; // 每次旋转-90度
}
// 云台偏航角查看图片
const rimbalGawDegreeFlag = ref(true);
function rotateGimbalYawDegree() {
if (!graffitiFlag.value) {
rimbalGawDegreeFlag.value = !rimbalGawDegreeFlag.value;
if (rimbalGawDegreeFlag.value) {
rotationAngle.value = parseFloat(props.nowShowImageData.gimbalYawDegree) + 180;
} else {
rotationAngle.value = 0;
}
} else {
createMessage.warn('涂鸦状态下不能使用云台偏航角查看图片');
}
}
// 宽高
const imageWidth = ref(1040);
const imageHeight = ref(800);
// 设置高度和宽度
function getImageWidthAndHeight() {
if (
props.nowShowImageData.width &&
props.nowShowImageData.height &&
props.nowShowImageData.width > 1040 &&
props.nowShowImageData.height > 800
) {
imageWidth.value = 1040;
imageHeight.value = (1040 / props.nowShowImageData.width) * props.nowShowImageData.height;
} else {
imageHeight.value = 800;
imageWidth.value = 1040;
}
}
onMounted(() => {
getImageWidthAndHeight();
});
// 100%比例
function setProportion() {
if (
props.nowShowImageData.width &&
props.nowShowImageData.height &&
props.nowShowImageData.width > 1040 &&
props.nowShowImageData.height > 800
) {
scale.value = props.nowShowImageData.width / 1040;
// imageWidth.value = props.nowShowImageData.width;
// imageHeight.value = props.nowShowImageData.height;
} else {
scale.value = 2;
// imageWidth.value = 2080;
// imageHeight.value = 1600;
}
}
// 刷新
function refresh() {
scale.value = 0.9;
// 是否云台偏航角查看图片
if (rimbalGawDegreeFlag.value) {
rotationAngle.value = parseFloat(props.nowShowImageData.gimbalYawDegree) + 180;
} else {
rotationAngle.value = 0;
}
getImageWidthAndHeight();
// 复位
const dragDocument: any = document.querySelector('.dragModal');
if (dragDocument) {
dragDocument.style.left = 0 + 'px';
dragDocument.style.top = 0 + 'px';
}
document.body.style.cursor = 'default';
}
// 移动到中心位置
function handlerLocation() {
emits('handlerLocation', props.nowShowImageData);
}
// 选择当前展示图片-----------------------------------------------
function setNowShowImageData(chooseValue) {
emits('setNowShowImageData', chooseValue);
graffitiFlag.value = false;
refresh();
}
// 调整滑动条的位置
function setScrollLeft() {
const imageChooseListDocument: any = document.querySelector('.imageChooseList');
if (imageChooseListDocument) {
let index = props.allImageDataList.findIndex((item) => item.id == props.nowShowImageData.id);
let length = props.allImageDataList.length;
if (index < 5) {
imageChooseListDocument.scrollLeft = 0;
} else if (index + 6 > length) {
imageChooseListDocument.scrollLeft = 95 * length;
} else {
imageChooseListDocument.scrollLeft = 95 * (index - 4);
}
}
}
// 鼠标拖动or涂鸦-------------------------------------------------
// 鼠标放上去的模式拖动or涂鸦
const graffitiFlag = ref(false);
// 设置鼠标格式
function setGraffiti() {
refresh();
graffitiFlag.value = !graffitiFlag.value;
// 正常查看图片
rimbalGawDegreeFlag.value = false;
rotationAngle.value = 0;
if (graffitiFlag.value) {
document.body.style.cursor = 'crosshair';
} else {
document.body.style.cursor = 'pointer';
}
}
// 标签--------------------------------
const addFileTagsFlag = ref(false);
const newFileTagsName = ref('');
const fileTags: any = ref([]);
// 所有已创建的矩形
const graffitis: any = ref([]);
const graffitisClone: any = ref([]);
watch(
() => props.nowShowImageData,
() => {
graffitis.value = props.nowShowImageData.graffitiJson
? props.nowShowImageData.graffitiJson
: [];
fileTags.value = props.nowShowImageData.fileTags ? props.nowShowImageData.fileTags : [];
if (rimbalGawDegreeFlag.value) {
rotationAngle.value = parseFloat(props.nowShowImageData.gimbalYawDegree) + 180;
}
// 调整滑动条的位置
setScrollLeft();
// 图片大小
getImageSize();
editNameFlag.value = true;
},
{
deep: true,
immediate: true,
},
);
onMounted(() => {
// 调整滑动条的位置
setScrollLeft();
});
// 图片大小
const imageSize = ref('');
async function getImageSize() {
try {
const response = await fetch(
VITE_GLOB_MEDIALIBRARY_IMAGE_URL + props.nowShowImageData.objectKey,
{ method: 'HEAD' },
);
if (!response.ok) {
imageSize.value = '--';
throw new Error(`HTTP error! status: ${response.status}`);
}
const contentLength = response.headers.get('Content-Length');
if (contentLength) {
const sizeInBytes = parseInt(contentLength, 10);
if (sizeInBytes > 1024 * 1024) {
imageSize.value = (sizeInBytes / (1024 * 1024)).toFixed(2) + 'MB';
} else if (sizeInBytes > 1024) {
imageSize.value = (sizeInBytes / 1024).toFixed(2) + 'KB';
} else if (sizeInBytes > 0) {
imageSize.value = sizeInBytes + 'B';
} else {
imageSize.value = '--';
}
} else {
imageSize.value = '--';
}
} catch (error) {
imageSize.value = '--';
}
}
// 打开标签弹窗
function addFileTagsChange() {
addFileTagsFlag.value = true;
}
// 添加标签方法
async function pressEnterFileTagsFunction() {
if (!newFileTagsName.value) {
return;
}
if (!fileTags.value.includes(newFileTagsName.value)) {
fileTags.value.push(newFileTagsName.value);
UpdatePicStatus({
id: props.nowShowImageData.id,
fileTags: JSON.stringify(fileTags.value),
graffitiJson: JSON.stringify(graffitis.value),
display: props.nowShowImageData.display ? 1 : 0,
showOnMap: props.nowShowImageData.showOnMap ? 1 : 0,
}).then((res) => {
addFileTagsFlag.value = true;
newFileTagsName.value = '';
emits('handleSuccessPath', props.nowShowImageData);
});
} else {
return createMessage.error('此标签已存在!');
}
}
// 删除标签
function deleteFileTags(value) {
createConfirm({
iconType: 'info',
title: '提醒',
content: '删除标签【' + value + '】,同标签名的涂鸦标记都会被删除!',
onOk: () => {
fileTags.value = fileTags.value.filter((item) => item !== value);
let json = graffitis.value;
if (json.some((item) => item.text == value)) {
json = json.filter((item) => item.text !== value);
graffitis.value = json;
}
UpdatePicStatus({
id: props.nowShowImageData.id,
fileTags: JSON.stringify(fileTags.value),
graffitiJson: JSON.stringify(graffitis.value),
display: props.nowShowImageData.display,
showOnMap: props.nowShowImageData.showOnMap,
}).then((res) => {
emits('handleSuccessPath', props.nowShowImageData);
});
},
onCancel: () => {},
});
}
// 涂鸦
// ref
const mouseCanvasRef = ref();
const graffitiColor = ref('#E23C39');
const nowGraffiti = ref(-1);
// 添加
function addGraffiti() {
graffitis.value.forEach((item) => {
if (item.text && !fileTags.value.includes(item.text)) {
if (item.text) {
fileTags.value.push(item.text);
}
}
});
UpdatePicStatus({
id: props.nowShowImageData.id,
fileTags: JSON.stringify(fileTags.value),
graffitiJson: JSON.stringify(graffitis.value),
display: props.nowShowImageData.display,
showOnMap: props.nowShowImageData.showOnMap,
}).then((res) => {
emits('handleSuccessPath', props.nowShowImageData);
});
}
// 删除
function deleteGraffiti(index = undefined, value = undefined) {
graffitis.value.splice(index, 1);
// 刷新or删除标签
if (!graffitis.value.some((item) => item.text == value)) {
fileTags.value = fileTags.value.filter((la) => la != value);
}
UpdatePicStatus({
id: props.nowShowImageData.id,
fileTags: JSON.stringify(fileTags.value),
graffitiJson: JSON.stringify(graffitis.value),
display: props.nowShowImageData.display,
showOnMap: props.nowShowImageData.showOnMap,
}).then((res) => {
emits('handleSuccessPath', props.nowShowImageData);
});
}
//------------------------------------------------------------------------
// 状态管理
const dragState = {
isDragging: false,
initialMouseX: 0,
initialMouseY: 0,
initialElementX: 0,
initialElementY: 0,
};
// 临时选框状态
const isDragging = ref(false);
// 涂鸦
let startX = 0;
let startY = 0;
let endX = 0;
let endY = 0;
// 鼠标按下
function onMouseDown(event) {
if (
graffitiFlag.value &&
(graffitis.value.length == 0 || !graffitis.value.some((item) => item.status != 'success'))
) {
// 涂鸦
if (nowGraffiti.value != -1) return;
if (graffitis.value.findIndex((item) => item.status == 'mouse') != -1) return;
// 获取相对于容器的坐标
const rect = mouseCanvasRef.value.getBoundingClientRect();
startX = event.x - rect.x;
startY = event.y - rect.y;
isDragging.value = true;
// 添加矩形到数组中
graffitis.value.push({
x: startX,
y: startY,
width: 0,
height: 0,
color: graffitiColor.value,
text: '',
status: 'edit',
});
graffitisClone.value = cloneDeep(graffitis.value);
window.addEventListener('mousemove', onMouseMoveGraffit);
window.addEventListener('mouseup', onMouseUpGraffit);
}
if (!graffitiFlag.value) {
const dragElement: any = document.querySelector('.dragModal');
if (!dragElement) return;
dragState.isDragging = true;
dragState.initialMouseX = event.clientX;
dragState.initialMouseY = event.clientY;
dragState.initialElementX = dragElement.offsetLeft;
dragState.initialElementY = dragElement.offsetTop;
dragElement.style.cursor = 'grabbing';
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
}
}
// 鼠标移动-涂鸦
function onMouseMoveGraffit(event) {
// 涂鸦
const rect = mouseCanvasRef.value.getBoundingClientRect();
if (!isDragging.value) return;
endX = event.x - rect.x;
endY = event.y - rect.y;
if (endX < 0 && endY < 0) {
endX = startX;
endY = startY;
}
setMouseData();
}
// 鼠标松开-涂鸦
function onMouseUpGraffit(event) {
// 涂鸦
if (!isDragging.value) return;
const rect = mouseCanvasRef.value.getBoundingClientRect();
endX = event.x - rect.x;
endY = event.y - rect.y;
if (endX < 0 && endY < 0) {
endX = startX;
endY = startY;
}
isDragging.value = false;
if (event.x > rect.right || event.y > rect.bottom) {
graffitis.value.splice(graffitis.value.length - 1, 1);
nowGraffiti.value = -1;
} else {
setMouseData();
nowGraffiti.value = graffitis.value.length - 1;
}
window.removeEventListener('mousemove', onMouseMoveGraffit);
window.removeEventListener('mouseup', onMouseUpGraffit);
}
// 鼠标移动-拖动
const handleMouseMove = (event) => {
const dragElement: any = document.querySelector('.dragModal');
if (!dragElement) return;
if (!dragState.isDragging) return;
const deltaX = event.clientX - dragState.initialMouseX;
const deltaY = event.clientY - dragState.initialMouseY;
dragElement.style.left = `${dragState.initialElementX + deltaX}px`;
dragElement.style.top = `${dragState.initialElementY + deltaY}px`;
};
// 鼠标松开-拖动
const handleMouseUp = () => {
const dragElement: any = document.querySelector('.dragModal');
if (!dragElement) return;
dragState.isDragging = false;
dragElement.style.cursor = 'default';
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
// 鼠标滚轮
function onWheel(event) {
if (!graffitiFlag.value) {
const delta = event.deltaY || event.detail || event.wheelDelta;
// 根据滚轮方向调整缩放比例
if (delta < 0) {
// 向上滚动,放大
scale.value += 0.1;
} else if (scale.value > 0.5) {
// 向下滚动,缩小
scale.value -= 0.1;
}
}
}
// 设置鼠标涂鸦绘画出的数据
function setMouseData() {
graffitis.value[graffitis.value.length - 1].x = Math.min(startX, endX);
graffitis.value[graffitis.value.length - 1].width = Math.abs(endX - startX);
graffitis.value[graffitis.value.length - 1].y = Math.min(startY, endY);
graffitis.value[graffitis.value.length - 1].height = Math.abs(endY - startY);
}
// 隐藏涂鸦
const hideOrShowGraffitiFlag = ref(true);
function hideOrShowGraffiti(value) {
hideOrShowGraffitiFlag.value = value;
}
//------------------------------------------------------------------------
// 鼠标聚焦
function mouseenter(rect, type) {
if (graffitiFlag.value) {
// 鼠标聚焦状态
if (rect.status != 'edit') {
rect.status = 'mouse';
document.body.style.cursor = 'pointer';
}
// 编辑状态
if (rect.status == 'edit') {
if (type == 'top' || type == 'bottom') {
document.body.style.cursor = 'ns-resize';
}
if (type == 'left' || type == 'right') {
document.body.style.cursor = 'ew-resize';
}
if (type == 'leftTop' || type == 'rightBottom') {
document.body.style.cursor = 'nwse-resize';
}
if (type == 'leftBottom' || type == 'rightTop') {
document.body.style.cursor = 'nesw-resize';
}
}
}
}
// 鼠标离开
function mouseleave(rect) {
if (graffitiFlag.value) {
if (rect.status == 'mouse' || rect.status == 'edit') {
if (rect.status == 'mouse') {
rect.status = 'success';
}
if (rect.status == 'edit') {
document.body.style.cursor = 'crosshair';
}
}
}
}
// 编辑状态下的鼠标按下
const mouseEditType = ref('');
function funMouseDownEdit(e, index, type) {
if (graffitiFlag.value) {
// 获取相对于容器的坐标
graffitisClone.value = cloneDeep(graffitis.value);
const rect = mouseCanvasRef.value.getBoundingClientRect();
startX = e.x - rect.x;
startY = e.y - rect.y;
isDragging.value = true;
nowGraffiti.value = index;
mouseEditType.value = type;
window.addEventListener('mousemove', funMouseMoveEdit);
window.addEventListener('mouseup', funMouseUpEdit);
}
}
// 编辑状态下的鼠标移动
function funMouseMoveEdit(e) {
if (graffitiFlag.value) {
const rect = mouseCanvasRef.value.getBoundingClientRect();
if (!isDragging.value) return;
endX = e.x - rect.x;
endY = e.y - rect.y;
if (endX < 0 && endY < 0) {
endX = startX;
endY = startY;
}
funSetMouseDataEdit();
}
}
// 编辑状态下的鼠标松开
function funMouseUpEdit(e) {
if (graffitiFlag.value) {
if (!isDragging.value) return;
const rect = mouseCanvasRef.value.getBoundingClientRect();
endX = e.x - rect.x;
endY = e.y - rect.y;
if (endX < 0 && endY < 0) {
endX = startX;
endY = startY;
}
isDragging.value = false;
if (e.x > rect.right || e.y > rect.bottom) {
graffitis.value[nowGraffiti.value] = graffitisClone.value[nowGraffiti.value];
} else {
funSetMouseDataEdit();
}
window.removeEventListener('mousemove', funMouseMoveEdit);
window.removeEventListener('mouseup', funMouseUpEdit);
}
}
// 编辑状态下的数据
function funSetMouseDataEdit() {
if (['top', 'leftTop', 'rightTop'].includes(mouseEditType.value)) {
graffitis.value[nowGraffiti.value].height =
graffitis.value[nowGraffiti.value].height + graffitis.value[nowGraffiti.value].y - endY;
graffitis.value[nowGraffiti.value].y = endY;
}
if (['bottom', 'leftBottom', 'rightBottom'].includes(mouseEditType.value)) {
graffitis.value[nowGraffiti.value].height = endY - graffitis.value[nowGraffiti.value].y;
}
if (['left', 'leftTop', 'leftBottom'].includes(mouseEditType.value)) {
graffitis.value[nowGraffiti.value].width =
graffitis.value[nowGraffiti.value].width + graffitis.value[nowGraffiti.value].x - endX;
graffitis.value[nowGraffiti.value].x = endX;
}
if (['right', 'rightTop', 'rightBottom'].includes(mouseEditType.value)) {
graffitis.value[nowGraffiti.value].width = endX - graffitis.value[nowGraffiti.value].x;
}
}
// 添加键盘事件监听器
const addEventListener = () => {
window.addEventListener('keydown', handleKeydown);
};
// 移除键盘事件监听器
const removeEventListener = () => {
window.removeEventListener('keydown', handleKeydown);
};
// 处理按键按下事件
const handleKeydown = (event: KeyboardEvent) => {
if (event.code === 'Escape' && graffitiFlag.value) {
setGraffiti();
}
};
// 在组件挂载时添加监听器
onMounted(() => {
addEventListener();
});
// 在组件卸载时移除监听器
onBeforeUnmount(() => {
removeEventListener();
});
</script>
<style lang="less" scoped>
.imageInfo {
// 页面不能被选中
-webkit-user-select: none; /* Safari */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* IE/Edge */
user-select: none;
position: relative;
width: 100%;
height: 100%;
background: #000000;
// 图片部分
.imageDiv {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: calc(100% - 120px);
background: #101010;
// 云台偏航角查看图片
.rimbalGawDegreeButton {
position: absolute;
right: 25px;
bottom: 12%;
z-index: 200;
background: #ffffff;
border-radius: 5px;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
}
// 全屏
.expandButton {
position: absolute;
right: 25px;
bottom: 5%;
z-index: 200;
background: #ffffff;
border-radius: 5px;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
}
}
// 底部按钮和图片列表
.bottomDiv {
width: 100%;
height: 120px;
background: #1c1c1c;
display: block;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
// 按钮列表
.buttonList {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
width: 100%;
height: 40px;
.button {
position: relative;
padding-left: 10px;
padding-right: 10px;
margin-left: 6px;
margin-right: 6px;
color: #ffffff;
font-size: 24px;
}
.button2 {
padding-top: 3px;
padding-left: 10px;
padding-right: 10px;
margin-left: 6px;
margin-right: 6px;
display: flex;
align-items: center;
justify-content: center;
}
}
// 图片选择列表
.imageChooseList {
display: inline-flex;
align-items: center;
// justify-content: center;
overflow-x: auto;
white-space: nowrap;
width: 80%;
height: 70px;
margin-bottom: 10px;
.bottom_div {
border-radius: 5px;
margin-left: 10px;
margin-right: 10px;
}
.bottom_div_choose {
outline: 3px solid #1088f2;
border-radius: 5px;
margin-left: 10px;
margin-right: 10px;
}
}
/* 强制滚动条一直显示Chrome 等 Webkit 浏览器) */
.imageChooseList::-webkit-scrollbar {
display: block;
height: 4px;
}
/* Firefox */
.imageChooseList {
scrollbar-width: auto;
scrollbar-color: #888 #1c1c1c;
}
}
}
// 标题
.titleDiv {
position: absolute;
top: 1%;
left: 20px;
height: 80px;
width: 80%;
// pointer-events: none;
z-index: 200;
.titleName {
font-size: 20px;
font-weight: bold;
text-decoration: underline;
text-decoration-color: white;
color: #ffffff;
}
.titleTime {
margin-top: 5px;
display: flex;
span {
font-size: 16px;
color: #ffffff;
}
}
.titleCoordinate {
margin-top: 5px;
span {
font-size: 16px;
color: #ffffff;
}
}
}
// 标签
.tagsDiv {
position: absolute;
top: 11%;
left: 20px;
height: 20px;
width: 80%;
z-index: 200;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: flex-start;
}
// 关闭按钮
.closeButton {
position: absolute;
top: 2%;
right: 20px;
z-index: 200;
}
// 上一张、下一张
.leftButton {
position: absolute;
left: 30px;
top: 45%;
z-index: 200;
background: #9c9c9c55;
border-radius: 70px;
width: 70px;
height: 70px;
display: flex;
align-items: center;
justify-content: center;
}
.rightButton {
position: absolute;
right: 30px;
top: 45%;
z-index: 200;
background: #9c9c9c55;
border-radius: 70px;
width: 70px;
height: 70px;
display: flex;
align-items: center;
justify-content: center;
}
// 涂鸦颜色选择-提示
.graffitiButton {
background: #ffffff;
position: absolute;
right: 25px;
top: 15%;
z-index: 200;
width: 30px;
height: 30px;
border-radius: 3px;
display: flex;
align-items: center;
justify-content: center;
}
.graffitiButton_choose {
background: linear-gradient(to bottom left, #2b85e4 10px, transparent 1px), #ffffff;
background-size:
100% 100%,
auto;
position: absolute;
right: 25px;
top: 15%;
z-index: 200;
width: 30px;
height: 30px;
border-radius: 3px;
display: flex;
align-items: center;
justify-content: center;
outline: 2px solid #2b85e4;
}
// 涂鸦颜色选择-提示框
.popoverClass {
width: 18px;
height: 18px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 3px;
}
// 隐藏or显示涂鸦和标签
.showTextboxClass {
position: absolute;
right: 25px;
top: 20%;
width: 30px;
height: 30px;
z-index: 200;
display: block;
align-items: center;
justify-content: center;
.button {
border-radius: 3px;
position: relative;
color: #2d8cf0;
background: #ffffff;
font-size: 22px;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
}
}
// 退出涂鸦
.escTip {
position: absolute;
left: 0%;
bottom: 120px;
width: 120px;
height: 40px;
background: #9c9c9c77;
border-radius: 5px;
z-index: 1001;
display: flex;
align-items: center;
justify-content: flex-start;
.whiteEsc {
height: 30px;
background: #ffffff;
font-size: 16px;
padding: 10px;
border-radius: 5px;
display: flex;
margin-left: 10px;
align-items: center;
}
.blackTip {
display: flex;
margin-left: 5px;
align-items: center;
color: #ffffff;
}
}
</style>