feat: extend supply workbench demo and map navigation
This commit is contained in:
@@ -35,6 +35,12 @@ import type { AccessTokenProvider } from "@/shared/auth/keycloak-auth";
|
||||
import { AgentTaskTicker } from "./components/agent-task-ticker";
|
||||
import { AnalysisResultsPanel } from "./components/analysis-results-panel";
|
||||
import { PeripheralSamplePanel } from "./peripheral-sample/peripheral-sample-panel";
|
||||
import type { PeripheralWorkOrder } from "./peripheral-sample/client";
|
||||
import {
|
||||
toWaterQualityConditionTask,
|
||||
toWaterQualityAgentTrigger,
|
||||
waterQualityTriggerPrompt
|
||||
} from "./peripheral-sample/work-order-agent-trigger";
|
||||
import { MapDevPanel } from "./components/map-dev-panel";
|
||||
import { MobileWorkbenchSheet } from "./components/mobile-workbench-sheet";
|
||||
import { FeaturePopover } from "./components/feature-popover";
|
||||
@@ -56,9 +62,7 @@ import {
|
||||
type ToolbarToolId
|
||||
} from "./components/toolbar-panel";
|
||||
import { WorkbenchTopBar } from "./components/workbench-top-bar";
|
||||
import {
|
||||
WorkbenchMainFrame
|
||||
} from "./workspace/workbench-main-frame";
|
||||
import { WorkbenchMainFrame } from "./workspace/workbench-main-frame";
|
||||
import { useArtifactWorkspace } from "./workspace/use-artifact-workspace";
|
||||
import {
|
||||
createInitialWorkspaceContext,
|
||||
@@ -71,10 +75,7 @@ import { useWorkbenchMap } from "./hooks/use-workbench-map";
|
||||
import { useWorkbenchMapController } from "./hooks/use-workbench-map-controller";
|
||||
import { useWorkbenchProjectPresentation } from "./hooks/use-workbench-project-presentation";
|
||||
import { useWorkbenchResponsiveLayout } from "./hooks/use-workbench-responsive-layout";
|
||||
import {
|
||||
formatHeaderDataTime,
|
||||
useWorkbenchRuntimeData
|
||||
} from "./hooks/use-workbench-runtime-data";
|
||||
import { formatHeaderDataTime, useWorkbenchRuntimeData } from "./hooks/use-workbench-runtime-data";
|
||||
import { toMapFeatureReference } from "./hooks/use-map-interactions";
|
||||
import {
|
||||
useWorkbenchMeasurement,
|
||||
@@ -121,6 +122,8 @@ export function MapWorkbenchPage({
|
||||
getAccessToken?: AccessTokenProvider;
|
||||
}) {
|
||||
const hasMapboxToken = Boolean(env.TJWATER_MAPBOX_ACCESS_TOKEN);
|
||||
const hasTiandituToken = Boolean(env.TJWATER_TIANDITU_TOKEN);
|
||||
const hasOnlineBasemap = hasMapboxToken || hasTiandituToken;
|
||||
const devPanelEnabled = env.TJWATER_ENABLE_DEV_PANEL;
|
||||
const projectPresentation = useWorkbenchProjectPresentation(user, getAccessToken);
|
||||
const mapContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
@@ -138,7 +141,7 @@ export function MapWorkbenchPage({
|
||||
requestId: number;
|
||||
} | null>(null);
|
||||
const [activeBaseLayerId, setActiveBaseLayerId] = useState(
|
||||
hasMapboxToken ? "mapbox-light" : "none"
|
||||
hasMapboxToken ? "mapbox-light" : hasTiandituToken ? "tianditu-vector" : "none"
|
||||
);
|
||||
const basemapTone = getWorkbenchBasemapTone(activeBaseLayerId);
|
||||
const [activeMeasureModeId, setActiveMeasureModeId] = useState<WorkbenchMeasureMode>("distance");
|
||||
@@ -149,7 +152,7 @@ export function MapWorkbenchPage({
|
||||
const [agentUiResults, setAgentUiResults] = useState<AgentUiResult[]>([]);
|
||||
const {
|
||||
headerDataTime,
|
||||
scheduledConditions,
|
||||
scheduledConditions: runtimeScheduledConditions,
|
||||
scheduledConditionsLoading,
|
||||
workbenchAlerts,
|
||||
runtimeDataSource,
|
||||
@@ -161,13 +164,22 @@ export function MapWorkbenchPage({
|
||||
operationalDate,
|
||||
operationalCursorTime
|
||||
} = useWorkbenchRuntimeData(getAccessToken);
|
||||
const [eventConditions, setEventConditions] = useState<ScheduledConditionRecord[]>([]);
|
||||
const scheduledConditions = useMemo(
|
||||
() => [...eventConditions, ...runtimeScheduledConditions],
|
||||
[eventConditions, runtimeScheduledConditions]
|
||||
);
|
||||
const baseLayerOptions = useMemo(
|
||||
() =>
|
||||
BASE_LAYER_OPTIONS.map((option) => ({
|
||||
...option,
|
||||
disabled: option.id !== "none" && !hasMapboxToken
|
||||
disabled: option.id.startsWith("mapbox-")
|
||||
? !hasMapboxToken
|
||||
: option.id.startsWith("tianditu-")
|
||||
? !hasTiandituToken
|
||||
: false
|
||||
})),
|
||||
[hasMapboxToken]
|
||||
[hasMapboxToken, hasTiandituToken]
|
||||
);
|
||||
|
||||
const handleSelectFeature = useCallback((feature: DetailFeature) => {
|
||||
@@ -215,19 +227,18 @@ export function MapWorkbenchPage({
|
||||
expandAgentPanel,
|
||||
onClearActiveTool: clearActiveTool
|
||||
});
|
||||
const currentAgentWidth = leftPanelOpen
|
||||
? agentPanelWidth
|
||||
: WORKBENCH_LAYOUT.collapsedAgentWidth;
|
||||
const currentAgentWidth = leftPanelOpen ? agentPanelWidth : WORKBENCH_LAYOUT.collapsedAgentWidth;
|
||||
const businessWorkspaceWidth = Math.max(0, viewportWidth - currentAgentWidth);
|
||||
const artifactWorkspace = useArtifactWorkspace(businessWorkspaceWidth);
|
||||
const artifactActive = artifactWorkspace.activeArtifact !== null;
|
||||
const activeSpatialOverlay = artifactWorkspace.activeArtifact?.spatialOverlay ?? null;
|
||||
const activeArtifactHasSpatialView = Boolean(activeSpatialOverlay?.features.length);
|
||||
const allowDemoSimulation = artifactWorkspace.activeArtifact === null
|
||||
|| artifactWorkspace.activeArtifact.origin === "demo";
|
||||
const allowDemoSimulation =
|
||||
artifactWorkspace.activeArtifact === null || artifactWorkspace.activeArtifact.origin === "demo";
|
||||
const latestAnalysisAvailable = Boolean(
|
||||
latestAnalysisArtifact?.blocks.some((block) =>
|
||||
block.kind === "chart" || block.kind === "data-table" || block.kind === "process-flow"
|
||||
latestAnalysisArtifact?.blocks.some(
|
||||
(block) =>
|
||||
block.kind === "chart" || block.kind === "data-table" || block.kind === "process-flow"
|
||||
)
|
||||
);
|
||||
const [timelineMode, setTimelineMode] = useState<OperationalTimelineMode>("compact");
|
||||
@@ -278,15 +289,62 @@ export function MapWorkbenchPage({
|
||||
|
||||
useEffect(() => {
|
||||
setImpactVisible(activeArtifactHasSpatialView);
|
||||
setLayerVisibility((current) => current.simulation === activeArtifactHasSpatialView
|
||||
? current
|
||||
: { ...current, simulation: activeArtifactHasSpatialView });
|
||||
setLayerVisibility((current) =>
|
||||
current.simulation === activeArtifactHasSpatialView
|
||||
? current
|
||||
: { ...current, simulation: activeArtifactHasSpatialView }
|
||||
);
|
||||
}, [activeArtifactHasSpatialView, artifactWorkspace.activeArtifact?.id]);
|
||||
|
||||
function openAgentWorkspaceForViewport() {
|
||||
openAgentPanelForViewport();
|
||||
}
|
||||
|
||||
async function handlePeripheralWorkOrderAgentTrigger(workOrder: PeripheralWorkOrder) {
|
||||
const trigger = toWaterQualityAgentTrigger(workOrder);
|
||||
if (!trigger) {
|
||||
throw new Error("工单不符合水质投诉事件触发条件。");
|
||||
}
|
||||
|
||||
showMapNotice({
|
||||
id: "water-quality-agent-trigger",
|
||||
tone: "loading",
|
||||
title: "水质投诉事件已接收",
|
||||
message: "正在建立结构化工况任务。",
|
||||
duration: Infinity
|
||||
});
|
||||
|
||||
try {
|
||||
const result = await agent.triggerEvent(trigger, waterQualityTriggerPrompt(workOrder));
|
||||
const condition = toWaterQualityConditionTask(workOrder, result.sessionId);
|
||||
setEventConditions((current) => [
|
||||
condition,
|
||||
...current.filter((item) => item.id !== condition.id)
|
||||
]);
|
||||
setPeripheralSamplePanelOpen(false);
|
||||
openConditionWorkspaceForViewport();
|
||||
setSelectedConditionId(condition.id);
|
||||
setConditionFocusRequest((current) => ({
|
||||
conditionId: condition.id,
|
||||
requestId: (current?.requestId ?? 0) + 1
|
||||
}));
|
||||
showMapNotice({
|
||||
id: "water-quality-agent-trigger",
|
||||
tone: "success",
|
||||
title: result.reused ? "已恢复水质工况任务" : "已建立水质工况任务",
|
||||
message: `关联模拟工单 ${workOrder.work_order_id.slice(0, 8)},可在任务中查看处理流程。`
|
||||
});
|
||||
} catch (error) {
|
||||
showMapNotice({
|
||||
id: "water-quality-agent-trigger",
|
||||
tone: "error",
|
||||
title: "工单已创建,Agent 待触发",
|
||||
message: error instanceof Error ? error.message : "Agent 事件入口当前不可用。"
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function openConditionWorkspaceForViewport() {
|
||||
openConditionFeedForViewport();
|
||||
}
|
||||
@@ -325,10 +383,10 @@ export function MapWorkbenchPage({
|
||||
networkView: projectPresentation.networkView,
|
||||
getAccessToken
|
||||
});
|
||||
const displayedDataTime = !["demo", "history"].includes(env.TJWATER_WORKBENCH_RUNTIME_MODE)
|
||||
&& scadaObservedAt
|
||||
? formatHeaderDataTime(new Date(scadaObservedAt), false)
|
||||
: headerDataTime;
|
||||
const displayedDataTime =
|
||||
!["demo", "history"].includes(env.TJWATER_WORKBENCH_RUNTIME_MODE) && scadaObservedAt
|
||||
? formatHeaderDataTime(new Date(scadaObservedAt), false)
|
||||
: headerDataTime;
|
||||
const { controller: mapController, state: mapControllerState } = useWorkbenchMapController({
|
||||
mapRef,
|
||||
mapReady,
|
||||
@@ -378,12 +436,12 @@ export function MapWorkbenchPage({
|
||||
});
|
||||
const taskTickerAvailable = runningTickerConditions.length > 0;
|
||||
const shouldShowTaskTicker =
|
||||
!artifactActive
|
||||
&& mobileSheet === null
|
||||
&& !conditionFeedExpanded
|
||||
&& detailFeature === null
|
||||
&& taskTickerVisible
|
||||
&& taskTickerAvailable;
|
||||
!artifactActive &&
|
||||
mobileSheet === null &&
|
||||
!conditionFeedExpanded &&
|
||||
detailFeature === null &&
|
||||
taskTickerVisible &&
|
||||
taskTickerAvailable;
|
||||
const taskTickerEnterState = prefersReducedMotion
|
||||
? {
|
||||
opacity: 1,
|
||||
@@ -436,41 +494,44 @@ export function MapWorkbenchPage({
|
||||
setConditionFocusRequest((current) => (current?.requestId === requestId ? null : current));
|
||||
}, []);
|
||||
|
||||
const handleSelectOperationalEvent = useCallback((event: OperationalEvent) => {
|
||||
dispatchWorkspaceContext({
|
||||
type: "select-operational-event",
|
||||
eventId: event.id,
|
||||
cursorTime: event.cursorTime
|
||||
});
|
||||
|
||||
const mapTarget = event.mapTargets[0];
|
||||
if (mapTarget) {
|
||||
const handleSelectOperationalEvent = useCallback(
|
||||
(event: OperationalEvent) => {
|
||||
dispatchWorkspaceContext({
|
||||
type: "select-spatial-feature",
|
||||
featureId: mapTarget.featureId,
|
||||
sourceId: mapTarget.sourceId
|
||||
type: "select-operational-event",
|
||||
eventId: event.id,
|
||||
cursorTime: event.cursorTime
|
||||
});
|
||||
void mapController.locateAndHighlight(mapTarget).then(() => {
|
||||
if (mapController.getSnapshot().errorCode) {
|
||||
showMapNotice({
|
||||
tone: "warning",
|
||||
title: "已选择运行事件",
|
||||
message: "关联要素当前不可定位,事件与工况报告仍可继续查看。"
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (event.conditionId) {
|
||||
setSelectedConditionId(event.conditionId);
|
||||
setConditionFocusRequest((current) => ({
|
||||
conditionId: event.conditionId!,
|
||||
requestId: (current?.requestId ?? 0) + 1
|
||||
}));
|
||||
openConditionFeedForViewport();
|
||||
handleConditionExpandedChange(true);
|
||||
}
|
||||
}, [handleConditionExpandedChange, mapController, openConditionFeedForViewport]);
|
||||
const mapTarget = event.mapTargets[0];
|
||||
if (mapTarget) {
|
||||
dispatchWorkspaceContext({
|
||||
type: "select-spatial-feature",
|
||||
featureId: mapTarget.featureId,
|
||||
sourceId: mapTarget.sourceId
|
||||
});
|
||||
void mapController.locateAndHighlight(mapTarget).then(() => {
|
||||
if (mapController.getSnapshot().errorCode) {
|
||||
showMapNotice({
|
||||
tone: "warning",
|
||||
title: "已选择运行事件",
|
||||
message: "关联要素当前不可定位,事件与工况报告仍可继续查看。"
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (event.conditionId) {
|
||||
setSelectedConditionId(event.conditionId);
|
||||
setConditionFocusRequest((current) => ({
|
||||
conditionId: event.conditionId!,
|
||||
requestId: (current?.requestId ?? 0) + 1
|
||||
}));
|
||||
openConditionFeedForViewport();
|
||||
handleConditionExpandedChange(true);
|
||||
}
|
||||
},
|
||||
[handleConditionExpandedChange, mapController, openConditionFeedForViewport]
|
||||
);
|
||||
|
||||
const handleClearOperationalSelection = useCallback(() => {
|
||||
dispatchWorkspaceContext({ type: "clear-operational-event" });
|
||||
@@ -902,16 +963,18 @@ export function MapWorkbenchPage({
|
||||
onShowShortcuts: handleShowShortcuts,
|
||||
onExportConfig: handleExportConfig
|
||||
};
|
||||
const selectedOperationalEvent = operationalEvents.find(
|
||||
(event) => event.id === workspaceContext.operational.selectedEventId
|
||||
) ?? null;
|
||||
const selectedOperationalEvent =
|
||||
operationalEvents.find((event) => event.id === workspaceContext.operational.selectedEventId) ??
|
||||
null;
|
||||
const agentWorkspaceContext = useMemo<AgentWorkspaceContextItem[]>(() => {
|
||||
const items: AgentWorkspaceContextItem[] = [{
|
||||
id: "operational-time",
|
||||
label: "运行时点",
|
||||
value: formatContextClock(workspaceContext.operational.cursorTime),
|
||||
tone: selectedOperationalEvent?.lane === "exception" ? "warning" : "neutral"
|
||||
}];
|
||||
const items: AgentWorkspaceContextItem[] = [
|
||||
{
|
||||
id: "operational-time",
|
||||
label: "运行时点",
|
||||
value: formatContextClock(workspaceContext.operational.cursorTime),
|
||||
tone: selectedOperationalEvent?.lane === "exception" ? "warning" : "neutral"
|
||||
}
|
||||
];
|
||||
|
||||
if (workspaceContext.spatial.featureId) {
|
||||
items.push({
|
||||
@@ -980,9 +1043,11 @@ export function MapWorkbenchPage({
|
||||
>
|
||||
<WorkbenchTopBar
|
||||
dataTime={displayedDataTime}
|
||||
modelName={agent.modelOptions.find((option) => option.id === agent.selectedModel)?.label
|
||||
|| agent.selectedModel
|
||||
|| "Agent 后端连接中"}
|
||||
modelName={
|
||||
agent.modelOptions.find((option) => option.id === agent.selectedModel)?.label ||
|
||||
agent.selectedModel ||
|
||||
"Agent 后端连接中"
|
||||
}
|
||||
activeAnalysisArtifact={artifactWorkspace.activeArtifact}
|
||||
alerts={workbenchAlerts}
|
||||
alertDataSource={alertDataSource}
|
||||
@@ -999,8 +1064,14 @@ export function MapWorkbenchPage({
|
||||
onToggleConditionFeed={toggleConditionWorkspaceForViewport}
|
||||
onToggleTimeline={toggleTimelineVisibility}
|
||||
onToggleTaskTicker={() => setTaskTickerVisible((current) => !current)}
|
||||
onToggleAnalysisResultsPanel={() => { setPeripheralSamplePanelOpen(false); setAnalysisResultsPanelOpen((current) => !current); }}
|
||||
onTogglePeripheralSamplePanel={() => { setAnalysisResultsPanelOpen(false); setPeripheralSamplePanelOpen((current) => !current); }}
|
||||
onToggleAnalysisResultsPanel={() => {
|
||||
setPeripheralSamplePanelOpen(false);
|
||||
setAnalysisResultsPanelOpen((current) => !current);
|
||||
}}
|
||||
onTogglePeripheralSamplePanel={() => {
|
||||
setAnalysisResultsPanelOpen(false);
|
||||
setPeripheralSamplePanelOpen((current) => !current);
|
||||
}}
|
||||
onToggleDevPanel={() => setDevPanelOpen((current) => !current)}
|
||||
onAnalyzeAlertsWithAgent={handleAnalyzeAlertsWithAgent}
|
||||
latestAnalysisAvailable={latestAnalysisAvailable}
|
||||
@@ -1029,6 +1100,7 @@ export function MapWorkbenchPage({
|
||||
open={peripheralSamplePanelOpen}
|
||||
onClose={() => setPeripheralSamplePanelOpen(false)}
|
||||
getAccessToken={getAccessToken}
|
||||
onAgentTrigger={handlePeripheralWorkOrderAgentTrigger}
|
||||
/>
|
||||
|
||||
<WorkbenchMainFrame
|
||||
@@ -1043,56 +1115,58 @@ export function MapWorkbenchPage({
|
||||
onDestroyArtifact={artifactWorkspace.destroyArtifact}
|
||||
timelineMode={timelineMode}
|
||||
timelineBottom={timelineBottom}
|
||||
timelineContent={(
|
||||
timelineVisible ? <>
|
||||
<div className="hidden lg:block">
|
||||
<OperationalTimeline
|
||||
date={workspaceContext.operational.date}
|
||||
cursorTime={workspaceContext.operational.cursorTime}
|
||||
events={operationalEvents}
|
||||
mode={timelineMode}
|
||||
selectedEventId={workspaceContext.operational.selectedEventId}
|
||||
onModeChange={setTimelineMode}
|
||||
onSelectEvent={handleSelectOperationalEvent}
|
||||
onClearSelection={handleClearOperationalSelection}
|
||||
onReturnNow={handleReturnOperationalNow}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
role="region"
|
||||
aria-label="移动端运行时间轴摘要"
|
||||
className="acrylic-control flex h-12 w-full items-center rounded-2xl border p-1 text-xs text-slate-700 lg:hidden"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="打开完整运行时间轴"
|
||||
onClick={openTimelineForViewport}
|
||||
className="flex h-10 min-w-0 flex-1 items-center gap-1.5 rounded-xl px-2 text-left transition-colors hover:bg-white/55 active:bg-white/75"
|
||||
timelineContent={
|
||||
timelineVisible ? (
|
||||
<>
|
||||
<div className="hidden lg:block">
|
||||
<OperationalTimeline
|
||||
date={workspaceContext.operational.date}
|
||||
cursorTime={workspaceContext.operational.cursorTime}
|
||||
events={operationalEvents}
|
||||
mode={timelineMode}
|
||||
selectedEventId={workspaceContext.operational.selectedEventId}
|
||||
onModeChange={setTimelineMode}
|
||||
onSelectEvent={handleSelectOperationalEvent}
|
||||
onClearSelection={handleClearOperationalSelection}
|
||||
onReturnNow={handleReturnOperationalNow}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
role="region"
|
||||
aria-label="移动端运行时间轴摘要"
|
||||
className="acrylic-control flex h-12 w-full items-center rounded-2xl border p-1 text-xs text-slate-700 lg:hidden"
|
||||
>
|
||||
<Clock3 size={15} className="shrink-0 text-blue-600" aria-hidden="true" />
|
||||
<span className="shrink-0 font-semibold">运行时间轴</span>
|
||||
<span className="flex min-w-0 items-center gap-1.5 whitespace-nowrap text-[10px]">
|
||||
<span className="text-slate-500">计划 {operationalSummary.planned}</span>
|
||||
<span className="text-slate-500">执行 {operationalSummary.active}</span>
|
||||
<span className="text-amber-700">关注 {operationalSummary.attention}</span>
|
||||
<span className="text-rose-700">异常 {operationalSummary.exceptions}</span>
|
||||
</span>
|
||||
<span className="ml-auto hidden shrink-0 text-slate-500 min-[360px]:inline">
|
||||
查看全天
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</> : null
|
||||
)}
|
||||
mapContent={(
|
||||
<button
|
||||
type="button"
|
||||
aria-label="打开完整运行时间轴"
|
||||
onClick={openTimelineForViewport}
|
||||
className="flex h-10 min-w-0 flex-1 items-center gap-1.5 rounded-xl px-2 text-left transition-colors hover:bg-white/55 active:bg-white/75"
|
||||
>
|
||||
<Clock3 size={15} className="shrink-0 text-blue-600" aria-hidden="true" />
|
||||
<span className="shrink-0 font-semibold">运行时间轴</span>
|
||||
<span className="flex min-w-0 items-center gap-1.5 whitespace-nowrap text-[10px]">
|
||||
<span className="text-slate-500">计划 {operationalSummary.planned}</span>
|
||||
<span className="text-slate-500">执行 {operationalSummary.active}</span>
|
||||
<span className="text-amber-700">关注 {operationalSummary.attention}</span>
|
||||
<span className="text-rose-700">异常 {operationalSummary.exceptions}</span>
|
||||
</span>
|
||||
<span className="ml-auto hidden shrink-0 text-slate-500 min-[360px]:inline">
|
||||
查看全天
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
) : null
|
||||
}
|
||||
mapContent={
|
||||
<div
|
||||
ref={mapContainerRef}
|
||||
className="map-grid absolute inset-0"
|
||||
style={{ position: "absolute", inset: 0 }}
|
||||
aria-label="供水管网地图"
|
||||
/>
|
||||
)}
|
||||
mapOverlay={(
|
||||
}
|
||||
mapOverlay={
|
||||
<div className="pointer-events-none absolute inset-0">
|
||||
{!devPanelOpen ? (
|
||||
<>
|
||||
@@ -1106,22 +1180,25 @@ export function MapWorkbenchPage({
|
||||
) : null}
|
||||
<div
|
||||
className="absolute right-3 flex flex-col items-end gap-2 bottom-[var(--mobile-map-control-bottom)] transition-[bottom] duration-200 ease-out motion-reduce:transition-none lg:right-0 lg:bottom-[var(--desktop-map-scale-bottom)]"
|
||||
style={{
|
||||
"--mobile-map-control-bottom": `${mobileOverlayLayout.mapControlBottom}px`,
|
||||
"--desktop-map-scale-bottom": `${timelineScaleLayout.scaleBottom}px`
|
||||
} as CSSProperties}
|
||||
style={
|
||||
{
|
||||
"--mobile-map-control-bottom": `${mobileOverlayLayout.mapControlBottom}px`,
|
||||
"--desktop-map-scale-bottom": `${timelineScaleLayout.scaleBottom}px`
|
||||
} as CSSProperties
|
||||
}
|
||||
>
|
||||
<div className="flex items-end gap-2 lg:pr-2">
|
||||
<MapZoom mapRef={mapRef} mapReady={mapReady} onHome={fitNetworkBounds} />
|
||||
<MapZoom
|
||||
mapRef={mapRef}
|
||||
mapReady={mapReady}
|
||||
onHome={fitNetworkBounds}
|
||||
reducedMotion={prefersReducedMotion ?? false}
|
||||
/>
|
||||
</div>
|
||||
<MapScaleLine
|
||||
className="hidden lg:inline-flex"
|
||||
mapRef={mapRef}
|
||||
mapReady={mapReady}
|
||||
/>
|
||||
<MapScaleLine className="hidden lg:inline-flex" mapRef={mapRef} mapReady={mapReady} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
}
|
||||
/>
|
||||
|
||||
<WorkbenchAgentPanels
|
||||
@@ -1307,11 +1384,11 @@ export function MapWorkbenchPage({
|
||||
{!mapReady && !mapError ? (
|
||||
<MapLoadingNotice message="正在加载底图样式、管网数据源和交互图层。" />
|
||||
) : null}
|
||||
{!hasMapboxToken ? (
|
||||
{!hasOnlineBasemap ? (
|
||||
<MapSourceStatusNotice
|
||||
sourceName="Mapbox 底图"
|
||||
sourceName="在线底图"
|
||||
status="degraded"
|
||||
message="未检测到 Mapbox token,当前已切换为无底图模式,仅展示业务图层。"
|
||||
message="未检测到 Mapbox 或天地图令牌,当前已切换为无底图模式,仅展示业务图层。"
|
||||
/>
|
||||
) : null}
|
||||
{sourceStatuses.filter(isNoticeSourceStatus).map((sourceStatus) => (
|
||||
|
||||
Reference in New Issue
Block a user