Compare commits

...

2 Commits

Author SHA1 Message Date
LiuJiaNan b544d62dd1 feat(RightUtils): 优化子菜单动画点击锁定与界面交互
- 新增 isAnimating 状态和 tryStartAnimation 方法防止动画期间重复点击
- 子菜单背景按钮禁用时添加 disabled 样式及无障碍 aria-disabled 属性
- 按钮点击事件中增加动画锁定判断,避免动画未完成前状态切换
- useChildMenuAnimation Hook 内部实现动画锁定机制,保证动画开始至结束期间禁用重复操作
- 在首次渲染和动画完成处正确管理动画锁,确保状态与动画同步
- index.less 中新增 disabled 样式防止用户交互,保持按钮和菜单状态一致
2026-07-15 09:36:02 +08:00
LiuJiaNan 42961e417c refactor(map): 将class重构成hook写法 2026-07-15 09:26:35 +08:00
7 changed files with 379 additions and 270 deletions

View File

@ -34,7 +34,11 @@ function RightUtils(props) {
currentBranchOffice && bottomUtilsCurrentIndex !== -1,
);
const { controls: childMenuControls } = useChildMenuAnimation(
const {
controls: childMenuControls,
isAnimating: isChildMenuAnimating,
tryStartAnimation: tryStartChildMenuAnimation,
} = useChildMenuAnimation(
currentBranchOffice && bottomUtilsCurrentIndex !== -1 && isShowChildLevel,
);
@ -178,8 +182,13 @@ function RightUtils(props) {
))}
</motion.div>
<div
className={`bg ${isShowChildLevel ? "active" : ""}`}
onClick={() => setIsShowChildLevel(!isShowChildLevel)}
className={`bg ${isShowChildLevel ? "active" : ""} ${isChildMenuAnimating ? "disabled" : ""}`}
aria-disabled={isChildMenuAnimating}
onClick={() => {
if (!tryStartChildMenuAnimation())
return;
setIsShowChildLevel(visible => !visible);
}}
>
<img src={buttonBg} alt="" />
</div>

View File

@ -64,6 +64,12 @@
transform: rotate(0deg);
}
// 子菜单动画执行期间禁止重复点击,确保按钮方向与菜单展开状态始终一致。
&.disabled {
pointer-events: none;
cursor: not-allowed;
}
img {
width: 101px;
height: 100px;

View File

@ -1,5 +1,5 @@
import { useAnimation } from "motion/react";
import { useEffect, useRef } from "react";
import { useEffect, useRef, useState } from "react";
/**
* RightUtils 子菜单动画 Hook
@ -60,6 +60,24 @@ export function useChildMenuAnimation(isVisible) {
// 作用:首次渲染时需要特殊处理,确保初始状态正确设置
const isFirstRender = useRef(true);
// 使用 ref 同步锁定点击,避免 React 状态尚未更新时连续点击穿透state 负责更新按钮禁用样式。
// 初始即需要展开时直接锁定,避免首次 effect 执行前的点击空窗。
const animationLockRef = useRef(isVisible);
const [isAnimating, setIsAnimating] = useState(isVisible);
const lockAnimation = () => {
if (animationLockRef.current)
return false;
animationLockRef.current = true;
setIsAnimating(true);
return true;
};
const unlockAnimation = () => {
animationLockRef.current = false;
setIsAnimating(false);
};
// ==================== 主题:监听可见性变化并执行弹跳动画 ====================
//
// useEffect 的职责:
@ -76,6 +94,7 @@ export function useChildMenuAnimation(isVisible) {
// ==================== 场景 1首次渲染 ====================
if (isFirstRender.current) {
if (isVisible) {
lockAnimation();
// 执行展开动画:从下向上炫酷弹跳进入
controls.start({
// Y 轴位置关键帧(四个关键帧实现弹跳效果):
@ -121,6 +140,7 @@ export function useChildMenuAnimation(isVisible) {
// 动画完成后标记状态
isExpanded.current = true; // 标记已展开
isFirstRender.current = false; // 标记首次渲染完成
unlockAnimation();
});
}
else {
@ -132,6 +152,8 @@ export function useChildMenuAnimation(isVisible) {
// ==================== 场景 2展开动画非首次渲染====================
if (isVisible && !isExpanded.current) {
// 正常点击时锁已由按钮同步占用;外部状态触发动画时在这里补充锁定。
lockAnimation();
// 从下向上炫酷弹跳进入
controls.start({
y: [80, -15, 5, 0], // 位置下方80px -> 上方15px过冲-> 下方5px回落-> 正常
@ -144,11 +166,13 @@ export function useChildMenuAnimation(isVisible) {
},
}).then(() => {
isExpanded.current = true; // 标记已展开
unlockAnimation();
});
}
// ==================== 场景 3收起动画 ====================
else if (!isVisible && isExpanded.current) {
lockAnimation();
// 从上向下炫酷弹跳离开
controls.start({
// Y 轴位置关键帧:
@ -191,6 +215,7 @@ export function useChildMenuAnimation(isVisible) {
},
}).then(() => {
isExpanded.current = false; // 标记已收起
unlockAnimation();
});
}
}, [isVisible, controls]);
@ -198,5 +223,8 @@ export function useChildMenuAnimation(isVisible) {
// ==================== 返回值 ====================
return {
controls, // motion 动画控制器,绑定到 motion.div 的 animate 属性
isAnimating,
// 点击处理器调用后会立即占用锁,确保同一帧内的连续点击也会被拦截。
tryStartAnimation: lockAnimation,
};
}

View File

@ -9,7 +9,7 @@ import Content from "./components/Content";
import Header from "./components/Header";
import RightUtils from "./components/RightUtils";
import { Context } from "./js/context";
import InitMap from "./js/initMap";
import useInitMap from "./js/initMap";
import mitt from "./js/mitt";
import {
changeCoverMaskVisibleMittKey,
@ -20,9 +20,8 @@ import "./index.less";
function Map(props) {
const query = useGetUrlQuery();
const { viewer, mapMethods, initMap, externalEntryPort } = useInitMap();
const viewer = useRef(null); // cesium地图实例
const mapMethods = useRef(null); // cesium地图方法实例
const fullscreenRef = useRef(null); // 全屏容器的ref
const [isFullscreen, { toggleFullscreen }] = useFullscreen(fullscreenRef);
@ -39,19 +38,17 @@ function Map(props) {
sessionStorage.removeItem("mapCurrentBranchOfficeId");
const initMap = new InitMap();
const { viewer: viewerInstance, mapMethods: mapMethodsInstance }
= initMap.initMap();
viewer.current = viewerInstance;
mapMethods.current = mapMethodsInstance;
initMap();
if (query.mapType === "港口") {
initMap.externalEntryPort(query);
const entered = externalEntryPort(query);
if (!entered)
message.warning("港口入口参数不完整,无法定位到对应港口");
}
else {
setTimeout(() => {
mapMethodsInstance.flyTo();
mapMethodsInstance.addPortPoint();
mapMethods.current.flyTo();
mapMethods.current.addPortPoint();
}, 500);
}

View File

@ -1,19 +1,74 @@
import { useRef } from "react";
import { createObliquePhotography } from "~/pages/Container/Map/js/mapUtils";
import MapMethods from "./mapMethods";
import PointClickEvent from "./pointClickEvent";
import useMapMethods from "./mapMethods";
import usePointClickEvent from "./pointClickEvent";
const Cesium = window.Cesium;
export default class InitMap {
#pointClickEvent;
#viewer;
#mapMethods;
export default function useInitMap() {
// 页面和地图工具共用同一个 Cesium 引用,避免内外各维护一份实例。
const viewer = useRef(null);
const mapMethodsValue = useMapMethods(viewer);
// Context 下游仍按 ref.current 调用地图方法,因此保留原有数据结构。
const mapMethods = useRef(mapMethodsValue);
mapMethods.current = mapMethodsValue;
// 点位点击处理集合:替代原 PointClickEvent class 实例,负责弹窗和层级进入。
const pointClickEvent = usePointClickEvent(viewer, mapMethodsValue);
// 获取当前 Cesium 实例,减少内部方法对 ref 结构的重复感知。
const getViewer = () => viewer.current;
// 倾斜摄影
const initObliquePhotography = () => {
getViewer().scene.globe.depthTestAgainstTerrain = true;
Cesium.ExperimentalFeatures.enableModelExperimental = true;
createObliquePhotography(
"http://192.168.192.215:8021/ware/upload/%E6%9B%B9%E5%A6%83%E7%94%B8%E6%B8%AF%E4%B8%9C/%E6%9B%B9%E5%A6%83%E7%94%B8%E6%B8%AF%E4%B8%9C/merge_tile.json",
getViewer(),
);
createObliquePhotography(
"http://192.168.192.215:8021/ware/upload/%E6%9B%B9%E5%A6%83%E7%94%B8%E6%B8%AF%E8%A5%BF/%E6%9B%B9%E5%A6%83%E7%94%B8%E6%B8%AF%E8%A5%BF/merge_tile.json",
getViewer(),
);
createObliquePhotography(
"http://192.168.192.215:8021/ware/upload/qhdxys/merge_tile.json",
getViewer(),
);
createObliquePhotography(
"http://192.168.192.215:8021/ware/upload/qhdgysh/merge_tile.json",
getViewer(),
);
createObliquePhotography(
"http://192.168.192.215:8021/ware/upload/%E6%B2%A7%E5%B7%9E%E6%B8%AF%E8%A5%BF/%E6%B2%A7%E5%B7%9E%E6%B8%AF%E8%A5%BF/merge_tile.json",
getViewer(),
);
createObliquePhotography(
"http://192.168.192.215:8021/ware/upload/%E6%B2%A7%E5%B7%9E%E6%B8%AF%E4%B8%9C/%E6%B2%A7%E5%B7%9E%E6%B8%AF%E4%B8%9C/merge_tile.json",
getViewer(),
);
};
// 注册点击事件
const registerClickEvent = () => {
getViewer().cesiumWidget.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);
getViewer().cesiumWidget.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK);
const screenSpaceEventHandler = new Cesium.ScreenSpaceEventHandler(getViewer().scene.canvas);
screenSpaceEventHandler.setInputAction((movement) => {
const pick = getViewer().scene.pick(movement.position);
if (Cesium.defined(pick) && pick.id?.id) {
pointClickEvent.pointClickEvent(pick.id);
}
else {
pointClickEvent.closePopup();
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
};
// 初始化地图
initMap = () => {
const initMap = () => {
const tianDiTuKey = "06494bcf2d4b66df7f3c586cc65af0cb";
const subdomains = ["0", "1", "2", "3", "4", "5", "6", "7"];
this.#viewer = new Cesium.Viewer("cesiumContainer", {
viewer.current = new Cesium.Viewer("cesiumContainer", {
// terrainProvider: Cesium.createWorldTerrain()
animation: false, // 动画
homeButton: true, // home键
@ -39,8 +94,8 @@ export default class InitMap {
show: true,
}),
});
this.#viewer._cesiumWidget._creditContainer.style.display = "none"; // 隐藏cesium ion
this.#viewer.imageryLayers.addImageryProvider(
getViewer()._cesiumWidget._creditContainer.style.display = "none"; // 隐藏cesium ion
getViewer().imageryLayers.addImageryProvider(
new Cesium.WebMapTileServiceImageryProvider({
// 影像注记
url:
@ -54,56 +109,48 @@ export default class InitMap {
show: true,
}),
);
this.#initObliquePhotography();
this.#registerClickEvent();
this.#mapMethods = new MapMethods(this.#viewer);
this.#pointClickEvent = new PointClickEvent(this.#viewer, this.#mapMethods);
return { mapMethods: this.#mapMethods, viewer: this.#viewer };
initObliquePhotography();
registerClickEvent();
return { mapMethods: mapMethods.current, viewer: getViewer() };
};
// 倾斜摄影
#initObliquePhotography = () => {
this.#viewer.scene.globe.depthTestAgainstTerrain = true;
Cesium.ExperimentalFeatures.enableModelExperimental = true;
createObliquePhotography(
"http://192.168.192.215:8021/ware/upload/%E6%9B%B9%E5%A6%83%E7%94%B8%E6%B8%AF%E4%B8%9C/%E6%9B%B9%E5%A6%83%E7%94%B8%E6%B8%AF%E4%B8%9C/merge_tile.json",
this.#viewer,
);
createObliquePhotography(
"http://192.168.192.215:8021/ware/upload/%E6%9B%B9%E5%A6%83%E7%94%B8%E6%B8%AF%E8%A5%BF/%E6%9B%B9%E5%A6%83%E7%94%B8%E6%B8%AF%E8%A5%BF/merge_tile.json",
this.#viewer,
);
createObliquePhotography(
"http://192.168.192.215:8021/ware/upload/qhdxys/merge_tile.json",
this.#viewer,
);
createObliquePhotography(
"http://192.168.192.215:8021/ware/upload/qhdgysh/merge_tile.json",
this.#viewer,
);
createObliquePhotography(
"http://192.168.192.215:8021/ware/upload/%E6%B2%A7%E5%B7%9E%E6%B8%AF%E8%A5%BF/%E6%B2%A7%E5%B7%9E%E6%B8%AF%E8%A5%BF/merge_tile.json",
this.#viewer,
);
createObliquePhotography(
"http://192.168.192.215:8021/ware/upload/%E6%B2%A7%E5%B7%9E%E6%B8%AF%E4%B8%9C/%E6%B2%A7%E5%B7%9E%E6%B8%AF%E4%B8%9C/merge_tile.json",
this.#viewer,
);
// 外部入口进入港口:只有港口标识、名称和有效坐标齐全时才进入,避免 Cesium 接收到无效坐标。
const externalEntryPort = (data) => {
const id = data.id || data.portId;
const name = data.name || data.portName;
const longitude = data.position?.x ?? data.longitude ?? data.lng ?? data.x;
const latitude = data.position?.y ?? data.latitude ?? data.lat ?? data.y;
// 空字符串会被 Number 转成 0需要先排除空值再判断是否为有效数字。
const hasCoordinate = value => value !== "" && value !== null && value !== undefined;
const hasValidPosition
= hasCoordinate(longitude)
&& hasCoordinate(latitude)
&& Number.isFinite(Number(longitude))
&& Number.isFinite(Number(latitude));
if (!id || !name || !hasValidPosition)
return false;
pointClickEvent.pointClickEvent({
monitorItems: {
data: {
...data,
id,
name,
corpinfoId: data.corpinfoId,
mapType: "港口",
isExternalEntry: "1",
position: { x: Number(longitude), y: Number(latitude) },
},
},
});
return true;
};
// 注册点击事件
#registerClickEvent = () => {
this.#viewer.cesiumWidget.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);
this.#viewer.cesiumWidget.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK);
const screenSpaceEventHandler = new Cesium.ScreenSpaceEventHandler(this.#viewer.scene.canvas);
screenSpaceEventHandler.setInputAction((movement) => {
const pick = this.#viewer.scene.pick(movement.position);
if (Cesium.defined(pick) && pick.id?.id) {
this.#pointClickEvent.pointClickEvent(pick.id);
}
else {
this.#pointClickEvent.closePopup();
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
return {
viewer,
mapMethods,
initMap,
externalEntryPort,
};
}

View File

@ -1,3 +1,4 @@
import { useRef } from "react";
import portEntityBillboardImage from "~/assets/images/map_bi/point/dianwei.png";
import branchOfficeEntityBillboardImage from "~/assets/images/map_bi/point/gongsidianwei.png";
import edgeCMT from "./edge/cmt.js";
@ -18,6 +19,40 @@ import { chunkedLoad, filterNull, formatPolygon } from "./utils";
import "./TrajectoryPolylineTrailLinkMaterialProperty.js";
import "./WallPolylineTrailLinkMaterialProperty";
const portPoint = [
{
id: "00002",
name: "沧州黄骅港矿石港务有限公司",
introduce:
"公司现共有10个泊位10-20万吨级设计年通过能力6400万吨。堆场面积176万平米堆存能力740万吨大型装卸设备44台套。",
position: { x: 117.91412, y: 38.35902 },
corpinfoId: "f8da1790b1034058ae2efefd69af3284",
},
{
id: "00003",
name: "秦皇岛港",
introduce:
"秦皇岛港分为东、西两个港区现有生产性泊位50个年设计通过能力2.26亿吨,经营货类主要包括煤炭、金属矿石、油品及液体化工、集装箱及其他杂货等。",
position: { x: 119.61254, y: 39.92572 },
},
{
id: "00004",
name: "曹妃甸实业港务",
introduce:
"公司现共有6个泊位(5-30万吨级)设计年通过能力6550万吨。堆场面积146万平米堆存能力1350万吨大型装卸设备23台套。",
position: { x: 118.51022, y: 38.93503 },
corpinfoId: "8854edee3aa94be496cee676b6d4845a",
},
{
id: "00005",
name: "曹妃甸煤炭港务",
introduce:
"公司现共有5个泊位5-10万吨级设计年通过能力5000万吨。堆场面积83万平米堆存能力373.5万吨大型装卸设备19台套。",
position: { x: 118.43701, y: 38.9866 },
corpinfoId: "6aa255d41602497fa0f934a822820df4",
},
];
const BranchOfficePoint = [
{
corpName: "秦港股份有限公司",
@ -357,91 +392,59 @@ const BranchOfficePoint = [
},
];
const Cesium = window.Cesium;
export default class MapMethods {
#viewer;
#defineCenterPoint = {
export default function useMapMethods(viewerRef) {
// 获取当前 Cesium 实例:初始化前为空,地图方法会在 initMap 完成后被调用。
const getViewer = () => viewerRef.current;
// 默认中心点:初始化和回到首页视角时使用,保持全局港区俯视高度。
const defineCenterPoint = {
longitude: 119.6486945226887,
latitude: 39.93555616569192,
height: 900000,
}; // 默认中心点
};
#currentCenterPoint = {
// 当前中心点:记录最近一次 flyTo 的视角,供外部按钮回到当前位置。
const currentCenterPointRef = useRef({
longitude: 119.6486945226887,
latitude: 39.93555616569192,
height: 900000,
}; // 当前中心点
});
#previousCenterPoint = {
// 上一次中心点:从分公司返回港口层级时,需要回退到进入前的视角。
const previousCenterPointRef = useRef({
longitude: 119.6486945226887,
latitude: 39.93555616569192,
height: 900000,
}; // 上一次中心点
constructor(viewer) {
this.#viewer = viewer;
}
});
// 设置中心点
flyTo = ({ longitude, latitude, height } = this.#defineCenterPoint) => {
this.#viewer.camera.flyTo({ destination: getPosition(longitude, latitude, height), duration: 2 });
this.#previousCenterPoint = { ...this.#currentCenterPoint };
this.#currentCenterPoint = { longitude, latitude, height };
const flyTo = ({ longitude, latitude, height } = defineCenterPoint) => {
getViewer().camera.flyTo({ destination: getPosition(longitude, latitude, height), duration: 2 });
previousCenterPointRef.current = { ...currentCenterPointRef.current };
currentCenterPointRef.current = { longitude, latitude, height };
};
// 返回当前中心点
returnCurrentCenterPoint = () => {
const { longitude, latitude, height } = this.#currentCenterPoint;
this.#viewer.camera.flyTo({ destination: getPosition(longitude, latitude, height), duration: 2 });
const returnCurrentCenterPoint = () => {
const { longitude, latitude, height } = currentCenterPointRef.current;
getViewer().camera.flyTo({ destination: getPosition(longitude, latitude, height), duration: 2 });
};
// 返回上一次中心点
returnPreviousCenterPoint = () => {
const { longitude, latitude, height } = this.#previousCenterPoint;
this.#viewer.camera.flyTo({ destination: getPosition(longitude, latitude, height), duration: 2 });
this.#currentCenterPoint = { longitude, latitude, height };
const returnPreviousCenterPoint = () => {
const { longitude, latitude, height } = previousCenterPointRef.current;
getViewer().camera.flyTo({ destination: getPosition(longitude, latitude, height), duration: 2 });
currentCenterPointRef.current = { longitude, latitude, height };
};
// 切换场景模式
changeSceneMode = (type = "3d") => {
this.#viewer.scene.mode = { "2d": Cesium.SceneMode.COLUMBUS_VIEW, "3d": Cesium.SceneMode.SCENE3D }[type];
const changeSceneMode = (type = "3d") => {
getViewer().scene.mode = { "2d": Cesium.SceneMode.COLUMBUS_VIEW, "3d": Cesium.SceneMode.SCENE3D }[type];
};
// 添加港口点
addPortPoint = async () => {
const portPoint = [
{
id: "00002",
name: "沧州黄骅港矿石港务有限公司",
introduce:
"公司现共有10个泊位10-20万吨级设计年通过能力6400万吨。堆场面积176万平米堆存能力740万吨大型装卸设备44台套。",
position: { x: 117.91412, y: 38.35902 },
corpinfoId: "f8da1790b1034058ae2efefd69af3284",
},
{
id: "00003",
name: "秦皇岛港",
introduce:
"秦皇岛港分为东、西两个港区现有生产性泊位50个年设计通过能力2.26亿吨,经营货类主要包括煤炭、金属矿石、油品及液体化工、集装箱及其他杂货等。",
position: { x: 119.61254, y: 39.92572 },
},
{
id: "00004",
name: "曹妃甸实业港务",
introduce:
"公司现共有6个泊位(5-30万吨级)设计年通过能力6550万吨。堆场面积146万平米堆存能力1350万吨大型装卸设备23台套。",
position: { x: 118.51022, y: 38.93503 },
corpinfoId: "8854edee3aa94be496cee676b6d4845a",
},
{
id: "00005",
name: "曹妃甸煤炭港务",
introduce:
"公司现共有5个泊位5-10万吨级设计年通过能力5000万吨。堆场面积83万平米堆存能力373.5万吨大型装卸设备19台套。",
position: { x: 118.43701, y: 38.9866 },
corpinfoId: "6aa255d41602497fa0f934a822820df4",
},
];
const addPortPoint = async () => {
await chunkedLoad(portPoint, 10, async (item) => {
const entityCollection = createEntityCollection("portEntityCollection");
entityCollection.entities.add(
@ -453,18 +456,18 @@ export default class MapMethods {
monitorItems: { data: { ...item, mapType: "港口" } },
}),
);
this.#viewer.dataSources.add(entityCollection);
getViewer().dataSources.add(entityCollection);
});
await addMergedEntityCollection(this.#viewer, "portEntityCollection");
await addMergedEntityCollection(getViewer(), "portEntityCollection");
};
// 移除港口点
removePortPoint = () => {
removeEntityCollection(this.#viewer, "portEntityCollectionMerged");
const removePortPoint = () => {
removeEntityCollection(getViewer(), "portEntityCollectionMerged");
};
// 添加分公司点
addBranchOfficePoint = async (area = "", pointInfo = null) => {
const addBranchOfficePoint = async (area = "", pointInfo = null) => {
mitt.emit(changeCoverMaskVisibleMittKey, true);
let branchOfficePoint = [];
if (!pointInfo) {
@ -486,19 +489,19 @@ export default class MapMethods {
},
}),
);
this.#viewer.dataSources.add(entityCollection);
getViewer().dataSources.add(entityCollection);
});
await addMergedEntityCollection(this.#viewer, "branchOfficeEntityCollection");
await addMergedEntityCollection(getViewer(), "branchOfficeEntityCollection");
mitt.emit(changeCoverMaskVisibleMittKey, false);
};
// 删除分公司点
removeBranchOfficePoint = () => {
removeEntityCollection(this.#viewer, "branchOfficeEntityCollectionMerged");
const removeBranchOfficePoint = () => {
removeEntityCollection(getViewer(), "branchOfficeEntityCollectionMerged");
};
// 添加四色图
addFourColorDiagram = (id, corpionId) => {
const addFourColorDiagram = (id, corpionId) => {
const edgeMap = {
"00003": edgeQHD,
"00002": edgeCZKS,
@ -551,17 +554,17 @@ export default class MapMethods {
);
});
this.#viewer.dataSources.add(entityCollection);
getViewer().dataSources.add(entityCollection);
mitt.emit(changeCoverMaskVisibleMittKey, false);
};
// 删除四色图
removeFourColorDiagram = () => {
removeEntityCollection(this.#viewer, "fourColorDiagramEntityCollection");
const removeFourColorDiagram = () => {
removeEntityCollection(getViewer(), "fourColorDiagramEntityCollection");
};
// 添加墙
addWall = (id, corpionId) => {
const addWall = (id, corpionId) => {
const edgeMap = {
"00003": edgeQHD,
"00002": edgeCZKS,
@ -592,7 +595,7 @@ export default class MapMethods {
id: createId(),
wall: {
positions,
material: new Cesium.WallPolylineTrailLinkMaterialProperty(this.#viewer),
material: new Cesium.WallPolylineTrailLinkMaterialProperty(getViewer()),
maximumHeights: Array.from({ length: pointCount }).fill(40),
minimumHeights: Array.from({ length: pointCount }).fill(0),
},
@ -600,63 +603,17 @@ export default class MapMethods {
);
});
this.#viewer.dataSources.add(entityCollection);
getViewer().dataSources.add(entityCollection);
mitt.emit(changeCoverMaskVisibleMittKey, false);
};
// 删除墙
removeWall = () => {
removeEntityCollection(this.#viewer, "wallEntityCollection");
};
/**
* desc: 添加点位
* @param {Array} pointList
* @param options {Object: {mapIcon, type, subLabel}} 配置项
* @param options.mapIcon {String} 扎点图标
* @param options.mapType {String} 扎点类型
* @param options.subLabel {String} 二级标题用于弹窗标题
*/
addMarkPoint = async (pointList, options) => {
if (!options.mapType)
throw new Error("请传入mapType扎点类型");
if (!options.mapIcon)
throw new Error("请传入mapIcon扎点图标");
mitt.emit(changeCoverMaskVisibleMittKey, true);
await chunkedLoad(pointList, 10, async (item) => {
const entityCollection = createEntityCollection(
`markEntityCollection_${options.mapType}`,
);
const name = item.MAP_POINT_NAME || item.AREA_NAME || "";
entityCollection.entities.add(
new Cesium.Entity({
id: createId(),
name,
position: getPosition(item.LONGITUDE, item.LATITUDE),
billboard: await getBillboard({ image: options.mapIcon, name }),
monitorItems: {
data: { ...item, mapType: `标记点_${options.mapType}`, subLabel: options.subLabel },
},
}),
);
this.#viewer.dataSources.add(entityCollection);
});
const dataSource = await addMergedEntityCollection(
this.#viewer,
"markEntityCollection",
options.mapType,
);
this.#enabledClustering(dataSource);
mitt.emit(changeCoverMaskVisibleMittKey, false);
};
// 删除点位
removeMarkPoint = (mapType) => {
removeEntityCollection(this.#viewer, `markEntityCollectionMerged${mapType ? `_${mapType}` : ""}`);
const removeWall = () => {
removeEntityCollection(getViewer(), "wallEntityCollection");
};
// 点位聚合
#enabledClustering = (dataSource) => {
const enabledClustering = (dataSource) => {
dataSource.clustering.enabled = true; // 聚合开启
dataSource.clustering.pixelRange = 50; // 设置像素范围,以扩展显示边框
dataSource.clustering.minimumClusterSize = 5; // 设置最小的聚合点数目,超过此数目才能聚合
@ -719,4 +676,67 @@ export default class MapMethods {
dataSource.clustering.pixelRange = pixelRange;
}
};
/**
* desc: 添加点位
* @param {Array} pointList
* @param options {Object: {mapIcon, type, subLabel}} 配置项
* @param options.mapIcon {String} 扎点图标
* @param options.mapType {String} 扎点类型
* @param options.subLabel {String} 二级标题用于弹窗标题
*/
const addMarkPoint = async (pointList, options) => {
if (!options.mapType)
throw new Error("请传入mapType扎点类型");
if (!options.mapIcon)
throw new Error("请传入mapIcon扎点图标");
mitt.emit(changeCoverMaskVisibleMittKey, true);
await chunkedLoad(pointList, 10, async (item) => {
const entityCollection = createEntityCollection(
`markEntityCollection_${options.mapType}`,
);
const name = item.MAP_POINT_NAME || item.AREA_NAME || "";
entityCollection.entities.add(
new Cesium.Entity({
id: createId(),
name,
position: getPosition(item.LONGITUDE, item.LATITUDE),
billboard: await getBillboard({ image: options.mapIcon, name }),
monitorItems: {
data: { ...item, mapType: `标记点_${options.mapType}`, subLabel: options.subLabel },
},
}),
);
getViewer().dataSources.add(entityCollection);
});
const dataSource = await addMergedEntityCollection(
getViewer(),
"markEntityCollection",
options.mapType,
);
enabledClustering(dataSource);
mitt.emit(changeCoverMaskVisibleMittKey, false);
};
// 删除点位
const removeMarkPoint = (mapType) => {
removeEntityCollection(getViewer(), `markEntityCollectionMerged${mapType ? `_${mapType}` : ""}`);
};
return {
flyTo,
returnCurrentCenterPoint,
returnPreviousCenterPoint,
changeSceneMode,
addPortPoint,
removePortPoint,
addBranchOfficePoint,
removeBranchOfficePoint,
addFourColorDiagram,
removeFourColorDiagram,
addWall,
removeWall,
addMarkPoint,
removeMarkPoint,
};
}

View File

@ -1,3 +1,4 @@
import { useRef } from "react";
import BranchOffice from "../components/popup/components/BranchOffice";
import Port from "../components/popup/components/Port";
import PopupInfo from "../components/popup/js/PopupInfo";
@ -12,71 +13,35 @@ import {
resetBottomCurrentIndexMittKey,
} from "./mittKey";
export default class PointClickEvent {
#viewer;
#popup;
#mapMethods;
export default function usePointClickEvent(viewerRef, mapMethods) {
const popupRef = useRef(null);
constructor(viewer, mapMethods) {
this.#viewer = viewer;
this.#mapMethods = mapMethods;
}
// 获取当前 Cesium 实例:点击事件触发时地图已经完成初始化。
const getViewer = () => viewerRef.current;
// 关闭弹窗
closePopup = () => {
if (this.#popup) {
this.#popup.remove();
this.#popup = null;
const closePopup = () => {
if (popupRef.current) {
popupRef.current.remove();
popupRef.current = null;
}
};
// 点位点击
pointClickEvent = (pick) => {
const data = pick.monitorItems.data;
this.closePopup();
if (data.isExternalEntry === "1" && data.mapType === "港口") {
this.#clickPortPointEnter(data);
return;
}
if (data.mapType === "港口")
this.#clickPortPoint(data);
if (data.mapType === "分公司")
this.#clickBranchOfficePoint(data);
if (data.mapType.includes("标记点"))
this.#clickMarkPoint(data);
};
// 港口点位点击
#clickPortPoint = (data) => {
const position = getPosition(data.position.x, data.position.y);
this.#popup = new PopupInfo(this.#viewer, {
component: Port,
position,
props: {
info: data,
close: this.closePopup,
enter: () => {
this.#clickPortPointEnter(data);
},
},
});
};
// 港口点位点击进入
#clickPortPointEnter = (data) => {
this.closePopup();
this.#mapMethods.removePortPoint();
const clickPortPointEnter = (data) => {
closePopup();
mapMethods.removePortPoint();
// setTimeout(() => {
if (data.id === "00003") {
this.#mapMethods.flyTo({ longitude: data.position.x, latitude: data.position.y, height: 10000 });
this.#mapMethods.addBranchOfficePoint();
mapMethods.flyTo({ longitude: data.position.x, latitude: data.position.y, height: 10000 });
mapMethods.addBranchOfficePoint();
mitt.emit(clickPortPointMittKey, data);
}
else {
this.#mapMethods.removeFourColorDiagram();
this.#mapMethods.removeWall();
this.#mapMethods.flyTo({ longitude: data.position.x, latitude: data.position.y, height: 2000 });
this.#mapMethods.addBranchOfficePoint("", {
mapMethods.removeFourColorDiagram();
mapMethods.removeWall();
mapMethods.flyTo({ longitude: data.position.x, latitude: data.position.y, height: 2000 });
mapMethods.addBranchOfficePoint("", {
corpName: data.name,
corpinfoId: data.corpinfoId,
longitude: data.position.x,
@ -91,35 +56,31 @@ export default class PointClickEvent {
mitt.emit(resetAllBottomUtilsCheckMittKey);
};
// 分公司点位点击
#clickBranchOfficePoint = async (data) => {
if (data.id === sessionStorage.getItem("mapCurrentBranchOfficeId"))
return;
// const { info } = await getCorpInfo({ id: data.id });
const info = {};
const position = getPosition(data.longitude, data.latitude);
this.#popup = new PopupInfo(this.#viewer, {
component: BranchOffice,
// 港口点位点击
const clickPortPoint = (data) => {
const position = getPosition(data.position.x, data.position.y);
popupRef.current = new PopupInfo(getViewer(), {
component: Port,
position,
props: {
info,
close: this.closePopup,
info: data,
close: closePopup,
enter: () => {
this.#clickBranchOfficePointEnter(data);
clickPortPointEnter(data);
},
},
});
};
// 分公司点位点击进入
#clickBranchOfficePointEnter = (data) => {
this.closePopup();
this.#mapMethods.removeBranchOfficePoint();
this.#mapMethods.removeMarkPoint();
this.#mapMethods.addBranchOfficePoint("", data);
this.#mapMethods.removeFourColorDiagram();
this.#mapMethods.removeWall();
this.#mapMethods.flyTo({ longitude: data.longitude, latitude: data.latitude, height: 2000 });
const clickBranchOfficePointEnter = (data) => {
closePopup();
mapMethods.removeBranchOfficePoint();
mapMethods.removeMarkPoint();
mapMethods.addBranchOfficePoint("", data);
mapMethods.removeFourColorDiagram();
mapMethods.removeWall();
mapMethods.flyTo({ longitude: data.longitude, latitude: data.latitude, height: 2000 });
mitt.emit(deletePeoplePositionPointMittKey);
mitt.emit(clickBranchOfficePointMittKey, data);
sessionStorage.setItem("mapCurrentBranchOfficeId", data.corpinfoId);
@ -127,8 +88,49 @@ export default class PointClickEvent {
mitt.emit(resetAllBottomUtilsCheckMittKey);
};
// 分公司点位点击
const clickBranchOfficePoint = async (data) => {
if (data.id === sessionStorage.getItem("mapCurrentBranchOfficeId"))
return;
// const { info } = await getCorpInfo({ id: data.id });
const info = {};
const position = getPosition(data.longitude, data.latitude);
popupRef.current = new PopupInfo(getViewer(), {
component: BranchOffice,
position,
props: {
info,
close: closePopup,
enter: () => {
clickBranchOfficePointEnter(data);
},
},
});
};
// 标记点位点击
#clickMarkPoint = (data) => {
const clickMarkPoint = (data) => {
mitt.emit(clickMarkPointMittKey, data);
};
// 点位点击
const pointClickEvent = (pick) => {
const data = pick.monitorItems.data;
closePopup();
if (data.isExternalEntry === "1" && data.mapType === "港口") {
clickPortPointEnter(data);
return;
}
if (data.mapType === "港口")
clickPortPoint(data);
if (data.mapType === "分公司")
clickBranchOfficePoint(data);
if (data.mapType.includes("标记点"))
clickMarkPoint(data);
};
return {
closePopup,
pointClickEvent,
};
}