调整数据小数点位数显示;更换 valve 图标,上调 valvesLayer 的显示层级;特殊处理流量的样式显示

This commit is contained in:
JIANG
2025-11-19 14:39:21 +08:00
parent bb040f1612
commit 538b9fe177
6 changed files with 140 additions and 86 deletions

View File

@@ -1 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1761789564862" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="70077" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M513.640525 513.911813m-510.088186 0a510.088187 510.088187 0 1 0 1020.176373 0 510.088187 510.088187 0 1 0-1020.176373 0Z" fill="#F1C339" p-id="70078"></path><path d="M483.307444 389.380334v-87.682364H351.164541c-17.997151 0-32.651827-14.204236-32.651827-31.628099 0-17.428981 14.670032-31.633217 32.651827-31.633217h320.186511c17.986913 0 32.64159 14.204236 32.64159 31.633217 0 17.613252-14.654677 31.628099-32.631353 31.628099h-131.948393v87.677246h10.349897c12.315456 0 22.286574 9.65888 22.286574 21.590438v51.590809l-121.393751-0.194509v-51.391181c0-11.931558 9.965999-21.590439 22.286574-21.590439h10.365254z m-217.450216 336.248814v36.362844c0 5.302915-4.463457 9.663999-10.078609 9.663999h-25.792845c-5.620271 0-10.083728-4.361084-10.083728-9.853389V529.226795c0-5.487186 4.463457-9.84827 10.083728-9.84827h25.792845c5.620271 0 10.078609 4.361084 10.078609 9.84827v47.352571h85.757754c3.572813-48.719249 45.141318-87.124432 95.918262-87.124432h118.471005c50.776944 0 92.345448 38.405183 95.928499 87.124432h85.942025v-47.736469c0-5.287559 4.268949-9.464372 9.704948-9.464372h26.366133c5.425762 0 9.689592 4.161457 9.689593 9.464372v233.333366c0.194508 5.313152-4.26383 9.479728-9.694711 9.479728h-26.371252c-5.425762 0-9.694711-4.161457-9.694711-9.479728v-36.547115h-92.304499c-14.009727 34.924505-48.821622 59.652672-89.566025 59.652672H447.533244c-40.739284 0-75.551179-24.71793-89.571144-59.652672H265.857228z" fill="#FFFFFF" p-id="70079"></path></svg> <?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1763523822720" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="21241" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M513.640525 513.911813m-510.088186 0a510.088187 510.088187 0 1 0 1020.176373 0 510.088187 510.088187 0 1 0-1020.176373 0Z" fill="#8a8a8a" p-id="21242"></path><path d="M483.307444 389.380334v-87.682364H351.164541c-17.997151 0-32.651827-14.204236-32.651827-31.628099 0-17.428981 14.670032-31.633217 32.651827-31.633217h320.186511c17.986913 0 32.64159 14.204236 32.64159 31.633217 0 17.613252-14.654677 31.628099-32.631353 31.628099h-131.948393v87.677246h10.349897c12.315456 0 22.286574 9.65888 22.286574 21.590438v51.590809l-121.393751-0.194509v-51.391181c0-11.931558 9.965999-21.590439 22.286574-21.590439h10.365254z m-217.450216 336.248814v36.362844c0 5.302915-4.463457 9.663999-10.078609 9.663999h-25.792845c-5.620271 0-10.083728-4.361084-10.083728-9.853389V529.226795c0-5.487186 4.463457-9.84827 10.083728-9.84827h25.792845c5.620271 0 10.078609 4.361084 10.078609 9.84827v47.352571h85.757754c3.572813-48.719249 45.141318-87.124432 95.918262-87.124432h118.471005c50.776944 0 92.345448 38.405183 95.928499 87.124432h85.942025v-47.736469c0-5.287559 4.268949-9.464372 9.704948-9.464372h26.366133c5.425762 0 9.689592 4.161457 9.689593 9.464372v233.333366c0.194508 5.313152-4.26383 9.479728-9.694711 9.479728h-26.371252c-5.425762 0-9.694711-4.161457-9.694711-9.479728v-36.547115h-92.304499c-14.009727 34.924505-48.821622 59.652672-89.566025 59.652672H447.533244c-40.739284 0-75.551179-24.71793-89.571144-59.652672H265.857228z" fill="#FFFFFF" p-id="21243"></path></svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -70,7 +70,6 @@ export interface HistoryDataPanelProps {
defaultTab?: "chart" | "table"; defaultTab?: "chart" | "table";
/** Y 轴数值的小数位数 */ /** Y 轴数值的小数位数 */
fractionDigits?: number; fractionDigits?: number;
// 清洗功能已移除,相关参数请通过外部面板/服务清洗后再传入
} }
type PanelTab = "chart" | "table"; type PanelTab = "chart" | "table";
@@ -235,7 +234,7 @@ const HistoryDataPanel: React.FC<HistoryDataPanelProps> = ({
fetchTimeSeriesData = defaultFetcher, fetchTimeSeriesData = defaultFetcher,
visible = true, visible = true,
defaultTab = "chart", defaultTab = "chart",
fractionDigits = 2, fractionDigits = 3,
}) => { }) => {
// 从 devices 中提取 id 列表用于渲染与查询 // 从 devices 中提取 id 列表用于渲染与查询
const deviceIds = devices?.map((d) => d.id) ?? []; const deviceIds = devices?.map((d) => d.id) ?? [];

View File

@@ -301,7 +301,7 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
if (layerId !== undefined && property !== undefined) { if (layerId !== undefined && property !== undefined) {
// 验证自定义断点设置 // 验证自定义断点设置
if (styleConfig.classificationMethod === "custom_breaks") { if (styleConfig.classificationMethod === "custom_breaks") {
const expected = styleConfig.segments + 1; const expected = styleConfig.segments;
const custom = styleConfig.customBreaks || []; const custom = styleConfig.customBreaks || [];
if ( if (
custom.length !== expected || custom.length !== expected ||
@@ -609,7 +609,6 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
// 更新当前 VectorTileSource 中的所有缓冲要素属性 // 更新当前 VectorTileSource 中的所有缓冲要素属性
const updateVectorTileSource = (property: string, data: any[]) => { const updateVectorTileSource = (property: string, data: any[]) => {
if (!map) return; if (!map) return;
const vectorTileSources = map const vectorTileSources = map
.getAllLayers() .getAllLayers()
.filter((layer) => layer instanceof WebGLVectorTileLayer) .filter((layer) => layer instanceof WebGLVectorTileLayer)
@@ -636,7 +635,12 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
const featureId = renderFeature.get("id"); const featureId = renderFeature.get("id");
const value = dataMap.get(featureId); const value = dataMap.get(featureId);
if (value !== undefined) { if (value !== undefined) {
(renderFeature as any).properties_[property] = value; if (property === "flow") {
// 特殊处理流量属性,取绝对值
(renderFeature as any).properties_[property] = Math.abs(value);
} else {
(renderFeature as any).properties_[property] = value;
}
} }
}); });
}); });
@@ -677,7 +681,12 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
const featureId = renderFeature.get("id"); const featureId = renderFeature.get("id");
const value = dataMap.get(featureId); const value = dataMap.get(featureId);
if (value !== undefined) { if (value !== undefined) {
(renderFeature as any).properties_[property] = value; if (property === "flow") {
// 特殊处理流量属性,取绝对值
(renderFeature as any).properties_[property] = Math.abs(value);
} else {
(renderFeature as any).properties_[property] = value;
}
} }
}); });
} }
@@ -845,7 +854,7 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
useEffect(() => { useEffect(() => {
if (styleConfig.classificationMethod !== "custom_breaks") return; if (styleConfig.classificationMethod !== "custom_breaks") return;
const numBreaks = styleConfig.segments + 1; const numBreaks = styleConfig.segments;
setStyleConfig((prev) => { setStyleConfig((prev) => {
const prevBreaks = prev.customBreaks || []; const prevBreaks = prev.customBreaks || [];
if (prevBreaks.length === numBreaks) return prev; if (prevBreaks.length === numBreaks) return prev;
@@ -1372,46 +1381,44 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
className="flex flex-col gap-2" className="flex flex-col gap-2"
sx={{ maxHeight: "240px", overflowY: "auto", paddingTop: "12px" }} sx={{ maxHeight: "240px", overflowY: "auto", paddingTop: "12px" }}
> >
{Array.from({ length: styleConfig.segments + 1 }).map( {Array.from({ length: styleConfig.segments }).map((_, idx) => (
(_, idx) => ( <TextField
<TextField key={idx}
key={idx} label={`阈值 ${idx + 1}`}
label={`阈值 ${idx + 1}`} type="number"
type="number" size="small"
size="small" slotProps={{ input: { inputProps: { min: 0, step: 0.1 } } }}
slotProps={{ input: { inputProps: { min: 0, step: 0.1 } } }} value={
value={ (styleConfig.customBreaks &&
(styleConfig.customBreaks && styleConfig.customBreaks[idx]) ??
styleConfig.customBreaks[idx]) ?? ""
"" }
} onChange={(e) => {
onChange={(e) => { const v = parseFloat(e.target.value);
const v = parseFloat(e.target.value); setStyleConfig((prev) => {
setStyleConfig((prev) => { const prevBreaks = prev.customBreaks
const prevBreaks = prev.customBreaks ? [...prev.customBreaks]
? [...prev.customBreaks] : [];
: []; // 保证长度
// 保证长度 while (prevBreaks.length < styleConfig.segments + 1)
while (prevBreaks.length < styleConfig.segments + 1) prevBreaks.push(0);
prevBreaks.push(0); prevBreaks[idx] = isNaN(v) ? 0 : Math.max(0, v);
prevBreaks[idx] = isNaN(v) ? 0 : Math.max(0, v); return { ...prev, customBreaks: prevBreaks };
return { ...prev, customBreaks: prevBreaks }; });
}); }}
}} onBlur={() => {
onBlur={() => { // on blur 保证升序
// on blur 保证升序 setStyleConfig((prev) => {
setStyleConfig((prev) => { const prevBreaks = (prev.customBreaks || []).slice(
const prevBreaks = (prev.customBreaks || []).slice( 0,
0, styleConfig.segments + 1
styleConfig.segments + 1 );
); prevBreaks.sort((a, b) => a - b);
prevBreaks.sort((a, b) => a - b); return { ...prev, customBreaks: prevBreaks };
return { ...prev, customBreaks: prevBreaks }; });
}); }}
}} />
/> ))}
)
)}
</Box> </Box>
</Box> </Box>
)} )}
@@ -1473,7 +1480,7 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
} }
/> />
} }
label="显示属性(放大后显示)" label="显示属性(缩放 >=15 级时显示)"
/> />
<div className="my-3"></div> <div className="my-3"></div>
{/* 操作按钮 */} {/* 操作按钮 */}

View File

@@ -403,7 +403,7 @@ const Toolbar: React.FC<ToolbarProps> = ({ hiddenButtons, queryType }) => {
result.properties.push({ result.properties.push({
label, label,
value: value:
computedProperties[key].toFixed?.(2) || computedProperties[key], computedProperties[key].toFixed?.(3) || computedProperties[key],
unit, unit,
}); });
} }
@@ -446,7 +446,7 @@ const Toolbar: React.FC<ToolbarProps> = ({ hiddenButtons, queryType }) => {
result.properties.push({ result.properties.push({
label, label,
value: value:
computedProperties[key].toFixed?.(2) || computedProperties[key], computedProperties[key].toFixed?.(3) || computedProperties[key],
unit, unit,
}); });
} }

View File

@@ -400,7 +400,7 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
useEffect(() => { useEffect(() => {
if (!mapRef.current) return; if (!mapRef.current) return;
// 缓存 junction、pipe 数据,提供给 deck.gl 显示标签使用 // 缓存 junction、pipe 数据,提供给 deck.gl 提供坐标供标签显示
junctionSource.on("tileloadend", (event) => { junctionSource.on("tileloadend", (event) => {
try { try {
if (event.tile instanceof VectorTile) { if (event.tile instanceof VectorTile) {
@@ -555,14 +555,14 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
}); });
// 重新排列图层顺序,确保顺序 点>线>面 // 重新排列图层顺序,确保顺序 点>线>面
availableLayers.sort((a, b) => { availableLayers.sort((a, b) => {
// 明确顺序(点类优先) // 明确顺序(点类优先),这里 valves 特殊处理
const order = [ const order = [
"valves",
"junctions", "junctions",
"scada", "scada",
"reservoirs", "reservoirs",
"pumps", "pumps",
"tanks", "tanks",
"valves",
"pipes", "pipes",
].reverse(); ].reverse();
// 取值时做安全检查兼容不同写法properties.value 或 直接 value // 取值时做安全检查兼容不同写法properties.value 或 直接 value
@@ -850,7 +850,9 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
pipeProperties === "flow" && record.value < 0 && p.flowFlag > 0 pipeProperties === "flow" && record.value < 0 && p.flowFlag > 0
? [...p.path].reverse() ? [...p.path].reverse()
: p.path, : p.path,
[pipeProperties]: record.value, // 流量数值
[pipeProperties]:
pipeProperties === "flow" ? Math.abs(record.value) : record.value,
}; };
} }
return p; return p;

View File

@@ -1,6 +1,6 @@
'use client'; "use client";
import React, { useState } from 'react'; import React, { useState } from "react";
import { import {
Box, Box,
Typography, Typography,
@@ -14,8 +14,11 @@ import {
Chip, Chip,
IconButton, IconButton,
Tooltip, Tooltip,
} from '@mui/material'; } from "@mui/material";
import { LocationOn as LocationIcon, Visibility as VisibilityIcon } from '@mui/icons-material'; import {
LocationOn as LocationIcon,
Visibility as VisibilityIcon,
} from "@mui/icons-material";
interface LocationResult { interface LocationResult {
id: number; id: number;
@@ -24,7 +27,7 @@ interface LocationResult {
pressure: number; pressure: number;
waterLevel: number; waterLevel: number;
flow: number; flow: number;
status: 'normal' | 'warning' | 'danger'; status: "normal" | "warning" | "danger";
coordinates: [number, number]; coordinates: [number, number];
} }
@@ -33,7 +36,10 @@ interface LocationResultsProps {
onViewDetail?: (id: number) => void; onViewDetail?: (id: number) => void;
} }
const LocationResults: React.FC<LocationResultsProps> = ({ onLocate, onViewDetail }) => { const LocationResults: React.FC<LocationResultsProps> = ({
onLocate,
onViewDetail,
}) => {
const [results, setResults] = useState<LocationResult[]>([ const [results, setResults] = useState<LocationResult[]>([
// 示例数据 // 示例数据
// { // {
@@ -50,27 +56,27 @@ const LocationResults: React.FC<LocationResultsProps> = ({ onLocate, onViewDetai
const getStatusColor = (status: string) => { const getStatusColor = (status: string) => {
switch (status) { switch (status) {
case 'normal': case "normal":
return 'success'; return "success";
case 'warning': case "warning":
return 'warning'; return "warning";
case 'danger': case "danger":
return 'error'; return "error";
default: default:
return 'default'; return "default";
} }
}; };
const getStatusText = (status: string) => { const getStatusText = (status: string) => {
switch (status) { switch (status) {
case 'normal': case "normal":
return '正常'; return "正常";
case 'warning': case "warning":
return '预警'; return "预警";
case 'danger': case "danger":
return '危险'; return "危险";
default: default:
return '未知'; return "未知";
} }
}; };
@@ -95,7 +101,7 @@ const LocationResults: React.FC<LocationResultsProps> = ({ onLocate, onViewDetai
</Typography> </Typography>
<Typography variant="h6" className="font-bold text-green-600"> <Typography variant="h6" className="font-bold text-green-600">
{results.filter((r) => r.status === 'normal').length} {results.filter((r) => r.status === "normal").length}
</Typography> </Typography>
</Box> </Box>
<Box> <Box>
@@ -103,7 +109,7 @@ const LocationResults: React.FC<LocationResultsProps> = ({ onLocate, onViewDetai
</Typography> </Typography>
<Typography variant="h6" className="font-bold text-orange-600"> <Typography variant="h6" className="font-bold text-orange-600">
{results.filter((r) => r.status === 'warning').length} {results.filter((r) => r.status === "warning").length}
</Typography> </Typography>
</Box> </Box>
<Box> <Box>
@@ -111,7 +117,7 @@ const LocationResults: React.FC<LocationResultsProps> = ({ onLocate, onViewDetai
</Typography> </Typography>
<Typography variant="h6" className="font-bold text-red-600"> <Typography variant="h6" className="font-bold text-red-600">
{results.filter((r) => r.status === 'danger').length} {results.filter((r) => r.status === "danger").length}
</Typography> </Typography>
</Box> </Box>
</Box> </Box>
@@ -129,12 +135,46 @@ const LocationResults: React.FC<LocationResultsProps> = ({ onLocate, onViewDetai
fill="none" fill="none"
className="opacity-40" className="opacity-40"
> >
<circle cx="40" cy="40" r="25" stroke="currentColor" strokeWidth="2" /> <circle
cx="40"
cy="40"
r="25"
stroke="currentColor"
strokeWidth="2"
/>
<circle cx="40" cy="40" r="5" fill="currentColor" /> <circle cx="40" cy="40" r="5" fill="currentColor" />
<line x1="40" y1="15" x2="40" y2="25" stroke="currentColor" strokeWidth="2" /> <line
<line x1="40" y1="55" x2="40" y2="65" stroke="currentColor" strokeWidth="2" /> x1="40"
<line x1="15" y1="40" x2="25" y2="40" stroke="currentColor" strokeWidth="2" /> y1="15"
<line x1="55" y1="40" x2="65" y2="40" stroke="currentColor" strokeWidth="2" /> x2="40"
y2="25"
stroke="currentColor"
strokeWidth="2"
/>
<line
x1="40"
y1="55"
x2="40"
y2="65"
stroke="currentColor"
strokeWidth="2"
/>
<line
x1="15"
y1="40"
x2="25"
y2="40"
stroke="currentColor"
strokeWidth="2"
/>
<line
x1="55"
y1="40"
x2="65"
y2="40"
stroke="currentColor"
strokeWidth="2"
/>
</svg> </svg>
</Box> </Box>
<Typography variant="body2"></Typography> <Typography variant="body2"></Typography>
@@ -161,9 +201,15 @@ const LocationResults: React.FC<LocationResultsProps> = ({ onLocate, onViewDetai
<TableRow key={result.id} hover> <TableRow key={result.id} hover>
<TableCell>{result.nodeName}</TableCell> <TableCell>{result.nodeName}</TableCell>
<TableCell>{result.nodeId}</TableCell> <TableCell>{result.nodeId}</TableCell>
<TableCell align="right">{result.pressure.toFixed(2)}</TableCell> <TableCell align="right">
<TableCell align="right">{result.waterLevel.toFixed(2)}</TableCell> {result.pressure.toFixed(3)}
<TableCell align="right">{result.flow.toFixed(1)}</TableCell> </TableCell>
<TableCell align="right">
{result.waterLevel.toFixed(3)}
</TableCell>
<TableCell align="right">
{result.flow.toFixed(1)}
</TableCell>
<TableCell align="center"> <TableCell align="center">
<Chip <Chip
label={getStatusText(result.status)} label={getStatusText(result.status)}