【类 型】:feat
【原 因】:飞行区域安全控制需求,需要前端支持禁飞区与限飞区的可视化设置与展示。 【过 程】:- 新增 RestrictflyControl 控件,支持限飞区绘制并弹窗设置高度; - 支持将限飞区多边形及高度通过 setRestrictflyData 接口保存; - 在飞行地图中新增 PolygonToggleControl 控件,可开关显示禁飞区/限飞区图层; - 限飞区标签支持显示限高信息,如“限飞高度120米”。 【影 响】: # 类型 包含: # feat:新功能(feature) # fix:修补bug # docs:文档(documentation) # style: 格式(不影响代码运行的变动) # refactor:重构(即不是新增功能,也不是修改bug的代码变动) # test:增加测试 # chore:构建过程或辅助工具的变动
This commit is contained in:
parent
d731a42bfa
commit
555d480efd
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
.DS_Store
|
||||
/node_modules
|
||||
/dist
|
||||
/package-lock.json
|
||||
|
31270
package-lock.json
generated
31270
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -9,16 +9,17 @@
|
||||
"lint": "eslint --ext .js,.vue src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@turf/turf": "^7.2.0",
|
||||
"axios": "^1.9.0",
|
||||
"core-js": "^3.6.5",
|
||||
"echarts": "^5.6.0",
|
||||
"element-ui": "^2.15.14",
|
||||
"geodist": "^0.2.1",
|
||||
"lodash": "^4.17.21",
|
||||
"mapbox-gl": "^2.15.0",
|
||||
"mqtt": "^2.18.9",
|
||||
"normalize.css": "^8.0.1",
|
||||
"nprogress": "^0.2.0",
|
||||
"geodist": "^0.2.1",
|
||||
"vue": "^2.6.11",
|
||||
"vue-router": "^3.6.5",
|
||||
"vue-template-compiler": "^2.7.16",
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
<script>
|
||||
import mapboxgl from 'mapbox-gl'
|
||||
import { MapboxStyleSwitcherControl, FollowControl, CustomFullscreenControl, NoFlyControl, SaveToFileControl, PolygonToggleControl } from '@/utils/mapboxgl_plugs'
|
||||
import { MapboxStyleSwitcherControl, FollowControl, CustomFullscreenControl, NoFlyControl, RestrictflyControl, SaveToFileControl, PolygonToggleControl } from '@/utils/mapboxgl_plugs'
|
||||
import planeIcon from '@/assets/svg/plane.svg'
|
||||
|
||||
export default {
|
||||
@ -172,6 +172,10 @@ export default {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
enableRestrictfly: { // 限飞区 设置 组件
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
enableShowNofly: { // 显示 禁飞区 限飞区 组件
|
||||
type: Boolean,
|
||||
default: false
|
||||
@ -345,22 +349,23 @@ export default {
|
||||
|
||||
// 禁飞区 限飞区 设置组件
|
||||
if (this.enableNofly) {
|
||||
const noflyPolygons = this.$store.state.noflyData[0]
|
||||
const restrictflyPolygons = this.$store.state.noflyData[1]
|
||||
const shopId = this.$store.state.user.shop_id
|
||||
|
||||
this.map.addControl(new NoFlyControl({
|
||||
noflyPolygons,
|
||||
restrictflyPolygons,
|
||||
shopId,
|
||||
onSave: (nofly, limit) => {
|
||||
console.log('保存成功:', nofly, limit)
|
||||
},
|
||||
onDrawFinish: (nofly) => {
|
||||
console.log('禁飞区绘制完成:', nofly)
|
||||
},
|
||||
onLimitFinish: (limit) => {
|
||||
console.log('限飞区绘制完成:', limit)
|
||||
defaultPolygons: this.$store.state.noflyData[0], // 禁飞
|
||||
shopId: this.$store.state.user.shop_id,
|
||||
onSave: (data) => {
|
||||
console.log('保存成功:', data)
|
||||
}
|
||||
}), 'top-left')
|
||||
}
|
||||
|
||||
// 禁飞区 限飞区 设置组件
|
||||
if (this.enableRestrictfly) {
|
||||
this.map.addControl(new RestrictflyControl({
|
||||
defaultPolygons: this.$store.state.noflyData[1], // 限飞区
|
||||
defaultHeights: this.$store.state.noflyData[2], // 限飞区对应高度
|
||||
shopId: this.$store.state.user.shop_id,
|
||||
onSave: (data) => {
|
||||
console.log('保存成功:', data)
|
||||
}
|
||||
}), 'top-left')
|
||||
}
|
||||
@ -369,6 +374,7 @@ export default {
|
||||
if (this.enableShowNofly) {
|
||||
const noflyPolygons = this.$store.state.noflyData[0] || []
|
||||
const restrictflyPolygons = this.$store.state.noflyData[1] || []
|
||||
const restrictflyHeights = this.$store.state.noflyData[2] || []
|
||||
|
||||
this.map.addControl(new PolygonToggleControl({
|
||||
defaultIconClass: 'iconfont icon-jinfeiqu_weidianji f-s-20 seatFontColor',
|
||||
@ -376,16 +382,18 @@ export default {
|
||||
|
||||
onToggle: (isActive) => {
|
||||
if (isActive) {
|
||||
// 激活时绘制禁飞区
|
||||
// 绘制禁飞区
|
||||
noflyPolygons.forEach((coords, i) => {
|
||||
this.drawPolygonWithLabel(coords, `nofly-polygon-${i}`, '#F33', '禁飞区')
|
||||
})
|
||||
// 绘制限飞区
|
||||
// 绘制限飞区,添加高度标签
|
||||
restrictflyPolygons.forEach((coords, i) => {
|
||||
this.drawPolygonWithLabel(coords, `restrict-polygon-${i}`, '#F83', '限飞区')
|
||||
const height = restrictflyHeights[i] || 0
|
||||
const label = `限飞高度${height}米`
|
||||
this.drawPolygonWithLabel(coords, `restrict-polygon-${i}`, '#F83', label)
|
||||
})
|
||||
} else {
|
||||
// 关闭时清除禁飞区和限飞区相关图层和数据源
|
||||
// 清除图层
|
||||
const layerIds = []
|
||||
const sourceIds = []
|
||||
|
||||
|
@ -53,7 +53,7 @@ const routes = [
|
||||
component: Layout,
|
||||
redirect: '/register/index',
|
||||
meta: {
|
||||
title: '飞机管理',
|
||||
title: '管理飞机',
|
||||
icon: 'el-icon-edit-outline',
|
||||
roles: ['admin', 'editor'],
|
||||
tapName: 'plane'
|
||||
@ -116,23 +116,33 @@ const routes = [
|
||||
{
|
||||
path: '/nofly',
|
||||
component: Layout,
|
||||
redirect: '/nofly/set',
|
||||
redirect: '/nofly/setNofly',
|
||||
meta: {
|
||||
title: '设置禁飞区',
|
||||
icon: 'iconfont icon-feihangluxian',
|
||||
title: '限制飞行',
|
||||
icon: 'iconfont icon-jinfeiqu',
|
||||
roles: ['admin', 'editor'],
|
||||
tapName: 'plane'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/nofly/set',
|
||||
component: () => import('@/views/layout/components/main/nofly/set'),
|
||||
path: '/nofly/setNofly',
|
||||
component: () => import('@/views/layout/components/main/nofly/setNofly'),
|
||||
meta: {
|
||||
title: '设置禁飞区',
|
||||
icon: 'iconfont icon-huizhi',
|
||||
roles: ['admin', 'editor'],
|
||||
tapName: 'plane'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/nofly/setRestrictfly',
|
||||
component: () => import('@/views/layout/components/main/nofly/setRestrictfly'),
|
||||
meta: {
|
||||
title: '设置限飞区',
|
||||
icon: 'iconfont icon-huizhi',
|
||||
roles: ['admin', 'editor'],
|
||||
tapName: 'plane'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -141,7 +151,7 @@ const routes = [
|
||||
component: Layout,
|
||||
redirect: '/route/index',
|
||||
meta: {
|
||||
title: '航线管理',
|
||||
title: '管理航线',
|
||||
icon: 'iconfont icon-feihangluxian',
|
||||
roles: ['admin', 'editor'],
|
||||
tapName: 'plane'
|
||||
@ -185,7 +195,7 @@ const routes = [
|
||||
component: Layout,
|
||||
redirect: '/site/index',
|
||||
meta: {
|
||||
title: '站点管理',
|
||||
title: '管理站点',
|
||||
icon: 'iconfont icon-zhandianguanli',
|
||||
roles: ['admin', 'editor'],
|
||||
tapName: 'plane'
|
||||
@ -252,7 +262,7 @@ const routes = [
|
||||
component: Layout,
|
||||
redirect: '/shop/edit',
|
||||
meta: {
|
||||
title: '商铺管理',
|
||||
title: '管理商铺',
|
||||
icon: 'iconfont icon-a-shanghu_choose2x1',
|
||||
roles: ['admin', 'editor'],
|
||||
tapName: 'admin'
|
||||
@ -262,7 +272,7 @@ const routes = [
|
||||
path: '/shop/edit',
|
||||
component: () => import('@/views/layout/components/main/shop/add'),
|
||||
meta: {
|
||||
title: '商铺设置',
|
||||
title: '设置商铺',
|
||||
icon: 'iconfont icon-dianpuguanli',
|
||||
roles: ['admin', 'editor'],
|
||||
tapName: 'admin'
|
||||
@ -340,7 +350,7 @@ const routes = [
|
||||
component: Layout,
|
||||
redirect: '/category/index',
|
||||
meta: {
|
||||
title: '分类管理',
|
||||
title: '管理分类',
|
||||
icon: 'iconfont icon-a-ziliaocaozuoxianshifenleishu',
|
||||
roles: ['admin', 'editor'],
|
||||
tapName: 'admin'
|
||||
@ -350,7 +360,7 @@ const routes = [
|
||||
path: '/category/index',
|
||||
component: () => import('@/views/layout/components/main/category/index'),
|
||||
meta: {
|
||||
title: '分类管理',
|
||||
title: '管理分类',
|
||||
icon: 'iconfont icon-a-ziliaocaozuoxianshifenleishu',
|
||||
roles: ['admin', 'editor'],
|
||||
tapName: 'admin'
|
||||
@ -363,7 +373,7 @@ const routes = [
|
||||
component: Layout,
|
||||
redirect: '/spu/index',
|
||||
meta: {
|
||||
title: '商品管理',
|
||||
title: '管理商品',
|
||||
icon: 'iconfont icon-chanpin',
|
||||
roles: ['admin', 'editor'],
|
||||
tapName: 'admin'
|
||||
@ -438,7 +448,7 @@ const routes = [
|
||||
component: Layout,
|
||||
redirect: '/broadcast/banner',
|
||||
meta: {
|
||||
title: '广告管理',
|
||||
title: '管理广告',
|
||||
icon: 'iconfont icon-guanggao',
|
||||
roles: ['admin', 'editor'],
|
||||
tapName: 'admin'
|
||||
@ -448,7 +458,7 @@ const routes = [
|
||||
path: '/broadcast/banner',
|
||||
component: () => import('@/views/layout/components/main/broadcast/banner'),
|
||||
meta: {
|
||||
title: 'banner设置',
|
||||
title: '设置banner',
|
||||
icon: 'iconfont icon-banner',
|
||||
roles: ['admin', 'editor'],
|
||||
tapName: 'admin'
|
||||
@ -458,7 +468,7 @@ const routes = [
|
||||
path: '/broadcast/notice',
|
||||
component: () => import('@/views/layout/components/main/broadcast/notice'),
|
||||
meta: {
|
||||
title: '滚动通知设置',
|
||||
title: '设置滚动通知',
|
||||
icon: 'iconfont icon-m-gundongwenzi',
|
||||
roles: ['admin', 'editor'],
|
||||
tapName: 'admin'
|
||||
@ -481,7 +491,7 @@ const routes = [
|
||||
path: '/order/index',
|
||||
component: () => import('@/views/layout/components/main/order/index'),
|
||||
meta: {
|
||||
title: '订单管理',
|
||||
title: '管理订单',
|
||||
icon: 'iconfont icon-a-SalesOrderManagement',
|
||||
roles: ['admin', 'editor'],
|
||||
tapName: 'admin'
|
||||
|
@ -17,7 +17,7 @@ const store = new Vuex.Store({
|
||||
airList: [], // 所有飞机列表
|
||||
siteList: [], // 站点列表
|
||||
routeList: [], // 航线列表
|
||||
noflyData: [[], []], // [0]禁飞区数据 [1]限制飞区
|
||||
noflyData: [[], [], []], // [0]禁飞区数据 [1]限制飞区 [2]限飞区高度
|
||||
categoryList: [], // 分类列表(小程序)
|
||||
spuList: [], // 商品spu列表
|
||||
skuList: [], // 商品sku列表
|
||||
@ -70,13 +70,14 @@ const store = new Vuex.Store({
|
||||
* @description: 设置禁飞区列表
|
||||
*/
|
||||
setNoflyData (state, payload) {
|
||||
if (payload && payload.nofly_data && payload.restrictfly_data) {
|
||||
if (payload && payload.nofly_data && payload.restrictfly_data && payload.restrictfly_height) {
|
||||
state.noflyData = [
|
||||
JSON.parse(payload.nofly_data || '[]'),
|
||||
JSON.parse(payload.restrictfly_data || '[]')
|
||||
JSON.parse(payload.restrictfly_data || '[]'),
|
||||
JSON.parse(payload.restrictfly_height || '[]')
|
||||
]
|
||||
} else {
|
||||
state.noflyData = [[], []]
|
||||
state.noflyData = [[], [], []]
|
||||
}
|
||||
},
|
||||
/**
|
||||
@ -625,7 +626,7 @@ const store = new Vuex.Store({
|
||||
if (res.data.status === 1) {
|
||||
commit('setNoflyData', res.data.noflyData)
|
||||
} else {
|
||||
commit('setNoflyData', [[], []])
|
||||
commit('setNoflyData', [[], [], []])
|
||||
Message.warning(res.data.msg || '暂无禁飞区数据')
|
||||
}
|
||||
return res
|
||||
|
@ -198,16 +198,15 @@ export async function saveFlyData (data) {
|
||||
* @param {Array} restrictfly_data 限制飞区数据数组
|
||||
* @returns {Object|null} 返回接口响应数据 或 null 表示失败
|
||||
*/
|
||||
export async function setNoflyData (shopId, noflyData, restrictflyData) {
|
||||
export async function setNoflyData (shopId, noflyData) {
|
||||
try {
|
||||
const params = new URLSearchParams()
|
||||
params.append('shop_id', shopId || '')
|
||||
params.append('nofly_data', JSON.stringify(noflyData || []))
|
||||
params.append('restrictfly_data', JSON.stringify(restrictflyData || []))
|
||||
|
||||
const res = await api.post('setNoflyData', params)
|
||||
if (res.data.status === 1) {
|
||||
store.dispatch('fetchNoflyData', shopId)// 更新禁飞区数据
|
||||
store.dispatch('fetchNoflyData', shopId)// 更新禁限飞区数据
|
||||
Message.success(res.data.msg)
|
||||
} else {
|
||||
Message.warning(res.data.msg)
|
||||
@ -218,3 +217,31 @@ export async function setNoflyData (shopId, noflyData, restrictflyData) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 保存限飞区数据
|
||||
* @param {string|number} shop_id 商铺ID
|
||||
* @param {Array} restrictfly_data 限制飞区数据数组
|
||||
* @param {Array} restrictfly_height 限制飞区数据高度组
|
||||
* @returns {Object|null} 返回接口响应数据 或 null 表示失败
|
||||
*/
|
||||
export async function setRestrictflyData (shopId, restrictflyData, restrictflyHeight) {
|
||||
try {
|
||||
const params = new URLSearchParams()
|
||||
params.append('shop_id', shopId || '')
|
||||
params.append('restrictfly_data', JSON.stringify(restrictflyData || []))
|
||||
params.append('restrictfly_height', JSON.stringify(restrictflyHeight || []))
|
||||
|
||||
const res = await api.post('setNoflyData', params)
|
||||
if (res.data.status === 1) {
|
||||
store.dispatch('fetchNoflyData', shopId)// 更新禁限飞区数据
|
||||
Message.success(res.data.msg)
|
||||
} else {
|
||||
Message.warning(res.data.msg)
|
||||
}
|
||||
return res.data
|
||||
} catch (error) {
|
||||
Message.error('保存限飞区数据失败')
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import mapboxgl from 'mapbox-gl'
|
||||
import MapboxDraw from '@mapbox/mapbox-gl-draw'
|
||||
import '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css'
|
||||
import { setNoflyData } from '@/utils/api/table'
|
||||
import { Message } from 'element-ui'
|
||||
import { setNoflyData, setRestrictflyData } from '@/utils/api/table'
|
||||
import { Message, MessageBox } from 'element-ui'
|
||||
|
||||
/**
|
||||
* 自定义地图样式切换控件
|
||||
@ -218,65 +218,45 @@ export class CustomFullscreenControl extends mapboxgl.FullscreenControl {
|
||||
}
|
||||
}
|
||||
|
||||
// 设置禁飞区 限飞区 保存 删除控件
|
||||
|
||||
// 禁飞区控件类
|
||||
export class NoFlyControl {
|
||||
constructor (options = {}) {
|
||||
this._map = null
|
||||
this._container = null
|
||||
|
||||
this._draw = null
|
||||
this._onDrawFinish = options.onDrawFinish || function () {}
|
||||
this._noflyPolygons = options.noflyPolygons || []
|
||||
|
||||
this._onLimitFinish = options.onLimitFinish || function () {}
|
||||
this._restrictflyPolygons = options.restrictflyPolygons || []
|
||||
|
||||
this._onSave = options.onSave || function () {}
|
||||
this._shopId = options.shopId
|
||||
|
||||
this._activeMode = 'nofly' // 当前绘制类型
|
||||
this._map = null // 地图实例
|
||||
this._container = null // 控件容器(用于放按钮)
|
||||
this._draw = null // MapboxDraw 实例
|
||||
this._onDrawFinish = options.onDrawFinish || function () {} // 绘制完成时的回调
|
||||
this._defaultPolygons = options.defaultPolygons || [] // 初始加载的禁飞区多边形坐标
|
||||
this._onSave = options.onSave || function () {} // 保存时的回调
|
||||
this._shopId = options.shopId // 商户ID,用于提交接口
|
||||
}
|
||||
|
||||
// 当控件添加到地图上时调用
|
||||
onAdd (map) {
|
||||
this._map = map
|
||||
|
||||
// 创建控件按钮容器
|
||||
this._container = document.createElement('div')
|
||||
this._container.className = 'mapboxgl-ctrl mapboxgl-ctrl-group'
|
||||
|
||||
// --- 绘制禁飞区按钮 ---
|
||||
// --- 绘制按钮 ---
|
||||
const drawButton = document.createElement('button')
|
||||
drawButton.className = 'mapboxgl-ctrl-icon'
|
||||
drawButton.type = 'button'
|
||||
drawButton.innerHTML = '✏️'
|
||||
drawButton.title = '绘制禁飞区'
|
||||
drawButton.onclick = () => {
|
||||
this._activeMode = 'nofly'
|
||||
this._enableDraw()
|
||||
}
|
||||
drawButton.innerHTML = '✏️' // 图标:铅笔
|
||||
drawButton.title = '绘制禁飞区' // 鼠标提示
|
||||
drawButton.onclick = () => this._enableDraw() // 点击启用绘制模式
|
||||
this._container.appendChild(drawButton)
|
||||
|
||||
// --- 绘制限飞区按钮 ---
|
||||
const limitButton = document.createElement('button')
|
||||
limitButton.className = 'mapboxgl-ctrl-icon'
|
||||
limitButton.type = 'button'
|
||||
limitButton.innerHTML = '✒️'
|
||||
limitButton.title = '绘制限飞区'
|
||||
limitButton.onclick = () => {
|
||||
this._activeMode = 'limit'
|
||||
this._enableDraw()
|
||||
}
|
||||
this._container.appendChild(limitButton)
|
||||
|
||||
// --- 保存按钮 ---
|
||||
const saveButton = document.createElement('button')
|
||||
saveButton.className = 'mapboxgl-ctrl-icon'
|
||||
saveButton.type = 'button'
|
||||
saveButton.innerHTML = '💾'
|
||||
saveButton.title = '保存禁飞区及限飞区'
|
||||
saveButton.innerHTML = '💾' // 图标:磁盘
|
||||
saveButton.title = '保存禁飞区'
|
||||
saveButton.onclick = () => this._savePolygons()
|
||||
this._container.appendChild(saveButton)
|
||||
|
||||
// --- 删除按钮 ---
|
||||
// --- 删除按钮(删除选中图形) ---
|
||||
const deleteButton = document.createElement('button')
|
||||
deleteButton.className = 'mapboxgl-ctrl-icon'
|
||||
deleteButton.type = 'button'
|
||||
@ -285,43 +265,248 @@ export class NoFlyControl {
|
||||
deleteButton.onclick = () => this._deleteSelected()
|
||||
this._container.appendChild(deleteButton)
|
||||
|
||||
// 初始化 Draw 控件
|
||||
// --- 初始化 MapboxDraw 控件 ---
|
||||
this._draw = new MapboxDraw({
|
||||
displayControlsDefault: false,
|
||||
styles: this._getDrawStyles()
|
||||
displayControlsDefault: false, // 不显示默认工具条
|
||||
styles: [ // 自定义绘制样式(橙色)
|
||||
{
|
||||
id: 'gl-draw-polygon-fill',
|
||||
type: 'fill',
|
||||
filter: ['all', ['==', '$type', 'Polygon'], ['!=', 'mode', 'static']],
|
||||
paint: {
|
||||
'fill-color': '#f33', // 填充颜色
|
||||
'fill-opacity': 0.3 // 透明度
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'gl-draw-polygon-stroke-active',
|
||||
type: 'line',
|
||||
filter: ['all', ['==', '$type', 'Polygon'], ['!=', 'mode', 'static']],
|
||||
paint: {
|
||||
'line-color': '#f33', // 边框颜色
|
||||
'line-width': 2
|
||||
}
|
||||
},
|
||||
// 控制点外圈(白色光晕)
|
||||
{
|
||||
id: 'gl-draw-polygon-and-line-vertex-halo-active',
|
||||
type: 'circle',
|
||||
filter: ['all', ['==', 'meta', 'vertex'], ['==', '$type', 'Point']],
|
||||
paint: {
|
||||
'circle-radius': 7,
|
||||
'circle-color': '#fff'
|
||||
}
|
||||
},
|
||||
// 控制点内圈
|
||||
{
|
||||
id: 'gl-draw-polygon-and-line-vertex-active',
|
||||
type: 'circle',
|
||||
filter: ['all', ['==', 'meta', 'vertex'], ['==', '$type', 'Point']],
|
||||
paint: {
|
||||
'circle-radius': 5,
|
||||
'circle-color': '#fff'
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// 将 Draw 控件添加到地图上
|
||||
map.addControl(this._draw)
|
||||
|
||||
// 加载已有禁飞区
|
||||
if (this._noflyPolygons.length > 0) {
|
||||
const features = this._noflyPolygons.map(coords => ({
|
||||
// --- 加载已有禁飞区(回显数据) ---
|
||||
if (this._defaultPolygons.length > 0) {
|
||||
const features = this._defaultPolygons.map(coords => ({
|
||||
type: 'Feature',
|
||||
geometry: {
|
||||
type: 'Polygon',
|
||||
coordinates: [coords] // 注意:GeoJSON Polygon 外层需要再包一层
|
||||
},
|
||||
properties: {}
|
||||
}))
|
||||
this._draw.set({
|
||||
type: 'FeatureCollection',
|
||||
features
|
||||
})
|
||||
}
|
||||
|
||||
// 监听绘制完成事件
|
||||
map.on('draw.create', this._handleDraw.bind(this))
|
||||
|
||||
return this._container
|
||||
}
|
||||
|
||||
// 当控件被移除时执行
|
||||
onRemove () {
|
||||
if (this._map && this._draw) {
|
||||
this._map.removeControl(this._draw)
|
||||
}
|
||||
if (this._container?.parentNode) {
|
||||
this._container.parentNode.removeChild(this._container)
|
||||
}
|
||||
this._map = null
|
||||
}
|
||||
|
||||
// 启用多边形绘制模式
|
||||
_enableDraw () {
|
||||
if (this._draw && this._map) {
|
||||
this._draw.changeMode('draw_polygon')
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制完成的处理函数
|
||||
_handleDraw (e) {
|
||||
const features = this._draw.getAll()
|
||||
if (features.features.length > 0) {
|
||||
const coordinates = features.features.map(f => f.geometry.coordinates)
|
||||
this._onDrawFinish(coordinates) // 通知外部最新绘制结果
|
||||
}
|
||||
}
|
||||
|
||||
// 删除当前选中的图形
|
||||
_deleteSelected () {
|
||||
if (this._draw) {
|
||||
const selected = this._draw.getSelectedIds()
|
||||
if (selected.length > 0) {
|
||||
this._draw.delete(selected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 提交所有禁飞区数据到服务器
|
||||
async _savePolygons () {
|
||||
if (!this._draw) return
|
||||
|
||||
const allFeatures = this._draw.getAll()
|
||||
|
||||
// 提取所有 Polygon 类型图形的坐标(只取外环)
|
||||
const polygons = allFeatures.features
|
||||
.filter(f => f.geometry.type === 'Polygon')
|
||||
.map(f => f.geometry.coordinates[0])
|
||||
|
||||
try {
|
||||
const shopId = this._shopId
|
||||
await setNoflyData(shopId, polygons)
|
||||
} catch (error) {
|
||||
Message.error('上传禁飞区数据时发生错误')
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
// 调用保存成功回调
|
||||
this._onSave(polygons)
|
||||
}
|
||||
}
|
||||
|
||||
// 限飞区控件类
|
||||
export class RestrictflyControl {
|
||||
constructor (options = {}) {
|
||||
this._map = null
|
||||
this._container = null
|
||||
this._draw = null
|
||||
this._shopId = options.shopId
|
||||
this._defaultPolygons = options.defaultPolygons || []
|
||||
this._defaultHeights = options.defaultHeights || []
|
||||
this._restrictflyHeights = [] // 保存限高值
|
||||
this._onSave = options.onSave || function () {}
|
||||
}
|
||||
|
||||
onAdd (map) {
|
||||
this._map = map
|
||||
|
||||
this._container = document.createElement('div')
|
||||
this._container.className = 'mapboxgl-ctrl mapboxgl-ctrl-group'
|
||||
|
||||
// 绘制按钮
|
||||
const drawButton = document.createElement('button')
|
||||
drawButton.className = 'mapboxgl-ctrl-icon'
|
||||
drawButton.type = 'button'
|
||||
drawButton.innerHTML = '✏️'
|
||||
drawButton.title = '绘制限飞区'
|
||||
drawButton.onclick = () => this._enableDraw()
|
||||
this._container.appendChild(drawButton)
|
||||
|
||||
// 保存按钮
|
||||
const saveButton = document.createElement('button')
|
||||
saveButton.className = 'mapboxgl-ctrl-icon'
|
||||
saveButton.type = 'button'
|
||||
saveButton.innerHTML = '💾'
|
||||
saveButton.title = '保存限飞区'
|
||||
saveButton.onclick = () => this._savePolygons()
|
||||
this._container.appendChild(saveButton)
|
||||
|
||||
// 删除按钮
|
||||
const deleteButton = document.createElement('button')
|
||||
deleteButton.className = 'mapboxgl-ctrl-icon'
|
||||
deleteButton.type = 'button'
|
||||
deleteButton.innerHTML = '🗑️'
|
||||
deleteButton.title = '删除选中图形'
|
||||
deleteButton.onclick = () => this._deleteSelected()
|
||||
this._container.appendChild(deleteButton)
|
||||
|
||||
// 初始化 MapboxDraw 控件(橙色样式)
|
||||
this._draw = new MapboxDraw({
|
||||
displayControlsDefault: false,
|
||||
styles: [
|
||||
{
|
||||
id: 'gl-draw-polygon-fill',
|
||||
type: 'fill',
|
||||
filter: ['all', ['==', '$type', 'Polygon'], ['!=', 'mode', 'static']],
|
||||
paint: {
|
||||
'fill-color': '#f83',
|
||||
'fill-opacity': 0.3
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'gl-draw-polygon-stroke-active',
|
||||
type: 'line',
|
||||
filter: ['all', ['==', '$type', 'Polygon'], ['!=', 'mode', 'static']],
|
||||
paint: {
|
||||
'line-color': '#f83',
|
||||
'line-width': 2
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'gl-draw-polygon-and-line-vertex-halo-active',
|
||||
type: 'circle',
|
||||
filter: ['all', ['==', 'meta', 'vertex'], ['==', '$type', 'Point']],
|
||||
paint: {
|
||||
'circle-radius': 7,
|
||||
'circle-color': '#fff'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'gl-draw-polygon-and-line-vertex-active',
|
||||
type: 'circle',
|
||||
filter: ['all', ['==', 'meta', 'vertex'], ['==', '$type', 'Point']],
|
||||
paint: {
|
||||
'circle-radius': 5,
|
||||
'circle-color': '#fff'
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
map.addControl(this._draw)
|
||||
|
||||
// 回显已有限飞区
|
||||
if (this._defaultPolygons.length > 0) {
|
||||
const features = this._defaultPolygons.map((coords, index) => ({
|
||||
type: 'Feature',
|
||||
geometry: {
|
||||
type: 'Polygon',
|
||||
coordinates: [coords]
|
||||
},
|
||||
properties: {
|
||||
type: 'nofly'
|
||||
height: this._defaultHeights[index] || 0
|
||||
}
|
||||
}))
|
||||
this._draw.add({ type: 'FeatureCollection', features })
|
||||
}
|
||||
|
||||
// 加载已有限飞区
|
||||
if (this._restrictflyPolygons.length > 0) {
|
||||
const features = this._restrictflyPolygons.map(coords => ({
|
||||
type: 'Feature',
|
||||
geometry: {
|
||||
type: 'Polygon',
|
||||
coordinates: [coords]
|
||||
},
|
||||
properties: {
|
||||
type: 'limit'
|
||||
}
|
||||
}))
|
||||
this._draw.add({ type: 'FeatureCollection', features })
|
||||
this._draw.set({
|
||||
type: 'FeatureCollection',
|
||||
features
|
||||
})
|
||||
this._restrictflyHeights = [...this._defaultHeights]
|
||||
}
|
||||
|
||||
// 监听绘制完成
|
||||
map.on('draw.create', this._handleDraw.bind(this))
|
||||
|
||||
return this._container
|
||||
@ -343,31 +528,21 @@ export class NoFlyControl {
|
||||
}
|
||||
}
|
||||
|
||||
_handleDraw (e) {
|
||||
const features = e.features
|
||||
if (features.length === 0) return
|
||||
async _handleDraw (e) {
|
||||
const features = this._draw.getAll()
|
||||
if (features.features.length > 0) {
|
||||
const newFeature = features.features[features.features.length - 1]
|
||||
|
||||
const type = this._activeMode === 'limit' ? 'limit' : 'nofly'
|
||||
|
||||
features.forEach(f => {
|
||||
f.properties.type = type
|
||||
})
|
||||
|
||||
// 重新添加以确保类型被写入并触发样式
|
||||
this._draw.delete(features.map(f => f.id))
|
||||
this._draw.add({
|
||||
type: 'FeatureCollection',
|
||||
features
|
||||
})
|
||||
|
||||
const allCoords = this._draw.getAll().features
|
||||
.filter(f => f.properties.type === type)
|
||||
.map(f => f.geometry.coordinates)
|
||||
|
||||
if (type === 'nofly') {
|
||||
this._onDrawFinish(allCoords)
|
||||
} else {
|
||||
this._onLimitFinish(allCoords)
|
||||
try {
|
||||
const height = await this._promptHeightInput()
|
||||
this._restrictflyHeights.push(parseFloat(height))
|
||||
// 可选:在 feature 上记录高度
|
||||
newFeature.properties.height = height
|
||||
} catch (error) {
|
||||
// 用户取消输入,移除刚才的图形
|
||||
this._draw.delete(newFeature.id)
|
||||
Message.info('已取消限飞区域绘制')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -375,94 +550,39 @@ export class NoFlyControl {
|
||||
if (this._draw) {
|
||||
const selected = this._draw.getSelectedIds()
|
||||
if (selected.length > 0) {
|
||||
// 同时删除对应的限高值(只支持最后一个被删)
|
||||
this._restrictflyHeights.pop()
|
||||
this._draw.delete(selected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async _savePolygons () {
|
||||
if (!this._draw) return
|
||||
|
||||
const allFeatures = this._draw.getAll()
|
||||
|
||||
const nofly = allFeatures.features
|
||||
.filter(f => f.properties.type === 'nofly')
|
||||
.map(f => f.geometry.coordinates[0])
|
||||
|
||||
const limit = allFeatures.features
|
||||
.filter(f => f.properties.type === 'limit')
|
||||
const polygons = allFeatures.features
|
||||
.filter(f => f.geometry.type === 'Polygon')
|
||||
.map(f => f.geometry.coordinates[0])
|
||||
|
||||
try {
|
||||
const shopId = this._shopId
|
||||
await setNoflyData(shopId, nofly, limit)
|
||||
this._onSave(nofly, limit)
|
||||
await setRestrictflyData(this._shopId, polygons, this._restrictflyHeights)
|
||||
this._onSave({ polygons, heights: this._restrictflyHeights })
|
||||
} catch (error) {
|
||||
Message.error('上传数据失败')
|
||||
Message.error('保存限飞区失败')
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
_getDrawStyles () {
|
||||
const noflyColor = '#ff3333'
|
||||
const limitColor = '#ff8833'
|
||||
|
||||
return [
|
||||
// 区域填充样式
|
||||
{
|
||||
id: 'custom-polygon-fill',
|
||||
type: 'fill',
|
||||
filter: ['all', ['==', '$type', 'Polygon']],
|
||||
paint: {
|
||||
'fill-color': [
|
||||
'case',
|
||||
['==', ['get', 'type'], 'nofly'], noflyColor,
|
||||
['==', ['get', 'type'], 'limit'], limitColor,
|
||||
limitColor // 默认颜色
|
||||
],
|
||||
'fill-opacity': 0.3
|
||||
}
|
||||
},
|
||||
// 区域边框线(不区分 static / active)
|
||||
{
|
||||
id: 'custom-polygon-stroke',
|
||||
type: 'line',
|
||||
filter: ['all', ['==', '$type', 'Polygon']],
|
||||
paint: {
|
||||
'line-color': [
|
||||
'case',
|
||||
['==', ['get', 'type'], 'nofly'], noflyColor,
|
||||
['==', ['get', 'type'], 'limit'], limitColor,
|
||||
limitColor
|
||||
],
|
||||
'line-width': 2,
|
||||
'line-dasharray': [2, 2]
|
||||
}
|
||||
},
|
||||
// 顶点圆点
|
||||
{
|
||||
id: 'custom-vertex-point',
|
||||
type: 'circle',
|
||||
filter: ['all', ['==', 'meta', 'vertex'], ['==', '$type', 'Point']],
|
||||
paint: {
|
||||
'circle-radius': 5,
|
||||
'circle-color': [
|
||||
'case',
|
||||
['==', ['get', 'type'], 'nofly'], noflyColor,
|
||||
['==', ['get', 'type'], 'limit'], limitColor,
|
||||
limitColor
|
||||
]
|
||||
}
|
||||
},
|
||||
// 顶点外光环
|
||||
{
|
||||
id: 'custom-vertex-halo',
|
||||
type: 'circle',
|
||||
filter: ['all', ['==', 'meta', 'vertex'], ['==', '$type', 'Point']],
|
||||
paint: {
|
||||
'circle-radius': 7,
|
||||
'circle-color': '#FFFFFF'
|
||||
}
|
||||
}
|
||||
]
|
||||
// Element UI 弹窗输入限高
|
||||
_promptHeightInput () {
|
||||
return MessageBox.prompt('请输入该区域的限飞高度(单位:米)', '限高设置', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
inputPattern: /^\d+(\.\d+)?$/,
|
||||
inputErrorMessage: '请输入有效的数字'
|
||||
}).then(({ value }) => value)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="h-100">
|
||||
<map-box v-if="showMapbox" :key="mapboxKey" ref="mapbox" :enableNofly="true" />
|
||||
<map-box v-if="showMapbox" :key="mapboxKey" ref="mapbox" :enableRestrictfly="true" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
import MapBox from '@/components/MapBox'
|
||||
|
||||
export default {
|
||||
name: 'Nofly',
|
||||
name: 'Restrictfly',
|
||||
components: {
|
||||
MapBox
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user