@fleet-frontend/mower-maps 0.2.5-beta.9 → 0.2.6-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/constants.d.ts +3 -0
- package/dist/config/constants.d.ts.map +1 -1
- package/dist/config/styles.d.ts.map +1 -1
- package/dist/context/common.d.ts +17 -0
- package/dist/context/common.d.ts.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +2660 -542
- package/dist/index.js +2671 -540
- package/dist/render/BoundarySvgRender.d.ts +3 -1
- package/dist/render/BoundarySvgRender.d.ts.map +1 -1
- package/dist/render/MowerMapRenderer.d.ts.map +1 -1
- package/dist/render/MowerPartitionPickerMap.d.ts +17 -0
- package/dist/render/MowerPartitionPickerMap.d.ts.map +1 -0
- package/dist/render/antennas/useAntennaInfo.d.ts.map +1 -1
- package/dist/render/boundaryLabels/useBoundaryLabels.d.ts.map +1 -1
- package/dist/render/charginPile/index.d.ts +11 -1
- package/dist/render/charginPile/index.d.ts.map +1 -1
- package/dist/render/charginPile/useChargingPile.d.ts +11 -1
- package/dist/render/charginPile/useChargingPile.d.ts.map +1 -1
- package/dist/render/partitionPicker/constants.d.ts +169 -0
- package/dist/render/partitionPicker/constants.d.ts.map +1 -0
- package/dist/render/partitionPicker/hooks/useBoundaryHover.d.ts +18 -0
- package/dist/render/partitionPicker/hooks/useBoundaryHover.d.ts.map +1 -0
- package/dist/render/partitionPicker/hooks/useBoundarySelection.d.ts +20 -0
- package/dist/render/partitionPicker/hooks/useBoundarySelection.d.ts.map +1 -0
- package/dist/render/partitionPicker/hooks/useContainerSize.d.ts +16 -0
- package/dist/render/partitionPicker/hooks/useContainerSize.d.ts.map +1 -0
- package/dist/render/partitionPicker/hooks/useGoogleMapsOverlay.d.ts +31 -0
- package/dist/render/partitionPicker/hooks/useGoogleMapsOverlay.d.ts.map +1 -0
- package/dist/render/partitionPicker/hooks/useIsolatedBoundaryIds.d.ts +6 -0
- package/dist/render/partitionPicker/hooks/useIsolatedBoundaryIds.d.ts.map +1 -0
- package/dist/render/partitionPicker/hooks/useMapViewport.d.ts +62 -0
- package/dist/render/partitionPicker/hooks/useMapViewport.d.ts.map +1 -0
- package/dist/render/partitionPicker/hooks/usePartitionLabelItems.d.ts +22 -0
- package/dist/render/partitionPicker/hooks/usePartitionLabelItems.d.ts.map +1 -0
- package/dist/render/partitionPicker/index.d.ts +9 -0
- package/dist/render/partitionPicker/index.d.ts.map +1 -0
- package/dist/render/partitionPicker/layers/MapGridBackground.d.ts +22 -0
- package/dist/render/partitionPicker/layers/MapGridBackground.d.ts.map +1 -0
- package/dist/render/partitionPicker/layers/PartitionNameLabels.d.ts +21 -0
- package/dist/render/partitionPicker/layers/PartitionNameLabels.d.ts.map +1 -0
- package/dist/render/partitionPicker/layers/PartitionPickerGridSvg.d.ts +10 -0
- package/dist/render/partitionPicker/layers/PartitionPickerGridSvg.d.ts.map +1 -0
- package/dist/render/partitionPicker/layers/PartitionPickerSvg.d.ts +24 -0
- package/dist/render/partitionPicker/layers/PartitionPickerSvg.d.ts.map +1 -0
- package/dist/render/partitionPicker/layers/SelectableBoundaryElement.d.ts +34 -0
- package/dist/render/partitionPicker/layers/SelectableBoundaryElement.d.ts.map +1 -0
- package/dist/render/partitionPicker/types.d.ts +163 -0
- package/dist/render/partitionPicker/types.d.ts.map +1 -0
- package/dist/render/partitionPicker/viewportUtils.d.ts +100 -0
- package/dist/render/partitionPicker/viewportUtils.d.ts.map +1 -0
- package/dist/render/svgElement/ChannelClipPath/constants.d.ts +9 -0
- package/dist/render/svgElement/ChannelClipPath/constants.d.ts.map +1 -0
- package/dist/render/svgElement/ChannelElement/index.d.ts.map +1 -1
- package/dist/render/svgElement/ObstacleElement/index.d.ts.map +1 -1
- package/dist/render/svgElement/PathElement/index.d.ts.map +1 -1
- package/dist/render/svgElement/PolygonELement/components/Magnifier/index.d.ts.map +1 -1
- package/dist/render/svgElement/PolygonELement/components/Tooltip.d.ts.map +1 -1
- package/dist/render/svgElement/PolygonELement/index.d.ts.map +1 -1
- package/dist/render/svgElement/TransformWrapper/DoodleTransformWrapper/DoodleTransformWrapper.d.ts.map +1 -1
- package/dist/render/svgElement/TransformWrapper/VisionOffTransformWrapper/VisionOffTransformWrapper.d.ts.map +1 -1
- package/dist/types/renderer.d.ts +21 -0
- package/dist/types/renderer.d.ts.map +1 -1
- package/dist/utils/dateTimeUtils.d.ts +6 -2
- package/dist/utils/dateTimeUtils.d.ts.map +1 -1
- package/dist/utils/googleStaticMapSign.d.ts +17 -0
- package/dist/utils/googleStaticMapSign.d.ts.map +1 -0
- package/dist/utils/mapBounds.d.ts.map +1 -1
- package/dist/utils/pathData.d.ts +2 -0
- package/dist/utils/pathData.d.ts.map +1 -0
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
2
|
import * as React from 'react';
|
|
3
|
-
import React__default, { memo, useMemo, useEffect, useState, useRef, useCallback, createContext, useContext, useLayoutEffect, forwardRef, useImperativeHandle } from 'react';
|
|
3
|
+
import React__default, { memo, useMemo, useEffect, useState, useRef, useCallback, createContext, useContext, useLayoutEffect, Fragment as Fragment$1, forwardRef, useImperativeHandle, useId } from 'react';
|
|
4
4
|
import ReactDOM, { createPortal } from 'react-dom';
|
|
5
5
|
|
|
6
6
|
function globals (defs) {
|
|
@@ -2089,7 +2089,7 @@ var ellipsoids = {
|
|
|
2089
2089
|
}
|
|
2090
2090
|
};
|
|
2091
2091
|
|
|
2092
|
-
const WGS84$
|
|
2092
|
+
const WGS84$2 = ellipsoids.WGS84; // default ellipsoid
|
|
2093
2093
|
|
|
2094
2094
|
function eccentricity(a, b, rf, R_A) {
|
|
2095
2095
|
var a2 = a * a; // used in geocentric
|
|
@@ -2114,7 +2114,7 @@ function sphere(a, b, rf, ellps, sphere) {
|
|
|
2114
2114
|
if (!a) { // do we have an ellipsoid?
|
|
2115
2115
|
var ellipse = match(ellipsoids, ellps);
|
|
2116
2116
|
if (!ellipse) {
|
|
2117
|
-
ellipse = WGS84$
|
|
2117
|
+
ellipse = WGS84$2;
|
|
2118
2118
|
}
|
|
2119
2119
|
a = ellipse.a;
|
|
2120
2120
|
b = ellipse.b;
|
|
@@ -10613,9 +10613,16 @@ var SvgMapOpacity;
|
|
|
10613
10613
|
* 地图渲染相关常量配置
|
|
10614
10614
|
*/
|
|
10615
10615
|
// 默认坐标
|
|
10616
|
+
// export const DEFAULT_COORDINATES = {
|
|
10617
|
+
// sw: [116.071044, 40.102099],
|
|
10618
|
+
// ne: [116.0734, 40.103901],
|
|
10619
|
+
// };
|
|
10620
|
+
/** 无效 GPS 时的兜底中心点 [lng, lat] */
|
|
10621
|
+
const DEFAULT_GPS_CENTER = [0, 0];
|
|
10622
|
+
/** 无 map_width/map_height 时的默认 GPS 边界 [lng, lat](中心约 (0,0)) */
|
|
10616
10623
|
const DEFAULT_COORDINATES = {
|
|
10617
|
-
sw: [
|
|
10618
|
-
ne: [
|
|
10624
|
+
sw: [-225e-6, -225e-6],
|
|
10625
|
+
ne: [0.000225, 0.000225],
|
|
10619
10626
|
};
|
|
10620
10627
|
/**
|
|
10621
10628
|
* 缩放因子 - 将米转换为像素
|
|
@@ -13915,6 +13922,13 @@ var MapType;
|
|
|
13915
13922
|
*/
|
|
13916
13923
|
MapType["GEO_FENCE"] = "geo-fence";
|
|
13917
13924
|
})(MapType || (MapType = {}));
|
|
13925
|
+
var HoverType;
|
|
13926
|
+
(function (HoverType) {
|
|
13927
|
+
HoverType["Translate"] = "translate";
|
|
13928
|
+
HoverType["Scale"] = "scale";
|
|
13929
|
+
HoverType["Rotate"] = "rotate";
|
|
13930
|
+
HoverType["Delete"] = "delete";
|
|
13931
|
+
})(HoverType || (HoverType = {}));
|
|
13918
13932
|
|
|
13919
13933
|
var RealTimeDataType;
|
|
13920
13934
|
(function (RealTimeDataType) {
|
|
@@ -18066,6 +18080,8 @@ const OBSTACLE_STYLES = {
|
|
|
18066
18080
|
hoverFillColor: 'rgba(255, 90, 0, 0.4)',
|
|
18067
18081
|
lineWidth: DEFAULT_LINE_WIDTHS.OBSTACLE,
|
|
18068
18082
|
opacity: DEFAULT_OPACITIES.FULL,
|
|
18083
|
+
disabledLineColor: 'rgba(255, 90, 0, 0.4)',
|
|
18084
|
+
disabledFillColor: 'rgba(255, 90, 0, 0.1)',
|
|
18069
18085
|
};
|
|
18070
18086
|
const CHARGING_PILE_STYLES = {
|
|
18071
18087
|
lineColor: 'blue',
|
|
@@ -18547,6 +18563,38 @@ function calculateBoundaryBoundsCenter(mapData) {
|
|
|
18547
18563
|
boundaryBounds.minY + (boundaryBounds.maxY - boundaryBounds.minY) / 2,
|
|
18548
18564
|
];
|
|
18549
18565
|
}
|
|
18566
|
+
/**
|
|
18567
|
+
* 由中心点与物理尺寸(米)计算 GPS 边界 [lng, lat]
|
|
18568
|
+
*/
|
|
18569
|
+
function computeGpsBoundsFromCenterAndSize(centerLng, centerLat, mapWidthMeters, mapHeightMeters) {
|
|
18570
|
+
const METERS_PER_DEGREE_LAT = EQUATORIAL_CIRCUMFERENCE / 360;
|
|
18571
|
+
const METERS_PER_DEGREE_LNG = METERS_PER_DEGREE_LAT * Math.cos((centerLat * Math.PI) / 180);
|
|
18572
|
+
const swLat = centerLat - mapHeightMeters / 2 / METERS_PER_DEGREE_LAT;
|
|
18573
|
+
const swLng = centerLng - mapWidthMeters / 2 / METERS_PER_DEGREE_LNG;
|
|
18574
|
+
const neLat = centerLat + mapHeightMeters / 2 / METERS_PER_DEGREE_LAT;
|
|
18575
|
+
const neLng = centerLng + mapWidthMeters / 2 / METERS_PER_DEGREE_LNG;
|
|
18576
|
+
return {
|
|
18577
|
+
sw: [swLng, swLat],
|
|
18578
|
+
ne: [neLng, neLat],
|
|
18579
|
+
};
|
|
18580
|
+
}
|
|
18581
|
+
/**
|
|
18582
|
+
* GPS 无效时:中心 (0,0) + JSON 的 map_width/map_height;无尺寸则用 DEFAULT_COORDINATES
|
|
18583
|
+
*/
|
|
18584
|
+
function getFallbackGpsBounds(mapData) {
|
|
18585
|
+
const [centerLng, centerLat] = DEFAULT_GPS_CENTER;
|
|
18586
|
+
const hasMapSize = typeof mapData.map_width === 'number' &&
|
|
18587
|
+
typeof mapData.map_height === 'number' &&
|
|
18588
|
+
mapData.map_width > 0 &&
|
|
18589
|
+
mapData.map_height > 0;
|
|
18590
|
+
if (hasMapSize) {
|
|
18591
|
+
return computeGpsBoundsFromCenterAndSize(centerLng, centerLat, mapData.map_width, mapData.map_height);
|
|
18592
|
+
}
|
|
18593
|
+
return {
|
|
18594
|
+
sw: [...DEFAULT_COORDINATES.sw],
|
|
18595
|
+
ne: [...DEFAULT_COORDINATES.ne],
|
|
18596
|
+
};
|
|
18597
|
+
}
|
|
18550
18598
|
/**
|
|
18551
18599
|
* 检查GPS坐标是否有效
|
|
18552
18600
|
*/
|
|
@@ -18582,10 +18630,10 @@ function estimateGpsFromMapBounds(mapData) {
|
|
|
18582
18630
|
const mapWidthMeters = maxXMeters - minXMeters;
|
|
18583
18631
|
const mapHeightMeters = maxYMeters - minYMeters;
|
|
18584
18632
|
// 凤凰岭的GPS坐标作为地图中心点
|
|
18585
|
-
const centerLat = 40.103;
|
|
18586
|
-
const centerLng = 116.072222;
|
|
18587
|
-
|
|
18588
|
-
|
|
18633
|
+
// const centerLat = 40.103;
|
|
18634
|
+
// const centerLng = 116.072222;
|
|
18635
|
+
const centerLat = 0;
|
|
18636
|
+
const centerLng = 0;
|
|
18589
18637
|
// 精确的坐标转换常数
|
|
18590
18638
|
// 1度纬度 = 约111,320米(在地球上任何地方都基本相同)
|
|
18591
18639
|
// 1度经度 = 约111,320 * cos(纬度) 米(随纬度变化)
|
|
@@ -18651,39 +18699,44 @@ function calculateMapGpsCenter(mapData) {
|
|
|
18651
18699
|
}
|
|
18652
18700
|
}
|
|
18653
18701
|
// 如果有西南角和东北角坐标,检查其有效性并计算中心点
|
|
18654
|
-
if (
|
|
18655
|
-
|
|
18656
|
-
|
|
18657
|
-
|
|
18658
|
-
|
|
18659
|
-
|
|
18660
|
-
|
|
18661
|
-
|
|
18662
|
-
|
|
18663
|
-
|
|
18664
|
-
|
|
18665
|
-
|
|
18666
|
-
|
|
18667
|
-
|
|
18668
|
-
|
|
18669
|
-
|
|
18670
|
-
|
|
18671
|
-
|
|
18672
|
-
|
|
18673
|
-
|
|
18674
|
-
|
|
18675
|
-
}
|
|
18702
|
+
// if (
|
|
18703
|
+
// mapData.sw_gps &&
|
|
18704
|
+
// mapData.ne_gps &&
|
|
18705
|
+
// mapData.sw_gps.length >= 2 &&
|
|
18706
|
+
// mapData.ne_gps.length >= 2
|
|
18707
|
+
// ) {
|
|
18708
|
+
// const swLat = mapData.sw_gps[1];
|
|
18709
|
+
// const swLng = mapData.sw_gps[0];
|
|
18710
|
+
// const neLat = mapData.ne_gps[1];
|
|
18711
|
+
// const neLng = mapData.ne_gps[0];
|
|
18712
|
+
// // 检查边界坐标的有效性
|
|
18713
|
+
// if (isValidGpsCoordinate(swLat, swLng) && isValidGpsCoordinate(neLat, neLng)) {
|
|
18714
|
+
// return {
|
|
18715
|
+
// lat: (swLat + neLat) / 2,
|
|
18716
|
+
// lng: (swLng + neLng) / 2,
|
|
18717
|
+
// };
|
|
18718
|
+
// } else {
|
|
18719
|
+
// console.warn('sw_gps或ne_gps坐标无效:', {
|
|
18720
|
+
// sw: { lat: swLat, lng: swLng },
|
|
18721
|
+
// ne: { lat: neLat, lng: neLng },
|
|
18722
|
+
// });
|
|
18723
|
+
// }
|
|
18724
|
+
// }
|
|
18676
18725
|
// 尝试从地图几何边界估算GPS坐标
|
|
18677
|
-
const estimatedBounds = estimateGpsFromMapBounds(mapData);
|
|
18678
|
-
if (estimatedBounds) {
|
|
18679
|
-
|
|
18680
|
-
|
|
18681
|
-
|
|
18682
|
-
|
|
18683
|
-
}
|
|
18726
|
+
// const estimatedBounds = estimateGpsFromMapBounds(mapData);
|
|
18727
|
+
// if (estimatedBounds) {
|
|
18728
|
+
// // 从估算的边界计算中心点
|
|
18729
|
+
// const centerLat = (estimatedBounds.sw[1] + estimatedBounds.ne[1]) / 2;
|
|
18730
|
+
// const centerLng = (estimatedBounds.sw[0] + estimatedBounds.ne[0]) / 2;
|
|
18731
|
+
// return { lat: centerLat, lng: centerLng };
|
|
18732
|
+
// }
|
|
18733
|
+
// return {
|
|
18734
|
+
// lat: 39.9042, // 北京纬度
|
|
18735
|
+
// lng: 116.4074, // 北京经度
|
|
18736
|
+
// };
|
|
18684
18737
|
return {
|
|
18685
|
-
lat:
|
|
18686
|
-
lng:
|
|
18738
|
+
lat: DEFAULT_GPS_CENTER[1],
|
|
18739
|
+
lng: DEFAULT_GPS_CENTER[0],
|
|
18687
18740
|
};
|
|
18688
18741
|
}
|
|
18689
18742
|
// 旋转坐标点
|
|
@@ -18713,50 +18766,22 @@ const getValidGpsBounds = (mapData, rotation = 0) => {
|
|
|
18713
18766
|
mapData.map_width > 0 &&
|
|
18714
18767
|
mapData.map_height > 0) {
|
|
18715
18768
|
const [centerLng, centerLat] = mapData.center_gps;
|
|
18716
|
-
|
|
18717
|
-
|
|
18718
|
-
|
|
18719
|
-
|
|
18720
|
-
|
|
18721
|
-
|
|
18722
|
-
|
|
18723
|
-
|
|
18724
|
-
|
|
18725
|
-
|
|
18726
|
-
|
|
18727
|
-
|
|
18728
|
-
|
|
18729
|
-
const neLat = centerLat + mapHeightMeters / 2 / METERS_PER_DEGREE_LAT;
|
|
18730
|
-
const neLng = centerLng + mapWidthMeters / 2 / METERS_PER_DEGREE_LNG;
|
|
18731
|
-
bounds = {
|
|
18732
|
-
sw: [swLng, swLat],
|
|
18733
|
-
ne: [neLng, neLat],
|
|
18734
|
-
};
|
|
18735
|
-
}
|
|
18736
|
-
else if (isValidGpsCoordinate(mapData.sw_gps?.[1], mapData.sw_gps?.[0]) &&
|
|
18737
|
-
isValidGpsCoordinate(mapData.ne_gps?.[1], mapData.ne_gps?.[0])) {
|
|
18738
|
-
// 降级方案:使用地图数据中的GPS坐标
|
|
18739
|
-
console.warn('center_gps 或 map_width/map_height 不可用,使用 sw_gps 和 ne_gps');
|
|
18740
|
-
bounds = {
|
|
18741
|
-
sw: mapData.sw_gps,
|
|
18742
|
-
ne: mapData.ne_gps,
|
|
18743
|
-
};
|
|
18744
|
-
}
|
|
18769
|
+
bounds = computeGpsBoundsFromCenterAndSize(centerLng, centerLat, mapData.map_width, mapData.map_height);
|
|
18770
|
+
}
|
|
18771
|
+
// else if (
|
|
18772
|
+
// isValidGpsCoordinate(mapData.sw_gps?.[1], mapData.sw_gps?.[0]) &&
|
|
18773
|
+
// isValidGpsCoordinate(mapData.ne_gps?.[1], mapData.ne_gps?.[0])
|
|
18774
|
+
// ) {
|
|
18775
|
+
// // 降级方案:使用地图数据中的GPS坐标
|
|
18776
|
+
// console.warn('center_gps 或 map_width/map_height 不可用,使用 sw_gps 和 ne_gps');
|
|
18777
|
+
// bounds = {
|
|
18778
|
+
// sw: mapData.sw_gps,
|
|
18779
|
+
// ne: mapData.ne_gps,
|
|
18780
|
+
// };
|
|
18781
|
+
// }
|
|
18745
18782
|
else {
|
|
18746
|
-
|
|
18747
|
-
|
|
18748
|
-
if (sw && ne) {
|
|
18749
|
-
console.warn('GPS坐标无效,使用地图几何数据估算边界:', sw, ne);
|
|
18750
|
-
bounds = {
|
|
18751
|
-
sw: [sw[0], sw[1]],
|
|
18752
|
-
ne: [ne[0], ne[1]],
|
|
18753
|
-
};
|
|
18754
|
-
}
|
|
18755
|
-
else {
|
|
18756
|
-
// 最后的fallback:使用默认坐标
|
|
18757
|
-
console.warn('无法获取有效的GPS边界,使用默认坐标');
|
|
18758
|
-
bounds = DEFAULT_COORDINATES;
|
|
18759
|
-
}
|
|
18783
|
+
console.warn('无法获取有效的GPS边界,使用 (0,0) 中心 + map_width/map_height 或 DEFAULT_COORDINATES');
|
|
18784
|
+
bounds = getFallbackGpsBounds(mapData);
|
|
18760
18785
|
}
|
|
18761
18786
|
// 如果有旋转角度,计算旋转后的边界
|
|
18762
18787
|
if (rotation !== 0) {
|
|
@@ -19189,7 +19214,7 @@ function getMowerImage(positonConfig, mowerIconConfig, modelType, hasEdger) {
|
|
|
19189
19214
|
const mowerImage = mowerIconConfig?.mapImgUrl || getMowerImageByModal(model, hasEdger);
|
|
19190
19215
|
const disabledImage = mowerIconConfig?.mapDisabledUrl || getDisabledMowerImageByModal(model);
|
|
19191
19216
|
const noPositionImage = mowerIconConfig?.mapNoPositionUrl || getNoPositionMowerImageByModal(model);
|
|
19192
|
-
const positonOutOfRange = isOutOfRange(positonConfig);
|
|
19217
|
+
// const positonOutOfRange = isOutOfRange(positonConfig);
|
|
19193
19218
|
const positionValid = isInvalidPosition(positonConfig);
|
|
19194
19219
|
const isOffLine = state === RobotStatus.DISCONNECTED;
|
|
19195
19220
|
const isInPark = state === RobotStatus.CHARGING || state === RobotStatus.PARKED;
|
|
@@ -19199,7 +19224,7 @@ function getMowerImage(positonConfig, mowerIconConfig, modelType, hasEdger) {
|
|
|
19199
19224
|
if (isInPark) {
|
|
19200
19225
|
return mowerImage;
|
|
19201
19226
|
}
|
|
19202
|
-
if (positionValid
|
|
19227
|
+
if (positionValid) {
|
|
19203
19228
|
return noPositionImage;
|
|
19204
19229
|
}
|
|
19205
19230
|
else {
|
|
@@ -19223,12 +19248,6 @@ function isInvalidPosition(positonConfig) {
|
|
|
19223
19248
|
isNoPosture(positonConfig.postureY) &&
|
|
19224
19249
|
isNoPosture(positonConfig.postureTheta));
|
|
19225
19250
|
}
|
|
19226
|
-
function isOutOfRange(positonConfig) {
|
|
19227
|
-
return (positonConfig.postureX != null &&
|
|
19228
|
-
Math.abs(positonConfig.postureX) > 1000 &&
|
|
19229
|
-
positonConfig.postureY != null &&
|
|
19230
|
-
Math.abs(positonConfig.postureY) > 1000);
|
|
19231
|
-
}
|
|
19232
19251
|
function getMindistanceByModel(modelType) {
|
|
19233
19252
|
if (isIModel(modelType)) {
|
|
19234
19253
|
return I_MIN_DISTANCE;
|
|
@@ -19525,8 +19544,16 @@ function formatTime(date) {
|
|
|
19525
19544
|
* @param date 目标日期
|
|
19526
19545
|
* @returns 星期几的英文缩写
|
|
19527
19546
|
*/
|
|
19528
|
-
function getWeekdayAbbr(date) {
|
|
19529
|
-
const weekdays = [
|
|
19547
|
+
function getWeekdayAbbr(date, locale) {
|
|
19548
|
+
const weekdays = [
|
|
19549
|
+
locale?.['map.renderer.weekday.sun'] || 'Sun',
|
|
19550
|
+
locale?.['map.renderer.weekday.mon'] || 'Mon',
|
|
19551
|
+
locale?.['map.renderer.weekday.tue'] || 'Tue',
|
|
19552
|
+
locale?.['map.renderer.weekday.wed'] || 'Wed',
|
|
19553
|
+
locale?.['map.renderer.weekday.thu'] || 'Thu',
|
|
19554
|
+
locale?.['map.renderer.weekday.fri'] || 'Fri',
|
|
19555
|
+
locale?.['map.renderer.weekday.sat'] || 'Sat'
|
|
19556
|
+
];
|
|
19530
19557
|
return weekdays[date.getDay()];
|
|
19531
19558
|
}
|
|
19532
19559
|
/**
|
|
@@ -19540,7 +19567,7 @@ function getWeekdayAbbr(date) {
|
|
|
19540
19567
|
* @param timestamp 时间戳(秒)
|
|
19541
19568
|
* @returns 格式化后的日期文本
|
|
19542
19569
|
*/
|
|
19543
|
-
function formatBoundaryDateText(timestamp) {
|
|
19570
|
+
function formatBoundaryDateText(timestamp, locale) {
|
|
19544
19571
|
if (!timestamp || timestamp <= 0) {
|
|
19545
19572
|
return '-';
|
|
19546
19573
|
}
|
|
@@ -19548,15 +19575,15 @@ function formatBoundaryDateText(timestamp) {
|
|
|
19548
19575
|
const timeStr = formatTime(date);
|
|
19549
19576
|
// 判断是否为今天
|
|
19550
19577
|
if (isToday(date)) {
|
|
19551
|
-
return
|
|
19578
|
+
return `${locale?.['map.renderer.today'] || 'Today'} ${timeStr}`;
|
|
19552
19579
|
}
|
|
19553
19580
|
// 判断是否为昨天
|
|
19554
19581
|
if (isYesterday(date)) {
|
|
19555
|
-
return
|
|
19582
|
+
return `${locale?.['map.renderer.yesterday'] || 'Yesterday'} ${timeStr}`;
|
|
19556
19583
|
}
|
|
19557
19584
|
// 判断是否为本周内
|
|
19558
19585
|
if (isThisWeek(date)) {
|
|
19559
|
-
const weekdayAbbr = getWeekdayAbbr(date);
|
|
19586
|
+
const weekdayAbbr = getWeekdayAbbr(date, locale);
|
|
19560
19587
|
return `${weekdayAbbr} ${timeStr}`;
|
|
19561
19588
|
}
|
|
19562
19589
|
// 其他情况显示完整日期
|
|
@@ -20048,12 +20075,12 @@ function styleInject(css, ref) {
|
|
|
20048
20075
|
}
|
|
20049
20076
|
}
|
|
20050
20077
|
|
|
20051
|
-
var css_248z$
|
|
20052
|
-
var styles$
|
|
20053
|
-
styleInject(css_248z$
|
|
20078
|
+
var css_248z$9 = ".index-module_edit__-5VvX {\n position: absolute;\n top: 0px;\n left: 0px;\n width: 100%;\n height: 100%;\n pointer-events: auto;\n z-index: 1000;\n cursor: move;\n touch-action: none;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n}\n.index-module_edit__-5VvX .index-module_border__JdNLM {\n position: absolute;\n inset: -10px;\n border: 2px dashed rgb(241, 102, 41);\n border-radius: 2px;\n pointer-events: none;\n}\n.index-module_edit__-5VvX .index-module_drag__uvdPG {\n position: absolute;\n inset: -10px;\n border-radius: 2px;\n}\n.index-module_edit__-5VvX .index-module_rotate__H-KIZ {\n position: absolute;\n top: -20px;\n right: -20px;\n width: 20px;\n height: 20px;\n cursor: grab;\n z-index: 1001;\n pointer-events: auto;\n}\n.index-module_edit__-5VvX .index-module_move__mZF8s {\n position: absolute;\n bottom: -20px;\n left: -20px;\n width: 20px;\n height: 20px;\n cursor: move;\n z-index: 1001;\n pointer-events: auto;\n}";
|
|
20079
|
+
var styles$9 = {"edit":"index-module_edit__-5VvX","border":"index-module_border__JdNLM","drag":"index-module_drag__uvdPG","rotate":"index-module_rotate__H-KIZ","move":"index-module_move__mZF8s"};
|
|
20080
|
+
styleInject(css_248z$9);
|
|
20054
20081
|
|
|
20055
20082
|
const RotateHandle = ({ onRotateStart, isRotating }) => {
|
|
20056
|
-
return (jsx("div", { className: styles$
|
|
20083
|
+
return (jsx("div", { className: styles$9.rotate, onMouseDown: (e) => onRotateStart(e), onTouchStart: (e) => onRotateStart(e), style: { cursor: isRotating ? 'grabbing' : 'grab' }, dangerouslySetInnerHTML: { __html: DEFAULT_ROTATE_ICON } }));
|
|
20057
20084
|
};
|
|
20058
20085
|
|
|
20059
20086
|
/**
|
|
@@ -20130,7 +20157,7 @@ const DragHandle = ({ onDragStart, isDragging }) => {
|
|
|
20130
20157
|
if (!isMobileDevice()) {
|
|
20131
20158
|
return null;
|
|
20132
20159
|
}
|
|
20133
|
-
return (jsx("div", { className: styles$
|
|
20160
|
+
return (jsx("div", { className: styles$9.move, onMouseDown: (e) => {
|
|
20134
20161
|
onDragStart(e);
|
|
20135
20162
|
}, onTouchStart: (e) => onDragStart(e), onMouseUp: (e) => {
|
|
20136
20163
|
// onDragEnd(e);
|
|
@@ -20162,7 +20189,7 @@ const MapDrag = ({ map: _map, dragCallbacks, onBoundaryLabelsCollapse, onTransfo
|
|
|
20162
20189
|
if (!isDragMap) {
|
|
20163
20190
|
return null;
|
|
20164
20191
|
}
|
|
20165
|
-
return (jsxs("div", { ref: containerRef, className: styles$
|
|
20192
|
+
return (jsxs("div", { ref: containerRef, className: styles$9.edit, style: { cursor: isDragging ? 'grabbing' : 'move' }, children: [jsx("div", { className: styles$9.border }), canRotateMap && jsx(RotateHandle, { onRotateStart: handleRotateStart, isRotating: isRotating }), jsx("div", { className: styles$9.drag, onMouseDown: handleDragStartEvent, onTouchStart: handleDragStartEvent }), jsx(DragHandle, { onDragStart: handleDragStartEvent, isDragging: isDragging })] }));
|
|
20166
20193
|
};
|
|
20167
20194
|
|
|
20168
20195
|
const SvgEditContext = createContext({
|
|
@@ -22678,13 +22705,17 @@ const CommonContext = createContext({
|
|
|
22678
22705
|
overlayScale: 1,
|
|
22679
22706
|
showStraddleBoundaryBorder: true,
|
|
22680
22707
|
googleMapStaticApiKey: '',
|
|
22708
|
+
googleMapStaticUrlSigningSecret: '',
|
|
22709
|
+
locale: {},
|
|
22710
|
+
signGoogleStaticMapUrl: undefined,
|
|
22681
22711
|
onH5FirstSelectObstaclePoint: undefined,
|
|
22712
|
+
channelClipPathId: '',
|
|
22682
22713
|
});
|
|
22683
22714
|
const CommonContextProvider = CommonContext.Provider;
|
|
22684
22715
|
const useCommonContext = () => useContext(CommonContext);
|
|
22685
22716
|
|
|
22686
22717
|
const ChannelClipPath = React__default.memo(() => {
|
|
22687
|
-
const { svgViewBox,
|
|
22718
|
+
const { svgViewBox, channelClipPathId } = useCommonContext();
|
|
22688
22719
|
const { svgElementDatas } = useSvgEditContext();
|
|
22689
22720
|
const boundaryData = svgElementDatas?.[DataType.BOUNDARY] || [];
|
|
22690
22721
|
const [minX, minY, maxX, maxY] = useMemo(() => {
|
|
@@ -22863,7 +22894,7 @@ const ChannelClipPath = React__default.memo(() => {
|
|
|
22863
22894
|
});
|
|
22864
22895
|
return baseD;
|
|
22865
22896
|
}, [boundaryData, minX, minY, maxX, maxY]);
|
|
22866
|
-
return (jsx("defs", { children: jsx("clipPath", { id:
|
|
22897
|
+
return (jsx("defs", { children: jsx("clipPath", { id: channelClipPathId, fillRule: "evenodd", children: jsx("path", { d: d }) }) }));
|
|
22867
22898
|
});
|
|
22868
22899
|
|
|
22869
22900
|
/**
|
|
@@ -22925,9 +22956,9 @@ const VertexElement = React__default.memo(({ r, stroke, fill, ...props }) => {
|
|
|
22925
22956
|
});
|
|
22926
22957
|
|
|
22927
22958
|
var _path$1;
|
|
22928
|
-
function _extends$
|
|
22959
|
+
function _extends$5() { return _extends$5 = 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$5.apply(null, arguments); }
|
|
22929
22960
|
var SvgDelete = function SvgDelete(props) {
|
|
22930
|
-
return /*#__PURE__*/React.createElement("svg", _extends$
|
|
22961
|
+
return /*#__PURE__*/React.createElement("svg", _extends$5({
|
|
22931
22962
|
xmlns: "http://www.w3.org/2000/svg",
|
|
22932
22963
|
width: 16,
|
|
22933
22964
|
height: 16,
|
|
@@ -23159,11 +23190,487 @@ const MapEditContext = createContext({
|
|
|
23159
23190
|
const MapEditContextProvider = MapEditContext.Provider;
|
|
23160
23191
|
const useMapEditContext = () => useContext(MapEditContext);
|
|
23161
23192
|
|
|
23193
|
+
/**
|
|
23194
|
+
* Google Maps Static API 数字签名
|
|
23195
|
+
* @see https://developers.google.com/maps/documentation/maps-static/digital-signature
|
|
23196
|
+
*
|
|
23197
|
+
* 使用纯 JS HMAC-SHA1,兼容 H5 WebView / 非 HTTPS 等无 crypto.subtle 的环境。
|
|
23198
|
+
*/
|
|
23199
|
+
/// <reference lib="dom" />
|
|
23200
|
+
function removeWebSafe(encoded) {
|
|
23201
|
+
return encoded.replace(/-/g, '+').replace(/_/g, '/');
|
|
23202
|
+
}
|
|
23203
|
+
function makeWebSafe(encoded) {
|
|
23204
|
+
return encoded.replace(/\+/g, '-').replace(/\//g, '_');
|
|
23205
|
+
}
|
|
23206
|
+
function decodeUrlSigningSecret(secret) {
|
|
23207
|
+
let base64 = removeWebSafe(secret);
|
|
23208
|
+
const remainder = base64.length % 4;
|
|
23209
|
+
if (remainder) {
|
|
23210
|
+
base64 += '='.repeat(4 - remainder);
|
|
23211
|
+
}
|
|
23212
|
+
const binary = globalThis.atob(base64);
|
|
23213
|
+
const bytes = new Uint8Array(binary.length);
|
|
23214
|
+
for (let i = 0; i < binary.length; i++) {
|
|
23215
|
+
bytes[i] = binary.charCodeAt(i);
|
|
23216
|
+
}
|
|
23217
|
+
return bytes;
|
|
23218
|
+
}
|
|
23219
|
+
function bytesToWebSafeBase64(bytes) {
|
|
23220
|
+
let binary = '';
|
|
23221
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
23222
|
+
binary += String.fromCharCode(bytes[i]);
|
|
23223
|
+
}
|
|
23224
|
+
return makeWebSafe(globalThis.btoa(binary));
|
|
23225
|
+
}
|
|
23226
|
+
function utf8Encode(text) {
|
|
23227
|
+
if (typeof globalThis.TextEncoder !== 'undefined') {
|
|
23228
|
+
return new globalThis.TextEncoder().encode(text);
|
|
23229
|
+
}
|
|
23230
|
+
// Static Map URL 仅含 ASCII;兼容无 TextEncoder 的旧 WebView / Jest jsdom
|
|
23231
|
+
const bytes = new Uint8Array(text.length);
|
|
23232
|
+
for (let i = 0; i < text.length; i++) {
|
|
23233
|
+
const code = text.charCodeAt(i);
|
|
23234
|
+
if (code > 127) {
|
|
23235
|
+
throw new Error('signGoogleStaticMapUrl: non-ASCII URL is not supported in this environment');
|
|
23236
|
+
}
|
|
23237
|
+
bytes[i] = code;
|
|
23238
|
+
}
|
|
23239
|
+
return bytes;
|
|
23240
|
+
}
|
|
23241
|
+
function concatBytes(...parts) {
|
|
23242
|
+
const total = parts.reduce((sum, part) => sum + part.length, 0);
|
|
23243
|
+
const out = new Uint8Array(total);
|
|
23244
|
+
let offset = 0;
|
|
23245
|
+
for (const part of parts) {
|
|
23246
|
+
out.set(part, offset);
|
|
23247
|
+
offset += part.length;
|
|
23248
|
+
}
|
|
23249
|
+
return out;
|
|
23250
|
+
}
|
|
23251
|
+
function leftRotate(value, amount) {
|
|
23252
|
+
return ((value << amount) | (value >>> (32 - amount))) >>> 0;
|
|
23253
|
+
}
|
|
23254
|
+
function sha1(message) {
|
|
23255
|
+
const originalLength = message.length;
|
|
23256
|
+
const bitLength = originalLength * 8;
|
|
23257
|
+
const paddedLength = Math.ceil((originalLength + 9) / 64) * 64;
|
|
23258
|
+
const padded = new Uint8Array(paddedLength);
|
|
23259
|
+
padded.set(message);
|
|
23260
|
+
padded[originalLength] = 0x80;
|
|
23261
|
+
const view = new DataView(padded.buffer);
|
|
23262
|
+
view.setUint32(paddedLength - 4, bitLength >>> 0, false);
|
|
23263
|
+
view.setUint32(paddedLength - 8, Math.floor(bitLength / 0x100000000), false);
|
|
23264
|
+
let h0 = 0x67452301;
|
|
23265
|
+
let h1 = 0xefcdab89;
|
|
23266
|
+
let h2 = 0x98badcfe;
|
|
23267
|
+
let h3 = 0x10325476;
|
|
23268
|
+
let h4 = 0xc3d2e1f0;
|
|
23269
|
+
const w = new Uint32Array(80);
|
|
23270
|
+
for (let offset = 0; offset < paddedLength; offset += 64) {
|
|
23271
|
+
for (let i = 0; i < 16; i++) {
|
|
23272
|
+
w[i] = view.getUint32(offset + i * 4, false);
|
|
23273
|
+
}
|
|
23274
|
+
for (let i = 16; i < 80; i++) {
|
|
23275
|
+
w[i] = leftRotate(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1);
|
|
23276
|
+
}
|
|
23277
|
+
let a = h0;
|
|
23278
|
+
let b = h1;
|
|
23279
|
+
let c = h2;
|
|
23280
|
+
let d = h3;
|
|
23281
|
+
let e = h4;
|
|
23282
|
+
for (let i = 0; i < 80; i++) {
|
|
23283
|
+
let f;
|
|
23284
|
+
let k;
|
|
23285
|
+
if (i < 20) {
|
|
23286
|
+
f = (b & c) | (~b & d);
|
|
23287
|
+
k = 0x5a827999;
|
|
23288
|
+
}
|
|
23289
|
+
else if (i < 40) {
|
|
23290
|
+
f = b ^ c ^ d;
|
|
23291
|
+
k = 0x6ed9eba1;
|
|
23292
|
+
}
|
|
23293
|
+
else if (i < 60) {
|
|
23294
|
+
f = (b & c) | (b & d) | (c & d);
|
|
23295
|
+
k = 0x8f1bbcdc;
|
|
23296
|
+
}
|
|
23297
|
+
else {
|
|
23298
|
+
f = b ^ c ^ d;
|
|
23299
|
+
k = 0xca62c1d6;
|
|
23300
|
+
}
|
|
23301
|
+
const temp = (leftRotate(a, 5) + f + e + k + w[i]) >>> 0;
|
|
23302
|
+
e = d;
|
|
23303
|
+
d = c;
|
|
23304
|
+
c = leftRotate(b, 30);
|
|
23305
|
+
b = a;
|
|
23306
|
+
a = temp;
|
|
23307
|
+
}
|
|
23308
|
+
h0 = (h0 + a) >>> 0;
|
|
23309
|
+
h1 = (h1 + b) >>> 0;
|
|
23310
|
+
h2 = (h2 + c) >>> 0;
|
|
23311
|
+
h3 = (h3 + d) >>> 0;
|
|
23312
|
+
h4 = (h4 + e) >>> 0;
|
|
23313
|
+
}
|
|
23314
|
+
const digest = new Uint8Array(20);
|
|
23315
|
+
const digestView = new DataView(digest.buffer);
|
|
23316
|
+
digestView.setUint32(0, h0, false);
|
|
23317
|
+
digestView.setUint32(4, h1, false);
|
|
23318
|
+
digestView.setUint32(8, h2, false);
|
|
23319
|
+
digestView.setUint32(12, h3, false);
|
|
23320
|
+
digestView.setUint32(16, h4, false);
|
|
23321
|
+
return digest;
|
|
23322
|
+
}
|
|
23323
|
+
function hmacSha1(key, message) {
|
|
23324
|
+
const blockSize = 64;
|
|
23325
|
+
let normalizedKey = key;
|
|
23326
|
+
if (normalizedKey.length > blockSize) {
|
|
23327
|
+
normalizedKey = sha1(normalizedKey);
|
|
23328
|
+
}
|
|
23329
|
+
const paddedKey = new Uint8Array(blockSize);
|
|
23330
|
+
paddedKey.set(normalizedKey);
|
|
23331
|
+
const innerPad = new Uint8Array(blockSize);
|
|
23332
|
+
const outerPad = new Uint8Array(blockSize);
|
|
23333
|
+
for (let i = 0; i < blockSize; i++) {
|
|
23334
|
+
innerPad[i] = paddedKey[i] ^ 0x36;
|
|
23335
|
+
outerPad[i] = paddedKey[i] ^ 0x5c;
|
|
23336
|
+
}
|
|
23337
|
+
return sha1(concatBytes(outerPad, sha1(concatBytes(innerPad, message))));
|
|
23338
|
+
}
|
|
23339
|
+
function signUrlToSign(urlToSign, urlSigningSecret) {
|
|
23340
|
+
const keyBytes = decodeUrlSigningSecret(urlSigningSecret);
|
|
23341
|
+
const signatureBytes = hmacSha1(keyBytes, utf8Encode(urlToSign));
|
|
23342
|
+
return bytesToWebSafeBase64(signatureBytes);
|
|
23343
|
+
}
|
|
23344
|
+
/**
|
|
23345
|
+
* 对 Maps Static API 请求 URL 追加 `signature` 参数。
|
|
23346
|
+
* 签名内容为 pathname + search(不含 scheme/host),算法 HMAC-SHA1 + URL-safe Base64。
|
|
23347
|
+
*
|
|
23348
|
+
* @param inputUrl 完整未签名 URL(须已包含 key 等 query 参数)
|
|
23349
|
+
* @param urlSigningSecret Cloud Console「URL 签名密钥」(URL-safe Base64)
|
|
23350
|
+
*/
|
|
23351
|
+
function signGoogleStaticMapUrlSync(inputUrl, urlSigningSecret) {
|
|
23352
|
+
if (!inputUrl || !urlSigningSecret) {
|
|
23353
|
+
throw new Error('signGoogleStaticMapUrl: inputUrl and urlSigningSecret are required');
|
|
23354
|
+
}
|
|
23355
|
+
const url = new globalThis.URL(inputUrl);
|
|
23356
|
+
url.searchParams.delete('signature');
|
|
23357
|
+
const urlToSign = `${url.pathname}${url.search}`;
|
|
23358
|
+
const signature = signUrlToSign(urlToSign, urlSigningSecret);
|
|
23359
|
+
return `${url.origin}${urlToSign}&signature=${signature}`;
|
|
23360
|
+
}
|
|
23361
|
+
/** 异步包装,便于与宿主 signGoogleStaticMapUrl 回调统一 Promise 接口 */
|
|
23362
|
+
async function signGoogleStaticMapUrl(inputUrl, urlSigningSecret) {
|
|
23363
|
+
return signGoogleStaticMapUrlSync(inputUrl, urlSigningSecret);
|
|
23364
|
+
}
|
|
23365
|
+
|
|
23162
23366
|
var chargingPileImage = "data:image/png;base64,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";
|
|
23163
23367
|
|
|
23164
|
-
const
|
|
23368
|
+
const DEFAULT_SCREEN_PADDING = 50;
|
|
23369
|
+
function normalizeScreenPadding(padding, defaultValue = DEFAULT_SCREEN_PADDING) {
|
|
23370
|
+
if (padding == null) {
|
|
23371
|
+
return {
|
|
23372
|
+
top: defaultValue,
|
|
23373
|
+
right: defaultValue,
|
|
23374
|
+
bottom: defaultValue,
|
|
23375
|
+
left: defaultValue,
|
|
23376
|
+
};
|
|
23377
|
+
}
|
|
23378
|
+
if (typeof padding === 'number') {
|
|
23379
|
+
return { top: padding, right: padding, bottom: padding, left: padding };
|
|
23380
|
+
}
|
|
23381
|
+
if (padding.length === 2) {
|
|
23382
|
+
const [vertical, horizontal] = padding;
|
|
23383
|
+
return { top: vertical, right: horizontal, bottom: vertical, left: horizontal };
|
|
23384
|
+
}
|
|
23385
|
+
if (padding.length === 3) {
|
|
23386
|
+
const [top, horizontal, bottom] = padding;
|
|
23387
|
+
return { top, right: horizontal, bottom, left: horizontal };
|
|
23388
|
+
}
|
|
23389
|
+
const [top, right, bottom, left] = padding;
|
|
23390
|
+
return { top, right, bottom, left };
|
|
23391
|
+
}
|
|
23392
|
+
function isZeroScreenPadding(padding) {
|
|
23393
|
+
return padding.top === 0 && padding.right === 0 && padding.bottom === 0 && padding.left === 0;
|
|
23394
|
+
}
|
|
23395
|
+
|
|
23396
|
+
/** 拖拽超过该像素阈值时视为平移,而非点击 */
|
|
23397
|
+
const PAN_CLICK_THRESHOLD_PX = 5;
|
|
23398
|
+
/**
|
|
23399
|
+
* 根据地图包围盒 + padding 计算「内容 viewBox」
|
|
23400
|
+
* 用于 ChannelClipPath 裁剪,padding 为 SVG 坐标系单位
|
|
23401
|
+
*/
|
|
23402
|
+
function boundsToContentViewBox(bounds, padding) {
|
|
23403
|
+
if (!Number.isFinite(bounds.minX) || !Number.isFinite(bounds.maxX)) {
|
|
23404
|
+
return { x: 0, y: 0, width: 100, height: 100 };
|
|
23405
|
+
}
|
|
23406
|
+
const width = Math.max(bounds.maxX - bounds.minX + padding * 2, 1);
|
|
23407
|
+
const height = Math.max(bounds.maxY - bounds.minY + padding * 2, 1);
|
|
23408
|
+
return {
|
|
23409
|
+
x: bounds.minX - padding,
|
|
23410
|
+
y: bounds.minY - padding,
|
|
23411
|
+
width,
|
|
23412
|
+
height,
|
|
23413
|
+
};
|
|
23414
|
+
}
|
|
23415
|
+
/**
|
|
23416
|
+
* 在固定容器宽高比下,计算 fit 的 viewBox(等价于 SVG preserveAspectRatio="meet")
|
|
23417
|
+
*
|
|
23418
|
+
* 若内容与容器比例不一致,会在 SVG 坐标系中扩展 viewBox 的宽或高,形成 letterbox 区域
|
|
23419
|
+
*/
|
|
23420
|
+
function computeFitViewBoxToContainer(content, containerWidth, containerHeight) {
|
|
23421
|
+
const containerAspect = containerWidth / containerHeight;
|
|
23422
|
+
const contentAspect = content.width / content.height;
|
|
23423
|
+
// 内容更「宽」:扩展 viewBox 高度,上下留白
|
|
23424
|
+
if (contentAspect > containerAspect) {
|
|
23425
|
+
const height = content.width / containerAspect;
|
|
23426
|
+
return {
|
|
23427
|
+
x: content.x,
|
|
23428
|
+
y: content.y - (height - content.height) / 2,
|
|
23429
|
+
width: content.width,
|
|
23430
|
+
height,
|
|
23431
|
+
};
|
|
23432
|
+
}
|
|
23433
|
+
// 内容更「高」:扩展 viewBox 宽度,左右留白
|
|
23434
|
+
const width = content.height * containerAspect;
|
|
23435
|
+
return {
|
|
23436
|
+
x: content.x - (width - content.width) / 2,
|
|
23437
|
+
y: content.y,
|
|
23438
|
+
width,
|
|
23439
|
+
height: content.height,
|
|
23440
|
+
};
|
|
23441
|
+
}
|
|
23442
|
+
/** 地图内容在屏幕坐标系下的包围盒(meet 渲染,未考虑旋转) */
|
|
23443
|
+
function getContentScreenBounds(content, viewBox, containerWidth, containerHeight) {
|
|
23444
|
+
const { x: offsetX, y: offsetY, scale } = getViewBoxMeetOffset(viewBox, containerWidth, containerHeight);
|
|
23445
|
+
return {
|
|
23446
|
+
left: offsetX + (content.x - viewBox.x) * scale,
|
|
23447
|
+
top: offsetY + (content.y - viewBox.y) * scale,
|
|
23448
|
+
right: offsetX + (content.x + content.width - viewBox.x) * scale,
|
|
23449
|
+
bottom: offsetY + (content.y + content.height - viewBox.y) * scale,
|
|
23450
|
+
};
|
|
23451
|
+
}
|
|
23452
|
+
function contentFitsInScreenPadding(content, viewBox, pad, containerWidth, containerHeight) {
|
|
23453
|
+
const bounds = getContentScreenBounds(content, viewBox, containerWidth, containerHeight);
|
|
23454
|
+
return (bounds.left >= pad.left &&
|
|
23455
|
+
bounds.top >= pad.top &&
|
|
23456
|
+
bounds.right <= containerWidth - pad.right &&
|
|
23457
|
+
bounds.bottom <= containerHeight - pad.bottom);
|
|
23458
|
+
}
|
|
23459
|
+
function expandViewBoxFromCenter(viewBox, factor) {
|
|
23460
|
+
const centerX = viewBox.x + viewBox.width / 2;
|
|
23461
|
+
const centerY = viewBox.y + viewBox.height / 2;
|
|
23462
|
+
const width = viewBox.width * factor;
|
|
23463
|
+
const height = viewBox.height * factor;
|
|
23464
|
+
return {
|
|
23465
|
+
x: centerX - width / 2,
|
|
23466
|
+
y: centerY - height / 2,
|
|
23467
|
+
width,
|
|
23468
|
+
height,
|
|
23469
|
+
};
|
|
23470
|
+
}
|
|
23471
|
+
/** 将 viewBox 中心对齐到 padding 内矩形中心(增大 top/left → 内容向对应方向避让) */
|
|
23472
|
+
function applyViewBoxPaddingOffset(viewBox, pad, containerWidth, containerHeight) {
|
|
23473
|
+
const offsetX = ((pad.right - pad.left) / 2) * (viewBox.width / containerWidth);
|
|
23474
|
+
const offsetY = ((pad.bottom - pad.top) / 2) * (viewBox.height / containerHeight);
|
|
23475
|
+
return {
|
|
23476
|
+
...viewBox,
|
|
23477
|
+
x: viewBox.x + offsetX,
|
|
23478
|
+
y: viewBox.y + offsetY,
|
|
23479
|
+
};
|
|
23480
|
+
}
|
|
23481
|
+
function buildPaddedFitViewBox(baseFit, pad, containerWidth, containerHeight, zoomOutFactor) {
|
|
23482
|
+
const expanded = expandViewBoxFromCenter(baseFit, zoomOutFactor);
|
|
23483
|
+
return applyViewBoxPaddingOffset(expanded, pad, containerWidth, containerHeight);
|
|
23484
|
+
}
|
|
23485
|
+
function findMinimumZoomOutForPadding(content, baseFit, pad, containerWidth, containerHeight) {
|
|
23486
|
+
const fits = (zoomOutFactor) => contentFitsInScreenPadding(content, buildPaddedFitViewBox(baseFit, pad, containerWidth, containerHeight, zoomOutFactor), pad, containerWidth, containerHeight);
|
|
23487
|
+
if (fits(1)) {
|
|
23488
|
+
return 1;
|
|
23489
|
+
}
|
|
23490
|
+
const innerWidth = Math.max(containerWidth - pad.left - pad.right, 1);
|
|
23491
|
+
const innerHeight = Math.max(containerHeight - pad.top - pad.bottom, 1);
|
|
23492
|
+
const legacyScale = Math.min(innerWidth / containerWidth, innerHeight / containerHeight);
|
|
23493
|
+
let hi = legacyScale > 0 && legacyScale < 1 ? 1 / legacyScale : 2;
|
|
23494
|
+
while (!fits(hi) && hi < 1024) {
|
|
23495
|
+
hi *= 2;
|
|
23496
|
+
}
|
|
23497
|
+
let lo = 1;
|
|
23498
|
+
for (let i = 0; i < 32; i++) {
|
|
23499
|
+
const mid = (lo + hi) / 2;
|
|
23500
|
+
if (fits(mid)) {
|
|
23501
|
+
hi = mid;
|
|
23502
|
+
}
|
|
23503
|
+
else {
|
|
23504
|
+
lo = mid;
|
|
23505
|
+
}
|
|
23506
|
+
}
|
|
23507
|
+
return hi;
|
|
23508
|
+
}
|
|
23509
|
+
/**
|
|
23510
|
+
* 计算默认 fit viewBox
|
|
23511
|
+
*
|
|
23512
|
+
* @param screenPadding 容器四边留出的屏幕边距(px),语法同 CSS padding
|
|
23513
|
+
*/
|
|
23514
|
+
function computeFitViewBox(content, containerWidth, containerHeight, screenPadding = 0) {
|
|
23515
|
+
const baseFit = computeFitViewBoxToContainer(content, containerWidth, containerHeight);
|
|
23516
|
+
const pad = normalizeScreenPadding(screenPadding, 0);
|
|
23517
|
+
if (isZeroScreenPadding(pad)) {
|
|
23518
|
+
return baseFit;
|
|
23519
|
+
}
|
|
23520
|
+
const zoomOutFactor = findMinimumZoomOutForPadding(content, baseFit, pad, containerWidth, containerHeight);
|
|
23521
|
+
return buildPaddedFitViewBox(baseFit, pad, containerWidth, containerHeight, zoomOutFactor);
|
|
23522
|
+
}
|
|
23523
|
+
function viewBoxToString(viewBox) {
|
|
23524
|
+
return `${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}`;
|
|
23525
|
+
}
|
|
23526
|
+
/** SVG preserveAspectRatio="xMidYMid meet" 下的实际缩放比(地图单位 → 屏幕 px) */
|
|
23527
|
+
function getViewBoxMeetScale(viewBox, containerWidth, containerHeight) {
|
|
23528
|
+
return Math.min(containerWidth / viewBox.width, containerHeight / viewBox.height);
|
|
23529
|
+
}
|
|
23530
|
+
/** meet 模式下 SVG 内容相对容器左上角的 letterbox 偏移及缩放比 */
|
|
23531
|
+
function getViewBoxMeetOffset(viewBox, containerWidth, containerHeight) {
|
|
23532
|
+
const scale = getViewBoxMeetScale(viewBox, containerWidth, containerHeight);
|
|
23533
|
+
return {
|
|
23534
|
+
x: (containerWidth - viewBox.width * scale) / 2,
|
|
23535
|
+
y: (containerHeight - viewBox.height * scale) / 2,
|
|
23536
|
+
scale,
|
|
23537
|
+
};
|
|
23538
|
+
}
|
|
23539
|
+
/** 将屏幕坐标绕指定中心旋转(度,顺时针) */
|
|
23540
|
+
function rotatePointAround(x, y, centerX, centerY, rotationDeg) {
|
|
23541
|
+
if (rotationDeg === 0)
|
|
23542
|
+
return { x, y };
|
|
23543
|
+
const rad = (rotationDeg * Math.PI) / 180;
|
|
23544
|
+
const cos = Math.cos(rad);
|
|
23545
|
+
const sin = Math.sin(rad);
|
|
23546
|
+
const dx = x - centerX;
|
|
23547
|
+
const dy = y - centerY;
|
|
23548
|
+
return {
|
|
23549
|
+
x: centerX + dx * cos - dy * sin,
|
|
23550
|
+
y: centerY + dx * sin + dy * cos,
|
|
23551
|
+
};
|
|
23552
|
+
}
|
|
23553
|
+
/** 在地图坐标系内绕 viewBox 中心旋转(与 SVG transform="rotate(deg, cx, cy)" 一致) */
|
|
23554
|
+
function rotateMapPointAroundViewBoxCenter(mapX, mapY, viewBox, rotationDeg) {
|
|
23555
|
+
if (rotationDeg === 0)
|
|
23556
|
+
return { x: mapX, y: mapY };
|
|
23557
|
+
const cx = viewBox.x + viewBox.width / 2;
|
|
23558
|
+
const cy = viewBox.y + viewBox.height / 2;
|
|
23559
|
+
return rotatePointAround(mapX, mapY, cx, cy, rotationDeg);
|
|
23560
|
+
}
|
|
23561
|
+
function getViewBoxCenter(viewBox) {
|
|
23562
|
+
return {
|
|
23563
|
+
x: viewBox.x + viewBox.width / 2,
|
|
23564
|
+
y: viewBox.y + viewBox.height / 2,
|
|
23565
|
+
};
|
|
23566
|
+
}
|
|
23567
|
+
/**
|
|
23568
|
+
* 将地图坐标转换为 HTML overlay 的定位百分比
|
|
23569
|
+
* 需与 SVG viewBox + preserveAspectRatio="meet" 的渲染规则一致
|
|
23570
|
+
*
|
|
23571
|
+
* @returns leftPct / topPct — 相对容器宽高的百分比,配合 translate(-50%, -50%) 居中
|
|
23572
|
+
*/
|
|
23573
|
+
function mapPointToContainerPercent(mapX, mapY, viewBox, containerWidth, containerHeight, mapRotationDeg = 0) {
|
|
23574
|
+
const { x: offsetX, y: offsetY, scale } = getViewBoxMeetOffset(viewBox, containerWidth, containerHeight);
|
|
23575
|
+
const { x: projectedX, y: projectedY } = rotateMapPointAroundViewBoxCenter(mapX, mapY, viewBox, mapRotationDeg);
|
|
23576
|
+
const screenX = offsetX + (projectedX - viewBox.x) * scale;
|
|
23577
|
+
const screenY = offsetY + (projectedY - viewBox.y) * scale;
|
|
23578
|
+
return {
|
|
23579
|
+
leftPct: (screenX / containerWidth) * 100,
|
|
23580
|
+
topPct: (screenY / containerHeight) * 100,
|
|
23581
|
+
};
|
|
23582
|
+
}
|
|
23583
|
+
/** 计算多边形包围盒在当前视口下的屏幕宽度(px) */
|
|
23584
|
+
function computePolygonScreenWidthPx(points, viewBox, containerWidth, containerHeight) {
|
|
23585
|
+
const valid = points.filter((point) => point.length >= 2);
|
|
23586
|
+
if (valid.length === 0)
|
|
23587
|
+
return 0;
|
|
23588
|
+
let minX = Infinity;
|
|
23589
|
+
let maxX = -Infinity;
|
|
23590
|
+
for (const [x] of valid) {
|
|
23591
|
+
minX = Math.min(minX, x);
|
|
23592
|
+
maxX = Math.max(maxX, x);
|
|
23593
|
+
}
|
|
23594
|
+
const mapWidth = maxX - minX;
|
|
23595
|
+
if (!Number.isFinite(mapWidth) || mapWidth <= 0)
|
|
23596
|
+
return 0;
|
|
23597
|
+
const scale = getViewBoxMeetScale(viewBox, containerWidth, containerHeight);
|
|
23598
|
+
return mapWidth * scale;
|
|
23599
|
+
}
|
|
23600
|
+
function scaleStrokeWidthForViewBox(viewBox, options) {
|
|
23601
|
+
const { containerWidth, containerHeight, targetScreenStrokePx = 2 } = options;
|
|
23602
|
+
const meetScale = getViewBoxMeetScale(viewBox, containerWidth, containerHeight);
|
|
23603
|
+
if (meetScale <= 0 || !Number.isFinite(targetScreenStrokePx)) {
|
|
23604
|
+
return targetScreenStrokePx;
|
|
23605
|
+
}
|
|
23606
|
+
return targetScreenStrokePx / meetScale;
|
|
23607
|
+
}
|
|
23608
|
+
/** 屏幕拖拽像素 → viewBox 坐标增量(与 mapPointToContainerPercent 互逆,供 applyPan 使用) */
|
|
23609
|
+
function containerDeltaToViewBoxDelta(deltaXPx, deltaYPx, viewBox, containerWidth, containerHeight, mapRotationDeg = 0) {
|
|
23610
|
+
const scale = getViewBoxMeetScale(viewBox, containerWidth, containerHeight);
|
|
23611
|
+
if (mapRotationDeg === 0) {
|
|
23612
|
+
return {
|
|
23613
|
+
dx: deltaXPx / scale,
|
|
23614
|
+
dy: deltaYPx / scale,
|
|
23615
|
+
};
|
|
23616
|
+
}
|
|
23617
|
+
// 地图在屏幕上旋转了 mapRotationDeg,将屏幕位移矢量逆旋转后再换算为 viewBox 增量
|
|
23618
|
+
const rad = (-mapRotationDeg * Math.PI) / 180;
|
|
23619
|
+
const cos = Math.cos(rad);
|
|
23620
|
+
const sin = Math.sin(rad);
|
|
23621
|
+
const mapDeltaXPx = deltaXPx * cos - deltaYPx * sin;
|
|
23622
|
+
const mapDeltaYPx = deltaXPx * sin + deltaYPx * cos;
|
|
23623
|
+
return {
|
|
23624
|
+
dx: mapDeltaXPx / scale,
|
|
23625
|
+
dy: mapDeltaYPx / scale,
|
|
23626
|
+
};
|
|
23627
|
+
}
|
|
23628
|
+
/**
|
|
23629
|
+
* 限制 viewBox 宽度在 zoom 范围内
|
|
23630
|
+
*
|
|
23631
|
+
* zoom = fitViewBox.width / viewBox.width
|
|
23632
|
+
* - zoom 越大 → viewBox 越小 → 放得越大
|
|
23633
|
+
* - minZoom=0.5 → 最多缩小到 fit 的 2 倍视野(viewBox.width 最大为 fit/0.5)
|
|
23634
|
+
* - maxZoom=4 → 最多放大 4 倍(viewBox.width 最小为 fit/4)
|
|
23635
|
+
*/
|
|
23636
|
+
function clampViewBoxWidth(width, fitViewBox, minZoom, maxZoom) {
|
|
23637
|
+
const minWidth = fitViewBox.width / maxZoom;
|
|
23638
|
+
const maxWidth = fitViewBox.width / minZoom;
|
|
23639
|
+
return Math.min(maxWidth, Math.max(minWidth, width));
|
|
23640
|
+
}
|
|
23641
|
+
/**
|
|
23642
|
+
* 以屏幕 focal 点为中心缩放 viewBox
|
|
23643
|
+
* 将容器像素坐标转换为 SVG 坐标,保持焦点下的地图点位置不变
|
|
23644
|
+
*
|
|
23645
|
+
* @param viewBox 缩放前的 viewBox
|
|
23646
|
+
* @param newWidth 缩放后的 viewBox 宽度(高度按容器宽高比推导)
|
|
23647
|
+
* @param containerWidth 容器宽度(px)
|
|
23648
|
+
* @param containerHeight 容器高度(px)
|
|
23649
|
+
* @param focalX 焦点相对容器左上角的 X(px)
|
|
23650
|
+
* @param focalY 焦点相对容器左上角的 Y(px)
|
|
23651
|
+
*/
|
|
23652
|
+
function resizeViewBoxAroundPoint(viewBox, newWidth, containerWidth, containerHeight, focalX, focalY) {
|
|
23653
|
+
const containerAspect = containerWidth / containerHeight;
|
|
23654
|
+
const newHeight = newWidth / containerAspect;
|
|
23655
|
+
const relX = focalX / containerWidth;
|
|
23656
|
+
const relY = focalY / containerHeight;
|
|
23657
|
+
const svgFocalX = viewBox.x + relX * viewBox.width;
|
|
23658
|
+
const svgFocalY = viewBox.y + relY * viewBox.height;
|
|
23659
|
+
return {
|
|
23660
|
+
x: svgFocalX - relX * newWidth,
|
|
23661
|
+
y: svgFocalY - relY * newHeight,
|
|
23662
|
+
width: newWidth,
|
|
23663
|
+
height: newHeight,
|
|
23664
|
+
};
|
|
23665
|
+
}
|
|
23666
|
+
|
|
23667
|
+
const useChargingPile = ({ viewBox, rotation = 0, viewRotationMode = 'overlay-parent', containerWidth, containerHeight, }) => {
|
|
23165
23668
|
const { svgElementDatas } = useSvgEditContext();
|
|
23166
23669
|
const { sn } = useCommonContext();
|
|
23670
|
+
const useMeetPositioning = containerWidth != null &&
|
|
23671
|
+
containerHeight != null &&
|
|
23672
|
+
containerWidth > 0 &&
|
|
23673
|
+
containerHeight > 0;
|
|
23167
23674
|
const items = useMemo(() => {
|
|
23168
23675
|
if (!svgElementDatas || !viewBox)
|
|
23169
23676
|
return [];
|
|
@@ -23179,13 +23686,26 @@ const useChargingPile = ({ viewBox, rotation = 0 }) => {
|
|
|
23179
23686
|
const direction = element?.direction || 0;
|
|
23180
23687
|
const angle = (direction * 180) / Math.PI;
|
|
23181
23688
|
const rotationDegree = 270 - angle; // 正东是正方向,需要处理下
|
|
23182
|
-
const actualRotation =
|
|
23183
|
-
|
|
23184
|
-
|
|
23185
|
-
|
|
23689
|
+
const actualRotation = viewRotationMode === 'svg-transform'
|
|
23690
|
+
? rotationDegree
|
|
23691
|
+
: rotationDegree - rotation;
|
|
23692
|
+
let leftPct;
|
|
23693
|
+
let topPct;
|
|
23694
|
+
if (useMeetPositioning) {
|
|
23695
|
+
const position = mapPointToContainerPercent(center[0], center[1], viewBox, containerWidth, containerHeight, viewRotationMode === 'svg-transform' ? rotation : 0);
|
|
23696
|
+
leftPct = position.leftPct;
|
|
23697
|
+
topPct = position.topPct;
|
|
23698
|
+
}
|
|
23699
|
+
else {
|
|
23700
|
+
const relX = (center[0] - viewBox.x) / viewBox.width;
|
|
23701
|
+
const relY = (center[1] - viewBox.y) / viewBox.height;
|
|
23702
|
+
leftPct = relX * 100;
|
|
23703
|
+
topPct = relY * 100;
|
|
23704
|
+
}
|
|
23705
|
+
if (isFinite(leftPct) && isFinite(topPct)) {
|
|
23186
23706
|
results.push({
|
|
23187
|
-
leftPct
|
|
23188
|
-
topPct
|
|
23707
|
+
leftPct,
|
|
23708
|
+
topPct,
|
|
23189
23709
|
size,
|
|
23190
23710
|
rotateDeg: actualRotation,
|
|
23191
23711
|
imageSrc: chargingPileImage,
|
|
@@ -23193,12 +23713,30 @@ const useChargingPile = ({ viewBox, rotation = 0 }) => {
|
|
|
23193
23713
|
}
|
|
23194
23714
|
}
|
|
23195
23715
|
return results;
|
|
23196
|
-
}, [
|
|
23716
|
+
}, [
|
|
23717
|
+
svgElementDatas,
|
|
23718
|
+
viewBox?.x,
|
|
23719
|
+
viewBox?.y,
|
|
23720
|
+
viewBox?.width,
|
|
23721
|
+
viewBox?.height,
|
|
23722
|
+
rotation,
|
|
23723
|
+
viewRotationMode,
|
|
23724
|
+
sn,
|
|
23725
|
+
useMeetPositioning,
|
|
23726
|
+
containerWidth,
|
|
23727
|
+
containerHeight,
|
|
23728
|
+
]);
|
|
23197
23729
|
return items;
|
|
23198
23730
|
};
|
|
23199
23731
|
|
|
23200
|
-
const CharginPile = React__default.memo(({ viewBox, rotation = 0, sizeScale = 1, sizeInSvgUnits = false, isHighlight = true, }) => {
|
|
23201
|
-
const items = useChargingPile({
|
|
23732
|
+
const CharginPile = React__default.memo(({ viewBox, rotation = 0, viewRotationMode = 'overlay-parent', containerWidth, containerHeight, sizeScale = 1, sizeInSvgUnits = false, minDisplaySizePx, isHighlight = true, }) => {
|
|
23733
|
+
const items = useChargingPile({
|
|
23734
|
+
viewBox: viewBox || null,
|
|
23735
|
+
rotation,
|
|
23736
|
+
viewRotationMode,
|
|
23737
|
+
containerWidth,
|
|
23738
|
+
containerHeight,
|
|
23739
|
+
});
|
|
23202
23740
|
const containerStyle = useMemo(() => ({
|
|
23203
23741
|
position: 'absolute',
|
|
23204
23742
|
top: 0,
|
|
@@ -23209,13 +23747,18 @@ const CharginPile = React__default.memo(({ viewBox, rotation = 0, sizeScale = 1,
|
|
|
23209
23747
|
zIndex: 750,
|
|
23210
23748
|
opacity: isHighlight ? SvgMapOpacity.HIGHLIGHT : SvgMapOpacity.UN_HIGHLIGHT,
|
|
23211
23749
|
}), [isHighlight]);
|
|
23750
|
+
const useMinPx = Boolean(sizeInSvgUnits && viewBox && minDisplaySizePx != null && minDisplaySizePx > 0);
|
|
23212
23751
|
return (jsx("div", { style: containerStyle, children: items?.map((item, idx) => (jsx("div", { className: "charging-pile", style: {
|
|
23213
23752
|
position: 'absolute',
|
|
23214
23753
|
width: sizeInSvgUnits && viewBox
|
|
23215
|
-
?
|
|
23754
|
+
? useMinPx
|
|
23755
|
+
? `max(${minDisplaySizePx}px, ${(item.size / Math.max(viewBox.width, 1)) * 100 * sizeScale}%)`
|
|
23756
|
+
: `${(item.size / Math.max(viewBox.width, 1)) * 100 * sizeScale}%`
|
|
23216
23757
|
: item.size * sizeScale,
|
|
23217
23758
|
height: sizeInSvgUnits && viewBox
|
|
23218
|
-
?
|
|
23759
|
+
? useMinPx
|
|
23760
|
+
? `max(${minDisplaySizePx}px, ${(item.size / Math.max(viewBox.height, 1)) * 100 * sizeScale}%)`
|
|
23761
|
+
: `${(item.size / Math.max(viewBox.height, 1)) * 100 * sizeScale}%`
|
|
23219
23762
|
: item.size * sizeScale,
|
|
23220
23763
|
left: `${item.leftPct}%`,
|
|
23221
23764
|
top: `${item.topPct}%`,
|
|
@@ -23228,8 +23771,10 @@ const CharginPile = React__default.memo(({ viewBox, rotation = 0, sizeScale = 1,
|
|
|
23228
23771
|
const MAX_STATIC_SIZE = 640;
|
|
23229
23772
|
const MAX_STATIC_MAP_RETRY = 3;
|
|
23230
23773
|
const STATIC_MAP_RETRY_COOLDOWN_MS = 5000;
|
|
23774
|
+
/** 放大镜内充电桩屏幕像素下限,避免过小导致通道与桩视觉错位 */
|
|
23775
|
+
const MAGNIFIER_CHARGING_PILE_MIN_DISPLAY_PX = 20;
|
|
23231
23776
|
const Magnifier = ({ visible, dragState, polygonPoints, fillColor = 'rgba(255, 122, 51, 0.1)', strokeColor = 'rgba(255, 122, 51, 1)', strokeWidth = 2, zoom = 3, size = 200, }) => {
|
|
23232
|
-
const { svgViewBox, mapRef, googleMapStaticApiKey, drag } = useCommonContext();
|
|
23777
|
+
const { svgViewBox, mapRef, googleMapStaticApiKey, googleMapStaticUrlSigningSecret, signGoogleStaticMapUrl: signGoogleStaticMapUrl$1, drag, } = useCommonContext();
|
|
23233
23778
|
const { svgElementDatas, svgRef } = useSvgEditContext();
|
|
23234
23779
|
const { editMapInfo } = useMapEditContext();
|
|
23235
23780
|
const [magnifierPosition, setMagnifierPosition] = useState({ x: 0, y: 0 });
|
|
@@ -23317,76 +23862,107 @@ const Magnifier = ({ visible, dragState, polygonPoints, fillColor = 'rgba(255, 1
|
|
|
23317
23862
|
const svgPoint = svgRef.createSVGPoint();
|
|
23318
23863
|
// 只要有 key 就尝试 static api;请求失败时不截图,直接回退灰色背景
|
|
23319
23864
|
if (googleMapStaticApiKey) {
|
|
23320
|
-
|
|
23321
|
-
|
|
23322
|
-
|
|
23323
|
-
|
|
23324
|
-
|
|
23325
|
-
|
|
23326
|
-
|
|
23327
|
-
|
|
23328
|
-
|
|
23329
|
-
|
|
23330
|
-
|
|
23331
|
-
|
|
23865
|
+
let cancelled = false;
|
|
23866
|
+
(async () => {
|
|
23867
|
+
// console.log('[Magnifier] Background source: static api viewport snapshot');
|
|
23868
|
+
const mapBounds = mapRef.getBounds?.();
|
|
23869
|
+
if (!mapBounds) {
|
|
23870
|
+
// console.log('[Magnifier] Skip static map effect: mapBounds missing');
|
|
23871
|
+
if (!cancelled) {
|
|
23872
|
+
setMapImageFrame(null);
|
|
23873
|
+
}
|
|
23874
|
+
return;
|
|
23875
|
+
}
|
|
23876
|
+
const screenToSvg = (screenX, screenY) => {
|
|
23877
|
+
svgPoint.x = screenX;
|
|
23878
|
+
svgPoint.y = screenY;
|
|
23879
|
+
const p = svgPoint.matrixTransform(inverseCtm);
|
|
23880
|
+
return { x: p.x, y: p.y };
|
|
23881
|
+
};
|
|
23882
|
+
// 使用当前地图视口矩形四角,将屏幕坐标映射到 SVG 坐标,确保底图与主视口对齐
|
|
23883
|
+
const topLeftSvg = screenToSvg(mapRect.left, mapRect.top);
|
|
23884
|
+
const topRightSvg = screenToSvg(mapRect.right, mapRect.top);
|
|
23885
|
+
const bottomLeftSvg = screenToSvg(mapRect.left, mapRect.bottom);
|
|
23886
|
+
if (!topLeftSvg || !topRightSvg || !bottomLeftSvg) {
|
|
23887
|
+
if (!cancelled) {
|
|
23888
|
+
setMapImageFrame(null);
|
|
23889
|
+
}
|
|
23890
|
+
return;
|
|
23891
|
+
}
|
|
23892
|
+
// 把当前主地图可视区域尺寸,按比例压到 Static API 允许的最大尺寸以内(受 Static API 640 上限约束)),同时不改变宽高比。
|
|
23893
|
+
const mapWidth = Math.max(1, Math.round(mapRect.width));
|
|
23894
|
+
const mapHeight = Math.max(1, Math.round(mapRect.height));
|
|
23895
|
+
const shrinkRatio = Math.min(1, MAX_STATIC_SIZE / Math.max(mapWidth, mapHeight));
|
|
23896
|
+
const staticWidth = Math.max(1, Math.round(mapWidth * shrinkRatio));
|
|
23897
|
+
const staticHeight = Math.max(1, Math.round(mapHeight * shrinkRatio));
|
|
23898
|
+
const center = mapBounds.getCenter();
|
|
23899
|
+
const currentZoom = mapRef.getZoom() || 20;
|
|
23900
|
+
// 尺寸缩小后同步补偿 zoom,保持与当前主图一致的地理覆盖范围
|
|
23901
|
+
const zoomCompensation = Math.log2(shrinkRatio);
|
|
23902
|
+
const adjustedZoom = currentZoom + zoomCompensation;
|
|
23903
|
+
const staticZoom = Math.max(0, Math.min(Math.floor(adjustedZoom), 22));
|
|
23904
|
+
const unsignedStaticMapUrl = `https://maps.googleapis.com/maps/api/staticmap?center=${center.lat()},${center.lng()}&zoom=${staticZoom}&size=${staticWidth}x${staticHeight}&maptype=satellite&key=${googleMapStaticApiKey}`;
|
|
23905
|
+
// 可选:对 Static Map URL 做 Google 数字签名(宿主回调优先,其次客户端 signing secret)
|
|
23906
|
+
let staticMapUrl = unsignedStaticMapUrl;
|
|
23907
|
+
try {
|
|
23908
|
+
if (signGoogleStaticMapUrl$1) {
|
|
23909
|
+
staticMapUrl = await Promise.resolve(signGoogleStaticMapUrl$1(unsignedStaticMapUrl));
|
|
23910
|
+
}
|
|
23911
|
+
else if (googleMapStaticUrlSigningSecret) {
|
|
23912
|
+
staticMapUrl = await signGoogleStaticMapUrl(unsignedStaticMapUrl, googleMapStaticUrlSigningSecret);
|
|
23913
|
+
console.log('[Magnifier] staticMapUrl', staticMapUrl);
|
|
23914
|
+
}
|
|
23915
|
+
}
|
|
23916
|
+
catch (error) {
|
|
23917
|
+
console.warn('[Magnifier] Static map URL signing failed', error);
|
|
23918
|
+
if (!cancelled) {
|
|
23919
|
+
setMapImageFrame(null);
|
|
23920
|
+
}
|
|
23921
|
+
return;
|
|
23922
|
+
}
|
|
23923
|
+
// 通过三角点推导静态图在 SVG 中的宽高和旋转角
|
|
23924
|
+
// Math.hypot(x, y) 等价于 sqrt(x*x + y*y),计算两点之间的距离
|
|
23925
|
+
const width = Math.hypot(topRightSvg.x - topLeftSvg.x, topRightSvg.y - topLeftSvg.y);
|
|
23926
|
+
const height = Math.hypot(bottomLeftSvg.x - topLeftSvg.x, bottomLeftSvg.y - topLeftSvg.y);
|
|
23927
|
+
// Math.atan2(y, x) 计算反正切值,返回 [-π, π] 弧度,*180/π 转换为角度
|
|
23928
|
+
const rotationDeg = (Math.atan2(topRightSvg.y - topLeftSvg.y, topRightSvg.x - topLeftSvg.x) * 180) / Math.PI;
|
|
23929
|
+
// Static API zoom 只能取整;额外补偿小数部分,避免底图视觉偏小
|
|
23930
|
+
// 在有旋转的坐标系里,做“以中心为锚点”的等比放大补偿。
|
|
23931
|
+
const fractionalZoomScale = Math.pow(2, adjustedZoom - staticZoom); //算出“还差多少缩放比例”。例子:adjustedZoom=19.6,staticZoom=19,差 0.6,比例是 2^0.6 ≈ 1.515。
|
|
23932
|
+
//把原始 width/height 乘这个比例,得到补偿后的尺寸。
|
|
23933
|
+
const compensatedWidth = width * fractionalZoomScale;
|
|
23934
|
+
const compensatedHeight = height * fractionalZoomScale;
|
|
23935
|
+
//尺寸变大后,多出来的宽/高的一半(要从两边均匀扩张)。
|
|
23936
|
+
const offsetAlongX = (compensatedWidth - width) / 2;
|
|
23937
|
+
const offsetAlongY = (compensatedHeight - height) / 2;
|
|
23938
|
+
//“右方向”单位向量(从左上指向右上),因为图可能旋转了,不能只按水平 x 轴算。
|
|
23939
|
+
const unitX = width > 0 ? (topRightSvg.x - topLeftSvg.x) / width : 1;
|
|
23940
|
+
const unitY = width > 0 ? (topRightSvg.y - topLeftSvg.y) / width : 0;
|
|
23941
|
+
//“下方向”单位向量(从左上指向左下),同理用于旋转场景。
|
|
23942
|
+
const unitDownX = height > 0 ? (bottomLeftSvg.x - topLeftSvg.x) / height : 0;
|
|
23943
|
+
const unitDownY = height > 0 ? (bottomLeftSvg.y - topLeftSvg.y) / height : 0;
|
|
23944
|
+
/**
|
|
23945
|
+
* 计算补偿后的新左上角:
|
|
23946
|
+
* 从原左上角沿“右方向”回退半个扩展量,再沿“下方向”回退半个扩展量。
|
|
23947
|
+
* 这样放大后仍以原区域中心为中心,不会放大后位置偏移。
|
|
23948
|
+
*/
|
|
23949
|
+
const compensatedTopLeftX = topLeftSvg.x - unitX * offsetAlongX - unitDownX * offsetAlongY;
|
|
23950
|
+
const compensatedTopLeftY = topLeftSvg.y - unitY * offsetAlongX - unitDownY * offsetAlongY;
|
|
23951
|
+
if (cancelled) {
|
|
23952
|
+
return;
|
|
23953
|
+
}
|
|
23954
|
+
setMapImageFrame({
|
|
23955
|
+
url: staticMapUrl,
|
|
23956
|
+
x: compensatedTopLeftX,
|
|
23957
|
+
y: compensatedTopLeftY,
|
|
23958
|
+
width: compensatedWidth,
|
|
23959
|
+
height: compensatedHeight,
|
|
23960
|
+
transform: `rotate(${rotationDeg} ${compensatedTopLeftX} ${compensatedTopLeftY})`,
|
|
23961
|
+
});
|
|
23962
|
+
})();
|
|
23963
|
+
return () => {
|
|
23964
|
+
cancelled = true;
|
|
23332
23965
|
};
|
|
23333
|
-
// 使用当前地图视口矩形四角,将屏幕坐标映射到 SVG 坐标,确保底图与主视口对齐
|
|
23334
|
-
const topLeftSvg = screenToSvg(mapRect.left, mapRect.top);
|
|
23335
|
-
const topRightSvg = screenToSvg(mapRect.right, mapRect.top);
|
|
23336
|
-
const bottomLeftSvg = screenToSvg(mapRect.left, mapRect.bottom);
|
|
23337
|
-
if (!topLeftSvg || !topRightSvg || !bottomLeftSvg) {
|
|
23338
|
-
setMapImageFrame(null);
|
|
23339
|
-
return;
|
|
23340
|
-
}
|
|
23341
|
-
// 把当前主地图可视区域尺寸,按比例压到 Static API 允许的最大尺寸以内(受 Static API 640 上限约束)),同时不改变宽高比。
|
|
23342
|
-
const mapWidth = Math.max(1, Math.round(mapRect.width));
|
|
23343
|
-
const mapHeight = Math.max(1, Math.round(mapRect.height));
|
|
23344
|
-
const shrinkRatio = Math.min(1, MAX_STATIC_SIZE / Math.max(mapWidth, mapHeight));
|
|
23345
|
-
const staticWidth = Math.max(1, Math.round(mapWidth * shrinkRatio));
|
|
23346
|
-
const staticHeight = Math.max(1, Math.round(mapHeight * shrinkRatio));
|
|
23347
|
-
const center = mapBounds.getCenter();
|
|
23348
|
-
const currentZoom = mapRef.getZoom() || 20;
|
|
23349
|
-
// 尺寸缩小后同步补偿 zoom,保持与当前主图一致的地理覆盖范围
|
|
23350
|
-
const zoomCompensation = Math.log2(shrinkRatio);
|
|
23351
|
-
const adjustedZoom = currentZoom + zoomCompensation;
|
|
23352
|
-
const staticZoom = Math.max(0, Math.min(Math.floor(adjustedZoom), 22));
|
|
23353
|
-
const staticMapUrl = `https://maps.googleapis.com/maps/api/staticmap?center=${center.lat()},${center.lng()}&zoom=${staticZoom}&size=${staticWidth}x${staticHeight}&maptype=satellite&key=${googleMapStaticApiKey}`;
|
|
23354
|
-
// 通过三角点推导静态图在 SVG 中的宽高和旋转角
|
|
23355
|
-
// Math.hypot(x, y) 等价于 sqrt(x*x + y*y),计算两点之间的距离
|
|
23356
|
-
const width = Math.hypot(topRightSvg.x - topLeftSvg.x, topRightSvg.y - topLeftSvg.y);
|
|
23357
|
-
const height = Math.hypot(bottomLeftSvg.x - topLeftSvg.x, bottomLeftSvg.y - topLeftSvg.y);
|
|
23358
|
-
// Math.atan2(y, x) 计算反正切值,返回 [-π, π] 弧度,*180/π 转换为角度
|
|
23359
|
-
const rotationDeg = (Math.atan2(topRightSvg.y - topLeftSvg.y, topRightSvg.x - topLeftSvg.x) * 180) / Math.PI;
|
|
23360
|
-
// Static API zoom 只能取整;额外补偿小数部分,避免底图视觉偏小
|
|
23361
|
-
// 在有旋转的坐标系里,做“以中心为锚点”的等比放大补偿。
|
|
23362
|
-
const fractionalZoomScale = Math.pow(2, adjustedZoom - staticZoom); //算出“还差多少缩放比例”。例子:adjustedZoom=19.6,staticZoom=19,差 0.6,比例是 2^0.6 ≈ 1.515。
|
|
23363
|
-
//把原始 width/height 乘这个比例,得到补偿后的尺寸。
|
|
23364
|
-
const compensatedWidth = width * fractionalZoomScale;
|
|
23365
|
-
const compensatedHeight = height * fractionalZoomScale;
|
|
23366
|
-
//尺寸变大后,多出来的宽/高的一半(要从两边均匀扩张)。
|
|
23367
|
-
const offsetAlongX = (compensatedWidth - width) / 2;
|
|
23368
|
-
const offsetAlongY = (compensatedHeight - height) / 2;
|
|
23369
|
-
//“右方向”单位向量(从左上指向右上),因为图可能旋转了,不能只按水平 x 轴算。
|
|
23370
|
-
const unitX = width > 0 ? (topRightSvg.x - topLeftSvg.x) / width : 1;
|
|
23371
|
-
const unitY = width > 0 ? (topRightSvg.y - topLeftSvg.y) / width : 0;
|
|
23372
|
-
//“下方向”单位向量(从左上指向左下),同理用于旋转场景。
|
|
23373
|
-
const unitDownX = height > 0 ? (bottomLeftSvg.x - topLeftSvg.x) / height : 0;
|
|
23374
|
-
const unitDownY = height > 0 ? (bottomLeftSvg.y - topLeftSvg.y) / height : 1;
|
|
23375
|
-
/**
|
|
23376
|
-
* 计算补偿后的新左上角:
|
|
23377
|
-
* 从原左上角沿“右方向”回退半个扩展量,再沿“下方向”回退半个扩展量。
|
|
23378
|
-
* 这样放大后仍以原区域中心为中心,不会放大后位置偏移。
|
|
23379
|
-
*/
|
|
23380
|
-
const compensatedTopLeftX = topLeftSvg.x - unitX * offsetAlongX - unitDownX * offsetAlongY;
|
|
23381
|
-
const compensatedTopLeftY = topLeftSvg.y - unitY * offsetAlongX - unitDownY * offsetAlongY;
|
|
23382
|
-
setMapImageFrame({
|
|
23383
|
-
url: staticMapUrl,
|
|
23384
|
-
x: compensatedTopLeftX,
|
|
23385
|
-
y: compensatedTopLeftY,
|
|
23386
|
-
width: compensatedWidth,
|
|
23387
|
-
height: compensatedHeight,
|
|
23388
|
-
transform: `rotate(${rotationDeg} ${compensatedTopLeftX} ${compensatedTopLeftY})`,
|
|
23389
|
-
});
|
|
23390
23966
|
}
|
|
23391
23967
|
else {
|
|
23392
23968
|
setMapImageFrame(null);
|
|
@@ -23406,6 +23982,8 @@ const Magnifier = ({ visible, dragState, polygonPoints, fillColor = 'rgba(255, 1
|
|
|
23406
23982
|
size,
|
|
23407
23983
|
zoom,
|
|
23408
23984
|
googleMapStaticApiKey,
|
|
23985
|
+
googleMapStaticUrlSigningSecret,
|
|
23986
|
+
signGoogleStaticMapUrl$1,
|
|
23409
23987
|
staticMapRetryCount,
|
|
23410
23988
|
staticMapBlockedUntil,
|
|
23411
23989
|
]);
|
|
@@ -23581,16 +24159,16 @@ const Magnifier = ({ visible, dragState, polygonPoints, fillColor = 'rgba(255, 1
|
|
|
23581
24159
|
? 3.6 / styleZoomFactor
|
|
23582
24160
|
: 3 / styleZoomFactor;
|
|
23583
24161
|
return (jsx("circle", { cx: x, cy: y, r: pointRadius, fill: pointFill, stroke: pointStroke, strokeWidth: pointStrokeWidth }, idx));
|
|
23584
|
-
})] })), dragState.dragType === 'new' && dragState.edgeInfo && (jsx(Fragment, { children: jsx("line", { x1: dragState.edgeInfo.startPoint.x, y1: dragState.edgeInfo.startPoint.y, x2: dragState.edgeInfo.endPoint.x, y2: dragState.edgeInfo.endPoint.y, stroke: "#999", strokeWidth: (strokeWidth * 3.2) / styleZoomFactor, strokeDasharray: `${6 / styleZoomFactor},${6 / styleZoomFactor}` }) }))] }), jsx(CharginPile, { viewBox: magnifierViewBox || null, rotation: actureRotate, sizeInSvgUnits: true,
|
|
24162
|
+
})] })), dragState.dragType === 'new' && dragState.edgeInfo && (jsx(Fragment, { children: jsx("line", { x1: dragState.edgeInfo.startPoint.x, y1: dragState.edgeInfo.startPoint.y, x2: dragState.edgeInfo.endPoint.x, y2: dragState.edgeInfo.endPoint.y, stroke: "#999", strokeWidth: (strokeWidth * 3.2) / styleZoomFactor, strokeDasharray: `${6 / styleZoomFactor},${6 / styleZoomFactor}` }) }))] }), jsx(CharginPile, { viewBox: magnifierViewBox || null, rotation: actureRotate, sizeInSvgUnits: true, minDisplaySizePx: MAGNIFIER_CHARGING_PILE_MIN_DISPLAY_PX,
|
|
23585
24163
|
// 充电桩最多保持原始尺寸,不允许放大超过 1 倍
|
|
23586
24164
|
sizeScale: Math.min(1, 1 / styleZoomFactor) })] }));
|
|
23587
24165
|
// 使用 Portal 渲染到 body,避免层级问题
|
|
23588
24166
|
return ReactDOM.createPortal(magnifierContent, document.body);
|
|
23589
24167
|
};
|
|
23590
24168
|
|
|
23591
|
-
var css_248z$
|
|
23592
|
-
var styles$
|
|
23593
|
-
styleInject(css_248z$
|
|
24169
|
+
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}";
|
|
24170
|
+
var styles$8 = {"pointerCursor":"index-module_pointerCursor__Ee6pr","polygonPath":"index-module_polygonPath__PynOn","dragging":"index-module_dragging__xSFdO","notCreate":"index-module_notCreate__bFnkV"};
|
|
24171
|
+
styleInject(css_248z$8);
|
|
23594
24172
|
|
|
23595
24173
|
/**
|
|
23596
24174
|
* 几何计算工具函数
|
|
@@ -24013,34 +24591,126 @@ const useCheckElement = () => {
|
|
|
24013
24591
|
};
|
|
24014
24592
|
};
|
|
24015
24593
|
|
|
24594
|
+
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}";
|
|
24595
|
+
var styles$7 = {"tooltip":"Tooltip-module_tooltip__Wexlk","tooltipHover":"Tooltip-module_tooltipHover__Tfqme"};
|
|
24596
|
+
styleInject(css_248z$7);
|
|
24597
|
+
|
|
24598
|
+
var classnames = {exports: {}};
|
|
24599
|
+
|
|
24600
|
+
/*!
|
|
24601
|
+
Copyright (c) 2018 Jed Watson.
|
|
24602
|
+
Licensed under the MIT License (MIT), see
|
|
24603
|
+
http://jedwatson.github.io/classnames
|
|
24604
|
+
*/
|
|
24605
|
+
|
|
24606
|
+
(function (module) {
|
|
24607
|
+
/* global define */
|
|
24608
|
+
|
|
24609
|
+
(function () {
|
|
24610
|
+
|
|
24611
|
+
var hasOwn = {}.hasOwnProperty;
|
|
24612
|
+
|
|
24613
|
+
function classNames () {
|
|
24614
|
+
var classes = '';
|
|
24615
|
+
|
|
24616
|
+
for (var i = 0; i < arguments.length; i++) {
|
|
24617
|
+
var arg = arguments[i];
|
|
24618
|
+
if (arg) {
|
|
24619
|
+
classes = appendClass(classes, parseValue(arg));
|
|
24620
|
+
}
|
|
24621
|
+
}
|
|
24622
|
+
|
|
24623
|
+
return classes;
|
|
24624
|
+
}
|
|
24625
|
+
|
|
24626
|
+
function parseValue (arg) {
|
|
24627
|
+
if (typeof arg === 'string' || typeof arg === 'number') {
|
|
24628
|
+
return arg;
|
|
24629
|
+
}
|
|
24630
|
+
|
|
24631
|
+
if (typeof arg !== 'object') {
|
|
24632
|
+
return '';
|
|
24633
|
+
}
|
|
24634
|
+
|
|
24635
|
+
if (Array.isArray(arg)) {
|
|
24636
|
+
return classNames.apply(null, arg);
|
|
24637
|
+
}
|
|
24638
|
+
|
|
24639
|
+
if (arg.toString !== Object.prototype.toString && !arg.toString.toString().includes('[native code]')) {
|
|
24640
|
+
return arg.toString();
|
|
24641
|
+
}
|
|
24642
|
+
|
|
24643
|
+
var classes = '';
|
|
24644
|
+
|
|
24645
|
+
for (var key in arg) {
|
|
24646
|
+
if (hasOwn.call(arg, key) && arg[key]) {
|
|
24647
|
+
classes = appendClass(classes, key);
|
|
24648
|
+
}
|
|
24649
|
+
}
|
|
24650
|
+
|
|
24651
|
+
return classes;
|
|
24652
|
+
}
|
|
24653
|
+
|
|
24654
|
+
function appendClass (value, newClass) {
|
|
24655
|
+
if (!newClass) {
|
|
24656
|
+
return value;
|
|
24657
|
+
}
|
|
24658
|
+
|
|
24659
|
+
if (value) {
|
|
24660
|
+
return value + ' ' + newClass;
|
|
24661
|
+
}
|
|
24662
|
+
|
|
24663
|
+
return value + newClass;
|
|
24664
|
+
}
|
|
24665
|
+
|
|
24666
|
+
if (module.exports) {
|
|
24667
|
+
classNames.default = classNames;
|
|
24668
|
+
module.exports = classNames;
|
|
24669
|
+
} else {
|
|
24670
|
+
window.classNames = classNames;
|
|
24671
|
+
}
|
|
24672
|
+
}());
|
|
24673
|
+
} (classnames));
|
|
24674
|
+
|
|
24675
|
+
var classnamesExports = classnames.exports;
|
|
24676
|
+
var classNames = /*@__PURE__*/getDefaultExportFromCjs(classnamesExports);
|
|
24677
|
+
|
|
24016
24678
|
const Tooltip = ({ open, x, y, text, style, onClick }) => {
|
|
24017
24679
|
if (!open)
|
|
24018
24680
|
return null;
|
|
24019
24681
|
const tooltipStyle = {
|
|
24020
|
-
position: 'fixed',
|
|
24021
24682
|
left: x,
|
|
24022
24683
|
top: y,
|
|
24023
|
-
backgroundColor: '#fff',
|
|
24024
|
-
borderRadius: '4px',
|
|
24025
|
-
height: '20px',
|
|
24026
|
-
lineHeight: '16px',
|
|
24027
|
-
fontSize: '12px',
|
|
24028
|
-
color: '#211F1F',
|
|
24029
|
-
padding: '2px 4px',
|
|
24030
|
-
whiteSpace: 'nowrap',
|
|
24031
|
-
zIndex: 9999,
|
|
24032
|
-
cursor: 'pointer',
|
|
24033
|
-
userSelect: 'none',
|
|
24034
24684
|
...style,
|
|
24035
24685
|
};
|
|
24036
24686
|
// 使用 createPortal 挂载到 body
|
|
24037
|
-
return createPortal(jsx("div", {
|
|
24687
|
+
return createPortal(jsx("div", { className: classNames(styles$7.tooltip, {
|
|
24688
|
+
[styles$7.tooltipHover]: !!onClick,
|
|
24689
|
+
}), style: tooltipStyle, onClick: (e) => {
|
|
24038
24690
|
e.preventDefault();
|
|
24039
24691
|
e.stopPropagation();
|
|
24040
24692
|
onClick?.();
|
|
24041
24693
|
}, children: text }), document.body);
|
|
24042
24694
|
};
|
|
24043
24695
|
|
|
24696
|
+
function getPathData(points) {
|
|
24697
|
+
// 1. 安全守卫:如果没有点,返回空字符串
|
|
24698
|
+
if (!points || points.length === 0)
|
|
24699
|
+
return '';
|
|
24700
|
+
// 2. 如果只有 1 个点,只生成起点 M 即可(或者在最外层判断中直接 return 过滤掉)
|
|
24701
|
+
if (points.length === 1) {
|
|
24702
|
+
return `M ${points[0][0]} ${points[0][1]}`;
|
|
24703
|
+
}
|
|
24704
|
+
// 3. 2个点及以上时,再进行 L 拼接
|
|
24705
|
+
const pathData = points.map((p, index) => {
|
|
24706
|
+
if (index === 0) {
|
|
24707
|
+
return `M ${p[0]} ${p[1]}`;
|
|
24708
|
+
}
|
|
24709
|
+
return ` L ${p[0]} ${p[1]}`;
|
|
24710
|
+
}).join('');
|
|
24711
|
+
return pathData;
|
|
24712
|
+
}
|
|
24713
|
+
|
|
24044
24714
|
/**
|
|
24045
24715
|
* 将坐标点数组转换为polygon points字符串
|
|
24046
24716
|
* @param coordinates 坐标点数组 [[x1, y1], [x2, y2], ...]
|
|
@@ -24124,15 +24794,12 @@ const groupCoordinatesByType = (coordinates, autoClose = true) => {
|
|
|
24124
24794
|
const createPathData = (points) => {
|
|
24125
24795
|
if (points.length < 2)
|
|
24126
24796
|
return '';
|
|
24127
|
-
|
|
24128
|
-
for (let i = 1; i < points.length; i++) {
|
|
24129
|
-
pathData += ` L ${points[i][0]} ${points[i][1]}`;
|
|
24130
|
-
}
|
|
24797
|
+
const pathData = getPathData(points);
|
|
24131
24798
|
return pathData;
|
|
24132
24799
|
};
|
|
24133
24800
|
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自动判断
|
|
24134
24801
|
}) => {
|
|
24135
|
-
const { overlayScale, unitType, showStraddleBoundaryBorder, platform, onH5FirstSelectObstaclePoint } = useCommonContext();
|
|
24802
|
+
const { overlayScale, unitType, showStraddleBoundaryBorder, platform, locale, onH5FirstSelectObstaclePoint, } = useCommonContext();
|
|
24136
24803
|
const { onHandleEvent } = useMapEditContext();
|
|
24137
24804
|
const { svgRef } = useSvgEditContext();
|
|
24138
24805
|
const [dragState, setDragState] = useState({
|
|
@@ -24180,11 +24847,12 @@ const PolygonElement = ({ canSelect = false, isHover = false, points, fillColor
|
|
|
24180
24847
|
const [tooltipIndex, setTooltipIndex] = useState(null);
|
|
24181
24848
|
const [tooltipPos, setTooltipPos] = useState({ x: 0, y: 0 });
|
|
24182
24849
|
const [completeTooltipOpen, setCompleteTooltipOpen] = useState(false);
|
|
24850
|
+
const [moveTooltipOpen, setMoveTooltipOpen] = useState(false);
|
|
24183
24851
|
const updateTooltipPos = (target) => {
|
|
24184
24852
|
const rect = target.getBoundingClientRect();
|
|
24185
24853
|
setTooltipPos({
|
|
24186
24854
|
x: rect.left + rect.width + 4,
|
|
24187
|
-
y: rect.top - 10
|
|
24855
|
+
y: rect.top - 10,
|
|
24188
24856
|
});
|
|
24189
24857
|
};
|
|
24190
24858
|
// 处理顶点点击(仅透出点击;不触发删除)
|
|
@@ -24319,6 +24987,13 @@ const PolygonElement = ({ canSelect = false, isHover = false, points, fillColor
|
|
|
24319
24987
|
edgeInfo: edgeInfo,
|
|
24320
24988
|
dragType: 'new',
|
|
24321
24989
|
});
|
|
24990
|
+
// 边上新点:insertIndex 可能与当前 selectedVertexIndex 相同(例如原选中 2,在 1-2 间插入后仍写 2),
|
|
24991
|
+
// React 会跳过 setState,依赖 selectedVertexIndex 的 useLayoutEffect 不会跑,故用微任务直接震一次
|
|
24992
|
+
if (platform === PlatformType.H5 && editMapInfo?.elementType === DataType.OBSTACLE) {
|
|
24993
|
+
void Promise.resolve().then(() => {
|
|
24994
|
+
onH5FirstSelectObstaclePoint?.();
|
|
24995
|
+
});
|
|
24996
|
+
}
|
|
24322
24997
|
// 将新创建的点设为选中态,便于后续直接拖拽
|
|
24323
24998
|
setSelectedVertexIndex(insertIndex);
|
|
24324
24999
|
}, [
|
|
@@ -24329,6 +25004,9 @@ const PolygonElement = ({ canSelect = false, isHover = false, points, fillColor
|
|
|
24329
25004
|
coordinates,
|
|
24330
25005
|
getSVGCoordinates,
|
|
24331
25006
|
calculatePerpendicularFoot$1,
|
|
25007
|
+
platform,
|
|
25008
|
+
editMapInfo?.elementType,
|
|
25009
|
+
onH5FirstSelectObstaclePoint,
|
|
24332
25010
|
]);
|
|
24333
25011
|
// H5平台长按边线创建新顶点
|
|
24334
25012
|
const handleEdgeLongPressStart = useCallback((e, edgeStartIndex) => {
|
|
@@ -24373,6 +25051,8 @@ const PolygonElement = ({ canSelect = false, isHover = false, points, fillColor
|
|
|
24373
25051
|
const vertexLongPressTimerRef = useRef(null);
|
|
24374
25052
|
const isVertexLongPressedRef = useRef(false);
|
|
24375
25053
|
const longPressVertexIndexRef = useRef(-1);
|
|
25054
|
+
/** 长按选中顶点且 index 发生变化时置 true,在 useLayoutEffect 里再触发震动,避免早于视觉选中态 */
|
|
25055
|
+
const pendingH5ObstacleSelectHapticRef = useRef(false);
|
|
24376
25056
|
// 长按拖拽判定相关
|
|
24377
25057
|
const touchStartPosRef = useRef(null);
|
|
24378
25058
|
const hasStartedDragViaLongPressRef = useRef(false);
|
|
@@ -24393,13 +25073,21 @@ const PolygonElement = ({ canSelect = false, isHover = false, points, fillColor
|
|
|
24393
25073
|
vertexLongPressTimerRef.current = window.setTimeout(() => {
|
|
24394
25074
|
isVertexLongPressedRef.current = true;
|
|
24395
25075
|
setSelectedVertexIndex((prev) => {
|
|
24396
|
-
|
|
24397
|
-
onH5FirstSelectObstaclePoint?.();
|
|
24398
|
-
}
|
|
25076
|
+
pendingH5ObstacleSelectHapticRef.current = prev !== index;
|
|
24399
25077
|
return index;
|
|
24400
25078
|
});
|
|
24401
25079
|
}, 1000);
|
|
24402
|
-
}, [editMode, createMode, completed, dragState.isDragging
|
|
25080
|
+
}, [editMode, createMode, completed, dragState.isDragging]);
|
|
25081
|
+
useLayoutEffect(() => {
|
|
25082
|
+
if (!pendingH5ObstacleSelectHapticRef.current)
|
|
25083
|
+
return;
|
|
25084
|
+
pendingH5ObstacleSelectHapticRef.current = false;
|
|
25085
|
+
if (platform !== PlatformType.H5)
|
|
25086
|
+
return;
|
|
25087
|
+
if (editMapInfo?.elementType !== DataType.OBSTACLE)
|
|
25088
|
+
return;
|
|
25089
|
+
onH5FirstSelectObstaclePoint?.();
|
|
25090
|
+
}, [selectedVertexIndex, platform, editMapInfo?.elementType, onH5FirstSelectObstaclePoint]);
|
|
24403
25091
|
const handleVertexLongPressEnd = useCallback(() => {
|
|
24404
25092
|
if (vertexLongPressTimerRef.current !== null) {
|
|
24405
25093
|
window.clearTimeout(vertexLongPressTimerRef.current);
|
|
@@ -24428,8 +25116,8 @@ const PolygonElement = ({ canSelect = false, isHover = false, points, fillColor
|
|
|
24428
25116
|
}, [dragState.currentPosition, checkCanNotCreateAtPosition, coordinates]);
|
|
24429
25117
|
const generateVertexClassName = useMemo(() => {
|
|
24430
25118
|
if (!dragState.isDragging)
|
|
24431
|
-
return styles$
|
|
24432
|
-
return showNotCreateCursor ? styles$
|
|
25119
|
+
return styles$8.pointerCursor;
|
|
25120
|
+
return showNotCreateCursor ? styles$8.notCreate : styles$8.dragging;
|
|
24433
25121
|
}, [dragState.isDragging, showNotCreateCursor]);
|
|
24434
25122
|
// 使用 useRef 来存储 requestAnimationFrame ID
|
|
24435
25123
|
const rafIdRef = useRef(null);
|
|
@@ -24720,7 +25408,7 @@ const PolygonElement = ({ canSelect = false, isHover = false, points, fillColor
|
|
|
24720
25408
|
WebkitTouchCallout: 'none',
|
|
24721
25409
|
touchAction: 'none',
|
|
24722
25410
|
} })] }));
|
|
24723
|
-
})()] })), renderCoordinates.length >= 2 && (jsx("polygon", { className: canSelect ? styles$
|
|
25411
|
+
})()] })), renderCoordinates.length >= 2 && (jsx("polygon", { className: canSelect ? styles$8.pointerCursor : '', points: polygonPoints, fill: isHover ? hoverFillColor || fillColor : fillColor, fillOpacity: fillOpacity, stroke: "none" // 边框透明
|
|
24724
25412
|
, onClick: onPolygonClick })), jsx("g", { children: renderCoordinates.length >= 2 &&
|
|
24725
25413
|
pathSegments.map((segment, index) => {
|
|
24726
25414
|
if (segment.points.length < 2)
|
|
@@ -24728,30 +25416,45 @@ const PolygonElement = ({ canSelect = false, isHover = false, points, fillColor
|
|
|
24728
25416
|
const pathData = createPathData(segment.points);
|
|
24729
25417
|
const isDash = segment.type === 1;
|
|
24730
25418
|
if (isDash && showStraddleBoundaryBorder) {
|
|
24731
|
-
return (jsx(DashPath, { className: canSelect ? styles$
|
|
25419
|
+
return (jsx(DashPath, { className: canSelect ? styles$8.polygonPath : '', points: segment.points, stroke: strokeColor, strokeWidth: isHover ? strokeWidth + 1 : strokeWidth, strokeOpacity: strokeOpacity }, index));
|
|
24732
25420
|
}
|
|
24733
|
-
return (jsxs(Fragment, { children: [(editMode || createMode) && (jsx("path", { d: pathData, fill: "none", stroke: '#fff', strokeWidth: strokeWidth * 2, strokeOpacity: strokeOpacity, strokeLinecap: "round", strokeLinejoin: "round", vectorEffect: "non-scaling-stroke", style: {
|
|
25421
|
+
return (jsxs(Fragment$1, { children: [(editMode || createMode) && (jsx("path", { d: pathData, fill: "none", stroke: '#fff', strokeWidth: strokeWidth * 2, strokeOpacity: strokeOpacity, strokeLinecap: "round", strokeLinejoin: "round", vectorEffect: "non-scaling-stroke", style: {
|
|
24734
25422
|
userSelect: 'none',
|
|
24735
25423
|
WebkitUserSelect: 'none',
|
|
24736
25424
|
WebkitTouchCallout: 'none',
|
|
24737
25425
|
touchAction: 'none',
|
|
24738
|
-
} }
|
|
25426
|
+
} })), 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: {
|
|
24739
25427
|
userSelect: 'none',
|
|
24740
25428
|
WebkitUserSelect: 'none',
|
|
24741
25429
|
WebkitTouchCallout: 'none',
|
|
24742
25430
|
touchAction: 'none',
|
|
24743
|
-
} }, index)] }));
|
|
25431
|
+
} }, index)] }, 'warp' + index));
|
|
24744
25432
|
}) }), (editMode || (createMode && completed)) &&
|
|
24745
25433
|
renderCoordinates.length >= 2 &&
|
|
24746
25434
|
renderCoordinates.map((coord, index) => {
|
|
24747
25435
|
const nextCoord = renderCoordinates[(index + 1) % renderCoordinates.length];
|
|
24748
25436
|
const isDashPath = coord[2] === 1;
|
|
24749
|
-
|
|
24750
|
-
|
|
24751
|
-
|
|
24752
|
-
|
|
24753
|
-
|
|
24754
|
-
|
|
25437
|
+
const edgeLineCommon = {
|
|
25438
|
+
x1: coord[0],
|
|
25439
|
+
y1: coord[1],
|
|
25440
|
+
x2: nextCoord[0],
|
|
25441
|
+
y2: nextCoord[1],
|
|
25442
|
+
};
|
|
25443
|
+
const edgeLineStyle = {
|
|
25444
|
+
userSelect: 'none',
|
|
25445
|
+
WebkitUserSelect: 'none',
|
|
25446
|
+
WebkitTouchCallout: 'none',
|
|
25447
|
+
touchAction: 'none',
|
|
25448
|
+
};
|
|
25449
|
+
// H5 实线:透明粗线仅作长按热区,描边只看上方 path;H5 虚线/Web:沿用透明或主题色边线 + 对应事件(合并为一个 line,避免两段重复)
|
|
25450
|
+
const isH5SolidHitOnly = platform === PlatformType.H5 && ((editMode && !isDashPath) || createMode);
|
|
25451
|
+
const edgeStroke = isH5SolidHitOnly || isDashPath ? 'transparent' : strokeColor;
|
|
25452
|
+
const edgeStrokeWidth = isH5SolidHitOnly
|
|
25453
|
+
? strokeWidth * 12
|
|
25454
|
+
: platform === PlatformType.H5
|
|
25455
|
+
? strokeWidth * 2
|
|
25456
|
+
: strokeWidth;
|
|
25457
|
+
return (jsx("line", { ...edgeLineCommon, stroke: edgeStroke, strokeWidth: edgeStrokeWidth, ...(isH5SolidHitOnly ? { strokeLinecap: 'round' } : {}), className: editMode ? styles$8.pointerCursor : '', style: edgeLineStyle, vectorEffect: "non-scaling-stroke", ...(platform === PlatformType.H5
|
|
24755
25458
|
? {
|
|
24756
25459
|
onTouchStart: createReactEventHandler((e) => handleEdgeLongPressStart(e, index)),
|
|
24757
25460
|
onTouchEnd: createReactEventHandler(() => handleEdgeLongPressEnd()),
|
|
@@ -24821,7 +25524,7 @@ const PolygonElement = ({ canSelect = false, isHover = false, points, fillColor
|
|
|
24821
25524
|
isDoubleClickingRef.current = false;
|
|
24822
25525
|
}, 300);
|
|
24823
25526
|
// 打开删除确认 Tooltip(仅在可编辑时)
|
|
24824
|
-
if (editMode || (createMode && completed)) {
|
|
25527
|
+
if ((editMode || (createMode && completed)) && renderCoordinates.length > 3) {
|
|
24825
25528
|
updateTooltipPos(e.target);
|
|
24826
25529
|
setTooltipIndex(idx);
|
|
24827
25530
|
}
|
|
@@ -24898,13 +25601,25 @@ const PolygonElement = ({ canSelect = false, isHover = false, points, fillColor
|
|
|
24898
25601
|
}
|
|
24899
25602
|
}
|
|
24900
25603
|
},
|
|
24901
|
-
onMouseEnter:
|
|
24902
|
-
|
|
24903
|
-
|
|
24904
|
-
|
|
24905
|
-
|
|
24906
|
-
|
|
24907
|
-
|
|
25604
|
+
onMouseEnter: (e) => {
|
|
25605
|
+
if (canComplete) {
|
|
25606
|
+
setCompleteTooltipOpen(true);
|
|
25607
|
+
}
|
|
25608
|
+
if (editMode || (createMode && completed)) {
|
|
25609
|
+
setMoveTooltipOpen(true);
|
|
25610
|
+
}
|
|
25611
|
+
if (tooltipIndex === null) {
|
|
25612
|
+
updateTooltipPos(e.target);
|
|
25613
|
+
}
|
|
25614
|
+
},
|
|
25615
|
+
onMouseLeave: () => {
|
|
25616
|
+
if (canComplete) {
|
|
25617
|
+
setCompleteTooltipOpen(false);
|
|
25618
|
+
}
|
|
25619
|
+
if (editMode || (createMode && completed)) {
|
|
25620
|
+
setMoveTooltipOpen(false);
|
|
25621
|
+
}
|
|
25622
|
+
},
|
|
24908
25623
|
}) }), platform === PlatformType.H5 && tooltipIndex === idx && coordinates.length > 3 && (jsxs("g", { transform: `translate(${coord[0] + 4 * overlayScale * 2}, ${coord[1] - 4 * overlayScale * 2}) scale(${overlayScale * 2})`, onClick: (e) => {
|
|
24909
25624
|
e.preventDefault();
|
|
24910
25625
|
e.stopPropagation();
|
|
@@ -24928,12 +25643,16 @@ const PolygonElement = ({ canSelect = false, isHover = false, points, fillColor
|
|
|
24928
25643
|
// >
|
|
24929
25644
|
// </rect>
|
|
24930
25645
|
)] }, `vertex-${idx}`));
|
|
24931
|
-
}), platform === PlatformType.WEB && tooltipIndex !== null && (jsx(Tooltip, { open: true, x: tooltipPos.x, y: tooltipPos.y, text:
|
|
24932
|
-
if (onVertexDelete &&
|
|
25646
|
+
}), platform === PlatformType.WEB && tooltipIndex !== null && (jsx(Tooltip, { open: true, x: tooltipPos.x, y: tooltipPos.y, text: locale?.['map.renderer.remove'] || 'Remove', onClick: () => {
|
|
25647
|
+
if (onVertexDelete &&
|
|
25648
|
+
(editMode || (createMode && completed)) &&
|
|
25649
|
+
coordinates.length > 3) {
|
|
24933
25650
|
onVertexDelete(renderCoordinates?.length - 1 - tooltipIndex);
|
|
24934
25651
|
}
|
|
24935
25652
|
setTooltipIndex(null);
|
|
24936
|
-
} })), platform === PlatformType.WEB && completeTooltipOpen && (jsx(Tooltip, { open: true, x: tooltipPos.x, y: tooltipPos.y, text:
|
|
25653
|
+
} })), platform === PlatformType.WEB && completeTooltipOpen && (jsx(Tooltip, { open: true, x: tooltipPos.x, y: tooltipPos.y, text: locale?.['map.renderer.tips.clickFinish'] || 'Click to finish' })), platform === PlatformType.WEB && moveTooltipOpen && tooltipIndex === null && (jsx(Tooltip, { open: true, x: tooltipPos.x, y: tooltipPos.y, text: renderCoordinates.length > 3 ?
|
|
25654
|
+
(locale?.['map.renderer.tips.dragAndRemove'] || 'Drag to change, double click to remove') :
|
|
25655
|
+
(locale?.['map.renderer.tips.drag'] || 'Drag to change') }))] }));
|
|
24937
25656
|
};
|
|
24938
25657
|
|
|
24939
25658
|
const MowPartitionContext = createContext({
|
|
@@ -25013,17 +25732,15 @@ const BoundaryElement = ({ data, isHover }) => {
|
|
|
25013
25732
|
};
|
|
25014
25733
|
|
|
25015
25734
|
const ChannelElement = ({ data }) => {
|
|
25016
|
-
const {
|
|
25735
|
+
const { channelClipPathId } = useCommonContext();
|
|
25736
|
+
const clipPathUrl = `url(#${channelClipPathId})`;
|
|
25017
25737
|
const points = data.points;
|
|
25018
25738
|
const style = data.style;
|
|
25019
|
-
|
|
25020
|
-
for (let i = 1; i < points.length; i++) {
|
|
25021
|
-
pathData += ` L ${points[i][0]} ${points[i][1]}`;
|
|
25022
|
-
}
|
|
25739
|
+
const pathData = getPathData(points);
|
|
25023
25740
|
const topLineWidth = dp2px(style.lineWidth).toString();
|
|
25024
25741
|
const bottomLineWidth = dp2px(style.bottomLineWidth).toString();
|
|
25025
25742
|
const dashLength = dp2px(style.lineWidth);
|
|
25026
|
-
return (jsxs("g", { id: data.id.toString(), children: [jsx("path", { d: pathData, fill: "none", stroke: "#8498A9", strokeWidth: bottomLineWidth, strokeLinejoin: "round", opacity: style.opacity || 1, style: { clipPath:
|
|
25743
|
+
return (jsxs("g", { id: data.id.toString(), children: [jsx("path", { d: pathData, fill: "none", stroke: "#8498A9", strokeWidth: bottomLineWidth, strokeLinejoin: "round", opacity: style.opacity || 1, style: { clipPath: clipPathUrl } }), jsx("path", { d: pathData, fill: "none", stroke: "#FFFFFF", strokeWidth: topLineWidth, strokeLinejoin: "round", strokeDasharray: `${dashLength * 2} ${dashLength}`, opacity: style.opacity || 1, style: { clipPath: clipPathUrl } })] }));
|
|
25027
25744
|
};
|
|
25028
25745
|
|
|
25029
25746
|
/**
|
|
@@ -25149,10 +25866,11 @@ const ObstacleElement = ({ data, isHover }) => {
|
|
|
25149
25866
|
const strokeWidth = useMemo(() => {
|
|
25150
25867
|
// 如果是h5选中的情况下,禁区只有在start模式下才需要加粗
|
|
25151
25868
|
// 其他模式展示的是其他的样式
|
|
25152
|
-
if (platform === PlatformType.H5 &&
|
|
25869
|
+
if ((platform === PlatformType.H5 &&
|
|
25153
25870
|
editMapInfo?.selectElement?.id === data.id &&
|
|
25154
|
-
editMapInfo.mobileMode === MobileEditMode.START)
|
|
25155
|
-
|
|
25871
|
+
editMapInfo.mobileMode === MobileEditMode.START) ||
|
|
25872
|
+
editMapInfo.mobileMode === MobileEditMode.EDIT) {
|
|
25873
|
+
return dp2px((style.lineWidth || 1) * 2.2);
|
|
25156
25874
|
// return dp2px(((style.lineWidth as number) || 3) * 3);
|
|
25157
25875
|
}
|
|
25158
25876
|
return dp2px(style.lineWidth || 3);
|
|
@@ -25187,7 +25905,10 @@ const ObstacleElement = ({ data, isHover }) => {
|
|
|
25187
25905
|
}
|
|
25188
25906
|
return editMapInfo?.selectElement?.id === data.id;
|
|
25189
25907
|
}, [editMapInfo, data, platform]);
|
|
25190
|
-
|
|
25908
|
+
const disabled = useMemo(() => {
|
|
25909
|
+
return data?.status === 0 && !editMode;
|
|
25910
|
+
}, [data?.status, editMode, editMapInfo?.createMode]);
|
|
25911
|
+
return (jsx(PolygonElement, { canSelect: Boolean(!editMapInfo?.elementType) && editMap, points: currentPoints, fillColor: disabled ? style.disabledFillColor : style.fillColor, hoverFillColor: style.hoverFillColor, fillOpacity: style.fillOpacity, strokeColor: disabled ? style.disabledLineColor : style.lineColor, strokeWidth: strokeWidth, editMode: editMode, isHover: isHover, showPoints: editMapInfo?.selectElement?.id === data.id, onPathClick: onPathClick, onPolygonClick: () => {
|
|
25191
25912
|
onPathClick();
|
|
25192
25913
|
}, onVertexDelete: (vertexIndex) => handleCreateVertexDelete(vertexIndex), onCoordinatesChange: (coordinates) => {
|
|
25193
25914
|
if (platform === PlatformType.H5 && editMapInfo.mobileMode === MobileEditMode.CREATE) {
|
|
@@ -25612,15 +26333,15 @@ const useVisionOffTransform = (data, scaleConstraints
|
|
|
25612
26333
|
};
|
|
25613
26334
|
};
|
|
25614
26335
|
|
|
25615
|
-
var _g$
|
|
25616
|
-
function _extends$
|
|
26336
|
+
var _g$3, _defs$3;
|
|
26337
|
+
function _extends$4() { return _extends$4 = 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$4.apply(null, arguments); }
|
|
25617
26338
|
var SvgTransformDelete = function SvgTransformDelete(props) {
|
|
25618
|
-
return /*#__PURE__*/React.createElement("svg", _extends$
|
|
26339
|
+
return /*#__PURE__*/React.createElement("svg", _extends$4({
|
|
25619
26340
|
xmlns: "http://www.w3.org/2000/svg",
|
|
25620
26341
|
width: 20,
|
|
25621
26342
|
height: 20,
|
|
25622
26343
|
fill: "none"
|
|
25623
|
-
}, props), _g$
|
|
26344
|
+
}, props), _g$3 || (_g$3 = /*#__PURE__*/React.createElement("g", {
|
|
25624
26345
|
clipPath: "url(#transform-delete_svg__a)"
|
|
25625
26346
|
}, /*#__PURE__*/React.createElement("circle", {
|
|
25626
26347
|
cx: 10,
|
|
@@ -25643,7 +26364,7 @@ var SvgTransformDelete = function SvgTransformDelete(props) {
|
|
|
25643
26364
|
fill: "#fff",
|
|
25644
26365
|
rx: 0.8,
|
|
25645
26366
|
transform: "rotate(45 6.323 5.191)"
|
|
25646
|
-
}))), _defs$
|
|
26367
|
+
}))), _defs$3 || (_defs$3 = /*#__PURE__*/React.createElement("defs", null, /*#__PURE__*/React.createElement("linearGradient", {
|
|
25647
26368
|
id: "transform-delete_svg__b",
|
|
25648
26369
|
x1: 17.727,
|
|
25649
26370
|
x2: -1.215,
|
|
@@ -25663,15 +26384,15 @@ var SvgTransformDelete = function SvgTransformDelete(props) {
|
|
|
25663
26384
|
})))));
|
|
25664
26385
|
};
|
|
25665
26386
|
|
|
25666
|
-
var _g$
|
|
25667
|
-
function _extends$
|
|
26387
|
+
var _g$2, _defs$2;
|
|
26388
|
+
function _extends$3() { return _extends$3 = 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$3.apply(null, arguments); }
|
|
25668
26389
|
var SvgTransformRotate = function SvgTransformRotate(props) {
|
|
25669
|
-
return /*#__PURE__*/React.createElement("svg", _extends$
|
|
26390
|
+
return /*#__PURE__*/React.createElement("svg", _extends$3({
|
|
25670
26391
|
xmlns: "http://www.w3.org/2000/svg",
|
|
25671
26392
|
width: 21,
|
|
25672
26393
|
height: 20,
|
|
25673
26394
|
fill: "none"
|
|
25674
|
-
}, props), _g$
|
|
26395
|
+
}, props), _g$2 || (_g$2 = /*#__PURE__*/React.createElement("g", {
|
|
25675
26396
|
clipPath: "url(#transform-rotate_svg__a)"
|
|
25676
26397
|
}, /*#__PURE__*/React.createElement("path", {
|
|
25677
26398
|
fill: "url(#transform-rotate_svg__b)",
|
|
@@ -25679,7 +26400,7 @@ var SvgTransformRotate = function SvgTransformRotate(props) {
|
|
|
25679
26400
|
}), /*#__PURE__*/React.createElement("path", {
|
|
25680
26401
|
fill: "#fff",
|
|
25681
26402
|
d: "M11.602 14.719a5.447 5.447 0 0 1-6.608-5.116l-.004-.205a.818.818 0 0 1 1.633-.084l.004.084.01.285a3.81 3.81 0 0 0 2.127 3.137l.26.115a3.8 3.8 0 0 0 2.227.184l.276-.071q.29-.088.561-.219l-1.147-.441a.817.817 0 0 1 .587-1.525l2.54.977a.817.817 0 0 1 .316 1.308l.001.001a5.45 5.45 0 0 1-2.783 1.57M8.623 4.548a5.447 5.447 0 0 1 6.608 5.116l.004.204a.818.818 0 0 1-1.633.084l-.004-.084-.01-.284a3.81 3.81 0 0 0-2.127-3.138l-.26-.115a3.8 3.8 0 0 0-2.227-.184l-.276.072q-.29.087-.561.218l1.147.442a.817.817 0 0 1-.586 1.524l-2.542-.976a.817.817 0 0 1-.315-1.309 5.45 5.45 0 0 1 2.782-1.57"
|
|
25682
|
-
}))), _defs$
|
|
26403
|
+
}))), _defs$2 || (_defs$2 = /*#__PURE__*/React.createElement("defs", null, /*#__PURE__*/React.createElement("linearGradient", {
|
|
25683
26404
|
id: "transform-rotate_svg__b",
|
|
25684
26405
|
x1: 17.84,
|
|
25685
26406
|
x2: -1.103,
|
|
@@ -25699,15 +26420,15 @@ var SvgTransformRotate = function SvgTransformRotate(props) {
|
|
|
25699
26420
|
})))));
|
|
25700
26421
|
};
|
|
25701
26422
|
|
|
25702
|
-
var _g$
|
|
25703
|
-
function _extends$
|
|
26423
|
+
var _g$1, _defs$1;
|
|
26424
|
+
function _extends$2() { return _extends$2 = 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$2.apply(null, arguments); }
|
|
25704
26425
|
var SvgTransformScale = function SvgTransformScale(props) {
|
|
25705
|
-
return /*#__PURE__*/React.createElement("svg", _extends$
|
|
26426
|
+
return /*#__PURE__*/React.createElement("svg", _extends$2({
|
|
25706
26427
|
xmlns: "http://www.w3.org/2000/svg",
|
|
25707
26428
|
width: 21,
|
|
25708
26429
|
height: 20,
|
|
25709
26430
|
fill: "none"
|
|
25710
|
-
}, props), _g$
|
|
26431
|
+
}, props), _g$1 || (_g$1 = /*#__PURE__*/React.createElement("g", {
|
|
25711
26432
|
clipPath: "url(#transform-scale_svg__a)"
|
|
25712
26433
|
}, /*#__PURE__*/React.createElement("path", {
|
|
25713
26434
|
fill: "url(#transform-scale_svg__b)",
|
|
@@ -25715,7 +26436,7 @@ var SvgTransformScale = function SvgTransformScale(props) {
|
|
|
25715
26436
|
}), /*#__PURE__*/React.createElement("path", {
|
|
25716
26437
|
fill: "#fff",
|
|
25717
26438
|
d: "M5.078 8.265a.8.8 0 0 0 1.6.001v-.544l5.69 5.69h-.494a.8.8 0 0 0 0 1.6l2.474-.002a.8.8 0 0 0 .311-.062.8.8 0 0 0 .49-.738v-2.474a.8.8 0 1 0-1.6 0v.594L7.81 6.59h.544a.8.8 0 0 0 0-1.6H5.879a.8.8 0 0 0-.8.8z"
|
|
25718
|
-
}))), _defs$
|
|
26439
|
+
}))), _defs$1 || (_defs$1 = /*#__PURE__*/React.createElement("defs", null, /*#__PURE__*/React.createElement("linearGradient", {
|
|
25719
26440
|
id: "transform-scale_svg__b",
|
|
25720
26441
|
x1: 17.84,
|
|
25721
26442
|
x2: -1.103,
|
|
@@ -25735,15 +26456,15 @@ var SvgTransformScale = function SvgTransformScale(props) {
|
|
|
25735
26456
|
})))));
|
|
25736
26457
|
};
|
|
25737
26458
|
|
|
25738
|
-
var _g
|
|
25739
|
-
function _extends$
|
|
26459
|
+
var _g, _defs;
|
|
26460
|
+
function _extends$1() { return _extends$1 = 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$1.apply(null, arguments); }
|
|
25740
26461
|
var SvgTransformTranslate = function SvgTransformTranslate(props) {
|
|
25741
|
-
return /*#__PURE__*/React.createElement("svg", _extends$
|
|
26462
|
+
return /*#__PURE__*/React.createElement("svg", _extends$1({
|
|
25742
26463
|
xmlns: "http://www.w3.org/2000/svg",
|
|
25743
26464
|
width: 20,
|
|
25744
26465
|
height: 20,
|
|
25745
26466
|
fill: "none"
|
|
25746
|
-
}, props), _g
|
|
26467
|
+
}, props), _g || (_g = /*#__PURE__*/React.createElement("g", {
|
|
25747
26468
|
clipPath: "url(#transform-translate_svg__a)"
|
|
25748
26469
|
}, /*#__PURE__*/React.createElement("circle", {
|
|
25749
26470
|
cx: 10,
|
|
@@ -25753,7 +26474,7 @@ var SvgTransformTranslate = function SvgTransformTranslate(props) {
|
|
|
25753
26474
|
}), /*#__PURE__*/React.createElement("path", {
|
|
25754
26475
|
fill: "#fff",
|
|
25755
26476
|
d: "M16.18 10.758a.8.8 0 0 0-.105-1.218l-1.66-1.66a.801.801 0 0 0-1.132 1.132l.383.381h-2.96V6.418l.385.386a.8.8 0 1 0 1.131-1.13l-1.75-1.75a.8.8 0 0 0-1.222.11L7.594 5.689a.8.8 0 0 0 1.131 1.13l.381-.38v2.954H6.137l.381-.38a.8.8 0 1 0-1.13-1.132l-1.752 1.75a.8.8 0 0 0 .106 1.22l1.66 1.66a.801.801 0 0 0 1.132-1.133l-.386-.385h2.958v2.972l-.38-.38a.8.8 0 0 0-1.132 1.131l1.751 1.75a.8.8 0 0 0 1.223-.11l1.654-1.655a.8.8 0 1 0-1.13-1.131l-.386.386v-2.963h2.978l-.385.384a.8.8 0 1 0 1.131 1.131z"
|
|
25756
|
-
}))), _defs
|
|
26477
|
+
}))), _defs || (_defs = /*#__PURE__*/React.createElement("defs", null, /*#__PURE__*/React.createElement("linearGradient", {
|
|
25757
26478
|
id: "transform-translate_svg__b",
|
|
25758
26479
|
x1: 17.727,
|
|
25759
26480
|
x2: -1.215,
|
|
@@ -25788,6 +26509,7 @@ const VisionOffTransformWrapper = ({ data, isSelected = false, isHover = false,
|
|
|
25788
26509
|
// 使用自定义hook管理所有变换逻辑
|
|
25789
26510
|
const { currentPoints, isDragging, isRotating, isScaling, dimensions, initializePoints, calculateSelectionBoxPoints, handleMouseDown, handleRotateStart, handleScaleStart, handleMouseMove, handleMouseUp, containerRef, // 从hook获取containerRef
|
|
25790
26511
|
} = useVisionOffTransform(dataPoints);
|
|
26512
|
+
const [hoverType, setHoverType] = useState(null);
|
|
25791
26513
|
const visionOffData = useMemo(() => {
|
|
25792
26514
|
return {
|
|
25793
26515
|
...data,
|
|
@@ -25948,7 +26670,7 @@ const VisionOffTransformWrapper = ({ data, isSelected = false, isHover = false,
|
|
|
25948
26670
|
}
|
|
25949
26671
|
: {
|
|
25950
26672
|
onMouseDown: handleCancel,
|
|
25951
|
-
}), transform: `translate(${selectionBoxPoints[0].x}, ${selectionBoxPoints[0].y})`, children: jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale})`, children: jsx(SvgTransformDelete, {}) }) })), jsx("g", { className: "control-point rotate-point", ...(platform === PlatformType.H5
|
|
26673
|
+
}), transform: `translate(${selectionBoxPoints[0].x}, ${selectionBoxPoints[0].y})`, children: jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale * (hoverType === HoverType.Delete ? 1.15 : 1)})`, onMouseEnter: () => setHoverType(HoverType.Delete), onMouseLeave: () => setHoverType(null), children: jsx(SvgTransformDelete, {}) }) })), jsx("g", { className: "control-point rotate-point", ...(platform === PlatformType.H5
|
|
25952
26674
|
? {
|
|
25953
26675
|
onTouchStart: createReactEventHandler((e) => {
|
|
25954
26676
|
handleRotateStartWithDisabled(e);
|
|
@@ -25956,7 +26678,7 @@ const VisionOffTransformWrapper = ({ data, isSelected = false, isHover = false,
|
|
|
25956
26678
|
}
|
|
25957
26679
|
: {
|
|
25958
26680
|
onMouseDown: handleRotateStartWithDisabled,
|
|
25959
|
-
}), transform: `translate(${selectionBoxPoints[3].x}, ${selectionBoxPoints[3].y})`, children: jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale})`, children: jsx(SvgTransformRotate, {}) }) }), jsx("g", { className: "control-point move-point", ...(platform === PlatformType.H5
|
|
26681
|
+
}), transform: `translate(${selectionBoxPoints[3].x}, ${selectionBoxPoints[3].y})`, children: jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale * (hoverType === HoverType.Rotate ? 1.15 : 1)})`, onMouseEnter: () => setHoverType(HoverType.Rotate), onMouseLeave: () => setHoverType(null), children: jsx(SvgTransformRotate, {}) }) }), jsx("g", { className: "control-point move-point", ...(platform === PlatformType.H5
|
|
25960
26682
|
? {
|
|
25961
26683
|
onTouchStart: createReactEventHandler((e) => {
|
|
25962
26684
|
handleMouseDownWithDisabled(e);
|
|
@@ -25964,7 +26686,7 @@ const VisionOffTransformWrapper = ({ data, isSelected = false, isHover = false,
|
|
|
25964
26686
|
}
|
|
25965
26687
|
: {
|
|
25966
26688
|
onMouseDown: handleMouseDownWithDisabled,
|
|
25967
|
-
}), transform: `translate(${selectionBoxPoints[1].x}, ${selectionBoxPoints[1].y})`, children: jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale})`, children: jsx(SvgTransformTranslate, {}) }) }), jsx("g", { className: "control-point scale-point", ...(platform === PlatformType.H5
|
|
26689
|
+
}), transform: `translate(${selectionBoxPoints[1].x}, ${selectionBoxPoints[1].y})`, children: jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale * (hoverType === HoverType.Translate ? 1.15 : 1)})`, onMouseEnter: () => setHoverType(HoverType.Translate), onMouseLeave: () => setHoverType(null), children: jsx(SvgTransformTranslate, {}) }) }), jsx("g", { className: "control-point scale-point", ...(platform === PlatformType.H5
|
|
25968
26690
|
? {
|
|
25969
26691
|
onTouchStart: createReactEventHandler((e) => {
|
|
25970
26692
|
handleScaleStartWithDisabled(e);
|
|
@@ -25972,7 +26694,7 @@ const VisionOffTransformWrapper = ({ data, isSelected = false, isHover = false,
|
|
|
25972
26694
|
}
|
|
25973
26695
|
: {
|
|
25974
26696
|
onMouseDown: handleScaleStartWithDisabled,
|
|
25975
|
-
}), transform: `translate(${selectionBoxPoints[2].x}, ${selectionBoxPoints[2].y})`, children: jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale})`, children: jsx(SvgTransformScale, {}) }) })] }));
|
|
26697
|
+
}), transform: `translate(${selectionBoxPoints[2].x}, ${selectionBoxPoints[2].y})`, children: jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale * (hoverType === HoverType.Scale ? 1.15 : 1)})`, onMouseEnter: () => setHoverType(HoverType.Scale), onMouseLeave: () => setHoverType(null), children: jsx(SvgTransformScale, {}) }) })] }));
|
|
25976
26698
|
})()] }));
|
|
25977
26699
|
};
|
|
25978
26700
|
|
|
@@ -26426,100 +27148,9 @@ const useDoodleTransform = (data, onTransformChange, options) => {
|
|
|
26426
27148
|
};
|
|
26427
27149
|
};
|
|
26428
27150
|
|
|
26429
|
-
var
|
|
26430
|
-
|
|
26431
|
-
|
|
26432
|
-
return /*#__PURE__*/React.createElement("svg", _extends$3({
|
|
26433
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
26434
|
-
width: 16,
|
|
26435
|
-
height: 16,
|
|
26436
|
-
fill: "none"
|
|
26437
|
-
}, props), _g$2 || (_g$2 = /*#__PURE__*/React.createElement("g", {
|
|
26438
|
-
clipPath: "url(#disabled-rotate_svg__a)"
|
|
26439
|
-
}, /*#__PURE__*/React.createElement("path", {
|
|
26440
|
-
fill: "#fff",
|
|
26441
|
-
d: "M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0"
|
|
26442
|
-
}), /*#__PURE__*/React.createElement("path", {
|
|
26443
|
-
fill: "#211F1F",
|
|
26444
|
-
fillOpacity: 0.4,
|
|
26445
|
-
d: "M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0"
|
|
26446
|
-
}), /*#__PURE__*/React.createElement("path", {
|
|
26447
|
-
fill: "#fff",
|
|
26448
|
-
fillOpacity: 0.7,
|
|
26449
|
-
d: "M9.192 11.775a4.36 4.36 0 0 1-5.277-3.93l-.013-.326v-.002a.655.655 0 0 1 1.31 0v.002a3.05 3.05 0 0 0 1.71 2.738l.208.091a3.05 3.05 0 0 0 1.781.147l.22-.057q.234-.07.451-.175l-.919-.354a.653.653 0 1 1 .47-1.22l2.033.782a.654.654 0 0 1 .251 1.047l.002.001a4.36 4.36 0 0 1-2.227 1.256M6.809 3.639a4.36 4.36 0 0 1 5.289 4.256v.002a.654.654 0 1 1-1.309 0v-.002a3.05 3.05 0 0 0-1.71-2.738l-.208-.091a3.05 3.05 0 0 0-1.781-.147l-.22.057a3 3 0 0 0-.451.175l.919.354a.654.654 0 0 1-.47 1.22l-2.033-.782a.654.654 0 0 1-.251-1.046l-.002-.002a4.36 4.36 0 0 1 2.227-1.256"
|
|
26450
|
-
}))), _defs$2 || (_defs$2 = /*#__PURE__*/React.createElement("defs", null, /*#__PURE__*/React.createElement("clipPath", {
|
|
26451
|
-
id: "disabled-rotate_svg__a"
|
|
26452
|
-
}, /*#__PURE__*/React.createElement("path", {
|
|
26453
|
-
fill: "#fff",
|
|
26454
|
-
d: "M16 0H0v16h16z"
|
|
26455
|
-
})))));
|
|
26456
|
-
};
|
|
26457
|
-
|
|
26458
|
-
var _g$1, _defs$1;
|
|
26459
|
-
function _extends$2() { return _extends$2 = 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$2.apply(null, arguments); }
|
|
26460
|
-
var SvgDisabledScale = function SvgDisabledScale(props) {
|
|
26461
|
-
return /*#__PURE__*/React.createElement("svg", _extends$2({
|
|
26462
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
26463
|
-
width: 16,
|
|
26464
|
-
height: 16,
|
|
26465
|
-
fill: "none"
|
|
26466
|
-
}, props), _g$1 || (_g$1 = /*#__PURE__*/React.createElement("g", {
|
|
26467
|
-
clipPath: "url(#disabled-scale_svg__a)"
|
|
26468
|
-
}, /*#__PURE__*/React.createElement("path", {
|
|
26469
|
-
fill: "#fff",
|
|
26470
|
-
d: "M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0"
|
|
26471
|
-
}), /*#__PURE__*/React.createElement("path", {
|
|
26472
|
-
fill: "#211F1F",
|
|
26473
|
-
fillOpacity: 0.4,
|
|
26474
|
-
d: "M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0"
|
|
26475
|
-
}), /*#__PURE__*/React.createElement("path", {
|
|
26476
|
-
fill: "#fff",
|
|
26477
|
-
fillOpacity: 0.7,
|
|
26478
|
-
d: "M3.972 6.612a.64.64 0 0 0 1.28 0v-.437l4.554 4.554H9.41a.64.64 0 0 0 0 1.28h1.979a.64.64 0 0 0 .636-.606l.003-.036.001-1.98a.64.64 0 1 0-1.28.001v.474L6.158 5.27h.435a.64.64 0 0 0 0-1.28h-1.98a.64.64 0 0 0-.64.64z"
|
|
26479
|
-
}))), _defs$1 || (_defs$1 = /*#__PURE__*/React.createElement("defs", null, /*#__PURE__*/React.createElement("clipPath", {
|
|
26480
|
-
id: "disabled-scale_svg__a"
|
|
26481
|
-
}, /*#__PURE__*/React.createElement("path", {
|
|
26482
|
-
fill: "#fff",
|
|
26483
|
-
d: "M0 16h16V0H0z"
|
|
26484
|
-
})))));
|
|
26485
|
-
};
|
|
26486
|
-
|
|
26487
|
-
var _g, _defs;
|
|
26488
|
-
function _extends$1() { return _extends$1 = 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$1.apply(null, arguments); }
|
|
26489
|
-
var SvgDisabledTranslate = function SvgDisabledTranslate(props) {
|
|
26490
|
-
return /*#__PURE__*/React.createElement("svg", _extends$1({
|
|
26491
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
26492
|
-
width: 16,
|
|
26493
|
-
height: 16,
|
|
26494
|
-
fill: "none"
|
|
26495
|
-
}, props), _g || (_g = /*#__PURE__*/React.createElement("g", {
|
|
26496
|
-
clipPath: "url(#disabled-translate_svg__a)"
|
|
26497
|
-
}, /*#__PURE__*/React.createElement("circle", {
|
|
26498
|
-
cx: 8,
|
|
26499
|
-
cy: 8,
|
|
26500
|
-
r: 8,
|
|
26501
|
-
fill: "#fff"
|
|
26502
|
-
}), /*#__PURE__*/React.createElement("circle", {
|
|
26503
|
-
cx: 8,
|
|
26504
|
-
cy: 8,
|
|
26505
|
-
r: 8,
|
|
26506
|
-
fill: "#211F1F",
|
|
26507
|
-
fillOpacity: 0.4
|
|
26508
|
-
}), /*#__PURE__*/React.createElement("path", {
|
|
26509
|
-
fill: "#fff",
|
|
26510
|
-
fillOpacity: 0.7,
|
|
26511
|
-
d: "M12.944 8.608a.64.64 0 0 0-.079-.97l-1.333-1.334a.64.64 0 0 0-.905.905l.305.305H8.564V5.136l.308.308a.642.642 0 0 0 .906-.906l-1.4-1.399a.64.64 0 0 0-.906 0q-.036.037-.064.08L6.075 4.55a.64.64 0 0 0 .905.905l.305-.304v2.362H4.91l.306-.306a.64.64 0 0 0-.904-.904l-1.4 1.4a.64.64 0 0 0 .077.97l1.334 1.333a.64.64 0 0 0 .906-.905l-.31-.309h2.367v2.38l-.304-.305a.64.64 0 1 0-.905.906l1.4 1.4a.64.64 0 0 0 .97-.08l1.332-1.333a.64.64 0 0 0-.905-.905l-.309.308v-2.37h2.385l-.31.309a.64.64 0 1 0 .905.904z"
|
|
26512
|
-
}))), _defs || (_defs = /*#__PURE__*/React.createElement("defs", null, /*#__PURE__*/React.createElement("clipPath", {
|
|
26513
|
-
id: "disabled-translate_svg__a"
|
|
26514
|
-
}, /*#__PURE__*/React.createElement("path", {
|
|
26515
|
-
fill: "#fff",
|
|
26516
|
-
d: "M16 0H0v16h16z"
|
|
26517
|
-
})))));
|
|
26518
|
-
};
|
|
26519
|
-
|
|
26520
|
-
var css_248z$5 = ".index-module_doodleHover__jIZHV path {\n fill: #2EC1B2;\n}";
|
|
26521
|
-
var styles$5 = {"doodleHover":"index-module_doodleHover__jIZHV"};
|
|
26522
|
-
styleInject(css_248z$5);
|
|
27151
|
+
var css_248z$6 = ".index-module_doodleHover__jIZHV path {\n fill: #00B3A1;\n}";
|
|
27152
|
+
var styles$6 = {"doodleHover":"index-module_doodleHover__jIZHV"};
|
|
27153
|
+
styleInject(css_248z$6);
|
|
26523
27154
|
|
|
26524
27155
|
const DoodleTransform = ({ data, isSelected: _isSelected, isHover, onSelect }) => {
|
|
26525
27156
|
const { editMapInfo } = useMapEditContext();
|
|
@@ -26578,7 +27209,7 @@ const DoodleTransform = ({ data, isSelected: _isSelected, isHover, onSelect }) =
|
|
|
26578
27209
|
return (jsx("g", { children: jsx("g", { style: {
|
|
26579
27210
|
cursor: canSelect ? 'pointer' : 'inherit',
|
|
26580
27211
|
pointerEvents: canSelect ? 'auto' : 'none',
|
|
26581
|
-
}, className: isHover ? styles$
|
|
27212
|
+
}, className: isHover ? styles$6.doodleHover : '', transform: transform, opacity: data?.style?.opacity || 1, dangerouslySetInnerHTML: { __html: svgString }, onClick: onDoodleClick }) }));
|
|
26582
27213
|
}
|
|
26583
27214
|
}
|
|
26584
27215
|
catch (error) {
|
|
@@ -26588,12 +27219,13 @@ const DoodleTransform = ({ data, isSelected: _isSelected, isHover, onSelect }) =
|
|
|
26588
27219
|
};
|
|
26589
27220
|
|
|
26590
27221
|
const DoodleTransformWrapper = ({ data, isSelected = false, isSelectedWithoutOperation = false, isHover = false, onSelect, onCancel, onTransformChange, disabled = false, className = '', minScale, onMinScaleReached, onDragMove, onDragEnd, showInfo = true, onClickInfo, }) => {
|
|
26591
|
-
const { platform, overlayScale, unitType } = useCommonContext();
|
|
27222
|
+
const { platform, overlayScale, unitType, locale } = useCommonContext();
|
|
26592
27223
|
// 使用自定义hook管理所有变换逻辑
|
|
26593
27224
|
const { currentCenter, currentScale, currentDirection, isDragging, isRotating, isScaling, initializeTransform, calculateSelectionBoxPoints, handleMouseDown, handleRotateStart, handleScaleStart, handleMouseMove, handleMouseUp, containerRef, dimensions, } = useDoodleTransform(data, onTransformChange, {
|
|
26594
27225
|
minScale,
|
|
26595
27226
|
onMinScaleReached,
|
|
26596
27227
|
});
|
|
27228
|
+
const [hoverType, setHoverType] = useState(null);
|
|
26597
27229
|
// 创建变换后的数据对象 - 需要将转换后的坐标转换回原始格式
|
|
26598
27230
|
const transformedData = useMemo(() => {
|
|
26599
27231
|
// 将转换后的坐标转换回原始格式(除以 SCALE_FACTOR 并翻转 Y 轴)
|
|
@@ -26715,7 +27347,6 @@ const DoodleTransformWrapper = ({ data, isSelected = false, isSelectedWithoutOpe
|
|
|
26715
27347
|
const infoBoxHeight = 40 * overlayScale; // 信息框高度
|
|
26716
27348
|
const infoBoxX = centerX - infoBoxWidth / 2; // 居中对齐
|
|
26717
27349
|
const circleScale = platform === PlatformType.H5 ? 1.5 * overlayScale : 2 * overlayScale;
|
|
26718
|
-
const disacledScale = circleScale * 1.2;
|
|
26719
27350
|
return (jsxs(Fragment, { children: [jsx("polygon", { points: selectionBoxPoints.map((point) => `${point.x},${point.y}`).join(' '), fill: "none", stroke: "#B2B4B9", strokeWidth: "2", strokeDasharray: "5,5", vectorEffect: "non-scaling-stroke", ...(platform === PlatformType.H5
|
|
26720
27351
|
? {
|
|
26721
27352
|
onTouchStart: createReactEventHandler((e) => {
|
|
@@ -26724,7 +27355,7 @@ const DoodleTransformWrapper = ({ data, isSelected = false, isSelectedWithoutOpe
|
|
|
26724
27355
|
}
|
|
26725
27356
|
: {
|
|
26726
27357
|
// onMouseDown: handleMouseDownWithDisabled,
|
|
26727
|
-
}) }), jsx("
|
|
27358
|
+
}) }), jsx("circle", { cx: selectionBoxPoints[0].x, cy: selectionBoxPoints[0].y, r: 10 * circleScale, fill: "#B2B4B9", stroke: "none" }), jsx("circle", { cx: selectionBoxPoints[3].x, cy: selectionBoxPoints[3].y, r: 10 * circleScale, fill: "#B2B4B9", stroke: "none" }), jsx("circle", { cx: selectionBoxPoints[1].x, cy: selectionBoxPoints[1].y, r: 10 * circleScale, fill: "#B2B4B9", stroke: "none" }), jsx("circle", { cx: selectionBoxPoints[2].x, cy: selectionBoxPoints[2].y, r: 10 * circleScale, fill: "#B2B4B9", stroke: "none" }), platform === PlatformType.H5 && (jsx("foreignObject", { x: infoBoxX, y: infoBoxY, width: infoBoxWidth, height: infoBoxHeight, style: { textAlign: 'center' }, onClick: () => {
|
|
26728
27359
|
onClickInfo?.();
|
|
26729
27360
|
}, children: jsx("div", { style: {
|
|
26730
27361
|
padding: `5px ${10 * overlayScale}px`,
|
|
@@ -26744,7 +27375,7 @@ const DoodleTransformWrapper = ({ data, isSelected = false, isSelectedWithoutOpe
|
|
|
26744
27375
|
justifyContent: 'center',
|
|
26745
27376
|
gap: '10px',
|
|
26746
27377
|
color: '#325069',
|
|
26747
|
-
}, children: jsxs("div", { children: [
|
|
27378
|
+
}, children: jsxs("div", { children: [locale?.['map.renderer.remainTime'] || 'Remaining time', ": ", remainingTime] }) }) }) }))] }));
|
|
26748
27379
|
};
|
|
26749
27380
|
useEffect(() => {
|
|
26750
27381
|
const isMove = isDragging || isRotating || isScaling;
|
|
@@ -26796,7 +27427,7 @@ const DoodleTransformWrapper = ({ data, isSelected = false, isSelectedWithoutOpe
|
|
|
26796
27427
|
const infoBoxWidth = 300 * overlayScale; // 信息框宽度
|
|
26797
27428
|
const infoBoxHeight = 100 * overlayScale; // 信息框高度
|
|
26798
27429
|
const infoBoxX = centerX - infoBoxWidth / 2; // 居中对齐
|
|
26799
|
-
const scale = platform === PlatformType.H5 ? 1.8 * overlayScale :
|
|
27430
|
+
const scale = platform === PlatformType.H5 ? 1.8 * overlayScale : 3 * overlayScale;
|
|
26800
27431
|
const offsetLeft = 10 * scale;
|
|
26801
27432
|
return (jsxs(Fragment, { children: [showInfo && (jsx("foreignObject", { x: infoBoxX, y: infoBoxY, width: infoBoxWidth, height: infoBoxHeight, style: { textAlign: 'center' }, onClick: () => {
|
|
26802
27433
|
onClickInfo?.();
|
|
@@ -26817,7 +27448,7 @@ const DoodleTransformWrapper = ({ data, isSelected = false, isSelectedWithoutOpe
|
|
|
26817
27448
|
alignItems: 'center',
|
|
26818
27449
|
justifyContent: 'center',
|
|
26819
27450
|
gap: `${10 * overlayScale}px`,
|
|
26820
|
-
}, children: jsxs("div", { children: [
|
|
27451
|
+
}, children: jsxs("div", { children: [locale?.['map.renderer.activeTime'] || 'Active Time', ": ", activeTime, " >"] }) })] }) })), jsx("polygon", { points: selectionBoxPoints.map((point) => `${point.x},${point.y}`).join(' '), fill: "none", stroke: "#9EA6BA", strokeWidth: "2", strokeDasharray: "5,5", vectorEffect: "non-scaling-stroke", style: { cursor: 'move' }, ...(platform === PlatformType.H5
|
|
26821
27452
|
? {
|
|
26822
27453
|
onTouchStart: createReactEventHandler((e) => {
|
|
26823
27454
|
handleMouseDownWithDisabled(e);
|
|
@@ -26833,7 +27464,7 @@ const DoodleTransformWrapper = ({ data, isSelected = false, isSelectedWithoutOpe
|
|
|
26833
27464
|
}
|
|
26834
27465
|
: {
|
|
26835
27466
|
onMouseDown: handleCancel,
|
|
26836
|
-
}), transform: `translate(${selectionBoxPoints[0].x}, ${selectionBoxPoints[0].y})`, children: jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale})`, children: jsx(SvgTransformDelete, {}) }) })), jsx("g", { className: "control-point rotate-point", ...(platform === PlatformType.H5
|
|
27467
|
+
}), transform: `translate(${selectionBoxPoints[0].x}, ${selectionBoxPoints[0].y})`, children: jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale * (hoverType === HoverType.Delete ? 1.15 : 1)})`, onMouseEnter: () => setHoverType(HoverType.Delete), onMouseLeave: () => setHoverType(null), children: jsx(SvgTransformDelete, {}) }) })), jsx("g", { className: "control-point rotate-point", ...(platform === PlatformType.H5
|
|
26837
27468
|
? {
|
|
26838
27469
|
onTouchStart: createReactEventHandler((e) => {
|
|
26839
27470
|
handleRotateStartWithDisabled(e);
|
|
@@ -26841,7 +27472,7 @@ const DoodleTransformWrapper = ({ data, isSelected = false, isSelectedWithoutOpe
|
|
|
26841
27472
|
}
|
|
26842
27473
|
: {
|
|
26843
27474
|
onMouseDown: handleRotateStartWithDisabled,
|
|
26844
|
-
}), transform: `translate(${selectionBoxPoints[3].x}, ${selectionBoxPoints[3].y})`, children: jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale})`, children: jsx(SvgTransformRotate, {}) }) }), jsx("g", { className: "control-point move-point", ...(platform === PlatformType.H5
|
|
27475
|
+
}), transform: `translate(${selectionBoxPoints[3].x}, ${selectionBoxPoints[3].y})`, children: jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale * (hoverType === HoverType.Rotate ? 1.15 : 1)})`, onMouseEnter: () => setHoverType(HoverType.Rotate), onMouseLeave: () => setHoverType(null), children: jsx(SvgTransformRotate, {}) }) }), jsx("g", { className: "control-point move-point", ...(platform === PlatformType.H5
|
|
26845
27476
|
? {
|
|
26846
27477
|
onTouchStart: createReactEventHandler((e) => {
|
|
26847
27478
|
handleMouseDownWithDisabled(e);
|
|
@@ -26849,7 +27480,7 @@ const DoodleTransformWrapper = ({ data, isSelected = false, isSelectedWithoutOpe
|
|
|
26849
27480
|
}
|
|
26850
27481
|
: {
|
|
26851
27482
|
onMouseDown: handleMouseDownWithDisabled,
|
|
26852
|
-
}), transform: `translate(${selectionBoxPoints[1].x}, ${selectionBoxPoints[1].y})`, children: jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale})`, children: jsx(SvgTransformTranslate, {}) }) }), jsx("g", { className: "control-point scale-point", ...(platform === PlatformType.H5
|
|
27483
|
+
}), transform: `translate(${selectionBoxPoints[1].x}, ${selectionBoxPoints[1].y})`, children: jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale * (hoverType === HoverType.Translate ? 1.15 : 1)})`, onMouseEnter: () => setHoverType(HoverType.Translate), onMouseLeave: () => setHoverType(null), children: jsx(SvgTransformTranslate, {}) }) }), jsx("g", { className: "control-point scale-point", ...(platform === PlatformType.H5
|
|
26853
27484
|
? {
|
|
26854
27485
|
onTouchStart: createReactEventHandler((e) => {
|
|
26855
27486
|
handleScaleStartWithDisabled(e);
|
|
@@ -26857,7 +27488,7 @@ const DoodleTransformWrapper = ({ data, isSelected = false, isSelectedWithoutOpe
|
|
|
26857
27488
|
}
|
|
26858
27489
|
: {
|
|
26859
27490
|
onMouseDown: handleScaleStartWithDisabled,
|
|
26860
|
-
}), transform: `translate(${selectionBoxPoints[2].x}, ${selectionBoxPoints[2].y})`, children: jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale})`, children: jsx(SvgTransformScale, {}) }) })] }));
|
|
27491
|
+
}), transform: `translate(${selectionBoxPoints[2].x}, ${selectionBoxPoints[2].y})`, children: jsx("g", { style: { cursor: 'pointer' }, transformOrigin: "center", transform: `translate(-${offsetLeft}, -${offsetLeft}) scale(${scale * (hoverType === HoverType.Scale ? 1.15 : 1)})`, onMouseEnter: () => setHoverType(HoverType.Scale), onMouseLeave: () => setHoverType(null), children: jsx(SvgTransformScale, {}) }) })] }));
|
|
26861
27492
|
})()] }));
|
|
26862
27493
|
};
|
|
26863
27494
|
|
|
@@ -27298,10 +27929,7 @@ const PathElement = forwardRef(({ mapConfig, pathData, mowPartitionData }, ref)
|
|
|
27298
27929
|
return;
|
|
27299
27930
|
}
|
|
27300
27931
|
// 构建路径数据
|
|
27301
|
-
|
|
27302
|
-
for (let i = 1; i < points.length; i++) {
|
|
27303
|
-
pathData += ` L ${points[i][0]} ${points[i][1]}`;
|
|
27304
|
-
}
|
|
27932
|
+
const pathData = getPathData(points);
|
|
27305
27933
|
// 根据路径类型设置不同的颜色
|
|
27306
27934
|
let lineColor;
|
|
27307
27935
|
if (pathType === PathSegmentType.TRANS) {
|
|
@@ -27334,7 +27962,7 @@ const PathElement = forwardRef(({ mapConfig, pathData, mowPartitionData }, ref)
|
|
|
27334
27962
|
const result = [];
|
|
27335
27963
|
partitionTypeGroups.forEach((groupData, groupKey) => {
|
|
27336
27964
|
const { pathData, style } = groupData;
|
|
27337
|
-
if (pathData.length === 0)
|
|
27965
|
+
if (!pathData || pathData.length === 0)
|
|
27338
27966
|
return;
|
|
27339
27967
|
// 从groupKey中提取分区ID
|
|
27340
27968
|
const partitionId = groupKey.split('-')[0];
|
|
@@ -27342,8 +27970,8 @@ const PathElement = forwardRef(({ mapConfig, pathData, mowPartitionData }, ref)
|
|
|
27342
27970
|
const clipPathId = clipPathData[partitionId]?.id;
|
|
27343
27971
|
if (!clipPathId)
|
|
27344
27972
|
return;
|
|
27345
|
-
|
|
27346
|
-
path:
|
|
27973
|
+
result.push({
|
|
27974
|
+
path: pathData.join(' '),
|
|
27347
27975
|
style,
|
|
27348
27976
|
clipPathId: clipPathId,
|
|
27349
27977
|
});
|
|
@@ -27358,7 +27986,7 @@ const PathElement = forwardRef(({ mapConfig, pathData, mowPartitionData }, ref)
|
|
|
27358
27986
|
return (jsxs("g", { id: "path", children: [jsx("defs", { children: Object.values(clipPathData).map((item) => {
|
|
27359
27987
|
return (jsx("clipPath", { id: item.id, clipRule: "evenodd", children: jsx("path", { d: item.path }) }, item.id));
|
|
27360
27988
|
}) }), Object.values(pathDataObj).map((item) => {
|
|
27361
|
-
return (jsx("g", { "clip-path": `url(#${item.clipPathId})`, opacity: "0.5", children: jsx("path", { className: "vector-path", d: item.path, fill: "none", "mix-blend-mode": "normal",
|
|
27989
|
+
return (jsx("g", { "clip-path": `url(#${item.clipPathId})`, opacity: "0.5", children: jsx("path", { className: "vector-path", d: item.path, fill: "none", "mix-blend-mode": "normal", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: item.style.lineWidth, stroke: item.style.lineColor }) }));
|
|
27362
27990
|
})] }));
|
|
27363
27991
|
});
|
|
27364
27992
|
|
|
@@ -27415,9 +28043,9 @@ const SvgMapComponent = forwardRef(({ editMap, pathData, mapConfig = {}, mowPart
|
|
|
27415
28043
|
// 设置显示名称以便调试
|
|
27416
28044
|
SvgMapComponent.displayName = 'SvgMapComponent';
|
|
27417
28045
|
|
|
27418
|
-
var css_248z$
|
|
27419
|
-
var styles$
|
|
27420
|
-
styleInject(css_248z$
|
|
28046
|
+
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}";
|
|
28047
|
+
var styles$5 = {"mowerPosition":"index-module_mowerPosition__yLpIU"};
|
|
28048
|
+
styleInject(css_248z$5);
|
|
27421
28049
|
|
|
27422
28050
|
const usePosition = ({ mowerPositionData, viewBox, modelType, hasEdger, mapData, realTimeData, onMowingPositionChange, }) => {
|
|
27423
28051
|
// DOM 元素引用,用于直接操作
|
|
@@ -27576,10 +28204,10 @@ const usePosition = ({ mowerPositionData, viewBox, modelType, hasEdger, mapData,
|
|
|
27576
28204
|
}
|
|
27577
28205
|
else {
|
|
27578
28206
|
// 检查位置是否有效
|
|
27579
|
-
const positionOutOfRange = isOutOfRange(mowerPositionData);
|
|
28207
|
+
// const positionOutOfRange = isOutOfRange(mowerPositionData);
|
|
27580
28208
|
const positionInvalid = isInvalidPosition(mowerPositionData);
|
|
27581
28209
|
const isStandby = mowerPositionData.vehicleState === RobotStatus.STANDBY;
|
|
27582
|
-
if (
|
|
28210
|
+
if (positionInvalid || isOffLine) {
|
|
27583
28211
|
// 位置无效时,尝试使用上次位置或充电桩位置
|
|
27584
28212
|
updatePositionByLastPosition(mowerPositionData);
|
|
27585
28213
|
}
|
|
@@ -27643,7 +28271,7 @@ const MowerPosition = React__default.memo(({ editMap, mowerPositionData, viewBox
|
|
|
27643
28271
|
zIndex: 1000,
|
|
27644
28272
|
opacity: isHighlight ? SvgMapOpacity.HIGHLIGHT : SvgMapOpacity.UN_HIGHLIGHT,
|
|
27645
28273
|
}), [isHighlight]);
|
|
27646
|
-
return (jsx("div", { className: styles$
|
|
28274
|
+
return (jsx("div", { className: styles$5.mowerPosition, style: containerStyle, children: jsx("div", { ref: elementRef, className: "mower-position", style: {
|
|
27647
28275
|
position: 'absolute',
|
|
27648
28276
|
width: 30,
|
|
27649
28277
|
height: 30,
|
|
@@ -27655,11 +28283,12 @@ const MowerPosition = React__default.memo(({ editMap, mowerPositionData, viewBox
|
|
|
27655
28283
|
}, children: jsx("img", { src: mowerImage, style: { width: '100%', height: '100%', objectFit: 'contain' } }) }) }));
|
|
27656
28284
|
});
|
|
27657
28285
|
|
|
27658
|
-
var css_248z$
|
|
27659
|
-
var styles$
|
|
27660
|
-
styleInject(css_248z$
|
|
28286
|
+
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}";
|
|
28287
|
+
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"};
|
|
28288
|
+
styleInject(css_248z$4);
|
|
27661
28289
|
|
|
27662
28290
|
const useBoundaryLabels = ({ mapData, pathData, unitType, viewBox, mowPartitionData, realTimeData = [], onlyRead = false, }) => {
|
|
28291
|
+
const { locale } = useCommonContext();
|
|
27663
28292
|
const [processStateIsMowing, updateProcessStateIsMowing] = useState(false);
|
|
27664
28293
|
// 处理地图分区边界
|
|
27665
28294
|
useMemo(() => {
|
|
@@ -27699,6 +28328,9 @@ const useBoundaryLabels = ({ mapData, pathData, unitType, viewBox, mowPartitionD
|
|
|
27699
28328
|
return generateBoundaryData(mapData, newPathData);
|
|
27700
28329
|
}
|
|
27701
28330
|
}, [mapData, pathData, realTimeData]);
|
|
28331
|
+
const coverageStr = useMemo(() => {
|
|
28332
|
+
return locale?.['map.renderer.coverage'] || 'Coverage';
|
|
28333
|
+
}, [locale]);
|
|
27702
28334
|
const items = useMemo(() => {
|
|
27703
28335
|
// console.log('boundartItme->', viewBox, mapData)
|
|
27704
28336
|
if (!mapData || !viewBox)
|
|
@@ -27708,7 +28340,7 @@ const useBoundaryLabels = ({ mapData, pathData, unitType, viewBox, mowPartitionD
|
|
|
27708
28340
|
const results = [];
|
|
27709
28341
|
for (const boundary of boundaryData) {
|
|
27710
28342
|
// 多边形重心
|
|
27711
|
-
const centroid = computePolygonCentroid(boundary.points);
|
|
28343
|
+
const centroid = computePolygonCentroid$1(boundary.points);
|
|
27712
28344
|
if (!centroid)
|
|
27713
28345
|
continue;
|
|
27714
28346
|
const relX = (centroid.x - viewBox.x) / viewBox.width;
|
|
@@ -27719,9 +28351,9 @@ const useBoundaryLabels = ({ mapData, pathData, unitType, viewBox, mowPartitionD
|
|
|
27719
28351
|
const baseText = onlyRead ? `${boundary.name}` : `${boundary.name} (${progress})`;
|
|
27720
28352
|
const totalArea = convertAreaByUnits(boundary.area || 0, unitType);
|
|
27721
28353
|
const finishedArea = convertAreaByUnits(boundary.finishedArea || 0, unitType);
|
|
27722
|
-
const coverageText =
|
|
28354
|
+
const coverageText = `${coverageStr}: ${finishedArea.value}/${totalArea.value}`;
|
|
27723
28355
|
const isMowing = mowingIds.includes(boundary.id);
|
|
27724
|
-
const dateText = formatBoundaryDateText(isMowing ? Date.now() / 1000 : boundary.endTime || 0);
|
|
28356
|
+
const dateText = formatBoundaryDateText(isMowing ? Date.now() / 1000 : boundary.endTime || 0, locale);
|
|
27725
28357
|
results.push({
|
|
27726
28358
|
id: boundary.id,
|
|
27727
28359
|
name: boundary.name,
|
|
@@ -27745,11 +28377,13 @@ const useBoundaryLabels = ({ mapData, pathData, unitType, viewBox, mowPartitionD
|
|
|
27745
28377
|
mowPartitionData,
|
|
27746
28378
|
boundaryData,
|
|
27747
28379
|
onlyRead,
|
|
28380
|
+
coverageStr,
|
|
28381
|
+
locale,
|
|
27748
28382
|
]);
|
|
27749
28383
|
const containerZIndex = 900;
|
|
27750
28384
|
return { items, containerZIndex };
|
|
27751
28385
|
};
|
|
27752
|
-
function computePolygonCentroid(points) {
|
|
28386
|
+
function computePolygonCentroid$1(points) {
|
|
27753
28387
|
if (!points || points.length < 3)
|
|
27754
28388
|
return null;
|
|
27755
28389
|
const valid = points.filter((p) => p.length >= 2);
|
|
@@ -27822,7 +28456,7 @@ const BoundaryLabels = React__default.memo(({ editMap, mapData, pathData, unitTy
|
|
|
27822
28456
|
display: editMap ? 'none' : 'block',
|
|
27823
28457
|
opacity: isHighlight ? SvgMapOpacity.HIGHLIGHT : SvgMapOpacity.UN_HIGHLIGHT,
|
|
27824
28458
|
}), [containerZIndex, expandedId, editMap, isHighlight]);
|
|
27825
|
-
return (jsx("div", { className: styles$
|
|
28459
|
+
return (jsx("div", { className: styles$4.container, style: containerStyle, children: items.map((item) => (jsxs("div", { className: `${styles$4.boundaryLabel} boundary-label`, "data-boundary-id": item.id, style: {
|
|
27826
28460
|
transform: `translate(-50%, -50%) rotate(${-rotation}deg)`,
|
|
27827
28461
|
left: `${item.leftPct}%`,
|
|
27828
28462
|
top: `${item.topPct}%`,
|
|
@@ -27833,12 +28467,12 @@ const BoundaryLabels = React__default.memo(({ editMap, mapData, pathData, unitTy
|
|
|
27833
28467
|
if (onlyRead)
|
|
27834
28468
|
return;
|
|
27835
28469
|
handleClick(item.id);
|
|
27836
|
-
}, children: [jsx("div", { className: styles$
|
|
28470
|
+
}, children: [jsx("div", { className: styles$4.base, children: item.baseText }), jsxs("div", { className: styles$4.extended, style: { display: expandedId === item.id ? 'block' : 'none' }, children: [jsx("div", { style: { marginBottom: 3, fontWeight: 'bold' }, children: item.coverageText }), jsx("div", { children: item.dateText })] }), item.isIsolated && (jsx("div", { className: styles$4.isolatedIcon, dangerouslySetInnerHTML: { __html: ISOLATED_BOUNDARY_SVG } }))] }, item.id))) }));
|
|
27837
28471
|
});
|
|
27838
28472
|
|
|
27839
|
-
var css_248z$
|
|
27840
|
-
var styles$
|
|
27841
|
-
styleInject(css_248z$
|
|
28473
|
+
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}";
|
|
28474
|
+
var styles$3 = {"container":"index-module_container__gB52e","item":"index-module_item__zHoZP","icon":"index-module_icon__CS56A","tooltip":"index-module_tooltip__NMCmR"};
|
|
28475
|
+
styleInject(css_248z$3);
|
|
27842
28476
|
|
|
27843
28477
|
var antennaOneOnline = "data:image/png;base64,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";
|
|
27844
28478
|
|
|
@@ -27850,6 +28484,7 @@ var antennaTwoOffline = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgC
|
|
|
27850
28484
|
|
|
27851
28485
|
const useAntennaInfo = (data = [], options = {}) => {
|
|
27852
28486
|
const { viewBox } = options;
|
|
28487
|
+
const { locale } = useCommonContext();
|
|
27853
28488
|
const items = useMemo(() => {
|
|
27854
28489
|
if (!data || data.length === 0 || !viewBox)
|
|
27855
28490
|
return [];
|
|
@@ -27880,8 +28515,8 @@ const useAntennaInfo = (data = [], options = {}) => {
|
|
|
27880
28515
|
isOnline: antenna1.status === 1,
|
|
27881
28516
|
imageSrc: getImage(1, antenna1.status === 1),
|
|
27882
28517
|
tooltip: {
|
|
27883
|
-
title: 'Primary Antenna',
|
|
27884
|
-
statusText: antenna1.status === 1 ? 'Online' : 'Offline',
|
|
28518
|
+
title: locale?.['map.renderer.primaryAntenna'] || 'Primary Antenna',
|
|
28519
|
+
statusText: antenna1.status === 1 ? (locale?.['map.renderer.online'] || 'Online') : (locale?.['map.renderer.online'] || 'Offline'),
|
|
27885
28520
|
minWidth: 130,
|
|
27886
28521
|
},
|
|
27887
28522
|
});
|
|
@@ -27920,15 +28555,15 @@ const useAntennaInfo = (data = [], options = {}) => {
|
|
|
27920
28555
|
isOnline: antenna2.status === 1,
|
|
27921
28556
|
imageSrc: getImage(2, antenna2.status === 1),
|
|
27922
28557
|
tooltip: {
|
|
27923
|
-
title: 'Signal Enhancement Antenna',
|
|
27924
|
-
statusText: antenna2.status === 1 ? 'Online' : 'Offline',
|
|
27925
|
-
syncText: antenna2.status === 1 ? '
|
|
28558
|
+
title: locale?.['map.renderer.signalAntenna'] || 'Signal Enhancement Antenna',
|
|
28559
|
+
statusText: antenna2.status === 1 ? (locale?.['map.renderer.online'] || 'Online') : (locale?.['map.renderer.offline'] || 'Offline'),
|
|
28560
|
+
syncText: antenna2.status === 1 ? `, ${locale?.['map.renderer.synced'] || 'Synced'}` : `, ${locale?.['map.renderer.toBeSynced'] || 'To be synced'}`,
|
|
27926
28561
|
minWidth: 195,
|
|
27927
28562
|
},
|
|
27928
28563
|
});
|
|
27929
28564
|
}
|
|
27930
28565
|
return results;
|
|
27931
|
-
}, [data, viewBox?.x, viewBox?.y, viewBox?.width, viewBox?.height]);
|
|
28566
|
+
}, [data, viewBox?.x, viewBox?.y, viewBox?.width, viewBox?.height, locale]);
|
|
27932
28567
|
// default stacking order (slightly higher than boundary labels' 900)
|
|
27933
28568
|
const containerZIndex = 950;
|
|
27934
28569
|
return { items, containerZIndex };
|
|
@@ -27956,13 +28591,13 @@ const Antennas = React__default.memo(({ editMap, antennaConfig = [], viewBox, ro
|
|
|
27956
28591
|
document.addEventListener('pointerdown', handleOutside);
|
|
27957
28592
|
return () => document.removeEventListener('pointerdown', handleOutside);
|
|
27958
28593
|
}, []);
|
|
27959
|
-
return (jsx("div", { className: styles$
|
|
28594
|
+
return (jsx("div", { className: styles$3.container, style: containerStyle, children: items.map((item) => (jsxs("div", { className: `antenna-container-item ${styles$3.item}`, style: {
|
|
27960
28595
|
left: `${item.leftPct}%`,
|
|
27961
28596
|
top: `${item.topPct}%`,
|
|
27962
28597
|
transform: `translate(calc(-50% + ${item.offsetX || 0}px), calc(-50% + ${item.offsetY || 0}px)) rotate(${-rotation}deg)`,
|
|
27963
28598
|
zIndex: openType === item.type ? 10000 : containerZIndex,
|
|
27964
28599
|
pointerEvents: onlyRead ? 'none' : 'auto',
|
|
27965
|
-
}, children: [jsx("div", { className: `antenna vector-antenna antenna-${item.type} ${item.isOnline ? 'antenna-online' : 'antenna-offline'} ${styles$
|
|
28600
|
+
}, children: [jsx("div", { className: `antenna vector-antenna antenna-${item.type} ${item.isOnline ? 'antenna-online' : 'antenna-offline'} ${styles$3.icon}`, style: {}, onClick: (e) => {
|
|
27966
28601
|
e.stopPropagation();
|
|
27967
28602
|
if (onlyRead)
|
|
27968
28603
|
return;
|
|
@@ -27977,95 +28612,15 @@ const Antennas = React__default.memo(({ editMap, antennaConfig = [], viewBox, ro
|
|
|
27977
28612
|
return;
|
|
27978
28613
|
e.currentTarget.style.transform =
|
|
27979
28614
|
'scale(1)';
|
|
27980
|
-
}, children: jsx("img", { src: item.imageSrc, style: { width: '100%', height: '100%', objectFit: 'contain', opacity: 1 } }) }), jsxs("div", { className: styles$
|
|
28615
|
+
}, children: jsx("img", { src: item.imageSrc, style: { width: '100%', height: '100%', objectFit: 'contain', opacity: 1 } }) }), jsxs("div", { className: styles$3.tooltip, style: {
|
|
27981
28616
|
display: openType === item.type ? 'block' : 'none',
|
|
27982
28617
|
minWidth: item.tooltip.minWidth,
|
|
27983
28618
|
}, children: [jsx("div", { style: { marginBottom: 4, fontWeight: 600 }, children: item.tooltip.title }), jsxs("div", { style: { color: 'rgba(65, 93, 116, 1)', fontWeight: 400 }, children: [item.tooltip.statusText, item.tooltip.syncText || ''] })] })] }, `antenna-${item.type}`))) }));
|
|
27984
28619
|
});
|
|
27985
28620
|
|
|
27986
|
-
var css_248z$
|
|
27987
|
-
var styles$
|
|
27988
|
-
styleInject(css_248z$
|
|
27989
|
-
|
|
27990
|
-
var classnames = {exports: {}};
|
|
27991
|
-
|
|
27992
|
-
/*!
|
|
27993
|
-
Copyright (c) 2018 Jed Watson.
|
|
27994
|
-
Licensed under the MIT License (MIT), see
|
|
27995
|
-
http://jedwatson.github.io/classnames
|
|
27996
|
-
*/
|
|
27997
|
-
|
|
27998
|
-
(function (module) {
|
|
27999
|
-
/* global define */
|
|
28000
|
-
|
|
28001
|
-
(function () {
|
|
28002
|
-
|
|
28003
|
-
var hasOwn = {}.hasOwnProperty;
|
|
28004
|
-
|
|
28005
|
-
function classNames () {
|
|
28006
|
-
var classes = '';
|
|
28007
|
-
|
|
28008
|
-
for (var i = 0; i < arguments.length; i++) {
|
|
28009
|
-
var arg = arguments[i];
|
|
28010
|
-
if (arg) {
|
|
28011
|
-
classes = appendClass(classes, parseValue(arg));
|
|
28012
|
-
}
|
|
28013
|
-
}
|
|
28014
|
-
|
|
28015
|
-
return classes;
|
|
28016
|
-
}
|
|
28017
|
-
|
|
28018
|
-
function parseValue (arg) {
|
|
28019
|
-
if (typeof arg === 'string' || typeof arg === 'number') {
|
|
28020
|
-
return arg;
|
|
28021
|
-
}
|
|
28022
|
-
|
|
28023
|
-
if (typeof arg !== 'object') {
|
|
28024
|
-
return '';
|
|
28025
|
-
}
|
|
28026
|
-
|
|
28027
|
-
if (Array.isArray(arg)) {
|
|
28028
|
-
return classNames.apply(null, arg);
|
|
28029
|
-
}
|
|
28030
|
-
|
|
28031
|
-
if (arg.toString !== Object.prototype.toString && !arg.toString.toString().includes('[native code]')) {
|
|
28032
|
-
return arg.toString();
|
|
28033
|
-
}
|
|
28034
|
-
|
|
28035
|
-
var classes = '';
|
|
28036
|
-
|
|
28037
|
-
for (var key in arg) {
|
|
28038
|
-
if (hasOwn.call(arg, key) && arg[key]) {
|
|
28039
|
-
classes = appendClass(classes, key);
|
|
28040
|
-
}
|
|
28041
|
-
}
|
|
28042
|
-
|
|
28043
|
-
return classes;
|
|
28044
|
-
}
|
|
28045
|
-
|
|
28046
|
-
function appendClass (value, newClass) {
|
|
28047
|
-
if (!newClass) {
|
|
28048
|
-
return value;
|
|
28049
|
-
}
|
|
28050
|
-
|
|
28051
|
-
if (value) {
|
|
28052
|
-
return value + ' ' + newClass;
|
|
28053
|
-
}
|
|
28054
|
-
|
|
28055
|
-
return value + newClass;
|
|
28056
|
-
}
|
|
28057
|
-
|
|
28058
|
-
if (module.exports) {
|
|
28059
|
-
classNames.default = classNames;
|
|
28060
|
-
module.exports = classNames;
|
|
28061
|
-
} else {
|
|
28062
|
-
window.classNames = classNames;
|
|
28063
|
-
}
|
|
28064
|
-
}());
|
|
28065
|
-
} (classnames));
|
|
28066
|
-
|
|
28067
|
-
var classnamesExports = classnames.exports;
|
|
28068
|
-
var classNames = /*@__PURE__*/getDefaultExportFromCjs(classnamesExports);
|
|
28621
|
+
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}";
|
|
28622
|
+
var styles$2 = {"mapEdit":"index-module_mapEdit__XC5Gw","createMode":"index-module_createMode__14EPH","notCreateCursor":"index-module_notCreateCursor__1-OjF"};
|
|
28623
|
+
styleInject(css_248z$2);
|
|
28069
28624
|
|
|
28070
28625
|
/******************************************************************************
|
|
28071
28626
|
Copyright (c) Microsoft Corporation.
|
|
@@ -29065,7 +29620,7 @@ const CreateObstacleElement = forwardRef(({ enabled = false, svgElement, onPoint
|
|
|
29065
29620
|
if (!enabled || points.length === 0) {
|
|
29066
29621
|
return null;
|
|
29067
29622
|
}
|
|
29068
|
-
return (jsx(PolygonElement, { points: points.map((p) => [p.x, p.y, 2]), fillColor: styles.fillColor, fillOpacity: 0.3, strokeColor: styles.strokeColor, strokeWidth: 1, strokeOpacity: 1, createMode: true, showPoints: true, editMode: false, completed: true, draggable: false }));
|
|
29623
|
+
return (jsx(PolygonElement, { points: points.map((p) => [p.x, p.y, 2]), fillColor: styles.fillColor, fillOpacity: 0.3, strokeColor: styles.strokeColor, strokeWidth: 1.8, strokeOpacity: 1, createMode: true, showPoints: true, editMode: false, completed: true, draggable: false }));
|
|
29069
29624
|
});
|
|
29070
29625
|
CreateObstacleElement.displayName = 'CreateObstacleElement';
|
|
29071
29626
|
|
|
@@ -29103,7 +29658,7 @@ const useCreateVisionOffElement = () => {
|
|
|
29103
29658
|
[maxX, minY],
|
|
29104
29659
|
];
|
|
29105
29660
|
return elementPoints;
|
|
29106
|
-
}, [centerPoint, elementWidth, editMapInfo.allVisionOffPoints]);
|
|
29661
|
+
}, [centerPoint, elementWidth, editMapInfo.allVisionOffPoints, svgViewBox]);
|
|
29107
29662
|
return {
|
|
29108
29663
|
centerPoint,
|
|
29109
29664
|
getVisionOffPoints,
|
|
@@ -29338,9 +29893,9 @@ const SvgEditMap = forwardRef(({ mapConfig, onEditInfoMapChange, editMap }, ref)
|
|
|
29338
29893
|
const onMouseLeave = () => {
|
|
29339
29894
|
setHoverDataId('');
|
|
29340
29895
|
};
|
|
29341
|
-
return (jsx("div", { ref: containerRef, className: classNames(styles$
|
|
29342
|
-
[styles$
|
|
29343
|
-
[styles$
|
|
29896
|
+
return (jsx("div", { ref: containerRef, className: classNames(styles$2.mapEdit, {
|
|
29897
|
+
[styles$2.createMode]: isCreating,
|
|
29898
|
+
[styles$2.notCreateCursor]: showNotCreateCursor,
|
|
29344
29899
|
}),
|
|
29345
29900
|
// style={{
|
|
29346
29901
|
// position: 'absolute',
|
|
@@ -29446,6 +30001,22 @@ const SvgEditMap = forwardRef(({ mapConfig, onEditInfoMapChange, editMap }, ref)
|
|
|
29446
30001
|
});
|
|
29447
30002
|
SvgEditMap.displayName = 'SvgEditMap';
|
|
29448
30003
|
|
|
30004
|
+
/** 清理为合法的 SVG/HTML id 片段 */
|
|
30005
|
+
function sanitizeSvgIdPart(value) {
|
|
30006
|
+
return value.replace(/[^a-zA-Z0-9_.-]/g, '_');
|
|
30007
|
+
}
|
|
30008
|
+
/**
|
|
30009
|
+
* 通道 clipPath 的 SVG id
|
|
30010
|
+
* 组合 sn、useId、时间戳、随机数,避免同页多地图实例 id 冲突
|
|
30011
|
+
*/
|
|
30012
|
+
function createChannelClipPathId(params) {
|
|
30013
|
+
const snPart = sanitizeSvgIdPart(params.sn || 'map');
|
|
30014
|
+
const reactPart = sanitizeSvgIdPart(params.reactId.replace(/:/g, ''));
|
|
30015
|
+
const timestamp = Date.now();
|
|
30016
|
+
const randomPart = Math.random().toString(36).slice(2, 10);
|
|
30017
|
+
return `channel-clip-path-${snPart}-${reactPart}-${timestamp}-${randomPart}`;
|
|
30018
|
+
}
|
|
30019
|
+
|
|
29449
30020
|
var _path;
|
|
29450
30021
|
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); }
|
|
29451
30022
|
var SvgMobileAddPoint = function SvgMobileAddPoint(props) {
|
|
@@ -29463,9 +30034,9 @@ var SvgMobileAddPoint = function SvgMobileAddPoint(props) {
|
|
|
29463
30034
|
})));
|
|
29464
30035
|
};
|
|
29465
30036
|
|
|
29466
|
-
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}";
|
|
29467
|
-
var styles = {"crosshair":"index-module_crosshair__etey6"};
|
|
29468
|
-
styleInject(css_248z);
|
|
30037
|
+
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}";
|
|
30038
|
+
var styles$1 = {"crosshair":"index-module_crosshair__etey6"};
|
|
30039
|
+
styleInject(css_248z$1);
|
|
29469
30040
|
|
|
29470
30041
|
/**
|
|
29471
30042
|
* 十字准心组件
|
|
@@ -29482,19 +30053,19 @@ const Crosshair = () => {
|
|
|
29482
30053
|
}
|
|
29483
30054
|
return false;
|
|
29484
30055
|
}, [platform, editMapInfo]);
|
|
29485
|
-
return isVisible ? (jsx("div", { className: styles.crosshair, children: jsx(SvgMobileAddPoint, {}) })) : null;
|
|
30056
|
+
return isVisible ? (jsx("div", { className: styles$1.crosshair, children: jsx(SvgMobileAddPoint, {}) })) : null;
|
|
29486
30057
|
};
|
|
29487
30058
|
|
|
29488
|
-
const WGS84 = 'EPSG:4326';
|
|
29489
|
-
const WEB_MERCATOR = 'EPSG:3857';
|
|
29490
|
-
proj4.defs(WGS84, '+proj=longlat +datum=WGS84 +no_defs');
|
|
29491
|
-
proj4.defs(WEB_MERCATOR, '+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs');
|
|
30059
|
+
const WGS84$1 = 'EPSG:4326';
|
|
30060
|
+
const WEB_MERCATOR$1 = 'EPSG:3857';
|
|
30061
|
+
proj4.defs(WGS84$1, '+proj=longlat +datum=WGS84 +no_defs');
|
|
30062
|
+
proj4.defs(WEB_MERCATOR$1, '+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs');
|
|
29492
30063
|
// 默认配置
|
|
29493
30064
|
const defaultMapConfig$1 = DEFAULT_STYLES;
|
|
29494
30065
|
// 地图渲染器组件
|
|
29495
30066
|
const MowerMapRenderer = forwardRef(({ sn, mowerIconConfig = {}, platform = PlatformType.WEB, mapType = MapType.NORMAL, edger = false, unitType = UnitsType.Imperial, language = 'en', onlyRead = false, isHighlight = true, mapConfig,
|
|
29496
30067
|
// pathConfig,
|
|
29497
|
-
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) => {
|
|
30068
|
+
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) => {
|
|
29498
30069
|
const [currentError, setCurrentError] = useState(null);
|
|
29499
30070
|
// const mapRef = useMap();
|
|
29500
30071
|
const [processStateIsMowing, setProcessStateIsMowing] = useState(false);
|
|
@@ -29522,6 +30093,8 @@ modelType, showStraddleBoundaryBorder = true, mapRef, mapJson, pathJson, realTim
|
|
|
29522
30093
|
...INIT_EDIT_MAP_INFO,
|
|
29523
30094
|
});
|
|
29524
30095
|
const [mowingPartitions, setMowingPartitions] = useState([]);
|
|
30096
|
+
const reactInstanceId = useId();
|
|
30097
|
+
const channelClipPathId = useMemo(() => createChannelClipPathId({ sn, reactId: reactInstanceId }), [sn, reactInstanceId]);
|
|
29525
30098
|
const minDistance = useMemo(() => {
|
|
29526
30099
|
return getMinSvgDistanceByModel(modelType);
|
|
29527
30100
|
}, [modelType]);
|
|
@@ -29612,7 +30185,6 @@ modelType, showStraddleBoundaryBorder = true, mapRef, mapJson, pathJson, realTim
|
|
|
29612
30185
|
const newSvgElementDatas = { ...svgElementDatas };
|
|
29613
30186
|
Object.keys(newSvgElementDatas).forEach((key) => {
|
|
29614
30187
|
if (key === DataType.OBSTACLE) {
|
|
29615
|
-
console.error('newSvgElementDatas[key] 000---->', newSvgElementDatas[key]);
|
|
29616
30188
|
newSvgElementDatas[key] =
|
|
29617
30189
|
newSvgElementDatas[key].filter((item) => {
|
|
29618
30190
|
return (item.status === 1 &&
|
|
@@ -29655,13 +30227,25 @@ modelType, showStraddleBoundaryBorder = true, mapRef, mapJson, pathJson, realTim
|
|
|
29655
30227
|
const mowerPositionData = useMemo(() => {
|
|
29656
30228
|
// realTimeData 中包含三个种类的数据,之需要实时坐标的数据即可。
|
|
29657
30229
|
// 在初始的状态按照正常图标进行渲染,所以构造一个基本在rtk桩的一个数据
|
|
29658
|
-
if (!realTimeData || realTimeData.length === 0)
|
|
29659
|
-
|
|
29660
|
-
|
|
29661
|
-
|
|
29662
|
-
|
|
29663
|
-
|
|
29664
|
-
|
|
30230
|
+
if (!realTimeData || realTimeData.length === 0) {
|
|
30231
|
+
const chargingPiles = svgElementDatas?.[DataType.CHARGING_PILE]?.[0];
|
|
30232
|
+
if (chargingPiles?.position) {
|
|
30233
|
+
return {
|
|
30234
|
+
postureX: chargingPiles.position?.[0],
|
|
30235
|
+
postureY: chargingPiles.position?.[1],
|
|
30236
|
+
postureTheta: chargingPiles.direction - Math.PI || 0,
|
|
30237
|
+
vehicleState: RobotStatus.PARKED,
|
|
30238
|
+
};
|
|
30239
|
+
}
|
|
30240
|
+
else {
|
|
30241
|
+
return {
|
|
30242
|
+
postureX: 0.01,
|
|
30243
|
+
postureY: 0.1,
|
|
30244
|
+
postureTheta: 0.01,
|
|
30245
|
+
vehicleState: RobotStatus.PARKED,
|
|
30246
|
+
};
|
|
30247
|
+
}
|
|
30248
|
+
}
|
|
29665
30249
|
let currentPositionData;
|
|
29666
30250
|
if (realTimeData.length === 1 && realTimeData[0].type === RealTimeDataType.LOCATION) {
|
|
29667
30251
|
currentPositionData = realTimeData[0];
|
|
@@ -29705,14 +30289,14 @@ modelType, showStraddleBoundaryBorder = true, mapRef, mapJson, pathJson, realTim
|
|
|
29705
30289
|
const validBounds = getValidGpsBounds(mapJson, 0);
|
|
29706
30290
|
const [swLng0, swLat0] = validBounds.sw;
|
|
29707
30291
|
const [neLng0, neLat0] = validBounds.ne;
|
|
29708
|
-
const [swX0, swY0] = proj4(WGS84, WEB_MERCATOR, [swLng0, swLat0]);
|
|
29709
|
-
const [neX0, neY0] = proj4(WGS84, WEB_MERCATOR, [neLng0, neLat0]);
|
|
30292
|
+
const [swX0, swY0] = proj4(WGS84$1, WEB_MERCATOR$1, [swLng0, swLat0]);
|
|
30293
|
+
const [neX0, neY0] = proj4(WGS84$1, WEB_MERCATOR$1, [neLng0, neLat0]);
|
|
29710
30294
|
const swX = swX0 + drag.x;
|
|
29711
30295
|
const swY = swY0 + drag.y;
|
|
29712
30296
|
const neX = neX0 + drag.x;
|
|
29713
30297
|
const neY = neY0 + drag.y;
|
|
29714
|
-
const [swLng, swLat] = proj4(WEB_MERCATOR, WGS84, [swX, swY]);
|
|
29715
|
-
const [neLng, neLat] = proj4(WEB_MERCATOR, WGS84, [neX, neY]);
|
|
30298
|
+
const [swLng, swLat] = proj4(WEB_MERCATOR$1, WGS84$1, [swX, swY]);
|
|
30299
|
+
const [neLng, neLat] = proj4(WEB_MERCATOR$1, WGS84$1, [neX, neY]);
|
|
29716
30300
|
return new window.google.maps.LatLngBounds(new window.google.maps.LatLng(swLat, swLng), new window.google.maps.LatLng(neLat, neLng));
|
|
29717
30301
|
}, [mapJson, drag.x, drag.y]);
|
|
29718
30302
|
const commonValue = useMemo(() => {
|
|
@@ -29740,7 +30324,11 @@ modelType, showStraddleBoundaryBorder = true, mapRef, mapJson, pathJson, realTim
|
|
|
29740
30324
|
showStraddleBoundaryBorder,
|
|
29741
30325
|
overlayLayout,
|
|
29742
30326
|
googleMapStaticApiKey,
|
|
30327
|
+
googleMapStaticUrlSigningSecret,
|
|
30328
|
+
locale,
|
|
30329
|
+
signGoogleStaticMapUrl,
|
|
29743
30330
|
onH5FirstSelectObstaclePoint,
|
|
30331
|
+
channelClipPathId,
|
|
29744
30332
|
};
|
|
29745
30333
|
}, [
|
|
29746
30334
|
sn,
|
|
@@ -29763,13 +30351,17 @@ modelType, showStraddleBoundaryBorder = true, mapRef, mapJson, pathJson, realTim
|
|
|
29763
30351
|
showStraddleBoundaryBorder,
|
|
29764
30352
|
overlayLayout,
|
|
29765
30353
|
googleMapStaticApiKey,
|
|
30354
|
+
googleMapStaticUrlSigningSecret,
|
|
30355
|
+
locale,
|
|
30356
|
+
signGoogleStaticMapUrl,
|
|
29766
30357
|
onH5FirstSelectObstaclePoint,
|
|
30358
|
+
channelClipPathId,
|
|
29767
30359
|
]);
|
|
29768
30360
|
/**
|
|
29769
30361
|
* 这里做了一个配置,以防以后如果有多种不同的类型的情况,需要展示不同的元素
|
|
29770
30362
|
*/
|
|
29771
30363
|
const mapHasElements = useMemo(() => {
|
|
29772
|
-
//
|
|
30364
|
+
// 地理围栏地图,有充电桩,没有天线,没有割草机位置,没有分区名称气泡
|
|
29773
30365
|
if (mapType === MapType.GEO_FENCE) {
|
|
29774
30366
|
return {
|
|
29775
30367
|
hasChargingPile: true,
|
|
@@ -29800,14 +30392,14 @@ modelType, showStraddleBoundaryBorder = true, mapRef, mapJson, pathJson, realTim
|
|
|
29800
30392
|
// 地图数据中的坐标格式是 [longitude, latitude]
|
|
29801
30393
|
const [swLng0, swLat0] = validBounds.sw;
|
|
29802
30394
|
const [neLng0, neLat0] = validBounds.ne;
|
|
29803
|
-
const [swX0, swY0] = proj4(WGS84, WEB_MERCATOR, [swLng0, swLat0]);
|
|
29804
|
-
const [neX0, neY0] = proj4(WGS84, WEB_MERCATOR, [neLng0, neLat0]);
|
|
30395
|
+
const [swX0, swY0] = proj4(WGS84$1, WEB_MERCATOR$1, [swLng0, swLat0]);
|
|
30396
|
+
const [neX0, neY0] = proj4(WGS84$1, WEB_MERCATOR$1, [neLng0, neLat0]);
|
|
29805
30397
|
const swX = swX0 + defaultTransform.x;
|
|
29806
30398
|
const swY = swY0 + defaultTransform.y;
|
|
29807
30399
|
const neX = neX0 + defaultTransform.x;
|
|
29808
30400
|
const neY = neY0 + defaultTransform.y;
|
|
29809
|
-
const [swLng, swLat] = proj4(WEB_MERCATOR, WGS84, [swX, swY]);
|
|
29810
|
-
const [neLng, neLat] = proj4(WEB_MERCATOR, WGS84, [neX, neY]);
|
|
30401
|
+
const [swLng, swLat] = proj4(WEB_MERCATOR$1, WGS84$1, [swX, swY]);
|
|
30402
|
+
const [neLng, neLat] = proj4(WEB_MERCATOR$1, WGS84$1, [neX, neY]);
|
|
29811
30403
|
const googleBounds = new window.google.maps.LatLngBounds(new window.google.maps.LatLng(swLat, swLng), // 西南角
|
|
29812
30404
|
new window.google.maps.LatLng(neLat, neLng) // 东北角
|
|
29813
30405
|
);
|
|
@@ -29834,8 +30426,8 @@ modelType, showStraddleBoundaryBorder = true, mapRef, mapJson, pathJson, realTim
|
|
|
29834
30426
|
const offsetLatLng = overlayProjection.fromDivPixelToLatLng(offsetPixel);
|
|
29835
30427
|
if (!offsetLatLng)
|
|
29836
30428
|
return;
|
|
29837
|
-
const [cx, cy] = proj4(WGS84, WEB_MERCATOR, [centerLatLng.lng(), centerLatLng.lat()]);
|
|
29838
|
-
const [ox, oy] = proj4(WGS84, WEB_MERCATOR, [offsetLatLng.lng(), offsetLatLng.lat()]);
|
|
30429
|
+
const [cx, cy] = proj4(WGS84$1, WEB_MERCATOR$1, [centerLatLng.lng(), centerLatLng.lat()]);
|
|
30430
|
+
const [ox, oy] = proj4(WGS84$1, WEB_MERCATOR$1, [offsetLatLng.lng(), offsetLatLng.lat()]);
|
|
29839
30431
|
const dx = ox - cx;
|
|
29840
30432
|
const dy = oy - cy;
|
|
29841
30433
|
setDrag((prev) => ({
|
|
@@ -29878,7 +30470,10 @@ modelType, showStraddleBoundaryBorder = true, mapRef, mapJson, pathJson, realTim
|
|
|
29878
30470
|
// 如果当前是taskDelay的状态,或者状态为mowing或者standby,则指定的地块需要高亮,或者全局高亮
|
|
29879
30471
|
if ((isTaskDelayRef.current ||
|
|
29880
30472
|
positionData?.vehicleState === RobotStatus.MOWING ||
|
|
29881
|
-
positionData?.vehicleState === RobotStatus.STANDBY
|
|
30473
|
+
positionData?.vehicleState === RobotStatus.STANDBY ||
|
|
30474
|
+
(!positionData &&
|
|
30475
|
+
(currentVehicleStateRef.current === RobotStatus.MOWING ||
|
|
30476
|
+
currentVehicleStateRef.current === RobotStatus.STANDBY))) &&
|
|
29882
30477
|
mowPartitionDataRef.current &&
|
|
29883
30478
|
!mowPartitionDataRef.current?.partitionIds) {
|
|
29884
30479
|
// 设置全局高亮
|
|
@@ -30105,7 +30700,9 @@ MowerMapRenderer.displayName = 'MowerMapRenderer';
|
|
|
30105
30700
|
|
|
30106
30701
|
// 默认配置
|
|
30107
30702
|
const defaultMapConfig = DEFAULT_STYLES;
|
|
30108
|
-
const BoundarySvgRender = React__default.memo(({ mapJson, unStructMapData, id, maxWidth = 300, mapConfig, sn }) => {
|
|
30703
|
+
const BoundarySvgRender = React__default.memo(({ mapJson, unStructMapData, id, maxWidth = 300, maxHeight, boundaryPadding = 50, mapConfig, sn, }) => {
|
|
30704
|
+
const reactInstanceId = useId();
|
|
30705
|
+
const channelClipPathId = useMemo(() => createChannelClipPathId({ sn, reactId: reactInstanceId }), [sn, reactInstanceId]);
|
|
30109
30706
|
// 合并配置
|
|
30110
30707
|
const mergedMapConfig = useMemo(() => {
|
|
30111
30708
|
return merge$1(defaultMapConfig, mapConfig);
|
|
@@ -30151,7 +30748,7 @@ const BoundarySvgRender = React__default.memo(({ mapJson, unStructMapData, id, m
|
|
|
30151
30748
|
maxY = Math.max(maxY, point[1]);
|
|
30152
30749
|
});
|
|
30153
30750
|
// 添加边距
|
|
30154
|
-
const padding =
|
|
30751
|
+
const padding = boundaryPadding;
|
|
30155
30752
|
const width = maxX - minX + padding * 2;
|
|
30156
30753
|
const height = maxY - minY + padding * 2;
|
|
30157
30754
|
return {
|
|
@@ -30160,25 +30757,40 @@ const BoundarySvgRender = React__default.memo(({ mapJson, unStructMapData, id, m
|
|
|
30160
30757
|
width,
|
|
30161
30758
|
height,
|
|
30162
30759
|
};
|
|
30163
|
-
}, [boundaryInfo]);
|
|
30760
|
+
}, [boundaryInfo, boundaryPadding]);
|
|
30164
30761
|
const commonValue = useMemo(() => {
|
|
30165
30762
|
return {
|
|
30166
30763
|
sn,
|
|
30167
30764
|
svgViewBox: boundaryViewBox,
|
|
30765
|
+
channelClipPathId,
|
|
30168
30766
|
};
|
|
30169
|
-
}, [sn, boundaryViewBox]);
|
|
30767
|
+
}, [sn, boundaryViewBox, channelClipPathId]);
|
|
30170
30768
|
const style = useMemo(() => {
|
|
30171
|
-
if (
|
|
30769
|
+
if (!maxHeight) {
|
|
30770
|
+
if (boundaryViewBox?.width > boundaryViewBox?.height) {
|
|
30771
|
+
return {
|
|
30772
|
+
width: maxWidth,
|
|
30773
|
+
height: maxWidth * (boundaryViewBox.height / boundaryViewBox.width),
|
|
30774
|
+
};
|
|
30775
|
+
}
|
|
30776
|
+
return {
|
|
30777
|
+
width: maxWidth * (boundaryViewBox.width / boundaryViewBox.height),
|
|
30778
|
+
height: maxWidth,
|
|
30779
|
+
};
|
|
30780
|
+
}
|
|
30781
|
+
const ratio = maxWidth / maxHeight;
|
|
30782
|
+
const boundaryRatio = boundaryViewBox.width / boundaryViewBox.height;
|
|
30783
|
+
if (ratio > boundaryRatio) {
|
|
30172
30784
|
return {
|
|
30173
|
-
width:
|
|
30174
|
-
height:
|
|
30785
|
+
width: Math.floor(maxHeight * boundaryRatio),
|
|
30786
|
+
height: maxHeight,
|
|
30175
30787
|
};
|
|
30176
30788
|
}
|
|
30177
30789
|
return {
|
|
30178
|
-
width: maxWidth
|
|
30179
|
-
height: maxWidth,
|
|
30790
|
+
width: maxWidth,
|
|
30791
|
+
height: Math.floor(maxWidth / boundaryRatio),
|
|
30180
30792
|
};
|
|
30181
|
-
}, [boundaryViewBox, maxWidth]);
|
|
30793
|
+
}, [boundaryViewBox, maxWidth, maxHeight]);
|
|
30182
30794
|
return (jsx(CommonContextProvider, { value: commonValue, children: jsx(SvgEditContextProvider, { value: svgEditValue, children: jsxs("div", { style: {
|
|
30183
30795
|
position: 'relative',
|
|
30184
30796
|
width: style.width,
|
|
@@ -30194,4 +30806,1510 @@ const BoundarySvgRender = React__default.memo(({ mapJson, unStructMapData, id, m
|
|
|
30194
30806
|
}) }), jsx(CharginPile, { viewBox: boundaryViewBox || null, rotation: 0 })] }) }) }));
|
|
30195
30807
|
});
|
|
30196
30808
|
|
|
30197
|
-
|
|
30809
|
+
const ISOLATED_BOUNDARY_SVG_2 = `<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
30810
|
+
<rect width="20" height="20" rx="10" fill="#8498A9" fill-opacity="0.7"/>
|
|
30811
|
+
<path d="M11.7178 12.4307C12.2201 11.9284 12.9629 11.8149 13.5752 12.0879L12.9756 12.6865C12.7427 12.6649 12.5034 12.7432 12.3223 12.917C12.3176 12.9214 12.3122 12.9251 12.3076 12.9297L11.7344 13.5029C11.4091 13.8284 11.409 14.3563 11.7344 14.6816L12.0879 15.0342C12.4133 15.3595 12.9412 15.3596 13.2666 15.0342L13.5527 14.7461L13.8408 14.46C14.011 14.2896 14.091 14.0629 14.083 13.8398L14.7002 13.2236C14.9614 13.8318 14.8443 14.5639 14.3477 15.0605L13.8613 15.5459C13.2105 16.1963 12.1556 16.1964 11.5049 15.5459L11.2314 15.2725C10.5816 14.6218 10.5815 13.5677 11.2314 12.917L11.7178 12.4307Z" fill="white"/>
|
|
30812
|
+
<path d="M12.1738 4.00488C11.6587 4.98031 10.6742 7.45647 10.8555 9.55762C11.3152 9.02665 12.5281 7.88542 13.7002 7.56934C13.2369 8.10318 12.5313 9.25837 12.1445 10.7949C11.6656 10.9223 11.2115 11.1723 10.835 11.5488L10.3496 12.0352C9.77429 12.6108 9.49095 13.367 9.49707 14.1211H4.72656C5.04472 13.1579 5.14564 10.847 3.00586 9.30957C4.04607 9.35761 5.61481 9.31003 6.83691 10.4141C6.66589 9.30985 6.83631 8.53141 4.90137 5.78711C5.69833 5.98284 7.68952 6.32958 8.74414 7.96875C9.20789 6.88364 10.4356 4.93597 12.1738 4.00488Z" fill="white"/>
|
|
30813
|
+
<path d="M14.8838 9.2666C15.5345 8.61618 16.5894 8.6153 17.2402 9.26562L17.5137 9.53906C18.164 10.19 18.1633 11.2458 17.5127 11.8965L17.0264 12.3828C16.5297 12.879 15.7985 12.9947 15.1904 12.7334L15.8115 12.1104C16.0351 12.1187 16.2619 12.0398 16.4326 11.8691L17.0068 11.2949C17.332 10.9697 17.3327 10.4417 17.0078 10.1162L16.6543 9.7627C16.3491 9.45791 15.8654 9.43902 15.5381 9.70605C15.5158 9.72417 15.4944 9.74388 15.4736 9.76465L14.9004 10.3379C14.7179 10.5204 14.6369 10.7665 14.6592 11.0049L14.0547 11.6084C13.7823 10.9964 13.8955 10.255 14.3975 9.75293L14.8838 9.2666Z" fill="white"/>
|
|
30814
|
+
</svg>`;
|
|
30815
|
+
/** ChannelClipPath 等内容裁剪使用的 SVG 坐标边距(与用户传入的屏幕 padding 无关) */
|
|
30816
|
+
const PARTITION_PICKER_CONTENT_CLIP_PADDING = 0;
|
|
30817
|
+
/** 可选中地块 polygon / 标签 data 属性 */
|
|
30818
|
+
const PARTITION_BOUNDARY_ID_ATTR = 'data-partition-boundary-id';
|
|
30819
|
+
/** 孤立子地块命中标记(不可选中) */
|
|
30820
|
+
const PARTITION_ISOLATED_BOUNDARY_ID_ATTR = 'data-partition-isolated-boundary-id';
|
|
30821
|
+
/** H5:子区域屏幕宽度超过该值才展示名称标签(px);与气泡宽度一致 */
|
|
30822
|
+
const H5_PARTITION_LABEL_MIN_SCREEN_WIDTH_PX = 72;
|
|
30823
|
+
/** H5:名称气泡总宽度(px) */
|
|
30824
|
+
const H5_PARTITION_LABEL_WIDTH_PX = 72;
|
|
30825
|
+
/** Web:序号徽章与名称标签间距(px) */
|
|
30826
|
+
const WEB_SELECTION_ORDER_GAP_PX = 8;
|
|
30827
|
+
/** H5:序号徽章与名称标签间距(px) */
|
|
30828
|
+
const H5_SELECTION_ORDER_GAP_PX = 4;
|
|
30829
|
+
/** Standalone:充电桩图标最小屏幕尺寸(px),缩小全图 fit 时避免过小;放大时随 viewBox 同比缩放 */
|
|
30830
|
+
const PARTITION_PICKER_CHARGING_PILE_MIN_DISPLAY_PX = 16;
|
|
30831
|
+
/** Web 地块描边屏幕像素宽度:未选中 2px,选中 3px */
|
|
30832
|
+
const WEB_PARTITION_BOUNDARY_STROKE_PX = {
|
|
30833
|
+
default: 2,
|
|
30834
|
+
selected: 3,
|
|
30835
|
+
};
|
|
30836
|
+
/** H5 地块描边屏幕像素宽度:选中与否均为 1px */
|
|
30837
|
+
const H5_PARTITION_BOUNDARY_STROKE_PX = {
|
|
30838
|
+
default: 1,
|
|
30839
|
+
selected: 1,
|
|
30840
|
+
};
|
|
30841
|
+
/**
|
|
30842
|
+
* Figma bg/mapping 地图网格背景(Standalone SVG 层)
|
|
30843
|
+
* @see https://www.figma.com/design/j0DdUYf8VFkXQ8ikkA6ZzP — node 20490:84886
|
|
30844
|
+
*/
|
|
30845
|
+
/** 网格区域底色,也用于容器 letterbox 留白 */
|
|
30846
|
+
const PARTITION_PICKER_GRID_BASE_COLOR = '#E3E5EB';
|
|
30847
|
+
/** 网格单元边长(SVG 地图坐标,与 viewBox 一致;zoom 时间距同比缩放) */
|
|
30848
|
+
const PARTITION_PICKER_GRID_TILE_SIZE = 10;
|
|
30849
|
+
/** 网格线宽(地图坐标;随 zoom 同比缩放,不做屏幕像素恒定) */
|
|
30850
|
+
const PARTITION_PICKER_GRID_LINE_WIDTH = 0.2;
|
|
30851
|
+
/** 网格交点圆点半径(地图坐标) */
|
|
30852
|
+
const PARTITION_PICKER_GRID_DOT_RADIUS = 0.6;
|
|
30853
|
+
/** 网格层整体不透明度(Figma 约 80%) */
|
|
30854
|
+
const PARTITION_PICKER_GRID_OPACITY = 0.4;
|
|
30855
|
+
/** 网格线与交点颜色 */
|
|
30856
|
+
const PARTITION_PICKER_GRID_STROKE = '#FFFFFF';
|
|
30857
|
+
/** 可见范围内交点超过该数量时跳过圆点,仅保留网格线(缩小视野时减轻 DOM 压力) */
|
|
30858
|
+
const PARTITION_PICKER_GRID_MAX_INTERSECTION_DOTS = 2000;
|
|
30859
|
+
/**
|
|
30860
|
+
* 分区选择地图专用样式
|
|
30861
|
+
*
|
|
30862
|
+
* Web:Figma 灰蓝未选中地块(Vector 360)
|
|
30863
|
+
* H5:高对比度绿色,面向卫星/深色底图
|
|
30864
|
+
*/
|
|
30865
|
+
const PARTITION_PICKER_MAP_CONFIG_WEB = {
|
|
30866
|
+
boundary: {
|
|
30867
|
+
lineColor: '#91A1B4',
|
|
30868
|
+
fillColor: '#D7DEE6',
|
|
30869
|
+
lineWidth: 2,
|
|
30870
|
+
},
|
|
30871
|
+
};
|
|
30872
|
+
const PARTITION_PICKER_MAP_CONFIG = {
|
|
30873
|
+
boundary: {
|
|
30874
|
+
lineColor: '#91A1B4',
|
|
30875
|
+
fillColor: '#D7DEE6',
|
|
30876
|
+
lineWidth: 2,
|
|
30877
|
+
},
|
|
30878
|
+
};
|
|
30879
|
+
/** Web 端地块配色(Figma Vector 360 / 361 / 99788) */
|
|
30880
|
+
const PARTITION_PICKER_COLORS_WEB = {
|
|
30881
|
+
defaultFill: 'rgba(222, 225, 231, 1)',
|
|
30882
|
+
defaultStroke: 'rgba(132, 152, 169, 1)',
|
|
30883
|
+
hoverFill: 'rgba(211, 216, 226, 1)',
|
|
30884
|
+
hoverStroke: 'rgba(132, 152, 169, 1)',
|
|
30885
|
+
selectedFill: 'rgba(202, 214, 238, 1)',
|
|
30886
|
+
selectedStroke: 'rgba(132, 152, 169, 1)',
|
|
30887
|
+
selectedHoverFill: 'rgba(202, 214, 238, 1)',
|
|
30888
|
+
selectedHoverStroke: 'rgba(132, 152, 169, 1)',
|
|
30889
|
+
isolatedFill: 'rgba(225, 227, 233, 1)',
|
|
30890
|
+
isolatedStroke: 'rgba(189, 198, 208, 1)',
|
|
30891
|
+
};
|
|
30892
|
+
/** H5 端地块配色 */
|
|
30893
|
+
const PARTITION_PICKER_COLORS_H5 = {
|
|
30894
|
+
defaultFill: 'rgba(222, 225, 231, 1)',
|
|
30895
|
+
defaultStroke: 'rgba(132, 152, 169, 1)',
|
|
30896
|
+
hoverFill: 'rgba(222, 225, 231, 1)',
|
|
30897
|
+
hoverStroke: 'rgba(132, 152, 169, 1)',
|
|
30898
|
+
selectedFill: 'rgba(202, 214, 238, 1)',
|
|
30899
|
+
selectedStroke: 'rgba(132, 152, 169, 1)',
|
|
30900
|
+
selectedHoverFill: 'rgba(202, 214, 238, 1)',
|
|
30901
|
+
selectedHoverStroke: 'rgba(132, 152, 169, 1)',
|
|
30902
|
+
isolatedFill: 'rgba(225, 227, 233, 1)',
|
|
30903
|
+
isolatedStroke: 'rgba(189, 198, 208, 1)',
|
|
30904
|
+
};
|
|
30905
|
+
function getPartitionPickerMapConfig(platform = PlatformType.WEB) {
|
|
30906
|
+
return platform === PlatformType.WEB
|
|
30907
|
+
? PARTITION_PICKER_MAP_CONFIG_WEB
|
|
30908
|
+
: PARTITION_PICKER_MAP_CONFIG;
|
|
30909
|
+
}
|
|
30910
|
+
function getPartitionPickerColors(platform = PlatformType.WEB) {
|
|
30911
|
+
return platform === PlatformType.WEB ? PARTITION_PICKER_COLORS_WEB : PARTITION_PICKER_COLORS_H5;
|
|
30912
|
+
}
|
|
30913
|
+
function getPartitionPickerBoundaryStrokePx(platform = PlatformType.WEB) {
|
|
30914
|
+
return platform === PlatformType.WEB
|
|
30915
|
+
? { ...WEB_PARTITION_BOUNDARY_STROKE_PX }
|
|
30916
|
+
: { ...H5_PARTITION_BOUNDARY_STROKE_PX };
|
|
30917
|
+
}
|
|
30918
|
+
/** Web 分区名气泡(Figma「地块名称」/ 选中区域) */
|
|
30919
|
+
const PARTITION_PICKER_LABEL_STYLES_WEB = {
|
|
30920
|
+
default: {
|
|
30921
|
+
backgroundColor: 'rgba(255, 255, 255, 0.15)',
|
|
30922
|
+
color: 'rgba(65, 93, 116, 0.7)',
|
|
30923
|
+
fontSize: 12,
|
|
30924
|
+
fontWeight: 500,
|
|
30925
|
+
padding: '4px 8px',
|
|
30926
|
+
borderRadius: 14,
|
|
30927
|
+
lineHeight: '20px',
|
|
30928
|
+
},
|
|
30929
|
+
selected: {
|
|
30930
|
+
backgroundColor: 'rgba(255, 90, 0, 1)',
|
|
30931
|
+
color: 'rgba(255, 255, 255, 1)',
|
|
30932
|
+
fontSize: 12,
|
|
30933
|
+
fontWeight: 500,
|
|
30934
|
+
padding: '4px 8px',
|
|
30935
|
+
borderRadius: 20,
|
|
30936
|
+
lineHeight: '20px',
|
|
30937
|
+
},
|
|
30938
|
+
selectionOrderBadge: {
|
|
30939
|
+
width: '24px',
|
|
30940
|
+
height: '24px',
|
|
30941
|
+
background: '#FF5A00',
|
|
30942
|
+
border: '1px solid #FFFFFF',
|
|
30943
|
+
color: '#FFFFFF',
|
|
30944
|
+
fontSize: 14,
|
|
30945
|
+
fontWeight: 500,
|
|
30946
|
+
lineHeight: '22px',
|
|
30947
|
+
},
|
|
30948
|
+
};
|
|
30949
|
+
/** H5 分区名气泡(深色底图 / 橙色渐变选中) */
|
|
30950
|
+
const PARTITION_PICKER_LABEL_STYLES_H5 = {
|
|
30951
|
+
default: {
|
|
30952
|
+
backgroundColor: 'rgba(255, 255, 255, 0.15)',
|
|
30953
|
+
color: 'rgba(65, 93, 116, 0.7)',
|
|
30954
|
+
fontSize: 12,
|
|
30955
|
+
fontWeight: 400,
|
|
30956
|
+
padding: '6px',
|
|
30957
|
+
width: `${H5_PARTITION_LABEL_WIDTH_PX}px`,
|
|
30958
|
+
maxWidth: `${H5_PARTITION_LABEL_WIDTH_PX}px`,
|
|
30959
|
+
borderRadius: 100,
|
|
30960
|
+
boxSizing: 'border-box',
|
|
30961
|
+
},
|
|
30962
|
+
selected: {
|
|
30963
|
+
background: 'linear-gradient(266.64deg, #F2942A 4.1%, #F26A2A 86.42%)',
|
|
30964
|
+
color: 'rgba(255, 255, 255, 1)',
|
|
30965
|
+
fontSize: 12,
|
|
30966
|
+
fontWeight: 400,
|
|
30967
|
+
padding: '6px',
|
|
30968
|
+
width: `${H5_PARTITION_LABEL_WIDTH_PX}px`,
|
|
30969
|
+
maxWidth: `${H5_PARTITION_LABEL_WIDTH_PX}px`,
|
|
30970
|
+
borderRadius: 100,
|
|
30971
|
+
boxSizing: 'border-box',
|
|
30972
|
+
},
|
|
30973
|
+
selectionOrderBadge: {
|
|
30974
|
+
width: '24px',
|
|
30975
|
+
height: '24px',
|
|
30976
|
+
background: 'linear-gradient(310.82deg, #F16629 14.52%, #F1A129 103.26%)',
|
|
30977
|
+
border: '1px solid #FFFFFF',
|
|
30978
|
+
color: '#FFFFFF',
|
|
30979
|
+
fontSize: 14,
|
|
30980
|
+
fontWeight: 600,
|
|
30981
|
+
lineHeight: '22px',
|
|
30982
|
+
},
|
|
30983
|
+
};
|
|
30984
|
+
/** 将 PartitionPickerLabelAppearance 转为 React 内联样式 */
|
|
30985
|
+
function labelAppearanceToStyle(appearance) {
|
|
30986
|
+
const { fontSize, borderRadius, ...rest } = appearance;
|
|
30987
|
+
return {
|
|
30988
|
+
...rest,
|
|
30989
|
+
fontSize: `${fontSize}px`,
|
|
30990
|
+
borderRadius: `${borderRadius}px`,
|
|
30991
|
+
};
|
|
30992
|
+
}
|
|
30993
|
+
function getPartitionPickerLabelStyles(platform = PlatformType.WEB) {
|
|
30994
|
+
return platform === PlatformType.WEB
|
|
30995
|
+
? PARTITION_PICKER_LABEL_STYLES_WEB
|
|
30996
|
+
: PARTITION_PICKER_LABEL_STYLES_H5;
|
|
30997
|
+
}
|
|
30998
|
+
const DEFAULT_PARTITION_PICKER_GRID_STYLE = {
|
|
30999
|
+
baseColor: PARTITION_PICKER_GRID_BASE_COLOR,
|
|
31000
|
+
strokeColor: PARTITION_PICKER_GRID_STROKE,
|
|
31001
|
+
tileSize: PARTITION_PICKER_GRID_TILE_SIZE,
|
|
31002
|
+
lineWidth: PARTITION_PICKER_GRID_LINE_WIDTH,
|
|
31003
|
+
dotRadius: PARTITION_PICKER_GRID_DOT_RADIUS,
|
|
31004
|
+
opacity: PARTITION_PICKER_GRID_OPACITY,
|
|
31005
|
+
maxIntersectionDots: PARTITION_PICKER_GRID_MAX_INTERSECTION_DOTS,
|
|
31006
|
+
};
|
|
31007
|
+
/**
|
|
31008
|
+
* 将 platform 内置默认值与外部 pickerStyleConfig 深度合并。
|
|
31009
|
+
* 调用方只需覆盖变化的字段,无需改组件源码。
|
|
31010
|
+
*/
|
|
31011
|
+
function resolvePartitionPickerStyles(platform = PlatformType.WEB, config) {
|
|
31012
|
+
const baseLabels = getPartitionPickerLabelStyles(platform);
|
|
31013
|
+
return {
|
|
31014
|
+
boundary: merge$1({}, getPartitionPickerColors(platform), config?.boundary),
|
|
31015
|
+
labels: config?.labels
|
|
31016
|
+
? {
|
|
31017
|
+
default: merge$1({}, baseLabels.default, config.labels.default),
|
|
31018
|
+
selected: merge$1({}, baseLabels.selected, config.labels.selected),
|
|
31019
|
+
selectionOrderBadge: merge$1({}, baseLabels.selectionOrderBadge, config.labels.selectionOrderBadge),
|
|
31020
|
+
}
|
|
31021
|
+
: baseLabels,
|
|
31022
|
+
grid: merge$1({}, DEFAULT_PARTITION_PICKER_GRID_STYLE, config?.grid),
|
|
31023
|
+
boundaryStrokePx: merge$1({}, getPartitionPickerBoundaryStrokePx(platform), config?.boundaryStrokePx),
|
|
31024
|
+
};
|
|
31025
|
+
}
|
|
31026
|
+
|
|
31027
|
+
/**
|
|
31028
|
+
* Standalone 模式下的视口控制:平移、滚轮缩放、H5 双指 pinch
|
|
31029
|
+
*
|
|
31030
|
+
* 核心思路:通过修改 SVG viewBox 实现平移/缩放,而非 CSS transform
|
|
31031
|
+
* - fitViewBox:初始 fit 状态,resetView 回到此状态
|
|
31032
|
+
* - viewBox:地块/通道/overlay 的可见区域
|
|
31033
|
+
* - gridViewBox:网格底图专用;有地图旋转时平移按屏幕方向(不逆旋转),与地块跟手平移解耦
|
|
31034
|
+
*
|
|
31035
|
+
* 平移可在地块/分区名等任意区域发起;仅当位移小于阈值时才视为点击
|
|
31036
|
+
*/
|
|
31037
|
+
function useMapViewport({ mapJson, containerRef: externalContainerRef, width, height, padding, minZoom = 0.5, maxZoom = 3, enablePanZoom = true, refitOnResize = true, onViewportTap, mapRotationDeg = 0, }) {
|
|
31038
|
+
/** 地图容器 DOM,用于绑定 wheel/pointer/touch 事件 */
|
|
31039
|
+
const internalContainerRef = useRef(null);
|
|
31040
|
+
const containerRef = externalContainerRef ?? internalContainerRef;
|
|
31041
|
+
/** 始终指向最新 padding,供 fitToView 异步调用时使用 */
|
|
31042
|
+
const paddingRef = useRef(padding);
|
|
31043
|
+
paddingRef.current = padding;
|
|
31044
|
+
/** padding 序列化 key,用于 useMemo 依赖(数组/对象形式 padding 变化时触发重算) */
|
|
31045
|
+
const paddingKey = typeof padding === 'number' ? padding : JSON.stringify(padding);
|
|
31046
|
+
/** 始终指向最新 onViewportTap,避免 effect 闭包过期 */
|
|
31047
|
+
const onViewportTapRef = useRef(onViewportTap);
|
|
31048
|
+
onViewportTapRef.current = onViewportTap;
|
|
31049
|
+
/** 始终指向最新 mapRotationDeg,供平移回调读取 */
|
|
31050
|
+
const mapRotationDegRef = useRef(mapRotationDeg);
|
|
31051
|
+
mapRotationDegRef.current = mapRotationDeg;
|
|
31052
|
+
/**
|
|
31053
|
+
* 内容 viewBox:地图包围盒 + 固定 SVG padding
|
|
31054
|
+
* 用于 ChannelClipPath 等裁剪,不随平移/缩放变化
|
|
31055
|
+
*/
|
|
31056
|
+
const contentViewBox = useMemo(() => {
|
|
31057
|
+
const bounds = calculateMapBounds(mapJson);
|
|
31058
|
+
return boundsToContentViewBox(bounds, PARTITION_PICKER_CONTENT_CLIP_PADDING);
|
|
31059
|
+
}, [mapJson]);
|
|
31060
|
+
/**
|
|
31061
|
+
* 初始 fit viewBox:在 contentViewBox 基础上适配容器宽高比与 screen padding
|
|
31062
|
+
* resetView 会回到此状态
|
|
31063
|
+
*/
|
|
31064
|
+
const fitViewBox = useMemo(() => computeFitViewBox(contentViewBox, width, height, padding), [contentViewBox, width, height, paddingKey]);
|
|
31065
|
+
/** fitViewBox 的 ref 副本,供事件回调中读取最新值(zoom 限制基准) */
|
|
31066
|
+
const fitViewBoxRef = useRef(fitViewBox);
|
|
31067
|
+
fitViewBoxRef.current = fitViewBox;
|
|
31068
|
+
/** 地块/通道/overlay 使用的 viewBox */
|
|
31069
|
+
const [viewBox, setViewBox] = useState(fitViewBox);
|
|
31070
|
+
/** 网格底图 viewBox:平移时按屏幕方向,避免与旋转后的地块反向滑动 */
|
|
31071
|
+
const [gridViewBox, setGridViewBox] = useState(fitViewBox);
|
|
31072
|
+
const viewBoxRef = useRef(viewBox);
|
|
31073
|
+
viewBoxRef.current = viewBox;
|
|
31074
|
+
const gridViewBoxRef = useRef(gridViewBox);
|
|
31075
|
+
gridViewBoxRef.current = gridViewBox;
|
|
31076
|
+
const pendingViewBoxRef = useRef(null);
|
|
31077
|
+
const pendingGridViewBoxRef = useRef(null);
|
|
31078
|
+
const viewBoxRafRef = useRef(null);
|
|
31079
|
+
const flushScheduledViewBox = useCallback(() => {
|
|
31080
|
+
viewBoxRafRef.current = null;
|
|
31081
|
+
const mapPending = pendingViewBoxRef.current;
|
|
31082
|
+
const gridPending = pendingGridViewBoxRef.current;
|
|
31083
|
+
if (mapPending) {
|
|
31084
|
+
pendingViewBoxRef.current = null;
|
|
31085
|
+
viewBoxRef.current = mapPending;
|
|
31086
|
+
setViewBox(mapPending);
|
|
31087
|
+
}
|
|
31088
|
+
if (gridPending) {
|
|
31089
|
+
pendingGridViewBoxRef.current = null;
|
|
31090
|
+
gridViewBoxRef.current = gridPending;
|
|
31091
|
+
setGridViewBox(gridPending);
|
|
31092
|
+
}
|
|
31093
|
+
}, []);
|
|
31094
|
+
const cancelScheduledViewBox = useCallback(() => {
|
|
31095
|
+
pendingViewBoxRef.current = null;
|
|
31096
|
+
pendingGridViewBoxRef.current = null;
|
|
31097
|
+
if (viewBoxRafRef.current != null) {
|
|
31098
|
+
cancelAnimationFrame(viewBoxRafRef.current);
|
|
31099
|
+
viewBoxRafRef.current = null;
|
|
31100
|
+
}
|
|
31101
|
+
}, []);
|
|
31102
|
+
const scheduleViewBox = useCallback((updater) => {
|
|
31103
|
+
const base = pendingViewBoxRef.current ?? viewBoxRef.current;
|
|
31104
|
+
const next = updater(base);
|
|
31105
|
+
pendingViewBoxRef.current = next;
|
|
31106
|
+
pendingGridViewBoxRef.current = next;
|
|
31107
|
+
viewBoxRef.current = next;
|
|
31108
|
+
gridViewBoxRef.current = next;
|
|
31109
|
+
if (viewBoxRafRef.current == null) {
|
|
31110
|
+
viewBoxRafRef.current = requestAnimationFrame(flushScheduledViewBox);
|
|
31111
|
+
}
|
|
31112
|
+
}, [flushScheduledViewBox]);
|
|
31113
|
+
const commitViewBox = useCallback((next) => {
|
|
31114
|
+
cancelScheduledViewBox();
|
|
31115
|
+
viewBoxRef.current = next;
|
|
31116
|
+
gridViewBoxRef.current = next;
|
|
31117
|
+
setViewBox(next);
|
|
31118
|
+
setGridViewBox(next);
|
|
31119
|
+
}, [cancelScheduledViewBox]);
|
|
31120
|
+
useEffect(() => () => {
|
|
31121
|
+
cancelScheduledViewBox();
|
|
31122
|
+
}, [cancelScheduledViewBox]);
|
|
31123
|
+
// 地图数据 / padding 变化时始终重新 fit(width/height 取触发时刻的实测值,不作为 resize 依赖)
|
|
31124
|
+
useEffect(() => {
|
|
31125
|
+
if (width <= 0 || height <= 0)
|
|
31126
|
+
return;
|
|
31127
|
+
commitViewBox(computeFitViewBox(contentViewBox, width, height, padding));
|
|
31128
|
+
}, [commitViewBox, contentViewBox, paddingKey]);
|
|
31129
|
+
const prevSizeRef = useRef({ width: 0, height: 0 });
|
|
31130
|
+
// 容器首次获得有效尺寸,或 refitOnResize 下的尺寸变化
|
|
31131
|
+
useEffect(() => {
|
|
31132
|
+
if (width <= 0 || height <= 0)
|
|
31133
|
+
return;
|
|
31134
|
+
const prev = prevSizeRef.current;
|
|
31135
|
+
const sizeChanged = prev.width !== width || prev.height !== height;
|
|
31136
|
+
const isInitialSize = prev.width <= 0 || prev.height <= 0;
|
|
31137
|
+
prevSizeRef.current = { width, height };
|
|
31138
|
+
if (isInitialSize || (refitOnResize && sizeChanged)) {
|
|
31139
|
+
commitViewBox(fitViewBox);
|
|
31140
|
+
}
|
|
31141
|
+
}, [
|
|
31142
|
+
commitViewBox,
|
|
31143
|
+
refitOnResize,
|
|
31144
|
+
width,
|
|
31145
|
+
height,
|
|
31146
|
+
fitViewBox.x,
|
|
31147
|
+
fitViewBox.y,
|
|
31148
|
+
fitViewBox.width,
|
|
31149
|
+
fitViewBox.height,
|
|
31150
|
+
]);
|
|
31151
|
+
/**
|
|
31152
|
+
* 以屏幕焦点为中心缩放
|
|
31153
|
+
* @param factor 缩放因子,>1 放大,<1 缩小
|
|
31154
|
+
* @param focalX 焦点相对容器左上角的 X(px)
|
|
31155
|
+
* @param focalY 焦点相对容器左上角的 Y(px)
|
|
31156
|
+
*/
|
|
31157
|
+
const applyZoom = useCallback((factor, focalX, focalY) => {
|
|
31158
|
+
scheduleViewBox((current) => {
|
|
31159
|
+
const targetWidth = clampViewBoxWidth(current.width / factor, fitViewBoxRef.current, minZoom, maxZoom);
|
|
31160
|
+
return resizeViewBoxAroundPoint(current, targetWidth, width, height, focalX, focalY);
|
|
31161
|
+
});
|
|
31162
|
+
}, [height, maxZoom, minZoom, scheduleViewBox, width]);
|
|
31163
|
+
/**
|
|
31164
|
+
* 平移视口:地块使用逆旋转后的 viewBox 增量(跟手);网格使用屏幕方向增量(同向滑动)
|
|
31165
|
+
*/
|
|
31166
|
+
const applyPan = useCallback((deltaXPx, deltaYPx) => {
|
|
31167
|
+
const mapBase = pendingViewBoxRef.current ?? viewBoxRef.current;
|
|
31168
|
+
const gridBase = pendingGridViewBoxRef.current ?? gridViewBoxRef.current;
|
|
31169
|
+
const mapDelta = containerDeltaToViewBoxDelta(deltaXPx, deltaYPx, mapBase, width, height, mapRotationDegRef.current);
|
|
31170
|
+
const gridDelta = containerDeltaToViewBoxDelta(deltaXPx, deltaYPx, gridBase, width, height, 0);
|
|
31171
|
+
const nextMap = {
|
|
31172
|
+
...mapBase,
|
|
31173
|
+
x: mapBase.x - mapDelta.dx,
|
|
31174
|
+
y: mapBase.y - mapDelta.dy,
|
|
31175
|
+
};
|
|
31176
|
+
const nextGrid = {
|
|
31177
|
+
...gridBase,
|
|
31178
|
+
x: gridBase.x - gridDelta.dx,
|
|
31179
|
+
y: gridBase.y - gridDelta.dy,
|
|
31180
|
+
width: nextMap.width,
|
|
31181
|
+
height: nextMap.height,
|
|
31182
|
+
};
|
|
31183
|
+
pendingViewBoxRef.current = nextMap;
|
|
31184
|
+
pendingGridViewBoxRef.current = nextGrid;
|
|
31185
|
+
viewBoxRef.current = nextMap;
|
|
31186
|
+
gridViewBoxRef.current = nextGrid;
|
|
31187
|
+
if (viewBoxRafRef.current == null) {
|
|
31188
|
+
viewBoxRafRef.current = requestAnimationFrame(flushScheduledViewBox);
|
|
31189
|
+
}
|
|
31190
|
+
}, [flushScheduledViewBox, height, width]);
|
|
31191
|
+
/** 恢复到初始 fitViewBox(ref API resetView) */
|
|
31192
|
+
const resetView = useCallback(() => {
|
|
31193
|
+
commitViewBox(fitViewBoxRef.current);
|
|
31194
|
+
}, [commitViewBox]);
|
|
31195
|
+
/**
|
|
31196
|
+
* 重新计算并应用 fit viewBox(ref API fitToView)
|
|
31197
|
+
* @param nextPadding 可选的新 padding,不传则使用当前 paddingRef
|
|
31198
|
+
*/
|
|
31199
|
+
const fitToView = useCallback((nextPadding) => {
|
|
31200
|
+
const bounds = calculateMapBounds(mapJson);
|
|
31201
|
+
const content = boundsToContentViewBox(bounds, PARTITION_PICKER_CONTENT_CLIP_PADDING);
|
|
31202
|
+
const fit = computeFitViewBox(content, width, height, nextPadding ?? paddingRef.current);
|
|
31203
|
+
fitViewBoxRef.current = fit;
|
|
31204
|
+
commitViewBox(fit);
|
|
31205
|
+
}, [commitViewBox, height, mapJson, width]);
|
|
31206
|
+
/** 桌面端:按 pointerId 追踪多个指针(预留多指,当前仅单指平移) */
|
|
31207
|
+
const pointerMapRef = useRef(new Map());
|
|
31208
|
+
/** H5:双指 pinch 缩放状态 */
|
|
31209
|
+
const pinchRef = useRef(null);
|
|
31210
|
+
/** 单指/单指针平移状态(桌面 pointer 与 H5 touch 共用) */
|
|
31211
|
+
const singlePanRef = useRef(null);
|
|
31212
|
+
/** 当前正在驱动平移的 pointer id(桌面端) */
|
|
31213
|
+
const activePanPointerIdRef = useRef(null);
|
|
31214
|
+
/** 清空所有平移/pinch 相关 ref,防止 pointer 丢失导致状态残留 */
|
|
31215
|
+
const clearPanState = useCallback(() => {
|
|
31216
|
+
pointerMapRef.current.clear();
|
|
31217
|
+
singlePanRef.current = null;
|
|
31218
|
+
activePanPointerIdRef.current = null;
|
|
31219
|
+
}, []);
|
|
31220
|
+
/** 计算 touch 列表中前两指间距 */
|
|
31221
|
+
const getTouchDistance = (touches) => {
|
|
31222
|
+
if (touches.length < 2)
|
|
31223
|
+
return 0;
|
|
31224
|
+
const dx = touches[0].clientX - touches[1].clientX;
|
|
31225
|
+
const dy = touches[0].clientY - touches[1].clientY;
|
|
31226
|
+
return Math.hypot(dx, dy);
|
|
31227
|
+
};
|
|
31228
|
+
/** 计算 touch 列表中前两指中心点,坐标相对容器左上角 */
|
|
31229
|
+
const getTouchCenter = (touches, rect) => {
|
|
31230
|
+
const x = (touches[0].clientX + touches[1].clientX) / 2 - rect.left;
|
|
31231
|
+
const y = (touches[0].clientY + touches[1].clientY) / 2 - rect.top;
|
|
31232
|
+
return { x, y };
|
|
31233
|
+
};
|
|
31234
|
+
/** 判断事件目标是否在地图容器内 */
|
|
31235
|
+
const isInsideContainer = (target, container) => {
|
|
31236
|
+
if (!(target instanceof Element))
|
|
31237
|
+
return false;
|
|
31238
|
+
return container.contains(target);
|
|
31239
|
+
};
|
|
31240
|
+
/** 位移是否超过 PAN_CLICK_THRESHOLD_PX,超过则视为拖拽 */
|
|
31241
|
+
const markPanMoved = (startX, startY, currentX, currentY) => Math.hypot(currentX - startX, currentY - startY) > PAN_CLICK_THRESHOLD_PX;
|
|
31242
|
+
/** 触发点击回调(仅当未发生拖拽时) */
|
|
31243
|
+
const emitViewportTap = (tapTarget, clientX, clientY) => {
|
|
31244
|
+
if (tapTarget) {
|
|
31245
|
+
onViewportTapRef.current?.({ target: tapTarget, clientX, clientY });
|
|
31246
|
+
}
|
|
31247
|
+
};
|
|
31248
|
+
// ── 绑定容器交互事件:滚轮缩放 / 桌面拖拽 / H5 pinch+单指平移 ──
|
|
31249
|
+
useEffect(() => {
|
|
31250
|
+
const container = containerRef.current;
|
|
31251
|
+
if (!container || !enablePanZoom)
|
|
31252
|
+
return;
|
|
31253
|
+
/** 滚轮缩放,以鼠标位置为焦点 */
|
|
31254
|
+
const handleWheel = (event) => {
|
|
31255
|
+
event.preventDefault();
|
|
31256
|
+
const rect = container.getBoundingClientRect();
|
|
31257
|
+
const focalX = event.clientX - rect.left;
|
|
31258
|
+
const focalY = event.clientY - rect.top;
|
|
31259
|
+
const factor = event.deltaY < 0 ? 1.1 : 1 / 1.1;
|
|
31260
|
+
applyZoom(factor, focalX, focalY);
|
|
31261
|
+
};
|
|
31262
|
+
/** 结束平移:未拖拽则触发 tap;释放 pointer capture 并清理状态 */
|
|
31263
|
+
const endPan = (event) => {
|
|
31264
|
+
// touch 由 touchend 统一处理,避免 pointerup 抢先清空 singlePanRef
|
|
31265
|
+
if (event?.pointerType === 'touch') {
|
|
31266
|
+
return;
|
|
31267
|
+
}
|
|
31268
|
+
if (event) {
|
|
31269
|
+
const state = pointerMapRef.current.get(event.pointerId);
|
|
31270
|
+
if (state && !state.moved && state.tapTarget) {
|
|
31271
|
+
emitViewportTap(state.tapTarget, event.clientX, event.clientY);
|
|
31272
|
+
}
|
|
31273
|
+
pointerMapRef.current.delete(event.pointerId);
|
|
31274
|
+
try {
|
|
31275
|
+
if (container.hasPointerCapture(event.pointerId)) {
|
|
31276
|
+
container.releasePointerCapture(event.pointerId);
|
|
31277
|
+
}
|
|
31278
|
+
}
|
|
31279
|
+
catch {
|
|
31280
|
+
// ignore
|
|
31281
|
+
}
|
|
31282
|
+
}
|
|
31283
|
+
if (!event || pointerMapRef.current.size === 0) {
|
|
31284
|
+
clearPanState();
|
|
31285
|
+
}
|
|
31286
|
+
};
|
|
31287
|
+
// 桌面端:pointer 事件处理平移(touch 交给下方 touch 事件,避免重复)
|
|
31288
|
+
const handlePointerDown = (event) => {
|
|
31289
|
+
if (!isInsideContainer(event.target, container))
|
|
31290
|
+
return;
|
|
31291
|
+
if (event.pointerType === 'touch') {
|
|
31292
|
+
return;
|
|
31293
|
+
}
|
|
31294
|
+
// 仅响应主键(左键)
|
|
31295
|
+
if (event.pointerType === 'mouse' && event.button !== 0) {
|
|
31296
|
+
return;
|
|
31297
|
+
}
|
|
31298
|
+
pointerMapRef.current.set(event.pointerId, {
|
|
31299
|
+
pointerId: event.pointerId,
|
|
31300
|
+
startX: event.clientX,
|
|
31301
|
+
startY: event.clientY,
|
|
31302
|
+
moved: false,
|
|
31303
|
+
tapTarget: event.target instanceof Element ? event.target : null,
|
|
31304
|
+
});
|
|
31305
|
+
activePanPointerIdRef.current = event.pointerId;
|
|
31306
|
+
singlePanRef.current = {
|
|
31307
|
+
startX: event.clientX,
|
|
31308
|
+
startY: event.clientY,
|
|
31309
|
+
viewBox: viewBoxRef.current,
|
|
31310
|
+
moved: false,
|
|
31311
|
+
tapTarget: event.target instanceof Element ? event.target : null,
|
|
31312
|
+
};
|
|
31313
|
+
container.setPointerCapture(event.pointerId);
|
|
31314
|
+
};
|
|
31315
|
+
const handlePointerMove = (event) => {
|
|
31316
|
+
// 主键已松开但 pointerup 未触发时(如释放在容器外),避免继续平移
|
|
31317
|
+
if (event.pointerType === 'mouse' && event.buttons === 0) {
|
|
31318
|
+
endPan(event);
|
|
31319
|
+
return;
|
|
31320
|
+
}
|
|
31321
|
+
if (activePanPointerIdRef.current !== event.pointerId)
|
|
31322
|
+
return;
|
|
31323
|
+
const state = pointerMapRef.current.get(event.pointerId);
|
|
31324
|
+
if (!state || !singlePanRef.current)
|
|
31325
|
+
return;
|
|
31326
|
+
const dx = event.clientX - state.startX;
|
|
31327
|
+
const dy = event.clientY - state.startY;
|
|
31328
|
+
if (markPanMoved(state.startX, state.startY, event.clientX, event.clientY)) {
|
|
31329
|
+
state.moved = true;
|
|
31330
|
+
singlePanRef.current.moved = true;
|
|
31331
|
+
}
|
|
31332
|
+
applyPan(dx, dy);
|
|
31333
|
+
pointerMapRef.current.set(event.pointerId, {
|
|
31334
|
+
...state,
|
|
31335
|
+
startX: event.clientX,
|
|
31336
|
+
startY: event.clientY,
|
|
31337
|
+
});
|
|
31338
|
+
};
|
|
31339
|
+
const handlePointerUp = (event) => {
|
|
31340
|
+
endPan(event);
|
|
31341
|
+
};
|
|
31342
|
+
const handleLostPointerCapture = (event) => {
|
|
31343
|
+
if (activePanPointerIdRef.current === event.pointerId) {
|
|
31344
|
+
endPan(event);
|
|
31345
|
+
}
|
|
31346
|
+
};
|
|
31347
|
+
const handleWindowPointerUp = (event) => {
|
|
31348
|
+
if (event instanceof PointerEvent && event.pointerType === 'touch') {
|
|
31349
|
+
return;
|
|
31350
|
+
}
|
|
31351
|
+
clearPanState();
|
|
31352
|
+
};
|
|
31353
|
+
// H5:双指 pinch 缩放、单指平移
|
|
31354
|
+
const handleTouchStart = (event) => {
|
|
31355
|
+
if (event.touches.length === 2) {
|
|
31356
|
+
event.preventDefault();
|
|
31357
|
+
const rect = container.getBoundingClientRect();
|
|
31358
|
+
const center = getTouchCenter(event.touches, rect);
|
|
31359
|
+
pinchRef.current = {
|
|
31360
|
+
initialDistance: getTouchDistance(event.touches),
|
|
31361
|
+
initialViewBox: viewBoxRef.current,
|
|
31362
|
+
centerX: center.x,
|
|
31363
|
+
centerY: center.y,
|
|
31364
|
+
};
|
|
31365
|
+
singlePanRef.current = null;
|
|
31366
|
+
return;
|
|
31367
|
+
}
|
|
31368
|
+
if (event.touches.length === 1 && isInsideContainer(event.target, container)) {
|
|
31369
|
+
singlePanRef.current = {
|
|
31370
|
+
startX: event.touches[0].clientX,
|
|
31371
|
+
startY: event.touches[0].clientY,
|
|
31372
|
+
viewBox: viewBoxRef.current,
|
|
31373
|
+
moved: false,
|
|
31374
|
+
tapTarget: event.target instanceof Element ? event.target : null,
|
|
31375
|
+
};
|
|
31376
|
+
}
|
|
31377
|
+
};
|
|
31378
|
+
const handleTouchMove = (event) => {
|
|
31379
|
+
if (event.touches.length === 2 && pinchRef.current) {
|
|
31380
|
+
event.preventDefault();
|
|
31381
|
+
const rect = container.getBoundingClientRect();
|
|
31382
|
+
const distance = getTouchDistance(event.touches);
|
|
31383
|
+
if (distance <= 0 || pinchRef.current.initialDistance <= 0)
|
|
31384
|
+
return;
|
|
31385
|
+
const scale = distance / pinchRef.current.initialDistance;
|
|
31386
|
+
const center = getTouchCenter(event.touches, rect);
|
|
31387
|
+
const targetWidth = clampViewBoxWidth(pinchRef.current.initialViewBox.width / scale, fitViewBoxRef.current, minZoom, maxZoom);
|
|
31388
|
+
scheduleViewBox(() => resizeViewBoxAroundPoint(pinchRef.current.initialViewBox, targetWidth, width, height, center.x, center.y));
|
|
31389
|
+
return;
|
|
31390
|
+
}
|
|
31391
|
+
if (event.touches.length === 1 && singlePanRef.current) {
|
|
31392
|
+
const panState = singlePanRef.current;
|
|
31393
|
+
const touch = event.touches[0];
|
|
31394
|
+
if (markPanMoved(panState.startX, panState.startY, touch.clientX, touch.clientY)) {
|
|
31395
|
+
panState.moved = true;
|
|
31396
|
+
}
|
|
31397
|
+
if (panState.moved) {
|
|
31398
|
+
event.preventDefault();
|
|
31399
|
+
}
|
|
31400
|
+
const dx = touch.clientX - panState.startX;
|
|
31401
|
+
const dy = touch.clientY - panState.startY;
|
|
31402
|
+
applyPan(dx, dy);
|
|
31403
|
+
singlePanRef.current = {
|
|
31404
|
+
...panState,
|
|
31405
|
+
startX: touch.clientX,
|
|
31406
|
+
startY: touch.clientY,
|
|
31407
|
+
};
|
|
31408
|
+
}
|
|
31409
|
+
};
|
|
31410
|
+
const handleTouchEnd = (event) => {
|
|
31411
|
+
const touchState = singlePanRef.current;
|
|
31412
|
+
if (touchState && !touchState.moved) {
|
|
31413
|
+
let tapTarget = touchState.tapTarget;
|
|
31414
|
+
const touch = event.changedTouches[0];
|
|
31415
|
+
let clientX = touchState.startX;
|
|
31416
|
+
let clientY = touchState.startY;
|
|
31417
|
+
if (touch) {
|
|
31418
|
+
clientX = touch.clientX;
|
|
31419
|
+
clientY = touch.clientY;
|
|
31420
|
+
const hitTarget = document.elementFromPoint(touch.clientX, touch.clientY);
|
|
31421
|
+
if (hitTarget instanceof Element) {
|
|
31422
|
+
tapTarget = hitTarget;
|
|
31423
|
+
}
|
|
31424
|
+
}
|
|
31425
|
+
emitViewportTap(tapTarget, clientX, clientY);
|
|
31426
|
+
}
|
|
31427
|
+
if (pinchRef.current) {
|
|
31428
|
+
pinchRef.current = null;
|
|
31429
|
+
}
|
|
31430
|
+
clearPanState();
|
|
31431
|
+
};
|
|
31432
|
+
container.addEventListener('wheel', handleWheel, { passive: false });
|
|
31433
|
+
container.addEventListener('pointerdown', handlePointerDown);
|
|
31434
|
+
container.addEventListener('pointermove', handlePointerMove);
|
|
31435
|
+
container.addEventListener('pointerup', handlePointerUp);
|
|
31436
|
+
container.addEventListener('pointercancel', handlePointerUp);
|
|
31437
|
+
container.addEventListener('lostpointercapture', handleLostPointerCapture);
|
|
31438
|
+
container.addEventListener('touchstart', handleTouchStart, { passive: false });
|
|
31439
|
+
container.addEventListener('touchmove', handleTouchMove, { passive: false });
|
|
31440
|
+
container.addEventListener('touchend', handleTouchEnd);
|
|
31441
|
+
container.addEventListener('touchcancel', handleTouchEnd);
|
|
31442
|
+
window.addEventListener('pointerup', handleWindowPointerUp);
|
|
31443
|
+
window.addEventListener('mouseup', handleWindowPointerUp);
|
|
31444
|
+
window.addEventListener('blur', handleWindowPointerUp);
|
|
31445
|
+
return () => {
|
|
31446
|
+
container.removeEventListener('wheel', handleWheel);
|
|
31447
|
+
container.removeEventListener('pointerdown', handlePointerDown);
|
|
31448
|
+
container.removeEventListener('pointermove', handlePointerMove);
|
|
31449
|
+
container.removeEventListener('pointerup', handlePointerUp);
|
|
31450
|
+
container.removeEventListener('pointercancel', handlePointerUp);
|
|
31451
|
+
container.removeEventListener('lostpointercapture', handleLostPointerCapture);
|
|
31452
|
+
container.removeEventListener('touchstart', handleTouchStart);
|
|
31453
|
+
container.removeEventListener('touchmove', handleTouchMove);
|
|
31454
|
+
container.removeEventListener('touchend', handleTouchEnd);
|
|
31455
|
+
container.removeEventListener('touchcancel', handleTouchEnd);
|
|
31456
|
+
window.removeEventListener('pointerup', handleWindowPointerUp);
|
|
31457
|
+
window.removeEventListener('mouseup', handleWindowPointerUp);
|
|
31458
|
+
window.removeEventListener('blur', handleWindowPointerUp);
|
|
31459
|
+
};
|
|
31460
|
+
}, [
|
|
31461
|
+
applyPan,
|
|
31462
|
+
applyZoom,
|
|
31463
|
+
clearPanState,
|
|
31464
|
+
enablePanZoom,
|
|
31465
|
+
height,
|
|
31466
|
+
maxZoom,
|
|
31467
|
+
minZoom,
|
|
31468
|
+
scheduleViewBox,
|
|
31469
|
+
width,
|
|
31470
|
+
]);
|
|
31471
|
+
return {
|
|
31472
|
+
/** 绑定到地图外层容器的 ref */
|
|
31473
|
+
containerRef,
|
|
31474
|
+
/** 地块/通道/overlay 使用的 viewBox */
|
|
31475
|
+
viewBox,
|
|
31476
|
+
/** 网格底图 viewBox(平移按屏幕方向,与旋转地块解耦) */
|
|
31477
|
+
gridViewBox,
|
|
31478
|
+
/** 初始 fit viewBox,供 clip path 等需要稳定坐标系的场景 */
|
|
31479
|
+
fitViewBox,
|
|
31480
|
+
/** 恢复到初始 fit 状态 */
|
|
31481
|
+
resetView,
|
|
31482
|
+
/** 重新 fit 地图到容器(可传新 padding) */
|
|
31483
|
+
fitToView,
|
|
31484
|
+
};
|
|
31485
|
+
}
|
|
31486
|
+
|
|
31487
|
+
/** WGS84 经纬度坐标系 */
|
|
31488
|
+
const WGS84 = 'EPSG:4326';
|
|
31489
|
+
/** Web Mercator 平面坐标(米),与 MowerMapRenderer / defaultTransform.x|y 一致 */
|
|
31490
|
+
const WEB_MERCATOR = 'EPSG:3857';
|
|
31491
|
+
proj4.defs(WGS84, '+proj=longlat +datum=WGS84 +no_defs');
|
|
31492
|
+
proj4.defs(WEB_MERCATOR, '+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs');
|
|
31493
|
+
/**
|
|
31494
|
+
* Map 模式专用:管理 OverlayViewF 所需的 bounds、viewBox、缩放与 fitBounds。
|
|
31495
|
+
* 逻辑对齐 MowerMapRenderer 的 Google Maps 叠加层。
|
|
31496
|
+
*/
|
|
31497
|
+
function useGoogleMapsOverlay({ mapJson, mapRef, defaultTransform, originNorthRotate, platform = PlatformType.WEB, padding = 50, }) {
|
|
31498
|
+
/** 仅当外部传入 mapRef 时执行 fitBounds / zoom 监听等副作用 */
|
|
31499
|
+
const enabled = Boolean(mapRef);
|
|
31500
|
+
const transformX = defaultTransform?.x ?? 0;
|
|
31501
|
+
const transformY = defaultTransform?.y ?? 0;
|
|
31502
|
+
const transformRotation = defaultTransform?.rotation ?? 0;
|
|
31503
|
+
/** 叠加层实际旋转 = 地图北向偏移 + 用户 rotation */
|
|
31504
|
+
const actureRotate = originNorthRotate + transformRotation;
|
|
31505
|
+
/** OverlayViewF onDraw 回调写入的 overlay 像素宽高 */
|
|
31506
|
+
const [overlayLayout, setOverlayLayout] = useState(null);
|
|
31507
|
+
const mapBounds = useMemo(() => calculateMapBounds(mapJson), [mapJson]);
|
|
31508
|
+
/**
|
|
31509
|
+
* SVG viewBox:地图 JSON 平面坐标全量范围。
|
|
31510
|
+
* 需等待 overlayLayout 就绪后才可计算(与 MowerMapRenderer svgViewBox 一致)。
|
|
31511
|
+
*/
|
|
31512
|
+
const svgViewBox = useMemo(() => {
|
|
31513
|
+
if (!overlayLayout?.width || !overlayLayout?.height)
|
|
31514
|
+
return null;
|
|
31515
|
+
const boundWidth = mapBounds.maxX - mapBounds.minX;
|
|
31516
|
+
const boundHeight = mapBounds.maxY - mapBounds.minY;
|
|
31517
|
+
if (boundWidth <= 0 || boundHeight <= 0)
|
|
31518
|
+
return null;
|
|
31519
|
+
return {
|
|
31520
|
+
x: mapBounds.minX,
|
|
31521
|
+
y: mapBounds.minY,
|
|
31522
|
+
width: boundWidth,
|
|
31523
|
+
height: boundHeight,
|
|
31524
|
+
};
|
|
31525
|
+
}, [mapBounds, overlayLayout?.height, overlayLayout?.width]);
|
|
31526
|
+
/**
|
|
31527
|
+
* OverlayViewF 的 LatLngBounds:
|
|
31528
|
+
* GPS 包围盒 + defaultTransform 在 Web Mercator 下的平移偏移。
|
|
31529
|
+
* 仅 Map 模式(enabled)且 Google Maps 已加载时才计算。
|
|
31530
|
+
*/
|
|
31531
|
+
const bounds = useMemo(() => {
|
|
31532
|
+
if (!enabled || !mapJson || typeof window === 'undefined' || !window.google?.maps) {
|
|
31533
|
+
return null;
|
|
31534
|
+
}
|
|
31535
|
+
const validBounds = getValidGpsBounds(mapJson, 0);
|
|
31536
|
+
const [swLng0, swLat0] = validBounds.sw;
|
|
31537
|
+
const [neLng0, neLat0] = validBounds.ne;
|
|
31538
|
+
const [swX0, swY0] = proj4(WGS84, WEB_MERCATOR, [swLng0, swLat0]);
|
|
31539
|
+
const [neX0, neY0] = proj4(WGS84, WEB_MERCATOR, [neLng0, neLat0]);
|
|
31540
|
+
const swX = swX0 + transformX;
|
|
31541
|
+
const swY = swY0 + transformY;
|
|
31542
|
+
const neX = neX0 + transformX;
|
|
31543
|
+
const neY = neY0 + transformY;
|
|
31544
|
+
const [swLng, swLat] = proj4(WEB_MERCATOR, WGS84, [swX, swY]);
|
|
31545
|
+
const [neLng, neLat] = proj4(WEB_MERCATOR, WGS84, [neX, neY]);
|
|
31546
|
+
return new window.google.maps.LatLngBounds(new window.google.maps.LatLng(swLat, swLng), new window.google.maps.LatLng(neLat, neLng));
|
|
31547
|
+
}, [enabled, mapJson, transformX, transformY]);
|
|
31548
|
+
/** OverlayViewF onDraw:解析 overlay 容器 style,得到像素 layout */
|
|
31549
|
+
const handleOverlayDraw = useCallback((style) => {
|
|
31550
|
+
const layout = Object.keys(style).reduce((acc, key) => {
|
|
31551
|
+
if (!key)
|
|
31552
|
+
return acc;
|
|
31553
|
+
acc[key] = parseFloat(style[key]);
|
|
31554
|
+
return acc;
|
|
31555
|
+
}, {});
|
|
31556
|
+
if (layout.width > 0 && layout.height > 0) {
|
|
31557
|
+
setOverlayLayout({ width: layout.width, height: layout.height });
|
|
31558
|
+
}
|
|
31559
|
+
}, []);
|
|
31560
|
+
/** Map 模式 fitToView:调用 google.maps.Map.fitBounds,考虑旋转后的 GPS 包围盒 */
|
|
31561
|
+
const fitToView = useCallback((nextPadding) => {
|
|
31562
|
+
if (!enabled || !mapRef || !mapJson || typeof window === 'undefined' || !window.google?.maps) {
|
|
31563
|
+
return;
|
|
31564
|
+
}
|
|
31565
|
+
const rotate = transformRotation + originNorthRotate;
|
|
31566
|
+
const validBounds = getValidGpsBounds(mapJson, rotate);
|
|
31567
|
+
const [swLng0, swLat0] = validBounds.sw;
|
|
31568
|
+
const [neLng0, neLat0] = validBounds.ne;
|
|
31569
|
+
const [swX0, swY0] = proj4(WGS84, WEB_MERCATOR, [swLng0, swLat0]);
|
|
31570
|
+
const [neX0, neY0] = proj4(WGS84, WEB_MERCATOR, [neLng0, neLat0]);
|
|
31571
|
+
const swX = swX0 + transformX;
|
|
31572
|
+
const swY = swY0 + transformY;
|
|
31573
|
+
const neX = neX0 + transformX;
|
|
31574
|
+
const neY = neY0 + transformY;
|
|
31575
|
+
const [swLng, swLat] = proj4(WEB_MERCATOR, WGS84, [swX, swY]);
|
|
31576
|
+
const [neLng, neLat] = proj4(WEB_MERCATOR, WGS84, [neX, neY]);
|
|
31577
|
+
const googleBounds = new window.google.maps.LatLngBounds(new window.google.maps.LatLng(swLat, swLng), new window.google.maps.LatLng(neLat, neLng));
|
|
31578
|
+
const pad = normalizeScreenPadding(nextPadding ?? padding, 0);
|
|
31579
|
+
const paddingArg = pad.top === pad.right && pad.right === pad.bottom && pad.bottom === pad.left
|
|
31580
|
+
? pad.top
|
|
31581
|
+
: { top: pad.top, right: pad.right, bottom: pad.bottom, left: pad.left };
|
|
31582
|
+
mapRef.fitBounds(googleBounds, paddingArg);
|
|
31583
|
+
}, [enabled, mapJson, mapRef, originNorthRotate, padding, transformRotation, transformX, transformY]);
|
|
31584
|
+
const resetView = useCallback(() => {
|
|
31585
|
+
fitToView();
|
|
31586
|
+
}, [fitToView]);
|
|
31587
|
+
/** 参考缩放级别,用于计算 overlayScale(通道线宽视觉恒定,与 MowerMapRenderer 一致) */
|
|
31588
|
+
const REFERENCE_ZOOM = platform === PlatformType.WEB ? 21 : 21;
|
|
31589
|
+
const [overlayScale, setOverlayScale] = useState(1);
|
|
31590
|
+
useEffect(() => {
|
|
31591
|
+
if (!enabled || !mapRef)
|
|
31592
|
+
return;
|
|
31593
|
+
const updateScale = () => {
|
|
31594
|
+
const currentZoom = mapRef.getZoom();
|
|
31595
|
+
if (currentZoom === undefined)
|
|
31596
|
+
return;
|
|
31597
|
+
const zoomDiff = currentZoom - REFERENCE_ZOOM;
|
|
31598
|
+
const scale = Math.pow(2, -zoomDiff);
|
|
31599
|
+
if (scale < 1) {
|
|
31600
|
+
setOverlayScale(1);
|
|
31601
|
+
}
|
|
31602
|
+
else if (scale > 200) {
|
|
31603
|
+
setOverlayScale(platform === PlatformType.H5 ? 300 : 200);
|
|
31604
|
+
}
|
|
31605
|
+
else {
|
|
31606
|
+
setOverlayScale(platform === PlatformType.H5 ? 1.5 * scale : scale);
|
|
31607
|
+
}
|
|
31608
|
+
};
|
|
31609
|
+
const handleZoomChanged = throttle$2(updateScale, 50);
|
|
31610
|
+
const listener = mapRef.addListener('zoom_changed', handleZoomChanged);
|
|
31611
|
+
updateScale();
|
|
31612
|
+
return () => {
|
|
31613
|
+
if (listener && window.google?.maps?.event) {
|
|
31614
|
+
window.google.maps.event.removeListener(listener);
|
|
31615
|
+
}
|
|
31616
|
+
handleZoomChanged.cancel();
|
|
31617
|
+
};
|
|
31618
|
+
}, [enabled, mapRef, platform, REFERENCE_ZOOM]);
|
|
31619
|
+
const overlayWidth = overlayLayout?.width ?? 0;
|
|
31620
|
+
const overlayHeight = overlayLayout?.height ?? 0;
|
|
31621
|
+
const isOverlayReady = overlayWidth > 0 && overlayHeight > 0 && svgViewBox != null;
|
|
31622
|
+
return {
|
|
31623
|
+
actureRotate,
|
|
31624
|
+
bounds,
|
|
31625
|
+
svgViewBox,
|
|
31626
|
+
fitViewBox: svgViewBox,
|
|
31627
|
+
overlayWidth,
|
|
31628
|
+
overlayHeight,
|
|
31629
|
+
overlayScale,
|
|
31630
|
+
isOverlayReady,
|
|
31631
|
+
handleOverlayDraw,
|
|
31632
|
+
fitToView,
|
|
31633
|
+
resetView,
|
|
31634
|
+
};
|
|
31635
|
+
}
|
|
31636
|
+
|
|
31637
|
+
function getInitialSize(width, height) {
|
|
31638
|
+
return {
|
|
31639
|
+
width: typeof width === 'number' && width > 0 ? width : 0,
|
|
31640
|
+
height: typeof height === 'number' && height > 0 ? height : 0,
|
|
31641
|
+
};
|
|
31642
|
+
}
|
|
31643
|
+
function cssLengthToStyle(value) {
|
|
31644
|
+
return value;
|
|
31645
|
+
}
|
|
31646
|
+
/**
|
|
31647
|
+
* 将 width/height(px 或 CSS length)应用到容器,并用 ResizeObserver 测量实际像素尺寸
|
|
31648
|
+
* 供 fit、标签定位、线宽换算等逻辑使用
|
|
31649
|
+
*/
|
|
31650
|
+
function useContainerSize(width, height) {
|
|
31651
|
+
const containerRef = useRef(null);
|
|
31652
|
+
const [size, setSize] = useState(() => getInitialSize(width, height));
|
|
31653
|
+
const layoutStyle = useMemo(() => ({
|
|
31654
|
+
width: cssLengthToStyle(width),
|
|
31655
|
+
height: cssLengthToStyle(height),
|
|
31656
|
+
}), [width, height]);
|
|
31657
|
+
useLayoutEffect(() => {
|
|
31658
|
+
const element = containerRef.current;
|
|
31659
|
+
if (!element)
|
|
31660
|
+
return;
|
|
31661
|
+
const updateSize = () => {
|
|
31662
|
+
const { width: nextWidth, height: nextHeight } = element.getBoundingClientRect();
|
|
31663
|
+
if (nextWidth <= 0 || nextHeight <= 0)
|
|
31664
|
+
return;
|
|
31665
|
+
setSize((prev) => prev.width === nextWidth && prev.height === nextHeight
|
|
31666
|
+
? prev
|
|
31667
|
+
: { width: nextWidth, height: nextHeight });
|
|
31668
|
+
};
|
|
31669
|
+
updateSize();
|
|
31670
|
+
const observer = new ResizeObserver(updateSize);
|
|
31671
|
+
observer.observe(element);
|
|
31672
|
+
return () => observer.disconnect();
|
|
31673
|
+
}, []);
|
|
31674
|
+
const isReady = size.width > 0 && size.height > 0;
|
|
31675
|
+
return {
|
|
31676
|
+
containerRef,
|
|
31677
|
+
layoutStyle,
|
|
31678
|
+
width: size.width,
|
|
31679
|
+
height: size.height,
|
|
31680
|
+
isReady,
|
|
31681
|
+
};
|
|
31682
|
+
}
|
|
31683
|
+
|
|
31684
|
+
/**
|
|
31685
|
+
* 地块 hover 状态管理(受控 / 非受控)
|
|
31686
|
+
*
|
|
31687
|
+
* 用于地图与外部表格等 UI 双向联动:
|
|
31688
|
+
* - 地图 hover → onBoundaryHoverChange → 宿主更新表格行高亮
|
|
31689
|
+
* - 表格 hover → 宿主传入 hoveredBoundaryId → 地图同步高亮
|
|
31690
|
+
*/
|
|
31691
|
+
function useBoundaryHover({ hoveredBoundaryId, defaultHoveredBoundaryId = null, onBoundaryHoverChange, }) {
|
|
31692
|
+
const isControlled = hoveredBoundaryId !== undefined;
|
|
31693
|
+
const [internalHoveredId, setInternalHoveredId] = useState(defaultHoveredBoundaryId);
|
|
31694
|
+
const hoveredId = isControlled ? hoveredBoundaryId : internalHoveredId;
|
|
31695
|
+
const setHoveredId = useCallback((id) => {
|
|
31696
|
+
if (!isControlled) {
|
|
31697
|
+
setInternalHoveredId(id);
|
|
31698
|
+
}
|
|
31699
|
+
onBoundaryHoverChange?.(id);
|
|
31700
|
+
}, [isControlled, onBoundaryHoverChange]);
|
|
31701
|
+
return {
|
|
31702
|
+
hoveredId,
|
|
31703
|
+
setHoveredId,
|
|
31704
|
+
};
|
|
31705
|
+
}
|
|
31706
|
+
|
|
31707
|
+
function filterSelectableIds(ids, disabledBoundaryIds) {
|
|
31708
|
+
if (!disabledBoundaryIds?.size)
|
|
31709
|
+
return ids;
|
|
31710
|
+
return ids.filter((id) => !disabledBoundaryIds.has(id));
|
|
31711
|
+
}
|
|
31712
|
+
/**
|
|
31713
|
+
* 地块多选状态管理(受控 / 非受控)
|
|
31714
|
+
*
|
|
31715
|
+
* - 传入 selectedBoundaryIds → 受控,由宿主维护选中列表(如表格勾选)
|
|
31716
|
+
* - 未传入 → 非受控,内部维护,仍可通过 onSelectionChange 感知变化
|
|
31717
|
+
*/
|
|
31718
|
+
function useBoundarySelection({ selectedBoundaryIds, defaultSelectedBoundaryIds = [], onSelectionChange, disabledBoundaryIds, }) {
|
|
31719
|
+
const isControlled = selectedBoundaryIds !== undefined;
|
|
31720
|
+
const [internalIds, setInternalIds] = useState(filterSelectableIds(defaultSelectedBoundaryIds, disabledBoundaryIds));
|
|
31721
|
+
const selectedIds = isControlled
|
|
31722
|
+
? filterSelectableIds(selectedBoundaryIds, disabledBoundaryIds)
|
|
31723
|
+
: internalIds;
|
|
31724
|
+
const setSelectedIds = useCallback((ids) => {
|
|
31725
|
+
const next = filterSelectableIds(ids, disabledBoundaryIds);
|
|
31726
|
+
if (!isControlled) {
|
|
31727
|
+
setInternalIds(next);
|
|
31728
|
+
}
|
|
31729
|
+
onSelectionChange?.(next);
|
|
31730
|
+
}, [disabledBoundaryIds, isControlled, onSelectionChange]);
|
|
31731
|
+
/** 点击同一地块时 toggle,支持多选;孤立子地块不可选 */
|
|
31732
|
+
const toggleBoundary = useCallback((id) => {
|
|
31733
|
+
if (disabledBoundaryIds?.has(id))
|
|
31734
|
+
return;
|
|
31735
|
+
const next = selectedIds.includes(id)
|
|
31736
|
+
? selectedIds.filter((item) => item !== id)
|
|
31737
|
+
: [...selectedIds, id];
|
|
31738
|
+
setSelectedIds(next);
|
|
31739
|
+
}, [disabledBoundaryIds, selectedIds, setSelectedIds]);
|
|
31740
|
+
return {
|
|
31741
|
+
selectedIds,
|
|
31742
|
+
toggleBoundary,
|
|
31743
|
+
setSelectedIds,
|
|
31744
|
+
};
|
|
31745
|
+
}
|
|
31746
|
+
|
|
31747
|
+
/** 计算地图中不可选中的孤立子地块 id 集合 */
|
|
31748
|
+
function useIsolatedBoundaryIds(mapJson) {
|
|
31749
|
+
return useMemo(() => {
|
|
31750
|
+
const boundaries = generateBoundaryData(mapJson);
|
|
31751
|
+
const ids = new Set();
|
|
31752
|
+
for (const boundary of boundaries) {
|
|
31753
|
+
if (boundary.isIsolated) {
|
|
31754
|
+
ids.add(boundary.id);
|
|
31755
|
+
}
|
|
31756
|
+
}
|
|
31757
|
+
return ids;
|
|
31758
|
+
}, [mapJson]);
|
|
31759
|
+
}
|
|
31760
|
+
/** 供宿主(如表格)判断某地块是否不可选 */
|
|
31761
|
+
function getIsolatedBoundaryIds(mapJson) {
|
|
31762
|
+
return generateBoundaryData(mapJson)
|
|
31763
|
+
.filter((boundary) => boundary.isIsolated)
|
|
31764
|
+
.map((boundary) => boundary.id);
|
|
31765
|
+
}
|
|
31766
|
+
|
|
31767
|
+
/** 计算多边形重心,作为分区名气泡的锚点 */
|
|
31768
|
+
function computePolygonCentroid(points) {
|
|
31769
|
+
if (!points || points.length < 3)
|
|
31770
|
+
return null;
|
|
31771
|
+
const valid = points.filter((p) => p.length >= 2);
|
|
31772
|
+
if (valid.length < 3)
|
|
31773
|
+
return null;
|
|
31774
|
+
const polygon = [...valid];
|
|
31775
|
+
const first = polygon[0];
|
|
31776
|
+
const last = polygon[polygon.length - 1];
|
|
31777
|
+
if (first[0] !== last[0] || first[1] !== last[1]) {
|
|
31778
|
+
polygon.push([first[0], first[1]]);
|
|
31779
|
+
}
|
|
31780
|
+
let area = 0;
|
|
31781
|
+
let cx = 0;
|
|
31782
|
+
let cy = 0;
|
|
31783
|
+
for (let i = 0; i < polygon.length - 1; i++) {
|
|
31784
|
+
const x0 = polygon[i][0];
|
|
31785
|
+
const y0 = polygon[i][1];
|
|
31786
|
+
const x1 = polygon[i + 1][0];
|
|
31787
|
+
const y1 = polygon[i + 1][1];
|
|
31788
|
+
const cross = x0 * y1 - x1 * y0;
|
|
31789
|
+
area += cross;
|
|
31790
|
+
cx += (x0 + x1) * cross;
|
|
31791
|
+
cy += (y0 + y1) * cross;
|
|
31792
|
+
}
|
|
31793
|
+
area = area / 2;
|
|
31794
|
+
if (Math.abs(area) < 1e-10) {
|
|
31795
|
+
let sx = 0;
|
|
31796
|
+
let sy = 0;
|
|
31797
|
+
for (const [x, y] of valid) {
|
|
31798
|
+
sx += x;
|
|
31799
|
+
sy += y;
|
|
31800
|
+
}
|
|
31801
|
+
return { x: sx / valid.length, y: sy / valid.length };
|
|
31802
|
+
}
|
|
31803
|
+
cx = cx / (6 * area);
|
|
31804
|
+
cy = cy / (6 * area);
|
|
31805
|
+
return { x: cx, y: cy };
|
|
31806
|
+
}
|
|
31807
|
+
function buildBoundaryLabelGeometry(mapData) {
|
|
31808
|
+
const boundaryData = generateBoundaryData(mapData);
|
|
31809
|
+
const results = [];
|
|
31810
|
+
for (const boundary of boundaryData) {
|
|
31811
|
+
const centroid = computePolygonCentroid(boundary.points);
|
|
31812
|
+
if (!centroid)
|
|
31813
|
+
continue;
|
|
31814
|
+
results.push({
|
|
31815
|
+
id: boundary.id,
|
|
31816
|
+
name: boundary.name,
|
|
31817
|
+
points: boundary.points,
|
|
31818
|
+
centroid,
|
|
31819
|
+
isIsolated: boundary.isIsolated,
|
|
31820
|
+
});
|
|
31821
|
+
}
|
|
31822
|
+
return results;
|
|
31823
|
+
}
|
|
31824
|
+
/**
|
|
31825
|
+
* 根据当前「显示 viewBox」计算分区名气泡位置(不预旋转;由 mapOverlayLayer CSS rotate 处理)
|
|
31826
|
+
*/
|
|
31827
|
+
function usePartitionLabelItems(mapData, viewBox, containerWidth, containerHeight, options) {
|
|
31828
|
+
const positionMode = options?.positionMode ?? 'meet';
|
|
31829
|
+
const boundaryGeometry = useMemo(() => (mapData ? buildBoundaryLabelGeometry(mapData) : []), [mapData]);
|
|
31830
|
+
return useMemo(() => {
|
|
31831
|
+
if (!viewBox || containerWidth <= 0 || containerHeight <= 0)
|
|
31832
|
+
return [];
|
|
31833
|
+
const results = [];
|
|
31834
|
+
for (const boundary of boundaryGeometry) {
|
|
31835
|
+
let leftPct;
|
|
31836
|
+
let topPct;
|
|
31837
|
+
if (positionMode === 'overlay') {
|
|
31838
|
+
const relX = (boundary.centroid.x - viewBox.x) / viewBox.width;
|
|
31839
|
+
const relY = (boundary.centroid.y - viewBox.y) / viewBox.height;
|
|
31840
|
+
leftPct = relX * 100;
|
|
31841
|
+
topPct = relY * 100;
|
|
31842
|
+
}
|
|
31843
|
+
else {
|
|
31844
|
+
const position = mapPointToContainerPercent(boundary.centroid.x, boundary.centroid.y, viewBox, containerWidth, containerHeight);
|
|
31845
|
+
leftPct = position.leftPct;
|
|
31846
|
+
topPct = position.topPct;
|
|
31847
|
+
}
|
|
31848
|
+
if (!Number.isFinite(leftPct) || !Number.isFinite(topPct))
|
|
31849
|
+
continue;
|
|
31850
|
+
const screenWidthPx = computePolygonScreenWidthPx(boundary.points, viewBox, containerWidth, containerHeight);
|
|
31851
|
+
results.push({
|
|
31852
|
+
id: boundary.id,
|
|
31853
|
+
name: boundary.name,
|
|
31854
|
+
leftPct,
|
|
31855
|
+
topPct,
|
|
31856
|
+
screenWidthPx,
|
|
31857
|
+
isIsolated: boundary.isIsolated,
|
|
31858
|
+
});
|
|
31859
|
+
}
|
|
31860
|
+
return results;
|
|
31861
|
+
}, [
|
|
31862
|
+
boundaryGeometry,
|
|
31863
|
+
containerWidth,
|
|
31864
|
+
containerHeight,
|
|
31865
|
+
viewBox?.x,
|
|
31866
|
+
viewBox?.y,
|
|
31867
|
+
viewBox?.width,
|
|
31868
|
+
viewBox?.height,
|
|
31869
|
+
positionMode,
|
|
31870
|
+
]);
|
|
31871
|
+
}
|
|
31872
|
+
|
|
31873
|
+
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/* 可点击地块:悬停显示 pointer,与地图容器 grab 区分 */\n.index-module_boundaryPolygon__uHe-E {\n cursor: pointer;\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/* 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: pointer;\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_labelOrderOnly__TDLBU {\n gap: 0;\n}\n\n/* H5 名称标签:气泡宽度 / 文案最大宽度由 constants.ts 内联样式控制 */\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 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: 100%;\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: 20px;\n height: 20px;\n flex-shrink: 0;\n pointer-events: none;\n}";
|
|
31874
|
+
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","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"};
|
|
31875
|
+
styleInject(css_248z);
|
|
31876
|
+
|
|
31877
|
+
const SelectableBoundaryElement = ({ data, boundaryColors, boundaryStrokePx, isIsolated = false, selected, hovered, viewBoxWidth, viewBoxHeight, containerWidth, containerHeight, onHoverChange, onBoundaryClick, onIsolatedTap, }) => {
|
|
31878
|
+
const style = data.style || {};
|
|
31879
|
+
const boundaryId = Number(data.id);
|
|
31880
|
+
const colors = boundaryColors;
|
|
31881
|
+
const strokePx = boundaryStrokePx ?? { ...WEB_PARTITION_BOUNDARY_STROKE_PX };
|
|
31882
|
+
const pointsString = useMemo(() => data.points.map((point) => `${point[0]},${point[1]}`).join(' '), [data.points]);
|
|
31883
|
+
const fillColor = useMemo(() => {
|
|
31884
|
+
if (isIsolated)
|
|
31885
|
+
return colors.isolatedFill;
|
|
31886
|
+
if (selected) {
|
|
31887
|
+
return hovered ? colors.selectedHoverFill : colors.selectedFill;
|
|
31888
|
+
}
|
|
31889
|
+
if (hovered)
|
|
31890
|
+
return colors.hoverFill;
|
|
31891
|
+
return style.fillColor || colors.defaultFill;
|
|
31892
|
+
}, [colors, hovered, isIsolated, selected, style.fillColor]);
|
|
31893
|
+
const strokeColor = useMemo(() => {
|
|
31894
|
+
if (isIsolated)
|
|
31895
|
+
return colors.isolatedStroke;
|
|
31896
|
+
if (selected) {
|
|
31897
|
+
return hovered ? colors.selectedHoverStroke : colors.selectedStroke;
|
|
31898
|
+
}
|
|
31899
|
+
if (hovered)
|
|
31900
|
+
return colors.hoverStroke;
|
|
31901
|
+
return style.lineColor || colors.defaultStroke;
|
|
31902
|
+
}, [colors, hovered, isIsolated, selected, style.lineColor]);
|
|
31903
|
+
const strokeWidth = useMemo(() => {
|
|
31904
|
+
const viewBox = { width: viewBoxWidth, height: viewBoxHeight };
|
|
31905
|
+
const targetScreenStrokePx = !isIsolated && (selected || hovered) ? strokePx.selected : strokePx.default;
|
|
31906
|
+
return scaleStrokeWidthForViewBox(viewBox, {
|
|
31907
|
+
containerWidth,
|
|
31908
|
+
containerHeight,
|
|
31909
|
+
targetScreenStrokePx,
|
|
31910
|
+
});
|
|
31911
|
+
}, [
|
|
31912
|
+
containerHeight,
|
|
31913
|
+
containerWidth,
|
|
31914
|
+
hovered,
|
|
31915
|
+
isIsolated,
|
|
31916
|
+
selected,
|
|
31917
|
+
strokePx,
|
|
31918
|
+
viewBoxHeight,
|
|
31919
|
+
viewBoxWidth,
|
|
31920
|
+
]);
|
|
31921
|
+
if (isIsolated) {
|
|
31922
|
+
return (jsx("polygon", { className: `${styles.boundaryPolygon} ${styles.boundaryPolygonIsolated}`, points: pointsString, fill: fillColor, stroke: strokeColor, strokeWidth: strokeWidth, strokeLinejoin: "round", [PARTITION_ISOLATED_BOUNDARY_ID_ATTR]: boundaryId, onClick: onIsolatedTap
|
|
31923
|
+
? (event) => {
|
|
31924
|
+
event.stopPropagation();
|
|
31925
|
+
onIsolatedTap(boundaryId, event.clientX, event.clientY);
|
|
31926
|
+
}
|
|
31927
|
+
: undefined }));
|
|
31928
|
+
}
|
|
31929
|
+
return (jsx("polygon", { className: styles.boundaryPolygon, points: pointsString, fill: fillColor, stroke: strokeColor, strokeWidth: strokeWidth, strokeLinejoin: "round", [PARTITION_BOUNDARY_ID_ATTR]: boundaryId, onMouseEnter: () => onHoverChange(boundaryId), onMouseLeave: () => onHoverChange(null), onClick: onBoundaryClick
|
|
31930
|
+
? (event) => {
|
|
31931
|
+
event.stopPropagation();
|
|
31932
|
+
onBoundaryClick(boundaryId);
|
|
31933
|
+
}
|
|
31934
|
+
: undefined }));
|
|
31935
|
+
};
|
|
31936
|
+
var SelectableBoundaryElement$1 = memo(SelectableBoundaryElement);
|
|
31937
|
+
|
|
31938
|
+
const PartitionPickerSvg = ({ svgElementDatas, viewBox, containerWidth, containerHeight, mapRotation = 0, boundaryColors, boundaryStrokePx, isolatedBoundaryIds, selectedIds, hoveredId, onHoverChange, onBoundaryClick, onIsolatedTap, }) => {
|
|
31939
|
+
const boundaries = (svgElementDatas[DataType.BOUNDARY] || []);
|
|
31940
|
+
const channels = (svgElementDatas[DataType.CHANNEL] || []);
|
|
31941
|
+
const selectedIdSet = useMemo(() => new Set(selectedIds), [selectedIds]);
|
|
31942
|
+
const mapContentTransform = useMemo(() => {
|
|
31943
|
+
if (mapRotation === 0)
|
|
31944
|
+
return undefined;
|
|
31945
|
+
const { x: cx, y: cy } = getViewBoxCenter(viewBox);
|
|
31946
|
+
return `rotate(${mapRotation}, ${cx}, ${cy})`;
|
|
31947
|
+
}, [mapRotation, viewBox.x, viewBox.y, viewBox.width, viewBox.height]);
|
|
31948
|
+
const mapContent = (jsxs(Fragment, { children: [jsx(GElement, { type: "boundary", children: boundaries.map((item) => {
|
|
31949
|
+
const boundaryId = Number(item.id);
|
|
31950
|
+
return (jsx(SelectableBoundaryElement$1, { data: item, boundaryColors: boundaryColors, boundaryStrokePx: boundaryStrokePx, isIsolated: isolatedBoundaryIds.has(boundaryId), selected: selectedIdSet.has(boundaryId), hovered: hoveredId === boundaryId, viewBoxWidth: viewBox.width, viewBoxHeight: viewBox.height, containerWidth: containerWidth, containerHeight: containerHeight, onHoverChange: onHoverChange, onBoundaryClick: onBoundaryClick, onIsolatedTap: onIsolatedTap }, item.id));
|
|
31951
|
+
}) }), jsxs(GElement, { type: "channel", children: [jsx(ChannelClipPath, {}), channels.map((item) => (jsx(ChannelElement, { data: item }, item.id)))] })] }));
|
|
31952
|
+
return (jsx("svg", { className: styles.mapSvg, xmlns: "http://www.w3.org/2000/svg", viewBox: viewBoxToString(viewBox), width: "100%", height: "100%", preserveAspectRatio: "xMidYMid meet", shapeRendering: "geometricPrecision", children: mapContentTransform ? (jsx("g", { transform: mapContentTransform, children: mapContent })) : (mapContent) }));
|
|
31953
|
+
};
|
|
31954
|
+
|
|
31955
|
+
function estimateGridIntersectionCount(viewBox, tileSize) {
|
|
31956
|
+
if (tileSize <= 0)
|
|
31957
|
+
return 0;
|
|
31958
|
+
const cols = Math.floor((viewBox.width + 1e-6) / tileSize) + 1;
|
|
31959
|
+
const rows = Math.floor((viewBox.height + 1e-6) / tileSize) + 1;
|
|
31960
|
+
return cols * rows;
|
|
31961
|
+
}
|
|
31962
|
+
/** 计算当前 viewBox 可见范围内的网格交点(每个交点只渲染一次圆点) */
|
|
31963
|
+
function getVisibleGridIntersections(viewBox, tileSize, maxDots) {
|
|
31964
|
+
if (tileSize <= 0 || estimateGridIntersectionCount(viewBox, tileSize) > maxDots)
|
|
31965
|
+
return [];
|
|
31966
|
+
const points = [];
|
|
31967
|
+
const minX = viewBox.x;
|
|
31968
|
+
const minY = viewBox.y;
|
|
31969
|
+
const maxX = viewBox.x + viewBox.width;
|
|
31970
|
+
const maxY = viewBox.y + viewBox.height;
|
|
31971
|
+
const startX = Math.floor(minX / tileSize) * tileSize;
|
|
31972
|
+
const startY = Math.floor(minY / tileSize) * tileSize;
|
|
31973
|
+
for (let x = startX; x <= maxX + 1e-6; x += tileSize) {
|
|
31974
|
+
for (let y = startY; y <= maxY + 1e-6; y += tileSize) {
|
|
31975
|
+
points.push({ x, y });
|
|
31976
|
+
}
|
|
31977
|
+
}
|
|
31978
|
+
return points;
|
|
31979
|
+
}
|
|
31980
|
+
const MapGridBackground = ({ viewBox, gridStyle }) => {
|
|
31981
|
+
const reactId = useId();
|
|
31982
|
+
const patternId = useMemo(() => `partition-picker-grid-lines-${reactId.replace(/:/g, '-')}`, [reactId]);
|
|
31983
|
+
const { baseColor, strokeColor, tileSize: tile, lineWidth, dotRadius, opacity, maxIntersectionDots, } = gridStyle;
|
|
31984
|
+
const intersections = useMemo(() => getVisibleGridIntersections(viewBox, tile, maxIntersectionDots), [maxIntersectionDots, tile, viewBox.x, viewBox.y, viewBox.width, viewBox.height]);
|
|
31985
|
+
/**
|
|
31986
|
+
* SVG stroke 以路径为中心;线在 tile 的 0 边绘制时,四格交点的视觉中心会偏向路径右下。
|
|
31987
|
+
* 线条内缩 lineWidth/2,圆点同步偏移,使圆心与网格线交叉中心对齐。
|
|
31988
|
+
*/
|
|
31989
|
+
const lineHalf = lineWidth / 2;
|
|
31990
|
+
return (jsxs("g", { className: "partition-picker-grid-bg", "aria-hidden": true, pointerEvents: "none", children: [jsx("defs", { children: jsxs("pattern", { id: patternId, patternUnits: "userSpaceOnUse", width: tile, height: tile, children: [jsx("line", { x1: 0, y1: lineHalf, x2: tile, y2: lineHalf, stroke: strokeColor, strokeWidth: lineWidth }), jsx("line", { x1: lineHalf, y1: 0, x2: lineHalf, y2: tile, stroke: strokeColor, strokeWidth: lineWidth })] }) }), jsx("rect", { x: viewBox.x, y: viewBox.y, width: viewBox.width, height: viewBox.height, fill: baseColor }), jsx("rect", { x: viewBox.x, y: viewBox.y, width: viewBox.width, height: viewBox.height, fill: `url(#${patternId})`, opacity: opacity }), jsx("g", { opacity: opacity, children: intersections.map((point, index) => (jsx("circle", { cx: point.x + lineHalf, cy: point.y + lineHalf, r: dotRadius, fill: strokeColor }, `${point.x}-${point.y}-${index}`))) })] }));
|
|
31991
|
+
};
|
|
31992
|
+
var MapGridBackground$1 = memo(MapGridBackground);
|
|
31993
|
+
|
|
31994
|
+
const PartitionPickerGridSvg = ({ viewBox, gridStyle }) => {
|
|
31995
|
+
return (jsx("svg", { className: styles.gridSvg, "data-viewport-background": "true", xmlns: "http://www.w3.org/2000/svg", viewBox: viewBoxToString(viewBox), width: "100%", height: "100%",
|
|
31996
|
+
// 设置缩放的规则
|
|
31997
|
+
//等比缩放 viewBox 内容,使其完整落在容器内;
|
|
31998
|
+
//水平、垂直都居中;
|
|
31999
|
+
//比例不一致时,上下或左右留空(letterboxing),不裁切、不拉伸变形。
|
|
32000
|
+
preserveAspectRatio: "xMidYMid meet",
|
|
32001
|
+
/**
|
|
32002
|
+
* 控制浏览器如何绘制路径/线条:
|
|
32003
|
+
* geometricPrecision:优先几何精度,线条更平滑、对齐更稳,缩放时不易发虚或抖动。
|
|
32004
|
+
* 网格是大量细线,用这个属性能让缩放/平移时网格更清晰稳定。
|
|
32005
|
+
*/
|
|
32006
|
+
shapeRendering: "geometricPrecision", "aria-hidden": true, children: jsx(MapGridBackground$1, { viewBox: viewBox, gridStyle: gridStyle }) }));
|
|
32007
|
+
};
|
|
32008
|
+
|
|
32009
|
+
const PartitionNameLabels = ({ items, selectedIds, labelStyles, platform = PlatformType.WEB, showSelectionOrder = false, rotation = 0, onHoverChange, onBoundaryClick, onIsolatedTap, }) => {
|
|
32010
|
+
const isWeb = platform === PlatformType.WEB;
|
|
32011
|
+
const orderBadgeStyle = useMemo(() => ({
|
|
32012
|
+
width: labelStyles.selectionOrderBadge.width,
|
|
32013
|
+
height: labelStyles.selectionOrderBadge.height,
|
|
32014
|
+
background: labelStyles.selectionOrderBadge.background,
|
|
32015
|
+
border: labelStyles.selectionOrderBadge.border,
|
|
32016
|
+
color: labelStyles.selectionOrderBadge.color,
|
|
32017
|
+
fontSize: `${labelStyles.selectionOrderBadge.fontSize}px`,
|
|
32018
|
+
fontWeight: labelStyles.selectionOrderBadge.fontWeight,
|
|
32019
|
+
fontFamily: labelStyles.selectionOrderBadge.fontFamily,
|
|
32020
|
+
lineHeight: labelStyles.selectionOrderBadge.lineHeight,
|
|
32021
|
+
}), [labelStyles]);
|
|
32022
|
+
return (jsx("div", { className: styles.labelsLayer, children: items.map((item) => {
|
|
32023
|
+
const isIsolated = item.isIsolated === true;
|
|
32024
|
+
const selected = !isIsolated && selectedIds.includes(item.id);
|
|
32025
|
+
const selectionOrder = selected ? selectedIds.indexOf(item.id) + 1 : null;
|
|
32026
|
+
const isLargeEnough = item.screenWidthPx > H5_PARTITION_LABEL_MIN_SCREEN_WIDTH_PX;
|
|
32027
|
+
// Web 始终展示名称;H5 按屏幕宽度决定
|
|
32028
|
+
const showNameLabel = isWeb || isLargeEnough;
|
|
32029
|
+
const showOrderBadge = showSelectionOrder && selected && selectionOrder != null;
|
|
32030
|
+
const h5OrderOnly = !isWeb && showOrderBadge && !isLargeEnough;
|
|
32031
|
+
if (!showNameLabel && !showOrderBadge && !isIsolated) {
|
|
32032
|
+
return null;
|
|
32033
|
+
}
|
|
32034
|
+
const pillStyle = labelAppearanceToStyle(selected ? labelStyles.selected : labelStyles.default);
|
|
32035
|
+
const labelClassName = [
|
|
32036
|
+
styles.label,
|
|
32037
|
+
!isWeb && showNameLabel ? styles.labelH5Wrap : '',
|
|
32038
|
+
isIsolated ? styles.labelIsolated : '',
|
|
32039
|
+
h5OrderOnly ? styles.labelOrderOnly : '',
|
|
32040
|
+
isIsolated ? styles.labelIsolatedLayout : '',
|
|
32041
|
+
]
|
|
32042
|
+
.filter(Boolean)
|
|
32043
|
+
.join(' ');
|
|
32044
|
+
const showOrderWithName = showOrderBadge && showNameLabel;
|
|
32045
|
+
const selectionOrderGapPx = isWeb ? WEB_SELECTION_ORDER_GAP_PX : H5_SELECTION_ORDER_GAP_PX;
|
|
32046
|
+
const boundaryIdProps = isIsolated
|
|
32047
|
+
? { [PARTITION_ISOLATED_BOUNDARY_ID_ATTR]: item.id }
|
|
32048
|
+
: { [PARTITION_BOUNDARY_ID_ATTR]: item.id };
|
|
32049
|
+
return (jsxs("div", { className: labelClassName, style: {
|
|
32050
|
+
left: `${item.leftPct}%`,
|
|
32051
|
+
top: `${item.topPct}%`,
|
|
32052
|
+
transform: `translate(-50%, -50%) rotate(${-rotation}deg)`,
|
|
32053
|
+
...(showOrderWithName ? { gap: selectionOrderGapPx } : undefined),
|
|
32054
|
+
}, ...boundaryIdProps, onMouseEnter: isIsolated ? undefined : () => onHoverChange(item.id), onMouseLeave: isIsolated ? undefined : () => onHoverChange(null), onClick: isIsolated
|
|
32055
|
+
? onIsolatedTap
|
|
32056
|
+
? (event) => {
|
|
32057
|
+
event.stopPropagation();
|
|
32058
|
+
onIsolatedTap(item.id, event.clientX, event.clientY);
|
|
32059
|
+
}
|
|
32060
|
+
: undefined
|
|
32061
|
+
: onBoundaryClick
|
|
32062
|
+
? (event) => {
|
|
32063
|
+
event.stopPropagation();
|
|
32064
|
+
onBoundaryClick(item.id);
|
|
32065
|
+
}
|
|
32066
|
+
: undefined, children: [showOrderBadge && (jsx("div", { className: styles.selectionOrderBadge, style: orderBadgeStyle, children: selectionOrder })), showNameLabel && (jsx("div", { className: `${styles.labelPill} ${!isWeb ? styles.labelPillH5 : ''}`, style: pillStyle, children: jsx("span", { className: `${styles.labelText} ${!isWeb ? styles.labelTextH5 : ''}`, children: item.name }) })), isIsolated && (jsx("div", { className: `${styles.isolatedIconBelow}`, dangerouslySetInnerHTML: { __html: ISOLATED_BOUNDARY_SVG_2 } }))] }, item.id));
|
|
32067
|
+
}) }));
|
|
32068
|
+
};
|
|
32069
|
+
|
|
32070
|
+
const DEFAULT_BACKGROUND = PARTITION_PICKER_GRID_BASE_COLOR;
|
|
32071
|
+
/** Map 模式下 OverlayViewF 默认 z-index */
|
|
32072
|
+
const DEFAULT_OVERLAY_Z_INDEX = 100;
|
|
32073
|
+
const MowerPartitionPickerMap = forwardRef(({ mapJson, mapConfig, pickerStyleConfig, sn = '', platform = PlatformType.WEB, width, height, padding = 0, backgroundColor = DEFAULT_BACKGROUND, defaultTransform, mapRef, minZoom = 0.5, maxZoom = 3, enablePanZoom = true, refitOnResize = true, onSizeChange, selectedBoundaryIds, defaultSelectedBoundaryIds, onSelectionChange, hoveredBoundaryId, defaultHoveredBoundaryId, onBoundaryHoverChange, showSelectionOrder = false, onIsolatedBoundaryTap, zIndex = DEFAULT_OVERLAY_Z_INDEX, className, style, }, ref) => {
|
|
32074
|
+
/**
|
|
32075
|
+
* 是否为 Map 模式(传入 mapRef 时为 true):
|
|
32076
|
+
* - true:Google Maps 底图 + OverlayViewF,平移/缩放/fit 由底图负责
|
|
32077
|
+
* - false:Standalone 固定容器 + 网格 SVG + useMapViewport
|
|
32078
|
+
*/
|
|
32079
|
+
const isMapMode = Boolean(mapRef);
|
|
32080
|
+
const reactInstanceId = useId();
|
|
32081
|
+
const channelClipPathId = useMemo(() => createChannelClipPathId({ sn, reactId: reactInstanceId }), [sn, reactInstanceId]);
|
|
32082
|
+
/** 外部 pickerStyleConfig 与内置 platform 默认值合并后的最终外观(地块/标签/网格等) */
|
|
32083
|
+
const mergedPickerStyles = useMemo(() => resolvePartitionPickerStyles(platform, pickerStyleConfig), [pickerStyleConfig, platform]);
|
|
32084
|
+
/** Standalone 容器背景与 grid.baseColor 对齐(合并后的最终值) */
|
|
32085
|
+
const resolvedBackgroundColor = useMemo(() => {
|
|
32086
|
+
if (isMapMode)
|
|
32087
|
+
return undefined;
|
|
32088
|
+
if (pickerStyleConfig?.grid?.baseColor != null) {
|
|
32089
|
+
return mergedPickerStyles.grid.baseColor;
|
|
32090
|
+
}
|
|
32091
|
+
return backgroundColor ?? mergedPickerStyles.grid.baseColor;
|
|
32092
|
+
}, [
|
|
32093
|
+
backgroundColor,
|
|
32094
|
+
isMapMode,
|
|
32095
|
+
mergedPickerStyles.grid.baseColor,
|
|
32096
|
+
pickerStyleConfig?.grid?.baseColor,
|
|
32097
|
+
]);
|
|
32098
|
+
/** Standalone 网格层:backgroundColor 未显式指定 grid.baseColor 时与容器背景同步 */
|
|
32099
|
+
const standaloneGridStyle = useMemo(() => {
|
|
32100
|
+
if (pickerStyleConfig?.grid?.baseColor != null) {
|
|
32101
|
+
return mergedPickerStyles.grid;
|
|
32102
|
+
}
|
|
32103
|
+
if (backgroundColor != null) {
|
|
32104
|
+
return { ...mergedPickerStyles.grid, baseColor: backgroundColor };
|
|
32105
|
+
}
|
|
32106
|
+
return mergedPickerStyles.grid;
|
|
32107
|
+
}, [
|
|
32108
|
+
backgroundColor,
|
|
32109
|
+
mergedPickerStyles.grid,
|
|
32110
|
+
pickerStyleConfig?.grid?.baseColor,
|
|
32111
|
+
]);
|
|
32112
|
+
const mergedMapConfig = useMemo(() => merge$1({}, DEFAULT_STYLES, getPartitionPickerMapConfig(platform), mapConfig), [mapConfig, platform]);
|
|
32113
|
+
const svgElementDatas = useMemo(() => UnifiedMapDataProcessor.processMapData(mapJson, mergedMapConfig) || {}, [mapJson, mergedMapConfig]);
|
|
32114
|
+
/** 地图 Y 轴相对正北的偏移(度),与 MowerMapRenderer.originNorthRotate 一致 */
|
|
32115
|
+
const originNorthRotate = useMemo(() => {
|
|
32116
|
+
if (mapJson.map_north_offset)
|
|
32117
|
+
return (mapJson.map_north_offset * 180) / Math.PI;
|
|
32118
|
+
return 0;
|
|
32119
|
+
}, [mapJson.map_north_offset]);
|
|
32120
|
+
/** Standalone 模式:叠加层实际旋转角(度)= 地图北向偏移 + 外部传入 rotation */
|
|
32121
|
+
const standaloneActureRotate = useMemo(() => originNorthRotate + (defaultTransform?.rotation ?? 0), [originNorthRotate, defaultTransform?.rotation]);
|
|
32122
|
+
/**
|
|
32123
|
+
* 稳定的内容 viewBox(地图全量范围 + SVG 裁剪边距)
|
|
32124
|
+
* 供 ChannelClipPath 使用,不随用户平移/缩放变化
|
|
32125
|
+
*/
|
|
32126
|
+
const contentViewBox = useMemo(() => {
|
|
32127
|
+
const bounds = calculateMapBounds(mapJson);
|
|
32128
|
+
return boundsToContentViewBox(bounds, PARTITION_PICKER_CONTENT_CLIP_PADDING);
|
|
32129
|
+
}, [mapJson]);
|
|
32130
|
+
/** 孤立地块 id 列表(未连接到主地块的子地块) */
|
|
32131
|
+
const isolatedBoundaryIds = useIsolatedBoundaryIds(mapJson);
|
|
32132
|
+
const boundaryNameById = useMemo(() => {
|
|
32133
|
+
const names = new Map();
|
|
32134
|
+
for (const boundary of generateBoundaryData(mapJson)) {
|
|
32135
|
+
names.set(boundary.id, boundary.name);
|
|
32136
|
+
}
|
|
32137
|
+
return names;
|
|
32138
|
+
}, [mapJson]);
|
|
32139
|
+
const handleIsolatedTap = useCallback((boundaryId, clientX, clientY) => {
|
|
32140
|
+
if (!onIsolatedBoundaryTap || !isolatedBoundaryIds.has(boundaryId))
|
|
32141
|
+
return;
|
|
32142
|
+
onIsolatedBoundaryTap({
|
|
32143
|
+
boundaryId,
|
|
32144
|
+
boundaryName: boundaryNameById.get(boundaryId),
|
|
32145
|
+
clientX,
|
|
32146
|
+
clientY,
|
|
32147
|
+
});
|
|
32148
|
+
}, [boundaryNameById, isolatedBoundaryIds, onIsolatedBoundaryTap]);
|
|
32149
|
+
const isolatedTapHandler = onIsolatedBoundaryTap ? handleIsolatedTap : undefined;
|
|
32150
|
+
/** 选中地块 id 列表(受控/非受控)与切换选中回调 */
|
|
32151
|
+
const { selectedIds, toggleBoundary, setSelectedIds } = useBoundarySelection({
|
|
32152
|
+
selectedBoundaryIds,
|
|
32153
|
+
defaultSelectedBoundaryIds,
|
|
32154
|
+
onSelectionChange,
|
|
32155
|
+
disabledBoundaryIds: isolatedBoundaryIds,
|
|
32156
|
+
});
|
|
32157
|
+
/** 当前 hover 的地块 id(受控/非受控)与切换 hover 回调 */
|
|
32158
|
+
const { hoveredId, setHoveredId } = useBoundaryHover({
|
|
32159
|
+
hoveredBoundaryId,
|
|
32160
|
+
defaultHoveredBoundaryId,
|
|
32161
|
+
onBoundaryHoverChange,
|
|
32162
|
+
});
|
|
32163
|
+
const handleHoverChange = useCallback((id) => {
|
|
32164
|
+
if (id != null && isolatedBoundaryIds.has(id))
|
|
32165
|
+
return;
|
|
32166
|
+
setHoveredId(id);
|
|
32167
|
+
}, [isolatedBoundaryIds, setHoveredId]);
|
|
32168
|
+
/** Map 模式:地块 polygon / 标签直接 onClick(无底图拖拽抢占) */
|
|
32169
|
+
const handleBoundaryClick = useCallback((id) => {
|
|
32170
|
+
if (isolatedBoundaryIds.has(id))
|
|
32171
|
+
return;
|
|
32172
|
+
toggleBoundary(id);
|
|
32173
|
+
}, [isolatedBoundaryIds, toggleBoundary]);
|
|
32174
|
+
/** Standalone 模式:由 viewport 统一处理点击(支持在地块上拖拽平移) */
|
|
32175
|
+
const handleViewportTap = useCallback(({ target, clientX, clientY }) => {
|
|
32176
|
+
const isolatedEl = target.closest(`[${PARTITION_ISOLATED_BOUNDARY_ID_ATTR}]`);
|
|
32177
|
+
if (isolatedEl) {
|
|
32178
|
+
const isolatedId = Number(isolatedEl.getAttribute(PARTITION_ISOLATED_BOUNDARY_ID_ATTR));
|
|
32179
|
+
if (Number.isFinite(isolatedId) && isolatedBoundaryIds.has(isolatedId)) {
|
|
32180
|
+
handleIsolatedTap(isolatedId, clientX, clientY);
|
|
32181
|
+
}
|
|
32182
|
+
return;
|
|
32183
|
+
}
|
|
32184
|
+
const boundaryEl = target.closest(`[${PARTITION_BOUNDARY_ID_ATTR}]`);
|
|
32185
|
+
if (!boundaryEl)
|
|
32186
|
+
return;
|
|
32187
|
+
const id = Number(boundaryEl.getAttribute(PARTITION_BOUNDARY_ID_ATTR));
|
|
32188
|
+
if (!Number.isFinite(id) || isolatedBoundaryIds.has(id))
|
|
32189
|
+
return;
|
|
32190
|
+
toggleBoundary(id);
|
|
32191
|
+
}, [handleIsolatedTap, isolatedBoundaryIds, toggleBoundary]);
|
|
32192
|
+
/**
|
|
32193
|
+
* Map 模式 overlay 状态(bounds / viewBox / fitBounds / overlayScale)。
|
|
32194
|
+
* 无 mapRef 时 hook 内部 enabled=false,返回值不参与渲染。
|
|
32195
|
+
*/
|
|
32196
|
+
const googleOverlay = useGoogleMapsOverlay({
|
|
32197
|
+
mapJson,
|
|
32198
|
+
mapRef,
|
|
32199
|
+
defaultTransform,
|
|
32200
|
+
originNorthRotate,
|
|
32201
|
+
platform,
|
|
32202
|
+
padding,
|
|
32203
|
+
});
|
|
32204
|
+
/** Standalone 才测量容器;Map 模式尺寸由 OverlayViewF onDraw 提供 */
|
|
32205
|
+
const { containerRef, layoutStyle, width: pxWidth, height: pxHeight, isReady, } = useContainerSize(isMapMode ? 0 : width, isMapMode ? 0 : height);
|
|
32206
|
+
useEffect(() => {
|
|
32207
|
+
if (!isMapMode && isReady) {
|
|
32208
|
+
onSizeChange?.(pxWidth, pxHeight);
|
|
32209
|
+
}
|
|
32210
|
+
}, [isMapMode, isReady, onSizeChange, pxWidth, pxHeight]);
|
|
32211
|
+
/** Standalone:平移/缩放 viewBox;Map 模式 enablePanZoom 关闭,仅保留 hook 结构 */
|
|
32212
|
+
const { viewBox: standaloneViewBox, gridViewBox, resetView: standaloneResetView, fitToView: standaloneFitToView, } = useMapViewport({
|
|
32213
|
+
mapJson,
|
|
32214
|
+
containerRef,
|
|
32215
|
+
width: pxWidth,
|
|
32216
|
+
height: pxHeight,
|
|
32217
|
+
padding,
|
|
32218
|
+
minZoom,
|
|
32219
|
+
maxZoom,
|
|
32220
|
+
refitOnResize,
|
|
32221
|
+
enablePanZoom: enablePanZoom && !isMapMode,
|
|
32222
|
+
onViewportTap: handleViewportTap,
|
|
32223
|
+
mapRotationDeg: standaloneActureRotate,
|
|
32224
|
+
});
|
|
32225
|
+
/** 按模式选取 viewBox / 尺寸 / 旋转 / 就绪状态(Map 与 Standalone 数据源不同) */
|
|
32226
|
+
const viewBox = isMapMode ? googleOverlay.svgViewBox : standaloneViewBox;
|
|
32227
|
+
const overlayWidth = isMapMode ? googleOverlay.overlayWidth : pxWidth;
|
|
32228
|
+
const overlayHeight = isMapMode ? googleOverlay.overlayHeight : pxHeight;
|
|
32229
|
+
const actureRotate = isMapMode ? googleOverlay.actureRotate : standaloneActureRotate;
|
|
32230
|
+
const isLayersReady = isMapMode ? googleOverlay.isOverlayReady : isReady;
|
|
32231
|
+
/**
|
|
32232
|
+
* Standalone:标签/充电桩置于 mapOverlayLayer,CSS rotate 与 MowerMapRenderer 一致。
|
|
32233
|
+
* Map 模式:旋转由 OverlayViewF 承担,此处不需要额外包裹层。
|
|
32234
|
+
*/
|
|
32235
|
+
const mapOverlayLayerStyle = useMemo(() => !isMapMode && actureRotate !== 0 ? { transform: `rotate(${actureRotate}deg)` } : undefined, [actureRotate, isMapMode]);
|
|
32236
|
+
/**
|
|
32237
|
+
* 分区名定位:
|
|
32238
|
+
* - Map 模式 overlay:相对 overlay 宽高百分比(与 BoundaryLabels 一致)
|
|
32239
|
+
* - Standalone meet:按 preserveAspectRatio 换算容器百分比
|
|
32240
|
+
*/
|
|
32241
|
+
const labelItems = usePartitionLabelItems(mapJson, viewBox, overlayWidth, overlayHeight, {
|
|
32242
|
+
positionMode: isMapMode ? 'overlay' : 'meet',
|
|
32243
|
+
});
|
|
32244
|
+
/**
|
|
32245
|
+
* 复用 ChannelClipPath / ChannelElement / CharginPile 所需的最小 Context
|
|
32246
|
+
* - svgViewBox:稳定 contentViewBox(见上)
|
|
32247
|
+
* - channelClipPathId:同页多实例时避免 SVG id 冲突
|
|
32248
|
+
* - platform:供子组件读取 Web / H5 差异
|
|
32249
|
+
* - Map 模式额外注入 mapRef / bounds / overlayScale(通道线宽等)
|
|
32250
|
+
*/
|
|
32251
|
+
const commonValue = useMemo(() => ({
|
|
32252
|
+
sn,
|
|
32253
|
+
platform,
|
|
32254
|
+
svgViewBox: contentViewBox,
|
|
32255
|
+
channelClipPathId,
|
|
32256
|
+
...(isMapMode && mapRef
|
|
32257
|
+
? {
|
|
32258
|
+
mapRef,
|
|
32259
|
+
bounds: googleOverlay.bounds,
|
|
32260
|
+
overlayScale: googleOverlay.overlayScale,
|
|
32261
|
+
}
|
|
32262
|
+
: {}),
|
|
32263
|
+
}), [
|
|
32264
|
+
sn,
|
|
32265
|
+
platform,
|
|
32266
|
+
contentViewBox,
|
|
32267
|
+
channelClipPathId,
|
|
32268
|
+
isMapMode,
|
|
32269
|
+
mapRef,
|
|
32270
|
+
googleOverlay.bounds,
|
|
32271
|
+
googleOverlay.overlayScale,
|
|
32272
|
+
]);
|
|
32273
|
+
const svgEditValue = useMemo(() => ({
|
|
32274
|
+
svgElementDatas,
|
|
32275
|
+
}), [svgElementDatas]);
|
|
32276
|
+
useImperativeHandle(ref, () => ({
|
|
32277
|
+
fitToView: isMapMode ? googleOverlay.fitToView : standaloneFitToView,
|
|
32278
|
+
resetView: isMapMode ? googleOverlay.resetView : standaloneResetView,
|
|
32279
|
+
getSelectedBoundaryIds: () => selectedIds,
|
|
32280
|
+
setSelectedBoundaryIds: setSelectedIds,
|
|
32281
|
+
getHoveredBoundaryId: () => hoveredId,
|
|
32282
|
+
setHoveredBoundaryId: setHoveredId,
|
|
32283
|
+
}), [
|
|
32284
|
+
googleOverlay.fitToView,
|
|
32285
|
+
googleOverlay.resetView,
|
|
32286
|
+
hoveredId,
|
|
32287
|
+
isMapMode,
|
|
32288
|
+
selectedIds,
|
|
32289
|
+
setHoveredId,
|
|
32290
|
+
setSelectedIds,
|
|
32291
|
+
standaloneFitToView,
|
|
32292
|
+
standaloneResetView,
|
|
32293
|
+
]);
|
|
32294
|
+
const containerStyle = useMemo(() => ({
|
|
32295
|
+
backgroundColor: isMapMode ? 'transparent' : resolvedBackgroundColor,
|
|
32296
|
+
...style,
|
|
32297
|
+
...(isMapMode ? {} : layoutStyle),
|
|
32298
|
+
}), [isMapMode, layoutStyle, resolvedBackgroundColor, style]);
|
|
32299
|
+
/** 地块 + 通道 SVG;Map 模式不在 SVG 内旋转(由 OverlayViewF rotate 处理) */
|
|
32300
|
+
const mapSvgLayer = viewBox && isLayersReady ? (jsx(PartitionPickerSvg, { svgElementDatas: svgElementDatas, viewBox: 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, onIsolatedTap: isMapMode ? isolatedTapHandler : undefined })) : null;
|
|
32301
|
+
/**
|
|
32302
|
+
* 分区标签 + 充电桩 HTML 层。
|
|
32303
|
+
* Map 模式:平铺于 overlay 根节点;Standalone:包在 mapOverlayLayer 内做 CSS 旋转。
|
|
32304
|
+
*/
|
|
32305
|
+
const overlayLayers = viewBox && isLayersReady ? (isMapMode ? (jsxs(Fragment, { children: [jsx(PartitionNameLabels, { items: labelItems, selectedIds: selectedIds, labelStyles: mergedPickerStyles.labels, platform: platform, showSelectionOrder: showSelectionOrder, rotation: actureRotate, onHoverChange: handleHoverChange, onBoundaryClick: handleBoundaryClick, onIsolatedTap: isolatedTapHandler }), jsx(CharginPile, { viewBox: viewBox, rotation: actureRotate })] })) : (jsxs("div", { className: styles.mapOverlayLayer, style: mapOverlayLayerStyle, children: [jsx(PartitionNameLabels, { items: labelItems, selectedIds: selectedIds, labelStyles: mergedPickerStyles.labels, platform: platform, showSelectionOrder: showSelectionOrder, rotation: actureRotate, onHoverChange: handleHoverChange, onIsolatedTap: isolatedTapHandler }), jsx(CharginPile, { viewBox: viewBox, rotation: actureRotate, viewRotationMode: "overlay-parent", containerWidth: overlayWidth, containerHeight: overlayHeight, sizeInSvgUnits: true, minDisplaySizePx: PARTITION_PICKER_CHARGING_PILE_MIN_DISPLAY_PX })] }))) : null;
|
|
32306
|
+
/** Map 模式:渲染 OverlayViewF,底图为 Google Maps */
|
|
32307
|
+
if (isMapMode && mapRef) {
|
|
32308
|
+
return (jsx(CommonContextProvider, { value: commonValue, children: jsx(SvgEditContextProvider, { value: svgEditValue, children: jsx(OverlayViewF, { map: mapRef, mapPaneName: "overlayMouseTarget", bounds: googleOverlay.bounds ?? undefined, rotate: actureRotate, zIndex: zIndex, onDraw: googleOverlay.handleOverlayDraw, children: jsxs("div", { className: `${styles.googleOverlayRoot} ${className || ''}`, style: containerStyle, onMouseLeave: () => setHoveredId(null), children: [mapSvgLayer, overlayLayers] }) }) }) }));
|
|
32309
|
+
}
|
|
32310
|
+
/** Standalone 模式:固定容器 + 网格底图 + 双层 SVG / HTML overlay */
|
|
32311
|
+
return (jsx(CommonContextProvider, { value: commonValue, children: jsx(SvgEditContextProvider, { value: svgEditValue, children: jsx("div", { ref: containerRef, className: `${styles.container} ${className || ''}`, "data-viewport-background": "true", style: containerStyle, onMouseLeave: () => setHoveredId(null), children: isLayersReady && (jsxs(Fragment, { children: [jsxs("div", { className: styles.mapSvgStack, children: [jsx(PartitionPickerGridSvg, { viewBox: gridViewBox, gridStyle: standaloneGridStyle }), mapSvgLayer] }), overlayLayers] })) }) }) }));
|
|
32312
|
+
});
|
|
32313
|
+
MowerPartitionPickerMap.displayName = 'MowerPartitionPickerMap';
|
|
32314
|
+
|
|
32315
|
+
export { ALL_DIRECTION_SELECTED, AntennaData, BaseData, BoundaryData, BoundarySvgRender, ChannelData, ChargingPileData, CheckDoodleErrorType, CheckObstaclePointErrorType, ClickEventType, CreateStatus, DEFAULT_SCREEN_PADDING, DataType, DoodleData, MAX_DIRECTION_ANGLE, MIN_DIRECTION_ANGLE, MapType, MobileEditMode, MowGateData, MowerMapRenderer, MowerPartitionPickerMap, ObstacleData, PARTITION_PICKER_COLORS_H5, PARTITION_PICKER_COLORS_WEB, PARTITION_PICKER_LABEL_STYLES_H5, PARTITION_PICKER_LABEL_STYLES_WEB, PathData, PlatformType, RecordFunctionEnum, RecordTypeEnum, RenderType, SCALE_FACTOR, SvgParserNative, UnifiedMapDataProcessor, VisionOffData, calculateMapGpsCenter, estimateGpsFromMapBounds, getIsolatedBoundaryIds, getPartitionPickerColors, getPartitionPickerLabelStyles, getValidGpsBounds, initBoundary, initChannel, initDoodle, initObstacle, initVisionOff, normalizeScreenPadding, resolvePartitionPickerStyles, restorePointsFormat, signGoogleStaticMapUrl, signGoogleStaticMapUrlSync };
|