@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.
Files changed (72) hide show
  1. package/README.md +0 -39
  2. package/dist/config/constants.d.ts +3 -0
  3. package/dist/config/constants.d.ts.map +1 -1
  4. package/dist/config/styles.d.ts.map +1 -1
  5. package/dist/context/common.d.ts +17 -0
  6. package/dist/context/common.d.ts.map +1 -1
  7. package/dist/index.d.ts +8 -1
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.esm.js +2524 -413
  10. package/dist/index.js +2535 -411
  11. package/dist/render/BoundarySvgRender.d.ts +3 -1
  12. package/dist/render/BoundarySvgRender.d.ts.map +1 -1
  13. package/dist/render/MowerMapRenderer.d.ts.map +1 -1
  14. package/dist/render/MowerPartitionPickerMap.d.ts +17 -0
  15. package/dist/render/MowerPartitionPickerMap.d.ts.map +1 -0
  16. package/dist/render/antennas/useAntennaInfo.d.ts.map +1 -1
  17. package/dist/render/boundaryLabels/index.d.ts +1 -1
  18. package/dist/render/boundaryLabels/index.d.ts.map +1 -1
  19. package/dist/render/boundaryLabels/useBoundaryLabels.d.ts.map +1 -1
  20. package/dist/render/charginPile/index.d.ts +12 -1
  21. package/dist/render/charginPile/index.d.ts.map +1 -1
  22. package/dist/render/charginPile/useChargingPile.d.ts +11 -1
  23. package/dist/render/charginPile/useChargingPile.d.ts.map +1 -1
  24. package/dist/render/partitionPicker/constants.d.ts +165 -0
  25. package/dist/render/partitionPicker/constants.d.ts.map +1 -0
  26. package/dist/render/partitionPicker/hooks/useBoundaryHover.d.ts +18 -0
  27. package/dist/render/partitionPicker/hooks/useBoundaryHover.d.ts.map +1 -0
  28. package/dist/render/partitionPicker/hooks/useBoundarySelection.d.ts +20 -0
  29. package/dist/render/partitionPicker/hooks/useBoundarySelection.d.ts.map +1 -0
  30. package/dist/render/partitionPicker/hooks/useContainerSize.d.ts +16 -0
  31. package/dist/render/partitionPicker/hooks/useContainerSize.d.ts.map +1 -0
  32. package/dist/render/partitionPicker/hooks/useGoogleMapsOverlay.d.ts +31 -0
  33. package/dist/render/partitionPicker/hooks/useGoogleMapsOverlay.d.ts.map +1 -0
  34. package/dist/render/partitionPicker/hooks/useIsolatedBoundaryIds.d.ts +6 -0
  35. package/dist/render/partitionPicker/hooks/useIsolatedBoundaryIds.d.ts.map +1 -0
  36. package/dist/render/partitionPicker/hooks/useMapViewport.d.ts +53 -0
  37. package/dist/render/partitionPicker/hooks/useMapViewport.d.ts.map +1 -0
  38. package/dist/render/partitionPicker/hooks/usePartitionLabelItems.d.ts +22 -0
  39. package/dist/render/partitionPicker/hooks/usePartitionLabelItems.d.ts.map +1 -0
  40. package/dist/render/partitionPicker/index.d.ts +9 -0
  41. package/dist/render/partitionPicker/index.d.ts.map +1 -0
  42. package/dist/render/partitionPicker/layers/MapGridBackground.d.ts +22 -0
  43. package/dist/render/partitionPicker/layers/MapGridBackground.d.ts.map +1 -0
  44. package/dist/render/partitionPicker/layers/PartitionNameLabels.d.ts +21 -0
  45. package/dist/render/partitionPicker/layers/PartitionNameLabels.d.ts.map +1 -0
  46. package/dist/render/partitionPicker/layers/PartitionPickerGridSvg.d.ts +10 -0
  47. package/dist/render/partitionPicker/layers/PartitionPickerGridSvg.d.ts.map +1 -0
  48. package/dist/render/partitionPicker/layers/PartitionPickerSvg.d.ts +24 -0
  49. package/dist/render/partitionPicker/layers/PartitionPickerSvg.d.ts.map +1 -0
  50. package/dist/render/partitionPicker/layers/SelectableBoundaryElement.d.ts +34 -0
  51. package/dist/render/partitionPicker/layers/SelectableBoundaryElement.d.ts.map +1 -0
  52. package/dist/render/partitionPicker/types.d.ts +140 -0
  53. package/dist/render/partitionPicker/types.d.ts.map +1 -0
  54. package/dist/render/partitionPicker/viewportUtils.d.ts +102 -0
  55. package/dist/render/partitionPicker/viewportUtils.d.ts.map +1 -0
  56. package/dist/render/svgElement/ChannelClipPath/constants.d.ts +9 -0
  57. package/dist/render/svgElement/ChannelClipPath/constants.d.ts.map +1 -0
  58. package/dist/render/svgElement/ChannelElement/index.d.ts.map +1 -1
  59. package/dist/render/svgElement/ObstacleElement/index.d.ts.map +1 -1
  60. package/dist/render/svgElement/PolygonELement/components/Magnifier/index.d.ts.map +1 -1
  61. package/dist/render/svgElement/PolygonELement/components/Tooltip.d.ts.map +1 -1
  62. package/dist/render/svgElement/PolygonELement/index.d.ts.map +1 -1
  63. package/dist/render/svgElement/TransformWrapper/DoodleTransformWrapper/DoodleTransformWrapper.d.ts.map +1 -1
  64. package/dist/render/svgElement/TransformWrapper/VisionOffTransformWrapper/VisionOffTransformWrapper.d.ts.map +1 -1
  65. package/dist/types/renderer.d.ts +33 -0
  66. package/dist/types/renderer.d.ts.map +1 -1
  67. package/dist/utils/dateTimeUtils.d.ts +6 -2
  68. package/dist/utils/dateTimeUtils.d.ts.map +1 -1
  69. package/dist/utils/googleStaticMapSign.d.ts +17 -0
  70. package/dist/utils/googleStaticMapSign.d.ts.map +1 -0
  71. package/dist/utils/mapBounds.d.ts.map +1 -1
  72. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2109,7 +2109,7 @@ var ellipsoids = {
2109
2109
  }
2110
2110
  };
2111
2111
 
2112
- const WGS84$1 = ellipsoids.WGS84; // default ellipsoid
2112
+ const WGS84$2 = ellipsoids.WGS84; // default ellipsoid
2113
2113
 
2114
2114
  function eccentricity(a, b, rf, R_A) {
2115
2115
  var a2 = a * a; // used in geocentric
@@ -2134,7 +2134,7 @@ function sphere(a, b, rf, ellps, sphere) {
2134
2134
  if (!a) { // do we have an ellipsoid?
2135
2135
  var ellipse = match(ellipsoids, ellps);
2136
2136
  if (!ellipse) {
2137
- ellipse = WGS84$1;
2137
+ ellipse = WGS84$2;
2138
2138
  }
2139
2139
  a = ellipse.a;
2140
2140
  b = ellipse.b;
@@ -10633,9 +10633,16 @@ var SvgMapOpacity;
10633
10633
  * 地图渲染相关常量配置
10634
10634
  */
10635
10635
  // 默认坐标
10636
+ // export const DEFAULT_COORDINATES = {
10637
+ // sw: [116.071044, 40.102099],
10638
+ // ne: [116.0734, 40.103901],
10639
+ // };
10640
+ /** 无效 GPS 时的兜底中心点 [lng, lat] */
10641
+ const DEFAULT_GPS_CENTER = [0, 0];
10642
+ /** 无 map_width/map_height 时的默认 GPS 边界 [lng, lat](中心约 (0,0)) */
10636
10643
  const DEFAULT_COORDINATES = {
10637
- sw: [116.071044, 40.102099],
10638
- ne: [116.0734, 40.103901],
10644
+ sw: [-225e-6, -225e-6],
10645
+ ne: [0.000225, 0.000225],
10639
10646
  };
10640
10647
  /**
10641
10648
  * 缩放因子 - 将米转换为像素
@@ -13925,6 +13932,23 @@ exports.CheckDoodleErrorType = void 0;
13925
13932
  // doodle与其他元素距离过近
13926
13933
  CheckDoodleErrorType["DOODLE_DISTANCE_TOO_CLOSE_TO_OTHER_ELEMENT"] = "doodle_distance_too_close_to_other_element";
13927
13934
  })(exports.CheckDoodleErrorType || (exports.CheckDoodleErrorType = {}));
13935
+ exports.MapType = void 0;
13936
+ (function (MapType) {
13937
+ // 普通地图
13938
+ MapType["NORMAL"] = "normal";
13939
+ /**
13940
+ * 主要是用在地理围栏相关的
13941
+ * 此地图没有分区名称气泡,没有孤立子地块,没有设备图标,没有天线
13942
+ */
13943
+ MapType["GEO_FENCE"] = "geo-fence";
13944
+ })(exports.MapType || (exports.MapType = {}));
13945
+ var HoverType;
13946
+ (function (HoverType) {
13947
+ HoverType["Translate"] = "translate";
13948
+ HoverType["Scale"] = "scale";
13949
+ HoverType["Rotate"] = "rotate";
13950
+ HoverType["Delete"] = "delete";
13951
+ })(HoverType || (HoverType = {}));
13928
13952
 
13929
13953
  var RealTimeDataType;
13930
13954
  (function (RealTimeDataType) {
@@ -18076,6 +18100,8 @@ const OBSTACLE_STYLES = {
18076
18100
  hoverFillColor: 'rgba(255, 90, 0, 0.4)',
18077
18101
  lineWidth: DEFAULT_LINE_WIDTHS.OBSTACLE,
18078
18102
  opacity: DEFAULT_OPACITIES.FULL,
18103
+ disabledLineColor: 'rgba(255, 90, 0, 0.4)',
18104
+ disabledFillColor: 'rgba(255, 90, 0, 0.1)',
18079
18105
  };
18080
18106
  const CHARGING_PILE_STYLES = {
18081
18107
  lineColor: 'blue',
@@ -18557,6 +18583,38 @@ function calculateBoundaryBoundsCenter(mapData) {
18557
18583
  boundaryBounds.minY + (boundaryBounds.maxY - boundaryBounds.minY) / 2,
18558
18584
  ];
18559
18585
  }
18586
+ /**
18587
+ * 由中心点与物理尺寸(米)计算 GPS 边界 [lng, lat]
18588
+ */
18589
+ function computeGpsBoundsFromCenterAndSize(centerLng, centerLat, mapWidthMeters, mapHeightMeters) {
18590
+ const METERS_PER_DEGREE_LAT = EQUATORIAL_CIRCUMFERENCE / 360;
18591
+ const METERS_PER_DEGREE_LNG = METERS_PER_DEGREE_LAT * Math.cos((centerLat * Math.PI) / 180);
18592
+ const swLat = centerLat - mapHeightMeters / 2 / METERS_PER_DEGREE_LAT;
18593
+ const swLng = centerLng - mapWidthMeters / 2 / METERS_PER_DEGREE_LNG;
18594
+ const neLat = centerLat + mapHeightMeters / 2 / METERS_PER_DEGREE_LAT;
18595
+ const neLng = centerLng + mapWidthMeters / 2 / METERS_PER_DEGREE_LNG;
18596
+ return {
18597
+ sw: [swLng, swLat],
18598
+ ne: [neLng, neLat],
18599
+ };
18600
+ }
18601
+ /**
18602
+ * GPS 无效时:中心 (0,0) + JSON 的 map_width/map_height;无尺寸则用 DEFAULT_COORDINATES
18603
+ */
18604
+ function getFallbackGpsBounds(mapData) {
18605
+ const [centerLng, centerLat] = DEFAULT_GPS_CENTER;
18606
+ const hasMapSize = typeof mapData.map_width === 'number' &&
18607
+ typeof mapData.map_height === 'number' &&
18608
+ mapData.map_width > 0 &&
18609
+ mapData.map_height > 0;
18610
+ if (hasMapSize) {
18611
+ return computeGpsBoundsFromCenterAndSize(centerLng, centerLat, mapData.map_width, mapData.map_height);
18612
+ }
18613
+ return {
18614
+ sw: [...DEFAULT_COORDINATES.sw],
18615
+ ne: [...DEFAULT_COORDINATES.ne],
18616
+ };
18617
+ }
18560
18618
  /**
18561
18619
  * 检查GPS坐标是否有效
18562
18620
  */
@@ -18592,10 +18650,10 @@ function estimateGpsFromMapBounds(mapData) {
18592
18650
  const mapWidthMeters = maxXMeters - minXMeters;
18593
18651
  const mapHeightMeters = maxYMeters - minYMeters;
18594
18652
  // 凤凰岭的GPS坐标作为地图中心点
18595
- const centerLat = 40.103;
18596
- const centerLng = 116.072222;
18597
- // const centerLat = 40.03806686401367;
18598
- // const centerLng = 116.35540771484375;
18653
+ // const centerLat = 40.103;
18654
+ // const centerLng = 116.072222;
18655
+ const centerLat = 0;
18656
+ const centerLng = 0;
18599
18657
  // 精确的坐标转换常数
18600
18658
  // 1度纬度 = 约111,320米(在地球上任何地方都基本相同)
18601
18659
  // 1度经度 = 约111,320 * cos(纬度) 米(随纬度变化)
@@ -18661,39 +18719,44 @@ function calculateMapGpsCenter(mapData) {
18661
18719
  }
18662
18720
  }
18663
18721
  // 如果有西南角和东北角坐标,检查其有效性并计算中心点
18664
- if (mapData.sw_gps &&
18665
- mapData.ne_gps &&
18666
- mapData.sw_gps.length >= 2 &&
18667
- mapData.ne_gps.length >= 2) {
18668
- const swLat = mapData.sw_gps[1];
18669
- const swLng = mapData.sw_gps[0];
18670
- const neLat = mapData.ne_gps[1];
18671
- const neLng = mapData.ne_gps[0];
18672
- // 检查边界坐标的有效性
18673
- if (isValidGpsCoordinate(swLat, swLng) && isValidGpsCoordinate(neLat, neLng)) {
18674
- return {
18675
- lat: (swLat + neLat) / 2,
18676
- lng: (swLng + neLng) / 2,
18677
- };
18678
- }
18679
- else {
18680
- console.warn('sw_gps或ne_gps坐标无效:', {
18681
- sw: { lat: swLat, lng: swLng },
18682
- ne: { lat: neLat, lng: neLng },
18683
- });
18684
- }
18685
- }
18722
+ // if (
18723
+ // mapData.sw_gps &&
18724
+ // mapData.ne_gps &&
18725
+ // mapData.sw_gps.length >= 2 &&
18726
+ // mapData.ne_gps.length >= 2
18727
+ // ) {
18728
+ // const swLat = mapData.sw_gps[1];
18729
+ // const swLng = mapData.sw_gps[0];
18730
+ // const neLat = mapData.ne_gps[1];
18731
+ // const neLng = mapData.ne_gps[0];
18732
+ // // 检查边界坐标的有效性
18733
+ // if (isValidGpsCoordinate(swLat, swLng) && isValidGpsCoordinate(neLat, neLng)) {
18734
+ // return {
18735
+ // lat: (swLat + neLat) / 2,
18736
+ // lng: (swLng + neLng) / 2,
18737
+ // };
18738
+ // } else {
18739
+ // console.warn('sw_gps或ne_gps坐标无效:', {
18740
+ // sw: { lat: swLat, lng: swLng },
18741
+ // ne: { lat: neLat, lng: neLng },
18742
+ // });
18743
+ // }
18744
+ // }
18686
18745
  // 尝试从地图几何边界估算GPS坐标
18687
- const estimatedBounds = estimateGpsFromMapBounds(mapData);
18688
- if (estimatedBounds) {
18689
- // 从估算的边界计算中心点
18690
- const centerLat = (estimatedBounds.sw[1] + estimatedBounds.ne[1]) / 2;
18691
- const centerLng = (estimatedBounds.sw[0] + estimatedBounds.ne[0]) / 2;
18692
- return { lat: centerLat, lng: centerLng };
18693
- }
18746
+ // const estimatedBounds = estimateGpsFromMapBounds(mapData);
18747
+ // if (estimatedBounds) {
18748
+ // // 从估算的边界计算中心点
18749
+ // const centerLat = (estimatedBounds.sw[1] + estimatedBounds.ne[1]) / 2;
18750
+ // const centerLng = (estimatedBounds.sw[0] + estimatedBounds.ne[0]) / 2;
18751
+ // return { lat: centerLat, lng: centerLng };
18752
+ // }
18753
+ // return {
18754
+ // lat: 39.9042, // 北京纬度
18755
+ // lng: 116.4074, // 北京经度
18756
+ // };
18694
18757
  return {
18695
- lat: 39.9042, // 北京纬度
18696
- lng: 116.4074, // 北京经度
18758
+ lat: DEFAULT_GPS_CENTER[1],
18759
+ lng: DEFAULT_GPS_CENTER[0],
18697
18760
  };
18698
18761
  }
18699
18762
  // 旋转坐标点
@@ -18723,50 +18786,22 @@ const getValidGpsBounds = (mapData, rotation = 0) => {
18723
18786
  mapData.map_width > 0 &&
18724
18787
  mapData.map_height > 0) {
18725
18788
  const [centerLng, centerLat] = mapData.center_gps;
18726
- const mapWidthMeters = mapData.map_width;
18727
- const mapHeightMeters = mapData.map_height;
18728
- // 精确的坐标转换常数(基于 WGS84 标准 - GPS 国际标准)
18729
- // WGS84 赤道半径 = 6,378,137 米
18730
- // 地球赤道周长 = 2 × π × 6,378,137 ≈ 40,075,017 米
18731
- // 1度纬度 = 40075017 / 360 ≈ 111,319.49 米(在地球上任何地方都基本相同)
18732
- // 1度经度 = (40075017 / 360) * cos(纬度) 米(随纬度变化)
18733
- const METERS_PER_DEGREE_LAT = EQUATORIAL_CIRCUMFERENCE / 360;
18734
- const METERS_PER_DEGREE_LNG = METERS_PER_DEGREE_LAT * Math.cos((centerLat * Math.PI) / 180);
18735
- // 计算SW(西南角)GPS坐标:从中心点减去半个地图的宽度和高度
18736
- const swLat = centerLat - mapHeightMeters / 2 / METERS_PER_DEGREE_LAT;
18737
- const swLng = centerLng - mapWidthMeters / 2 / METERS_PER_DEGREE_LNG;
18738
- // 计算NE(东北角)GPS坐标:从中心点加上半个地图的宽度和高度
18739
- const neLat = centerLat + mapHeightMeters / 2 / METERS_PER_DEGREE_LAT;
18740
- const neLng = centerLng + mapWidthMeters / 2 / METERS_PER_DEGREE_LNG;
18741
- bounds = {
18742
- sw: [swLng, swLat],
18743
- ne: [neLng, neLat],
18744
- };
18745
- }
18746
- else if (isValidGpsCoordinate(mapData.sw_gps?.[1], mapData.sw_gps?.[0]) &&
18747
- isValidGpsCoordinate(mapData.ne_gps?.[1], mapData.ne_gps?.[0])) {
18748
- // 降级方案:使用地图数据中的GPS坐标
18749
- console.warn('center_gps 或 map_width/map_height 不可用,使用 sw_gps 和 ne_gps');
18750
- bounds = {
18751
- sw: mapData.sw_gps,
18752
- ne: mapData.ne_gps,
18753
- };
18754
- }
18789
+ bounds = computeGpsBoundsFromCenterAndSize(centerLng, centerLat, mapData.map_width, mapData.map_height);
18790
+ }
18791
+ // else if (
18792
+ // isValidGpsCoordinate(mapData.sw_gps?.[1], mapData.sw_gps?.[0]) &&
18793
+ // isValidGpsCoordinate(mapData.ne_gps?.[1], mapData.ne_gps?.[0])
18794
+ // ) {
18795
+ // // 降级方案:使用地图数据中的GPS坐标
18796
+ // console.warn('center_gps map_width/map_height 不可用,使用 sw_gps 和 ne_gps');
18797
+ // bounds = {
18798
+ // sw: mapData.sw_gps,
18799
+ // ne: mapData.ne_gps,
18800
+ // };
18801
+ // }
18755
18802
  else {
18756
- // 如果GPS坐标无效,尝试从地图几何数据估算
18757
- const { sw, ne } = estimateGpsFromMapBounds(mapData);
18758
- if (sw && ne) {
18759
- console.warn('GPS坐标无效,使用地图几何数据估算边界:', sw, ne);
18760
- bounds = {
18761
- sw: [sw[0], sw[1]],
18762
- ne: [ne[0], ne[1]],
18763
- };
18764
- }
18765
- else {
18766
- // 最后的fallback:使用默认坐标
18767
- console.warn('无法获取有效的GPS边界,使用默认坐标');
18768
- bounds = DEFAULT_COORDINATES;
18769
- }
18803
+ console.warn('无法获取有效的GPS边界,使用 (0,0) 中心 + map_width/map_height 或 DEFAULT_COORDINATES');
18804
+ bounds = getFallbackGpsBounds(mapData);
18770
18805
  }
18771
18806
  // 如果有旋转角度,计算旋转后的边界
18772
18807
  if (rotation !== 0) {
@@ -19199,7 +19234,7 @@ function getMowerImage(positonConfig, mowerIconConfig, modelType, hasEdger) {
19199
19234
  const mowerImage = mowerIconConfig?.mapImgUrl || getMowerImageByModal(model, hasEdger);
19200
19235
  const disabledImage = mowerIconConfig?.mapDisabledUrl || getDisabledMowerImageByModal(model);
19201
19236
  const noPositionImage = mowerIconConfig?.mapNoPositionUrl || getNoPositionMowerImageByModal(model);
19202
- const positonOutOfRange = isOutOfRange(positonConfig);
19237
+ // const positonOutOfRange = isOutOfRange(positonConfig);
19203
19238
  const positionValid = isInvalidPosition(positonConfig);
19204
19239
  const isOffLine = state === RobotStatus.DISCONNECTED;
19205
19240
  const isInPark = state === RobotStatus.CHARGING || state === RobotStatus.PARKED;
@@ -19209,7 +19244,7 @@ function getMowerImage(positonConfig, mowerIconConfig, modelType, hasEdger) {
19209
19244
  if (isInPark) {
19210
19245
  return mowerImage;
19211
19246
  }
19212
- if (positionValid || positonOutOfRange) {
19247
+ if (positionValid) {
19213
19248
  return noPositionImage;
19214
19249
  }
19215
19250
  else {
@@ -19233,12 +19268,6 @@ function isInvalidPosition(positonConfig) {
19233
19268
  isNoPosture(positonConfig.postureY) &&
19234
19269
  isNoPosture(positonConfig.postureTheta));
19235
19270
  }
19236
- function isOutOfRange(positonConfig) {
19237
- return (positonConfig.postureX != null &&
19238
- Math.abs(positonConfig.postureX) > 1000 &&
19239
- positonConfig.postureY != null &&
19240
- Math.abs(positonConfig.postureY) > 1000);
19241
- }
19242
19271
  function getMindistanceByModel(modelType) {
19243
19272
  if (isIModel(modelType)) {
19244
19273
  return I_MIN_DISTANCE;
@@ -19535,8 +19564,16 @@ function formatTime(date) {
19535
19564
  * @param date 目标日期
19536
19565
  * @returns 星期几的英文缩写
19537
19566
  */
19538
- function getWeekdayAbbr(date) {
19539
- const weekdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
19567
+ function getWeekdayAbbr(date, locale) {
19568
+ const weekdays = [
19569
+ locale?.['map.renderer.weekday.sun'] || 'Sun',
19570
+ locale?.['map.renderer.weekday.mon'] || 'Mon',
19571
+ locale?.['map.renderer.weekday.tue'] || 'Tue',
19572
+ locale?.['map.renderer.weekday.wed'] || 'Wed',
19573
+ locale?.['map.renderer.weekday.thu'] || 'Thu',
19574
+ locale?.['map.renderer.weekday.fri'] || 'Fri',
19575
+ locale?.['map.renderer.weekday.sat'] || 'Sat'
19576
+ ];
19540
19577
  return weekdays[date.getDay()];
19541
19578
  }
19542
19579
  /**
@@ -19550,7 +19587,7 @@ function getWeekdayAbbr(date) {
19550
19587
  * @param timestamp 时间戳(秒)
19551
19588
  * @returns 格式化后的日期文本
19552
19589
  */
19553
- function formatBoundaryDateText(timestamp) {
19590
+ function formatBoundaryDateText(timestamp, locale) {
19554
19591
  if (!timestamp || timestamp <= 0) {
19555
19592
  return '-';
19556
19593
  }
@@ -19558,15 +19595,15 @@ function formatBoundaryDateText(timestamp) {
19558
19595
  const timeStr = formatTime(date);
19559
19596
  // 判断是否为今天
19560
19597
  if (isToday(date)) {
19561
- return `Today ${timeStr}`;
19598
+ return `${locale?.['map.renderer.today'] || 'Today'} ${timeStr}`;
19562
19599
  }
19563
19600
  // 判断是否为昨天
19564
19601
  if (isYesterday(date)) {
19565
- return `Yesterday ${timeStr}`;
19602
+ return `${locale?.['map.renderer.yesterday'] || 'Yesterday'} ${timeStr}`;
19566
19603
  }
19567
19604
  // 判断是否为本周内
19568
19605
  if (isThisWeek(date)) {
19569
- const weekdayAbbr = getWeekdayAbbr(date);
19606
+ const weekdayAbbr = getWeekdayAbbr(date, locale);
19570
19607
  return `${weekdayAbbr} ${timeStr}`;
19571
19608
  }
19572
19609
  // 其他情况显示完整日期
@@ -20058,12 +20095,12 @@ function styleInject(css, ref) {
20058
20095
  }
20059
20096
  }
20060
20097
 
20061
- var css_248z$7 = ".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}";
20062
- var styles$7 = {"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"};
20063
- styleInject(css_248z$7);
20098
+ 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}";
20099
+ 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"};
20100
+ styleInject(css_248z$9);
20064
20101
 
20065
20102
  const RotateHandle = ({ onRotateStart, isRotating }) => {
20066
- return (jsxRuntime.jsx("div", { className: styles$7.rotate, onMouseDown: (e) => onRotateStart(e), onTouchStart: (e) => onRotateStart(e), style: { cursor: isRotating ? 'grabbing' : 'grab' }, dangerouslySetInnerHTML: { __html: DEFAULT_ROTATE_ICON } }));
20103
+ return (jsxRuntime.jsx("div", { className: styles$9.rotate, onMouseDown: (e) => onRotateStart(e), onTouchStart: (e) => onRotateStart(e), style: { cursor: isRotating ? 'grabbing' : 'grab' }, dangerouslySetInnerHTML: { __html: DEFAULT_ROTATE_ICON } }));
20067
20104
  };
20068
20105
 
20069
20106
  /**
@@ -20140,7 +20177,7 @@ const DragHandle = ({ onDragStart, isDragging }) => {
20140
20177
  if (!isMobileDevice()) {
20141
20178
  return null;
20142
20179
  }
20143
- return (jsxRuntime.jsx("div", { className: styles$7.move, onMouseDown: (e) => {
20180
+ return (jsxRuntime.jsx("div", { className: styles$9.move, onMouseDown: (e) => {
20144
20181
  onDragStart(e);
20145
20182
  }, onTouchStart: (e) => onDragStart(e), onMouseUp: (e) => {
20146
20183
  // onDragEnd(e);
@@ -20172,7 +20209,7 @@ const MapDrag = ({ map: _map, dragCallbacks, onBoundaryLabelsCollapse, onTransfo
20172
20209
  if (!isDragMap) {
20173
20210
  return null;
20174
20211
  }
20175
- return (jsxRuntime.jsxs("div", { ref: containerRef, className: styles$7.edit, style: { cursor: isDragging ? 'grabbing' : 'move' }, children: [jsxRuntime.jsx("div", { className: styles$7.border }), canRotateMap && jsxRuntime.jsx(RotateHandle, { onRotateStart: handleRotateStart, isRotating: isRotating }), jsxRuntime.jsx("div", { className: styles$7.drag, onMouseDown: handleDragStartEvent, onTouchStart: handleDragStartEvent }), jsxRuntime.jsx(DragHandle, { onDragStart: handleDragStartEvent, isDragging: isDragging })] }));
20212
+ return (jsxRuntime.jsxs("div", { ref: containerRef, className: styles$9.edit, style: { cursor: isDragging ? 'grabbing' : 'move' }, children: [jsxRuntime.jsx("div", { className: styles$9.border }), canRotateMap && jsxRuntime.jsx(RotateHandle, { onRotateStart: handleRotateStart, isRotating: isRotating }), jsxRuntime.jsx("div", { className: styles$9.drag, onMouseDown: handleDragStartEvent, onTouchStart: handleDragStartEvent }), jsxRuntime.jsx(DragHandle, { onDragStart: handleDragStartEvent, isDragging: isDragging })] }));
20176
20213
  };
20177
20214
 
20178
20215
  const SvgEditContext = React.createContext({
@@ -22688,13 +22725,17 @@ const CommonContext = React.createContext({
22688
22725
  overlayScale: 1,
22689
22726
  showStraddleBoundaryBorder: true,
22690
22727
  googleMapStaticApiKey: '',
22728
+ googleMapStaticUrlSigningSecret: '',
22729
+ locale: {},
22730
+ signGoogleStaticMapUrl: undefined,
22691
22731
  onH5FirstSelectObstaclePoint: undefined,
22732
+ channelClipPathId: '',
22692
22733
  });
22693
22734
  const CommonContextProvider = CommonContext.Provider;
22694
22735
  const useCommonContext = () => React.useContext(CommonContext);
22695
22736
 
22696
22737
  const ChannelClipPath = React.memo(() => {
22697
- const { svgViewBox, sn } = useCommonContext();
22738
+ const { svgViewBox, channelClipPathId } = useCommonContext();
22698
22739
  const { svgElementDatas } = useSvgEditContext();
22699
22740
  const boundaryData = svgElementDatas?.[exports.DataType.BOUNDARY] || [];
22700
22741
  const [minX, minY, maxX, maxY] = React.useMemo(() => {
@@ -22873,7 +22914,7 @@ const ChannelClipPath = React.memo(() => {
22873
22914
  });
22874
22915
  return baseD;
22875
22916
  }, [boundaryData, minX, minY, maxX, maxY]);
22876
- return (jsxRuntime.jsx("defs", { children: jsxRuntime.jsx("clipPath", { id: `channel-clip-path-${sn}`, fillRule: "evenodd", children: jsxRuntime.jsx("path", { d: d }) }) }));
22917
+ return (jsxRuntime.jsx("defs", { children: jsxRuntime.jsx("clipPath", { id: channelClipPathId, fillRule: "evenodd", children: jsxRuntime.jsx("path", { d: d }) }) }));
22877
22918
  });
22878
22919
 
22879
22920
  /**
@@ -23169,11 +23210,440 @@ const MapEditContext = React.createContext({
23169
23210
  const MapEditContextProvider = MapEditContext.Provider;
23170
23211
  const useMapEditContext = () => React.useContext(MapEditContext);
23171
23212
 
23213
+ /**
23214
+ * Google Maps Static API 数字签名
23215
+ * @see https://developers.google.com/maps/documentation/maps-static/digital-signature
23216
+ *
23217
+ * 使用纯 JS HMAC-SHA1,兼容 H5 WebView / 非 HTTPS 等无 crypto.subtle 的环境。
23218
+ */
23219
+ /// <reference lib="dom" />
23220
+ function removeWebSafe(encoded) {
23221
+ return encoded.replace(/-/g, '+').replace(/_/g, '/');
23222
+ }
23223
+ function makeWebSafe(encoded) {
23224
+ return encoded.replace(/\+/g, '-').replace(/\//g, '_');
23225
+ }
23226
+ function decodeUrlSigningSecret(secret) {
23227
+ let base64 = removeWebSafe(secret);
23228
+ const remainder = base64.length % 4;
23229
+ if (remainder) {
23230
+ base64 += '='.repeat(4 - remainder);
23231
+ }
23232
+ const binary = globalThis.atob(base64);
23233
+ const bytes = new Uint8Array(binary.length);
23234
+ for (let i = 0; i < binary.length; i++) {
23235
+ bytes[i] = binary.charCodeAt(i);
23236
+ }
23237
+ return bytes;
23238
+ }
23239
+ function bytesToWebSafeBase64(bytes) {
23240
+ let binary = '';
23241
+ for (let i = 0; i < bytes.length; i++) {
23242
+ binary += String.fromCharCode(bytes[i]);
23243
+ }
23244
+ return makeWebSafe(globalThis.btoa(binary));
23245
+ }
23246
+ function utf8Encode(text) {
23247
+ if (typeof globalThis.TextEncoder !== 'undefined') {
23248
+ return new globalThis.TextEncoder().encode(text);
23249
+ }
23250
+ // Static Map URL 仅含 ASCII;兼容无 TextEncoder 的旧 WebView / Jest jsdom
23251
+ const bytes = new Uint8Array(text.length);
23252
+ for (let i = 0; i < text.length; i++) {
23253
+ const code = text.charCodeAt(i);
23254
+ if (code > 127) {
23255
+ throw new Error('signGoogleStaticMapUrl: non-ASCII URL is not supported in this environment');
23256
+ }
23257
+ bytes[i] = code;
23258
+ }
23259
+ return bytes;
23260
+ }
23261
+ function concatBytes(...parts) {
23262
+ const total = parts.reduce((sum, part) => sum + part.length, 0);
23263
+ const out = new Uint8Array(total);
23264
+ let offset = 0;
23265
+ for (const part of parts) {
23266
+ out.set(part, offset);
23267
+ offset += part.length;
23268
+ }
23269
+ return out;
23270
+ }
23271
+ function leftRotate(value, amount) {
23272
+ return ((value << amount) | (value >>> (32 - amount))) >>> 0;
23273
+ }
23274
+ function sha1(message) {
23275
+ const originalLength = message.length;
23276
+ const bitLength = originalLength * 8;
23277
+ const paddedLength = Math.ceil((originalLength + 9) / 64) * 64;
23278
+ const padded = new Uint8Array(paddedLength);
23279
+ padded.set(message);
23280
+ padded[originalLength] = 0x80;
23281
+ const view = new DataView(padded.buffer);
23282
+ view.setUint32(paddedLength - 4, bitLength >>> 0, false);
23283
+ view.setUint32(paddedLength - 8, Math.floor(bitLength / 0x100000000), false);
23284
+ let h0 = 0x67452301;
23285
+ let h1 = 0xefcdab89;
23286
+ let h2 = 0x98badcfe;
23287
+ let h3 = 0x10325476;
23288
+ let h4 = 0xc3d2e1f0;
23289
+ const w = new Uint32Array(80);
23290
+ for (let offset = 0; offset < paddedLength; offset += 64) {
23291
+ for (let i = 0; i < 16; i++) {
23292
+ w[i] = view.getUint32(offset + i * 4, false);
23293
+ }
23294
+ for (let i = 16; i < 80; i++) {
23295
+ w[i] = leftRotate(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1);
23296
+ }
23297
+ let a = h0;
23298
+ let b = h1;
23299
+ let c = h2;
23300
+ let d = h3;
23301
+ let e = h4;
23302
+ for (let i = 0; i < 80; i++) {
23303
+ let f;
23304
+ let k;
23305
+ if (i < 20) {
23306
+ f = (b & c) | (~b & d);
23307
+ k = 0x5a827999;
23308
+ }
23309
+ else if (i < 40) {
23310
+ f = b ^ c ^ d;
23311
+ k = 0x6ed9eba1;
23312
+ }
23313
+ else if (i < 60) {
23314
+ f = (b & c) | (b & d) | (c & d);
23315
+ k = 0x8f1bbcdc;
23316
+ }
23317
+ else {
23318
+ f = b ^ c ^ d;
23319
+ k = 0xca62c1d6;
23320
+ }
23321
+ const temp = (leftRotate(a, 5) + f + e + k + w[i]) >>> 0;
23322
+ e = d;
23323
+ d = c;
23324
+ c = leftRotate(b, 30);
23325
+ b = a;
23326
+ a = temp;
23327
+ }
23328
+ h0 = (h0 + a) >>> 0;
23329
+ h1 = (h1 + b) >>> 0;
23330
+ h2 = (h2 + c) >>> 0;
23331
+ h3 = (h3 + d) >>> 0;
23332
+ h4 = (h4 + e) >>> 0;
23333
+ }
23334
+ const digest = new Uint8Array(20);
23335
+ const digestView = new DataView(digest.buffer);
23336
+ digestView.setUint32(0, h0, false);
23337
+ digestView.setUint32(4, h1, false);
23338
+ digestView.setUint32(8, h2, false);
23339
+ digestView.setUint32(12, h3, false);
23340
+ digestView.setUint32(16, h4, false);
23341
+ return digest;
23342
+ }
23343
+ function hmacSha1(key, message) {
23344
+ const blockSize = 64;
23345
+ let normalizedKey = key;
23346
+ if (normalizedKey.length > blockSize) {
23347
+ normalizedKey = sha1(normalizedKey);
23348
+ }
23349
+ const paddedKey = new Uint8Array(blockSize);
23350
+ paddedKey.set(normalizedKey);
23351
+ const innerPad = new Uint8Array(blockSize);
23352
+ const outerPad = new Uint8Array(blockSize);
23353
+ for (let i = 0; i < blockSize; i++) {
23354
+ innerPad[i] = paddedKey[i] ^ 0x36;
23355
+ outerPad[i] = paddedKey[i] ^ 0x5c;
23356
+ }
23357
+ return sha1(concatBytes(outerPad, sha1(concatBytes(innerPad, message))));
23358
+ }
23359
+ function signUrlToSign(urlToSign, urlSigningSecret) {
23360
+ const keyBytes = decodeUrlSigningSecret(urlSigningSecret);
23361
+ const signatureBytes = hmacSha1(keyBytes, utf8Encode(urlToSign));
23362
+ return bytesToWebSafeBase64(signatureBytes);
23363
+ }
23364
+ /**
23365
+ * 对 Maps Static API 请求 URL 追加 `signature` 参数。
23366
+ * 签名内容为 pathname + search(不含 scheme/host),算法 HMAC-SHA1 + URL-safe Base64。
23367
+ *
23368
+ * @param inputUrl 完整未签名 URL(须已包含 key 等 query 参数)
23369
+ * @param urlSigningSecret Cloud Console「URL 签名密钥」(URL-safe Base64)
23370
+ */
23371
+ function signGoogleStaticMapUrlSync(inputUrl, urlSigningSecret) {
23372
+ if (!inputUrl || !urlSigningSecret) {
23373
+ throw new Error('signGoogleStaticMapUrl: inputUrl and urlSigningSecret are required');
23374
+ }
23375
+ const url = new globalThis.URL(inputUrl);
23376
+ url.searchParams.delete('signature');
23377
+ const urlToSign = `${url.pathname}${url.search}`;
23378
+ const signature = signUrlToSign(urlToSign, urlSigningSecret);
23379
+ return `${url.origin}${urlToSign}&signature=${signature}`;
23380
+ }
23381
+ /** 异步包装,便于与宿主 signGoogleStaticMapUrl 回调统一 Promise 接口 */
23382
+ async function signGoogleStaticMapUrl(inputUrl, urlSigningSecret) {
23383
+ return signGoogleStaticMapUrlSync(inputUrl, urlSigningSecret);
23384
+ }
23385
+
23172
23386
  var chargingPileImage = "data:image/png;base64,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";
23173
23387
 
23174
- const useChargingPile = ({ viewBox, rotation = 0 }) => {
23388
+ const DEFAULT_SCREEN_PADDING = 50;
23389
+ function normalizeScreenPadding(padding, defaultValue = DEFAULT_SCREEN_PADDING) {
23390
+ if (padding == null) {
23391
+ return {
23392
+ top: defaultValue,
23393
+ right: defaultValue,
23394
+ bottom: defaultValue,
23395
+ left: defaultValue,
23396
+ };
23397
+ }
23398
+ if (typeof padding === 'number') {
23399
+ return { top: padding, right: padding, bottom: padding, left: padding };
23400
+ }
23401
+ if (padding.length === 2) {
23402
+ const [vertical, horizontal] = padding;
23403
+ return { top: vertical, right: horizontal, bottom: vertical, left: horizontal };
23404
+ }
23405
+ if (padding.length === 3) {
23406
+ const [top, horizontal, bottom] = padding;
23407
+ return { top, right: horizontal, bottom, left: horizontal };
23408
+ }
23409
+ const [top, right, bottom, left] = padding;
23410
+ return { top, right, bottom, left };
23411
+ }
23412
+ function isZeroScreenPadding(padding) {
23413
+ return padding.top === 0 && padding.right === 0 && padding.bottom === 0 && padding.left === 0;
23414
+ }
23415
+
23416
+ /** 拖拽超过该像素阈值时视为平移,而非点击 */
23417
+ const PAN_CLICK_THRESHOLD_PX = 5;
23418
+ /**
23419
+ * 根据地图包围盒 + padding 计算「内容 viewBox」
23420
+ * 用于 ChannelClipPath 裁剪,padding 为 SVG 坐标系单位
23421
+ */
23422
+ function boundsToContentViewBox(bounds, padding) {
23423
+ if (!Number.isFinite(bounds.minX) || !Number.isFinite(bounds.maxX)) {
23424
+ return { x: 0, y: 0, width: 100, height: 100 };
23425
+ }
23426
+ const width = Math.max(bounds.maxX - bounds.minX + padding * 2, 1);
23427
+ const height = Math.max(bounds.maxY - bounds.minY + padding * 2, 1);
23428
+ return {
23429
+ x: bounds.minX - padding,
23430
+ y: bounds.minY - padding,
23431
+ width,
23432
+ height,
23433
+ };
23434
+ }
23435
+ /**
23436
+ * 在固定容器宽高比下,计算 fit 的 viewBox(等价于 SVG preserveAspectRatio="meet")
23437
+ *
23438
+ * 若内容与容器比例不一致,会在 SVG 坐标系中扩展 viewBox 的宽或高,形成 letterbox 区域
23439
+ */
23440
+ function computeFitViewBoxToContainer(content, containerWidth, containerHeight) {
23441
+ const containerAspect = containerWidth / containerHeight;
23442
+ const contentAspect = content.width / content.height;
23443
+ // 内容更「宽」:扩展 viewBox 高度,上下留白
23444
+ if (contentAspect > containerAspect) {
23445
+ const height = content.width / containerAspect;
23446
+ return {
23447
+ x: content.x,
23448
+ y: content.y - (height - content.height) / 2,
23449
+ width: content.width,
23450
+ height,
23451
+ };
23452
+ }
23453
+ // 内容更「高」:扩展 viewBox 宽度,左右留白
23454
+ const width = content.height * containerAspect;
23455
+ return {
23456
+ x: content.x - (width - content.width) / 2,
23457
+ y: content.y,
23458
+ width,
23459
+ height: content.height,
23460
+ };
23461
+ }
23462
+ /**
23463
+ * 计算默认 fit viewBox
23464
+ *
23465
+ * @param screenPadding 容器四边留出的屏幕边距(px),语法同 CSS padding
23466
+ */
23467
+ function computeFitViewBox(content, containerWidth, containerHeight, screenPadding = 0) {
23468
+ const baseFit = computeFitViewBoxToContainer(content, containerWidth, containerHeight);
23469
+ const pad = normalizeScreenPadding(screenPadding, 0);
23470
+ if (isZeroScreenPadding(pad)) {
23471
+ return baseFit;
23472
+ }
23473
+ const innerWidth = Math.max(containerWidth - pad.left - pad.right, 1);
23474
+ const innerHeight = Math.max(containerHeight - pad.top - pad.bottom, 1);
23475
+ const scaleX = innerWidth / containerWidth;
23476
+ const scaleY = innerHeight / containerHeight;
23477
+ const scale = Math.min(scaleX, scaleY);
23478
+ if (!Number.isFinite(scale) || scale <= 0 || scale >= 1) {
23479
+ return baseFit;
23480
+ }
23481
+ const newWidth = baseFit.width / scale;
23482
+ const newHeight = baseFit.height / scale;
23483
+ const centerX = baseFit.x + baseFit.width / 2;
23484
+ const centerY = baseFit.y + baseFit.height / 2;
23485
+ const offsetX = ((pad.left - pad.right) / 2) * (newWidth / containerWidth);
23486
+ const offsetY = ((pad.top - pad.bottom) / 2) * (newHeight / containerHeight);
23487
+ return {
23488
+ x: centerX - newWidth / 2 + offsetX,
23489
+ y: centerY - newHeight / 2 + offsetY,
23490
+ width: newWidth,
23491
+ height: newHeight,
23492
+ };
23493
+ }
23494
+ function viewBoxToString(viewBox) {
23495
+ return `${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}`;
23496
+ }
23497
+ /** SVG preserveAspectRatio="xMidYMid meet" 下的实际缩放比(地图单位 → 屏幕 px) */
23498
+ function getViewBoxMeetScale(viewBox, containerWidth, containerHeight) {
23499
+ return Math.min(containerWidth / viewBox.width, containerHeight / viewBox.height);
23500
+ }
23501
+ /** meet 模式下 SVG 内容相对容器左上角的 letterbox 偏移及缩放比 */
23502
+ function getViewBoxMeetOffset(viewBox, containerWidth, containerHeight) {
23503
+ const scale = getViewBoxMeetScale(viewBox, containerWidth, containerHeight);
23504
+ return {
23505
+ x: (containerWidth - viewBox.width * scale) / 2,
23506
+ y: (containerHeight - viewBox.height * scale) / 2,
23507
+ scale,
23508
+ };
23509
+ }
23510
+ /** 将屏幕坐标绕指定中心旋转(度,顺时针) */
23511
+ function rotatePointAround(x, y, centerX, centerY, rotationDeg) {
23512
+ if (rotationDeg === 0)
23513
+ return { x, y };
23514
+ const rad = (rotationDeg * Math.PI) / 180;
23515
+ const cos = Math.cos(rad);
23516
+ const sin = Math.sin(rad);
23517
+ const dx = x - centerX;
23518
+ const dy = y - centerY;
23519
+ return {
23520
+ x: centerX + dx * cos - dy * sin,
23521
+ y: centerY + dx * sin + dy * cos,
23522
+ };
23523
+ }
23524
+ /** 在地图坐标系内绕 viewBox 中心旋转(与 SVG transform="rotate(deg, cx, cy)" 一致) */
23525
+ function rotateMapPointAroundViewBoxCenter(mapX, mapY, viewBox, rotationDeg) {
23526
+ if (rotationDeg === 0)
23527
+ return { x: mapX, y: mapY };
23528
+ const cx = viewBox.x + viewBox.width / 2;
23529
+ const cy = viewBox.y + viewBox.height / 2;
23530
+ return rotatePointAround(mapX, mapY, cx, cy, rotationDeg);
23531
+ }
23532
+ function getViewBoxCenter(viewBox) {
23533
+ return {
23534
+ x: viewBox.x + viewBox.width / 2,
23535
+ y: viewBox.y + viewBox.height / 2,
23536
+ };
23537
+ }
23538
+ /**
23539
+ * 将地图坐标转换为 HTML overlay 的定位百分比
23540
+ * 需与 SVG viewBox + preserveAspectRatio="meet" 的渲染规则一致
23541
+ *
23542
+ * @returns leftPct / topPct — 相对容器宽高的百分比,配合 translate(-50%, -50%) 居中
23543
+ */
23544
+ function mapPointToContainerPercent(mapX, mapY, viewBox, containerWidth, containerHeight, mapRotationDeg = 0) {
23545
+ const { x: offsetX, y: offsetY, scale } = getViewBoxMeetOffset(viewBox, containerWidth, containerHeight);
23546
+ const { x: projectedX, y: projectedY } = rotateMapPointAroundViewBoxCenter(mapX, mapY, viewBox, mapRotationDeg);
23547
+ const screenX = offsetX + (projectedX - viewBox.x) * scale;
23548
+ const screenY = offsetY + (projectedY - viewBox.y) * scale;
23549
+ return {
23550
+ leftPct: (screenX / containerWidth) * 100,
23551
+ topPct: (screenY / containerHeight) * 100,
23552
+ };
23553
+ }
23554
+ /** 计算多边形包围盒在当前视口下的屏幕宽度(px) */
23555
+ function computePolygonScreenWidthPx(points, viewBox, containerWidth, containerHeight) {
23556
+ const valid = points.filter((point) => point.length >= 2);
23557
+ if (valid.length === 0)
23558
+ return 0;
23559
+ let minX = Infinity;
23560
+ let maxX = -Infinity;
23561
+ for (const [x] of valid) {
23562
+ minX = Math.min(minX, x);
23563
+ maxX = Math.max(maxX, x);
23564
+ }
23565
+ const mapWidth = maxX - minX;
23566
+ if (!Number.isFinite(mapWidth) || mapWidth <= 0)
23567
+ return 0;
23568
+ const scale = getViewBoxMeetScale(viewBox, containerWidth, containerHeight);
23569
+ return mapWidth * scale;
23570
+ }
23571
+ function scaleStrokeWidthForViewBox(baseStrokeWidth, viewBox, fitViewBox, options) {
23572
+ const { containerWidth, containerHeight, minScreenStrokePx = 2 } = options;
23573
+ const meetScale = getViewBoxMeetScale(viewBox, containerWidth, containerHeight);
23574
+ const fitMeetScale = getViewBoxMeetScale(fitViewBox, containerWidth, containerHeight);
23575
+ if (meetScale <= 0 || fitMeetScale <= 0 || !Number.isFinite(baseStrokeWidth)) {
23576
+ return baseStrokeWidth;
23577
+ }
23578
+ const targetScreenPx = Math.max(baseStrokeWidth * fitMeetScale, minScreenStrokePx);
23579
+ return targetScreenPx / meetScale;
23580
+ }
23581
+ /** 屏幕拖拽像素 → viewBox 坐标增量(与 mapPointToContainerPercent 互逆,供 applyPan 使用) */
23582
+ function containerDeltaToViewBoxDelta(deltaXPx, deltaYPx, viewBox, containerWidth, containerHeight, mapRotationDeg = 0) {
23583
+ const scale = getViewBoxMeetScale(viewBox, containerWidth, containerHeight);
23584
+ if (mapRotationDeg === 0) {
23585
+ return {
23586
+ dx: deltaXPx / scale,
23587
+ dy: deltaYPx / scale,
23588
+ };
23589
+ }
23590
+ // 地图在屏幕上旋转了 mapRotationDeg,将屏幕位移矢量逆旋转后再换算为 viewBox 增量
23591
+ const rad = (-mapRotationDeg * Math.PI) / 180;
23592
+ const cos = Math.cos(rad);
23593
+ const sin = Math.sin(rad);
23594
+ const mapDeltaXPx = deltaXPx * cos - deltaYPx * sin;
23595
+ const mapDeltaYPx = deltaXPx * sin + deltaYPx * cos;
23596
+ return {
23597
+ dx: mapDeltaXPx / scale,
23598
+ dy: mapDeltaYPx / scale,
23599
+ };
23600
+ }
23601
+ /**
23602
+ * 限制 viewBox 宽度在 zoom 范围内
23603
+ *
23604
+ * zoom = fitViewBox.width / viewBox.width
23605
+ * - zoom 越大 → viewBox 越小 → 放得越大
23606
+ * - minZoom=0.5 → 最多缩小到 fit 的 2 倍视野(viewBox.width 最大为 fit/0.5)
23607
+ * - maxZoom=4 → 最多放大 4 倍(viewBox.width 最小为 fit/4)
23608
+ */
23609
+ function clampViewBoxWidth(width, fitViewBox, minZoom, maxZoom) {
23610
+ const minWidth = fitViewBox.width / maxZoom;
23611
+ const maxWidth = fitViewBox.width / minZoom;
23612
+ return Math.min(maxWidth, Math.max(minWidth, width));
23613
+ }
23614
+ /**
23615
+ * 以屏幕 focal 点为中心缩放 viewBox
23616
+ * 将容器像素坐标转换为 SVG 坐标,保持焦点下的地图点位置不变
23617
+ *
23618
+ * @param viewBox 缩放前的 viewBox
23619
+ * @param newWidth 缩放后的 viewBox 宽度(高度按容器宽高比推导)
23620
+ * @param containerWidth 容器宽度(px)
23621
+ * @param containerHeight 容器高度(px)
23622
+ * @param focalX 焦点相对容器左上角的 X(px)
23623
+ * @param focalY 焦点相对容器左上角的 Y(px)
23624
+ */
23625
+ function resizeViewBoxAroundPoint(viewBox, newWidth, containerWidth, containerHeight, focalX, focalY) {
23626
+ const containerAspect = containerWidth / containerHeight;
23627
+ const newHeight = newWidth / containerAspect;
23628
+ const relX = focalX / containerWidth;
23629
+ const relY = focalY / containerHeight;
23630
+ const svgFocalX = viewBox.x + relX * viewBox.width;
23631
+ const svgFocalY = viewBox.y + relY * viewBox.height;
23632
+ return {
23633
+ x: svgFocalX - relX * newWidth,
23634
+ y: svgFocalY - relY * newHeight,
23635
+ width: newWidth,
23636
+ height: newHeight,
23637
+ };
23638
+ }
23639
+
23640
+ const useChargingPile = ({ viewBox, rotation = 0, viewRotationMode = 'overlay-parent', containerWidth, containerHeight, }) => {
23175
23641
  const { svgElementDatas } = useSvgEditContext();
23176
23642
  const { sn } = useCommonContext();
23643
+ const useMeetPositioning = containerWidth != null &&
23644
+ containerHeight != null &&
23645
+ containerWidth > 0 &&
23646
+ containerHeight > 0;
23177
23647
  const items = React.useMemo(() => {
23178
23648
  if (!svgElementDatas || !viewBox)
23179
23649
  return [];
@@ -23189,13 +23659,26 @@ const useChargingPile = ({ viewBox, rotation = 0 }) => {
23189
23659
  const direction = element?.direction || 0;
23190
23660
  const angle = (direction * 180) / Math.PI;
23191
23661
  const rotationDegree = 270 - angle; // 正东是正方向,需要处理下
23192
- const actualRotation = rotationDegree - rotation;
23193
- const relX = (center[0] - viewBox.x) / viewBox.width;
23194
- const relY = (center[1] - viewBox.y) / viewBox.height;
23195
- if (isFinite(relX) && isFinite(relY)) {
23662
+ const actualRotation = viewRotationMode === 'svg-transform'
23663
+ ? rotationDegree
23664
+ : rotationDegree - rotation;
23665
+ let leftPct;
23666
+ let topPct;
23667
+ if (useMeetPositioning) {
23668
+ const position = mapPointToContainerPercent(center[0], center[1], viewBox, containerWidth, containerHeight, viewRotationMode === 'svg-transform' ? rotation : 0);
23669
+ leftPct = position.leftPct;
23670
+ topPct = position.topPct;
23671
+ }
23672
+ else {
23673
+ const relX = (center[0] - viewBox.x) / viewBox.width;
23674
+ const relY = (center[1] - viewBox.y) / viewBox.height;
23675
+ leftPct = relX * 100;
23676
+ topPct = relY * 100;
23677
+ }
23678
+ if (isFinite(leftPct) && isFinite(topPct)) {
23196
23679
  results.push({
23197
- leftPct: relX * 100,
23198
- topPct: relY * 100,
23680
+ leftPct,
23681
+ topPct,
23199
23682
  size,
23200
23683
  rotateDeg: actualRotation,
23201
23684
  imageSrc: chargingPileImage,
@@ -23203,12 +23686,30 @@ const useChargingPile = ({ viewBox, rotation = 0 }) => {
23203
23686
  }
23204
23687
  }
23205
23688
  return results;
23206
- }, [svgElementDatas, viewBox?.x, viewBox?.y, viewBox?.width, viewBox?.height, rotation, sn]);
23689
+ }, [
23690
+ svgElementDatas,
23691
+ viewBox?.x,
23692
+ viewBox?.y,
23693
+ viewBox?.width,
23694
+ viewBox?.height,
23695
+ rotation,
23696
+ viewRotationMode,
23697
+ sn,
23698
+ useMeetPositioning,
23699
+ containerWidth,
23700
+ containerHeight,
23701
+ ]);
23207
23702
  return items;
23208
23703
  };
23209
23704
 
23210
- const CharginPile = React.memo(({ viewBox, rotation = 0, sizeScale = 1, isHighlight = true }) => {
23211
- const items = useChargingPile({ viewBox: viewBox || null, rotation });
23705
+ const CharginPile = React.memo(({ viewBox, rotation = 0, viewRotationMode = 'overlay-parent', containerWidth, containerHeight, sizeScale = 1, sizeInSvgUnits = false, minDisplaySizePx, isHighlight = true, }) => {
23706
+ const items = useChargingPile({
23707
+ viewBox: viewBox || null,
23708
+ rotation,
23709
+ viewRotationMode,
23710
+ containerWidth,
23711
+ containerHeight,
23712
+ });
23212
23713
  const containerStyle = React.useMemo(() => ({
23213
23714
  position: 'absolute',
23214
23715
  top: 0,
@@ -23219,10 +23720,19 @@ const CharginPile = React.memo(({ viewBox, rotation = 0, sizeScale = 1, isHighli
23219
23720
  zIndex: 750,
23220
23721
  opacity: isHighlight ? SvgMapOpacity.HIGHLIGHT : SvgMapOpacity.UN_HIGHLIGHT,
23221
23722
  }), [isHighlight]);
23723
+ const useMinPx = Boolean(sizeInSvgUnits && viewBox && minDisplaySizePx != null && minDisplaySizePx > 0);
23222
23724
  return (jsxRuntime.jsx("div", { style: containerStyle, children: items?.map((item, idx) => (jsxRuntime.jsx("div", { className: "charging-pile", style: {
23223
23725
  position: 'absolute',
23224
- width: item.size * sizeScale,
23225
- height: item.size * sizeScale,
23726
+ width: sizeInSvgUnits && viewBox
23727
+ ? useMinPx
23728
+ ? `max(${minDisplaySizePx}px, ${(item.size / Math.max(viewBox.width, 1)) * 100 * sizeScale}%)`
23729
+ : `${(item.size / Math.max(viewBox.width, 1)) * 100 * sizeScale}%`
23730
+ : item.size * sizeScale,
23731
+ height: sizeInSvgUnits && viewBox
23732
+ ? useMinPx
23733
+ ? `max(${minDisplaySizePx}px, ${(item.size / Math.max(viewBox.height, 1)) * 100 * sizeScale}%)`
23734
+ : `${(item.size / Math.max(viewBox.height, 1)) * 100 * sizeScale}%`
23735
+ : item.size * sizeScale,
23226
23736
  left: `${item.leftPct}%`,
23227
23737
  top: `${item.topPct}%`,
23228
23738
  transform: `translate(-50%, -50%) rotate(${item.rotateDeg}deg)`,
@@ -23233,8 +23743,11 @@ const CharginPile = React.memo(({ viewBox, rotation = 0, sizeScale = 1, isHighli
23233
23743
 
23234
23744
  const MAX_STATIC_SIZE = 640;
23235
23745
  const MAX_STATIC_MAP_RETRY = 3;
23746
+ const STATIC_MAP_RETRY_COOLDOWN_MS = 5000;
23747
+ /** 放大镜内充电桩屏幕像素下限,避免过小导致通道与桩视觉错位 */
23748
+ const MAGNIFIER_CHARGING_PILE_MIN_DISPLAY_PX = 20;
23236
23749
  const Magnifier = ({ visible, dragState, polygonPoints, fillColor = 'rgba(255, 122, 51, 0.1)', strokeColor = 'rgba(255, 122, 51, 1)', strokeWidth = 2, zoom = 3, size = 200, }) => {
23237
- const { svgViewBox, mapRef, googleMapStaticApiKey, drag } = useCommonContext();
23750
+ const { svgViewBox, mapRef, googleMapStaticApiKey, googleMapStaticUrlSigningSecret, signGoogleStaticMapUrl: signGoogleStaticMapUrl$1, drag, } = useCommonContext();
23238
23751
  const { svgElementDatas, svgRef } = useSvgEditContext();
23239
23752
  const { editMapInfo } = useMapEditContext();
23240
23753
  const [magnifierPosition, setMagnifierPosition] = React.useState({ x: 0, y: 0 });
@@ -23247,6 +23760,7 @@ const Magnifier = ({ visible, dragState, polygonPoints, fillColor = 'rgba(255, 1
23247
23760
  const hasInitializedSideRef = React.useRef(false);
23248
23761
  const [mapImageFrame, setMapImageFrame] = React.useState(null);
23249
23762
  const [staticMapRetryCount, setStaticMapRetryCount] = React.useState(0);
23763
+ const [staticMapBlockedUntil, setStaticMapBlockedUntil] = React.useState(0);
23250
23764
  // 获取当前正在拖拽的禁区 ID
23251
23765
  const currentObstacleId = editMapInfo?.selectElement?.id;
23252
23766
  // 计算当前 SVG 单位到屏幕像素的比例(用于实现真正 1:1 预览)
@@ -23297,6 +23811,11 @@ const Magnifier = ({ visible, dragState, polygonPoints, fillColor = 'rgba(255, 1
23297
23811
  // });
23298
23812
  setMapImageFrame(null);
23299
23813
  setStaticMapRetryCount(0);
23814
+ setStaticMapBlockedUntil(0);
23815
+ return;
23816
+ }
23817
+ // 弱网连续失败时进入短暂冷却,避免重试风暴导致底图反复闪烁
23818
+ if (Date.now() < staticMapBlockedUntil) {
23300
23819
  return;
23301
23820
  }
23302
23821
  const mapDiv = mapRef.getDiv?.();
@@ -23309,86 +23828,119 @@ const Magnifier = ({ visible, dragState, polygonPoints, fillColor = 'rgba(255, 1
23309
23828
  // });
23310
23829
  setMapImageFrame(null);
23311
23830
  setStaticMapRetryCount(0);
23831
+ setStaticMapBlockedUntil(0);
23312
23832
  return;
23313
23833
  }
23314
23834
  const mapRect = mapDiv.getBoundingClientRect();
23315
23835
  const svgPoint = svgRef.createSVGPoint();
23316
23836
  // 只要有 key 就尝试 static api;请求失败时不截图,直接回退灰色背景
23317
23837
  if (googleMapStaticApiKey) {
23318
- // console.log('[Magnifier] Background source: static api viewport snapshot');
23319
- const mapBounds = mapRef.getBounds?.();
23320
- if (!mapBounds) {
23321
- // console.log('[Magnifier] Skip static map effect: mapBounds missing');
23322
- setMapImageFrame(null);
23323
- return;
23324
- }
23325
- const screenToSvg = (screenX, screenY) => {
23326
- svgPoint.x = screenX;
23327
- svgPoint.y = screenY;
23328
- const p = svgPoint.matrixTransform(inverseCtm);
23329
- return { x: p.x, y: p.y };
23838
+ let cancelled = false;
23839
+ (async () => {
23840
+ // console.log('[Magnifier] Background source: static api viewport snapshot');
23841
+ const mapBounds = mapRef.getBounds?.();
23842
+ if (!mapBounds) {
23843
+ // console.log('[Magnifier] Skip static map effect: mapBounds missing');
23844
+ if (!cancelled) {
23845
+ setMapImageFrame(null);
23846
+ }
23847
+ return;
23848
+ }
23849
+ const screenToSvg = (screenX, screenY) => {
23850
+ svgPoint.x = screenX;
23851
+ svgPoint.y = screenY;
23852
+ const p = svgPoint.matrixTransform(inverseCtm);
23853
+ return { x: p.x, y: p.y };
23854
+ };
23855
+ // 使用当前地图视口矩形四角,将屏幕坐标映射到 SVG 坐标,确保底图与主视口对齐
23856
+ const topLeftSvg = screenToSvg(mapRect.left, mapRect.top);
23857
+ const topRightSvg = screenToSvg(mapRect.right, mapRect.top);
23858
+ const bottomLeftSvg = screenToSvg(mapRect.left, mapRect.bottom);
23859
+ if (!topLeftSvg || !topRightSvg || !bottomLeftSvg) {
23860
+ if (!cancelled) {
23861
+ setMapImageFrame(null);
23862
+ }
23863
+ return;
23864
+ }
23865
+ // 把当前主地图可视区域尺寸,按比例压到 Static API 允许的最大尺寸以内(受 Static API 640 上限约束)),同时不改变宽高比。
23866
+ const mapWidth = Math.max(1, Math.round(mapRect.width));
23867
+ const mapHeight = Math.max(1, Math.round(mapRect.height));
23868
+ const shrinkRatio = Math.min(1, MAX_STATIC_SIZE / Math.max(mapWidth, mapHeight));
23869
+ const staticWidth = Math.max(1, Math.round(mapWidth * shrinkRatio));
23870
+ const staticHeight = Math.max(1, Math.round(mapHeight * shrinkRatio));
23871
+ const center = mapBounds.getCenter();
23872
+ const currentZoom = mapRef.getZoom() || 20;
23873
+ // 尺寸缩小后同步补偿 zoom,保持与当前主图一致的地理覆盖范围
23874
+ const zoomCompensation = Math.log2(shrinkRatio);
23875
+ const adjustedZoom = currentZoom + zoomCompensation;
23876
+ const staticZoom = Math.max(0, Math.min(Math.floor(adjustedZoom), 22));
23877
+ const unsignedStaticMapUrl = `https://maps.googleapis.com/maps/api/staticmap?center=${center.lat()},${center.lng()}&zoom=${staticZoom}&size=${staticWidth}x${staticHeight}&maptype=satellite&key=${googleMapStaticApiKey}`;
23878
+ // 可选:对 Static Map URL 做 Google 数字签名(宿主回调优先,其次客户端 signing secret)
23879
+ let staticMapUrl = unsignedStaticMapUrl;
23880
+ try {
23881
+ if (signGoogleStaticMapUrl$1) {
23882
+ staticMapUrl = await Promise.resolve(signGoogleStaticMapUrl$1(unsignedStaticMapUrl));
23883
+ }
23884
+ else if (googleMapStaticUrlSigningSecret) {
23885
+ staticMapUrl = await signGoogleStaticMapUrl(unsignedStaticMapUrl, googleMapStaticUrlSigningSecret);
23886
+ console.log('[Magnifier] staticMapUrl', staticMapUrl);
23887
+ }
23888
+ }
23889
+ catch (error) {
23890
+ console.warn('[Magnifier] Static map URL signing failed', error);
23891
+ if (!cancelled) {
23892
+ setMapImageFrame(null);
23893
+ }
23894
+ return;
23895
+ }
23896
+ // 通过三角点推导静态图在 SVG 中的宽高和旋转角
23897
+ // Math.hypot(x, y) 等价于 sqrt(x*x + y*y),计算两点之间的距离
23898
+ const width = Math.hypot(topRightSvg.x - topLeftSvg.x, topRightSvg.y - topLeftSvg.y);
23899
+ const height = Math.hypot(bottomLeftSvg.x - topLeftSvg.x, bottomLeftSvg.y - topLeftSvg.y);
23900
+ // Math.atan2(y, x) 计算反正切值,返回 [-π, π] 弧度,*180/π 转换为角度
23901
+ const rotationDeg = (Math.atan2(topRightSvg.y - topLeftSvg.y, topRightSvg.x - topLeftSvg.x) * 180) / Math.PI;
23902
+ // Static API zoom 只能取整;额外补偿小数部分,避免底图视觉偏小
23903
+ // 在有旋转的坐标系里,做“以中心为锚点”的等比放大补偿。
23904
+ const fractionalZoomScale = Math.pow(2, adjustedZoom - staticZoom); //算出“还差多少缩放比例”。例子:adjustedZoom=19.6,staticZoom=19,差 0.6,比例是 2^0.6 ≈ 1.515。
23905
+ //把原始 width/height 乘这个比例,得到补偿后的尺寸。
23906
+ const compensatedWidth = width * fractionalZoomScale;
23907
+ const compensatedHeight = height * fractionalZoomScale;
23908
+ //尺寸变大后,多出来的宽/高的一半(要从两边均匀扩张)。
23909
+ const offsetAlongX = (compensatedWidth - width) / 2;
23910
+ const offsetAlongY = (compensatedHeight - height) / 2;
23911
+ //“右方向”单位向量(从左上指向右上),因为图可能旋转了,不能只按水平 x 轴算。
23912
+ const unitX = width > 0 ? (topRightSvg.x - topLeftSvg.x) / width : 1;
23913
+ const unitY = width > 0 ? (topRightSvg.y - topLeftSvg.y) / width : 0;
23914
+ //“下方向”单位向量(从左上指向左下),同理用于旋转场景。
23915
+ const unitDownX = height > 0 ? (bottomLeftSvg.x - topLeftSvg.x) / height : 0;
23916
+ const unitDownY = height > 0 ? (bottomLeftSvg.y - topLeftSvg.y) / height : 0;
23917
+ /**
23918
+ * 计算补偿后的新左上角:
23919
+ * 从原左上角沿“右方向”回退半个扩展量,再沿“下方向”回退半个扩展量。
23920
+ * 这样放大后仍以原区域中心为中心,不会放大后位置偏移。
23921
+ */
23922
+ const compensatedTopLeftX = topLeftSvg.x - unitX * offsetAlongX - unitDownX * offsetAlongY;
23923
+ const compensatedTopLeftY = topLeftSvg.y - unitY * offsetAlongX - unitDownY * offsetAlongY;
23924
+ if (cancelled) {
23925
+ return;
23926
+ }
23927
+ setMapImageFrame({
23928
+ url: staticMapUrl,
23929
+ x: compensatedTopLeftX,
23930
+ y: compensatedTopLeftY,
23931
+ width: compensatedWidth,
23932
+ height: compensatedHeight,
23933
+ transform: `rotate(${rotationDeg} ${compensatedTopLeftX} ${compensatedTopLeftY})`,
23934
+ });
23935
+ })();
23936
+ return () => {
23937
+ cancelled = true;
23330
23938
  };
23331
- // 使用当前地图视口矩形四角,将屏幕坐标映射到 SVG 坐标,确保底图与主视口对齐
23332
- const topLeftSvg = screenToSvg(mapRect.left, mapRect.top);
23333
- const topRightSvg = screenToSvg(mapRect.right, mapRect.top);
23334
- const bottomLeftSvg = screenToSvg(mapRect.left, mapRect.bottom);
23335
- if (!topLeftSvg || !topRightSvg || !bottomLeftSvg) {
23336
- setMapImageFrame(null);
23337
- return;
23338
- }
23339
- // 把当前主地图可视区域尺寸,按比例压到 Static API 允许的最大尺寸以内(受 Static API 640 上限约束)),同时不改变宽高比。
23340
- const mapWidth = Math.max(1, Math.round(mapRect.width));
23341
- const mapHeight = Math.max(1, Math.round(mapRect.height));
23342
- const shrinkRatio = Math.min(1, MAX_STATIC_SIZE / Math.max(mapWidth, mapHeight));
23343
- const staticWidth = Math.max(1, Math.round(mapWidth * shrinkRatio));
23344
- const staticHeight = Math.max(1, Math.round(mapHeight * shrinkRatio));
23345
- const center = mapBounds.getCenter();
23346
- const currentZoom = mapRef.getZoom() || 20;
23347
- // 尺寸缩小后同步补偿 zoom,保持与当前主图一致的地理覆盖范围
23348
- const zoomCompensation = Math.log2(shrinkRatio);
23349
- const adjustedZoom = currentZoom + zoomCompensation;
23350
- const staticZoom = Math.max(0, Math.min(Math.floor(adjustedZoom), 22));
23351
- const staticMapUrl = `https://maps.googleapis.com/maps/api/staticmap?center=${center.lat()},${center.lng()}&zoom=${staticZoom}&size=${staticWidth}x${staticHeight}&maptype=satellite&key=${googleMapStaticApiKey}`;
23352
- // 通过三角点推导静态图在 SVG 中的宽高和旋转角
23353
- // Math.hypot(x, y) 等价于 sqrt(x*x + y*y),计算两点之间的距离
23354
- const width = Math.hypot(topRightSvg.x - topLeftSvg.x, topRightSvg.y - topLeftSvg.y);
23355
- const height = Math.hypot(bottomLeftSvg.x - topLeftSvg.x, bottomLeftSvg.y - topLeftSvg.y);
23356
- // Math.atan2(y, x) 计算反正切值,返回 [-π, π] 弧度,*180/π 转换为角度
23357
- const rotationDeg = (Math.atan2(topRightSvg.y - topLeftSvg.y, topRightSvg.x - topLeftSvg.x) * 180) / Math.PI;
23358
- // Static API zoom 只能取整;额外补偿小数部分,避免底图视觉偏小
23359
- // 在有旋转的坐标系里,做“以中心为锚点”的等比放大补偿。
23360
- const fractionalZoomScale = Math.pow(2, adjustedZoom - staticZoom); //算出“还差多少缩放比例”。例子:adjustedZoom=19.6,staticZoom=19,差 0.6,比例是 2^0.6 ≈ 1.515。
23361
- //把原始 width/height 乘这个比例,得到补偿后的尺寸。
23362
- const compensatedWidth = width * fractionalZoomScale;
23363
- const compensatedHeight = height * fractionalZoomScale;
23364
- //尺寸变大后,多出来的宽/高的一半(要从两边均匀扩张)。
23365
- const offsetAlongX = (compensatedWidth - width) / 2;
23366
- const offsetAlongY = (compensatedHeight - height) / 2;
23367
- //“右方向”单位向量(从左上指向右上),因为图可能旋转了,不能只按水平 x 轴算。
23368
- const unitX = width > 0 ? (topRightSvg.x - topLeftSvg.x) / width : 1;
23369
- const unitY = width > 0 ? (topRightSvg.y - topLeftSvg.y) / width : 0;
23370
- //“下方向”单位向量(从左上指向左下),同理用于旋转场景。
23371
- const unitDownX = height > 0 ? (bottomLeftSvg.x - topLeftSvg.x) / height : 0;
23372
- const unitDownY = height > 0 ? (bottomLeftSvg.y - topLeftSvg.y) / height : 1;
23373
- /**
23374
- * 计算补偿后的新左上角:
23375
- * 从原左上角沿“右方向”回退半个扩展量,再沿“下方向”回退半个扩展量。
23376
- * 这样放大后仍以原区域中心为中心,不会放大后位置偏移。
23377
- */
23378
- const compensatedTopLeftX = topLeftSvg.x - unitX * offsetAlongX - unitDownX * offsetAlongY;
23379
- const compensatedTopLeftY = topLeftSvg.y - unitY * offsetAlongX - unitDownY * offsetAlongY;
23380
- setMapImageFrame({
23381
- url: staticMapUrl,
23382
- x: compensatedTopLeftX,
23383
- y: compensatedTopLeftY,
23384
- width: compensatedWidth,
23385
- height: compensatedHeight,
23386
- transform: `rotate(${rotationDeg} ${compensatedTopLeftX} ${compensatedTopLeftY})`,
23387
- });
23388
23939
  }
23389
23940
  else {
23390
23941
  setMapImageFrame(null);
23391
23942
  setStaticMapRetryCount(0);
23943
+ setStaticMapBlockedUntil(0);
23392
23944
  // console.log('[Magnifier] Background source: gray fallback (no static api key)');
23393
23945
  }
23394
23946
  }, [
@@ -23403,7 +23955,10 @@ const Magnifier = ({ visible, dragState, polygonPoints, fillColor = 'rgba(255, 1
23403
23955
  size,
23404
23956
  zoom,
23405
23957
  googleMapStaticApiKey,
23958
+ googleMapStaticUrlSigningSecret,
23959
+ signGoogleStaticMapUrl$1,
23406
23960
  staticMapRetryCount,
23961
+ staticMapBlockedUntil,
23407
23962
  ]);
23408
23963
  React.useLayoutEffect(() => {
23409
23964
  if (!visible) {
@@ -23539,6 +24094,7 @@ const Magnifier = ({ visible, dragState, polygonPoints, fillColor = 'rgba(255, 1
23539
24094
  }, children: [mapImageFrame && (jsxRuntime.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: () => {
23540
24095
  // console.log('✅ Map background loaded in magnifier');
23541
24096
  setStaticMapRetryCount(0);
24097
+ setStaticMapBlockedUntil(0);
23542
24098
  }, onError: (_e) => {
23543
24099
  // console.error('❌ Failed to load map background in magnifier', {
23544
24100
  // retryCount: staticMapRetryCount,
@@ -23549,7 +24105,8 @@ const Magnifier = ({ visible, dragState, polygonPoints, fillColor = 'rgba(255, 1
23549
24105
  setStaticMapRetryCount((prev) => {
23550
24106
  if (prev < MAX_STATIC_MAP_RETRY)
23551
24107
  return prev + 1;
23552
- setMapImageFrame(null);
24108
+ // 重试上限后进入冷却,不立刻清空已显示底图,避免弱网下闪烁
24109
+ setStaticMapBlockedUntil(Date.now() + STATIC_MAP_RETRY_COOLDOWN_MS);
23553
24110
  return 0;
23554
24111
  });
23555
24112
  } }, `magnifier-static-map-${staticMapRetryCount}`)), svgElementDatas &&
@@ -23575,14 +24132,16 @@ const Magnifier = ({ visible, dragState, polygonPoints, fillColor = 'rgba(255, 1
23575
24132
  ? 3.6 / styleZoomFactor
23576
24133
  : 3 / styleZoomFactor;
23577
24134
  return (jsxRuntime.jsx("circle", { cx: x, cy: y, r: pointRadius, fill: pointFill, stroke: pointStroke, strokeWidth: pointStrokeWidth }, idx));
23578
- })] })), dragState.dragType === 'new' && dragState.edgeInfo && (jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.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}` }) }))] }), jsxRuntime.jsx(CharginPile, { viewBox: magnifierViewBox || null, rotation: actureRotate, sizeScale: Math.max(1, zoom) })] }));
24135
+ })] })), dragState.dragType === 'new' && dragState.edgeInfo && (jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.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}` }) }))] }), jsxRuntime.jsx(CharginPile, { viewBox: magnifierViewBox || null, rotation: actureRotate, sizeInSvgUnits: true, minDisplaySizePx: MAGNIFIER_CHARGING_PILE_MIN_DISPLAY_PX,
24136
+ // 充电桩最多保持原始尺寸,不允许放大超过 1 倍
24137
+ sizeScale: Math.min(1, 1 / styleZoomFactor) })] }));
23579
24138
  // 使用 Portal 渲染到 body,避免层级问题
23580
24139
  return ReactDOM.createPortal(magnifierContent, document.body);
23581
24140
  };
23582
24141
 
23583
- var css_248z$6 = ".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}";
23584
- var styles$6 = {"pointerCursor":"index-module_pointerCursor__Ee6pr","polygonPath":"index-module_polygonPath__PynOn","dragging":"index-module_dragging__xSFdO","notCreate":"index-module_notCreate__bFnkV"};
23585
- styleInject(css_248z$6);
24142
+ 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}";
24143
+ var styles$8 = {"pointerCursor":"index-module_pointerCursor__Ee6pr","polygonPath":"index-module_polygonPath__PynOn","dragging":"index-module_dragging__xSFdO","notCreate":"index-module_notCreate__bFnkV"};
24144
+ styleInject(css_248z$8);
23586
24145
 
23587
24146
  /**
23588
24147
  * 几何计算工具函数
@@ -24005,28 +24564,102 @@ const useCheckElement = () => {
24005
24564
  };
24006
24565
  };
24007
24566
 
24567
+ 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}";
24568
+ var styles$7 = {"tooltip":"Tooltip-module_tooltip__Wexlk","tooltipHover":"Tooltip-module_tooltipHover__Tfqme"};
24569
+ styleInject(css_248z$7);
24570
+
24571
+ var classnames = {exports: {}};
24572
+
24573
+ /*!
24574
+ Copyright (c) 2018 Jed Watson.
24575
+ Licensed under the MIT License (MIT), see
24576
+ http://jedwatson.github.io/classnames
24577
+ */
24578
+
24579
+ (function (module) {
24580
+ /* global define */
24581
+
24582
+ (function () {
24583
+
24584
+ var hasOwn = {}.hasOwnProperty;
24585
+
24586
+ function classNames () {
24587
+ var classes = '';
24588
+
24589
+ for (var i = 0; i < arguments.length; i++) {
24590
+ var arg = arguments[i];
24591
+ if (arg) {
24592
+ classes = appendClass(classes, parseValue(arg));
24593
+ }
24594
+ }
24595
+
24596
+ return classes;
24597
+ }
24598
+
24599
+ function parseValue (arg) {
24600
+ if (typeof arg === 'string' || typeof arg === 'number') {
24601
+ return arg;
24602
+ }
24603
+
24604
+ if (typeof arg !== 'object') {
24605
+ return '';
24606
+ }
24607
+
24608
+ if (Array.isArray(arg)) {
24609
+ return classNames.apply(null, arg);
24610
+ }
24611
+
24612
+ if (arg.toString !== Object.prototype.toString && !arg.toString.toString().includes('[native code]')) {
24613
+ return arg.toString();
24614
+ }
24615
+
24616
+ var classes = '';
24617
+
24618
+ for (var key in arg) {
24619
+ if (hasOwn.call(arg, key) && arg[key]) {
24620
+ classes = appendClass(classes, key);
24621
+ }
24622
+ }
24623
+
24624
+ return classes;
24625
+ }
24626
+
24627
+ function appendClass (value, newClass) {
24628
+ if (!newClass) {
24629
+ return value;
24630
+ }
24631
+
24632
+ if (value) {
24633
+ return value + ' ' + newClass;
24634
+ }
24635
+
24636
+ return value + newClass;
24637
+ }
24638
+
24639
+ if (module.exports) {
24640
+ classNames.default = classNames;
24641
+ module.exports = classNames;
24642
+ } else {
24643
+ window.classNames = classNames;
24644
+ }
24645
+ }());
24646
+ } (classnames));
24647
+
24648
+ var classnamesExports = classnames.exports;
24649
+ var classNames = /*@__PURE__*/getDefaultExportFromCjs(classnamesExports);
24650
+
24008
24651
  const Tooltip = ({ open, x, y, text, style, onClick }) => {
24009
24652
  if (!open)
24010
24653
  return null;
24011
24654
  const tooltipStyle = {
24012
- position: 'fixed',
24013
24655
  left: x,
24014
24656
  top: y,
24015
- backgroundColor: '#fff',
24016
- borderRadius: '4px',
24017
- height: '20px',
24018
- lineHeight: '16px',
24019
- fontSize: '12px',
24020
- color: '#211F1F',
24021
- padding: '2px 4px',
24022
- whiteSpace: 'nowrap',
24023
- zIndex: 9999,
24024
- cursor: 'pointer',
24025
- userSelect: 'none',
24026
24657
  ...style,
24027
24658
  };
24028
24659
  // 使用 createPortal 挂载到 body
24029
- return ReactDOM.createPortal(jsxRuntime.jsx("div", { style: tooltipStyle, onClick: (e) => {
24660
+ return ReactDOM.createPortal(jsxRuntime.jsx("div", { className: classNames(styles$7.tooltip, {
24661
+ [styles$7.tooltipHover]: !!onClick,
24662
+ }), style: tooltipStyle, onClick: (e) => {
24030
24663
  e.preventDefault();
24031
24664
  e.stopPropagation();
24032
24665
  onClick?.();
@@ -24124,7 +24757,7 @@ const createPathData = (points) => {
24124
24757
  };
24125
24758
  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自动判断
24126
24759
  }) => {
24127
- const { overlayScale, unitType, showStraddleBoundaryBorder, platform, onH5FirstSelectObstaclePoint } = useCommonContext();
24760
+ const { overlayScale, unitType, showStraddleBoundaryBorder, platform, locale, onH5FirstSelectObstaclePoint, } = useCommonContext();
24128
24761
  const { onHandleEvent } = useMapEditContext();
24129
24762
  const { svgRef } = useSvgEditContext();
24130
24763
  const [dragState, setDragState] = React.useState({
@@ -24172,11 +24805,12 @@ const PolygonElement = ({ canSelect = false, isHover = false, points, fillColor
24172
24805
  const [tooltipIndex, setTooltipIndex] = React.useState(null);
24173
24806
  const [tooltipPos, setTooltipPos] = React.useState({ x: 0, y: 0 });
24174
24807
  const [completeTooltipOpen, setCompleteTooltipOpen] = React.useState(false);
24808
+ const [moveTooltipOpen, setMoveTooltipOpen] = React.useState(false);
24175
24809
  const updateTooltipPos = (target) => {
24176
24810
  const rect = target.getBoundingClientRect();
24177
24811
  setTooltipPos({
24178
24812
  x: rect.left + rect.width + 4,
24179
- y: rect.top - 10
24813
+ y: rect.top - 10,
24180
24814
  });
24181
24815
  };
24182
24816
  // 处理顶点点击(仅透出点击;不触发删除)
@@ -24311,6 +24945,13 @@ const PolygonElement = ({ canSelect = false, isHover = false, points, fillColor
24311
24945
  edgeInfo: edgeInfo,
24312
24946
  dragType: 'new',
24313
24947
  });
24948
+ // 边上新点:insertIndex 可能与当前 selectedVertexIndex 相同(例如原选中 2,在 1-2 间插入后仍写 2),
24949
+ // React 会跳过 setState,依赖 selectedVertexIndex 的 useLayoutEffect 不会跑,故用微任务直接震一次
24950
+ if (platform === exports.PlatformType.H5 && editMapInfo?.elementType === exports.DataType.OBSTACLE) {
24951
+ void Promise.resolve().then(() => {
24952
+ onH5FirstSelectObstaclePoint?.();
24953
+ });
24954
+ }
24314
24955
  // 将新创建的点设为选中态,便于后续直接拖拽
24315
24956
  setSelectedVertexIndex(insertIndex);
24316
24957
  }, [
@@ -24321,6 +24962,9 @@ const PolygonElement = ({ canSelect = false, isHover = false, points, fillColor
24321
24962
  coordinates,
24322
24963
  getSVGCoordinates,
24323
24964
  calculatePerpendicularFoot$1,
24965
+ platform,
24966
+ editMapInfo?.elementType,
24967
+ onH5FirstSelectObstaclePoint,
24324
24968
  ]);
24325
24969
  // H5平台长按边线创建新顶点
24326
24970
  const handleEdgeLongPressStart = React.useCallback((e, edgeStartIndex) => {
@@ -24365,6 +25009,8 @@ const PolygonElement = ({ canSelect = false, isHover = false, points, fillColor
24365
25009
  const vertexLongPressTimerRef = React.useRef(null);
24366
25010
  const isVertexLongPressedRef = React.useRef(false);
24367
25011
  const longPressVertexIndexRef = React.useRef(-1);
25012
+ /** 长按选中顶点且 index 发生变化时置 true,在 useLayoutEffect 里再触发震动,避免早于视觉选中态 */
25013
+ const pendingH5ObstacleSelectHapticRef = React.useRef(false);
24368
25014
  // 长按拖拽判定相关
24369
25015
  const touchStartPosRef = React.useRef(null);
24370
25016
  const hasStartedDragViaLongPressRef = React.useRef(false);
@@ -24385,13 +25031,21 @@ const PolygonElement = ({ canSelect = false, isHover = false, points, fillColor
24385
25031
  vertexLongPressTimerRef.current = window.setTimeout(() => {
24386
25032
  isVertexLongPressedRef.current = true;
24387
25033
  setSelectedVertexIndex((prev) => {
24388
- if (prev !== index) {
24389
- onH5FirstSelectObstaclePoint?.();
24390
- }
25034
+ pendingH5ObstacleSelectHapticRef.current = prev !== index;
24391
25035
  return index;
24392
25036
  });
24393
25037
  }, 1000);
24394
- }, [editMode, createMode, completed, dragState.isDragging, onH5FirstSelectObstaclePoint]);
25038
+ }, [editMode, createMode, completed, dragState.isDragging]);
25039
+ React.useLayoutEffect(() => {
25040
+ if (!pendingH5ObstacleSelectHapticRef.current)
25041
+ return;
25042
+ pendingH5ObstacleSelectHapticRef.current = false;
25043
+ if (platform !== exports.PlatformType.H5)
25044
+ return;
25045
+ if (editMapInfo?.elementType !== exports.DataType.OBSTACLE)
25046
+ return;
25047
+ onH5FirstSelectObstaclePoint?.();
25048
+ }, [selectedVertexIndex, platform, editMapInfo?.elementType, onH5FirstSelectObstaclePoint]);
24395
25049
  const handleVertexLongPressEnd = React.useCallback(() => {
24396
25050
  if (vertexLongPressTimerRef.current !== null) {
24397
25051
  window.clearTimeout(vertexLongPressTimerRef.current);
@@ -24420,8 +25074,8 @@ const PolygonElement = ({ canSelect = false, isHover = false, points, fillColor
24420
25074
  }, [dragState.currentPosition, checkCanNotCreateAtPosition, coordinates]);
24421
25075
  const generateVertexClassName = React.useMemo(() => {
24422
25076
  if (!dragState.isDragging)
24423
- return styles$6.pointerCursor;
24424
- return showNotCreateCursor ? styles$6.notCreate : styles$6.dragging;
25077
+ return styles$8.pointerCursor;
25078
+ return showNotCreateCursor ? styles$8.notCreate : styles$8.dragging;
24425
25079
  }, [dragState.isDragging, showNotCreateCursor]);
24426
25080
  // 使用 useRef 来存储 requestAnimationFrame ID
24427
25081
  const rafIdRef = React.useRef(null);
@@ -24712,7 +25366,7 @@ const PolygonElement = ({ canSelect = false, isHover = false, points, fillColor
24712
25366
  WebkitTouchCallout: 'none',
24713
25367
  touchAction: 'none',
24714
25368
  } })] }));
24715
- })()] })), renderCoordinates.length >= 2 && (jsxRuntime.jsx("polygon", { className: canSelect ? styles$6.pointerCursor : '', points: polygonPoints, fill: isHover ? (hoverFillColor || fillColor) : fillColor, fillOpacity: fillOpacity, stroke: "none" // 边框透明
25369
+ })()] })), renderCoordinates.length >= 2 && (jsxRuntime.jsx("polygon", { className: canSelect ? styles$8.pointerCursor : '', points: polygonPoints, fill: isHover ? hoverFillColor || fillColor : fillColor, fillOpacity: fillOpacity, stroke: "none" // 边框透明
24716
25370
  , onClick: onPolygonClick })), jsxRuntime.jsx("g", { children: renderCoordinates.length >= 2 &&
24717
25371
  pathSegments.map((segment, index) => {
24718
25372
  if (segment.points.length < 2)
@@ -24720,14 +25374,14 @@ const PolygonElement = ({ canSelect = false, isHover = false, points, fillColor
24720
25374
  const pathData = createPathData(segment.points);
24721
25375
  const isDash = segment.type === 1;
24722
25376
  if (isDash && showStraddleBoundaryBorder) {
24723
- return (jsxRuntime.jsx(DashPath, { className: canSelect ? styles$6.polygonPath : '', points: segment.points, stroke: strokeColor, strokeWidth: isHover ? strokeWidth + 1 : strokeWidth, strokeOpacity: strokeOpacity }, index));
25377
+ return (jsxRuntime.jsx(DashPath, { className: canSelect ? styles$8.polygonPath : '', points: segment.points, stroke: strokeColor, strokeWidth: isHover ? strokeWidth + 1 : strokeWidth, strokeOpacity: strokeOpacity }, index));
24724
25378
  }
24725
25379
  return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [(editMode || createMode) && (jsxRuntime.jsx("path", { d: pathData, fill: "none", stroke: '#fff', strokeWidth: strokeWidth * 2, strokeOpacity: strokeOpacity, strokeLinecap: "round", strokeLinejoin: "round", vectorEffect: "non-scaling-stroke", style: {
24726
25380
  userSelect: 'none',
24727
25381
  WebkitUserSelect: 'none',
24728
25382
  WebkitTouchCallout: 'none',
24729
25383
  touchAction: 'none',
24730
- } }, 'warp' + index)), jsxRuntime.jsx("path", { d: pathData, fill: "none", stroke: strokeColor, strokeWidth: isHover ? strokeWidth + 1 : strokeWidth, strokeOpacity: strokeOpacity, strokeLinecap: "round", strokeLinejoin: "round", className: canSelect ? styles$6.polygonPath : '', vectorEffect: "non-scaling-stroke", onClick: onPathClick, style: {
25384
+ } }, 'warp' + index)), jsxRuntime.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: {
24731
25385
  userSelect: 'none',
24732
25386
  WebkitUserSelect: 'none',
24733
25387
  WebkitTouchCallout: 'none',
@@ -24738,14 +25392,29 @@ const PolygonElement = ({ canSelect = false, isHover = false, points, fillColor
24738
25392
  renderCoordinates.map((coord, index) => {
24739
25393
  const nextCoord = renderCoordinates[(index + 1) % renderCoordinates.length];
24740
25394
  const isDashPath = coord[2] === 1;
24741
- return (jsxRuntime.jsx("line", { x1: coord[0], y1: coord[1], x2: nextCoord[0], y2: nextCoord[1], stroke: isDashPath ? 'transparent' : strokeColor, strokeWidth: platform === exports.PlatformType.H5 ? strokeWidth * 3 : strokeWidth, className: editMode ? styles$6.pointerCursor : '', style: {
24742
- userSelect: 'none',
24743
- WebkitUserSelect: 'none',
24744
- WebkitTouchCallout: 'none',
24745
- touchAction: 'none',
24746
- }, vectorEffect: "non-scaling-stroke", ...(platform === exports.PlatformType.H5
24747
- ? {
24748
- onTouchStart: createReactEventHandler((e) => handleEdgeLongPressStart(e, index)),
25395
+ const edgeLineCommon = {
25396
+ x1: coord[0],
25397
+ y1: coord[1],
25398
+ x2: nextCoord[0],
25399
+ y2: nextCoord[1],
25400
+ };
25401
+ const edgeLineStyle = {
25402
+ userSelect: 'none',
25403
+ WebkitUserSelect: 'none',
25404
+ WebkitTouchCallout: 'none',
25405
+ touchAction: 'none',
25406
+ };
25407
+ // H5 实线:透明粗线仅作长按热区,描边只看上方 path;H5 虚线/Web:沿用透明或主题色边线 + 对应事件(合并为一个 line,避免两段重复)
25408
+ const isH5SolidHitOnly = platform === exports.PlatformType.H5 && ((editMode && !isDashPath) || createMode);
25409
+ const edgeStroke = isH5SolidHitOnly || isDashPath ? 'transparent' : strokeColor;
25410
+ const edgeStrokeWidth = isH5SolidHitOnly
25411
+ ? strokeWidth * 12
25412
+ : platform === exports.PlatformType.H5
25413
+ ? strokeWidth * 2
25414
+ : strokeWidth;
25415
+ return (jsxRuntime.jsx("line", { ...edgeLineCommon, stroke: edgeStroke, strokeWidth: edgeStrokeWidth, ...(isH5SolidHitOnly ? { strokeLinecap: 'round' } : {}), className: editMode ? styles$8.pointerCursor : '', style: edgeLineStyle, vectorEffect: "non-scaling-stroke", ...(platform === exports.PlatformType.H5
25416
+ ? {
25417
+ onTouchStart: createReactEventHandler((e) => handleEdgeLongPressStart(e, index)),
24749
25418
  onTouchEnd: createReactEventHandler(() => handleEdgeLongPressEnd()),
24750
25419
  onTouchCancel: createReactEventHandler(() => handleEdgeLongPressEnd()),
24751
25420
  }
@@ -24813,7 +25482,7 @@ const PolygonElement = ({ canSelect = false, isHover = false, points, fillColor
24813
25482
  isDoubleClickingRef.current = false;
24814
25483
  }, 300);
24815
25484
  // 打开删除确认 Tooltip(仅在可编辑时)
24816
- if (editMode || (createMode && completed)) {
25485
+ if ((editMode || (createMode && completed)) && renderCoordinates.length > 3) {
24817
25486
  updateTooltipPos(e.target);
24818
25487
  setTooltipIndex(idx);
24819
25488
  }
@@ -24890,13 +25559,25 @@ const PolygonElement = ({ canSelect = false, isHover = false, points, fillColor
24890
25559
  }
24891
25560
  }
24892
25561
  },
24893
- onMouseEnter: canComplete ? (e) => {
24894
- setCompleteTooltipOpen(true);
24895
- updateTooltipPos(e.target);
24896
- } : undefined,
24897
- onMouseLeave: canComplete ? () => {
24898
- setCompleteTooltipOpen(false);
24899
- } : undefined,
25562
+ onMouseEnter: (e) => {
25563
+ if (canComplete) {
25564
+ setCompleteTooltipOpen(true);
25565
+ }
25566
+ if (editMode || (createMode && completed)) {
25567
+ setMoveTooltipOpen(true);
25568
+ }
25569
+ if (tooltipIndex === null) {
25570
+ updateTooltipPos(e.target);
25571
+ }
25572
+ },
25573
+ onMouseLeave: () => {
25574
+ if (canComplete) {
25575
+ setCompleteTooltipOpen(false);
25576
+ }
25577
+ if (editMode || (createMode && completed)) {
25578
+ setMoveTooltipOpen(false);
25579
+ }
25580
+ },
24900
25581
  }) }), platform === exports.PlatformType.H5 && tooltipIndex === idx && coordinates.length > 3 && (jsxRuntime.jsxs("g", { transform: `translate(${coord[0] + 4 * overlayScale * 2}, ${coord[1] - 4 * overlayScale * 2}) scale(${overlayScale * 2})`, onClick: (e) => {
24901
25582
  e.preventDefault();
24902
25583
  e.stopPropagation();
@@ -24920,12 +25601,16 @@ const PolygonElement = ({ canSelect = false, isHover = false, points, fillColor
24920
25601
  // >
24921
25602
  // </rect>
24922
25603
  )] }, `vertex-${idx}`));
24923
- }), platform === exports.PlatformType.WEB && tooltipIndex !== null && (jsxRuntime.jsx(Tooltip, { open: true, x: tooltipPos.x, y: tooltipPos.y, text: "Remove", onClick: () => {
24924
- if (onVertexDelete && (editMode || (createMode && completed)) && coordinates.length > 3) {
25604
+ }), platform === exports.PlatformType.WEB && tooltipIndex !== null && (jsxRuntime.jsx(Tooltip, { open: true, x: tooltipPos.x, y: tooltipPos.y, text: locale?.['map.renderer.remove'] || 'Remove', onClick: () => {
25605
+ if (onVertexDelete &&
25606
+ (editMode || (createMode && completed)) &&
25607
+ coordinates.length > 3) {
24925
25608
  onVertexDelete(renderCoordinates?.length - 1 - tooltipIndex);
24926
25609
  }
24927
25610
  setTooltipIndex(null);
24928
- } })), platform === exports.PlatformType.WEB && completeTooltipOpen && (jsxRuntime.jsx(Tooltip, { open: true, x: tooltipPos.x, y: tooltipPos.y, text: "Click to finish" }))] }));
25611
+ } })), platform === exports.PlatformType.WEB && completeTooltipOpen && (jsxRuntime.jsx(Tooltip, { open: true, x: tooltipPos.x, y: tooltipPos.y, text: locale?.['map.renderer.tips.clickFinish'] || 'Click to finish' })), platform === exports.PlatformType.WEB && moveTooltipOpen && tooltipIndex === null && (jsxRuntime.jsx(Tooltip, { open: true, x: tooltipPos.x, y: tooltipPos.y, text: renderCoordinates.length > 3 ?
25612
+ (locale?.['map.renderer.tips.dragAndRemove'] || 'Drag to change, double click to remove') :
25613
+ (locale?.['map.renderer.tips.drag'] || 'Drag to change') }))] }));
24929
25614
  };
24930
25615
 
24931
25616
  const MowPartitionContext = React.createContext({
@@ -25005,7 +25690,8 @@ const BoundaryElement = ({ data, isHover }) => {
25005
25690
  };
25006
25691
 
25007
25692
  const ChannelElement = ({ data }) => {
25008
- const { sn } = useCommonContext();
25693
+ const { channelClipPathId } = useCommonContext();
25694
+ const clipPathUrl = `url(#${channelClipPathId})`;
25009
25695
  const points = data.points;
25010
25696
  const style = data.style;
25011
25697
  let pathData = `M ${points[0][0]} ${points[0][1]}`;
@@ -25015,7 +25701,7 @@ const ChannelElement = ({ data }) => {
25015
25701
  const topLineWidth = dp2px(style.lineWidth).toString();
25016
25702
  const bottomLineWidth = dp2px(style.bottomLineWidth).toString();
25017
25703
  const dashLength = dp2px(style.lineWidth);
25018
- return (jsxRuntime.jsxs("g", { id: data.id.toString(), children: [jsxRuntime.jsx("path", { d: pathData, fill: "none", stroke: "#8498A9", strokeWidth: bottomLineWidth, strokeLinejoin: "round", opacity: style.opacity || 1, style: { clipPath: `url(#channel-clip-path-${sn})` } }), jsxRuntime.jsx("path", { d: pathData, fill: "none", stroke: "#FFFFFF", strokeWidth: topLineWidth, strokeLinejoin: "round", strokeDasharray: `${dashLength * 2} ${dashLength}`, opacity: style.opacity || 1, style: { clipPath: `url(#channel-clip-path-${sn})` } })] }));
25704
+ return (jsxRuntime.jsxs("g", { id: data.id.toString(), children: [jsxRuntime.jsx("path", { d: pathData, fill: "none", stroke: "#8498A9", strokeWidth: bottomLineWidth, strokeLinejoin: "round", opacity: style.opacity || 1, style: { clipPath: clipPathUrl } }), jsxRuntime.jsx("path", { d: pathData, fill: "none", stroke: "#FFFFFF", strokeWidth: topLineWidth, strokeLinejoin: "round", strokeDasharray: `${dashLength * 2} ${dashLength}`, opacity: style.opacity || 1, style: { clipPath: clipPathUrl } })] }));
25019
25705
  };
25020
25706
 
25021
25707
  /**
@@ -25141,10 +25827,11 @@ const ObstacleElement = ({ data, isHover }) => {
25141
25827
  const strokeWidth = React.useMemo(() => {
25142
25828
  // 如果是h5选中的情况下,禁区只有在start模式下才需要加粗
25143
25829
  // 其他模式展示的是其他的样式
25144
- if (platform === exports.PlatformType.H5 &&
25830
+ if ((platform === exports.PlatformType.H5 &&
25145
25831
  editMapInfo?.selectElement?.id === data.id &&
25146
- editMapInfo.mobileMode === exports.MobileEditMode.START) {
25147
- return dp2px((style.lineWidth || 1) * 2);
25832
+ editMapInfo.mobileMode === exports.MobileEditMode.START) ||
25833
+ editMapInfo.mobileMode === exports.MobileEditMode.EDIT) {
25834
+ return dp2px((style.lineWidth || 1) * 2.2);
25148
25835
  // return dp2px(((style.lineWidth as number) || 3) * 3);
25149
25836
  }
25150
25837
  return dp2px(style.lineWidth || 3);
@@ -25179,7 +25866,10 @@ const ObstacleElement = ({ data, isHover }) => {
25179
25866
  }
25180
25867
  return editMapInfo?.selectElement?.id === data.id;
25181
25868
  }, [editMapInfo, data, platform]);
25182
- return (jsxRuntime.jsx(PolygonElement, { canSelect: Boolean(!editMapInfo?.elementType) && editMap, points: currentPoints, fillColor: style.fillColor, hoverFillColor: style.hoverFillColor, fillOpacity: style.fillOpacity, strokeColor: style.lineColor, strokeWidth: strokeWidth, editMode: editMode, isHover: isHover, showPoints: editMapInfo?.selectElement?.id === data.id, onPathClick: onPathClick, onPolygonClick: () => {
25869
+ const disabled = React.useMemo(() => {
25870
+ return data?.status === 0 && !editMode;
25871
+ }, [data?.status, editMode, editMapInfo?.createMode]);
25872
+ return (jsxRuntime.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: () => {
25183
25873
  onPathClick();
25184
25874
  }, onVertexDelete: (vertexIndex) => handleCreateVertexDelete(vertexIndex), onCoordinatesChange: (coordinates) => {
25185
25875
  if (platform === exports.PlatformType.H5 && editMapInfo.mobileMode === exports.MobileEditMode.CREATE) {
@@ -25780,6 +26470,7 @@ const VisionOffTransformWrapper = ({ data, isSelected = false, isHover = false,
25780
26470
  // 使用自定义hook管理所有变换逻辑
25781
26471
  const { currentPoints, isDragging, isRotating, isScaling, dimensions, initializePoints, calculateSelectionBoxPoints, handleMouseDown, handleRotateStart, handleScaleStart, handleMouseMove, handleMouseUp, containerRef, // 从hook获取containerRef
25782
26472
  } = useVisionOffTransform(dataPoints);
26473
+ const [hoverType, setHoverType] = React.useState(null);
25783
26474
  const visionOffData = React.useMemo(() => {
25784
26475
  return {
25785
26476
  ...data,
@@ -25940,7 +26631,7 @@ const VisionOffTransformWrapper = ({ data, isSelected = false, isHover = false,
25940
26631
  }
25941
26632
  : {
25942
26633
  onMouseDown: handleCancel,
25943
- }), transform: `translate(${selectionBoxPoints[0].x}, ${selectionBoxPoints[0].y})`, children: jsxRuntime.jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale})`, children: jsxRuntime.jsx(SvgTransformDelete, {}) }) })), jsxRuntime.jsx("g", { className: "control-point rotate-point", ...(platform === exports.PlatformType.H5
26634
+ }), transform: `translate(${selectionBoxPoints[0].x}, ${selectionBoxPoints[0].y})`, children: jsxRuntime.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: jsxRuntime.jsx(SvgTransformDelete, {}) }) })), jsxRuntime.jsx("g", { className: "control-point rotate-point", ...(platform === exports.PlatformType.H5
25944
26635
  ? {
25945
26636
  onTouchStart: createReactEventHandler((e) => {
25946
26637
  handleRotateStartWithDisabled(e);
@@ -25948,7 +26639,7 @@ const VisionOffTransformWrapper = ({ data, isSelected = false, isHover = false,
25948
26639
  }
25949
26640
  : {
25950
26641
  onMouseDown: handleRotateStartWithDisabled,
25951
- }), transform: `translate(${selectionBoxPoints[3].x}, ${selectionBoxPoints[3].y})`, children: jsxRuntime.jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale})`, children: jsxRuntime.jsx(SvgTransformRotate, {}) }) }), jsxRuntime.jsx("g", { className: "control-point move-point", ...(platform === exports.PlatformType.H5
26642
+ }), transform: `translate(${selectionBoxPoints[3].x}, ${selectionBoxPoints[3].y})`, children: jsxRuntime.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: jsxRuntime.jsx(SvgTransformRotate, {}) }) }), jsxRuntime.jsx("g", { className: "control-point move-point", ...(platform === exports.PlatformType.H5
25952
26643
  ? {
25953
26644
  onTouchStart: createReactEventHandler((e) => {
25954
26645
  handleMouseDownWithDisabled(e);
@@ -25956,7 +26647,7 @@ const VisionOffTransformWrapper = ({ data, isSelected = false, isHover = false,
25956
26647
  }
25957
26648
  : {
25958
26649
  onMouseDown: handleMouseDownWithDisabled,
25959
- }), transform: `translate(${selectionBoxPoints[1].x}, ${selectionBoxPoints[1].y})`, children: jsxRuntime.jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale})`, children: jsxRuntime.jsx(SvgTransformTranslate, {}) }) }), jsxRuntime.jsx("g", { className: "control-point scale-point", ...(platform === exports.PlatformType.H5
26650
+ }), transform: `translate(${selectionBoxPoints[1].x}, ${selectionBoxPoints[1].y})`, children: jsxRuntime.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: jsxRuntime.jsx(SvgTransformTranslate, {}) }) }), jsxRuntime.jsx("g", { className: "control-point scale-point", ...(platform === exports.PlatformType.H5
25960
26651
  ? {
25961
26652
  onTouchStart: createReactEventHandler((e) => {
25962
26653
  handleScaleStartWithDisabled(e);
@@ -25964,7 +26655,7 @@ const VisionOffTransformWrapper = ({ data, isSelected = false, isHover = false,
25964
26655
  }
25965
26656
  : {
25966
26657
  onMouseDown: handleScaleStartWithDisabled,
25967
- }), transform: `translate(${selectionBoxPoints[2].x}, ${selectionBoxPoints[2].y})`, children: jsxRuntime.jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale})`, children: jsxRuntime.jsx(SvgTransformScale, {}) }) })] }));
26658
+ }), transform: `translate(${selectionBoxPoints[2].x}, ${selectionBoxPoints[2].y})`, children: jsxRuntime.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: jsxRuntime.jsx(SvgTransformScale, {}) }) })] }));
25968
26659
  })()] }));
25969
26660
  };
25970
26661
 
@@ -26509,9 +27200,9 @@ var SvgDisabledTranslate = function SvgDisabledTranslate(props) {
26509
27200
  })))));
26510
27201
  };
26511
27202
 
26512
- var css_248z$5 = ".index-module_doodleHover__jIZHV path {\n fill: #2EC1B2;\n}";
26513
- var styles$5 = {"doodleHover":"index-module_doodleHover__jIZHV"};
26514
- styleInject(css_248z$5);
27203
+ var css_248z$6 = ".index-module_doodleHover__jIZHV path {\n fill: #00B3A1;\n}";
27204
+ var styles$6 = {"doodleHover":"index-module_doodleHover__jIZHV"};
27205
+ styleInject(css_248z$6);
26515
27206
 
26516
27207
  const DoodleTransform = ({ data, isSelected: _isSelected, isHover, onSelect }) => {
26517
27208
  const { editMapInfo } = useMapEditContext();
@@ -26570,7 +27261,7 @@ const DoodleTransform = ({ data, isSelected: _isSelected, isHover, onSelect }) =
26570
27261
  return (jsxRuntime.jsx("g", { children: jsxRuntime.jsx("g", { style: {
26571
27262
  cursor: canSelect ? 'pointer' : 'inherit',
26572
27263
  pointerEvents: canSelect ? 'auto' : 'none',
26573
- }, className: isHover ? styles$5.doodleHover : '', transform: transform, opacity: data?.style?.opacity || 1, dangerouslySetInnerHTML: { __html: svgString }, onClick: onDoodleClick }) }));
27264
+ }, className: isHover ? styles$6.doodleHover : '', transform: transform, opacity: data?.style?.opacity || 1, dangerouslySetInnerHTML: { __html: svgString }, onClick: onDoodleClick }) }));
26574
27265
  }
26575
27266
  }
26576
27267
  catch (error) {
@@ -26580,12 +27271,13 @@ const DoodleTransform = ({ data, isSelected: _isSelected, isHover, onSelect }) =
26580
27271
  };
26581
27272
 
26582
27273
  const DoodleTransformWrapper = ({ data, isSelected = false, isSelectedWithoutOperation = false, isHover = false, onSelect, onCancel, onTransformChange, disabled = false, className = '', minScale, onMinScaleReached, onDragMove, onDragEnd, showInfo = true, onClickInfo, }) => {
26583
- const { platform, overlayScale, unitType } = useCommonContext();
27274
+ const { platform, overlayScale, unitType, locale } = useCommonContext();
26584
27275
  // 使用自定义hook管理所有变换逻辑
26585
27276
  const { currentCenter, currentScale, currentDirection, isDragging, isRotating, isScaling, initializeTransform, calculateSelectionBoxPoints, handleMouseDown, handleRotateStart, handleScaleStart, handleMouseMove, handleMouseUp, containerRef, dimensions, } = useDoodleTransform(data, onTransformChange, {
26586
27277
  minScale,
26587
27278
  onMinScaleReached,
26588
27279
  });
27280
+ const [hoverType, setHoverType] = React.useState(null);
26589
27281
  // 创建变换后的数据对象 - 需要将转换后的坐标转换回原始格式
26590
27282
  const transformedData = React.useMemo(() => {
26591
27283
  // 将转换后的坐标转换回原始格式(除以 SCALE_FACTOR 并翻转 Y 轴)
@@ -26706,7 +27398,7 @@ const DoodleTransformWrapper = ({ data, isSelected = false, isSelectedWithoutOpe
26706
27398
  const infoBoxWidth = 300 * overlayScale; // 信息框宽度
26707
27399
  const infoBoxHeight = 40 * overlayScale; // 信息框高度
26708
27400
  const infoBoxX = centerX - infoBoxWidth / 2; // 居中对齐
26709
- const circleScale = platform === exports.PlatformType.H5 ? 1.5 * overlayScale : 2 * overlayScale;
27401
+ const circleScale = platform === exports.PlatformType.H5 ? 1.5 * overlayScale : 3 * overlayScale;
26710
27402
  const disacledScale = circleScale * 1.2;
26711
27403
  return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.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 === exports.PlatformType.H5
26712
27404
  ? {
@@ -26736,7 +27428,7 @@ const DoodleTransformWrapper = ({ data, isSelected = false, isSelectedWithoutOpe
26736
27428
  justifyContent: 'center',
26737
27429
  gap: '10px',
26738
27430
  color: '#325069',
26739
- }, children: jsxRuntime.jsxs("div", { children: ["Remaining time: ", remainingTime] }) }) }) }))] }));
27431
+ }, children: jsxRuntime.jsxs("div", { children: [locale?.['map.renderer.remainingTime'] || 'Remaining time', ": ", remainingTime] }) }) }) }))] }));
26740
27432
  };
26741
27433
  React.useEffect(() => {
26742
27434
  const isMove = isDragging || isRotating || isScaling;
@@ -26788,7 +27480,7 @@ const DoodleTransformWrapper = ({ data, isSelected = false, isSelectedWithoutOpe
26788
27480
  const infoBoxWidth = 300 * overlayScale; // 信息框宽度
26789
27481
  const infoBoxHeight = 100 * overlayScale; // 信息框高度
26790
27482
  const infoBoxX = centerX - infoBoxWidth / 2; // 居中对齐
26791
- const scale = platform === exports.PlatformType.H5 ? 1.8 * overlayScale : 2 * overlayScale;
27483
+ const scale = platform === exports.PlatformType.H5 ? 1.8 * overlayScale : 3 * overlayScale;
26792
27484
  const offsetLeft = 10 * scale;
26793
27485
  return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [showInfo && (jsxRuntime.jsx("foreignObject", { x: infoBoxX, y: infoBoxY, width: infoBoxWidth, height: infoBoxHeight, style: { textAlign: 'center' }, onClick: () => {
26794
27486
  onClickInfo?.();
@@ -26809,7 +27501,7 @@ const DoodleTransformWrapper = ({ data, isSelected = false, isSelectedWithoutOpe
26809
27501
  alignItems: 'center',
26810
27502
  justifyContent: 'center',
26811
27503
  gap: `${10 * overlayScale}px`,
26812
- }, children: jsxRuntime.jsxs("div", { children: ["Active Time: ", activeTime, " >"] }) })] }) })), jsxRuntime.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 === exports.PlatformType.H5
27504
+ }, children: jsxRuntime.jsxs("div", { children: [locale?.['map.renderer.activeTime'] || 'Active Time', ": ", activeTime, " >"] }) })] }) })), jsxRuntime.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 === exports.PlatformType.H5
26813
27505
  ? {
26814
27506
  onTouchStart: createReactEventHandler((e) => {
26815
27507
  handleMouseDownWithDisabled(e);
@@ -26825,7 +27517,7 @@ const DoodleTransformWrapper = ({ data, isSelected = false, isSelectedWithoutOpe
26825
27517
  }
26826
27518
  : {
26827
27519
  onMouseDown: handleCancel,
26828
- }), transform: `translate(${selectionBoxPoints[0].x}, ${selectionBoxPoints[0].y})`, children: jsxRuntime.jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale})`, children: jsxRuntime.jsx(SvgTransformDelete, {}) }) })), jsxRuntime.jsx("g", { className: "control-point rotate-point", ...(platform === exports.PlatformType.H5
27520
+ }), transform: `translate(${selectionBoxPoints[0].x}, ${selectionBoxPoints[0].y})`, children: jsxRuntime.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: jsxRuntime.jsx(SvgTransformDelete, {}) }) })), jsxRuntime.jsx("g", { className: "control-point rotate-point", ...(platform === exports.PlatformType.H5
26829
27521
  ? {
26830
27522
  onTouchStart: createReactEventHandler((e) => {
26831
27523
  handleRotateStartWithDisabled(e);
@@ -26833,7 +27525,7 @@ const DoodleTransformWrapper = ({ data, isSelected = false, isSelectedWithoutOpe
26833
27525
  }
26834
27526
  : {
26835
27527
  onMouseDown: handleRotateStartWithDisabled,
26836
- }), transform: `translate(${selectionBoxPoints[3].x}, ${selectionBoxPoints[3].y})`, children: jsxRuntime.jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale})`, children: jsxRuntime.jsx(SvgTransformRotate, {}) }) }), jsxRuntime.jsx("g", { className: "control-point move-point", ...(platform === exports.PlatformType.H5
27528
+ }), transform: `translate(${selectionBoxPoints[3].x}, ${selectionBoxPoints[3].y})`, children: jsxRuntime.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: jsxRuntime.jsx(SvgTransformRotate, {}) }) }), jsxRuntime.jsx("g", { className: "control-point move-point", ...(platform === exports.PlatformType.H5
26837
27529
  ? {
26838
27530
  onTouchStart: createReactEventHandler((e) => {
26839
27531
  handleMouseDownWithDisabled(e);
@@ -26841,7 +27533,7 @@ const DoodleTransformWrapper = ({ data, isSelected = false, isSelectedWithoutOpe
26841
27533
  }
26842
27534
  : {
26843
27535
  onMouseDown: handleMouseDownWithDisabled,
26844
- }), transform: `translate(${selectionBoxPoints[1].x}, ${selectionBoxPoints[1].y})`, children: jsxRuntime.jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale})`, children: jsxRuntime.jsx(SvgTransformTranslate, {}) }) }), jsxRuntime.jsx("g", { className: "control-point scale-point", ...(platform === exports.PlatformType.H5
27536
+ }), transform: `translate(${selectionBoxPoints[1].x}, ${selectionBoxPoints[1].y})`, children: jsxRuntime.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: jsxRuntime.jsx(SvgTransformTranslate, {}) }) }), jsxRuntime.jsx("g", { className: "control-point scale-point", ...(platform === exports.PlatformType.H5
26845
27537
  ? {
26846
27538
  onTouchStart: createReactEventHandler((e) => {
26847
27539
  handleScaleStartWithDisabled(e);
@@ -26849,7 +27541,7 @@ const DoodleTransformWrapper = ({ data, isSelected = false, isSelectedWithoutOpe
26849
27541
  }
26850
27542
  : {
26851
27543
  onMouseDown: handleScaleStartWithDisabled,
26852
- }), transform: `translate(${selectionBoxPoints[2].x}, ${selectionBoxPoints[2].y})`, children: jsxRuntime.jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale})`, children: jsxRuntime.jsx(SvgTransformScale, {}) }) })] }));
27544
+ }), transform: `translate(${selectionBoxPoints[2].x}, ${selectionBoxPoints[2].y})`, children: jsxRuntime.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: jsxRuntime.jsx(SvgTransformScale, {}) }) })] }));
26853
27545
  })()] }));
26854
27546
  };
26855
27547
 
@@ -27407,9 +28099,9 @@ const SvgMapComponent = React.forwardRef(({ editMap, pathData, mapConfig = {}, m
27407
28099
  // 设置显示名称以便调试
27408
28100
  SvgMapComponent.displayName = 'SvgMapComponent';
27409
28101
 
27410
- var css_248z$4 = ".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}";
27411
- var styles$4 = {"mowerPosition":"index-module_mowerPosition__yLpIU"};
27412
- styleInject(css_248z$4);
28102
+ 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}";
28103
+ var styles$5 = {"mowerPosition":"index-module_mowerPosition__yLpIU"};
28104
+ styleInject(css_248z$5);
27413
28105
 
27414
28106
  const usePosition = ({ mowerPositionData, viewBox, modelType, hasEdger, mapData, realTimeData, onMowingPositionChange, }) => {
27415
28107
  // DOM 元素引用,用于直接操作
@@ -27568,10 +28260,10 @@ const usePosition = ({ mowerPositionData, viewBox, modelType, hasEdger, mapData,
27568
28260
  }
27569
28261
  else {
27570
28262
  // 检查位置是否有效
27571
- const positionOutOfRange = isOutOfRange(mowerPositionData);
28263
+ // const positionOutOfRange = isOutOfRange(mowerPositionData);
27572
28264
  const positionInvalid = isInvalidPosition(mowerPositionData);
27573
28265
  const isStandby = mowerPositionData.vehicleState === RobotStatus.STANDBY;
27574
- if (positionOutOfRange || positionInvalid || isOffLine) {
28266
+ if (positionInvalid || isOffLine) {
27575
28267
  // 位置无效时,尝试使用上次位置或充电桩位置
27576
28268
  updatePositionByLastPosition(mowerPositionData);
27577
28269
  }
@@ -27635,7 +28327,7 @@ const MowerPosition = React.memo(({ editMap, mowerPositionData, viewBox, modelTy
27635
28327
  zIndex: 1000,
27636
28328
  opacity: isHighlight ? SvgMapOpacity.HIGHLIGHT : SvgMapOpacity.UN_HIGHLIGHT,
27637
28329
  }), [isHighlight]);
27638
- return (jsxRuntime.jsx("div", { className: styles$4.mowerPosition, style: containerStyle, children: jsxRuntime.jsx("div", { ref: elementRef, className: "mower-position", style: {
28330
+ return (jsxRuntime.jsx("div", { className: styles$5.mowerPosition, style: containerStyle, children: jsxRuntime.jsx("div", { ref: elementRef, className: "mower-position", style: {
27639
28331
  position: 'absolute',
27640
28332
  width: 30,
27641
28333
  height: 30,
@@ -27647,11 +28339,12 @@ const MowerPosition = React.memo(({ editMap, mowerPositionData, viewBox, modelTy
27647
28339
  }, children: jsxRuntime.jsx("img", { src: mowerImage, style: { width: '100%', height: '100%', objectFit: 'contain' } }) }) }));
27648
28340
  });
27649
28341
 
27650
- var css_248z$3 = ".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}";
27651
- var styles$3 = {"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"};
27652
- styleInject(css_248z$3);
28342
+ 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}";
28343
+ 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"};
28344
+ styleInject(css_248z$4);
27653
28345
 
27654
28346
  const useBoundaryLabels = ({ mapData, pathData, unitType, viewBox, mowPartitionData, realTimeData = [], onlyRead = false, }) => {
28347
+ const { locale } = useCommonContext();
27655
28348
  const [processStateIsMowing, updateProcessStateIsMowing] = React.useState(false);
27656
28349
  // 处理地图分区边界
27657
28350
  React.useMemo(() => {
@@ -27691,6 +28384,9 @@ const useBoundaryLabels = ({ mapData, pathData, unitType, viewBox, mowPartitionD
27691
28384
  return generateBoundaryData(mapData, newPathData);
27692
28385
  }
27693
28386
  }, [mapData, pathData, realTimeData]);
28387
+ const coverageStr = React.useMemo(() => {
28388
+ return locale?.['map.renderer.coverage'] || 'Coverage';
28389
+ }, [locale]);
27694
28390
  const items = React.useMemo(() => {
27695
28391
  // console.log('boundartItme->', viewBox, mapData)
27696
28392
  if (!mapData || !viewBox)
@@ -27700,7 +28396,7 @@ const useBoundaryLabels = ({ mapData, pathData, unitType, viewBox, mowPartitionD
27700
28396
  const results = [];
27701
28397
  for (const boundary of boundaryData) {
27702
28398
  // 多边形重心
27703
- const centroid = computePolygonCentroid(boundary.points);
28399
+ const centroid = computePolygonCentroid$1(boundary.points);
27704
28400
  if (!centroid)
27705
28401
  continue;
27706
28402
  const relX = (centroid.x - viewBox.x) / viewBox.width;
@@ -27711,9 +28407,9 @@ const useBoundaryLabels = ({ mapData, pathData, unitType, viewBox, mowPartitionD
27711
28407
  const baseText = onlyRead ? `${boundary.name}` : `${boundary.name} (${progress})`;
27712
28408
  const totalArea = convertAreaByUnits(boundary.area || 0, unitType);
27713
28409
  const finishedArea = convertAreaByUnits(boundary.finishedArea || 0, unitType);
27714
- const coverageText = `Coverage: ${finishedArea.value}/${totalArea.value}`;
28410
+ const coverageText = `${coverageStr}: ${finishedArea.value}/${totalArea.value}`;
27715
28411
  const isMowing = mowingIds.includes(boundary.id);
27716
- const dateText = formatBoundaryDateText(isMowing ? Date.now() / 1000 : boundary.endTime || 0);
28412
+ const dateText = formatBoundaryDateText(isMowing ? Date.now() / 1000 : boundary.endTime || 0, locale);
27717
28413
  results.push({
27718
28414
  id: boundary.id,
27719
28415
  name: boundary.name,
@@ -27737,11 +28433,13 @@ const useBoundaryLabels = ({ mapData, pathData, unitType, viewBox, mowPartitionD
27737
28433
  mowPartitionData,
27738
28434
  boundaryData,
27739
28435
  onlyRead,
28436
+ coverageStr,
28437
+ locale,
27740
28438
  ]);
27741
28439
  const containerZIndex = 900;
27742
28440
  return { items, containerZIndex };
27743
28441
  };
27744
- function computePolygonCentroid(points) {
28442
+ function computePolygonCentroid$1(points) {
27745
28443
  if (!points || points.length < 3)
27746
28444
  return null;
27747
28445
  const valid = points.filter((p) => p.length >= 2);
@@ -27814,7 +28512,7 @@ const BoundaryLabels = React.memo(({ editMap, mapData, pathData, unitType, langu
27814
28512
  display: editMap ? 'none' : 'block',
27815
28513
  opacity: isHighlight ? SvgMapOpacity.HIGHLIGHT : SvgMapOpacity.UN_HIGHLIGHT,
27816
28514
  }), [containerZIndex, expandedId, editMap, isHighlight]);
27817
- return (jsxRuntime.jsx("div", { className: styles$3.container, style: containerStyle, children: items.map((item) => (jsxRuntime.jsxs("div", { className: `${styles$3.boundaryLabel} boundary-label`, "data-boundary-id": item.id, style: {
28515
+ return (jsxRuntime.jsx("div", { className: styles$4.container, style: containerStyle, children: items.map((item) => (jsxRuntime.jsxs("div", { className: `${styles$4.boundaryLabel} boundary-label`, "data-boundary-id": item.id, style: {
27818
28516
  transform: `translate(-50%, -50%) rotate(${-rotation}deg)`,
27819
28517
  left: `${item.leftPct}%`,
27820
28518
  top: `${item.topPct}%`,
@@ -27825,12 +28523,12 @@ const BoundaryLabels = React.memo(({ editMap, mapData, pathData, unitType, langu
27825
28523
  if (onlyRead)
27826
28524
  return;
27827
28525
  handleClick(item.id);
27828
- }, children: [jsxRuntime.jsx("div", { className: styles$3.base, children: item.baseText }), jsxRuntime.jsxs("div", { className: styles$3.extended, style: { display: expandedId === item.id ? 'block' : 'none' }, children: [jsxRuntime.jsx("div", { style: { marginBottom: 3, fontWeight: 'bold' }, children: item.coverageText }), jsxRuntime.jsx("div", { children: item.dateText })] }), item.isIsolated && (jsxRuntime.jsx("div", { className: styles$3.isolatedIcon, dangerouslySetInnerHTML: { __html: ISOLATED_BOUNDARY_SVG } }))] }, item.id))) }));
28526
+ }, children: [jsxRuntime.jsx("div", { className: styles$4.base, children: item.baseText }), jsxRuntime.jsxs("div", { className: styles$4.extended, style: { display: expandedId === item.id ? 'block' : 'none' }, children: [jsxRuntime.jsx("div", { style: { marginBottom: 3, fontWeight: 'bold' }, children: item.coverageText }), jsxRuntime.jsx("div", { children: item.dateText })] }), item.isIsolated && (jsxRuntime.jsx("div", { className: styles$4.isolatedIcon, dangerouslySetInnerHTML: { __html: ISOLATED_BOUNDARY_SVG } }))] }, item.id))) }));
27829
28527
  });
27830
28528
 
27831
- var css_248z$2 = ".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}";
27832
- var styles$2 = {"container":"index-module_container__gB52e","item":"index-module_item__zHoZP","icon":"index-module_icon__CS56A","tooltip":"index-module_tooltip__NMCmR"};
27833
- styleInject(css_248z$2);
28529
+ 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}";
28530
+ var styles$3 = {"container":"index-module_container__gB52e","item":"index-module_item__zHoZP","icon":"index-module_icon__CS56A","tooltip":"index-module_tooltip__NMCmR"};
28531
+ styleInject(css_248z$3);
27834
28532
 
27835
28533
  var antennaOneOnline = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAACXBIWXMAACxLAAAsSwGlPZapAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAA02SURBVHgB7V1bjNXGGf59LnsPWchSKWoVDlAuTakKUdrQl7K0BaVSpVBFqpRIbZb2saqAh4pHwmtVqUXNa5PNUx+Bh0ikRLBQECkg2CRqIOGyZguqFBZ2ueztrM+Zzm/P2GMfn5nx8ewen7P5tLO2x+M59n+dGY//sSCjIIT0081WltbQVGKpX0gipliyhe0dmkYxWZY1BRmEBRkBI/gemnbQNAgesU1ilKUzNI1Qhtiw3IFEp2k/TafJ0uM0TUM0lWC5gT70IGkO0evhKE17oJ1BAmkfI9nFGE1D0G6gD7WPpknSOhgj7cAI4pmaMdK6GCOtyAh60yXi2dV2wXtkkZy18WYovdG36OavUNtOb6QuWFhwoFIlUKlU6dZLiCrbctBmJeRzOX+by1lQLOahkM+7eQZg03SY1jUMBmGMAcRrxx+iaT80CE7wBacC83QbJXKjyFGGdBTy0FEsUKYU0jIEheuwqY6dEQYw9TwNDXaeFhwHZucW3C0yYbHR1VF0mdFBtw3Cpmmnic5cagZQguFQARI/kclBQs/OlWF2vrwkRI8DakZPVydlRt7dTwibpl9SJoxCCqRiALP3wwmvaTrho0Dio1b0dHdCAxiiTHgfGkTDDKDE2weePdTGHCX6DCW+KdtuGlwjujoTm6YDlAmJaMHREAOSEh8J/mR6zrXxrQD0D309XUnNUkNMSMyApGYHpX56dj4z5kYX2FLq7e5Kqg2JzVEiBjCHe1WzLMxQwqOtb2V0d3ZAL9WGBNiWxDFrMyBJUxNNzuOnM+BUsmnrk6KQz8GKvh5dk4T9g226TVQtBrBOFkp+SVUWiT/1ZCazjrZRIPH7n9Fmgg0eE5SdNV0vgz3ckqpQuxIfkfDZSuDRTAmlBhBvNPA9Vbl2Jr6IhJqgdMpSBuja/eVCfI4ETFD6A1UNWqYHHe5yIT6CNzKq6qY1+s6/yArUZQAzPUOgwPTMXNu0dpIAn3mWNrM1sIfScrDeybomiF40Bgrpx07WU8qA5QzsrHV3daiK2dQMrY07EasBTPpLIAGq4bSeBLQ1ZubmdcwvviGMfU8SqwE60v/46SyUFxbABO7e+wouXvwcJu5Pwvy8VycR/wu2Fnctmk+Ce+V7/I8XAu8UEU4T6KRDC6tXr4It310PW7Z8G0ygSF/2PPtMr6oYOuS10b5BDQN0mp0mTc8/T34M167Z/LchREgv09tE90PH4jW1TOPZQTnv3Msvvwg/++l2MIG+nm6dcaOaAbs4Biil/+Gjp0ZaPSMjl+HK1S9cQsYSPMoMJB0J8qNS7hOd5ZHYfRD0B+CHP9gCu3b9CNLCbZqu6IWc/HVnjS8oiAfEmx1WktWgafOUeESZeOny5xAQz5MGwojM97nEWtzwhAgqMk4gbAwTASKawspcuDAKGza+AKU134Q0QJrM0Xcdipc66AsGKRNGeEbUCb8Fqh+ZN2P3z527CtWKA5VKxU1Vmhx27OXT5FT88+45+j4Bt04lyPcSK8/3q94W6+R1OMJv+WWq3vGpU/8GE8CRX42+wSHxwNcA1uuVzo8sG5qpMH7nf/DJJ9fBc6dV0cb4ZkQ0SVHzVFuOxJodt37mV2pMkHD9rZvjcPv2XVi37luQBvhbGlowiIOb3BmLGjAICuDrRBM4c/aSK31hqaz4ku1EpLrqH0elOSz5TkgjWLmqoCmiZuA8I143zf/oo/NgAprvP4b4jugDpOanXDYj/bZ9D27d+i87CiQzVpJZViiHRKUcavwB+o+QKSDxLSP/Oppu3LDpfY3D+vUvQBq4c5uoqSwWCrJirwF7pZtjF5VAoQGm2vynTn0csr9cYqtVLpmVkF+oxPgD91xV0BZH0BT0E45XV1U4F/UZWIb7FJ5/4sRZMIFZtaUYZO9YfA0YlJV2bVs5PQOuXPkP3KRSFrLREXvMfjBmP2hiEqFlw+8vdl/QKKE6CJqz4XNffnnb1YQNG0qQBjizDzVQ0SRFfzvMfcAOaYULFTCBkyfPR6TXCUloWGLD9j1o0YiawiRZKF+tRrWG+QPHS5UY/+E4vB4HPvjgFKSFZ4aUNHNpzjVgq6ykCfNz+fJnMDExydr0EJLAcGeK5flZ4fwgW8wXy0W1IZxPYvoFwT6Ba9dvwBdUEzZtXAdpgHNcO4tSPzCI/wok+BqxLspOeg349NPrroQhaokm7gdEtsRjv0zYfISY4e+TMCNZfuDwxd+qZeT585dSMwCb7AqUkPbIIinx8SZNtH5Q+lHdeZ1+/V4G1GhASOIJhHuwROIvSF2tEAfuSGwryvsR2x6HtECaafiBrUoGOBUz9h9bOWjz6ztcgUiRTlZAt5iyAHWcsKgFEVPDqqjVBK/j1tnZ8KzpENAPKMyQy4CStBJDDvj5578B9p27woBZlJjefpw5EpkQNx7kX8GlmGXU9B0i9Qd8CX4PZ8Stem4lmEAV3xTKeVlCBqyRVmLoXe/27dvg3PmL7kPGOcqALmGJ5WVEwtYSv9axhuqwxN8KgMTmefyjDTze8eP0o6OIivpVrcsA6bx+/DzIBDZtWg+vvEKZcM4b+KqVTlBqR6iMsB8lZNy7AE7g2PNCXT9/9Sfw4nc2gglU1MK7RmmC4m62Ubz5xh63nX72XxfCjjJiClS/GyUiNx1R7RIh9xceUPJ/8+tfgSloMKDfojcxCRIteDj1RGeINRHOnL3gMuH+Vw/g/sSD0DlOSNEkRBF3rl5evev5bwwMPAerB1bB66//wpjkc+BLmlXP9smKTCEDpNSdmHwMzcaHH56GP/35HfaSBgkIEL3raN7BP/4Bdu8ehGZjYOUK6XlpGykzoNTFz07dXUZkK8fYQULF2A60DFqCAdiZyefzAL4OAARUJkJ+kGflWoMLLcEAJGY+n+xWDX2cvejAp8JXY3WdMDqSZs/7DDQgQJwfCF2TAQ3QEIIpJQOyANSAQiHvEjxowUguwHLanz4sHjSEwGdAXeQzoAGWlWtJE5SzlEJg41PZIBmQy1MumnkZ2TisGBOkvKZBEzQx8RD+9s67MD5+z887ePD3sHlT8mmMefX3A4+QAXdkJVD1odxcFngakGf74rBD2A+Ix41owMmTZ+HY8RMwMzMLJqBhgnwNqF+J1XxbihAJGt6vZUpSoNT//d1/wPXrN8EkMFyOAjZSdzRlJUuCqESLx9ERzaRAqefE7+nphpde+h6YgIbZHEUNkDLAcnuhzXfEUYgDcKqBOF1s3rwefvfbN2HiwUO4cuUzSAOkWU4tEKMFnCJHb9wGyagovtXJyhfvormpN/CWVBNQ6t+gI7W7d+1wj5EBaYEBohRwo/nytt0ZkDAAZ3llgQHKcX9Jvgx7XnvVZYJJFNXBoFzLwz3siLQyDGiUgXZ1PeJz8yO+dEkC08RHFNUagELvM+CYrCQ+kEaFi45oS6hRgi82MPiThsCO4D+XAWyq9Ii0UvWXgEuCeq2hLDFBw/yM8I+3xUb+cdkV6AeaZYZaZGDTBbZ+FFNREO/75YXMYcVFTdMCw29EFxWacedG+I7PAB0zhMGLsuCM65mbZpshHp9UgeOWEDsiOs5wWHYlPmAWfIFq5kOz4Dpf9QDcsHgQKs2+3rNBAlcLctkZH8qK83UjLqrNj03vN9TijKPkEZAAH7ine2m1gA+4RSEbflhqxmja/hoLE8eAYVC8pOnq6FjSfoE3X7d2WqHsOI05wo7ZwMBKP6nQUSy65kcBOy7wd71YERhYQhrnxg3S9Hja+KStVgMyfiV+Ia82y3u1GYDQCVmA40PTyzxcTV9vt670r407IWPbXlAAHTKm5You+uxdehHY67Yu6zKAtYikY0QIdD4YV3O5AZ+5Ty+g67AlWfRBJ2gfxguVTlv5OmhfXdigWGdAWgO78ACYu6GWR8JnPWwpIugqa2Hqc0RVbjkwIeEzHrE01pv5OnSxJpoauphVtBMUHTS3Qnaj7eSY8VkSEn+npbnIz6KFr0dgH6HVw9djU7OXtvQSDG0kCl+fSExZxcr+AQfG3e/r7crEEHZSuAs44P3TlID4e62Ei/o0uoSJcqhChBtjlGrD/EJrLGGCb/+e6c3oEiYcSZmAmCuXYWY244v4UHPT1ZHxRXw4iGZo+ygw8iIG/8sKI9DEYPhhHFZpYBh7r5ViecPUxpk55qOg0UQVgcQvO05TNSIl4d2WodXMhdw4SMqlDDEeEcakm1uCafDeHKcCJXxRFddNBhuyspQhB+usvU3TPmgQPOAdMgNjFJlczLPTXcgz7xI95dsyHBV428rSYp4imF84BA1qQ6QuN1wORmyp4pK2lCGYx7cieIsFv0pxUyHnft1jgOAcSPADluHlbBcFxFvQeZi0D3Bx6hK0GuhND5HWX9J8EFodpPUYMUnqLLjQ0iDZZ8QYTfsJC6ratqAPiAvbHCXZwWnSDqYmKYjnrIcYAZpB9KZLe2aGKUkQvxrT90ERzbEB2OBNPj5D0zFT7fi0yOw4MQkCymIqgRdcsJ/t90PtRIEpIdngfYCOWxwqGM0KwaP4P/LyfA2l5RlyAAAAAElFTkSuQmCC";
27836
28534
 
@@ -27842,6 +28540,7 @@ var antennaTwoOffline = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgC
27842
28540
 
27843
28541
  const useAntennaInfo = (data = [], options = {}) => {
27844
28542
  const { viewBox } = options;
28543
+ const { locale } = useCommonContext();
27845
28544
  const items = React.useMemo(() => {
27846
28545
  if (!data || data.length === 0 || !viewBox)
27847
28546
  return [];
@@ -27872,8 +28571,8 @@ const useAntennaInfo = (data = [], options = {}) => {
27872
28571
  isOnline: antenna1.status === 1,
27873
28572
  imageSrc: getImage(1, antenna1.status === 1),
27874
28573
  tooltip: {
27875
- title: 'Primary Antenna',
27876
- statusText: antenna1.status === 1 ? 'Online' : 'Offline',
28574
+ title: locale?.['map.renderer.primaryAntenna'] || 'Primary Antenna',
28575
+ statusText: antenna1.status === 1 ? (locale?.['map.renderer.online'] || 'Online') : (locale?.['map.renderer.online'] || 'Offline'),
27877
28576
  minWidth: 130,
27878
28577
  },
27879
28578
  });
@@ -27912,15 +28611,15 @@ const useAntennaInfo = (data = [], options = {}) => {
27912
28611
  isOnline: antenna2.status === 1,
27913
28612
  imageSrc: getImage(2, antenna2.status === 1),
27914
28613
  tooltip: {
27915
- title: 'Signal Enhancement Antenna',
27916
- statusText: antenna2.status === 1 ? 'Online' : 'Offline',
27917
- syncText: antenna2.status === 1 ? ', Synced' : ', To be synced',
28614
+ title: locale?.['map.renderer.signalAntenna'] || 'Signal Enhancement Antenna',
28615
+ statusText: antenna2.status === 1 ? (locale?.['map.renderer.online'] || 'Online') : (locale?.['map.renderer.offline'] || 'Offline'),
28616
+ syncText: antenna2.status === 1 ? `, ${locale?.['map.renderer.synced'] || 'Synced'}` : `, ${locale?.['map.renderer.toBeSynced'] || 'To be synced'}`,
27918
28617
  minWidth: 195,
27919
28618
  },
27920
28619
  });
27921
28620
  }
27922
28621
  return results;
27923
- }, [data, viewBox?.x, viewBox?.y, viewBox?.width, viewBox?.height]);
28622
+ }, [data, viewBox?.x, viewBox?.y, viewBox?.width, viewBox?.height, locale]);
27924
28623
  // default stacking order (slightly higher than boundary labels' 900)
27925
28624
  const containerZIndex = 950;
27926
28625
  return { items, containerZIndex };
@@ -27948,13 +28647,13 @@ const Antennas = React.memo(({ editMap, antennaConfig = [], viewBox, rotation =
27948
28647
  document.addEventListener('pointerdown', handleOutside);
27949
28648
  return () => document.removeEventListener('pointerdown', handleOutside);
27950
28649
  }, []);
27951
- return (jsxRuntime.jsx("div", { className: styles$2.container, style: containerStyle, children: items.map((item) => (jsxRuntime.jsxs("div", { className: `antenna-container-item ${styles$2.item}`, style: {
28650
+ return (jsxRuntime.jsx("div", { className: styles$3.container, style: containerStyle, children: items.map((item) => (jsxRuntime.jsxs("div", { className: `antenna-container-item ${styles$3.item}`, style: {
27952
28651
  left: `${item.leftPct}%`,
27953
28652
  top: `${item.topPct}%`,
27954
28653
  transform: `translate(calc(-50% + ${item.offsetX || 0}px), calc(-50% + ${item.offsetY || 0}px)) rotate(${-rotation}deg)`,
27955
28654
  zIndex: openType === item.type ? 10000 : containerZIndex,
27956
28655
  pointerEvents: onlyRead ? 'none' : 'auto',
27957
- }, children: [jsxRuntime.jsx("div", { className: `antenna vector-antenna antenna-${item.type} ${item.isOnline ? 'antenna-online' : 'antenna-offline'} ${styles$2.icon}`, style: {}, onClick: (e) => {
28656
+ }, children: [jsxRuntime.jsx("div", { className: `antenna vector-antenna antenna-${item.type} ${item.isOnline ? 'antenna-online' : 'antenna-offline'} ${styles$3.icon}`, style: {}, onClick: (e) => {
27958
28657
  e.stopPropagation();
27959
28658
  if (onlyRead)
27960
28659
  return;
@@ -27969,95 +28668,15 @@ const Antennas = React.memo(({ editMap, antennaConfig = [], viewBox, rotation =
27969
28668
  return;
27970
28669
  e.currentTarget.style.transform =
27971
28670
  'scale(1)';
27972
- }, children: jsxRuntime.jsx("img", { src: item.imageSrc, style: { width: '100%', height: '100%', objectFit: 'contain', opacity: 1 } }) }), jsxRuntime.jsxs("div", { className: styles$2.tooltip, style: {
28671
+ }, children: jsxRuntime.jsx("img", { src: item.imageSrc, style: { width: '100%', height: '100%', objectFit: 'contain', opacity: 1 } }) }), jsxRuntime.jsxs("div", { className: styles$3.tooltip, style: {
27973
28672
  display: openType === item.type ? 'block' : 'none',
27974
28673
  minWidth: item.tooltip.minWidth,
27975
28674
  }, children: [jsxRuntime.jsx("div", { style: { marginBottom: 4, fontWeight: 600 }, children: item.tooltip.title }), jsxRuntime.jsxs("div", { style: { color: 'rgba(65, 93, 116, 1)', fontWeight: 400 }, children: [item.tooltip.statusText, item.tooltip.syncText || ''] })] })] }, `antenna-${item.type}`))) }));
27976
28675
  });
27977
28676
 
27978
- var css_248z$1 = ".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}";
27979
- var styles$1 = {"mapEdit":"index-module_mapEdit__XC5Gw","createMode":"index-module_createMode__14EPH","notCreateCursor":"index-module_notCreateCursor__1-OjF"};
27980
- styleInject(css_248z$1);
27981
-
27982
- var classnames = {exports: {}};
27983
-
27984
- /*!
27985
- Copyright (c) 2018 Jed Watson.
27986
- Licensed under the MIT License (MIT), see
27987
- http://jedwatson.github.io/classnames
27988
- */
27989
-
27990
- (function (module) {
27991
- /* global define */
27992
-
27993
- (function () {
27994
-
27995
- var hasOwn = {}.hasOwnProperty;
27996
-
27997
- function classNames () {
27998
- var classes = '';
27999
-
28000
- for (var i = 0; i < arguments.length; i++) {
28001
- var arg = arguments[i];
28002
- if (arg) {
28003
- classes = appendClass(classes, parseValue(arg));
28004
- }
28005
- }
28006
-
28007
- return classes;
28008
- }
28009
-
28010
- function parseValue (arg) {
28011
- if (typeof arg === 'string' || typeof arg === 'number') {
28012
- return arg;
28013
- }
28014
-
28015
- if (typeof arg !== 'object') {
28016
- return '';
28017
- }
28018
-
28019
- if (Array.isArray(arg)) {
28020
- return classNames.apply(null, arg);
28021
- }
28022
-
28023
- if (arg.toString !== Object.prototype.toString && !arg.toString.toString().includes('[native code]')) {
28024
- return arg.toString();
28025
- }
28026
-
28027
- var classes = '';
28028
-
28029
- for (var key in arg) {
28030
- if (hasOwn.call(arg, key) && arg[key]) {
28031
- classes = appendClass(classes, key);
28032
- }
28033
- }
28034
-
28035
- return classes;
28036
- }
28037
-
28038
- function appendClass (value, newClass) {
28039
- if (!newClass) {
28040
- return value;
28041
- }
28042
-
28043
- if (value) {
28044
- return value + ' ' + newClass;
28045
- }
28046
-
28047
- return value + newClass;
28048
- }
28049
-
28050
- if (module.exports) {
28051
- classNames.default = classNames;
28052
- module.exports = classNames;
28053
- } else {
28054
- window.classNames = classNames;
28055
- }
28056
- }());
28057
- } (classnames));
28058
-
28059
- var classnamesExports = classnames.exports;
28060
- var classNames = /*@__PURE__*/getDefaultExportFromCjs(classnamesExports);
28677
+ 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}";
28678
+ var styles$2 = {"mapEdit":"index-module_mapEdit__XC5Gw","createMode":"index-module_createMode__14EPH","notCreateCursor":"index-module_notCreateCursor__1-OjF"};
28679
+ styleInject(css_248z$2);
28061
28680
 
28062
28681
  /******************************************************************************
28063
28682
  Copyright (c) Microsoft Corporation.
@@ -29057,7 +29676,7 @@ const CreateObstacleElement = React.forwardRef(({ enabled = false, svgElement, o
29057
29676
  if (!enabled || points.length === 0) {
29058
29677
  return null;
29059
29678
  }
29060
- return (jsxRuntime.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 }));
29679
+ return (jsxRuntime.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 }));
29061
29680
  });
29062
29681
  CreateObstacleElement.displayName = 'CreateObstacleElement';
29063
29682
 
@@ -29095,7 +29714,7 @@ const useCreateVisionOffElement = () => {
29095
29714
  [maxX, minY],
29096
29715
  ];
29097
29716
  return elementPoints;
29098
- }, [centerPoint, elementWidth, editMapInfo.allVisionOffPoints]);
29717
+ }, [centerPoint, elementWidth, editMapInfo.allVisionOffPoints, svgViewBox]);
29099
29718
  return {
29100
29719
  centerPoint,
29101
29720
  getVisionOffPoints,
@@ -29330,9 +29949,9 @@ const SvgEditMap = React.forwardRef(({ mapConfig, onEditInfoMapChange, editMap }
29330
29949
  const onMouseLeave = () => {
29331
29950
  setHoverDataId('');
29332
29951
  };
29333
- return (jsxRuntime.jsx("div", { ref: containerRef, className: classNames(styles$1.mapEdit, {
29334
- [styles$1.createMode]: isCreating,
29335
- [styles$1.notCreateCursor]: showNotCreateCursor,
29952
+ return (jsxRuntime.jsx("div", { ref: containerRef, className: classNames(styles$2.mapEdit, {
29953
+ [styles$2.createMode]: isCreating,
29954
+ [styles$2.notCreateCursor]: showNotCreateCursor,
29336
29955
  }),
29337
29956
  // style={{
29338
29957
  // position: 'absolute',
@@ -29438,6 +30057,22 @@ const SvgEditMap = React.forwardRef(({ mapConfig, onEditInfoMapChange, editMap }
29438
30057
  });
29439
30058
  SvgEditMap.displayName = 'SvgEditMap';
29440
30059
 
30060
+ /** 清理为合法的 SVG/HTML id 片段 */
30061
+ function sanitizeSvgIdPart(value) {
30062
+ return value.replace(/[^a-zA-Z0-9_.-]/g, '_');
30063
+ }
30064
+ /**
30065
+ * 通道 clipPath 的 SVG id
30066
+ * 组合 sn、useId、时间戳、随机数,避免同页多地图实例 id 冲突
30067
+ */
30068
+ function createChannelClipPathId(params) {
30069
+ const snPart = sanitizeSvgIdPart(params.sn || 'map');
30070
+ const reactPart = sanitizeSvgIdPart(params.reactId.replace(/:/g, ''));
30071
+ const timestamp = Date.now();
30072
+ const randomPart = Math.random().toString(36).slice(2, 10);
30073
+ return `channel-clip-path-${snPart}-${reactPart}-${timestamp}-${randomPart}`;
30074
+ }
30075
+
29441
30076
  var _path;
29442
30077
  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); }
29443
30078
  var SvgMobileAddPoint = function SvgMobileAddPoint(props) {
@@ -29455,9 +30090,9 @@ var SvgMobileAddPoint = function SvgMobileAddPoint(props) {
29455
30090
  })));
29456
30091
  };
29457
30092
 
29458
- 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}";
29459
- var styles = {"crosshair":"index-module_crosshair__etey6"};
29460
- styleInject(css_248z);
30093
+ 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}";
30094
+ var styles$1 = {"crosshair":"index-module_crosshair__etey6"};
30095
+ styleInject(css_248z$1);
29461
30096
 
29462
30097
  /**
29463
30098
  * 十字准心组件
@@ -29474,19 +30109,19 @@ const Crosshair = () => {
29474
30109
  }
29475
30110
  return false;
29476
30111
  }, [platform, editMapInfo]);
29477
- return isVisible ? (jsxRuntime.jsx("div", { className: styles.crosshair, children: jsxRuntime.jsx(SvgMobileAddPoint, {}) })) : null;
30112
+ return isVisible ? (jsxRuntime.jsx("div", { className: styles$1.crosshair, children: jsxRuntime.jsx(SvgMobileAddPoint, {}) })) : null;
29478
30113
  };
29479
30114
 
29480
- const WGS84 = 'EPSG:4326';
29481
- const WEB_MERCATOR = 'EPSG:3857';
29482
- proj4.defs(WGS84, '+proj=longlat +datum=WGS84 +no_defs');
29483
- proj4.defs(WEB_MERCATOR, '+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs');
30115
+ const WGS84$1 = 'EPSG:4326';
30116
+ const WEB_MERCATOR$1 = 'EPSG:3857';
30117
+ proj4.defs(WGS84$1, '+proj=longlat +datum=WGS84 +no_defs');
30118
+ proj4.defs(WEB_MERCATOR$1, '+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs');
29484
30119
  // 默认配置
29485
30120
  const defaultMapConfig$1 = DEFAULT_STYLES;
29486
30121
  // 地图渲染器组件
29487
- const MowerMapRenderer = React.forwardRef(({ sn, mowerIconConfig = {}, platform = exports.PlatformType.WEB, edger = false, unitType = UnitsType.Imperial, language = 'en', onlyRead = false, isHighlight = true, mapConfig,
30122
+ const MowerMapRenderer = React.forwardRef(({ sn, mowerIconConfig = {}, platform = exports.PlatformType.WEB, mapType = exports.MapType.NORMAL, edger = false, unitType = UnitsType.Imperial, language = 'en', onlyRead = false, isHighlight = true, mapConfig,
29488
30123
  // pathConfig,
29489
- 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) => {
30124
+ 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) => {
29490
30125
  const [currentError, setCurrentError] = React.useState(null);
29491
30126
  // const mapRef = useMap();
29492
30127
  const [processStateIsMowing, setProcessStateIsMowing] = React.useState(false);
@@ -29514,6 +30149,8 @@ modelType, showStraddleBoundaryBorder = true, mapRef, mapJson, pathJson, realTim
29514
30149
  ...INIT_EDIT_MAP_INFO,
29515
30150
  });
29516
30151
  const [mowingPartitions, setMowingPartitions] = React.useState([]);
30152
+ const reactInstanceId = React.useId();
30153
+ const channelClipPathId = React.useMemo(() => createChannelClipPathId({ sn, reactId: reactInstanceId }), [sn, reactInstanceId]);
29517
30154
  const minDistance = React.useMemo(() => {
29518
30155
  return getMinSvgDistanceByModel(modelType);
29519
30156
  }, [modelType]);
@@ -29647,13 +30284,25 @@ modelType, showStraddleBoundaryBorder = true, mapRef, mapJson, pathJson, realTim
29647
30284
  const mowerPositionData = React.useMemo(() => {
29648
30285
  // realTimeData 中包含三个种类的数据,之需要实时坐标的数据即可。
29649
30286
  // 在初始的状态按照正常图标进行渲染,所以构造一个基本在rtk桩的一个数据
29650
- if (!realTimeData || realTimeData.length === 0)
29651
- return {
29652
- postureX: 0.01,
29653
- postureY: 0.1,
29654
- postureTheta: 0.01,
29655
- vehicleState: RobotStatus.PARKED,
29656
- };
30287
+ if (!realTimeData || realTimeData.length === 0) {
30288
+ const chargingPiles = svgElementDatas?.[exports.DataType.CHARGING_PILE]?.[0];
30289
+ if (chargingPiles?.position) {
30290
+ return {
30291
+ postureX: chargingPiles.position?.[0],
30292
+ postureY: chargingPiles.position?.[1],
30293
+ postureTheta: chargingPiles.direction - Math.PI || 0,
30294
+ vehicleState: RobotStatus.PARKED,
30295
+ };
30296
+ }
30297
+ else {
30298
+ return {
30299
+ postureX: 0.01,
30300
+ postureY: 0.1,
30301
+ postureTheta: 0.01,
30302
+ vehicleState: RobotStatus.PARKED,
30303
+ };
30304
+ }
30305
+ }
29657
30306
  let currentPositionData;
29658
30307
  if (realTimeData.length === 1 && realTimeData[0].type === RealTimeDataType.LOCATION) {
29659
30308
  currentPositionData = realTimeData[0];
@@ -29697,14 +30346,14 @@ modelType, showStraddleBoundaryBorder = true, mapRef, mapJson, pathJson, realTim
29697
30346
  const validBounds = getValidGpsBounds(mapJson, 0);
29698
30347
  const [swLng0, swLat0] = validBounds.sw;
29699
30348
  const [neLng0, neLat0] = validBounds.ne;
29700
- const [swX0, swY0] = proj4(WGS84, WEB_MERCATOR, [swLng0, swLat0]);
29701
- const [neX0, neY0] = proj4(WGS84, WEB_MERCATOR, [neLng0, neLat0]);
30349
+ const [swX0, swY0] = proj4(WGS84$1, WEB_MERCATOR$1, [swLng0, swLat0]);
30350
+ const [neX0, neY0] = proj4(WGS84$1, WEB_MERCATOR$1, [neLng0, neLat0]);
29702
30351
  const swX = swX0 + drag.x;
29703
30352
  const swY = swY0 + drag.y;
29704
30353
  const neX = neX0 + drag.x;
29705
30354
  const neY = neY0 + drag.y;
29706
- const [swLng, swLat] = proj4(WEB_MERCATOR, WGS84, [swX, swY]);
29707
- const [neLng, neLat] = proj4(WEB_MERCATOR, WGS84, [neX, neY]);
30355
+ const [swLng, swLat] = proj4(WEB_MERCATOR$1, WGS84$1, [swX, swY]);
30356
+ const [neLng, neLat] = proj4(WEB_MERCATOR$1, WGS84$1, [neX, neY]);
29708
30357
  return new window.google.maps.LatLngBounds(new window.google.maps.LatLng(swLat, swLng), new window.google.maps.LatLng(neLat, neLng));
29709
30358
  }, [mapJson, drag.x, drag.y]);
29710
30359
  const commonValue = React.useMemo(() => {
@@ -29732,7 +30381,11 @@ modelType, showStraddleBoundaryBorder = true, mapRef, mapJson, pathJson, realTim
29732
30381
  showStraddleBoundaryBorder,
29733
30382
  overlayLayout,
29734
30383
  googleMapStaticApiKey,
30384
+ googleMapStaticUrlSigningSecret,
30385
+ locale,
30386
+ signGoogleStaticMapUrl,
29735
30387
  onH5FirstSelectObstaclePoint,
30388
+ channelClipPathId,
29736
30389
  };
29737
30390
  }, [
29738
30391
  sn,
@@ -29755,8 +30408,32 @@ modelType, showStraddleBoundaryBorder = true, mapRef, mapJson, pathJson, realTim
29755
30408
  showStraddleBoundaryBorder,
29756
30409
  overlayLayout,
29757
30410
  googleMapStaticApiKey,
30411
+ googleMapStaticUrlSigningSecret,
30412
+ locale,
30413
+ signGoogleStaticMapUrl,
29758
30414
  onH5FirstSelectObstaclePoint,
30415
+ channelClipPathId,
29759
30416
  ]);
30417
+ /**
30418
+ * 这里做了一个配置,以防以后如果有多种不同的类型的情况,需要展示不同的元素
30419
+ */
30420
+ const mapHasElements = React.useMemo(() => {
30421
+ // 地理围栏地图,有充电桩,没有天线,没有割草机位置,没有分区名称气泡
30422
+ if (mapType === exports.MapType.GEO_FENCE) {
30423
+ return {
30424
+ hasChargingPile: true,
30425
+ hasAntenna: false,
30426
+ hasMowerPosition: false,
30427
+ hasBoundaryLabels: false,
30428
+ };
30429
+ }
30430
+ return {
30431
+ hasChargingPile: true,
30432
+ hasAntenna: true,
30433
+ hasMowerPosition: true,
30434
+ hasBoundaryLabels: true,
30435
+ };
30436
+ }, [mapType]);
29760
30437
  const fitBounds = React.useCallback((padding = 0) => {
29761
30438
  if (!mapJson || !mapRef)
29762
30439
  return null;
@@ -29772,14 +30449,14 @@ modelType, showStraddleBoundaryBorder = true, mapRef, mapJson, pathJson, realTim
29772
30449
  // 地图数据中的坐标格式是 [longitude, latitude]
29773
30450
  const [swLng0, swLat0] = validBounds.sw;
29774
30451
  const [neLng0, neLat0] = validBounds.ne;
29775
- const [swX0, swY0] = proj4(WGS84, WEB_MERCATOR, [swLng0, swLat0]);
29776
- const [neX0, neY0] = proj4(WGS84, WEB_MERCATOR, [neLng0, neLat0]);
30452
+ const [swX0, swY0] = proj4(WGS84$1, WEB_MERCATOR$1, [swLng0, swLat0]);
30453
+ const [neX0, neY0] = proj4(WGS84$1, WEB_MERCATOR$1, [neLng0, neLat0]);
29777
30454
  const swX = swX0 + defaultTransform.x;
29778
30455
  const swY = swY0 + defaultTransform.y;
29779
30456
  const neX = neX0 + defaultTransform.x;
29780
30457
  const neY = neY0 + defaultTransform.y;
29781
- const [swLng, swLat] = proj4(WEB_MERCATOR, WGS84, [swX, swY]);
29782
- const [neLng, neLat] = proj4(WEB_MERCATOR, WGS84, [neX, neY]);
30458
+ const [swLng, swLat] = proj4(WEB_MERCATOR$1, WGS84$1, [swX, swY]);
30459
+ const [neLng, neLat] = proj4(WEB_MERCATOR$1, WGS84$1, [neX, neY]);
29783
30460
  const googleBounds = new window.google.maps.LatLngBounds(new window.google.maps.LatLng(swLat, swLng), // 西南角
29784
30461
  new window.google.maps.LatLng(neLat, neLng) // 东北角
29785
30462
  );
@@ -29806,8 +30483,8 @@ modelType, showStraddleBoundaryBorder = true, mapRef, mapJson, pathJson, realTim
29806
30483
  const offsetLatLng = overlayProjection.fromDivPixelToLatLng(offsetPixel);
29807
30484
  if (!offsetLatLng)
29808
30485
  return;
29809
- const [cx, cy] = proj4(WGS84, WEB_MERCATOR, [centerLatLng.lng(), centerLatLng.lat()]);
29810
- const [ox, oy] = proj4(WGS84, WEB_MERCATOR, [offsetLatLng.lng(), offsetLatLng.lat()]);
30486
+ const [cx, cy] = proj4(WGS84$1, WEB_MERCATOR$1, [centerLatLng.lng(), centerLatLng.lat()]);
30487
+ const [ox, oy] = proj4(WGS84$1, WEB_MERCATOR$1, [offsetLatLng.lng(), offsetLatLng.lat()]);
29811
30488
  const dx = ox - cx;
29812
30489
  const dy = oy - cy;
29813
30490
  setDrag((prev) => ({
@@ -29850,7 +30527,10 @@ modelType, showStraddleBoundaryBorder = true, mapRef, mapJson, pathJson, realTim
29850
30527
  // 如果当前是taskDelay的状态,或者状态为mowing或者standby,则指定的地块需要高亮,或者全局高亮
29851
30528
  if ((isTaskDelayRef.current ||
29852
30529
  positionData?.vehicleState === RobotStatus.MOWING ||
29853
- positionData?.vehicleState === RobotStatus.STANDBY) &&
30530
+ positionData?.vehicleState === RobotStatus.STANDBY ||
30531
+ (!positionData &&
30532
+ (currentVehicleStateRef.current === RobotStatus.MOWING ||
30533
+ currentVehicleStateRef.current === RobotStatus.STANDBY))) &&
29854
30534
  mowPartitionDataRef.current &&
29855
30535
  !mowPartitionDataRef.current?.partitionIds) {
29856
30536
  // 设置全局高亮
@@ -30071,13 +30751,15 @@ modelType, showStraddleBoundaryBorder = true, mapRef, mapJson, pathJson, realTim
30071
30751
  // svgEditMapRef?.current?.adjustSvgSize?.(layout);
30072
30752
  setOverlayLayout(layout);
30073
30753
  }
30074
- }, map: mapRef, mapPaneName: 'overlayMouseTarget', bounds: bounds, rotate: actureRotate, zIndex: zIndex, children: [jsxRuntime.jsx(MapDrag, { map: mapRef, dragCallbacks: dragCallbacks, onBoundaryLabelsCollapse: () => { }, onTransformChange: handleDragMove, isDragMap: dragMap, canRotateMap: canRotateMap }), jsxRuntime.jsx(MowPartitionContextProvider, { value: mowPartitionValue, children: jsxRuntime.jsx(SvgMapComponent, { isHighlight: isHighlight, editMap: editMap, ref: svgMapRef, pathData: pathJson, mapConfig: mergedMapConfig, mowPartitionData: mowPartitionData }) }), jsxRuntime.jsx(MowerPosition, { editMap: editMap, mowerPositionData: mowerPositionData, viewBox: svgViewBox || null, modelType: modelType, hasEdger: edger, mapData: mapJson, mapConfig: mergedMapConfig, realTimeData: realTimeData, onMowingPositionChange: onMowingPositionChange, isHighlight: isHighlight }), jsxRuntime.jsx(CharginPile, { viewBox: svgViewBox || null, rotation: actureRotate, isHighlight: isHighlight }), jsxRuntime.jsx(BoundaryLabels, { editMap: editMap, mapData: mapJson, onlyRead: onlyRead, pathData: pathJson, unitType: unitType, viewBox: svgViewBox || null, rotation: actureRotate, mowPartitionData: mowPartitionData, realTimeData: realTimeData, isHighlight: isHighlight }), jsxRuntime.jsx(Antennas, { editMap: editMap, antennaConfig: antennaConfig || [], layout: overlayLayout || undefined, viewBox: svgViewBox || null, rotation: actureRotate, onlyRead: onlyRead, isHighlight: isHighlight }), jsxRuntime.jsx(SvgEditMap, { editMap: editMap, ref: svgEditMapRef, mapConfig: mergedMapConfig, onEditInfoMapChange: onEditInfoMapChange })] })] }) }) }));
30754
+ }, map: mapRef, mapPaneName: 'overlayMouseTarget', bounds: bounds, rotate: actureRotate, zIndex: zIndex, children: [jsxRuntime.jsx(MapDrag, { map: mapRef, dragCallbacks: dragCallbacks, onBoundaryLabelsCollapse: () => { }, onTransformChange: handleDragMove, isDragMap: dragMap, canRotateMap: canRotateMap }), jsxRuntime.jsx(MowPartitionContextProvider, { value: mowPartitionValue, children: jsxRuntime.jsx(SvgMapComponent, { isHighlight: isHighlight, editMap: editMap, ref: svgMapRef, pathData: pathJson, mapConfig: mergedMapConfig, mowPartitionData: mowPartitionData }) }), mapHasElements.hasMowerPosition && (jsxRuntime.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 && (jsxRuntime.jsx(CharginPile, { viewBox: svgViewBox || null, rotation: actureRotate, isHighlight: isHighlight })), mapHasElements.hasBoundaryLabels && (jsxRuntime.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 && (jsxRuntime.jsx(Antennas, { editMap: editMap, antennaConfig: antennaConfig || [], layout: overlayLayout || undefined, viewBox: svgViewBox || null, rotation: actureRotate, onlyRead: onlyRead, isHighlight: isHighlight })), jsxRuntime.jsx(SvgEditMap, { editMap: editMap, ref: svgEditMapRef, mapConfig: mergedMapConfig, onEditInfoMapChange: onEditInfoMapChange })] })] }) }) }));
30075
30755
  });
30076
30756
  MowerMapRenderer.displayName = 'MowerMapRenderer';
30077
30757
 
30078
30758
  // 默认配置
30079
30759
  const defaultMapConfig = DEFAULT_STYLES;
30080
- const BoundarySvgRender = React.memo(({ mapJson, unStructMapData, id, maxWidth = 300, mapConfig, sn }) => {
30760
+ const BoundarySvgRender = React.memo(({ mapJson, unStructMapData, id, maxWidth = 300, maxHeight, boundaryPadding = 50, mapConfig, sn, }) => {
30761
+ const reactInstanceId = React.useId();
30762
+ const channelClipPathId = React.useMemo(() => createChannelClipPathId({ sn, reactId: reactInstanceId }), [sn, reactInstanceId]);
30081
30763
  // 合并配置
30082
30764
  const mergedMapConfig = React.useMemo(() => {
30083
30765
  return merge$1(defaultMapConfig, mapConfig);
@@ -30123,7 +30805,7 @@ const BoundarySvgRender = React.memo(({ mapJson, unStructMapData, id, maxWidth =
30123
30805
  maxY = Math.max(maxY, point[1]);
30124
30806
  });
30125
30807
  // 添加边距
30126
- const padding = 50;
30808
+ const padding = boundaryPadding;
30127
30809
  const width = maxX - minX + padding * 2;
30128
30810
  const height = maxY - minY + padding * 2;
30129
30811
  return {
@@ -30132,25 +30814,40 @@ const BoundarySvgRender = React.memo(({ mapJson, unStructMapData, id, maxWidth =
30132
30814
  width,
30133
30815
  height,
30134
30816
  };
30135
- }, [boundaryInfo]);
30817
+ }, [boundaryInfo, boundaryPadding]);
30136
30818
  const commonValue = React.useMemo(() => {
30137
30819
  return {
30138
30820
  sn,
30139
30821
  svgViewBox: boundaryViewBox,
30822
+ channelClipPathId,
30140
30823
  };
30141
- }, [sn, boundaryViewBox]);
30824
+ }, [sn, boundaryViewBox, channelClipPathId]);
30142
30825
  const style = React.useMemo(() => {
30143
- if (boundaryViewBox?.width > boundaryViewBox?.height) {
30826
+ if (!maxHeight) {
30827
+ if (boundaryViewBox?.width > boundaryViewBox?.height) {
30828
+ return {
30829
+ width: maxWidth,
30830
+ height: maxWidth * (boundaryViewBox.height / boundaryViewBox.width),
30831
+ };
30832
+ }
30144
30833
  return {
30145
- width: maxWidth,
30146
- height: maxWidth * (boundaryViewBox.height / boundaryViewBox.width),
30834
+ width: maxWidth * (boundaryViewBox.width / boundaryViewBox.height),
30835
+ height: maxWidth,
30836
+ };
30837
+ }
30838
+ const ratio = maxWidth / maxHeight;
30839
+ const boundaryRatio = boundaryViewBox.width / boundaryViewBox.height;
30840
+ if (ratio > boundaryRatio) {
30841
+ return {
30842
+ width: Math.floor(maxHeight * boundaryRatio),
30843
+ height: maxHeight,
30147
30844
  };
30148
30845
  }
30149
30846
  return {
30150
- width: maxWidth * (boundaryViewBox.width / boundaryViewBox.height),
30151
- height: maxWidth,
30847
+ width: maxWidth,
30848
+ height: Math.floor(maxWidth / boundaryRatio),
30152
30849
  };
30153
- }, [boundaryViewBox, maxWidth]);
30850
+ }, [boundaryViewBox, maxWidth, maxHeight]);
30154
30851
  return (jsxRuntime.jsx(CommonContextProvider, { value: commonValue, children: jsxRuntime.jsx(SvgEditContextProvider, { value: svgEditValue, children: jsxRuntime.jsxs("div", { style: {
30155
30852
  position: 'relative',
30156
30853
  width: style.width,
@@ -30166,6 +30863,1420 @@ const BoundarySvgRender = React.memo(({ mapJson, unStructMapData, id, maxWidth =
30166
30863
  }) }), jsxRuntime.jsx(CharginPile, { viewBox: boundaryViewBox || null, rotation: 0 })] }) }) }));
30167
30864
  });
30168
30865
 
30866
+ /** ChannelClipPath 等内容裁剪使用的 SVG 坐标边距(与用户传入的屏幕 padding 无关) */
30867
+ const PARTITION_PICKER_CONTENT_CLIP_PADDING = 50;
30868
+ /** H5:子区域屏幕宽度超过该值才展示名称标签(px) */
30869
+ const H5_PARTITION_LABEL_MIN_SCREEN_WIDTH_PX = 72;
30870
+ /** H5:名称气泡总宽度(px) */
30871
+ const H5_PARTITION_LABEL_WIDTH_PX = 72;
30872
+ /** 边界线目标屏幕像素宽度(默认态,全缩放级别保持一致) */
30873
+ const PARTITION_BOUNDARY_TARGET_SCREEN_STROKE_PX = 2;
30874
+ /** 边界线目标屏幕像素宽度(hover / 选中态) */
30875
+ const PARTITION_BOUNDARY_EMPHASIZED_TARGET_SCREEN_STROKE_PX = 2.5;
30876
+ /**
30877
+ * Figma bg/mapping 地图网格背景(Standalone SVG 层)
30878
+ * @see https://www.figma.com/design/j0DdUYf8VFkXQ8ikkA6ZzP — node 20490:84886
30879
+ */
30880
+ /** 网格区域底色,也用于容器 letterbox 留白 */
30881
+ const PARTITION_PICKER_GRID_BASE_COLOR = '#E3E5EB';
30882
+ /** 网格单元边长(SVG 地图坐标,与 viewBox 一致;zoom 时间距同比缩放) */
30883
+ const PARTITION_PICKER_GRID_TILE_SIZE = 200;
30884
+ /** 网格线宽(地图坐标;随 zoom 同比缩放,不做屏幕像素恒定) */
30885
+ const PARTITION_PICKER_GRID_LINE_WIDTH = 4;
30886
+ /** 网格交点圆点半径(地图坐标) */
30887
+ const PARTITION_PICKER_GRID_DOT_RADIUS = 8;
30888
+ /** 网格层整体不透明度(Figma 约 80%) */
30889
+ const PARTITION_PICKER_GRID_OPACITY = 0.8;
30890
+ /** 网格线与交点颜色 */
30891
+ const PARTITION_PICKER_GRID_STROKE = '#FFFFFF';
30892
+ /** 可见范围内交点超过该数量时跳过圆点,仅保留网格线(缩小视野时减轻 DOM 压力) */
30893
+ const PARTITION_PICKER_GRID_MAX_INTERSECTION_DOTS = 400;
30894
+ /**
30895
+ * 分区选择地图专用样式
30896
+ *
30897
+ * Web:Figma 灰蓝未选中地块(Vector 360)
30898
+ * H5:高对比度绿色,面向卫星/深色底图
30899
+ */
30900
+ const PARTITION_PICKER_MAP_CONFIG_WEB = {
30901
+ boundary: {
30902
+ lineColor: '#91A1B4',
30903
+ fillColor: '#D7DEE6',
30904
+ lineWidth: 2,
30905
+ },
30906
+ };
30907
+ const PARTITION_PICKER_MAP_CONFIG = {
30908
+ boundary: {
30909
+ lineColor: '#91A1B4',
30910
+ fillColor: '#D7DEE6',
30911
+ lineWidth: 2,
30912
+ },
30913
+ };
30914
+ /** Web 端地块配色(Figma Vector 360 / 361 / 99788) */
30915
+ const PARTITION_PICKER_COLORS_WEB = {
30916
+ defaultFill: 'rgba(222, 225, 231, 1)',
30917
+ defaultStroke: 'rgba(132, 152, 169, 1)',
30918
+ defaultStrokeWidth: 2,
30919
+ hoverFill: 'rgba(211, 216, 226, 1)',
30920
+ hoverStroke: 'rgba(132, 152, 169, 1)',
30921
+ hoverStrokeWidth: 3,
30922
+ selectedFill: 'rgba(202, 214, 238, 1)',
30923
+ selectedStroke: 'rgba(132, 152, 169, 1)',
30924
+ selectedStrokeWidth: 3,
30925
+ selectedHoverFill: 'rgba(202, 214, 238, 1)',
30926
+ selectedHoverStroke: 'rgba(132, 152, 169, 1)',
30927
+ selectedHoverStrokeWidth: 3,
30928
+ isolatedFill: 'rgba(225, 227, 233, 1)',
30929
+ isolatedStroke: 'rgba(189, 198, 208, 1)',
30930
+ isolatedStrokeWidth: 2,
30931
+ };
30932
+ /** H5 端地块配色 */
30933
+ const PARTITION_PICKER_COLORS_H5 = {
30934
+ defaultFill: 'rgba(222, 225, 231, 1)',
30935
+ defaultStroke: 'rgba(132, 152, 169, 1)',
30936
+ defaultStrokeWidth: 1,
30937
+ hoverFill: 'rgba(222, 225, 231, 1)',
30938
+ hoverStroke: 'rgba(132, 152, 169, 1)',
30939
+ hoverStrokeWidth: 1,
30940
+ selectedFill: 'rgba(202, 214, 238, 1)',
30941
+ selectedStroke: 'rgba(132, 152, 169, 1)',
30942
+ selectedStrokeWidth: 1,
30943
+ selectedHoverFill: 'rgba(202, 214, 238, 1)',
30944
+ selectedHoverStroke: 'rgba(132, 152, 169, 1)',
30945
+ selectedHoverStrokeWidth: 1,
30946
+ isolatedFill: 'rgba(225, 227, 233, 1)',
30947
+ isolatedStroke: 'rgba(189, 198, 208, 1)',
30948
+ isolatedStrokeWidth: 1,
30949
+ };
30950
+ function getPartitionPickerMapConfig(platform = exports.PlatformType.WEB) {
30951
+ return platform === exports.PlatformType.WEB
30952
+ ? PARTITION_PICKER_MAP_CONFIG_WEB
30953
+ : PARTITION_PICKER_MAP_CONFIG;
30954
+ }
30955
+ function getPartitionPickerColors(platform = exports.PlatformType.WEB) {
30956
+ return platform === exports.PlatformType.WEB ? PARTITION_PICKER_COLORS_WEB : PARTITION_PICKER_COLORS_H5;
30957
+ }
30958
+ /** Web 分区名气泡(Figma「地块名称」/ 选中区域) */
30959
+ const PARTITION_PICKER_LABEL_STYLES_WEB = {
30960
+ default: {
30961
+ backgroundColor: 'rgba(255, 255, 255, 0.15)',
30962
+ color: 'rgba(65, 93, 116, 0.7)',
30963
+ fontSize: 12,
30964
+ fontWeight: 500,
30965
+ padding: '4px 8px',
30966
+ borderRadius: 14,
30967
+ lineHeight: '20px',
30968
+ },
30969
+ selected: {
30970
+ backgroundColor: 'rgba(255, 90, 0, 1)',
30971
+ color: 'rgba(255, 255, 255, 1)',
30972
+ fontSize: 12,
30973
+ fontWeight: 500,
30974
+ padding: '4px 8px',
30975
+ borderRadius: 20,
30976
+ lineHeight: '20px',
30977
+ },
30978
+ selectionOrderBadge: {
30979
+ width: '24px',
30980
+ height: '24px',
30981
+ background: '#FF5A00',
30982
+ border: '1px solid #FFFFFF',
30983
+ color: '#FFFFFF',
30984
+ fontSize: 14,
30985
+ fontWeight: 500,
30986
+ lineHeight: '22px',
30987
+ },
30988
+ };
30989
+ /** H5 分区名气泡(深色底图 / 橙色渐变选中) */
30990
+ const PARTITION_PICKER_LABEL_STYLES_H5 = {
30991
+ default: {
30992
+ backgroundColor: 'rgba(255, 255, 255, 0.15)',
30993
+ color: 'rgba(65, 93, 116, 0.7)',
30994
+ fontSize: 12,
30995
+ fontWeight: 400,
30996
+ padding: '6px',
30997
+ width: `${H5_PARTITION_LABEL_WIDTH_PX}px`,
30998
+ borderRadius: 100,
30999
+ boxSizing: 'border-box',
31000
+ },
31001
+ selected: {
31002
+ background: 'linear-gradient(266.64deg, #F2942A 4.1%, #F26A2A 86.42%)',
31003
+ color: 'rgba(255, 255, 255, 1)',
31004
+ fontSize: 12,
31005
+ fontWeight: 400,
31006
+ padding: '6px',
31007
+ width: `${H5_PARTITION_LABEL_WIDTH_PX}px`,
31008
+ borderRadius: 100,
31009
+ boxSizing: 'border-box',
31010
+ },
31011
+ selectionOrderBadge: {
31012
+ width: '24px',
31013
+ height: '24px',
31014
+ background: 'linear-gradient(310.82deg, #F16629 14.52%, #F1A129 103.26%)',
31015
+ border: '1px solid #FFFFFF',
31016
+ color: '#FFFFFF',
31017
+ fontSize: 14,
31018
+ fontWeight: 600,
31019
+ lineHeight: '22px',
31020
+ },
31021
+ };
31022
+ /** 将 PartitionPickerLabelAppearance 转为 React 内联样式 */
31023
+ function labelAppearanceToStyle(appearance) {
31024
+ const { fontSize, borderRadius, ...rest } = appearance;
31025
+ return {
31026
+ ...rest,
31027
+ fontSize: `${fontSize}px`,
31028
+ borderRadius: `${borderRadius}px`,
31029
+ };
31030
+ }
31031
+ function getPartitionPickerLabelStyles(platform = exports.PlatformType.WEB) {
31032
+ return platform === exports.PlatformType.WEB
31033
+ ? PARTITION_PICKER_LABEL_STYLES_WEB
31034
+ : PARTITION_PICKER_LABEL_STYLES_H5;
31035
+ }
31036
+ const DEFAULT_PARTITION_PICKER_GRID_STYLE = {
31037
+ baseColor: PARTITION_PICKER_GRID_BASE_COLOR,
31038
+ strokeColor: PARTITION_PICKER_GRID_STROKE,
31039
+ tileSize: PARTITION_PICKER_GRID_TILE_SIZE,
31040
+ lineWidth: PARTITION_PICKER_GRID_LINE_WIDTH,
31041
+ dotRadius: PARTITION_PICKER_GRID_DOT_RADIUS,
31042
+ opacity: PARTITION_PICKER_GRID_OPACITY,
31043
+ maxIntersectionDots: PARTITION_PICKER_GRID_MAX_INTERSECTION_DOTS,
31044
+ };
31045
+ const DEFAULT_PARTITION_PICKER_BOUNDARY_STROKE_PX = {
31046
+ default: PARTITION_BOUNDARY_TARGET_SCREEN_STROKE_PX,
31047
+ emphasized: PARTITION_BOUNDARY_EMPHASIZED_TARGET_SCREEN_STROKE_PX,
31048
+ };
31049
+ /**
31050
+ * 将 platform 内置默认值与外部 pickerStyleConfig 深度合并。
31051
+ * 调用方只需覆盖变化的字段,无需改组件源码。
31052
+ */
31053
+ function resolvePartitionPickerStyles(platform = exports.PlatformType.WEB, config) {
31054
+ const baseLabels = getPartitionPickerLabelStyles(platform);
31055
+ return {
31056
+ boundary: merge$1({}, getPartitionPickerColors(platform), config?.boundary),
31057
+ labels: config?.labels
31058
+ ? {
31059
+ default: merge$1({}, baseLabels.default, config.labels.default),
31060
+ selected: merge$1({}, baseLabels.selected, config.labels.selected),
31061
+ selectionOrderBadge: merge$1({}, baseLabels.selectionOrderBadge, config.labels.selectionOrderBadge),
31062
+ }
31063
+ : baseLabels,
31064
+ grid: merge$1({}, DEFAULT_PARTITION_PICKER_GRID_STYLE, config?.grid),
31065
+ boundaryStrokePx: merge$1({}, DEFAULT_PARTITION_PICKER_BOUNDARY_STROKE_PX, config?.boundaryStrokePx),
31066
+ labelMinScreenWidthPx: config?.labelMinScreenWidthPx ?? H5_PARTITION_LABEL_MIN_SCREEN_WIDTH_PX,
31067
+ };
31068
+ }
31069
+
31070
+ /**
31071
+ * Standalone 模式下的视口控制:平移、滚轮缩放、H5 双指 pinch
31072
+ *
31073
+ * 核心思路:通过修改 SVG viewBox 实现平移/缩放,而非 CSS transform
31074
+ * - fitViewBox:初始 fit 状态,resetView 回到此状态
31075
+ * - viewBox:地块/通道/overlay 的可见区域
31076
+ * - gridViewBox:网格底图专用;有地图旋转时平移按屏幕方向(不逆旋转),与地块跟手平移解耦
31077
+ *
31078
+ * 平移可在地块/分区名等任意区域发起;仅当位移小于阈值时才视为点击
31079
+ */
31080
+ function useMapViewport({ mapJson, containerRef: externalContainerRef, width, height, padding, minZoom = 0.5, maxZoom = 4, enablePanZoom = true, refitOnResize = true, onViewportTap, mapRotationDeg = 0, }) {
31081
+ /** 地图容器 DOM,用于绑定 wheel/pointer/touch 事件 */
31082
+ const internalContainerRef = React.useRef(null);
31083
+ const containerRef = externalContainerRef ?? internalContainerRef;
31084
+ /** 始终指向最新 padding,供 fitToView 异步调用时使用 */
31085
+ const paddingRef = React.useRef(padding);
31086
+ paddingRef.current = padding;
31087
+ /** padding 序列化 key,用于 useMemo 依赖(数组/对象形式 padding 变化时触发重算) */
31088
+ const paddingKey = typeof padding === 'number' ? padding : JSON.stringify(padding);
31089
+ /** 始终指向最新 onViewportTap,避免 effect 闭包过期 */
31090
+ const onViewportTapRef = React.useRef(onViewportTap);
31091
+ onViewportTapRef.current = onViewportTap;
31092
+ /** 始终指向最新 mapRotationDeg,供平移回调读取 */
31093
+ const mapRotationDegRef = React.useRef(mapRotationDeg);
31094
+ mapRotationDegRef.current = mapRotationDeg;
31095
+ /**
31096
+ * 内容 viewBox:地图包围盒 + 固定 SVG padding
31097
+ * 用于 ChannelClipPath 等裁剪,不随平移/缩放变化
31098
+ */
31099
+ const contentViewBox = React.useMemo(() => {
31100
+ const bounds = calculateMapBounds(mapJson);
31101
+ return boundsToContentViewBox(bounds, PARTITION_PICKER_CONTENT_CLIP_PADDING);
31102
+ }, [mapJson]);
31103
+ /**
31104
+ * 初始 fit viewBox:在 contentViewBox 基础上适配容器宽高比与 screen padding
31105
+ * resetView 会回到此状态
31106
+ */
31107
+ const fitViewBox = React.useMemo(() => computeFitViewBox(contentViewBox, width, height, padding), [contentViewBox, width, height, paddingKey]);
31108
+ /** fitViewBox 的 ref 副本,供事件回调中读取最新值(zoom 限制基准) */
31109
+ const fitViewBoxRef = React.useRef(fitViewBox);
31110
+ fitViewBoxRef.current = fitViewBox;
31111
+ /** 地块/通道/overlay 使用的 viewBox */
31112
+ const [viewBox, setViewBox] = React.useState(fitViewBox);
31113
+ /** 网格底图 viewBox:平移时按屏幕方向,避免与旋转后的地块反向滑动 */
31114
+ const [gridViewBox, setGridViewBox] = React.useState(fitViewBox);
31115
+ const viewBoxRef = React.useRef(viewBox);
31116
+ viewBoxRef.current = viewBox;
31117
+ const gridViewBoxRef = React.useRef(gridViewBox);
31118
+ gridViewBoxRef.current = gridViewBox;
31119
+ const pendingViewBoxRef = React.useRef(null);
31120
+ const pendingGridViewBoxRef = React.useRef(null);
31121
+ const viewBoxRafRef = React.useRef(null);
31122
+ const flushScheduledViewBox = React.useCallback(() => {
31123
+ viewBoxRafRef.current = null;
31124
+ const mapPending = pendingViewBoxRef.current;
31125
+ const gridPending = pendingGridViewBoxRef.current;
31126
+ if (mapPending) {
31127
+ pendingViewBoxRef.current = null;
31128
+ viewBoxRef.current = mapPending;
31129
+ setViewBox(mapPending);
31130
+ }
31131
+ if (gridPending) {
31132
+ pendingGridViewBoxRef.current = null;
31133
+ gridViewBoxRef.current = gridPending;
31134
+ setGridViewBox(gridPending);
31135
+ }
31136
+ }, []);
31137
+ const cancelScheduledViewBox = React.useCallback(() => {
31138
+ pendingViewBoxRef.current = null;
31139
+ pendingGridViewBoxRef.current = null;
31140
+ if (viewBoxRafRef.current != null) {
31141
+ cancelAnimationFrame(viewBoxRafRef.current);
31142
+ viewBoxRafRef.current = null;
31143
+ }
31144
+ }, []);
31145
+ const scheduleViewBox = React.useCallback((updater) => {
31146
+ const base = pendingViewBoxRef.current ?? viewBoxRef.current;
31147
+ const next = updater(base);
31148
+ pendingViewBoxRef.current = next;
31149
+ pendingGridViewBoxRef.current = next;
31150
+ viewBoxRef.current = next;
31151
+ gridViewBoxRef.current = next;
31152
+ if (viewBoxRafRef.current == null) {
31153
+ viewBoxRafRef.current = requestAnimationFrame(flushScheduledViewBox);
31154
+ }
31155
+ }, [flushScheduledViewBox]);
31156
+ const commitViewBox = React.useCallback((next) => {
31157
+ cancelScheduledViewBox();
31158
+ viewBoxRef.current = next;
31159
+ gridViewBoxRef.current = next;
31160
+ setViewBox(next);
31161
+ setGridViewBox(next);
31162
+ }, [cancelScheduledViewBox]);
31163
+ React.useEffect(() => () => {
31164
+ cancelScheduledViewBox();
31165
+ }, [cancelScheduledViewBox]);
31166
+ // 地图数据 / padding 变化时始终重新 fit(width/height 取触发时刻的实测值,不作为 resize 依赖)
31167
+ React.useEffect(() => {
31168
+ if (width <= 0 || height <= 0)
31169
+ return;
31170
+ commitViewBox(computeFitViewBox(contentViewBox, width, height, padding));
31171
+ }, [commitViewBox, contentViewBox, paddingKey]);
31172
+ const prevSizeRef = React.useRef({ width: 0, height: 0 });
31173
+ // 容器首次获得有效尺寸,或 refitOnResize 下的尺寸变化
31174
+ React.useEffect(() => {
31175
+ if (width <= 0 || height <= 0)
31176
+ return;
31177
+ const prev = prevSizeRef.current;
31178
+ const sizeChanged = prev.width !== width || prev.height !== height;
31179
+ const isInitialSize = prev.width <= 0 || prev.height <= 0;
31180
+ prevSizeRef.current = { width, height };
31181
+ if (isInitialSize || (refitOnResize && sizeChanged)) {
31182
+ commitViewBox(fitViewBox);
31183
+ }
31184
+ }, [
31185
+ commitViewBox,
31186
+ refitOnResize,
31187
+ width,
31188
+ height,
31189
+ fitViewBox.x,
31190
+ fitViewBox.y,
31191
+ fitViewBox.width,
31192
+ fitViewBox.height,
31193
+ ]);
31194
+ /**
31195
+ * 以屏幕焦点为中心缩放
31196
+ * @param factor 缩放因子,>1 放大,<1 缩小
31197
+ * @param focalX 焦点相对容器左上角的 X(px)
31198
+ * @param focalY 焦点相对容器左上角的 Y(px)
31199
+ */
31200
+ const applyZoom = React.useCallback((factor, focalX, focalY) => {
31201
+ scheduleViewBox((current) => {
31202
+ const targetWidth = clampViewBoxWidth(current.width / factor, fitViewBoxRef.current, minZoom, maxZoom);
31203
+ return resizeViewBoxAroundPoint(current, targetWidth, width, height, focalX, focalY);
31204
+ });
31205
+ }, [height, maxZoom, minZoom, scheduleViewBox, width]);
31206
+ /**
31207
+ * 平移视口:地块使用逆旋转后的 viewBox 增量(跟手);网格使用屏幕方向增量(同向滑动)
31208
+ */
31209
+ const applyPan = React.useCallback((deltaXPx, deltaYPx) => {
31210
+ const mapBase = pendingViewBoxRef.current ?? viewBoxRef.current;
31211
+ const gridBase = pendingGridViewBoxRef.current ?? gridViewBoxRef.current;
31212
+ const mapDelta = containerDeltaToViewBoxDelta(deltaXPx, deltaYPx, mapBase, width, height, mapRotationDegRef.current);
31213
+ const gridDelta = containerDeltaToViewBoxDelta(deltaXPx, deltaYPx, gridBase, width, height, 0);
31214
+ const nextMap = {
31215
+ ...mapBase,
31216
+ x: mapBase.x - mapDelta.dx,
31217
+ y: mapBase.y - mapDelta.dy,
31218
+ };
31219
+ const nextGrid = {
31220
+ ...gridBase,
31221
+ x: gridBase.x - gridDelta.dx,
31222
+ y: gridBase.y - gridDelta.dy,
31223
+ width: nextMap.width,
31224
+ height: nextMap.height,
31225
+ };
31226
+ pendingViewBoxRef.current = nextMap;
31227
+ pendingGridViewBoxRef.current = nextGrid;
31228
+ viewBoxRef.current = nextMap;
31229
+ gridViewBoxRef.current = nextGrid;
31230
+ if (viewBoxRafRef.current == null) {
31231
+ viewBoxRafRef.current = requestAnimationFrame(flushScheduledViewBox);
31232
+ }
31233
+ }, [flushScheduledViewBox, height, width]);
31234
+ /** 恢复到初始 fitViewBox(ref API resetView) */
31235
+ const resetView = React.useCallback(() => {
31236
+ commitViewBox(fitViewBoxRef.current);
31237
+ }, [commitViewBox]);
31238
+ /**
31239
+ * 重新计算并应用 fit viewBox(ref API fitToView)
31240
+ * @param nextPadding 可选的新 padding,不传则使用当前 paddingRef
31241
+ */
31242
+ const fitToView = React.useCallback((nextPadding) => {
31243
+ const bounds = calculateMapBounds(mapJson);
31244
+ const content = boundsToContentViewBox(bounds, PARTITION_PICKER_CONTENT_CLIP_PADDING);
31245
+ const fit = computeFitViewBox(content, width, height, nextPadding ?? paddingRef.current);
31246
+ fitViewBoxRef.current = fit;
31247
+ commitViewBox(fit);
31248
+ }, [commitViewBox, height, mapJson, width]);
31249
+ /** 桌面端:按 pointerId 追踪多个指针(预留多指,当前仅单指平移) */
31250
+ const pointerMapRef = React.useRef(new Map());
31251
+ /** H5:双指 pinch 缩放状态 */
31252
+ const pinchRef = React.useRef(null);
31253
+ /** 单指/单指针平移状态(桌面 pointer 与 H5 touch 共用) */
31254
+ const singlePanRef = React.useRef(null);
31255
+ /** 当前正在驱动平移的 pointer id(桌面端) */
31256
+ const activePanPointerIdRef = React.useRef(null);
31257
+ /** 清空所有平移/pinch 相关 ref,防止 pointer 丢失导致状态残留 */
31258
+ const clearPanState = React.useCallback(() => {
31259
+ pointerMapRef.current.clear();
31260
+ singlePanRef.current = null;
31261
+ activePanPointerIdRef.current = null;
31262
+ }, []);
31263
+ /** 计算 touch 列表中前两指间距 */
31264
+ const getTouchDistance = (touches) => {
31265
+ if (touches.length < 2)
31266
+ return 0;
31267
+ const dx = touches[0].clientX - touches[1].clientX;
31268
+ const dy = touches[0].clientY - touches[1].clientY;
31269
+ return Math.hypot(dx, dy);
31270
+ };
31271
+ /** 计算 touch 列表中前两指中心点,坐标相对容器左上角 */
31272
+ const getTouchCenter = (touches, rect) => {
31273
+ const x = (touches[0].clientX + touches[1].clientX) / 2 - rect.left;
31274
+ const y = (touches[0].clientY + touches[1].clientY) / 2 - rect.top;
31275
+ return { x, y };
31276
+ };
31277
+ /** 判断事件目标是否在地图容器内 */
31278
+ const isInsideContainer = (target, container) => {
31279
+ if (!(target instanceof Element))
31280
+ return false;
31281
+ return container.contains(target);
31282
+ };
31283
+ /** 位移是否超过 PAN_CLICK_THRESHOLD_PX,超过则视为拖拽 */
31284
+ const markPanMoved = (startX, startY, currentX, currentY) => Math.hypot(currentX - startX, currentY - startY) > PAN_CLICK_THRESHOLD_PX;
31285
+ /** 触发点击回调(仅当未发生拖拽时) */
31286
+ const emitViewportTap = (tapTarget) => {
31287
+ if (tapTarget) {
31288
+ onViewportTapRef.current?.(tapTarget);
31289
+ }
31290
+ };
31291
+ // ── 绑定容器交互事件:滚轮缩放 / 桌面拖拽 / H5 pinch+单指平移 ──
31292
+ React.useEffect(() => {
31293
+ const container = containerRef.current;
31294
+ if (!container || !enablePanZoom)
31295
+ return;
31296
+ /** 滚轮缩放,以鼠标位置为焦点 */
31297
+ const handleWheel = (event) => {
31298
+ event.preventDefault();
31299
+ const rect = container.getBoundingClientRect();
31300
+ const focalX = event.clientX - rect.left;
31301
+ const focalY = event.clientY - rect.top;
31302
+ const factor = event.deltaY < 0 ? 1.1 : 1 / 1.1;
31303
+ applyZoom(factor, focalX, focalY);
31304
+ };
31305
+ /** 结束平移:未拖拽则触发 tap;释放 pointer capture 并清理状态 */
31306
+ const endPan = (event) => {
31307
+ // touch 由 touchend 统一处理,避免 pointerup 抢先清空 singlePanRef
31308
+ if (event?.pointerType === 'touch') {
31309
+ return;
31310
+ }
31311
+ if (event) {
31312
+ const state = pointerMapRef.current.get(event.pointerId);
31313
+ if (state && !state.moved && state.tapTarget) {
31314
+ emitViewportTap(state.tapTarget);
31315
+ }
31316
+ pointerMapRef.current.delete(event.pointerId);
31317
+ try {
31318
+ if (container.hasPointerCapture(event.pointerId)) {
31319
+ container.releasePointerCapture(event.pointerId);
31320
+ }
31321
+ }
31322
+ catch {
31323
+ // ignore
31324
+ }
31325
+ }
31326
+ if (!event || pointerMapRef.current.size === 0) {
31327
+ clearPanState();
31328
+ }
31329
+ };
31330
+ // 桌面端:pointer 事件处理平移(touch 交给下方 touch 事件,避免重复)
31331
+ const handlePointerDown = (event) => {
31332
+ if (!isInsideContainer(event.target, container))
31333
+ return;
31334
+ if (event.pointerType === 'touch') {
31335
+ return;
31336
+ }
31337
+ // 仅响应主键(左键)
31338
+ if (event.pointerType === 'mouse' && event.button !== 0) {
31339
+ return;
31340
+ }
31341
+ pointerMapRef.current.set(event.pointerId, {
31342
+ pointerId: event.pointerId,
31343
+ startX: event.clientX,
31344
+ startY: event.clientY,
31345
+ moved: false,
31346
+ tapTarget: event.target instanceof Element ? event.target : null,
31347
+ });
31348
+ activePanPointerIdRef.current = event.pointerId;
31349
+ singlePanRef.current = {
31350
+ startX: event.clientX,
31351
+ startY: event.clientY,
31352
+ viewBox: viewBoxRef.current,
31353
+ moved: false,
31354
+ tapTarget: event.target instanceof Element ? event.target : null,
31355
+ };
31356
+ container.setPointerCapture(event.pointerId);
31357
+ };
31358
+ const handlePointerMove = (event) => {
31359
+ // 主键已松开但 pointerup 未触发时(如释放在容器外),避免继续平移
31360
+ if (event.pointerType === 'mouse' && event.buttons === 0) {
31361
+ endPan(event);
31362
+ return;
31363
+ }
31364
+ if (activePanPointerIdRef.current !== event.pointerId)
31365
+ return;
31366
+ const state = pointerMapRef.current.get(event.pointerId);
31367
+ if (!state || !singlePanRef.current)
31368
+ return;
31369
+ const dx = event.clientX - state.startX;
31370
+ const dy = event.clientY - state.startY;
31371
+ if (markPanMoved(state.startX, state.startY, event.clientX, event.clientY)) {
31372
+ state.moved = true;
31373
+ singlePanRef.current.moved = true;
31374
+ }
31375
+ applyPan(dx, dy);
31376
+ pointerMapRef.current.set(event.pointerId, {
31377
+ ...state,
31378
+ startX: event.clientX,
31379
+ startY: event.clientY,
31380
+ });
31381
+ };
31382
+ const handlePointerUp = (event) => {
31383
+ endPan(event);
31384
+ };
31385
+ const handleLostPointerCapture = (event) => {
31386
+ if (activePanPointerIdRef.current === event.pointerId) {
31387
+ endPan(event);
31388
+ }
31389
+ };
31390
+ const handleWindowPointerUp = (event) => {
31391
+ if (event instanceof PointerEvent && event.pointerType === 'touch') {
31392
+ return;
31393
+ }
31394
+ clearPanState();
31395
+ };
31396
+ // H5:双指 pinch 缩放、单指平移
31397
+ const handleTouchStart = (event) => {
31398
+ if (event.touches.length === 2) {
31399
+ event.preventDefault();
31400
+ const rect = container.getBoundingClientRect();
31401
+ const center = getTouchCenter(event.touches, rect);
31402
+ pinchRef.current = {
31403
+ initialDistance: getTouchDistance(event.touches),
31404
+ initialViewBox: viewBoxRef.current,
31405
+ centerX: center.x,
31406
+ centerY: center.y,
31407
+ };
31408
+ singlePanRef.current = null;
31409
+ return;
31410
+ }
31411
+ if (event.touches.length === 1 && isInsideContainer(event.target, container)) {
31412
+ singlePanRef.current = {
31413
+ startX: event.touches[0].clientX,
31414
+ startY: event.touches[0].clientY,
31415
+ viewBox: viewBoxRef.current,
31416
+ moved: false,
31417
+ tapTarget: event.target instanceof Element ? event.target : null,
31418
+ };
31419
+ }
31420
+ };
31421
+ const handleTouchMove = (event) => {
31422
+ if (event.touches.length === 2 && pinchRef.current) {
31423
+ event.preventDefault();
31424
+ const rect = container.getBoundingClientRect();
31425
+ const distance = getTouchDistance(event.touches);
31426
+ if (distance <= 0 || pinchRef.current.initialDistance <= 0)
31427
+ return;
31428
+ const scale = distance / pinchRef.current.initialDistance;
31429
+ const center = getTouchCenter(event.touches, rect);
31430
+ const targetWidth = clampViewBoxWidth(pinchRef.current.initialViewBox.width / scale, fitViewBoxRef.current, minZoom, maxZoom);
31431
+ scheduleViewBox(() => resizeViewBoxAroundPoint(pinchRef.current.initialViewBox, targetWidth, width, height, center.x, center.y));
31432
+ return;
31433
+ }
31434
+ if (event.touches.length === 1 && singlePanRef.current) {
31435
+ const panState = singlePanRef.current;
31436
+ const touch = event.touches[0];
31437
+ if (markPanMoved(panState.startX, panState.startY, touch.clientX, touch.clientY)) {
31438
+ panState.moved = true;
31439
+ }
31440
+ if (panState.moved) {
31441
+ event.preventDefault();
31442
+ }
31443
+ const dx = touch.clientX - panState.startX;
31444
+ const dy = touch.clientY - panState.startY;
31445
+ applyPan(dx, dy);
31446
+ singlePanRef.current = {
31447
+ ...panState,
31448
+ startX: touch.clientX,
31449
+ startY: touch.clientY,
31450
+ };
31451
+ }
31452
+ };
31453
+ const handleTouchEnd = (event) => {
31454
+ const touchState = singlePanRef.current;
31455
+ if (touchState && !touchState.moved) {
31456
+ let tapTarget = touchState.tapTarget;
31457
+ const touch = event.changedTouches[0];
31458
+ if (touch) {
31459
+ const hitTarget = document.elementFromPoint(touch.clientX, touch.clientY);
31460
+ if (hitTarget instanceof Element) {
31461
+ tapTarget = hitTarget;
31462
+ }
31463
+ }
31464
+ emitViewportTap(tapTarget);
31465
+ }
31466
+ if (pinchRef.current) {
31467
+ pinchRef.current = null;
31468
+ }
31469
+ clearPanState();
31470
+ };
31471
+ container.addEventListener('wheel', handleWheel, { passive: false });
31472
+ container.addEventListener('pointerdown', handlePointerDown);
31473
+ container.addEventListener('pointermove', handlePointerMove);
31474
+ container.addEventListener('pointerup', handlePointerUp);
31475
+ container.addEventListener('pointercancel', handlePointerUp);
31476
+ container.addEventListener('lostpointercapture', handleLostPointerCapture);
31477
+ container.addEventListener('touchstart', handleTouchStart, { passive: false });
31478
+ container.addEventListener('touchmove', handleTouchMove, { passive: false });
31479
+ container.addEventListener('touchend', handleTouchEnd);
31480
+ container.addEventListener('touchcancel', handleTouchEnd);
31481
+ window.addEventListener('pointerup', handleWindowPointerUp);
31482
+ window.addEventListener('mouseup', handleWindowPointerUp);
31483
+ window.addEventListener('blur', handleWindowPointerUp);
31484
+ return () => {
31485
+ container.removeEventListener('wheel', handleWheel);
31486
+ container.removeEventListener('pointerdown', handlePointerDown);
31487
+ container.removeEventListener('pointermove', handlePointerMove);
31488
+ container.removeEventListener('pointerup', handlePointerUp);
31489
+ container.removeEventListener('pointercancel', handlePointerUp);
31490
+ container.removeEventListener('lostpointercapture', handleLostPointerCapture);
31491
+ container.removeEventListener('touchstart', handleTouchStart);
31492
+ container.removeEventListener('touchmove', handleTouchMove);
31493
+ container.removeEventListener('touchend', handleTouchEnd);
31494
+ container.removeEventListener('touchcancel', handleTouchEnd);
31495
+ window.removeEventListener('pointerup', handleWindowPointerUp);
31496
+ window.removeEventListener('mouseup', handleWindowPointerUp);
31497
+ window.removeEventListener('blur', handleWindowPointerUp);
31498
+ };
31499
+ }, [applyPan, applyZoom, clearPanState, enablePanZoom, height, maxZoom, minZoom, scheduleViewBox, width]);
31500
+ return {
31501
+ /** 绑定到地图外层容器的 ref */
31502
+ containerRef,
31503
+ /** 地块/通道/overlay 使用的 viewBox */
31504
+ viewBox,
31505
+ /** 网格底图 viewBox(平移按屏幕方向,与旋转地块解耦) */
31506
+ gridViewBox,
31507
+ /** 初始 fit viewBox,供 clip path 等需要稳定坐标系的场景 */
31508
+ fitViewBox,
31509
+ /** 恢复到初始 fit 状态 */
31510
+ resetView,
31511
+ /** 重新 fit 地图到容器(可传新 padding) */
31512
+ fitToView,
31513
+ };
31514
+ }
31515
+
31516
+ /** WGS84 经纬度坐标系 */
31517
+ const WGS84 = 'EPSG:4326';
31518
+ /** Web Mercator 平面坐标(米),与 MowerMapRenderer / defaultTransform.x|y 一致 */
31519
+ const WEB_MERCATOR = 'EPSG:3857';
31520
+ proj4.defs(WGS84, '+proj=longlat +datum=WGS84 +no_defs');
31521
+ proj4.defs(WEB_MERCATOR, '+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs');
31522
+ /**
31523
+ * Map 模式专用:管理 OverlayViewF 所需的 bounds、viewBox、缩放与 fitBounds。
31524
+ * 逻辑对齐 MowerMapRenderer 的 Google Maps 叠加层。
31525
+ */
31526
+ function useGoogleMapsOverlay({ mapJson, mapRef, defaultTransform, originNorthRotate, platform = exports.PlatformType.WEB, padding = 50, }) {
31527
+ /** 仅当外部传入 mapRef 时执行 fitBounds / zoom 监听等副作用 */
31528
+ const enabled = Boolean(mapRef);
31529
+ const transformX = defaultTransform?.x ?? 0;
31530
+ const transformY = defaultTransform?.y ?? 0;
31531
+ const transformRotation = defaultTransform?.rotation ?? 0;
31532
+ /** 叠加层实际旋转 = 地图北向偏移 + 用户 rotation */
31533
+ const actureRotate = originNorthRotate + transformRotation;
31534
+ /** OverlayViewF onDraw 回调写入的 overlay 像素宽高 */
31535
+ const [overlayLayout, setOverlayLayout] = React.useState(null);
31536
+ const mapBounds = React.useMemo(() => calculateMapBounds(mapJson), [mapJson]);
31537
+ /**
31538
+ * SVG viewBox:地图 JSON 平面坐标全量范围。
31539
+ * 需等待 overlayLayout 就绪后才可计算(与 MowerMapRenderer svgViewBox 一致)。
31540
+ */
31541
+ const svgViewBox = React.useMemo(() => {
31542
+ if (!overlayLayout?.width || !overlayLayout?.height)
31543
+ return null;
31544
+ const boundWidth = mapBounds.maxX - mapBounds.minX;
31545
+ const boundHeight = mapBounds.maxY - mapBounds.minY;
31546
+ if (boundWidth <= 0 || boundHeight <= 0)
31547
+ return null;
31548
+ return {
31549
+ x: mapBounds.minX,
31550
+ y: mapBounds.minY,
31551
+ width: boundWidth,
31552
+ height: boundHeight,
31553
+ };
31554
+ }, [mapBounds, overlayLayout?.height, overlayLayout?.width]);
31555
+ /**
31556
+ * OverlayViewF 的 LatLngBounds:
31557
+ * GPS 包围盒 + defaultTransform 在 Web Mercator 下的平移偏移。
31558
+ */
31559
+ const bounds = React.useMemo(() => {
31560
+ if (!mapJson || typeof window === 'undefined' || !window.google?.maps)
31561
+ return null;
31562
+ const validBounds = getValidGpsBounds(mapJson, 0);
31563
+ const [swLng0, swLat0] = validBounds.sw;
31564
+ const [neLng0, neLat0] = validBounds.ne;
31565
+ const [swX0, swY0] = proj4(WGS84, WEB_MERCATOR, [swLng0, swLat0]);
31566
+ const [neX0, neY0] = proj4(WGS84, WEB_MERCATOR, [neLng0, neLat0]);
31567
+ const swX = swX0 + transformX;
31568
+ const swY = swY0 + transformY;
31569
+ const neX = neX0 + transformX;
31570
+ const neY = neY0 + transformY;
31571
+ const [swLng, swLat] = proj4(WEB_MERCATOR, WGS84, [swX, swY]);
31572
+ const [neLng, neLat] = proj4(WEB_MERCATOR, WGS84, [neX, neY]);
31573
+ return new window.google.maps.LatLngBounds(new window.google.maps.LatLng(swLat, swLng), new window.google.maps.LatLng(neLat, neLng));
31574
+ }, [mapJson, transformX, transformY]);
31575
+ /** OverlayViewF onDraw:解析 overlay 容器 style,得到像素 layout */
31576
+ const handleOverlayDraw = React.useCallback((style) => {
31577
+ const layout = Object.keys(style).reduce((acc, key) => {
31578
+ if (!key)
31579
+ return acc;
31580
+ acc[key] = parseFloat(style[key]);
31581
+ return acc;
31582
+ }, {});
31583
+ if (layout.width > 0 && layout.height > 0) {
31584
+ setOverlayLayout({ width: layout.width, height: layout.height });
31585
+ }
31586
+ }, []);
31587
+ /** Map 模式 fitToView:调用 google.maps.Map.fitBounds,考虑旋转后的 GPS 包围盒 */
31588
+ const fitToView = React.useCallback((nextPadding) => {
31589
+ if (!enabled || !mapRef || !mapJson)
31590
+ return;
31591
+ const rotate = transformRotation + originNorthRotate;
31592
+ const validBounds = getValidGpsBounds(mapJson, rotate);
31593
+ const [swLng0, swLat0] = validBounds.sw;
31594
+ const [neLng0, neLat0] = validBounds.ne;
31595
+ const [swX0, swY0] = proj4(WGS84, WEB_MERCATOR, [swLng0, swLat0]);
31596
+ const [neX0, neY0] = proj4(WGS84, WEB_MERCATOR, [neLng0, neLat0]);
31597
+ const swX = swX0 + transformX;
31598
+ const swY = swY0 + transformY;
31599
+ const neX = neX0 + transformX;
31600
+ const neY = neY0 + transformY;
31601
+ const [swLng, swLat] = proj4(WEB_MERCATOR, WGS84, [swX, swY]);
31602
+ const [neLng, neLat] = proj4(WEB_MERCATOR, WGS84, [neX, neY]);
31603
+ const googleBounds = new window.google.maps.LatLngBounds(new window.google.maps.LatLng(swLat, swLng), new window.google.maps.LatLng(neLat, neLng));
31604
+ const pad = normalizeScreenPadding(nextPadding ?? padding, 0);
31605
+ const paddingArg = pad.top === pad.right && pad.right === pad.bottom && pad.bottom === pad.left
31606
+ ? pad.top
31607
+ : { top: pad.top, right: pad.right, bottom: pad.bottom, left: pad.left };
31608
+ mapRef.fitBounds(googleBounds, paddingArg);
31609
+ }, [enabled, mapJson, mapRef, originNorthRotate, padding, transformRotation, transformX, transformY]);
31610
+ const resetView = React.useCallback(() => {
31611
+ fitToView();
31612
+ }, [fitToView]);
31613
+ /** 参考缩放级别,用于计算 overlayScale(通道线宽视觉恒定,与 MowerMapRenderer 一致) */
31614
+ const REFERENCE_ZOOM = platform === exports.PlatformType.WEB ? 21 : 21;
31615
+ const [overlayScale, setOverlayScale] = React.useState(1);
31616
+ React.useEffect(() => {
31617
+ if (!enabled || !mapRef)
31618
+ return;
31619
+ const updateScale = () => {
31620
+ const currentZoom = mapRef.getZoom();
31621
+ if (currentZoom === undefined)
31622
+ return;
31623
+ const zoomDiff = currentZoom - REFERENCE_ZOOM;
31624
+ const scale = Math.pow(2, -zoomDiff);
31625
+ if (scale < 1) {
31626
+ setOverlayScale(1);
31627
+ }
31628
+ else if (scale > 200) {
31629
+ setOverlayScale(platform === exports.PlatformType.H5 ? 300 : 200);
31630
+ }
31631
+ else {
31632
+ setOverlayScale(platform === exports.PlatformType.H5 ? 1.5 * scale : scale);
31633
+ }
31634
+ };
31635
+ const handleZoomChanged = throttle$2(updateScale, 50);
31636
+ const listener = mapRef.addListener('zoom_changed', handleZoomChanged);
31637
+ updateScale();
31638
+ return () => {
31639
+ if (listener) {
31640
+ window.google.maps.event.removeListener(listener);
31641
+ }
31642
+ handleZoomChanged.cancel();
31643
+ };
31644
+ }, [enabled, mapRef, platform, REFERENCE_ZOOM]);
31645
+ const overlayWidth = overlayLayout?.width ?? 0;
31646
+ const overlayHeight = overlayLayout?.height ?? 0;
31647
+ const isOverlayReady = overlayWidth > 0 && overlayHeight > 0 && svgViewBox != null;
31648
+ return {
31649
+ actureRotate,
31650
+ bounds,
31651
+ svgViewBox,
31652
+ fitViewBox: svgViewBox,
31653
+ overlayWidth,
31654
+ overlayHeight,
31655
+ overlayScale,
31656
+ isOverlayReady,
31657
+ handleOverlayDraw,
31658
+ fitToView,
31659
+ resetView,
31660
+ };
31661
+ }
31662
+
31663
+ function getInitialSize(width, height) {
31664
+ return {
31665
+ width: typeof width === 'number' && width > 0 ? width : 0,
31666
+ height: typeof height === 'number' && height > 0 ? height : 0,
31667
+ };
31668
+ }
31669
+ function cssLengthToStyle(value) {
31670
+ return value;
31671
+ }
31672
+ /**
31673
+ * 将 width/height(px 或 CSS length)应用到容器,并用 ResizeObserver 测量实际像素尺寸
31674
+ * 供 fit、标签定位、线宽换算等逻辑使用
31675
+ */
31676
+ function useContainerSize(width, height) {
31677
+ const containerRef = React.useRef(null);
31678
+ const [size, setSize] = React.useState(() => getInitialSize(width, height));
31679
+ const layoutStyle = React.useMemo(() => ({
31680
+ width: cssLengthToStyle(width),
31681
+ height: cssLengthToStyle(height),
31682
+ }), [width, height]);
31683
+ React.useLayoutEffect(() => {
31684
+ const element = containerRef.current;
31685
+ if (!element)
31686
+ return;
31687
+ const updateSize = () => {
31688
+ const { width: nextWidth, height: nextHeight } = element.getBoundingClientRect();
31689
+ if (nextWidth <= 0 || nextHeight <= 0)
31690
+ return;
31691
+ setSize((prev) => prev.width === nextWidth && prev.height === nextHeight
31692
+ ? prev
31693
+ : { width: nextWidth, height: nextHeight });
31694
+ };
31695
+ updateSize();
31696
+ const observer = new ResizeObserver(updateSize);
31697
+ observer.observe(element);
31698
+ return () => observer.disconnect();
31699
+ }, []);
31700
+ const isReady = size.width > 0 && size.height > 0;
31701
+ return {
31702
+ containerRef,
31703
+ layoutStyle,
31704
+ width: size.width,
31705
+ height: size.height,
31706
+ isReady,
31707
+ };
31708
+ }
31709
+
31710
+ /**
31711
+ * 地块 hover 状态管理(受控 / 非受控)
31712
+ *
31713
+ * 用于地图与外部表格等 UI 双向联动:
31714
+ * - 地图 hover → onBoundaryHoverChange → 宿主更新表格行高亮
31715
+ * - 表格 hover → 宿主传入 hoveredBoundaryId → 地图同步高亮
31716
+ */
31717
+ function useBoundaryHover({ hoveredBoundaryId, defaultHoveredBoundaryId = null, onBoundaryHoverChange, }) {
31718
+ const isControlled = hoveredBoundaryId !== undefined;
31719
+ const [internalHoveredId, setInternalHoveredId] = React.useState(defaultHoveredBoundaryId);
31720
+ const hoveredId = isControlled ? hoveredBoundaryId : internalHoveredId;
31721
+ const setHoveredId = React.useCallback((id) => {
31722
+ if (!isControlled) {
31723
+ setInternalHoveredId(id);
31724
+ }
31725
+ onBoundaryHoverChange?.(id);
31726
+ }, [isControlled, onBoundaryHoverChange]);
31727
+ return {
31728
+ hoveredId,
31729
+ setHoveredId,
31730
+ };
31731
+ }
31732
+
31733
+ function filterSelectableIds(ids, disabledBoundaryIds) {
31734
+ if (!disabledBoundaryIds?.size)
31735
+ return ids;
31736
+ return ids.filter((id) => !disabledBoundaryIds.has(id));
31737
+ }
31738
+ /**
31739
+ * 地块多选状态管理(受控 / 非受控)
31740
+ *
31741
+ * - 传入 selectedBoundaryIds → 受控,由宿主维护选中列表(如表格勾选)
31742
+ * - 未传入 → 非受控,内部维护,仍可通过 onSelectionChange 感知变化
31743
+ */
31744
+ function useBoundarySelection({ selectedBoundaryIds, defaultSelectedBoundaryIds = [], onSelectionChange, disabledBoundaryIds, }) {
31745
+ const isControlled = selectedBoundaryIds !== undefined;
31746
+ const [internalIds, setInternalIds] = React.useState(filterSelectableIds(defaultSelectedBoundaryIds, disabledBoundaryIds));
31747
+ const selectedIds = isControlled
31748
+ ? filterSelectableIds(selectedBoundaryIds, disabledBoundaryIds)
31749
+ : internalIds;
31750
+ const setSelectedIds = React.useCallback((ids) => {
31751
+ const next = filterSelectableIds(ids, disabledBoundaryIds);
31752
+ if (!isControlled) {
31753
+ setInternalIds(next);
31754
+ }
31755
+ onSelectionChange?.(next);
31756
+ }, [disabledBoundaryIds, isControlled, onSelectionChange]);
31757
+ /** 点击同一地块时 toggle,支持多选;孤立子地块不可选 */
31758
+ const toggleBoundary = React.useCallback((id) => {
31759
+ if (disabledBoundaryIds?.has(id))
31760
+ return;
31761
+ const next = selectedIds.includes(id)
31762
+ ? selectedIds.filter((item) => item !== id)
31763
+ : [...selectedIds, id];
31764
+ setSelectedIds(next);
31765
+ }, [disabledBoundaryIds, selectedIds, setSelectedIds]);
31766
+ return {
31767
+ selectedIds,
31768
+ toggleBoundary,
31769
+ setSelectedIds,
31770
+ };
31771
+ }
31772
+
31773
+ /** 计算地图中不可选中的孤立子地块 id 集合 */
31774
+ function useIsolatedBoundaryIds(mapJson) {
31775
+ return React.useMemo(() => {
31776
+ const boundaries = generateBoundaryData(mapJson);
31777
+ const ids = new Set();
31778
+ for (const boundary of boundaries) {
31779
+ if (boundary.isIsolated) {
31780
+ ids.add(boundary.id);
31781
+ }
31782
+ }
31783
+ return ids;
31784
+ }, [mapJson]);
31785
+ }
31786
+ /** 供宿主(如表格)判断某地块是否不可选 */
31787
+ function getIsolatedBoundaryIds(mapJson) {
31788
+ return generateBoundaryData(mapJson)
31789
+ .filter((boundary) => boundary.isIsolated)
31790
+ .map((boundary) => boundary.id);
31791
+ }
31792
+
31793
+ /** 计算多边形重心,作为分区名气泡的锚点 */
31794
+ function computePolygonCentroid(points) {
31795
+ if (!points || points.length < 3)
31796
+ return null;
31797
+ const valid = points.filter((p) => p.length >= 2);
31798
+ if (valid.length < 3)
31799
+ return null;
31800
+ const polygon = [...valid];
31801
+ const first = polygon[0];
31802
+ const last = polygon[polygon.length - 1];
31803
+ if (first[0] !== last[0] || first[1] !== last[1]) {
31804
+ polygon.push([first[0], first[1]]);
31805
+ }
31806
+ let area = 0;
31807
+ let cx = 0;
31808
+ let cy = 0;
31809
+ for (let i = 0; i < polygon.length - 1; i++) {
31810
+ const x0 = polygon[i][0];
31811
+ const y0 = polygon[i][1];
31812
+ const x1 = polygon[i + 1][0];
31813
+ const y1 = polygon[i + 1][1];
31814
+ const cross = x0 * y1 - x1 * y0;
31815
+ area += cross;
31816
+ cx += (x0 + x1) * cross;
31817
+ cy += (y0 + y1) * cross;
31818
+ }
31819
+ area = area / 2;
31820
+ if (Math.abs(area) < 1e-10) {
31821
+ let sx = 0;
31822
+ let sy = 0;
31823
+ for (const [x, y] of valid) {
31824
+ sx += x;
31825
+ sy += y;
31826
+ }
31827
+ return { x: sx / valid.length, y: sy / valid.length };
31828
+ }
31829
+ cx = cx / (6 * area);
31830
+ cy = cy / (6 * area);
31831
+ return { x: cx, y: cy };
31832
+ }
31833
+ function buildBoundaryLabelGeometry(mapData) {
31834
+ const boundaryData = generateBoundaryData(mapData);
31835
+ const results = [];
31836
+ for (const boundary of boundaryData) {
31837
+ const centroid = computePolygonCentroid(boundary.points);
31838
+ if (!centroid)
31839
+ continue;
31840
+ results.push({
31841
+ id: boundary.id,
31842
+ name: boundary.name,
31843
+ points: boundary.points,
31844
+ centroid,
31845
+ isIsolated: boundary.isIsolated,
31846
+ });
31847
+ }
31848
+ return results;
31849
+ }
31850
+ /**
31851
+ * 根据当前「显示 viewBox」计算分区名气泡位置(不预旋转;由 mapOverlayLayer CSS rotate 处理)
31852
+ */
31853
+ function usePartitionLabelItems(mapData, viewBox, containerWidth, containerHeight, options) {
31854
+ const positionMode = options?.positionMode ?? 'meet';
31855
+ const boundaryGeometry = React.useMemo(() => (mapData ? buildBoundaryLabelGeometry(mapData) : []), [mapData]);
31856
+ return React.useMemo(() => {
31857
+ if (!viewBox || containerWidth <= 0 || containerHeight <= 0)
31858
+ return [];
31859
+ const results = [];
31860
+ for (const boundary of boundaryGeometry) {
31861
+ let leftPct;
31862
+ let topPct;
31863
+ if (positionMode === 'overlay') {
31864
+ const relX = (boundary.centroid.x - viewBox.x) / viewBox.width;
31865
+ const relY = (boundary.centroid.y - viewBox.y) / viewBox.height;
31866
+ leftPct = relX * 100;
31867
+ topPct = relY * 100;
31868
+ }
31869
+ else {
31870
+ const position = mapPointToContainerPercent(boundary.centroid.x, boundary.centroid.y, viewBox, containerWidth, containerHeight);
31871
+ leftPct = position.leftPct;
31872
+ topPct = position.topPct;
31873
+ }
31874
+ if (!Number.isFinite(leftPct) || !Number.isFinite(topPct))
31875
+ continue;
31876
+ const screenWidthPx = computePolygonScreenWidthPx(boundary.points, viewBox, containerWidth, containerHeight);
31877
+ results.push({
31878
+ id: boundary.id,
31879
+ name: boundary.name,
31880
+ leftPct,
31881
+ topPct,
31882
+ screenWidthPx,
31883
+ isIsolated: boundary.isIsolated,
31884
+ });
31885
+ }
31886
+ return results;
31887
+ }, [
31888
+ boundaryGeometry,
31889
+ containerWidth,
31890
+ containerHeight,
31891
+ viewBox?.x,
31892
+ viewBox?.y,
31893
+ viewBox?.width,
31894
+ viewBox?.height,
31895
+ positionMode,
31896
+ ]);
31897
+ }
31898
+
31899
+ 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}";
31900
+ 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"};
31901
+ styleInject(css_248z);
31902
+
31903
+ const PARTITION_BOUNDARY_ID_ATTR$1 = 'data-partition-boundary-id';
31904
+ const SelectableBoundaryElement = ({ data, boundaryColors, boundaryStrokePx, isIsolated = false, selected, hovered, viewBoxWidth, viewBoxHeight, fitViewBox, containerWidth, containerHeight, onHoverChange, onBoundaryClick, }) => {
31905
+ const style = data.style || {};
31906
+ const boundaryId = Number(data.id);
31907
+ const colors = boundaryColors;
31908
+ const strokePx = boundaryStrokePx ?? {
31909
+ default: PARTITION_BOUNDARY_TARGET_SCREEN_STROKE_PX,
31910
+ emphasized: PARTITION_BOUNDARY_EMPHASIZED_TARGET_SCREEN_STROKE_PX,
31911
+ };
31912
+ const pointsString = React.useMemo(() => data.points.map((point) => `${point[0]},${point[1]}`).join(' '), [data.points]);
31913
+ const fillColor = React.useMemo(() => {
31914
+ if (isIsolated)
31915
+ return colors.isolatedFill;
31916
+ if (selected) {
31917
+ return hovered ? colors.selectedHoverFill : colors.selectedFill;
31918
+ }
31919
+ if (hovered)
31920
+ return colors.hoverFill;
31921
+ return style.fillColor || colors.defaultFill;
31922
+ }, [colors, hovered, isIsolated, selected, style.fillColor]);
31923
+ const strokeColor = React.useMemo(() => {
31924
+ if (isIsolated)
31925
+ return colors.isolatedStroke;
31926
+ if (selected) {
31927
+ return hovered ? colors.selectedHoverStroke : colors.selectedStroke;
31928
+ }
31929
+ if (hovered)
31930
+ return colors.hoverStroke;
31931
+ return style.lineColor || colors.defaultStroke;
31932
+ }, [colors, hovered, isIsolated, selected, style.lineColor]);
31933
+ const strokeWidth = React.useMemo(() => {
31934
+ let baseWidth;
31935
+ let emphasized = false;
31936
+ if (isIsolated)
31937
+ baseWidth = colors.isolatedStrokeWidth;
31938
+ else if (selected) {
31939
+ emphasized = true;
31940
+ baseWidth = hovered ? colors.selectedHoverStrokeWidth : colors.selectedStrokeWidth;
31941
+ }
31942
+ else if (hovered) {
31943
+ emphasized = true;
31944
+ baseWidth = colors.hoverStrokeWidth;
31945
+ }
31946
+ else
31947
+ baseWidth = colors.defaultStrokeWidth;
31948
+ const viewBox = { width: viewBoxWidth, height: viewBoxHeight };
31949
+ return scaleStrokeWidthForViewBox(baseWidth, viewBox, fitViewBox, {
31950
+ containerWidth,
31951
+ containerHeight,
31952
+ minScreenStrokePx: emphasized ? strokePx.emphasized : strokePx.default,
31953
+ });
31954
+ }, [
31955
+ colors,
31956
+ containerHeight,
31957
+ containerWidth,
31958
+ fitViewBox,
31959
+ hovered,
31960
+ isIsolated,
31961
+ selected,
31962
+ strokePx,
31963
+ viewBoxHeight,
31964
+ viewBoxWidth,
31965
+ ]);
31966
+ if (isIsolated) {
31967
+ return (jsxRuntime.jsx("polygon", { className: `${styles.boundaryPolygon} ${styles.boundaryPolygonIsolated}`, points: pointsString, fill: fillColor, stroke: strokeColor, strokeWidth: strokeWidth, strokeLinejoin: "round" }));
31968
+ }
31969
+ return (jsxRuntime.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
31970
+ ? (event) => {
31971
+ event.stopPropagation();
31972
+ onBoundaryClick(boundaryId);
31973
+ }
31974
+ : undefined }));
31975
+ };
31976
+ var SelectableBoundaryElement$1 = React.memo(SelectableBoundaryElement);
31977
+
31978
+ const PartitionPickerSvg = ({ svgElementDatas, viewBox, fitViewBox, containerWidth, containerHeight, mapRotation = 0, boundaryColors, boundaryStrokePx, isolatedBoundaryIds, selectedIds, hoveredId, onHoverChange, onBoundaryClick, }) => {
31979
+ const boundaries = (svgElementDatas[exports.DataType.BOUNDARY] || []);
31980
+ const channels = (svgElementDatas[exports.DataType.CHANNEL] || []);
31981
+ const selectedIdSet = React.useMemo(() => new Set(selectedIds), [selectedIds]);
31982
+ const mapContentTransform = React.useMemo(() => {
31983
+ if (mapRotation === 0)
31984
+ return undefined;
31985
+ const { x: cx, y: cy } = getViewBoxCenter(viewBox);
31986
+ return `rotate(${mapRotation}, ${cx}, ${cy})`;
31987
+ }, [mapRotation, viewBox.x, viewBox.y, viewBox.width, viewBox.height]);
31988
+ const mapContent = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(GElement, { type: "boundary", children: boundaries.map((item) => {
31989
+ const boundaryId = Number(item.id);
31990
+ return (jsxRuntime.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));
31991
+ }) }), jsxRuntime.jsxs(GElement, { type: "channel", children: [jsxRuntime.jsx(ChannelClipPath, {}), channels.map((item) => (jsxRuntime.jsx(ChannelElement, { data: item }, item.id)))] })] }));
31992
+ return (jsxRuntime.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 ? (jsxRuntime.jsx("g", { transform: mapContentTransform, children: mapContent })) : (mapContent) }));
31993
+ };
31994
+
31995
+ function estimateGridIntersectionCount(viewBox, tileSize) {
31996
+ if (tileSize <= 0)
31997
+ return 0;
31998
+ const cols = Math.floor((viewBox.width + 1e-6) / tileSize) + 1;
31999
+ const rows = Math.floor((viewBox.height + 1e-6) / tileSize) + 1;
32000
+ return cols * rows;
32001
+ }
32002
+ /** 计算当前 viewBox 可见范围内的网格交点(每个交点只渲染一次圆点) */
32003
+ function getVisibleGridIntersections(viewBox, tileSize, maxDots) {
32004
+ if (tileSize <= 0 || estimateGridIntersectionCount(viewBox, tileSize) > maxDots)
32005
+ return [];
32006
+ const points = [];
32007
+ const minX = viewBox.x;
32008
+ const minY = viewBox.y;
32009
+ const maxX = viewBox.x + viewBox.width;
32010
+ const maxY = viewBox.y + viewBox.height;
32011
+ const startX = Math.floor(minX / tileSize) * tileSize;
32012
+ const startY = Math.floor(minY / tileSize) * tileSize;
32013
+ for (let x = startX; x <= maxX + 1e-6; x += tileSize) {
32014
+ for (let y = startY; y <= maxY + 1e-6; y += tileSize) {
32015
+ points.push({ x, y });
32016
+ }
32017
+ }
32018
+ return points;
32019
+ }
32020
+ const MapGridBackground = ({ viewBox, gridStyle }) => {
32021
+ const reactId = React.useId();
32022
+ const patternId = React.useMemo(() => `partition-picker-grid-lines-${reactId.replace(/:/g, '-')}`, [reactId]);
32023
+ const { baseColor, strokeColor, tileSize: tile, lineWidth, dotRadius, opacity, maxIntersectionDots, } = gridStyle;
32024
+ const intersections = React.useMemo(() => getVisibleGridIntersections(viewBox, tile, maxIntersectionDots), [maxIntersectionDots, tile, viewBox.x, viewBox.y, viewBox.width, viewBox.height]);
32025
+ return (jsxRuntime.jsxs("g", { className: "partition-picker-grid-bg", "aria-hidden": true, pointerEvents: "none", children: [jsxRuntime.jsx("defs", { children: jsxRuntime.jsxs("pattern", { id: patternId, patternUnits: "userSpaceOnUse", width: tile, height: tile, children: [jsxRuntime.jsx("line", { x1: 0, y1: 0, x2: tile, y2: 0, stroke: strokeColor, strokeWidth: lineWidth }), jsxRuntime.jsx("line", { x1: 0, y1: 0, x2: 0, y2: tile, stroke: strokeColor, strokeWidth: lineWidth })] }) }), jsxRuntime.jsx("rect", { x: viewBox.x, y: viewBox.y, width: viewBox.width, height: viewBox.height, fill: baseColor }), jsxRuntime.jsx("rect", { x: viewBox.x, y: viewBox.y, width: viewBox.width, height: viewBox.height, fill: `url(#${patternId})`, opacity: opacity }), jsxRuntime.jsx("g", { opacity: opacity, children: intersections.map((point, index) => (jsxRuntime.jsx("circle", { cx: point.x, cy: point.y, r: dotRadius, fill: strokeColor }, `${point.x}-${point.y}-${index}`))) })] }));
32026
+ };
32027
+ var MapGridBackground$1 = React.memo(MapGridBackground);
32028
+
32029
+ const PartitionPickerGridSvg = ({ viewBox, gridStyle }) => {
32030
+ return (jsxRuntime.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: jsxRuntime.jsx(MapGridBackground$1, { viewBox: viewBox, gridStyle: gridStyle }) }));
32031
+ };
32032
+
32033
+ const PartitionNameLabels = ({ items, selectedIds, labelStyles, labelMinScreenWidthPx = 72, platform = exports.PlatformType.WEB, showSelectionOrder = false, rotation = 0, onHoverChange, onBoundaryClick, }) => {
32034
+ const isWeb = platform === exports.PlatformType.WEB;
32035
+ const orderBadgeStyle = React.useMemo(() => ({
32036
+ width: labelStyles.selectionOrderBadge.width,
32037
+ height: labelStyles.selectionOrderBadge.height,
32038
+ background: labelStyles.selectionOrderBadge.background,
32039
+ border: labelStyles.selectionOrderBadge.border,
32040
+ color: labelStyles.selectionOrderBadge.color,
32041
+ fontSize: `${labelStyles.selectionOrderBadge.fontSize}px`,
32042
+ fontWeight: labelStyles.selectionOrderBadge.fontWeight,
32043
+ lineHeight: labelStyles.selectionOrderBadge.lineHeight,
32044
+ }), [labelStyles]);
32045
+ return (jsxRuntime.jsx("div", { className: styles.labelsLayer, children: items.map((item) => {
32046
+ const isIsolated = item.isIsolated === true;
32047
+ const selected = !isIsolated && selectedIds.includes(item.id);
32048
+ const selectionOrder = selected ? selectedIds.indexOf(item.id) + 1 : null;
32049
+ const isLargeEnough = item.screenWidthPx > labelMinScreenWidthPx;
32050
+ // Web 始终展示名称;H5 按屏幕宽度决定
32051
+ const showNameLabel = isWeb || isLargeEnough;
32052
+ const showOrderBadge = showSelectionOrder && selected && selectionOrder != null;
32053
+ const h5OrderOnly = !isWeb && showOrderBadge && !isLargeEnough;
32054
+ const h5OrderWithName = !isWeb && showOrderBadge && isLargeEnough;
32055
+ if (!showNameLabel && !showOrderBadge && !isIsolated) {
32056
+ return null;
32057
+ }
32058
+ const pillStyle = labelAppearanceToStyle(selected ? labelStyles.selected : labelStyles.default);
32059
+ const labelClassName = [
32060
+ styles.label,
32061
+ !isWeb && showNameLabel ? styles.labelH5Wrap : '',
32062
+ isIsolated ? styles.labelIsolated : '',
32063
+ isWeb && showOrderBadge ? styles.labelWithOrder : '',
32064
+ h5OrderWithName ? styles.labelWithOrderH5 : '',
32065
+ h5OrderOnly ? styles.labelOrderOnly : '',
32066
+ isIsolated ? styles.labelIsolatedLayout : '',
32067
+ ]
32068
+ .filter(Boolean)
32069
+ .join(' ');
32070
+ const boundaryIdProps = isIsolated ? {} : { [PARTITION_BOUNDARY_ID_ATTR$1]: item.id };
32071
+ return (jsxRuntime.jsxs("div", { className: labelClassName, style: {
32072
+ left: `${item.leftPct}%`,
32073
+ top: `${item.topPct}%`,
32074
+ transform: `translate(-50%, -50%) rotate(${-rotation}deg)`,
32075
+ }, ...boundaryIdProps, onMouseEnter: isIsolated ? undefined : () => onHoverChange(item.id), onMouseLeave: isIsolated ? undefined : () => onHoverChange(null), onClick: onBoundaryClick && !isIsolated
32076
+ ? (event) => {
32077
+ event.stopPropagation();
32078
+ onBoundaryClick(item.id);
32079
+ }
32080
+ : undefined, children: [showOrderBadge && (jsxRuntime.jsx("div", { className: styles.selectionOrderBadge, style: orderBadgeStyle, children: selectionOrder })), showNameLabel && (jsxRuntime.jsx("div", { className: `${styles.labelPill} ${!isWeb ? styles.labelPillH5 : ''}`, style: pillStyle, children: jsxRuntime.jsx("span", { className: `${styles.labelText} ${!isWeb ? styles.labelTextH5 : ''}`, children: item.name }) })), isIsolated && (jsxRuntime.jsx("div", { className: `${styles.isolatedIconBelow} ${isWeb ? styles.isolatedIconWeb : ''}`, dangerouslySetInnerHTML: { __html: ISOLATED_BOUNDARY_SVG } }))] }, item.id));
32081
+ }) }));
32082
+ };
32083
+
32084
+ const DEFAULT_BACKGROUND = PARTITION_PICKER_GRID_BASE_COLOR;
32085
+ /** 地块 polygon / 标签上的 data 属性,供 viewport 点击命中与表格联动 */
32086
+ const PARTITION_BOUNDARY_ID_ATTR = 'data-partition-boundary-id';
32087
+ /** Map 模式下 OverlayViewF 默认 z-index */
32088
+ const DEFAULT_OVERLAY_Z_INDEX = 100;
32089
+ const MowerPartitionPickerMap = React.forwardRef(({ mapJson, mapConfig, pickerStyleConfig, sn = '', platform = exports.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) => {
32090
+ /**
32091
+ * 是否为 Map 模式(传入 mapRef 时为 true):
32092
+ * - true:Google Maps 底图 + OverlayViewF,平移/缩放/fit 由底图负责
32093
+ * - false:Standalone 固定容器 + 网格 SVG + useMapViewport
32094
+ */
32095
+ const isMapMode = Boolean(mapRef);
32096
+ const reactInstanceId = React.useId();
32097
+ const channelClipPathId = React.useMemo(() => createChannelClipPathId({ sn, reactId: reactInstanceId }), [sn, reactInstanceId]);
32098
+ /** 外部 pickerStyleConfig 与内置 platform 默认值合并后的最终外观(地块/标签/网格等) */
32099
+ const mergedPickerStyles = React.useMemo(() => resolvePartitionPickerStyles(platform, pickerStyleConfig), [pickerStyleConfig, platform]);
32100
+ /** Standalone 容器留白与网格底色;pickerStyleConfig.grid.baseColor 优先于 backgroundColor */
32101
+ const resolvedBackgroundColor = pickerStyleConfig?.grid?.baseColor ?? backgroundColor ?? DEFAULT_BACKGROUND;
32102
+ const mergedMapConfig = React.useMemo(() => merge$1({}, DEFAULT_STYLES, getPartitionPickerMapConfig(platform), mapConfig), [mapConfig, platform]);
32103
+ const svgElementDatas = React.useMemo(() => UnifiedMapDataProcessor.processMapData(mapJson, mergedMapConfig) || {}, [mapJson, mergedMapConfig]);
32104
+ /** 地图 Y 轴相对正北的偏移(度),与 MowerMapRenderer.originNorthRotate 一致 */
32105
+ const originNorthRotate = React.useMemo(() => {
32106
+ if (mapJson.map_north_offset)
32107
+ return (mapJson.map_north_offset * 180) / Math.PI;
32108
+ return 0;
32109
+ }, [mapJson.map_north_offset]);
32110
+ /** Standalone 模式:叠加层实际旋转角(度)= 地图北向偏移 + 外部传入 rotation */
32111
+ const standaloneActureRotate = React.useMemo(() => originNorthRotate + (defaultTransform?.rotation ?? 0), [originNorthRotate, defaultTransform?.rotation]);
32112
+ /**
32113
+ * 稳定的内容 viewBox(地图全量范围 + SVG 裁剪边距)
32114
+ * 供 ChannelClipPath 使用,不随用户平移/缩放变化
32115
+ */
32116
+ const contentViewBox = React.useMemo(() => {
32117
+ const bounds = calculateMapBounds(mapJson);
32118
+ return boundsToContentViewBox(bounds, PARTITION_PICKER_CONTENT_CLIP_PADDING);
32119
+ }, [mapJson]);
32120
+ /** 孤立地块 id 列表(未连接到主地块的子地块) */
32121
+ const isolatedBoundaryIds = useIsolatedBoundaryIds(mapJson);
32122
+ /** 选中地块 id 列表(受控/非受控)与切换选中回调 */
32123
+ const { selectedIds, toggleBoundary, setSelectedIds } = useBoundarySelection({
32124
+ selectedBoundaryIds,
32125
+ defaultSelectedBoundaryIds,
32126
+ onSelectionChange,
32127
+ disabledBoundaryIds: isolatedBoundaryIds,
32128
+ });
32129
+ /** 当前 hover 的地块 id(受控/非受控)与切换 hover 回调 */
32130
+ const { hoveredId, setHoveredId } = useBoundaryHover({
32131
+ hoveredBoundaryId,
32132
+ defaultHoveredBoundaryId,
32133
+ onBoundaryHoverChange,
32134
+ });
32135
+ const handleHoverChange = React.useCallback((id) => {
32136
+ if (id != null && isolatedBoundaryIds.has(id))
32137
+ return;
32138
+ setHoveredId(id);
32139
+ }, [isolatedBoundaryIds, setHoveredId]);
32140
+ /** Map 模式:地块 polygon / 标签直接 onClick(无底图拖拽抢占) */
32141
+ const handleBoundaryClick = React.useCallback((id) => {
32142
+ if (isolatedBoundaryIds.has(id))
32143
+ return;
32144
+ toggleBoundary(id);
32145
+ }, [isolatedBoundaryIds, toggleBoundary]);
32146
+ /** Standalone 模式:由 viewport 统一处理点击(支持在地块上拖拽平移) */
32147
+ const handleViewportTap = React.useCallback((target) => {
32148
+ const boundaryEl = target.closest(`[${PARTITION_BOUNDARY_ID_ATTR}]`);
32149
+ if (!boundaryEl)
32150
+ return;
32151
+ const id = Number(boundaryEl.getAttribute(PARTITION_BOUNDARY_ID_ATTR));
32152
+ if (!Number.isFinite(id) || isolatedBoundaryIds.has(id))
32153
+ return;
32154
+ toggleBoundary(id);
32155
+ }, [isolatedBoundaryIds, toggleBoundary]);
32156
+ /**
32157
+ * Map 模式 overlay 状态(bounds / viewBox / fitBounds / overlayScale)。
32158
+ * 无 mapRef 时 hook 内部 enabled=false,返回值不参与渲染。
32159
+ */
32160
+ const googleOverlay = useGoogleMapsOverlay({
32161
+ mapJson,
32162
+ mapRef,
32163
+ defaultTransform,
32164
+ originNorthRotate,
32165
+ platform,
32166
+ padding,
32167
+ });
32168
+ /** Standalone 才测量容器;Map 模式尺寸由 OverlayViewF onDraw 提供 */
32169
+ const { containerRef, layoutStyle, width: pxWidth, height: pxHeight, isReady, } = useContainerSize(isMapMode ? 0 : width, isMapMode ? 0 : height);
32170
+ React.useEffect(() => {
32171
+ if (!isMapMode && isReady) {
32172
+ onSizeChange?.(pxWidth, pxHeight);
32173
+ }
32174
+ }, [isMapMode, isReady, onSizeChange, pxWidth, pxHeight]);
32175
+ /** Standalone:平移/缩放 viewBox;Map 模式 enablePanZoom 关闭,仅保留 hook 结构 */
32176
+ const { viewBox: standaloneViewBox, gridViewBox, fitViewBox: standaloneFitViewBox, resetView: standaloneResetView, fitToView: standaloneFitToView, } = useMapViewport({
32177
+ mapJson,
32178
+ containerRef,
32179
+ width: pxWidth,
32180
+ height: pxHeight,
32181
+ padding,
32182
+ minZoom,
32183
+ maxZoom,
32184
+ refitOnResize,
32185
+ enablePanZoom: enablePanZoom && !isMapMode,
32186
+ onViewportTap: handleViewportTap,
32187
+ mapRotationDeg: standaloneActureRotate,
32188
+ });
32189
+ /** 按模式选取 viewBox / 尺寸 / 旋转 / 就绪状态(Map 与 Standalone 数据源不同) */
32190
+ const viewBox = isMapMode ? googleOverlay.svgViewBox : standaloneViewBox;
32191
+ const fitViewBox = isMapMode ? googleOverlay.fitViewBox : standaloneFitViewBox;
32192
+ const overlayWidth = isMapMode ? googleOverlay.overlayWidth : pxWidth;
32193
+ const overlayHeight = isMapMode ? googleOverlay.overlayHeight : pxHeight;
32194
+ const actureRotate = isMapMode ? googleOverlay.actureRotate : standaloneActureRotate;
32195
+ const isLayersReady = isMapMode ? googleOverlay.isOverlayReady : isReady;
32196
+ /**
32197
+ * Standalone:标签/充电桩置于 mapOverlayLayer,CSS rotate 与 MowerMapRenderer 一致。
32198
+ * Map 模式:旋转由 OverlayViewF 承担,此处不需要额外包裹层。
32199
+ */
32200
+ const mapOverlayLayerStyle = React.useMemo(() => !isMapMode && actureRotate !== 0 ? { transform: `rotate(${actureRotate}deg)` } : undefined, [actureRotate, isMapMode]);
32201
+ /**
32202
+ * 分区名定位:
32203
+ * - Map 模式 overlay:相对 overlay 宽高百分比(与 BoundaryLabels 一致)
32204
+ * - Standalone meet:按 preserveAspectRatio 换算容器百分比
32205
+ */
32206
+ const labelItems = usePartitionLabelItems(mapJson, viewBox, overlayWidth, overlayHeight, {
32207
+ positionMode: isMapMode ? 'overlay' : 'meet',
32208
+ });
32209
+ /**
32210
+ * 复用 ChannelClipPath / ChannelElement / CharginPile 所需的最小 Context
32211
+ * - svgViewBox:稳定 contentViewBox(见上)
32212
+ * - channelClipPathId:同页多实例时避免 SVG id 冲突
32213
+ * - platform:供子组件读取 Web / H5 差异
32214
+ * - Map 模式额外注入 mapRef / bounds / overlayScale(通道线宽等)
32215
+ */
32216
+ const commonValue = React.useMemo(() => ({
32217
+ sn,
32218
+ platform,
32219
+ svgViewBox: contentViewBox,
32220
+ channelClipPathId,
32221
+ ...(isMapMode && mapRef
32222
+ ? {
32223
+ mapRef,
32224
+ bounds: googleOverlay.bounds,
32225
+ overlayScale: googleOverlay.overlayScale,
32226
+ }
32227
+ : {}),
32228
+ }), [
32229
+ sn,
32230
+ platform,
32231
+ contentViewBox,
32232
+ channelClipPathId,
32233
+ isMapMode,
32234
+ mapRef,
32235
+ googleOverlay.bounds,
32236
+ googleOverlay.overlayScale,
32237
+ ]);
32238
+ const svgEditValue = React.useMemo(() => ({
32239
+ svgElementDatas,
32240
+ }), [svgElementDatas]);
32241
+ React.useImperativeHandle(ref, () => ({
32242
+ fitToView: isMapMode ? googleOverlay.fitToView : standaloneFitToView,
32243
+ resetView: isMapMode ? googleOverlay.resetView : standaloneResetView,
32244
+ getSelectedBoundaryIds: () => selectedIds,
32245
+ setSelectedBoundaryIds: setSelectedIds,
32246
+ getHoveredBoundaryId: () => hoveredId,
32247
+ setHoveredBoundaryId: setHoveredId,
32248
+ }), [
32249
+ googleOverlay.fitToView,
32250
+ googleOverlay.resetView,
32251
+ hoveredId,
32252
+ isMapMode,
32253
+ selectedIds,
32254
+ setHoveredId,
32255
+ setSelectedIds,
32256
+ standaloneFitToView,
32257
+ standaloneResetView,
32258
+ ]);
32259
+ const containerStyle = React.useMemo(() => ({
32260
+ backgroundColor: isMapMode ? 'transparent' : resolvedBackgroundColor,
32261
+ ...style,
32262
+ ...(isMapMode ? {} : layoutStyle),
32263
+ }), [isMapMode, layoutStyle, resolvedBackgroundColor, style]);
32264
+ /** 地块 + 通道 SVG;Map 模式不在 SVG 内旋转(由 OverlayViewF rotate 处理) */
32265
+ const mapSvgLayer = viewBox && isLayersReady ? (jsxRuntime.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;
32266
+ /**
32267
+ * 分区标签 + 充电桩 HTML 层。
32268
+ * Map 模式:平铺于 overlay 根节点;Standalone:包在 mapOverlayLayer 内做 CSS 旋转。
32269
+ */
32270
+ const overlayLayers = viewBox && isLayersReady ? (isMapMode ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(PartitionNameLabels, { items: labelItems, selectedIds: selectedIds, labelStyles: mergedPickerStyles.labels, labelMinScreenWidthPx: mergedPickerStyles.labelMinScreenWidthPx, platform: platform, showSelectionOrder: showSelectionOrder, rotation: actureRotate, onHoverChange: handleHoverChange, onBoundaryClick: handleBoundaryClick }), jsxRuntime.jsx(CharginPile, { viewBox: viewBox, rotation: actureRotate })] })) : (jsxRuntime.jsxs("div", { className: styles.mapOverlayLayer, style: mapOverlayLayerStyle, children: [jsxRuntime.jsx(PartitionNameLabels, { items: labelItems, selectedIds: selectedIds, labelStyles: mergedPickerStyles.labels, labelMinScreenWidthPx: mergedPickerStyles.labelMinScreenWidthPx, platform: platform, showSelectionOrder: showSelectionOrder, rotation: actureRotate, onHoverChange: handleHoverChange }), jsxRuntime.jsx(CharginPile, { viewBox: viewBox, rotation: actureRotate, viewRotationMode: "overlay-parent", containerWidth: overlayWidth, containerHeight: overlayHeight })] }))) : null;
32271
+ /** Map 模式:渲染 OverlayViewF,底图为 Google Maps */
32272
+ if (isMapMode && mapRef) {
32273
+ return (jsxRuntime.jsx(CommonContextProvider, { value: commonValue, children: jsxRuntime.jsx(SvgEditContextProvider, { value: svgEditValue, children: jsxRuntime.jsx(OverlayViewF, { map: mapRef, mapPaneName: "overlayMouseTarget", bounds: googleOverlay.bounds ?? undefined, rotate: actureRotate, zIndex: zIndex, onDraw: googleOverlay.handleOverlayDraw, children: jsxRuntime.jsxs("div", { className: `${styles.googleOverlayRoot} ${className || ''}`, style: containerStyle, onMouseLeave: () => setHoveredId(null), children: [mapSvgLayer, overlayLayers] }) }) }) }));
32274
+ }
32275
+ /** Standalone 模式:固定容器 + 网格底图 + 双层 SVG / HTML overlay */
32276
+ return (jsxRuntime.jsx(CommonContextProvider, { value: commonValue, children: jsxRuntime.jsx(SvgEditContextProvider, { value: svgEditValue, children: jsxRuntime.jsx("div", { ref: containerRef, className: `${styles.container} ${className || ''}`, "data-viewport-background": "true", style: containerStyle, onMouseLeave: () => setHoveredId(null), children: isLayersReady && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: styles.mapSvgStack, children: [jsxRuntime.jsx(PartitionPickerGridSvg, { viewBox: gridViewBox, gridStyle: mergedPickerStyles.grid }), mapSvgLayer] }), overlayLayers] })) }) }) }));
32277
+ });
32278
+ MowerPartitionPickerMap.displayName = 'MowerPartitionPickerMap';
32279
+
30169
32280
  exports.ALL_DIRECTION_SELECTED = ALL_DIRECTION_SELECTED;
30170
32281
  exports.AntennaData = AntennaData;
30171
32282
  exports.BaseData = BaseData;
@@ -30173,12 +32284,18 @@ exports.BoundaryData = BoundaryData;
30173
32284
  exports.BoundarySvgRender = BoundarySvgRender;
30174
32285
  exports.ChannelData = ChannelData;
30175
32286
  exports.ChargingPileData = ChargingPileData;
32287
+ exports.DEFAULT_SCREEN_PADDING = DEFAULT_SCREEN_PADDING;
30176
32288
  exports.DoodleData = DoodleData;
30177
32289
  exports.MAX_DIRECTION_ANGLE = MAX_DIRECTION_ANGLE;
30178
32290
  exports.MIN_DIRECTION_ANGLE = MIN_DIRECTION_ANGLE;
30179
32291
  exports.MowGateData = MowGateData;
30180
32292
  exports.MowerMapRenderer = MowerMapRenderer;
32293
+ exports.MowerPartitionPickerMap = MowerPartitionPickerMap;
30181
32294
  exports.ObstacleData = ObstacleData;
32295
+ exports.PARTITION_PICKER_COLORS_H5 = PARTITION_PICKER_COLORS_H5;
32296
+ exports.PARTITION_PICKER_COLORS_WEB = PARTITION_PICKER_COLORS_WEB;
32297
+ exports.PARTITION_PICKER_LABEL_STYLES_H5 = PARTITION_PICKER_LABEL_STYLES_H5;
32298
+ exports.PARTITION_PICKER_LABEL_STYLES_WEB = PARTITION_PICKER_LABEL_STYLES_WEB;
30182
32299
  exports.PathData = PathData;
30183
32300
  exports.SCALE_FACTOR = SCALE_FACTOR;
30184
32301
  exports.SvgParserNative = SvgParserNative;
@@ -30186,10 +32303,17 @@ exports.UnifiedMapDataProcessor = UnifiedMapDataProcessor;
30186
32303
  exports.VisionOffData = VisionOffData;
30187
32304
  exports.calculateMapGpsCenter = calculateMapGpsCenter;
30188
32305
  exports.estimateGpsFromMapBounds = estimateGpsFromMapBounds;
32306
+ exports.getIsolatedBoundaryIds = getIsolatedBoundaryIds;
32307
+ exports.getPartitionPickerColors = getPartitionPickerColors;
32308
+ exports.getPartitionPickerLabelStyles = getPartitionPickerLabelStyles;
30189
32309
  exports.getValidGpsBounds = getValidGpsBounds;
30190
32310
  exports.initBoundary = initBoundary;
30191
32311
  exports.initChannel = initChannel;
30192
32312
  exports.initDoodle = initDoodle;
30193
32313
  exports.initObstacle = initObstacle;
30194
32314
  exports.initVisionOff = initVisionOff;
32315
+ exports.normalizeScreenPadding = normalizeScreenPadding;
32316
+ exports.resolvePartitionPickerStyles = resolvePartitionPickerStyles;
30195
32317
  exports.restorePointsFormat = restorePointsFormat;
32318
+ exports.signGoogleStaticMapUrl = signGoogleStaticMapUrl;
32319
+ exports.signGoogleStaticMapUrlSync = signGoogleStaticMapUrlSync;