From fa15c9dcf6f5bd8b8d9339cb682303776c641e64 Mon Sep 17 00:00:00 2001 From: caill <815519168@qq.com> Date: Tue, 25 Nov 2025 09:48:26 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9C=B0=E5=9B=BE=E8=BD=A6=E8=BE=86=E8=B6=85?= =?UTF-8?q?=E5=87=BA=E8=8C=83=E5=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config/mork.js | 12 +- src/pages/modules/map/index copy.vue | 373 ++++++++++++++++++++++++--- src/pages/modules/map/index.vue | 20 +- 3 files changed, 364 insertions(+), 41 deletions(-) diff --git a/src/config/mork.js b/src/config/mork.js index e2f3e11..a1226e1 100644 --- a/src/config/mork.js +++ b/src/config/mork.js @@ -53,8 +53,8 @@ export const queryMapAllStation = () => { "station_name": "起点", "station_type": "Station", "action_type": "Customize", - "x": 1.20309, - "y": -0.238922, + "x": 0, + "y": -0, "angle": -0.006407 }, { @@ -346,8 +346,8 @@ export const getRouteInfo = () => { "route_id": 1, "start_id": 1, "end_id": 2, - "start_x": 1.20309, - "start_y": -0.238922, + "start_x": 0, + "start_y": -0, "end_x": 4.01522, "end_y": 0.0628335, "navigation_mode": "0", @@ -711,8 +711,8 @@ export const getRouteInfo = () => { "end_id": 1, "start_x": 2.33115, "start_y": -0.196742, - "end_x": 1.20309, - "end_y": -0.238922, + "end_x": 0, + "end_y": -0, "navigation_mode": "1", "route_type": "1" } diff --git a/src/pages/modules/map/index copy.vue b/src/pages/modules/map/index copy.vue index 6875639..e039154 100644 --- a/src/pages/modules/map/index copy.vue +++ b/src/pages/modules/map/index copy.vue @@ -64,6 +64,46 @@ +
+
+
{{ $t('pathType') }}
+
+
+
+ + + +
+
+ {{ $t('straightPath') }} +
+
+
+
+ + + +
+
+ {{ $t('reversePath') }} +
+
+
+
+ + + +
+
+ + + +
+
+ {{ $t('bidirectionalPath') }} +
+
+
{ - const nextPoint = this.pathData[index + 1] - let controlX, controlY - if (nextPoint) { - // 有下一个点时,使用当前终点和下一个起点的中点作为控制点 - controlX = (point.px_end_x + nextPoint.px_start_x) / 2 - controlY = (point.px_end_y + nextPoint.px_start_y) / 2 - } else { - // 最后一个点,使用当前终点作为控制点 - controlX = point.px_end_x - controlY = point.px_end_y + // 根据 navigation_mode 设置路径颜色 + let pathColor, pathColor1 + switch(point.navigation_mode) { + case '0': // 直行 - 绿色 + pathColor = 'rgba(0, 167, 26, 0.5)' + pathColor1= '#00a71a' + break + case '1': // 后退 - 黄色 + pathColor = 'rgba(255, 87, 34, 0.5)' + pathColor1= '#ff5722' + break + case '2': // 双向 - 蓝色 + pathColor = 'rgba(0, 125, 255, 0.5)' + pathColor1= '#007dff' + break + default: + pathColor = '#fff' + pathColor1= '#fff' } - this.ctx.beginPath() - this.ctx.moveTo(point.px_start_x, point.px_start_y) - this.ctx.quadraticCurveTo(controlX, controlY, point.px_end_x, point.px_end_y) + + // 选中状态高亮 if (this.selectedPath.route_id === point.route_id) { - this.ctx.strokeStyle = '#ff5722' // 橙色高亮 - this.ctx.lineWidth = 4 + this.ctx.strokeStyle = pathColor1 + } else { - this.ctx.strokeStyle = '#009de5' // 默认蓝色 - this.ctx.lineWidth = 2 + this.ctx.strokeStyle = pathColor + } + this.ctx.lineWidth = 6 + this.ctx.beginPath() + + if (point.route_type === '0') { + this.drawArc(point, index) + } else { + this.drawStraightLine(point) } this.ctx.stroke() + + // 绘制箭头(大于号样式) + this.drawArrowheads(point, index) }) }, + drawStraightLine(point) { + this.ctx.moveTo(point.px_start_x, point.px_start_y) + this.ctx.lineTo(point.px_end_x, point.px_end_y) + }, + drawArc(point, index) { + const prevPoint = this.pathData[index - 1] + const nextPoint = this.pathData[index + 1] + + // 计算进入方向(从上一个线段) + let entryDirection + if (prevPoint && prevPoint.route_type === "1") { + // 上一个点是直线,使用上一个线段的朝向 + entryDirection = Math.atan2( + point.px_start_y - prevPoint.px_start_y, + point.px_start_x - prevPoint.px_start_x + ) + } else { + // 没有上一个点或上一个点也是圆弧,使用默认方向 + entryDirection = Math.atan2( + point.px_end_y - point.px_start_y, + point.px_end_x - point.px_start_x + ) + } + + // 计算离开方向(到下一个线段) + let exitDirection + if (nextPoint && nextPoint.route_type === "1") { + // 下一个点是直线,使用下一个线段的朝向 + exitDirection = Math.atan2( + nextPoint.px_end_y - point.px_end_y, + nextPoint.px_end_x - point.px_end_x + ) + } else { + // 没有下一个点或下一个点也是圆弧,使用默认方向 + exitDirection = Math.atan2( + point.px_end_y - point.px_start_y, + point.px_end_x - point.px_start_x + ) + } + + // 计算圆弧控制点 + const controlPoint = this.calculateArcControlPoint( + point.px_start_x, point.px_start_y, + point.px_end_x, point.px_end_y, + entryDirection, exitDirection + ) + + this.ctx.moveTo(point.px_start_x, point.px_start_y) + this.ctx.quadraticCurveTo(controlPoint.x, controlPoint.y, point.px_end_x, point.px_end_y) + }, + // 绘制箭头(大于号样式) + drawArrowheads(point, index) { + const t = 0.5 // 箭头位置在路径中间 + + let arrowX, arrowY, dirX, dirY + + if (point.route_type === '1') { + // 直线路径 + arrowX = point.px_start_x + (point.px_end_x - point.px_start_x) * t + arrowY = point.px_start_y + (point.px_end_y - point.px_start_y) * t + dirX = point.px_end_x - point.px_start_x + dirY = point.px_end_y - point.px_start_y + } else { + // 圆弧路径 + const prevPoint = this.pathData[index - 1] + const nextPoint = this.pathData[index + 1] + const entryDirection = prevPoint && prevPoint.route_type === "1" + ? Math.atan2(point.px_start_y - prevPoint.px_start_y, point.px_start_x - prevPoint.px_start_x) + : Math.atan2(point.px_end_y - point.px_start_y, point.px_end_x - point.px_start_x) + const exitDirection = nextPoint && nextPoint.route_type === "1" + ? Math.atan2(nextPoint.px_end_y - point.px_end_y, nextPoint.px_end_x - point.px_end_x) + : Math.atan2(point.px_end_y - point.px_start_y, point.px_end_x - point.px_start_x) + const controlPoint = this.calculateArcControlPoint( + point.px_start_x, point.px_start_y, + point.px_end_x, point.px_end_y, + entryDirection, exitDirection + ) + + // 二次贝塞尔曲线中间点 + arrowX = Math.pow(1 - t, 2) * point.px_start_x + + 2 * (1 - t) * t * controlPoint.x + + Math.pow(t, 2) * point.px_end_x + arrowY = Math.pow(1 - t, 2) * point.px_start_y + + 2 * (1 - t) * t * controlPoint.y + + Math.pow(t, 2) * point.px_end_y + + // 曲线在中间点的方向向量 + dirX = 2 * (1 - t) * (controlPoint.x - point.px_start_x) + + 2 * t * (point.px_end_x - controlPoint.x) + dirY = 2 * (1 - t) * (controlPoint.y - point.px_start_y) + + 2 * t * (point.px_end_y - controlPoint.y) + } + + // 归一化方向向量 + const length = Math.sqrt(dirX ** 2 + dirY ** 2) + if (length === 0) return + + const normDirX = dirX / length + const normDirY = dirY / length + + // 根据 navigation_mode 绘制箭头 + switch(point.navigation_mode) { + case '0': // 直行 - 单个向前箭头 + this.drawGreaterThanArrow(arrowX, arrowY, normDirX, normDirY) + break + + case '1': // 后退 - 也是单个向前箭头 + this.drawGreaterThanArrow(arrowX, arrowY, normDirX, normDirY) + break + + case '2': // 双向 - 两个方向箭头 + // 向前箭头(稍微偏移) + this.drawGreaterThanArrow( + arrowX - normDirX * 8, + arrowY - normDirY * 8, + normDirX, + normDirY + ) + // 向后箭头(稍微偏移) + this.drawGreaterThanArrow( + arrowX + normDirX * 8, + arrowY + normDirY * 8, + -normDirX, + -normDirY + ) + break + } + }, + // 绘制大于号样式的箭头 + drawGreaterThanArrow(x, y, dirX, dirY) { + const arrowLength = 2 // 箭头长度 + const arrowWidth = 4 // 箭头宽度 + + // 计算垂直方向向量(旋转90度) + const perpX = -dirY + const perpY = dirX + + // 计算大于号的三个点 + // 顶点(箭头尖端) + const tipX = x + dirX * arrowLength + const tipY = y + dirY * arrowLength + + // 左侧点 + const leftX = x - dirX * arrowLength / 2 + perpX * arrowWidth / 2 + const leftY = y - dirY * arrowLength / 2 + perpY * arrowWidth / 2 + + // 右侧点 + const rightX = x - dirX * arrowLength / 2 - perpX * arrowWidth / 2 + const rightY = y - dirY * arrowLength / 2 - perpY * arrowWidth / 2 + + // 绘制大于号形状 + this.ctx.beginPath() + this.ctx.moveTo(leftX, leftY) // 从左侧点开始 + this.ctx.lineTo(tipX, tipY) // 画到尖端 + this.ctx.lineTo(rightX, rightY) // 画到右侧点 + this.ctx.strokeStyle = '#fff' + this.ctx.lineWidth = 1.5 + this.ctx.lineCap = 'round' + this.ctx.lineJoin = 'round' + this.ctx.stroke() + }, + // 计算圆弧控制点 + calculateArcControlPoint(startX, startY, endX, endY, entryDir, exitDir) { + const midX = (startX + endX) / 2 + const midY = (startY + endY) / 2 + + // 计算方向差异 + let angleDiff = exitDir - entryDir + + // 规范化角度差异到 [-PI, PI] 范围 + while (angleDiff > Math.PI) angleDiff -= 2 * Math.PI + while (angleDiff < -Math.PI) angleDiff += 2 * Math.PI + + // 计算弯曲方向和强度 + const distance = Math.sqrt((endX - startX) ** 2 + (endY - startY) ** 2) + const bendStrength = Math.tan(angleDiff / 4) * distance * 0.5 + + // 计算垂直于平均方向的控制点偏移 + const avgDirection = (entryDir + exitDir) / 2 + const perpendicularDir = avgDirection + Math.PI / 2 + + const controlX = midX + bendStrength * Math.cos(perpendicularDir) + const controlY = midY + bendStrength * Math.sin(perpendicularDir) + + return { x: controlX, y: controlY } + }, drawMarkers () { if (!this.pointData.length || !this.ctx) return if (!this.cachedImages.marker) { @@ -385,24 +633,24 @@ export default { } return // 等待图标加载完成后再绘制 } - this.pointData.forEach(point => { - // 绘制选中状态 - if (point.station_id === this.selectedPointId) { + this.pointData.forEach((point, index) => { + if (point.station_type === 'Station') { this.ctx.beginPath() this.ctx.arc(point.px_x, point.px_y, 5, 0, Math.PI * 2) - this.ctx.fillStyle = '#59ccd2' + if (point.station_id === this.selectedPointId) { + this.ctx.fillStyle = index === 0 ? '#d700c1' : '#009de5' + this.ctx.fill() + } else { + this.ctx.strokeStyle = index === 0 ? 'rgba(215, 0, 193, 0.6)' : 'rgba(0, 157, 229, 0.6)' + this.ctx.lineWidth = 11 + this.ctx.stroke() + this.ctx.fillStyle = 'rgba(255,255,255,0.6)' + } this.ctx.fill() - } else { - this.ctx.drawImage(this.cachedImages.marker, point.px_x - MARKER_SIZE / 2, point.px_y - MARKER_SIZE / 2, MARKER_SIZE, MARKER_SIZE) - } - // 绘制点位名称(文字大小随缩放变化) - if (point.station_type === 'Station') { this.ctx.font = '12px Arial' - this.ctx.fillStyle = '#62fa0a' + this.ctx.fillStyle = index === 0 ? '#d700c1' : '#62fa0a' this.ctx.textAlign = 'center' this.ctx.fillText(point.station_name, point.px_x, point.px_y + 22) - // this.ctx.fillText(point.x, point.px_x, point.px_y + 34) - // this.ctx.fillText(point.y, point.px_x, point.px_y + 50) } }) }, @@ -501,7 +749,7 @@ export default { this.showPathPopup = false } else { this.selectedPath = path - this.calculatePopupPosition(event, 95, this.pathPopupStyle) + this.calculatePopupPosition(event, 143, this.pathPopupStyle) this.showPathPopup = true } this.redrawCanvas() @@ -893,4 +1141,69 @@ export default { font-size: 0.18rem; line-height: 0.32rem; color: #fff; + +.marker_wrap + position absolute + bottom 10px + right 10px + z-index 20 +.path-legend + border 1px solid rgba(77, 155, 205, 0.6) + border-radius 8px + padding 12px + box-shadow 0 2px 8px rgba(0, 0, 0, 0.3) +.legend-title + font-size 14px + font-weight bold + color #fff + margin-bottom 10px + text-align center + border-bottom 1px solid rgba(255, 255, 255, 0.2) + padding-bottom 5px +.legend-item + display flex + align-items center + margin-bottom 8px + &:last-child + margin-bottom 0 +.line-sample + position relative + width 50px + height 10px + margin-right 10px + display flex + align-items center + justify-content center +.straight-line + background rgba(0, 167, 26, 0.4) // 直行 - 绿色 +.reverse-line + background rgba(255, 87, 34, 0.4) // 后退 - 橙色 +.bidirectional-line + background rgba(0, 125, 255, 0.4) // 双向 - 蓝色 + justify-content space-between +.legend-text + font-size 12px + color #e0e0e0 + white-space nowrap +.arrow-container + position absolute + display flex + align-items center + justify-content center + +/* 直行和后退路径箭头 - 右侧 */ +.straight-line .arrow-container, +.reverse-line .arrow-container + left calc(50% - 3px) + +/* 双向路径箭头 */ +.bidirectional-line .arrow-container + &.left-arrow + left 12px + &.right-arrow + right 12px + +/* 箭头SVG样式 */ +.arrow-svg + filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.5)) \ No newline at end of file diff --git a/src/pages/modules/map/index.vue b/src/pages/modules/map/index.vue index e039154..3a5e245 100644 --- a/src/pages/modules/map/index.vue +++ b/src/pages/modules/map/index.vue @@ -18,6 +18,8 @@ class="mapSvg" ref="mapSvg" xmlns="http://www.w3.org/2000/svg" + width="5000" + height="5000" >