@agions/taroviz 1.11.1 → 2.0.3

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.
Files changed (164) hide show
  1. package/CHANGELOG.md +245 -0
  2. package/README.md +104 -302
  3. package/dist/cjs/index.js +1 -1
  4. package/dist/cjs/vendors.js +1 -0
  5. package/dist/cjs/vendors~echarts.js +1 -0
  6. package/dist/esm/index.js +1 -58151
  7. package/dist/esm/vendors.js +1 -0
  8. package/dist/esm/vendors~echarts.js +1 -0
  9. package/package.json +19 -25
  10. package/src/adapters/MiniAppAdapter.ts +136 -0
  11. package/src/adapters/__tests__/index.test.ts +1 -1
  12. package/src/adapters/h5/__tests__/index.test.ts +4 -2
  13. package/src/adapters/h5/index.ts +63 -64
  14. package/src/adapters/harmony/index.ts +23 -245
  15. package/src/adapters/index.ts +49 -45
  16. package/src/adapters/swan/index.ts +6 -69
  17. package/src/adapters/tt/index.ts +7 -70
  18. package/src/adapters/types.ts +25 -58
  19. package/src/adapters/weapp/index.ts +6 -69
  20. package/src/charts/__tests__/testUtils.tsx +87 -0
  21. package/src/charts/boxplot/__tests__/index.test.tsx +49 -103
  22. package/src/charts/boxplot/index.tsx +2 -1
  23. package/src/charts/boxplot/types.ts +17 -16
  24. package/src/charts/common/BaseChartWrapper.tsx +90 -82
  25. package/src/charts/common/__mocks__/BaseChartWrapper.tsx +17 -0
  26. package/src/charts/createChartComponent.tsx +36 -0
  27. package/src/charts/createOptionChartComponent.tsx +32 -0
  28. package/src/charts/funnel/__tests__/index.test.tsx +99 -0
  29. package/src/charts/funnel/index.tsx +60 -10
  30. package/src/charts/funnel/types.ts +6 -0
  31. package/src/charts/graph/__tests__/index.test.tsx +102 -33
  32. package/src/charts/graph/index.tsx +66 -9
  33. package/src/charts/graph/types.ts +6 -0
  34. package/src/charts/heatmap/__tests__/index.test.tsx +139 -0
  35. package/src/charts/heatmap/index.tsx +103 -10
  36. package/src/charts/heatmap/types.ts +6 -0
  37. package/src/charts/index.ts +74 -26
  38. package/src/charts/liquid/__tests__/index.test.tsx +52 -0
  39. package/src/charts/liquid/index.tsx +239 -182
  40. package/src/charts/liquid/types.ts +11 -11
  41. package/src/charts/parallel/__tests__/index.test.tsx +40 -67
  42. package/src/charts/parallel/index.tsx +2 -1
  43. package/src/charts/parallel/types.ts +19 -18
  44. package/src/charts/radar/__tests__/index.test.tsx +210 -0
  45. package/src/charts/radar/index.tsx +143 -10
  46. package/src/charts/radar/types.ts +13 -0
  47. package/src/charts/sankey/__tests__/index.test.tsx +124 -0
  48. package/src/charts/sankey/index.tsx +62 -10
  49. package/src/charts/sankey/types.ts +6 -0
  50. package/src/charts/tree/__tests__/index.test.tsx +71 -0
  51. package/src/charts/tree/index.tsx +5 -2
  52. package/src/charts/tree/types.ts +9 -9
  53. package/src/charts/types.ts +208 -106
  54. package/src/charts/utils.ts +9 -7
  55. package/src/charts/wordcloud/__tests__/index.test.tsx +98 -31
  56. package/src/charts/wordcloud/index.tsx +75 -9
  57. package/src/charts/wordcloud/types.ts +6 -0
  58. package/src/components/DataFilter/index.tsx +32 -10
  59. package/src/core/animation/types.ts +6 -6
  60. package/src/core/components/Annotation.tsx +6 -7
  61. package/src/core/components/BaseChart.tsx +110 -168
  62. package/src/core/components/ErrorBoundary.tsx +17 -4
  63. package/src/core/components/LazyChart.tsx +54 -55
  64. package/src/core/components/hooks/index.ts +6 -2
  65. package/src/core/components/hooks/useChartInit.ts +6 -3
  66. package/src/core/components/hooks/usePerformance.ts +8 -2
  67. package/src/core/components/hooks/useVirtualScroll.ts +2 -1
  68. package/src/core/index.ts +1 -1
  69. package/src/core/themes/ThemeManager.ts +1 -1
  70. package/src/core/types/common.ts +2 -1
  71. package/src/core/types/index.ts +0 -12
  72. package/src/core/types/platform.ts +3 -5
  73. package/src/core/utils/__tests__/deepClone.test.ts +317 -0
  74. package/src/core/utils/__tests__/index.test.ts +2 -1
  75. package/src/core/utils/chartInstances.ts +13 -0
  76. package/src/core/utils/common.ts +20 -29
  77. package/src/core/utils/deepClone.ts +114 -0
  78. package/src/core/utils/download.ts +128 -0
  79. package/src/core/utils/drillDown.ts +34 -353
  80. package/src/core/utils/drillDownHelpers.ts +426 -0
  81. package/src/core/utils/events.ts +12 -0
  82. package/src/core/utils/export/ExportUtils.ts +36 -67
  83. package/src/core/utils/format.ts +44 -0
  84. package/src/core/utils/index.ts +21 -154
  85. package/src/core/utils/merge.ts +25 -0
  86. package/src/core/utils/performance/PerformanceAnalyzer.ts +38 -21
  87. package/src/core/utils/performance/hooks.ts +7 -0
  88. package/src/core/utils/performance/index.ts +2 -0
  89. package/src/{hooks → core/utils/performance}/useAnimation.ts +45 -41
  90. package/src/core/utils/performance/useDataZoom.ts +324 -0
  91. package/src/{hooks → core/utils/performance}/usePerformance.ts +49 -41
  92. package/src/core/utils/performance/usePerformanceHooks.ts +278 -0
  93. package/src/core/utils/performanceUtils.ts +310 -0
  94. package/src/core/utils/runtime.ts +190 -0
  95. package/src/core/utils/setOptionUtils.ts +59 -0
  96. package/src/core/version.ts +14 -0
  97. package/src/editor/EnhancedThemeEditor.tsx +362 -540
  98. package/src/editor/ThemeEditor.tsx +55 -321
  99. package/src/editor/components/ThemeBasicSettings.tsx +113 -0
  100. package/src/editor/components/ThemeColorEditor.tsx +105 -0
  101. package/src/editor/components/ThemeSelector.tsx +70 -0
  102. package/src/editor/hooks/useThemeEditorState.ts +201 -0
  103. package/src/editor/index.ts +10 -2
  104. package/src/hooks/__tests__/index.test.tsx +3 -1
  105. package/src/hooks/chartConnectHelpers.ts +341 -0
  106. package/src/hooks/index.ts +55 -660
  107. package/src/hooks/types.ts +189 -0
  108. package/src/hooks/useChartAutoResize.ts +73 -0
  109. package/src/hooks/useChartConnect.ts +92 -238
  110. package/src/hooks/useChartDownload.ts +25 -27
  111. package/src/hooks/useChartHistory.ts +34 -49
  112. package/src/hooks/useChartInit.ts +59 -0
  113. package/src/hooks/useChartOptions.ts +259 -0
  114. package/src/hooks/useChartPerformance.ts +109 -0
  115. package/src/hooks/useChartSelection.ts +52 -49
  116. package/src/hooks/useChartTheme.ts +51 -0
  117. package/src/hooks/useDataTransform.ts +19 -4
  118. package/src/hooks/utils/chartDownloadUtils.ts +40 -53
  119. package/src/hooks/utils/dataTransformUtils.ts +22 -0
  120. package/src/index.ts +48 -34
  121. package/src/main.tsx +4 -9
  122. package/src/react-dom.d.ts +3 -3
  123. package/src/themes/index.ts +30 -855
  124. package/src/themes/palettes/blue-green.ts +13 -0
  125. package/src/themes/palettes/chalk.ts +13 -0
  126. package/src/themes/palettes/cyber.ts +44 -0
  127. package/src/themes/palettes/dark.ts +52 -0
  128. package/src/themes/palettes/default.ts +52 -0
  129. package/src/themes/palettes/elegant.ts +34 -0
  130. package/src/themes/palettes/forest.ts +13 -0
  131. package/src/themes/palettes/glass.ts +49 -0
  132. package/src/themes/palettes/golden.ts +13 -0
  133. package/src/themes/palettes/neon.ts +43 -0
  134. package/src/themes/palettes/ocean.ts +39 -0
  135. package/src/themes/palettes/pastel.ts +37 -0
  136. package/src/themes/palettes/purple-passion.ts +13 -0
  137. package/src/themes/palettes/retro.ts +33 -0
  138. package/src/themes/palettes/sunset.ts +40 -0
  139. package/src/themes/palettes/walden.ts +13 -0
  140. package/src/themes/registry.ts +184 -0
  141. package/src/themes/types.ts +213 -0
  142. package/src/charts/bar/__tests__/index.test.tsx +0 -113
  143. package/src/charts/bar/index.tsx +0 -14
  144. package/src/charts/candlestick/__tests__/index.test.tsx +0 -40
  145. package/src/charts/candlestick/index.tsx +0 -13
  146. package/src/charts/gauge/index.tsx +0 -14
  147. package/src/charts/line/__tests__/index.test.tsx +0 -107
  148. package/src/charts/line/index.tsx +0 -15
  149. package/src/charts/pie/__tests__/index.test.tsx +0 -112
  150. package/src/charts/pie/index.tsx +0 -14
  151. package/src/charts/scatter/index.tsx +0 -14
  152. package/src/charts/sunburst/index.tsx +0 -18
  153. package/src/charts/treemap/index.tsx +0 -18
  154. package/src/core/utils/codeGenerator/CodeGenerator.ts +0 -669
  155. package/src/core/utils/codeGenerator/index.ts +0 -13
  156. package/src/core/utils/codeGenerator/types.ts +0 -198
  157. package/src/core/utils/configGenerator/ConfigGenerator.ts +0 -583
  158. package/src/core/utils/configGenerator/index.ts +0 -13
  159. package/src/core/utils/configGenerator/types.ts +0 -445
  160. package/src/core/utils/debug/DebugPanel.tsx +0 -637
  161. package/src/core/utils/debug/debugger.ts +0 -322
  162. package/src/core/utils/debug/index.ts +0 -21
  163. package/src/core/utils/debug/types.ts +0 -142
  164. package/src/hooks/useDataZoom.ts +0 -323
@@ -1,263 +1,238 @@
1
1
  /**
2
- * TaroViz 增强版主题编辑器组件
3
- * 提供实时预览、导入导出、预设主题等高级功能
2
+ * 增强型主题编辑器组件
3
+ * 提供更完善的主题编辑功能,包含预设主题、导入导出、实时预览等
4
4
  */
5
- import React, { useState, useEffect, useCallback } from 'react';
6
- import { ThemeOptions, getRegisteredThemes, registerTheme, switchTheme } from '../themes';
7
- import { LineChart } from '../charts';
5
+ import React, { useState, useCallback } from 'react';
8
6
 
7
+ import type { ThemeOptions } from '../themes';
8
+ import { useThemeEditorState } from './hooks/useThemeEditorState';
9
+ import ThemeSelector from './components/ThemeSelector';
10
+ import ThemeColorEditor from './components/ThemeColorEditor';
11
+ import ThemeBasicSettings from './components/ThemeBasicSettings';
12
+
13
+ // ============================================================================
14
+ // 类型定义
15
+ // ============================================================================
16
+
17
+ /**
18
+ * 主题导出选项
19
+ */
20
+ export interface ThemeExportOptions {
21
+ format: 'json' | 'css' | 'scss';
22
+ includeVariables: boolean;
23
+ minify: boolean;
24
+ }
25
+
26
+ /**
27
+ * 增强型主题编辑器属性
28
+ */
9
29
  export interface EnhancedThemeEditorProps {
10
30
  /** 当前选中的主题名称 */
11
31
  selectedTheme?: string;
12
- /** 主题变更回调 */
32
+ /** 主题变更回调函数 */
13
33
  onThemeChange?: (theme: ThemeOptions) => void;
14
- /** 主题保存回调 */
34
+ /** 主题保存回调函数 */
15
35
  onThemeSave?: (theme: ThemeOptions) => void;
36
+ /** 主题导入回调函数 */
37
+ onThemeImport?: (theme: ThemeOptions) => void;
38
+ /** 主题导出回调函数 */
39
+ onThemeExport?: (theme: ThemeOptions) => string;
16
40
  /** 是否禁用编辑器 */
17
41
  disabled?: boolean;
18
- /** 是否启用实时预览 */
19
- enableLivePreview?: boolean;
20
- /** 是否启用导入导出 */
21
- enableImportExport?: boolean;
22
- /** 是否启用预设主题 */
23
- enablePresets?: boolean;
24
42
  /** 编辑器样式 */
25
43
  style?: React.CSSProperties;
26
44
  /** 编辑器类名 */
27
45
  className?: string;
46
+ /** 是否显示实时预览 */
47
+ showPreview?: boolean;
48
+ /** 是否启用自动保存 */
49
+ autoSave?: boolean;
50
+ /** 自动保存延迟(毫秒) */
51
+ autoSaveDelay?: number;
28
52
  }
29
53
 
30
- export interface ThemeExportOptions {
31
- format: 'json' | 'css' | 'scss';
32
- includeVariables: boolean;
33
- minify: boolean;
34
- }
54
+ /** 编辑器标签页 */
55
+ type EditorTab = 'colors' | 'layout' | 'typography' | 'preview';
35
56
 
36
- const PRESET_THEMES: ThemeOptions[] = [
37
- {
38
- name: '预设-科技蓝',
39
- colors: ['#0050b3', '#1890ff', '#40a9ff', '#69c0ff', '#bae7ff'],
40
- backgroundColor: '#001529',
41
- textColor: '#ffffff',
42
- darkMode: true,
43
- },
44
- {
45
- name: '预设-活力橙',
46
- colors: ['#d46b08', '#ff7a45', '#ffa940', '#ffc53d', '#ffe58f'],
47
- backgroundColor: '#f5f5f5',
48
- textColor: '#333333',
49
- darkMode: false,
50
- },
51
- {
52
- name: '预设-森林绿',
53
- colors: ['#237804', '#52c41a', '#73d13d', '#95de64', '#b7eb8f'],
54
- backgroundColor: '#f6ffed',
55
- textColor: '#333333',
56
- darkMode: false,
57
- },
58
- {
59
- name: '预设-神秘紫',
60
- colors: ['#531dab', '#722ed1', '#9254de', '#b37feb', '#d3adf7'],
61
- backgroundColor: '#f9f0ff',
62
- textColor: '#333333',
63
- darkMode: false,
64
- },
65
- {
66
- name: '预设-商务灰',
67
- colors: ['#434343', '#595959', '#8c8c8c', '#bfbfbf', '#e8e8e8'],
68
- backgroundColor: '#fafafa',
69
- textColor: '#333333',
70
- darkMode: false,
71
- },
72
- ];
57
+ // ============================================================================
58
+ // 组件实现
59
+ // ============================================================================
73
60
 
61
+ /**
62
+ * 增强型主题编辑器组件
63
+ */
74
64
  const EnhancedThemeEditor: React.FC<EnhancedThemeEditorProps> = ({
75
65
  selectedTheme,
76
66
  onThemeChange,
77
67
  onThemeSave,
68
+ onThemeImport,
69
+ onThemeExport,
78
70
  disabled = false,
79
- enableLivePreview = true,
80
- enableImportExport = true,
81
- enablePresets = true,
82
71
  style = {},
83
72
  className = '',
73
+ showPreview = true,
74
+ autoSave = false,
75
+ autoSaveDelay = 1000,
84
76
  }) => {
85
- const registeredThemes = getRegisteredThemes();
86
-
87
- // 状态
88
- const [currentTheme, setCurrentTheme] = useState<ThemeOptions>(
89
- registeredThemes.find(t => t.name === selectedTheme) || registeredThemes[0] || {}
90
- );
91
- const [newThemeName, setNewThemeName] = useState<string>('');
92
- const [isEditing, setIsEditing] = useState<boolean>(false);
93
- const [colors, setColors] = useState<string[]>(currentTheme.colors || []);
94
- const [backgroundColor, setBackgroundColor] = useState<string>(currentTheme.backgroundColor || '#ffffff');
95
- const [textColor, setTextColor] = useState<string>(currentTheme.textColor || '#333333');
96
- const [darkMode, setDarkMode] = useState<boolean>(currentTheme.darkMode || false);
97
- const [activeTab, setActiveTab] = useState<'basic' | 'advanced' | 'presets'>('basic');
98
-
99
- // 预览数据
100
- const previewOption = {
101
- title: { text: '主题预览', textStyle: { color: textColor } },
102
- xAxis: { type: 'category' as const, data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], axisLabel: { color: textColor } },
103
- yAxis: { type: 'value' as const, axisLabel: { color: textColor } },
104
- series: [
105
- { data: [120, 200, 150, 80, 70], type: 'bar' as const, itemStyle: { color: colors[0] || '#1890ff' } },
106
- { data: [120, 200, 150, 80, 70].map((v) => ({ value: v * 1.2, itemStyle: { color: colors[1] || '#40a9ff' } })), type: 'bar' as const },
107
- ],
108
- backgroundColor: backgroundColor,
109
- };
77
+ const {
78
+ currentTheme,
79
+ registeredThemes,
80
+ colors,
81
+ backgroundColor,
82
+ textColor,
83
+ darkMode,
84
+ isEditing,
85
+ newThemeName,
86
+ setCurrentTheme,
87
+ updateTheme,
88
+ handleColorChange,
89
+ handleAddColor,
90
+ handleRemoveColor,
91
+ handleStartEdit,
92
+ setNewThemeName,
93
+ saveTheme,
94
+ cancelEdit,
95
+ } = useThemeEditorState({ selectedTheme, onThemeChange });
96
+
97
+ // 当前标签页
98
+ const [activeTab, setActiveTab] = useState<EditorTab>('colors');
99
+ // 导出数据
100
+ const [exportData, setExportData] = useState<string>('');
101
+ // 导入数据
102
+ const [importData, setImportData] = useState<string>('');
103
+ // 导入错误
104
+ const [importError, setImportError] = useState<string>('');
105
+
106
+ // 处理保存主题
107
+ const handleSaveTheme = useCallback(() => {
108
+ const themeToSave = saveTheme();
109
+ onThemeSave?.(themeToSave);
110
+ }, [saveTheme, onThemeSave]);
110
111
 
111
- // 同步主题状态
112
- useEffect(() => {
113
- if (selectedTheme) {
114
- const theme = registeredThemes.find(t => t.name === selectedTheme);
115
- if (theme) {
116
- setCurrentTheme(theme);
117
- setColors(theme.colors || []);
118
- setBackgroundColor(theme.backgroundColor || '#ffffff');
119
- setTextColor(theme.textColor || '#333333');
120
- setDarkMode(theme.darkMode || false);
112
+ // 导出主题
113
+ const handleExport = useCallback(() => {
114
+ try {
115
+ if (onThemeExport) {
116
+ const data = onThemeExport(currentTheme);
117
+ setExportData(data);
118
+ } else {
119
+ setExportData(JSON.stringify(currentTheme, null, 2));
121
120
  }
121
+ } catch {
122
+ setExportData('');
122
123
  }
123
- }, [selectedTheme, registeredThemes]);
124
-
125
- // 更新主题
126
- const updateTheme = useCallback((updates: Partial<ThemeOptions>) => {
127
- const updated = { ...currentTheme, ...updates };
128
- setCurrentTheme(updated);
129
- setColors(updated.colors || []);
130
- setBackgroundColor(updated.backgroundColor || '#ffffff');
131
- setTextColor(updated.textColor || '#333333');
132
- setDarkMode(updated.darkMode || false);
133
-
134
- if (enableLivePreview) {
135
- switchTheme(updated);
136
- }
137
- onThemeChange?.(updated);
138
- }, [currentTheme, enableLivePreview, onThemeChange]);
139
-
140
- // 处理颜色变化
141
- const handleColorChange = (index: number, color: string) => {
142
- const newColors = [...colors];
143
- newColors[index] = color;
144
- updateTheme({ colors: newColors });
145
- };
146
-
147
- // 添加颜色
148
- const handleAddColor = () => {
149
- const newColors = [...colors, '#000000'];
150
- updateTheme({ colors: newColors });
151
- };
152
-
153
- // 删除颜色
154
- const handleRemoveColor = (index: number) => {
155
- if (colors.length <= 1) return;
156
- const newColors = colors.filter((_, i) => i !== index);
157
- updateTheme({ colors: newColors });
158
- };
159
-
160
- // 选择预设主题
161
- const handlePresetSelect = (preset: ThemeOptions) => {
162
- updateTheme({
163
- ...preset,
164
- name: isEditing && newThemeName ? newThemeName : preset.name,
165
- });
166
- if (!isEditing) {
167
- switchTheme(preset);
168
- onThemeChange?.(preset);
124
+ }, [currentTheme, onThemeExport]);
125
+
126
+ // 复制到剪贴板
127
+ const handleCopyExport = useCallback(async () => {
128
+ if (!exportData) return;
129
+ try {
130
+ await navigator.clipboard.writeText(exportData);
131
+ } catch {
132
+ // 复制失败时静默处理
169
133
  }
170
- };
134
+ }, [exportData]);
171
135
 
172
- // 保存主题
173
- const handleSaveTheme = () => {
174
- const name = isEditing && newThemeName ? newThemeName : currentTheme.name || 'custom';
175
- const themeToSave: ThemeOptions = {
176
- ...currentTheme,
177
- name,
178
- colors,
179
- backgroundColor,
180
- textColor,
181
- darkMode,
182
- };
183
-
184
- registerTheme(name, themeToSave);
185
- setIsEditing(false);
186
- setNewThemeName('');
187
-
188
- if (onThemeSave) {
189
- onThemeSave(themeToSave);
136
+ // 导入主题
137
+ const handleImport = useCallback(() => {
138
+ setImportError('');
139
+ if (!importData.trim()) {
140
+ setImportError('请输入主题数据');
141
+ return;
190
142
  }
191
- };
192
-
193
- // 导出主题
194
- const handleExportTheme = (format: 'json' | 'css' | 'scss') => {
195
- const theme: ThemeOptions = {
196
- ...currentTheme,
197
- colors,
198
- backgroundColor,
199
- textColor,
200
- darkMode,
201
- };
202
-
203
- let content = '';
204
- let filename = '';
205
- let mimeType = '';
206
-
207
- if (format === 'json') {
208
- content = JSON.stringify(theme, null, 2);
209
- filename = `${theme.name || 'theme'}.json`;
210
- mimeType = 'application/json';
211
- } else if (format === 'css') {
212
- content = `:root {\n --theme-colors: ${colors.join(', ')};\n --theme-bg: ${backgroundColor};\n --theme-text: ${textColor};\n}`;
213
- filename = `${theme.name || 'theme'}.css`;
214
- mimeType = 'text/css';
215
- } else {
216
- content = `$theme-colors: (${colors.join(', ')});\n$theme-bg: ${backgroundColor};\n$theme-text: ${textColor};`;
217
- filename = `${theme.name || 'theme'}.scss`;
218
- mimeType = 'text/x-scss';
143
+ try {
144
+ const theme = JSON.parse(importData) as ThemeOptions;
145
+ if (!theme.name || !Array.isArray(theme.colors)) {
146
+ setImportError('无效的主题格式');
147
+ return;
148
+ }
149
+ updateTheme(theme);
150
+ onThemeImport?.(theme);
151
+ setImportData('');
152
+ } catch {
153
+ setImportError('JSON 解析失败');
219
154
  }
155
+ }, [importData, updateTheme, onThemeImport]);
156
+
157
+ // 标签页内容
158
+ const renderTabContent = () => {
159
+ switch (activeTab) {
160
+ case 'colors':
161
+ return (
162
+ <>
163
+ <ThemeColorEditor
164
+ colors={colors}
165
+ disabled={disabled}
166
+ onColorChange={handleColorChange}
167
+ onAddColor={handleAddColor}
168
+ onRemoveColor={handleRemoveColor}
169
+ />
170
+ <ThemeBasicSettings
171
+ backgroundColor={backgroundColor}
172
+ textColor={textColor}
173
+ darkMode={darkMode}
174
+ disabled={disabled}
175
+ onBackgroundColorChange={(color) => updateTheme({ backgroundColor: color })}
176
+ onTextColorChange={(color) => updateTheme({ textColor: color })}
177
+ onDarkModeChange={(mode) => updateTheme({ darkMode: mode })}
178
+ />
179
+ </>
180
+ );
220
181
 
221
- const blob = new Blob([content], { type: mimeType });
222
- const url = URL.createObjectURL(blob);
223
- const a = document.createElement('a');
224
- a.href = url;
225
- a.download = filename;
226
- a.click();
227
- URL.revokeObjectURL(url);
228
- };
182
+ case 'layout':
183
+ return (
184
+ <div style={{ padding: '20px', textAlign: 'center', color: '#999' }}>
185
+ 布局配置功能即将上线
186
+ </div>
187
+ );
229
188
 
230
- // 导入主题
231
- const handleImportTheme = (event: React.ChangeEvent<HTMLInputElement>) => {
232
- const file = event.target.files?.[0];
233
- if (!file) return;
189
+ case 'typography':
190
+ return (
191
+ <div style={{ padding: '20px', textAlign: 'center', color: '#999' }}>
192
+ 字体配置功能即将上线
193
+ </div>
194
+ );
234
195
 
235
- const reader = new FileReader();
236
- reader.onload = (e) => {
237
- try {
238
- const content = e.target?.result as string;
239
- if (file.name.endsWith('.json')) {
240
- const theme = JSON.parse(content) as ThemeOptions;
241
- updateTheme(theme);
242
- } else {
243
- alert('请选择 JSON 格式的主题文件');
244
- }
245
- } catch {
246
- alert('文件格式错误');
247
- }
248
- };
249
- reader.readAsText(file);
250
- };
196
+ case 'preview':
197
+ return (
198
+ <div
199
+ style={{
200
+ padding: '20px',
201
+ backgroundColor,
202
+ color: textColor,
203
+ borderRadius: '4px',
204
+ minHeight: '200px',
205
+ }}
206
+ >
207
+ <h4 style={{ color: textColor }}>主题预览</h4>
208
+ <div style={{ display: 'flex', gap: '10px', marginTop: '10px' }}>
209
+ {colors.map((color, index) => (
210
+ <div
211
+ key={index}
212
+ style={{
213
+ width: '40px',
214
+ height: '40px',
215
+ backgroundColor: color,
216
+ borderRadius: '4px',
217
+ }}
218
+ />
219
+ ))}
220
+ </div>
221
+ <p style={{ marginTop: '10px', color: textColor }}>
222
+ 背景色: {backgroundColor} | 文本色: {textColor} | 深色模式:{' '}
223
+ {darkMode ? '开启' : '关闭'}
224
+ </p>
225
+ </div>
226
+ );
251
227
 
252
- // 开始编辑新主题
253
- const handleStartEdit = () => {
254
- setIsEditing(true);
255
- setNewThemeName('');
228
+ default:
229
+ return null;
230
+ }
256
231
  };
257
232
 
258
233
  return (
259
234
  <div
260
- className={`taroviz-enhanced-theme-editor ${className}`}
235
+ className={`taroviz-theme-editor-enhanced ${className}`}
261
236
  style={{
262
237
  padding: '20px',
263
238
  border: '1px solid #e0e0e0',
@@ -267,356 +242,203 @@ const EnhancedThemeEditor: React.FC<EnhancedThemeEditorProps> = ({
267
242
  ...style,
268
243
  }}
269
244
  >
270
- <h3 style={{ marginBottom: '20px' }}>增强版主题编辑器</h3>
271
-
272
- {/* 标签页 */}
273
- <div style={{ display: 'flex', borderBottom: '1px solid #e0e0e0', marginBottom: '20px' }}>
274
- {(['basic', 'advanced', 'presets'] as const).map(tab => (
275
- <button
276
- key={tab}
277
- onClick={() => setActiveTab(tab)}
278
- style={{
279
- padding: '10px 20px',
280
- border: 'none',
281
- borderBottom: activeTab === tab ? '2px solid #1890ff' : '2px solid transparent',
282
- backgroundColor: 'transparent',
283
- color: activeTab === tab ? '#1890ff' : '#666',
284
- cursor: 'pointer',
285
- fontWeight: activeTab === tab ? 'bold' : 'normal',
286
- }}
287
- >
288
- {tab === 'basic' ? '基础配置' : tab === 'advanced' ? '高级配置' : '预设主题'}
289
- </button>
290
- ))}
291
- </div>
292
-
293
- {/* 基础配置 */}
294
- {activeTab === 'basic' && (
295
- <>
296
- {/* 主题选择 */}
297
- <div style={{ marginBottom: '20px' }}>
298
- <h4>选择主题</h4>
299
- <div style={{ display: 'flex', flexWrap: 'wrap', gap: '10px', marginTop: '10px' }}>
300
- {registeredThemes.map(theme => (
301
- <button
302
- key={theme.name}
303
- onClick={() => {
304
- setIsEditing(false);
305
- updateTheme(theme);
306
- }}
307
- disabled={disabled}
308
- style={{
309
- padding: '8px 16px',
310
- border: `2px solid ${currentTheme.name === theme.name ? '#1890ff' : '#e0e0e0'}`,
311
- borderRadius: '4px',
312
- backgroundColor: currentTheme.name === theme.name ? '#1890ff' : '#ffffff',
313
- color: currentTheme.name === theme.name ? '#ffffff' : '#333',
314
- cursor: disabled ? 'not-allowed' : 'pointer',
315
- opacity: disabled ? 0.6 : 1,
316
- }}
317
- >
318
- {theme.name}
319
- </button>
320
- ))}
321
- <button
322
- onClick={handleStartEdit}
323
- disabled={disabled}
324
- style={{
325
- padding: '8px 16px',
326
- border: '2px dashed #e0e0e0',
327
- borderRadius: '4px',
328
- backgroundColor: '#f5f5f5',
329
- color: '#333',
330
- cursor: disabled ? 'not-allowed' : 'pointer',
331
- opacity: disabled ? 0.6 : 1,
332
- }}
333
- >
334
- + 新主题
335
- </button>
336
- </div>
337
- </div>
338
-
339
- {/* 新主题编辑 */}
340
- {isEditing && (
341
- <div style={{ marginBottom: '20px', padding: '15px', backgroundColor: '#f9f9f9', borderRadius: '4px' }}>
342
- <h4>新建主题</h4>
343
- <input
344
- type="text"
345
- value={newThemeName}
346
- onChange={(e) => setNewThemeName(e.target.value)}
347
- disabled={disabled}
348
- placeholder="输入主题名称"
349
- style={{
350
- padding: '8px',
351
- border: '1px solid #e0e0e0',
352
- borderRadius: '4px',
353
- width: '100%',
354
- marginTop: '10px',
355
- opacity: disabled ? 0.6 : 1,
356
- }}
357
- />
358
- </div>
359
- )}
360
-
361
- {/* 颜色配置 */}
362
- <div style={{ marginBottom: '20px' }}>
363
- <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '10px' }}>
364
- <h4>主题颜色</h4>
365
- <button onClick={handleAddColor} disabled={disabled} style={{
366
- padding: '4px 8px',
245
+ <h3>增强型主题编辑器</h3>
246
+
247
+ {/* 主题选择 */}
248
+ <ThemeSelector
249
+ themes={registeredThemes}
250
+ currentTheme={currentTheme}
251
+ disabled={disabled}
252
+ onSelect={setCurrentTheme}
253
+ onCreateNew={handleStartEdit}
254
+ />
255
+
256
+ {/* 新主题编辑 */}
257
+ {isEditing && (
258
+ <div
259
+ style={{
260
+ marginBottom: '20px',
261
+ padding: '15px',
262
+ backgroundColor: '#f9f9f9',
263
+ borderRadius: '4px',
264
+ }}
265
+ >
266
+ <h4>新建主题</h4>
267
+ <div style={{ marginBottom: '10px' }}>
268
+ <label style={{ display: 'block', marginBottom: '5px' }}>主题名称:</label>
269
+ <input
270
+ type="text"
271
+ value={newThemeName}
272
+ onChange={(e) => setNewThemeName(e.target.value)}
273
+ disabled={disabled}
274
+ style={{
275
+ padding: '8px',
367
276
  border: '1px solid #e0e0e0',
368
277
  borderRadius: '4px',
369
- backgroundColor: '#ffffff',
370
- cursor: disabled ? 'not-allowed' : 'pointer',
278
+ width: '100%',
371
279
  opacity: disabled ? 0.6 : 1,
372
- }}>
373
- + 添加颜色
374
- </button>
375
- </div>
376
- <div style={{ display: 'flex', flexWrap: 'wrap', gap: '10px' }}>
377
- {colors.map((color, index) => (
378
- <div key={index} style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
379
- <input
380
- type="color"
381
- value={color}
382
- onChange={(e) => handleColorChange(index, e.target.value)}
383
- disabled={disabled}
384
- style={{ width: '50px', height: '30px', border: 'none', cursor: 'pointer' }}
385
- />
386
- <input
387
- type="text"
388
- value={color}
389
- onChange={(e) => handleColorChange(index, e.target.value)}
390
- disabled={disabled}
391
- style={{ width: '80px', padding: '4px', border: '1px solid #e0e0e0', borderRadius: '4px' }}
392
- />
393
- <button
394
- onClick={() => handleRemoveColor(index)}
395
- disabled={disabled || colors.length <= 1}
396
- style={{
397
- padding: '4px 8px',
398
- border: '1px solid #ff4d4f',
399
- borderRadius: '4px',
400
- backgroundColor: '#ffffff',
401
- color: '#ff4d4f',
402
- cursor: disabled || colors.length <= 1 ? 'not-allowed' : 'pointer',
403
- opacity: disabled || colors.length <= 1 ? 0.6 : 1,
404
- }}
405
- >
406
- 删除
407
- </button>
408
- </div>
409
- ))}
410
- </div>
411
- </div>
412
-
413
- {/* 基础配置 */}
414
- <div style={{ marginBottom: '20px' }}>
415
- <h4>基础配置</h4>
416
- <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '15px', marginTop: '10px' }}>
417
- <div>
418
- <label style={{ display: 'block', marginBottom: '5px' }}>背景色:</label>
419
- <div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
420
- <input
421
- type="color"
422
- value={backgroundColor}
423
- onChange={(e) => updateTheme({ backgroundColor: e.target.value })}
424
- disabled={disabled}
425
- style={{ width: '50px', height: '30px', border: 'none', cursor: 'pointer' }}
426
- />
427
- <input
428
- type="text"
429
- value={backgroundColor}
430
- onChange={(e) => updateTheme({ backgroundColor: e.target.value })}
431
- disabled={disabled}
432
- style={{ width: '100px', padding: '4px', border: '1px solid #e0e0e0', borderRadius: '4px' }}
433
- />
434
- </div>
435
- </div>
436
- <div>
437
- <label style={{ display: 'block', marginBottom: '5px' }}>文本颜色:</label>
438
- <div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
439
- <input
440
- type="color"
441
- value={textColor}
442
- onChange={(e) => updateTheme({ textColor: e.target.value })}
443
- disabled={disabled}
444
- style={{ width: '50px', height: '30px', border: 'none', cursor: 'pointer' }}
445
- />
446
- <input
447
- type="text"
448
- value={textColor}
449
- onChange={(e) => updateTheme({ textColor: e.target.value })}
450
- disabled={disabled}
451
- style={{ width: '100px', padding: '4px', border: '1px solid #e0e0e0', borderRadius: '4px' }}
452
- />
453
- </div>
454
- </div>
455
- </div>
456
- <div style={{ marginTop: '10px' }}>
457
- <label style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
458
- <input
459
- type="checkbox"
460
- checked={darkMode}
461
- onChange={(e) => updateTheme({ darkMode: e.target.checked })}
462
- disabled={disabled}
463
- />
464
- 深色模式
465
- </label>
466
- </div>
467
- </div>
468
- </>
469
- )}
470
-
471
- {/* 预设主题 */}
472
- {activeTab === 'presets' && enablePresets && (
473
- <div>
474
- <h4>选择预设主题</h4>
475
- <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))', gap: '15px', marginTop: '15px' }}>
476
- {PRESET_THEMES.map(preset => (
477
- <div
478
- key={preset.name}
479
- onClick={() => handlePresetSelect(preset)}
480
- style={{
481
- padding: '15px',
482
- border: '2px solid #e0e0e0',
483
- borderRadius: '8px',
484
- cursor: 'pointer',
485
- backgroundColor: preset.backgroundColor,
486
- color: preset.textColor,
487
- transition: 'all 0.2s',
488
- }}
489
- >
490
- <div style={{ display: 'flex', gap: '5px', marginBottom: '10px' }}>
491
- {(preset.colors || []).map((color, i) => (
492
- <div
493
- key={i}
494
- style={{
495
- width: '30px',
496
- height: '30px',
497
- backgroundColor: color,
498
- borderRadius: '4px',
499
- }}
500
- />
501
- ))}
502
- </div>
503
- <div style={{ fontSize: '14px', fontWeight: 'bold' }}>{preset.name}</div>
504
- <div style={{ fontSize: '12px', opacity: 0.7 }}>{preset.darkMode ? '深色' : '浅色'}</div>
505
- </div>
506
- ))}
507
- </div>
508
- </div>
509
- )}
510
-
511
- {/* 高级配置 */}
512
- {activeTab === 'advanced' && (
513
- <div>
514
- <h4>导入导出</h4>
515
- {enableImportExport && (
516
- <div style={{ display: 'flex', gap: '10px', marginTop: '15px' }}>
517
- <button
518
- onClick={() => handleExportTheme('json')}
519
- disabled={disabled}
520
- style={{
521
- padding: '8px 16px',
522
- border: '1px solid #e0e0e0',
523
- borderRadius: '4px',
524
- backgroundColor: '#ffffff',
525
- cursor: disabled ? 'not-allowed' : 'pointer',
526
- opacity: disabled ? 0.6 : 1,
527
- }}
528
- >
529
- 导出 JSON
530
- </button>
531
- <button
532
- onClick={() => handleExportTheme('css')}
533
- disabled={disabled}
534
- style={{
535
- padding: '8px 16px',
536
- border: '1px solid #e0e0e0',
537
- borderRadius: '4px',
538
- backgroundColor: '#ffffff',
539
- cursor: disabled ? 'not-allowed' : 'pointer',
540
- opacity: disabled ? 0.6 : 1,
541
- }}
542
- >
543
- 导出 CSS
544
- </button>
545
- <label
546
- style={{
547
- padding: '8px 16px',
548
- border: '1px solid #1890ff',
549
- borderRadius: '4px',
550
- backgroundColor: '#1890ff',
551
- color: '#ffffff',
552
- cursor: 'pointer',
553
- }}
554
- >
555
- 导入 JSON
556
- <input
557
- type="file"
558
- accept=".json"
559
- onChange={handleImportTheme}
560
- style={{ display: 'none' }}
561
- />
562
- </label>
563
- </div>
564
- )}
565
- </div>
566
- )}
567
-
568
- {/* 实时预览 */}
569
- {enableLivePreview && (
570
- <div style={{ marginTop: '20px' }}>
571
- <h4>实时预览</h4>
572
- <div style={{
573
- marginTop: '10px',
574
- border: '1px solid #e0e0e0',
575
- borderRadius: '8px',
576
- overflow: 'hidden',
577
- }}>
578
- <LineChart
579
- option={previewOption}
580
- width="100%"
581
- height={200}
280
+ }}
281
+ placeholder="输入主题名称"
582
282
  />
583
283
  </div>
284
+ <button
285
+ onClick={cancelEdit}
286
+ disabled={disabled}
287
+ style={{
288
+ padding: '6px 12px',
289
+ border: '1px solid #e0e0e0',
290
+ borderRadius: '4px',
291
+ backgroundColor: '#ffffff',
292
+ color: '#333333',
293
+ cursor: disabled ? 'not-allowed' : 'pointer',
294
+ opacity: disabled ? 0.6 : 1,
295
+ }}
296
+ >
297
+ 取消
298
+ </button>
584
299
  </div>
585
300
  )}
586
301
 
587
- {/* 保存按钮 */}
588
- <div style={{ marginTop: '20px', display: 'flex', gap: '10px' }}>
302
+ {/* 导入导出工具 */}
303
+ <div style={{ marginBottom: '20px', display: 'flex', gap: '10px' }}>
589
304
  <button
590
- onClick={handleSaveTheme}
305
+ onClick={handleExport}
591
306
  disabled={disabled}
592
307
  style={{
593
- padding: '10px 20px',
594
- border: 'none',
308
+ padding: '8px 16px',
309
+ border: '1px solid #e0e0e0',
595
310
  borderRadius: '4px',
596
- backgroundColor: '#1890ff',
597
- color: '#ffffff',
311
+ backgroundColor: '#ffffff',
312
+ color: '#333333',
598
313
  cursor: disabled ? 'not-allowed' : 'pointer',
599
314
  opacity: disabled ? 0.6 : 1,
600
315
  }}
601
316
  >
602
- 保存主题
317
+ 导出主题
603
318
  </button>
604
319
  <button
605
- onClick={() => updateTheme(currentTheme)}
320
+ onClick={handleCopyExport}
321
+ disabled={disabled || !exportData}
322
+ style={{
323
+ padding: '8px 16px',
324
+ border: '1px solid #e0e0e0',
325
+ borderRadius: '4px',
326
+ backgroundColor: '#ffffff',
327
+ color: '#333333',
328
+ cursor: disabled || !exportData ? 'not-allowed' : 'pointer',
329
+ opacity: disabled || !exportData ? 0.6 : 1,
330
+ }}
331
+ >
332
+ 复制到剪贴板
333
+ </button>
334
+ </div>
335
+
336
+ {exportData && (
337
+ <div style={{ marginBottom: '20px' }}>
338
+ <textarea
339
+ value={exportData}
340
+ readOnly
341
+ style={{
342
+ width: '100%',
343
+ height: '100px',
344
+ padding: '10px',
345
+ border: '1px solid #e0e0e0',
346
+ borderRadius: '4px',
347
+ resize: 'vertical',
348
+ }}
349
+ />
350
+ </div>
351
+ )}
352
+
353
+ {/* 导入区域 */}
354
+ <div style={{ marginBottom: '20px' }}>
355
+ <h4>导入主题</h4>
356
+ <textarea
357
+ value={importData}
358
+ onChange={(e) => setImportData(e.target.value)}
606
359
  disabled={disabled}
360
+ placeholder="粘贴主题 JSON 数据..."
607
361
  style={{
608
- padding: '10px 20px',
362
+ width: '100%',
363
+ height: '80px',
364
+ padding: '10px',
365
+ border: `1px solid ${importError ? '#ff4d4f' : '#e0e0e0'}`,
366
+ borderRadius: '4px',
367
+ resize: 'vertical',
368
+ opacity: disabled ? 0.6 : 1,
369
+ }}
370
+ />
371
+ {importError && (
372
+ <p style={{ color: '#ff4d4f', marginTop: '5px', fontSize: '12px' }}>{importError}</p>
373
+ )}
374
+ <button
375
+ onClick={handleImport}
376
+ disabled={disabled}
377
+ style={{
378
+ marginTop: '10px',
379
+ padding: '8px 16px',
609
380
  border: '1px solid #e0e0e0',
610
381
  borderRadius: '4px',
611
382
  backgroundColor: '#ffffff',
612
- color: '#333',
383
+ color: '#333333',
613
384
  cursor: disabled ? 'not-allowed' : 'pointer',
614
385
  opacity: disabled ? 0.6 : 1,
615
386
  }}
616
387
  >
617
- 重置预览
388
+ 导入
618
389
  </button>
619
390
  </div>
391
+
392
+ {/* 标签页切换 */}
393
+ <div
394
+ style={{
395
+ display: 'flex',
396
+ borderBottom: '1px solid #e0e0e0',
397
+ marginBottom: '20px',
398
+ }}
399
+ >
400
+ {[
401
+ { key: 'colors' as EditorTab, label: '颜色' },
402
+ { key: 'layout' as EditorTab, label: '布局' },
403
+ { key: 'typography' as EditorTab, label: '字体' },
404
+ ...(showPreview ? [{ key: 'preview' as EditorTab, label: '预览' }] : []),
405
+ ].map((tab) => (
406
+ <button
407
+ key={tab.key}
408
+ onClick={() => setActiveTab(tab.key)}
409
+ style={{
410
+ padding: '10px 20px',
411
+ border: 'none',
412
+ borderBottom: `2px solid ${activeTab === tab.key ? '#1890ff' : 'transparent'}`,
413
+ backgroundColor: 'transparent',
414
+ color: activeTab === tab.key ? '#1890ff' : '#333333',
415
+ cursor: 'pointer',
416
+ }}
417
+ >
418
+ {tab.label}
419
+ </button>
420
+ ))}
421
+ </div>
422
+
423
+ {/* 标签页内容 */}
424
+ <div style={{ marginBottom: '20px' }}>{renderTabContent()}</div>
425
+
426
+ {/* 保存按钮 */}
427
+ <button
428
+ onClick={handleSaveTheme}
429
+ disabled={disabled}
430
+ style={{
431
+ padding: '10px 20px',
432
+ border: 'none',
433
+ borderRadius: '4px',
434
+ backgroundColor: '#1890ff',
435
+ color: '#ffffff',
436
+ cursor: disabled ? 'not-allowed' : 'pointer',
437
+ opacity: disabled ? 0.6 : 1,
438
+ }}
439
+ >
440
+ 保存主题
441
+ </button>
620
442
  </div>
621
443
  );
622
444
  };