diff --git a/src/config/mork.js b/src/config/mork.js index bfe1e8e..be9c515 100644 --- a/src/config/mork.js +++ b/src/config/mork.js @@ -482,7 +482,7 @@ export const getRouteInfo = () => { "start_y": 0.98493, "end_x": 27.1645, "end_y": 1.29409, - "navigation_mode": "0", + "navigation_mode": "2", "route_type": "1" }, { diff --git a/src/i18n/langs/en.js b/src/i18n/langs/en.js index 22806a3..82e255e 100644 --- a/src/i18n/langs/en.js +++ b/src/i18n/langs/en.js @@ -80,9 +80,11 @@ module.exports = { next: 'Next', previous: 'Previous', close: 'Done', - driverTxt1: 'This shows the surrounding environment scanned by the current vehicle. Drag the vehicle to start recording the travel route.', - driverTxt2: 'When the vehicle moves to a work point, click the "Mark Point" button to record the current position of the vehicle as a work point.', - driverTxt3: 'To complete map - building, click the "Finish Map - building" button and wait for the map to be generated automatically.', + driverTxt1: 'The vehicle will automatically scan the surrounding environment. Dragging the vehicle will start recording the travel route.', + driverTxt2: 'When the vehicle moves to the target work point, click the "Mark Point" button to save the current location as a work point.', + driverTxt3: 'After the route and work points are recorded, click "Finish Mapping" and wait for the system to automatically generate the map.', + NewcomerGuidance: 'Newcomer Guidance', + Gotit: 'Got it', carbuildingmap: 'The cart is building the map...', errorbuildingredone: 'An error occurred during map - building. Map - building needs to be redone.', sureendbuilding: 'Are you sure to end the map - building?', @@ -124,5 +126,16 @@ module.exports = { autobackfailedmanually:'The automatic fallback has failed. Please take over manually.', vehicleautobacknotmanwaitcompleted: 'The vehicle is in the process of automatic fallback. Do not manually move the vehicle. Please wait until the automatic fallback is completed.', yousurereposition: 'Are you sure you want to reposition?', - systemcalculatingnotmovevehicle: 'The system is calculating whether an automatic fallback is possible. Do not move the vehicle.' + systemcalculatingnotmovevehicle: 'The system is calculating whether an automatic fallback is possible. Do not move the vehicle.', + Direction: 'Direction', + Gostraight: 'Go straight', + Reverse: 'Reverse', + Twoway: 'Two - way', + Curve: 'Curve', + Straightline: 'Straight line', + Blindtravel: 'Blind travel', + pathType: 'Path Type', + straightPath: 'Straight Path', + reversePath: 'Reverse Path', + bidirectionalPath: 'Bidirectional Path' } diff --git a/src/i18n/langs/zh.js b/src/i18n/langs/zh.js index 7a3e580..2aa4a1c 100644 --- a/src/i18n/langs/zh.js +++ b/src/i18n/langs/zh.js @@ -80,9 +80,11 @@ module.exports = { next: '下一步', previous: '上一步', close: '完成', - driverTxt1: '当前车辆扫描的周围环境,拉动车辆将开始记录行走路线。', - driverTxt2: '移动到工位点时,点击打点按钮记录当前车辆所在位置为工位点。', - driverTxt3: '完成建图,点击结束建图按钮,等待地图自动生成。', + driverTxt1: '车辆将自动扫描周边环境,拖动车辆即可开始记录行走路线。', + driverTxt2: '当车辆移动到目标工位点时,点击「打点」按钮,即可保存当前位置为工位点。', + driverTxt3: '路线与工位点记录完成后,点击「结束建图」,等待系统自动生成地图即可。', + NewcomerGuidance: '新手指导', + Gotit: '我知道了', carbuildingmap: '小车正在建图中', errorbuildingredone: '建图过程中出现错误,需要重新进行建图。', sureendbuilding: '确定是否结束建图?', @@ -124,5 +126,16 @@ module.exports = { autobackfailedmanually:'自动回退失败,请手动接管。', vehicleautobacknotmanwaitcompleted: '车子正在自动回退中,请勿手动移动车辆,等待车辆自动回退完成。', yousurereposition: '是否确定重定位?', - systemcalculatingnotmovevehicle: '正在计算是否可以自动回退,请勿移动车辆' + systemcalculatingnotmovevehicle: '正在计算是否可以自动回退,请勿移动车辆', + Direction: '方向', + Gostraight: '直行', + Reverse: '后退', + Twoway: '双向', + Curve: '曲线', + Straightline: '直线', + Blindtravel: '盲走', + pathType: '路径类型', + straightPath: '直行路径', + reversePath: '后退路径', + bidirectionalPath: '双向路径' } diff --git a/src/images/new/en_step_1.jpg b/src/images/new/en_step_1.jpg new file mode 100644 index 0000000..3ff3652 Binary files /dev/null and b/src/images/new/en_step_1.jpg differ diff --git a/src/images/new/en_step_2.jpg b/src/images/new/en_step_2.jpg new file mode 100644 index 0000000..857267c Binary files /dev/null and b/src/images/new/en_step_2.jpg differ diff --git a/src/images/new/en_step_3.jpg b/src/images/new/en_step_3.jpg new file mode 100644 index 0000000..b022b87 Binary files /dev/null and b/src/images/new/en_step_3.jpg differ diff --git a/src/pages/modules/build/driver-modal.vue b/src/pages/modules/build/driver-modal.vue index beec127..6f85b84 100644 --- a/src/pages/modules/build/driver-modal.vue +++ b/src/pages/modules/build/driver-modal.vue @@ -2,25 +2,34 @@ -
-

1.车辆将自动扫描周边环境,拖动车辆即可开始记录行走路线;

+
+ + +
+

1.{{ $t('driverTxt1') }}

-
-

2.当车辆移动到目标工位点时,点击「打点」按钮,即可保存当前位置为工位点;

+
+ + +
+

2.{{ $t('driverTxt2') }}

-
-

3.路线与工位点记录完成后,点击「结束建图」,等待系统自动生成地图即可。

+
+ + +
+

3.{{ $t('driverTxt3') }}

- +
diff --git a/src/pages/modules/build/gl-map.vue b/src/pages/modules/build/gl-map.vue index ed4b48d..f56fefa 100644 --- a/src/pages/modules/build/gl-map.vue +++ b/src/pages/modules/build/gl-map.vue @@ -66,8 +66,10 @@ export default { animationFrameId: null, // 优化相关参数 updateThrottle: 50, // 数据更新节流间隔(ms) - lastUpdateTime: 0 + lastUpdateTime: 0, // resizeHandler: null // 存储resize事件处理函数 + yellowDotSprite: null, + yellowDotInitialPos: null } }, computed: { @@ -110,6 +112,14 @@ export default { this.carTexture.dispose(); this.carTexture = null; } + if (this.yellowDotSprite) { + if (this.yellowDotSprite.material.map) { + this.yellowDotSprite.material.map.dispose(); + } + this.yellowDotSprite.material.dispose(); + this.scene.remove(this.yellowDotSprite); + this.yellowDotSprite = null; + } if (this.scene) { this.scene.clear(); this.scene = null; @@ -210,6 +220,41 @@ export default { this.scene.add(this.carMesh); }); }, + createYellowDot(initialX, initialY) { + // 存储初始位置(后续不更新) + this.yellowDotInitialPos = { x: initialX, y: initialY }; + + // 创建圆点纹理(黄色纯色纹理) + const canvas = document.createElement('canvas'); + canvas.width = 60; + canvas.height = 60; + const ctx = canvas.getContext('2d'); + ctx.fillStyle = '#FFD700'; // 黄色 + ctx.beginPath(); + ctx.arc(30, 30, 15, 0, Math.PI * 2); + ctx.fill(); + + const dotTexture = new THREE.CanvasTexture(canvas); + const dotMaterial = new THREE.SpriteMaterial({ + map: dotTexture, + transparent: true, + depthWrite: false, // 避免遮挡问题 + sizeAttenuation: false // 缩放时保持大小不变 + }); + + this.yellowDotSprite = new THREE.Sprite(dotMaterial); + // 设置圆点大小(根据场景比例调整) + const actualRangeY = this.pointCloudRange.maxY - this.pointCloudRange.minY; + const carScale = actualRangeY / 5; + this.yellowDotSprite.scale.set(carScale * 1.5, carScale * 1.5, 1); + // 固定在小车初始位置 + this.yellowDotSprite.position.set(initialX, initialY, 5); // z轴4,在点云之上、小车之下 + this.scene.add(this.yellowDotSprite); + if (this.renderer && this.scene && this.camera) { + this.renderer.render(this.scene, this.camera); + } + }, + updatePointCloud(points) { if (!points || !Array.isArray(points) || points.length === 0) return; @@ -567,6 +612,7 @@ export default { }, init() { this.initWebSocket() + this.createYellowDot(this.carPosition.x, this.carPosition.y) // this.isLoading = false; // const pointData = points.data // this.updatePointCloud(pointData); diff --git a/src/pages/modules/map/PathPopup.vue b/src/pages/modules/map/PathPopup.vue index 663e756..e9d6d46 100644 --- a/src/pages/modules/map/PathPopup.vue +++ b/src/pages/modules/map/PathPopup.vue @@ -12,6 +12,14 @@

{{ $t('EndCode') }}

{{ getStationName(path.end_id) }}

+ +

{{ $t('Direction') }}

+

{{ [$t('Gostraight'), $t('Reverse'), $t('Twoway')][Number(path.navigation_mode)] }}

+
+ +

{{$t('Type')}}

+

{{ [$t('Curve'), $t('Straightline'), $t('Blindtravel')][Number(path.route_type)] }}

+
diff --git a/src/pages/modules/map/index.vue b/src/pages/modules/map/index.vue index 86800f7..e039154 100644 --- a/src/pages/modules/map/index.vue +++ b/src/pages/modules/map/index.vue @@ -64,6 +64,46 @@ +
+
+
{{ $t('pathType') }}
+
+
+
+ + + +
+
+ {{ $t('straightPath') }} +
+
+
+
+ + + +
+
+ {{ $t('reversePath') }} +
+
+
+
+ + + +
+
+ + + +
+
+ {{ $t('bidirectionalPath') }} +
+
+
{ - // const nextPoint = this.pathData[index + 1] - if (this.selectedPath.route_id === point.route_id) { - this.ctx.strokeStyle = '#ff5722' // 橙色高亮 - this.ctx.lineWidth = 4 - } else { - this.ctx.strokeStyle = '#009de5' // 默认蓝色 - this.ctx.lineWidth = 2 + // 根据 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' } + + // 选中状态高亮 + if (this.selectedPath.route_id === point.route_id) { + this.ctx.strokeStyle = pathColor1 + + } else { + this.ctx.strokeStyle = pathColor + } + this.ctx.lineWidth = 6 this.ctx.beginPath() - // this.ctx.moveTo(point.px_start_x, point.px_start_y) + if (point.route_type === '0') { this.drawArc(point, index) - // 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 - // } - // this.ctx.quadraticCurveTo(controlX, controlY, point.px_end_x, point.px_end_y) - } else if (point.route_type === '1') { + } else { this.drawStraightLine(point) - // this.ctx.lineTo(point.px_end_x, point.px_end_y) } this.ctx.stroke() + + // 绘制箭头(大于号样式) + this.drawArrowheads(point, index) }) }, drawStraightLine(point) { @@ -430,6 +486,117 @@ export default { 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 @@ -467,27 +634,23 @@ export default { return // 等待图标加载完成后再绘制 } this.pointData.forEach((point, index) => { - // 绘制选中状态 - this.ctx.beginPath() - this.ctx.arc(point.px_x, point.px_y, 5, 0, Math.PI * 2) - 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() - // 绘制点位名称(文字大小随缩放变化) if (point.station_type === 'Station') { + this.ctx.beginPath() + this.ctx.arc(point.px_x, point.px_y, 5, 0, Math.PI * 2) + 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() this.ctx.font = '12px Arial' 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) } }) }, @@ -586,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() @@ -978,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