持久化样式状态;分离样式设置和图例显示;完成时间轴组件代码修复建议

This commit is contained in:
JIANG
2025-10-20 11:15:49 +08:00
parent 4e819b20ea
commit f62ab1c30e
3 changed files with 77 additions and 65 deletions

View File

@@ -22,7 +22,7 @@ import WebGLVectorTileLayer from "ol/layer/WebGLVectorTile";
import VectorTileSource from "ol/source/VectorTile";
import { useData, useMap } from "../MapComponent";
import StyleLegend, { LegendStyleConfig } from "./StyleLegend";
import { LegendStyleConfig } from "./StyleLegend";
import { FlatStyleLike } from "ol/style/flat";
import { calculateClassification } from "@utils/breaks_classification";
@@ -55,7 +55,10 @@ interface LayerStyleState {
legendConfig: LegendStyleConfig;
isActive: boolean;
}
// 持久化存储
const STORAGE_KEYS = {
layerStyleStates: "styleEditor_layerStyleStates",
};
// 预设颜色方案
const SINGLE_COLOR_PALETTES = [
{
@@ -116,6 +119,7 @@ const StyleEditorPanel: React.FC = () => {
setShowPipeText,
setJunctionText,
setPipeText,
updateLegendConfigs,
} = data;
const { open, close } = useNotification();
@@ -128,11 +132,6 @@ const StyleEditorPanel: React.FC = () => {
const [renderLayers, setRenderLayers] = useState<WebGLVectorTileLayer[]>([]);
const [selectedRenderLayer, setSelectedRenderLayer] =
useState<WebGLVectorTileLayer>();
const [selectedProperty, setSelectedProperty] = useState<{
name: string;
value: string;
}>({ name: "", value: "" });
const [availableProperties, setAvailableProperties] = useState<
{ name: string; value: string }[]
>([]);
@@ -154,8 +153,18 @@ const StyleEditorPanel: React.FC = () => {
});
// 样式状态管理 - 存储多个图层的样式状态
const [layerStyleStates, setLayerStyleStates] = useState<LayerStyleState[]>(
[]
() => {
const saved = sessionStorage.getItem(STORAGE_KEYS.layerStyleStates);
return saved ? JSON.parse(saved) : [];
}
);
// 保存layerStyleStates到sessionStorage
useEffect(() => {
sessionStorage.setItem(
STORAGE_KEYS.layerStyleStates,
JSON.stringify(layerStyleStates)
);
}, [layerStyleStates]);
// 颜色方案选择
const [singlePaletteIndex, setSinglePaletteIndex] = useState(0);
const [gradientPaletteIndex, setGradientPaletteIndex] = useState(0);
@@ -186,31 +195,37 @@ const StyleEditorPanel: React.FC = () => {
);
// 保存当前图层的样式状态
const saveLayerStyle = useCallback(
(layerId?: string, legendConfig?: LegendStyleConfig) => {
(layerId?: string, newLegendConfig?: LegendStyleConfig) => {
if (!selectedRenderLayer || !styleConfig.property) {
console.warn("无法保存样式:缺少必要的图层或样式配置");
return;
}
if (!layerId) return; // 如果没有传入 layerId则不保存
const layerName = selectedRenderLayer.get("name") || `图层${layerId}`;
// 如果没有传入图例配置,则创建一个默认的空配置
const finalLegendConfig: LegendStyleConfig = legendConfig || {
const layerName =
newLegendConfig?.layerName ||
selectedRenderLayer.get("name") ||
`图层${layerId}`;
const property = availableProperties.find(
(p) => p.value === styleConfig.property
);
let legendConfig: LegendStyleConfig = newLegendConfig || {
layerId,
layerName,
property: selectedProperty.name,
property: property?.name || styleConfig.property,
colors: [],
type: "point",
type: selectedRenderLayer.get("type"),
dimensions: [],
breaks: [],
};
const newStyleState: LayerStyleState = {
layerId,
layerName,
styleConfig: { ...styleConfig },
legendConfig: { ...finalLegendConfig },
legendConfig: { ...legendConfig },
isActive: true,
};
setLayerStyleStates((prev) => {
// 检查是否已存在该图层的样式状态
const existingIndex = prev.findIndex(
@@ -323,12 +338,12 @@ const StyleEditorPanel: React.FC = () => {
return;
}
const styleConfig = layerStyleConfig.styleConfig;
const selectedRenderLayer = renderLayers.filter((layer) => {
const renderLayer = renderLayers.filter((layer) => {
return layer.get("value") === layerStyleConfig.layerId;
})[0];
if (!selectedRenderLayer || !styleConfig?.property) return;
const layerType: string = selectedRenderLayer?.get("type");
const source = selectedRenderLayer.getSource();
if (!renderLayer || !styleConfig?.property) return;
const layerType: string = renderLayer?.get("type");
const source = renderLayer.getSource();
if (!source) return;
const breaksLength = breaks.length;
@@ -416,13 +431,17 @@ const StyleEditorPanel: React.FC = () => {
dynamicStyle["circle-stroke-width"] = 2;
}
selectedRenderLayer.setStyle(dynamicStyle);
renderLayer.setStyle(dynamicStyle);
// 用初始化时的样式配置更新图例配置,避免覆盖已有的图例名称和属性
const layerId = renderLayer.get("value");
const initLayerStyleState = layerStyleStates.find(
(s) => s.layerId === layerId
);
// 创建图例配置对象
const legendConfig: LegendStyleConfig = {
layerName: selectedRenderLayer.get("name"),
layerId: selectedRenderLayer.get("value"),
property: selectedProperty.name,
layerName: initLayerStyleState?.layerName || `图层${layerId}`,
layerId: layerId,
property: initLayerStyleState?.legendConfig.property || "",
colors: colors,
type: layerType,
dimensions: dimensions,
@@ -430,7 +449,7 @@ const StyleEditorPanel: React.FC = () => {
};
// 自动保存样式状态,直接传入图例配置
setTimeout(() => {
saveLayerStyle(selectedRenderLayer.get("value"), legendConfig);
saveLayerStyle(renderLayer.get("value"), legendConfig);
}, 100);
};
// 重置样式
@@ -695,20 +714,7 @@ const StyleEditorPanel: React.FC = () => {
setStyleConfig(cachedStyleState.styleConfig);
}
}, [renderLayers, selectedRenderLayer, map, renderLayers, layerStyleStates]);
// 同步属性状态
useEffect(() => {
// 当属性值变化时自动同步 selectedProperty
if (styleConfig.property) {
const prop = availableProperties.find(
(p) => p.value === styleConfig.property
);
if (prop) {
setSelectedProperty({ name: prop.name, value: prop.value });
}
} else {
setSelectedProperty({ name: "", value: "" });
}
}, [styleConfig.property, availableProperties]);
// 监听颜色类型变化,当切换到单一色时自动勾选宽度调整选项
useEffect(() => {
if (styleConfig.colorType === "single") {
@@ -720,14 +726,17 @@ const StyleEditorPanel: React.FC = () => {
}, [styleConfig.colorType]);
// 获取所有激活的图例配置
const getActiveLegendConfigs = useCallback(() => {
return layerStyleStates
.filter((state) => state.isActive && state.legendConfig.property)
.map((state) => ({
...state.legendConfig,
layerName: state.layerName,
layerId: state.layerId,
}));
useEffect(() => {
if (!updateLegendConfigs) return;
updateLegendConfigs(
layerStyleStates
.filter((state) => state.isActive && state.legendConfig.property)
.map((state) => ({
...state.legendConfig,
layerName: state.layerName,
layerId: state.layerId,
}))
);
}, [layerStyleStates]);
const getColorSetting = () => {
@@ -1184,16 +1193,6 @@ const StyleEditorPanel: React.FC = () => {
</Button>
</Box>
</div>
{/* 显示多图层图例 */}
{getActiveLegendConfigs().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">
{getActiveLegendConfigs().map((config, index) => (
<StyleLegend key={`${config.layerId}-${index}`} {...config} />
))}
</div>
</div>
)}
</>
);
};

View File

@@ -55,7 +55,6 @@ const Timeline: React.FC = () => {
const [isPlaying, setIsPlaying] = useState<boolean>(false);
const [playInterval, setPlayInterval] = useState<number>(5000); // 毫秒
const [calculatedInterval, setCalculatedInterval] = useState<number>(1440); // 分钟
const [sliderValue, setSliderValue] = useState<number>(0);
const intervalRef = useRef<NodeJS.Timeout | null>(null);
const timelineRef = useRef<HTMLDivElement>(null);
@@ -192,7 +191,6 @@ const Timeline: React.FC = () => {
const handleSliderChange = useCallback(
(event: Event, newValue: number | number[]) => {
const value = Array.isArray(newValue) ? newValue[0] : newValue;
setSliderValue(value);
// 防抖设置currentTime避免频繁触发数据获取
if (debounceRef.current) {
clearTimeout(debounceRef.current);
@@ -219,7 +217,6 @@ const Timeline: React.FC = () => {
intervalRef.current = setInterval(() => {
setCurrentTime((prev) => {
const next = prev >= 1440 ? 0 : prev + 15; // 到达24:00后回到00:00
setSliderValue(next);
return next;
});
}, playInterval);
@@ -237,7 +234,6 @@ const Timeline: React.FC = () => {
const handleStop = useCallback(() => {
setIsPlaying(false);
setCurrentTime(0);
setSliderValue(0);
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
@@ -248,7 +244,6 @@ const Timeline: React.FC = () => {
const handleStepBackward = useCallback(() => {
setCurrentTime((prev) => {
const next = prev <= 0 ? 1440 : prev - 15;
setSliderValue(next);
return next;
});
}, []);
@@ -256,7 +251,6 @@ const Timeline: React.FC = () => {
const handleStepForward = useCallback(() => {
setCurrentTime((prev) => {
const next = prev >= 1440 ? 0 : prev + 15;
setSliderValue(next);
return next;
});
}, []);
@@ -280,7 +274,6 @@ const Timeline: React.FC = () => {
intervalRef.current = setInterval(() => {
setCurrentTime((prev) => {
const next = prev >= 1440 ? 0 : prev + 15;
setSliderValue(next);
return next;
});
}, newInterval);
@@ -485,7 +478,7 @@ const Timeline: React.FC = () => {
{/* 时间轴滑块 */}
<Box ref={timelineRef} sx={{ px: 2 }}>
<Slider
value={sliderValue}
value={currentTime}
min={0}
max={1440} // 24:00 = 1440分钟
step={15} // 每15分钟一个步进

View File

@@ -25,6 +25,8 @@ import { TextLayer } from "@deck.gl/layers";
import { TripsLayer } from "@deck.gl/geo-layers";
import { CollisionFilterExtension } from "@deck.gl/extensions";
import StyleLegend from "./Controls/StyleLegend"; // 假设 StyleLegend 在同一目录
interface MapComponentProps {
children?: React.ReactNode;
}
@@ -45,6 +47,7 @@ interface DataContextType {
pipeText: string;
setJunctionText?: React.Dispatch<React.SetStateAction<string>>;
setPipeText?: React.Dispatch<React.SetStateAction<string>>;
updateLegendConfigs?: (configs: any[]) => void;
}
// 创建自定义Layer类来包装deck.gl
@@ -125,6 +128,12 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
const [pipeText, setPipeText] = useState("");
const flowAnimation = useRef(false); // 添加动画控制标志
const [currentZoom, setCurrentZoom] = useState(12); // 当前缩放级别
// 图例配置
const [activeLegendConfigs, setActiveLegendConfigs] = useState<any[]>([]); // 存储图例配置
// 从 StyleEditorPanel 接收图例配置的回调
const updateLegendConfigs = (configs: any[]) => {
setActiveLegendConfigs(configs);
};
// 防抖更新函数
const debouncedUpdateData = useRef(
debounce(() => {
@@ -616,6 +625,7 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
showPipeText,
junctionText,
pipeText,
updateLegendConfigs,
}}
>
<MapContext.Provider value={map}>
@@ -626,6 +636,16 @@ const MapComponent: React.FC<MapComponentProps> = ({ children }) => {
</div>
<canvas id="deck-canvas" />
</MapContext.Provider>
{/* 图例始终渲染 */}
{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>
)}
</DataContext.Provider>
</>
);