建图、地图修改
This commit is contained in:
@@ -482,7 +482,7 @@ export const getRouteInfo = () => {
|
|||||||
"start_y": 0.98493,
|
"start_y": 0.98493,
|
||||||
"end_x": 27.1645,
|
"end_x": 27.1645,
|
||||||
"end_y": 1.29409,
|
"end_y": 1.29409,
|
||||||
"navigation_mode": "0",
|
"navigation_mode": "2",
|
||||||
"route_type": "1"
|
"route_type": "1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -80,9 +80,11 @@ module.exports = {
|
|||||||
next: 'Next',
|
next: 'Next',
|
||||||
previous: 'Previous',
|
previous: 'Previous',
|
||||||
close: 'Done',
|
close: 'Done',
|
||||||
driverTxt1: 'This shows the surrounding environment scanned by the current vehicle. Drag the vehicle to start recording the travel route.',
|
driverTxt1: 'The vehicle will automatically scan the surrounding environment. Dragging the vehicle will 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.',
|
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: 'To complete map - building, click the "Finish Map - building" button and wait for the map to be generated automatically.',
|
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...',
|
carbuildingmap: 'The cart is building the map...',
|
||||||
errorbuildingredone: 'An error occurred during map - building. Map - building needs to be redone.',
|
errorbuildingredone: 'An error occurred during map - building. Map - building needs to be redone.',
|
||||||
sureendbuilding: 'Are you sure to end the map - building?',
|
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.',
|
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.',
|
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?',
|
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'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,9 +80,11 @@ module.exports = {
|
|||||||
next: '下一步',
|
next: '下一步',
|
||||||
previous: '上一步',
|
previous: '上一步',
|
||||||
close: '完成',
|
close: '完成',
|
||||||
driverTxt1: '当前车辆扫描的周围环境,拉动车辆将开始记录行走路线。',
|
driverTxt1: '车辆将自动扫描周边环境,拖动车辆即可开始记录行走路线。',
|
||||||
driverTxt2: '移动到工位点时,点击打点按钮记录当前车辆所在位置为工位点。',
|
driverTxt2: '当车辆移动到目标工位点时,点击「打点」按钮,即可保存当前位置为工位点。',
|
||||||
driverTxt3: '完成建图,点击结束建图按钮,等待地图自动生成。',
|
driverTxt3: '路线与工位点记录完成后,点击「结束建图」,等待系统自动生成地图即可。',
|
||||||
|
NewcomerGuidance: '新手指导',
|
||||||
|
Gotit: '我知道了',
|
||||||
carbuildingmap: '小车正在建图中',
|
carbuildingmap: '小车正在建图中',
|
||||||
errorbuildingredone: '建图过程中出现错误,需要重新进行建图。',
|
errorbuildingredone: '建图过程中出现错误,需要重新进行建图。',
|
||||||
sureendbuilding: '确定是否结束建图?',
|
sureendbuilding: '确定是否结束建图?',
|
||||||
@@ -124,5 +126,16 @@ module.exports = {
|
|||||||
autobackfailedmanually:'自动回退失败,请手动接管。',
|
autobackfailedmanually:'自动回退失败,请手动接管。',
|
||||||
vehicleautobacknotmanwaitcompleted: '车子正在自动回退中,请勿手动移动车辆,等待车辆自动回退完成。',
|
vehicleautobacknotmanwaitcompleted: '车子正在自动回退中,请勿手动移动车辆,等待车辆自动回退完成。',
|
||||||
yousurereposition: '是否确定重定位?',
|
yousurereposition: '是否确定重定位?',
|
||||||
systemcalculatingnotmovevehicle: '正在计算是否可以自动回退,请勿移动车辆'
|
systemcalculatingnotmovevehicle: '正在计算是否可以自动回退,请勿移动车辆',
|
||||||
|
Direction: '方向',
|
||||||
|
Gostraight: '直行',
|
||||||
|
Reverse: '后退',
|
||||||
|
Twoway: '双向',
|
||||||
|
Curve: '曲线',
|
||||||
|
Straightline: '直线',
|
||||||
|
Blindtravel: '盲走',
|
||||||
|
pathType: '路径类型',
|
||||||
|
straightPath: '直行路径',
|
||||||
|
reversePath: '后退路径',
|
||||||
|
bidirectionalPath: '双向路径'
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
src/images/new/en_step_1.jpg
Normal file
BIN
src/images/new/en_step_1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
BIN
src/images/new/en_step_2.jpg
Normal file
BIN
src/images/new/en_step_2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
BIN
src/images/new/en_step_3.jpg
Normal file
BIN
src/images/new/en_step_3.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@@ -2,25 +2,34 @@
|
|||||||
<el-dialog
|
<el-dialog
|
||||||
class="config_dialog config_dialog_h"
|
class="config_dialog config_dialog_h"
|
||||||
:class="{'enClass': $i18n.locale === 'en-us'}"
|
:class="{'enClass': $i18n.locale === 'en-us'}"
|
||||||
title="新手指导"
|
:title="$t('NewcomerGuidance')"
|
||||||
:visible.sync="dialogVisible"
|
:visible.sync="dialogVisible"
|
||||||
width="80%"
|
width="80%"
|
||||||
:before-close="handleClose">
|
:before-close="handleClose">
|
||||||
<el-row type="flex" justify="space-between" style="height: calc(100% - .46rem);padding: 0 .2rem;">
|
<el-row type="flex" justify="space-between" style="height: calc(100% - .46rem);padding: 0 .2rem;">
|
||||||
<el-col :span="7">
|
<el-col :span="7">
|
||||||
<div class="step_w"><img src="../../../images/new/step_1.jpg" alt=""></div>
|
<div class="step_w">
|
||||||
<p class="step_p">1.车辆将自动扫描周边环境,拖动车辆即可开始记录行走路线;</p>
|
<img v-if="$i18n.locale === 'en-us'" src="../../../images/new/en_step_1.jpg" alt="">
|
||||||
|
<img v-else src="../../../images/new/step_1.jpg" alt="">
|
||||||
|
</div>
|
||||||
|
<p class="step_p">1.{{ $t('driverTxt1') }}</p>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="7">
|
<el-col :span="7">
|
||||||
<div class="step_w"><img src="../../../images/new/step_2.jpg" alt=""></div>
|
<div class="step_w">
|
||||||
<p class="step_p">2.当车辆移动到目标工位点时,点击「打点」按钮,即可保存当前位置为工位点;</p>
|
<img v-if="$i18n.locale === 'en-us'" src="../../../images/new/en_step_2.jpg" alt="">
|
||||||
|
<img v-else src="../../../images/new/step_2.jpg" alt="">
|
||||||
|
</div>
|
||||||
|
<p class="step_p">2.{{ $t('driverTxt2') }}</p>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="7">
|
<el-col :span="7">
|
||||||
<div class="step_w"><img src="../../../images/new/step_3.jpg" alt=""></div>
|
<div class="step_w">
|
||||||
<p class="step_p">3.路线与工位点记录完成后,点击「结束建图」,等待系统自动生成地图即可。</p>
|
<img v-if="$i18n.locale === 'en-us'" src="../../../images/new/en_step_3.jpg" alt="">
|
||||||
|
<img v-else src="../../../images/new/step_3.jpg" alt="">
|
||||||
|
</div>
|
||||||
|
<p class="step_p">3.{{ $t('driverTxt3') }}</p>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
<button class="button_control" style="display: inherit;margin:.1rem auto 0 auto;" @click="driverConfirm"><p>我知道了</p></button>
|
<button class="button_control" style="display: inherit;margin:.1rem auto 0 auto;" @click="driverConfirm"><p>{{$t('Gotit')}}</p></button>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -66,8 +66,10 @@ export default {
|
|||||||
animationFrameId: null,
|
animationFrameId: null,
|
||||||
// 优化相关参数
|
// 优化相关参数
|
||||||
updateThrottle: 50, // 数据更新节流间隔(ms)
|
updateThrottle: 50, // 数据更新节流间隔(ms)
|
||||||
lastUpdateTime: 0
|
lastUpdateTime: 0,
|
||||||
// resizeHandler: null // 存储resize事件处理函数
|
// resizeHandler: null // 存储resize事件处理函数
|
||||||
|
yellowDotSprite: null,
|
||||||
|
yellowDotInitialPos: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -110,6 +112,14 @@ export default {
|
|||||||
this.carTexture.dispose();
|
this.carTexture.dispose();
|
||||||
this.carTexture = null;
|
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) {
|
if (this.scene) {
|
||||||
this.scene.clear();
|
this.scene.clear();
|
||||||
this.scene = null;
|
this.scene = null;
|
||||||
@@ -210,6 +220,41 @@ export default {
|
|||||||
this.scene.add(this.carMesh);
|
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) {
|
updatePointCloud(points) {
|
||||||
if (!points || !Array.isArray(points) || points.length === 0) return;
|
if (!points || !Array.isArray(points) || points.length === 0) return;
|
||||||
|
|
||||||
@@ -567,6 +612,7 @@ export default {
|
|||||||
},
|
},
|
||||||
init() {
|
init() {
|
||||||
this.initWebSocket()
|
this.initWebSocket()
|
||||||
|
this.createYellowDot(this.carPosition.x, this.carPosition.y)
|
||||||
// this.isLoading = false;
|
// this.isLoading = false;
|
||||||
// const pointData = points.data
|
// const pointData = points.data
|
||||||
// this.updatePointCloud(pointData);
|
// this.updatePointCloud(pointData);
|
||||||
|
|||||||
@@ -12,6 +12,14 @@
|
|||||||
<el-col :span="10"><h3>{{ $t('EndCode') }}</h3></el-col>
|
<el-col :span="10"><h3>{{ $t('EndCode') }}</h3></el-col>
|
||||||
<el-col :span="14"><p>{{ getStationName(path.end_id) }}</p></el-col>
|
<el-col :span="14"><p>{{ getStationName(path.end_id) }}</p></el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
<el-row type="flex" justify="space-between" class="popup-content">
|
||||||
|
<el-col :span="10"><h3>{{ $t('Direction') }}</h3></el-col>
|
||||||
|
<el-col :span="14"><p>{{ [$t('Gostraight'), $t('Reverse'), $t('Twoway')][Number(path.navigation_mode)] }}</p></el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row type="flex" justify="space-between" class="popup-content">
|
||||||
|
<el-col :span="10"><h3>{{$t('Type')}}</h3></el-col>
|
||||||
|
<el-col :span="14"><p>{{ [$t('Curve'), $t('Straightline'), $t('Blindtravel')][Number(path.route_type)] }}</p></el-col>
|
||||||
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,46 @@
|
|||||||
<el-button class="zoom_btn" style="margin-top: .2rem !important" type="primary" icon="el-icon-rank" size="mini" @click="resetZoomAndDrag"></el-button>
|
<el-button class="zoom_btn" style="margin-top: .2rem !important" type="primary" icon="el-icon-rank" size="mini" @click="resetZoomAndDrag"></el-button>
|
||||||
<el-button class="zoom_btn" type="primary" icon="el-icon-location" size="mini" :disabled="isCarLocked" @click="handleRelocate"></el-button>
|
<el-button class="zoom_btn" type="primary" icon="el-icon-location" size="mini" :disabled="isCarLocked" @click="handleRelocate"></el-button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="marker_wrap">
|
||||||
|
<div class="path-legend">
|
||||||
|
<div class="legend-title">{{ $t('pathType') }}</div>
|
||||||
|
<div class="legend-item">
|
||||||
|
<div class="line-sample straight-line">
|
||||||
|
<div class="arrow-container">
|
||||||
|
<svg class="arrow-svg" viewBox="0 0 5 6" width="5" height="6">
|
||||||
|
<path d="M0,0 L5,3 L0,6" fill="none" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="legend-text">{{ $t('straightPath') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="legend-item">
|
||||||
|
<div class="line-sample reverse-line">
|
||||||
|
<div class="arrow-container">
|
||||||
|
<svg class="arrow-svg" viewBox="0 0 5 6" width="5" height="6">
|
||||||
|
<path d="M0,0 L5,3 L0,6" fill="none" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="legend-text">{{ $t('reversePath') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="legend-item">
|
||||||
|
<div class="line-sample bidirectional-line">
|
||||||
|
<div class="arrow-container left-arrow">
|
||||||
|
<svg class="arrow-svg" viewBox="0 0 5 6" width="5" height="6">
|
||||||
|
<path d="M0,0 L5,3 L0,6" fill="none" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="arrow-container right-arrow">
|
||||||
|
<svg class="arrow-svg" viewBox="0 0 5 6" width="5" height="6">
|
||||||
|
<path d="M5,0 L0,3 L5,6" fill="none" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="legend-text">{{ $t('bidirectionalPath') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<PointPopup
|
<PointPopup
|
||||||
v-if="showPopup"
|
v-if="showPopup"
|
||||||
@@ -101,6 +141,10 @@ const ZOOM_CONFIG = {
|
|||||||
TOUCH_STEP_FACTOR: 50
|
TOUCH_STEP_FACTOR: 50
|
||||||
}
|
}
|
||||||
const PATH_CLICK_THRESHOLD = 5
|
const PATH_CLICK_THRESHOLD = 5
|
||||||
|
const ARROW_CONFIG = {
|
||||||
|
LENGTH: 10, // 箭头长度(像素)
|
||||||
|
ANGLE: Math.PI / 6 // 箭头与路径的夹角(60度)
|
||||||
|
}
|
||||||
const MARKER_SIZE = 16
|
const MARKER_SIZE = 16
|
||||||
const CAR_SIZE = 15
|
const CAR_SIZE = 15
|
||||||
export default {
|
export default {
|
||||||
@@ -349,35 +393,47 @@ export default {
|
|||||||
}, 30),
|
}, 30),
|
||||||
drawPath () {
|
drawPath () {
|
||||||
if (!this.pathData.length) return
|
if (!this.pathData.length) return
|
||||||
|
|
||||||
this.pathData.forEach((point, index) => {
|
this.pathData.forEach((point, index) => {
|
||||||
// const nextPoint = this.pathData[index + 1]
|
// 根据 navigation_mode 设置路径颜色
|
||||||
if (this.selectedPath.route_id === point.route_id) {
|
let pathColor, pathColor1
|
||||||
this.ctx.strokeStyle = '#ff5722' // 橙色高亮
|
switch(point.navigation_mode) {
|
||||||
this.ctx.lineWidth = 4
|
case '0': // 直行 - 绿色
|
||||||
} else {
|
pathColor = 'rgba(0, 167, 26, 0.5)'
|
||||||
this.ctx.strokeStyle = '#009de5' // 默认蓝色
|
pathColor1= '#00a71a'
|
||||||
this.ctx.lineWidth = 2
|
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.beginPath()
|
||||||
// this.ctx.moveTo(point.px_start_x, point.px_start_y)
|
|
||||||
if (point.route_type === '0') {
|
if (point.route_type === '0') {
|
||||||
this.drawArc(point, index)
|
this.drawArc(point, index)
|
||||||
// let controlX, controlY
|
} else {
|
||||||
// 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') {
|
|
||||||
this.drawStraightLine(point)
|
this.drawStraightLine(point)
|
||||||
// this.ctx.lineTo(point.px_end_x, point.px_end_y)
|
|
||||||
}
|
}
|
||||||
this.ctx.stroke()
|
this.ctx.stroke()
|
||||||
|
|
||||||
|
// 绘制箭头(大于号样式)
|
||||||
|
this.drawArrowheads(point, index)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
drawStraightLine(point) {
|
drawStraightLine(point) {
|
||||||
@@ -430,6 +486,117 @@ export default {
|
|||||||
this.ctx.moveTo(point.px_start_x, point.px_start_y)
|
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)
|
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) {
|
calculateArcControlPoint(startX, startY, endX, endY, entryDir, exitDir) {
|
||||||
const midX = (startX + endX) / 2
|
const midX = (startX + endX) / 2
|
||||||
@@ -467,7 +634,7 @@ export default {
|
|||||||
return // 等待图标加载完成后再绘制
|
return // 等待图标加载完成后再绘制
|
||||||
}
|
}
|
||||||
this.pointData.forEach((point, index) => {
|
this.pointData.forEach((point, index) => {
|
||||||
// 绘制选中状态
|
if (point.station_type === 'Station') {
|
||||||
this.ctx.beginPath()
|
this.ctx.beginPath()
|
||||||
this.ctx.arc(point.px_x, point.px_y, 5, 0, Math.PI * 2)
|
this.ctx.arc(point.px_x, point.px_y, 5, 0, Math.PI * 2)
|
||||||
if (point.station_id === this.selectedPointId) {
|
if (point.station_id === this.selectedPointId) {
|
||||||
@@ -480,14 +647,10 @@ export default {
|
|||||||
this.ctx.fillStyle = 'rgba(255,255,255,0.6)'
|
this.ctx.fillStyle = 'rgba(255,255,255,0.6)'
|
||||||
}
|
}
|
||||||
this.ctx.fill()
|
this.ctx.fill()
|
||||||
// 绘制点位名称(文字大小随缩放变化)
|
|
||||||
if (point.station_type === 'Station') {
|
|
||||||
this.ctx.font = '12px Arial'
|
this.ctx.font = '12px Arial'
|
||||||
this.ctx.fillStyle = index === 0 ? '#d700c1' : '#62fa0a'
|
this.ctx.fillStyle = index === 0 ? '#d700c1' : '#62fa0a'
|
||||||
this.ctx.textAlign = 'center'
|
this.ctx.textAlign = 'center'
|
||||||
this.ctx.fillText(point.station_name, point.px_x, point.px_y + 22)
|
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
|
this.showPathPopup = false
|
||||||
} else {
|
} else {
|
||||||
this.selectedPath = path
|
this.selectedPath = path
|
||||||
this.calculatePopupPosition(event, 95, this.pathPopupStyle)
|
this.calculatePopupPosition(event, 143, this.pathPopupStyle)
|
||||||
this.showPathPopup = true
|
this.showPathPopup = true
|
||||||
}
|
}
|
||||||
this.redrawCanvas()
|
this.redrawCanvas()
|
||||||
@@ -978,4 +1141,69 @@ export default {
|
|||||||
font-size: 0.18rem;
|
font-size: 0.18rem;
|
||||||
line-height: 0.32rem;
|
line-height: 0.32rem;
|
||||||
color: #fff;
|
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))
|
||||||
</style>
|
</style>
|
||||||
Reference in New Issue
Block a user