diff --git a/frontend/src/adapters/gateways/EChartsOptionBuilder/index.ts b/frontend/src/adapters/gateways/EChartsOptionBuilder/index.ts index 4cf6f13..9c187fc 100644 --- a/frontend/src/adapters/gateways/EChartsOptionBuilder/index.ts +++ b/frontend/src/adapters/gateways/EChartsOptionBuilder/index.ts @@ -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[]): Record { - 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' } }; } } } diff --git a/frontend/src/frameworks/components/dataImport/ImportModal.tsx b/frontend/src/frameworks/components/dataImport/ImportModal.tsx index 708eb9f..a9915a7 100644 --- a/frontend/src/frameworks/components/dataImport/ImportModal.tsx +++ b/frontend/src/frameworks/components/dataImport/ImportModal.tsx @@ -71,7 +71,7 @@ export default function ImportModal() { cancelText="取消" width={currentStep === 1 ? 960 : 600} okButtonProps={{ disabled: currentStep === 0 }} - destroyOnClose + destroyOnHidden > { } - destroyOnClose + destroyOnHidden >
diff --git a/frontend/src/frameworks/components/template/TemplateModal.tsx b/frontend/src/frameworks/components/template/TemplateModal.tsx index 7b26080..59a92cf 100644 --- a/frontend/src/frameworks/components/template/TemplateModal.tsx +++ b/frontend/src/frameworks/components/template/TemplateModal.tsx @@ -37,7 +37,7 @@ export const TemplateModal: React.FC = () => { okText={activeTab === 'save' ? '保存模板' : '确定'} cancelText="关闭" width={720} - destroyOnClose + destroyOnHidden >