@fleet-frontend/mower-maps 0.2.5-beta.8 → 0.2.6-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -39
- package/dist/config/constants.d.ts +3 -0
- package/dist/config/constants.d.ts.map +1 -1
- package/dist/config/styles.d.ts.map +1 -1
- package/dist/context/common.d.ts +17 -0
- package/dist/context/common.d.ts.map +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +2524 -413
- package/dist/index.js +2535 -411
- package/dist/render/BoundarySvgRender.d.ts +3 -1
- package/dist/render/BoundarySvgRender.d.ts.map +1 -1
- package/dist/render/MowerMapRenderer.d.ts.map +1 -1
- package/dist/render/MowerPartitionPickerMap.d.ts +17 -0
- package/dist/render/MowerPartitionPickerMap.d.ts.map +1 -0
- package/dist/render/antennas/useAntennaInfo.d.ts.map +1 -1
- package/dist/render/boundaryLabels/index.d.ts +1 -1
- package/dist/render/boundaryLabels/index.d.ts.map +1 -1
- package/dist/render/boundaryLabels/useBoundaryLabels.d.ts.map +1 -1
- package/dist/render/charginPile/index.d.ts +12 -1
- package/dist/render/charginPile/index.d.ts.map +1 -1
- package/dist/render/charginPile/useChargingPile.d.ts +11 -1
- package/dist/render/charginPile/useChargingPile.d.ts.map +1 -1
- package/dist/render/partitionPicker/constants.d.ts +165 -0
- package/dist/render/partitionPicker/constants.d.ts.map +1 -0
- package/dist/render/partitionPicker/hooks/useBoundaryHover.d.ts +18 -0
- package/dist/render/partitionPicker/hooks/useBoundaryHover.d.ts.map +1 -0
- package/dist/render/partitionPicker/hooks/useBoundarySelection.d.ts +20 -0
- package/dist/render/partitionPicker/hooks/useBoundarySelection.d.ts.map +1 -0
- package/dist/render/partitionPicker/hooks/useContainerSize.d.ts +16 -0
- package/dist/render/partitionPicker/hooks/useContainerSize.d.ts.map +1 -0
- package/dist/render/partitionPicker/hooks/useGoogleMapsOverlay.d.ts +31 -0
- package/dist/render/partitionPicker/hooks/useGoogleMapsOverlay.d.ts.map +1 -0
- package/dist/render/partitionPicker/hooks/useIsolatedBoundaryIds.d.ts +6 -0
- package/dist/render/partitionPicker/hooks/useIsolatedBoundaryIds.d.ts.map +1 -0
- package/dist/render/partitionPicker/hooks/useMapViewport.d.ts +53 -0
- package/dist/render/partitionPicker/hooks/useMapViewport.d.ts.map +1 -0
- package/dist/render/partitionPicker/hooks/usePartitionLabelItems.d.ts +22 -0
- package/dist/render/partitionPicker/hooks/usePartitionLabelItems.d.ts.map +1 -0
- package/dist/render/partitionPicker/index.d.ts +9 -0
- package/dist/render/partitionPicker/index.d.ts.map +1 -0
- package/dist/render/partitionPicker/layers/MapGridBackground.d.ts +22 -0
- package/dist/render/partitionPicker/layers/MapGridBackground.d.ts.map +1 -0
- package/dist/render/partitionPicker/layers/PartitionNameLabels.d.ts +21 -0
- package/dist/render/partitionPicker/layers/PartitionNameLabels.d.ts.map +1 -0
- package/dist/render/partitionPicker/layers/PartitionPickerGridSvg.d.ts +10 -0
- package/dist/render/partitionPicker/layers/PartitionPickerGridSvg.d.ts.map +1 -0
- package/dist/render/partitionPicker/layers/PartitionPickerSvg.d.ts +24 -0
- package/dist/render/partitionPicker/layers/PartitionPickerSvg.d.ts.map +1 -0
- package/dist/render/partitionPicker/layers/SelectableBoundaryElement.d.ts +34 -0
- package/dist/render/partitionPicker/layers/SelectableBoundaryElement.d.ts.map +1 -0
- package/dist/render/partitionPicker/types.d.ts +140 -0
- package/dist/render/partitionPicker/types.d.ts.map +1 -0
- package/dist/render/partitionPicker/viewportUtils.d.ts +102 -0
- package/dist/render/partitionPicker/viewportUtils.d.ts.map +1 -0
- package/dist/render/svgElement/ChannelClipPath/constants.d.ts +9 -0
- package/dist/render/svgElement/ChannelClipPath/constants.d.ts.map +1 -0
- package/dist/render/svgElement/ChannelElement/index.d.ts.map +1 -1
- package/dist/render/svgElement/ObstacleElement/index.d.ts.map +1 -1
- package/dist/render/svgElement/PolygonELement/components/Magnifier/index.d.ts.map +1 -1
- package/dist/render/svgElement/PolygonELement/components/Tooltip.d.ts.map +1 -1
- package/dist/render/svgElement/PolygonELement/index.d.ts.map +1 -1
- package/dist/render/svgElement/TransformWrapper/DoodleTransformWrapper/DoodleTransformWrapper.d.ts.map +1 -1
- package/dist/render/svgElement/TransformWrapper/VisionOffTransformWrapper/VisionOffTransformWrapper.d.ts.map +1 -1
- package/dist/types/renderer.d.ts +33 -0
- package/dist/types/renderer.d.ts.map +1 -1
- package/dist/utils/dateTimeUtils.d.ts +6 -2
- package/dist/utils/dateTimeUtils.d.ts.map +1 -1
- package/dist/utils/googleStaticMapSign.d.ts +17 -0
- package/dist/utils/googleStaticMapSign.d.ts.map +1 -0
- package/dist/utils/mapBounds.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
2
|
import * as React from 'react';
|
|
3
|
-
import React__default, { memo, useMemo, useEffect, useState, useRef, useCallback, createContext, useContext, useLayoutEffect, forwardRef, useImperativeHandle } from 'react';
|
|
3
|
+
import React__default, { memo, useMemo, useEffect, useState, useRef, useCallback, createContext, useContext, useLayoutEffect, forwardRef, useImperativeHandle, useId } from 'react';
|
|
4
4
|
import ReactDOM, { createPortal } from 'react-dom';
|
|
5
5
|
|
|
6
6
|
function globals (defs) {
|
|
@@ -2089,7 +2089,7 @@ var ellipsoids = {
|
|
|
2089
2089
|
}
|
|
2090
2090
|
};
|
|
2091
2091
|
|
|
2092
|
-
const WGS84$
|
|
2092
|
+
const WGS84$2 = ellipsoids.WGS84; // default ellipsoid
|
|
2093
2093
|
|
|
2094
2094
|
function eccentricity(a, b, rf, R_A) {
|
|
2095
2095
|
var a2 = a * a; // used in geocentric
|
|
@@ -2114,7 +2114,7 @@ function sphere(a, b, rf, ellps, sphere) {
|
|
|
2114
2114
|
if (!a) { // do we have an ellipsoid?
|
|
2115
2115
|
var ellipse = match(ellipsoids, ellps);
|
|
2116
2116
|
if (!ellipse) {
|
|
2117
|
-
ellipse = WGS84$
|
|
2117
|
+
ellipse = WGS84$2;
|
|
2118
2118
|
}
|
|
2119
2119
|
a = ellipse.a;
|
|
2120
2120
|
b = ellipse.b;
|
|
@@ -10613,9 +10613,16 @@ var SvgMapOpacity;
|
|
|
10613
10613
|
* 地图渲染相关常量配置
|
|
10614
10614
|
*/
|
|
10615
10615
|
// 默认坐标
|
|
10616
|
+
// export const DEFAULT_COORDINATES = {
|
|
10617
|
+
// sw: [116.071044, 40.102099],
|
|
10618
|
+
// ne: [116.0734, 40.103901],
|
|
10619
|
+
// };
|
|
10620
|
+
/** 无效 GPS 时的兜底中心点 [lng, lat] */
|
|
10621
|
+
const DEFAULT_GPS_CENTER = [0, 0];
|
|
10622
|
+
/** 无 map_width/map_height 时的默认 GPS 边界 [lng, lat](中心约 (0,0)) */
|
|
10616
10623
|
const DEFAULT_COORDINATES = {
|
|
10617
|
-
sw: [
|
|
10618
|
-
ne: [
|
|
10624
|
+
sw: [-225e-6, -225e-6],
|
|
10625
|
+
ne: [0.000225, 0.000225],
|
|
10619
10626
|
};
|
|
10620
10627
|
/**
|
|
10621
10628
|
* 缩放因子 - 将米转换为像素
|
|
@@ -13905,6 +13912,23 @@ var CheckDoodleErrorType;
|
|
|
13905
13912
|
// doodle与其他元素距离过近
|
|
13906
13913
|
CheckDoodleErrorType["DOODLE_DISTANCE_TOO_CLOSE_TO_OTHER_ELEMENT"] = "doodle_distance_too_close_to_other_element";
|
|
13907
13914
|
})(CheckDoodleErrorType || (CheckDoodleErrorType = {}));
|
|
13915
|
+
var MapType;
|
|
13916
|
+
(function (MapType) {
|
|
13917
|
+
// 普通地图
|
|
13918
|
+
MapType["NORMAL"] = "normal";
|
|
13919
|
+
/**
|
|
13920
|
+
* 主要是用在地理围栏相关的
|
|
13921
|
+
* 此地图没有分区名称气泡,没有孤立子地块,没有设备图标,没有天线
|
|
13922
|
+
*/
|
|
13923
|
+
MapType["GEO_FENCE"] = "geo-fence";
|
|
13924
|
+
})(MapType || (MapType = {}));
|
|
13925
|
+
var HoverType;
|
|
13926
|
+
(function (HoverType) {
|
|
13927
|
+
HoverType["Translate"] = "translate";
|
|
13928
|
+
HoverType["Scale"] = "scale";
|
|
13929
|
+
HoverType["Rotate"] = "rotate";
|
|
13930
|
+
HoverType["Delete"] = "delete";
|
|
13931
|
+
})(HoverType || (HoverType = {}));
|
|
13908
13932
|
|
|
13909
13933
|
var RealTimeDataType;
|
|
13910
13934
|
(function (RealTimeDataType) {
|
|
@@ -18056,6 +18080,8 @@ const OBSTACLE_STYLES = {
|
|
|
18056
18080
|
hoverFillColor: 'rgba(255, 90, 0, 0.4)',
|
|
18057
18081
|
lineWidth: DEFAULT_LINE_WIDTHS.OBSTACLE,
|
|
18058
18082
|
opacity: DEFAULT_OPACITIES.FULL,
|
|
18083
|
+
disabledLineColor: 'rgba(255, 90, 0, 0.4)',
|
|
18084
|
+
disabledFillColor: 'rgba(255, 90, 0, 0.1)',
|
|
18059
18085
|
};
|
|
18060
18086
|
const CHARGING_PILE_STYLES = {
|
|
18061
18087
|
lineColor: 'blue',
|
|
@@ -18537,6 +18563,38 @@ function calculateBoundaryBoundsCenter(mapData) {
|
|
|
18537
18563
|
boundaryBounds.minY + (boundaryBounds.maxY - boundaryBounds.minY) / 2,
|
|
18538
18564
|
];
|
|
18539
18565
|
}
|
|
18566
|
+
/**
|
|
18567
|
+
* 由中心点与物理尺寸(米)计算 GPS 边界 [lng, lat]
|
|
18568
|
+
*/
|
|
18569
|
+
function computeGpsBoundsFromCenterAndSize(centerLng, centerLat, mapWidthMeters, mapHeightMeters) {
|
|
18570
|
+
const METERS_PER_DEGREE_LAT = EQUATORIAL_CIRCUMFERENCE / 360;
|
|
18571
|
+
const METERS_PER_DEGREE_LNG = METERS_PER_DEGREE_LAT * Math.cos((centerLat * Math.PI) / 180);
|
|
18572
|
+
const swLat = centerLat - mapHeightMeters / 2 / METERS_PER_DEGREE_LAT;
|
|
18573
|
+
const swLng = centerLng - mapWidthMeters / 2 / METERS_PER_DEGREE_LNG;
|
|
18574
|
+
const neLat = centerLat + mapHeightMeters / 2 / METERS_PER_DEGREE_LAT;
|
|
18575
|
+
const neLng = centerLng + mapWidthMeters / 2 / METERS_PER_DEGREE_LNG;
|
|
18576
|
+
return {
|
|
18577
|
+
sw: [swLng, swLat],
|
|
18578
|
+
ne: [neLng, neLat],
|
|
18579
|
+
};
|
|
18580
|
+
}
|
|
18581
|
+
/**
|
|
18582
|
+
* GPS 无效时:中心 (0,0) + JSON 的 map_width/map_height;无尺寸则用 DEFAULT_COORDINATES
|
|
18583
|
+
*/
|
|
18584
|
+
function getFallbackGpsBounds(mapData) {
|
|
18585
|
+
const [centerLng, centerLat] = DEFAULT_GPS_CENTER;
|
|
18586
|
+
const hasMapSize = typeof mapData.map_width === 'number' &&
|
|
18587
|
+
typeof mapData.map_height === 'number' &&
|
|
18588
|
+
mapData.map_width > 0 &&
|
|
18589
|
+
mapData.map_height > 0;
|
|
18590
|
+
if (hasMapSize) {
|
|
18591
|
+
return computeGpsBoundsFromCenterAndSize(centerLng, centerLat, mapData.map_width, mapData.map_height);
|
|
18592
|
+
}
|
|
18593
|
+
return {
|
|
18594
|
+
sw: [...DEFAULT_COORDINATES.sw],
|
|
18595
|
+
ne: [...DEFAULT_COORDINATES.ne],
|
|
18596
|
+
};
|
|
18597
|
+
}
|
|
18540
18598
|
/**
|
|
18541
18599
|
* 检查GPS坐标是否有效
|
|
18542
18600
|
*/
|
|
@@ -18572,10 +18630,10 @@ function estimateGpsFromMapBounds(mapData) {
|
|
|
18572
18630
|
const mapWidthMeters = maxXMeters - minXMeters;
|
|
18573
18631
|
const mapHeightMeters = maxYMeters - minYMeters;
|
|
18574
18632
|
// 凤凰岭的GPS坐标作为地图中心点
|
|
18575
|
-
const centerLat = 40.103;
|
|
18576
|
-
const centerLng = 116.072222;
|
|
18577
|
-
|
|
18578
|
-
|
|
18633
|
+
// const centerLat = 40.103;
|
|
18634
|
+
// const centerLng = 116.072222;
|
|
18635
|
+
const centerLat = 0;
|
|
18636
|
+
const centerLng = 0;
|
|
18579
18637
|
// 精确的坐标转换常数
|
|
18580
18638
|
// 1度纬度 = 约111,320米(在地球上任何地方都基本相同)
|
|
18581
18639
|
// 1度经度 = 约111,320 * cos(纬度) 米(随纬度变化)
|
|
@@ -18641,39 +18699,44 @@ function calculateMapGpsCenter(mapData) {
|
|
|
18641
18699
|
}
|
|
18642
18700
|
}
|
|
18643
18701
|
// 如果有西南角和东北角坐标,检查其有效性并计算中心点
|
|
18644
|
-
if (
|
|
18645
|
-
|
|
18646
|
-
|
|
18647
|
-
|
|
18648
|
-
|
|
18649
|
-
|
|
18650
|
-
|
|
18651
|
-
|
|
18652
|
-
|
|
18653
|
-
|
|
18654
|
-
|
|
18655
|
-
|
|
18656
|
-
|
|
18657
|
-
|
|
18658
|
-
|
|
18659
|
-
|
|
18660
|
-
|
|
18661
|
-
|
|
18662
|
-
|
|
18663
|
-
|
|
18664
|
-
|
|
18665
|
-
}
|
|
18702
|
+
// if (
|
|
18703
|
+
// mapData.sw_gps &&
|
|
18704
|
+
// mapData.ne_gps &&
|
|
18705
|
+
// mapData.sw_gps.length >= 2 &&
|
|
18706
|
+
// mapData.ne_gps.length >= 2
|
|
18707
|
+
// ) {
|
|
18708
|
+
// const swLat = mapData.sw_gps[1];
|
|
18709
|
+
// const swLng = mapData.sw_gps[0];
|
|
18710
|
+
// const neLat = mapData.ne_gps[1];
|
|
18711
|
+
// const neLng = mapData.ne_gps[0];
|
|
18712
|
+
// // 检查边界坐标的有效性
|
|
18713
|
+
// if (isValidGpsCoordinate(swLat, swLng) && isValidGpsCoordinate(neLat, neLng)) {
|
|
18714
|
+
// return {
|
|
18715
|
+
// lat: (swLat + neLat) / 2,
|
|
18716
|
+
// lng: (swLng + neLng) / 2,
|
|
18717
|
+
// };
|
|
18718
|
+
// } else {
|
|
18719
|
+
// console.warn('sw_gps或ne_gps坐标无效:', {
|
|
18720
|
+
// sw: { lat: swLat, lng: swLng },
|
|
18721
|
+
// ne: { lat: neLat, lng: neLng },
|
|
18722
|
+
// });
|
|
18723
|
+
// }
|
|
18724
|
+
// }
|
|
18666
18725
|
// 尝试从地图几何边界估算GPS坐标
|
|
18667
|
-
const estimatedBounds = estimateGpsFromMapBounds(mapData);
|
|
18668
|
-
if (estimatedBounds) {
|
|
18669
|
-
|
|
18670
|
-
|
|
18671
|
-
|
|
18672
|
-
|
|
18673
|
-
}
|
|
18726
|
+
// const estimatedBounds = estimateGpsFromMapBounds(mapData);
|
|
18727
|
+
// if (estimatedBounds) {
|
|
18728
|
+
// // 从估算的边界计算中心点
|
|
18729
|
+
// const centerLat = (estimatedBounds.sw[1] + estimatedBounds.ne[1]) / 2;
|
|
18730
|
+
// const centerLng = (estimatedBounds.sw[0] + estimatedBounds.ne[0]) / 2;
|
|
18731
|
+
// return { lat: centerLat, lng: centerLng };
|
|
18732
|
+
// }
|
|
18733
|
+
// return {
|
|
18734
|
+
// lat: 39.9042, // 北京纬度
|
|
18735
|
+
// lng: 116.4074, // 北京经度
|
|
18736
|
+
// };
|
|
18674
18737
|
return {
|
|
18675
|
-
lat:
|
|
18676
|
-
lng:
|
|
18738
|
+
lat: DEFAULT_GPS_CENTER[1],
|
|
18739
|
+
lng: DEFAULT_GPS_CENTER[0],
|
|
18677
18740
|
};
|
|
18678
18741
|
}
|
|
18679
18742
|
// 旋转坐标点
|
|
@@ -18703,50 +18766,22 @@ const getValidGpsBounds = (mapData, rotation = 0) => {
|
|
|
18703
18766
|
mapData.map_width > 0 &&
|
|
18704
18767
|
mapData.map_height > 0) {
|
|
18705
18768
|
const [centerLng, centerLat] = mapData.center_gps;
|
|
18706
|
-
|
|
18707
|
-
|
|
18708
|
-
|
|
18709
|
-
|
|
18710
|
-
|
|
18711
|
-
|
|
18712
|
-
|
|
18713
|
-
|
|
18714
|
-
|
|
18715
|
-
|
|
18716
|
-
|
|
18717
|
-
|
|
18718
|
-
|
|
18719
|
-
const neLat = centerLat + mapHeightMeters / 2 / METERS_PER_DEGREE_LAT;
|
|
18720
|
-
const neLng = centerLng + mapWidthMeters / 2 / METERS_PER_DEGREE_LNG;
|
|
18721
|
-
bounds = {
|
|
18722
|
-
sw: [swLng, swLat],
|
|
18723
|
-
ne: [neLng, neLat],
|
|
18724
|
-
};
|
|
18725
|
-
}
|
|
18726
|
-
else if (isValidGpsCoordinate(mapData.sw_gps?.[1], mapData.sw_gps?.[0]) &&
|
|
18727
|
-
isValidGpsCoordinate(mapData.ne_gps?.[1], mapData.ne_gps?.[0])) {
|
|
18728
|
-
// 降级方案:使用地图数据中的GPS坐标
|
|
18729
|
-
console.warn('center_gps 或 map_width/map_height 不可用,使用 sw_gps 和 ne_gps');
|
|
18730
|
-
bounds = {
|
|
18731
|
-
sw: mapData.sw_gps,
|
|
18732
|
-
ne: mapData.ne_gps,
|
|
18733
|
-
};
|
|
18734
|
-
}
|
|
18769
|
+
bounds = computeGpsBoundsFromCenterAndSize(centerLng, centerLat, mapData.map_width, mapData.map_height);
|
|
18770
|
+
}
|
|
18771
|
+
// else if (
|
|
18772
|
+
// isValidGpsCoordinate(mapData.sw_gps?.[1], mapData.sw_gps?.[0]) &&
|
|
18773
|
+
// isValidGpsCoordinate(mapData.ne_gps?.[1], mapData.ne_gps?.[0])
|
|
18774
|
+
// ) {
|
|
18775
|
+
// // 降级方案:使用地图数据中的GPS坐标
|
|
18776
|
+
// console.warn('center_gps 或 map_width/map_height 不可用,使用 sw_gps 和 ne_gps');
|
|
18777
|
+
// bounds = {
|
|
18778
|
+
// sw: mapData.sw_gps,
|
|
18779
|
+
// ne: mapData.ne_gps,
|
|
18780
|
+
// };
|
|
18781
|
+
// }
|
|
18735
18782
|
else {
|
|
18736
|
-
|
|
18737
|
-
|
|
18738
|
-
if (sw && ne) {
|
|
18739
|
-
console.warn('GPS坐标无效,使用地图几何数据估算边界:', sw, ne);
|
|
18740
|
-
bounds = {
|
|
18741
|
-
sw: [sw[0], sw[1]],
|
|
18742
|
-
ne: [ne[0], ne[1]],
|
|
18743
|
-
};
|
|
18744
|
-
}
|
|
18745
|
-
else {
|
|
18746
|
-
// 最后的fallback:使用默认坐标
|
|
18747
|
-
console.warn('无法获取有效的GPS边界,使用默认坐标');
|
|
18748
|
-
bounds = DEFAULT_COORDINATES;
|
|
18749
|
-
}
|
|
18783
|
+
console.warn('无法获取有效的GPS边界,使用 (0,0) 中心 + map_width/map_height 或 DEFAULT_COORDINATES');
|
|
18784
|
+
bounds = getFallbackGpsBounds(mapData);
|
|
18750
18785
|
}
|
|
18751
18786
|
// 如果有旋转角度,计算旋转后的边界
|
|
18752
18787
|
if (rotation !== 0) {
|
|
@@ -19179,7 +19214,7 @@ function getMowerImage(positonConfig, mowerIconConfig, modelType, hasEdger) {
|
|
|
19179
19214
|
const mowerImage = mowerIconConfig?.mapImgUrl || getMowerImageByModal(model, hasEdger);
|
|
19180
19215
|
const disabledImage = mowerIconConfig?.mapDisabledUrl || getDisabledMowerImageByModal(model);
|
|
19181
19216
|
const noPositionImage = mowerIconConfig?.mapNoPositionUrl || getNoPositionMowerImageByModal(model);
|
|
19182
|
-
const positonOutOfRange = isOutOfRange(positonConfig);
|
|
19217
|
+
// const positonOutOfRange = isOutOfRange(positonConfig);
|
|
19183
19218
|
const positionValid = isInvalidPosition(positonConfig);
|
|
19184
19219
|
const isOffLine = state === RobotStatus.DISCONNECTED;
|
|
19185
19220
|
const isInPark = state === RobotStatus.CHARGING || state === RobotStatus.PARKED;
|
|
@@ -19189,7 +19224,7 @@ function getMowerImage(positonConfig, mowerIconConfig, modelType, hasEdger) {
|
|
|
19189
19224
|
if (isInPark) {
|
|
19190
19225
|
return mowerImage;
|
|
19191
19226
|
}
|
|
19192
|
-
if (positionValid
|
|
19227
|
+
if (positionValid) {
|
|
19193
19228
|
return noPositionImage;
|
|
19194
19229
|
}
|
|
19195
19230
|
else {
|
|
@@ -19213,12 +19248,6 @@ function isInvalidPosition(positonConfig) {
|
|
|
19213
19248
|
isNoPosture(positonConfig.postureY) &&
|
|
19214
19249
|
isNoPosture(positonConfig.postureTheta));
|
|
19215
19250
|
}
|
|
19216
|
-
function isOutOfRange(positonConfig) {
|
|
19217
|
-
return (positonConfig.postureX != null &&
|
|
19218
|
-
Math.abs(positonConfig.postureX) > 1000 &&
|
|
19219
|
-
positonConfig.postureY != null &&
|
|
19220
|
-
Math.abs(positonConfig.postureY) > 1000);
|
|
19221
|
-
}
|
|
19222
19251
|
function getMindistanceByModel(modelType) {
|
|
19223
19252
|
if (isIModel(modelType)) {
|
|
19224
19253
|
return I_MIN_DISTANCE;
|
|
@@ -19515,8 +19544,16 @@ function formatTime(date) {
|
|
|
19515
19544
|
* @param date 目标日期
|
|
19516
19545
|
* @returns 星期几的英文缩写
|
|
19517
19546
|
*/
|
|
19518
|
-
function getWeekdayAbbr(date) {
|
|
19519
|
-
const weekdays = [
|
|
19547
|
+
function getWeekdayAbbr(date, locale) {
|
|
19548
|
+
const weekdays = [
|
|
19549
|
+
locale?.['map.renderer.weekday.sun'] || 'Sun',
|
|
19550
|
+
locale?.['map.renderer.weekday.mon'] || 'Mon',
|
|
19551
|
+
locale?.['map.renderer.weekday.tue'] || 'Tue',
|
|
19552
|
+
locale?.['map.renderer.weekday.wed'] || 'Wed',
|
|
19553
|
+
locale?.['map.renderer.weekday.thu'] || 'Thu',
|
|
19554
|
+
locale?.['map.renderer.weekday.fri'] || 'Fri',
|
|
19555
|
+
locale?.['map.renderer.weekday.sat'] || 'Sat'
|
|
19556
|
+
];
|
|
19520
19557
|
return weekdays[date.getDay()];
|
|
19521
19558
|
}
|
|
19522
19559
|
/**
|
|
@@ -19530,7 +19567,7 @@ function getWeekdayAbbr(date) {
|
|
|
19530
19567
|
* @param timestamp 时间戳(秒)
|
|
19531
19568
|
* @returns 格式化后的日期文本
|
|
19532
19569
|
*/
|
|
19533
|
-
function formatBoundaryDateText(timestamp) {
|
|
19570
|
+
function formatBoundaryDateText(timestamp, locale) {
|
|
19534
19571
|
if (!timestamp || timestamp <= 0) {
|
|
19535
19572
|
return '-';
|
|
19536
19573
|
}
|
|
@@ -19538,15 +19575,15 @@ function formatBoundaryDateText(timestamp) {
|
|
|
19538
19575
|
const timeStr = formatTime(date);
|
|
19539
19576
|
// 判断是否为今天
|
|
19540
19577
|
if (isToday(date)) {
|
|
19541
|
-
return
|
|
19578
|
+
return `${locale?.['map.renderer.today'] || 'Today'} ${timeStr}`;
|
|
19542
19579
|
}
|
|
19543
19580
|
// 判断是否为昨天
|
|
19544
19581
|
if (isYesterday(date)) {
|
|
19545
|
-
return
|
|
19582
|
+
return `${locale?.['map.renderer.yesterday'] || 'Yesterday'} ${timeStr}`;
|
|
19546
19583
|
}
|
|
19547
19584
|
// 判断是否为本周内
|
|
19548
19585
|
if (isThisWeek(date)) {
|
|
19549
|
-
const weekdayAbbr = getWeekdayAbbr(date);
|
|
19586
|
+
const weekdayAbbr = getWeekdayAbbr(date, locale);
|
|
19550
19587
|
return `${weekdayAbbr} ${timeStr}`;
|
|
19551
19588
|
}
|
|
19552
19589
|
// 其他情况显示完整日期
|
|
@@ -20038,12 +20075,12 @@ function styleInject(css, ref) {
|
|
|
20038
20075
|
}
|
|
20039
20076
|
}
|
|
20040
20077
|
|
|
20041
|
-
var css_248z$
|
|
20042
|
-
var styles$
|
|
20043
|
-
styleInject(css_248z$
|
|
20078
|
+
var css_248z$9 = ".index-module_edit__-5VvX {\n position: absolute;\n top: 0px;\n left: 0px;\n width: 100%;\n height: 100%;\n pointer-events: auto;\n z-index: 1000;\n cursor: move;\n touch-action: none;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n}\n.index-module_edit__-5VvX .index-module_border__JdNLM {\n position: absolute;\n inset: -10px;\n border: 2px dashed rgb(241, 102, 41);\n border-radius: 2px;\n pointer-events: none;\n}\n.index-module_edit__-5VvX .index-module_drag__uvdPG {\n position: absolute;\n inset: -10px;\n border-radius: 2px;\n}\n.index-module_edit__-5VvX .index-module_rotate__H-KIZ {\n position: absolute;\n top: -20px;\n right: -20px;\n width: 20px;\n height: 20px;\n cursor: grab;\n z-index: 1001;\n pointer-events: auto;\n}\n.index-module_edit__-5VvX .index-module_move__mZF8s {\n position: absolute;\n bottom: -20px;\n left: -20px;\n width: 20px;\n height: 20px;\n cursor: move;\n z-index: 1001;\n pointer-events: auto;\n}";
|
|
20079
|
+
var styles$9 = {"edit":"index-module_edit__-5VvX","border":"index-module_border__JdNLM","drag":"index-module_drag__uvdPG","rotate":"index-module_rotate__H-KIZ","move":"index-module_move__mZF8s"};
|
|
20080
|
+
styleInject(css_248z$9);
|
|
20044
20081
|
|
|
20045
20082
|
const RotateHandle = ({ onRotateStart, isRotating }) => {
|
|
20046
|
-
return (jsx("div", { className: styles$
|
|
20083
|
+
return (jsx("div", { className: styles$9.rotate, onMouseDown: (e) => onRotateStart(e), onTouchStart: (e) => onRotateStart(e), style: { cursor: isRotating ? 'grabbing' : 'grab' }, dangerouslySetInnerHTML: { __html: DEFAULT_ROTATE_ICON } }));
|
|
20047
20084
|
};
|
|
20048
20085
|
|
|
20049
20086
|
/**
|
|
@@ -20120,7 +20157,7 @@ const DragHandle = ({ onDragStart, isDragging }) => {
|
|
|
20120
20157
|
if (!isMobileDevice()) {
|
|
20121
20158
|
return null;
|
|
20122
20159
|
}
|
|
20123
|
-
return (jsx("div", { className: styles$
|
|
20160
|
+
return (jsx("div", { className: styles$9.move, onMouseDown: (e) => {
|
|
20124
20161
|
onDragStart(e);
|
|
20125
20162
|
}, onTouchStart: (e) => onDragStart(e), onMouseUp: (e) => {
|
|
20126
20163
|
// onDragEnd(e);
|
|
@@ -20152,7 +20189,7 @@ const MapDrag = ({ map: _map, dragCallbacks, onBoundaryLabelsCollapse, onTransfo
|
|
|
20152
20189
|
if (!isDragMap) {
|
|
20153
20190
|
return null;
|
|
20154
20191
|
}
|
|
20155
|
-
return (jsxs("div", { ref: containerRef, className: styles$
|
|
20192
|
+
return (jsxs("div", { ref: containerRef, className: styles$9.edit, style: { cursor: isDragging ? 'grabbing' : 'move' }, children: [jsx("div", { className: styles$9.border }), canRotateMap && jsx(RotateHandle, { onRotateStart: handleRotateStart, isRotating: isRotating }), jsx("div", { className: styles$9.drag, onMouseDown: handleDragStartEvent, onTouchStart: handleDragStartEvent }), jsx(DragHandle, { onDragStart: handleDragStartEvent, isDragging: isDragging })] }));
|
|
20156
20193
|
};
|
|
20157
20194
|
|
|
20158
20195
|
const SvgEditContext = createContext({
|
|
@@ -22668,13 +22705,17 @@ const CommonContext = createContext({
|
|
|
22668
22705
|
overlayScale: 1,
|
|
22669
22706
|
showStraddleBoundaryBorder: true,
|
|
22670
22707
|
googleMapStaticApiKey: '',
|
|
22708
|
+
googleMapStaticUrlSigningSecret: '',
|
|
22709
|
+
locale: {},
|
|
22710
|
+
signGoogleStaticMapUrl: undefined,
|
|
22671
22711
|
onH5FirstSelectObstaclePoint: undefined,
|
|
22712
|
+
channelClipPathId: '',
|
|
22672
22713
|
});
|
|
22673
22714
|
const CommonContextProvider = CommonContext.Provider;
|
|
22674
22715
|
const useCommonContext = () => useContext(CommonContext);
|
|
22675
22716
|
|
|
22676
22717
|
const ChannelClipPath = React__default.memo(() => {
|
|
22677
|
-
const { svgViewBox,
|
|
22718
|
+
const { svgViewBox, channelClipPathId } = useCommonContext();
|
|
22678
22719
|
const { svgElementDatas } = useSvgEditContext();
|
|
22679
22720
|
const boundaryData = svgElementDatas?.[DataType.BOUNDARY] || [];
|
|
22680
22721
|
const [minX, minY, maxX, maxY] = useMemo(() => {
|
|
@@ -22853,7 +22894,7 @@ const ChannelClipPath = React__default.memo(() => {
|
|
|
22853
22894
|
});
|
|
22854
22895
|
return baseD;
|
|
22855
22896
|
}, [boundaryData, minX, minY, maxX, maxY]);
|
|
22856
|
-
return (jsx("defs", { children: jsx("clipPath", { id:
|
|
22897
|
+
return (jsx("defs", { children: jsx("clipPath", { id: channelClipPathId, fillRule: "evenodd", children: jsx("path", { d: d }) }) }));
|
|
22857
22898
|
});
|
|
22858
22899
|
|
|
22859
22900
|
/**
|
|
@@ -23149,11 +23190,440 @@ const MapEditContext = createContext({
|
|
|
23149
23190
|
const MapEditContextProvider = MapEditContext.Provider;
|
|
23150
23191
|
const useMapEditContext = () => useContext(MapEditContext);
|
|
23151
23192
|
|
|
23193
|
+
/**
|
|
23194
|
+
* Google Maps Static API 数字签名
|
|
23195
|
+
* @see https://developers.google.com/maps/documentation/maps-static/digital-signature
|
|
23196
|
+
*
|
|
23197
|
+
* 使用纯 JS HMAC-SHA1,兼容 H5 WebView / 非 HTTPS 等无 crypto.subtle 的环境。
|
|
23198
|
+
*/
|
|
23199
|
+
/// <reference lib="dom" />
|
|
23200
|
+
function removeWebSafe(encoded) {
|
|
23201
|
+
return encoded.replace(/-/g, '+').replace(/_/g, '/');
|
|
23202
|
+
}
|
|
23203
|
+
function makeWebSafe(encoded) {
|
|
23204
|
+
return encoded.replace(/\+/g, '-').replace(/\//g, '_');
|
|
23205
|
+
}
|
|
23206
|
+
function decodeUrlSigningSecret(secret) {
|
|
23207
|
+
let base64 = removeWebSafe(secret);
|
|
23208
|
+
const remainder = base64.length % 4;
|
|
23209
|
+
if (remainder) {
|
|
23210
|
+
base64 += '='.repeat(4 - remainder);
|
|
23211
|
+
}
|
|
23212
|
+
const binary = globalThis.atob(base64);
|
|
23213
|
+
const bytes = new Uint8Array(binary.length);
|
|
23214
|
+
for (let i = 0; i < binary.length; i++) {
|
|
23215
|
+
bytes[i] = binary.charCodeAt(i);
|
|
23216
|
+
}
|
|
23217
|
+
return bytes;
|
|
23218
|
+
}
|
|
23219
|
+
function bytesToWebSafeBase64(bytes) {
|
|
23220
|
+
let binary = '';
|
|
23221
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
23222
|
+
binary += String.fromCharCode(bytes[i]);
|
|
23223
|
+
}
|
|
23224
|
+
return makeWebSafe(globalThis.btoa(binary));
|
|
23225
|
+
}
|
|
23226
|
+
function utf8Encode(text) {
|
|
23227
|
+
if (typeof globalThis.TextEncoder !== 'undefined') {
|
|
23228
|
+
return new globalThis.TextEncoder().encode(text);
|
|
23229
|
+
}
|
|
23230
|
+
// Static Map URL 仅含 ASCII;兼容无 TextEncoder 的旧 WebView / Jest jsdom
|
|
23231
|
+
const bytes = new Uint8Array(text.length);
|
|
23232
|
+
for (let i = 0; i < text.length; i++) {
|
|
23233
|
+
const code = text.charCodeAt(i);
|
|
23234
|
+
if (code > 127) {
|
|
23235
|
+
throw new Error('signGoogleStaticMapUrl: non-ASCII URL is not supported in this environment');
|
|
23236
|
+
}
|
|
23237
|
+
bytes[i] = code;
|
|
23238
|
+
}
|
|
23239
|
+
return bytes;
|
|
23240
|
+
}
|
|
23241
|
+
function concatBytes(...parts) {
|
|
23242
|
+
const total = parts.reduce((sum, part) => sum + part.length, 0);
|
|
23243
|
+
const out = new Uint8Array(total);
|
|
23244
|
+
let offset = 0;
|
|
23245
|
+
for (const part of parts) {
|
|
23246
|
+
out.set(part, offset);
|
|
23247
|
+
offset += part.length;
|
|
23248
|
+
}
|
|
23249
|
+
return out;
|
|
23250
|
+
}
|
|
23251
|
+
function leftRotate(value, amount) {
|
|
23252
|
+
return ((value << amount) | (value >>> (32 - amount))) >>> 0;
|
|
23253
|
+
}
|
|
23254
|
+
function sha1(message) {
|
|
23255
|
+
const originalLength = message.length;
|
|
23256
|
+
const bitLength = originalLength * 8;
|
|
23257
|
+
const paddedLength = Math.ceil((originalLength + 9) / 64) * 64;
|
|
23258
|
+
const padded = new Uint8Array(paddedLength);
|
|
23259
|
+
padded.set(message);
|
|
23260
|
+
padded[originalLength] = 0x80;
|
|
23261
|
+
const view = new DataView(padded.buffer);
|
|
23262
|
+
view.setUint32(paddedLength - 4, bitLength >>> 0, false);
|
|
23263
|
+
view.setUint32(paddedLength - 8, Math.floor(bitLength / 0x100000000), false);
|
|
23264
|
+
let h0 = 0x67452301;
|
|
23265
|
+
let h1 = 0xefcdab89;
|
|
23266
|
+
let h2 = 0x98badcfe;
|
|
23267
|
+
let h3 = 0x10325476;
|
|
23268
|
+
let h4 = 0xc3d2e1f0;
|
|
23269
|
+
const w = new Uint32Array(80);
|
|
23270
|
+
for (let offset = 0; offset < paddedLength; offset += 64) {
|
|
23271
|
+
for (let i = 0; i < 16; i++) {
|
|
23272
|
+
w[i] = view.getUint32(offset + i * 4, false);
|
|
23273
|
+
}
|
|
23274
|
+
for (let i = 16; i < 80; i++) {
|
|
23275
|
+
w[i] = leftRotate(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1);
|
|
23276
|
+
}
|
|
23277
|
+
let a = h0;
|
|
23278
|
+
let b = h1;
|
|
23279
|
+
let c = h2;
|
|
23280
|
+
let d = h3;
|
|
23281
|
+
let e = h4;
|
|
23282
|
+
for (let i = 0; i < 80; i++) {
|
|
23283
|
+
let f;
|
|
23284
|
+
let k;
|
|
23285
|
+
if (i < 20) {
|
|
23286
|
+
f = (b & c) | (~b & d);
|
|
23287
|
+
k = 0x5a827999;
|
|
23288
|
+
}
|
|
23289
|
+
else if (i < 40) {
|
|
23290
|
+
f = b ^ c ^ d;
|
|
23291
|
+
k = 0x6ed9eba1;
|
|
23292
|
+
}
|
|
23293
|
+
else if (i < 60) {
|
|
23294
|
+
f = (b & c) | (b & d) | (c & d);
|
|
23295
|
+
k = 0x8f1bbcdc;
|
|
23296
|
+
}
|
|
23297
|
+
else {
|
|
23298
|
+
f = b ^ c ^ d;
|
|
23299
|
+
k = 0xca62c1d6;
|
|
23300
|
+
}
|
|
23301
|
+
const temp = (leftRotate(a, 5) + f + e + k + w[i]) >>> 0;
|
|
23302
|
+
e = d;
|
|
23303
|
+
d = c;
|
|
23304
|
+
c = leftRotate(b, 30);
|
|
23305
|
+
b = a;
|
|
23306
|
+
a = temp;
|
|
23307
|
+
}
|
|
23308
|
+
h0 = (h0 + a) >>> 0;
|
|
23309
|
+
h1 = (h1 + b) >>> 0;
|
|
23310
|
+
h2 = (h2 + c) >>> 0;
|
|
23311
|
+
h3 = (h3 + d) >>> 0;
|
|
23312
|
+
h4 = (h4 + e) >>> 0;
|
|
23313
|
+
}
|
|
23314
|
+
const digest = new Uint8Array(20);
|
|
23315
|
+
const digestView = new DataView(digest.buffer);
|
|
23316
|
+
digestView.setUint32(0, h0, false);
|
|
23317
|
+
digestView.setUint32(4, h1, false);
|
|
23318
|
+
digestView.setUint32(8, h2, false);
|
|
23319
|
+
digestView.setUint32(12, h3, false);
|
|
23320
|
+
digestView.setUint32(16, h4, false);
|
|
23321
|
+
return digest;
|
|
23322
|
+
}
|
|
23323
|
+
function hmacSha1(key, message) {
|
|
23324
|
+
const blockSize = 64;
|
|
23325
|
+
let normalizedKey = key;
|
|
23326
|
+
if (normalizedKey.length > blockSize) {
|
|
23327
|
+
normalizedKey = sha1(normalizedKey);
|
|
23328
|
+
}
|
|
23329
|
+
const paddedKey = new Uint8Array(blockSize);
|
|
23330
|
+
paddedKey.set(normalizedKey);
|
|
23331
|
+
const innerPad = new Uint8Array(blockSize);
|
|
23332
|
+
const outerPad = new Uint8Array(blockSize);
|
|
23333
|
+
for (let i = 0; i < blockSize; i++) {
|
|
23334
|
+
innerPad[i] = paddedKey[i] ^ 0x36;
|
|
23335
|
+
outerPad[i] = paddedKey[i] ^ 0x5c;
|
|
23336
|
+
}
|
|
23337
|
+
return sha1(concatBytes(outerPad, sha1(concatBytes(innerPad, message))));
|
|
23338
|
+
}
|
|
23339
|
+
function signUrlToSign(urlToSign, urlSigningSecret) {
|
|
23340
|
+
const keyBytes = decodeUrlSigningSecret(urlSigningSecret);
|
|
23341
|
+
const signatureBytes = hmacSha1(keyBytes, utf8Encode(urlToSign));
|
|
23342
|
+
return bytesToWebSafeBase64(signatureBytes);
|
|
23343
|
+
}
|
|
23344
|
+
/**
|
|
23345
|
+
* 对 Maps Static API 请求 URL 追加 `signature` 参数。
|
|
23346
|
+
* 签名内容为 pathname + search(不含 scheme/host),算法 HMAC-SHA1 + URL-safe Base64。
|
|
23347
|
+
*
|
|
23348
|
+
* @param inputUrl 完整未签名 URL(须已包含 key 等 query 参数)
|
|
23349
|
+
* @param urlSigningSecret Cloud Console「URL 签名密钥」(URL-safe Base64)
|
|
23350
|
+
*/
|
|
23351
|
+
function signGoogleStaticMapUrlSync(inputUrl, urlSigningSecret) {
|
|
23352
|
+
if (!inputUrl || !urlSigningSecret) {
|
|
23353
|
+
throw new Error('signGoogleStaticMapUrl: inputUrl and urlSigningSecret are required');
|
|
23354
|
+
}
|
|
23355
|
+
const url = new globalThis.URL(inputUrl);
|
|
23356
|
+
url.searchParams.delete('signature');
|
|
23357
|
+
const urlToSign = `${url.pathname}${url.search}`;
|
|
23358
|
+
const signature = signUrlToSign(urlToSign, urlSigningSecret);
|
|
23359
|
+
return `${url.origin}${urlToSign}&signature=${signature}`;
|
|
23360
|
+
}
|
|
23361
|
+
/** 异步包装,便于与宿主 signGoogleStaticMapUrl 回调统一 Promise 接口 */
|
|
23362
|
+
async function signGoogleStaticMapUrl(inputUrl, urlSigningSecret) {
|
|
23363
|
+
return signGoogleStaticMapUrlSync(inputUrl, urlSigningSecret);
|
|
23364
|
+
}
|
|
23365
|
+
|
|
23152
23366
|
var chargingPileImage = "data:image/png;base64,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";
|
|
23153
23367
|
|
|
23154
|
-
const
|
|
23368
|
+
const DEFAULT_SCREEN_PADDING = 50;
|
|
23369
|
+
function normalizeScreenPadding(padding, defaultValue = DEFAULT_SCREEN_PADDING) {
|
|
23370
|
+
if (padding == null) {
|
|
23371
|
+
return {
|
|
23372
|
+
top: defaultValue,
|
|
23373
|
+
right: defaultValue,
|
|
23374
|
+
bottom: defaultValue,
|
|
23375
|
+
left: defaultValue,
|
|
23376
|
+
};
|
|
23377
|
+
}
|
|
23378
|
+
if (typeof padding === 'number') {
|
|
23379
|
+
return { top: padding, right: padding, bottom: padding, left: padding };
|
|
23380
|
+
}
|
|
23381
|
+
if (padding.length === 2) {
|
|
23382
|
+
const [vertical, horizontal] = padding;
|
|
23383
|
+
return { top: vertical, right: horizontal, bottom: vertical, left: horizontal };
|
|
23384
|
+
}
|
|
23385
|
+
if (padding.length === 3) {
|
|
23386
|
+
const [top, horizontal, bottom] = padding;
|
|
23387
|
+
return { top, right: horizontal, bottom, left: horizontal };
|
|
23388
|
+
}
|
|
23389
|
+
const [top, right, bottom, left] = padding;
|
|
23390
|
+
return { top, right, bottom, left };
|
|
23391
|
+
}
|
|
23392
|
+
function isZeroScreenPadding(padding) {
|
|
23393
|
+
return padding.top === 0 && padding.right === 0 && padding.bottom === 0 && padding.left === 0;
|
|
23394
|
+
}
|
|
23395
|
+
|
|
23396
|
+
/** 拖拽超过该像素阈值时视为平移,而非点击 */
|
|
23397
|
+
const PAN_CLICK_THRESHOLD_PX = 5;
|
|
23398
|
+
/**
|
|
23399
|
+
* 根据地图包围盒 + padding 计算「内容 viewBox」
|
|
23400
|
+
* 用于 ChannelClipPath 裁剪,padding 为 SVG 坐标系单位
|
|
23401
|
+
*/
|
|
23402
|
+
function boundsToContentViewBox(bounds, padding) {
|
|
23403
|
+
if (!Number.isFinite(bounds.minX) || !Number.isFinite(bounds.maxX)) {
|
|
23404
|
+
return { x: 0, y: 0, width: 100, height: 100 };
|
|
23405
|
+
}
|
|
23406
|
+
const width = Math.max(bounds.maxX - bounds.minX + padding * 2, 1);
|
|
23407
|
+
const height = Math.max(bounds.maxY - bounds.minY + padding * 2, 1);
|
|
23408
|
+
return {
|
|
23409
|
+
x: bounds.minX - padding,
|
|
23410
|
+
y: bounds.minY - padding,
|
|
23411
|
+
width,
|
|
23412
|
+
height,
|
|
23413
|
+
};
|
|
23414
|
+
}
|
|
23415
|
+
/**
|
|
23416
|
+
* 在固定容器宽高比下,计算 fit 的 viewBox(等价于 SVG preserveAspectRatio="meet")
|
|
23417
|
+
*
|
|
23418
|
+
* 若内容与容器比例不一致,会在 SVG 坐标系中扩展 viewBox 的宽或高,形成 letterbox 区域
|
|
23419
|
+
*/
|
|
23420
|
+
function computeFitViewBoxToContainer(content, containerWidth, containerHeight) {
|
|
23421
|
+
const containerAspect = containerWidth / containerHeight;
|
|
23422
|
+
const contentAspect = content.width / content.height;
|
|
23423
|
+
// 内容更「宽」:扩展 viewBox 高度,上下留白
|
|
23424
|
+
if (contentAspect > containerAspect) {
|
|
23425
|
+
const height = content.width / containerAspect;
|
|
23426
|
+
return {
|
|
23427
|
+
x: content.x,
|
|
23428
|
+
y: content.y - (height - content.height) / 2,
|
|
23429
|
+
width: content.width,
|
|
23430
|
+
height,
|
|
23431
|
+
};
|
|
23432
|
+
}
|
|
23433
|
+
// 内容更「高」:扩展 viewBox 宽度,左右留白
|
|
23434
|
+
const width = content.height * containerAspect;
|
|
23435
|
+
return {
|
|
23436
|
+
x: content.x - (width - content.width) / 2,
|
|
23437
|
+
y: content.y,
|
|
23438
|
+
width,
|
|
23439
|
+
height: content.height,
|
|
23440
|
+
};
|
|
23441
|
+
}
|
|
23442
|
+
/**
|
|
23443
|
+
* 计算默认 fit viewBox
|
|
23444
|
+
*
|
|
23445
|
+
* @param screenPadding 容器四边留出的屏幕边距(px),语法同 CSS padding
|
|
23446
|
+
*/
|
|
23447
|
+
function computeFitViewBox(content, containerWidth, containerHeight, screenPadding = 0) {
|
|
23448
|
+
const baseFit = computeFitViewBoxToContainer(content, containerWidth, containerHeight);
|
|
23449
|
+
const pad = normalizeScreenPadding(screenPadding, 0);
|
|
23450
|
+
if (isZeroScreenPadding(pad)) {
|
|
23451
|
+
return baseFit;
|
|
23452
|
+
}
|
|
23453
|
+
const innerWidth = Math.max(containerWidth - pad.left - pad.right, 1);
|
|
23454
|
+
const innerHeight = Math.max(containerHeight - pad.top - pad.bottom, 1);
|
|
23455
|
+
const scaleX = innerWidth / containerWidth;
|
|
23456
|
+
const scaleY = innerHeight / containerHeight;
|
|
23457
|
+
const scale = Math.min(scaleX, scaleY);
|
|
23458
|
+
if (!Number.isFinite(scale) || scale <= 0 || scale >= 1) {
|
|
23459
|
+
return baseFit;
|
|
23460
|
+
}
|
|
23461
|
+
const newWidth = baseFit.width / scale;
|
|
23462
|
+
const newHeight = baseFit.height / scale;
|
|
23463
|
+
const centerX = baseFit.x + baseFit.width / 2;
|
|
23464
|
+
const centerY = baseFit.y + baseFit.height / 2;
|
|
23465
|
+
const offsetX = ((pad.left - pad.right) / 2) * (newWidth / containerWidth);
|
|
23466
|
+
const offsetY = ((pad.top - pad.bottom) / 2) * (newHeight / containerHeight);
|
|
23467
|
+
return {
|
|
23468
|
+
x: centerX - newWidth / 2 + offsetX,
|
|
23469
|
+
y: centerY - newHeight / 2 + offsetY,
|
|
23470
|
+
width: newWidth,
|
|
23471
|
+
height: newHeight,
|
|
23472
|
+
};
|
|
23473
|
+
}
|
|
23474
|
+
function viewBoxToString(viewBox) {
|
|
23475
|
+
return `${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}`;
|
|
23476
|
+
}
|
|
23477
|
+
/** SVG preserveAspectRatio="xMidYMid meet" 下的实际缩放比(地图单位 → 屏幕 px) */
|
|
23478
|
+
function getViewBoxMeetScale(viewBox, containerWidth, containerHeight) {
|
|
23479
|
+
return Math.min(containerWidth / viewBox.width, containerHeight / viewBox.height);
|
|
23480
|
+
}
|
|
23481
|
+
/** meet 模式下 SVG 内容相对容器左上角的 letterbox 偏移及缩放比 */
|
|
23482
|
+
function getViewBoxMeetOffset(viewBox, containerWidth, containerHeight) {
|
|
23483
|
+
const scale = getViewBoxMeetScale(viewBox, containerWidth, containerHeight);
|
|
23484
|
+
return {
|
|
23485
|
+
x: (containerWidth - viewBox.width * scale) / 2,
|
|
23486
|
+
y: (containerHeight - viewBox.height * scale) / 2,
|
|
23487
|
+
scale,
|
|
23488
|
+
};
|
|
23489
|
+
}
|
|
23490
|
+
/** 将屏幕坐标绕指定中心旋转(度,顺时针) */
|
|
23491
|
+
function rotatePointAround(x, y, centerX, centerY, rotationDeg) {
|
|
23492
|
+
if (rotationDeg === 0)
|
|
23493
|
+
return { x, y };
|
|
23494
|
+
const rad = (rotationDeg * Math.PI) / 180;
|
|
23495
|
+
const cos = Math.cos(rad);
|
|
23496
|
+
const sin = Math.sin(rad);
|
|
23497
|
+
const dx = x - centerX;
|
|
23498
|
+
const dy = y - centerY;
|
|
23499
|
+
return {
|
|
23500
|
+
x: centerX + dx * cos - dy * sin,
|
|
23501
|
+
y: centerY + dx * sin + dy * cos,
|
|
23502
|
+
};
|
|
23503
|
+
}
|
|
23504
|
+
/** 在地图坐标系内绕 viewBox 中心旋转(与 SVG transform="rotate(deg, cx, cy)" 一致) */
|
|
23505
|
+
function rotateMapPointAroundViewBoxCenter(mapX, mapY, viewBox, rotationDeg) {
|
|
23506
|
+
if (rotationDeg === 0)
|
|
23507
|
+
return { x: mapX, y: mapY };
|
|
23508
|
+
const cx = viewBox.x + viewBox.width / 2;
|
|
23509
|
+
const cy = viewBox.y + viewBox.height / 2;
|
|
23510
|
+
return rotatePointAround(mapX, mapY, cx, cy, rotationDeg);
|
|
23511
|
+
}
|
|
23512
|
+
function getViewBoxCenter(viewBox) {
|
|
23513
|
+
return {
|
|
23514
|
+
x: viewBox.x + viewBox.width / 2,
|
|
23515
|
+
y: viewBox.y + viewBox.height / 2,
|
|
23516
|
+
};
|
|
23517
|
+
}
|
|
23518
|
+
/**
|
|
23519
|
+
* 将地图坐标转换为 HTML overlay 的定位百分比
|
|
23520
|
+
* 需与 SVG viewBox + preserveAspectRatio="meet" 的渲染规则一致
|
|
23521
|
+
*
|
|
23522
|
+
* @returns leftPct / topPct — 相对容器宽高的百分比,配合 translate(-50%, -50%) 居中
|
|
23523
|
+
*/
|
|
23524
|
+
function mapPointToContainerPercent(mapX, mapY, viewBox, containerWidth, containerHeight, mapRotationDeg = 0) {
|
|
23525
|
+
const { x: offsetX, y: offsetY, scale } = getViewBoxMeetOffset(viewBox, containerWidth, containerHeight);
|
|
23526
|
+
const { x: projectedX, y: projectedY } = rotateMapPointAroundViewBoxCenter(mapX, mapY, viewBox, mapRotationDeg);
|
|
23527
|
+
const screenX = offsetX + (projectedX - viewBox.x) * scale;
|
|
23528
|
+
const screenY = offsetY + (projectedY - viewBox.y) * scale;
|
|
23529
|
+
return {
|
|
23530
|
+
leftPct: (screenX / containerWidth) * 100,
|
|
23531
|
+
topPct: (screenY / containerHeight) * 100,
|
|
23532
|
+
};
|
|
23533
|
+
}
|
|
23534
|
+
/** 计算多边形包围盒在当前视口下的屏幕宽度(px) */
|
|
23535
|
+
function computePolygonScreenWidthPx(points, viewBox, containerWidth, containerHeight) {
|
|
23536
|
+
const valid = points.filter((point) => point.length >= 2);
|
|
23537
|
+
if (valid.length === 0)
|
|
23538
|
+
return 0;
|
|
23539
|
+
let minX = Infinity;
|
|
23540
|
+
let maxX = -Infinity;
|
|
23541
|
+
for (const [x] of valid) {
|
|
23542
|
+
minX = Math.min(minX, x);
|
|
23543
|
+
maxX = Math.max(maxX, x);
|
|
23544
|
+
}
|
|
23545
|
+
const mapWidth = maxX - minX;
|
|
23546
|
+
if (!Number.isFinite(mapWidth) || mapWidth <= 0)
|
|
23547
|
+
return 0;
|
|
23548
|
+
const scale = getViewBoxMeetScale(viewBox, containerWidth, containerHeight);
|
|
23549
|
+
return mapWidth * scale;
|
|
23550
|
+
}
|
|
23551
|
+
function scaleStrokeWidthForViewBox(baseStrokeWidth, viewBox, fitViewBox, options) {
|
|
23552
|
+
const { containerWidth, containerHeight, minScreenStrokePx = 2 } = options;
|
|
23553
|
+
const meetScale = getViewBoxMeetScale(viewBox, containerWidth, containerHeight);
|
|
23554
|
+
const fitMeetScale = getViewBoxMeetScale(fitViewBox, containerWidth, containerHeight);
|
|
23555
|
+
if (meetScale <= 0 || fitMeetScale <= 0 || !Number.isFinite(baseStrokeWidth)) {
|
|
23556
|
+
return baseStrokeWidth;
|
|
23557
|
+
}
|
|
23558
|
+
const targetScreenPx = Math.max(baseStrokeWidth * fitMeetScale, minScreenStrokePx);
|
|
23559
|
+
return targetScreenPx / meetScale;
|
|
23560
|
+
}
|
|
23561
|
+
/** 屏幕拖拽像素 → viewBox 坐标增量(与 mapPointToContainerPercent 互逆,供 applyPan 使用) */
|
|
23562
|
+
function containerDeltaToViewBoxDelta(deltaXPx, deltaYPx, viewBox, containerWidth, containerHeight, mapRotationDeg = 0) {
|
|
23563
|
+
const scale = getViewBoxMeetScale(viewBox, containerWidth, containerHeight);
|
|
23564
|
+
if (mapRotationDeg === 0) {
|
|
23565
|
+
return {
|
|
23566
|
+
dx: deltaXPx / scale,
|
|
23567
|
+
dy: deltaYPx / scale,
|
|
23568
|
+
};
|
|
23569
|
+
}
|
|
23570
|
+
// 地图在屏幕上旋转了 mapRotationDeg,将屏幕位移矢量逆旋转后再换算为 viewBox 增量
|
|
23571
|
+
const rad = (-mapRotationDeg * Math.PI) / 180;
|
|
23572
|
+
const cos = Math.cos(rad);
|
|
23573
|
+
const sin = Math.sin(rad);
|
|
23574
|
+
const mapDeltaXPx = deltaXPx * cos - deltaYPx * sin;
|
|
23575
|
+
const mapDeltaYPx = deltaXPx * sin + deltaYPx * cos;
|
|
23576
|
+
return {
|
|
23577
|
+
dx: mapDeltaXPx / scale,
|
|
23578
|
+
dy: mapDeltaYPx / scale,
|
|
23579
|
+
};
|
|
23580
|
+
}
|
|
23581
|
+
/**
|
|
23582
|
+
* 限制 viewBox 宽度在 zoom 范围内
|
|
23583
|
+
*
|
|
23584
|
+
* zoom = fitViewBox.width / viewBox.width
|
|
23585
|
+
* - zoom 越大 → viewBox 越小 → 放得越大
|
|
23586
|
+
* - minZoom=0.5 → 最多缩小到 fit 的 2 倍视野(viewBox.width 最大为 fit/0.5)
|
|
23587
|
+
* - maxZoom=4 → 最多放大 4 倍(viewBox.width 最小为 fit/4)
|
|
23588
|
+
*/
|
|
23589
|
+
function clampViewBoxWidth(width, fitViewBox, minZoom, maxZoom) {
|
|
23590
|
+
const minWidth = fitViewBox.width / maxZoom;
|
|
23591
|
+
const maxWidth = fitViewBox.width / minZoom;
|
|
23592
|
+
return Math.min(maxWidth, Math.max(minWidth, width));
|
|
23593
|
+
}
|
|
23594
|
+
/**
|
|
23595
|
+
* 以屏幕 focal 点为中心缩放 viewBox
|
|
23596
|
+
* 将容器像素坐标转换为 SVG 坐标,保持焦点下的地图点位置不变
|
|
23597
|
+
*
|
|
23598
|
+
* @param viewBox 缩放前的 viewBox
|
|
23599
|
+
* @param newWidth 缩放后的 viewBox 宽度(高度按容器宽高比推导)
|
|
23600
|
+
* @param containerWidth 容器宽度(px)
|
|
23601
|
+
* @param containerHeight 容器高度(px)
|
|
23602
|
+
* @param focalX 焦点相对容器左上角的 X(px)
|
|
23603
|
+
* @param focalY 焦点相对容器左上角的 Y(px)
|
|
23604
|
+
*/
|
|
23605
|
+
function resizeViewBoxAroundPoint(viewBox, newWidth, containerWidth, containerHeight, focalX, focalY) {
|
|
23606
|
+
const containerAspect = containerWidth / containerHeight;
|
|
23607
|
+
const newHeight = newWidth / containerAspect;
|
|
23608
|
+
const relX = focalX / containerWidth;
|
|
23609
|
+
const relY = focalY / containerHeight;
|
|
23610
|
+
const svgFocalX = viewBox.x + relX * viewBox.width;
|
|
23611
|
+
const svgFocalY = viewBox.y + relY * viewBox.height;
|
|
23612
|
+
return {
|
|
23613
|
+
x: svgFocalX - relX * newWidth,
|
|
23614
|
+
y: svgFocalY - relY * newHeight,
|
|
23615
|
+
width: newWidth,
|
|
23616
|
+
height: newHeight,
|
|
23617
|
+
};
|
|
23618
|
+
}
|
|
23619
|
+
|
|
23620
|
+
const useChargingPile = ({ viewBox, rotation = 0, viewRotationMode = 'overlay-parent', containerWidth, containerHeight, }) => {
|
|
23155
23621
|
const { svgElementDatas } = useSvgEditContext();
|
|
23156
23622
|
const { sn } = useCommonContext();
|
|
23623
|
+
const useMeetPositioning = containerWidth != null &&
|
|
23624
|
+
containerHeight != null &&
|
|
23625
|
+
containerWidth > 0 &&
|
|
23626
|
+
containerHeight > 0;
|
|
23157
23627
|
const items = useMemo(() => {
|
|
23158
23628
|
if (!svgElementDatas || !viewBox)
|
|
23159
23629
|
return [];
|
|
@@ -23169,13 +23639,26 @@ const useChargingPile = ({ viewBox, rotation = 0 }) => {
|
|
|
23169
23639
|
const direction = element?.direction || 0;
|
|
23170
23640
|
const angle = (direction * 180) / Math.PI;
|
|
23171
23641
|
const rotationDegree = 270 - angle; // 正东是正方向,需要处理下
|
|
23172
|
-
const actualRotation =
|
|
23173
|
-
|
|
23174
|
-
|
|
23175
|
-
|
|
23642
|
+
const actualRotation = viewRotationMode === 'svg-transform'
|
|
23643
|
+
? rotationDegree
|
|
23644
|
+
: rotationDegree - rotation;
|
|
23645
|
+
let leftPct;
|
|
23646
|
+
let topPct;
|
|
23647
|
+
if (useMeetPositioning) {
|
|
23648
|
+
const position = mapPointToContainerPercent(center[0], center[1], viewBox, containerWidth, containerHeight, viewRotationMode === 'svg-transform' ? rotation : 0);
|
|
23649
|
+
leftPct = position.leftPct;
|
|
23650
|
+
topPct = position.topPct;
|
|
23651
|
+
}
|
|
23652
|
+
else {
|
|
23653
|
+
const relX = (center[0] - viewBox.x) / viewBox.width;
|
|
23654
|
+
const relY = (center[1] - viewBox.y) / viewBox.height;
|
|
23655
|
+
leftPct = relX * 100;
|
|
23656
|
+
topPct = relY * 100;
|
|
23657
|
+
}
|
|
23658
|
+
if (isFinite(leftPct) && isFinite(topPct)) {
|
|
23176
23659
|
results.push({
|
|
23177
|
-
leftPct
|
|
23178
|
-
topPct
|
|
23660
|
+
leftPct,
|
|
23661
|
+
topPct,
|
|
23179
23662
|
size,
|
|
23180
23663
|
rotateDeg: actualRotation,
|
|
23181
23664
|
imageSrc: chargingPileImage,
|
|
@@ -23183,12 +23666,30 @@ const useChargingPile = ({ viewBox, rotation = 0 }) => {
|
|
|
23183
23666
|
}
|
|
23184
23667
|
}
|
|
23185
23668
|
return results;
|
|
23186
|
-
}, [
|
|
23669
|
+
}, [
|
|
23670
|
+
svgElementDatas,
|
|
23671
|
+
viewBox?.x,
|
|
23672
|
+
viewBox?.y,
|
|
23673
|
+
viewBox?.width,
|
|
23674
|
+
viewBox?.height,
|
|
23675
|
+
rotation,
|
|
23676
|
+
viewRotationMode,
|
|
23677
|
+
sn,
|
|
23678
|
+
useMeetPositioning,
|
|
23679
|
+
containerWidth,
|
|
23680
|
+
containerHeight,
|
|
23681
|
+
]);
|
|
23187
23682
|
return items;
|
|
23188
23683
|
};
|
|
23189
23684
|
|
|
23190
|
-
const CharginPile = React__default.memo(({ viewBox, rotation = 0, sizeScale = 1, isHighlight = true }) => {
|
|
23191
|
-
const items = useChargingPile({
|
|
23685
|
+
const CharginPile = React__default.memo(({ viewBox, rotation = 0, viewRotationMode = 'overlay-parent', containerWidth, containerHeight, sizeScale = 1, sizeInSvgUnits = false, minDisplaySizePx, isHighlight = true, }) => {
|
|
23686
|
+
const items = useChargingPile({
|
|
23687
|
+
viewBox: viewBox || null,
|
|
23688
|
+
rotation,
|
|
23689
|
+
viewRotationMode,
|
|
23690
|
+
containerWidth,
|
|
23691
|
+
containerHeight,
|
|
23692
|
+
});
|
|
23192
23693
|
const containerStyle = useMemo(() => ({
|
|
23193
23694
|
position: 'absolute',
|
|
23194
23695
|
top: 0,
|
|
@@ -23199,10 +23700,19 @@ const CharginPile = React__default.memo(({ viewBox, rotation = 0, sizeScale = 1,
|
|
|
23199
23700
|
zIndex: 750,
|
|
23200
23701
|
opacity: isHighlight ? SvgMapOpacity.HIGHLIGHT : SvgMapOpacity.UN_HIGHLIGHT,
|
|
23201
23702
|
}), [isHighlight]);
|
|
23703
|
+
const useMinPx = Boolean(sizeInSvgUnits && viewBox && minDisplaySizePx != null && minDisplaySizePx > 0);
|
|
23202
23704
|
return (jsx("div", { style: containerStyle, children: items?.map((item, idx) => (jsx("div", { className: "charging-pile", style: {
|
|
23203
23705
|
position: 'absolute',
|
|
23204
|
-
width:
|
|
23205
|
-
|
|
23706
|
+
width: sizeInSvgUnits && viewBox
|
|
23707
|
+
? useMinPx
|
|
23708
|
+
? `max(${minDisplaySizePx}px, ${(item.size / Math.max(viewBox.width, 1)) * 100 * sizeScale}%)`
|
|
23709
|
+
: `${(item.size / Math.max(viewBox.width, 1)) * 100 * sizeScale}%`
|
|
23710
|
+
: item.size * sizeScale,
|
|
23711
|
+
height: sizeInSvgUnits && viewBox
|
|
23712
|
+
? useMinPx
|
|
23713
|
+
? `max(${minDisplaySizePx}px, ${(item.size / Math.max(viewBox.height, 1)) * 100 * sizeScale}%)`
|
|
23714
|
+
: `${(item.size / Math.max(viewBox.height, 1)) * 100 * sizeScale}%`
|
|
23715
|
+
: item.size * sizeScale,
|
|
23206
23716
|
left: `${item.leftPct}%`,
|
|
23207
23717
|
top: `${item.topPct}%`,
|
|
23208
23718
|
transform: `translate(-50%, -50%) rotate(${item.rotateDeg}deg)`,
|
|
@@ -23213,8 +23723,11 @@ const CharginPile = React__default.memo(({ viewBox, rotation = 0, sizeScale = 1,
|
|
|
23213
23723
|
|
|
23214
23724
|
const MAX_STATIC_SIZE = 640;
|
|
23215
23725
|
const MAX_STATIC_MAP_RETRY = 3;
|
|
23726
|
+
const STATIC_MAP_RETRY_COOLDOWN_MS = 5000;
|
|
23727
|
+
/** 放大镜内充电桩屏幕像素下限,避免过小导致通道与桩视觉错位 */
|
|
23728
|
+
const MAGNIFIER_CHARGING_PILE_MIN_DISPLAY_PX = 20;
|
|
23216
23729
|
const Magnifier = ({ visible, dragState, polygonPoints, fillColor = 'rgba(255, 122, 51, 0.1)', strokeColor = 'rgba(255, 122, 51, 1)', strokeWidth = 2, zoom = 3, size = 200, }) => {
|
|
23217
|
-
const { svgViewBox, mapRef, googleMapStaticApiKey, drag } = useCommonContext();
|
|
23730
|
+
const { svgViewBox, mapRef, googleMapStaticApiKey, googleMapStaticUrlSigningSecret, signGoogleStaticMapUrl: signGoogleStaticMapUrl$1, drag, } = useCommonContext();
|
|
23218
23731
|
const { svgElementDatas, svgRef } = useSvgEditContext();
|
|
23219
23732
|
const { editMapInfo } = useMapEditContext();
|
|
23220
23733
|
const [magnifierPosition, setMagnifierPosition] = useState({ x: 0, y: 0 });
|
|
@@ -23227,6 +23740,7 @@ const Magnifier = ({ visible, dragState, polygonPoints, fillColor = 'rgba(255, 1
|
|
|
23227
23740
|
const hasInitializedSideRef = useRef(false);
|
|
23228
23741
|
const [mapImageFrame, setMapImageFrame] = useState(null);
|
|
23229
23742
|
const [staticMapRetryCount, setStaticMapRetryCount] = useState(0);
|
|
23743
|
+
const [staticMapBlockedUntil, setStaticMapBlockedUntil] = useState(0);
|
|
23230
23744
|
// 获取当前正在拖拽的禁区 ID
|
|
23231
23745
|
const currentObstacleId = editMapInfo?.selectElement?.id;
|
|
23232
23746
|
// 计算当前 SVG 单位到屏幕像素的比例(用于实现真正 1:1 预览)
|
|
@@ -23277,6 +23791,11 @@ const Magnifier = ({ visible, dragState, polygonPoints, fillColor = 'rgba(255, 1
|
|
|
23277
23791
|
// });
|
|
23278
23792
|
setMapImageFrame(null);
|
|
23279
23793
|
setStaticMapRetryCount(0);
|
|
23794
|
+
setStaticMapBlockedUntil(0);
|
|
23795
|
+
return;
|
|
23796
|
+
}
|
|
23797
|
+
// 弱网连续失败时进入短暂冷却,避免重试风暴导致底图反复闪烁
|
|
23798
|
+
if (Date.now() < staticMapBlockedUntil) {
|
|
23280
23799
|
return;
|
|
23281
23800
|
}
|
|
23282
23801
|
const mapDiv = mapRef.getDiv?.();
|
|
@@ -23289,86 +23808,119 @@ const Magnifier = ({ visible, dragState, polygonPoints, fillColor = 'rgba(255, 1
|
|
|
23289
23808
|
// });
|
|
23290
23809
|
setMapImageFrame(null);
|
|
23291
23810
|
setStaticMapRetryCount(0);
|
|
23811
|
+
setStaticMapBlockedUntil(0);
|
|
23292
23812
|
return;
|
|
23293
23813
|
}
|
|
23294
23814
|
const mapRect = mapDiv.getBoundingClientRect();
|
|
23295
23815
|
const svgPoint = svgRef.createSVGPoint();
|
|
23296
23816
|
// 只要有 key 就尝试 static api;请求失败时不截图,直接回退灰色背景
|
|
23297
23817
|
if (googleMapStaticApiKey) {
|
|
23298
|
-
|
|
23299
|
-
|
|
23300
|
-
|
|
23301
|
-
|
|
23302
|
-
|
|
23303
|
-
|
|
23304
|
-
|
|
23305
|
-
|
|
23306
|
-
|
|
23307
|
-
|
|
23308
|
-
|
|
23309
|
-
|
|
23818
|
+
let cancelled = false;
|
|
23819
|
+
(async () => {
|
|
23820
|
+
// console.log('[Magnifier] Background source: static api viewport snapshot');
|
|
23821
|
+
const mapBounds = mapRef.getBounds?.();
|
|
23822
|
+
if (!mapBounds) {
|
|
23823
|
+
// console.log('[Magnifier] Skip static map effect: mapBounds missing');
|
|
23824
|
+
if (!cancelled) {
|
|
23825
|
+
setMapImageFrame(null);
|
|
23826
|
+
}
|
|
23827
|
+
return;
|
|
23828
|
+
}
|
|
23829
|
+
const screenToSvg = (screenX, screenY) => {
|
|
23830
|
+
svgPoint.x = screenX;
|
|
23831
|
+
svgPoint.y = screenY;
|
|
23832
|
+
const p = svgPoint.matrixTransform(inverseCtm);
|
|
23833
|
+
return { x: p.x, y: p.y };
|
|
23834
|
+
};
|
|
23835
|
+
// 使用当前地图视口矩形四角,将屏幕坐标映射到 SVG 坐标,确保底图与主视口对齐
|
|
23836
|
+
const topLeftSvg = screenToSvg(mapRect.left, mapRect.top);
|
|
23837
|
+
const topRightSvg = screenToSvg(mapRect.right, mapRect.top);
|
|
23838
|
+
const bottomLeftSvg = screenToSvg(mapRect.left, mapRect.bottom);
|
|
23839
|
+
if (!topLeftSvg || !topRightSvg || !bottomLeftSvg) {
|
|
23840
|
+
if (!cancelled) {
|
|
23841
|
+
setMapImageFrame(null);
|
|
23842
|
+
}
|
|
23843
|
+
return;
|
|
23844
|
+
}
|
|
23845
|
+
// 把当前主地图可视区域尺寸,按比例压到 Static API 允许的最大尺寸以内(受 Static API 640 上限约束)),同时不改变宽高比。
|
|
23846
|
+
const mapWidth = Math.max(1, Math.round(mapRect.width));
|
|
23847
|
+
const mapHeight = Math.max(1, Math.round(mapRect.height));
|
|
23848
|
+
const shrinkRatio = Math.min(1, MAX_STATIC_SIZE / Math.max(mapWidth, mapHeight));
|
|
23849
|
+
const staticWidth = Math.max(1, Math.round(mapWidth * shrinkRatio));
|
|
23850
|
+
const staticHeight = Math.max(1, Math.round(mapHeight * shrinkRatio));
|
|
23851
|
+
const center = mapBounds.getCenter();
|
|
23852
|
+
const currentZoom = mapRef.getZoom() || 20;
|
|
23853
|
+
// 尺寸缩小后同步补偿 zoom,保持与当前主图一致的地理覆盖范围
|
|
23854
|
+
const zoomCompensation = Math.log2(shrinkRatio);
|
|
23855
|
+
const adjustedZoom = currentZoom + zoomCompensation;
|
|
23856
|
+
const staticZoom = Math.max(0, Math.min(Math.floor(adjustedZoom), 22));
|
|
23857
|
+
const unsignedStaticMapUrl = `https://maps.googleapis.com/maps/api/staticmap?center=${center.lat()},${center.lng()}&zoom=${staticZoom}&size=${staticWidth}x${staticHeight}&maptype=satellite&key=${googleMapStaticApiKey}`;
|
|
23858
|
+
// 可选:对 Static Map URL 做 Google 数字签名(宿主回调优先,其次客户端 signing secret)
|
|
23859
|
+
let staticMapUrl = unsignedStaticMapUrl;
|
|
23860
|
+
try {
|
|
23861
|
+
if (signGoogleStaticMapUrl$1) {
|
|
23862
|
+
staticMapUrl = await Promise.resolve(signGoogleStaticMapUrl$1(unsignedStaticMapUrl));
|
|
23863
|
+
}
|
|
23864
|
+
else if (googleMapStaticUrlSigningSecret) {
|
|
23865
|
+
staticMapUrl = await signGoogleStaticMapUrl(unsignedStaticMapUrl, googleMapStaticUrlSigningSecret);
|
|
23866
|
+
console.log('[Magnifier] staticMapUrl', staticMapUrl);
|
|
23867
|
+
}
|
|
23868
|
+
}
|
|
23869
|
+
catch (error) {
|
|
23870
|
+
console.warn('[Magnifier] Static map URL signing failed', error);
|
|
23871
|
+
if (!cancelled) {
|
|
23872
|
+
setMapImageFrame(null);
|
|
23873
|
+
}
|
|
23874
|
+
return;
|
|
23875
|
+
}
|
|
23876
|
+
// 通过三角点推导静态图在 SVG 中的宽高和旋转角
|
|
23877
|
+
// Math.hypot(x, y) 等价于 sqrt(x*x + y*y),计算两点之间的距离
|
|
23878
|
+
const width = Math.hypot(topRightSvg.x - topLeftSvg.x, topRightSvg.y - topLeftSvg.y);
|
|
23879
|
+
const height = Math.hypot(bottomLeftSvg.x - topLeftSvg.x, bottomLeftSvg.y - topLeftSvg.y);
|
|
23880
|
+
// Math.atan2(y, x) 计算反正切值,返回 [-π, π] 弧度,*180/π 转换为角度
|
|
23881
|
+
const rotationDeg = (Math.atan2(topRightSvg.y - topLeftSvg.y, topRightSvg.x - topLeftSvg.x) * 180) / Math.PI;
|
|
23882
|
+
// Static API zoom 只能取整;额外补偿小数部分,避免底图视觉偏小
|
|
23883
|
+
// 在有旋转的坐标系里,做“以中心为锚点”的等比放大补偿。
|
|
23884
|
+
const fractionalZoomScale = Math.pow(2, adjustedZoom - staticZoom); //算出“还差多少缩放比例”。例子:adjustedZoom=19.6,staticZoom=19,差 0.6,比例是 2^0.6 ≈ 1.515。
|
|
23885
|
+
//把原始 width/height 乘这个比例,得到补偿后的尺寸。
|
|
23886
|
+
const compensatedWidth = width * fractionalZoomScale;
|
|
23887
|
+
const compensatedHeight = height * fractionalZoomScale;
|
|
23888
|
+
//尺寸变大后,多出来的宽/高的一半(要从两边均匀扩张)。
|
|
23889
|
+
const offsetAlongX = (compensatedWidth - width) / 2;
|
|
23890
|
+
const offsetAlongY = (compensatedHeight - height) / 2;
|
|
23891
|
+
//“右方向”单位向量(从左上指向右上),因为图可能旋转了,不能只按水平 x 轴算。
|
|
23892
|
+
const unitX = width > 0 ? (topRightSvg.x - topLeftSvg.x) / width : 1;
|
|
23893
|
+
const unitY = width > 0 ? (topRightSvg.y - topLeftSvg.y) / width : 0;
|
|
23894
|
+
//“下方向”单位向量(从左上指向左下),同理用于旋转场景。
|
|
23895
|
+
const unitDownX = height > 0 ? (bottomLeftSvg.x - topLeftSvg.x) / height : 0;
|
|
23896
|
+
const unitDownY = height > 0 ? (bottomLeftSvg.y - topLeftSvg.y) / height : 0;
|
|
23897
|
+
/**
|
|
23898
|
+
* 计算补偿后的新左上角:
|
|
23899
|
+
* 从原左上角沿“右方向”回退半个扩展量,再沿“下方向”回退半个扩展量。
|
|
23900
|
+
* 这样放大后仍以原区域中心为中心,不会放大后位置偏移。
|
|
23901
|
+
*/
|
|
23902
|
+
const compensatedTopLeftX = topLeftSvg.x - unitX * offsetAlongX - unitDownX * offsetAlongY;
|
|
23903
|
+
const compensatedTopLeftY = topLeftSvg.y - unitY * offsetAlongX - unitDownY * offsetAlongY;
|
|
23904
|
+
if (cancelled) {
|
|
23905
|
+
return;
|
|
23906
|
+
}
|
|
23907
|
+
setMapImageFrame({
|
|
23908
|
+
url: staticMapUrl,
|
|
23909
|
+
x: compensatedTopLeftX,
|
|
23910
|
+
y: compensatedTopLeftY,
|
|
23911
|
+
width: compensatedWidth,
|
|
23912
|
+
height: compensatedHeight,
|
|
23913
|
+
transform: `rotate(${rotationDeg} ${compensatedTopLeftX} ${compensatedTopLeftY})`,
|
|
23914
|
+
});
|
|
23915
|
+
})();
|
|
23916
|
+
return () => {
|
|
23917
|
+
cancelled = true;
|
|
23310
23918
|
};
|
|
23311
|
-
// 使用当前地图视口矩形四角,将屏幕坐标映射到 SVG 坐标,确保底图与主视口对齐
|
|
23312
|
-
const topLeftSvg = screenToSvg(mapRect.left, mapRect.top);
|
|
23313
|
-
const topRightSvg = screenToSvg(mapRect.right, mapRect.top);
|
|
23314
|
-
const bottomLeftSvg = screenToSvg(mapRect.left, mapRect.bottom);
|
|
23315
|
-
if (!topLeftSvg || !topRightSvg || !bottomLeftSvg) {
|
|
23316
|
-
setMapImageFrame(null);
|
|
23317
|
-
return;
|
|
23318
|
-
}
|
|
23319
|
-
// 把当前主地图可视区域尺寸,按比例压到 Static API 允许的最大尺寸以内(受 Static API 640 上限约束)),同时不改变宽高比。
|
|
23320
|
-
const mapWidth = Math.max(1, Math.round(mapRect.width));
|
|
23321
|
-
const mapHeight = Math.max(1, Math.round(mapRect.height));
|
|
23322
|
-
const shrinkRatio = Math.min(1, MAX_STATIC_SIZE / Math.max(mapWidth, mapHeight));
|
|
23323
|
-
const staticWidth = Math.max(1, Math.round(mapWidth * shrinkRatio));
|
|
23324
|
-
const staticHeight = Math.max(1, Math.round(mapHeight * shrinkRatio));
|
|
23325
|
-
const center = mapBounds.getCenter();
|
|
23326
|
-
const currentZoom = mapRef.getZoom() || 20;
|
|
23327
|
-
// 尺寸缩小后同步补偿 zoom,保持与当前主图一致的地理覆盖范围
|
|
23328
|
-
const zoomCompensation = Math.log2(shrinkRatio);
|
|
23329
|
-
const adjustedZoom = currentZoom + zoomCompensation;
|
|
23330
|
-
const staticZoom = Math.max(0, Math.min(Math.floor(adjustedZoom), 22));
|
|
23331
|
-
const staticMapUrl = `https://maps.googleapis.com/maps/api/staticmap?center=${center.lat()},${center.lng()}&zoom=${staticZoom}&size=${staticWidth}x${staticHeight}&maptype=satellite&key=${googleMapStaticApiKey}`;
|
|
23332
|
-
// 通过三角点推导静态图在 SVG 中的宽高和旋转角
|
|
23333
|
-
// Math.hypot(x, y) 等价于 sqrt(x*x + y*y),计算两点之间的距离
|
|
23334
|
-
const width = Math.hypot(topRightSvg.x - topLeftSvg.x, topRightSvg.y - topLeftSvg.y);
|
|
23335
|
-
const height = Math.hypot(bottomLeftSvg.x - topLeftSvg.x, bottomLeftSvg.y - topLeftSvg.y);
|
|
23336
|
-
// Math.atan2(y, x) 计算反正切值,返回 [-π, π] 弧度,*180/π 转换为角度
|
|
23337
|
-
const rotationDeg = (Math.atan2(topRightSvg.y - topLeftSvg.y, topRightSvg.x - topLeftSvg.x) * 180) / Math.PI;
|
|
23338
|
-
// Static API zoom 只能取整;额外补偿小数部分,避免底图视觉偏小
|
|
23339
|
-
// 在有旋转的坐标系里,做“以中心为锚点”的等比放大补偿。
|
|
23340
|
-
const fractionalZoomScale = Math.pow(2, adjustedZoom - staticZoom); //算出“还差多少缩放比例”。例子:adjustedZoom=19.6,staticZoom=19,差 0.6,比例是 2^0.6 ≈ 1.515。
|
|
23341
|
-
//把原始 width/height 乘这个比例,得到补偿后的尺寸。
|
|
23342
|
-
const compensatedWidth = width * fractionalZoomScale;
|
|
23343
|
-
const compensatedHeight = height * fractionalZoomScale;
|
|
23344
|
-
//尺寸变大后,多出来的宽/高的一半(要从两边均匀扩张)。
|
|
23345
|
-
const offsetAlongX = (compensatedWidth - width) / 2;
|
|
23346
|
-
const offsetAlongY = (compensatedHeight - height) / 2;
|
|
23347
|
-
//“右方向”单位向量(从左上指向右上),因为图可能旋转了,不能只按水平 x 轴算。
|
|
23348
|
-
const unitX = width > 0 ? (topRightSvg.x - topLeftSvg.x) / width : 1;
|
|
23349
|
-
const unitY = width > 0 ? (topRightSvg.y - topLeftSvg.y) / width : 0;
|
|
23350
|
-
//“下方向”单位向量(从左上指向左下),同理用于旋转场景。
|
|
23351
|
-
const unitDownX = height > 0 ? (bottomLeftSvg.x - topLeftSvg.x) / height : 0;
|
|
23352
|
-
const unitDownY = height > 0 ? (bottomLeftSvg.y - topLeftSvg.y) / height : 1;
|
|
23353
|
-
/**
|
|
23354
|
-
* 计算补偿后的新左上角:
|
|
23355
|
-
* 从原左上角沿“右方向”回退半个扩展量,再沿“下方向”回退半个扩展量。
|
|
23356
|
-
* 这样放大后仍以原区域中心为中心,不会放大后位置偏移。
|
|
23357
|
-
*/
|
|
23358
|
-
const compensatedTopLeftX = topLeftSvg.x - unitX * offsetAlongX - unitDownX * offsetAlongY;
|
|
23359
|
-
const compensatedTopLeftY = topLeftSvg.y - unitY * offsetAlongX - unitDownY * offsetAlongY;
|
|
23360
|
-
setMapImageFrame({
|
|
23361
|
-
url: staticMapUrl,
|
|
23362
|
-
x: compensatedTopLeftX,
|
|
23363
|
-
y: compensatedTopLeftY,
|
|
23364
|
-
width: compensatedWidth,
|
|
23365
|
-
height: compensatedHeight,
|
|
23366
|
-
transform: `rotate(${rotationDeg} ${compensatedTopLeftX} ${compensatedTopLeftY})`,
|
|
23367
|
-
});
|
|
23368
23919
|
}
|
|
23369
23920
|
else {
|
|
23370
23921
|
setMapImageFrame(null);
|
|
23371
23922
|
setStaticMapRetryCount(0);
|
|
23923
|
+
setStaticMapBlockedUntil(0);
|
|
23372
23924
|
// console.log('[Magnifier] Background source: gray fallback (no static api key)');
|
|
23373
23925
|
}
|
|
23374
23926
|
}, [
|
|
@@ -23383,7 +23935,10 @@ const Magnifier = ({ visible, dragState, polygonPoints, fillColor = 'rgba(255, 1
|
|
|
23383
23935
|
size,
|
|
23384
23936
|
zoom,
|
|
23385
23937
|
googleMapStaticApiKey,
|
|
23938
|
+
googleMapStaticUrlSigningSecret,
|
|
23939
|
+
signGoogleStaticMapUrl$1,
|
|
23386
23940
|
staticMapRetryCount,
|
|
23941
|
+
staticMapBlockedUntil,
|
|
23387
23942
|
]);
|
|
23388
23943
|
useLayoutEffect(() => {
|
|
23389
23944
|
if (!visible) {
|
|
@@ -23519,6 +24074,7 @@ const Magnifier = ({ visible, dragState, polygonPoints, fillColor = 'rgba(255, 1
|
|
|
23519
24074
|
}, children: [mapImageFrame && (jsx("image", { href: mapImageFrame.url, x: mapImageFrame.x, y: mapImageFrame.y, width: mapImageFrame.width, height: mapImageFrame.height, transform: mapImageFrame.transform, preserveAspectRatio: "none", style: { opacity: 1 }, onLoad: () => {
|
|
23520
24075
|
// console.log('✅ Map background loaded in magnifier');
|
|
23521
24076
|
setStaticMapRetryCount(0);
|
|
24077
|
+
setStaticMapBlockedUntil(0);
|
|
23522
24078
|
}, onError: (_e) => {
|
|
23523
24079
|
// console.error('❌ Failed to load map background in magnifier', {
|
|
23524
24080
|
// retryCount: staticMapRetryCount,
|
|
@@ -23529,7 +24085,8 @@ const Magnifier = ({ visible, dragState, polygonPoints, fillColor = 'rgba(255, 1
|
|
|
23529
24085
|
setStaticMapRetryCount((prev) => {
|
|
23530
24086
|
if (prev < MAX_STATIC_MAP_RETRY)
|
|
23531
24087
|
return prev + 1;
|
|
23532
|
-
|
|
24088
|
+
// 重试上限后进入冷却,不立刻清空已显示底图,避免弱网下闪烁
|
|
24089
|
+
setStaticMapBlockedUntil(Date.now() + STATIC_MAP_RETRY_COOLDOWN_MS);
|
|
23533
24090
|
return 0;
|
|
23534
24091
|
});
|
|
23535
24092
|
} }, `magnifier-static-map-${staticMapRetryCount}`)), svgElementDatas &&
|
|
@@ -23555,14 +24112,16 @@ const Magnifier = ({ visible, dragState, polygonPoints, fillColor = 'rgba(255, 1
|
|
|
23555
24112
|
? 3.6 / styleZoomFactor
|
|
23556
24113
|
: 3 / styleZoomFactor;
|
|
23557
24114
|
return (jsx("circle", { cx: x, cy: y, r: pointRadius, fill: pointFill, stroke: pointStroke, strokeWidth: pointStrokeWidth }, idx));
|
|
23558
|
-
})] })), dragState.dragType === 'new' && dragState.edgeInfo && (jsx(Fragment, { children: jsx("line", { x1: dragState.edgeInfo.startPoint.x, y1: dragState.edgeInfo.startPoint.y, x2: dragState.edgeInfo.endPoint.x, y2: dragState.edgeInfo.endPoint.y, stroke: "#999", strokeWidth: (strokeWidth * 3.2) / styleZoomFactor, strokeDasharray: `${6 / styleZoomFactor},${6 / styleZoomFactor}` }) }))] }), jsx(CharginPile, { viewBox: magnifierViewBox || null, rotation: actureRotate,
|
|
24115
|
+
})] })), dragState.dragType === 'new' && dragState.edgeInfo && (jsx(Fragment, { children: jsx("line", { x1: dragState.edgeInfo.startPoint.x, y1: dragState.edgeInfo.startPoint.y, x2: dragState.edgeInfo.endPoint.x, y2: dragState.edgeInfo.endPoint.y, stroke: "#999", strokeWidth: (strokeWidth * 3.2) / styleZoomFactor, strokeDasharray: `${6 / styleZoomFactor},${6 / styleZoomFactor}` }) }))] }), jsx(CharginPile, { viewBox: magnifierViewBox || null, rotation: actureRotate, sizeInSvgUnits: true, minDisplaySizePx: MAGNIFIER_CHARGING_PILE_MIN_DISPLAY_PX,
|
|
24116
|
+
// 充电桩最多保持原始尺寸,不允许放大超过 1 倍
|
|
24117
|
+
sizeScale: Math.min(1, 1 / styleZoomFactor) })] }));
|
|
23559
24118
|
// 使用 Portal 渲染到 body,避免层级问题
|
|
23560
24119
|
return ReactDOM.createPortal(magnifierContent, document.body);
|
|
23561
24120
|
};
|
|
23562
24121
|
|
|
23563
|
-
var css_248z$
|
|
23564
|
-
var styles$
|
|
23565
|
-
styleInject(css_248z$
|
|
24122
|
+
var css_248z$8 = ".index-module_pointerCursor__Ee6pr {\n cursor: pointer;\n}\n\n.index-module_polygonPath__PynOn {\n pointer-events: stroke;\n cursor: pointer;\n}\n\n.index-module_dragging__xSFdO {\n cursor: grabbing;\n}\n\n.index-module_notCreate__bFnkV {\n cursor: no-drop;\n}";
|
|
24123
|
+
var styles$8 = {"pointerCursor":"index-module_pointerCursor__Ee6pr","polygonPath":"index-module_polygonPath__PynOn","dragging":"index-module_dragging__xSFdO","notCreate":"index-module_notCreate__bFnkV"};
|
|
24124
|
+
styleInject(css_248z$8);
|
|
23566
24125
|
|
|
23567
24126
|
/**
|
|
23568
24127
|
* 几何计算工具函数
|
|
@@ -23985,28 +24544,102 @@ const useCheckElement = () => {
|
|
|
23985
24544
|
};
|
|
23986
24545
|
};
|
|
23987
24546
|
|
|
24547
|
+
var css_248z$7 = ".Tooltip-module_tooltip__Wexlk {\n position: fixed;\n background-color: #fff;\n border-radius: 4px;\n height: 20px;\n line-height: 20px;\n font-size: 12px;\n color: #211F1F;\n padding: 0 4px;\n white-space: nowrap;\n z-index: 9999;\n cursor: pointer;\n user-select: none;\n}\n.Tooltip-module_tooltip__Wexlk.Tooltip-module_tooltipHover__Tfqme {\n height: 30px;\n padding: 5px 12px;\n}\n.Tooltip-module_tooltip__Wexlk.Tooltip-module_tooltipHover__Tfqme:hover {\n background-color: #FFEFE6;\n color: #FF5A00;\n}";
|
|
24548
|
+
var styles$7 = {"tooltip":"Tooltip-module_tooltip__Wexlk","tooltipHover":"Tooltip-module_tooltipHover__Tfqme"};
|
|
24549
|
+
styleInject(css_248z$7);
|
|
24550
|
+
|
|
24551
|
+
var classnames = {exports: {}};
|
|
24552
|
+
|
|
24553
|
+
/*!
|
|
24554
|
+
Copyright (c) 2018 Jed Watson.
|
|
24555
|
+
Licensed under the MIT License (MIT), see
|
|
24556
|
+
http://jedwatson.github.io/classnames
|
|
24557
|
+
*/
|
|
24558
|
+
|
|
24559
|
+
(function (module) {
|
|
24560
|
+
/* global define */
|
|
24561
|
+
|
|
24562
|
+
(function () {
|
|
24563
|
+
|
|
24564
|
+
var hasOwn = {}.hasOwnProperty;
|
|
24565
|
+
|
|
24566
|
+
function classNames () {
|
|
24567
|
+
var classes = '';
|
|
24568
|
+
|
|
24569
|
+
for (var i = 0; i < arguments.length; i++) {
|
|
24570
|
+
var arg = arguments[i];
|
|
24571
|
+
if (arg) {
|
|
24572
|
+
classes = appendClass(classes, parseValue(arg));
|
|
24573
|
+
}
|
|
24574
|
+
}
|
|
24575
|
+
|
|
24576
|
+
return classes;
|
|
24577
|
+
}
|
|
24578
|
+
|
|
24579
|
+
function parseValue (arg) {
|
|
24580
|
+
if (typeof arg === 'string' || typeof arg === 'number') {
|
|
24581
|
+
return arg;
|
|
24582
|
+
}
|
|
24583
|
+
|
|
24584
|
+
if (typeof arg !== 'object') {
|
|
24585
|
+
return '';
|
|
24586
|
+
}
|
|
24587
|
+
|
|
24588
|
+
if (Array.isArray(arg)) {
|
|
24589
|
+
return classNames.apply(null, arg);
|
|
24590
|
+
}
|
|
24591
|
+
|
|
24592
|
+
if (arg.toString !== Object.prototype.toString && !arg.toString.toString().includes('[native code]')) {
|
|
24593
|
+
return arg.toString();
|
|
24594
|
+
}
|
|
24595
|
+
|
|
24596
|
+
var classes = '';
|
|
24597
|
+
|
|
24598
|
+
for (var key in arg) {
|
|
24599
|
+
if (hasOwn.call(arg, key) && arg[key]) {
|
|
24600
|
+
classes = appendClass(classes, key);
|
|
24601
|
+
}
|
|
24602
|
+
}
|
|
24603
|
+
|
|
24604
|
+
return classes;
|
|
24605
|
+
}
|
|
24606
|
+
|
|
24607
|
+
function appendClass (value, newClass) {
|
|
24608
|
+
if (!newClass) {
|
|
24609
|
+
return value;
|
|
24610
|
+
}
|
|
24611
|
+
|
|
24612
|
+
if (value) {
|
|
24613
|
+
return value + ' ' + newClass;
|
|
24614
|
+
}
|
|
24615
|
+
|
|
24616
|
+
return value + newClass;
|
|
24617
|
+
}
|
|
24618
|
+
|
|
24619
|
+
if (module.exports) {
|
|
24620
|
+
classNames.default = classNames;
|
|
24621
|
+
module.exports = classNames;
|
|
24622
|
+
} else {
|
|
24623
|
+
window.classNames = classNames;
|
|
24624
|
+
}
|
|
24625
|
+
}());
|
|
24626
|
+
} (classnames));
|
|
24627
|
+
|
|
24628
|
+
var classnamesExports = classnames.exports;
|
|
24629
|
+
var classNames = /*@__PURE__*/getDefaultExportFromCjs(classnamesExports);
|
|
24630
|
+
|
|
23988
24631
|
const Tooltip = ({ open, x, y, text, style, onClick }) => {
|
|
23989
24632
|
if (!open)
|
|
23990
24633
|
return null;
|
|
23991
24634
|
const tooltipStyle = {
|
|
23992
|
-
position: 'fixed',
|
|
23993
24635
|
left: x,
|
|
23994
24636
|
top: y,
|
|
23995
|
-
backgroundColor: '#fff',
|
|
23996
|
-
borderRadius: '4px',
|
|
23997
|
-
height: '20px',
|
|
23998
|
-
lineHeight: '16px',
|
|
23999
|
-
fontSize: '12px',
|
|
24000
|
-
color: '#211F1F',
|
|
24001
|
-
padding: '2px 4px',
|
|
24002
|
-
whiteSpace: 'nowrap',
|
|
24003
|
-
zIndex: 9999,
|
|
24004
|
-
cursor: 'pointer',
|
|
24005
|
-
userSelect: 'none',
|
|
24006
24637
|
...style,
|
|
24007
24638
|
};
|
|
24008
24639
|
// 使用 createPortal 挂载到 body
|
|
24009
|
-
return createPortal(jsx("div", {
|
|
24640
|
+
return createPortal(jsx("div", { className: classNames(styles$7.tooltip, {
|
|
24641
|
+
[styles$7.tooltipHover]: !!onClick,
|
|
24642
|
+
}), style: tooltipStyle, onClick: (e) => {
|
|
24010
24643
|
e.preventDefault();
|
|
24011
24644
|
e.stopPropagation();
|
|
24012
24645
|
onClick?.();
|
|
@@ -24104,7 +24737,7 @@ const createPathData = (points) => {
|
|
|
24104
24737
|
};
|
|
24105
24738
|
const PolygonElement = ({ canSelect = false, isHover = false, points, fillColor = 'rgba(0, 0, 0, 0.1)', hoverFillColor = '', fillOpacity = 1, strokeColor = '#000', strokeWidth = 2, strokeOpacity = 1, createMode = false, showPoints = false, onPointClick, completed = false, mousePos = null, editMode = false, onCoordinatesChange, onPathClick, onPolygonClick, onVertexDelete, draggable = true, // 新增参数,如果未指定则根据createMode和editMode自动判断
|
|
24106
24739
|
}) => {
|
|
24107
|
-
const { overlayScale, unitType, showStraddleBoundaryBorder, platform, onH5FirstSelectObstaclePoint } = useCommonContext();
|
|
24740
|
+
const { overlayScale, unitType, showStraddleBoundaryBorder, platform, locale, onH5FirstSelectObstaclePoint, } = useCommonContext();
|
|
24108
24741
|
const { onHandleEvent } = useMapEditContext();
|
|
24109
24742
|
const { svgRef } = useSvgEditContext();
|
|
24110
24743
|
const [dragState, setDragState] = useState({
|
|
@@ -24152,11 +24785,12 @@ const PolygonElement = ({ canSelect = false, isHover = false, points, fillColor
|
|
|
24152
24785
|
const [tooltipIndex, setTooltipIndex] = useState(null);
|
|
24153
24786
|
const [tooltipPos, setTooltipPos] = useState({ x: 0, y: 0 });
|
|
24154
24787
|
const [completeTooltipOpen, setCompleteTooltipOpen] = useState(false);
|
|
24788
|
+
const [moveTooltipOpen, setMoveTooltipOpen] = useState(false);
|
|
24155
24789
|
const updateTooltipPos = (target) => {
|
|
24156
24790
|
const rect = target.getBoundingClientRect();
|
|
24157
24791
|
setTooltipPos({
|
|
24158
24792
|
x: rect.left + rect.width + 4,
|
|
24159
|
-
y: rect.top - 10
|
|
24793
|
+
y: rect.top - 10,
|
|
24160
24794
|
});
|
|
24161
24795
|
};
|
|
24162
24796
|
// 处理顶点点击(仅透出点击;不触发删除)
|
|
@@ -24291,6 +24925,13 @@ const PolygonElement = ({ canSelect = false, isHover = false, points, fillColor
|
|
|
24291
24925
|
edgeInfo: edgeInfo,
|
|
24292
24926
|
dragType: 'new',
|
|
24293
24927
|
});
|
|
24928
|
+
// 边上新点:insertIndex 可能与当前 selectedVertexIndex 相同(例如原选中 2,在 1-2 间插入后仍写 2),
|
|
24929
|
+
// React 会跳过 setState,依赖 selectedVertexIndex 的 useLayoutEffect 不会跑,故用微任务直接震一次
|
|
24930
|
+
if (platform === PlatformType.H5 && editMapInfo?.elementType === DataType.OBSTACLE) {
|
|
24931
|
+
void Promise.resolve().then(() => {
|
|
24932
|
+
onH5FirstSelectObstaclePoint?.();
|
|
24933
|
+
});
|
|
24934
|
+
}
|
|
24294
24935
|
// 将新创建的点设为选中态,便于后续直接拖拽
|
|
24295
24936
|
setSelectedVertexIndex(insertIndex);
|
|
24296
24937
|
}, [
|
|
@@ -24301,6 +24942,9 @@ const PolygonElement = ({ canSelect = false, isHover = false, points, fillColor
|
|
|
24301
24942
|
coordinates,
|
|
24302
24943
|
getSVGCoordinates,
|
|
24303
24944
|
calculatePerpendicularFoot$1,
|
|
24945
|
+
platform,
|
|
24946
|
+
editMapInfo?.elementType,
|
|
24947
|
+
onH5FirstSelectObstaclePoint,
|
|
24304
24948
|
]);
|
|
24305
24949
|
// H5平台长按边线创建新顶点
|
|
24306
24950
|
const handleEdgeLongPressStart = useCallback((e, edgeStartIndex) => {
|
|
@@ -24345,6 +24989,8 @@ const PolygonElement = ({ canSelect = false, isHover = false, points, fillColor
|
|
|
24345
24989
|
const vertexLongPressTimerRef = useRef(null);
|
|
24346
24990
|
const isVertexLongPressedRef = useRef(false);
|
|
24347
24991
|
const longPressVertexIndexRef = useRef(-1);
|
|
24992
|
+
/** 长按选中顶点且 index 发生变化时置 true,在 useLayoutEffect 里再触发震动,避免早于视觉选中态 */
|
|
24993
|
+
const pendingH5ObstacleSelectHapticRef = useRef(false);
|
|
24348
24994
|
// 长按拖拽判定相关
|
|
24349
24995
|
const touchStartPosRef = useRef(null);
|
|
24350
24996
|
const hasStartedDragViaLongPressRef = useRef(false);
|
|
@@ -24365,13 +25011,21 @@ const PolygonElement = ({ canSelect = false, isHover = false, points, fillColor
|
|
|
24365
25011
|
vertexLongPressTimerRef.current = window.setTimeout(() => {
|
|
24366
25012
|
isVertexLongPressedRef.current = true;
|
|
24367
25013
|
setSelectedVertexIndex((prev) => {
|
|
24368
|
-
|
|
24369
|
-
onH5FirstSelectObstaclePoint?.();
|
|
24370
|
-
}
|
|
25014
|
+
pendingH5ObstacleSelectHapticRef.current = prev !== index;
|
|
24371
25015
|
return index;
|
|
24372
25016
|
});
|
|
24373
25017
|
}, 1000);
|
|
24374
|
-
}, [editMode, createMode, completed, dragState.isDragging
|
|
25018
|
+
}, [editMode, createMode, completed, dragState.isDragging]);
|
|
25019
|
+
useLayoutEffect(() => {
|
|
25020
|
+
if (!pendingH5ObstacleSelectHapticRef.current)
|
|
25021
|
+
return;
|
|
25022
|
+
pendingH5ObstacleSelectHapticRef.current = false;
|
|
25023
|
+
if (platform !== PlatformType.H5)
|
|
25024
|
+
return;
|
|
25025
|
+
if (editMapInfo?.elementType !== DataType.OBSTACLE)
|
|
25026
|
+
return;
|
|
25027
|
+
onH5FirstSelectObstaclePoint?.();
|
|
25028
|
+
}, [selectedVertexIndex, platform, editMapInfo?.elementType, onH5FirstSelectObstaclePoint]);
|
|
24375
25029
|
const handleVertexLongPressEnd = useCallback(() => {
|
|
24376
25030
|
if (vertexLongPressTimerRef.current !== null) {
|
|
24377
25031
|
window.clearTimeout(vertexLongPressTimerRef.current);
|
|
@@ -24400,8 +25054,8 @@ const PolygonElement = ({ canSelect = false, isHover = false, points, fillColor
|
|
|
24400
25054
|
}, [dragState.currentPosition, checkCanNotCreateAtPosition, coordinates]);
|
|
24401
25055
|
const generateVertexClassName = useMemo(() => {
|
|
24402
25056
|
if (!dragState.isDragging)
|
|
24403
|
-
return styles$
|
|
24404
|
-
return showNotCreateCursor ? styles$
|
|
25057
|
+
return styles$8.pointerCursor;
|
|
25058
|
+
return showNotCreateCursor ? styles$8.notCreate : styles$8.dragging;
|
|
24405
25059
|
}, [dragState.isDragging, showNotCreateCursor]);
|
|
24406
25060
|
// 使用 useRef 来存储 requestAnimationFrame ID
|
|
24407
25061
|
const rafIdRef = useRef(null);
|
|
@@ -24692,7 +25346,7 @@ const PolygonElement = ({ canSelect = false, isHover = false, points, fillColor
|
|
|
24692
25346
|
WebkitTouchCallout: 'none',
|
|
24693
25347
|
touchAction: 'none',
|
|
24694
25348
|
} })] }));
|
|
24695
|
-
})()] })), renderCoordinates.length >= 2 && (jsx("polygon", { className: canSelect ? styles$
|
|
25349
|
+
})()] })), renderCoordinates.length >= 2 && (jsx("polygon", { className: canSelect ? styles$8.pointerCursor : '', points: polygonPoints, fill: isHover ? hoverFillColor || fillColor : fillColor, fillOpacity: fillOpacity, stroke: "none" // 边框透明
|
|
24696
25350
|
, onClick: onPolygonClick })), jsx("g", { children: renderCoordinates.length >= 2 &&
|
|
24697
25351
|
pathSegments.map((segment, index) => {
|
|
24698
25352
|
if (segment.points.length < 2)
|
|
@@ -24700,14 +25354,14 @@ const PolygonElement = ({ canSelect = false, isHover = false, points, fillColor
|
|
|
24700
25354
|
const pathData = createPathData(segment.points);
|
|
24701
25355
|
const isDash = segment.type === 1;
|
|
24702
25356
|
if (isDash && showStraddleBoundaryBorder) {
|
|
24703
|
-
return (jsx(DashPath, { className: canSelect ? styles$
|
|
25357
|
+
return (jsx(DashPath, { className: canSelect ? styles$8.polygonPath : '', points: segment.points, stroke: strokeColor, strokeWidth: isHover ? strokeWidth + 1 : strokeWidth, strokeOpacity: strokeOpacity }, index));
|
|
24704
25358
|
}
|
|
24705
25359
|
return (jsxs(Fragment, { children: [(editMode || createMode) && (jsx("path", { d: pathData, fill: "none", stroke: '#fff', strokeWidth: strokeWidth * 2, strokeOpacity: strokeOpacity, strokeLinecap: "round", strokeLinejoin: "round", vectorEffect: "non-scaling-stroke", style: {
|
|
24706
25360
|
userSelect: 'none',
|
|
24707
25361
|
WebkitUserSelect: 'none',
|
|
24708
25362
|
WebkitTouchCallout: 'none',
|
|
24709
25363
|
touchAction: 'none',
|
|
24710
|
-
} }, 'warp' + index)), jsx("path", { d: pathData, fill: "none", stroke: strokeColor, strokeWidth: isHover ? strokeWidth + 1 : strokeWidth, strokeOpacity: strokeOpacity, strokeLinecap: "round", strokeLinejoin: "round", className: canSelect ? styles$
|
|
25364
|
+
} }, 'warp' + index)), jsx("path", { d: pathData, fill: "none", stroke: strokeColor, strokeWidth: isHover ? strokeWidth + 1 : strokeWidth, strokeOpacity: strokeOpacity, strokeLinecap: "round", strokeLinejoin: "round", className: canSelect ? styles$8.polygonPath : '', vectorEffect: "non-scaling-stroke", onClick: onPathClick, style: {
|
|
24711
25365
|
userSelect: 'none',
|
|
24712
25366
|
WebkitUserSelect: 'none',
|
|
24713
25367
|
WebkitTouchCallout: 'none',
|
|
@@ -24718,14 +25372,29 @@ const PolygonElement = ({ canSelect = false, isHover = false, points, fillColor
|
|
|
24718
25372
|
renderCoordinates.map((coord, index) => {
|
|
24719
25373
|
const nextCoord = renderCoordinates[(index + 1) % renderCoordinates.length];
|
|
24720
25374
|
const isDashPath = coord[2] === 1;
|
|
24721
|
-
|
|
24722
|
-
|
|
24723
|
-
|
|
24724
|
-
|
|
24725
|
-
|
|
24726
|
-
|
|
24727
|
-
|
|
24728
|
-
|
|
25375
|
+
const edgeLineCommon = {
|
|
25376
|
+
x1: coord[0],
|
|
25377
|
+
y1: coord[1],
|
|
25378
|
+
x2: nextCoord[0],
|
|
25379
|
+
y2: nextCoord[1],
|
|
25380
|
+
};
|
|
25381
|
+
const edgeLineStyle = {
|
|
25382
|
+
userSelect: 'none',
|
|
25383
|
+
WebkitUserSelect: 'none',
|
|
25384
|
+
WebkitTouchCallout: 'none',
|
|
25385
|
+
touchAction: 'none',
|
|
25386
|
+
};
|
|
25387
|
+
// H5 实线:透明粗线仅作长按热区,描边只看上方 path;H5 虚线/Web:沿用透明或主题色边线 + 对应事件(合并为一个 line,避免两段重复)
|
|
25388
|
+
const isH5SolidHitOnly = platform === PlatformType.H5 && ((editMode && !isDashPath) || createMode);
|
|
25389
|
+
const edgeStroke = isH5SolidHitOnly || isDashPath ? 'transparent' : strokeColor;
|
|
25390
|
+
const edgeStrokeWidth = isH5SolidHitOnly
|
|
25391
|
+
? strokeWidth * 12
|
|
25392
|
+
: platform === PlatformType.H5
|
|
25393
|
+
? strokeWidth * 2
|
|
25394
|
+
: strokeWidth;
|
|
25395
|
+
return (jsx("line", { ...edgeLineCommon, stroke: edgeStroke, strokeWidth: edgeStrokeWidth, ...(isH5SolidHitOnly ? { strokeLinecap: 'round' } : {}), className: editMode ? styles$8.pointerCursor : '', style: edgeLineStyle, vectorEffect: "non-scaling-stroke", ...(platform === PlatformType.H5
|
|
25396
|
+
? {
|
|
25397
|
+
onTouchStart: createReactEventHandler((e) => handleEdgeLongPressStart(e, index)),
|
|
24729
25398
|
onTouchEnd: createReactEventHandler(() => handleEdgeLongPressEnd()),
|
|
24730
25399
|
onTouchCancel: createReactEventHandler(() => handleEdgeLongPressEnd()),
|
|
24731
25400
|
}
|
|
@@ -24793,7 +25462,7 @@ const PolygonElement = ({ canSelect = false, isHover = false, points, fillColor
|
|
|
24793
25462
|
isDoubleClickingRef.current = false;
|
|
24794
25463
|
}, 300);
|
|
24795
25464
|
// 打开删除确认 Tooltip(仅在可编辑时)
|
|
24796
|
-
if (editMode || (createMode && completed)) {
|
|
25465
|
+
if ((editMode || (createMode && completed)) && renderCoordinates.length > 3) {
|
|
24797
25466
|
updateTooltipPos(e.target);
|
|
24798
25467
|
setTooltipIndex(idx);
|
|
24799
25468
|
}
|
|
@@ -24870,13 +25539,25 @@ const PolygonElement = ({ canSelect = false, isHover = false, points, fillColor
|
|
|
24870
25539
|
}
|
|
24871
25540
|
}
|
|
24872
25541
|
},
|
|
24873
|
-
onMouseEnter:
|
|
24874
|
-
|
|
24875
|
-
|
|
24876
|
-
|
|
24877
|
-
|
|
24878
|
-
|
|
24879
|
-
|
|
25542
|
+
onMouseEnter: (e) => {
|
|
25543
|
+
if (canComplete) {
|
|
25544
|
+
setCompleteTooltipOpen(true);
|
|
25545
|
+
}
|
|
25546
|
+
if (editMode || (createMode && completed)) {
|
|
25547
|
+
setMoveTooltipOpen(true);
|
|
25548
|
+
}
|
|
25549
|
+
if (tooltipIndex === null) {
|
|
25550
|
+
updateTooltipPos(e.target);
|
|
25551
|
+
}
|
|
25552
|
+
},
|
|
25553
|
+
onMouseLeave: () => {
|
|
25554
|
+
if (canComplete) {
|
|
25555
|
+
setCompleteTooltipOpen(false);
|
|
25556
|
+
}
|
|
25557
|
+
if (editMode || (createMode && completed)) {
|
|
25558
|
+
setMoveTooltipOpen(false);
|
|
25559
|
+
}
|
|
25560
|
+
},
|
|
24880
25561
|
}) }), platform === PlatformType.H5 && tooltipIndex === idx && coordinates.length > 3 && (jsxs("g", { transform: `translate(${coord[0] + 4 * overlayScale * 2}, ${coord[1] - 4 * overlayScale * 2}) scale(${overlayScale * 2})`, onClick: (e) => {
|
|
24881
25562
|
e.preventDefault();
|
|
24882
25563
|
e.stopPropagation();
|
|
@@ -24900,12 +25581,16 @@ const PolygonElement = ({ canSelect = false, isHover = false, points, fillColor
|
|
|
24900
25581
|
// >
|
|
24901
25582
|
// </rect>
|
|
24902
25583
|
)] }, `vertex-${idx}`));
|
|
24903
|
-
}), platform === PlatformType.WEB && tooltipIndex !== null && (jsx(Tooltip, { open: true, x: tooltipPos.x, y: tooltipPos.y, text:
|
|
24904
|
-
if (onVertexDelete &&
|
|
25584
|
+
}), platform === PlatformType.WEB && tooltipIndex !== null && (jsx(Tooltip, { open: true, x: tooltipPos.x, y: tooltipPos.y, text: locale?.['map.renderer.remove'] || 'Remove', onClick: () => {
|
|
25585
|
+
if (onVertexDelete &&
|
|
25586
|
+
(editMode || (createMode && completed)) &&
|
|
25587
|
+
coordinates.length > 3) {
|
|
24905
25588
|
onVertexDelete(renderCoordinates?.length - 1 - tooltipIndex);
|
|
24906
25589
|
}
|
|
24907
25590
|
setTooltipIndex(null);
|
|
24908
|
-
} })), platform === PlatformType.WEB && completeTooltipOpen && (jsx(Tooltip, { open: true, x: tooltipPos.x, y: tooltipPos.y, text:
|
|
25591
|
+
} })), platform === PlatformType.WEB && completeTooltipOpen && (jsx(Tooltip, { open: true, x: tooltipPos.x, y: tooltipPos.y, text: locale?.['map.renderer.tips.clickFinish'] || 'Click to finish' })), platform === PlatformType.WEB && moveTooltipOpen && tooltipIndex === null && (jsx(Tooltip, { open: true, x: tooltipPos.x, y: tooltipPos.y, text: renderCoordinates.length > 3 ?
|
|
25592
|
+
(locale?.['map.renderer.tips.dragAndRemove'] || 'Drag to change, double click to remove') :
|
|
25593
|
+
(locale?.['map.renderer.tips.drag'] || 'Drag to change') }))] }));
|
|
24909
25594
|
};
|
|
24910
25595
|
|
|
24911
25596
|
const MowPartitionContext = createContext({
|
|
@@ -24985,7 +25670,8 @@ const BoundaryElement = ({ data, isHover }) => {
|
|
|
24985
25670
|
};
|
|
24986
25671
|
|
|
24987
25672
|
const ChannelElement = ({ data }) => {
|
|
24988
|
-
const {
|
|
25673
|
+
const { channelClipPathId } = useCommonContext();
|
|
25674
|
+
const clipPathUrl = `url(#${channelClipPathId})`;
|
|
24989
25675
|
const points = data.points;
|
|
24990
25676
|
const style = data.style;
|
|
24991
25677
|
let pathData = `M ${points[0][0]} ${points[0][1]}`;
|
|
@@ -24995,7 +25681,7 @@ const ChannelElement = ({ data }) => {
|
|
|
24995
25681
|
const topLineWidth = dp2px(style.lineWidth).toString();
|
|
24996
25682
|
const bottomLineWidth = dp2px(style.bottomLineWidth).toString();
|
|
24997
25683
|
const dashLength = dp2px(style.lineWidth);
|
|
24998
|
-
return (jsxs("g", { id: data.id.toString(), children: [jsx("path", { d: pathData, fill: "none", stroke: "#8498A9", strokeWidth: bottomLineWidth, strokeLinejoin: "round", opacity: style.opacity || 1, style: { clipPath:
|
|
25684
|
+
return (jsxs("g", { id: data.id.toString(), children: [jsx("path", { d: pathData, fill: "none", stroke: "#8498A9", strokeWidth: bottomLineWidth, strokeLinejoin: "round", opacity: style.opacity || 1, style: { clipPath: clipPathUrl } }), jsx("path", { d: pathData, fill: "none", stroke: "#FFFFFF", strokeWidth: topLineWidth, strokeLinejoin: "round", strokeDasharray: `${dashLength * 2} ${dashLength}`, opacity: style.opacity || 1, style: { clipPath: clipPathUrl } })] }));
|
|
24999
25685
|
};
|
|
25000
25686
|
|
|
25001
25687
|
/**
|
|
@@ -25121,10 +25807,11 @@ const ObstacleElement = ({ data, isHover }) => {
|
|
|
25121
25807
|
const strokeWidth = useMemo(() => {
|
|
25122
25808
|
// 如果是h5选中的情况下,禁区只有在start模式下才需要加粗
|
|
25123
25809
|
// 其他模式展示的是其他的样式
|
|
25124
|
-
if (platform === PlatformType.H5 &&
|
|
25810
|
+
if ((platform === PlatformType.H5 &&
|
|
25125
25811
|
editMapInfo?.selectElement?.id === data.id &&
|
|
25126
|
-
editMapInfo.mobileMode === MobileEditMode.START)
|
|
25127
|
-
|
|
25812
|
+
editMapInfo.mobileMode === MobileEditMode.START) ||
|
|
25813
|
+
editMapInfo.mobileMode === MobileEditMode.EDIT) {
|
|
25814
|
+
return dp2px((style.lineWidth || 1) * 2.2);
|
|
25128
25815
|
// return dp2px(((style.lineWidth as number) || 3) * 3);
|
|
25129
25816
|
}
|
|
25130
25817
|
return dp2px(style.lineWidth || 3);
|
|
@@ -25159,7 +25846,10 @@ const ObstacleElement = ({ data, isHover }) => {
|
|
|
25159
25846
|
}
|
|
25160
25847
|
return editMapInfo?.selectElement?.id === data.id;
|
|
25161
25848
|
}, [editMapInfo, data, platform]);
|
|
25162
|
-
|
|
25849
|
+
const disabled = useMemo(() => {
|
|
25850
|
+
return data?.status === 0 && !editMode;
|
|
25851
|
+
}, [data?.status, editMode, editMapInfo?.createMode]);
|
|
25852
|
+
return (jsx(PolygonElement, { canSelect: Boolean(!editMapInfo?.elementType) && editMap, points: currentPoints, fillColor: disabled ? style.disabledFillColor : style.fillColor, hoverFillColor: style.hoverFillColor, fillOpacity: style.fillOpacity, strokeColor: disabled ? style.disabledLineColor : style.lineColor, strokeWidth: strokeWidth, editMode: editMode, isHover: isHover, showPoints: editMapInfo?.selectElement?.id === data.id, onPathClick: onPathClick, onPolygonClick: () => {
|
|
25163
25853
|
onPathClick();
|
|
25164
25854
|
}, onVertexDelete: (vertexIndex) => handleCreateVertexDelete(vertexIndex), onCoordinatesChange: (coordinates) => {
|
|
25165
25855
|
if (platform === PlatformType.H5 && editMapInfo.mobileMode === MobileEditMode.CREATE) {
|
|
@@ -25760,6 +26450,7 @@ const VisionOffTransformWrapper = ({ data, isSelected = false, isHover = false,
|
|
|
25760
26450
|
// 使用自定义hook管理所有变换逻辑
|
|
25761
26451
|
const { currentPoints, isDragging, isRotating, isScaling, dimensions, initializePoints, calculateSelectionBoxPoints, handleMouseDown, handleRotateStart, handleScaleStart, handleMouseMove, handleMouseUp, containerRef, // 从hook获取containerRef
|
|
25762
26452
|
} = useVisionOffTransform(dataPoints);
|
|
26453
|
+
const [hoverType, setHoverType] = useState(null);
|
|
25763
26454
|
const visionOffData = useMemo(() => {
|
|
25764
26455
|
return {
|
|
25765
26456
|
...data,
|
|
@@ -25920,7 +26611,7 @@ const VisionOffTransformWrapper = ({ data, isSelected = false, isHover = false,
|
|
|
25920
26611
|
}
|
|
25921
26612
|
: {
|
|
25922
26613
|
onMouseDown: handleCancel,
|
|
25923
|
-
}), transform: `translate(${selectionBoxPoints[0].x}, ${selectionBoxPoints[0].y})`, children: jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale})`, children: jsx(SvgTransformDelete, {}) }) })), jsx("g", { className: "control-point rotate-point", ...(platform === PlatformType.H5
|
|
26614
|
+
}), transform: `translate(${selectionBoxPoints[0].x}, ${selectionBoxPoints[0].y})`, children: jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale * (hoverType === HoverType.Delete ? 1.15 : 1)})`, onMouseEnter: () => setHoverType(HoverType.Delete), onMouseLeave: () => setHoverType(null), children: jsx(SvgTransformDelete, {}) }) })), jsx("g", { className: "control-point rotate-point", ...(platform === PlatformType.H5
|
|
25924
26615
|
? {
|
|
25925
26616
|
onTouchStart: createReactEventHandler((e) => {
|
|
25926
26617
|
handleRotateStartWithDisabled(e);
|
|
@@ -25928,7 +26619,7 @@ const VisionOffTransformWrapper = ({ data, isSelected = false, isHover = false,
|
|
|
25928
26619
|
}
|
|
25929
26620
|
: {
|
|
25930
26621
|
onMouseDown: handleRotateStartWithDisabled,
|
|
25931
|
-
}), transform: `translate(${selectionBoxPoints[3].x}, ${selectionBoxPoints[3].y})`, children: jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale})`, children: jsx(SvgTransformRotate, {}) }) }), jsx("g", { className: "control-point move-point", ...(platform === PlatformType.H5
|
|
26622
|
+
}), transform: `translate(${selectionBoxPoints[3].x}, ${selectionBoxPoints[3].y})`, children: jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale * (hoverType === HoverType.Rotate ? 1.15 : 1)})`, onMouseEnter: () => setHoverType(HoverType.Rotate), onMouseLeave: () => setHoverType(null), children: jsx(SvgTransformRotate, {}) }) }), jsx("g", { className: "control-point move-point", ...(platform === PlatformType.H5
|
|
25932
26623
|
? {
|
|
25933
26624
|
onTouchStart: createReactEventHandler((e) => {
|
|
25934
26625
|
handleMouseDownWithDisabled(e);
|
|
@@ -25936,7 +26627,7 @@ const VisionOffTransformWrapper = ({ data, isSelected = false, isHover = false,
|
|
|
25936
26627
|
}
|
|
25937
26628
|
: {
|
|
25938
26629
|
onMouseDown: handleMouseDownWithDisabled,
|
|
25939
|
-
}), transform: `translate(${selectionBoxPoints[1].x}, ${selectionBoxPoints[1].y})`, children: jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale})`, children: jsx(SvgTransformTranslate, {}) }) }), jsx("g", { className: "control-point scale-point", ...(platform === PlatformType.H5
|
|
26630
|
+
}), transform: `translate(${selectionBoxPoints[1].x}, ${selectionBoxPoints[1].y})`, children: jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale * (hoverType === HoverType.Translate ? 1.15 : 1)})`, onMouseEnter: () => setHoverType(HoverType.Translate), onMouseLeave: () => setHoverType(null), children: jsx(SvgTransformTranslate, {}) }) }), jsx("g", { className: "control-point scale-point", ...(platform === PlatformType.H5
|
|
25940
26631
|
? {
|
|
25941
26632
|
onTouchStart: createReactEventHandler((e) => {
|
|
25942
26633
|
handleScaleStartWithDisabled(e);
|
|
@@ -25944,7 +26635,7 @@ const VisionOffTransformWrapper = ({ data, isSelected = false, isHover = false,
|
|
|
25944
26635
|
}
|
|
25945
26636
|
: {
|
|
25946
26637
|
onMouseDown: handleScaleStartWithDisabled,
|
|
25947
|
-
}), transform: `translate(${selectionBoxPoints[2].x}, ${selectionBoxPoints[2].y})`, children: jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale})`, children: jsx(SvgTransformScale, {}) }) })] }));
|
|
26638
|
+
}), transform: `translate(${selectionBoxPoints[2].x}, ${selectionBoxPoints[2].y})`, children: jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale * (hoverType === HoverType.Scale ? 1.15 : 1)})`, onMouseEnter: () => setHoverType(HoverType.Scale), onMouseLeave: () => setHoverType(null), children: jsx(SvgTransformScale, {}) }) })] }));
|
|
25948
26639
|
})()] }));
|
|
25949
26640
|
};
|
|
25950
26641
|
|
|
@@ -26489,9 +27180,9 @@ var SvgDisabledTranslate = function SvgDisabledTranslate(props) {
|
|
|
26489
27180
|
})))));
|
|
26490
27181
|
};
|
|
26491
27182
|
|
|
26492
|
-
var css_248z$
|
|
26493
|
-
var styles$
|
|
26494
|
-
styleInject(css_248z$
|
|
27183
|
+
var css_248z$6 = ".index-module_doodleHover__jIZHV path {\n fill: #00B3A1;\n}";
|
|
27184
|
+
var styles$6 = {"doodleHover":"index-module_doodleHover__jIZHV"};
|
|
27185
|
+
styleInject(css_248z$6);
|
|
26495
27186
|
|
|
26496
27187
|
const DoodleTransform = ({ data, isSelected: _isSelected, isHover, onSelect }) => {
|
|
26497
27188
|
const { editMapInfo } = useMapEditContext();
|
|
@@ -26550,7 +27241,7 @@ const DoodleTransform = ({ data, isSelected: _isSelected, isHover, onSelect }) =
|
|
|
26550
27241
|
return (jsx("g", { children: jsx("g", { style: {
|
|
26551
27242
|
cursor: canSelect ? 'pointer' : 'inherit',
|
|
26552
27243
|
pointerEvents: canSelect ? 'auto' : 'none',
|
|
26553
|
-
}, className: isHover ? styles$
|
|
27244
|
+
}, className: isHover ? styles$6.doodleHover : '', transform: transform, opacity: data?.style?.opacity || 1, dangerouslySetInnerHTML: { __html: svgString }, onClick: onDoodleClick }) }));
|
|
26554
27245
|
}
|
|
26555
27246
|
}
|
|
26556
27247
|
catch (error) {
|
|
@@ -26560,12 +27251,13 @@ const DoodleTransform = ({ data, isSelected: _isSelected, isHover, onSelect }) =
|
|
|
26560
27251
|
};
|
|
26561
27252
|
|
|
26562
27253
|
const DoodleTransformWrapper = ({ data, isSelected = false, isSelectedWithoutOperation = false, isHover = false, onSelect, onCancel, onTransformChange, disabled = false, className = '', minScale, onMinScaleReached, onDragMove, onDragEnd, showInfo = true, onClickInfo, }) => {
|
|
26563
|
-
const { platform, overlayScale, unitType } = useCommonContext();
|
|
27254
|
+
const { platform, overlayScale, unitType, locale } = useCommonContext();
|
|
26564
27255
|
// 使用自定义hook管理所有变换逻辑
|
|
26565
27256
|
const { currentCenter, currentScale, currentDirection, isDragging, isRotating, isScaling, initializeTransform, calculateSelectionBoxPoints, handleMouseDown, handleRotateStart, handleScaleStart, handleMouseMove, handleMouseUp, containerRef, dimensions, } = useDoodleTransform(data, onTransformChange, {
|
|
26566
27257
|
minScale,
|
|
26567
27258
|
onMinScaleReached,
|
|
26568
27259
|
});
|
|
27260
|
+
const [hoverType, setHoverType] = useState(null);
|
|
26569
27261
|
// 创建变换后的数据对象 - 需要将转换后的坐标转换回原始格式
|
|
26570
27262
|
const transformedData = useMemo(() => {
|
|
26571
27263
|
// 将转换后的坐标转换回原始格式(除以 SCALE_FACTOR 并翻转 Y 轴)
|
|
@@ -26686,7 +27378,7 @@ const DoodleTransformWrapper = ({ data, isSelected = false, isSelectedWithoutOpe
|
|
|
26686
27378
|
const infoBoxWidth = 300 * overlayScale; // 信息框宽度
|
|
26687
27379
|
const infoBoxHeight = 40 * overlayScale; // 信息框高度
|
|
26688
27380
|
const infoBoxX = centerX - infoBoxWidth / 2; // 居中对齐
|
|
26689
|
-
const circleScale = platform === PlatformType.H5 ? 1.5 * overlayScale :
|
|
27381
|
+
const circleScale = platform === PlatformType.H5 ? 1.5 * overlayScale : 3 * overlayScale;
|
|
26690
27382
|
const disacledScale = circleScale * 1.2;
|
|
26691
27383
|
return (jsxs(Fragment, { children: [jsx("polygon", { points: selectionBoxPoints.map((point) => `${point.x},${point.y}`).join(' '), fill: "none", stroke: "#B2B4B9", strokeWidth: "2", strokeDasharray: "5,5", vectorEffect: "non-scaling-stroke", ...(platform === PlatformType.H5
|
|
26692
27384
|
? {
|
|
@@ -26716,7 +27408,7 @@ const DoodleTransformWrapper = ({ data, isSelected = false, isSelectedWithoutOpe
|
|
|
26716
27408
|
justifyContent: 'center',
|
|
26717
27409
|
gap: '10px',
|
|
26718
27410
|
color: '#325069',
|
|
26719
|
-
}, children: jsxs("div", { children: [
|
|
27411
|
+
}, children: jsxs("div", { children: [locale?.['map.renderer.remainingTime'] || 'Remaining time', ": ", remainingTime] }) }) }) }))] }));
|
|
26720
27412
|
};
|
|
26721
27413
|
useEffect(() => {
|
|
26722
27414
|
const isMove = isDragging || isRotating || isScaling;
|
|
@@ -26768,7 +27460,7 @@ const DoodleTransformWrapper = ({ data, isSelected = false, isSelectedWithoutOpe
|
|
|
26768
27460
|
const infoBoxWidth = 300 * overlayScale; // 信息框宽度
|
|
26769
27461
|
const infoBoxHeight = 100 * overlayScale; // 信息框高度
|
|
26770
27462
|
const infoBoxX = centerX - infoBoxWidth / 2; // 居中对齐
|
|
26771
|
-
const scale = platform === PlatformType.H5 ? 1.8 * overlayScale :
|
|
27463
|
+
const scale = platform === PlatformType.H5 ? 1.8 * overlayScale : 3 * overlayScale;
|
|
26772
27464
|
const offsetLeft = 10 * scale;
|
|
26773
27465
|
return (jsxs(Fragment, { children: [showInfo && (jsx("foreignObject", { x: infoBoxX, y: infoBoxY, width: infoBoxWidth, height: infoBoxHeight, style: { textAlign: 'center' }, onClick: () => {
|
|
26774
27466
|
onClickInfo?.();
|
|
@@ -26789,7 +27481,7 @@ const DoodleTransformWrapper = ({ data, isSelected = false, isSelectedWithoutOpe
|
|
|
26789
27481
|
alignItems: 'center',
|
|
26790
27482
|
justifyContent: 'center',
|
|
26791
27483
|
gap: `${10 * overlayScale}px`,
|
|
26792
|
-
}, children: jsxs("div", { children: [
|
|
27484
|
+
}, children: jsxs("div", { children: [locale?.['map.renderer.activeTime'] || 'Active Time', ": ", activeTime, " >"] }) })] }) })), jsx("polygon", { points: selectionBoxPoints.map((point) => `${point.x},${point.y}`).join(' '), fill: "none", stroke: "#9EA6BA", strokeWidth: "2", strokeDasharray: "5,5", vectorEffect: "non-scaling-stroke", style: { cursor: 'move' }, ...(platform === PlatformType.H5
|
|
26793
27485
|
? {
|
|
26794
27486
|
onTouchStart: createReactEventHandler((e) => {
|
|
26795
27487
|
handleMouseDownWithDisabled(e);
|
|
@@ -26805,7 +27497,7 @@ const DoodleTransformWrapper = ({ data, isSelected = false, isSelectedWithoutOpe
|
|
|
26805
27497
|
}
|
|
26806
27498
|
: {
|
|
26807
27499
|
onMouseDown: handleCancel,
|
|
26808
|
-
}), transform: `translate(${selectionBoxPoints[0].x}, ${selectionBoxPoints[0].y})`, children: jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale})`, children: jsx(SvgTransformDelete, {}) }) })), jsx("g", { className: "control-point rotate-point", ...(platform === PlatformType.H5
|
|
27500
|
+
}), transform: `translate(${selectionBoxPoints[0].x}, ${selectionBoxPoints[0].y})`, children: jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale * (hoverType === HoverType.Delete ? 1.15 : 1)})`, onMouseEnter: () => setHoverType(HoverType.Delete), onMouseLeave: () => setHoverType(null), children: jsx(SvgTransformDelete, {}) }) })), jsx("g", { className: "control-point rotate-point", ...(platform === PlatformType.H5
|
|
26809
27501
|
? {
|
|
26810
27502
|
onTouchStart: createReactEventHandler((e) => {
|
|
26811
27503
|
handleRotateStartWithDisabled(e);
|
|
@@ -26813,7 +27505,7 @@ const DoodleTransformWrapper = ({ data, isSelected = false, isSelectedWithoutOpe
|
|
|
26813
27505
|
}
|
|
26814
27506
|
: {
|
|
26815
27507
|
onMouseDown: handleRotateStartWithDisabled,
|
|
26816
|
-
}), transform: `translate(${selectionBoxPoints[3].x}, ${selectionBoxPoints[3].y})`, children: jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale})`, children: jsx(SvgTransformRotate, {}) }) }), jsx("g", { className: "control-point move-point", ...(platform === PlatformType.H5
|
|
27508
|
+
}), transform: `translate(${selectionBoxPoints[3].x}, ${selectionBoxPoints[3].y})`, children: jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale * (hoverType === HoverType.Rotate ? 1.15 : 1)})`, onMouseEnter: () => setHoverType(HoverType.Rotate), onMouseLeave: () => setHoverType(null), children: jsx(SvgTransformRotate, {}) }) }), jsx("g", { className: "control-point move-point", ...(platform === PlatformType.H5
|
|
26817
27509
|
? {
|
|
26818
27510
|
onTouchStart: createReactEventHandler((e) => {
|
|
26819
27511
|
handleMouseDownWithDisabled(e);
|
|
@@ -26821,7 +27513,7 @@ const DoodleTransformWrapper = ({ data, isSelected = false, isSelectedWithoutOpe
|
|
|
26821
27513
|
}
|
|
26822
27514
|
: {
|
|
26823
27515
|
onMouseDown: handleMouseDownWithDisabled,
|
|
26824
|
-
}), transform: `translate(${selectionBoxPoints[1].x}, ${selectionBoxPoints[1].y})`, children: jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale})`, children: jsx(SvgTransformTranslate, {}) }) }), jsx("g", { className: "control-point scale-point", ...(platform === PlatformType.H5
|
|
27516
|
+
}), transform: `translate(${selectionBoxPoints[1].x}, ${selectionBoxPoints[1].y})`, children: jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale * (hoverType === HoverType.Translate ? 1.15 : 1)})`, onMouseEnter: () => setHoverType(HoverType.Translate), onMouseLeave: () => setHoverType(null), children: jsx(SvgTransformTranslate, {}) }) }), jsx("g", { className: "control-point scale-point", ...(platform === PlatformType.H5
|
|
26825
27517
|
? {
|
|
26826
27518
|
onTouchStart: createReactEventHandler((e) => {
|
|
26827
27519
|
handleScaleStartWithDisabled(e);
|
|
@@ -26829,7 +27521,7 @@ const DoodleTransformWrapper = ({ data, isSelected = false, isSelectedWithoutOpe
|
|
|
26829
27521
|
}
|
|
26830
27522
|
: {
|
|
26831
27523
|
onMouseDown: handleScaleStartWithDisabled,
|
|
26832
|
-
}), transform: `translate(${selectionBoxPoints[2].x}, ${selectionBoxPoints[2].y})`, children: jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale})`, children: jsx(SvgTransformScale, {}) }) })] }));
|
|
27524
|
+
}), transform: `translate(${selectionBoxPoints[2].x}, ${selectionBoxPoints[2].y})`, children: jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale * (hoverType === HoverType.Scale ? 1.15 : 1)})`, onMouseEnter: () => setHoverType(HoverType.Scale), onMouseLeave: () => setHoverType(null), children: jsx(SvgTransformScale, {}) }) })] }));
|
|
26833
27525
|
})()] }));
|
|
26834
27526
|
};
|
|
26835
27527
|
|
|
@@ -27387,9 +28079,9 @@ const SvgMapComponent = forwardRef(({ editMap, pathData, mapConfig = {}, mowPart
|
|
|
27387
28079
|
// 设置显示名称以便调试
|
|
27388
28080
|
SvgMapComponent.displayName = 'SvgMapComponent';
|
|
27389
28081
|
|
|
27390
|
-
var css_248z$
|
|
27391
|
-
var styles$
|
|
27392
|
-
styleInject(css_248z$
|
|
28082
|
+
var css_248z$5 = ".index-module_mowerPosition__yLpIU {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n right: 0;\n width: 100%;\n height: 100%;\n}";
|
|
28083
|
+
var styles$5 = {"mowerPosition":"index-module_mowerPosition__yLpIU"};
|
|
28084
|
+
styleInject(css_248z$5);
|
|
27393
28085
|
|
|
27394
28086
|
const usePosition = ({ mowerPositionData, viewBox, modelType, hasEdger, mapData, realTimeData, onMowingPositionChange, }) => {
|
|
27395
28087
|
// DOM 元素引用,用于直接操作
|
|
@@ -27548,10 +28240,10 @@ const usePosition = ({ mowerPositionData, viewBox, modelType, hasEdger, mapData,
|
|
|
27548
28240
|
}
|
|
27549
28241
|
else {
|
|
27550
28242
|
// 检查位置是否有效
|
|
27551
|
-
const positionOutOfRange = isOutOfRange(mowerPositionData);
|
|
28243
|
+
// const positionOutOfRange = isOutOfRange(mowerPositionData);
|
|
27552
28244
|
const positionInvalid = isInvalidPosition(mowerPositionData);
|
|
27553
28245
|
const isStandby = mowerPositionData.vehicleState === RobotStatus.STANDBY;
|
|
27554
|
-
if (
|
|
28246
|
+
if (positionInvalid || isOffLine) {
|
|
27555
28247
|
// 位置无效时,尝试使用上次位置或充电桩位置
|
|
27556
28248
|
updatePositionByLastPosition(mowerPositionData);
|
|
27557
28249
|
}
|
|
@@ -27615,7 +28307,7 @@ const MowerPosition = React__default.memo(({ editMap, mowerPositionData, viewBox
|
|
|
27615
28307
|
zIndex: 1000,
|
|
27616
28308
|
opacity: isHighlight ? SvgMapOpacity.HIGHLIGHT : SvgMapOpacity.UN_HIGHLIGHT,
|
|
27617
28309
|
}), [isHighlight]);
|
|
27618
|
-
return (jsx("div", { className: styles$
|
|
28310
|
+
return (jsx("div", { className: styles$5.mowerPosition, style: containerStyle, children: jsx("div", { ref: elementRef, className: "mower-position", style: {
|
|
27619
28311
|
position: 'absolute',
|
|
27620
28312
|
width: 30,
|
|
27621
28313
|
height: 30,
|
|
@@ -27627,11 +28319,12 @@ const MowerPosition = React__default.memo(({ editMap, mowerPositionData, viewBox
|
|
|
27627
28319
|
}, children: jsx("img", { src: mowerImage, style: { width: '100%', height: '100%', objectFit: 'contain' } }) }) }));
|
|
27628
28320
|
});
|
|
27629
28321
|
|
|
27630
|
-
var css_248z$
|
|
27631
|
-
var styles$
|
|
27632
|
-
styleInject(css_248z$
|
|
28322
|
+
var css_248z$4 = ".index-module_container__7IatW {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n pointer-events: none;\n}\n\n.index-module_boundaryLabel__GDHFn {\n position: absolute;\n background-color: rgba(30, 30, 31, 0.6);\n color: rgb(255, 255, 255);\n padding: 6px;\n border-radius: 12px;\n font-size: 12px;\n font-weight: bold;\n max-width: 220px;\n pointer-events: auto;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);\n cursor: pointer;\n user-select: none;\n white-space: nowrap;\n}\n.index-module_boundaryLabel__GDHFn .index-module_isolatedIcon__1pRXN {\n position: absolute;\n width: 24px;\n height: 24px;\n top: -30px;\n left: 50%;\n transform: translateX(-50%);\n}\n\n.index-module_base__qy2SG {\n font-size: 14px;\n}\n\n.index-module_extended__0TMAt {\n margin-top: 6px;\n font-size: 11px;\n opacity: 0.9;\n border-top: 1px solid rgba(255, 255, 255, 0.2);\n padding-top: 6px;\n}";
|
|
28323
|
+
var styles$4 = {"container":"index-module_container__7IatW","boundaryLabel":"index-module_boundaryLabel__GDHFn","isolatedIcon":"index-module_isolatedIcon__1pRXN","base":"index-module_base__qy2SG","extended":"index-module_extended__0TMAt"};
|
|
28324
|
+
styleInject(css_248z$4);
|
|
27633
28325
|
|
|
27634
28326
|
const useBoundaryLabels = ({ mapData, pathData, unitType, viewBox, mowPartitionData, realTimeData = [], onlyRead = false, }) => {
|
|
28327
|
+
const { locale } = useCommonContext();
|
|
27635
28328
|
const [processStateIsMowing, updateProcessStateIsMowing] = useState(false);
|
|
27636
28329
|
// 处理地图分区边界
|
|
27637
28330
|
useMemo(() => {
|
|
@@ -27671,6 +28364,9 @@ const useBoundaryLabels = ({ mapData, pathData, unitType, viewBox, mowPartitionD
|
|
|
27671
28364
|
return generateBoundaryData(mapData, newPathData);
|
|
27672
28365
|
}
|
|
27673
28366
|
}, [mapData, pathData, realTimeData]);
|
|
28367
|
+
const coverageStr = useMemo(() => {
|
|
28368
|
+
return locale?.['map.renderer.coverage'] || 'Coverage';
|
|
28369
|
+
}, [locale]);
|
|
27674
28370
|
const items = useMemo(() => {
|
|
27675
28371
|
// console.log('boundartItme->', viewBox, mapData)
|
|
27676
28372
|
if (!mapData || !viewBox)
|
|
@@ -27680,7 +28376,7 @@ const useBoundaryLabels = ({ mapData, pathData, unitType, viewBox, mowPartitionD
|
|
|
27680
28376
|
const results = [];
|
|
27681
28377
|
for (const boundary of boundaryData) {
|
|
27682
28378
|
// 多边形重心
|
|
27683
|
-
const centroid = computePolygonCentroid(boundary.points);
|
|
28379
|
+
const centroid = computePolygonCentroid$1(boundary.points);
|
|
27684
28380
|
if (!centroid)
|
|
27685
28381
|
continue;
|
|
27686
28382
|
const relX = (centroid.x - viewBox.x) / viewBox.width;
|
|
@@ -27691,9 +28387,9 @@ const useBoundaryLabels = ({ mapData, pathData, unitType, viewBox, mowPartitionD
|
|
|
27691
28387
|
const baseText = onlyRead ? `${boundary.name}` : `${boundary.name} (${progress})`;
|
|
27692
28388
|
const totalArea = convertAreaByUnits(boundary.area || 0, unitType);
|
|
27693
28389
|
const finishedArea = convertAreaByUnits(boundary.finishedArea || 0, unitType);
|
|
27694
|
-
const coverageText =
|
|
28390
|
+
const coverageText = `${coverageStr}: ${finishedArea.value}/${totalArea.value}`;
|
|
27695
28391
|
const isMowing = mowingIds.includes(boundary.id);
|
|
27696
|
-
const dateText = formatBoundaryDateText(isMowing ? Date.now() / 1000 : boundary.endTime || 0);
|
|
28392
|
+
const dateText = formatBoundaryDateText(isMowing ? Date.now() / 1000 : boundary.endTime || 0, locale);
|
|
27697
28393
|
results.push({
|
|
27698
28394
|
id: boundary.id,
|
|
27699
28395
|
name: boundary.name,
|
|
@@ -27717,11 +28413,13 @@ const useBoundaryLabels = ({ mapData, pathData, unitType, viewBox, mowPartitionD
|
|
|
27717
28413
|
mowPartitionData,
|
|
27718
28414
|
boundaryData,
|
|
27719
28415
|
onlyRead,
|
|
28416
|
+
coverageStr,
|
|
28417
|
+
locale,
|
|
27720
28418
|
]);
|
|
27721
28419
|
const containerZIndex = 900;
|
|
27722
28420
|
return { items, containerZIndex };
|
|
27723
28421
|
};
|
|
27724
|
-
function computePolygonCentroid(points) {
|
|
28422
|
+
function computePolygonCentroid$1(points) {
|
|
27725
28423
|
if (!points || points.length < 3)
|
|
27726
28424
|
return null;
|
|
27727
28425
|
const valid = points.filter((p) => p.length >= 2);
|
|
@@ -27794,7 +28492,7 @@ const BoundaryLabels = React__default.memo(({ editMap, mapData, pathData, unitTy
|
|
|
27794
28492
|
display: editMap ? 'none' : 'block',
|
|
27795
28493
|
opacity: isHighlight ? SvgMapOpacity.HIGHLIGHT : SvgMapOpacity.UN_HIGHLIGHT,
|
|
27796
28494
|
}), [containerZIndex, expandedId, editMap, isHighlight]);
|
|
27797
|
-
return (jsx("div", { className: styles$
|
|
28495
|
+
return (jsx("div", { className: styles$4.container, style: containerStyle, children: items.map((item) => (jsxs("div", { className: `${styles$4.boundaryLabel} boundary-label`, "data-boundary-id": item.id, style: {
|
|
27798
28496
|
transform: `translate(-50%, -50%) rotate(${-rotation}deg)`,
|
|
27799
28497
|
left: `${item.leftPct}%`,
|
|
27800
28498
|
top: `${item.topPct}%`,
|
|
@@ -27805,12 +28503,12 @@ const BoundaryLabels = React__default.memo(({ editMap, mapData, pathData, unitTy
|
|
|
27805
28503
|
if (onlyRead)
|
|
27806
28504
|
return;
|
|
27807
28505
|
handleClick(item.id);
|
|
27808
|
-
}, children: [jsx("div", { className: styles$
|
|
28506
|
+
}, children: [jsx("div", { className: styles$4.base, children: item.baseText }), jsxs("div", { className: styles$4.extended, style: { display: expandedId === item.id ? 'block' : 'none' }, children: [jsx("div", { style: { marginBottom: 3, fontWeight: 'bold' }, children: item.coverageText }), jsx("div", { children: item.dateText })] }), item.isIsolated && (jsx("div", { className: styles$4.isolatedIcon, dangerouslySetInnerHTML: { __html: ISOLATED_BOUNDARY_SVG } }))] }, item.id))) }));
|
|
27809
28507
|
});
|
|
27810
28508
|
|
|
27811
|
-
var css_248z$
|
|
27812
|
-
var styles$
|
|
27813
|
-
styleInject(css_248z$
|
|
28509
|
+
var css_248z$3 = ".index-module_container__gB52e {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n pointer-events: none;\n}\n\n.index-module_item__zHoZP {\n position: absolute;\n pointer-events: auto;\n}\n\n.index-module_icon__CS56A {\n position: relative;\n width: 24px;\n height: 24px;\n cursor: pointer;\n transition: transform 0.2s ease;\n}\n\n.index-module_tooltip__NMCmR {\n position: absolute;\n bottom: 40px;\n left: 0;\n background-color: rgb(247, 251, 255);\n color: rgb(65, 93, 116);\n padding: 10px;\n border-radius: 16px;\n font-size: 14px;\n text-align: left;\n}";
|
|
28510
|
+
var styles$3 = {"container":"index-module_container__gB52e","item":"index-module_item__zHoZP","icon":"index-module_icon__CS56A","tooltip":"index-module_tooltip__NMCmR"};
|
|
28511
|
+
styleInject(css_248z$3);
|
|
27814
28512
|
|
|
27815
28513
|
var antennaOneOnline = "data:image/png;base64,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";
|
|
27816
28514
|
|
|
@@ -27822,6 +28520,7 @@ var antennaTwoOffline = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgC
|
|
|
27822
28520
|
|
|
27823
28521
|
const useAntennaInfo = (data = [], options = {}) => {
|
|
27824
28522
|
const { viewBox } = options;
|
|
28523
|
+
const { locale } = useCommonContext();
|
|
27825
28524
|
const items = useMemo(() => {
|
|
27826
28525
|
if (!data || data.length === 0 || !viewBox)
|
|
27827
28526
|
return [];
|
|
@@ -27852,8 +28551,8 @@ const useAntennaInfo = (data = [], options = {}) => {
|
|
|
27852
28551
|
isOnline: antenna1.status === 1,
|
|
27853
28552
|
imageSrc: getImage(1, antenna1.status === 1),
|
|
27854
28553
|
tooltip: {
|
|
27855
|
-
title: 'Primary Antenna',
|
|
27856
|
-
statusText: antenna1.status === 1 ? 'Online' : 'Offline',
|
|
28554
|
+
title: locale?.['map.renderer.primaryAntenna'] || 'Primary Antenna',
|
|
28555
|
+
statusText: antenna1.status === 1 ? (locale?.['map.renderer.online'] || 'Online') : (locale?.['map.renderer.online'] || 'Offline'),
|
|
27857
28556
|
minWidth: 130,
|
|
27858
28557
|
},
|
|
27859
28558
|
});
|
|
@@ -27892,15 +28591,15 @@ const useAntennaInfo = (data = [], options = {}) => {
|
|
|
27892
28591
|
isOnline: antenna2.status === 1,
|
|
27893
28592
|
imageSrc: getImage(2, antenna2.status === 1),
|
|
27894
28593
|
tooltip: {
|
|
27895
|
-
title: 'Signal Enhancement Antenna',
|
|
27896
|
-
statusText: antenna2.status === 1 ? 'Online' : 'Offline',
|
|
27897
|
-
syncText: antenna2.status === 1 ? '
|
|
28594
|
+
title: locale?.['map.renderer.signalAntenna'] || 'Signal Enhancement Antenna',
|
|
28595
|
+
statusText: antenna2.status === 1 ? (locale?.['map.renderer.online'] || 'Online') : (locale?.['map.renderer.offline'] || 'Offline'),
|
|
28596
|
+
syncText: antenna2.status === 1 ? `, ${locale?.['map.renderer.synced'] || 'Synced'}` : `, ${locale?.['map.renderer.toBeSynced'] || 'To be synced'}`,
|
|
27898
28597
|
minWidth: 195,
|
|
27899
28598
|
},
|
|
27900
28599
|
});
|
|
27901
28600
|
}
|
|
27902
28601
|
return results;
|
|
27903
|
-
}, [data, viewBox?.x, viewBox?.y, viewBox?.width, viewBox?.height]);
|
|
28602
|
+
}, [data, viewBox?.x, viewBox?.y, viewBox?.width, viewBox?.height, locale]);
|
|
27904
28603
|
// default stacking order (slightly higher than boundary labels' 900)
|
|
27905
28604
|
const containerZIndex = 950;
|
|
27906
28605
|
return { items, containerZIndex };
|
|
@@ -27928,13 +28627,13 @@ const Antennas = React__default.memo(({ editMap, antennaConfig = [], viewBox, ro
|
|
|
27928
28627
|
document.addEventListener('pointerdown', handleOutside);
|
|
27929
28628
|
return () => document.removeEventListener('pointerdown', handleOutside);
|
|
27930
28629
|
}, []);
|
|
27931
|
-
return (jsx("div", { className: styles$
|
|
28630
|
+
return (jsx("div", { className: styles$3.container, style: containerStyle, children: items.map((item) => (jsxs("div", { className: `antenna-container-item ${styles$3.item}`, style: {
|
|
27932
28631
|
left: `${item.leftPct}%`,
|
|
27933
28632
|
top: `${item.topPct}%`,
|
|
27934
28633
|
transform: `translate(calc(-50% + ${item.offsetX || 0}px), calc(-50% + ${item.offsetY || 0}px)) rotate(${-rotation}deg)`,
|
|
27935
28634
|
zIndex: openType === item.type ? 10000 : containerZIndex,
|
|
27936
28635
|
pointerEvents: onlyRead ? 'none' : 'auto',
|
|
27937
|
-
}, children: [jsx("div", { className: `antenna vector-antenna antenna-${item.type} ${item.isOnline ? 'antenna-online' : 'antenna-offline'} ${styles$
|
|
28636
|
+
}, children: [jsx("div", { className: `antenna vector-antenna antenna-${item.type} ${item.isOnline ? 'antenna-online' : 'antenna-offline'} ${styles$3.icon}`, style: {}, onClick: (e) => {
|
|
27938
28637
|
e.stopPropagation();
|
|
27939
28638
|
if (onlyRead)
|
|
27940
28639
|
return;
|
|
@@ -27949,95 +28648,15 @@ const Antennas = React__default.memo(({ editMap, antennaConfig = [], viewBox, ro
|
|
|
27949
28648
|
return;
|
|
27950
28649
|
e.currentTarget.style.transform =
|
|
27951
28650
|
'scale(1)';
|
|
27952
|
-
}, children: jsx("img", { src: item.imageSrc, style: { width: '100%', height: '100%', objectFit: 'contain', opacity: 1 } }) }), jsxs("div", { className: styles$
|
|
28651
|
+
}, children: jsx("img", { src: item.imageSrc, style: { width: '100%', height: '100%', objectFit: 'contain', opacity: 1 } }) }), jsxs("div", { className: styles$3.tooltip, style: {
|
|
27953
28652
|
display: openType === item.type ? 'block' : 'none',
|
|
27954
28653
|
minWidth: item.tooltip.minWidth,
|
|
27955
28654
|
}, children: [jsx("div", { style: { marginBottom: 4, fontWeight: 600 }, children: item.tooltip.title }), jsxs("div", { style: { color: 'rgba(65, 93, 116, 1)', fontWeight: 400 }, children: [item.tooltip.statusText, item.tooltip.syncText || ''] })] })] }, `antenna-${item.type}`))) }));
|
|
27956
28655
|
});
|
|
27957
28656
|
|
|
27958
|
-
var css_248z$
|
|
27959
|
-
var styles$
|
|
27960
|
-
styleInject(css_248z$
|
|
27961
|
-
|
|
27962
|
-
var classnames = {exports: {}};
|
|
27963
|
-
|
|
27964
|
-
/*!
|
|
27965
|
-
Copyright (c) 2018 Jed Watson.
|
|
27966
|
-
Licensed under the MIT License (MIT), see
|
|
27967
|
-
http://jedwatson.github.io/classnames
|
|
27968
|
-
*/
|
|
27969
|
-
|
|
27970
|
-
(function (module) {
|
|
27971
|
-
/* global define */
|
|
27972
|
-
|
|
27973
|
-
(function () {
|
|
27974
|
-
|
|
27975
|
-
var hasOwn = {}.hasOwnProperty;
|
|
27976
|
-
|
|
27977
|
-
function classNames () {
|
|
27978
|
-
var classes = '';
|
|
27979
|
-
|
|
27980
|
-
for (var i = 0; i < arguments.length; i++) {
|
|
27981
|
-
var arg = arguments[i];
|
|
27982
|
-
if (arg) {
|
|
27983
|
-
classes = appendClass(classes, parseValue(arg));
|
|
27984
|
-
}
|
|
27985
|
-
}
|
|
27986
|
-
|
|
27987
|
-
return classes;
|
|
27988
|
-
}
|
|
27989
|
-
|
|
27990
|
-
function parseValue (arg) {
|
|
27991
|
-
if (typeof arg === 'string' || typeof arg === 'number') {
|
|
27992
|
-
return arg;
|
|
27993
|
-
}
|
|
27994
|
-
|
|
27995
|
-
if (typeof arg !== 'object') {
|
|
27996
|
-
return '';
|
|
27997
|
-
}
|
|
27998
|
-
|
|
27999
|
-
if (Array.isArray(arg)) {
|
|
28000
|
-
return classNames.apply(null, arg);
|
|
28001
|
-
}
|
|
28002
|
-
|
|
28003
|
-
if (arg.toString !== Object.prototype.toString && !arg.toString.toString().includes('[native code]')) {
|
|
28004
|
-
return arg.toString();
|
|
28005
|
-
}
|
|
28006
|
-
|
|
28007
|
-
var classes = '';
|
|
28008
|
-
|
|
28009
|
-
for (var key in arg) {
|
|
28010
|
-
if (hasOwn.call(arg, key) && arg[key]) {
|
|
28011
|
-
classes = appendClass(classes, key);
|
|
28012
|
-
}
|
|
28013
|
-
}
|
|
28014
|
-
|
|
28015
|
-
return classes;
|
|
28016
|
-
}
|
|
28017
|
-
|
|
28018
|
-
function appendClass (value, newClass) {
|
|
28019
|
-
if (!newClass) {
|
|
28020
|
-
return value;
|
|
28021
|
-
}
|
|
28022
|
-
|
|
28023
|
-
if (value) {
|
|
28024
|
-
return value + ' ' + newClass;
|
|
28025
|
-
}
|
|
28026
|
-
|
|
28027
|
-
return value + newClass;
|
|
28028
|
-
}
|
|
28029
|
-
|
|
28030
|
-
if (module.exports) {
|
|
28031
|
-
classNames.default = classNames;
|
|
28032
|
-
module.exports = classNames;
|
|
28033
|
-
} else {
|
|
28034
|
-
window.classNames = classNames;
|
|
28035
|
-
}
|
|
28036
|
-
}());
|
|
28037
|
-
} (classnames));
|
|
28038
|
-
|
|
28039
|
-
var classnamesExports = classnames.exports;
|
|
28040
|
-
var classNames = /*@__PURE__*/getDefaultExportFromCjs(classnamesExports);
|
|
28657
|
+
var css_248z$2 = ".index-module_mapEdit__XC5Gw {\n position: absolute;\n left: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n width: 100%;\n height: 100%;\n cursor: default;\n min-width: 1px;\n min-height: 1px;\n}\n.index-module_mapEdit__XC5Gw.index-module_createMode__14EPH {\n cursor: crosshair;\n}\n.index-module_mapEdit__XC5Gw.index-module_notCreateCursor__1-OjF {\n cursor: no-drop;\n}";
|
|
28658
|
+
var styles$2 = {"mapEdit":"index-module_mapEdit__XC5Gw","createMode":"index-module_createMode__14EPH","notCreateCursor":"index-module_notCreateCursor__1-OjF"};
|
|
28659
|
+
styleInject(css_248z$2);
|
|
28041
28660
|
|
|
28042
28661
|
/******************************************************************************
|
|
28043
28662
|
Copyright (c) Microsoft Corporation.
|
|
@@ -29037,7 +29656,7 @@ const CreateObstacleElement = forwardRef(({ enabled = false, svgElement, onPoint
|
|
|
29037
29656
|
if (!enabled || points.length === 0) {
|
|
29038
29657
|
return null;
|
|
29039
29658
|
}
|
|
29040
|
-
return (jsx(PolygonElement, { points: points.map((p) => [p.x, p.y, 2]), fillColor: styles.fillColor, fillOpacity: 0.3, strokeColor: styles.strokeColor, strokeWidth: 1, strokeOpacity: 1, createMode: true, showPoints: true, editMode: false, completed: true, draggable: false }));
|
|
29659
|
+
return (jsx(PolygonElement, { points: points.map((p) => [p.x, p.y, 2]), fillColor: styles.fillColor, fillOpacity: 0.3, strokeColor: styles.strokeColor, strokeWidth: 1.8, strokeOpacity: 1, createMode: true, showPoints: true, editMode: false, completed: true, draggable: false }));
|
|
29041
29660
|
});
|
|
29042
29661
|
CreateObstacleElement.displayName = 'CreateObstacleElement';
|
|
29043
29662
|
|
|
@@ -29075,7 +29694,7 @@ const useCreateVisionOffElement = () => {
|
|
|
29075
29694
|
[maxX, minY],
|
|
29076
29695
|
];
|
|
29077
29696
|
return elementPoints;
|
|
29078
|
-
}, [centerPoint, elementWidth, editMapInfo.allVisionOffPoints]);
|
|
29697
|
+
}, [centerPoint, elementWidth, editMapInfo.allVisionOffPoints, svgViewBox]);
|
|
29079
29698
|
return {
|
|
29080
29699
|
centerPoint,
|
|
29081
29700
|
getVisionOffPoints,
|
|
@@ -29310,9 +29929,9 @@ const SvgEditMap = forwardRef(({ mapConfig, onEditInfoMapChange, editMap }, ref)
|
|
|
29310
29929
|
const onMouseLeave = () => {
|
|
29311
29930
|
setHoverDataId('');
|
|
29312
29931
|
};
|
|
29313
|
-
return (jsx("div", { ref: containerRef, className: classNames(styles$
|
|
29314
|
-
[styles$
|
|
29315
|
-
[styles$
|
|
29932
|
+
return (jsx("div", { ref: containerRef, className: classNames(styles$2.mapEdit, {
|
|
29933
|
+
[styles$2.createMode]: isCreating,
|
|
29934
|
+
[styles$2.notCreateCursor]: showNotCreateCursor,
|
|
29316
29935
|
}),
|
|
29317
29936
|
// style={{
|
|
29318
29937
|
// position: 'absolute',
|
|
@@ -29418,6 +30037,22 @@ const SvgEditMap = forwardRef(({ mapConfig, onEditInfoMapChange, editMap }, ref)
|
|
|
29418
30037
|
});
|
|
29419
30038
|
SvgEditMap.displayName = 'SvgEditMap';
|
|
29420
30039
|
|
|
30040
|
+
/** 清理为合法的 SVG/HTML id 片段 */
|
|
30041
|
+
function sanitizeSvgIdPart(value) {
|
|
30042
|
+
return value.replace(/[^a-zA-Z0-9_.-]/g, '_');
|
|
30043
|
+
}
|
|
30044
|
+
/**
|
|
30045
|
+
* 通道 clipPath 的 SVG id
|
|
30046
|
+
* 组合 sn、useId、时间戳、随机数,避免同页多地图实例 id 冲突
|
|
30047
|
+
*/
|
|
30048
|
+
function createChannelClipPathId(params) {
|
|
30049
|
+
const snPart = sanitizeSvgIdPart(params.sn || 'map');
|
|
30050
|
+
const reactPart = sanitizeSvgIdPart(params.reactId.replace(/:/g, ''));
|
|
30051
|
+
const timestamp = Date.now();
|
|
30052
|
+
const randomPart = Math.random().toString(36).slice(2, 10);
|
|
30053
|
+
return `channel-clip-path-${snPart}-${reactPart}-${timestamp}-${randomPart}`;
|
|
30054
|
+
}
|
|
30055
|
+
|
|
29421
30056
|
var _path;
|
|
29422
30057
|
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
|
29423
30058
|
var SvgMobileAddPoint = function SvgMobileAddPoint(props) {
|
|
@@ -29435,9 +30070,9 @@ var SvgMobileAddPoint = function SvgMobileAddPoint(props) {
|
|
|
29435
30070
|
})));
|
|
29436
30071
|
};
|
|
29437
30072
|
|
|
29438
|
-
var css_248z = ".index-module_crosshair__etey6 {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 40px;\n height: 40px;\n pointer-events: none;\n z-index: 99999;\n display: flex;\n justify-content: center;\n align-items: center;\n}";
|
|
29439
|
-
var styles = {"crosshair":"index-module_crosshair__etey6"};
|
|
29440
|
-
styleInject(css_248z);
|
|
30073
|
+
var css_248z$1 = ".index-module_crosshair__etey6 {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 40px;\n height: 40px;\n pointer-events: none;\n z-index: 99999;\n display: flex;\n justify-content: center;\n align-items: center;\n}";
|
|
30074
|
+
var styles$1 = {"crosshair":"index-module_crosshair__etey6"};
|
|
30075
|
+
styleInject(css_248z$1);
|
|
29441
30076
|
|
|
29442
30077
|
/**
|
|
29443
30078
|
* 十字准心组件
|
|
@@ -29454,19 +30089,19 @@ const Crosshair = () => {
|
|
|
29454
30089
|
}
|
|
29455
30090
|
return false;
|
|
29456
30091
|
}, [platform, editMapInfo]);
|
|
29457
|
-
return isVisible ? (jsx("div", { className: styles.crosshair, children: jsx(SvgMobileAddPoint, {}) })) : null;
|
|
30092
|
+
return isVisible ? (jsx("div", { className: styles$1.crosshair, children: jsx(SvgMobileAddPoint, {}) })) : null;
|
|
29458
30093
|
};
|
|
29459
30094
|
|
|
29460
|
-
const WGS84 = 'EPSG:4326';
|
|
29461
|
-
const WEB_MERCATOR = 'EPSG:3857';
|
|
29462
|
-
proj4.defs(WGS84, '+proj=longlat +datum=WGS84 +no_defs');
|
|
29463
|
-
proj4.defs(WEB_MERCATOR, '+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs');
|
|
30095
|
+
const WGS84$1 = 'EPSG:4326';
|
|
30096
|
+
const WEB_MERCATOR$1 = 'EPSG:3857';
|
|
30097
|
+
proj4.defs(WGS84$1, '+proj=longlat +datum=WGS84 +no_defs');
|
|
30098
|
+
proj4.defs(WEB_MERCATOR$1, '+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs');
|
|
29464
30099
|
// 默认配置
|
|
29465
30100
|
const defaultMapConfig$1 = DEFAULT_STYLES;
|
|
29466
30101
|
// 地图渲染器组件
|
|
29467
|
-
const MowerMapRenderer = forwardRef(({ sn, mowerIconConfig = {}, platform = PlatformType.WEB, edger = false, unitType = UnitsType.Imperial, language = 'en', onlyRead = false, isHighlight = true, mapConfig,
|
|
30102
|
+
const MowerMapRenderer = forwardRef(({ sn, mowerIconConfig = {}, platform = PlatformType.WEB, mapType = MapType.NORMAL, edger = false, unitType = UnitsType.Imperial, language = 'en', onlyRead = false, isHighlight = true, mapConfig,
|
|
29468
30103
|
// pathConfig,
|
|
29469
|
-
modelType, showStraddleBoundaryBorder = true, mapRef, mapJson, pathJson, realTimeData, antennaConfig, onError, className, style, zIndex, googleMapInstance: _googleMapInstance, dragMap = false, canRotateMap = false, dragCallbacks, defaultTransform, debug: _debug = false, doodleList = [], editMap = false, unStructMapData, disabledObstacles = false, onHandleEnterRecord, onHandleCompleteRecord, onHandleStopRecord, onHandleDeleteElement, onSaveBoundaryData, onSaveMap, onEditInfoMapChange, onUpdateBoundary, heightOptions, globalHeight, googleMapStaticApiKey, onHandleEvent, onSelectElement, onH5FirstSelectObstaclePoint, }, ref) => {
|
|
30104
|
+
modelType, showStraddleBoundaryBorder = true, mapRef, mapJson, pathJson, realTimeData, antennaConfig, onError, className, style, zIndex, googleMapInstance: _googleMapInstance, dragMap = false, canRotateMap = false, dragCallbacks, defaultTransform, debug: _debug = false, doodleList = [], editMap = false, unStructMapData, disabledObstacles = false, onHandleEnterRecord, onHandleCompleteRecord, onHandleStopRecord, onHandleDeleteElement, onSaveBoundaryData, onSaveMap, onEditInfoMapChange, onUpdateBoundary, heightOptions, globalHeight, googleMapStaticApiKey, googleMapStaticUrlSigningSecret, signGoogleStaticMapUrl, onHandleEvent, onSelectElement, onH5FirstSelectObstaclePoint, locale, }, ref) => {
|
|
29470
30105
|
const [currentError, setCurrentError] = useState(null);
|
|
29471
30106
|
// const mapRef = useMap();
|
|
29472
30107
|
const [processStateIsMowing, setProcessStateIsMowing] = useState(false);
|
|
@@ -29494,6 +30129,8 @@ modelType, showStraddleBoundaryBorder = true, mapRef, mapJson, pathJson, realTim
|
|
|
29494
30129
|
...INIT_EDIT_MAP_INFO,
|
|
29495
30130
|
});
|
|
29496
30131
|
const [mowingPartitions, setMowingPartitions] = useState([]);
|
|
30132
|
+
const reactInstanceId = useId();
|
|
30133
|
+
const channelClipPathId = useMemo(() => createChannelClipPathId({ sn, reactId: reactInstanceId }), [sn, reactInstanceId]);
|
|
29497
30134
|
const minDistance = useMemo(() => {
|
|
29498
30135
|
return getMinSvgDistanceByModel(modelType);
|
|
29499
30136
|
}, [modelType]);
|
|
@@ -29627,13 +30264,25 @@ modelType, showStraddleBoundaryBorder = true, mapRef, mapJson, pathJson, realTim
|
|
|
29627
30264
|
const mowerPositionData = useMemo(() => {
|
|
29628
30265
|
// realTimeData 中包含三个种类的数据,之需要实时坐标的数据即可。
|
|
29629
30266
|
// 在初始的状态按照正常图标进行渲染,所以构造一个基本在rtk桩的一个数据
|
|
29630
|
-
if (!realTimeData || realTimeData.length === 0)
|
|
29631
|
-
|
|
29632
|
-
|
|
29633
|
-
|
|
29634
|
-
|
|
29635
|
-
|
|
29636
|
-
|
|
30267
|
+
if (!realTimeData || realTimeData.length === 0) {
|
|
30268
|
+
const chargingPiles = svgElementDatas?.[DataType.CHARGING_PILE]?.[0];
|
|
30269
|
+
if (chargingPiles?.position) {
|
|
30270
|
+
return {
|
|
30271
|
+
postureX: chargingPiles.position?.[0],
|
|
30272
|
+
postureY: chargingPiles.position?.[1],
|
|
30273
|
+
postureTheta: chargingPiles.direction - Math.PI || 0,
|
|
30274
|
+
vehicleState: RobotStatus.PARKED,
|
|
30275
|
+
};
|
|
30276
|
+
}
|
|
30277
|
+
else {
|
|
30278
|
+
return {
|
|
30279
|
+
postureX: 0.01,
|
|
30280
|
+
postureY: 0.1,
|
|
30281
|
+
postureTheta: 0.01,
|
|
30282
|
+
vehicleState: RobotStatus.PARKED,
|
|
30283
|
+
};
|
|
30284
|
+
}
|
|
30285
|
+
}
|
|
29637
30286
|
let currentPositionData;
|
|
29638
30287
|
if (realTimeData.length === 1 && realTimeData[0].type === RealTimeDataType.LOCATION) {
|
|
29639
30288
|
currentPositionData = realTimeData[0];
|
|
@@ -29677,14 +30326,14 @@ modelType, showStraddleBoundaryBorder = true, mapRef, mapJson, pathJson, realTim
|
|
|
29677
30326
|
const validBounds = getValidGpsBounds(mapJson, 0);
|
|
29678
30327
|
const [swLng0, swLat0] = validBounds.sw;
|
|
29679
30328
|
const [neLng0, neLat0] = validBounds.ne;
|
|
29680
|
-
const [swX0, swY0] = proj4(WGS84, WEB_MERCATOR, [swLng0, swLat0]);
|
|
29681
|
-
const [neX0, neY0] = proj4(WGS84, WEB_MERCATOR, [neLng0, neLat0]);
|
|
30329
|
+
const [swX0, swY0] = proj4(WGS84$1, WEB_MERCATOR$1, [swLng0, swLat0]);
|
|
30330
|
+
const [neX0, neY0] = proj4(WGS84$1, WEB_MERCATOR$1, [neLng0, neLat0]);
|
|
29682
30331
|
const swX = swX0 + drag.x;
|
|
29683
30332
|
const swY = swY0 + drag.y;
|
|
29684
30333
|
const neX = neX0 + drag.x;
|
|
29685
30334
|
const neY = neY0 + drag.y;
|
|
29686
|
-
const [swLng, swLat] = proj4(WEB_MERCATOR, WGS84, [swX, swY]);
|
|
29687
|
-
const [neLng, neLat] = proj4(WEB_MERCATOR, WGS84, [neX, neY]);
|
|
30335
|
+
const [swLng, swLat] = proj4(WEB_MERCATOR$1, WGS84$1, [swX, swY]);
|
|
30336
|
+
const [neLng, neLat] = proj4(WEB_MERCATOR$1, WGS84$1, [neX, neY]);
|
|
29688
30337
|
return new window.google.maps.LatLngBounds(new window.google.maps.LatLng(swLat, swLng), new window.google.maps.LatLng(neLat, neLng));
|
|
29689
30338
|
}, [mapJson, drag.x, drag.y]);
|
|
29690
30339
|
const commonValue = useMemo(() => {
|
|
@@ -29712,7 +30361,11 @@ modelType, showStraddleBoundaryBorder = true, mapRef, mapJson, pathJson, realTim
|
|
|
29712
30361
|
showStraddleBoundaryBorder,
|
|
29713
30362
|
overlayLayout,
|
|
29714
30363
|
googleMapStaticApiKey,
|
|
30364
|
+
googleMapStaticUrlSigningSecret,
|
|
30365
|
+
locale,
|
|
30366
|
+
signGoogleStaticMapUrl,
|
|
29715
30367
|
onH5FirstSelectObstaclePoint,
|
|
30368
|
+
channelClipPathId,
|
|
29716
30369
|
};
|
|
29717
30370
|
}, [
|
|
29718
30371
|
sn,
|
|
@@ -29735,8 +30388,32 @@ modelType, showStraddleBoundaryBorder = true, mapRef, mapJson, pathJson, realTim
|
|
|
29735
30388
|
showStraddleBoundaryBorder,
|
|
29736
30389
|
overlayLayout,
|
|
29737
30390
|
googleMapStaticApiKey,
|
|
30391
|
+
googleMapStaticUrlSigningSecret,
|
|
30392
|
+
locale,
|
|
30393
|
+
signGoogleStaticMapUrl,
|
|
29738
30394
|
onH5FirstSelectObstaclePoint,
|
|
30395
|
+
channelClipPathId,
|
|
29739
30396
|
]);
|
|
30397
|
+
/**
|
|
30398
|
+
* 这里做了一个配置,以防以后如果有多种不同的类型的情况,需要展示不同的元素
|
|
30399
|
+
*/
|
|
30400
|
+
const mapHasElements = useMemo(() => {
|
|
30401
|
+
// 地理围栏地图,有充电桩,没有天线,没有割草机位置,没有分区名称气泡
|
|
30402
|
+
if (mapType === MapType.GEO_FENCE) {
|
|
30403
|
+
return {
|
|
30404
|
+
hasChargingPile: true,
|
|
30405
|
+
hasAntenna: false,
|
|
30406
|
+
hasMowerPosition: false,
|
|
30407
|
+
hasBoundaryLabels: false,
|
|
30408
|
+
};
|
|
30409
|
+
}
|
|
30410
|
+
return {
|
|
30411
|
+
hasChargingPile: true,
|
|
30412
|
+
hasAntenna: true,
|
|
30413
|
+
hasMowerPosition: true,
|
|
30414
|
+
hasBoundaryLabels: true,
|
|
30415
|
+
};
|
|
30416
|
+
}, [mapType]);
|
|
29740
30417
|
const fitBounds = useCallback((padding = 0) => {
|
|
29741
30418
|
if (!mapJson || !mapRef)
|
|
29742
30419
|
return null;
|
|
@@ -29752,14 +30429,14 @@ modelType, showStraddleBoundaryBorder = true, mapRef, mapJson, pathJson, realTim
|
|
|
29752
30429
|
// 地图数据中的坐标格式是 [longitude, latitude]
|
|
29753
30430
|
const [swLng0, swLat0] = validBounds.sw;
|
|
29754
30431
|
const [neLng0, neLat0] = validBounds.ne;
|
|
29755
|
-
const [swX0, swY0] = proj4(WGS84, WEB_MERCATOR, [swLng0, swLat0]);
|
|
29756
|
-
const [neX0, neY0] = proj4(WGS84, WEB_MERCATOR, [neLng0, neLat0]);
|
|
30432
|
+
const [swX0, swY0] = proj4(WGS84$1, WEB_MERCATOR$1, [swLng0, swLat0]);
|
|
30433
|
+
const [neX0, neY0] = proj4(WGS84$1, WEB_MERCATOR$1, [neLng0, neLat0]);
|
|
29757
30434
|
const swX = swX0 + defaultTransform.x;
|
|
29758
30435
|
const swY = swY0 + defaultTransform.y;
|
|
29759
30436
|
const neX = neX0 + defaultTransform.x;
|
|
29760
30437
|
const neY = neY0 + defaultTransform.y;
|
|
29761
|
-
const [swLng, swLat] = proj4(WEB_MERCATOR, WGS84, [swX, swY]);
|
|
29762
|
-
const [neLng, neLat] = proj4(WEB_MERCATOR, WGS84, [neX, neY]);
|
|
30438
|
+
const [swLng, swLat] = proj4(WEB_MERCATOR$1, WGS84$1, [swX, swY]);
|
|
30439
|
+
const [neLng, neLat] = proj4(WEB_MERCATOR$1, WGS84$1, [neX, neY]);
|
|
29763
30440
|
const googleBounds = new window.google.maps.LatLngBounds(new window.google.maps.LatLng(swLat, swLng), // 西南角
|
|
29764
30441
|
new window.google.maps.LatLng(neLat, neLng) // 东北角
|
|
29765
30442
|
);
|
|
@@ -29786,8 +30463,8 @@ modelType, showStraddleBoundaryBorder = true, mapRef, mapJson, pathJson, realTim
|
|
|
29786
30463
|
const offsetLatLng = overlayProjection.fromDivPixelToLatLng(offsetPixel);
|
|
29787
30464
|
if (!offsetLatLng)
|
|
29788
30465
|
return;
|
|
29789
|
-
const [cx, cy] = proj4(WGS84, WEB_MERCATOR, [centerLatLng.lng(), centerLatLng.lat()]);
|
|
29790
|
-
const [ox, oy] = proj4(WGS84, WEB_MERCATOR, [offsetLatLng.lng(), offsetLatLng.lat()]);
|
|
30466
|
+
const [cx, cy] = proj4(WGS84$1, WEB_MERCATOR$1, [centerLatLng.lng(), centerLatLng.lat()]);
|
|
30467
|
+
const [ox, oy] = proj4(WGS84$1, WEB_MERCATOR$1, [offsetLatLng.lng(), offsetLatLng.lat()]);
|
|
29791
30468
|
const dx = ox - cx;
|
|
29792
30469
|
const dy = oy - cy;
|
|
29793
30470
|
setDrag((prev) => ({
|
|
@@ -29830,7 +30507,10 @@ modelType, showStraddleBoundaryBorder = true, mapRef, mapJson, pathJson, realTim
|
|
|
29830
30507
|
// 如果当前是taskDelay的状态,或者状态为mowing或者standby,则指定的地块需要高亮,或者全局高亮
|
|
29831
30508
|
if ((isTaskDelayRef.current ||
|
|
29832
30509
|
positionData?.vehicleState === RobotStatus.MOWING ||
|
|
29833
|
-
positionData?.vehicleState === RobotStatus.STANDBY
|
|
30510
|
+
positionData?.vehicleState === RobotStatus.STANDBY ||
|
|
30511
|
+
(!positionData &&
|
|
30512
|
+
(currentVehicleStateRef.current === RobotStatus.MOWING ||
|
|
30513
|
+
currentVehicleStateRef.current === RobotStatus.STANDBY))) &&
|
|
29834
30514
|
mowPartitionDataRef.current &&
|
|
29835
30515
|
!mowPartitionDataRef.current?.partitionIds) {
|
|
29836
30516
|
// 设置全局高亮
|
|
@@ -30051,13 +30731,15 @@ modelType, showStraddleBoundaryBorder = true, mapRef, mapJson, pathJson, realTim
|
|
|
30051
30731
|
// svgEditMapRef?.current?.adjustSvgSize?.(layout);
|
|
30052
30732
|
setOverlayLayout(layout);
|
|
30053
30733
|
}
|
|
30054
|
-
}, map: mapRef, mapPaneName: 'overlayMouseTarget', bounds: bounds, rotate: actureRotate, zIndex: zIndex, children: [jsx(MapDrag, { map: mapRef, dragCallbacks: dragCallbacks, onBoundaryLabelsCollapse: () => { }, onTransformChange: handleDragMove, isDragMap: dragMap, canRotateMap: canRotateMap }), jsx(MowPartitionContextProvider, { value: mowPartitionValue, children: jsx(SvgMapComponent, { isHighlight: isHighlight, editMap: editMap, ref: svgMapRef, pathData: pathJson, mapConfig: mergedMapConfig, mowPartitionData: mowPartitionData }) }), jsx(MowerPosition, { editMap: editMap, mowerPositionData: mowerPositionData, viewBox: svgViewBox || null, modelType: modelType, hasEdger: edger, mapData: mapJson, mapConfig: mergedMapConfig, realTimeData: realTimeData, onMowingPositionChange: onMowingPositionChange, isHighlight: isHighlight }), jsx(CharginPile, { viewBox: svgViewBox || null, rotation: actureRotate, isHighlight: isHighlight }), jsx(BoundaryLabels, { editMap: editMap, mapData: mapJson, onlyRead: onlyRead, pathData: pathJson, unitType: unitType, viewBox: svgViewBox || null, rotation: actureRotate, mowPartitionData: mowPartitionData, realTimeData: realTimeData, isHighlight: isHighlight }), jsx(Antennas, { editMap: editMap, antennaConfig: antennaConfig || [], layout: overlayLayout || undefined, viewBox: svgViewBox || null, rotation: actureRotate, onlyRead: onlyRead, isHighlight: isHighlight }), jsx(SvgEditMap, { editMap: editMap, ref: svgEditMapRef, mapConfig: mergedMapConfig, onEditInfoMapChange: onEditInfoMapChange })] })] }) }) }));
|
|
30734
|
+
}, map: mapRef, mapPaneName: 'overlayMouseTarget', bounds: bounds, rotate: actureRotate, zIndex: zIndex, children: [jsx(MapDrag, { map: mapRef, dragCallbacks: dragCallbacks, onBoundaryLabelsCollapse: () => { }, onTransformChange: handleDragMove, isDragMap: dragMap, canRotateMap: canRotateMap }), jsx(MowPartitionContextProvider, { value: mowPartitionValue, children: jsx(SvgMapComponent, { isHighlight: isHighlight, editMap: editMap, ref: svgMapRef, pathData: pathJson, mapConfig: mergedMapConfig, mowPartitionData: mowPartitionData }) }), mapHasElements.hasMowerPosition && (jsx(MowerPosition, { editMap: editMap, mowerPositionData: mowerPositionData, viewBox: svgViewBox || null, modelType: modelType, hasEdger: edger, mapData: mapJson, mapConfig: mergedMapConfig, realTimeData: realTimeData, onMowingPositionChange: onMowingPositionChange, isHighlight: isHighlight })), mapHasElements.hasChargingPile && (jsx(CharginPile, { viewBox: svgViewBox || null, rotation: actureRotate, isHighlight: isHighlight })), mapHasElements.hasBoundaryLabels && (jsx(BoundaryLabels, { editMap: editMap, mapData: mapJson, onlyRead: onlyRead, pathData: pathJson, unitType: unitType, viewBox: svgViewBox || null, rotation: actureRotate, mowPartitionData: mowPartitionData, realTimeData: realTimeData, isHighlight: isHighlight })), mapHasElements.hasAntenna && (jsx(Antennas, { editMap: editMap, antennaConfig: antennaConfig || [], layout: overlayLayout || undefined, viewBox: svgViewBox || null, rotation: actureRotate, onlyRead: onlyRead, isHighlight: isHighlight })), jsx(SvgEditMap, { editMap: editMap, ref: svgEditMapRef, mapConfig: mergedMapConfig, onEditInfoMapChange: onEditInfoMapChange })] })] }) }) }));
|
|
30055
30735
|
});
|
|
30056
30736
|
MowerMapRenderer.displayName = 'MowerMapRenderer';
|
|
30057
30737
|
|
|
30058
30738
|
// 默认配置
|
|
30059
30739
|
const defaultMapConfig = DEFAULT_STYLES;
|
|
30060
|
-
const BoundarySvgRender = React__default.memo(({ mapJson, unStructMapData, id, maxWidth = 300, mapConfig, sn }) => {
|
|
30740
|
+
const BoundarySvgRender = React__default.memo(({ mapJson, unStructMapData, id, maxWidth = 300, maxHeight, boundaryPadding = 50, mapConfig, sn, }) => {
|
|
30741
|
+
const reactInstanceId = useId();
|
|
30742
|
+
const channelClipPathId = useMemo(() => createChannelClipPathId({ sn, reactId: reactInstanceId }), [sn, reactInstanceId]);
|
|
30061
30743
|
// 合并配置
|
|
30062
30744
|
const mergedMapConfig = useMemo(() => {
|
|
30063
30745
|
return merge$1(defaultMapConfig, mapConfig);
|
|
@@ -30103,7 +30785,7 @@ const BoundarySvgRender = React__default.memo(({ mapJson, unStructMapData, id, m
|
|
|
30103
30785
|
maxY = Math.max(maxY, point[1]);
|
|
30104
30786
|
});
|
|
30105
30787
|
// 添加边距
|
|
30106
|
-
const padding =
|
|
30788
|
+
const padding = boundaryPadding;
|
|
30107
30789
|
const width = maxX - minX + padding * 2;
|
|
30108
30790
|
const height = maxY - minY + padding * 2;
|
|
30109
30791
|
return {
|
|
@@ -30112,25 +30794,40 @@ const BoundarySvgRender = React__default.memo(({ mapJson, unStructMapData, id, m
|
|
|
30112
30794
|
width,
|
|
30113
30795
|
height,
|
|
30114
30796
|
};
|
|
30115
|
-
}, [boundaryInfo]);
|
|
30797
|
+
}, [boundaryInfo, boundaryPadding]);
|
|
30116
30798
|
const commonValue = useMemo(() => {
|
|
30117
30799
|
return {
|
|
30118
30800
|
sn,
|
|
30119
30801
|
svgViewBox: boundaryViewBox,
|
|
30802
|
+
channelClipPathId,
|
|
30120
30803
|
};
|
|
30121
|
-
}, [sn, boundaryViewBox]);
|
|
30804
|
+
}, [sn, boundaryViewBox, channelClipPathId]);
|
|
30122
30805
|
const style = useMemo(() => {
|
|
30123
|
-
if (
|
|
30806
|
+
if (!maxHeight) {
|
|
30807
|
+
if (boundaryViewBox?.width > boundaryViewBox?.height) {
|
|
30808
|
+
return {
|
|
30809
|
+
width: maxWidth,
|
|
30810
|
+
height: maxWidth * (boundaryViewBox.height / boundaryViewBox.width),
|
|
30811
|
+
};
|
|
30812
|
+
}
|
|
30124
30813
|
return {
|
|
30125
|
-
width: maxWidth,
|
|
30126
|
-
height: maxWidth
|
|
30814
|
+
width: maxWidth * (boundaryViewBox.width / boundaryViewBox.height),
|
|
30815
|
+
height: maxWidth,
|
|
30816
|
+
};
|
|
30817
|
+
}
|
|
30818
|
+
const ratio = maxWidth / maxHeight;
|
|
30819
|
+
const boundaryRatio = boundaryViewBox.width / boundaryViewBox.height;
|
|
30820
|
+
if (ratio > boundaryRatio) {
|
|
30821
|
+
return {
|
|
30822
|
+
width: Math.floor(maxHeight * boundaryRatio),
|
|
30823
|
+
height: maxHeight,
|
|
30127
30824
|
};
|
|
30128
30825
|
}
|
|
30129
30826
|
return {
|
|
30130
|
-
width: maxWidth
|
|
30131
|
-
height: maxWidth,
|
|
30827
|
+
width: maxWidth,
|
|
30828
|
+
height: Math.floor(maxWidth / boundaryRatio),
|
|
30132
30829
|
};
|
|
30133
|
-
}, [boundaryViewBox, maxWidth]);
|
|
30830
|
+
}, [boundaryViewBox, maxWidth, maxHeight]);
|
|
30134
30831
|
return (jsx(CommonContextProvider, { value: commonValue, children: jsx(SvgEditContextProvider, { value: svgEditValue, children: jsxs("div", { style: {
|
|
30135
30832
|
position: 'relative',
|
|
30136
30833
|
width: style.width,
|
|
@@ -30146,4 +30843,1418 @@ const BoundarySvgRender = React__default.memo(({ mapJson, unStructMapData, id, m
|
|
|
30146
30843
|
}) }), jsx(CharginPile, { viewBox: boundaryViewBox || null, rotation: 0 })] }) }) }));
|
|
30147
30844
|
});
|
|
30148
30845
|
|
|
30149
|
-
|
|
30846
|
+
/** ChannelClipPath 等内容裁剪使用的 SVG 坐标边距(与用户传入的屏幕 padding 无关) */
|
|
30847
|
+
const PARTITION_PICKER_CONTENT_CLIP_PADDING = 50;
|
|
30848
|
+
/** H5:子区域屏幕宽度超过该值才展示名称标签(px) */
|
|
30849
|
+
const H5_PARTITION_LABEL_MIN_SCREEN_WIDTH_PX = 72;
|
|
30850
|
+
/** H5:名称气泡总宽度(px) */
|
|
30851
|
+
const H5_PARTITION_LABEL_WIDTH_PX = 72;
|
|
30852
|
+
/** 边界线目标屏幕像素宽度(默认态,全缩放级别保持一致) */
|
|
30853
|
+
const PARTITION_BOUNDARY_TARGET_SCREEN_STROKE_PX = 2;
|
|
30854
|
+
/** 边界线目标屏幕像素宽度(hover / 选中态) */
|
|
30855
|
+
const PARTITION_BOUNDARY_EMPHASIZED_TARGET_SCREEN_STROKE_PX = 2.5;
|
|
30856
|
+
/**
|
|
30857
|
+
* Figma bg/mapping 地图网格背景(Standalone SVG 层)
|
|
30858
|
+
* @see https://www.figma.com/design/j0DdUYf8VFkXQ8ikkA6ZzP — node 20490:84886
|
|
30859
|
+
*/
|
|
30860
|
+
/** 网格区域底色,也用于容器 letterbox 留白 */
|
|
30861
|
+
const PARTITION_PICKER_GRID_BASE_COLOR = '#E3E5EB';
|
|
30862
|
+
/** 网格单元边长(SVG 地图坐标,与 viewBox 一致;zoom 时间距同比缩放) */
|
|
30863
|
+
const PARTITION_PICKER_GRID_TILE_SIZE = 200;
|
|
30864
|
+
/** 网格线宽(地图坐标;随 zoom 同比缩放,不做屏幕像素恒定) */
|
|
30865
|
+
const PARTITION_PICKER_GRID_LINE_WIDTH = 4;
|
|
30866
|
+
/** 网格交点圆点半径(地图坐标) */
|
|
30867
|
+
const PARTITION_PICKER_GRID_DOT_RADIUS = 8;
|
|
30868
|
+
/** 网格层整体不透明度(Figma 约 80%) */
|
|
30869
|
+
const PARTITION_PICKER_GRID_OPACITY = 0.8;
|
|
30870
|
+
/** 网格线与交点颜色 */
|
|
30871
|
+
const PARTITION_PICKER_GRID_STROKE = '#FFFFFF';
|
|
30872
|
+
/** 可见范围内交点超过该数量时跳过圆点,仅保留网格线(缩小视野时减轻 DOM 压力) */
|
|
30873
|
+
const PARTITION_PICKER_GRID_MAX_INTERSECTION_DOTS = 400;
|
|
30874
|
+
/**
|
|
30875
|
+
* 分区选择地图专用样式
|
|
30876
|
+
*
|
|
30877
|
+
* Web:Figma 灰蓝未选中地块(Vector 360)
|
|
30878
|
+
* H5:高对比度绿色,面向卫星/深色底图
|
|
30879
|
+
*/
|
|
30880
|
+
const PARTITION_PICKER_MAP_CONFIG_WEB = {
|
|
30881
|
+
boundary: {
|
|
30882
|
+
lineColor: '#91A1B4',
|
|
30883
|
+
fillColor: '#D7DEE6',
|
|
30884
|
+
lineWidth: 2,
|
|
30885
|
+
},
|
|
30886
|
+
};
|
|
30887
|
+
const PARTITION_PICKER_MAP_CONFIG = {
|
|
30888
|
+
boundary: {
|
|
30889
|
+
lineColor: '#91A1B4',
|
|
30890
|
+
fillColor: '#D7DEE6',
|
|
30891
|
+
lineWidth: 2,
|
|
30892
|
+
},
|
|
30893
|
+
};
|
|
30894
|
+
/** Web 端地块配色(Figma Vector 360 / 361 / 99788) */
|
|
30895
|
+
const PARTITION_PICKER_COLORS_WEB = {
|
|
30896
|
+
defaultFill: 'rgba(222, 225, 231, 1)',
|
|
30897
|
+
defaultStroke: 'rgba(132, 152, 169, 1)',
|
|
30898
|
+
defaultStrokeWidth: 2,
|
|
30899
|
+
hoverFill: 'rgba(211, 216, 226, 1)',
|
|
30900
|
+
hoverStroke: 'rgba(132, 152, 169, 1)',
|
|
30901
|
+
hoverStrokeWidth: 3,
|
|
30902
|
+
selectedFill: 'rgba(202, 214, 238, 1)',
|
|
30903
|
+
selectedStroke: 'rgba(132, 152, 169, 1)',
|
|
30904
|
+
selectedStrokeWidth: 3,
|
|
30905
|
+
selectedHoverFill: 'rgba(202, 214, 238, 1)',
|
|
30906
|
+
selectedHoverStroke: 'rgba(132, 152, 169, 1)',
|
|
30907
|
+
selectedHoverStrokeWidth: 3,
|
|
30908
|
+
isolatedFill: 'rgba(225, 227, 233, 1)',
|
|
30909
|
+
isolatedStroke: 'rgba(189, 198, 208, 1)',
|
|
30910
|
+
isolatedStrokeWidth: 2,
|
|
30911
|
+
};
|
|
30912
|
+
/** H5 端地块配色 */
|
|
30913
|
+
const PARTITION_PICKER_COLORS_H5 = {
|
|
30914
|
+
defaultFill: 'rgba(222, 225, 231, 1)',
|
|
30915
|
+
defaultStroke: 'rgba(132, 152, 169, 1)',
|
|
30916
|
+
defaultStrokeWidth: 1,
|
|
30917
|
+
hoverFill: 'rgba(222, 225, 231, 1)',
|
|
30918
|
+
hoverStroke: 'rgba(132, 152, 169, 1)',
|
|
30919
|
+
hoverStrokeWidth: 1,
|
|
30920
|
+
selectedFill: 'rgba(202, 214, 238, 1)',
|
|
30921
|
+
selectedStroke: 'rgba(132, 152, 169, 1)',
|
|
30922
|
+
selectedStrokeWidth: 1,
|
|
30923
|
+
selectedHoverFill: 'rgba(202, 214, 238, 1)',
|
|
30924
|
+
selectedHoverStroke: 'rgba(132, 152, 169, 1)',
|
|
30925
|
+
selectedHoverStrokeWidth: 1,
|
|
30926
|
+
isolatedFill: 'rgba(225, 227, 233, 1)',
|
|
30927
|
+
isolatedStroke: 'rgba(189, 198, 208, 1)',
|
|
30928
|
+
isolatedStrokeWidth: 1,
|
|
30929
|
+
};
|
|
30930
|
+
function getPartitionPickerMapConfig(platform = PlatformType.WEB) {
|
|
30931
|
+
return platform === PlatformType.WEB
|
|
30932
|
+
? PARTITION_PICKER_MAP_CONFIG_WEB
|
|
30933
|
+
: PARTITION_PICKER_MAP_CONFIG;
|
|
30934
|
+
}
|
|
30935
|
+
function getPartitionPickerColors(platform = PlatformType.WEB) {
|
|
30936
|
+
return platform === PlatformType.WEB ? PARTITION_PICKER_COLORS_WEB : PARTITION_PICKER_COLORS_H5;
|
|
30937
|
+
}
|
|
30938
|
+
/** Web 分区名气泡(Figma「地块名称」/ 选中区域) */
|
|
30939
|
+
const PARTITION_PICKER_LABEL_STYLES_WEB = {
|
|
30940
|
+
default: {
|
|
30941
|
+
backgroundColor: 'rgba(255, 255, 255, 0.15)',
|
|
30942
|
+
color: 'rgba(65, 93, 116, 0.7)',
|
|
30943
|
+
fontSize: 12,
|
|
30944
|
+
fontWeight: 500,
|
|
30945
|
+
padding: '4px 8px',
|
|
30946
|
+
borderRadius: 14,
|
|
30947
|
+
lineHeight: '20px',
|
|
30948
|
+
},
|
|
30949
|
+
selected: {
|
|
30950
|
+
backgroundColor: 'rgba(255, 90, 0, 1)',
|
|
30951
|
+
color: 'rgba(255, 255, 255, 1)',
|
|
30952
|
+
fontSize: 12,
|
|
30953
|
+
fontWeight: 500,
|
|
30954
|
+
padding: '4px 8px',
|
|
30955
|
+
borderRadius: 20,
|
|
30956
|
+
lineHeight: '20px',
|
|
30957
|
+
},
|
|
30958
|
+
selectionOrderBadge: {
|
|
30959
|
+
width: '24px',
|
|
30960
|
+
height: '24px',
|
|
30961
|
+
background: '#FF5A00',
|
|
30962
|
+
border: '1px solid #FFFFFF',
|
|
30963
|
+
color: '#FFFFFF',
|
|
30964
|
+
fontSize: 14,
|
|
30965
|
+
fontWeight: 500,
|
|
30966
|
+
lineHeight: '22px',
|
|
30967
|
+
},
|
|
30968
|
+
};
|
|
30969
|
+
/** H5 分区名气泡(深色底图 / 橙色渐变选中) */
|
|
30970
|
+
const PARTITION_PICKER_LABEL_STYLES_H5 = {
|
|
30971
|
+
default: {
|
|
30972
|
+
backgroundColor: 'rgba(255, 255, 255, 0.15)',
|
|
30973
|
+
color: 'rgba(65, 93, 116, 0.7)',
|
|
30974
|
+
fontSize: 12,
|
|
30975
|
+
fontWeight: 400,
|
|
30976
|
+
padding: '6px',
|
|
30977
|
+
width: `${H5_PARTITION_LABEL_WIDTH_PX}px`,
|
|
30978
|
+
borderRadius: 100,
|
|
30979
|
+
boxSizing: 'border-box',
|
|
30980
|
+
},
|
|
30981
|
+
selected: {
|
|
30982
|
+
background: 'linear-gradient(266.64deg, #F2942A 4.1%, #F26A2A 86.42%)',
|
|
30983
|
+
color: 'rgba(255, 255, 255, 1)',
|
|
30984
|
+
fontSize: 12,
|
|
30985
|
+
fontWeight: 400,
|
|
30986
|
+
padding: '6px',
|
|
30987
|
+
width: `${H5_PARTITION_LABEL_WIDTH_PX}px`,
|
|
30988
|
+
borderRadius: 100,
|
|
30989
|
+
boxSizing: 'border-box',
|
|
30990
|
+
},
|
|
30991
|
+
selectionOrderBadge: {
|
|
30992
|
+
width: '24px',
|
|
30993
|
+
height: '24px',
|
|
30994
|
+
background: 'linear-gradient(310.82deg, #F16629 14.52%, #F1A129 103.26%)',
|
|
30995
|
+
border: '1px solid #FFFFFF',
|
|
30996
|
+
color: '#FFFFFF',
|
|
30997
|
+
fontSize: 14,
|
|
30998
|
+
fontWeight: 600,
|
|
30999
|
+
lineHeight: '22px',
|
|
31000
|
+
},
|
|
31001
|
+
};
|
|
31002
|
+
/** 将 PartitionPickerLabelAppearance 转为 React 内联样式 */
|
|
31003
|
+
function labelAppearanceToStyle(appearance) {
|
|
31004
|
+
const { fontSize, borderRadius, ...rest } = appearance;
|
|
31005
|
+
return {
|
|
31006
|
+
...rest,
|
|
31007
|
+
fontSize: `${fontSize}px`,
|
|
31008
|
+
borderRadius: `${borderRadius}px`,
|
|
31009
|
+
};
|
|
31010
|
+
}
|
|
31011
|
+
function getPartitionPickerLabelStyles(platform = PlatformType.WEB) {
|
|
31012
|
+
return platform === PlatformType.WEB
|
|
31013
|
+
? PARTITION_PICKER_LABEL_STYLES_WEB
|
|
31014
|
+
: PARTITION_PICKER_LABEL_STYLES_H5;
|
|
31015
|
+
}
|
|
31016
|
+
const DEFAULT_PARTITION_PICKER_GRID_STYLE = {
|
|
31017
|
+
baseColor: PARTITION_PICKER_GRID_BASE_COLOR,
|
|
31018
|
+
strokeColor: PARTITION_PICKER_GRID_STROKE,
|
|
31019
|
+
tileSize: PARTITION_PICKER_GRID_TILE_SIZE,
|
|
31020
|
+
lineWidth: PARTITION_PICKER_GRID_LINE_WIDTH,
|
|
31021
|
+
dotRadius: PARTITION_PICKER_GRID_DOT_RADIUS,
|
|
31022
|
+
opacity: PARTITION_PICKER_GRID_OPACITY,
|
|
31023
|
+
maxIntersectionDots: PARTITION_PICKER_GRID_MAX_INTERSECTION_DOTS,
|
|
31024
|
+
};
|
|
31025
|
+
const DEFAULT_PARTITION_PICKER_BOUNDARY_STROKE_PX = {
|
|
31026
|
+
default: PARTITION_BOUNDARY_TARGET_SCREEN_STROKE_PX,
|
|
31027
|
+
emphasized: PARTITION_BOUNDARY_EMPHASIZED_TARGET_SCREEN_STROKE_PX,
|
|
31028
|
+
};
|
|
31029
|
+
/**
|
|
31030
|
+
* 将 platform 内置默认值与外部 pickerStyleConfig 深度合并。
|
|
31031
|
+
* 调用方只需覆盖变化的字段,无需改组件源码。
|
|
31032
|
+
*/
|
|
31033
|
+
function resolvePartitionPickerStyles(platform = PlatformType.WEB, config) {
|
|
31034
|
+
const baseLabels = getPartitionPickerLabelStyles(platform);
|
|
31035
|
+
return {
|
|
31036
|
+
boundary: merge$1({}, getPartitionPickerColors(platform), config?.boundary),
|
|
31037
|
+
labels: config?.labels
|
|
31038
|
+
? {
|
|
31039
|
+
default: merge$1({}, baseLabels.default, config.labels.default),
|
|
31040
|
+
selected: merge$1({}, baseLabels.selected, config.labels.selected),
|
|
31041
|
+
selectionOrderBadge: merge$1({}, baseLabels.selectionOrderBadge, config.labels.selectionOrderBadge),
|
|
31042
|
+
}
|
|
31043
|
+
: baseLabels,
|
|
31044
|
+
grid: merge$1({}, DEFAULT_PARTITION_PICKER_GRID_STYLE, config?.grid),
|
|
31045
|
+
boundaryStrokePx: merge$1({}, DEFAULT_PARTITION_PICKER_BOUNDARY_STROKE_PX, config?.boundaryStrokePx),
|
|
31046
|
+
labelMinScreenWidthPx: config?.labelMinScreenWidthPx ?? H5_PARTITION_LABEL_MIN_SCREEN_WIDTH_PX,
|
|
31047
|
+
};
|
|
31048
|
+
}
|
|
31049
|
+
|
|
31050
|
+
/**
|
|
31051
|
+
* Standalone 模式下的视口控制:平移、滚轮缩放、H5 双指 pinch
|
|
31052
|
+
*
|
|
31053
|
+
* 核心思路:通过修改 SVG viewBox 实现平移/缩放,而非 CSS transform
|
|
31054
|
+
* - fitViewBox:初始 fit 状态,resetView 回到此状态
|
|
31055
|
+
* - viewBox:地块/通道/overlay 的可见区域
|
|
31056
|
+
* - gridViewBox:网格底图专用;有地图旋转时平移按屏幕方向(不逆旋转),与地块跟手平移解耦
|
|
31057
|
+
*
|
|
31058
|
+
* 平移可在地块/分区名等任意区域发起;仅当位移小于阈值时才视为点击
|
|
31059
|
+
*/
|
|
31060
|
+
function useMapViewport({ mapJson, containerRef: externalContainerRef, width, height, padding, minZoom = 0.5, maxZoom = 4, enablePanZoom = true, refitOnResize = true, onViewportTap, mapRotationDeg = 0, }) {
|
|
31061
|
+
/** 地图容器 DOM,用于绑定 wheel/pointer/touch 事件 */
|
|
31062
|
+
const internalContainerRef = useRef(null);
|
|
31063
|
+
const containerRef = externalContainerRef ?? internalContainerRef;
|
|
31064
|
+
/** 始终指向最新 padding,供 fitToView 异步调用时使用 */
|
|
31065
|
+
const paddingRef = useRef(padding);
|
|
31066
|
+
paddingRef.current = padding;
|
|
31067
|
+
/** padding 序列化 key,用于 useMemo 依赖(数组/对象形式 padding 变化时触发重算) */
|
|
31068
|
+
const paddingKey = typeof padding === 'number' ? padding : JSON.stringify(padding);
|
|
31069
|
+
/** 始终指向最新 onViewportTap,避免 effect 闭包过期 */
|
|
31070
|
+
const onViewportTapRef = useRef(onViewportTap);
|
|
31071
|
+
onViewportTapRef.current = onViewportTap;
|
|
31072
|
+
/** 始终指向最新 mapRotationDeg,供平移回调读取 */
|
|
31073
|
+
const mapRotationDegRef = useRef(mapRotationDeg);
|
|
31074
|
+
mapRotationDegRef.current = mapRotationDeg;
|
|
31075
|
+
/**
|
|
31076
|
+
* 内容 viewBox:地图包围盒 + 固定 SVG padding
|
|
31077
|
+
* 用于 ChannelClipPath 等裁剪,不随平移/缩放变化
|
|
31078
|
+
*/
|
|
31079
|
+
const contentViewBox = useMemo(() => {
|
|
31080
|
+
const bounds = calculateMapBounds(mapJson);
|
|
31081
|
+
return boundsToContentViewBox(bounds, PARTITION_PICKER_CONTENT_CLIP_PADDING);
|
|
31082
|
+
}, [mapJson]);
|
|
31083
|
+
/**
|
|
31084
|
+
* 初始 fit viewBox:在 contentViewBox 基础上适配容器宽高比与 screen padding
|
|
31085
|
+
* resetView 会回到此状态
|
|
31086
|
+
*/
|
|
31087
|
+
const fitViewBox = useMemo(() => computeFitViewBox(contentViewBox, width, height, padding), [contentViewBox, width, height, paddingKey]);
|
|
31088
|
+
/** fitViewBox 的 ref 副本,供事件回调中读取最新值(zoom 限制基准) */
|
|
31089
|
+
const fitViewBoxRef = useRef(fitViewBox);
|
|
31090
|
+
fitViewBoxRef.current = fitViewBox;
|
|
31091
|
+
/** 地块/通道/overlay 使用的 viewBox */
|
|
31092
|
+
const [viewBox, setViewBox] = useState(fitViewBox);
|
|
31093
|
+
/** 网格底图 viewBox:平移时按屏幕方向,避免与旋转后的地块反向滑动 */
|
|
31094
|
+
const [gridViewBox, setGridViewBox] = useState(fitViewBox);
|
|
31095
|
+
const viewBoxRef = useRef(viewBox);
|
|
31096
|
+
viewBoxRef.current = viewBox;
|
|
31097
|
+
const gridViewBoxRef = useRef(gridViewBox);
|
|
31098
|
+
gridViewBoxRef.current = gridViewBox;
|
|
31099
|
+
const pendingViewBoxRef = useRef(null);
|
|
31100
|
+
const pendingGridViewBoxRef = useRef(null);
|
|
31101
|
+
const viewBoxRafRef = useRef(null);
|
|
31102
|
+
const flushScheduledViewBox = useCallback(() => {
|
|
31103
|
+
viewBoxRafRef.current = null;
|
|
31104
|
+
const mapPending = pendingViewBoxRef.current;
|
|
31105
|
+
const gridPending = pendingGridViewBoxRef.current;
|
|
31106
|
+
if (mapPending) {
|
|
31107
|
+
pendingViewBoxRef.current = null;
|
|
31108
|
+
viewBoxRef.current = mapPending;
|
|
31109
|
+
setViewBox(mapPending);
|
|
31110
|
+
}
|
|
31111
|
+
if (gridPending) {
|
|
31112
|
+
pendingGridViewBoxRef.current = null;
|
|
31113
|
+
gridViewBoxRef.current = gridPending;
|
|
31114
|
+
setGridViewBox(gridPending);
|
|
31115
|
+
}
|
|
31116
|
+
}, []);
|
|
31117
|
+
const cancelScheduledViewBox = useCallback(() => {
|
|
31118
|
+
pendingViewBoxRef.current = null;
|
|
31119
|
+
pendingGridViewBoxRef.current = null;
|
|
31120
|
+
if (viewBoxRafRef.current != null) {
|
|
31121
|
+
cancelAnimationFrame(viewBoxRafRef.current);
|
|
31122
|
+
viewBoxRafRef.current = null;
|
|
31123
|
+
}
|
|
31124
|
+
}, []);
|
|
31125
|
+
const scheduleViewBox = useCallback((updater) => {
|
|
31126
|
+
const base = pendingViewBoxRef.current ?? viewBoxRef.current;
|
|
31127
|
+
const next = updater(base);
|
|
31128
|
+
pendingViewBoxRef.current = next;
|
|
31129
|
+
pendingGridViewBoxRef.current = next;
|
|
31130
|
+
viewBoxRef.current = next;
|
|
31131
|
+
gridViewBoxRef.current = next;
|
|
31132
|
+
if (viewBoxRafRef.current == null) {
|
|
31133
|
+
viewBoxRafRef.current = requestAnimationFrame(flushScheduledViewBox);
|
|
31134
|
+
}
|
|
31135
|
+
}, [flushScheduledViewBox]);
|
|
31136
|
+
const commitViewBox = useCallback((next) => {
|
|
31137
|
+
cancelScheduledViewBox();
|
|
31138
|
+
viewBoxRef.current = next;
|
|
31139
|
+
gridViewBoxRef.current = next;
|
|
31140
|
+
setViewBox(next);
|
|
31141
|
+
setGridViewBox(next);
|
|
31142
|
+
}, [cancelScheduledViewBox]);
|
|
31143
|
+
useEffect(() => () => {
|
|
31144
|
+
cancelScheduledViewBox();
|
|
31145
|
+
}, [cancelScheduledViewBox]);
|
|
31146
|
+
// 地图数据 / padding 变化时始终重新 fit(width/height 取触发时刻的实测值,不作为 resize 依赖)
|
|
31147
|
+
useEffect(() => {
|
|
31148
|
+
if (width <= 0 || height <= 0)
|
|
31149
|
+
return;
|
|
31150
|
+
commitViewBox(computeFitViewBox(contentViewBox, width, height, padding));
|
|
31151
|
+
}, [commitViewBox, contentViewBox, paddingKey]);
|
|
31152
|
+
const prevSizeRef = useRef({ width: 0, height: 0 });
|
|
31153
|
+
// 容器首次获得有效尺寸,或 refitOnResize 下的尺寸变化
|
|
31154
|
+
useEffect(() => {
|
|
31155
|
+
if (width <= 0 || height <= 0)
|
|
31156
|
+
return;
|
|
31157
|
+
const prev = prevSizeRef.current;
|
|
31158
|
+
const sizeChanged = prev.width !== width || prev.height !== height;
|
|
31159
|
+
const isInitialSize = prev.width <= 0 || prev.height <= 0;
|
|
31160
|
+
prevSizeRef.current = { width, height };
|
|
31161
|
+
if (isInitialSize || (refitOnResize && sizeChanged)) {
|
|
31162
|
+
commitViewBox(fitViewBox);
|
|
31163
|
+
}
|
|
31164
|
+
}, [
|
|
31165
|
+
commitViewBox,
|
|
31166
|
+
refitOnResize,
|
|
31167
|
+
width,
|
|
31168
|
+
height,
|
|
31169
|
+
fitViewBox.x,
|
|
31170
|
+
fitViewBox.y,
|
|
31171
|
+
fitViewBox.width,
|
|
31172
|
+
fitViewBox.height,
|
|
31173
|
+
]);
|
|
31174
|
+
/**
|
|
31175
|
+
* 以屏幕焦点为中心缩放
|
|
31176
|
+
* @param factor 缩放因子,>1 放大,<1 缩小
|
|
31177
|
+
* @param focalX 焦点相对容器左上角的 X(px)
|
|
31178
|
+
* @param focalY 焦点相对容器左上角的 Y(px)
|
|
31179
|
+
*/
|
|
31180
|
+
const applyZoom = useCallback((factor, focalX, focalY) => {
|
|
31181
|
+
scheduleViewBox((current) => {
|
|
31182
|
+
const targetWidth = clampViewBoxWidth(current.width / factor, fitViewBoxRef.current, minZoom, maxZoom);
|
|
31183
|
+
return resizeViewBoxAroundPoint(current, targetWidth, width, height, focalX, focalY);
|
|
31184
|
+
});
|
|
31185
|
+
}, [height, maxZoom, minZoom, scheduleViewBox, width]);
|
|
31186
|
+
/**
|
|
31187
|
+
* 平移视口:地块使用逆旋转后的 viewBox 增量(跟手);网格使用屏幕方向增量(同向滑动)
|
|
31188
|
+
*/
|
|
31189
|
+
const applyPan = useCallback((deltaXPx, deltaYPx) => {
|
|
31190
|
+
const mapBase = pendingViewBoxRef.current ?? viewBoxRef.current;
|
|
31191
|
+
const gridBase = pendingGridViewBoxRef.current ?? gridViewBoxRef.current;
|
|
31192
|
+
const mapDelta = containerDeltaToViewBoxDelta(deltaXPx, deltaYPx, mapBase, width, height, mapRotationDegRef.current);
|
|
31193
|
+
const gridDelta = containerDeltaToViewBoxDelta(deltaXPx, deltaYPx, gridBase, width, height, 0);
|
|
31194
|
+
const nextMap = {
|
|
31195
|
+
...mapBase,
|
|
31196
|
+
x: mapBase.x - mapDelta.dx,
|
|
31197
|
+
y: mapBase.y - mapDelta.dy,
|
|
31198
|
+
};
|
|
31199
|
+
const nextGrid = {
|
|
31200
|
+
...gridBase,
|
|
31201
|
+
x: gridBase.x - gridDelta.dx,
|
|
31202
|
+
y: gridBase.y - gridDelta.dy,
|
|
31203
|
+
width: nextMap.width,
|
|
31204
|
+
height: nextMap.height,
|
|
31205
|
+
};
|
|
31206
|
+
pendingViewBoxRef.current = nextMap;
|
|
31207
|
+
pendingGridViewBoxRef.current = nextGrid;
|
|
31208
|
+
viewBoxRef.current = nextMap;
|
|
31209
|
+
gridViewBoxRef.current = nextGrid;
|
|
31210
|
+
if (viewBoxRafRef.current == null) {
|
|
31211
|
+
viewBoxRafRef.current = requestAnimationFrame(flushScheduledViewBox);
|
|
31212
|
+
}
|
|
31213
|
+
}, [flushScheduledViewBox, height, width]);
|
|
31214
|
+
/** 恢复到初始 fitViewBox(ref API resetView) */
|
|
31215
|
+
const resetView = useCallback(() => {
|
|
31216
|
+
commitViewBox(fitViewBoxRef.current);
|
|
31217
|
+
}, [commitViewBox]);
|
|
31218
|
+
/**
|
|
31219
|
+
* 重新计算并应用 fit viewBox(ref API fitToView)
|
|
31220
|
+
* @param nextPadding 可选的新 padding,不传则使用当前 paddingRef
|
|
31221
|
+
*/
|
|
31222
|
+
const fitToView = useCallback((nextPadding) => {
|
|
31223
|
+
const bounds = calculateMapBounds(mapJson);
|
|
31224
|
+
const content = boundsToContentViewBox(bounds, PARTITION_PICKER_CONTENT_CLIP_PADDING);
|
|
31225
|
+
const fit = computeFitViewBox(content, width, height, nextPadding ?? paddingRef.current);
|
|
31226
|
+
fitViewBoxRef.current = fit;
|
|
31227
|
+
commitViewBox(fit);
|
|
31228
|
+
}, [commitViewBox, height, mapJson, width]);
|
|
31229
|
+
/** 桌面端:按 pointerId 追踪多个指针(预留多指,当前仅单指平移) */
|
|
31230
|
+
const pointerMapRef = useRef(new Map());
|
|
31231
|
+
/** H5:双指 pinch 缩放状态 */
|
|
31232
|
+
const pinchRef = useRef(null);
|
|
31233
|
+
/** 单指/单指针平移状态(桌面 pointer 与 H5 touch 共用) */
|
|
31234
|
+
const singlePanRef = useRef(null);
|
|
31235
|
+
/** 当前正在驱动平移的 pointer id(桌面端) */
|
|
31236
|
+
const activePanPointerIdRef = useRef(null);
|
|
31237
|
+
/** 清空所有平移/pinch 相关 ref,防止 pointer 丢失导致状态残留 */
|
|
31238
|
+
const clearPanState = useCallback(() => {
|
|
31239
|
+
pointerMapRef.current.clear();
|
|
31240
|
+
singlePanRef.current = null;
|
|
31241
|
+
activePanPointerIdRef.current = null;
|
|
31242
|
+
}, []);
|
|
31243
|
+
/** 计算 touch 列表中前两指间距 */
|
|
31244
|
+
const getTouchDistance = (touches) => {
|
|
31245
|
+
if (touches.length < 2)
|
|
31246
|
+
return 0;
|
|
31247
|
+
const dx = touches[0].clientX - touches[1].clientX;
|
|
31248
|
+
const dy = touches[0].clientY - touches[1].clientY;
|
|
31249
|
+
return Math.hypot(dx, dy);
|
|
31250
|
+
};
|
|
31251
|
+
/** 计算 touch 列表中前两指中心点,坐标相对容器左上角 */
|
|
31252
|
+
const getTouchCenter = (touches, rect) => {
|
|
31253
|
+
const x = (touches[0].clientX + touches[1].clientX) / 2 - rect.left;
|
|
31254
|
+
const y = (touches[0].clientY + touches[1].clientY) / 2 - rect.top;
|
|
31255
|
+
return { x, y };
|
|
31256
|
+
};
|
|
31257
|
+
/** 判断事件目标是否在地图容器内 */
|
|
31258
|
+
const isInsideContainer = (target, container) => {
|
|
31259
|
+
if (!(target instanceof Element))
|
|
31260
|
+
return false;
|
|
31261
|
+
return container.contains(target);
|
|
31262
|
+
};
|
|
31263
|
+
/** 位移是否超过 PAN_CLICK_THRESHOLD_PX,超过则视为拖拽 */
|
|
31264
|
+
const markPanMoved = (startX, startY, currentX, currentY) => Math.hypot(currentX - startX, currentY - startY) > PAN_CLICK_THRESHOLD_PX;
|
|
31265
|
+
/** 触发点击回调(仅当未发生拖拽时) */
|
|
31266
|
+
const emitViewportTap = (tapTarget) => {
|
|
31267
|
+
if (tapTarget) {
|
|
31268
|
+
onViewportTapRef.current?.(tapTarget);
|
|
31269
|
+
}
|
|
31270
|
+
};
|
|
31271
|
+
// ── 绑定容器交互事件:滚轮缩放 / 桌面拖拽 / H5 pinch+单指平移 ──
|
|
31272
|
+
useEffect(() => {
|
|
31273
|
+
const container = containerRef.current;
|
|
31274
|
+
if (!container || !enablePanZoom)
|
|
31275
|
+
return;
|
|
31276
|
+
/** 滚轮缩放,以鼠标位置为焦点 */
|
|
31277
|
+
const handleWheel = (event) => {
|
|
31278
|
+
event.preventDefault();
|
|
31279
|
+
const rect = container.getBoundingClientRect();
|
|
31280
|
+
const focalX = event.clientX - rect.left;
|
|
31281
|
+
const focalY = event.clientY - rect.top;
|
|
31282
|
+
const factor = event.deltaY < 0 ? 1.1 : 1 / 1.1;
|
|
31283
|
+
applyZoom(factor, focalX, focalY);
|
|
31284
|
+
};
|
|
31285
|
+
/** 结束平移:未拖拽则触发 tap;释放 pointer capture 并清理状态 */
|
|
31286
|
+
const endPan = (event) => {
|
|
31287
|
+
// touch 由 touchend 统一处理,避免 pointerup 抢先清空 singlePanRef
|
|
31288
|
+
if (event?.pointerType === 'touch') {
|
|
31289
|
+
return;
|
|
31290
|
+
}
|
|
31291
|
+
if (event) {
|
|
31292
|
+
const state = pointerMapRef.current.get(event.pointerId);
|
|
31293
|
+
if (state && !state.moved && state.tapTarget) {
|
|
31294
|
+
emitViewportTap(state.tapTarget);
|
|
31295
|
+
}
|
|
31296
|
+
pointerMapRef.current.delete(event.pointerId);
|
|
31297
|
+
try {
|
|
31298
|
+
if (container.hasPointerCapture(event.pointerId)) {
|
|
31299
|
+
container.releasePointerCapture(event.pointerId);
|
|
31300
|
+
}
|
|
31301
|
+
}
|
|
31302
|
+
catch {
|
|
31303
|
+
// ignore
|
|
31304
|
+
}
|
|
31305
|
+
}
|
|
31306
|
+
if (!event || pointerMapRef.current.size === 0) {
|
|
31307
|
+
clearPanState();
|
|
31308
|
+
}
|
|
31309
|
+
};
|
|
31310
|
+
// 桌面端:pointer 事件处理平移(touch 交给下方 touch 事件,避免重复)
|
|
31311
|
+
const handlePointerDown = (event) => {
|
|
31312
|
+
if (!isInsideContainer(event.target, container))
|
|
31313
|
+
return;
|
|
31314
|
+
if (event.pointerType === 'touch') {
|
|
31315
|
+
return;
|
|
31316
|
+
}
|
|
31317
|
+
// 仅响应主键(左键)
|
|
31318
|
+
if (event.pointerType === 'mouse' && event.button !== 0) {
|
|
31319
|
+
return;
|
|
31320
|
+
}
|
|
31321
|
+
pointerMapRef.current.set(event.pointerId, {
|
|
31322
|
+
pointerId: event.pointerId,
|
|
31323
|
+
startX: event.clientX,
|
|
31324
|
+
startY: event.clientY,
|
|
31325
|
+
moved: false,
|
|
31326
|
+
tapTarget: event.target instanceof Element ? event.target : null,
|
|
31327
|
+
});
|
|
31328
|
+
activePanPointerIdRef.current = event.pointerId;
|
|
31329
|
+
singlePanRef.current = {
|
|
31330
|
+
startX: event.clientX,
|
|
31331
|
+
startY: event.clientY,
|
|
31332
|
+
viewBox: viewBoxRef.current,
|
|
31333
|
+
moved: false,
|
|
31334
|
+
tapTarget: event.target instanceof Element ? event.target : null,
|
|
31335
|
+
};
|
|
31336
|
+
container.setPointerCapture(event.pointerId);
|
|
31337
|
+
};
|
|
31338
|
+
const handlePointerMove = (event) => {
|
|
31339
|
+
// 主键已松开但 pointerup 未触发时(如释放在容器外),避免继续平移
|
|
31340
|
+
if (event.pointerType === 'mouse' && event.buttons === 0) {
|
|
31341
|
+
endPan(event);
|
|
31342
|
+
return;
|
|
31343
|
+
}
|
|
31344
|
+
if (activePanPointerIdRef.current !== event.pointerId)
|
|
31345
|
+
return;
|
|
31346
|
+
const state = pointerMapRef.current.get(event.pointerId);
|
|
31347
|
+
if (!state || !singlePanRef.current)
|
|
31348
|
+
return;
|
|
31349
|
+
const dx = event.clientX - state.startX;
|
|
31350
|
+
const dy = event.clientY - state.startY;
|
|
31351
|
+
if (markPanMoved(state.startX, state.startY, event.clientX, event.clientY)) {
|
|
31352
|
+
state.moved = true;
|
|
31353
|
+
singlePanRef.current.moved = true;
|
|
31354
|
+
}
|
|
31355
|
+
applyPan(dx, dy);
|
|
31356
|
+
pointerMapRef.current.set(event.pointerId, {
|
|
31357
|
+
...state,
|
|
31358
|
+
startX: event.clientX,
|
|
31359
|
+
startY: event.clientY,
|
|
31360
|
+
});
|
|
31361
|
+
};
|
|
31362
|
+
const handlePointerUp = (event) => {
|
|
31363
|
+
endPan(event);
|
|
31364
|
+
};
|
|
31365
|
+
const handleLostPointerCapture = (event) => {
|
|
31366
|
+
if (activePanPointerIdRef.current === event.pointerId) {
|
|
31367
|
+
endPan(event);
|
|
31368
|
+
}
|
|
31369
|
+
};
|
|
31370
|
+
const handleWindowPointerUp = (event) => {
|
|
31371
|
+
if (event instanceof PointerEvent && event.pointerType === 'touch') {
|
|
31372
|
+
return;
|
|
31373
|
+
}
|
|
31374
|
+
clearPanState();
|
|
31375
|
+
};
|
|
31376
|
+
// H5:双指 pinch 缩放、单指平移
|
|
31377
|
+
const handleTouchStart = (event) => {
|
|
31378
|
+
if (event.touches.length === 2) {
|
|
31379
|
+
event.preventDefault();
|
|
31380
|
+
const rect = container.getBoundingClientRect();
|
|
31381
|
+
const center = getTouchCenter(event.touches, rect);
|
|
31382
|
+
pinchRef.current = {
|
|
31383
|
+
initialDistance: getTouchDistance(event.touches),
|
|
31384
|
+
initialViewBox: viewBoxRef.current,
|
|
31385
|
+
centerX: center.x,
|
|
31386
|
+
centerY: center.y,
|
|
31387
|
+
};
|
|
31388
|
+
singlePanRef.current = null;
|
|
31389
|
+
return;
|
|
31390
|
+
}
|
|
31391
|
+
if (event.touches.length === 1 && isInsideContainer(event.target, container)) {
|
|
31392
|
+
singlePanRef.current = {
|
|
31393
|
+
startX: event.touches[0].clientX,
|
|
31394
|
+
startY: event.touches[0].clientY,
|
|
31395
|
+
viewBox: viewBoxRef.current,
|
|
31396
|
+
moved: false,
|
|
31397
|
+
tapTarget: event.target instanceof Element ? event.target : null,
|
|
31398
|
+
};
|
|
31399
|
+
}
|
|
31400
|
+
};
|
|
31401
|
+
const handleTouchMove = (event) => {
|
|
31402
|
+
if (event.touches.length === 2 && pinchRef.current) {
|
|
31403
|
+
event.preventDefault();
|
|
31404
|
+
const rect = container.getBoundingClientRect();
|
|
31405
|
+
const distance = getTouchDistance(event.touches);
|
|
31406
|
+
if (distance <= 0 || pinchRef.current.initialDistance <= 0)
|
|
31407
|
+
return;
|
|
31408
|
+
const scale = distance / pinchRef.current.initialDistance;
|
|
31409
|
+
const center = getTouchCenter(event.touches, rect);
|
|
31410
|
+
const targetWidth = clampViewBoxWidth(pinchRef.current.initialViewBox.width / scale, fitViewBoxRef.current, minZoom, maxZoom);
|
|
31411
|
+
scheduleViewBox(() => resizeViewBoxAroundPoint(pinchRef.current.initialViewBox, targetWidth, width, height, center.x, center.y));
|
|
31412
|
+
return;
|
|
31413
|
+
}
|
|
31414
|
+
if (event.touches.length === 1 && singlePanRef.current) {
|
|
31415
|
+
const panState = singlePanRef.current;
|
|
31416
|
+
const touch = event.touches[0];
|
|
31417
|
+
if (markPanMoved(panState.startX, panState.startY, touch.clientX, touch.clientY)) {
|
|
31418
|
+
panState.moved = true;
|
|
31419
|
+
}
|
|
31420
|
+
if (panState.moved) {
|
|
31421
|
+
event.preventDefault();
|
|
31422
|
+
}
|
|
31423
|
+
const dx = touch.clientX - panState.startX;
|
|
31424
|
+
const dy = touch.clientY - panState.startY;
|
|
31425
|
+
applyPan(dx, dy);
|
|
31426
|
+
singlePanRef.current = {
|
|
31427
|
+
...panState,
|
|
31428
|
+
startX: touch.clientX,
|
|
31429
|
+
startY: touch.clientY,
|
|
31430
|
+
};
|
|
31431
|
+
}
|
|
31432
|
+
};
|
|
31433
|
+
const handleTouchEnd = (event) => {
|
|
31434
|
+
const touchState = singlePanRef.current;
|
|
31435
|
+
if (touchState && !touchState.moved) {
|
|
31436
|
+
let tapTarget = touchState.tapTarget;
|
|
31437
|
+
const touch = event.changedTouches[0];
|
|
31438
|
+
if (touch) {
|
|
31439
|
+
const hitTarget = document.elementFromPoint(touch.clientX, touch.clientY);
|
|
31440
|
+
if (hitTarget instanceof Element) {
|
|
31441
|
+
tapTarget = hitTarget;
|
|
31442
|
+
}
|
|
31443
|
+
}
|
|
31444
|
+
emitViewportTap(tapTarget);
|
|
31445
|
+
}
|
|
31446
|
+
if (pinchRef.current) {
|
|
31447
|
+
pinchRef.current = null;
|
|
31448
|
+
}
|
|
31449
|
+
clearPanState();
|
|
31450
|
+
};
|
|
31451
|
+
container.addEventListener('wheel', handleWheel, { passive: false });
|
|
31452
|
+
container.addEventListener('pointerdown', handlePointerDown);
|
|
31453
|
+
container.addEventListener('pointermove', handlePointerMove);
|
|
31454
|
+
container.addEventListener('pointerup', handlePointerUp);
|
|
31455
|
+
container.addEventListener('pointercancel', handlePointerUp);
|
|
31456
|
+
container.addEventListener('lostpointercapture', handleLostPointerCapture);
|
|
31457
|
+
container.addEventListener('touchstart', handleTouchStart, { passive: false });
|
|
31458
|
+
container.addEventListener('touchmove', handleTouchMove, { passive: false });
|
|
31459
|
+
container.addEventListener('touchend', handleTouchEnd);
|
|
31460
|
+
container.addEventListener('touchcancel', handleTouchEnd);
|
|
31461
|
+
window.addEventListener('pointerup', handleWindowPointerUp);
|
|
31462
|
+
window.addEventListener('mouseup', handleWindowPointerUp);
|
|
31463
|
+
window.addEventListener('blur', handleWindowPointerUp);
|
|
31464
|
+
return () => {
|
|
31465
|
+
container.removeEventListener('wheel', handleWheel);
|
|
31466
|
+
container.removeEventListener('pointerdown', handlePointerDown);
|
|
31467
|
+
container.removeEventListener('pointermove', handlePointerMove);
|
|
31468
|
+
container.removeEventListener('pointerup', handlePointerUp);
|
|
31469
|
+
container.removeEventListener('pointercancel', handlePointerUp);
|
|
31470
|
+
container.removeEventListener('lostpointercapture', handleLostPointerCapture);
|
|
31471
|
+
container.removeEventListener('touchstart', handleTouchStart);
|
|
31472
|
+
container.removeEventListener('touchmove', handleTouchMove);
|
|
31473
|
+
container.removeEventListener('touchend', handleTouchEnd);
|
|
31474
|
+
container.removeEventListener('touchcancel', handleTouchEnd);
|
|
31475
|
+
window.removeEventListener('pointerup', handleWindowPointerUp);
|
|
31476
|
+
window.removeEventListener('mouseup', handleWindowPointerUp);
|
|
31477
|
+
window.removeEventListener('blur', handleWindowPointerUp);
|
|
31478
|
+
};
|
|
31479
|
+
}, [applyPan, applyZoom, clearPanState, enablePanZoom, height, maxZoom, minZoom, scheduleViewBox, width]);
|
|
31480
|
+
return {
|
|
31481
|
+
/** 绑定到地图外层容器的 ref */
|
|
31482
|
+
containerRef,
|
|
31483
|
+
/** 地块/通道/overlay 使用的 viewBox */
|
|
31484
|
+
viewBox,
|
|
31485
|
+
/** 网格底图 viewBox(平移按屏幕方向,与旋转地块解耦) */
|
|
31486
|
+
gridViewBox,
|
|
31487
|
+
/** 初始 fit viewBox,供 clip path 等需要稳定坐标系的场景 */
|
|
31488
|
+
fitViewBox,
|
|
31489
|
+
/** 恢复到初始 fit 状态 */
|
|
31490
|
+
resetView,
|
|
31491
|
+
/** 重新 fit 地图到容器(可传新 padding) */
|
|
31492
|
+
fitToView,
|
|
31493
|
+
};
|
|
31494
|
+
}
|
|
31495
|
+
|
|
31496
|
+
/** WGS84 经纬度坐标系 */
|
|
31497
|
+
const WGS84 = 'EPSG:4326';
|
|
31498
|
+
/** Web Mercator 平面坐标(米),与 MowerMapRenderer / defaultTransform.x|y 一致 */
|
|
31499
|
+
const WEB_MERCATOR = 'EPSG:3857';
|
|
31500
|
+
proj4.defs(WGS84, '+proj=longlat +datum=WGS84 +no_defs');
|
|
31501
|
+
proj4.defs(WEB_MERCATOR, '+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs');
|
|
31502
|
+
/**
|
|
31503
|
+
* Map 模式专用:管理 OverlayViewF 所需的 bounds、viewBox、缩放与 fitBounds。
|
|
31504
|
+
* 逻辑对齐 MowerMapRenderer 的 Google Maps 叠加层。
|
|
31505
|
+
*/
|
|
31506
|
+
function useGoogleMapsOverlay({ mapJson, mapRef, defaultTransform, originNorthRotate, platform = PlatformType.WEB, padding = 50, }) {
|
|
31507
|
+
/** 仅当外部传入 mapRef 时执行 fitBounds / zoom 监听等副作用 */
|
|
31508
|
+
const enabled = Boolean(mapRef);
|
|
31509
|
+
const transformX = defaultTransform?.x ?? 0;
|
|
31510
|
+
const transformY = defaultTransform?.y ?? 0;
|
|
31511
|
+
const transformRotation = defaultTransform?.rotation ?? 0;
|
|
31512
|
+
/** 叠加层实际旋转 = 地图北向偏移 + 用户 rotation */
|
|
31513
|
+
const actureRotate = originNorthRotate + transformRotation;
|
|
31514
|
+
/** OverlayViewF onDraw 回调写入的 overlay 像素宽高 */
|
|
31515
|
+
const [overlayLayout, setOverlayLayout] = useState(null);
|
|
31516
|
+
const mapBounds = useMemo(() => calculateMapBounds(mapJson), [mapJson]);
|
|
31517
|
+
/**
|
|
31518
|
+
* SVG viewBox:地图 JSON 平面坐标全量范围。
|
|
31519
|
+
* 需等待 overlayLayout 就绪后才可计算(与 MowerMapRenderer svgViewBox 一致)。
|
|
31520
|
+
*/
|
|
31521
|
+
const svgViewBox = useMemo(() => {
|
|
31522
|
+
if (!overlayLayout?.width || !overlayLayout?.height)
|
|
31523
|
+
return null;
|
|
31524
|
+
const boundWidth = mapBounds.maxX - mapBounds.minX;
|
|
31525
|
+
const boundHeight = mapBounds.maxY - mapBounds.minY;
|
|
31526
|
+
if (boundWidth <= 0 || boundHeight <= 0)
|
|
31527
|
+
return null;
|
|
31528
|
+
return {
|
|
31529
|
+
x: mapBounds.minX,
|
|
31530
|
+
y: mapBounds.minY,
|
|
31531
|
+
width: boundWidth,
|
|
31532
|
+
height: boundHeight,
|
|
31533
|
+
};
|
|
31534
|
+
}, [mapBounds, overlayLayout?.height, overlayLayout?.width]);
|
|
31535
|
+
/**
|
|
31536
|
+
* OverlayViewF 的 LatLngBounds:
|
|
31537
|
+
* GPS 包围盒 + defaultTransform 在 Web Mercator 下的平移偏移。
|
|
31538
|
+
*/
|
|
31539
|
+
const bounds = useMemo(() => {
|
|
31540
|
+
if (!mapJson || typeof window === 'undefined' || !window.google?.maps)
|
|
31541
|
+
return null;
|
|
31542
|
+
const validBounds = getValidGpsBounds(mapJson, 0);
|
|
31543
|
+
const [swLng0, swLat0] = validBounds.sw;
|
|
31544
|
+
const [neLng0, neLat0] = validBounds.ne;
|
|
31545
|
+
const [swX0, swY0] = proj4(WGS84, WEB_MERCATOR, [swLng0, swLat0]);
|
|
31546
|
+
const [neX0, neY0] = proj4(WGS84, WEB_MERCATOR, [neLng0, neLat0]);
|
|
31547
|
+
const swX = swX0 + transformX;
|
|
31548
|
+
const swY = swY0 + transformY;
|
|
31549
|
+
const neX = neX0 + transformX;
|
|
31550
|
+
const neY = neY0 + transformY;
|
|
31551
|
+
const [swLng, swLat] = proj4(WEB_MERCATOR, WGS84, [swX, swY]);
|
|
31552
|
+
const [neLng, neLat] = proj4(WEB_MERCATOR, WGS84, [neX, neY]);
|
|
31553
|
+
return new window.google.maps.LatLngBounds(new window.google.maps.LatLng(swLat, swLng), new window.google.maps.LatLng(neLat, neLng));
|
|
31554
|
+
}, [mapJson, transformX, transformY]);
|
|
31555
|
+
/** OverlayViewF onDraw:解析 overlay 容器 style,得到像素 layout */
|
|
31556
|
+
const handleOverlayDraw = useCallback((style) => {
|
|
31557
|
+
const layout = Object.keys(style).reduce((acc, key) => {
|
|
31558
|
+
if (!key)
|
|
31559
|
+
return acc;
|
|
31560
|
+
acc[key] = parseFloat(style[key]);
|
|
31561
|
+
return acc;
|
|
31562
|
+
}, {});
|
|
31563
|
+
if (layout.width > 0 && layout.height > 0) {
|
|
31564
|
+
setOverlayLayout({ width: layout.width, height: layout.height });
|
|
31565
|
+
}
|
|
31566
|
+
}, []);
|
|
31567
|
+
/** Map 模式 fitToView:调用 google.maps.Map.fitBounds,考虑旋转后的 GPS 包围盒 */
|
|
31568
|
+
const fitToView = useCallback((nextPadding) => {
|
|
31569
|
+
if (!enabled || !mapRef || !mapJson)
|
|
31570
|
+
return;
|
|
31571
|
+
const rotate = transformRotation + originNorthRotate;
|
|
31572
|
+
const validBounds = getValidGpsBounds(mapJson, rotate);
|
|
31573
|
+
const [swLng0, swLat0] = validBounds.sw;
|
|
31574
|
+
const [neLng0, neLat0] = validBounds.ne;
|
|
31575
|
+
const [swX0, swY0] = proj4(WGS84, WEB_MERCATOR, [swLng0, swLat0]);
|
|
31576
|
+
const [neX0, neY0] = proj4(WGS84, WEB_MERCATOR, [neLng0, neLat0]);
|
|
31577
|
+
const swX = swX0 + transformX;
|
|
31578
|
+
const swY = swY0 + transformY;
|
|
31579
|
+
const neX = neX0 + transformX;
|
|
31580
|
+
const neY = neY0 + transformY;
|
|
31581
|
+
const [swLng, swLat] = proj4(WEB_MERCATOR, WGS84, [swX, swY]);
|
|
31582
|
+
const [neLng, neLat] = proj4(WEB_MERCATOR, WGS84, [neX, neY]);
|
|
31583
|
+
const googleBounds = new window.google.maps.LatLngBounds(new window.google.maps.LatLng(swLat, swLng), new window.google.maps.LatLng(neLat, neLng));
|
|
31584
|
+
const pad = normalizeScreenPadding(nextPadding ?? padding, 0);
|
|
31585
|
+
const paddingArg = pad.top === pad.right && pad.right === pad.bottom && pad.bottom === pad.left
|
|
31586
|
+
? pad.top
|
|
31587
|
+
: { top: pad.top, right: pad.right, bottom: pad.bottom, left: pad.left };
|
|
31588
|
+
mapRef.fitBounds(googleBounds, paddingArg);
|
|
31589
|
+
}, [enabled, mapJson, mapRef, originNorthRotate, padding, transformRotation, transformX, transformY]);
|
|
31590
|
+
const resetView = useCallback(() => {
|
|
31591
|
+
fitToView();
|
|
31592
|
+
}, [fitToView]);
|
|
31593
|
+
/** 参考缩放级别,用于计算 overlayScale(通道线宽视觉恒定,与 MowerMapRenderer 一致) */
|
|
31594
|
+
const REFERENCE_ZOOM = platform === PlatformType.WEB ? 21 : 21;
|
|
31595
|
+
const [overlayScale, setOverlayScale] = useState(1);
|
|
31596
|
+
useEffect(() => {
|
|
31597
|
+
if (!enabled || !mapRef)
|
|
31598
|
+
return;
|
|
31599
|
+
const updateScale = () => {
|
|
31600
|
+
const currentZoom = mapRef.getZoom();
|
|
31601
|
+
if (currentZoom === undefined)
|
|
31602
|
+
return;
|
|
31603
|
+
const zoomDiff = currentZoom - REFERENCE_ZOOM;
|
|
31604
|
+
const scale = Math.pow(2, -zoomDiff);
|
|
31605
|
+
if (scale < 1) {
|
|
31606
|
+
setOverlayScale(1);
|
|
31607
|
+
}
|
|
31608
|
+
else if (scale > 200) {
|
|
31609
|
+
setOverlayScale(platform === PlatformType.H5 ? 300 : 200);
|
|
31610
|
+
}
|
|
31611
|
+
else {
|
|
31612
|
+
setOverlayScale(platform === PlatformType.H5 ? 1.5 * scale : scale);
|
|
31613
|
+
}
|
|
31614
|
+
};
|
|
31615
|
+
const handleZoomChanged = throttle$2(updateScale, 50);
|
|
31616
|
+
const listener = mapRef.addListener('zoom_changed', handleZoomChanged);
|
|
31617
|
+
updateScale();
|
|
31618
|
+
return () => {
|
|
31619
|
+
if (listener) {
|
|
31620
|
+
window.google.maps.event.removeListener(listener);
|
|
31621
|
+
}
|
|
31622
|
+
handleZoomChanged.cancel();
|
|
31623
|
+
};
|
|
31624
|
+
}, [enabled, mapRef, platform, REFERENCE_ZOOM]);
|
|
31625
|
+
const overlayWidth = overlayLayout?.width ?? 0;
|
|
31626
|
+
const overlayHeight = overlayLayout?.height ?? 0;
|
|
31627
|
+
const isOverlayReady = overlayWidth > 0 && overlayHeight > 0 && svgViewBox != null;
|
|
31628
|
+
return {
|
|
31629
|
+
actureRotate,
|
|
31630
|
+
bounds,
|
|
31631
|
+
svgViewBox,
|
|
31632
|
+
fitViewBox: svgViewBox,
|
|
31633
|
+
overlayWidth,
|
|
31634
|
+
overlayHeight,
|
|
31635
|
+
overlayScale,
|
|
31636
|
+
isOverlayReady,
|
|
31637
|
+
handleOverlayDraw,
|
|
31638
|
+
fitToView,
|
|
31639
|
+
resetView,
|
|
31640
|
+
};
|
|
31641
|
+
}
|
|
31642
|
+
|
|
31643
|
+
function getInitialSize(width, height) {
|
|
31644
|
+
return {
|
|
31645
|
+
width: typeof width === 'number' && width > 0 ? width : 0,
|
|
31646
|
+
height: typeof height === 'number' && height > 0 ? height : 0,
|
|
31647
|
+
};
|
|
31648
|
+
}
|
|
31649
|
+
function cssLengthToStyle(value) {
|
|
31650
|
+
return value;
|
|
31651
|
+
}
|
|
31652
|
+
/**
|
|
31653
|
+
* 将 width/height(px 或 CSS length)应用到容器,并用 ResizeObserver 测量实际像素尺寸
|
|
31654
|
+
* 供 fit、标签定位、线宽换算等逻辑使用
|
|
31655
|
+
*/
|
|
31656
|
+
function useContainerSize(width, height) {
|
|
31657
|
+
const containerRef = useRef(null);
|
|
31658
|
+
const [size, setSize] = useState(() => getInitialSize(width, height));
|
|
31659
|
+
const layoutStyle = useMemo(() => ({
|
|
31660
|
+
width: cssLengthToStyle(width),
|
|
31661
|
+
height: cssLengthToStyle(height),
|
|
31662
|
+
}), [width, height]);
|
|
31663
|
+
useLayoutEffect(() => {
|
|
31664
|
+
const element = containerRef.current;
|
|
31665
|
+
if (!element)
|
|
31666
|
+
return;
|
|
31667
|
+
const updateSize = () => {
|
|
31668
|
+
const { width: nextWidth, height: nextHeight } = element.getBoundingClientRect();
|
|
31669
|
+
if (nextWidth <= 0 || nextHeight <= 0)
|
|
31670
|
+
return;
|
|
31671
|
+
setSize((prev) => prev.width === nextWidth && prev.height === nextHeight
|
|
31672
|
+
? prev
|
|
31673
|
+
: { width: nextWidth, height: nextHeight });
|
|
31674
|
+
};
|
|
31675
|
+
updateSize();
|
|
31676
|
+
const observer = new ResizeObserver(updateSize);
|
|
31677
|
+
observer.observe(element);
|
|
31678
|
+
return () => observer.disconnect();
|
|
31679
|
+
}, []);
|
|
31680
|
+
const isReady = size.width > 0 && size.height > 0;
|
|
31681
|
+
return {
|
|
31682
|
+
containerRef,
|
|
31683
|
+
layoutStyle,
|
|
31684
|
+
width: size.width,
|
|
31685
|
+
height: size.height,
|
|
31686
|
+
isReady,
|
|
31687
|
+
};
|
|
31688
|
+
}
|
|
31689
|
+
|
|
31690
|
+
/**
|
|
31691
|
+
* 地块 hover 状态管理(受控 / 非受控)
|
|
31692
|
+
*
|
|
31693
|
+
* 用于地图与外部表格等 UI 双向联动:
|
|
31694
|
+
* - 地图 hover → onBoundaryHoverChange → 宿主更新表格行高亮
|
|
31695
|
+
* - 表格 hover → 宿主传入 hoveredBoundaryId → 地图同步高亮
|
|
31696
|
+
*/
|
|
31697
|
+
function useBoundaryHover({ hoveredBoundaryId, defaultHoveredBoundaryId = null, onBoundaryHoverChange, }) {
|
|
31698
|
+
const isControlled = hoveredBoundaryId !== undefined;
|
|
31699
|
+
const [internalHoveredId, setInternalHoveredId] = useState(defaultHoveredBoundaryId);
|
|
31700
|
+
const hoveredId = isControlled ? hoveredBoundaryId : internalHoveredId;
|
|
31701
|
+
const setHoveredId = useCallback((id) => {
|
|
31702
|
+
if (!isControlled) {
|
|
31703
|
+
setInternalHoveredId(id);
|
|
31704
|
+
}
|
|
31705
|
+
onBoundaryHoverChange?.(id);
|
|
31706
|
+
}, [isControlled, onBoundaryHoverChange]);
|
|
31707
|
+
return {
|
|
31708
|
+
hoveredId,
|
|
31709
|
+
setHoveredId,
|
|
31710
|
+
};
|
|
31711
|
+
}
|
|
31712
|
+
|
|
31713
|
+
function filterSelectableIds(ids, disabledBoundaryIds) {
|
|
31714
|
+
if (!disabledBoundaryIds?.size)
|
|
31715
|
+
return ids;
|
|
31716
|
+
return ids.filter((id) => !disabledBoundaryIds.has(id));
|
|
31717
|
+
}
|
|
31718
|
+
/**
|
|
31719
|
+
* 地块多选状态管理(受控 / 非受控)
|
|
31720
|
+
*
|
|
31721
|
+
* - 传入 selectedBoundaryIds → 受控,由宿主维护选中列表(如表格勾选)
|
|
31722
|
+
* - 未传入 → 非受控,内部维护,仍可通过 onSelectionChange 感知变化
|
|
31723
|
+
*/
|
|
31724
|
+
function useBoundarySelection({ selectedBoundaryIds, defaultSelectedBoundaryIds = [], onSelectionChange, disabledBoundaryIds, }) {
|
|
31725
|
+
const isControlled = selectedBoundaryIds !== undefined;
|
|
31726
|
+
const [internalIds, setInternalIds] = useState(filterSelectableIds(defaultSelectedBoundaryIds, disabledBoundaryIds));
|
|
31727
|
+
const selectedIds = isControlled
|
|
31728
|
+
? filterSelectableIds(selectedBoundaryIds, disabledBoundaryIds)
|
|
31729
|
+
: internalIds;
|
|
31730
|
+
const setSelectedIds = useCallback((ids) => {
|
|
31731
|
+
const next = filterSelectableIds(ids, disabledBoundaryIds);
|
|
31732
|
+
if (!isControlled) {
|
|
31733
|
+
setInternalIds(next);
|
|
31734
|
+
}
|
|
31735
|
+
onSelectionChange?.(next);
|
|
31736
|
+
}, [disabledBoundaryIds, isControlled, onSelectionChange]);
|
|
31737
|
+
/** 点击同一地块时 toggle,支持多选;孤立子地块不可选 */
|
|
31738
|
+
const toggleBoundary = useCallback((id) => {
|
|
31739
|
+
if (disabledBoundaryIds?.has(id))
|
|
31740
|
+
return;
|
|
31741
|
+
const next = selectedIds.includes(id)
|
|
31742
|
+
? selectedIds.filter((item) => item !== id)
|
|
31743
|
+
: [...selectedIds, id];
|
|
31744
|
+
setSelectedIds(next);
|
|
31745
|
+
}, [disabledBoundaryIds, selectedIds, setSelectedIds]);
|
|
31746
|
+
return {
|
|
31747
|
+
selectedIds,
|
|
31748
|
+
toggleBoundary,
|
|
31749
|
+
setSelectedIds,
|
|
31750
|
+
};
|
|
31751
|
+
}
|
|
31752
|
+
|
|
31753
|
+
/** 计算地图中不可选中的孤立子地块 id 集合 */
|
|
31754
|
+
function useIsolatedBoundaryIds(mapJson) {
|
|
31755
|
+
return useMemo(() => {
|
|
31756
|
+
const boundaries = generateBoundaryData(mapJson);
|
|
31757
|
+
const ids = new Set();
|
|
31758
|
+
for (const boundary of boundaries) {
|
|
31759
|
+
if (boundary.isIsolated) {
|
|
31760
|
+
ids.add(boundary.id);
|
|
31761
|
+
}
|
|
31762
|
+
}
|
|
31763
|
+
return ids;
|
|
31764
|
+
}, [mapJson]);
|
|
31765
|
+
}
|
|
31766
|
+
/** 供宿主(如表格)判断某地块是否不可选 */
|
|
31767
|
+
function getIsolatedBoundaryIds(mapJson) {
|
|
31768
|
+
return generateBoundaryData(mapJson)
|
|
31769
|
+
.filter((boundary) => boundary.isIsolated)
|
|
31770
|
+
.map((boundary) => boundary.id);
|
|
31771
|
+
}
|
|
31772
|
+
|
|
31773
|
+
/** 计算多边形重心,作为分区名气泡的锚点 */
|
|
31774
|
+
function computePolygonCentroid(points) {
|
|
31775
|
+
if (!points || points.length < 3)
|
|
31776
|
+
return null;
|
|
31777
|
+
const valid = points.filter((p) => p.length >= 2);
|
|
31778
|
+
if (valid.length < 3)
|
|
31779
|
+
return null;
|
|
31780
|
+
const polygon = [...valid];
|
|
31781
|
+
const first = polygon[0];
|
|
31782
|
+
const last = polygon[polygon.length - 1];
|
|
31783
|
+
if (first[0] !== last[0] || first[1] !== last[1]) {
|
|
31784
|
+
polygon.push([first[0], first[1]]);
|
|
31785
|
+
}
|
|
31786
|
+
let area = 0;
|
|
31787
|
+
let cx = 0;
|
|
31788
|
+
let cy = 0;
|
|
31789
|
+
for (let i = 0; i < polygon.length - 1; i++) {
|
|
31790
|
+
const x0 = polygon[i][0];
|
|
31791
|
+
const y0 = polygon[i][1];
|
|
31792
|
+
const x1 = polygon[i + 1][0];
|
|
31793
|
+
const y1 = polygon[i + 1][1];
|
|
31794
|
+
const cross = x0 * y1 - x1 * y0;
|
|
31795
|
+
area += cross;
|
|
31796
|
+
cx += (x0 + x1) * cross;
|
|
31797
|
+
cy += (y0 + y1) * cross;
|
|
31798
|
+
}
|
|
31799
|
+
area = area / 2;
|
|
31800
|
+
if (Math.abs(area) < 1e-10) {
|
|
31801
|
+
let sx = 0;
|
|
31802
|
+
let sy = 0;
|
|
31803
|
+
for (const [x, y] of valid) {
|
|
31804
|
+
sx += x;
|
|
31805
|
+
sy += y;
|
|
31806
|
+
}
|
|
31807
|
+
return { x: sx / valid.length, y: sy / valid.length };
|
|
31808
|
+
}
|
|
31809
|
+
cx = cx / (6 * area);
|
|
31810
|
+
cy = cy / (6 * area);
|
|
31811
|
+
return { x: cx, y: cy };
|
|
31812
|
+
}
|
|
31813
|
+
function buildBoundaryLabelGeometry(mapData) {
|
|
31814
|
+
const boundaryData = generateBoundaryData(mapData);
|
|
31815
|
+
const results = [];
|
|
31816
|
+
for (const boundary of boundaryData) {
|
|
31817
|
+
const centroid = computePolygonCentroid(boundary.points);
|
|
31818
|
+
if (!centroid)
|
|
31819
|
+
continue;
|
|
31820
|
+
results.push({
|
|
31821
|
+
id: boundary.id,
|
|
31822
|
+
name: boundary.name,
|
|
31823
|
+
points: boundary.points,
|
|
31824
|
+
centroid,
|
|
31825
|
+
isIsolated: boundary.isIsolated,
|
|
31826
|
+
});
|
|
31827
|
+
}
|
|
31828
|
+
return results;
|
|
31829
|
+
}
|
|
31830
|
+
/**
|
|
31831
|
+
* 根据当前「显示 viewBox」计算分区名气泡位置(不预旋转;由 mapOverlayLayer CSS rotate 处理)
|
|
31832
|
+
*/
|
|
31833
|
+
function usePartitionLabelItems(mapData, viewBox, containerWidth, containerHeight, options) {
|
|
31834
|
+
const positionMode = options?.positionMode ?? 'meet';
|
|
31835
|
+
const boundaryGeometry = useMemo(() => (mapData ? buildBoundaryLabelGeometry(mapData) : []), [mapData]);
|
|
31836
|
+
return useMemo(() => {
|
|
31837
|
+
if (!viewBox || containerWidth <= 0 || containerHeight <= 0)
|
|
31838
|
+
return [];
|
|
31839
|
+
const results = [];
|
|
31840
|
+
for (const boundary of boundaryGeometry) {
|
|
31841
|
+
let leftPct;
|
|
31842
|
+
let topPct;
|
|
31843
|
+
if (positionMode === 'overlay') {
|
|
31844
|
+
const relX = (boundary.centroid.x - viewBox.x) / viewBox.width;
|
|
31845
|
+
const relY = (boundary.centroid.y - viewBox.y) / viewBox.height;
|
|
31846
|
+
leftPct = relX * 100;
|
|
31847
|
+
topPct = relY * 100;
|
|
31848
|
+
}
|
|
31849
|
+
else {
|
|
31850
|
+
const position = mapPointToContainerPercent(boundary.centroid.x, boundary.centroid.y, viewBox, containerWidth, containerHeight);
|
|
31851
|
+
leftPct = position.leftPct;
|
|
31852
|
+
topPct = position.topPct;
|
|
31853
|
+
}
|
|
31854
|
+
if (!Number.isFinite(leftPct) || !Number.isFinite(topPct))
|
|
31855
|
+
continue;
|
|
31856
|
+
const screenWidthPx = computePolygonScreenWidthPx(boundary.points, viewBox, containerWidth, containerHeight);
|
|
31857
|
+
results.push({
|
|
31858
|
+
id: boundary.id,
|
|
31859
|
+
name: boundary.name,
|
|
31860
|
+
leftPct,
|
|
31861
|
+
topPct,
|
|
31862
|
+
screenWidthPx,
|
|
31863
|
+
isIsolated: boundary.isIsolated,
|
|
31864
|
+
});
|
|
31865
|
+
}
|
|
31866
|
+
return results;
|
|
31867
|
+
}, [
|
|
31868
|
+
boundaryGeometry,
|
|
31869
|
+
containerWidth,
|
|
31870
|
+
containerHeight,
|
|
31871
|
+
viewBox?.x,
|
|
31872
|
+
viewBox?.y,
|
|
31873
|
+
viewBox?.width,
|
|
31874
|
+
viewBox?.height,
|
|
31875
|
+
positionMode,
|
|
31876
|
+
]);
|
|
31877
|
+
}
|
|
31878
|
+
|
|
31879
|
+
var css_248z = "@charset \"UTF-8\";\n/* 地图容器:裁剪溢出,禁用浏览器默认 touch 手势以便自定义 pinch */\n.index-module_container__nNYv7 {\n position: relative;\n overflow: hidden;\n touch-action: none;\n user-select: none;\n cursor: grab;\n /* H5 点击时不出现矩形 tap 高亮 */\n -webkit-tap-highlight-color: transparent;\n tap-highlight-color: transparent;\n}\n\n/* 网格 + 地块双层 SVG 叠放,共用 viewBox 保证平移/缩放同步 */\n.index-module_mapSvgStack__nz2s2 {\n position: absolute;\n inset: 0;\n pointer-events: none;\n z-index: 1;\n}\n\n.index-module_gridSvg__MoJD6 {\n position: absolute;\n inset: 0;\n display: block;\n pointer-events: none;\n}\n\n/* 地图 SVG:地块/通道,可绕 viewBox 中心旋转 */\n.index-module_mapSvg__vN0LV {\n position: absolute;\n inset: 0;\n display: block;\n pointer-events: none;\n -webkit-tap-highlight-color: transparent;\n tap-highlight-color: transparent;\n}\n\n/* 可点击地块:点击选中由 viewport 统一处理,此处保留 hover */\n.index-module_boundaryPolygon__uHe-E {\n cursor: grab;\n pointer-events: auto;\n -webkit-tap-highlight-color: transparent;\n tap-highlight-color: transparent;\n outline: none;\n -webkit-touch-callout: none;\n}\n\n.index-module_container__nNYv7:active {\n cursor: grabbing;\n}\n\n.index-module_boundaryPolygon__uHe-E:active {\n cursor: grabbing;\n}\n\n/* Map 模式:OverlayViewF 内根节点,铺满 overlay 像素区域 */\n.index-module_googleOverlayRoot__TQ3II {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n pointer-events: none;\n overflow: visible;\n}\n\n/* HTML overlay 层:CSS rotate 与 MowerMapRenderer OverlayViewF 一致,子元素自行反向补偿 */\n.index-module_mapOverlayLayer__7jTDy {\n position: absolute;\n inset: 0;\n transform-origin: 50% 50%;\n pointer-events: none;\n z-index: 2;\n}\n\n/* 分区名气泡层,绝对定位覆盖在 SVG 之上 */\n.index-module_labelsLayer__Rcikw {\n position: absolute;\n inset: 0;\n pointer-events: none;\n}\n\n.index-module_label__xo3Kc {\n position: absolute;\n display: flex;\n flex-direction: column;\n align-items: center;\n pointer-events: auto;\n cursor: grab;\n white-space: nowrap;\n -webkit-tap-highlight-color: transparent;\n tap-highlight-color: transparent;\n outline: none;\n -webkit-touch-callout: none;\n}\n\n.index-module_label__xo3Kc:active {\n cursor: grabbing;\n}\n\n.index-module_labelWithOrder__uiHNo {\n gap: 8px;\n}\n\n.index-module_labelWithOrderH5__fg3nU {\n gap: 4px;\n}\n\n.index-module_labelOrderOnly__TDLBU {\n gap: 0;\n}\n\n/* H5 名称标签:总宽 72px,文案最大 60px 可换行 */\n.index-module_labelH5Wrap__Es5cA {\n white-space: normal;\n}\n\n.index-module_labelPill__7jnOo {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: none;\n}\n\n.index-module_labelPillH5__iPeHi {\n width: 72px;\n max-width: 72px;\n box-sizing: border-box;\n}\n\n.index-module_labelText__ialST {\n word-break: break-word;\n text-align: center;\n}\n\n.index-module_labelTextH5__jz8SO {\n max-width: 60px;\n white-space: normal;\n overflow-wrap: anywhere;\n line-height: 1.33;\n}\n\n/* 选中序号徽章:尺寸与颜色由 constants 按 platform 注入 */\n.index-module_selectionOrderBadge__uj7LX {\n flex-shrink: 0;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.index-module_boundaryPolygonIsolated__syN4V {\n cursor: not-allowed;\n}\n\n.index-module_boundaryPolygonIsolated__syN4V:active {\n cursor: grabbing;\n}\n\n.index-module_labelIsolated__K3WAB {\n cursor: not-allowed;\n pointer-events: auto;\n}\n\n.index-module_labelIsolated__K3WAB:active {\n cursor: grabbing;\n}\n\n.index-module_labelIsolatedLayout__k-rwI {\n gap: 4px;\n}\n\n.index-module_isolatedIconBelow__PuVIF {\n position: static;\n transform: none;\n width: 24px;\n height: 24px;\n flex-shrink: 0;\n pointer-events: none;\n}\n\n.index-module_isolatedIconBelow__PuVIF.index-module_isolatedIconWeb__iPkYp {\n width: 20px;\n height: 20px;\n}\n\n.index-module_isolatedIcon__Vbxl0 {\n position: absolute;\n width: 24px;\n height: 24px;\n left: 50%;\n bottom: calc(100% + 4px);\n transform: translateX(-50%);\n}\n\n.index-module_isolatedIconWeb__iPkYp {\n width: 20px;\n height: 20px;\n bottom: calc(100% + 2px);\n}";
|
|
31880
|
+
var styles = {"container":"index-module_container__nNYv7","mapSvgStack":"index-module_mapSvgStack__nz2s2","gridSvg":"index-module_gridSvg__MoJD6","mapSvg":"index-module_mapSvg__vN0LV","boundaryPolygon":"index-module_boundaryPolygon__uHe-E","googleOverlayRoot":"index-module_googleOverlayRoot__TQ3II","mapOverlayLayer":"index-module_mapOverlayLayer__7jTDy","labelsLayer":"index-module_labelsLayer__Rcikw","label":"index-module_label__xo3Kc","labelWithOrder":"index-module_labelWithOrder__uiHNo","labelWithOrderH5":"index-module_labelWithOrderH5__fg3nU","labelOrderOnly":"index-module_labelOrderOnly__TDLBU","labelH5Wrap":"index-module_labelH5Wrap__Es5cA","labelPill":"index-module_labelPill__7jnOo","labelPillH5":"index-module_labelPillH5__iPeHi","labelText":"index-module_labelText__ialST","labelTextH5":"index-module_labelTextH5__jz8SO","selectionOrderBadge":"index-module_selectionOrderBadge__uj7LX","boundaryPolygonIsolated":"index-module_boundaryPolygonIsolated__syN4V","labelIsolated":"index-module_labelIsolated__K3WAB","labelIsolatedLayout":"index-module_labelIsolatedLayout__k-rwI","isolatedIconBelow":"index-module_isolatedIconBelow__PuVIF","isolatedIconWeb":"index-module_isolatedIconWeb__iPkYp"};
|
|
31881
|
+
styleInject(css_248z);
|
|
31882
|
+
|
|
31883
|
+
const PARTITION_BOUNDARY_ID_ATTR$1 = 'data-partition-boundary-id';
|
|
31884
|
+
const SelectableBoundaryElement = ({ data, boundaryColors, boundaryStrokePx, isIsolated = false, selected, hovered, viewBoxWidth, viewBoxHeight, fitViewBox, containerWidth, containerHeight, onHoverChange, onBoundaryClick, }) => {
|
|
31885
|
+
const style = data.style || {};
|
|
31886
|
+
const boundaryId = Number(data.id);
|
|
31887
|
+
const colors = boundaryColors;
|
|
31888
|
+
const strokePx = boundaryStrokePx ?? {
|
|
31889
|
+
default: PARTITION_BOUNDARY_TARGET_SCREEN_STROKE_PX,
|
|
31890
|
+
emphasized: PARTITION_BOUNDARY_EMPHASIZED_TARGET_SCREEN_STROKE_PX,
|
|
31891
|
+
};
|
|
31892
|
+
const pointsString = useMemo(() => data.points.map((point) => `${point[0]},${point[1]}`).join(' '), [data.points]);
|
|
31893
|
+
const fillColor = useMemo(() => {
|
|
31894
|
+
if (isIsolated)
|
|
31895
|
+
return colors.isolatedFill;
|
|
31896
|
+
if (selected) {
|
|
31897
|
+
return hovered ? colors.selectedHoverFill : colors.selectedFill;
|
|
31898
|
+
}
|
|
31899
|
+
if (hovered)
|
|
31900
|
+
return colors.hoverFill;
|
|
31901
|
+
return style.fillColor || colors.defaultFill;
|
|
31902
|
+
}, [colors, hovered, isIsolated, selected, style.fillColor]);
|
|
31903
|
+
const strokeColor = useMemo(() => {
|
|
31904
|
+
if (isIsolated)
|
|
31905
|
+
return colors.isolatedStroke;
|
|
31906
|
+
if (selected) {
|
|
31907
|
+
return hovered ? colors.selectedHoverStroke : colors.selectedStroke;
|
|
31908
|
+
}
|
|
31909
|
+
if (hovered)
|
|
31910
|
+
return colors.hoverStroke;
|
|
31911
|
+
return style.lineColor || colors.defaultStroke;
|
|
31912
|
+
}, [colors, hovered, isIsolated, selected, style.lineColor]);
|
|
31913
|
+
const strokeWidth = useMemo(() => {
|
|
31914
|
+
let baseWidth;
|
|
31915
|
+
let emphasized = false;
|
|
31916
|
+
if (isIsolated)
|
|
31917
|
+
baseWidth = colors.isolatedStrokeWidth;
|
|
31918
|
+
else if (selected) {
|
|
31919
|
+
emphasized = true;
|
|
31920
|
+
baseWidth = hovered ? colors.selectedHoverStrokeWidth : colors.selectedStrokeWidth;
|
|
31921
|
+
}
|
|
31922
|
+
else if (hovered) {
|
|
31923
|
+
emphasized = true;
|
|
31924
|
+
baseWidth = colors.hoverStrokeWidth;
|
|
31925
|
+
}
|
|
31926
|
+
else
|
|
31927
|
+
baseWidth = colors.defaultStrokeWidth;
|
|
31928
|
+
const viewBox = { width: viewBoxWidth, height: viewBoxHeight };
|
|
31929
|
+
return scaleStrokeWidthForViewBox(baseWidth, viewBox, fitViewBox, {
|
|
31930
|
+
containerWidth,
|
|
31931
|
+
containerHeight,
|
|
31932
|
+
minScreenStrokePx: emphasized ? strokePx.emphasized : strokePx.default,
|
|
31933
|
+
});
|
|
31934
|
+
}, [
|
|
31935
|
+
colors,
|
|
31936
|
+
containerHeight,
|
|
31937
|
+
containerWidth,
|
|
31938
|
+
fitViewBox,
|
|
31939
|
+
hovered,
|
|
31940
|
+
isIsolated,
|
|
31941
|
+
selected,
|
|
31942
|
+
strokePx,
|
|
31943
|
+
viewBoxHeight,
|
|
31944
|
+
viewBoxWidth,
|
|
31945
|
+
]);
|
|
31946
|
+
if (isIsolated) {
|
|
31947
|
+
return (jsx("polygon", { className: `${styles.boundaryPolygon} ${styles.boundaryPolygonIsolated}`, points: pointsString, fill: fillColor, stroke: strokeColor, strokeWidth: strokeWidth, strokeLinejoin: "round" }));
|
|
31948
|
+
}
|
|
31949
|
+
return (jsx("polygon", { className: styles.boundaryPolygon, points: pointsString, fill: fillColor, stroke: strokeColor, strokeWidth: strokeWidth, strokeLinejoin: "round", [PARTITION_BOUNDARY_ID_ATTR$1]: boundaryId, onMouseEnter: () => onHoverChange(boundaryId), onMouseLeave: () => onHoverChange(null), onClick: onBoundaryClick
|
|
31950
|
+
? (event) => {
|
|
31951
|
+
event.stopPropagation();
|
|
31952
|
+
onBoundaryClick(boundaryId);
|
|
31953
|
+
}
|
|
31954
|
+
: undefined }));
|
|
31955
|
+
};
|
|
31956
|
+
var SelectableBoundaryElement$1 = memo(SelectableBoundaryElement);
|
|
31957
|
+
|
|
31958
|
+
const PartitionPickerSvg = ({ svgElementDatas, viewBox, fitViewBox, containerWidth, containerHeight, mapRotation = 0, boundaryColors, boundaryStrokePx, isolatedBoundaryIds, selectedIds, hoveredId, onHoverChange, onBoundaryClick, }) => {
|
|
31959
|
+
const boundaries = (svgElementDatas[DataType.BOUNDARY] || []);
|
|
31960
|
+
const channels = (svgElementDatas[DataType.CHANNEL] || []);
|
|
31961
|
+
const selectedIdSet = useMemo(() => new Set(selectedIds), [selectedIds]);
|
|
31962
|
+
const mapContentTransform = useMemo(() => {
|
|
31963
|
+
if (mapRotation === 0)
|
|
31964
|
+
return undefined;
|
|
31965
|
+
const { x: cx, y: cy } = getViewBoxCenter(viewBox);
|
|
31966
|
+
return `rotate(${mapRotation}, ${cx}, ${cy})`;
|
|
31967
|
+
}, [mapRotation, viewBox.x, viewBox.y, viewBox.width, viewBox.height]);
|
|
31968
|
+
const mapContent = (jsxs(Fragment, { children: [jsx(GElement, { type: "boundary", children: boundaries.map((item) => {
|
|
31969
|
+
const boundaryId = Number(item.id);
|
|
31970
|
+
return (jsx(SelectableBoundaryElement$1, { data: item, boundaryColors: boundaryColors, boundaryStrokePx: boundaryStrokePx, isIsolated: isolatedBoundaryIds.has(boundaryId), selected: selectedIdSet.has(boundaryId), hovered: hoveredId === boundaryId, viewBoxWidth: viewBox.width, viewBoxHeight: viewBox.height, fitViewBox: fitViewBox, containerWidth: containerWidth, containerHeight: containerHeight, onHoverChange: onHoverChange, onBoundaryClick: onBoundaryClick }, item.id));
|
|
31971
|
+
}) }), jsxs(GElement, { type: "channel", children: [jsx(ChannelClipPath, {}), channels.map((item) => (jsx(ChannelElement, { data: item }, item.id)))] })] }));
|
|
31972
|
+
return (jsx("svg", { className: styles.mapSvg, xmlns: "http://www.w3.org/2000/svg", viewBox: viewBoxToString(viewBox), width: "100%", height: "100%", preserveAspectRatio: "xMidYMid meet", shapeRendering: "geometricPrecision", children: mapContentTransform ? (jsx("g", { transform: mapContentTransform, children: mapContent })) : (mapContent) }));
|
|
31973
|
+
};
|
|
31974
|
+
|
|
31975
|
+
function estimateGridIntersectionCount(viewBox, tileSize) {
|
|
31976
|
+
if (tileSize <= 0)
|
|
31977
|
+
return 0;
|
|
31978
|
+
const cols = Math.floor((viewBox.width + 1e-6) / tileSize) + 1;
|
|
31979
|
+
const rows = Math.floor((viewBox.height + 1e-6) / tileSize) + 1;
|
|
31980
|
+
return cols * rows;
|
|
31981
|
+
}
|
|
31982
|
+
/** 计算当前 viewBox 可见范围内的网格交点(每个交点只渲染一次圆点) */
|
|
31983
|
+
function getVisibleGridIntersections(viewBox, tileSize, maxDots) {
|
|
31984
|
+
if (tileSize <= 0 || estimateGridIntersectionCount(viewBox, tileSize) > maxDots)
|
|
31985
|
+
return [];
|
|
31986
|
+
const points = [];
|
|
31987
|
+
const minX = viewBox.x;
|
|
31988
|
+
const minY = viewBox.y;
|
|
31989
|
+
const maxX = viewBox.x + viewBox.width;
|
|
31990
|
+
const maxY = viewBox.y + viewBox.height;
|
|
31991
|
+
const startX = Math.floor(minX / tileSize) * tileSize;
|
|
31992
|
+
const startY = Math.floor(minY / tileSize) * tileSize;
|
|
31993
|
+
for (let x = startX; x <= maxX + 1e-6; x += tileSize) {
|
|
31994
|
+
for (let y = startY; y <= maxY + 1e-6; y += tileSize) {
|
|
31995
|
+
points.push({ x, y });
|
|
31996
|
+
}
|
|
31997
|
+
}
|
|
31998
|
+
return points;
|
|
31999
|
+
}
|
|
32000
|
+
const MapGridBackground = ({ viewBox, gridStyle }) => {
|
|
32001
|
+
const reactId = useId();
|
|
32002
|
+
const patternId = useMemo(() => `partition-picker-grid-lines-${reactId.replace(/:/g, '-')}`, [reactId]);
|
|
32003
|
+
const { baseColor, strokeColor, tileSize: tile, lineWidth, dotRadius, opacity, maxIntersectionDots, } = gridStyle;
|
|
32004
|
+
const intersections = useMemo(() => getVisibleGridIntersections(viewBox, tile, maxIntersectionDots), [maxIntersectionDots, tile, viewBox.x, viewBox.y, viewBox.width, viewBox.height]);
|
|
32005
|
+
return (jsxs("g", { className: "partition-picker-grid-bg", "aria-hidden": true, pointerEvents: "none", children: [jsx("defs", { children: jsxs("pattern", { id: patternId, patternUnits: "userSpaceOnUse", width: tile, height: tile, children: [jsx("line", { x1: 0, y1: 0, x2: tile, y2: 0, stroke: strokeColor, strokeWidth: lineWidth }), jsx("line", { x1: 0, y1: 0, x2: 0, y2: tile, stroke: strokeColor, strokeWidth: lineWidth })] }) }), jsx("rect", { x: viewBox.x, y: viewBox.y, width: viewBox.width, height: viewBox.height, fill: baseColor }), jsx("rect", { x: viewBox.x, y: viewBox.y, width: viewBox.width, height: viewBox.height, fill: `url(#${patternId})`, opacity: opacity }), jsx("g", { opacity: opacity, children: intersections.map((point, index) => (jsx("circle", { cx: point.x, cy: point.y, r: dotRadius, fill: strokeColor }, `${point.x}-${point.y}-${index}`))) })] }));
|
|
32006
|
+
};
|
|
32007
|
+
var MapGridBackground$1 = memo(MapGridBackground);
|
|
32008
|
+
|
|
32009
|
+
const PartitionPickerGridSvg = ({ viewBox, gridStyle }) => {
|
|
32010
|
+
return (jsx("svg", { className: styles.gridSvg, "data-viewport-background": "true", xmlns: "http://www.w3.org/2000/svg", viewBox: viewBoxToString(viewBox), width: "100%", height: "100%", preserveAspectRatio: "xMidYMid meet", shapeRendering: "geometricPrecision", "aria-hidden": true, children: jsx(MapGridBackground$1, { viewBox: viewBox, gridStyle: gridStyle }) }));
|
|
32011
|
+
};
|
|
32012
|
+
|
|
32013
|
+
const PartitionNameLabels = ({ items, selectedIds, labelStyles, labelMinScreenWidthPx = 72, platform = PlatformType.WEB, showSelectionOrder = false, rotation = 0, onHoverChange, onBoundaryClick, }) => {
|
|
32014
|
+
const isWeb = platform === PlatformType.WEB;
|
|
32015
|
+
const orderBadgeStyle = useMemo(() => ({
|
|
32016
|
+
width: labelStyles.selectionOrderBadge.width,
|
|
32017
|
+
height: labelStyles.selectionOrderBadge.height,
|
|
32018
|
+
background: labelStyles.selectionOrderBadge.background,
|
|
32019
|
+
border: labelStyles.selectionOrderBadge.border,
|
|
32020
|
+
color: labelStyles.selectionOrderBadge.color,
|
|
32021
|
+
fontSize: `${labelStyles.selectionOrderBadge.fontSize}px`,
|
|
32022
|
+
fontWeight: labelStyles.selectionOrderBadge.fontWeight,
|
|
32023
|
+
lineHeight: labelStyles.selectionOrderBadge.lineHeight,
|
|
32024
|
+
}), [labelStyles]);
|
|
32025
|
+
return (jsx("div", { className: styles.labelsLayer, children: items.map((item) => {
|
|
32026
|
+
const isIsolated = item.isIsolated === true;
|
|
32027
|
+
const selected = !isIsolated && selectedIds.includes(item.id);
|
|
32028
|
+
const selectionOrder = selected ? selectedIds.indexOf(item.id) + 1 : null;
|
|
32029
|
+
const isLargeEnough = item.screenWidthPx > labelMinScreenWidthPx;
|
|
32030
|
+
// Web 始终展示名称;H5 按屏幕宽度决定
|
|
32031
|
+
const showNameLabel = isWeb || isLargeEnough;
|
|
32032
|
+
const showOrderBadge = showSelectionOrder && selected && selectionOrder != null;
|
|
32033
|
+
const h5OrderOnly = !isWeb && showOrderBadge && !isLargeEnough;
|
|
32034
|
+
const h5OrderWithName = !isWeb && showOrderBadge && isLargeEnough;
|
|
32035
|
+
if (!showNameLabel && !showOrderBadge && !isIsolated) {
|
|
32036
|
+
return null;
|
|
32037
|
+
}
|
|
32038
|
+
const pillStyle = labelAppearanceToStyle(selected ? labelStyles.selected : labelStyles.default);
|
|
32039
|
+
const labelClassName = [
|
|
32040
|
+
styles.label,
|
|
32041
|
+
!isWeb && showNameLabel ? styles.labelH5Wrap : '',
|
|
32042
|
+
isIsolated ? styles.labelIsolated : '',
|
|
32043
|
+
isWeb && showOrderBadge ? styles.labelWithOrder : '',
|
|
32044
|
+
h5OrderWithName ? styles.labelWithOrderH5 : '',
|
|
32045
|
+
h5OrderOnly ? styles.labelOrderOnly : '',
|
|
32046
|
+
isIsolated ? styles.labelIsolatedLayout : '',
|
|
32047
|
+
]
|
|
32048
|
+
.filter(Boolean)
|
|
32049
|
+
.join(' ');
|
|
32050
|
+
const boundaryIdProps = isIsolated ? {} : { [PARTITION_BOUNDARY_ID_ATTR$1]: item.id };
|
|
32051
|
+
return (jsxs("div", { className: labelClassName, style: {
|
|
32052
|
+
left: `${item.leftPct}%`,
|
|
32053
|
+
top: `${item.topPct}%`,
|
|
32054
|
+
transform: `translate(-50%, -50%) rotate(${-rotation}deg)`,
|
|
32055
|
+
}, ...boundaryIdProps, onMouseEnter: isIsolated ? undefined : () => onHoverChange(item.id), onMouseLeave: isIsolated ? undefined : () => onHoverChange(null), onClick: onBoundaryClick && !isIsolated
|
|
32056
|
+
? (event) => {
|
|
32057
|
+
event.stopPropagation();
|
|
32058
|
+
onBoundaryClick(item.id);
|
|
32059
|
+
}
|
|
32060
|
+
: undefined, children: [showOrderBadge && (jsx("div", { className: styles.selectionOrderBadge, style: orderBadgeStyle, children: selectionOrder })), showNameLabel && (jsx("div", { className: `${styles.labelPill} ${!isWeb ? styles.labelPillH5 : ''}`, style: pillStyle, children: jsx("span", { className: `${styles.labelText} ${!isWeb ? styles.labelTextH5 : ''}`, children: item.name }) })), isIsolated && (jsx("div", { className: `${styles.isolatedIconBelow} ${isWeb ? styles.isolatedIconWeb : ''}`, dangerouslySetInnerHTML: { __html: ISOLATED_BOUNDARY_SVG } }))] }, item.id));
|
|
32061
|
+
}) }));
|
|
32062
|
+
};
|
|
32063
|
+
|
|
32064
|
+
const DEFAULT_BACKGROUND = PARTITION_PICKER_GRID_BASE_COLOR;
|
|
32065
|
+
/** 地块 polygon / 标签上的 data 属性,供 viewport 点击命中与表格联动 */
|
|
32066
|
+
const PARTITION_BOUNDARY_ID_ATTR = 'data-partition-boundary-id';
|
|
32067
|
+
/** Map 模式下 OverlayViewF 默认 z-index */
|
|
32068
|
+
const DEFAULT_OVERLAY_Z_INDEX = 100;
|
|
32069
|
+
const MowerPartitionPickerMap = forwardRef(({ mapJson, mapConfig, pickerStyleConfig, sn = '', platform = PlatformType.WEB, width, height, padding = 50, backgroundColor = DEFAULT_BACKGROUND, defaultTransform, mapRef, minZoom = 0.5, maxZoom = 4, enablePanZoom = true, refitOnResize = true, onSizeChange, selectedBoundaryIds, defaultSelectedBoundaryIds, onSelectionChange, hoveredBoundaryId, defaultHoveredBoundaryId, onBoundaryHoverChange, showSelectionOrder = false, zIndex = DEFAULT_OVERLAY_Z_INDEX, className, style, }, ref) => {
|
|
32070
|
+
/**
|
|
32071
|
+
* 是否为 Map 模式(传入 mapRef 时为 true):
|
|
32072
|
+
* - true:Google Maps 底图 + OverlayViewF,平移/缩放/fit 由底图负责
|
|
32073
|
+
* - false:Standalone 固定容器 + 网格 SVG + useMapViewport
|
|
32074
|
+
*/
|
|
32075
|
+
const isMapMode = Boolean(mapRef);
|
|
32076
|
+
const reactInstanceId = useId();
|
|
32077
|
+
const channelClipPathId = useMemo(() => createChannelClipPathId({ sn, reactId: reactInstanceId }), [sn, reactInstanceId]);
|
|
32078
|
+
/** 外部 pickerStyleConfig 与内置 platform 默认值合并后的最终外观(地块/标签/网格等) */
|
|
32079
|
+
const mergedPickerStyles = useMemo(() => resolvePartitionPickerStyles(platform, pickerStyleConfig), [pickerStyleConfig, platform]);
|
|
32080
|
+
/** Standalone 容器留白与网格底色;pickerStyleConfig.grid.baseColor 优先于 backgroundColor */
|
|
32081
|
+
const resolvedBackgroundColor = pickerStyleConfig?.grid?.baseColor ?? backgroundColor ?? DEFAULT_BACKGROUND;
|
|
32082
|
+
const mergedMapConfig = useMemo(() => merge$1({}, DEFAULT_STYLES, getPartitionPickerMapConfig(platform), mapConfig), [mapConfig, platform]);
|
|
32083
|
+
const svgElementDatas = useMemo(() => UnifiedMapDataProcessor.processMapData(mapJson, mergedMapConfig) || {}, [mapJson, mergedMapConfig]);
|
|
32084
|
+
/** 地图 Y 轴相对正北的偏移(度),与 MowerMapRenderer.originNorthRotate 一致 */
|
|
32085
|
+
const originNorthRotate = useMemo(() => {
|
|
32086
|
+
if (mapJson.map_north_offset)
|
|
32087
|
+
return (mapJson.map_north_offset * 180) / Math.PI;
|
|
32088
|
+
return 0;
|
|
32089
|
+
}, [mapJson.map_north_offset]);
|
|
32090
|
+
/** Standalone 模式:叠加层实际旋转角(度)= 地图北向偏移 + 外部传入 rotation */
|
|
32091
|
+
const standaloneActureRotate = useMemo(() => originNorthRotate + (defaultTransform?.rotation ?? 0), [originNorthRotate, defaultTransform?.rotation]);
|
|
32092
|
+
/**
|
|
32093
|
+
* 稳定的内容 viewBox(地图全量范围 + SVG 裁剪边距)
|
|
32094
|
+
* 供 ChannelClipPath 使用,不随用户平移/缩放变化
|
|
32095
|
+
*/
|
|
32096
|
+
const contentViewBox = useMemo(() => {
|
|
32097
|
+
const bounds = calculateMapBounds(mapJson);
|
|
32098
|
+
return boundsToContentViewBox(bounds, PARTITION_PICKER_CONTENT_CLIP_PADDING);
|
|
32099
|
+
}, [mapJson]);
|
|
32100
|
+
/** 孤立地块 id 列表(未连接到主地块的子地块) */
|
|
32101
|
+
const isolatedBoundaryIds = useIsolatedBoundaryIds(mapJson);
|
|
32102
|
+
/** 选中地块 id 列表(受控/非受控)与切换选中回调 */
|
|
32103
|
+
const { selectedIds, toggleBoundary, setSelectedIds } = useBoundarySelection({
|
|
32104
|
+
selectedBoundaryIds,
|
|
32105
|
+
defaultSelectedBoundaryIds,
|
|
32106
|
+
onSelectionChange,
|
|
32107
|
+
disabledBoundaryIds: isolatedBoundaryIds,
|
|
32108
|
+
});
|
|
32109
|
+
/** 当前 hover 的地块 id(受控/非受控)与切换 hover 回调 */
|
|
32110
|
+
const { hoveredId, setHoveredId } = useBoundaryHover({
|
|
32111
|
+
hoveredBoundaryId,
|
|
32112
|
+
defaultHoveredBoundaryId,
|
|
32113
|
+
onBoundaryHoverChange,
|
|
32114
|
+
});
|
|
32115
|
+
const handleHoverChange = useCallback((id) => {
|
|
32116
|
+
if (id != null && isolatedBoundaryIds.has(id))
|
|
32117
|
+
return;
|
|
32118
|
+
setHoveredId(id);
|
|
32119
|
+
}, [isolatedBoundaryIds, setHoveredId]);
|
|
32120
|
+
/** Map 模式:地块 polygon / 标签直接 onClick(无底图拖拽抢占) */
|
|
32121
|
+
const handleBoundaryClick = useCallback((id) => {
|
|
32122
|
+
if (isolatedBoundaryIds.has(id))
|
|
32123
|
+
return;
|
|
32124
|
+
toggleBoundary(id);
|
|
32125
|
+
}, [isolatedBoundaryIds, toggleBoundary]);
|
|
32126
|
+
/** Standalone 模式:由 viewport 统一处理点击(支持在地块上拖拽平移) */
|
|
32127
|
+
const handleViewportTap = useCallback((target) => {
|
|
32128
|
+
const boundaryEl = target.closest(`[${PARTITION_BOUNDARY_ID_ATTR}]`);
|
|
32129
|
+
if (!boundaryEl)
|
|
32130
|
+
return;
|
|
32131
|
+
const id = Number(boundaryEl.getAttribute(PARTITION_BOUNDARY_ID_ATTR));
|
|
32132
|
+
if (!Number.isFinite(id) || isolatedBoundaryIds.has(id))
|
|
32133
|
+
return;
|
|
32134
|
+
toggleBoundary(id);
|
|
32135
|
+
}, [isolatedBoundaryIds, toggleBoundary]);
|
|
32136
|
+
/**
|
|
32137
|
+
* Map 模式 overlay 状态(bounds / viewBox / fitBounds / overlayScale)。
|
|
32138
|
+
* 无 mapRef 时 hook 内部 enabled=false,返回值不参与渲染。
|
|
32139
|
+
*/
|
|
32140
|
+
const googleOverlay = useGoogleMapsOverlay({
|
|
32141
|
+
mapJson,
|
|
32142
|
+
mapRef,
|
|
32143
|
+
defaultTransform,
|
|
32144
|
+
originNorthRotate,
|
|
32145
|
+
platform,
|
|
32146
|
+
padding,
|
|
32147
|
+
});
|
|
32148
|
+
/** Standalone 才测量容器;Map 模式尺寸由 OverlayViewF onDraw 提供 */
|
|
32149
|
+
const { containerRef, layoutStyle, width: pxWidth, height: pxHeight, isReady, } = useContainerSize(isMapMode ? 0 : width, isMapMode ? 0 : height);
|
|
32150
|
+
useEffect(() => {
|
|
32151
|
+
if (!isMapMode && isReady) {
|
|
32152
|
+
onSizeChange?.(pxWidth, pxHeight);
|
|
32153
|
+
}
|
|
32154
|
+
}, [isMapMode, isReady, onSizeChange, pxWidth, pxHeight]);
|
|
32155
|
+
/** Standalone:平移/缩放 viewBox;Map 模式 enablePanZoom 关闭,仅保留 hook 结构 */
|
|
32156
|
+
const { viewBox: standaloneViewBox, gridViewBox, fitViewBox: standaloneFitViewBox, resetView: standaloneResetView, fitToView: standaloneFitToView, } = useMapViewport({
|
|
32157
|
+
mapJson,
|
|
32158
|
+
containerRef,
|
|
32159
|
+
width: pxWidth,
|
|
32160
|
+
height: pxHeight,
|
|
32161
|
+
padding,
|
|
32162
|
+
minZoom,
|
|
32163
|
+
maxZoom,
|
|
32164
|
+
refitOnResize,
|
|
32165
|
+
enablePanZoom: enablePanZoom && !isMapMode,
|
|
32166
|
+
onViewportTap: handleViewportTap,
|
|
32167
|
+
mapRotationDeg: standaloneActureRotate,
|
|
32168
|
+
});
|
|
32169
|
+
/** 按模式选取 viewBox / 尺寸 / 旋转 / 就绪状态(Map 与 Standalone 数据源不同) */
|
|
32170
|
+
const viewBox = isMapMode ? googleOverlay.svgViewBox : standaloneViewBox;
|
|
32171
|
+
const fitViewBox = isMapMode ? googleOverlay.fitViewBox : standaloneFitViewBox;
|
|
32172
|
+
const overlayWidth = isMapMode ? googleOverlay.overlayWidth : pxWidth;
|
|
32173
|
+
const overlayHeight = isMapMode ? googleOverlay.overlayHeight : pxHeight;
|
|
32174
|
+
const actureRotate = isMapMode ? googleOverlay.actureRotate : standaloneActureRotate;
|
|
32175
|
+
const isLayersReady = isMapMode ? googleOverlay.isOverlayReady : isReady;
|
|
32176
|
+
/**
|
|
32177
|
+
* Standalone:标签/充电桩置于 mapOverlayLayer,CSS rotate 与 MowerMapRenderer 一致。
|
|
32178
|
+
* Map 模式:旋转由 OverlayViewF 承担,此处不需要额外包裹层。
|
|
32179
|
+
*/
|
|
32180
|
+
const mapOverlayLayerStyle = useMemo(() => !isMapMode && actureRotate !== 0 ? { transform: `rotate(${actureRotate}deg)` } : undefined, [actureRotate, isMapMode]);
|
|
32181
|
+
/**
|
|
32182
|
+
* 分区名定位:
|
|
32183
|
+
* - Map 模式 overlay:相对 overlay 宽高百分比(与 BoundaryLabels 一致)
|
|
32184
|
+
* - Standalone meet:按 preserveAspectRatio 换算容器百分比
|
|
32185
|
+
*/
|
|
32186
|
+
const labelItems = usePartitionLabelItems(mapJson, viewBox, overlayWidth, overlayHeight, {
|
|
32187
|
+
positionMode: isMapMode ? 'overlay' : 'meet',
|
|
32188
|
+
});
|
|
32189
|
+
/**
|
|
32190
|
+
* 复用 ChannelClipPath / ChannelElement / CharginPile 所需的最小 Context
|
|
32191
|
+
* - svgViewBox:稳定 contentViewBox(见上)
|
|
32192
|
+
* - channelClipPathId:同页多实例时避免 SVG id 冲突
|
|
32193
|
+
* - platform:供子组件读取 Web / H5 差异
|
|
32194
|
+
* - Map 模式额外注入 mapRef / bounds / overlayScale(通道线宽等)
|
|
32195
|
+
*/
|
|
32196
|
+
const commonValue = useMemo(() => ({
|
|
32197
|
+
sn,
|
|
32198
|
+
platform,
|
|
32199
|
+
svgViewBox: contentViewBox,
|
|
32200
|
+
channelClipPathId,
|
|
32201
|
+
...(isMapMode && mapRef
|
|
32202
|
+
? {
|
|
32203
|
+
mapRef,
|
|
32204
|
+
bounds: googleOverlay.bounds,
|
|
32205
|
+
overlayScale: googleOverlay.overlayScale,
|
|
32206
|
+
}
|
|
32207
|
+
: {}),
|
|
32208
|
+
}), [
|
|
32209
|
+
sn,
|
|
32210
|
+
platform,
|
|
32211
|
+
contentViewBox,
|
|
32212
|
+
channelClipPathId,
|
|
32213
|
+
isMapMode,
|
|
32214
|
+
mapRef,
|
|
32215
|
+
googleOverlay.bounds,
|
|
32216
|
+
googleOverlay.overlayScale,
|
|
32217
|
+
]);
|
|
32218
|
+
const svgEditValue = useMemo(() => ({
|
|
32219
|
+
svgElementDatas,
|
|
32220
|
+
}), [svgElementDatas]);
|
|
32221
|
+
useImperativeHandle(ref, () => ({
|
|
32222
|
+
fitToView: isMapMode ? googleOverlay.fitToView : standaloneFitToView,
|
|
32223
|
+
resetView: isMapMode ? googleOverlay.resetView : standaloneResetView,
|
|
32224
|
+
getSelectedBoundaryIds: () => selectedIds,
|
|
32225
|
+
setSelectedBoundaryIds: setSelectedIds,
|
|
32226
|
+
getHoveredBoundaryId: () => hoveredId,
|
|
32227
|
+
setHoveredBoundaryId: setHoveredId,
|
|
32228
|
+
}), [
|
|
32229
|
+
googleOverlay.fitToView,
|
|
32230
|
+
googleOverlay.resetView,
|
|
32231
|
+
hoveredId,
|
|
32232
|
+
isMapMode,
|
|
32233
|
+
selectedIds,
|
|
32234
|
+
setHoveredId,
|
|
32235
|
+
setSelectedIds,
|
|
32236
|
+
standaloneFitToView,
|
|
32237
|
+
standaloneResetView,
|
|
32238
|
+
]);
|
|
32239
|
+
const containerStyle = useMemo(() => ({
|
|
32240
|
+
backgroundColor: isMapMode ? 'transparent' : resolvedBackgroundColor,
|
|
32241
|
+
...style,
|
|
32242
|
+
...(isMapMode ? {} : layoutStyle),
|
|
32243
|
+
}), [isMapMode, layoutStyle, resolvedBackgroundColor, style]);
|
|
32244
|
+
/** 地块 + 通道 SVG;Map 模式不在 SVG 内旋转(由 OverlayViewF rotate 处理) */
|
|
32245
|
+
const mapSvgLayer = viewBox && isLayersReady ? (jsx(PartitionPickerSvg, { svgElementDatas: svgElementDatas, viewBox: viewBox, fitViewBox: fitViewBox ?? viewBox, containerWidth: overlayWidth, containerHeight: overlayHeight, mapRotation: isMapMode ? 0 : actureRotate, boundaryColors: mergedPickerStyles.boundary, boundaryStrokePx: mergedPickerStyles.boundaryStrokePx, isolatedBoundaryIds: isolatedBoundaryIds, selectedIds: selectedIds, hoveredId: hoveredId, onHoverChange: handleHoverChange, onBoundaryClick: isMapMode ? handleBoundaryClick : undefined })) : null;
|
|
32246
|
+
/**
|
|
32247
|
+
* 分区标签 + 充电桩 HTML 层。
|
|
32248
|
+
* Map 模式:平铺于 overlay 根节点;Standalone:包在 mapOverlayLayer 内做 CSS 旋转。
|
|
32249
|
+
*/
|
|
32250
|
+
const overlayLayers = viewBox && isLayersReady ? (isMapMode ? (jsxs(Fragment, { children: [jsx(PartitionNameLabels, { items: labelItems, selectedIds: selectedIds, labelStyles: mergedPickerStyles.labels, labelMinScreenWidthPx: mergedPickerStyles.labelMinScreenWidthPx, platform: platform, showSelectionOrder: showSelectionOrder, rotation: actureRotate, onHoverChange: handleHoverChange, onBoundaryClick: handleBoundaryClick }), jsx(CharginPile, { viewBox: viewBox, rotation: actureRotate })] })) : (jsxs("div", { className: styles.mapOverlayLayer, style: mapOverlayLayerStyle, children: [jsx(PartitionNameLabels, { items: labelItems, selectedIds: selectedIds, labelStyles: mergedPickerStyles.labels, labelMinScreenWidthPx: mergedPickerStyles.labelMinScreenWidthPx, platform: platform, showSelectionOrder: showSelectionOrder, rotation: actureRotate, onHoverChange: handleHoverChange }), jsx(CharginPile, { viewBox: viewBox, rotation: actureRotate, viewRotationMode: "overlay-parent", containerWidth: overlayWidth, containerHeight: overlayHeight })] }))) : null;
|
|
32251
|
+
/** Map 模式:渲染 OverlayViewF,底图为 Google Maps */
|
|
32252
|
+
if (isMapMode && mapRef) {
|
|
32253
|
+
return (jsx(CommonContextProvider, { value: commonValue, children: jsx(SvgEditContextProvider, { value: svgEditValue, children: jsx(OverlayViewF, { map: mapRef, mapPaneName: "overlayMouseTarget", bounds: googleOverlay.bounds ?? undefined, rotate: actureRotate, zIndex: zIndex, onDraw: googleOverlay.handleOverlayDraw, children: jsxs("div", { className: `${styles.googleOverlayRoot} ${className || ''}`, style: containerStyle, onMouseLeave: () => setHoveredId(null), children: [mapSvgLayer, overlayLayers] }) }) }) }));
|
|
32254
|
+
}
|
|
32255
|
+
/** Standalone 模式:固定容器 + 网格底图 + 双层 SVG / HTML overlay */
|
|
32256
|
+
return (jsx(CommonContextProvider, { value: commonValue, children: jsx(SvgEditContextProvider, { value: svgEditValue, children: jsx("div", { ref: containerRef, className: `${styles.container} ${className || ''}`, "data-viewport-background": "true", style: containerStyle, onMouseLeave: () => setHoveredId(null), children: isLayersReady && (jsxs(Fragment, { children: [jsxs("div", { className: styles.mapSvgStack, children: [jsx(PartitionPickerGridSvg, { viewBox: gridViewBox, gridStyle: mergedPickerStyles.grid }), mapSvgLayer] }), overlayLayers] })) }) }) }));
|
|
32257
|
+
});
|
|
32258
|
+
MowerPartitionPickerMap.displayName = 'MowerPartitionPickerMap';
|
|
32259
|
+
|
|
32260
|
+
export { ALL_DIRECTION_SELECTED, AntennaData, BaseData, BoundaryData, BoundarySvgRender, ChannelData, ChargingPileData, CheckDoodleErrorType, CheckObstaclePointErrorType, ClickEventType, CreateStatus, DEFAULT_SCREEN_PADDING, DataType, DoodleData, MAX_DIRECTION_ANGLE, MIN_DIRECTION_ANGLE, MapType, MobileEditMode, MowGateData, MowerMapRenderer, MowerPartitionPickerMap, ObstacleData, PARTITION_PICKER_COLORS_H5, PARTITION_PICKER_COLORS_WEB, PARTITION_PICKER_LABEL_STYLES_H5, PARTITION_PICKER_LABEL_STYLES_WEB, PathData, PlatformType, RecordFunctionEnum, RecordTypeEnum, RenderType, SCALE_FACTOR, SvgParserNative, UnifiedMapDataProcessor, VisionOffData, calculateMapGpsCenter, estimateGpsFromMapBounds, getIsolatedBoundaryIds, getPartitionPickerColors, getPartitionPickerLabelStyles, getValidGpsBounds, initBoundary, initChannel, initDoodle, initObstacle, initVisionOff, normalizeScreenPadding, resolvePartitionPickerStyles, restorePointsFormat, signGoogleStaticMapUrl, signGoogleStaticMapUrlSync };
|