@agions/taroviz 1.9.0 → 1.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agions/taroviz",
3
- "version": "1.9.0",
3
+ "version": "1.11.1",
4
4
  "description": "基于 Taro 和 ECharts 的多端图表组件库",
5
5
  "type": "module",
6
6
  "main": "dist/cjs/index.js",
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * 箱线图类型定义
3
3
  */
4
+ import type { EChartsType, ECElementEvent } from 'echarts';
5
+ import type { LoadingOptions } from '../types';
4
6
 
5
7
  export type BoxplotChartProps = {
6
8
  option?: BoxplotOption;
@@ -8,11 +10,11 @@ export type BoxplotChartProps = {
8
10
  height?: string | number;
9
11
  className?: string;
10
12
  style?: React.CSSProperties;
11
- onEvents?: Record<string, (params: any) => void>;
13
+ onEvents?: Record<string, (params: ECElementEvent) => void>;
12
14
  loading?: boolean;
13
- loadingOption?: any;
15
+ loadingOption?: LoadingOptions;
14
16
  theme?: string;
15
- onChartReady?: (chart: any) => void;
17
+ onChartReady?: (chart: EChartsType) => void;
16
18
  opts?: {
17
19
  devicePixelRatio?: number;
18
20
  renderer?: 'canvas' | 'svg';
@@ -14,7 +14,10 @@ describe('CandlestickChart', () => {
14
14
 
15
15
  it('renders with custom width and height', () => {
16
16
  const { container } = render(<CandlestickChart width={500} height={400} />);
17
- expect(container.firstChild).toHaveStyle({ width: '500px', height: '400px' });
17
+ // BaseChartWrapper renders a fragment with a hidden accessibility table (first child)
18
+ // and the actual chart div (last child), so we query the last child for styles
19
+ const chartDiv = container.lastChild;
20
+ expect(chartDiv).toHaveStyle({ width: '500px', height: '400px' });
18
21
  });
19
22
 
20
23
  it('renders with stock data', () => {
@@ -14,12 +14,18 @@ describe('GraphChart', () => {
14
14
 
15
15
  it('renders with custom className', () => {
16
16
  const { container } = render(<GraphChart className="test-graph" />);
17
- expect(container.firstChild).toHaveClass('test-graph');
17
+ // BaseChartWrapper renders a fragment with a hidden accessibility table (first child)
18
+ // and the actual chart div (last child), so we query the last child for className
19
+ const chartDiv = container.lastChild;
20
+ expect(chartDiv).toHaveClass('test-graph');
18
21
  });
19
22
 
20
23
  it('renders with custom width and height', () => {
21
24
  const { container } = render(<GraphChart width={500} height={400} />);
22
- expect(container.firstChild).toHaveStyle({ width: '500px', height: '400px' });
25
+ // BaseChartWrapper renders a fragment with a hidden accessibility table (first child)
26
+ // and the actual chart div (last child), so we query the last child for styles
27
+ const chartDiv = container.lastChild;
28
+ expect(chartDiv).toHaveStyle({ width: '500px', height: '400px' });
23
29
  });
24
30
 
25
31
  it('renders with basic option', () => {
@@ -2,17 +2,20 @@
2
2
  * 平行坐标图类型定义
3
3
  */
4
4
 
5
+ import type { EChartsType, ECElementEvent } from 'echarts';
6
+ import type { LoadingOptions } from '../types';
7
+
5
8
  export type ParallelChartProps = {
6
9
  option?: ParallelOption;
7
10
  width?: string | number;
8
11
  height?: string | number;
9
12
  className?: string;
10
13
  style?: React.CSSProperties;
11
- onEvents?: Record<string, (params: any) => void>;
14
+ onEvents?: Record<string, (params: ECElementEvent) => void>;
12
15
  loading?: boolean;
13
- loadingOption?: any;
16
+ loadingOption?: LoadingOptions;
14
17
  theme?: string;
15
- onChartReady?: (chart: any) => void;
18
+ onChartReady?: (chart: EChartsType) => void;
16
19
  opts?: {
17
20
  devicePixelRatio?: number;
18
21
  renderer?: 'canvas' | 'svg';
@@ -2,7 +2,7 @@
2
2
  * 树图类型定义
3
3
  * ECharts 内置 tree 类型
4
4
  */
5
- import type { EChartsOption } from 'echarts';
5
+ import type { EChartsOption, EChartsType, ECElementEvent } from 'echarts';
6
6
 
7
7
  // ============================================================================
8
8
  // 树图数据节点
@@ -166,9 +166,9 @@ export interface TreeChartProps {
166
166
  /** 加载配置 */
167
167
  loadingOption?: Record<string, unknown>;
168
168
  /** 图表初始化回调 */
169
- onChartInit?: (chart: any) => void;
169
+ onChartInit?: (chart: EChartsType) => void;
170
170
  /** 图表就绪回调 */
171
- onChartReady?: (chart: any) => void;
171
+ onChartReady?: (chart: EChartsType) => void;
172
172
  /** 事件回调 */
173
- onEvents?: Record<string, (params: any) => void>;
173
+ onEvents?: Record<string, (params: ECElementEvent) => void>;
174
174
  }
@@ -14,7 +14,10 @@ describe('WordCloudChart', () => {
14
14
 
15
15
  it('renders with custom width and height', () => {
16
16
  const { container } = render(<WordCloudChart width={600} height={400} />);
17
- expect(container.firstChild).toHaveStyle({ width: '600px', height: '400px' });
17
+ // BaseChartWrapper renders a fragment with a hidden accessibility table (first child)
18
+ // and the actual chart div (last child), so we query the last child for styles
19
+ const chartDiv = container.lastChild;
20
+ expect(chartDiv).toHaveStyle({ width: '600px', height: '400px' });
18
21
  });
19
22
 
20
23
  it('renders with word data', () => {
@@ -10,6 +10,9 @@ import {
10
10
  AnimationManagerConfig,
11
11
  AnimationType,
12
12
  AnimationEventType,
13
+ AnimationEventHandler,
14
+ AnimationEventData,
15
+ EChartsAnimationConfigResult,
13
16
  } from './types';
14
17
 
15
18
  /**
@@ -131,7 +134,7 @@ export class AnimationManager {
131
134
  private templates: Map<string, AnimationTemplate> = new Map();
132
135
  private defaultConfig: AnimationConfig;
133
136
  private performanceConfig: AnimationManagerConfig['performance'];
134
- private eventHandlers: Map<string, Set<(event: any) => void>> = new Map();
137
+ private eventHandlers: Map<string, Set<AnimationEventHandler>> = new Map();
135
138
 
136
139
  /**
137
140
  * 私有构造函数
@@ -304,7 +307,7 @@ export class AnimationManager {
304
307
  public generateEChartsAnimationConfig(
305
308
  config: Partial<AnimationConfig> = {},
306
309
  dataLength: number = 0
307
- ): any {
310
+ ): EChartsAnimationConfigResult {
308
311
  const optimizedConfig = this.getOptimizedConfig(config, dataLength);
309
312
 
310
313
  if (!optimizedConfig.enabled) {
@@ -331,7 +334,7 @@ export class AnimationManager {
331
334
  /**
332
335
  * 绑定动画事件
333
336
  */
334
- public on(eventType: AnimationEventType, handler: (event: any) => void): void {
337
+ public on(eventType: AnimationEventType, handler: AnimationEventHandler): void {
335
338
  if (!this.eventHandlers.has(eventType)) {
336
339
  this.eventHandlers.set(eventType, new Set());
337
340
  }
@@ -341,7 +344,7 @@ export class AnimationManager {
341
344
  /**
342
345
  * 解绑动画事件
343
346
  */
344
- public off(eventType: AnimationEventType, handler?: (event: any) => void): void {
347
+ public off(eventType: AnimationEventType, handler?: AnimationEventHandler): void {
345
348
  if (!handler) {
346
349
  this.eventHandlers.delete(eventType);
347
350
  return;
@@ -352,7 +355,7 @@ export class AnimationManager {
352
355
  /**
353
356
  * 触发动画事件
354
357
  */
355
- public emit(eventType: AnimationEventType, data: any): void {
358
+ public emit(eventType: AnimationEventType, data: AnimationEventData): void {
356
359
  const handlers = this.eventHandlers.get(eventType);
357
360
  if (handlers) {
358
361
  handlers.forEach((handler) => {
@@ -427,7 +430,7 @@ export function getAnimationPreset(name: string): AnimationPreset | undefined {
427
430
  export function generateEChartsAnimationConfig(
428
431
  config: Partial<AnimationConfig> = {},
429
432
  dataLength: number = 0
430
- ): any {
433
+ ): EChartsAnimationConfigResult {
431
434
  const manager = AnimationManager.getInstance();
432
435
  return manager.generateEChartsAnimationConfig(config, dataLength);
433
436
  }
@@ -174,6 +174,36 @@ export interface AnimationTemplate {
174
174
  scenarios?: string[];
175
175
  }
176
176
 
177
+ /**
178
+ * ECharts 动画配置返回类型
179
+ */
180
+ export interface EChartsAnimationConfigResult {
181
+ animation: boolean;
182
+ animationDuration?: number;
183
+ animationEasing?: string;
184
+ animationDelay?: number;
185
+ animationDurationUpdate?: number;
186
+ animationEasingUpdate?: string;
187
+ animationDelayUpdate?: number;
188
+ animationThreshold?: number;
189
+ progressive?: boolean;
190
+ progressiveThreshold?: number;
191
+ progressiveChunkMode?: string;
192
+ }
193
+
194
+ /**
195
+ * 动画事件数据
196
+ */
197
+ export interface AnimationEventData {
198
+ type: AnimationEventType;
199
+ animationType: AnimationType;
200
+ chartId?: string;
201
+ seriesIndex?: number;
202
+ dataIndex?: number;
203
+ timestamp: number;
204
+ duration: number;
205
+ }
206
+
177
207
  /**
178
208
  * 动画管理器配置
179
209
  */
@@ -32,7 +32,7 @@ export interface MarkAreaStyle {
32
32
  }
33
33
 
34
34
  /**
35
- * 标记线数据点
35
+ * 标记线数据点 — 支持常规坐标和 ECharts 统计类型
36
36
  */
37
37
  export interface MarkLineDataPoint {
38
38
  /** X轴值 */
@@ -41,6 +41,8 @@ export interface MarkLineDataPoint {
41
41
  yAxis?: string | number;
42
42
  /** 数据点名称 */
43
43
  name?: string;
44
+ /** ECharts 统计/特殊类型('average' | 'max' | 'min' | 'median') */
45
+ type?: string;
44
46
  }
45
47
 
46
48
  /**
@@ -97,7 +99,7 @@ export interface ScatterAnnotationConfig {
97
99
  data: Array<{
98
100
  coord: [number | string, number];
99
101
  value?: number;
100
- name?: string;
102
+ name: string;
101
103
  }>;
102
104
  /** 符号类型 */
103
105
  symbol?: string;
@@ -109,7 +111,7 @@ export interface ScatterAnnotationConfig {
109
111
  label?: {
110
112
  show?: boolean;
111
113
  position?: string;
112
- formatter?: string | ((value: any) => string);
114
+ formatter?: string | ((value: unknown) => string);
113
115
  color?: string;
114
116
  };
115
117
  }
@@ -223,14 +225,14 @@ export function convertAnnotationToScatter(
223
225
  itemStyle,
224
226
  label: {
225
227
  show: label?.show !== false,
226
- position: label?.position || 'top',
228
+ position: label?.position || ('top' as const),
227
229
  formatter: label?.formatter,
228
230
  color: label?.color || '#333',
229
231
  },
230
232
  data,
231
233
  },
232
- } as any,
233
- ];
234
+ },
235
+ ] as unknown as EChartsOption['series'];
234
236
  }
235
237
 
236
238
  /**
@@ -269,28 +271,28 @@ export function useAnnotation(props: AnnotationProps): EChartsOption {
269
271
  export const AnnotationPresets = {
270
272
  /** 平均线 */
271
273
  averageLine: (color = '#1890ff'): MarkLineConfig => ({
272
- data: [{ type: 'average', name: '平均值' }] as any,
274
+ data: [{ type: 'average', name: '平均值' }],
273
275
  lineStyle: { color, type: 'dashed', width: 2 },
274
276
  label: { show: true, position: 'end', color },
275
277
  }),
276
278
 
277
279
  /** 最大值线 */
278
280
  maxLine: (color = '#f5222d'): MarkLineConfig => ({
279
- data: [{ type: 'max', name: '最大值' }] as any,
281
+ data: [{ type: 'max', name: '最大值' }],
280
282
  lineStyle: { color, type: 'dashed', width: 2 },
281
283
  label: { show: true, position: 'end', color },
282
284
  }),
283
285
 
284
286
  /** 最小值线 */
285
287
  minLine: (color = '#52c41a'): MarkLineConfig => ({
286
- data: [{ type: 'min', name: '最小值' }] as any,
288
+ data: [{ type: 'min', name: '最小值' }],
287
289
  lineStyle: { color, type: 'dashed', width: 2 },
288
290
  label: { show: true, position: 'end', color },
289
291
  }),
290
292
 
291
293
  /** 警戒线 */
292
294
  thresholdLine: (value: number, color = '#faad14'): MarkLineConfig => ({
293
- data: [{ yAxis: value, name: '警戒线' }] as any,
295
+ data: [{ yAxis: value, name: '警戒线' }],
294
296
  lineStyle: { color, type: 'solid', width: 2 },
295
297
  label: { show: true, position: 'start', color },
296
298
  }),
@@ -16,6 +16,13 @@ import { PerformanceAnalyzer } from '../utils/performance';
16
16
  import { normalizeSize, calculateDataLength, filterDataByKeys } from '../utils/chartUtils';
17
17
  import BaseChartWrapper from '../../charts/common/BaseChartWrapper';
18
18
  import type { BaseChartProps } from '../../charts/types';
19
+ import type {
20
+ EChartsMouseEventParams,
21
+ EChartsDataZoomEventParams,
22
+ EChartsLegendEventParams,
23
+ EChartsTooltipEventParams,
24
+ } from '../types/common';
25
+ import type { ECElementEvent } from 'echarts';
19
26
 
20
27
  // ============================================================================
21
28
  // 接口定义
@@ -95,8 +102,8 @@ export interface ChartProps {
95
102
  enableZoom?: boolean;
96
103
  onZoom?: (data: { start: number; end: number; dataZoomIndex: number }) => void;
97
104
  enableDataFiltering?: boolean;
98
- filters?: Record<string, any>;
99
- onDataFiltered?: (filteredData: any[], filters: Record<string, any>) => void;
105
+ filters?: Record<string, string | number | boolean | string[] | null>;
106
+ onDataFiltered?: (filteredData: unknown[], filters: Record<string, unknown>) => void;
100
107
  enableLegendInteraction?: boolean;
101
108
  legendInteractionMode?: 'single' | 'multiple' | 'all';
102
109
  onLegendSelect?: (params: { name: string; selected: Record<string, boolean> }) => void;
@@ -104,10 +111,10 @@ export interface ChartProps {
104
111
  onLegendSelectAll?: (params: { selected: Record<string, boolean> }) => void;
105
112
  onLegendInverseSelect?: (params: { selected: Record<string, boolean> }) => void;
106
113
  enableCustomTooltip?: boolean;
107
- customTooltipContent?: (params: any) => React.ReactNode;
114
+ customTooltipContent?: (params: EChartsMouseEventParams | EChartsMouseEventParams[]) => React.ReactNode;
108
115
  customTooltipStyle?: React.CSSProperties;
109
- onTooltipShow?: (params: any) => void;
110
- onTooltipHide?: (params: any) => void;
116
+ onTooltipShow?: (params: EChartsTooltipEventParams) => void;
117
+ onTooltipHide?: (params: EChartsTooltipEventParams) => void;
111
118
  onExport?: (dataURL: string, options: ChartExportOptions) => void;
112
119
  linkageConfig?: ChartLinkageConfig;
113
120
  onDataUpdate?: (
@@ -197,15 +204,16 @@ const BaseChart: React.FC<ChartProps> = (props) => {
197
204
  // Wrapper option that applies virtual scroll + data filtering
198
205
  const wrappedOption = useMemo(() => {
199
206
  if (!option) return undefined;
200
- let processed = { ...option };
207
+ let processed: Record<string, unknown> = { ...option };
201
208
 
202
209
  // Apply data filtering
203
210
  if (enableDataFiltering && filters && Object.keys(filters).length > 0) {
204
- processed = JSON.parse(JSON.stringify(processed));
211
+ processed = JSON.parse(JSON.stringify(processed)) as typeof processed;
205
212
  if (processed.series && Array.isArray(processed.series)) {
206
- processed.series = processed.series.map((s: any) => {
207
- if (s.data && Array.isArray(s.data)) {
208
- const filtered = filterDataByKeys(s.data, filters);
213
+ processed.series = (processed.series as unknown[]).map((s: unknown) => {
214
+ const seriesItem = s as { data?: unknown[]; [key: string]: unknown };
215
+ if (seriesItem.data && Array.isArray(seriesItem.data)) {
216
+ const filtered = filterDataByKeys(seriesItem.data, filters);
209
217
  if (onDataFiltered) onDataFiltered(filtered, filters);
210
218
  if (virtualScroll) {
211
219
  virtualScrollRef.current.totalDataCount = filtered.length;
@@ -217,11 +225,11 @@ const BaseChart: React.FC<ChartProps> = (props) => {
217
225
  start + virtualScrollPageSize + virtualScrollPreloadSize,
218
226
  filtered.length
219
227
  );
220
- return { ...s, data: filtered.slice(start, end) };
228
+ return { ...seriesItem, data: filtered.slice(start, end) };
221
229
  }
222
- return { ...s, data: filtered };
230
+ return { ...seriesItem, data: filtered };
223
231
  }
224
- return s;
232
+ return seriesItem;
225
233
  });
226
234
  }
227
235
  }
@@ -285,7 +293,7 @@ const BaseChart: React.FC<ChartProps> = (props) => {
285
293
  if (instance) {
286
294
  // Click linkage
287
295
  if (linkageConfig.enableClickLinkage && chartId && linkageConfig.linkedChartIds) {
288
- instance.on('click', (params: any) => {
296
+ instance.on('click', (params: ECElementEvent) => {
289
297
  linkageConfig.linkedChartIds!.forEach((lid) => {
290
298
  const linked = getChart(lid);
291
299
  if (linked) linked.dispatchAction({ type: 'highlight', name: params.name });
@@ -294,17 +302,18 @@ const BaseChart: React.FC<ChartProps> = (props) => {
294
302
  }
295
303
 
296
304
  // Zoom + zoom linkage + virtual scroll page update
297
- instance.on('datazoom', (params: any) => {
305
+ instance.on('datazoom', (params: unknown) => {
306
+ const p = params as { start?: number; end?: number; dataZoomIndex?: number; batch?: Array<{ start?: number; end?: number; dataZoomIndex?: number }> };
298
307
  if (onZoom)
299
308
  onZoom({
300
- start: params.start || 0,
301
- end: params.end || 100,
302
- dataZoomIndex: params.dataZoomIndex || 0,
309
+ start: p.start || 0,
310
+ end: p.end || 100,
311
+ dataZoomIndex: p.dataZoomIndex || 0,
303
312
  });
304
313
  if (virtualScroll && !virtualScrollRef.current.isScrolling) {
305
314
  virtualScrollRef.current.isScrolling = true;
306
315
  const newPage = Math.floor(
307
- ((params.start || 0) / 100) * virtualScrollRef.current.totalPages
316
+ ((p.start || 0) / 100) * virtualScrollRef.current.totalPages
308
317
  );
309
318
  if (newPage !== virtualScrollRef.current.currentPage) {
310
319
  virtualScrollRef.current.currentPage = newPage;
@@ -320,9 +329,9 @@ const BaseChart: React.FC<ChartProps> = (props) => {
320
329
  if (linked)
321
330
  linked.dispatchAction({
322
331
  type: 'dataZoom',
323
- start: params.start,
324
- end: params.end,
325
- dataZoomIndex: params.dataZoomIndex,
332
+ start: p.start,
333
+ end: p.end,
334
+ dataZoomIndex: p.dataZoomIndex,
326
335
  });
327
336
  });
328
337
  }
@@ -330,8 +339,9 @@ const BaseChart: React.FC<ChartProps> = (props) => {
330
339
 
331
340
  // Legend interaction
332
341
  if (enableLegendInteraction) {
333
- instance.on('legendselectchanged', (params: any) => {
334
- const { name, selected } = params;
342
+ instance.on('legendselectchanged', (params: unknown) => {
343
+ const p = params as { name?: string; selected: Record<string, boolean> };
344
+ const { name, selected } = p;
335
345
  if (linkageConfig.enableLegendLinkage && chartId && linkageConfig.linkedChartIds) {
336
346
  linkageConfig.linkedChartIds!.forEach((lid) => {
337
347
  const linked = getChart(lid);
@@ -344,21 +354,21 @@ const BaseChart: React.FC<ChartProps> = (props) => {
344
354
  newSelected[k] = k === name;
345
355
  });
346
356
  instance.setOption({ legend: { selected: newSelected } });
347
- onLegendSelect?.({ name, selected: newSelected });
357
+ if (name !== undefined) onLegendSelect?.({ name, selected: newSelected });
348
358
  } else {
349
- if (selected[name]) onLegendSelect?.({ name, selected });
350
- else onLegendUnselect?.({ name, selected });
359
+ if (name !== undefined && selected[name]) onLegendSelect?.({ name, selected });
360
+ else if (name !== undefined) onLegendUnselect?.({ name, selected });
351
361
  }
352
362
  });
353
363
  }
354
364
 
355
365
  // Custom tooltip
356
366
  if (enableCustomTooltip && customTooltipContent) {
357
- instance.on('tooltipshow', (params: any) => onTooltipShow?.(params));
358
- instance.on('tooltiphide', (params: any) => onTooltipHide?.(params));
367
+ instance.on('tooltipshow', (params: unknown) => onTooltipShow?.(params as EChartsTooltipEventParams));
368
+ instance.on('tooltiphide', (params: unknown) => onTooltipHide?.(params as EChartsTooltipEventParams));
359
369
  instance.setOption({
360
370
  tooltip: {
361
- formatter: (params: any) => String(customTooltipContent(params)),
371
+ formatter: (params: unknown) => String(customTooltipContent(params as EChartsMouseEventParams)),
362
372
  ...(customTooltipStyle && {
363
373
  backgroundColor: 'transparent',
364
374
  borderColor: 'transparent',
@@ -485,7 +495,7 @@ const BaseChart: React.FC<ChartProps> = (props) => {
485
495
  };
486
496
 
487
497
  const wrapperProps: BaseChartProps & { chartType: string } = {
488
- option: wrappedOption as any,
498
+ option: wrappedOption as unknown as Record<string, unknown>,
489
499
  width,
490
500
  height,
491
501
  theme: typeof theme === 'string' ? theme : (theme as Record<string, unknown>),
@@ -65,25 +65,30 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
65
65
  return fallback(error, this.handleReset);
66
66
  }
67
67
 
68
- // 默认错误展示
68
+ // 默认错误展示(使用 CSS 变量,与 ThemeManager 对齐)
69
69
  return (
70
70
  <div
71
+ role="alert"
72
+ aria-live="assertive"
71
73
  style={{
72
74
  display: 'flex',
73
75
  flexDirection: 'column',
74
76
  alignItems: 'center',
75
77
  justifyContent: 'center',
76
- padding: '20px',
77
- backgroundColor: '#fff',
78
- border: '1px solid #ff4d4f',
79
- borderRadius: '8px',
80
- color: '#333',
78
+ padding: 'var(--tv-border-radius, 16px)',
79
+ backgroundColor: 'var(--tv-bg-color, #fff)',
80
+ border: '1px solid var(--tv-error-color, #ff4d4f)',
81
+ borderRadius: 'var(--tv-border-radius, 8px)',
82
+ color: 'var(--tv-text-color, #333)',
81
83
  minHeight: '200px',
84
+ fontFamily: 'var(--tv-font-family, sans-serif)',
82
85
  }}
83
86
  >
84
- <div style={{ fontSize: '48px', marginBottom: '16px' }}>⚠️</div>
85
- <h3 style={{ margin: '0 0 12px', color: '#ff4d4f' }}>图表渲染失败</h3>
86
- <p style={{ margin: '0 0 16px', color: '#666', textAlign: 'center' }}>
87
+ <div style={{ fontSize: '48px', marginBottom: '16px' }} aria-hidden="true">⚠️</div>
88
+ <h3 style={{ margin: '0 0 12px', color: 'var(--tv-error-color, #ff4d4f)', fontWeight: 700 }}>
89
+ 图表渲染失败
90
+ </h3>
91
+ <p style={{ margin: '0 0 16px', color: 'var(--tv-text-color-secondary, #666)', textAlign: 'center', maxWidth: '320px' }}>
87
92
  图表在渲染过程中遇到错误,请检查数据配置是否正确
88
93
  </p>
89
94
  {showDetails && (
@@ -91,15 +96,15 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
91
96
  style={{
92
97
  width: '100%',
93
98
  padding: '12px',
94
- backgroundColor: '#f5f5f5',
95
- borderRadius: '4px',
96
- fontSize: '12px',
97
- fontFamily: 'monospace',
99
+ backgroundColor: 'var(--tv-bg-color-secondary, #f5f5f5)',
100
+ borderRadius: 'var(--tv-border-radius-small, 4px)',
101
+ fontSize: 'var(--tv-font-size-small, 12px)',
102
+ fontFamily: 'var(--tv-font-family, monospace)',
98
103
  overflow: 'auto',
99
104
  maxHeight: '150px',
100
105
  }}
101
106
  >
102
- <summary style={{ cursor: 'pointer', marginBottom: '8px' }}>错误详情</summary>
107
+ <summary style={{ cursor: 'pointer', marginBottom: '8px', fontWeight: 600 }}>错误详情</summary>
103
108
  <pre style={{ margin: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-all' }}>
104
109
  {error.message}
105
110
  {'\n\n'}
@@ -112,12 +117,20 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
112
117
  style={{
113
118
  marginTop: '16px',
114
119
  padding: '8px 24px',
115
- backgroundColor: '#1890ff',
120
+ backgroundColor: 'var(--tv-primary-color, #1890ff)',
116
121
  color: '#fff',
117
122
  border: 'none',
118
- borderRadius: '4px',
123
+ borderRadius: 'var(--tv-border-radius-small, 4px)',
119
124
  cursor: 'pointer',
120
- fontSize: '14px',
125
+ fontSize: 'var(--tv-font-size, 14px)',
126
+ fontWeight: 600,
127
+ transition: 'background-color var(--tv-transition-duration, 0.3s)',
128
+ }}
129
+ onMouseEnter={(e) => {
130
+ e.currentTarget.style.backgroundColor = 'var(--tv-primary-color-hover, #40a9ff)';
131
+ }}
132
+ onMouseLeave={(e) => {
133
+ e.currentTarget.style.backgroundColor = 'var(--tv-primary-color, #1890ff)';
121
134
  }}
122
135
  >
123
136
  重试