zn-admin-vue3-wcs/src/views/mapPage/realTimeMap/components/indexPage.vue
2025-03-21 14:28:13 +08:00

1189 lines
36 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div
class="affix-container"
id="indexpage-container"
:style="{
height: heightVal + 'px',
cursor: isDrag ? 'pointer' : 'default',
scale: isSizeRadio,
transformOrigin: '0 0',
width: isFull?'100%':(widthVal + 'px')
}"
@wheel="handleWheel"
>
<div
:class="isDrag ? 'indexpage-container-active' : 'indexpage-container'"
v-if="imgUrl"
v-drag="isDrag"
:style="{ scale: 1, transformOrigin: '0 0' }"
ref="draggableElement"
>
<div class="indexpage-container-box">
<img :src="imgUrl" class="indexpage-container-box-img" />
<div class="indexpage-container-box-point">
<!-- 连线 -->
<div v-if="legendObj.driveLineShow">
<div class="line-box">
<svg id="svg" :width="widthVal" :height="heightVal">
<template v-for="(item, index) in lineList" :key="index">
<!-- 定义箭头 -->
<defs>
<marker
id="forward-arrow"
viewBox="0 0 10 10"
refX="10"
refY="5"
orient="auto"
markerWidth="2"
markerHeight="2"
>
<path d="M 0 0 L 10 5 L 0 10 z" fill="black" />
</marker>
<!-- 反向箭头 -->
<marker
id="backward-arrow"
viewBox="0 0 10 10"
refX="0"
refY="5"
orient="auto"
markerWidth="2"
markerHeight="2"
>
<path d="M 10 0 L 0 5 L 10 10 z" fill="black" />
</marker>
</defs>
<template v-if="item.method == 0">
<line
:x1="Number(item.startPointX) * radio"
:y1="Number(item.startPointY) * radio"
:x2="Number(item.endPointX) * radio"
:y2="Number(item.endPointY) * radio"
:stroke="item.isSelect ? '#f48924' : '#00329F'"
:stroke-width="4 * radio"
:marker-start="item.direction === 2 ? 'url(#double-arrow-start)' : ''"
marker-end="url(#single-arrow)"
@click="handleChooseRoute(item, index)"
/>
<path
:d="getLineMidArrowPath(item)"
stroke="none"
fill="black"
:stroke-width="4 * radio"
:marker-start="item.direction === 2 ? 'url(#backward-arrow)' : ''"
:marker-end="
item.direction === 2 ? 'url(#forward-arrow)' : 'url(#forward-arrow)'
"
/>
</template>
<template v-else>
<path
:d="getCurvePath(item)"
:stroke="item.isSelect ? '#f48924' : '#00329F'"
:stroke-width="4 * radio"
fill="none"
:marker-start="item.direction === 2 ? 'url(#backward-arrow)' : ''"
:marker-end="
item.direction === 2 ? 'url(#forward-arrow)' : 'url(#forward-arrow)'
"
@click="handleChooseRoute(item, index)"
/>
</template>
</template>
</svg>
</div>
</div>
<!-- 小车 -->
<div
class="indexpage-car-item"
v-for="(item, index) in testCarList"
@dblclick="carDbClick(item, index)"
:key="index"
:style="{
left: item.realX * radio + 'px',
top: item.realY * radio + 'px',
width: legendObj.carShow
? (carWidth / nowObject.showYamlJson.resolution / 100) * radio + 'px'
: '0',
height: legendObj.carShow
? (carHeight / nowObject.showYamlJson.resolution / 100) * radio + 'px'
: '0',
transform: 'rotate(' + radianToDegree(item.data.pose2d.yaw) + 'deg)',
transition: 'all 0.2s linear',
zIndex: 9999
}"
>
<img
src="@/assets/imgs/indexPage/chache-4备份 7@2x.png"
style="width: 100%; height: 100%"
/>
</div>
<div
class="indexpage-container-box-point-item"
v-for="(item, index) in pointList"
:key="index"
:style="{
left:
(Number(item.locationX) - Number(item.locationWidePx) / 2) * Number(radio) + 'px',
top:
(Number(item.locationY) - Number(item.locationDeepPx) / 2) * Number(radio) + 'px',
width: Number(item.locationWidePx) * Number(radio) + 'px',
height: Number(item.locationDeepPx) * Number(radio) + 'px'
}"
>
<!-- 1 路径点 -->
<el-tooltip class="box-item" effect="dark" :content="item.sortNum + ''" placement="top">
<div
v-if="item.type === 1"
:style="{
width: Number(item.locationWidePx) * Number(radio) + 'px',
height: Number(item.locationDeepPx) * Number(radio) + 'px',
backgroundColor: '#000',
borderRadius: '50%'
}"
>
</div>
</el-tooltip>
<!-- 库位点 -->
<el-popover placement="top-start" trigger="hover" width="auto">
<template #reference>
<img
v-if="item.showData && item.type == 2"
:src="item.imgUrl"
:style="nodeStyle(item, index)"
@dblclick="storeClick(item)"
/>
</template>
<div>
<div class="indexpage-popover-item">
<div> 库位名: </div>
<div>
{{ item.showData?.locationNo || '' }}
</div>
</div>
<div class="indexpage-popover-item">
<div> 所属线库: </div>
<div>
{{ item.showData?.laneName || '' }}
</div>
</div>
<div class="indexpage-popover-item">
<div> 所属区域: </div>
<div>
{{ item.showData?.areaName || '' }}
</div>
</div>
</div>
</el-popover>
<!-- 设备点 -->
<img
v-if="item.type == 3"
:src="
item.formattedData.mapImageUrl ||
'https://api.znkjfw.com/admin-api/infra/file/4/get/设备点_png_179_1739327151877.png'
"
:style="nodeStyle(item, index)"
/>
<!-- 停车点 -->
<img
v-if="item.type == 4"
:src="
item.formattedData.mapImageUrl ||
'https://api.znkjfw.com/admin-api/infra/file/4/get/停车场-01_png_179_1739326933020.png'
"
:style="nodeStyle(item, index)"
/>
<!-- 路径点 -->
<img
v-if="item.type == 5"
:src="
item.formattedData.mapImageUrl ||
'https://api.znkjfw.com/admin-api/infra/file/4/get/区域_png_179_1739327151876.png'
"
:style="nodeStyle(item, index)"
/>
<!-- 等待点 -->
<img
v-if="item.type == 6"
:src="
item.formattedData.mapImageUrl ||
'https://api.znkjfw.com/admin-api/infra/file/4/get/等待点_png_179_1739326991439.png'
"
:style="nodeStyle(item, index)"
/>
</div>
</div>
</div>
</div>
</div>
<!-- 左下角图层 -->
<!-- :style="{ left: boxLeft + 'px' }" -->
<div class="affix-container-left" :style="{ left: 25 + 'px' }" v-if="!isAllBoard">
<div class="affix-container-left-box">
<div
class="affix-container-left-box-item-box"
:style="{
height: legendObj.legendShow ? '5.25rem' : '0',
overflow: 'hidden',
transition: 'all 0.3s ease-in-out'
}"
>
<div class="affix-container-left-box-item">
<div class="affix-container-left-box-item-left"> 行驶路线 </div>
<img
src="@/assets/imgs/indexPage/yanjing_xianshi_o.png"
class="affix-container-left-box-item-img"
v-if="legendObj.driveLineShow"
@click="changDriveLineShow"
/>
<img
src="@/assets/imgs/indexPage/yanjing_yincang_o.png"
class="affix-container-left-box-item-img"
v-if="!legendObj.driveLineShow"
@click="changDriveLineShow"
/>
</div>
<div class="affix-container-left-box-item">
<div class="affix-container-left-box-item-left"> 车辆 </div>
<img
src="@/assets/imgs/indexPage/yanjing_xianshi_o.png"
class="affix-container-left-box-item-img"
v-if="legendObj.carShow"
@click="changCarShow"
/>
<img
src="@/assets/imgs/indexPage/yanjing_yincang_o.png"
class="affix-container-left-box-item-img"
v-if="!legendObj.carShow"
@click="changCarShow"
/>
</div>
</div>
<div
class="affix-container-left-box-item-bottom"
@click="legendObj.legendShow = !legendObj.legendShow"
>
<div class="affix-container-left-box-item-bottom-left"> 图例 </div>
<img
src="@/assets/imgs/indexPage/zhankai@2x.png"
class="affix-container-left-box-item-bottom-img"
:style="{
transform: legendObj.legendShow ? 'rotate(180deg)' : 'rotate(0deg)',
transition: 'all 0.3s ease-in-out'
}"
/>
</div>
</div>
</div>
<!-- 右下角的功能 -->
<div class="affix-container-right" v-if="!isAllBoard">
<!-- 拖拽 -->
<div class="affix-container-right-item" @click="changeIsDrag">
<img src="@/assets/imgs/indexPage/编组 12.png" style="width: 100%; height: 100%" />
</div>
<!-- 放大 -->
<div class="affix-container-right-item">
<img
src="@/assets/imgs/indexPage/编组 14.png"
style="width: 100%; height: 100%"
@click="changeSizeRaio(0.2)"
/>
</div>
<!-- 缩小 -->
<div class="affix-container-right-item">
<img
src="@/assets/imgs/indexPage/编组 15.png"
style="width: 100%; height: 100%"
@click="changeSizeRaio(-0.2)"
/>
</div>
<!-- 全屏 -->
<div class="affix-container-right-item">
<img
src="@/assets/imgs/indexPage/编组 22.png"
style="width: 100%; height: 100%"
@click="toggleFullScreen"
/>
</div>
</div>
<storeDialog ref="storeDialogRef" @success="emitParent" />
<carDialog ref="carDialogRef" />
</template>
<script setup>
import {
ref,
defineComponent,
reactive,
nextTick,
onMounted,
onBeforeUnmount,
onUnmounted
} from 'vue'
import * as MapApi from '@/api/map/map'
import WebSocketClient from '../webSocket.js'
import storeDialog from './storeDialog.vue'
import { color } from 'echarts'
import { resetDragPosition } from '@/utils/drag'
import carDialog from './carDialog.vue'
import { is } from 'bpmn-js/lib/util/ModelUtil'
import JSONBigInt from 'json-bigint'
import { propTypes } from '@/utils/propTypes'
const imgUrl = ref('')
const carDialogRef = ref(null)
const socketClient = ref(null)
const router = useRouter() // 路由对象
const emit = defineEmits(['transmitMapId'])
const storeDialogRef = ref(null) // 仓库信息弹窗
const list = ref([])
const nowObject = ref(null) // 地图当前对象 父组件传过来的
const testCarList = ref([]) //小车数组
const carWidth = ref(60)
const carHeight = ref(32)
const nodeStyle = (item, index) => {
console.log(item.locationWidePx, item.locationDeepPx,item)
return {
verticalAlign: 'top',
objectFit: 'cover',
width: Number(item.locationWidePx) * Number(radio.value) + 'px',
height: Number(item.locationDeepPx) * Number(radio.value) + 'px'
}
}
const isFull = ref(false)
// 定义属性
const props = defineProps({
// 当前选中的链接
isAllBoard: propTypes.bool.def(false)
})
const convertActualToBrowser = (pointX, pointY) => {
let resolution = Number(nowObject.value.showYamlJson.resolution)
let origin = nowObject.value.showYamlJson.origin
const y1 = Number(origin[1]) + Number(nowObject.value.showYamlJson.height) * resolution
let x = Math.max(Number(pointX) - Number(origin[0]), 0)
let y = Math.max(y1 - Number(pointY), 0)
return {
x: x / resolution - carWidth.value / resolution / 100 / 2,
y: y / resolution - carHeight.value / resolution / 100 / 2
}
}
//是否可以拖拽
const isDrag = ref(false)
const changeIsDrag = () => {
nextTick(() => {
isDrag.value = !isDrag.value
// console.log(isDrag.value)
if (!isDrag.value) {
//还原位置
resetPosition()
}
})
}
//点击选中线 如果选中就变成非选中 否则相反
const handleChooseRoute = (val, i) => {
// console.log('============================', val)
if (lineList.value.length) {
if (val.isSelect) {
lineList.value.forEach((item, index) => {
if (index == i) {
item.isSelect = false
} else {
item.isSelect = false
}
})
} else {
lineList.value.forEach((item, index) => {
if (index == i) {
item.isSelect = true
} else {
item.isSelect = false
}
})
}
}
}
// 获取曲线的路径
const getCurvePath = (curve) => {
let startPointX = Number(curve.startPointX) * radio.value
let startPointY = Number(curve.startPointY) * radio.value
let endPointX = Number(curve.endPointX) * radio.value
let endPointY = Number(curve.endPointY) * radio.value
return `M ${startPointX} ${startPointY} C ${curve.beginControlX * radio.value} ${curve.beginControlY * radio.value}, ${curve.endControlX * radio.value} ${curve.endControlY * radio.value}, ${endPointX} ${endPointY}`
}
//放大缩小
const isSizeRadio = ref(1)
const changeSizeRaio = (type) => {
if (type < 0 && isSizeRadio.value + type <= 0) {
return
}
isSizeRadio.value += type
}
//图层状态
const legendObj = reactive({
driveLineShow: true,
carShow: true,
legendShow: true
})
// 车辆是否显示
const changCarShow = () => {
legendObj.carShow = !legendObj.carShow
}
// 车辆行驶路线是否展示
const changDriveLineShow = () => {
legendObj.driveLineShow = !legendObj.driveLineShow
}
const toggleFullScreen = () => {
var elem = document.getElementById('indexpage-container') // 获取元素
if (!document.fullscreenElement) {
// 检查是否已经是全屏模式
if (elem.requestFullscreen) {
// 支持requestFullscreen API的标准方式
elem.requestFullscreen().catch((err) => {
alert(`无法进入全屏模式: ${err.message}`) // 处理错误情况
})
} else if (elem.mozRequestFullScreen) {
// 旧版Firefox的API名称已废弃
elem.mozRequestFullScreen()
} else if (elem.webkitRequestFullscreen) {
// WebKit/Safari/Chrome的API名称已废弃
elem.webkitRequestFullscreen()
} else if (elem.msRequestFullscreen) {
// IE/Edge的API名称已废弃
elem.msRequestFullscreen()
}
isDrag.value = true
} else {
// 退出全屏模式
if (document.exitFullscreen) {
// 标准API退出全屏模式
document.exitFullscreen()
} else if (document.mozCancelFullScreen) {
// 旧版Firefox的API名称已废弃
document.mozCancelFullScreen()
} else if (document.webkitExitFullscreen) {
// WebKit/Safari/Chrome的API名称已废弃
document.webkitExitFullscreen()
} else if (document.msExitFullscreen) {
// IE/Edge的API名称已废弃
document.msExitFullscreen()
}
console.log('退出全屏')
isDrag.value = false
resetPosition()
}
// 监听全屏状态变化事件
document.addEventListener('fullscreenchange', function () {
if (!document.fullscreenElement) {
console.log('已退出全屏模式')
// 在这里可以添加退出全屏后的逻辑
isDrag.value = false
isFull.value = false
radio.value = 1
if (pointList.value.length) {
pointList.value.forEach((item) => {
item.radio = radio.value
})
}
if (testCarList.value.length) {
testCarList.value.forEach((item) => {
item.radio = radio.value
})
}
resetPosition()
} else {
console.log('已进入全屏模式')
// 在这里可以添加进入全屏后的逻辑
isDrag.value = true
isFull.value = true
nextTick(() => {
let width = getElementWidthByClass('affix-container')
getImageWidth(imgUrl.value, 'width').then((res) => {
console.log(res)
let ratioVal = width / res
radio.value = ratioVal
console.log(radio.value)
// widthVal.value = res * radio.value
if (pointList.value.length) {
pointList.value.forEach((item) => {
item.radio = radio.value
})
}
if (testCarList.value.length) {
testCarList.value.forEach((item) => {
item.radio = radio.value
})
}
})
})
}
})
// 监听旧版浏览器的全屏状态变化事件
document.addEventListener('mozfullscreenchange', function () {
if (!document.mozFullScreenElement) {
console.log('已退出全屏模式 (Firefox旧版)')
isDrag.value = false
resetPosition()
} else {
console.log('已进入全屏模式 (Firefox旧版)')
isDrag.value = true
}
})
document.addEventListener('webkitfullscreenchange', function () {
if (!document.webkitFullscreenElement) {
console.log('已退出全屏模式 (WebKit旧版)')
isDrag.value = false
resetPosition()
} else {
console.log('已进入全屏模式 (WebKit旧版)')
isDrag.value = true
}
})
document.addEventListener('msfullscreenchange', function () {
if (!document.msFullscreenElement) {
console.log('已退出全屏模式 (IE/Edge旧版)')
isDrag.value = false
resetPosition()
} else {
console.log('已进入全屏模式 (IE/Edge旧版)')
isDrag.value = true
}
})
}
//库位双击
const storeClick = async (item) => {
let storeData = await MapApi.houseLocationGetByMapItemId({
mapId: item.positionMapId,
mapItemId: item.id
})
storeDialogRef.value.open(JSON.parse(JSON.stringify(storeData)))
}
const lineList = ref([])
const pointList = ref([])
const getPositionMapListFun = async (positionMapId) => {
pointList.value = await MapApi.getPositionMapItemList({ positionMapId: positionMapId })
pointList.value?.forEach((item) => {
item.formattedData = item.dataJson ? JSON.parse(item.dataJson) : ''
item.showData = item.dataJson ? JSON.parse(item.dataJson)[0] : null
item.imgUrl = formatTypeImg(item.type)
if (item.type === 1) {
item.locationDeep = 40
item.locationWide = 40
} else if (item.type === 5 || item.type === 6) {
item.locationDeep = 150
item.locationWide = 150
} else if (item.type === 2) {
//库位点
item.dataList = JSONBigInt({ storeAsString: true }).parse(item.dataJson)
item.locationDeep = item.dataList[0].locationDeep
item.locationWide = item.dataList[0].locationWide
} else if (item.type === 3) {
item.dataObj = JSONBigInt({ storeAsString: true }).parse(item.dataJson)
item.locationDeep = item.dataObj.locationDeep
item.locationWide = item.dataObj.locationWide
} else if (item.type === 4) {
item.dataObj = JSONBigInt({ storeAsString: true }).parse(item.dataJson)
item.locationDeep = item.dataObj.locationDeep
item.locationWide = item.dataObj.locationWide
}
//要将实际的cm改成px
if (item.locationWide && item.locationDeep) {
let pxObj = cmConversionPx(item.locationWide, item.locationDeep)
item.locationWidePx = pxObj.pWidth
item.locationDeepPx = pxObj.pHeight
}
})
}
//将节点实际宽高cm转换成px
const cmConversionPx = (cWidth, cHeight) => {
let pWidth = Number(cWidth) / Number(nowObject.value.showYamlJson.resolution) / 100
let pHeight = Number(cHeight) / Number(nowObject.value.showYamlJson.resolution) / 100
return {
pWidth,
pHeight
}
}
const draggableElement = ref(null)
const resetPosition = () => {
if (draggableElement.value) {
resetDragPosition(draggableElement.value)
}
}
// 计算直线中间箭头的路径
const getLineMidArrowPath = (item) => {
const midX = ((Number(item.startPointX) + Number(item.endPointX)) / 2) * radio.value
const midY = ((Number(item.startPointY) + Number(item.endPointY)) / 2) * radio.value
let dx = item.endPointX * radio.value - item.startPointX * radio.value
let dy = item.endPointY * radio.value - item.startPointY * radio.value
let length = Math.sqrt(dx * dx + dy * dy)
if (length === 0) {
return `M ${midX} ${midY} L ${midX} ${midY}`
}
const unitDx = dx / length
const unitDy = dy / length
const arrowLength = 1
const startXArrow = midX - unitDx * arrowLength
const startYArrow = midY - unitDy * arrowLength
const endXArrow = midX
const endYArrow = midY
return `M ${startXArrow} ${startYArrow} L ${endXArrow} ${endYArrow}`
}
const calculateDistanceAndAngle = (point1, point2) => {
// 计算水平和垂直方向的差值
const dx = point2.left - point1.left
const dy = point2.top - point1.top
// 计算两点之间的长度(使用勾股定理)
const distance = Math.sqrt(dx * dx + dy * dy)
// 计算两点连线与水平轴正方向的夹角(弧度)
const angleInRadians = Math.atan2(dy, dx)
// 将弧度转换为角度
const angleInDegrees = angleInRadians * (180 / Math.PI)
return {
distance: distance,
angle: angleInDegrees,
left: point1.left,
top: point1.top
}
}
const formatTypeImg = (type) => {
switch (type) {
case 1:
return ''
case 2:
return 'https://api.znkjfw.com/admin-api/infra/file/4/get/库位库存_png_179_1739326653035.png'
case 3:
return 'https://api.znkjfw.com/admin-api/infra/file/4/get/设备点_png_179_1739327151877.png'
case 4:
return 'https://api.znkjfw.com/admin-api/infra/file/4/get/停车场-01_png_179_1739326933020.png'
case 5:
return 'https://api.znkjfw.com/admin-api/infra/file/4/get/区域_png_179_1739327151876.png'
case 6:
return 'https://api.znkjfw.com/admin-api/infra/file/4/get/等待点_png_179_1739326991439.png'
default:
return ''
}
}
const replaceHttpWithWs = (str) => {
return str.replace(/^http/, 'ws')
}
// 连接websocket
const linkWebSocket = (url) => {
socketClient.value = new WebSocketClient(url)
if (socketClient.value) {
// 监听消息
socketClient.value.onMessage((message) => {
// console.log('收到消息:', message)
//心跳信息不处理
if (message == 'ping' || message == 'pong') return
let jsonMsg = JSON.parse(message)
// 车辆信息
if (jsonMsg.type == 'map_push') {
let data = JSON.parse(jsonMsg.content)
// console.log('======车位点======', data)
let dataList = []
for (let key in data) {
dataList.push({
macAddress: key,
data: JSON.parse(data[key])
})
}
// console.log('=====', dataList)
testCarList.value = dataList
computedRatio()
}
//告警信息
if (jsonMsg.type == 'agv_warn') {
ElMessage({
message: () =>
h(
'div',
{
style: 'background-color: #C60606;'
},
[
h(
'span',
{ style: 'color: rgba(255,255,255,0.88);font-size: .875rem;' },
`${JSON.parse(jsonMsg.content)}`
),
h(
'span',
{
onClick: () => lookError(),
style:
'color: rgba(255,255,255,0.88);font-size: .875rem;cursor: pointer;text-decoration-line: underline;margin-left: 2rem;'
},
'详情'
)
]
),
showClose: false,
duration: 3000, // 让消息持续显示,直到用户关闭
type: 'info',
customClass: 'indexpage-custom-message-style'
})
}
// 规划路线信息
if (jsonMsg.type == 'planning_move_pose') {
let data = JSON.parse(jsonMsg.content)
// console.log('======规划路线======', JSON.parse(data).data)
let dataList = JSON.parse(data).data
if(lineList.value.length > 0) {
console.log(lineList.value)
lineList.value = setIsSelect(lineList.value, dataList)
}
}
})
}
}
const setIsSelect =(arr1, arr2) => {
for (let i = 0; i < arr1.length; i++) {
const element = arr1[i];
const isExist = arr2.includes(element.id);
element.isSelect = isExist;
}
return arr1;
}
// 查看更多异常列表
const lookError = () => {
// console.log('点击了')
router.push({
path: '/carError'
})
}
// 发送websocket消息
const sendMessage = () => {
socketClient.value.send('Hello, WebSocket!')
}
// 断开连接
const disconnect = () => {
socketClient.value.disconnect()
}
const emitParent = () => {
getMapData(nowObject.value)
}
//获取扫描图
const getMapData = async (item) => {
// console.log('===22=========', JSON.parse(item.yamlJson))
widthVal.value = JSON.parse(item.yamlJson).width
heightVal.value = JSON.parse(item.yamlJson).height
testCarList.value = []
nowObject.value = JSON.parse(JSON.stringify(item))
nowObject.value.showYamlJson = JSON.parse(item.yamlJson)
let websocketUrl = `${replaceHttpWithWs(import.meta.env.VITE_BASE_URL)}/infra/ws?type=map&floor=${nowObject.value.floor}&area=${nowObject.value.area}`
linkWebSocket(websocketUrl)
getPositionMapListFun(nowObject.value.id)
emit('transmitMapInfo', {
id: item.id,
floor: item.floor,
area: item.area
})
let data = await MapApi.getPositionMapdDwnloadPngBase64({
floor: item.floor,
area: item.area
})
imgUrl.value = data
computedRatio()
getMapLineList()
}
//偏航率斜率算旋转
const radianToDegree = (radian) => {
// 将弧度转换为角度
const degree = radian * (180 / Math.PI)
// 返回带有单位 'deg' 的 CSS 角度值
return `${degree}`
}
const heightVal = ref(0)
const widthVal = ref(0)
const radio = ref(1)
const computedRatio = () => {
nextTick(() => {
if (imgUrl.value) {
//这段代码之后会删掉 yaml会给原始宽高
getImageSize(imgUrl.value)
.then(({ width, height }) => {
if (testCarList.value.length) {
testCarList.value.forEach((item) => {
item.originWidth = width
item.originHeight = height
item.origin = JSON.parse(nowObject.value.yamlJson).origin
item.realX = convertActualToBrowser(item.data.pose2d.x, item.data.pose2d.y).x
item.realY = convertActualToBrowser(item.data.pose2d.x, item.data.pose2d.y).y
})
}
})
.catch((error) => {
console.error(error.message)
})
let width = getElementWidthByClass('indexpage-container')
getImageWidth(imgUrl.value, 'width').then((res) => {
console.log(res)
let ratioVal = width / res
radio.value = ratioVal
widthVal.value = res * radio.value
if (pointList.value.length) {
pointList.value.forEach((item) => {
item.radio = radio.value
})
}
if (testCarList.value.length) {
testCarList.value.forEach((item) => {
item.radio = radio.value
})
}
})
getImageWidth(imgUrl.value, 'height').then((res) => {
heightVal.value = res * radio.value
})
}
})
}
// 获取地图连线列表
const getMapLineList = () => {
MapApi.mapLineListGet({ positionMapId: nowObject.value.id }).then((res) => {
lineList.value = res
})
}
//鼠标滚轮
const handleWheel = (event) => {
// 判断 Ctrl 键是否被按下
if (event.ctrlKey) {
// 阻止默认的滚动行为
event.preventDefault()
// 根据滚轮滚动方向调整缩放比例
if (event.deltaY < 0) {
// 向上滚动,放大
//放大
if (isSizeRadio.value < 4) {
isSizeRadio.value += 0.2
} else {
message.warning('不能在放大了')
}
} else {
//缩小
if (isSizeRadio.value > 0.2) {
isSizeRadio.value -= 0.2
} else {
message.warning('不能在缩小了')
}
}
}
}
// 获取图片尺寸
const getImageSize = (url) => {
return new Promise((resolve, reject) => {
const img = new Image()
img.src = url
// 图片加载成功时触发
img.onload = () => {
const width = img.width
const height = img.height
resolve({ width, height })
}
// 图片加载失败时触发
img.onerror = () => {
reject(new Error(`Failed to load image from URL 获取图片尺寸失败: ${url}`))
}
})
}
const convertToFrontendCoordinates = (origin, width, height, target) => {
// 提取原点的 x 和 y 坐标
const [originX, originY] = origin
// 提取目标点的 x 和 y 坐标
const [targetX, targetY] = target
// 计算目标点相对于原点在 x 方向上的偏移量
const offsetX = targetX - originX
// 计算目标点相对于原点在 y 方向上的偏移量
const offsetY = targetY - originY
// 计算前端页面中的 left 坐标
const left = offsetX
// 计算前端页面中的 top 坐标,需要考虑坐标系的转换
const top = height - offsetY
return {
top,
left,
origin
}
}
const getImageWidth = (imageUrl, name) => {
return new Promise((resolve, reject) => {
const img = new Image()
img.onload = function () {
resolve(img[name])
}
img.onerror = function () {
reject(new Error('图片加载失败'))
}
img.src = imageUrl
})
}
const getElementWidthByClass = (className) => {
const element = document.querySelector(`.${className}`)
if (element) {
// return window.getComputedStyle(element).width
const widthWithUnit = window.getComputedStyle(element).width
return widthWithUnit.slice(0, -2)
}
return null
}
//小车双击
const carDbClick = (item, index) => {
// console.log(item)
carDialogRef.value.open(JSON.parse(JSON.stringify(item)))
}
const boxLeft = ref(0)
//获取元素距离左边的距离
const getLeftPx = () => {
let indexpageContainer = document.getElementById('indexpage-container')
// console.log('距离左边的距离', indexpageContainer.getBoundingClientRect().left)
boxLeft.value = indexpageContainer.getBoundingClientRect().left + 6
console.log(boxLeft.value)
}
// 封装监听元素宽度变化的方法
const observeWidthChange = (id, callback) => {
const targetElement = document.getElementById(id)
if (!targetElement) {
console.error(`未找到 ID 为 ${id} 的元素`)
return
}
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
const newWidth = entry.contentRect.width
callback(newWidth)
}
})
resizeObserver.observe(targetElement)
return () => {
resizeObserver.disconnect()
}
}
let stopObserving
defineExpose({ getMapData }) // 提供 open 方法,用于打开弹窗
onMounted(() => {
// getList()
// window.addEventListener('resize', computedRatio)
stopObserving = observeWidthChange('indexpage-container', (newWidth) => {
// 在这里执行宽度变化时的自定义操作
// console.log(`元素宽度已变为: ${newWidth}px`);
// 你可以添加更多的操作,比如更新组件的状态等
getLeftPx()
// computedRatio()
})
})
onBeforeUnmount(() => {
// window.removeEventListener('resize', computedRatio)
})
// 组件卸载时停止监听
onUnmounted(() => {
// if (stopObserving) {
// stopObserving()
// }
})
</script>
<style lang="scss" scoped>
.affix-container {
width: 100%;
position: relative;
// overflow: hidden;
// background: rgba(0, 0, 0, 0.8);
}
.indexpage-container {
width: 100%;
position: relative;
}
.indexpage-container-active {
width: 100%;
position: relative;
cursor: grab;
}
.indexpage-container-active:active {
cursor: grabbing;
}
.indexpage-container .affix-container-top {
width: 100%;
height: 5rem;
background-color: #fff;
border-bottom: 0.0625rem solid #f5f5f5;
}
.indexpage-container-box-point {
width: 100%;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
.indexpage-container-box-point-item {
box-sizing: border-box;
position: absolute;
cursor: pointer;
}
.scrollbar-flex-content {
padding: 0.125rem 0.375rem;
display: flex;
align-items: center;
flex-wrap: wrap;
}
.scrollbar-demo-item {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
width: 5.625rem;
height: 1.875rem;
margin: 0.625rem 0.375rem;
text-align: center;
border-radius: 0.25rem;
background: var(--el-color-primary-light-9);
color: var(--el-color-primary);
position: relative;
}
.scrollbar-demo-item {
cursor: pointer;
border: none;
}
:focus-visible {
outline: none;
}
.indexpage-container-box {
width: 100%;
position: relative;
}
.indexpage-container-box-img {
width: 100%;
height: auto;
}
.indexpage-popover-item {
display: flex;
font-family:
PingFangSC,
PingFang SC;
font-weight: 400;
font-size: 0.875rem;
color: #0d162a;
margin-bottom: 0.5rem;
}
.line-box {
position: absolute;
}
.indexpage-car-item {
position: absolute;
cursor: pointer;
}
.affix-container-left {
position: absolute;
bottom: 1.625rem;
z-index: 999;
}
.affix-container-left-box {
width: 9rem;
}
.affix-container-left-box-item-box {
width: 100%;
border-bottom: 0.0625rem solid #eeeeee;
background: #ffffff;
}
.affix-container-left-box-item {
width: 100%;
padding: 0 1.125rem;
height: 2.625rem;
display: flex;
align-items: center;
justify-content: space-between;
}
.affix-container-left-box-item-left {
font-family:
PingFangSC,
PingFang SC;
font-weight: 400;
font-size: 0.875rem;
color: rgba(0, 0, 0, 0.88);
}
.affix-container-left-box-item-img {
cursor: pointer;
flex-shrink: 0;
width: 1.25rem;
height: 0.75rem;
}
.affix-container-left-box-item-bottom {
width: 100%;
height: 2.25rem;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
background: #ffffff;
}
.affix-container-left-box-item-bottom-img {
cursor: pointer;
flex-shrink: 0;
width: 0.75rem;
height: 0.4375rem;
}
.affix-container-left-box-item-bottom-left {
cursor: pointer;
flex-shrink: 0;
font-family:
PingFangSC,
PingFang SC;
font-weight: 400;
font-size: 1rem;
color: #98a4bf;
margin-right: 0.125rem;
}
.affix-container-right {
position: fixed;
z-index: 999;
right: 2.5rem;
bottom: 1.25rem;
display: flex;
align-items: center;
justify-content: flex-end;
}
.affix-container-right-item {
width: 1.75rem;
height: 1.75rem;
cursor: pointer;
flex-shrink: 0;
margin-left: 0.5rem;
}
</style>