feat(scada): align map asset fields
This commit is contained in:
+1
-1
@@ -41,7 +41,7 @@ Closed、Off、Inactive 只把 core 改为 inactive 灰,不改变几何身份
|
||||
|
||||
## 状态与图层顺序
|
||||
|
||||
状态优先级是 `selected > hover > status > source`。五个业务 vector source 使用 `promoteId: "id"`,hover 与 selected 通过 feature-state 控制,opacity 使用 120ms 过渡且不脉冲。
|
||||
状态优先级是 `selected > hover > status > source`。常规业务 vector source 使用 `promoteId: "id"`,SCADA source 使用 `promoteId: "scada_id"`,hover 与 selected 通过 feature-state 控制,opacity 使用 120ms 过渡且不脉冲。
|
||||
|
||||
- hover:白色增宽 casing 加 source/status core,selected 时 opacity 为 0。
|
||||
- selected:保留普通 core,外侧使用白色分隔、操作蓝外框和窄白外缘。
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { Target, TriangleAlert, X } from "lucide-react";
|
||||
import { Check, Copy, Target, TriangleAlert, X } from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import { useMemo } from "react";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { showMapNotice } from "@/features/map/core/components/notice";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { SCADA_ICON_PATHS } from "../map/scada";
|
||||
import type { DetailFeature } from "../types";
|
||||
@@ -17,6 +18,15 @@ type FeatureInsightPanelProps = {
|
||||
};
|
||||
|
||||
export function FeatureInsightPanel({ feature, onClose }: FeatureInsightPanelProps) {
|
||||
const [copiedScadaUuid, setCopiedScadaUuid] = useState(false);
|
||||
const copyResetTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
useEffect(() => () => {
|
||||
if (copyResetTimeoutRef.current) {
|
||||
clearTimeout(copyResetTimeoutRef.current);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const metrics = useMemo(() => {
|
||||
if (!feature) {
|
||||
return [];
|
||||
@@ -60,7 +70,7 @@ export function FeatureInsightPanel({ feature, onClose }: FeatureInsightPanelPro
|
||||
];
|
||||
case "scada":
|
||||
return [
|
||||
["设备类型", formatFeaturePropertyValue("device_type_name", feature.properties.device_type_name)],
|
||||
["设备类型", formatFeaturePropertyValue("kind", feature.properties.kind)],
|
||||
["运行状态", formatFeaturePropertyValue("active", feature.properties.active)],
|
||||
["关联检查井", formatFeaturePropertyValue("junction_id", feature.properties.junction_id)],
|
||||
["同步时间", formatFeaturePropertyValue("synced_at", feature.properties.synced_at)]
|
||||
@@ -73,9 +83,43 @@ export function FeatureInsightPanel({ feature, onClose }: FeatureInsightPanelPro
|
||||
return [];
|
||||
}
|
||||
|
||||
return getLocalizedFeatureProperties(feature.properties);
|
||||
const properties = feature.layer === "scada" && feature.properties.scada_id === undefined
|
||||
? { scada_id: feature.id, ...feature.properties }
|
||||
: feature.properties;
|
||||
return getLocalizedFeatureProperties(properties);
|
||||
}, [feature]);
|
||||
|
||||
const scadaQueryUuid = feature?.layer === "scada"
|
||||
? getCopyablePropertyValue(feature.properties.scada_id ?? feature.id)
|
||||
: null;
|
||||
|
||||
const handleCopyScadaUuid = async () => {
|
||||
if (!scadaQueryUuid) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!navigator.clipboard) {
|
||||
throw new Error("Clipboard API unavailable");
|
||||
}
|
||||
|
||||
await navigator.clipboard.writeText(scadaQueryUuid);
|
||||
setCopiedScadaUuid(true);
|
||||
|
||||
if (copyResetTimeoutRef.current) {
|
||||
clearTimeout(copyResetTimeoutRef.current);
|
||||
}
|
||||
|
||||
copyResetTimeoutRef.current = setTimeout(() => setCopiedScadaUuid(false), 1500);
|
||||
} catch {
|
||||
showMapNotice({
|
||||
tone: "error",
|
||||
title: "无法复制 UUID",
|
||||
message: "请手动选择查询 UUID 后复制。"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<aside className="acrylic-panel pointer-events-auto flex h-full min-h-0 flex-col rounded border">
|
||||
<div className="border-b border-slate-200/80 p-4">
|
||||
@@ -114,14 +158,33 @@ export function FeatureInsightPanel({ feature, onClose }: FeatureInsightPanelPro
|
||||
</div>
|
||||
|
||||
{feature.layer === "scada" ? (
|
||||
<section className={cn("mt-4 rounded border p-3", getScadaPresentation(feature.properties.device_type_id).sectionClassName)}>
|
||||
<section className={cn("mt-4 rounded border p-3", getScadaPresentation(feature.properties.kind).sectionClassName)}>
|
||||
<div className="flex items-center gap-3">
|
||||
<Image src={getScadaPresentation(feature.properties.device_type_id).iconPath} alt="" width={42} height={42} />
|
||||
<Image src={getScadaPresentation(feature.properties.kind).iconPath} alt="" width={42} height={42} />
|
||||
<div className="min-w-0">
|
||||
<p className={cn("truncate text-sm font-semibold", getScadaPresentation(feature.properties.device_type_id).labelClassName)}>{formatFeaturePropertyValue("point_name", feature.properties.point_name)}</p>
|
||||
<p className="mt-0.5 truncate text-xs text-slate-500 tabular-nums">{formatFeaturePropertyValue("device_external_id", feature.properties.device_external_id)}</p>
|
||||
<p className={cn("truncate text-sm font-semibold", getScadaPresentation(feature.properties.kind).labelClassName)}>{formatFeaturePropertyValue("name", feature.properties.name)}</p>
|
||||
<p className="mt-0.5 truncate text-xs text-slate-500 tabular-nums">{formatFeaturePropertyValue("external_id", feature.properties.external_id)}</p>
|
||||
</div>
|
||||
</div>
|
||||
{scadaQueryUuid ? (
|
||||
<div className="mt-3 rounded border border-white/70 bg-white/65 px-3 py-2 shadow-sm">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<p className="text-xs font-semibold text-slate-500">编号 UUID</p>
|
||||
<button
|
||||
type="button"
|
||||
aria-label={copiedScadaUuid ? "编号 UUID 已复制" : "复制编号 UUID"}
|
||||
title={copiedScadaUuid ? "编号 UUID 已复制" : "复制编号 UUID"}
|
||||
onClick={() => void handleCopyScadaUuid()}
|
||||
className="relative grid h-8 w-8 shrink-0 place-items-center rounded border border-slate-200/80 bg-white/80 text-slate-500 transition-[background-color,color,transform] duration-150 hover:text-slate-950 active:scale-95 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600/25"
|
||||
>
|
||||
<Copy size={14} aria-hidden="true" className={cn("absolute transition-[opacity,transform] duration-150", copiedScadaUuid ? "scale-90 opacity-0" : "scale-100 opacity-100")} />
|
||||
<Check size={15} aria-hidden="true" className={cn("absolute text-emerald-600 transition-[opacity,transform] duration-150", copiedScadaUuid ? "scale-100 opacity-100" : "scale-90 opacity-0")} />
|
||||
</button>
|
||||
</div>
|
||||
<p className="mt-1 break-all font-mono text-xs font-semibold leading-5 text-slate-900">{scadaQueryUuid}</p>
|
||||
<p className="mt-1 text-xs leading-5 text-slate-500">SCADA 时序查询 asset_ids</p>
|
||||
</div>
|
||||
) : null}
|
||||
<p className="mt-2 text-sm leading-6 text-slate-700">当前点位由管网节点空间均匀采样生成,仅用于空间展示,不代表设备真实安装坐标。图层暂未提供实时测值或历史曲线。</p>
|
||||
</section>
|
||||
) : null}
|
||||
@@ -141,8 +204,11 @@ export function FeatureInsightPanel({ feature, onClose }: FeatureInsightPanelPro
|
||||
<dl className="surface-reading mt-2 divide-y divide-slate-100 rounded border">
|
||||
{propertyEntries.map((entry) => (
|
||||
<div key={entry.key} className="grid grid-cols-[110px_1fr] gap-2 px-3 py-2 text-sm">
|
||||
<dt className="truncate text-slate-500">{entry.label}</dt>
|
||||
<dd className="truncate font-medium text-slate-800">{entry.value}</dd>
|
||||
<dt className="truncate text-slate-500">{feature.layer === "scada" && entry.key === "scada_id" ? "编号 UUID" : entry.label}</dt>
|
||||
<dd className={cn(
|
||||
"font-medium text-slate-800",
|
||||
feature.layer === "scada" && entry.key === "scada_id" ? "break-all font-mono text-xs leading-5" : "truncate"
|
||||
)}>{entry.value}</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
@@ -160,14 +226,27 @@ export function FeatureInsightPanel({ feature, onClose }: FeatureInsightPanelPro
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<span className="sr-only" aria-live="polite">{copiedScadaUuid ? "编号 UUID 已复制" : ""}</span>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
function getScadaPresentation(rawTypeId: unknown) {
|
||||
const typeId = Number(rawTypeId);
|
||||
if (typeId === 72) return { iconPath: SCADA_ICON_PATHS.waterQuality, sectionClassName: "material-tone-normal border-teal-200", labelClassName: "text-teal-800" };
|
||||
if (typeId === 58) return { iconPath: SCADA_ICON_PATHS.radarLevel, sectionClassName: "material-tone-info border-blue-200", labelClassName: "text-blue-800" };
|
||||
if (typeId === 45) return { iconPath: SCADA_ICON_PATHS.ultrasonicFlow, sectionClassName: "material-tone-warning border-orange-200", labelClassName: "text-orange-800" };
|
||||
function getCopyablePropertyValue(value: unknown) {
|
||||
if (typeof value === "string" && value.trim().length > 0) {
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
if (typeof value === "number" || typeof value === "bigint") {
|
||||
return String(value);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getScadaPresentation(rawKind: unknown) {
|
||||
const kind = String(rawKind ?? "");
|
||||
if (kind === "water_quality") return { iconPath: SCADA_ICON_PATHS.waterQuality, sectionClassName: "material-tone-normal border-teal-200", labelClassName: "text-teal-800" };
|
||||
if (kind === "radar_level") return { iconPath: SCADA_ICON_PATHS.radarLevel, sectionClassName: "material-tone-info border-blue-200", labelClassName: "text-blue-800" };
|
||||
if (kind === "ultrasonic_flow") return { iconPath: SCADA_ICON_PATHS.ultrasonicFlow, sectionClassName: "material-tone-warning border-orange-200", labelClassName: "text-orange-800" };
|
||||
return { iconPath: SCADA_ICON_PATHS.unknown, sectionClassName: "surface-control border-transparent", labelClassName: "text-slate-800" };
|
||||
}
|
||||
|
||||
@@ -210,11 +210,12 @@ type ScadaFeaturePopoverProps = {
|
||||
|
||||
function ScadaFeaturePopover({ feature, copied, canCopy, onCopy, onClose }: ScadaFeaturePopoverProps) {
|
||||
const properties = feature.properties;
|
||||
const presentation = getScadaPresentation(properties.device_type_id);
|
||||
const presentation = getScadaPresentation(properties.kind);
|
||||
const active = properties.active === true || String(properties.active).toLowerCase() === "true";
|
||||
const pointId = formatFeaturePropertyValue("point_external_id", properties.point_external_id ?? feature.id);
|
||||
const scadaId = formatFeaturePropertyValue("scada_id", properties.scada_id ?? feature.id);
|
||||
const attributes = [
|
||||
["设备编号", formatFeaturePropertyValue("device_external_id", properties.device_external_id)],
|
||||
["站点编号", formatFeaturePropertyValue("site_external_id", properties.site_external_id)],
|
||||
["设备编号", formatFeaturePropertyValue("external_id", properties.external_id)],
|
||||
["关联检查井", formatFeaturePropertyValue("junction_id", properties.junction_id)],
|
||||
["同步时间", formatFeaturePropertyValue("synced_at", properties.synced_at)]
|
||||
];
|
||||
@@ -233,8 +234,8 @@ function ScadaFeaturePopover({ feature, copied, canCopy, onCopy, onClose }: Scad
|
||||
<Image src={presentation.iconPath} alt="" width={48} height={48} className="drop-shadow-sm" />
|
||||
<div className="min-w-0">
|
||||
<div className="flex min-w-0 flex-nowrap items-center gap-1.5">
|
||||
<span className={cn("min-w-0 truncate text-xs font-semibold", presentation.labelClassName)} title={formatFeaturePropertyValue("device_type_name", properties.device_type_name)}>
|
||||
{formatFeaturePropertyValue("device_type_name", properties.device_type_name)}
|
||||
<span className={cn("min-w-0 truncate text-xs font-semibold", presentation.labelClassName)} title={formatFeaturePropertyValue("kind", properties.kind)}>
|
||||
{formatFeaturePropertyValue("kind", properties.kind)}
|
||||
</span>
|
||||
<span className={cn(
|
||||
"inline-flex shrink-0 items-center gap-1.5 rounded-full px-2 py-0.5 text-xs font-semibold ring-1 ring-inset",
|
||||
@@ -246,9 +247,9 @@ function ScadaFeaturePopover({ feature, copied, canCopy, onCopy, onClose }: Scad
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="mt-1 truncate text-base font-semibold text-slate-950">
|
||||
{formatFeaturePropertyValue("point_name", properties.point_name)}
|
||||
{formatFeaturePropertyValue("name", properties.name)}
|
||||
</h3>
|
||||
<p className="mt-0.5 truncate text-xs text-slate-600 tabular-nums">测点 {pointId}</p>
|
||||
<p className="mt-0.5 truncate text-xs text-slate-600 tabular-nums">编号 UUID {scadaId}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-0.5">
|
||||
{canCopy ? (
|
||||
@@ -292,10 +293,10 @@ function ScadaFeaturePopover({ feature, copied, canCopy, onCopy, onClose }: Scad
|
||||
);
|
||||
}
|
||||
|
||||
function getScadaPresentation(rawTypeId: unknown) {
|
||||
const typeId = Number(rawTypeId);
|
||||
if (typeId === 72) return { iconPath: SCADA_ICON_PATHS.waterQuality, headerClassName: "bg-teal-50/70", labelClassName: "text-teal-800" };
|
||||
if (typeId === 58) return { iconPath: SCADA_ICON_PATHS.radarLevel, headerClassName: "bg-blue-50/70", labelClassName: "text-blue-800" };
|
||||
if (typeId === 45) return { iconPath: SCADA_ICON_PATHS.ultrasonicFlow, headerClassName: "bg-orange-50/70", labelClassName: "text-orange-800" };
|
||||
function getScadaPresentation(rawKind: unknown) {
|
||||
const kind = String(rawKind ?? "");
|
||||
if (kind === "water_quality") return { iconPath: SCADA_ICON_PATHS.waterQuality, headerClassName: "bg-teal-50/70", labelClassName: "text-teal-800" };
|
||||
if (kind === "radar_level") return { iconPath: SCADA_ICON_PATHS.radarLevel, headerClassName: "bg-blue-50/70", labelClassName: "text-blue-800" };
|
||||
if (kind === "ultrasonic_flow") return { iconPath: SCADA_ICON_PATHS.ultrasonicFlow, headerClassName: "bg-orange-50/70", labelClassName: "text-orange-800" };
|
||||
return { iconPath: SCADA_ICON_PATHS.unknown, headerClassName: "bg-slate-50/70", labelClassName: "text-slate-700" };
|
||||
}
|
||||
|
||||
@@ -61,25 +61,24 @@ describe("drainage feature adapter", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("uses the promoted SCADA point id for feature-state interactions", () => {
|
||||
it("uses the promoted SCADA id for feature-state interactions", () => {
|
||||
const detail = toDetailFeature({
|
||||
id: "aaedc54a-e5b4-5791-b729-4b7e838a8d00",
|
||||
id: "4bfa834b-cbbb-5f35-9523-7487ac021ed2",
|
||||
sourceLayer: SOURCE_LAYERS.scada,
|
||||
properties: {
|
||||
id: "4bfa834b-cbbb-5f35-9523-7487ac021ed2",
|
||||
point_id: "aaedc54a-e5b4-5791-b729-4b7e838a8d00",
|
||||
point_external_id: "36455",
|
||||
point_name: "DY22东市南街环城南路西段交叉口",
|
||||
device_external_id: "26825171",
|
||||
device_type_name: "2代水质仪"
|
||||
scada_id: "4bfa834b-cbbb-5f35-9523-7487ac021ed2",
|
||||
site_external_id: "36455",
|
||||
name: "DY22东市南街环城南路西段交叉口",
|
||||
external_id: "26825171",
|
||||
kind: "water_quality"
|
||||
}
|
||||
} as unknown as MapGeoJSONFeature);
|
||||
|
||||
expect(detail).toMatchObject({
|
||||
id: "aaedc54a-e5b4-5791-b729-4b7e838a8d00",
|
||||
id: "4bfa834b-cbbb-5f35-9523-7487ac021ed2",
|
||||
layer: "scada",
|
||||
title: "DY22东市南街环城南路西段交叉口",
|
||||
subtitle: "2代水质仪 · 设备 26825171"
|
||||
subtitle: "water_quality · 设备 26825171"
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,10 +3,12 @@ import type { DetailFeature } from "../types";
|
||||
import { formatValue } from "../utils/format-value";
|
||||
import { SOURCE_LAYERS, type WaterNetworkSourceId } from "./sources";
|
||||
|
||||
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
||||
|
||||
export function getFeatureId(feature: MapGeoJSONFeature) {
|
||||
const layer = getWaterNetworkSourceId(feature);
|
||||
const raw = layer === "scada"
|
||||
? feature.id ?? feature.properties?.id ?? feature.properties?.point_id ?? feature.properties?.point_external_id
|
||||
? feature.properties?.scada_id ?? (typeof feature.id === "string" && UUID_PATTERN.test(feature.id) ? feature.id : undefined)
|
||||
: feature.id ?? feature.properties?.id;
|
||||
|
||||
return raw === undefined || raw === null ? "" : String(raw);
|
||||
@@ -70,8 +72,8 @@ function getFeaturePresentation(
|
||||
};
|
||||
case "scada":
|
||||
return {
|
||||
title: String(properties.point_name || `SCADA 测点 ${displayId}`),
|
||||
subtitle: `${formatValue(properties.device_type_name)} · 设备 ${formatValue(properties.device_external_id)}`
|
||||
title: String(properties.name || `SCADA 测点 ${displayId}`),
|
||||
subtitle: `${formatValue(properties.kind)} · 设备 ${formatValue(properties.external_id)}`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,10 +14,10 @@ import { waterNetworkLayers } from "./layers";
|
||||
describe("SCADA map presentation", () => {
|
||||
it("maps the three authoritative device types and falls back to unknown", () => {
|
||||
expect(SCADA_ICON_EXPRESSION).toEqual([
|
||||
"match", ["to-number", ["get", "device_type_id"]],
|
||||
72, SCADA_IMAGE_IDS.waterQuality,
|
||||
58, SCADA_IMAGE_IDS.radarLevel,
|
||||
45, SCADA_IMAGE_IDS.ultrasonicFlow,
|
||||
"match", ["get", "kind"],
|
||||
"water_quality", SCADA_IMAGE_IDS.waterQuality,
|
||||
"radar_level", SCADA_IMAGE_IDS.radarLevel,
|
||||
"ultrasonic_flow", SCADA_IMAGE_IDS.ultrasonicFlow,
|
||||
SCADA_IMAGE_IDS.unknown
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -27,10 +27,10 @@ export const SCADA_IMAGE_IDS = {
|
||||
} as const;
|
||||
|
||||
export const SCADA_ICON_EXPRESSION: ExpressionSpecification = [
|
||||
"match", ["to-number", ["get", "device_type_id"]],
|
||||
72, SCADA_IMAGE_IDS.waterQuality,
|
||||
58, SCADA_IMAGE_IDS.radarLevel,
|
||||
45, SCADA_IMAGE_IDS.ultrasonicFlow,
|
||||
"match", ["get", "kind"],
|
||||
"water_quality", SCADA_IMAGE_IDS.waterQuality,
|
||||
"radar_level", SCADA_IMAGE_IDS.radarLevel,
|
||||
"ultrasonic_flow", SCADA_IMAGE_IDS.ultrasonicFlow,
|
||||
SCADA_IMAGE_IDS.unknown
|
||||
];
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ describe("createWaterNetworkSources", () => {
|
||||
expect(scada.tiles).toEqual([
|
||||
expect.stringContaining("/lingang:scada_points/point/WebMercatorQuad/")
|
||||
]);
|
||||
expect(scada.promoteId).toBe("point_id");
|
||||
expect(scada.promoteId).toBe("scada_id");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ export function createWaterNetworkSources() {
|
||||
SOURCE_LAYERS.scada,
|
||||
"point",
|
||||
[121.7350340307058, 30.84636502815, 121.97051839928491, 30.994737681416165],
|
||||
"point_id"
|
||||
"scada_id"
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -71,27 +71,23 @@ describe("feature panel properties", () => {
|
||||
|
||||
it("shows SCADA identity, device, state, and sync fields", () => {
|
||||
const model = getFeaturePanelModel("scada", {
|
||||
id: "4bfa834b-cbbb-5f35-9523-7487ac021ed2",
|
||||
point_id: "aaedc54a-e5b4-5791-b729-4b7e838a8d00",
|
||||
point_external_id: "36455",
|
||||
point_name: "DY22东市南街环城南路西段交叉口",
|
||||
device_external_id: "26825171",
|
||||
scada_id: "4bfa834b-cbbb-5f35-9523-7487ac021ed2",
|
||||
site_external_id: "36455",
|
||||
name: "DY22东市南街环城南路西段交叉口",
|
||||
external_id: "26825171",
|
||||
kind: "water_quality",
|
||||
active: true,
|
||||
device_type_id: 72,
|
||||
device_type_name: "2代水质仪",
|
||||
junction_id: "31011502010001809",
|
||||
synced_at: "Jul 15, 2026, 7:38:45 AM"
|
||||
});
|
||||
|
||||
expect(model.id).toBe("36455");
|
||||
expect(model.id).toBe("4bfa834b-cbbb-5f35-9523-7487ac021ed2");
|
||||
expect(model.badge).toEqual({ label: "在线", tone: "active" });
|
||||
expect(model.attributes.map((entry) => entry.key)).toEqual([
|
||||
"id",
|
||||
"point_id",
|
||||
"point_name",
|
||||
"device_external_id",
|
||||
"device_type_id",
|
||||
"device_type_name",
|
||||
"site_external_id",
|
||||
"name",
|
||||
"external_id",
|
||||
"kind",
|
||||
"junction_id",
|
||||
"synced_at"
|
||||
]);
|
||||
@@ -99,12 +95,12 @@ describe("feature panel properties", () => {
|
||||
|
||||
it("omits the internal SCADA clustering size from user-facing properties", () => {
|
||||
const entries = getLocalizedFeatureProperties({
|
||||
point_name: "THC雷达液位计(MQTT)",
|
||||
name: "THC雷达液位计(MQTT)",
|
||||
cluster_size: 29,
|
||||
device_external_id: "DEVICE-002"
|
||||
external_id: "DEVICE-002"
|
||||
});
|
||||
|
||||
expect(entries.map((entry) => entry.key)).toEqual(["point_name", "device_external_id"]);
|
||||
expect(entries.map((entry) => entry.key)).toEqual(["name", "external_id"]);
|
||||
});
|
||||
|
||||
it("labels uniformly sampled SCADA locations", () => {
|
||||
|
||||
@@ -23,6 +23,7 @@ export type FeaturePanelModel = {
|
||||
const PROPERTY_LABELS: Record<string, string> = {
|
||||
fid: "GeoServer 要素 ID",
|
||||
id: "编号",
|
||||
scada_id: "编号 UUID",
|
||||
gid: "要素编号",
|
||||
name: "名称",
|
||||
code: "编码",
|
||||
@@ -69,14 +70,18 @@ const PROPERTY_LABELS: Record<string, string> = {
|
||||
device_type_name: "设备类型",
|
||||
device_icon_code: "设备图标编码",
|
||||
active: "运行状态",
|
||||
external_id: "设备编号",
|
||||
junction_id: "关联检查井",
|
||||
kind: "设备类型",
|
||||
location_source: "位置来源",
|
||||
site_external_id: "站点编号",
|
||||
synced_at: "同步时间"
|
||||
};
|
||||
|
||||
const PROPERTY_ORDER: Record<string, number> = {
|
||||
fid: 9,
|
||||
id: 10,
|
||||
scada_id: 10,
|
||||
gid: 11,
|
||||
name: 12,
|
||||
code: 13,
|
||||
@@ -136,7 +141,7 @@ const FEATURE_PANEL_PROPERTY_KEYS: Record<WaterNetworkSourceId, readonly string[
|
||||
junctions: ["id", "invert_elevation", "max_depth"],
|
||||
orifices: ["id", "discharge_coeff", "gated", "shape", "geom1", "geom2"],
|
||||
outfalls: ["id", "invert_elevation", "outfall_type"],
|
||||
scada: ["id", "point_id", "point_external_id", "point_name", "device_external_id", "active", "device_type_id", "device_type_name", "junction_id", "synced_at"]
|
||||
scada: ["scada_id", "site_external_id", "name", "external_id", "kind", "active", "junction_id", "synced_at"]
|
||||
};
|
||||
|
||||
const FEATURE_PANEL_BADGE_KEYS: Partial<Record<WaterNetworkSourceId, string>> = {
|
||||
@@ -159,7 +164,7 @@ export function getFeaturePanelProperties(
|
||||
featureId?: string
|
||||
): LocalizedFeatureProperty[] {
|
||||
return FEATURE_PANEL_PROPERTY_KEYS[layer].map((key) => {
|
||||
const value = key === "id" ? properties[key] ?? featureId : properties[key];
|
||||
const value = key === "id" || key === "scada_id" ? properties[key] ?? featureId : properties[key];
|
||||
|
||||
return {
|
||||
key,
|
||||
@@ -175,7 +180,7 @@ export function getFeaturePanelModel(
|
||||
featureId?: string
|
||||
): FeaturePanelModel {
|
||||
const entries = getFeaturePanelProperties(layer, properties, featureId);
|
||||
const idKey = layer === "scada" ? "point_external_id" : "id";
|
||||
const idKey = layer === "scada" ? "scada_id" : "id";
|
||||
const id = entries.find((entry) => entry.key === idKey)?.value ?? "暂无";
|
||||
const badgeKey = FEATURE_PANEL_BADGE_KEYS[layer];
|
||||
const badgeEntry = badgeKey ? entries.find((entry) => entry.key === badgeKey) : undefined;
|
||||
|
||||
Reference in New Issue
Block a user