851 lines
28 KiB
TypeScript
851 lines
28 KiB
TypeScript
import React, { useState, useEffect, useCallback } from "react";
|
||
import { useData, useMap } from "../MapComponent";
|
||
import ToolbarButton from "@/components/olmap/common/ToolbarButton";
|
||
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
|
||
import EditOutlinedIcon from "@mui/icons-material/EditOutlined";
|
||
import PaletteOutlinedIcon from "@mui/icons-material/PaletteOutlined";
|
||
import QueryStatsOutlinedIcon from "@mui/icons-material/QueryStatsOutlined";
|
||
import PropertyPanel from "./PropertyPanel"; // 引入属性面板组件
|
||
import DrawPanel from "./DrawPanel"; // 引入绘图面板组件
|
||
import HistoryDataPanel from "./HistoryDataPanel"; // 引入绘图面板组件
|
||
|
||
import VectorSource from "ol/source/Vector";
|
||
import VectorLayer from "ol/layer/Vector";
|
||
import { Style, Stroke, Fill, Circle } from "ol/style";
|
||
import Feature from "ol/Feature";
|
||
import StyleEditorPanel from "./StyleEditorPanel";
|
||
import { LayerStyleState } from "./StyleEditorPanel";
|
||
import StyleLegend from "./StyleLegend"; // 引入图例组件
|
||
import { handleMapClickSelectFeatures as mapClickSelectFeatures } from "@/utils/mapQueryService";
|
||
import { useNotification } from "@refinedev/core";
|
||
|
||
import { config } from "@/config/config";
|
||
const backendUrl = config.BACKEND_URL;
|
||
|
||
// 添加接口定义隐藏按钮的props
|
||
interface ToolbarProps {
|
||
hiddenButtons?: string[]; // 可选的隐藏按钮列表,例如 ['info', 'draw', 'style']
|
||
queryType?: string; // 可选的查询类型参数
|
||
HistoryPanel?: React.FC<any>; // 可选的自定义历史数据面板
|
||
}
|
||
const Toolbar: React.FC<ToolbarProps> = ({
|
||
hiddenButtons,
|
||
queryType,
|
||
HistoryPanel,
|
||
}) => {
|
||
const map = useMap();
|
||
const data = useData();
|
||
const { open } = useNotification();
|
||
if (!data) return null;
|
||
const { currentTime, selectedDate, schemeName } = data;
|
||
const [activeTools, setActiveTools] = useState<string[]>([]);
|
||
const [highlightFeatures, setHighlightFeatures] = useState<Feature[]>([]);
|
||
const [showPropertyPanel, setShowPropertyPanel] = useState<boolean>(false);
|
||
const [showDrawPanel, setShowDrawPanel] = useState<boolean>(false);
|
||
const [showStyleEditor, setShowStyleEditor] = useState<boolean>(false);
|
||
const [showHistoryPanel, setShowHistoryPanel] = useState<boolean>(false);
|
||
const [highlightLayer, setHighlightLayer] =
|
||
useState<VectorLayer<VectorSource> | null>(null);
|
||
|
||
// 样式状态管理 - 在 Toolbar 中管理,带有默认样式
|
||
const [layerStyleStates, setLayerStyleStates] = useState<LayerStyleState[]>([
|
||
{
|
||
isActive: false, // 默认不激活,不显示图例
|
||
layerId: "junctions",
|
||
layerName: "节点",
|
||
styleConfig: {
|
||
property: "pressure",
|
||
classificationMethod: "custom_breaks",
|
||
customBreaks: [16, 18, 20, 22, 24, 26],
|
||
customColors: [
|
||
"rgba(255, 0, 0, 1)",
|
||
"rgba(255, 127, 0, 1)",
|
||
"rgba(255, 215, 0, 1)",
|
||
"rgba(199, 224, 0, 1)",
|
||
"rgba(76, 175, 80, 1)",
|
||
"rgba(0, 158, 115, 1)",
|
||
],
|
||
segments: 6,
|
||
minSize: 4,
|
||
maxSize: 12,
|
||
minStrokeWidth: 2,
|
||
maxStrokeWidth: 8,
|
||
fixedStrokeWidth: 3,
|
||
colorType: "rainbow",
|
||
singlePaletteIndex: 0,
|
||
gradientPaletteIndex: 0,
|
||
rainbowPaletteIndex: 0,
|
||
showLabels: false,
|
||
showId: false,
|
||
opacity: 0.9,
|
||
adjustWidthByProperty: true,
|
||
},
|
||
legendConfig: {
|
||
layerId: "junctions",
|
||
layerName: "节点",
|
||
property: "压力", // 暂时为空,等计算后更新
|
||
colors: [],
|
||
type: "point",
|
||
dimensions: [],
|
||
breaks: [],
|
||
},
|
||
},
|
||
{
|
||
isActive: false, // 默认不激活,不显示图例
|
||
layerId: "pipes",
|
||
layerName: "管道",
|
||
styleConfig: {
|
||
property: "flow",
|
||
classificationMethod: "pretty_breaks",
|
||
segments: 6,
|
||
minSize: 4,
|
||
maxSize: 12,
|
||
minStrokeWidth: 2,
|
||
maxStrokeWidth: 8,
|
||
fixedStrokeWidth: 3,
|
||
colorType: "gradient",
|
||
singlePaletteIndex: 0,
|
||
gradientPaletteIndex: 0,
|
||
rainbowPaletteIndex: 0,
|
||
showLabels: false,
|
||
showId: false,
|
||
opacity: 0.9,
|
||
adjustWidthByProperty: true,
|
||
},
|
||
legendConfig: {
|
||
layerId: "pipes",
|
||
layerName: "管道",
|
||
property: "流量", // 暂时为空,等计算后更新
|
||
colors: [],
|
||
type: "linestring",
|
||
dimensions: [],
|
||
breaks: [],
|
||
},
|
||
},
|
||
]);
|
||
|
||
// 计算激活的图例配置
|
||
const activeLegendConfigs = layerStyleStates
|
||
.filter((state) => state.isActive && state.legendConfig.property)
|
||
.map((state) => ({
|
||
...state.legendConfig,
|
||
layerName: state.layerName,
|
||
layerId: state.layerId,
|
||
}));
|
||
// 创建高亮图层
|
||
useEffect(() => {
|
||
if (!map) return;
|
||
|
||
const highLightSource = new VectorSource();
|
||
const highLightLayer = new VectorLayer({
|
||
source: highLightSource,
|
||
style: new Style({
|
||
stroke: new Stroke({
|
||
color: `rgba(255, 0, 0, 1)`,
|
||
width: 5,
|
||
}),
|
||
fill: new Fill({
|
||
color: `rgba(255, 0, 0, 0.2)`,
|
||
}),
|
||
image: new Circle({
|
||
radius: 7,
|
||
stroke: new Stroke({
|
||
color: `rgba(255, 0, 0, 1)`,
|
||
width: 3,
|
||
}),
|
||
fill: new Fill({
|
||
color: `rgba(255, 0, 0, 0.2)`,
|
||
}),
|
||
}),
|
||
}),
|
||
properties: {
|
||
name: "属性查询高亮图层", // 设置图层名称
|
||
value: "info_highlight_layer",
|
||
type: "multigeometry",
|
||
properties: [],
|
||
},
|
||
});
|
||
|
||
map.addLayer(highLightLayer);
|
||
setHighlightLayer(highLightLayer);
|
||
|
||
return () => {
|
||
map.removeLayer(highLightLayer);
|
||
};
|
||
}, [map]);
|
||
// 高亮要素的函数
|
||
useEffect(() => {
|
||
if (!highlightLayer) {
|
||
return;
|
||
}
|
||
const source = highlightLayer.getSource();
|
||
if (!source) {
|
||
return;
|
||
}
|
||
// 清除之前的高亮
|
||
source.clear();
|
||
// 添加新的高亮要素
|
||
highlightFeatures.forEach((feature) => {
|
||
if (feature instanceof Feature) {
|
||
source.addFeature(feature);
|
||
}
|
||
});
|
||
}, [highlightFeatures, highlightLayer]);
|
||
// 地图点击选择要素事件处理函数
|
||
const handleMapClickSelectFeatures = useCallback(
|
||
async (event: { coordinate: number[] }) => {
|
||
if (!map) return;
|
||
const feature = await mapClickSelectFeatures(event, map); // 调用导入的函数
|
||
|
||
if (!feature || !(feature instanceof Feature)) {
|
||
// 如果没有点击到要素,且当前是 info 模式,则清除高亮
|
||
if (activeTools.includes("info")) {
|
||
setHighlightFeatures([]);
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (activeTools.includes("history")) {
|
||
// 历史查询模式:支持同类型多选
|
||
const featureId = feature.getProperties().id;
|
||
const layerId = feature.getId()?.toString().split(".")[0] || "";
|
||
console.log("点击选择要素", feature, "图层:", layerId);
|
||
// 简单的类型检查函数
|
||
const getBaseType = (lid: string) => {
|
||
if (lid.includes("pipe")) return "pipe";
|
||
if (lid.includes("junction")) return "junction";
|
||
if (lid.includes("tank")) return "tank";
|
||
if (lid.includes("reservoir")) return "reservoir";
|
||
if (lid.includes("pump")) return "pump";
|
||
if (lid.includes("valve")) return "valve";
|
||
return lid;
|
||
};
|
||
|
||
// 检查是否与已选要素类型一致
|
||
if (highlightFeatures.length > 0) {
|
||
const firstLayerId =
|
||
highlightFeatures[0].getId()?.toString().split(".")[0] || "";
|
||
|
||
if (getBaseType(layerId) !== getBaseType(firstLayerId)) {
|
||
// 如果点击的是已选中的要素(为了取消选中),则不报错
|
||
const isAlreadySelected = highlightFeatures.some(
|
||
(f) => f.getProperties().id === featureId
|
||
);
|
||
if (!isAlreadySelected) {
|
||
open?.({
|
||
type: "error",
|
||
message: "请选择相同类型的要素进行多选查询。",
|
||
});
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
setHighlightFeatures((prev) => {
|
||
const existingIndex = prev.findIndex(
|
||
(f) => f.getProperties().id === featureId
|
||
);
|
||
|
||
if (existingIndex !== -1) {
|
||
// 如果已存在,移除
|
||
return prev.filter((_, i) => i !== existingIndex);
|
||
} else {
|
||
// 如果不存在,添加
|
||
return [...prev, feature];
|
||
}
|
||
});
|
||
} else {
|
||
// 其他模式(如 info):单选
|
||
setHighlightFeatures([feature]);
|
||
}
|
||
},
|
||
[map, activeTools, highlightFeatures, open]
|
||
);
|
||
// 添加矢量属性查询事件监听器
|
||
useEffect(() => {
|
||
if (!map) return;
|
||
// 监听 info 或 history 工具激活时添加
|
||
if (activeTools.includes("info") || activeTools.includes("history")) {
|
||
map.on("click", handleMapClickSelectFeatures);
|
||
|
||
return () => {
|
||
map.un("click", handleMapClickSelectFeatures);
|
||
};
|
||
}
|
||
}, [activeTools, map, handleMapClickSelectFeatures]);
|
||
|
||
// 处理工具栏按钮点击事件
|
||
const handleToolClick = (tool: string) => {
|
||
// 样式工具的特殊处理 - 只有再次点击时才会取消激活和关闭
|
||
if (tool === "style") {
|
||
if (activeTools.includes("style")) {
|
||
// 如果样式工具已激活,点击时关闭
|
||
setShowStyleEditor(false);
|
||
setActiveTools((prev) => prev.filter((t) => t !== "style"));
|
||
} else {
|
||
// 激活样式工具,打开样式面板
|
||
setActiveTools((prev) => [...prev, "style"]);
|
||
setShowStyleEditor(true);
|
||
}
|
||
return;
|
||
}
|
||
|
||
// 其他工具的处理逻辑
|
||
if (activeTools.includes(tool)) {
|
||
// 如果当前工具已激活,再次点击时取消激活并关闭面板
|
||
deactivateTool(tool);
|
||
setActiveTools((prev) => prev.filter((t) => t !== tool));
|
||
} else {
|
||
// 如果当前工具未激活,先关闭所有其他工具,然后激活当前工具
|
||
// 关闭所有面板(但保持样式编辑器状态)
|
||
closeAllPanelsExceptStyle();
|
||
|
||
// 取消激活所有非样式工具
|
||
setActiveTools((prev) => {
|
||
const styleActive = prev.includes("style");
|
||
return styleActive ? ["style", tool] : [tool];
|
||
});
|
||
|
||
// 激活当前工具并打开对应面板
|
||
activateTool(tool);
|
||
}
|
||
};
|
||
|
||
// 取消激活指定工具并关闭对应面板
|
||
const deactivateTool = (tool: string) => {
|
||
switch (tool) {
|
||
case "info":
|
||
setShowPropertyPanel(false);
|
||
setHighlightFeatures([]);
|
||
break;
|
||
case "draw":
|
||
setShowDrawPanel(false);
|
||
break;
|
||
case "history":
|
||
setShowHistoryPanel(false);
|
||
setHighlightFeatures([]);
|
||
break;
|
||
}
|
||
};
|
||
|
||
// 激活指定工具并打开对应面板
|
||
const activateTool = (tool: string) => {
|
||
switch (tool) {
|
||
case "info":
|
||
setShowPropertyPanel(true);
|
||
break;
|
||
case "draw":
|
||
setShowDrawPanel(true);
|
||
break;
|
||
case "history":
|
||
setShowHistoryPanel(true);
|
||
// 激活历史查询后:HistoryDataPanel 自行负责根据传入的 props 拉取数据。
|
||
break;
|
||
}
|
||
};
|
||
|
||
// 关闭所有面板(除了样式编辑器)
|
||
const closeAllPanelsExceptStyle = () => {
|
||
setShowPropertyPanel(false);
|
||
setHighlightFeatures([]);
|
||
setShowDrawPanel(false);
|
||
setShowHistoryPanel(false);
|
||
// 样式编辑器保持其当前状态,不自动关闭
|
||
};
|
||
const [computedProperties, setComputedProperties] = useState<
|
||
Record<string, any>
|
||
>({});
|
||
// 添加 useEffect 来查询计算属性
|
||
useEffect(() => {
|
||
if (highlightFeatures.length === 0 || !selectedDate || !showPropertyPanel) {
|
||
setComputedProperties({});
|
||
return;
|
||
}
|
||
|
||
const highlightFeature = highlightFeatures[0];
|
||
const id = highlightFeature.getProperties().id;
|
||
if (!id) {
|
||
setComputedProperties({});
|
||
return;
|
||
}
|
||
|
||
const queryComputedProperties = async () => {
|
||
try {
|
||
const properties = highlightFeature?.getProperties?.() || {};
|
||
const type =
|
||
properties.geometry?.getType?.() === "LineString" ? "link" : "node";
|
||
// selectedDate 格式化为 YYYY-MM-DD
|
||
let dateObj: Date;
|
||
if (selectedDate instanceof Date) {
|
||
dateObj = new Date(selectedDate);
|
||
} else {
|
||
dateObj = new Date(selectedDate);
|
||
}
|
||
const minutes = Number(currentTime) || 0;
|
||
dateObj.setHours(Math.floor(minutes / 60), minutes % 60, 0, 0);
|
||
// 转为 UTC ISO 字符串
|
||
const querytime = dateObj.toISOString(); // 例如 "2025-09-16T16:30:00.000Z"
|
||
let response;
|
||
if (queryType === "scheme") {
|
||
response = await fetch(
|
||
// `${backendUrl}/queryschemesimulationrecordsbyidtime/?scheme_name=${schemeName}&id=${id}&querytime=${querytime}&type=${type}`
|
||
`${backendUrl}/timescaledb/scheme/query/by-id-time?scheme_name=${schemeName}&id=${id}&type=${type}&query_time=${querytime}`
|
||
);
|
||
} else {
|
||
response = await fetch(
|
||
// `${backendUrl}/querysimulationrecordsbyidtime/?id=${id}&querytime=${querytime}&type=${type}`
|
||
`${backendUrl}/timescaledb/realtime/query/by-id-time?id=${id}&type=${type}&query_time=${querytime}`
|
||
);
|
||
}
|
||
if (!response.ok) {
|
||
throw new Error("API request failed");
|
||
}
|
||
const data = await response.json();
|
||
setComputedProperties(data.results[0] || {});
|
||
} catch (error) {
|
||
console.error("Error querying computed properties:", error);
|
||
setComputedProperties({});
|
||
}
|
||
};
|
||
// 仅当 currentTime 有效时查询
|
||
if (currentTime !== -1 && queryType) queryComputedProperties();
|
||
}, [highlightFeatures, currentTime, selectedDate]);
|
||
|
||
// 从要素属性中提取属性面板需要的数据
|
||
const getFeatureProperties = useCallback(() => {
|
||
if (highlightFeatures.length === 0) return {};
|
||
const highlightFeature = highlightFeatures[0];
|
||
const layer = highlightFeature?.getId()?.toString().split(".")[0];
|
||
const properties = highlightFeature.getProperties();
|
||
// 计算属性字段,增加 key 字段
|
||
const pipeComputedFields = [
|
||
{ key: "flow", label: "流量", unit: "m³/h" },
|
||
{ 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: "m³/h" },
|
||
{ 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];
|
||
// 如果是单位水头损失且后端未返回,则通过水头损失/长度计算 (单位 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) => {
|
||
const d = properties?.[`demand${idx}`]?.toFixed?.(3);
|
||
const p = properties?.[`pattern${idx}`];
|
||
// 仅当 demand 有效时展示该行
|
||
if (d !== undefined && d !== null && d !== "") {
|
||
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) {
|
||
result.properties.push({
|
||
label,
|
||
value:
|
||
computedProperties[key].toFixed?.(3) || computedProperties[key],
|
||
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]);
|
||
|
||
return (
|
||
<>
|
||
<div className="absolute top-4 left-4 bg-white p-1 rounded-xl shadow-lg flex opacity-85 hover:opacity-100 transition-opacity">
|
||
{!hiddenButtons?.includes("info") && (
|
||
<ToolbarButton
|
||
icon={<InfoOutlinedIcon />}
|
||
name="查看属性"
|
||
isActive={activeTools.includes("info")}
|
||
onClick={() => handleToolClick("info")}
|
||
/>
|
||
)}
|
||
{!hiddenButtons?.includes("history") && (
|
||
<ToolbarButton
|
||
icon={<QueryStatsOutlinedIcon />}
|
||
name="查询历史数据"
|
||
isActive={activeTools.includes("history")}
|
||
onClick={() => handleToolClick("history")}
|
||
/>
|
||
)}
|
||
{!hiddenButtons?.includes("draw") && (
|
||
<ToolbarButton
|
||
icon={<EditOutlinedIcon />}
|
||
name="标记绘制"
|
||
isActive={activeTools.includes("draw")}
|
||
onClick={() => handleToolClick("draw")}
|
||
/>
|
||
)}
|
||
{!hiddenButtons?.includes("style") && (
|
||
<ToolbarButton
|
||
icon={<PaletteOutlinedIcon />}
|
||
name="图层样式"
|
||
isActive={activeTools.includes("style")}
|
||
onClick={() => handleToolClick("style")}
|
||
/>
|
||
)}
|
||
</div>
|
||
{showPropertyPanel && <PropertyPanel {...getFeatureProperties()} />}
|
||
{showDrawPanel && map && <DrawPanel />}
|
||
<div style={{ display: showStyleEditor ? "block" : "none" }}>
|
||
<StyleEditorPanel
|
||
layerStyleStates={layerStyleStates}
|
||
setLayerStyleStates={setLayerStyleStates}
|
||
/>
|
||
</div>
|
||
{showHistoryPanel &&
|
||
(HistoryPanel ? (
|
||
<HistoryPanel
|
||
featureInfos={(() => {
|
||
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={queryType as "realtime" | "scheme" | "none"}
|
||
/>
|
||
) : (
|
||
<HistoryDataPanel
|
||
featureInfos={(() => {
|
||
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={queryType as "realtime" | "scheme" | "none"}
|
||
/>
|
||
))}
|
||
|
||
{/* 图例显示 */}
|
||
{activeLegendConfigs.length > 0 && (
|
||
<div className="absolute bottom-40 right-4 drop-shadow-xl flex flex-row items-end max-w-screen-lg overflow-x-auto z-10">
|
||
<div className="flex flex-row gap-3">
|
||
{activeLegendConfigs.map((config, index) => (
|
||
<StyleLegend key={`${config.layerId}-${index}`} {...config} />
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</>
|
||
);
|
||
};
|
||
|
||
export default Toolbar;
|