fix: ensure complete StyleConfig defaults in ECharts builder

Root cause: backend returns minimal style object without title/legend/axis
sub-objects, causing TypeError 'Cannot read properties of undefined'.
Fix: normalize style with full defaults at the builder entry point,
protecting ALL chart type builders uniformly.
Also fix antd deprecation warnings (destroyOnClose -> destroyOnHidden).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hailin 2026-04-05 02:31:37 -07:00
parent b7edc7f86e
commit 36c5b42f53
4 changed files with 44 additions and 23 deletions

View File

@ -1,5 +1,6 @@
import { type IChartRenderer } from '@/application/ports/output/IChartRenderer';
import { type ChartInstance } from '@/domain/entities/ChartInstance';
import { type StyleConfig } from '@/domain/entities/StyleConfig';
import { buildBarOption } from './barOptionBuilder';
import { buildLineOption } from './lineOptionBuilder';
import { buildPieOption } from './pieOptionBuilder';
@ -10,44 +11,64 @@ import { buildMapOption } from './mapOptionBuilder';
import { buildWordcloudOption } from './wordcloudOptionBuilder';
import { buildComboOption } from './comboOptionBuilder';
const DEFAULT_STYLE: StyleConfig = {
title: { text: '', visible: false, fontSize: 16, fontWeight: 'bold', color: '#333', align: 'left' },
colors: ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4'],
legend: { visible: true, position: 'top', orient: 'horizontal', fontSize: 12, color: '#666' },
xAxis: { visible: true, labelVisible: true, labelFontSize: 12, labelColor: '#666', labelRotation: 0, titleVisible: false, titleText: '', gridVisible: true, gridColor: '#eee' },
yAxis: { visible: true, labelVisible: true, labelFontSize: 12, labelColor: '#666', labelRotation: 0, titleVisible: false, titleText: '', gridVisible: true, gridColor: '#eee' },
dataLabel: { visible: false, fontSize: 12, color: '#333', position: 'outside', format: 'value' },
background: { color: 'transparent', opacity: 1, borderRadius: 4 },
border: { visible: false, color: '#ddd', width: 1, style: 'solid' },
animation: { enabled: true, duration: 500, easing: 'ease-out' },
};
function ensureStyle(style: any): StyleConfig {
if (!style || typeof style !== 'object') return DEFAULT_STYLE;
return {
title: { ...DEFAULT_STYLE.title, ...(style.title ?? {}) },
colors: style.colors ?? DEFAULT_STYLE.colors,
legend: { ...DEFAULT_STYLE.legend, ...(style.legend ?? {}) },
xAxis: { ...DEFAULT_STYLE.xAxis, ...(style.xAxis ?? {}) },
yAxis: { ...DEFAULT_STYLE.yAxis, ...(style.yAxis ?? {}) },
dataLabel: { ...DEFAULT_STYLE.dataLabel, ...(style.dataLabel ?? {}) },
background: { ...DEFAULT_STYLE.background, ...(style.background ?? {}) },
border: { ...DEFAULT_STYLE.border, ...(style.border ?? {}) },
animation: { ...DEFAULT_STYLE.animation, ...(style.animation ?? {}) },
};
}
export class EChartsOptionBuilder implements IChartRenderer {
build(chart: ChartInstance, data: Record<string, any>[]): Record<string, any> {
switch (chart.type) {
const safeChart = { ...chart, style: ensureStyle(chart.style) };
switch (safeChart.type) {
case 'bar':
case 'grouped-bar':
case 'stacked-bar':
case 'horizontal-bar':
return buildBarOption(chart, data);
return buildBarOption(safeChart, data);
case 'line':
case 'area':
return buildLineOption(chart, data);
return buildLineOption(safeChart, data);
case 'pie':
case 'donut':
return buildPieOption(chart, data);
return buildPieOption(safeChart, data);
case 'scatter':
case 'boston-matrix':
return buildScatterOption(chart, data);
return buildScatterOption(safeChart, data);
case 'radar':
return buildRadarOption(chart, data);
return buildRadarOption(safeChart, data);
case 'heatmap':
return buildHeatmapOption(chart, data);
return buildHeatmapOption(safeChart, data);
case 'map':
return buildMapOption(chart, data);
return buildMapOption(safeChart, data);
case 'wordcloud':
return buildWordcloudOption(chart, data);
return buildWordcloudOption(safeChart, data);
case 'combo':
return buildComboOption(chart, data);
return buildComboOption(safeChart, data);
default:
throw new Error(`Unsupported chart type: ${chart.type}`);
return { title: { text: 'Unsupported chart type' } };
}
}
}

View File

@ -71,7 +71,7 @@ export default function ImportModal() {
cancelText="取消"
width={currentStep === 1 ? 960 : 600}
okButtonProps={{ disabled: currentStep === 0 }}
destroyOnClose
destroyOnHidden
>
<Steps
current={currentStep}

View File

@ -270,7 +270,7 @@ export const ExportDialog: React.FC = () => {
</Button>
</Space>
}
destroyOnClose
destroyOnHidden
>
<Spin spinning={loading} tip="正在导出...">
<div>

View File

@ -37,7 +37,7 @@ export const TemplateModal: React.FC = () => {
okText={activeTab === 'save' ? '保存模板' : '确定'}
cancelText="关闭"
width={720}
destroyOnClose
destroyOnHidden
>
<Tabs
activeKey={activeTab}