|
|
@@ -1,4 +1,4 @@
|
|
|
-import vjmap, { Map, GeoBounds, GeoProjection, Service } from 'vjmap';
|
|
|
+import vjmap, { Map, GeoBounds, GeoProjection } from 'vjmap';
|
|
|
// import { App, MapThreeLayer } from 'vjmap3d';
|
|
|
import 'vjmap/dist/vjmap.min.css';
|
|
|
import { useAppStore } from '/@/store/modules/app';
|
|
|
@@ -9,535 +9,506 @@ import GoafPopup from './components/GoafPopup.vue';
|
|
|
import { get } from 'lodash-es';
|
|
|
import { StatusColorEnum } from '/@/enums/jeecgEnum';
|
|
|
|
|
|
-// 图片路径
|
|
|
import customImage from '/@/assets/icons/location-icon.svg';
|
|
|
import customImage3D from '/@/assets/icons/location-icon3D.png';
|
|
|
import { env } from './env';
|
|
|
+import { tryOnUnmounted } from '@vueuse/core';
|
|
|
|
|
|
-// ===================== 全局状态 =====================
|
|
|
-let map: Map | null = null;
|
|
|
-let svc: Service | null = null;
|
|
|
-let prj: GeoProjection | null = null;
|
|
|
-let allMarkers: any[] = [];
|
|
|
-let sensorId = 1;
|
|
|
-let currentPopup: any = null;
|
|
|
-
|
|
|
-// ===================== 地图初始化部分 =====================
|
|
|
-// 初始化地图
|
|
|
-export const initMap2d = async (container: HTMLElement, config: { fileUrl?: string; style?: vjmap.IMapStyleParam; wjId?: string } = {}) => {
|
|
|
- if (!container) {
|
|
|
- message.error('地图容器不存在!');
|
|
|
- throw new Error('地图容器不存在!');
|
|
|
- }
|
|
|
+export type UseMap2DOptions = {
|
|
|
+ // fileUrl?: string;
|
|
|
+ style?: vjmap.IMapStyleParam;
|
|
|
+ wjId?: string;
|
|
|
+};
|
|
|
|
|
|
- const appStore = useAppStore();
|
|
|
- const {
|
|
|
- // fileUrl = 'https://vjmap.com/static/assets/data/gym.dwg',
|
|
|
- style = vjmap.openMapLightStyle(),
|
|
|
- wjId,
|
|
|
- } = config;
|
|
|
-
|
|
|
- container.style.background = 'transparent';
|
|
|
- container.style.width = `${get(container, 'parentElement.clientWidth', 0) / appStore.widthScale}px`;
|
|
|
- container.style.height = `${get(container, 'parentElement.clientHeight', 0) / appStore.heightScale}px`;
|
|
|
-
|
|
|
- svc = new vjmap.Service(env.serviceUrl, env.accessToken);
|
|
|
-
|
|
|
- const res = await svc.openMap({
|
|
|
- mapid: wjId || 'c56c59154345', // 地图ID,上传文件后获得的mapid
|
|
|
- fileid: wjId || 'c56c59154345', // 地图ID,上传文件后获得的mapid
|
|
|
- mapopenway: vjmap.MapOpenWay.GeomRender,
|
|
|
- style,
|
|
|
- });
|
|
|
-
|
|
|
- if (res.error) {
|
|
|
- message.error(`地图加载失败:${res.error}`);
|
|
|
- console.error(res.error);
|
|
|
- // return null;
|
|
|
- }
|
|
|
+export type GoafItem = Record<string, any>;
|
|
|
|
|
|
- // 获取地图范围
|
|
|
- const mapExtent = GeoBounds.fromString(res.bounds);
|
|
|
- // const mapExtent = GeoBounds.fromString(res.drawBounds);
|
|
|
- // 根据地图范围建立几何投影坐标系
|
|
|
- prj = new GeoProjection(mapExtent);
|
|
|
- // 地图对象
|
|
|
- map = new vjmap.Map({
|
|
|
- container, // DIV容器ID
|
|
|
- style: svc.rasterStyle(),
|
|
|
- center: prj.toLngLat(mapExtent.center()),
|
|
|
- zoom: 8, // 设置地图缩放级别
|
|
|
- // pitch: 0,
|
|
|
- // trackResize: false,
|
|
|
- antialias: true, // 反锯齿
|
|
|
- renderWorldCopies: false, // 不显示多屏地图
|
|
|
- });
|
|
|
-
|
|
|
- // 关联服务对象和投影对象
|
|
|
- map.attach(svc, prj);
|
|
|
- map.fitMapBounds();
|
|
|
-
|
|
|
- await map.onLoad();
|
|
|
-
|
|
|
- return map;
|
|
|
- // const mapLayer = new MapThreeLayer(map, {
|
|
|
- // stat: { show: false, left: '0' },
|
|
|
- // scene: {
|
|
|
- // showAxesHelper: false,
|
|
|
- // axesHelperSize: 1,
|
|
|
- // defaultLights: false,
|
|
|
- // gridHelper: {
|
|
|
- // visible: env.gridHelper ?? true,
|
|
|
- // args: [1000, 1000],
|
|
|
- // cellSize: 10,
|
|
|
- // sectionSize: 100,
|
|
|
- // },
|
|
|
- // },
|
|
|
- // });
|
|
|
- // const app: App = mapLayer.app;
|
|
|
- // map.addLayer(new vjmap.ThreeLayer({ context: mapLayer as any }));
|
|
|
- // map.doubleClickZoom.disable();
|
|
|
-
|
|
|
- // return app;
|
|
|
+export type MarkerStatus = {
|
|
|
+ sensorId: any;
|
|
|
+ id: any;
|
|
|
+ data: any;
|
|
|
+ lnglat: any;
|
|
|
+ position: any;
|
|
|
};
|
|
|
|
|
|
-// 对外导出地图初始化方法
|
|
|
-// export const initMap2d = (id?) => createMapApp(id);
|
|
|
-
|
|
|
-// ===================== 地图操作处理部分 =====================
|
|
|
-// 拾取点位
|
|
|
-const pickPoint = async (markerOptions: any) => {
|
|
|
- if (!map) return null;
|
|
|
- let marker: any = null;
|
|
|
- const actionPoint = await vjmap.Draw.actionDrawPoint(map, {
|
|
|
- updatecoordinate: (e: any) => {
|
|
|
- if (!e.lnglat) return;
|
|
|
- if (!marker) {
|
|
|
- marker = new vjmap.createMarker(markerOptions);
|
|
|
- marker.setLngLat(e.lnglat);
|
|
|
- marker.addTo(map);
|
|
|
- } else {
|
|
|
- marker.setLngLat(e.lnglat);
|
|
|
- }
|
|
|
- },
|
|
|
- contextMenu: (e: any) => {
|
|
|
- new vjmap.ContextMenu({
|
|
|
- event: e.event.originalEvent,
|
|
|
- theme: 'dark',
|
|
|
- width: '250px',
|
|
|
- items: [
|
|
|
- {
|
|
|
- label: '取消',
|
|
|
- onClick: () => map?.fire('keyup', { keyCode: 27 }),
|
|
|
- },
|
|
|
- ],
|
|
|
- });
|
|
|
- },
|
|
|
- });
|
|
|
- if (actionPoint.cancel) {
|
|
|
- marker?.remove();
|
|
|
- return null;
|
|
|
- }
|
|
|
- return marker;
|
|
|
+export type UseMap2DReturn = {
|
|
|
+ map: vjmap.Map | null;
|
|
|
+ initMap2d: (container: HTMLElement, config?: UseMap2DOptions) => Promise<Map>;
|
|
|
+ destroyMap: () => void;
|
|
|
+ bindPosition: (props?: any) => Promise<any | null>;
|
|
|
+ removePosition: (sensorId: number | string, id: string | number) => Promise<boolean>;
|
|
|
+ getMarkerStatusList: () => MarkerStatus[];
|
|
|
+ getGoafIsPositioned: (goafItem: any) => boolean;
|
|
|
+ renderGoafMarkers: (goafList: any[]) => void;
|
|
|
};
|
|
|
|
|
|
-/**
|
|
|
- * 判断坐标是否有效
|
|
|
- */
|
|
|
-export const getGoafIsPositioned = (goafItem: any): boolean => {
|
|
|
- const hasValidX = isValidSingleCoord(goafItem.xcoordinate);
|
|
|
- const hasValidY = isValidSingleCoord(goafItem.ycoordinate);
|
|
|
- return hasValidX && hasValidY;
|
|
|
-};
|
|
|
+export function useMap2D(): UseMap2DReturn {
|
|
|
+ const allMarkers: any[] = [];
|
|
|
+ let prj: GeoProjection | null = null;
|
|
|
+ let map: vjmap.Map | null = null;
|
|
|
+ let sensorId = 1;
|
|
|
+ let currentPopup: any = null;
|
|
|
|
|
|
-// 获取所有标记状态列表
|
|
|
-export const getMarkerStatusList = () => {
|
|
|
- if (!map) return [];
|
|
|
- return allMarkers.map((marker) => ({
|
|
|
- sensorId: marker.data?.sensorId,
|
|
|
- id: marker.data?.id,
|
|
|
- data: marker.data,
|
|
|
- lnglat: marker.getLngLat(),
|
|
|
- position: map?.fromLngLat(marker.getLngLat()),
|
|
|
- }));
|
|
|
-};
|
|
|
+ const svc = new vjmap.Service(env.serviceUrl, env.accessToken);
|
|
|
|
|
|
-// 移除指定传感器标记
|
|
|
-export const removePosition = async (sensorId: number | string, id: string | number) => {
|
|
|
- if (!map) {
|
|
|
- message.error('地图未初始化完成!');
|
|
|
- return false;
|
|
|
- }
|
|
|
+ const destroyMap = (): void => {
|
|
|
+ closeAndCleanPopup();
|
|
|
+ cleanMarkers();
|
|
|
+ if (map) {
|
|
|
+ map?.destory();
|
|
|
+ }
|
|
|
+ map = null;
|
|
|
+ prj = null;
|
|
|
+ };
|
|
|
|
|
|
- const targetIndex = allMarkers.findIndex((marker) => marker.data?.sensorId === sensorId || marker.data?.id === id);
|
|
|
- if (targetIndex === -1) {
|
|
|
- message.warning('未找到该传感器标记!');
|
|
|
- return false;
|
|
|
- }
|
|
|
+ tryOnUnmounted(destroyMap);
|
|
|
+
|
|
|
+ async function initMap2d(container: HTMLElement, config: UseMap2DOptions = {}): Promise<Map> {
|
|
|
+ if (!container) {
|
|
|
+ message.error('地图容器不存在!');
|
|
|
+ throw new Error('地图容器不存在!');
|
|
|
+ }
|
|
|
+
|
|
|
+ destroyMap();
|
|
|
|
|
|
- try {
|
|
|
- await updatateGoaf({
|
|
|
- id: id,
|
|
|
- xcoordinate: '',
|
|
|
- ycoordinate: '',
|
|
|
+ const appStore = useAppStore();
|
|
|
+ const { style = vjmap.openMapLightStyle(), wjId } = config;
|
|
|
+
|
|
|
+ container.style.background = 'transparent';
|
|
|
+ container.style.width = `${get(container, 'parentElement.clientWidth', 0) / appStore.widthScale}px`;
|
|
|
+ container.style.height = `${get(container, 'parentElement.clientHeight', 0) / appStore.heightScale}px`;
|
|
|
+
|
|
|
+ const res = await svc.openMap({
|
|
|
+ mapid: wjId || 'c56c59154345',
|
|
|
+ fileid: wjId || 'c56c59154345',
|
|
|
+ mapopenway: vjmap.MapOpenWay.GeomRender,
|
|
|
+ style,
|
|
|
+ });
|
|
|
+
|
|
|
+ if (res.error) {
|
|
|
+ message.error(`地图加载失败:${res.error}`);
|
|
|
+ console.error(res.error);
|
|
|
+ }
|
|
|
+
|
|
|
+ const mapExtent = GeoBounds.fromString(res.bounds);
|
|
|
+ prj = new GeoProjection(mapExtent);
|
|
|
+
|
|
|
+ map = new vjmap.Map({
|
|
|
+ container,
|
|
|
+ style: svc.rasterStyle(),
|
|
|
+ center: prj.toLngLat(mapExtent.center()),
|
|
|
+ zoom: 8,
|
|
|
+ antialias: true,
|
|
|
+ renderWorldCopies: false,
|
|
|
});
|
|
|
- console.info('移除布点成功!');
|
|
|
- } catch (apiError) {
|
|
|
- message.error(`同步移除信息失败:${(apiError as Error).message}`);
|
|
|
- console.error('移除接口调用失败:', apiError);
|
|
|
+
|
|
|
+ map.attach(svc, prj);
|
|
|
+ map.fitMapBounds();
|
|
|
+ await map.onLoad();
|
|
|
+
|
|
|
+ return map;
|
|
|
}
|
|
|
|
|
|
- allMarkers[targetIndex].remove();
|
|
|
- allMarkers.splice(targetIndex, 1);
|
|
|
- syncLocalStorage();
|
|
|
+ function isValidSingleCoord(coord: any): boolean {
|
|
|
+ return coord && coord.trim() !== '' && !isNaN(Number(coord));
|
|
|
+ }
|
|
|
|
|
|
- return true;
|
|
|
-};
|
|
|
+ function getGoafIsPositioned(goafItem: any): boolean {
|
|
|
+ const hasValidX = isValidSingleCoord(goafItem.xcoordinate);
|
|
|
+ const hasValidY = isValidSingleCoord(goafItem.ycoordinate);
|
|
|
+ return hasValidX && hasValidY;
|
|
|
+ }
|
|
|
|
|
|
-// 布点方法
|
|
|
-export const bindPosition = async (props?: any) => {
|
|
|
- if (!map || !svc) {
|
|
|
- message.error('地图未初始化完成,请先加载地图!');
|
|
|
- return null;
|
|
|
+ function getMarkerStatusList(): MarkerStatus[] {
|
|
|
+ if (!map) return [];
|
|
|
+ return allMarkers.map((marker) => ({
|
|
|
+ sensorId: marker.data?.sensorId,
|
|
|
+ id: marker.data?.id,
|
|
|
+ data: marker.data,
|
|
|
+ lnglat: marker.getLngLat(),
|
|
|
+ position: map?.fromLngLat(marker.getLngLat()),
|
|
|
+ }));
|
|
|
}
|
|
|
|
|
|
- map.fire('keyup', { keyCode: 27 });
|
|
|
-
|
|
|
- const el = createMarkerElement(
|
|
|
- {
|
|
|
- alarmLevel: props?.alarmLevel,
|
|
|
- devicePos: props?.devicePos,
|
|
|
- },
|
|
|
- false
|
|
|
- );
|
|
|
- const markerOptions = {
|
|
|
- element: el,
|
|
|
- anchor: 'bottom',
|
|
|
- offset: [0, 0],
|
|
|
- };
|
|
|
+ function syncLocalStorage(): void {
|
|
|
+ if (!map) return;
|
|
|
+ const markerData = allMarkers.map((m) => ({
|
|
|
+ position: map?.fromLngLat(m.getLngLat()),
|
|
|
+ data: m.data,
|
|
|
+ }));
|
|
|
+ localStorage.setItem('marker_sensor', JSON.stringify(markerData));
|
|
|
+ }
|
|
|
|
|
|
- let marker: any = null;
|
|
|
- if (!props || !props.position) {
|
|
|
- marker = await pickPoint(markerOptions);
|
|
|
- if (!marker) return null;
|
|
|
-
|
|
|
- const confirmResult = await new Promise<boolean>((resolve) => {
|
|
|
- Modal.confirm({
|
|
|
- title: '保存布点',
|
|
|
- content: '是否确认保存该布点?',
|
|
|
- okText: '确定',
|
|
|
- cancelText: '取消',
|
|
|
- maskClosable: false,
|
|
|
- centered: true,
|
|
|
- onOk: () => resolve(true),
|
|
|
- onCancel: () => resolve(false),
|
|
|
- });
|
|
|
+ function closeAndCleanPopup(): void {
|
|
|
+ currentPopup?.remove();
|
|
|
+ currentPopup = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ async function pickPoint(markerOptions: any): Promise<any | null> {
|
|
|
+ if (!map) return null;
|
|
|
+
|
|
|
+ let marker: any = null;
|
|
|
+ const actionPoint = await vjmap.Draw.actionDrawPoint(map, {
|
|
|
+ updatecoordinate: (e: any) => {
|
|
|
+ if (!e.lnglat) return;
|
|
|
+ if (!marker) {
|
|
|
+ marker = vjmap.createMarker(markerOptions);
|
|
|
+ marker.setLngLat(e.lnglat);
|
|
|
+ marker.addTo(map);
|
|
|
+ } else {
|
|
|
+ marker.setLngLat(e.lnglat);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ contextMenu: (e: any) => {
|
|
|
+ new vjmap.ContextMenu({
|
|
|
+ event: e.event.originalEvent,
|
|
|
+ theme: 'dark',
|
|
|
+ width: '250px',
|
|
|
+ items: [
|
|
|
+ {
|
|
|
+ label: '取消',
|
|
|
+ onClick: () => map?.fire('keyup', { keyCode: 27 }),
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ });
|
|
|
+ },
|
|
|
});
|
|
|
|
|
|
- if (!confirmResult) {
|
|
|
- marker.remove();
|
|
|
+ if (actionPoint.cancel) {
|
|
|
+ marker?.remove();
|
|
|
return null;
|
|
|
}
|
|
|
+ return marker;
|
|
|
+ }
|
|
|
|
|
|
- const lnglat = marker.getLngLat();
|
|
|
- const id = props?.id;
|
|
|
+ function bindMarkerEvents(marker: any, id: string | number): void {
|
|
|
+ marker.setDraggable(false);
|
|
|
+ let isDragging = false;
|
|
|
|
|
|
- if (!id) {
|
|
|
- marker.remove();
|
|
|
- message.error('缺少密闭ID,无法同步布点信息到服务器');
|
|
|
- throw new Error('缺少密闭ID,无法同步布点信息到服务器');
|
|
|
+ marker.on('dragstart', () => {
|
|
|
+ isDragging = false;
|
|
|
+ });
|
|
|
+ marker.on('dragend', () => {
|
|
|
+ setTimeout(() => {
|
|
|
+ isDragging = false;
|
|
|
+ }, 150);
|
|
|
+ const lnglat = marker.getLngLat();
|
|
|
+ if (id) {
|
|
|
+ updatateGoaf({
|
|
|
+ id,
|
|
|
+ xcoordinate: lnglat.lng.toString(),
|
|
|
+ ycoordinate: lnglat.lat.toString(),
|
|
|
+ }).catch((err: Error) => {
|
|
|
+ message.error(`拖拽后同步坐标失败:${err.message}`);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ marker.getElement().addEventListener('click', () => {
|
|
|
+ if (!isDragging) {
|
|
|
+ showMarkerPopup(marker);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ function createMarkerElement(goafItem: any, showInfo: boolean = false): HTMLDivElement {
|
|
|
+ const riskMap = {
|
|
|
+ '1': '低风险',
|
|
|
+ '2': '一般风险',
|
|
|
+ '3': '较高风险',
|
|
|
+ '4': '高风险',
|
|
|
+ };
|
|
|
+
|
|
|
+ const alarmLevelColor = [StatusColorEnum.blue, StatusColorEnum.yellow, StatusColorEnum.gold, StatusColorEnum.red];
|
|
|
+ const el = document.createElement('div');
|
|
|
+ el.className = 'marker';
|
|
|
+ el.style.cssText = `
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ cursor: move;
|
|
|
+ `;
|
|
|
+
|
|
|
+ const level = parseInt(goafItem.alarmLevel, 10);
|
|
|
+ const color = level >= 1 && level <= 4 ? alarmLevelColor[level - 1] : '#333';
|
|
|
+
|
|
|
+ if (showInfo) {
|
|
|
+ const alarmLevelDiv = document.createElement('div');
|
|
|
+ alarmLevelDiv.style.cssText = `
|
|
|
+ font-size: 14px;
|
|
|
+ white-space: nowrap;
|
|
|
+ font-weight: bold;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ `;
|
|
|
+
|
|
|
+ const labelSpan = document.createElement('span');
|
|
|
+ labelSpan.textContent = '风险等级:';
|
|
|
+ labelSpan.style.color = '#333';
|
|
|
+
|
|
|
+ const valueSpan = document.createElement('span');
|
|
|
+ const riskText = riskMap[String(goafItem.alarmLevel)] || '正常';
|
|
|
+ valueSpan.textContent = goafItem.alarmLevel ? riskText : '正常';
|
|
|
+ valueSpan.style.color = color;
|
|
|
+
|
|
|
+ alarmLevelDiv.appendChild(labelSpan);
|
|
|
+ alarmLevelDiv.appendChild(valueSpan);
|
|
|
+ el.appendChild(alarmLevelDiv);
|
|
|
}
|
|
|
|
|
|
- try {
|
|
|
- await updatateGoaf({
|
|
|
- id: id,
|
|
|
- xcoordinate: lnglat.lng.toString(),
|
|
|
- ycoordinate: lnglat.lat.toString(),
|
|
|
- });
|
|
|
- } catch (apiError) {
|
|
|
- marker.remove();
|
|
|
- message.error(`同步布点信息失败:${(apiError as Error).message}`);
|
|
|
- throw new Error(`同步布点信息失败:${(apiError as Error).message}`);
|
|
|
+ const iconDiv = document.createElement('div');
|
|
|
+ iconDiv.style.cssText = `
|
|
|
+ background-image: url("${showInfo ? customImage3D : customImage}");
|
|
|
+ width: 40px;
|
|
|
+ height: 45px;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ background-position: center;
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ `;
|
|
|
+ el.appendChild(iconDiv);
|
|
|
+
|
|
|
+ if (showInfo) {
|
|
|
+ const devicePosDiv = document.createElement('div');
|
|
|
+ devicePosDiv.style.cssText = `
|
|
|
+ position: absolute;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #333;
|
|
|
+ margin-top: 2px;
|
|
|
+ white-space: nowrap;
|
|
|
+ max-width: 120px;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ bottom: -20px;
|
|
|
+ `;
|
|
|
+ devicePosDiv.textContent = goafItem.devicePos;
|
|
|
+ el.appendChild(devicePosDiv);
|
|
|
+ }
|
|
|
+
|
|
|
+ return el;
|
|
|
+ }
|
|
|
+
|
|
|
+ function showMarkerPopup(marker: any): void {
|
|
|
+ if (!map) return;
|
|
|
+
|
|
|
+ closeAndCleanPopup();
|
|
|
+
|
|
|
+ const lnglat = marker.getLngLat();
|
|
|
+ const data = marker.data || {};
|
|
|
+
|
|
|
+ const tempContainer = document.createElement('div');
|
|
|
+ const popupApp = createApp({
|
|
|
+ render: () => h(GoafPopup, { data }),
|
|
|
+ });
|
|
|
+ popupApp.mount(tempContainer);
|
|
|
+
|
|
|
+ currentPopup = new vjmap.Popup({
|
|
|
+ closeButton: true,
|
|
|
+ closeOnClick: false,
|
|
|
+ maxWidth: '800px',
|
|
|
+ offset: [0, 0],
|
|
|
+ })
|
|
|
+ .setLngLat(lnglat)
|
|
|
+ .setDOMContent(tempContainer)
|
|
|
+ .addTo(map);
|
|
|
+
|
|
|
+ currentPopup.on('close', () => {
|
|
|
+ popupApp.unmount();
|
|
|
+ tempContainer.remove();
|
|
|
+ currentPopup = null;
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ async function bindPosition(props?: any): Promise<any | null> {
|
|
|
+ if (!map) {
|
|
|
+ message.error('地图未初始化完成,请先加载地图!');
|
|
|
+ return null;
|
|
|
}
|
|
|
|
|
|
- const currentLngLat = marker.getLngLat();
|
|
|
- marker.remove();
|
|
|
+ map.fire('keyup', { keyCode: 27 });
|
|
|
|
|
|
- const finalEl = createMarkerElement(
|
|
|
+ const el = createMarkerElement(
|
|
|
{
|
|
|
alarmLevel: props?.alarmLevel,
|
|
|
devicePos: props?.devicePos,
|
|
|
},
|
|
|
- true
|
|
|
+ false
|
|
|
);
|
|
|
|
|
|
- marker = vjmap.createMarker({
|
|
|
- element: finalEl,
|
|
|
+ const markerOptions: vjmap.createMarkerOptions = {
|
|
|
+ element: el,
|
|
|
anchor: 'bottom',
|
|
|
offset: [0, 0],
|
|
|
- });
|
|
|
- marker.setLngLat(currentLngLat);
|
|
|
- marker.addTo(map);
|
|
|
- marker.data = {
|
|
|
- sensorId: sensorId++,
|
|
|
- id: id,
|
|
|
- ...(props || {}),
|
|
|
- };
|
|
|
- } else {
|
|
|
- if (!Array.isArray(props.position)) {
|
|
|
- throw new Error('预设坐标position格式错误,必须是[x, y]数组');
|
|
|
- }
|
|
|
- const lngLat = map.toLngLat(props.position);
|
|
|
- if (!lngLat) {
|
|
|
- throw new Error('转换坐标失败,无效的position');
|
|
|
- }
|
|
|
-
|
|
|
- marker = vjmap.createMarker(markerOptions);
|
|
|
- marker.setLngLat(lngLat);
|
|
|
- marker.data = {
|
|
|
- ...props.data,
|
|
|
- sensorId: props.data?.sensorId || sensorId++,
|
|
|
- id: props.data?.id || props.id,
|
|
|
- ...(props || {}),
|
|
|
};
|
|
|
- marker.addTo(map);
|
|
|
- }
|
|
|
|
|
|
- bindMarkerEvents(marker, marker.data?.id);
|
|
|
- allMarkers.push(marker);
|
|
|
- syncLocalStorage();
|
|
|
+ let marker: any = null;
|
|
|
+ if (!props || !props.position) {
|
|
|
+ marker = await pickPoint(markerOptions);
|
|
|
+ if (!marker) return null;
|
|
|
+
|
|
|
+ const confirmResult = await new Promise<boolean>((resolve) => {
|
|
|
+ Modal.confirm({
|
|
|
+ title: '保存布点',
|
|
|
+ content: '是否确认保存该布点?',
|
|
|
+ okText: '确定',
|
|
|
+ cancelText: '取消',
|
|
|
+ maskClosable: false,
|
|
|
+ centered: true,
|
|
|
+ onOk: () => resolve(true),
|
|
|
+ onCancel: () => resolve(false),
|
|
|
+ });
|
|
|
+ });
|
|
|
|
|
|
- return {
|
|
|
- sensorId: marker.data.sensorId,
|
|
|
- id: marker.data.id,
|
|
|
- lnglat: marker.getLngLat(),
|
|
|
- xcoordinate: marker.getLngLat().lng.toString(),
|
|
|
- ycoordinate: marker.getLngLat().lat.toString(),
|
|
|
- };
|
|
|
-};
|
|
|
+ if (!confirmResult) {
|
|
|
+ marker.remove();
|
|
|
+ return null;
|
|
|
+ }
|
|
|
|
|
|
-// 渲染密闭列表标记
|
|
|
-export const renderGoafMarkers = (goafList: any[]) => {
|
|
|
- if (!map || !prj) {
|
|
|
- message.error('地图或投影坐标系未初始化完成!');
|
|
|
- return;
|
|
|
- }
|
|
|
+ const lnglat = marker.getLngLat();
|
|
|
+ const id = props?.id;
|
|
|
+ if (!id) {
|
|
|
+ marker.remove();
|
|
|
+ message.error('缺少密闭ID,无法同步布点信息到服务器');
|
|
|
+ throw new Error('缺少密闭ID,无法同步布点信息到服务器');
|
|
|
+ }
|
|
|
|
|
|
- allMarkers.forEach((marker) => marker.remove());
|
|
|
- allMarkers = [];
|
|
|
- sensorId = 1;
|
|
|
+ try {
|
|
|
+ await updatateGoaf({
|
|
|
+ id,
|
|
|
+ xcoordinate: lnglat.lng.toString(),
|
|
|
+ ycoordinate: lnglat.lat.toString(),
|
|
|
+ });
|
|
|
+ } catch (apiError) {
|
|
|
+ marker.remove();
|
|
|
+ message.error(`同步布点信息失败:${(apiError as Error).message}`);
|
|
|
+ throw new Error(`同步布点信息失败:${(apiError as Error).message}`);
|
|
|
+ }
|
|
|
|
|
|
- const validGoafList = goafList.filter((item) => getGoafIsPositioned(item));
|
|
|
- if (validGoafList.length === 0) {
|
|
|
- console.info('当前密闭列表无有效布点坐标,暂无标记可渲染');
|
|
|
- return;
|
|
|
- }
|
|
|
+ const currentLngLat = marker.getLngLat();
|
|
|
+ marker.remove();
|
|
|
|
|
|
- for (const goafItem of validGoafList) {
|
|
|
- try {
|
|
|
- const lnglat = new vjmap.LngLat(Number(goafItem.xcoordinate), Number(goafItem.ycoordinate));
|
|
|
- const el = createMarkerElement(goafItem, true);
|
|
|
+ const finalEl = createMarkerElement(
|
|
|
+ {
|
|
|
+ alarmLevel: props?.alarmLevel,
|
|
|
+ devicePos: props?.devicePos,
|
|
|
+ },
|
|
|
+ true
|
|
|
+ );
|
|
|
|
|
|
- const marker = vjmap.createMarker({
|
|
|
- element: el,
|
|
|
+ marker = vjmap.createMarker({
|
|
|
+ element: finalEl,
|
|
|
anchor: 'bottom',
|
|
|
offset: [0, 0],
|
|
|
});
|
|
|
- marker.setLngLat(lnglat);
|
|
|
+ marker.setLngLat(currentLngLat);
|
|
|
+ marker.addTo(map);
|
|
|
marker.data = {
|
|
|
sensorId: sensorId++,
|
|
|
- id: goafItem.id,
|
|
|
- ...goafItem,
|
|
|
+ id,
|
|
|
+ ...(props || {}),
|
|
|
};
|
|
|
- marker.addTo(map);
|
|
|
+ } else {
|
|
|
+ if (!Array.isArray(props.position)) {
|
|
|
+ throw new Error('预设坐标position格式错误,必须是[x, y]数组');
|
|
|
+ }
|
|
|
+ const lngLat = map.toLngLat(props.position);
|
|
|
+ if (!lngLat) {
|
|
|
+ throw new Error('转换坐标失败,无效的position');
|
|
|
+ }
|
|
|
|
|
|
- bindMarkerEvents(marker, goafItem.id);
|
|
|
- allMarkers.push(marker);
|
|
|
- } catch (err) {
|
|
|
- console.error(`渲染密闭${goafItem.id}标记失败:`, err);
|
|
|
+ marker = vjmap.createMarker(markerOptions);
|
|
|
+ marker.setLngLat(lngLat);
|
|
|
+ marker.data = {
|
|
|
+ ...props.data,
|
|
|
+ sensorId: props.data?.sensorId || sensorId++,
|
|
|
+ id: props.data?.id || props.id,
|
|
|
+ ...(props || {}),
|
|
|
+ };
|
|
|
+ marker.addTo(map);
|
|
|
}
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-// ===================== 内部辅助函数 =====================
|
|
|
-/**
|
|
|
- * 校验单个坐标是否有效
|
|
|
- */
|
|
|
-const isValidSingleCoord = (coord: any): boolean => {
|
|
|
- return coord && coord.trim() !== '' && !isNaN(Number(coord));
|
|
|
-};
|
|
|
|
|
|
-/**
|
|
|
- * 同步标记数据到本地缓存
|
|
|
- */
|
|
|
-const syncLocalStorage = (): void => {
|
|
|
- if (!map) return;
|
|
|
- const markerData = allMarkers.map((m) => ({
|
|
|
- position: map?.fromLngLat(m.getLngLat()),
|
|
|
- data: m.data,
|
|
|
- }));
|
|
|
- localStorage.setItem('marker_sensor', JSON.stringify(markerData));
|
|
|
-};
|
|
|
+ bindMarkerEvents(marker, marker.data?.id);
|
|
|
+ allMarkers.push(marker);
|
|
|
+ syncLocalStorage();
|
|
|
|
|
|
-/**
|
|
|
- * 关闭并清理弹窗
|
|
|
- */
|
|
|
-const closeAndCleanPopup = (): void => {
|
|
|
- if (currentPopup) {
|
|
|
- currentPopup.remove();
|
|
|
- currentPopup = null;
|
|
|
+ return {
|
|
|
+ sensorId: marker.data.sensorId,
|
|
|
+ id: marker.data.id,
|
|
|
+ lnglat: marker.getLngLat(),
|
|
|
+ xcoordinate: marker.getLngLat().lng.toString(),
|
|
|
+ ycoordinate: marker.getLngLat().lat.toString(),
|
|
|
+ };
|
|
|
}
|
|
|
-};
|
|
|
|
|
|
-/**
|
|
|
- * 绑定标记的拖拽和点击事件
|
|
|
- */
|
|
|
-const bindMarkerEvents = (marker: any, id: string | number): void => {
|
|
|
- marker.setDraggable(false);
|
|
|
- let isDragging = false;
|
|
|
-
|
|
|
- marker.on('dragstart', () => {
|
|
|
- isDragging = false;
|
|
|
- });
|
|
|
- marker.on('dragend', () => {
|
|
|
- setTimeout(() => {
|
|
|
- isDragging = false;
|
|
|
- }, 150);
|
|
|
- const lnglat = marker.getLngLat();
|
|
|
- if (id) {
|
|
|
- updatateGoaf({
|
|
|
- id,
|
|
|
- xcoordinate: lnglat.lng.toString(),
|
|
|
- ycoordinate: lnglat.lat.toString(),
|
|
|
- }).catch((err: Error) => {
|
|
|
- message.error(`拖拽后同步坐标失败:${err.message}`);
|
|
|
- });
|
|
|
+ function renderGoafMarkers(goafList: any[]): void {
|
|
|
+ if (!map || !prj) {
|
|
|
+ message.error('地图或投影坐标系未初始化完成!');
|
|
|
+ return;
|
|
|
}
|
|
|
- });
|
|
|
|
|
|
- marker.getElement().addEventListener('click', () => {
|
|
|
- if (!isDragging) {
|
|
|
- showMarkerPopup(marker);
|
|
|
- }
|
|
|
- });
|
|
|
-};
|
|
|
-/**
|
|
|
- * 创建传感器标记 DOM 元素
|
|
|
- */
|
|
|
-const createMarkerElement = (goafItem: any, showInfo: boolean = false): HTMLDivElement => {
|
|
|
- const riskMap = {
|
|
|
- '1': '低风险',
|
|
|
- '2': '一般风险',
|
|
|
- '3': '较高风险',
|
|
|
- '4': '高风险',
|
|
|
- };
|
|
|
+ cleanMarkers();
|
|
|
|
|
|
- // 定义颜色映射,索引对应 alarmLevel - 1
|
|
|
- // StatusColorEnum.blue, gold, purple, red 分别对应 1, 2, 3, 4
|
|
|
- const alarmLevelColor = [StatusColorEnum.blue, StatusColorEnum.yellow, StatusColorEnum.gold, StatusColorEnum.red];
|
|
|
-
|
|
|
- // const customImage = `/src/assets/icons/location-icon.svg`;
|
|
|
- // const customImage3D = `/src/assets/icons/location-icon3D.png`;
|
|
|
- const el = document.createElement('div');
|
|
|
- el.className = 'marker';
|
|
|
- el.style.cssText = `
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- align-items: center;
|
|
|
- cursor: move;
|
|
|
- `;
|
|
|
-
|
|
|
- // 获取当前等级对应的颜色
|
|
|
- const level = parseInt(goafItem.alarmLevel, 10);
|
|
|
- const color = level >= 1 && level <= 4 ? alarmLevelColor[level - 1] : '#333';
|
|
|
-
|
|
|
- if (showInfo) {
|
|
|
- const alarmLevelDiv = document.createElement('div');
|
|
|
- alarmLevelDiv.style.cssText = `
|
|
|
- font-size: 14px;
|
|
|
- white-space: nowrap;
|
|
|
- font-weight: bold;
|
|
|
- display: flex; /* 使用 flex 布局让文字并排 */
|
|
|
- align-items: center;
|
|
|
- `;
|
|
|
-
|
|
|
- // 1. 创建固定文本节点 "风险等级:"
|
|
|
- const labelSpan = document.createElement('span');
|
|
|
- labelSpan.textContent = '风险等级:';
|
|
|
- labelSpan.style.color = '#333'; // 强制设置为深色/默认色
|
|
|
-
|
|
|
- // 2. 创建动态文本节点 (应用动态颜色)
|
|
|
- const valueSpan = document.createElement('span');
|
|
|
- const riskText = riskMap[String(goafItem.alarmLevel)] || '正常';
|
|
|
- valueSpan.textContent = goafItem.alarmLevel ? riskText : '正常';
|
|
|
- valueSpan.style.color = color; // 应用动态颜色
|
|
|
+ const validGoafList = goafList.filter((item) => getGoafIsPositioned(item));
|
|
|
+ if (validGoafList.length === 0) {
|
|
|
+ console.info('当前密闭列表无有效布点坐标,暂无标记可渲染');
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- // 组装
|
|
|
- alarmLevelDiv.appendChild(labelSpan);
|
|
|
- alarmLevelDiv.appendChild(valueSpan);
|
|
|
+ for (const goafItem of validGoafList) {
|
|
|
+ try {
|
|
|
+ const lnglat = new vjmap.LngLat(Number(goafItem.xcoordinate), Number(goafItem.ycoordinate));
|
|
|
+ const el = createMarkerElement(goafItem, true);
|
|
|
+
|
|
|
+ const marker = vjmap.createMarker({
|
|
|
+ element: el,
|
|
|
+ anchor: 'bottom',
|
|
|
+ offset: [0, 0],
|
|
|
+ });
|
|
|
+ marker.setLngLat(lnglat);
|
|
|
+ // @ts-ignore next-line
|
|
|
+ marker.data = {
|
|
|
+ sensorId: sensorId++,
|
|
|
+ id: goafItem.id,
|
|
|
+ ...goafItem,
|
|
|
+ };
|
|
|
+ marker.addTo(map);
|
|
|
|
|
|
- el.appendChild(alarmLevelDiv);
|
|
|
+ bindMarkerEvents(marker, goafItem.id);
|
|
|
+ allMarkers.push(marker);
|
|
|
+ } catch (err) {
|
|
|
+ console.error(`渲染密闭${goafItem.id}标记失败:`, err);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- const iconDiv = document.createElement('div');
|
|
|
- iconDiv.style.cssText = `
|
|
|
- background-image: url("${showInfo ? customImage3D : customImage}");
|
|
|
- width: 40px;
|
|
|
- height: 45px;
|
|
|
- background-size: 100% 100%;
|
|
|
- background-position: center;
|
|
|
- background-repeat: no-repeat;
|
|
|
- `;
|
|
|
- el.appendChild(iconDiv);
|
|
|
-
|
|
|
- if (showInfo) {
|
|
|
- const devicePosDiv = document.createElement('div');
|
|
|
- devicePosDiv.style.cssText = `
|
|
|
- position: absolute;
|
|
|
- font-size: 14px;
|
|
|
- color: #333;
|
|
|
- margin-top: 2px;
|
|
|
- white-space: nowrap;
|
|
|
- max-width: 120px;
|
|
|
- overflow: hidden;
|
|
|
- text-overflow: ellipsis;
|
|
|
- bottom: -20px;
|
|
|
- `;
|
|
|
- devicePosDiv.textContent = goafItem.devicePos;
|
|
|
- el.appendChild(devicePosDiv);
|
|
|
+ function cleanMarkers(): void {
|
|
|
+ allMarkers.forEach((marker) => marker.remove());
|
|
|
+ allMarkers.length = 0;
|
|
|
+ sensorId = 1;
|
|
|
}
|
|
|
|
|
|
- return el;
|
|
|
-};
|
|
|
+ return {
|
|
|
+ map,
|
|
|
+ initMap2d,
|
|
|
+ destroyMap,
|
|
|
+ bindPosition,
|
|
|
+ removePosition,
|
|
|
+ getMarkerStatusList,
|
|
|
+ getGoafIsPositioned,
|
|
|
+ renderGoafMarkers,
|
|
|
+ };
|
|
|
+}
|
|
|
|
|
|
-// 显示标记信息弹窗
|
|
|
-const showMarkerPopup = (marker: any) => {
|
|
|
- if (!map) return;
|
|
|
-
|
|
|
- closeAndCleanPopup();
|
|
|
-
|
|
|
- const lnglat = marker.getLngLat();
|
|
|
- const data = marker.data || {};
|
|
|
-
|
|
|
- const tempContainer = document.createElement('div');
|
|
|
- const popupApp = createApp({
|
|
|
- render: () => h(GoafPopup, { data }),
|
|
|
- });
|
|
|
- popupApp.mount(tempContainer);
|
|
|
-
|
|
|
- currentPopup = new vjmap.Popup({
|
|
|
- closeButton: true,
|
|
|
- closeOnClick: false,
|
|
|
- maxWidth: '800px',
|
|
|
- offset: [0, 0],
|
|
|
- })
|
|
|
- .setLngLat(lnglat)
|
|
|
- .setDOMContent(tempContainer)
|
|
|
- .addTo(map);
|
|
|
-
|
|
|
- currentPopup.on('close', () => {
|
|
|
- popupApp.unmount();
|
|
|
- tempContainer.remove();
|
|
|
- currentPopup = null;
|
|
|
- });
|
|
|
+const defaultMap2DInstance: { current: UseMap2DReturn | null } = { current: null };
|
|
|
+const getDefaultMap2D = (): UseMap2DReturn => {
|
|
|
+ if (!defaultMap2DInstance.current) {
|
|
|
+ defaultMap2DInstance.current = useMap2D();
|
|
|
+ }
|
|
|
+ return defaultMap2DInstance.current;
|
|
|
};
|
|
|
+
|
|
|
+export const initMap2d = (container: HTMLElement, config: UseMap2DOptions = {}) => getDefaultMap2D().initMap2d(container, config);
|
|
|
+export const destroyMap = () => getDefaultMap2D().destroyMap();
|
|
|
+export const bindPosition = (props?: any) => getDefaultMap2D().bindPosition(props);
|
|
|
+export const removePosition = (sensorId: number | string, id: string | number) => getDefaultMap2D().removePosition(sensorId, id);
|
|
|
+export const getMarkerStatusList = () => getDefaultMap2D().getMarkerStatusList();
|
|
|
+export const getGoafIsPositioned = (goafItem: any) => getDefaultMap2D().getGoafIsPositioned(goafItem);
|
|
|
+export const renderGoafMarkers = (goafList: any[]) => getDefaultMap2D().renderGoafMarkers(goafList);
|