地图编辑

This commit is contained in:
yyy 2025-02-15 17:15:48 +08:00
parent b3bd0635fc
commit 48d26d8479
10 changed files with 1764 additions and 207 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -62,7 +62,7 @@ export const useAppStore = defineStore('app', {
tagsViewIcon: true, // 是否显示标签图标
logo: true, // logo
fixedHeader: true, // 固定toolheader
footer: true, // 显示页脚
footer: false, // 显示页脚
greyMode: false, // 是否开始灰色模式,用于特殊悼念日
fixedMenu: wsCache.get('fixedMenu') || false, // 是否固定菜单
layout: wsCache.get(CACHE_KEY.LAYOUT) || 'classic', // layout布局

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,151 @@
<template>
<div class="map-container" ref="mapContainer">
<!-- 地图 -->
<div
class="map"
:style="{
backgroundImage: `url(${mapImage})`,
transform: `scale(${scale})`,
transformOrigin: '0 0',
width: `${mapWidth}px`,
height: `${mapHeight}px`
}"
@click="addLocation"
>
<!-- 库位 -->
<div
v-for="(location, index) in locations"
:key="index"
class="location"
:style="{
left: `${location.projectX}px`,
top: `${location.projectY}px`,
transform: `scale(${scale})` //
}"
>
<img src="@/assets/location-icon.png" alt="库位" />
</div>
</div>
<!-- 放大缩小按钮 -->
<div class="controls">
<button @click="zoomIn">放大</button>
<button @click="zoomOut">缩小</button>
<button @click="saveLocations">保存</button>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import mapImage from '@/assets/warehouse-map.png' //
import locationIcon from '@/assets/location-icon.png' //
const origin = [-54.4, -34.2]
const resolution = 0.05
const mapContainer = ref(null) //
const scale = ref(1) //
const locations = ref([]) //
const mapWidth = ref(0) //
const mapHeight = ref(0) //
//
onMounted(() => {
const img = new Image()
img.src = mapImage
img.onload = () => {
mapWidth.value = img.width
mapHeight.value = img.height
}
})
//
const addLocation = (event) => {
const rect = mapContainer.value.getBoundingClientRect()
const scrollLeft = mapContainer.value.scrollLeft //
const scrollTop = mapContainer.value.scrollTop //
const devicePixelRatio = window.devicePixelRatio || 1
//
const projectX = (event.clientX - rect.left + scrollLeft) / scale.value / devicePixelRatio
const projectY = (event.clientY - rect.top + scrollTop) / scale.value / devicePixelRatio
//
const actualX = (projectX - origin[0]) / resolution
const actualY = (projectY - origin[1]) / resolution
//
locations.value.push({
projectX,
projectY,
actualX,
actualY
})
}
//
const zoomIn = () => {
scale.value *= 1.2
}
//
const zoomOut = () => {
scale.value /= 1.2
}
//
const saveLocations = () => {
console.log(
'项目点位坐标:',
locations.value.map((loc) => ({ x: loc.projectX, y: loc.projectY }))
)
console.log(
'实际现场点位坐标:',
locations.value.map((loc) => ({ x: loc.actualX, y: loc.actualY }))
)
alert('点位坐标已保存,请查看控制台输出。')
}
</script>
<style scoped>
.map-container {
position: relative;
width: 100%;
height: 80vh;
overflow: auto;
border: 1px solid #ccc;
}
.map {
background-size: contain;
background-repeat: no-repeat;
position: relative;
}
.location {
position: absolute;
width: 20px;
height: 20px;
cursor: pointer;
}
.location img {
width: 100%;
height: 100%;
}
.controls {
position: fixed;
top: 110px;
right: 110px;
display: flex;
gap: 10px;
}
.controls button {
padding: 5px 10px;
font-size: 14px;
cursor: pointer;
}
</style>

View File

@ -115,24 +115,23 @@
</div>
</div>
<div class="map-box" ref="imgWrap" :style="{ cursor: state.cursorStyle }">
<div class="map-container" ref="mapContainer" :style="{ cursor: state.cursorStyle }">
<div
class="map-box-inner"
class="map-bg"
:style="{
backgroundImage: `url(${imgBgObj.imgUrl})`,
transform: `scale(${state.imageChangeMultiple})`,
transformOrigin: '0 0',
width: `${imgBgObj.width}px`,
height: `${imgBgObj.height}px`
}"
@mousedown.stop="startDrawSelection"
@mousemove.stop="updateDrawSelection"
@mouseup.stop="endDrawSelection"
>
<img
:src="imgBgObj.imgUrl"
:style="{
width: imgBgObj.width + 'px',
height: imgBgObj.height + 'px'
}"
id="mapBg"
/>
<div
ref="mapBackgroundRef"
class="map-box-inner-dot"
class="map-box-inner"
@click="mapClick"
:class="state.isShowGrid ? 'grid-show' : ''"
:style="{
@ -155,9 +154,9 @@
@resizestop="(x, y, width, height) => resizeEnd(x, y, width, height, item, index)"
@activated="() => activatedHandle(item, index)"
@deactivated="deactivatedHandle"
:draggable="item.draggable"
:resizable="item.resizable"
:rotatable="item.rotatable"
:draggable="item.draggable && !state.prohibitedOperation"
:resizable="item.resizable && !state.prohibitedOperation"
:rotatable="item.rotatable && !state.prohibitedOperation"
:lock-aspect-ratio="item.lockAspectRatio"
style="border: none"
>
@ -212,7 +211,7 @@
src="https://api.znkjfw.com/admin-api/infra/file/4/get/区域_png_179_1739327151876.png"
alt=""
:style="{
width: item.locationWide + 'px',
width: item.locationWidee + 'px',
height: item.locationDeep + 'px',
border: currentItemIndex === index ? '1px dashed #000' : 'none'
}"
@ -324,6 +323,7 @@
/>
</div>
</div>
<!-- 节点编辑 -->
<editNodeProperties
ref="editNodePropertiesRef"
@ -418,7 +418,7 @@ const activatedHandle = (item, index) => {
currentItemIndex.value = index
//
if (toolbarSwitchType.value === 'editNode' && item.type !== 7) {
if (toolbarSwitchType.value === 'editNode') {
editNodePropertiesRef.value.open(JSON.parse(JSON.stringify(item)))
}
}
@ -476,7 +476,7 @@ const mapClick = (e) => {
locationDeep: 16,
locationWide: 16,
angle: 0,
draggable: false,
draggable: true,
resizable: false,
rotatable: false,
lockAspectRatio: false, //
@ -737,7 +737,9 @@ const state = reactive({
inputBoxValue: '', //
isShowLayer: false, //
measureDistancesPoints: [], //
measureDistancesNum: 0 //
measureDistancesNum: 0, //
imageChangeMultiple: 1, //
prohibitedOperation: false //
})
const toolbarClick = (item) => {
let type = item.switchType
@ -782,6 +784,17 @@ const toolbarClick = (item) => {
state.measureDistancesNum = 0 //
}
//
if (
toolbarSwitchType.value === 'ranging' ||
toolbarSwitchType.value === 'lineLibrary' ||
toolbarSwitchType.value === 'region'
) {
state.prohibitedOperation = true
} else {
state.prohibitedOperation = false
}
switch (type) {
case 'open':
//
@ -903,18 +916,16 @@ const toolbarClick = (item) => {
break
case 'larger':
//
if (imgBgObj.width < 10000) {
imgBgObj.width *= 1.2
imgBgObj.height *= 1.2
if (state.imageChangeMultiple < 3) {
state.imageChangeMultiple *= 1.2
} else {
message.warning('不能在放大了')
}
break
case 'smaller':
//
if (imgBgObj.width > 500) {
imgBgObj.width *= 0.8
imgBgObj.height *= 0.8
if (state.imageChangeMultiple > 0.3) {
state.imageChangeMultiple *= 0.8
} else {
message.warning('不能在缩小了')
}
@ -964,8 +975,8 @@ const startDrawSelection = (event) => {
const backgroundRect = mapBackgroundRef.value.getBoundingClientRect()
const x = Number(event.clientX) - backgroundRect.left
const y = event.clientY - backgroundRect.top
const x = disposeEventPoints(event).x
const y = disposeEventPoints(event).y
//
if (x >= 0 && x <= backgroundRect.width && y >= 0 && y <= backgroundRect.height) {
@ -981,9 +992,8 @@ const startDrawSelection = (event) => {
const updateDrawSelection = (event) => {
if (toolbarSwitchType.value !== 'lineLibrary' && toolbarSwitchType.value !== 'region') return
if (state.drawSelectionAreaShow) {
const backgroundRect = mapBackgroundRef.value.getBoundingClientRect()
const x = event.clientX - backgroundRect.left
const y = event.clientY - backgroundRect.top
const x = disposeEventPoints(event).x
const y = disposeEventPoints(event).y
state.drawSelectionAreaBox.width = Number(x) - Number(state.drawSelectionStartPoint.x)
state.drawSelectionAreaBox.height = Number(y) - Number(state.drawSelectionStartPoint.y)
@ -1112,33 +1122,26 @@ const computedLineWidth = computed(() => {
//
const measureDistancesClick = (event) => {
//
const x = event.clientX
const y = event.clientY
const x = disposeEventPoints(event).x
const y = disposeEventPoints(event).y
//
const backgroundRect = mapBackgroundRef.value.getBoundingClientRect()
if (
x >= backgroundRect.left &&
x <= backgroundRect.right &&
y >= backgroundRect.top &&
y <= backgroundRect.bottom
) {
if (state.measureDistancesPoints.length === 2) {
//
state.measureDistancesPoints = []
state.measureDistancesNum = null
} else {
//
const offsetX = x
const offsetY = y
state.measureDistancesPoints.push({ x: offsetX, y: offsetY })
if (state.measureDistancesPoints.length === 2) {
//
state.measureDistancesPoints = []
state.measureDistancesNum = null
} else {
//
const offsetX = x - backgroundRect.left
const offsetY = y - backgroundRect.top
state.measureDistancesPoints.push({ x: offsetX, y: offsetY })
if (state.measureDistancesPoints.length === 2) {
//
state.measureDistancesNum = calculateDistance(
state.measureDistancesPoints[0],
state.measureDistancesPoints[1]
)
}
//
state.measureDistancesNum = calculateDistance(
state.measureDistancesPoints[0],
state.measureDistancesPoints[1]
)
}
}
}
@ -1150,7 +1153,9 @@ const imgBgObj = reactive({
width: '',
height: '',
floor: '',
area: ''
area: '',
resolution: 0.05,
origin: [-54.4, -34.2]
})
//
const { query } = useRoute() //
@ -1201,7 +1206,7 @@ const getAllNodeList = async () => {
item.dataList = []
item.locationDeep = 16
item.locationWide = 16
item.draggable = false
item.draggable = true
item.resizable = false
item.rotatable = false
item.lockAspectRatio = true
@ -1211,7 +1216,7 @@ const getAllNodeList = async () => {
item.locationDeep = item.dataList[0].locationDeep
item.locationWide = item.dataList[0].locationWide
item.draggable = true
item.resizable = false
item.resizable = true
item.rotatable = false
item.lockAspectRatio = true
} else if (item.type === 3 || item.type === 4) {
@ -1220,7 +1225,7 @@ const getAllNodeList = async () => {
item.locationDeep = item.dataObj.locationDeep
item.locationWide = item.dataObj.locationWide
item.draggable = true
item.resizable = false
item.resizable = true
item.rotatable = false
item.lockAspectRatio = true
} else if (item.type === 7) {
@ -1285,33 +1290,50 @@ const layerSelectionSuccess = (typeList) => {
})
}
//
const disposeEventPoints = (event) => {
const rect = mapBackgroundRef.value.getBoundingClientRect()
const scrollLeft = mapBackgroundRef.value.scrollLeft //
const scrollTop = mapBackgroundRef.value.scrollTop //
const devicePixelRatio = window.devicePixelRatio || 1
//
const x = (event.clientX - rect.left + scrollLeft) / state.imageChangeMultiple / devicePixelRatio
const y = (event.clientY - rect.top + scrollTop) / state.imageChangeMultiple / devicePixelRatio
//
const actualLocationX = (x - imgBgObj.origin[0]) / imgBgObj.resolution
const actualLocationY = (y - imgBgObj.origin[1]) / imgBgObj.resolution
return {
x,
y,
actualLocationX,
actualLocationY
}
}
onMounted(() => {
getMapList()
})
</script>
<style lang="scss" scoped>
.map-box {
width: 100%;
.map-container {
position: relative;
.map-box-inner {
width: 100%;
overflow: auto;
border: 1px solid #ccc;
// height: 80vh;
.map-bg {
background-size: contain;
background-repeat: no-repeat;
position: relative;
width: 100%;
.map-box-inner-dot {
position: absolute;
top: 0;
left: 0;
}
.grid-show {
background: linear-gradient(-90deg, rgba(0, 0, 0, 0.1) 1px, transparent 1px),
linear-gradient(rgba(0, 0, 0, 0.1) 1px, transparent 1px);
background-size:
10px 10px,
10px 10px;
}
}
.map-box-inner {
position: absolute;
top: 0;
left: 0;
}
}
@ -1411,4 +1433,12 @@ onMounted(() => {
width: 80px;
margin-left: 4px;
}
.grid-show {
background: linear-gradient(-90deg, rgba(0, 0, 0, 0.1) 1px, transparent 1px),
linear-gradient(rgba(0, 0, 0, 0.1) 1px, transparent 1px);
background-size:
10px 10px,
10px 10px;
}
</style>

View File

@ -17,10 +17,7 @@
<!-- 背景区域 -->
<div
ref="background"
@mousedown="startSelection"
@mousemove="updateSelection"
@mouseup="endSelection"
style="margin-left: 200px; height: 100vh; position: relative; background-color: #fff"
style="margin-left: 200px; height: 200vh; position: relative; background-color: #fff"
>
<!-- 其他定位元素如图片 -->
<img
@ -29,146 +26,112 @@
alt="示例图片"
/>
<!-- 绘制框选区域 -->
<div
v-for="(rect, index) in selectionRects"
:key="index"
:style="{
position: 'absolute',
left: `${rect.x}px`,
top: `${rect.y}px`,
width: `${rect.width}px`,
height: `${rect.height}px`,
border: '2px solid blue',
backgroundColor: 'rgba(0, 0, 255, 0.1)'
}"
></div>
<!-- 显示测量结果 -->
<div v-if="distance !== null" style="position: fixed; top: 20px; left: 220px">
距离{{ distance.toFixed(2) }} 像素
</div>
<!-- 当前框选区域 -->
<div
v-if="isSelecting"
:style="{
position: 'absolute',
left: `${selectionBox.x}px`,
top: `${selectionBox.y}px`,
width: `${selectionBox.width}px`,
height: `${selectionBox.height}px`,
border: '2px dashed red',
backgroundColor: 'rgba(255, 0, 0, 0.1)'
}"
></div>
<!-- 绘制点位 -->
<div
v-for="(point, index) in points"
:key="index"
:style="{
position: 'absolute',
left: `${point.x}px`,
top: `${point.y}px`,
width: '10px',
height: '10px',
backgroundColor: isPointSelected(point) ? 'red' : 'black',
borderRadius: '50%'
}"
></div>
<!-- 绘制点和连线 -->
<template v-if="points.length > 0">
<div
v-for="(point, index) in points"
:key="index"
:style="{
position: 'absolute',
left: `${point.x}px`,
top: `${point.y}px`,
width: '10px',
height: '10px',
backgroundColor: 'red',
borderRadius: '50%'
}"
></div>
<div
v-if="points.length === 2"
:style="{
position: 'absolute',
left: `${points[0].x}px`,
top: `${points[0].y}px`,
width: `${lineWidth}px`,
height: '2px',
backgroundColor: 'blue',
transform: `rotate(${lineAngle}deg)`,
transformOrigin: '0 0'
}"
></div>
</template>
</div>
<!-- 确定按钮 -->
<button @click="confirmSelection" style="position: fixed; top: 20px; left: 220px">
确定
</button>
</div>
</template>
<script>
import { ref, computed, onMounted } from 'vue'
<script setup>
const points = ref([]) //
const distance = ref(null) //
const background = ref()
export default {
setup() {
const background = ref(null) // DOM
const points = ref([
{ x: 10, y: 10 },
{ x: 30, y: 100 },
{ x: 40, y: 40 },
{ x: 230, y: 400 },
{ x: 750, y: 640 }
]) //
const isSelecting = ref(false) //
const selectionBox = ref({ x: 0, y: 0, width: 0, height: 0 }) //
const selectionRects = ref([]) //
const startPos = ref({ x: 0, y: 0 }) //
//
const calculateDistance = (point1, point2) => {
const dx = point2.x - point1.x
const dy = point2.y - point1.y
return Math.sqrt(dx * dx + dy * dy)
}
//
const isPointInRect = (point, rect) => {
return (
point.x >= rect.x &&
point.x <= rect.x + rect.width &&
point.y >= rect.y &&
point.y <= rect.y + rect.height
)
}
// 线
const lineAngle = computed(() => {
if (points.value.length === 2) {
const dx = points.value[1].x - points.value[0].x
const dy = points.value[1].y - points.value[0].y
return Math.atan2(dy, dx) * (180 / Math.PI)
}
return 0
})
//
const isPointSelected = (point) => {
return selectionRects.value.some((rect) => isPointInRect(point, rect))
}
// 线
const lineWidth = computed(() => {
if (points.value.length === 2) {
return calculateDistance(points.value[0], points.value[1])
}
return 0
})
//
const startSelection = (event) => {
const backgroundRect = background.value.getBoundingClientRect()
const x = event.clientX - backgroundRect.left
const y = event.clientY - backgroundRect.top
//
const handleClick = (event) => {
//
const x = event.clientX + window.scrollX
const y = event.clientY + window.scrollY
//
if (x >= 0 && x <= backgroundRect.width && y >= 0 && y <= backgroundRect.height) {
isSelecting.value = true
startPos.value = { x, y }
selectionBox.value = { x, y, width: 0, height: 0 }
//
const backgroundRect = background.value.getBoundingClientRect()
if (
x >= backgroundRect.left + window.scrollX &&
x <= backgroundRect.right + window.scrollX &&
y >= backgroundRect.top + window.scrollY &&
y <= backgroundRect.bottom + window.scrollY
) {
if (points.value.length === 2) {
//
points.value = []
distance.value = null
} else {
//
points.value.push({ x, y })
if (points.value.length === 2) {
//
distance.value = calculateDistance(points.value[0], points.value[1])
}
}
//
const updateSelection = (event) => {
if (isSelecting.value) {
const backgroundRect = background.value.getBoundingClientRect()
const x = event.clientX - backgroundRect.left
const y = event.clientY - backgroundRect.top
selectionBox.value.width = x - startPos.value.x
selectionBox.value.height = y - startPos.value.y
}
}
//
const endSelection = () => {
if (isSelecting.value) {
isSelecting.value = false
selectionRects.value.push({ ...selectionBox.value })
}
}
//
const confirmSelection = () => {
const selectedPoints = points.value.filter((point) => isPointSelected(point))
console.log('选中的点位:', selectedPoints)
alert(`选中的点位:${JSON.stringify(selectedPoints)}`)
}
return {
background,
points,
isSelecting,
selectionBox,
selectionRects,
startSelection,
updateSelection,
endSelection,
confirmSelection,
isPointSelected
}
}
}
//
onMounted(() => {
window.addEventListener('click', handleClick)
})
//
onUnmounted(() => {
window.removeEventListener('click', handleClick)
})
</script>
<style scoped>