Browse Source

路径导航

master
xuhuajiao 1 month ago
parent
commit
1393ca2ef6
  1. BIN
      src/assets/images/red.png
  2. 792
      src/views/deviceManage/map/indexv3.vue
  3. 1372
      src/views/deviceManage/map/indexv4.vue
  4. 10
      vue.config.js

BIN
src/assets/images/red.png

After

Width: 594  |  Height: 594  |  Size: 21 KiB

792
src/views/deviceManage/map/indexv3.vue

@ -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: [],
// Tab12
activeTab: '1',
//
startInput: '',
endInput: '',
// startInput/endInput
activeInput: '',
//
searchResult: [],
selectedIndex: -1,
showSearchResult: false,
//
naviResult: {},
showNaviResult: false,
// shortestbest
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
}
// VueDOMDOM
this.domMarker = new fengmap.FMDomMarker({
x: position.x,
y: position.y,
domHeight: 80,
domWidth: 200,
anchor: fengmap.FMMarkerAnchor.BOTTOM,
// window.vueInstanceVue
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

1372
src/views/deviceManage/map/indexv4.vue
File diff suppressed because it is too large
View File

10
vue.config.js

@ -53,12 +53,12 @@ module.exports = {
pathRewrite: {
'^/auth': 'auth'
}
},
'/fengmap': {
target: 'https://developer.fengmap.com/',
changeOrigin: true,
pathRewrite: { '^/fengmap': '' }
}
// '/fengmap': {
// target: 'https://developer.fengmap.com/',
// changeOrigin: true,
// pathRewrite: { '^/fengmap': '' }
// }
}
},
configureWebpack: {

Loading…
Cancel
Save