拆分、重构 Toolbar
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect, useCallback, useRef } from "react";
|
import React, { useState, useEffect, useCallback, useMemo } from "react";
|
||||||
import { useData, useMap } from "../MapComponent";
|
import { useData, useMap } from "../MapComponent";
|
||||||
import ToolbarButton from "@/components/olmap/common/ToolbarButton";
|
import ToolbarButton from "@/components/olmap/common/ToolbarButton";
|
||||||
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
|
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
|
||||||
@@ -8,27 +8,24 @@ import QueryStatsOutlinedIcon from "@mui/icons-material/QueryStatsOutlined";
|
|||||||
import CompareArrowsOutlinedIcon from "@mui/icons-material/CompareArrowsOutlined";
|
import CompareArrowsOutlinedIcon from "@mui/icons-material/CompareArrowsOutlined";
|
||||||
import PropertyPanel from "./PropertyPanel"; // 引入属性面板组件
|
import PropertyPanel from "./PropertyPanel"; // 引入属性面板组件
|
||||||
import DrawPanel from "./DrawPanel"; // 引入绘图面板组件
|
import DrawPanel from "./DrawPanel"; // 引入绘图面板组件
|
||||||
import HistoryDataPanel from "./HistoryDataPanel"; // 引入绘图面板组件
|
|
||||||
import SCADADataPanel from "@components/olmap/SCADA/SCADADataPanel";
|
|
||||||
|
|
||||||
import VectorSource from "ol/source/Vector";
|
import VectorSource from "ol/source/Vector";
|
||||||
import VectorLayer from "ol/layer/Vector";
|
import VectorLayer from "ol/layer/Vector";
|
||||||
import { Style, Stroke, Fill, Circle } from "ol/style";
|
import { Style, Stroke, Fill, Circle } from "ol/style";
|
||||||
import Feature from "ol/Feature";
|
import Feature from "ol/Feature";
|
||||||
import { GeoJSON } from "ol/format";
|
|
||||||
import Point from "ol/geom/Point";
|
|
||||||
import { bbox, featureCollection } from "@turf/turf";
|
|
||||||
import StyleEditorPanel from "./StyleEditorPanel";
|
import StyleEditorPanel from "./StyleEditorPanel";
|
||||||
import { LayerStyleState } from "./StyleEditorPanel";
|
import { LayerStyleState } from "./StyleEditorPanel";
|
||||||
import StyleLegend from "./StyleLegend"; // 引入图例组件
|
import StyleLegend from "./StyleLegend"; // 引入图例组件
|
||||||
import { handleMapClickSelectFeatures as mapClickSelectFeatures, queryFeaturesByIds } from "@/utils/mapQueryService";
|
import { handleMapClickSelectFeatures as mapClickSelectFeatures } from "@/utils/mapQueryService";
|
||||||
import { useNotification } from "@refinedev/core";
|
import { useNotification } from "@refinedev/core";
|
||||||
import { useChatToolActionHandler } from "@/hooks/useChatToolActionHandler";
|
import ToolbarHistoryPanel from "./ToolbarHistoryPanel";
|
||||||
import { applyJunctionAreaRender } from "@components/olmap/DMALeakDetection/applyJunctionAreaRender";
|
import {
|
||||||
|
buildFeatureProperties,
|
||||||
|
} from "./toolbarFeatureHelpers";
|
||||||
|
import { useToolbarChatActions } from "./useToolbarChatActions";
|
||||||
|
|
||||||
import { config } from "@/config/config";
|
import { config } from "@/config/config";
|
||||||
import { apiFetch } from "@/lib/apiFetch";
|
import { apiFetch } from "@/lib/apiFetch";
|
||||||
import { FLOW_DISPLAY_UNIT, toM3h } from "@utils/units";
|
|
||||||
|
|
||||||
// 添加接口定义隐藏按钮的props
|
// 添加接口定义隐藏按钮的props
|
||||||
interface ToolbarProps {
|
interface ToolbarProps {
|
||||||
@@ -82,119 +79,15 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
|||||||
startTime?: string;
|
startTime?: string;
|
||||||
endTime?: string;
|
endTime?: string;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
const chatJunctionRenderCleanupRef = useRef<(() => void) | null>(null);
|
|
||||||
|
|
||||||
const disposeChatJunctionRender = useCallback(() => {
|
useToolbarChatActions({
|
||||||
chatJunctionRenderCleanupRef.current?.();
|
setHighlightFeatures,
|
||||||
chatJunctionRenderCleanupRef.current = null;
|
setChatPanelFeatureInfos,
|
||||||
}, []);
|
setChatPanelType,
|
||||||
|
setChatPanelTimeRange,
|
||||||
useEffect(() => () => disposeChatJunctionRender(), [disposeChatJunctionRender]);
|
setShowHistoryPanel,
|
||||||
|
setActiveTools,
|
||||||
// Wire up chat tool actions (locate, view_history, view_scada, render_junctions)
|
});
|
||||||
useChatToolActionHandler(
|
|
||||||
useCallback(
|
|
||||||
(action) => {
|
|
||||||
const geojsonFormat = new GeoJSON();
|
|
||||||
const zoomToFeatures = (
|
|
||||||
features: Feature[],
|
|
||||||
geometryKind: "point" | "line",
|
|
||||||
) => {
|
|
||||||
if (features.length === 0) return;
|
|
||||||
|
|
||||||
if (geometryKind === "point" && features.length === 1) {
|
|
||||||
const geometry = features[0].getGeometry();
|
|
||||||
if (geometry instanceof Point) {
|
|
||||||
map?.getView().animate({
|
|
||||||
center: geometry.getCoordinates(),
|
|
||||||
zoom: 18,
|
|
||||||
duration: 1000,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const geojsonFeatures = features.map((f) =>
|
|
||||||
geojsonFormat.writeFeatureObject(f),
|
|
||||||
);
|
|
||||||
const extent = bbox(featureCollection(geojsonFeatures as any));
|
|
||||||
if (extent) {
|
|
||||||
map?.getView().fit(extent, {
|
|
||||||
maxZoom: 18,
|
|
||||||
duration: 1000,
|
|
||||||
padding: geometryKind === "line" ? [60, 60, 60, 60] : [40, 40, 40, 40],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const locateFeatures = (
|
|
||||||
ids: string[],
|
|
||||||
layer: string,
|
|
||||||
geometryKind: "point" | "line",
|
|
||||||
) => {
|
|
||||||
queryFeaturesByIds(ids, layer).then((features) => {
|
|
||||||
if (features.length > 0) {
|
|
||||||
setHighlightFeatures(features);
|
|
||||||
zoomToFeatures(features, geometryKind);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (action.type) {
|
|
||||||
case "locate_features": {
|
|
||||||
locateFeatures(action.ids, action.layer, action.geometryKind);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "view_history": {
|
|
||||||
setChatPanelFeatureInfos(action.featureInfos);
|
|
||||||
setChatPanelType(action.dataType);
|
|
||||||
setChatPanelTimeRange({
|
|
||||||
startTime: action.startTime,
|
|
||||||
endTime: action.endTime,
|
|
||||||
});
|
|
||||||
setShowHistoryPanel(true);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "view_scada": {
|
|
||||||
setChatPanelFeatureInfos(action.featureInfos);
|
|
||||||
setChatPanelType("none");
|
|
||||||
setChatPanelTimeRange({
|
|
||||||
startTime: action.startTime,
|
|
||||||
endTime: action.endTime,
|
|
||||||
});
|
|
||||||
setShowHistoryPanel(true);
|
|
||||||
setActiveTools((prev) => {
|
|
||||||
if (prev.includes("history")) {
|
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
return [...prev, "history"];
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "render_junctions": {
|
|
||||||
disposeChatJunctionRender();
|
|
||||||
|
|
||||||
if (Object.keys(action.nodeAreaMap).length === 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (map) {
|
|
||||||
chatJunctionRenderCleanupRef.current = applyJunctionAreaRender(
|
|
||||||
map,
|
|
||||||
{
|
|
||||||
nodeAreaMap: action.nodeAreaMap,
|
|
||||||
areaIds: action.areaIds,
|
|
||||||
areaColors: action.areaColors,
|
|
||||||
},
|
|
||||||
{ propertyKey: "chat_junction_render_index" },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[disposeChatJunctionRender, map],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// 样式状态管理 - 在 Toolbar 中管理,带有默认样式
|
// 样式状态管理 - 在 Toolbar 中管理,带有默认样式
|
||||||
const [layerStyleStates, setLayerStyleStates] = useState<LayerStyleState[]>([
|
const [layerStyleStates, setLayerStyleStates] = useState<LayerStyleState[]>([
|
||||||
@@ -556,306 +449,10 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
|||||||
if (currentTime !== -1 && queryType) queryComputedProperties();
|
if (currentTime !== -1 && queryType) queryComputedProperties();
|
||||||
}, [highlightFeatures, currentTime, selectedDate, queryType, schemeName, schemeType, showPropertyPanel]);
|
}, [highlightFeatures, currentTime, selectedDate, queryType, schemeName, schemeType, showPropertyPanel]);
|
||||||
|
|
||||||
// 从要素属性中提取属性面板需要的数据
|
const propertyPanelData = useMemo(
|
||||||
const getFeatureProperties = useCallback(() => {
|
() => buildFeatureProperties(highlightFeatures[0], computedProperties),
|
||||||
if (highlightFeatures.length === 0) return {};
|
[highlightFeatures, computedProperties],
|
||||||
const highlightFeature = highlightFeatures[0];
|
);
|
||||||
const layer = highlightFeature?.getId()?.toString().split(".")[0];
|
|
||||||
const properties = highlightFeature.getProperties();
|
|
||||||
// 计算属性字段,增加 key 字段
|
|
||||||
const pipeComputedFields = [
|
|
||||||
{ key: "flow", label: "流量", unit: `${FLOW_DISPLAY_UNIT}` },
|
|
||||||
{ key: "friction", label: "摩阻", unit: "" },
|
|
||||||
{ key: "headloss", label: "水头损失", unit: "m" },
|
|
||||||
{ key: "unit_headloss", label: "单位水头损失", unit: "m/km" },
|
|
||||||
{ key: "quality", label: "水质", unit: "mg/L" },
|
|
||||||
{ key: "reaction", label: "反应", unit: "1/d" },
|
|
||||||
{ key: "setting", label: "设置", unit: "" },
|
|
||||||
{ key: "status", label: "状态", unit: "" },
|
|
||||||
{ key: "velocity", label: "流速", unit: "m/s" },
|
|
||||||
];
|
|
||||||
const nodeComputedFields = [
|
|
||||||
{ key: "actual_demand", label: "实际需水量", unit: `${FLOW_DISPLAY_UNIT}` },
|
|
||||||
{ key: "total_head", label: "水头", unit: "m" },
|
|
||||||
{ key: "pressure", label: "压力", unit: "m" },
|
|
||||||
{ key: "quality", label: "水质", unit: "mg/L" },
|
|
||||||
];
|
|
||||||
|
|
||||||
if (layer === "geo_pipes_mat" || layer === "geo_pipes") {
|
|
||||||
let result = {
|
|
||||||
id: properties.id,
|
|
||||||
type: "管道",
|
|
||||||
properties: [
|
|
||||||
{ label: "起始节点ID", value: properties.node1 },
|
|
||||||
{ label: "终点节点ID", value: properties.node2 },
|
|
||||||
{ label: "长度", value: properties.length?.toFixed?.(1), unit: "m" },
|
|
||||||
{
|
|
||||||
label: "管径",
|
|
||||||
value: properties.diameter?.toFixed?.(1),
|
|
||||||
unit: "mm",
|
|
||||||
},
|
|
||||||
{ label: "粗糙度", value: properties.roughness },
|
|
||||||
{ label: "局部损失", value: properties.minor_loss },
|
|
||||||
{ label: "初始状态", value: "开" },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
// 追加计算属性
|
|
||||||
if (computedProperties) {
|
|
||||||
pipeComputedFields.forEach(({ key, label, unit }) => {
|
|
||||||
let value = computedProperties[key];
|
|
||||||
|
|
||||||
if (key === "flow" && value !== undefined) {
|
|
||||||
value = toM3h(value, "lps");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果是单位水头损失且后端未返回,则通过水头损失/长度计算 (单位 m/km)
|
|
||||||
if (
|
|
||||||
key === "unit_headloss" &&
|
|
||||||
value === undefined &&
|
|
||||||
computedProperties.headloss !== undefined &&
|
|
||||||
properties.length
|
|
||||||
) {
|
|
||||||
value = (computedProperties.headloss / properties.length) * 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value !== undefined) {
|
|
||||||
result.properties.push({
|
|
||||||
label,
|
|
||||||
value: typeof value === "number" ? value.toFixed(3) : value,
|
|
||||||
unit,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
if (layer === "geo_junctions_mat" || layer === "geo_junctions") {
|
|
||||||
let result = {
|
|
||||||
id: properties.id,
|
|
||||||
type: "节点",
|
|
||||||
properties: [
|
|
||||||
{
|
|
||||||
label: "高程",
|
|
||||||
value: properties.elevation?.toFixed?.(1),
|
|
||||||
unit: "m",
|
|
||||||
},
|
|
||||||
// 将 demand1~demand5 与 pattern1~pattern5 作为二级表格展示
|
|
||||||
{
|
|
||||||
type: "table",
|
|
||||||
label: "基本需水量",
|
|
||||||
columns: ["demand", "pattern"],
|
|
||||||
rows: Array.from({ length: 5 }, (_, i) => i + 1)
|
|
||||||
.map((idx) => {
|
|
||||||
let d = properties?.[`demand${idx}`];
|
|
||||||
const p = properties?.[`pattern${idx}`];
|
|
||||||
// 仅当 demand 有效时展示该行
|
|
||||||
if (d !== undefined && d !== null && d !== "") {
|
|
||||||
d = toM3h(Number(d), "lps");
|
|
||||||
return [typeof d === "number" ? d.toFixed(3) : d, p ?? "-"];
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter(Boolean) as (string | number)[][],
|
|
||||||
} as any,
|
|
||||||
],
|
|
||||||
};
|
|
||||||
// 追加计算属性
|
|
||||||
if (computedProperties) {
|
|
||||||
nodeComputedFields.forEach(({ key, label, unit }) => {
|
|
||||||
if (computedProperties[key] !== undefined) {
|
|
||||||
let value = computedProperties[key];
|
|
||||||
if (key === "actual_demand") {
|
|
||||||
value = toM3h(value, "lps");
|
|
||||||
}
|
|
||||||
result.properties.push({
|
|
||||||
label,
|
|
||||||
value:
|
|
||||||
value?.toFixed?.(3) || value,
|
|
||||||
unit,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
if (layer === "geo_tanks_mat" || layer === "geo_tanks") {
|
|
||||||
return {
|
|
||||||
id: properties.id,
|
|
||||||
type: "水池",
|
|
||||||
properties: [
|
|
||||||
{
|
|
||||||
label: "高程",
|
|
||||||
value: properties.elevation?.toFixed?.(1),
|
|
||||||
unit: "m",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "初始水位",
|
|
||||||
value: properties.init_level?.toFixed?.(1),
|
|
||||||
unit: "m",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "最低水位",
|
|
||||||
value: properties.min_level?.toFixed?.(1),
|
|
||||||
unit: "m",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "最高水位",
|
|
||||||
value: properties.max_level?.toFixed?.(1),
|
|
||||||
unit: "m",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "直径",
|
|
||||||
value: properties.diameter?.toFixed?.(1),
|
|
||||||
unit: "m",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "最小容积",
|
|
||||||
value: properties.min_vol?.toFixed?.(1),
|
|
||||||
unit: "m³",
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// label: "容积曲线",
|
|
||||||
// value: properties.vol_curve,
|
|
||||||
// },
|
|
||||||
{
|
|
||||||
label: "溢出",
|
|
||||||
value: properties.overflow ? "是" : "否",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (layer === "geo_reservoirs_mat" || layer === "geo_reservoirs") {
|
|
||||||
return {
|
|
||||||
id: properties.id,
|
|
||||||
type: "水库",
|
|
||||||
properties: [
|
|
||||||
{
|
|
||||||
label: "水头",
|
|
||||||
value: properties.head?.toFixed?.(1),
|
|
||||||
unit: "m",
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// label: "模式",
|
|
||||||
// value: properties.pattern,
|
|
||||||
// },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (layer === "geo_pumps_mat" || layer === "geo_pumps") {
|
|
||||||
return {
|
|
||||||
id: properties.id,
|
|
||||||
type: "水泵",
|
|
||||||
properties: [
|
|
||||||
{ label: "起始节点 ID", value: properties.node1 },
|
|
||||||
{ label: "终点节点 ID", value: properties.node2 },
|
|
||||||
{
|
|
||||||
label: "功率",
|
|
||||||
value: properties.power?.toFixed?.(1),
|
|
||||||
unit: "kW",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "扬程",
|
|
||||||
value: properties.head?.toFixed?.(1),
|
|
||||||
unit: "m",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "转速",
|
|
||||||
value: properties.speed?.toFixed?.(1),
|
|
||||||
unit: "rpm",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "模式",
|
|
||||||
value: properties.pattern,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (layer === "geo_valves_mat" || layer === "geo_valves") {
|
|
||||||
return {
|
|
||||||
id: properties.id,
|
|
||||||
type: "阀门",
|
|
||||||
properties: [
|
|
||||||
{ label: "起始节点 ID", value: properties.node1 },
|
|
||||||
{ label: "终点节点 ID", value: properties.node2 },
|
|
||||||
{
|
|
||||||
label: "直径",
|
|
||||||
value: properties.diameter?.toFixed?.(1),
|
|
||||||
unit: "mm",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "阀门类型",
|
|
||||||
value: properties.v_type,
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// label: "设置",
|
|
||||||
// value: properties.setting?.toFixed?.(2),
|
|
||||||
// },
|
|
||||||
{
|
|
||||||
label: "局部损失",
|
|
||||||
value: properties.minor_loss?.toFixed?.(2),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// 传输频率文字对应
|
|
||||||
const getTransmissionFrequency = (transmission_frequency: string) => {
|
|
||||||
// 传输频率文本:00:01:00,00:05:00,00:10:00,00:30:00,01:00:00,转换为分钟数
|
|
||||||
const parts = transmission_frequency.split(":");
|
|
||||||
if (parts.length !== 3) return transmission_frequency;
|
|
||||||
const hours = parseInt(parts[0], 10);
|
|
||||||
const minutes = parseInt(parts[1], 10);
|
|
||||||
const seconds = parseInt(parts[2], 10);
|
|
||||||
const totalMinutes = hours * 60 + minutes + (seconds >= 30 ? 1 : 0);
|
|
||||||
return totalMinutes;
|
|
||||||
};
|
|
||||||
// 可靠度文字映射
|
|
||||||
const getReliability = (reliability: number) => {
|
|
||||||
switch (reliability) {
|
|
||||||
case 1:
|
|
||||||
return "高";
|
|
||||||
case 2:
|
|
||||||
return "中";
|
|
||||||
case 3:
|
|
||||||
return "低";
|
|
||||||
default:
|
|
||||||
return "未知";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (layer === "geo_scada_mat" || layer === "geo_scada") {
|
|
||||||
let result = {
|
|
||||||
id: properties.id,
|
|
||||||
type: "SCADA设备",
|
|
||||||
properties: [
|
|
||||||
{
|
|
||||||
label: "类型",
|
|
||||||
value:
|
|
||||||
properties.type === "pipe_flow" ? "流量传感器" : "压力传感器",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "关联节点 ID",
|
|
||||||
value: properties.associated_element_id,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "传输模式",
|
|
||||||
value:
|
|
||||||
properties.transmission_mode === "non_realtime"
|
|
||||||
? "定时传输"
|
|
||||||
: "实时传输",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "传输频率",
|
|
||||||
value: getTransmissionFrequency(properties.transmission_frequency),
|
|
||||||
unit: "分钟",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "可靠性",
|
|
||||||
value: getReliability(properties.reliability),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}, [highlightFeatures, computedProperties]);
|
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return null;
|
return null;
|
||||||
@@ -908,7 +505,7 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
{showPropertyPanel && (
|
{showPropertyPanel && (
|
||||||
<PropertyPanel
|
<PropertyPanel
|
||||||
{...getFeatureProperties()}
|
{...propertyPanelData}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
deactivateTool("info");
|
deactivateTool("info");
|
||||||
setActiveTools((prev) => prev.filter((t) => t !== "info"));
|
setActiveTools((prev) => prev.filter((t) => t !== "info"));
|
||||||
@@ -922,115 +519,20 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
|||||||
setLayerStyleStates={setLayerStyleStates}
|
setLayerStyleStates={setLayerStyleStates}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{showHistoryPanel &&
|
<ToolbarHistoryPanel
|
||||||
(chatPanelType === "none" && chatPanelFeatureInfos ? (
|
showHistoryPanel={showHistoryPanel}
|
||||||
<SCADADataPanel
|
chatPanelType={chatPanelType}
|
||||||
deviceIds={chatPanelFeatureInfos.map(([id]) => id)}
|
chatPanelFeatureInfos={chatPanelFeatureInfos}
|
||||||
visible={showHistoryPanel}
|
chatPanelTimeRange={chatPanelTimeRange}
|
||||||
start_time={chatPanelTimeRange?.startTime}
|
highlightFeatures={highlightFeatures}
|
||||||
end_time={chatPanelTimeRange?.endTime}
|
HistoryPanel={HistoryPanel}
|
||||||
onClose={() => {
|
schemeName={schemeName}
|
||||||
deactivateTool("history");
|
queryType={queryType}
|
||||||
setActiveTools((prev) => prev.filter((t) => t !== "history"));
|
onClose={() => {
|
||||||
}}
|
deactivateTool("history");
|
||||||
/>
|
setActiveTools((prev) => prev.filter((t) => t !== "history"));
|
||||||
) : HistoryPanel ? (
|
}}
|
||||||
<HistoryPanel
|
/>
|
||||||
featureInfos={chatPanelFeatureInfos ?? (() => {
|
|
||||||
if (highlightFeatures.length === 0 || !showHistoryPanel)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
return highlightFeatures
|
|
||||||
.map((feature) => {
|
|
||||||
const properties = feature.getProperties();
|
|
||||||
const id = properties.id;
|
|
||||||
if (!id) return null;
|
|
||||||
|
|
||||||
// 从图层名称推断类型
|
|
||||||
const layerId =
|
|
||||||
feature.getId()?.toString().split(".")[0] || "";
|
|
||||||
let type = "unknown";
|
|
||||||
|
|
||||||
if (layerId.includes("pipe")) {
|
|
||||||
type = "pipe";
|
|
||||||
} else if (layerId.includes("junction")) {
|
|
||||||
type = "junction";
|
|
||||||
} else if (layerId.includes("tank")) {
|
|
||||||
type = "tank";
|
|
||||||
} else if (layerId.includes("reservoir")) {
|
|
||||||
type = "reservoir";
|
|
||||||
} else if (layerId.includes("pump")) {
|
|
||||||
type = "pump";
|
|
||||||
} else if (layerId.includes("valve")) {
|
|
||||||
type = "valve";
|
|
||||||
}
|
|
||||||
// 仅处理 type 为 pipe 或 junction 的情况
|
|
||||||
if (type !== "pipe" && type !== "junction") {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return [id, type];
|
|
||||||
})
|
|
||||||
.filter(Boolean) as [string, string][];
|
|
||||||
})()}
|
|
||||||
scheme_type="burst_analysis"
|
|
||||||
scheme_name={schemeName}
|
|
||||||
type={chatPanelFeatureInfos ? chatPanelType : (queryType as "realtime" | "scheme" | "none")}
|
|
||||||
start_time={chatPanelTimeRange?.startTime}
|
|
||||||
end_time={chatPanelTimeRange?.endTime}
|
|
||||||
onClose={() => {
|
|
||||||
deactivateTool("history");
|
|
||||||
setActiveTools((prev) => prev.filter((t) => t !== "history"));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<HistoryDataPanel
|
|
||||||
featureInfos={chatPanelFeatureInfos ?? (() => {
|
|
||||||
if (highlightFeatures.length === 0 || !showHistoryPanel)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
return highlightFeatures
|
|
||||||
.map((feature) => {
|
|
||||||
const properties = feature.getProperties();
|
|
||||||
const id = properties.id;
|
|
||||||
if (!id) return null;
|
|
||||||
|
|
||||||
// 从图层名称推断类型
|
|
||||||
const layerId =
|
|
||||||
feature.getId()?.toString().split(".")[0] || "";
|
|
||||||
let type = "unknown";
|
|
||||||
|
|
||||||
if (layerId.includes("pipe")) {
|
|
||||||
type = "pipe";
|
|
||||||
} else if (layerId.includes("junction")) {
|
|
||||||
type = "junction";
|
|
||||||
} else if (layerId.includes("tank")) {
|
|
||||||
type = "tank";
|
|
||||||
} else if (layerId.includes("reservoir")) {
|
|
||||||
type = "reservoir";
|
|
||||||
} else if (layerId.includes("pump")) {
|
|
||||||
type = "pump";
|
|
||||||
} else if (layerId.includes("valve")) {
|
|
||||||
type = "valve";
|
|
||||||
}
|
|
||||||
// 仅处理 type 为 pipe 或 junction 的情况
|
|
||||||
if (type !== "pipe" && type !== "junction") {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return [id, type];
|
|
||||||
})
|
|
||||||
.filter(Boolean) as [string, string][];
|
|
||||||
})()}
|
|
||||||
scheme_type="burst_analysis"
|
|
||||||
scheme_name={schemeName}
|
|
||||||
type={chatPanelFeatureInfos ? chatPanelType : (queryType as "realtime" | "scheme" | "none")}
|
|
||||||
start_time={chatPanelTimeRange?.startTime}
|
|
||||||
end_time={chatPanelTimeRange?.endTime}
|
|
||||||
onClose={() => {
|
|
||||||
deactivateTool("history");
|
|
||||||
setActiveTools((prev) => prev.filter((t) => t !== "history"));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{/* 图例显示 */}
|
{/* 图例显示 */}
|
||||||
{activeLegendConfigs.length > 0 && (
|
{activeLegendConfigs.length > 0 && (
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { useMemo } from "react";
|
||||||
|
import Feature from "ol/Feature";
|
||||||
|
|
||||||
|
import SCADADataPanel from "@components/olmap/SCADA/SCADADataPanel";
|
||||||
|
import { inferHistoryFeatureInfos } from "./toolbarFeatureHelpers";
|
||||||
|
import HistoryDataPanel from "./HistoryDataPanel";
|
||||||
|
|
||||||
|
type ToolbarHistoryPanelProps = {
|
||||||
|
showHistoryPanel: boolean;
|
||||||
|
chatPanelType: "realtime" | "scheme" | "none";
|
||||||
|
chatPanelFeatureInfos: [string, string][] | null;
|
||||||
|
chatPanelTimeRange: {
|
||||||
|
startTime?: string;
|
||||||
|
endTime?: string;
|
||||||
|
} | null;
|
||||||
|
highlightFeatures: Feature[];
|
||||||
|
HistoryPanel?: React.FC<any>;
|
||||||
|
schemeName?: string;
|
||||||
|
queryType?: string;
|
||||||
|
onClose: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ToolbarHistoryPanel: React.FC<ToolbarHistoryPanelProps> = ({
|
||||||
|
showHistoryPanel,
|
||||||
|
chatPanelType,
|
||||||
|
chatPanelFeatureInfos,
|
||||||
|
chatPanelTimeRange,
|
||||||
|
highlightFeatures,
|
||||||
|
HistoryPanel,
|
||||||
|
schemeName,
|
||||||
|
queryType,
|
||||||
|
onClose,
|
||||||
|
}) => {
|
||||||
|
const featureInfos = useMemo(
|
||||||
|
() => chatPanelFeatureInfos ?? inferHistoryFeatureInfos(highlightFeatures),
|
||||||
|
[chatPanelFeatureInfos, highlightFeatures],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!showHistoryPanel) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chatPanelType === "none" && chatPanelFeatureInfos) {
|
||||||
|
return (
|
||||||
|
<SCADADataPanel
|
||||||
|
deviceIds={chatPanelFeatureInfos.map(([id]) => id)}
|
||||||
|
visible={showHistoryPanel}
|
||||||
|
start_time={chatPanelTimeRange?.startTime}
|
||||||
|
end_time={chatPanelTimeRange?.endTime}
|
||||||
|
onClose={onClose}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HistoryPanel) {
|
||||||
|
return (
|
||||||
|
<HistoryPanel
|
||||||
|
featureInfos={featureInfos}
|
||||||
|
scheme_type="burst_analysis"
|
||||||
|
scheme_name={schemeName}
|
||||||
|
type={
|
||||||
|
chatPanelFeatureInfos
|
||||||
|
? chatPanelType
|
||||||
|
: (queryType as "realtime" | "scheme" | "none")
|
||||||
|
}
|
||||||
|
start_time={chatPanelTimeRange?.startTime}
|
||||||
|
end_time={chatPanelTimeRange?.endTime}
|
||||||
|
onClose={onClose}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HistoryDataPanel
|
||||||
|
featureInfos={featureInfos}
|
||||||
|
scheme_type="burst_analysis"
|
||||||
|
scheme_name={schemeName}
|
||||||
|
type={
|
||||||
|
chatPanelFeatureInfos
|
||||||
|
? chatPanelType
|
||||||
|
: (queryType as "realtime" | "scheme" | "none")
|
||||||
|
}
|
||||||
|
start_time={chatPanelTimeRange?.startTime}
|
||||||
|
end_time={chatPanelTimeRange?.endTime}
|
||||||
|
onClose={onClose}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ToolbarHistoryPanel;
|
||||||
@@ -0,0 +1,350 @@
|
|||||||
|
import Feature from "ol/Feature";
|
||||||
|
|
||||||
|
import { FLOW_DISPLAY_UNIT, toM3h } from "@utils/units";
|
||||||
|
|
||||||
|
type ToolbarBaseProperty = {
|
||||||
|
label: string;
|
||||||
|
value: string | number;
|
||||||
|
unit?: string;
|
||||||
|
formatter?: (value: string | number) => string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ToolbarTableProperty = {
|
||||||
|
type: "table";
|
||||||
|
label: string;
|
||||||
|
columns: string[];
|
||||||
|
rows: (string | number)[][];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ToolbarPropertyItem = ToolbarBaseProperty | ToolbarTableProperty;
|
||||||
|
|
||||||
|
export type ToolbarPropertyPanelData = {
|
||||||
|
id?: string;
|
||||||
|
type?: string;
|
||||||
|
properties?: ToolbarPropertyItem[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFeatureHistoryType = (feature: Feature): string | null => {
|
||||||
|
const layerId = feature.getId()?.toString().split(".")[0] || "";
|
||||||
|
if (layerId.includes("pipe")) return "pipe";
|
||||||
|
if (layerId.includes("junction")) return "junction";
|
||||||
|
if (layerId.includes("tank")) return "tank";
|
||||||
|
if (layerId.includes("reservoir")) return "reservoir";
|
||||||
|
if (layerId.includes("pump")) return "pump";
|
||||||
|
if (layerId.includes("valve")) return "valve";
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const inferHistoryFeatureInfos = (
|
||||||
|
highlightFeatures: Feature[],
|
||||||
|
): [string, string][] =>
|
||||||
|
highlightFeatures
|
||||||
|
.map((feature) => {
|
||||||
|
const properties = feature.getProperties();
|
||||||
|
const id = properties.id;
|
||||||
|
if (!id) return null;
|
||||||
|
|
||||||
|
const type = getFeatureHistoryType(feature);
|
||||||
|
if (type !== "pipe" && type !== "junction") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [id, type] as [string, string];
|
||||||
|
})
|
||||||
|
.filter(Boolean) as [string, string][];
|
||||||
|
|
||||||
|
export const buildFeatureProperties = (
|
||||||
|
highlightFeature: Feature | undefined,
|
||||||
|
computedProperties: Record<string, any>,
|
||||||
|
): ToolbarPropertyPanelData => {
|
||||||
|
if (!highlightFeature) return {};
|
||||||
|
|
||||||
|
const layer = highlightFeature.getId()?.toString().split(".")[0];
|
||||||
|
const properties = highlightFeature.getProperties();
|
||||||
|
const pipeComputedFields = [
|
||||||
|
{ key: "flow", label: "流量", unit: `${FLOW_DISPLAY_UNIT}` },
|
||||||
|
{ key: "friction", label: "摩阻", unit: "" },
|
||||||
|
{ key: "headloss", label: "水头损失", unit: "m" },
|
||||||
|
{ key: "unit_headloss", label: "单位水头损失", unit: "m/km" },
|
||||||
|
{ key: "quality", label: "水质", unit: "mg/L" },
|
||||||
|
{ key: "reaction", label: "反应", unit: "1/d" },
|
||||||
|
{ key: "setting", label: "设置", unit: "" },
|
||||||
|
{ key: "status", label: "状态", unit: "" },
|
||||||
|
{ key: "velocity", label: "流速", unit: "m/s" },
|
||||||
|
];
|
||||||
|
const nodeComputedFields = [
|
||||||
|
{ key: "actual_demand", label: "实际需水量", unit: `${FLOW_DISPLAY_UNIT}` },
|
||||||
|
{ key: "total_head", label: "水头", unit: "m" },
|
||||||
|
{ key: "pressure", label: "压力", unit: "m" },
|
||||||
|
{ key: "quality", label: "水质", unit: "mg/L" },
|
||||||
|
];
|
||||||
|
|
||||||
|
if (layer === "geo_pipes_mat" || layer === "geo_pipes") {
|
||||||
|
const result: ToolbarPropertyPanelData = {
|
||||||
|
id: properties.id,
|
||||||
|
type: "管道",
|
||||||
|
properties: [
|
||||||
|
{ label: "起始节点ID", value: properties.node1 },
|
||||||
|
{ label: "终点节点ID", value: properties.node2 },
|
||||||
|
{ label: "长度", value: properties.length?.toFixed?.(1), unit: "m" },
|
||||||
|
{
|
||||||
|
label: "管径",
|
||||||
|
value: properties.diameter?.toFixed?.(1),
|
||||||
|
unit: "mm",
|
||||||
|
},
|
||||||
|
{ label: "粗糙度", value: properties.roughness },
|
||||||
|
{ label: "局部损失", value: properties.minor_loss },
|
||||||
|
{ label: "初始状态", value: "开" },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
pipeComputedFields.forEach(({ key, label, unit }) => {
|
||||||
|
let value = computedProperties[key];
|
||||||
|
|
||||||
|
if (key === "flow" && value !== undefined) {
|
||||||
|
value = toM3h(value, "lps");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
key === "unit_headloss" &&
|
||||||
|
value === undefined &&
|
||||||
|
computedProperties.headloss !== undefined &&
|
||||||
|
properties.length
|
||||||
|
) {
|
||||||
|
value = (computedProperties.headloss / properties.length) * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value !== undefined) {
|
||||||
|
result.properties?.push({
|
||||||
|
label,
|
||||||
|
value: typeof value === "number" ? value.toFixed(3) : value,
|
||||||
|
unit,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (layer === "geo_junctions_mat" || layer === "geo_junctions") {
|
||||||
|
const result: ToolbarPropertyPanelData = {
|
||||||
|
id: properties.id,
|
||||||
|
type: "节点",
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
label: "高程",
|
||||||
|
value: properties.elevation?.toFixed?.(1),
|
||||||
|
unit: "m",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "table",
|
||||||
|
label: "基本需水量",
|
||||||
|
columns: ["demand", "pattern"],
|
||||||
|
rows: Array.from({ length: 5 }, (_, i) => i + 1)
|
||||||
|
.map((idx) => {
|
||||||
|
let demand = properties?.[`demand${idx}`];
|
||||||
|
const pattern = properties?.[`pattern${idx}`];
|
||||||
|
if (
|
||||||
|
demand !== undefined &&
|
||||||
|
demand !== null &&
|
||||||
|
demand !== ""
|
||||||
|
) {
|
||||||
|
demand = toM3h(Number(demand), "lps");
|
||||||
|
return [
|
||||||
|
typeof demand === "number" ? demand.toFixed(3) : demand,
|
||||||
|
pattern ?? "-",
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.filter(Boolean) as (string | number)[][],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
nodeComputedFields.forEach(({ key, label, unit }) => {
|
||||||
|
if (computedProperties[key] !== undefined) {
|
||||||
|
let value = computedProperties[key];
|
||||||
|
if (key === "actual_demand") {
|
||||||
|
value = toM3h(value, "lps");
|
||||||
|
}
|
||||||
|
result.properties?.push({
|
||||||
|
label,
|
||||||
|
value: value?.toFixed?.(3) || value,
|
||||||
|
unit,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (layer === "geo_tanks_mat" || layer === "geo_tanks") {
|
||||||
|
return {
|
||||||
|
id: properties.id,
|
||||||
|
type: "水池",
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
label: "高程",
|
||||||
|
value: properties.elevation?.toFixed?.(1),
|
||||||
|
unit: "m",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "初始水位",
|
||||||
|
value: properties.init_level?.toFixed?.(1),
|
||||||
|
unit: "m",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "最低水位",
|
||||||
|
value: properties.min_level?.toFixed?.(1),
|
||||||
|
unit: "m",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "最高水位",
|
||||||
|
value: properties.max_level?.toFixed?.(1),
|
||||||
|
unit: "m",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "直径",
|
||||||
|
value: properties.diameter?.toFixed?.(1),
|
||||||
|
unit: "m",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "最小容积",
|
||||||
|
value: properties.min_vol?.toFixed?.(1),
|
||||||
|
unit: "m³",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "溢出",
|
||||||
|
value: properties.overflow ? "是" : "否",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (layer === "geo_reservoirs_mat" || layer === "geo_reservoirs") {
|
||||||
|
return {
|
||||||
|
id: properties.id,
|
||||||
|
type: "水库",
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
label: "水头",
|
||||||
|
value: properties.head?.toFixed?.(1),
|
||||||
|
unit: "m",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (layer === "geo_pumps_mat" || layer === "geo_pumps") {
|
||||||
|
return {
|
||||||
|
id: properties.id,
|
||||||
|
type: "水泵",
|
||||||
|
properties: [
|
||||||
|
{ label: "起始节点 ID", value: properties.node1 },
|
||||||
|
{ label: "终点节点 ID", value: properties.node2 },
|
||||||
|
{
|
||||||
|
label: "功率",
|
||||||
|
value: properties.power?.toFixed?.(1),
|
||||||
|
unit: "kW",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "扬程",
|
||||||
|
value: properties.head?.toFixed?.(1),
|
||||||
|
unit: "m",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "转速",
|
||||||
|
value: properties.speed?.toFixed?.(1),
|
||||||
|
unit: "rpm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "模式",
|
||||||
|
value: properties.pattern,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (layer === "geo_valves_mat" || layer === "geo_valves") {
|
||||||
|
return {
|
||||||
|
id: properties.id,
|
||||||
|
type: "阀门",
|
||||||
|
properties: [
|
||||||
|
{ label: "起始节点 ID", value: properties.node1 },
|
||||||
|
{ label: "终点节点 ID", value: properties.node2 },
|
||||||
|
{
|
||||||
|
label: "直径",
|
||||||
|
value: properties.diameter?.toFixed?.(1),
|
||||||
|
unit: "mm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "阀门类型",
|
||||||
|
value: properties.v_type,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "局部损失",
|
||||||
|
value: properties.minor_loss?.toFixed?.(2),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTransmissionFrequency = (transmissionFrequency: string) => {
|
||||||
|
const parts = transmissionFrequency.split(":");
|
||||||
|
if (parts.length !== 3) return transmissionFrequency;
|
||||||
|
const hours = parseInt(parts[0], 10);
|
||||||
|
const minutes = parseInt(parts[1], 10);
|
||||||
|
const seconds = parseInt(parts[2], 10);
|
||||||
|
return hours * 60 + minutes + (seconds >= 30 ? 1 : 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getReliability = (reliability: number) => {
|
||||||
|
switch (reliability) {
|
||||||
|
case 1:
|
||||||
|
return "高";
|
||||||
|
case 2:
|
||||||
|
return "中";
|
||||||
|
case 3:
|
||||||
|
return "低";
|
||||||
|
default:
|
||||||
|
return "未知";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (layer === "geo_scada_mat" || layer === "geo_scada") {
|
||||||
|
return {
|
||||||
|
id: properties.id,
|
||||||
|
type: "SCADA设备",
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
label: "类型",
|
||||||
|
value:
|
||||||
|
properties.type === "pipe_flow" ? "流量传感器" : "压力传感器",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "关联节点 ID",
|
||||||
|
value: properties.associated_element_id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "传输模式",
|
||||||
|
value:
|
||||||
|
properties.transmission_mode === "non_realtime"
|
||||||
|
? "定时传输"
|
||||||
|
: "实时传输",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "传输频率",
|
||||||
|
value: getTransmissionFrequency(properties.transmission_frequency),
|
||||||
|
unit: "分钟",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "可靠性",
|
||||||
|
value: getReliability(properties.reliability),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
};
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
import { useCallback, useEffect, useRef, type Dispatch, type SetStateAction } from "react";
|
||||||
|
import Feature from "ol/Feature";
|
||||||
|
import { GeoJSON } from "ol/format";
|
||||||
|
import Point from "ol/geom/Point";
|
||||||
|
import { bbox, featureCollection } from "@turf/turf";
|
||||||
|
|
||||||
|
import { useChatToolActionHandler } from "@/hooks/useChatToolActionHandler";
|
||||||
|
import { applyJunctionAreaRender } from "@components/olmap/DMALeakDetection/applyJunctionAreaRender";
|
||||||
|
import { queryFeaturesByIds } from "@/utils/mapQueryService";
|
||||||
|
import { useMap } from "../MapComponent";
|
||||||
|
|
||||||
|
type UseToolbarChatActionsParams = {
|
||||||
|
setHighlightFeatures: Dispatch<SetStateAction<Feature[]>>;
|
||||||
|
setChatPanelFeatureInfos: Dispatch<SetStateAction<[string, string][] | null>>;
|
||||||
|
setChatPanelType: Dispatch<SetStateAction<"realtime" | "scheme" | "none">>;
|
||||||
|
setChatPanelTimeRange: Dispatch<
|
||||||
|
SetStateAction<{ startTime?: string; endTime?: string } | null>
|
||||||
|
>;
|
||||||
|
setShowHistoryPanel: Dispatch<SetStateAction<boolean>>;
|
||||||
|
setActiveTools: Dispatch<SetStateAction<string[]>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useToolbarChatActions = ({
|
||||||
|
setHighlightFeatures,
|
||||||
|
setChatPanelFeatureInfos,
|
||||||
|
setChatPanelType,
|
||||||
|
setChatPanelTimeRange,
|
||||||
|
setShowHistoryPanel,
|
||||||
|
setActiveTools,
|
||||||
|
}: UseToolbarChatActionsParams) => {
|
||||||
|
const map = useMap();
|
||||||
|
const chatJunctionRenderCleanupRef = useRef<(() => void) | null>(null);
|
||||||
|
|
||||||
|
const disposeChatJunctionRender = useCallback(() => {
|
||||||
|
chatJunctionRenderCleanupRef.current?.();
|
||||||
|
chatJunctionRenderCleanupRef.current = null;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => () => disposeChatJunctionRender(), [disposeChatJunctionRender]);
|
||||||
|
|
||||||
|
useChatToolActionHandler(
|
||||||
|
useCallback(
|
||||||
|
(action) => {
|
||||||
|
const geojsonFormat = new GeoJSON();
|
||||||
|
const zoomToFeatures = (
|
||||||
|
features: Feature[],
|
||||||
|
geometryKind: "point" | "line",
|
||||||
|
) => {
|
||||||
|
if (features.length === 0) return;
|
||||||
|
|
||||||
|
if (geometryKind === "point" && features.length === 1) {
|
||||||
|
const geometry = features[0].getGeometry();
|
||||||
|
if (geometry instanceof Point) {
|
||||||
|
map?.getView().animate({
|
||||||
|
center: geometry.getCoordinates(),
|
||||||
|
zoom: 18,
|
||||||
|
duration: 1000,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const geojsonFeatures = features.map((feature) =>
|
||||||
|
geojsonFormat.writeFeatureObject(feature),
|
||||||
|
);
|
||||||
|
const extent = bbox(featureCollection(geojsonFeatures as any));
|
||||||
|
if (extent) {
|
||||||
|
map?.getView().fit(extent, {
|
||||||
|
maxZoom: 18,
|
||||||
|
duration: 1000,
|
||||||
|
padding:
|
||||||
|
geometryKind === "line"
|
||||||
|
? [60, 60, 60, 60]
|
||||||
|
: [40, 40, 40, 40],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const locateFeatures = (
|
||||||
|
ids: string[],
|
||||||
|
layer: string,
|
||||||
|
geometryKind: "point" | "line",
|
||||||
|
) => {
|
||||||
|
queryFeaturesByIds(ids, layer).then((features) => {
|
||||||
|
if (features.length > 0) {
|
||||||
|
setHighlightFeatures(features);
|
||||||
|
zoomToFeatures(features, geometryKind);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (action.type) {
|
||||||
|
case "locate_features": {
|
||||||
|
locateFeatures(action.ids, action.layer, action.geometryKind);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "view_history": {
|
||||||
|
setChatPanelFeatureInfos(action.featureInfos);
|
||||||
|
setChatPanelType(action.dataType);
|
||||||
|
setChatPanelTimeRange({
|
||||||
|
startTime: action.startTime,
|
||||||
|
endTime: action.endTime,
|
||||||
|
});
|
||||||
|
setShowHistoryPanel(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "view_scada": {
|
||||||
|
setChatPanelFeatureInfos(action.featureInfos);
|
||||||
|
setChatPanelType("none");
|
||||||
|
setChatPanelTimeRange({
|
||||||
|
startTime: action.startTime,
|
||||||
|
endTime: action.endTime,
|
||||||
|
});
|
||||||
|
setShowHistoryPanel(true);
|
||||||
|
setActiveTools((prev) => {
|
||||||
|
if (prev.includes("history")) {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
return [...prev, "history"];
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "render_junctions": {
|
||||||
|
disposeChatJunctionRender();
|
||||||
|
|
||||||
|
if (Object.keys(action.nodeAreaMap).length === 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (map) {
|
||||||
|
chatJunctionRenderCleanupRef.current = applyJunctionAreaRender(
|
||||||
|
map,
|
||||||
|
{
|
||||||
|
nodeAreaMap: action.nodeAreaMap,
|
||||||
|
areaIds: action.areaIds,
|
||||||
|
areaColors: action.areaColors,
|
||||||
|
},
|
||||||
|
{ propertyKey: "chat_junction_render_index" },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
disposeChatJunctionRender,
|
||||||
|
map,
|
||||||
|
setActiveTools,
|
||||||
|
setChatPanelFeatureInfos,
|
||||||
|
setChatPanelTimeRange,
|
||||||
|
setChatPanelType,
|
||||||
|
setHighlightFeatures,
|
||||||
|
setShowHistoryPanel,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user