地图右键菜单

This commit is contained in:
Teo
2025-04-24 18:00:57 +08:00
parent 0ab7056dd1
commit c061111280
3 changed files with 177 additions and 77 deletions

View File

@ -1,17 +1,45 @@
<template>
<div class="flex justify-between">
<el-tree
style="width: 340px; height: 500px; overflow-y: auto"
<el-tree-v2
style="width: 340px; overflow-y: auto"
show-checkbox
:data="jsonData"
:height="500"
@check-change="handleCheckChange"
@node-contextmenu="nodeMenu"
:props="treeProps"
@node-contextmenu="showMenu"
>
<template #default="{ node, data }">
<span @dblclick="handlePosition(data)">{{ data.name }}</span>
</template>
</el-tree>
<div class="ol-map" id="olMap"></div>
</el-tree-v2>
<div>
<div class="ol-map" id="olMap"></div>
<div class="bg-#909399 p-1 m-0 border-rd c-white w-100 pl-xl mt-2 text-3 pr" v-if="selectLayer.length">
<p v-for="(item, index) in selectLayer" class="flex justify-between items-center cursor-pointer" @click="delLayer(index)">
{{ item }}
<el-icon>
<Close />
</el-icon>
</p>
</div>
</div>
<div v-if="isMenuVisible" :style="{ left: menuX + 'px', top: menuY + 'px' }" class="fixed bg-white shadow-md rounded-md overflow-hidden">
<ul class="py-1 pl-0">
<li class="px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 cursor-pointer" @click="handleMenuItemClick('光伏板')">
<i class="fa-solid fa-check mr-2"></i>光伏板
</li>
<li class="px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 cursor-pointer" @click="handleMenuItemClick('桩点/支架')">
<i class="fa-solid fa-times mr-2"></i>桩点/支架
</li>
<li class="px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 cursor-pointer" @click="handleMenuItemClick('方阵')">
<i class="fa-solid fa-times mr-2"></i>方阵
</li>
<li class="px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 cursor-pointer" @click="handleMenuItemClick('名称')">
<i class="fa-solid fa-times mr-2"></i>名称
</li>
</ul>
</div>
</div>
</template>
@ -27,11 +55,11 @@ import Feature from 'ol/Feature'; // OpenLayers的要素类表示地图上的
import Point from 'ol/geom/Point'; // OpenLayers的点几何类用于表示点状的地理数据
import { Vector as VectorLayer } from 'ol/layer'; // OpenLayers的矢量图层类用于显示矢量数据
import { Vector as VectorSource } from 'ol/source'; // OpenLayers的矢量数据源类用于管理和提供矢量数据
import { Circle, Style, Stroke, Fill, Icon } from 'ol/style'; // OpenLayers的样式类用于定义图层的样式包括圆形样式、基本样式、边框、填充和图标
import { Circle, Style, Stroke, Fill, Icon, Text } from 'ol/style'; // OpenLayers的样式类用于定义图层的样式包括圆形样式、基本样式、边框、填充和图标
import LineString from 'ol/geom/LineString'; // OpenLayers的线几何类用于表示线状的地理数据
import Polygon from 'ol/geom/Polygon'; // OpenLayers的多边形几何类用于表示面状的地理数据
import * as turf from '@turf/turf';
import { Snowflake } from '@/utils/snowflake';
import { SnowflakeIdGenerator } from '@/utils/snowflake';
const props = defineProps({
treeData: {
type: Array,
@ -39,19 +67,25 @@ const props = defineProps({
},
projectId: String
});
const snowflake = new Snowflake(1, 1);
const contextMenu = ref(null);
const selectLayer = ref([]);
const treeProps = {
value: 'name'
};
const snowflake = new SnowflakeIdGenerator(1, 1);
let map: any = null;
const layerData = reactive<any>({});
const centerPosition = ref(fromLonLat([107.12932603888963, 23.80590052110889]));
const centerPosition = ref(fromLonLat([107.13761560163239, 23.80480003743964]));
const nodeMenu = (e: any) => {
console.log(e);
};
const jsonData = computed(() => {
let id = 0;
props.treeData.forEach((item: any) => {
item.features.forEach((itm, idx) => {
itm.geometry.id = snowflake.nextId();
item.features.forEach((itm) => {
itm.geometry.id = ++id;
itm.geometry.coordinates = convertStrToNum(itm.geometry.coordinates);
});
});
@ -70,7 +104,7 @@ const handleCheckChange = (data: any, bool) => {
if (bool) {
if (!layerData[data.features[0].properties.id]) {
data.features.forEach((item: any) => {
creatPoint(item.geometry.coordinates, item.geometry.type, item.geometry.id);
creatPoint(item.geometry.coordinates, item.geometry.type, item.geometry.id, item.properties.text);
});
} else {
data.features.forEach((item: any) => {
@ -164,13 +198,13 @@ function convertStrToNum(arr) {
}
/**
* Date:2024/3/26
* Author:zx
* Function:【面】
* @param
*/
const creatPoint = (pointObj: Array<any>, type: string, id: string) => {
* 创建图层
* @param {*} pointObj 坐标数组
* @param {*} type 类型
* @param {*} id 唯一id
* @param {*} name 名称
* */
const creatPoint = (pointObj: Array<any>, type: string, id: string, name?: string) => {
// 创建多边形的几何对象
let polygon;
if (type === 'Point') {
@ -193,6 +227,14 @@ const creatPoint = (pointObj: Array<any>, type: string, id: string) => {
fill: new Fill({
color: 'red'
})
}),
text: new Text({
font: '12px Microsoft YaHei',
text: name,
scale: 1,
fill: new Fill({
color: '#7bdd63'
})
})
});
const polygonStyle = new Style({
@ -216,15 +258,68 @@ const creatPoint = (pointObj: Array<any>, type: string, id: string) => {
map.addLayer(layerData[id]);
};
// 控制菜单是否显示
const isMenuVisible = ref(false);
// 菜单的 x 坐标
const menuX = ref(0);
// 菜单的 y 坐标
const menuY = ref(0);
// 显示菜单的方法
const showMenu = (event: MouseEvent, data) => {
console.log(data);
contextMenu.value = data.name;
isMenuVisible.value = true;
menuX.value = event.clientX;
menuY.value = event.clientY;
};
// 处理菜单项点击事件的方法
const handleMenuItemClick = (option: string) => {
selectLayer.value.push(`${contextMenu.value}被选中为${option}`);
isMenuVisible.value = false;
};
//删除菜单
const delLayer = (index) => {
selectLayer.value.splice(index, 1);
};
// 点击页面其他区域隐藏菜单
const closeMenuOnClickOutside = (event: MouseEvent) => {
if (isMenuVisible.value) {
const menuElement = document.querySelector('.fixed.bg-white');
if (menuElement && !menuElement.contains(event.target as Node)) {
isMenuVisible.value = false;
}
}
};
// 添加全局点击事件监听器
window.addEventListener('click', closeMenuOnClickOutside);
// 组件卸载时移除事件监听器
const onUnmounted = () => {
window.removeEventListener('click', closeMenuOnClickOutside);
};
onMounted(() => {
// 地图初始化
initOLMap();
});
onDeactivated(() => {
console.log('地图销毁');
map.getOverlays().clear();
creatPoint(
[
[
[107.13761912095511, 23.80479336386864],
[107.13776644838967, 23.804823038597075],
[107.13775276784109, 23.80486256412652],
[107.1376054403658, 23.80483288938397],
[107.13761912095511, 23.80479336386864]
]
],
'Polygon',
'1'
);
// creatPoint([107.13761560163239, 23.80480003743964], 'Point', '2', '点');
});
</script>
<style scoped lang="scss">
@ -250,4 +345,7 @@ onDeactivated(() => {
border-color: #79bbff;
}
}
li {
list-style-type: none;
}
</style>

View File

@ -1,59 +1,33 @@
export class Snowflake {
private startTimeStamp: number = 1609459200000;
private workerIdBits: number = 5;
private dataCenterIdBits: number = 5;
private sequenceBits: number = 12;
export class SnowflakeIdGenerator {
private readonly startTimeStamp = 1609459200000; // 起始时间戳,这里设置为 2021-01-01 00:00:00
private readonly workerIdBits = 5;
private readonly dataCenterIdBits = 5;
private readonly sequenceBits = 12;
private maxWorkerId: number = -1 ^ (-1 << this.workerIdBits);
private maxDataCenterId: number = -1 ^ (-1 << this.dataCenterIdBits);
private readonly maxWorkerId = -1 ^ (-1 << this.workerIdBits);
private readonly maxDataCenterId = -1 ^ (-1 << this.dataCenterIdBits);
private workerIdShift: number = this.sequenceBits;
private dataCenterIdShift: number = this.sequenceBits + this.workerIdBits;
private timestampLeftShift: number = this.sequenceBits + this.workerIdBits + this.dataCenterIdBits;
private sequenceMask: number = -1 ^ (-1 << this.sequenceBits);
private readonly workerIdShift = this.sequenceBits;
private readonly dataCenterIdShift = this.sequenceBits + this.workerIdBits;
private readonly timestampLeftShift = this.sequenceBits + this.workerIdBits + this.dataCenterIdBits;
private readonly sequenceMask = -1 ^ (-1 << this.sequenceBits);
private workerId: number;
private dataCenterId: number;
private sequence: number = 0;
private lastTimestamp: number = -1;
private sequence = 0;
private lastTimestamp = -1;
constructor(workerId: number, dataCenterId: number) {
if (workerId > this.maxWorkerId || workerId < 0) {
throw new Error(`Worker ID 不能大于 ${this.maxWorkerId} 或小于 0`);
throw new Error(`Worker ID 必须在 0 到 ${this.maxWorkerId} 之间`);
}
if (dataCenterId > this.maxDataCenterId || dataCenterId < 0) {
throw new Error(`数据中心 ID 不能大于 ${this.maxDataCenterId} 或小于 0`);
throw new Error(`数据中心 ID 必须在 0 到 ${this.maxDataCenterId} 之间`);
}
this.workerId = workerId;
this.dataCenterId = dataCenterId;
}
public nextId(): number {
let timestamp = this.getCurrentTimestamp();
if (timestamp < this.lastTimestamp) {
throw new Error('检测到时钟回拨,拒绝生成 ID');
}
if (timestamp === this.lastTimestamp) {
this.sequence = (this.sequence + 1) & this.sequenceMask;
if (this.sequence === 0) {
timestamp = this.waitNextMillis(this.lastTimestamp);
}
} else {
this.sequence = 0;
}
this.lastTimestamp = timestamp;
return (
((timestamp - this.startTimeStamp) << this.timestampLeftShift) |
(this.dataCenterId << this.dataCenterIdShift) |
(this.workerId << this.workerIdShift) |
this.sequence
);
}
private getCurrentTimestamp(): number {
return Date.now();
}
private waitNextMillis(lastTimestamp: number): number {
let timestamp = this.getCurrentTimestamp();
while (timestamp <= lastTimestamp) {
@ -61,11 +35,34 @@ export class Snowflake {
}
return timestamp;
}
}
// 使用示例
// const workerId: number = 1;
// const dataCenterId: number = 1;
// const snowflake = new Snowflake(workerId, dataCenterId);
// const id: number = snowflake.nextId();
// console.log('生成的唯一 ID:', id);
private getCurrentTimestamp(): number {
return Date.now();
}
nextId(): number {
let timestamp = this.getCurrentTimestamp();
if (timestamp < this.lastTimestamp) {
throw new Error('时钟回拨,拒绝生成 ID 达 ' + (this.lastTimestamp - timestamp) + ' 毫秒');
}
if (timestamp === this.lastTimestamp) {
this.sequence = (this.sequence + 1) & this.sequenceMask;
if (this.sequence === 0) {
timestamp = this.waitNextMillis(this.lastTimestamp);
}
} else {
this.sequence = 0;
}
this.lastTimestamp = timestamp;
return (
((timestamp - this.startTimeStamp) << this.timestampLeftShift) |
(this.dataCenterId << this.dataCenterIdShift) |
(this.workerId << this.workerIdShift) |
this.sequence
);
}
}

View File

@ -261,7 +261,12 @@
</el-dialog>
<!-- 选取方阵地址 -->
<el-dialog title="设置方阵" v-model="polygonStatus" width="1400px">
<open-layers-map :tree-data="jsonData" :project-id="projectId"></open-layers-map>
<open-layers-map :tree-data="jsonData" :project-id="projectId" v-if="polygonStatus"></open-layers-map>
<el-radio-group v-model="layerType" class="ml-100">
<el-radio value="1" size="large">光伏板</el-radio>
<el-radio value="2" size="large">桩点/支架</el-radio>
<el-radio value="3" size="large">方阵</el-radio>
</el-radio-group>
<template #footer>
<span>
<el-button @click="polygonStatus = false">取消</el-button>
@ -280,9 +285,6 @@ const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_normal_disable, project_category_type, project_type } = toRefs<any>(
proxy?.useDict('sys_normal_disable', 'project_category_type', 'project_type')
);
const change = (val: any) => {
console.log(val, 1212, form.value.playCardStart);
};
const projectList = ref<ProjectVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
@ -297,6 +299,7 @@ const projectFormRef = ref<ElFormInstance>();
const polygonStatus = ref(false);
const dxfFile = ref(null);
const projectId = ref<string>('');
const layerType = ref(null);
const dialog = reactive<DialogOption>({
visible: false,
title: ''
@ -460,7 +463,9 @@ const handleShowUpload = (row?: ProjectVO) => {
};
const handleOpenLayer = async (row: ProjectVO) => {
if (projectId.value == row.id) return (polygonStatus.value = true);
fullscreenLoading.value = true;
const res = await listDXFProject(row.designId);
projectId.value = row.id;
jsonData.value = res.data.layers;