|
|
|
@ -4,6 +4,161 @@ |
|
|
|
<div class="toolBarDiv" /> |
|
|
|
<div class="rightMask" /> |
|
|
|
<div id="info" class="info" /> |
|
|
|
|
|
|
|
<div id="panel" class="panel"> |
|
|
|
<el-tabs |
|
|
|
v-model="activeTab" |
|
|
|
type="card" |
|
|
|
class="path-tabs" |
|
|
|
@tab-click="handleTabChange" |
|
|
|
> |
|
|
|
<!-- 正常模式 Tab --> |
|
|
|
<el-tab-pane label="正常模式" name="1"> |
|
|
|
<div class="tab-content"> |
|
|
|
<!-- 起终点输入区 --> |
|
|
|
<div class="startend"> |
|
|
|
<div class="startandendinput"> |
|
|
|
<!-- 起点输入 --> |
|
|
|
<div class="input-group"> |
|
|
|
<div class="front-input" style="border: 2px solid #7ed321;" /> |
|
|
|
<el-input |
|
|
|
v-model="startInput" |
|
|
|
placeholder="请输入起点" |
|
|
|
clearable |
|
|
|
@input="handleInput('startInput')" |
|
|
|
@focus="activeInput = 'startInput'" |
|
|
|
@clear="handleInputClear('startInput')" |
|
|
|
/> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 分隔线 --> |
|
|
|
<div class="divide" /> |
|
|
|
|
|
|
|
<!-- 终点输入 --> |
|
|
|
<div class="input-group"> |
|
|
|
<div class="front-input" style="border: 2px solid #FF4C3F;" /> |
|
|
|
<el-input |
|
|
|
v-model="endInput" |
|
|
|
placeholder="请输入终点" |
|
|
|
clearable |
|
|
|
@input="handleInput('endInput')" |
|
|
|
@focus="activeInput = 'endInput'" |
|
|
|
@clear="handleInputClear('endInput')" |
|
|
|
/> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 交换起终点按钮 --> |
|
|
|
<el-button |
|
|
|
icon="el-icon-refresh-left" |
|
|
|
class="exchange-btn" |
|
|
|
circle |
|
|
|
size="mini" |
|
|
|
:disabled="!startInput && !endInput" |
|
|
|
@click="handleExchange" |
|
|
|
/> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 功能按钮区 --> |
|
|
|
<div class="btn-group"> |
|
|
|
<el-button |
|
|
|
type="primary" |
|
|
|
size="mini" |
|
|
|
@click="handleClearAll" |
|
|
|
> |
|
|
|
清除路线 |
|
|
|
</el-button> |
|
|
|
<el-button |
|
|
|
type="default" |
|
|
|
size="mini" |
|
|
|
:disabled="!start || !dest" |
|
|
|
@click="handleNaviAndDrawLine" |
|
|
|
> |
|
|
|
路线搜索 |
|
|
|
</el-button> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 路径模式选择(最短/最优) --> |
|
|
|
<div class="route-type"> |
|
|
|
<el-button |
|
|
|
:class="{ 'stairs-types-selected': currentMode === 'shortest' }" |
|
|
|
size="mini" |
|
|
|
@click="handleChangeMode('shortest')" |
|
|
|
> |
|
|
|
最短路径 |
|
|
|
</el-button> |
|
|
|
<el-button |
|
|
|
:class="{ 'stairs-types-selected': currentMode === 'best' }" |
|
|
|
size="mini" |
|
|
|
@click="handleChangeMode('best')" |
|
|
|
> |
|
|
|
最优路径 |
|
|
|
</el-button> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 搜索结果区域 --> |
|
|
|
<div v-if="showSearchResult" class="search-result"> |
|
|
|
<!-- 无结果状态 --> |
|
|
|
<div v-if="searchResult.length === 0" class="no-result"> |
|
|
|
<div class="no-result-image" /> |
|
|
|
<div class="no-result-title">暂无搜索结果</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 有结果状态 --> |
|
|
|
<div v-else class="result"> |
|
|
|
<div class="result-header">搜索结果</div> |
|
|
|
<div class="result-content"> |
|
|
|
<el-card |
|
|
|
v-for="(item, index) in searchResult" |
|
|
|
:key="index" |
|
|
|
class="result-item" |
|
|
|
:shadow="selectedIndex === index ? 'hover' : 'never'" |
|
|
|
:border="selectedIndex === index" |
|
|
|
@click="handleResultSelected(index)" |
|
|
|
> |
|
|
|
<div class="result-item-title">{{ item.name }}</div> |
|
|
|
<div class="result-item-position"> |
|
|
|
<i class="el-icon-location" /> |
|
|
|
<span>{{ getFloorNameByGid(item.level) }}</span> |
|
|
|
</div> |
|
|
|
</el-card> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 路径结果区域 --> |
|
|
|
<div v-if="showNaviResult" class="navi-result" style="position: fixed; left: 0; top:0;"> |
|
|
|
<el-card class="navi-result-card"> |
|
|
|
<!-- 路径总信息 --> |
|
|
|
<div class="navi-result-title"> |
|
|
|
全程 |
|
|
|
<span class="highlight">{{ naviResult.distance ? naviResult.distance.toFixed(2) : 0 }}</span> 米, |
|
|
|
预计用时 |
|
|
|
<span class="highlight">{{ naviResult.distance ? Math.ceil(naviResult.distance / walkSpeed) : 0 }}</span> 分钟 |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 路径步骤列表 --> |
|
|
|
<el-timeline :reverse="false" class="navi-timeline"> |
|
|
|
<el-timeline-item |
|
|
|
v-for="(step, index) in naviResult.subs || []" |
|
|
|
:key="index" |
|
|
|
:icon="getDirectionIcon(step.waypoint.direction.zh[1])" |
|
|
|
placement="top" |
|
|
|
@click="handleRouteClick(step)" |
|
|
|
> |
|
|
|
<div class="timeline-content"> |
|
|
|
<div class="navi-route-desc">{{ step.instruction.zh }}</div> |
|
|
|
</div> |
|
|
|
</el-timeline-item> |
|
|
|
</el-timeline> |
|
|
|
</el-card> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</el-tab-pane> |
|
|
|
|
|
|
|
</el-tabs> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- <div v-if="debugInfo" class="debug-info"> |
|
|
|
<pre>{{ debugInfo }}</pre> |
|
|
|
</div> --> |
|
|
|
@ -29,8 +184,9 @@ |
|
|
|
<script> |
|
|
|
import fengmap from '@/assets/fengmap/fengmap.map.min' |
|
|
|
import { FMControlPosition, FMCompass, FMZoomBar, FMToolbar, FMScaleBar } from '@/assets/fengmap/fengmap.plugin.ui.min.js' |
|
|
|
import { FMSearchRequest, FMSearchAnalyser } from '@/assets/fengmap/fengmap.analyser.min' |
|
|
|
import { FMSearchRequest, FMSearchAnalyser, FMNaviWalkAnalyser, FMNaviMode, FMNaviPriority, FMNaviObstruction } from '@/assets/fengmap/fengmap.analyser.min' |
|
|
|
import '@/assets/fengmap/fengmap.effect.min' |
|
|
|
import locationIcon from '@/assets/images/red.png' |
|
|
|
|
|
|
|
export default { |
|
|
|
name: 'FengNiaoYun', |
|
|
|
@ -50,6 +206,52 @@ export default { |
|
|
|
marker: null, |
|
|
|
floorInfo: [], |
|
|
|
|
|
|
|
// Tab切换状态(1:正常模式,2:避障模式) |
|
|
|
activeTab: '1', |
|
|
|
// 起终点输入框值 |
|
|
|
startInput: '', |
|
|
|
endInput: '', |
|
|
|
// 当前活跃的输入框(startInput/endInput) |
|
|
|
activeInput: '', |
|
|
|
// 搜索结果相关 |
|
|
|
searchResult: [], |
|
|
|
selectedIndex: -1, |
|
|
|
showSearchResult: false, |
|
|
|
// 路径规划结果相关 |
|
|
|
naviResult: {}, |
|
|
|
showNaviResult: false, |
|
|
|
// 路径模式(shortest:最短路径,best:最优路径) |
|
|
|
currentMode: 'shortest', |
|
|
|
// 蜂鸟地图核心实例 |
|
|
|
searchAnalyser: null, // 搜索分析器 |
|
|
|
routeAnalyser: null, // 路径分析器 |
|
|
|
floorInfos: [], // 楼层信息 |
|
|
|
// 起终点数据与图标 |
|
|
|
start: null, |
|
|
|
dest: null, |
|
|
|
startMarker: null, |
|
|
|
endMarker: null, |
|
|
|
// 路径线与选点弹窗 |
|
|
|
lineMarker: null, |
|
|
|
domMarker: null, |
|
|
|
clickPoint: null, // 地图点击坐标 |
|
|
|
// 状态标记 |
|
|
|
searchLoaded: false, // 搜索分析器初始化状态 |
|
|
|
obstructMarkers: [], // 避障区域图标 |
|
|
|
// 常量配置 |
|
|
|
walkSpeed: 80, // 步行速度(米/分钟) |
|
|
|
directionImg: { // 方向-图标映射 |
|
|
|
'左': 'left.png', |
|
|
|
'左前': 'left-front.png', |
|
|
|
'左后': 'left-back.png', |
|
|
|
'右前': 'right-front.png', |
|
|
|
'右后': 'right-back.png', |
|
|
|
'右': 'right.png', |
|
|
|
'前': 'front.png', |
|
|
|
'终': 'end-icon.png' |
|
|
|
}, |
|
|
|
obstacleAreas: [], // 避障区域数据(需根据实际场景配置) |
|
|
|
|
|
|
|
debugInfo: null, |
|
|
|
markerColors: ['#409EFF', '#67C23A', '#E6A23C'] // 三个点的颜色 |
|
|
|
} |
|
|
|
@ -60,9 +262,23 @@ export default { |
|
|
|
console.log('容器尺寸:', container.offsetWidth, container.offsetHeight) |
|
|
|
this.$nextTick(() => { |
|
|
|
this.initMap() |
|
|
|
|
|
|
|
window.vueInstance = null |
|
|
|
}) |
|
|
|
}, |
|
|
|
methods: { |
|
|
|
// 重新加载 |
|
|
|
reload(options) { |
|
|
|
if (this.map) { |
|
|
|
this.map.dispose() |
|
|
|
} |
|
|
|
this.map = new fengmap.FMMap(options) |
|
|
|
}, |
|
|
|
// 释放地图 |
|
|
|
dispose() { |
|
|
|
this.map.dispose() |
|
|
|
this.map = null |
|
|
|
}, |
|
|
|
initMap() { |
|
|
|
try { |
|
|
|
console.log('开始初始化地图') |
|
|
|
@ -75,13 +291,43 @@ export default { |
|
|
|
// mapURL: '/fengmap/data/', |
|
|
|
// themeURL: '/fengmap/data/theme/', |
|
|
|
// level: 3, |
|
|
|
mapZoom: 20.2 |
|
|
|
mapZoom: 20.2, |
|
|
|
backgroundColor: '#fff' |
|
|
|
} |
|
|
|
console.log('地图配置:', options) |
|
|
|
// console.log('地图配置:', options) |
|
|
|
|
|
|
|
// const options = { |
|
|
|
// container: document.getElementById('fengmap'), |
|
|
|
// appName: '蜂鸟研发SDK_2_0', |
|
|
|
// key: '57c7f309aca507497d028a9c00207cf8', |
|
|
|
// mapID: '1514920297309614082', |
|
|
|
// // mapURL: '/fengmap/data/', |
|
|
|
// themeID: '1580453922356207618', |
|
|
|
// themeURL: '/fengmap/data/theme/', |
|
|
|
// mapZoom: 19.5, |
|
|
|
// level: 3 |
|
|
|
// } |
|
|
|
|
|
|
|
this.map = new fengmap.FMMap(options) |
|
|
|
console.log('地图实例已创建') |
|
|
|
|
|
|
|
// 在地图初始化前添加此判断逻辑 |
|
|
|
if (options.mapURL) { |
|
|
|
// 检查是否是离线模式且尚未刷新过 |
|
|
|
const hasRefreshed = localStorage.getItem('mapOfflineRefreshed') |
|
|
|
|
|
|
|
if (!hasRefreshed) { |
|
|
|
// 标记已刷新 |
|
|
|
localStorage.setItem('mapOfflineRefreshed', 'true') |
|
|
|
// 强制刷新页面(不使用缓存) |
|
|
|
window.location.reload(true) |
|
|
|
// this.reload(options) |
|
|
|
} else { |
|
|
|
// 已经刷新过,清除标记以便下次进入时可用 |
|
|
|
localStorage.removeItem('mapOfflineRefreshed') |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
this.map.on('loadingProcess', (event) => { |
|
|
|
console.log('加载进度:', event.progress) |
|
|
|
this.debugInfo = `加载进度: ${event.progress}%` |
|
|
|
@ -89,7 +335,7 @@ export default { |
|
|
|
|
|
|
|
this.map.on('loaded', () => { |
|
|
|
console.log('地图加载完成') |
|
|
|
this.debugInfo = '地图加载完成' |
|
|
|
|
|
|
|
this.map.setViewMode(fengmap.FMViewMode.MODE_3D) |
|
|
|
|
|
|
|
// 指北针控件 |
|
|
|
@ -159,6 +405,20 @@ export default { |
|
|
|
// UI.initList() |
|
|
|
}) |
|
|
|
|
|
|
|
// 初始化路径分析类 |
|
|
|
this.routeAnalyser = new FMNaviWalkAnalyser({ map: this.map }, () => { |
|
|
|
// console.log('路径分析类创建成功') |
|
|
|
}, () => { |
|
|
|
alert('路径分析类创建失败') |
|
|
|
}) |
|
|
|
// 初始化搜索分析对象 |
|
|
|
const searchOption = { |
|
|
|
map: this.map |
|
|
|
} |
|
|
|
this.searchAnalyser = new FMSearchAnalyser(searchOption, () => { |
|
|
|
this.searchLoaded = true |
|
|
|
}) |
|
|
|
|
|
|
|
console.log('当前楼层', this.map.getLevel()) |
|
|
|
console.log('所有楼层', this.map.getLevels()) |
|
|
|
|
|
|
|
@ -183,32 +443,45 @@ export default { |
|
|
|
this.search(ops) |
|
|
|
|
|
|
|
// 根据FID查询 |
|
|
|
const floor = this.map.getFloor(this.level) |
|
|
|
const model = floor.getLayers(fengmap.FMType.MODEL_LAYER)[0].getFeatures().find(item => item.FID === '1026050201310') |
|
|
|
console.log('model', model.getData()) |
|
|
|
model.setColor('red') |
|
|
|
model.flash('red') |
|
|
|
// const floor = this.map.getFloor(this.level) |
|
|
|
// const model = floor.getLayers(fengmap.FMType.MODEL_LAYER)[0].getFeatures().find(item => item.FID === '1026050201310') |
|
|
|
// console.log('model', model.getData()) |
|
|
|
// model.setColor('red') |
|
|
|
// model.flash('red') |
|
|
|
|
|
|
|
// ✅ 此时挂载:确保实例和方法都已存在,且在弹窗创建前完成 |
|
|
|
window.vueInstance = this |
|
|
|
// 可选:在控制台打印,验证是否挂载成功(调试用) |
|
|
|
console.log('Vue实例已全局挂载:', window.vueInstance) |
|
|
|
}, { passive: true }) |
|
|
|
|
|
|
|
this.map.on('click', (e) => { |
|
|
|
this.marker && this.marker.remove() |
|
|
|
this.marker = null |
|
|
|
const clickMode = e.targets.find(it => it.type === fengmap.FMType.MODEL) |
|
|
|
let floor = this.floorInfo.find(it => it.level === e.level) |
|
|
|
const floor = this.floorInfo.find(it => it.level === e.level) |
|
|
|
|
|
|
|
// 更新界面的点击信息 |
|
|
|
this.updateClickUI(e, clickMode, floor) |
|
|
|
|
|
|
|
// 添加点击mark |
|
|
|
this.marker = new fengmap.FMImageMarker({ |
|
|
|
url: 'https://developer.fengmap.com/fmAPI/images/red.png', |
|
|
|
x: e.coords.x, |
|
|
|
y: e.coords.y, |
|
|
|
anchor: fengmap.FMMarkerAnchor.BOTTOM |
|
|
|
}) |
|
|
|
const level = this.map.getLevel() |
|
|
|
floor = this.map.getFloor(level) |
|
|
|
this.marker.addTo(floor) |
|
|
|
// this.marker = new fengmap.FMImageMarker({ |
|
|
|
// url: locationIcon, |
|
|
|
// x: e.coords.x, |
|
|
|
// y: e.coords.y, |
|
|
|
// anchor: fengmap.FMMarkerAnchor.BOTTOM |
|
|
|
// }) |
|
|
|
// const level = this.map.getLevel() |
|
|
|
// floor = this.map.getFloor(level) |
|
|
|
// this.marker.addTo(floor) |
|
|
|
|
|
|
|
if (e.level) { |
|
|
|
const model = e.targets.find(item => item.type === fengmap.FMType.MODEL) |
|
|
|
if (model) { |
|
|
|
this.clickPoint = { level: e.level, x: model.x, y: model.y, name: model.name } |
|
|
|
this.showDomMarker(this.clickPoint) |
|
|
|
} |
|
|
|
} |
|
|
|
}, { passive: true }) |
|
|
|
|
|
|
|
this.map.on('mapInitError', (err) => { |
|
|
|
@ -220,6 +493,485 @@ export default { |
|
|
|
this.debugInfo = `初始化失败: ${error.message}` |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
/** |
|
|
|
* 5. 交换起终点 |
|
|
|
*/ |
|
|
|
handleExchange() { |
|
|
|
// 交换起终点数据 |
|
|
|
const tempStart = { ...this.start } |
|
|
|
const tempDest = { ...this.dest } |
|
|
|
this.start = tempDest |
|
|
|
this.dest = tempStart |
|
|
|
|
|
|
|
// 交换输入框值 |
|
|
|
const tempStartInput = this.startInput |
|
|
|
const tempEndInput = this.endInput |
|
|
|
this.startInput = tempEndInput |
|
|
|
this.endInput = tempStartInput |
|
|
|
|
|
|
|
// 重新渲染起终点图标 |
|
|
|
if (this.start) { |
|
|
|
this.clickPoint = this.start |
|
|
|
this.setStart() |
|
|
|
} |
|
|
|
if (this.dest) { |
|
|
|
this.clickPoint = this.dest |
|
|
|
this.setEnd() |
|
|
|
} |
|
|
|
|
|
|
|
// 重新规划路径 |
|
|
|
this.handleNaviAndDrawLine() |
|
|
|
}, |
|
|
|
|
|
|
|
/** |
|
|
|
* 6. 显示起终点选择弹窗(替代showDomMarker) |
|
|
|
*/ |
|
|
|
showDomMarker(position) { |
|
|
|
// 清除已有弹窗 |
|
|
|
if (this.domMarker) { |
|
|
|
this.domMarker.remove() |
|
|
|
this.domMarker = null |
|
|
|
} |
|
|
|
|
|
|
|
// 创建Vue实例挂载的弹窗DOM(避免直接操作DOM) |
|
|
|
this.domMarker = new fengmap.FMDomMarker({ |
|
|
|
x: position.x, |
|
|
|
y: position.y, |
|
|
|
domHeight: 80, |
|
|
|
domWidth: 200, |
|
|
|
anchor: fengmap.FMMarkerAnchor.BOTTOM, |
|
|
|
// 弹窗内容(通过window.vueInstance绑定Vue方法) |
|
|
|
content: ` |
|
|
|
<div style="display: inline-block; width: 200px; text-align: center;"> |
|
|
|
<div style="display: flex; margin-bottom: 10px; border-radius: 6px; background-color: white; text-align: center;"> |
|
|
|
<span |
|
|
|
style="flex: 1; height: 30px; color: white; padding: 10px 16px; line-height: 30px; background-color: #2F65EE; border-radius: 6px 0 0 6px; cursor: pointer;" |
|
|
|
onclick="window.vueInstance.setStart()" |
|
|
|
> |
|
|
|
作为起点 |
|
|
|
</span> |
|
|
|
<span |
|
|
|
style="flex: 1; height: 30px; padding: 10px 16px; line-height: 30px; cursor: pointer;" |
|
|
|
onclick="window.vueInstance.setEnd()" |
|
|
|
> |
|
|
|
作为终点 |
|
|
|
</span> |
|
|
|
</div> |
|
|
|
<div style="background-image: url('https://developer.fengmap.com/fmAPI/images/navi/marker.png'); display: inline-block; width: 32px; height: 32px; background-size: 100%;"></div> |
|
|
|
</div> |
|
|
|
` |
|
|
|
}) |
|
|
|
|
|
|
|
// 将弹窗添加到对应楼层 |
|
|
|
const floor = this.map.getFloor(position.level) |
|
|
|
this.domMarker.addTo(floor) |
|
|
|
}, |
|
|
|
|
|
|
|
/** |
|
|
|
* 7. 根据楼层level获取楼层名称(替代getFloorNameByGid) |
|
|
|
*/ |
|
|
|
getFloorNameByGid(level) { |
|
|
|
const floor = this.floorInfos.find(item => item.level === level) |
|
|
|
return floor ? floor.name : '未知楼层' |
|
|
|
}, |
|
|
|
|
|
|
|
/** |
|
|
|
* 8. 输入框内容变化处理(替代jQuery的input事件) |
|
|
|
*/ |
|
|
|
handleInput(inputType) { |
|
|
|
this.activeInput = inputType |
|
|
|
const inputValue = inputType === 'startInput' ? this.startInput : this.endInput |
|
|
|
this.searchModel(inputValue) |
|
|
|
}, |
|
|
|
|
|
|
|
/** |
|
|
|
* 9. 输入框清空处理(Element UI clear事件) |
|
|
|
*/ |
|
|
|
handleInputClear(inputType) { |
|
|
|
this.showSearchResult = false |
|
|
|
this.searchResult = [] |
|
|
|
// 清空对应起终点数据 |
|
|
|
if (inputType === 'startInput') { |
|
|
|
this.start = null |
|
|
|
if (this.startMarker) { |
|
|
|
this.startMarker.remove() |
|
|
|
this.startMarker = null |
|
|
|
} |
|
|
|
} else { |
|
|
|
this.dest = null |
|
|
|
if (this.endMarker) { |
|
|
|
this.endMarker.remove() |
|
|
|
this.endMarker = null |
|
|
|
} |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
/** |
|
|
|
* 10. 关键字搜索(替代searchModel) |
|
|
|
*/ |
|
|
|
searchModel(keyword) { |
|
|
|
// 搜索分析器未初始化则提示 |
|
|
|
if (!this.searchLoaded) { |
|
|
|
this.$message.warning('搜索功能初始化中,请稍后...') |
|
|
|
return |
|
|
|
} |
|
|
|
// 空关键词则隐藏搜索结果 |
|
|
|
if (!keyword || keyword.trim() === '') { |
|
|
|
this.showSearchResult = false |
|
|
|
this.searchResult = [] |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
// 构造搜索请求 |
|
|
|
const searchRequest = new FMSearchRequest() |
|
|
|
searchRequest.levels = [5] // 只搜索level=5的楼层(与原逻辑一致) |
|
|
|
searchRequest.type = fengmap.FMType.MODEL |
|
|
|
searchRequest.addCondition({ keyword: keyword.trim() }) |
|
|
|
|
|
|
|
// 执行搜索 |
|
|
|
this.searchAnalyser.query(searchRequest, (result) => { |
|
|
|
this.searchResult = result |
|
|
|
this.selectedIndex = -1 |
|
|
|
this.showSearchResult = true |
|
|
|
this.showNaviResult = false // 隐藏路径结果 |
|
|
|
}) |
|
|
|
}, |
|
|
|
|
|
|
|
/** |
|
|
|
* 11. 设置起点图标和数据(替代setStart) |
|
|
|
*/ |
|
|
|
setStart() { |
|
|
|
if (!this.clickPoint) return |
|
|
|
|
|
|
|
// 清除原有起点图标 |
|
|
|
if (this.startMarker) { |
|
|
|
this.startMarker.remove() |
|
|
|
this.startMarker = null |
|
|
|
} |
|
|
|
|
|
|
|
// 保存起点数据 |
|
|
|
this.start = { ...this.clickPoint } |
|
|
|
|
|
|
|
// 创建新起点图标 |
|
|
|
this.startMarker = new fengmap.FMImageMarker({ |
|
|
|
url: 'https://developer.fengmap.com/fmAPI/images/navi/start.png', |
|
|
|
x: this.clickPoint.x, |
|
|
|
y: this.clickPoint.y |
|
|
|
}) |
|
|
|
|
|
|
|
// 添加图标到对应楼层 |
|
|
|
const floor = this.map.getFloor(this.clickPoint.level) |
|
|
|
this.startMarker.addTo(floor) |
|
|
|
|
|
|
|
// 清除选点弹窗 |
|
|
|
if (this.domMarker) { |
|
|
|
this.domMarker.remove() |
|
|
|
this.domMarker = null |
|
|
|
} |
|
|
|
|
|
|
|
// 更新输入框值 |
|
|
|
this.startInput = this.start.name || '地图选点' |
|
|
|
}, |
|
|
|
|
|
|
|
/** |
|
|
|
* 12. 设置终点图标和数据(替代setEnd) |
|
|
|
*/ |
|
|
|
setEnd() { |
|
|
|
if (!this.clickPoint) return |
|
|
|
|
|
|
|
// 清除原有终点图标 |
|
|
|
if (this.endMarker) { |
|
|
|
this.endMarker.remove() |
|
|
|
this.endMarker = null |
|
|
|
} |
|
|
|
|
|
|
|
// 保存终点数据 |
|
|
|
this.dest = { ...this.clickPoint } |
|
|
|
|
|
|
|
// 创建新终点图标 |
|
|
|
this.endMarker = new fengmap.FMImageMarker({ |
|
|
|
url: 'https://developer.fengmap.com/fmAPI/images/navi/end.png', |
|
|
|
x: this.clickPoint.x, |
|
|
|
y: this.clickPoint.y |
|
|
|
}) |
|
|
|
|
|
|
|
// 添加图标到对应楼层 |
|
|
|
const floor = this.map.getFloor(this.clickPoint.level) |
|
|
|
this.endMarker.addTo(floor) |
|
|
|
|
|
|
|
// 清除选点弹窗 |
|
|
|
if (this.domMarker) { |
|
|
|
this.domMarker.remove() |
|
|
|
this.domMarker = null |
|
|
|
} |
|
|
|
|
|
|
|
// 更新输入框值 |
|
|
|
this.endInput = this.dest.name || '地图选点' |
|
|
|
}, |
|
|
|
|
|
|
|
/** |
|
|
|
* 13. 切换路径模式(最短/最优,替代onChangeMode) |
|
|
|
*/ |
|
|
|
handleChangeMode(modeType) { |
|
|
|
this.currentMode = modeType |
|
|
|
// 重新规划路径 |
|
|
|
this.handleNaviAndDrawLine() |
|
|
|
}, |
|
|
|
|
|
|
|
/** |
|
|
|
* 14. 分析路径并绘制路线(替代naviAndDrawLine) |
|
|
|
*/ |
|
|
|
handleNaviAndDrawLine() { |
|
|
|
// 起终点不全则返回 |
|
|
|
if (!this.start || !this.dest) { |
|
|
|
this.$message.warning('请先选择起点和终点') |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
// 清除原有路径线 |
|
|
|
if (this.lineMarker) { |
|
|
|
this.lineMarker.remove() |
|
|
|
this.lineMarker = null |
|
|
|
} |
|
|
|
|
|
|
|
// 设置避障区域(仅避障模式生效) |
|
|
|
if (this.activeTab === '2') { |
|
|
|
const obstructions = this.obstacleAreas.map(points => { |
|
|
|
return new FMNaviObstruction({ |
|
|
|
level: this.map.getLevel(), |
|
|
|
points |
|
|
|
}) |
|
|
|
}) |
|
|
|
this.routeAnalyser.setObstructions(obstructions) |
|
|
|
} else { |
|
|
|
this.routeAnalyser.setObstructions([]) |
|
|
|
} |
|
|
|
|
|
|
|
// 构造路径请求参数 |
|
|
|
const naviMode = this.currentMode === 'shortest' |
|
|
|
? FMNaviMode.MODULE_SHORTEST |
|
|
|
: FMNaviMode.MODULE_BEST |
|
|
|
const request = { |
|
|
|
start: this.start, |
|
|
|
dest: this.dest, |
|
|
|
mode: naviMode, |
|
|
|
priority: FMNaviPriority.PRIORITY_LIFTFIRST // 直梯优先(与原逻辑一致) |
|
|
|
} |
|
|
|
|
|
|
|
// 执行路径分析 |
|
|
|
this.routeAnalyser.route( |
|
|
|
request, |
|
|
|
(result) => { |
|
|
|
// 隐藏搜索结果,显示路径结果 |
|
|
|
this.showSearchResult = false |
|
|
|
this.showNaviResult = true |
|
|
|
this.naviResult = result |
|
|
|
|
|
|
|
// 绘制路径线 |
|
|
|
const segments = [] |
|
|
|
let currentSegment = new fengmap.FMSegment() |
|
|
|
|
|
|
|
result.subs.forEach((leg, index) => { |
|
|
|
// 同一楼层的路径点合并为一个线段 |
|
|
|
if (leg.levels[0] === leg.levels[1]) { |
|
|
|
// 设置路径点z轴高度(避免贴地) |
|
|
|
leg.waypoint.points.forEach(point => { |
|
|
|
point.z = 0.5 |
|
|
|
}) |
|
|
|
|
|
|
|
if (currentSegment.points) { |
|
|
|
// 移除重复的第一个点(避免线段重叠) |
|
|
|
const points = leg.waypoint.points.slice(1) |
|
|
|
currentSegment.points = currentSegment.points.concat(points) |
|
|
|
} else { |
|
|
|
currentSegment.points = leg.waypoint.points |
|
|
|
} |
|
|
|
currentSegment.level = leg.levels[0] |
|
|
|
|
|
|
|
// 最后一个路径段直接添加 |
|
|
|
if (index === result.subs.length - 1) { |
|
|
|
segments.push(currentSegment) |
|
|
|
} |
|
|
|
} else { |
|
|
|
// 跨楼层则结束当前线段,开始新线段 |
|
|
|
segments.push(currentSegment) |
|
|
|
currentSegment = new fengmap.FMSegment() |
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
// 创建路径线图标并添加到地图 |
|
|
|
this.lineMarker = new fengmap.FMLineMarker({ |
|
|
|
segments: segments, |
|
|
|
smooth: true // 路径线平滑处理 |
|
|
|
}) |
|
|
|
this.lineMarker.addTo(this.map) |
|
|
|
}, |
|
|
|
() => { |
|
|
|
// 路径规划失败处理 |
|
|
|
this.showNaviResult = true |
|
|
|
this.naviResult = { subs: [], distance: 0 } |
|
|
|
this.$message.error('路径规划失败,请检查起终点是否可达') |
|
|
|
} |
|
|
|
) |
|
|
|
}, |
|
|
|
|
|
|
|
/** |
|
|
|
* 15. 清除地图所有元素(替代clearAll) |
|
|
|
*/ |
|
|
|
handleClearAll() { |
|
|
|
// 清空输入框 |
|
|
|
this.startInput = '' |
|
|
|
this.endInput = '' |
|
|
|
|
|
|
|
// 隐藏结果区域 |
|
|
|
this.showSearchResult = false |
|
|
|
this.showNaviResult = false |
|
|
|
|
|
|
|
// 清空起终点数据 |
|
|
|
this.start = null |
|
|
|
this.dest = null |
|
|
|
|
|
|
|
// 清除起终点图标 |
|
|
|
if (this.startMarker) { |
|
|
|
this.startMarker.remove() |
|
|
|
this.startMarker = null |
|
|
|
} |
|
|
|
if (this.endMarker) { |
|
|
|
this.endMarker.remove() |
|
|
|
this.endMarker = null |
|
|
|
} |
|
|
|
|
|
|
|
// 清除路径线 |
|
|
|
if (this.lineMarker) { |
|
|
|
this.lineMarker.remove() |
|
|
|
this.lineMarker = null |
|
|
|
} |
|
|
|
|
|
|
|
// 清除选点弹窗 |
|
|
|
if (this.domMarker) { |
|
|
|
this.domMarker.remove() |
|
|
|
this.domMarker = null |
|
|
|
} |
|
|
|
|
|
|
|
// 清除搜索结果 |
|
|
|
this.searchResult = [] |
|
|
|
this.selectedIndex = -1 |
|
|
|
}, |
|
|
|
|
|
|
|
/** |
|
|
|
* 16. 选中搜索结果(替代onResultSelected) |
|
|
|
*/ |
|
|
|
handleResultSelected(index) { |
|
|
|
const selectedItem = this.searchResult[index] |
|
|
|
if (!selectedItem) return |
|
|
|
|
|
|
|
// 更新输入框值 |
|
|
|
const floorName = this.getFloorNameByGid(selectedItem.level) |
|
|
|
if (this.activeInput === 'startInput') { |
|
|
|
this.startInput = `${selectedItem.name} ${floorName}` |
|
|
|
} else { |
|
|
|
this.endInput = `${selectedItem.name} ${floorName}` |
|
|
|
} |
|
|
|
|
|
|
|
// 设置点击坐标并渲染起/终点图标 |
|
|
|
this.clickPoint = { |
|
|
|
level: selectedItem.level, |
|
|
|
x: selectedItem.center.x, |
|
|
|
y: selectedItem.center.y, |
|
|
|
name: selectedItem.name |
|
|
|
} |
|
|
|
|
|
|
|
if (this.activeInput === 'startInput') { |
|
|
|
this.setStart() |
|
|
|
} else { |
|
|
|
this.setEnd() |
|
|
|
} |
|
|
|
|
|
|
|
// 隐藏搜索结果 |
|
|
|
this.showSearchResult = false |
|
|
|
}, |
|
|
|
|
|
|
|
/** |
|
|
|
* 17. 点击路径步骤定位到对应位置(替代onRouteClick) |
|
|
|
*/ |
|
|
|
handleRouteClick(step) { |
|
|
|
const points = step.waypoint.points |
|
|
|
if (points.length < 2) return |
|
|
|
|
|
|
|
// 计算路径段中心点 |
|
|
|
const centerX = (points[0].x + points[1].x) / 2 |
|
|
|
const centerY = (points[0].y + points[1].y) / 2 |
|
|
|
|
|
|
|
// 地图定位到中心点并放大 |
|
|
|
this.map.setCenter({ |
|
|
|
x: centerX, |
|
|
|
y: centerY, |
|
|
|
animate: true, |
|
|
|
finish: () => { |
|
|
|
this.map.setZoom({ zoom: 21, animate: true }) |
|
|
|
} |
|
|
|
}) |
|
|
|
}, |
|
|
|
|
|
|
|
/** |
|
|
|
* 18. 清除避障区域图标(替代clearObstructMarker) |
|
|
|
*/ |
|
|
|
clearObstructMarker() { |
|
|
|
this.obstructMarkers.forEach(marker => { |
|
|
|
marker.remove() |
|
|
|
}) |
|
|
|
this.obstructMarkers = [] |
|
|
|
}, |
|
|
|
|
|
|
|
/** |
|
|
|
* 19. 添加避障区域图标(替代addObstructMarker) |
|
|
|
*/ |
|
|
|
addObstructMarker() { |
|
|
|
const floor = this.map.getFloor(this.map.getLevel()) |
|
|
|
const markers = this.obstacleAreas.map(points => { |
|
|
|
const marker = new fengmap.FMPolygonMarker({ |
|
|
|
points, |
|
|
|
opacity: 0.5, |
|
|
|
color: '#FF4C3F' // 避障区域红色半透明(与原逻辑视觉一致) |
|
|
|
}) |
|
|
|
marker.addTo(floor) |
|
|
|
return marker |
|
|
|
}) |
|
|
|
this.obstructMarkers = markers |
|
|
|
}, |
|
|
|
|
|
|
|
/** |
|
|
|
* 20. Tab切换事件(替代layui的tab事件) |
|
|
|
*/ |
|
|
|
handleTabChange() { |
|
|
|
// 切换Tab时清除所有元素 |
|
|
|
this.handleClearAll() |
|
|
|
// 避障模式添加避障区域图标 |
|
|
|
if (this.activeTab === '2') { |
|
|
|
this.addObstructMarker() |
|
|
|
} else { |
|
|
|
this.clearObstructMarker() |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
/** |
|
|
|
* 21. 清除所有资源(组件销毁前调用) |
|
|
|
*/ |
|
|
|
clearAllResources() { |
|
|
|
this.clearObstructMarker() |
|
|
|
if (this.searchAnalyser) this.searchAnalyser.destroy() |
|
|
|
if (this.routeAnalyser) this.routeAnalyser.destroy() |
|
|
|
}, |
|
|
|
|
|
|
|
/** |
|
|
|
* 22. 获取方向图标URL(用于路径步骤显示) |
|
|
|
*/ |
|
|
|
getDirectionIcon(direction) { |
|
|
|
const imgName = this.directionImg[direction] || 'front.png' |
|
|
|
return `https://developer.fengmap.com/fmAPI/images/navi/${imgName}` |
|
|
|
}, |
|
|
|
|
|
|
|
updateClickUI(event, clickMode, floor) { |
|
|
|
// 拾取模型对象 |
|
|
|
const target = event.targets[0] |
|
|
|
@ -277,7 +1029,7 @@ export default { |
|
|
|
console.log('搜索结果:', data) |
|
|
|
this.imageMarkers = data.map(feature => { |
|
|
|
const marker = new fengmap.FMImageMarker({ |
|
|
|
url: 'https://developer.fengmap.com/fmAPI/images/red.png', |
|
|
|
url: locationIcon, |
|
|
|
x: feature.center.x, |
|
|
|
y: feature.center.y, |
|
|
|
anchor: fengmap.FMMarkerAnchor.BOTTOM |
|
|
|
|