绘制路线

This commit is contained in:
yyy 2025-02-17 16:07:07 +08:00
parent c7939bd39a
commit d5fe81953d
5 changed files with 583 additions and 120 deletions

View File

@ -0,0 +1,189 @@
<template>
<div>
<!-- SVG 画布 -->
<svg
ref="svgRef"
width="500"
height="300"
@mousedown="handleMouseDown"
@mousemove="handleMouseMove"
@mouseup="handleMouseUp"
>
<!-- 绘制所有曲线 -->
<path
v-for="(curve, index) in curveList"
:key="index"
:d="getCurvePath(curve)"
stroke="#000"
fill="none"
stroke-width="2"
:class="{ selected: selectedCurve === curve }"
@click="svgClick(curve)"
/>
<!-- 绘制控制点和连线 -->
<line
v-if="selectedCurve"
:x1="selectedCurve.start.x"
:y1="selectedCurve.start.y"
:x2="selectedCurve.control1.x"
:y2="selectedCurve.control1.y"
stroke="gray"
stroke-dasharray="2"
/>
<line
v-if="selectedCurve"
:x1="selectedCurve.end.x"
:y1="selectedCurve.end.y"
:x2="selectedCurve.control2.x"
:y2="selectedCurve.control2.y"
stroke="gray"
stroke-dasharray="2"
/>
<!-- 绘制起点终点和控制点 -->
<circle
v-for="(curve, index) in curveList"
:key="'start' + index"
:cx="curve.start.x"
:cy="curve.start.y"
r="5"
fill="red"
@mousedown="startDrag($event, curve, 'start')"
/>
<circle
v-for="(curve, index) in curveList"
:key="'end' + index"
:cx="curve.end.x"
:cy="curve.end.y"
r="5"
fill="red"
@mousedown="startDrag($event, curve, 'end')"
/>
<circle
v-if="selectedCurve"
:cx="selectedCurve.control1.x"
:cy="selectedCurve.control1.y"
r="5"
fill="green"
@mousedown="startDrag($event, selectedCurve, 'control1')"
/>
<circle
v-if="selectedCurve"
:cx="selectedCurve.control2.x"
:cy="selectedCurve.control2.y"
r="5"
fill="green"
@mousedown="startDrag($event, selectedCurve, 'control2')"
/>
</svg>
<!-- 显示控制点坐标 -->
<div class="points" v-if="selectedCurve">
<p>起点: ({{ selectedCurve.start.x }}, {{ selectedCurve.start.y }})</p>
<p>控制点 1: ({{ selectedCurve.control1.x }}, {{ selectedCurve.control1.y }})</p>
<p>控制点 2: ({{ selectedCurve.control2.x }}, {{ selectedCurve.control2.y }})</p>
<p>终点: ({{ selectedCurve.end.x }}, {{ selectedCurve.end.y }})</p>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const props = defineProps({
mapRouteList: {
type: Array,
default: () => []
}
})
const svgRef = ref(null) // SVG
const curveList = computed(() => props.mapRouteList) // 线
const selectedCurve = ref(null) // 线
const isDragging = ref(false) //
const dragTarget = ref(null) //
//
const getMousePos = (event) => {
const rect = svgRef.value.getBoundingClientRect()
return {
x: event.clientX - rect.left,
y: event.clientY - rect.top
}
}
//
const startDrag = (event, curve, target) => {
event.preventDefault()
isDragging.value = true
selectedCurve.value = curve
dragTarget.value = target
}
//
const handleMouseDown = (event) => {
const mousePos = getMousePos(event)
// 线
if (!isDragging.value && curveList.value.length === 0) {
const newCurve = {
start: mousePos,
end: mousePos,
control1: { x: mousePos.x + 50, y: mousePos.y - 50 },
control2: { x: mousePos.x + 100, y: mousePos.y - 50 }
}
curveList.value.push(newCurve)
selectedCurve.value = newCurve
dragTarget.value = 'end'
isDragging.value = true
}
}
//
const handleMouseMove = (event) => {
if (!isDragging.value || !selectedCurve.value) return
const mousePos = getMousePos(event)
//
if (dragTarget.value === 'start') {
selectedCurve.value.start = mousePos
} else if (dragTarget.value === 'end') {
selectedCurve.value.end = mousePos
} else if (dragTarget.value === 'control1') {
selectedCurve.value.control1 = mousePos
} else if (dragTarget.value === 'control2') {
selectedCurve.value.control2 = mousePos
}
}
//
const handleMouseUp = () => {
isDragging.value = false
dragTarget.value = null
}
// 线
const getCurvePath = (curve) => {
return `M ${curve.startPointX} ${curve.startPointY} C ${curve.beginControlX} ${curve.beginControlX}, ${curve.control2.x} ${curve.control2.y}, ${curve.endPointX} ${curve.endPointY}`
}
const svgClick = (item) => {
console.log(item)
}
</script>
<style scoped>
svg {
border: 1px solid #000;
cursor: crosshair;
}
path.selected {
stroke: blue;
}
.points {
margin-top: 10px;
font-size: 14px;
}
</style>

View File

@ -246,6 +246,16 @@ const typeChange = () => {
form.value.inDirection = undefined
form.value.outDirection = undefined
}
if (form.value.type === 1) {
form.value.locationDeep = 10
form.value.locationWide = 10
}
if (form.value.type === 5 || form.value.type === 6) {
form.value.locationDeep = 30
form.value.locationWide = 30
}
}
//
const directionChange = (e) => {

View File

@ -47,38 +47,48 @@
}"
>
<div v-if="item.type == 2" style="width: 100%; height: 100%">
<el-popover
placement="top-start"
trigger="hover"
width="auto"
>
<el-popover placement="top-start" trigger="hover" width="auto">
<template #reference>
<img :src="item.imgUrl" alt="" style="width: 100%; height: 100%" @dblclick="storeClick(item)"/>
<img
:src="item.imgUrl"
alt=""
style="width: 100%; height: 100%"
@dblclick="storeClick(item)"
/>
</template>
<div class="indexpage-container-box-point-item-inner-popover">
<div class="indexpage-container-box-point-item-inner-popover-item" style="margin-bottom: 8px;">
<div
class="indexpage-container-box-point-item-inner-popover-item"
style="margin-bottom: 8px"
>
<div class="indexpage-container-box-point-item-inner-popover-name">
库位名
库位名
</div>
<div class="indexpage-container-box-point-item-inner-popover-value">
{{ item.showData.locationNo || '' }}
</div>
</div>
<div class="indexpage-container-box-point-item-inner-popover-value">
{{ item.showData.locationNo || '' }}
</div>
</div>
<div class="indexpage-container-box-point-item-inner-popover-item" style="margin-bottom: 8px;">
<div
class="indexpage-container-box-point-item-inner-popover-item"
style="margin-bottom: 8px"
>
<div class="indexpage-container-box-point-item-inner-popover-name">
所属线库
所属线库
</div>
<div class="indexpage-container-box-point-item-inner-popover-value">
{{ item.showData.laneName || '' }}
</div>
</div>
<div class="indexpage-container-box-point-item-inner-popover-value">
{{ item.showData.laneName || '' }}
</div>
</div>
<div class="indexpage-container-box-point-item-inner-popover-item" style="margin-bottom: 8px;">
<div
class="indexpage-container-box-point-item-inner-popover-item"
style="margin-bottom: 8px"
>
<div class="indexpage-container-box-point-item-inner-popover-name">
所属区域
</div>
<div class="indexpage-container-box-point-item-inner-popover-value">
{{ item.showData.areaName || '' }}
</div>
所属区域
</div>
<div class="indexpage-container-box-point-item-inner-popover-value">
{{ item.showData.areaName || '' }}
</div>
</div>
<!-- <div class="indexpage-container-box-point-item-inner-popover-item" style="margin-bottom: 8px;">
<div class="indexpage-container-box-point-item-inner-popover-name">
@ -334,11 +344,13 @@ onBeforeUnmount(() => {
width: 100%;
height: auto;
}
.indexpage-container-box-point-item-inner-popover-item{
.indexpage-container-box-point-item-inner-popover-item {
display: flex;
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 14px;
color: #0D162A;
font-family:
PingFangSC,
PingFang SC;
font-weight: 400;
font-size: 14px;
color: #0d162a;
}
</style>

View File

@ -325,6 +325,111 @@
:maxlength="30"
class="input-box-class"
/>
<!-- 编辑路径 -->
<div v-if="imgBgObj.width && imgBgObj.height">
<!-- SVG 画布 -->
<svg
ref="svgRef"
:width="imgBgObj.width"
:height="imgBgObj.height"
@mousedown="handleMouseDown"
@mousemove="handleMouseMove"
@mouseup="handleMouseUp"
>
<!-- 绘制所有曲线 -->
<path
v-for="(curve, index) in state.mapRouteList"
:key="index"
:d="getCurvePath(curve)"
stroke="#000"
fill="none"
stroke-width="2"
:class="{ selected: state.selectedCurve === curve }"
@click="svgClick(curve)"
/>
<!-- 绘制控制点和连线 -->
<!-- 第一条控制线 -->
<line
v-if="state.selectedCurve"
:x1="state.selectedCurve.startPointX"
:y1="state.selectedCurve.startPointY"
:x2="state.selectedCurve.beginControlX"
:y2="state.selectedCurve.beginControlY"
stroke="gray"
stroke-dasharray="2"
/>
<!-- 第二条控制线 -->
<line
v-if="state.selectedCurve"
:x1="state.selectedCurve.endPointX"
:y1="state.selectedCurve.endPointY"
:x2="state.selectedCurve.endControlX"
:y2="state.selectedCurve.endControlY"
stroke="gray"
stroke-dasharray="2"
/>
<!-- 绘制起点终点和控制点 -->
<!-- 起点 -->
<circle
style="z-index: 9999"
v-for="(curve, index) in state.mapRouteList"
:key="'start' + index"
:cx="curve.startPointX"
:cy="curve.startPointY"
r="5"
fill="red"
@mousedown="startDrag($event, curve, 'start')"
/>
<!-- 终点 -->
<circle
style="z-index: 9999"
v-for="(curve, index) in state.mapRouteList"
:key="'end' + index"
:cx="curve.endPointX"
:cy="curve.endPointY"
r="5"
fill="red"
@mousedown="startDrag($event, curve, 'end')"
/>
<!-- 控制点1 -->
<circle
style="z-index: 9999"
v-if="state.selectedCurve"
:cx="state.selectedCurve.beginControlX"
:cy="state.selectedCurve.beginControlY"
r="5"
fill="green"
@mousedown="startDrag($event, state.selectedCurve, 'control1')"
/>
<!-- 控制点2 -->
<circle
style="z-index: 9999"
v-if="state.selectedCurve"
:cx="state.selectedCurve.endControlX"
:cy="state.selectedCurve.endControlY"
r="5"
fill="green"
@mousedown="startDrag($event, state.selectedCurve, 'control2')"
/>
</svg>
<!-- 显示控制点坐标 -->
<div class="points" v-if="state.selectedCurve">
<p
>起点: ({{ state.selectedCurve.startPointX }}, {{ state.selectedCurve.startPointY }})</p
>
<p
>控制点 1: ({{ state.selectedCurve.beginControlX }},
{{ state.selectedCurve.beginControlY }})</p
>
<p
>控制点 2: ({{ state.selectedCurve.endControlX }},
{{ state.selectedCurve.endControlY }})</p
>
<p>终点: ({{ state.selectedCurve.endPointX }}, {{ state.selectedCurve.endPointY }})</p>
</div>
</div>
</div>
</div>
@ -469,16 +574,17 @@ const backNextStep = () => {
//
const mapClick = (e) => {
//
const x = disposeEventPoints(e).x
const y = disposeEventPoints(e).y
//
if (toolbarSwitchType.value === 'drawNodes') {
//
allMapPointInfo.value.push({
positionMapId: imgBgObj.positionMapId, //id
layerSelectionShow: true,
locationX: e.offsetX,
locationY: e.offsetY,
locationDeep: 16,
locationWide: 16,
locationX: x,
locationY: y,
locationDeep: 10,
locationWide: 10,
angle: 0,
draggable: true,
resizable: false,
@ -495,8 +601,8 @@ const mapClick = (e) => {
//
if (toolbarSwitchType.value === 'text') {
state.showInputBox = true
state.inputBoxStyle.locationX = e.offsetX
state.inputBoxStyle.locationY = e.offsetY
state.inputBoxStyle.locationX = x
state.inputBoxStyle.locationY = y
//
setTimeout(() => {
@ -507,7 +613,6 @@ const mapClick = (e) => {
if (toolbarSwitchType.value === 'ranging') {
measureDistancesClick(e)
}
disposeEventPoints(e)
}
//
const textFormSuccess = (form) => {
@ -744,7 +849,11 @@ const state = reactive({
measureDistancesPoints: [], //
measureDistancesNum: 0, //
imageChangeMultiple: 1, //
prohibitedOperation: false //
prohibitedOperation: false, //
mapRouteList: [], //线
isDragging: false, //
currentDragTarget: '', //
selectedCurve: '' // 线
})
const toolbarClick = (item) => {
let type = item.switchType
@ -1046,12 +1155,12 @@ const clickDrawSelectionArea = () => {
})
})
console.log(state.drawSelectionPointList, '选中的')
//
state.allDrawSelectionAreaBox = []
//
let binLocation = state.drawSelectionPointList.filter((item) => item.type === 2)
//
let routeList = state.drawSelectionPointList.filter((item) => item.type === 1)
if (toolbarSwitchType.value === 'lineLibrary') {
//线
@ -1075,6 +1184,34 @@ const clickDrawSelectionArea = () => {
}
if (toolbarSwitchType.value === 'drawRoute') {
//
if (routeList.length !== 2) {
message.warning('只能选择两个路径点')
return
}
let curve = {
startPointX: routeList[0].locationX + routeList[0].locationWide / 2, //
startPointY: routeList[0].locationY + routeList[0].locationDeep / 2, //
endPointX: routeList[1].locationX + routeList[1].locationWide / 2, //
endPointY: routeList[1].locationY + routeList[1].locationWide / 2, //
beginControlX: routeList[0].locationX + routeList[0].locationWide / 2 + 50, //x
beginControlY: routeList[0].locationY + routeList[0].locationWide / 2 + 50, //y
endControlX: routeList[1].locationX + routeList[1].locationWide / 2 - 50, //x
endControlY: routeList[1].locationY + routeList[1].locationWide / 2 - 50, //y
expansionZoneFront: 0, //
expansionZoneAfter: 0, //
expansionZoneLeft: 0, //
expansionZoneRight: 0, //
method: 0, // 0.线 1.线2.线3.线 4.线
direction: 2, // 1. 2.,
forwardSpeedLimit: '1.5', //
reverseSpeedLimit: '0.4' //
}
state.selectedCurve = curve
state.mapRouteList.push(curve)
state.currentDragTarget = 'end'
state.isDragging = true
}
}
//线
@ -1115,6 +1252,51 @@ const isStraightLine = (binLocation) => {
return Math.abs(currentSlope - slope) < Number.EPSILON //
})
}
//线
//
const startDrag = (event, curve, target) => {
console.log(event, curve, target)
event.preventDefault()
state.isDragging = true
state.selectedCurve = curve
state.currentDragTarget = target
}
//
const handleMouseDown = (event) => {}
//
const handleMouseMove = (event) => {
console.log(event)
if (!state.isDragging || !state.selectedCurve) return
const mousePos = disposeEventPoints(event)
//
if (state.currentDragTarget === 'start') {
state.selectedCurve.startPointX = mousePos.x
state.selectedCurve.startPointY = mousePos.y
} else if (state.currentDragTarget === 'end') {
state.selectedCurve.endPointX = mousePos.x
state.selectedCurve.endPointY = mousePos.y
} else if (state.currentDragTarget === 'control1') {
state.selectedCurve.beginControlX = mousePos.x
state.selectedCurve.beginControlY = mousePos.y
} else if (state.currentDragTarget === 'control2') {
state.selectedCurve.endControlX = mousePos.x
state.selectedCurve.endControlY = mousePos.y
}
}
//
const handleMouseUp = () => {
state.isDragging = false
state.currentDragTarget = null
}
// 线
const getCurvePath = (curve) => {
return `M ${curve.startPointX} ${curve.startPointY} C ${curve.beginControlX} ${curve.beginControlY}, ${curve.endControlX} ${curve.endControlY}, ${curve.endPointX} ${curve.endPointY}`
}
//
//
@ -1229,8 +1411,17 @@ const getAllNodeList = async () => {
if (item.type === 1 || item.type === 5 || item.type === 6) {
item.dataObj = {}
item.dataList = []
item.locationDeep = 16
item.locationWide = 16
item.locationDeep = 10
item.locationWide = 10
item.draggable = true
item.resizable = false
item.rotatable = false
item.lockAspectRatio = true
} else if (item.type === 5 || item.type === 6) {
item.dataObj = {}
item.dataList = []
item.locationDeep = 30
item.locationWide = 30
item.draggable = true
item.resizable = false
item.rotatable = false
@ -1330,8 +1521,6 @@ const disposeEventPoints = (event) => {
const actualLocationX = imgBgObj.origin[0] + x * imgBgObj.resolution
const actualLocationY = imgBgObj.origin[1] + (imgBgObj.height - y) * imgBgObj.resolution
console.log(actualLocationX, actualLocationY, '实际坐标')
return {
x,
y,
@ -1468,4 +1657,17 @@ onMounted(() => {
10px 10px,
10px 10px;
}
svg {
cursor: crosshair;
path.selected {
stroke: blue;
}
.points {
margin-top: 10px;
font-size: 14px;
}
}
</style>

View File

@ -1,95 +1,145 @@
<template>
<div class="map-container" ref="mapContainer">
<!-- 地图 -->
<div
class="map"
:style="{
backgroundImage: `url(${mapImage})`,
width: `${mapWidth}px`,
height: `${mapHeight}px`
}"
@click="handleClick"
></div>
<svg
:width="width"
:height="height"
@mousedown="handleMouseDown"
@mousemove="handleMouseMove"
@mouseup="handleMouseUp"
>
<!-- 渲染直线或贝塞尔曲线 -->
<template v-for="(line, index) in lines" :key="index">
<!-- 直线 -->
<line
v-if="!line.isCurve"
:x1="line.startPointX"
:y1="line.startPointY"
:x2="line.endPointX"
:y2="line.endPointY"
stroke="black"
stroke-width="2"
@click="selectLine(index)"
/>
<!-- 显示坐标信息 -->
<div v-if="clickedPoint" class="coordinates">
<p>地图坐标 ({{ clickedPoint.mapX.toFixed(2) }}, {{ clickedPoint.mapY.toFixed(2) }})</p>
<p
>现场坐标 ({{ clickedPoint.actualX.toFixed(2) }},
{{ clickedPoint.actualY.toFixed(2) }})</p
>
</div>
</div>
<!-- 贝塞尔曲线 -->
<path
v-else
:d="getCurvePath(line)"
stroke="black"
stroke-width="2"
fill="none"
@click="selectLine(index)"
/>
<!-- 控制点 -->
<circle
v-if="line.isCurve"
:cx="line.controlPoint1.x"
:cy="line.controlPoint1.y"
r="5"
fill="red"
@mousedown="startDrag(index, 'controlPoint1', $event)"
/>
<circle
v-if="line.isCurve"
:cx="line.controlPoint2.x"
:cy="line.controlPoint2.y"
r="5"
fill="red"
@mousedown="startDrag(index, 'controlPoint2', $event)"
/>
</template>
</svg>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import mapImage from '@/assets/warehouse-map.png' //
import { ref, computed } from 'vue'
const origin = [-54.4, -34.2]
const resolution = 0.05
const width = 800
const height = 800
const lines = ref([
{ startPointX: 100, startPointY: 100, endPointX: 200, endPointY: 200 },
{ startPointX: 300, startPointY: 300, endPointX: 400, endPointY: 400 }
])
const mapContainer = ref(null) //
const mapWidth = ref(0) //
const mapHeight = ref(0) //
const clickedPoint = ref(null) //
//
onMounted(() => {
const img = new Image()
img.src = mapImage
img.onload = () => {
mapWidth.value = img.width
mapHeight.value = img.height
}
const dragging = ref({
isDragging: false,
lineIndex: null,
controlPoint: null // 'controlPoint1' 'controlPoint2'
})
//
const handleClick = (event) => {
const rect = mapContainer.value.getBoundingClientRect()
const scrollLeft = mapContainer.value.scrollLeft //
const scrollTop = mapContainer.value.scrollTop //
const devicePixelRatio = window.devicePixelRatio || 1
//
const initializedLines = ref(
lines.value.map((line) => ({
...line,
isCurve: false,
controlPoint1: { x: line.startPointX + 50, y: line.startPointY + 50 },
controlPoint2: { x: line.endPointX - 50, y: line.endPointY - 50 }
}))
)
//
const mapX = (event.clientX - rect.left + scrollLeft) / devicePixelRatio
const mapY = (event.clientY - rect.top + scrollTop) / devicePixelRatio
// 线
const getCurvePath = (line) => {
const { startPointX, startPointY, endPointX, endPointY, controlPoint1, controlPoint2 } = line
return `M ${startPointX} ${startPointY} C ${controlPoint1.x} ${controlPoint1.y}, ${controlPoint2.x} ${controlPoint2.y}, ${endPointX} ${endPointY}`
}
//
const actualX = origin[0] + mapX * resolution
const actualY = origin[1] + (mapHeight.value - mapY) * resolution
//
const startDrag = (lineIndex, controlPoint, event) => {
dragging.value = {
isDragging: true,
lineIndex,
controlPoint
}
event.preventDefault()
}
//
clickedPoint.value = {
mapX,
mapY,
actualX,
actualY
//
const handleMouseMove = (event) => {
if (dragging.value.isDragging) {
const rect = event.target.getBoundingClientRect()
const x = event.clientX - rect.left
const y = event.clientY - rect.top
const { lineIndex, controlPoint } = dragging.value
initializedLines.value[lineIndex][controlPoint] = { x, y }
}
}
//
const handleMouseUp = () => {
dragging.value = {
isDragging: false,
lineIndex: null,
controlPoint: null
}
}
// 线
const selectLine = (index) => {
const selectedLine = initializedLines.value[index]
emit('select', selectedLine)
}
// 线
const handleMouseDown = (event) => {
if (!dragging.value.isDragging) {
const index = initializedLines.value.findIndex((line) => {
const path = getCurvePath(line)
const point = new DOMPoint(event.offsetX, event.offsetY)
return document
.createElementNS('http://www.w3.org/2000/svg', 'path')
.isPointInStroke(new Path2D(path), point)
})
if (index !== -1) {
initializedLines.value[index].isCurve = true
}
}
}
</script>
<style scoped>
.map-container {
position: relative;
width: 100%;
height: 80vh;
overflow: auto;
svg {
border: 1px solid #ccc;
}
.map {
background-size: contain;
background-repeat: no-repeat;
}
.coordinates {
position: fixed;
top: 120px;
left: 220px;
background-color: rgba(255, 255, 255, 0.8);
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
}
</style>