feat(workbench): integrate runtime data and map tooling

This commit is contained in:
2026-09-22 10:45:46 +08:00
parent bf44cc0f25
commit bebcf2a06c
163 changed files with 15642 additions and 1312 deletions
@@ -1,5 +1,4 @@
import {
Activity,
Camera,
Circle,
Copy,
@@ -15,6 +14,7 @@ import {
Route,
Ruler,
Share2,
Square,
Trash2,
Undo2,
Waypoints,
@@ -28,7 +28,6 @@ import {
MapDrawToolbar,
MapLayerPanel,
MapLegendList,
MapMetricTile,
MapMeasurePanel,
MapPanelSection,
type BaseLayerOption,
@@ -233,7 +232,8 @@ export function ToolbarPanel({
title="图层"
description={`${layerItems.filter((item) => item.visible).length}/${layerItems.length} 个业务图层可见`}
icon={Icon}
widthClassName={panelWidthClassName}
widthClassName={panelWidthClassName ?? "flex w-[324px] max-h-[calc(100dvh-13.25rem)] flex-col"}
contentClassName={panelWidthClassName ? undefined : "min-h-0 overflow-y-auto overscroll-contain pr-0.5"}
visible={panelVisible}
>
<MapLayerPanel
@@ -248,8 +248,23 @@ export function ToolbarPanel({
}
if (renderedToolId === "measure") {
const measureStatus = getMeasureStatus({
enabled: measureEnabled,
measuring,
hasPoints: hasMeasurePoints,
mode: activeMeasureModeId
});
return (
<MapControlPanel key="measure" title="测量" description="距离与范围测量" icon={Icon} widthClassName={panelWidthClassName ?? "w-[312px]"} visible={panelVisible}>
<MapControlPanel
key="measure"
title="测量"
description="距离、面积与管段测量"
icon={Icon}
widthClassName={panelWidthClassName ?? "flex w-[324px] max-h-[calc(100dvh-13.25rem)] flex-col"}
contentClassName={panelWidthClassName ? undefined : "min-h-0 overflow-y-auto overscroll-contain pr-0.5"}
visible={panelVisible}
>
<MapMeasurePanel
modes={MEASURE_MODES}
activeModeId={activeMeasureModeId}
@@ -259,20 +274,21 @@ export function ToolbarPanel({
metrics={measureResult.metrics}
units={MEASURE_UNITS}
activeUnitId={activeMeasureUnitId}
status={measureStatus}
actions={[
{
id: measuring ? "stop" : "start",
icon: Ruler,
icon: measuring ? Square : Ruler,
label: measuring ? "停止测量" : "开始测量",
description:
activeMeasureModeId === "segment"
? measuring
? "暂停管线选择"
: "点击管线选择资产"
: measuring
? "暂停地图取点"
: "在地图上连续点击取点",
variant: measuring ? "default" : "primary",
measuring
? "暂停并保留当前读数"
: activeMeasureModeId === "segment"
? "在地图中选择管线"
: activeMeasureModeId === "area"
? "在地图中圈定范围"
: "在地图中连续取点",
variant: measuring ? "active" : "primary",
disabled: !measureEnabled,
onClick: measuring ? onStopMeasure : onStartMeasure
},
@@ -303,8 +319,16 @@ export function ToolbarPanel({
if (renderedToolId === "analysis") {
return (
<MapControlPanel key="analysis" title="分析" description="图例与当前覆盖" icon={Icon} widthClassName={panelWidthClassName ?? "w-[316px]"} visible={panelVisible}>
<AnalysisPanel legendItems={legendItems} />
<MapControlPanel
key="analysis"
title="图例"
description={`${legendItems.length} 项当前地图符号`}
icon={Icon}
widthClassName={panelWidthClassName ?? "flex w-[324px] max-h-[calc(100dvh-13.25rem)] flex-col"}
contentClassName={panelWidthClassName ? undefined : "min-h-0 overflow-y-auto overscroll-contain pr-0.5"}
visible={panelVisible}
>
<LegendPanel legendItems={legendItems} />
</MapControlPanel>
);
}
@@ -368,21 +392,68 @@ export function ToolbarPanel({
);
}
function AnalysisPanel({ legendItems }: { legendItems: MapLegendItem[] }) {
return (
<div className="space-y-3">
<MapPanelSection title="图例">
<MapLegendList items={legendItems} showStatusDot />
</MapPanelSection>
function getMeasureStatus({
enabled,
measuring,
hasPoints,
mode
}: {
enabled: boolean;
measuring: boolean;
hasPoints: boolean;
mode: WorkbenchMeasureMode;
}) {
if (!enabled) {
return {
label: "地图未就绪",
description: "等待地图加载后再开始测量",
tone: "disabled" as const
};
}
<MapPanelSection title="当前覆盖">
<div className="grid grid-cols-2 gap-2">
<MapMetricTile label="影响范围" value="未开启" />
<MapMetricTile label="风险预览" value="待生成" />
</div>
<MapActionRow icon={Activity} label="模拟结果" description="影响面、事故点与标签" />
<MapActionRow icon={MapPinned} label="选中对象" description="点击管线或节点查看详情" />
</MapPanelSection>
if (measuring) {
return {
label: "测量中",
description:
mode === "segment"
? "点击地图中的管线追加资产"
: mode === "area"
? "连续点击范围顶点,停止后保留读数"
: "连续点击地图取点,停止后保留读数",
tone: "active" as const
};
}
if (hasPoints) {
return {
label: "读数已保留",
description: "可继续测量、复制结果或清除",
tone: "ready" as const
};
}
return {
label: "等待开始",
description:
mode === "segment"
? "开始后点击管线选择资产"
: mode === "area"
? "开始后连续点击范围顶点"
: "开始后连续点击地图取点",
tone: "idle" as const
};
}
function LegendPanel({ legendItems }: { legendItems: MapLegendItem[] }) {
return (
<div className="space-y-2">
<MapLegendList items={legendItems} />
<div role="note" className="surface-control rounded-xl border px-3 py-2.5">
<p className="text-[11px] font-semibold text-slate-700">图例随图层显隐同步</p>
<p className="mt-1 text-[10px] leading-relaxed text-slate-500">
管线颜色和线宽表示口径,设施颜色表示类型,风险色仅用于分析覆盖。
</p>
</div>
</div>
);
}