地图路线拖动绘制
This commit is contained in:
parent
64d93c27df
commit
59a7eea5fe
@ -2,7 +2,12 @@
|
||||
<template>
|
||||
<div :style="wrapperStyle" class="vue-ruler-wrapper" onselectstart="return false;" ref="el">
|
||||
<section>
|
||||
<div ref="horizontalRuler" class="vue-ruler-h" @mousedown.stop="horizontalDragRuler">
|
||||
<div
|
||||
ref="horizontalRuler"
|
||||
class="vue-ruler-h"
|
||||
@mousedown.stop="horizontalDragRuler"
|
||||
:style="{ width: width + 'px' }"
|
||||
>
|
||||
<span
|
||||
v-for="(item, index) in xScale"
|
||||
:key="index"
|
||||
@ -11,7 +16,12 @@
|
||||
>{{ item.id }}</span
|
||||
>
|
||||
</div>
|
||||
<div ref="verticalRuler" class="vue-ruler-v" @mousedown.stop="verticalDragRuler">
|
||||
<div
|
||||
ref="verticalRuler"
|
||||
class="vue-ruler-v"
|
||||
@mousedown.stop="verticalDragRuler"
|
||||
:style="{ height: height + 'px' }"
|
||||
>
|
||||
<span
|
||||
v-for="(item, index) in yScale"
|
||||
:key="index"
|
||||
@ -82,6 +92,12 @@ const props = defineProps({
|
||||
type: Number,
|
||||
default: 100,
|
||||
validator: (val) => val % 10 === 0
|
||||
},
|
||||
width: {
|
||||
type: Number
|
||||
},
|
||||
height: {
|
||||
type: Number
|
||||
}
|
||||
})
|
||||
|
||||
@ -166,12 +182,10 @@ onBeforeUnmount(() => {
|
||||
off(document, 'mouseup', dottedLineUp)
|
||||
off(window, 'resize', windowResize)
|
||||
})
|
||||
//function
|
||||
const init = () => {
|
||||
box()
|
||||
scaleCalc()
|
||||
}
|
||||
|
||||
const windowResize = () => {
|
||||
xScale.value = [{ id: 0 }]
|
||||
yScale.value = [{ id: 0 }]
|
||||
@ -193,8 +207,10 @@ const box = () => {
|
||||
getCalcRevise(xScale.value, contentLeft)
|
||||
getCalcRevise(yScale.value, contentTop)
|
||||
}
|
||||
windowWidth.value = document.documentElement.clientWidth - leftSpacing
|
||||
windowHeight.value = document.documentElement.clientHeight - topSpacing - 80
|
||||
// windowWidth.value = document.documentElement.clientWidth - leftSpacing
|
||||
// windowHeight.value = document.documentElement.clientHeight - topSpacing - 80
|
||||
windowWidth.value = props.width + 18
|
||||
windowHeight.value = props.height + 18
|
||||
rulerWidth = verticalRuler.value.clientWidth
|
||||
rulerHeight = horizontalRuler.value.clientHeight
|
||||
}
|
||||
@ -204,8 +220,8 @@ const setSpacing = () => {
|
||||
}
|
||||
// 计算刻度
|
||||
const scaleCalc = () => {
|
||||
getCalc(xScale.value, windowWidth.value)
|
||||
getCalc(yScale.value, windowHeight.value)
|
||||
getCalc(xScale.value, props.width)
|
||||
getCalc(yScale.value, props.height)
|
||||
}
|
||||
|
||||
//获取刻度
|
||||
@ -360,7 +376,7 @@ const dragVerticalLine = (id) => {
|
||||
}
|
||||
|
||||
.vue-ruler-h {
|
||||
width: calc(100% - 18px);
|
||||
// width: calc(100% - 18px);
|
||||
background-color: #333;
|
||||
height: 18px;
|
||||
left: 18px;
|
||||
@ -371,7 +387,7 @@ const dragVerticalLine = (id) => {
|
||||
|
||||
.vue-ruler-v {
|
||||
width: 18px;
|
||||
height: 85vh;
|
||||
// height: 85vh;
|
||||
top: 18px;
|
||||
opacity: 0.6;
|
||||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAAyCAMAAABmvHtTAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAlQTFRFMzMzAAAA////BqjYlAAAACBJREFUeNpiYGBEBwwMTGiAakI0NX7U9aOuHyGuBwgwAH6bBkAR6jkzAAAAAElFTkSuQmCC)
|
||||
|
@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<div class="edit-map-page">
|
||||
<div class="top-tool">
|
||||
<div class="top-tool-list">
|
||||
<div v-for="item in state.topToolList" :key="item.switchType" class="top-tool-item">
|
||||
@ -202,6 +203,14 @@
|
||||
</div>
|
||||
|
||||
<div class="map-container" ref="mapContainer" :style="{ cursor: state.cursorStyle }">
|
||||
<map-scale-tool
|
||||
v-if="imgBgObj.height && imgBgObj.width"
|
||||
@input="input"
|
||||
:value="presetLine"
|
||||
:step-length="50"
|
||||
:height="imgBgObj.height"
|
||||
:width="imgBgObj.width"
|
||||
>
|
||||
<div
|
||||
class="map-bg"
|
||||
:style="{
|
||||
@ -240,12 +249,14 @@
|
||||
@resizestop="(x, y, width, height) => resizeEnd(x, y, width, height, item, index)"
|
||||
@activated="() => activatedHandle(item, index)"
|
||||
@deactivated="deactivatedHandle"
|
||||
:draggable="item.draggable && !state.prohibitedOperation"
|
||||
:resizable="item.resizable && !state.prohibitedOperation"
|
||||
:rotatable="item.rotatable && !state.prohibitedOperation"
|
||||
:draggable="!state.prohibitedOperation && item.draggable"
|
||||
:resizable="!state.prohibitedOperation && item.resizable"
|
||||
:rotatable="!state.prohibitedOperation && item.rotatable"
|
||||
:lock-aspect-ratio="item.lockAspectRatio"
|
||||
style="border: none; z-index: 999"
|
||||
>
|
||||
<!-- 节点合集 -->
|
||||
<div @mousedown="startFromPoint(index, $event)">
|
||||
<!-- 1 路径点 -->
|
||||
<div
|
||||
v-if="item.type === 1 && item.layerSelectionShow"
|
||||
@ -327,6 +338,7 @@
|
||||
>
|
||||
{{ item.text }}
|
||||
</div>
|
||||
</div>
|
||||
</VueDragResizeRotate>
|
||||
|
||||
<div v-if="imgBgObj.width && imgBgObj.height">
|
||||
@ -337,6 +349,17 @@
|
||||
id="svgId"
|
||||
>
|
||||
<template v-for="(curve, index) in state.mapRouteList" :key="index">
|
||||
<!-- 实时绘制当前直线 -->
|
||||
<line
|
||||
v-if="state.isDrawing && toolbarSwitchType === 'clickDrawRoute'"
|
||||
:x1="Number(state.startDrawPoint.locationX)"
|
||||
:y1="Number(state.startDrawPoint.locationY)"
|
||||
:x2="Number(state.currentDrawX)"
|
||||
:y2="Number(state.currentDrawY)"
|
||||
stroke="#00329F"
|
||||
stroke-width="5"
|
||||
/>
|
||||
|
||||
<!-- 直线 -->
|
||||
<line
|
||||
v-if="curve.method === 0"
|
||||
@ -482,6 +505,7 @@
|
||||
class="input-box-class"
|
||||
/>
|
||||
</div>
|
||||
</map-scale-tool>
|
||||
</div>
|
||||
|
||||
<!-- 节点编辑 -->
|
||||
@ -528,6 +552,7 @@
|
||||
ref="itemAreaManagementDialogRef"
|
||||
:positionMapId="imgBgObj.positionMapId"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@ -567,6 +592,11 @@ const currentIndex = ref(0) //用于记录是哪条历史记录的
|
||||
const currentItemIndex = ref(-1) //用于记录是在编辑那个具体的节点、图标等
|
||||
const allMapPointInfo = ref([]) //所有的图标的列表
|
||||
|
||||
const presetLine = ref([])
|
||||
const input = (list) => {
|
||||
presetLine.value = list
|
||||
}
|
||||
|
||||
// 缩放停止
|
||||
const interfaceRefreshed = ref(true)
|
||||
const resizeEnd = (locationX, locationY, w, h, item, index) => {
|
||||
@ -955,11 +985,17 @@ const state = reactive({
|
||||
isActive: false
|
||||
},
|
||||
{
|
||||
switchType: 'drawRoute',
|
||||
switchType: 'clickDrawRoute',
|
||||
name: '绘制路线',
|
||||
icon: 'ep:semi-select',
|
||||
isActive: false
|
||||
},
|
||||
{
|
||||
switchType: 'drawRoute',
|
||||
name: '框选绘制',
|
||||
icon: 'ep:semi-select',
|
||||
isActive: false
|
||||
},
|
||||
{
|
||||
switchType: 'editRoute',
|
||||
name: '编辑路线',
|
||||
@ -1006,7 +1042,11 @@ const state = reactive({
|
||||
type: null
|
||||
}, //当前拖拽的目标(起点、终点、控制点)
|
||||
selectedCurve: '', // 当前选中的曲线
|
||||
svgZIndex: 9999
|
||||
startDrawPointIndex: -1, // 起始点的索引
|
||||
startDrawPoint: null,
|
||||
isDrawing: false,
|
||||
currentDrawX: 0,
|
||||
currentDrawY: 0
|
||||
})
|
||||
const toolbarClick = (item) => {
|
||||
let type = item.switchType
|
||||
@ -1063,7 +1103,10 @@ const toolbarClick = (item) => {
|
||||
|
||||
//禁止操作 在框选测距等操作时,禁止所有拖动等操作
|
||||
if (
|
||||
toolbarSwitchType.value === 'ranging' ||
|
||||
toolbarSwitchType.value === 'drawNodes' ||
|
||||
toolbarSwitchType.value === 'editNode' ||
|
||||
toolbarSwitchType.value === 'clickDrawRoute' ||
|
||||
toolbarSwitchType.value === 'drawRoute' ||
|
||||
toolbarSwitchType.value === 'createLineLibrary' ||
|
||||
toolbarSwitchType.value === 'createRegion'
|
||||
) {
|
||||
@ -1268,15 +1311,23 @@ const rotationFormSubmit = () => {
|
||||
allHistoryList.value[currentIndex.value][currentItemIndex.value].angle = state.rotationForm.angle
|
||||
}
|
||||
|
||||
//鼠标拖动绘制节点
|
||||
// 从点开始绘制
|
||||
const startFromPoint = (index, event) => {
|
||||
let list = allHistoryList.value[currentIndex.value]
|
||||
const point = list[index]
|
||||
state.startDrawPoint = point //开始点
|
||||
state.startDrawPointIndex = index
|
||||
state.isDrawing = true
|
||||
event.preventDefault() // 防止默认行为
|
||||
}
|
||||
//开始框选绘制
|
||||
const startDrawSelection = (event) => {
|
||||
if (
|
||||
toolbarSwitchType.value !== 'createLineLibrary' &&
|
||||
toolbarSwitchType.value !== 'createRegion' &&
|
||||
toolbarSwitchType.value !== 'drawRoute'
|
||||
)
|
||||
return
|
||||
|
||||
toolbarSwitchType.value === 'createLineLibrary' ||
|
||||
toolbarSwitchType.value === 'createRegion' ||
|
||||
toolbarSwitchType.value === 'drawRoute'
|
||||
) {
|
||||
const backgroundRect = mapBackgroundRef.value.getBoundingClientRect()
|
||||
|
||||
const x = disposeEventPoints(event).x
|
||||
@ -1289,42 +1340,151 @@ const startDrawSelection = (event) => {
|
||||
state.drawSelectionAreaBox = { x: x, y: y, width: 0, height: 0 }
|
||||
}
|
||||
|
||||
// 阻止默认行为(避免选中图片或文本)
|
||||
event.preventDefault()
|
||||
event.preventDefault() // 阻止默认行为(避免选中图片或文本)
|
||||
return
|
||||
}
|
||||
}
|
||||
// 更新框选区域
|
||||
const updateDrawSelection = (event) => {
|
||||
if (
|
||||
toolbarSwitchType.value !== 'createLineLibrary' &&
|
||||
toolbarSwitchType.value !== 'createRegion' &&
|
||||
toolbarSwitchType.value !== 'drawRoute'
|
||||
)
|
||||
return
|
||||
toolbarSwitchType.value === 'createLineLibrary' ||
|
||||
toolbarSwitchType.value === 'createRegion' ||
|
||||
toolbarSwitchType.value === 'drawRoute'
|
||||
) {
|
||||
if (state.drawSelectionAreaShow) {
|
||||
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)
|
||||
state.drawSelectionAreaBox = {
|
||||
x: Math.min(state.drawSelectionStartPoint.x, x),
|
||||
y: Math.min(state.drawSelectionStartPoint.y, y),
|
||||
width: Math.abs(Number(x) - Number(state.drawSelectionStartPoint.x)),
|
||||
height: Math.abs(Number(y) - Number(state.drawSelectionStartPoint.y))
|
||||
}
|
||||
// 阻止默认行为(避免选中图片或文本)
|
||||
event.preventDefault()
|
||||
}
|
||||
event.preventDefault() // 阻止默认行为(避免选中图片或文本)
|
||||
return
|
||||
}
|
||||
if (toolbarSwitchType.value === 'clickDrawRoute') {
|
||||
// 实时绘制
|
||||
if (state.isDrawing) {
|
||||
const x = disposeEventPoints(event).x
|
||||
const y = disposeEventPoints(event).y
|
||||
|
||||
state.currentDrawX = x
|
||||
state.currentDrawY = y
|
||||
}
|
||||
}
|
||||
event.preventDefault() // 阻止默认行为(避免选中图片或文本)
|
||||
return
|
||||
}
|
||||
//结束框选绘制
|
||||
const endDrawSelection = (event) => {
|
||||
if (
|
||||
toolbarSwitchType.value !== 'createLineLibrary' &&
|
||||
toolbarSwitchType.value !== 'createRegion' &&
|
||||
toolbarSwitchType.value !== 'drawRoute'
|
||||
)
|
||||
return
|
||||
toolbarSwitchType.value === 'createLineLibrary' ||
|
||||
toolbarSwitchType.value === 'createRegion' ||
|
||||
toolbarSwitchType.value === 'drawRoute'
|
||||
) {
|
||||
state.drawSelectionAreaShow = false
|
||||
state.allDrawSelectionAreaBox.push({ ...state.drawSelectionAreaBox })
|
||||
state.drawSelectionAreaBox = { x: 0, y: 0, width: 0, height: 0 }
|
||||
event.preventDefault() // 阻止默认行为(避免选中图片或文本)
|
||||
return
|
||||
}
|
||||
if (toolbarSwitchType.value === 'clickDrawRoute') {
|
||||
if (state.isDrawing) {
|
||||
// 找到最近的终点
|
||||
const endPointIndex = findClosestPoint(state.currentDrawX, state.currentDrawY)
|
||||
|
||||
// 阻止默认行为(避免选中图片或文本)
|
||||
event.preventDefault()
|
||||
if (endPointIndex !== null && endPointIndex !== state.startDrawPointIndex) {
|
||||
let list = allHistoryList.value[currentIndex.value]
|
||||
const endPoint = list[endPointIndex]
|
||||
const newLine = {
|
||||
startPointX: state.startDrawPoint.locationX,
|
||||
startPointY: state.startDrawPoint.locationY,
|
||||
endPointX: endPoint.locationX,
|
||||
endPointY: endPoint.locationY
|
||||
}
|
||||
// 检查是否已存在相同的直线
|
||||
const isDuplicate = state.mapRouteList.some(
|
||||
(line) =>
|
||||
(line.startPointX === newLine.startPointX &&
|
||||
line.startPointY === newLine.startPointY &&
|
||||
line.endPointX === newLine.endPointX &&
|
||||
line.endPointY === newLine.endPointY) ||
|
||||
(line.startPointX === newLine.endPointX &&
|
||||
line.startPointY === newLine.endPointY &&
|
||||
line.endPointX === newLine.startPointX &&
|
||||
line.endPointY === newLine.startPointY)
|
||||
)
|
||||
if (isDuplicate) {
|
||||
message.warning('此路线已存在')
|
||||
} else {
|
||||
// 保存当前直线
|
||||
state.mapRouteList.push({
|
||||
isSelected: false, //是否选中
|
||||
startingPointId: state.startDrawPoint.id,
|
||||
endPointId: endPoint.id,
|
||||
startPointX: state.startDrawPoint.locationX, //开始点
|
||||
startPointY: state.startDrawPoint.locationY, //开始点
|
||||
endPointX: endPoint.locationX, //结束点
|
||||
endPointY: endPoint.locationY, //结束点
|
||||
actualStartPointX: state.startDrawPoint.actualLocationX, //实际开始点位x轴
|
||||
actualStartPointY: state.startDrawPoint.actualLocationY, //实际开始点位y轴
|
||||
actualEndPointX: endPoint.actualLocationX, //实际结束点位x轴
|
||||
actualEndPointY: endPoint.actualLocationY, //实际结束点位y轴
|
||||
actualBeginControlX: '', //实际开始控制点x轴
|
||||
actualBeginControlY: '', //实际开始控制点y轴
|
||||
actualEndControlX: '', //实际结束控制点x轴
|
||||
actualEndControlY: '', //实际结束控制点y轴
|
||||
beginControlX: 0, //开始控制点x轴
|
||||
beginControlY: 0, //开始控制点y轴
|
||||
endControlX: 0, //结束控制点x轴
|
||||
endControlY: 0, //结束控制点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, // 反向限速
|
||||
toward: 0, // 车头朝向( 0:正正 1:正反 2:反正 3:反反)
|
||||
beginWidth: state.startDrawPoint.locationWide, //起点宽
|
||||
beginHigh: state.startDrawPoint.locationDeep, // 起点高
|
||||
endWidth: endPoint.locationWide, // 终点宽
|
||||
endHigh: endPoint.locationDeep // 终点高
|
||||
})
|
||||
}
|
||||
}
|
||||
// 重置状态
|
||||
state.startDrawPointIndex = -1 // 起始点的索引
|
||||
state.startDrawPoint = null
|
||||
state.isDrawing = false
|
||||
state.currentDrawX = 0
|
||||
state.currentDrawY = 0
|
||||
}
|
||||
|
||||
event.preventDefault() // 阻止默认行为(避免选中图片或文本)
|
||||
return
|
||||
}
|
||||
}
|
||||
// 找到最近的点
|
||||
const findClosestPoint = (x, y) => {
|
||||
let minDistance = Infinity
|
||||
let closestIndex = null
|
||||
let list = allHistoryList.value[currentIndex.value]
|
||||
list.forEach((point, index) => {
|
||||
const distance = Math.sqrt((point.locationX - x) ** 2 + (point.locationY - y) ** 2)
|
||||
if (distance < minDistance && distance < point.locationWide) {
|
||||
// 10 是点的捕捉范围
|
||||
minDistance = distance
|
||||
closestIndex = index
|
||||
}
|
||||
})
|
||||
return closestIndex
|
||||
}
|
||||
|
||||
//点击区域
|
||||
const clickDrawSelectionArea = () => {
|
||||
let points = allHistoryList.value[currentIndex.value]
|
||||
@ -1351,6 +1511,8 @@ const clickDrawSelectionArea = () => {
|
||||
// let routeList = state.drawSelectionPointList.filter((item) => item.type === 1)
|
||||
let routeList = state.drawSelectionPointList
|
||||
|
||||
console.log(routeList)
|
||||
|
||||
let isHaveId = binLocation.every((item) => {
|
||||
item.id
|
||||
})
|
||||
@ -1642,7 +1804,6 @@ const measureDistancesClick = (event) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//获取扫描图 地图背景相关的信息
|
||||
const imgBgObj = reactive({
|
||||
imgUrl: '',
|
||||
@ -1769,7 +1930,6 @@ const getAllNodeList = async () => {
|
||||
const getAllMapRoute = async () => {
|
||||
state.mapRouteList = await MapApi.getPositionMapLineByPositionMapId(imgBgObj.positionMapId)
|
||||
}
|
||||
|
||||
//保存地图按钮
|
||||
const saveMap = async () => {
|
||||
const loading = ElLoading.service({
|
||||
@ -1859,7 +2019,6 @@ const disposeEventPoint = (x, y) => {
|
||||
actualLocationY
|
||||
}
|
||||
}
|
||||
|
||||
document.onmousedown = function (e) {
|
||||
//左击
|
||||
if (e.button == 2) {
|
||||
@ -1871,7 +2030,6 @@ document.onmousedown = function (e) {
|
||||
currentItemIndex.value = -1
|
||||
}
|
||||
}
|
||||
|
||||
// 阻止默认菜单弹出
|
||||
window.document.oncontextmenu = function () {
|
||||
return false
|
||||
@ -1883,11 +2041,14 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.edit-map-page {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.map-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
// height: 85vh;
|
||||
.map-bg {
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
@ -1902,6 +2063,7 @@ onMounted(() => {
|
||||
|
||||
.top-tool {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 3px;
|
||||
.top-tool-list {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -1909,6 +2071,7 @@ onMounted(() => {
|
||||
background-color: #fff;
|
||||
padding: 0 12px;
|
||||
height: 70px;
|
||||
box-shadow: rgba(0, 0, 0, 0.06) 0px 2px 3px;
|
||||
|
||||
.top-tool-item {
|
||||
display: flex;
|
||||
@ -1949,7 +2112,7 @@ onMounted(() => {
|
||||
.right-tool-list {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 94px;
|
||||
top: 114px;
|
||||
background-color: #fff;
|
||||
z-index: 999;
|
||||
text-align: center;
|
||||
@ -1986,11 +2149,28 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.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;
|
||||
// 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:
|
||||
// 20px 20px,
|
||||
// 20px 20px;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: repeating-linear-gradient(
|
||||
to right,
|
||||
rgba(0, 0, 0, 0.1),
|
||||
rgba(0, 0, 0, 0.1) 1px,
|
||||
transparent 1px,
|
||||
transparent 50px
|
||||
),
|
||||
repeating-linear-gradient(
|
||||
to bottom,
|
||||
rgba(0, 0, 0, 0.1),
|
||||
rgba(0, 0, 0, 0.1) 1px,
|
||||
transparent 1px,
|
||||
transparent 50px
|
||||
);
|
||||
}
|
||||
|
||||
.svg-div {
|
||||
|
@ -1,127 +1,190 @@
|
||||
<template>
|
||||
<div class="box">
|
||||
<svg :width="boxWidth" :height="boxHeight">
|
||||
<path
|
||||
v-for="(curve, index) in curves"
|
||||
<div>
|
||||
<svg
|
||||
ref="svgElement"
|
||||
:width="width"
|
||||
:height="height"
|
||||
@mousedown="startDrawing"
|
||||
@mousemove="drawLine"
|
||||
@mouseup="stopDrawing"
|
||||
@mouseleave="stopDrawing"
|
||||
>
|
||||
<!-- 渲染所有点 -->
|
||||
<circle
|
||||
v-for="(point, index) in points"
|
||||
:key="index"
|
||||
:d="getCurvePath(curve)"
|
||||
:stroke="curve.isSelected ? 'red' : 'blue'"
|
||||
:cx="point.x"
|
||||
:cy="point.y"
|
||||
r="5"
|
||||
fill="red"
|
||||
@mousedown="startFromPoint(index, $event)"
|
||||
/>
|
||||
|
||||
<!-- 渲染所有直线 -->
|
||||
<path
|
||||
v-for="(line, index) in lines"
|
||||
:key="'line-' + index"
|
||||
:d="`M ${line.startPointX} ${line.startPointY} L ${line.endPointX} ${line.endPointY}`"
|
||||
stroke="black"
|
||||
stroke-width="2"
|
||||
fill="none"
|
||||
@mousedown="selectCurve(index)"
|
||||
/>
|
||||
<circle
|
||||
v-for="(curve, index) in curves"
|
||||
:key="'start-' + index"
|
||||
:cx="curve.startControlX"
|
||||
:cy="curve.startControlY"
|
||||
r="5"
|
||||
fill="green"
|
||||
@mousedown="startDrag(index, 'start')"
|
||||
/>
|
||||
<circle
|
||||
v-for="(curve, index) in curves"
|
||||
:key="'end-' + index"
|
||||
:cx="curve.endControlX"
|
||||
:cy="curve.endControlY"
|
||||
r="5"
|
||||
fill="green"
|
||||
@mousedown="startDrag(index, 'end')"
|
||||
|
||||
<!-- 实时绘制当前直线 -->
|
||||
<path
|
||||
v-if="isDrawing"
|
||||
:d="`M ${startX} ${startY} L ${currentX} ${currentY}`"
|
||||
stroke="blue"
|
||||
stroke-width="2"
|
||||
fill="none"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<!-- 显示直线数据 -->
|
||||
<div>
|
||||
<h3>直线数据:</h3>
|
||||
<pre>{{ lines }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
<script>
|
||||
import { ref } from 'vue'
|
||||
|
||||
// 盒子的宽度和高度
|
||||
const boxWidth = ref(800)
|
||||
const boxHeight = ref(600)
|
||||
export default {
|
||||
setup() {
|
||||
const svgElement = ref(null) // SVG 元素的引用
|
||||
const width = 800 // SVG 宽度
|
||||
const height = 600 // SVG 高度
|
||||
|
||||
// 初始点位数据
|
||||
const curves = ref([
|
||||
{
|
||||
startX: 100,
|
||||
startY: 100,
|
||||
endX: 300,
|
||||
endY: 100,
|
||||
startControlX: 100,
|
||||
startControlY: 100,
|
||||
endControlX: 300,
|
||||
endControlY: 100,
|
||||
isSelected: false
|
||||
},
|
||||
{
|
||||
startX: 200,
|
||||
startY: 200,
|
||||
endX: 400,
|
||||
endY: 200,
|
||||
startControlX: 200,
|
||||
startControlY: 200,
|
||||
endControlX: 400,
|
||||
endControlY: 400,
|
||||
isSelected: false
|
||||
// 点位数据
|
||||
const points = ref([
|
||||
{ x: 10, y: 10 },
|
||||
{ x: 30, y: 100 },
|
||||
{ x: 40, y: 40 },
|
||||
{ x: 230, y: 400 },
|
||||
{ x: 750, y: 640 }
|
||||
])
|
||||
|
||||
// 直线数据
|
||||
const lines = ref([])
|
||||
|
||||
// 当前绘制的直线状态
|
||||
const isDrawing = ref(false)
|
||||
const startX = ref(0)
|
||||
const startY = ref(0)
|
||||
const currentX = ref(0)
|
||||
const currentY = ref(0)
|
||||
const startPointIndex = ref(null) // 起始点的索引
|
||||
|
||||
// 从点开始绘制
|
||||
const startFromPoint = (index, event) => {
|
||||
const point = points.value[index]
|
||||
startX.value = point.x
|
||||
startY.value = point.y
|
||||
startPointIndex.value = index
|
||||
isDrawing.value = true
|
||||
event.preventDefault() // 防止默认行为
|
||||
}
|
||||
])
|
||||
|
||||
// 当前拖动的曲线索引和控制点类型
|
||||
const dragging = ref({
|
||||
index: null,
|
||||
type: null
|
||||
})
|
||||
// 实时绘制
|
||||
const drawLine = (event) => {
|
||||
if (isDrawing.value) {
|
||||
const rect = svgElement.value.getBoundingClientRect()
|
||||
currentX.value = event.clientX - rect.left
|
||||
currentY.value = event.clientY - rect.top
|
||||
}
|
||||
}
|
||||
|
||||
// 获取曲线的路径字符串
|
||||
const getCurvePath = (curve) => {
|
||||
return `M${curve.startX},${curve.startY} C${curve.startControlX},${curve.startControlY} ${curve.endControlX},${curve.endControlY} ${curve.endX},${curve.endY}`
|
||||
}
|
||||
// 结束绘制
|
||||
const stopDrawing = () => {
|
||||
if (isDrawing.value) {
|
||||
// 找到最近的终点
|
||||
const endPointIndex = findClosestPoint(currentX.value, currentY.value)
|
||||
|
||||
// 选中曲线
|
||||
const selectCurve = (index) => {
|
||||
curves.value.forEach((curve, i) => {
|
||||
curve.isSelected = i === index
|
||||
if (endPointIndex !== null && endPointIndex !== startPointIndex.value) {
|
||||
const endPoint = points.value[endPointIndex]
|
||||
const newLine = {
|
||||
startPointX: startX.value,
|
||||
startPointY: startY.value,
|
||||
endPointX: endPoint.x,
|
||||
endPointY: endPoint.y
|
||||
}
|
||||
|
||||
// 检查是否已存在相同的直线
|
||||
const isDuplicate = lines.value.some(
|
||||
(line) =>
|
||||
(line.startPointX === newLine.startPointX &&
|
||||
line.startPointY === newLine.startPointY &&
|
||||
line.endPointX === newLine.endPointX &&
|
||||
line.endPointY === newLine.endPointY) ||
|
||||
(line.startPointX === newLine.endPointX &&
|
||||
line.startPointY === newLine.endPointY &&
|
||||
line.endPointX === newLine.startPointX &&
|
||||
line.endPointY === newLine.startPointY)
|
||||
)
|
||||
|
||||
if (!isDuplicate) {
|
||||
// 保存当前直线
|
||||
lines.value.push(newLine)
|
||||
}
|
||||
}
|
||||
|
||||
// 重置状态
|
||||
isDrawing.value = false
|
||||
startX.value = 0
|
||||
startY.value = 0
|
||||
currentX.value = 0
|
||||
currentY.value = 0
|
||||
startPointIndex.value = null
|
||||
}
|
||||
}
|
||||
|
||||
// 找到最近的点
|
||||
const findClosestPoint = (x, y) => {
|
||||
let minDistance = Infinity
|
||||
let closestIndex = null
|
||||
|
||||
points.value.forEach((point, index) => {
|
||||
const distance = Math.sqrt((point.x - x) ** 2 + (point.y - y) ** 2)
|
||||
if (distance < minDistance && distance < 10) {
|
||||
// 10 是点的捕捉范围
|
||||
minDistance = distance
|
||||
closestIndex = index
|
||||
}
|
||||
})
|
||||
console.log('Selected curve:', curves.value[index])
|
||||
}
|
||||
|
||||
// 开始拖动控制点
|
||||
const startDrag = (index, type) => {
|
||||
dragging.value = { index, type }
|
||||
window.addEventListener('mousemove', handleDrag)
|
||||
window.addEventListener('mouseup', endDrag)
|
||||
}
|
||||
return closestIndex
|
||||
}
|
||||
|
||||
// 处理拖动事件
|
||||
const handleDrag = (event) => {
|
||||
if (dragging.value.index !== null) {
|
||||
const curve = curves.value[dragging.value.index]
|
||||
let newX = event.offsetX
|
||||
let newY = event.offsetY
|
||||
|
||||
// 确保控制点不超出盒子范围
|
||||
newX = Math.max(0, Math.min(newX, boxWidth.value))
|
||||
newY = Math.max(0, Math.min(newY, boxHeight.value))
|
||||
|
||||
if (dragging.value.type === 'start') {
|
||||
curve.startControlX = newX
|
||||
curve.startControlY = newY
|
||||
} else {
|
||||
curve.endControlX = newX
|
||||
curve.endControlY = newY
|
||||
return {
|
||||
svgElement,
|
||||
width,
|
||||
height,
|
||||
points,
|
||||
lines,
|
||||
isDrawing,
|
||||
startX,
|
||||
startY,
|
||||
currentX,
|
||||
currentY,
|
||||
startFromPoint,
|
||||
drawLine,
|
||||
stopDrawing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 结束拖动
|
||||
const endDrag = () => {
|
||||
dragging.value = { index: null, type: null }
|
||||
window.removeEventListener('mousemove', handleDrag)
|
||||
window.removeEventListener('mouseup', endDrag)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.box {
|
||||
border: 1px solid black;
|
||||
svg {
|
||||
border: 1px solid #ccc;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: #f5f5f5;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
</style>
|
||||
|
Loading…
Reference in New Issue
Block a user