fix(wavelet): 修复全球态势板地图容器尺寸为 0

地图注册完成后再挂载 ResizeObserver,并在获得有效宽高后才初始化 ECharts。
This commit is contained in:
ryan
2026-06-18 19:12:08 +08:00
parent 194560c887
commit 49c40b20b7
3 changed files with 60 additions and 36 deletions
@@ -214,6 +214,10 @@ export function WorldStageMap({
}, []); }, []);
useEffect(() => { useEffect(() => {
if (!mapReady) {
return;
}
const container = chartContainerRef.current; const container = chartContainerRef.current;
if (!container) { if (!container) {
return; return;
@@ -222,18 +226,23 @@ export function WorldStageMap({
const updateSize = () => { const updateSize = () => {
const nextWidth = container.clientWidth; const nextWidth = container.clientWidth;
const nextHeight = container.clientHeight; const nextHeight = container.clientHeight;
if (nextWidth <= 0 || nextHeight <= 0) {
return;
}
setContainerSize((previous) => setContainerSize((previous) =>
previous.width === nextWidth && previous.height === nextHeight previous.width === nextWidth && previous.height === nextHeight
? previous ? previous
: { width: nextWidth, height: nextHeight }, : {width: nextWidth, height: nextHeight},
); );
}; };
updateSize(); updateSize();
const frameId = window.requestAnimationFrame(updateSize);
if (typeof ResizeObserver === 'undefined') { if (typeof ResizeObserver === 'undefined') {
window.addEventListener('resize', updateSize); window.addEventListener('resize', updateSize);
return () => { return () => {
window.cancelAnimationFrame(frameId);
window.removeEventListener('resize', updateSize); window.removeEventListener('resize', updateSize);
}; };
} }
@@ -244,16 +253,17 @@ export function WorldStageMap({
observer.observe(container); observer.observe(container);
return () => { return () => {
window.cancelAnimationFrame(frameId);
observer.disconnect(); observer.disconnect();
}; };
}, []); }, [mapReady]);
const mapPalette = useMemo( const mapPalette = useMemo(
() => () =>
isDark isDark
? { ? {
areaColor: '#1c1f26', areaColor: '#1e293b',
borderColor: 'rgba(148,163,184,0.16)', borderColor: 'rgba(148,163,184,0.32)',
labelColor: '#e2e8f0', labelColor: '#e2e8f0',
healthyColor: '#34d399', healthyColor: '#34d399',
warningColor: '#fbbf24', warningColor: '#fbbf24',
@@ -263,8 +273,8 @@ export function WorldStageMap({
dangerBorder: '#fca5a5', dangerBorder: '#fca5a5',
} }
: { : {
areaColor: '#f4f4f5', areaColor: '#e2e8f0',
borderColor: 'rgba(148,163,184,0.28)', borderColor: 'rgba(100,116,139,0.45)',
labelColor: '#18181b', labelColor: '#18181b',
healthyColor: '#10b981', healthyColor: '#10b981',
warningColor: '#f59e0b', warningColor: '#f59e0b',
@@ -544,23 +554,38 @@ export function WorldStageMap({
); );
} }
const chartReady = containerSize.width > 0 && containerSize.height > 0;
return ( return (
<div ref={chartContainerRef} className="h-full w-full"> <div ref={chartContainerRef} className="h-full min-h-0 w-full">
<ReactEChartsCore {chartReady ? (
echarts={echarts} <ReactEChartsCore
option={mapOption} echarts={echarts}
notMerge option={mapOption}
lazyUpdate notMerge
opts={{ renderer: 'canvas' }} lazyUpdate
onEvents={{ opts={{renderer: 'canvas'}}
click: (params: { data?: MapNodeDatum }) => { onEvents={{
if (params.data?.route) { click: (params: {data?: MapNodeDatum}) => {
router.push(params.data.route); if (params.data?.route) {
} router.push(params.data.route);
}, }
}} },
style={{ height: '100%', width: '100%' }} }}
/> style={{
height: containerSize.height,
width: containerSize.width,
}}
/>
) : (
<div className="flex h-full items-center justify-center">
<EmptyState
title="全球地图加载中"
description="正在测量地图容器尺寸..."
iconSize="sm"
/>
</div>
)}
</div> </div>
); );
} }
@@ -229,21 +229,19 @@ export function WorldStage({
<div className="grid gap-3 lg:grid-cols-[minmax(0,1fr)_12.5rem] lg:gap-4"> <div className="grid gap-3 lg:grid-cols-[minmax(0,1fr)_12.5rem] lg:gap-4">
<div <div
ref={mapViewportRef} ref={mapViewportRef}
className="relative h-[200px] overflow-hidden rounded-lg border border-dashed bg-muted/20 sm:h-[240px] lg:h-[260px]" className="relative h-[200px] min-h-[200px] min-w-0 overflow-hidden rounded-lg border border-dashed bg-muted/20 sm:h-[240px] sm:min-h-[240px] lg:h-[260px] lg:min-h-[260px]"
> >
<div className="absolute inset-0"> {shouldRenderMap ? (
{shouldRenderMap ? ( <WorldStageMap nodes={nodes} sourceCountries={sourceCountries} />
<WorldStageMap nodes={nodes} sourceCountries={sourceCountries} /> ) : (
) : ( <div className="flex h-full items-center justify-center px-4">
<div className="flex h-full items-center justify-center px-4"> <EmptyState
<EmptyState title="地图准备中"
title="地图准备中" description="进入可视区域后加载"
description="进入可视区域后加载" iconSize="sm"
iconSize="sm" />
/> </div>
</div> )}
)}
</div>
{shouldRenderMap && nodes.length === 0 ? ( {shouldRenderMap && nodes.length === 0 ? (
<div className="pointer-events-none absolute inset-x-3 bottom-2 z-10"> <div className="pointer-events-none absolute inset-x-3 bottom-2 z-10">
+1
View File
@@ -22,6 +22,7 @@ sidebar: false
- Wavelet 总览仪表盘迁移旧前端「全球态势板」:世界地图展示节点健康与来源国家热度,并保留在线覆盖、流量与容量摘要指标。 - Wavelet 总览仪表盘迁移旧前端「全球态势板」:世界地图展示节点健康与来源国家热度,并保留在线覆盖、流量与容量摘要指标。
- Wavelet 全球态势板视觉对齐系统风格:改用 `Card` / `Badge` / `Progress` 与虚线边框指标卡,地图配色与 tooltip 跟随主题 token。 - Wavelet 全球态势板视觉对齐系统风格:改用 `Card` / `Badge` / `Progress` 与虚线边框指标卡,地图配色与 tooltip 跟随主题 token。
- Wavelet 全球态势板布局紧凑化:地图高度降至约 260px,指标改为侧栏密集单元格,图例改为行内色点标注。 - Wavelet 全球态势板布局紧凑化:地图高度降至约 260px,指标改为侧栏密集单元格,图例改为行内色点标注。
- 修复 Wavelet 全球态势板 ECharts 地图在紧凑布局下容器尺寸为 0 导致无法渲染的问题。
- 将 `openflare-server` 后端业务域迁移至 `Wavelet/internal/apps/openflare/`,通过 `/api/*` legacy 兼容层保持旧前端 API 路径不变。 - 将 `openflare-server` 后端业务域迁移至 `Wavelet/internal/apps/openflare/`,通过 `/api/*` legacy 兼容层保持旧前端 API 路径不变。
- 新增 OpenFlare 业务表 goose 迁移(`of_options`、`of_origins`、`of_proxy_routes`、`of_nodes`、`of_waf_*`、`of_tls_*`、`of_config_versions`、`of_pages_*`、`of_apply_logs` 等)。 - 新增 OpenFlare 业务表 goose 迁移(`of_options`、`of_origins`、`of_proxy_routes`、`of_nodes`、`of_waf_*`、`of_tls_*`、`of_config_versions`、`of_pages_*`、`of_apply_logs` 等)。