@quantlife/qlchart 0.0.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.
Files changed (321) hide show
  1. package/.idea/QLChart.iml +12 -0
  2. package/.idea/modules.xml +8 -0
  3. package/.idea/vcs.xml +6 -0
  4. package/README.md +75 -0
  5. package/demo/App.css +213 -0
  6. package/demo/App.tsx +46 -0
  7. package/demo/components/ControlPanel.tsx +13 -0
  8. package/demo/components/DemoNav.tsx +27 -0
  9. package/demo/index.html +16 -0
  10. package/demo/main.tsx +10 -0
  11. package/demo/pages/BasicChartDemo.tsx +61 -0
  12. package/demo/pages/DrawingDemo.tsx +22 -0
  13. package/demo/pages/IndicatorDemo.tsx +22 -0
  14. package/demo/pages/LayoutDemo.tsx +35 -0
  15. package/demo/pages/MultiPeriodDemo.tsx +31 -0
  16. package/demo/pages/ReplayDemo.tsx +195 -0
  17. package/demo/pages/SaveDemo.tsx +27 -0
  18. package/demo/pages/ThemeDemo.tsx +29 -0
  19. package/demo/standalone-demo.html +597 -0
  20. package/demo/vite.config.demo.ts +17 -0
  21. package/dist/index.d.ts +1973 -0
  22. package/dist/qlchart.js +23169 -0
  23. package/dist/style.css +1 -0
  24. package/doc/api/indicator-data-processor.md +35 -0
  25. package/doc/api-reference/.nojekyll +1 -0
  26. package/doc/api-reference/assets/hierarchy.js +1 -0
  27. package/doc/api-reference/assets/highlight.css +43 -0
  28. package/doc/api-reference/assets/icons.js +18 -0
  29. package/doc/api-reference/assets/icons.svg +1 -0
  30. package/doc/api-reference/assets/main.js +60 -0
  31. package/doc/api-reference/assets/navigation.js +1 -0
  32. package/doc/api-reference/assets/search.js +1 -0
  33. package/doc/api-reference/assets/style.css +1611 -0
  34. package/doc/api-reference/classes/ChartManager.html +16 -0
  35. package/doc/api-reference/classes/DataManager.html +13 -0
  36. package/doc/api-reference/classes/DrawingAdapter.html +64 -0
  37. package/doc/api-reference/classes/DrawingPersistence.html +21 -0
  38. package/doc/api-reference/classes/EventManager.html +12 -0
  39. package/doc/api-reference/classes/HollowCandlestickSeries.html +22 -0
  40. package/doc/api-reference/classes/IndicatorRenderer.html +20 -0
  41. package/doc/api-reference/classes/KlineReplay.html +31 -0
  42. package/doc/api-reference/classes/MockDataService.html +13 -0
  43. package/doc/api-reference/classes/MockIndicatorService.html +4 -0
  44. package/doc/api-reference/classes/OverlayIndicator.html +11 -0
  45. package/doc/api-reference/classes/PaneIndicator.html +16 -0
  46. package/doc/api-reference/classes/PaneManager.html +24 -0
  47. package/doc/api-reference/classes/RealtimeDataFeed.html +22 -0
  48. package/doc/api-reference/classes/RenkoSeries.html +22 -0
  49. package/doc/api-reference/classes/ScreenshotUtil.html +10 -0
  50. package/doc/api-reference/classes/SeriesManager.html +30 -0
  51. package/doc/api-reference/classes/ThemeManager.html +18 -0
  52. package/doc/api-reference/enums/ChartEvent.html +12 -0
  53. package/doc/api-reference/enums/IndicatorType.html +4 -0
  54. package/doc/api-reference/enums/SeriesType.html +13 -0
  55. package/doc/api-reference/functions/ChartFunctionMenu.html +1 -0
  56. package/doc/api-reference/functions/DrawingModule.html +8 -0
  57. package/doc/api-reference/functions/IndicatorPanel.html +2 -0
  58. package/doc/api-reference/functions/IndicatorTag.html +2 -0
  59. package/doc/api-reference/functions/KlineTypeSelector.html +1 -0
  60. package/doc/api-reference/functions/LayoutSwitcher.html +1 -0
  61. package/doc/api-reference/functions/PeriodSelector.html +1 -0
  62. package/doc/api-reference/functions/QLChartLayout.html +1 -0
  63. package/doc/api-reference/functions/QLChartPanel.html +10 -0
  64. package/doc/api-reference/functions/QLChartProvider.html +2 -0
  65. package/doc/api-reference/functions/QLToolbar.html +1 -0
  66. package/doc/api-reference/functions/ReplayController.html +1 -0
  67. package/doc/api-reference/functions/TimeBarModule.html +4 -0
  68. package/doc/api-reference/functions/TimeRangeSelector.html +1 -0
  69. package/doc/api-reference/functions/createIndicatorConfig.html +2 -0
  70. package/doc/api-reference/functions/getToolConfig.html +2 -0
  71. package/doc/api-reference/functions/getToolsByCategory.html +2 -0
  72. package/doc/api-reference/functions/getToolsByPriority.html +2 -0
  73. package/doc/api-reference/functions/mapLibTypeToOurs.html +2 -0
  74. package/doc/api-reference/functions/mapToolTypeToLib.html +3 -0
  75. package/doc/api-reference/functions/transformCandlestickData.html +3 -0
  76. package/doc/api-reference/functions/transformIndicatorData.html +2 -0
  77. package/doc/api-reference/functions/transformVolumeData.html +3 -0
  78. package/doc/api-reference/functions/useChart.html +4 -0
  79. package/doc/api-reference/functions/useChartStore.html +8 -0
  80. package/doc/api-reference/functions/useCrosshairSync.html +8 -0
  81. package/doc/api-reference/functions/useDrawingModule.html +1 -0
  82. package/doc/api-reference/functions/useDrawingStore.html +8 -0
  83. package/doc/api-reference/functions/useIndicatorStore.html +8 -0
  84. package/doc/api-reference/functions/useQLChartConfig.html +2 -0
  85. package/doc/api-reference/functions/useReplayStore.html +8 -0
  86. package/doc/api-reference/functions/useTheme.html +2 -0
  87. package/doc/api-reference/functions/useTimeBarStore.html +8 -0
  88. package/doc/api-reference/index.html +1 -0
  89. package/doc/api-reference/interfaces/CandlestickData.html +7 -0
  90. package/doc/api-reference/interfaces/CandlestickRawData.html +8 -0
  91. package/doc/api-reference/interfaces/ChartFunctionMenuProps.html +2 -0
  92. package/doc/api-reference/interfaces/ChartManagerCreateOptions.html +4 -0
  93. package/doc/api-reference/interfaces/ChartOptions.html +8 -0
  94. package/doc/api-reference/interfaces/ChartRequestParams.html +8 -0
  95. package/doc/api-reference/interfaces/ChartResponse.html +5 -0
  96. package/doc/api-reference/interfaces/ChartState.html +24 -0
  97. package/doc/api-reference/interfaces/ChartThemeOptions.html +5 -0
  98. package/doc/api-reference/interfaces/CrosshairData.html +5 -0
  99. package/doc/api-reference/interfaces/DrawingModuleProps.html +5 -0
  100. package/doc/api-reference/interfaces/DrawingState.html +48 -0
  101. package/doc/api-reference/interfaces/HistogramData.html +5 -0
  102. package/doc/api-reference/interfaces/HollowCandlestickData.html +14 -0
  103. package/doc/api-reference/interfaces/IndicatorConfig.html +11 -0
  104. package/doc/api-reference/interfaces/IndicatorDataPoint.html +4 -0
  105. package/doc/api-reference/interfaces/IndicatorDataResponse.html +5 -0
  106. package/doc/api-reference/interfaces/IndicatorDefinition.html +9 -0
  107. package/doc/api-reference/interfaces/IndicatorPanelProps.html +3 -0
  108. package/doc/api-reference/interfaces/IndicatorParamDef.html +8 -0
  109. package/doc/api-reference/interfaces/IndicatorRawData.html +4 -0
  110. package/doc/api-reference/interfaces/IndicatorState.html +19 -0
  111. package/doc/api-reference/interfaces/IndicatorTagProps.html +2 -0
  112. package/doc/api-reference/interfaces/KlineReplayOptions.html +4 -0
  113. package/doc/api-reference/interfaces/LayoutSwitcherProps.html +3 -0
  114. package/doc/api-reference/interfaces/LineData.html +4 -0
  115. package/doc/api-reference/interfaces/MockDataConfig.html +8 -0
  116. package/doc/api-reference/interfaces/MockIndicatorConfig.html +5 -0
  117. package/doc/api-reference/interfaces/PairInfo.html +6 -0
  118. package/doc/api-reference/interfaces/PaneInfo.html +6 -0
  119. package/doc/api-reference/interfaces/PanelConfig.html +9 -0
  120. package/doc/api-reference/interfaces/PersistenceConfig.html +12 -0
  121. package/doc/api-reference/interfaces/QLChartConfig.html +18 -0
  122. package/doc/api-reference/interfaces/QLChartLayoutProps.html +9 -0
  123. package/doc/api-reference/interfaces/QLChartPanelProps.html +13 -0
  124. package/doc/api-reference/interfaces/QLChartPanelRef.html +14 -0
  125. package/doc/api-reference/interfaces/QLToolbarProps.html +7 -0
  126. package/doc/api-reference/interfaces/RealtimeCandle.html +9 -0
  127. package/doc/api-reference/interfaces/RealtimeSubscribeFn.html +2 -0
  128. package/doc/api-reference/interfaces/RenkoData.html +16 -0
  129. package/doc/api-reference/interfaces/ReplayControllerProps.html +2 -0
  130. package/doc/api-reference/interfaces/ReplayState.html +19 -0
  131. package/doc/api-reference/interfaces/ThemeConfig.html +14 -0
  132. package/doc/api-reference/interfaces/TimeBarModuleProps.html +5 -0
  133. package/doc/api-reference/interfaces/TimeBarState.html +13 -0
  134. package/doc/api-reference/interfaces/UseChartReturn.html +4 -0
  135. package/doc/api-reference/interfaces/UseDrawingModuleOptions.html +11 -0
  136. package/doc/api-reference/interfaces/UseDrawingModuleReturn.html +6 -0
  137. package/doc/api-reference/interfaces/UseThemeReturn.html +5 -0
  138. package/doc/api-reference/types/EventHandler.html +2 -0
  139. package/doc/api-reference/types/FetchFn.html +2 -0
  140. package/doc/api-reference/types/LayoutMode.html +2 -0
  141. package/doc/api-reference/types/MarketTrend.html +2 -0
  142. package/doc/api-reference/types/ThemePreset.html +2 -0
  143. package/doc/api-reference/variables/BUILTIN_INDICATORS.html +2 -0
  144. package/doc/api-reference/variables/CATEGORY_LABELS.html +2 -0
  145. package/doc/api-reference/variables/DRAWING_TOOLS.html +3 -0
  146. package/doc/api-reference/variables/MARKET_PRESETS.html +1 -0
  147. package/doc/api-reference/variables/PAIR_PRESETS.html +1 -0
  148. package/doc/api-reference/variables/darkPreset.html +1 -0
  149. package/doc/api-reference/variables/lightPreset.html +1 -0
  150. package/doc/components/drawing-module.md +24 -0
  151. package/doc/components/indicator-list-panel.md +24 -0
  152. package/doc/components/indicator-panel.md +17 -0
  153. package/doc/components/pane-divider.md +25 -0
  154. package/doc/components/qlchart-layout.md +30 -0
  155. package/doc/components/qlchart-panel.md +93 -0
  156. package/doc/components/qlchart-provider.md +73 -0
  157. package/doc/components/qltoolbar.md +17 -0
  158. package/doc/components/replay-controller.md +23 -0
  159. package/doc/components/timebar-module.md +13 -0
  160. package/doc/core/chart-manager.md +14 -0
  161. package/doc/core/data-manager.md +33 -0
  162. package/doc/core/event-manager.md +26 -0
  163. package/doc/core/pane-manager.md +13 -0
  164. package/doc/core/series-manager.md +19 -0
  165. package/doc/core/theme-manager.md +21 -0
  166. package/doc/examples/basic-chart.md +24 -0
  167. package/doc/examples/data-format-guide.md +119 -0
  168. package/doc/examples/drawing-tools.md +30 -0
  169. package/doc/examples/indicator-properties.md +34 -0
  170. package/doc/examples/multi-pane.md +24 -0
  171. package/doc/examples/multi-panel.md +23 -0
  172. package/doc/examples/realtime-data.md +147 -0
  173. package/doc/examples/standalone-js.md +333 -0
  174. package/doc/guide/architecture.md +87 -0
  175. package/doc/guide/data-flow.md +310 -0
  176. package/doc/guide/deployment.md +59 -0
  177. package/doc/guide/drawing-properties.md +40 -0
  178. package/doc/guide/getting-started.md +94 -0
  179. package/doc/guide/pane-system.md +47 -0
  180. package/doc/guide/theme-switching.md +58 -0
  181. package/doc/hooks/use-chart.md +20 -0
  182. package/doc/hooks/use-crosshair-sync.md +14 -0
  183. package/doc/hooks/use-drawing-module.md +43 -0
  184. package/doc/hooks/use-theme.md +15 -0
  185. package/doc/index.md +33 -0
  186. package/doc/plugins/drawing/overview.md +36 -0
  187. package/doc/plugins/drawing/persistence.md +42 -0
  188. package/doc/plugins/drawing/tool-registry.md +29 -0
  189. package/doc/plugins/hollow-candlestick.md +18 -0
  190. package/doc/plugins/indicators.md +28 -0
  191. package/doc/plugins/renko.md +17 -0
  192. package/doc/plugins/replay.md +21 -0
  193. package/doc/plugins/screenshot.md +20 -0
  194. package/docs/api.md +94 -0
  195. package/package.json +54 -0
  196. package/python/qlchart/__init__.py +9 -0
  197. package/python/qlchart/__pycache__/__init__.cpython-311.pyc +0 -0
  198. package/python/qlchart/__pycache__/chart.cpython-311.pyc +0 -0
  199. package/python/qlchart/chart.py +333 -0
  200. package/python/qlchart/templates/chart_template.html +304 -0
  201. package/python/requirements.txt +1 -0
  202. package/python/setup.py +18 -0
  203. package/python/tests/__init__.py +1 -0
  204. package/python/tests/__pycache__/__init__.cpython-311.pyc +0 -0
  205. package/python/tests/__pycache__/test_chart.cpython-311-pytest-8.3.3.pyc +0 -0
  206. package/python/tests/test_chart.py +114 -0
  207. package/quantlife-qlchart-0.0.1.tgz +0 -0
  208. package/src/api/chartApi.ts +30 -0
  209. package/src/api/indicatorApi.ts +27 -0
  210. package/src/components/ChartFunctionMenu.tsx +64 -0
  211. package/src/components/PaneChartPanel.tsx +116 -0
  212. package/src/components/PaneDivider.tsx +66 -0
  213. package/src/components/QLChartLayout.tsx +151 -0
  214. package/src/components/QLChartPanel.tsx +560 -0
  215. package/src/components/QLChartProvider.tsx +90 -0
  216. package/src/components/context-menu/ChartContextMenu.tsx +139 -0
  217. package/src/components/context-menu/index.ts +2 -0
  218. package/src/components/drawing/DrawingModule.tsx +36 -0
  219. package/src/components/drawing/DrawingPropertyPanel.tsx +347 -0
  220. package/src/components/drawing/DrawingToolbar.tsx +305 -0
  221. package/src/components/drawing/index.ts +5 -0
  222. package/src/components/index.ts +43 -0
  223. package/src/components/indicator/IndicatorListPanel.tsx +94 -0
  224. package/src/components/indicator/IndicatorModal.tsx +171 -0
  225. package/src/components/indicator/IndicatorPanel.tsx +9 -0
  226. package/src/components/indicator/IndicatorPropertyPanel.tsx +130 -0
  227. package/src/components/indicator/IndicatorTag.tsx +173 -0
  228. package/src/components/indicator/index.ts +4 -0
  229. package/src/components/replay/ReplayController.css +97 -0
  230. package/src/components/replay/ReplayController.tsx +138 -0
  231. package/src/components/timebar/TimeBarModule.tsx +30 -0
  232. package/src/components/timebar/TimeRangeSelector.tsx +96 -0
  233. package/src/components/timebar/index.ts +3 -0
  234. package/src/components/toolbar/GlobalToolbar.tsx +58 -0
  235. package/src/components/toolbar/KlineTypeSelector.tsx +123 -0
  236. package/src/components/toolbar/LayoutSwitcher.tsx +45 -0
  237. package/src/components/toolbar/PeriodSelector.tsx +35 -0
  238. package/src/components/toolbar/QLToolbar.tsx +71 -0
  239. package/src/components/toolbar/TimeRangeSelector.tsx +89 -0
  240. package/src/components/ui/Modal.tsx +67 -0
  241. package/src/core/ChartManager.ts +95 -0
  242. package/src/core/DataManager.ts +427 -0
  243. package/src/core/EventManager.ts +63 -0
  244. package/src/core/IndicatorDataProcessor.ts +104 -0
  245. package/src/core/PaneManager.ts +121 -0
  246. package/src/core/RealtimeDataFeed.ts +110 -0
  247. package/src/core/SeriesManager.ts +210 -0
  248. package/src/core/ThemeManager.ts +59 -0
  249. package/src/core/index.ts +10 -0
  250. package/src/css.d.ts +4 -0
  251. package/src/hooks/useChart.ts +62 -0
  252. package/src/hooks/useCrosshairSync.ts +109 -0
  253. package/src/hooks/useDrawingModule.ts +475 -0
  254. package/src/hooks/useTheme.ts +31 -0
  255. package/src/index.ts +170 -0
  256. package/src/mock/MockDataService.ts +102 -0
  257. package/src/mock/MockIndicatorService.ts +40 -0
  258. package/src/mock/index.ts +5 -0
  259. package/src/mock/presets.ts +16 -0
  260. package/src/plugins/drawing/DrawingAdapter.ts +1762 -0
  261. package/src/plugins/drawing/DrawingPersistence.ts +273 -0
  262. package/src/plugins/drawing/DrawingPropertyTemplates.ts +327 -0
  263. package/src/plugins/drawing/DrawingSharedService.ts +125 -0
  264. package/src/plugins/drawing/DrawingToolRegistry.ts +684 -0
  265. package/src/plugins/drawing/TextLabelOverlay.ts +101 -0
  266. package/src/plugins/drawing/colorUtils.ts +53 -0
  267. package/src/plugins/drawing/index.ts +10 -0
  268. package/src/plugins/drawing/lineStyleMap.ts +46 -0
  269. package/src/plugins/drawing/migration.ts +105 -0
  270. package/src/plugins/drawing/patterns/PatternDefinitions.ts +57 -0
  271. package/src/plugins/drawing/patterns/index.ts +2 -0
  272. package/src/plugins/drawing/periodUtils.ts +51 -0
  273. package/src/plugins/indicators/AutoIndicatorRenderer.ts +204 -0
  274. package/src/plugins/indicators/IndicatorRenderer.ts +350 -0
  275. package/src/plugins/indicators/OverlayIndicator.ts +114 -0
  276. package/src/plugins/indicators/PaneIndicator.ts +137 -0
  277. package/src/plugins/indicators/index.ts +4 -0
  278. package/src/plugins/replay/KlineReplay.ts +163 -0
  279. package/src/plugins/replay/index.ts +2 -0
  280. package/src/plugins/screenshot/ScreenshotUtil.ts +123 -0
  281. package/src/plugins/screenshot/index.ts +1 -0
  282. package/src/plugins/series/HollowCandlestickSeries.ts +111 -0
  283. package/src/plugins/series/RenkoSeries.ts +104 -0
  284. package/src/plugins/series/VolumeCandlestickSeries.ts +127 -0
  285. package/src/plugins/series/index.ts +6 -0
  286. package/src/standalone.ts +386 -0
  287. package/src/store/useChartStore.ts +101 -0
  288. package/src/store/useDrawingStore.ts +135 -0
  289. package/src/store/useIndicatorStore.ts +100 -0
  290. package/src/store/usePanelRegistry.ts +50 -0
  291. package/src/store/useReplayStore.ts +42 -0
  292. package/src/store/useTimeBarStore.ts +34 -0
  293. package/src/styles/chart.css +312 -0
  294. package/src/styles/components.css +184 -0
  295. package/src/styles/context-menu.css +60 -0
  296. package/src/styles/drawing.css +524 -0
  297. package/src/styles/indicator-modal.css +216 -0
  298. package/src/styles/indicator-tag.css +210 -0
  299. package/src/styles/pane-chart.css +9 -0
  300. package/src/styles/responsive.css +71 -0
  301. package/src/styles/themes/dark.css +63 -0
  302. package/src/styles/themes/light.css +61 -0
  303. package/src/styles/toolbar.css +129 -0
  304. package/src/types/api.ts +36 -0
  305. package/src/types/chart.ts +44 -0
  306. package/src/types/drawing.ts +265 -0
  307. package/src/types/index.ts +40 -0
  308. package/src/types/indicator.ts +344 -0
  309. package/src/types/series.ts +53 -0
  310. package/src/types/theme.ts +48 -0
  311. package/src/utils/dataTransformer.ts +63 -0
  312. package/src/utils/heikinAshi.ts +41 -0
  313. package/src/utils/index.ts +3 -0
  314. package/src/utils/lineBreak.ts +88 -0
  315. package/src/utils/themePresets.ts +69 -0
  316. package/src/utils/timeFormatter.ts +29 -0
  317. package/src/utils/timeScaleUtils.ts +68 -0
  318. package/tsconfig.json +21 -0
  319. package/typedoc.json +10 -0
  320. package/vite.config.standalone.ts +31 -0
  321. package/vite.config.ts +24 -0
@@ -0,0 +1,560 @@
1
+ import { useEffect, useState, useCallback, useRef, forwardRef, useImperativeHandle } from 'react';
2
+ import { useChart } from '../hooks/useChart.js';
3
+ import { useTheme } from '../hooks/useTheme.js';
4
+ import { useChartStore, selectPanelPeriod } from '../store/useChartStore.js';
5
+ import { useIndicatorStore } from '../store/useIndicatorStore.js';
6
+ import { useQLChartConfig } from './QLChartProvider.js';
7
+ import { transformCandlestickData, transformVolumeData, transformVolumeCandlestickData } from '../utils/dataTransformer.js';
8
+ import { generateFutureWhitespace } from '../utils/timeScaleUtils.js';
9
+ import { transformToHeikinAshi } from '../utils/heikinAshi.js';
10
+ import { transformToLineBreak } from '../utils/lineBreak.js';
11
+ import { SeriesType } from '../types/index.js';
12
+ import type { CandlestickRawData } from '../types/index.js';
13
+ import { QLToolbar } from './toolbar/QLToolbar.js';
14
+ import { IndicatorRenderer } from '../plugins/indicators/IndicatorRenderer.js';
15
+ import { IndicatorTag } from './indicator/IndicatorTag.js';
16
+ import { MockIndicatorService } from '../mock/MockIndicatorService.js';
17
+ import { DrawingPropertyPanel } from './drawing/DrawingPropertyPanel.js';
18
+ import { ChartContextMenu } from './context-menu/ChartContextMenu.js';
19
+ import { ChartFunctionMenu } from './ChartFunctionMenu.js';
20
+ import { useDrawingModule } from '../hooks/useDrawingModule.js';
21
+ import { DrawingModule } from './drawing/DrawingModule.js';
22
+ import type { IChartApi, ISeriesApi, SeriesType as LWCSeriesType } from 'lightweight-charts';
23
+ import { useDrawingStore } from '../store/useDrawingStore.js';
24
+ import { RealtimeDataFeed, type RealtimeCandle } from '../core/RealtimeDataFeed.js';
25
+ import { DataManager } from '../core/DataManager.js';
26
+
27
+ import '../styles/chart.css';
28
+ import '../styles/toolbar.css';
29
+ import '../styles/drawing.css';
30
+ import '../styles/context-menu.css';
31
+ import '../styles/indicator-modal.css';
32
+ import '../styles/themes/dark.css';
33
+ import '../styles/themes/light.css';
34
+ import '../styles/responsive.css';
35
+ import '../styles/pane-chart.css';
36
+ import '../styles/components.css';
37
+
38
+ export interface QLChartPanelProps {
39
+ pairId: string;
40
+ product: string;
41
+ period?: string;
42
+ seriesType?: SeriesType;
43
+ showToolbar?: boolean;
44
+ panelId?: string;
45
+ panelIndex?: number;
46
+ onChartCreated?: (chart: IChartApi | null, index: number) => void;
47
+ /** chart 和 series 就绪回调 */
48
+ onChartReady?: (chart: IChartApi | null, mainSeries: ISeriesApi<LWCSeriesType> | null) => void;
49
+ /** 是否为单面板模式(控制绘图UI渲染位置) */
50
+ isSinglePanel?: boolean;
51
+ }
52
+
53
+ /** ★ Req1:QLChartPanel 暴露的命令式API */
54
+ export interface QLChartPanelRef {
55
+ /** 推送实时K线数据(增量更新) */
56
+ pushRealtimeData: (data: RealtimeCandle) => void;
57
+ /** 批量推送实时数据 */
58
+ pushRealtimeBatch: (datas: RealtimeCandle[]) => void;
59
+ /** 重载数据 */
60
+ reload: () => void;
61
+ /** 获取chart实例 */
62
+ getChart: () => IChartApi | null;
63
+ /** 获取主序列 */
64
+ getMainSeries: () => ISeriesApi<LWCSeriesType> | null;
65
+ /** 清除缓存 */
66
+ clearCache: () => void;
67
+ }
68
+
69
+
70
+
71
+ export const QLChartPanel = forwardRef<QLChartPanelRef, QLChartPanelProps>(
72
+ function QLChartPanel({
73
+ pairId,
74
+ product,
75
+ period = '1h',
76
+ seriesType,
77
+ showToolbar = true,
78
+ panelId,
79
+ panelIndex,
80
+ onChartCreated,
81
+ onChartReady,
82
+ isSinglePanel = true,
83
+ }, ref) {
84
+ const { containerRef, chartManager, seriesManager } = useChart();
85
+ const { config: themeConfig, theme, setTheme } = useTheme();
86
+ const config = useQLChartConfig();
87
+ const [loading, setLoading] = useState(false);
88
+ const [error, setError] = useState<string | null>(null);
89
+
90
+ // ★ Req1:DataManager 和 RealtimeDataFeed refs
91
+ const dataManagerRef = useRef<DataManager | null>(null);
92
+ const realtimeFeedRef = useRef<RealtimeDataFeed | null>(null);
93
+ // ★ Req1:保存 volumeSeriesId 以便 RealtimeDataFeed 获取引用
94
+ const volumeSeriesIdRef = useRef<string | null>(null);
95
+
96
+ // ★ Req1:初始化 DataManager
97
+ useEffect(() => {
98
+ if (config.cacheEnabled !== false && config.fetchFn) {
99
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
100
+ const dm = new DataManager(config.fetchFn as any);
101
+ if (config.cacheTTL) dm.setCacheTTL(config.cacheTTL);
102
+ dataManagerRef.current = dm;
103
+ }
104
+ return () => { dataManagerRef.current = null; };
105
+ }, [config.cacheEnabled, config.cacheTTL, config.fetchFn]);
106
+
107
+ // ★ Req1:暴露命令式API
108
+ useImperativeHandle(ref, () => ({
109
+ pushRealtimeData: (data: RealtimeCandle) => {
110
+ realtimeFeedRef.current?.push(data);
111
+ },
112
+ pushRealtimeBatch: (datas: RealtimeCandle[]) => {
113
+ realtimeFeedRef.current?.pushBatch(datas);
114
+ },
115
+ reload: () => loadData(),
116
+ getChart: () => chartManager?.getChart() ?? null,
117
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
118
+ getMainSeries: () => seriesManager?.getFirstMainSeries() as any ?? null,
119
+ clearCache: () => dataManagerRef.current?.clearCache(),
120
+ // eslint-disable-next-line react-hooks/exhaustive-deps
121
+ }), [chartManager, seriesManager]);
122
+
123
+ // 同步 defaultTheme 到 zustand store
124
+ useEffect(() => {
125
+ if (config.defaultTheme && config.defaultTheme !== theme) {
126
+ setTheme(config.defaultTheme);
127
+ }
128
+ }, [config.defaultTheme, theme, setTheme]);
129
+
130
+ const activePeriod = useChartStore((s) => s.activePeriod);
131
+ const panelPeriod = useChartStore(selectPanelPeriod(panelId));
132
+ const setPanelPeriod = useChartStore((s) => s.setPanelPeriod);
133
+ const activeSeriesType = useChartStore((s) => s.activeSeriesType);
134
+
135
+ // period prop 同步到 store(仅 period prop 变化时触发)
136
+ useEffect(() => {
137
+ if (period && period !== activePeriod) {
138
+ // 多面板模式:写入面板级状态
139
+ if (panelId) {
140
+ setPanelPeriod(panelId, period);
141
+ } else {
142
+ useChartStore.getState().setPeriod(period);
143
+ }
144
+ }
145
+ }, [period]); // eslint-disable-line react-hooks/exhaustive-deps
146
+
147
+ // period prop 初始化到面板级状态(仅首次)
148
+ useEffect(() => {
149
+ if (panelId && period) {
150
+ const current = useChartStore.getState().panelPeriods[panelId];
151
+ if (!current) {
152
+ setPanelPeriod(panelId, period);
153
+ }
154
+ }
155
+ }, [panelId]); // eslint-disable-line react-hooks/exhaustive-deps
156
+
157
+ // currentPeriod 统一从面板级状态取值
158
+ const currentPeriod = panelPeriod || period || '1h';
159
+ const currentSeriesType = seriesType ?? activeSeriesType;
160
+
161
+ // Theme: set data-theme attribute on container
162
+ useEffect(() => {
163
+ const container = containerRef.current?.closest('.qlchart-container');
164
+ if (container) {
165
+ container.setAttribute('data-theme', theme);
166
+ }
167
+ }, [theme]);
168
+
169
+ // Theme update: apply chart options without reloading data
170
+ useEffect(() => {
171
+ if (!chartManager) return;
172
+ chartManager.updateOptions(themeConfig.chart);
173
+ }, [chartManager, themeConfig]);
174
+
175
+ // Track whether data has been loaded (series exist)
176
+ const [dataLoaded, setDataLoaded] = useState(false);
177
+
178
+ // ★ F1: loadData generation 序列号 — 丢弃过期异步结果,消除竞态
179
+ const loadGenerationRef = useRef(0);
180
+
181
+ // Indicator integration
182
+ const rendererRef = useRef<IndicatorRenderer | null>(null);
183
+ const activeIndicators = useIndicatorStore((s) => s.activeIndicators);
184
+ const setIndicatorData = useIndicatorStore((s) => s.setIndicatorData);
185
+ const prevIndicatorIdsRef = useRef<string>('');
186
+
187
+ // ★ 绘图模块 Hook
188
+ const {
189
+ drawingAdapterRef,
190
+ recentTools,
191
+ handleDrawingStyleChange,
192
+ handleDeleteDrawing,
193
+ handleContextMenuToolSelect,
194
+ } = useDrawingModule({
195
+ chartManager,
196
+ seriesManager,
197
+ containerRef,
198
+ pairId,
199
+ product,
200
+ period: currentPeriod,
201
+ panelId,
202
+ panelIndex,
203
+ onChartCreated,
204
+ dataLoaded,
205
+ indicatorRendererRef: rendererRef, // ★ V4 需求1: 传 ref 桥接副图绘图
206
+ });
207
+
208
+ const selectedDrawingId = useDrawingStore((s) => s.selectedDrawingId);
209
+
210
+ const loadData = useCallback(async () => {
211
+ if (!chartManager || !seriesManager || !config.fetchFn) return;
212
+
213
+ const chart = chartManager.getChart();
214
+ if (!chart) return;
215
+
216
+ // ★ F1: 递增 generation,使前一次 loadData 的 continuation 作废
217
+ const myGeneration = ++loadGenerationRef.current;
218
+
219
+ setLoading(true);
220
+ setError(null);
221
+ setDataLoaded(false);
222
+
223
+ try {
224
+ // 清除旧序列
225
+ seriesManager.clearAll();
226
+
227
+ // 初始化 IndicatorRenderer
228
+ const renderer = new IndicatorRenderer();
229
+ renderer.setChart(chart);
230
+ renderer.setSeriesManager(seriesManager);
231
+ rendererRef.current = renderer;
232
+
233
+ // ★ Req1:通过 DataManager 获取数据(自动缓存),如果没有则回退到直接调用 fetchFn
234
+ const fetcher = dataManagerRef.current;
235
+ const result = (fetcher
236
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
237
+ ? await fetcher.fetchChartData({ pairId, product, period: currentPeriod } as any)
238
+ : await config.fetchFn({ pairId, product, period: currentPeriod })) as { data?: CandlestickRawData[] };
239
+
240
+ // ★ F1: 检查 generation — 如果不是最新调用,丢弃过期结果
241
+ if (myGeneration !== loadGenerationRef.current) {
242
+ console.log('[QLChartPanel] loadData generation expired, discarding result', { myGeneration, current: loadGenerationRef.current });
243
+ return;
244
+ }
245
+
246
+ if (!result?.data || !Array.isArray(result.data)) {
247
+ throw new Error('Invalid data format');
248
+ }
249
+
250
+ const candlestickData = transformCandlestickData(result.data);
251
+ const volumeData = transformVolumeData(result.data);
252
+
253
+ // 添加主序列
254
+ const mainSeries = seriesManager.addMainSeries(currentSeriesType, {
255
+ upColor: themeConfig.upColor,
256
+ downColor: themeConfig.downColor,
257
+ borderDownColor: themeConfig.downColor,
258
+ borderUpColor: themeConfig.upColor,
259
+ wickDownColor: themeConfig.downColor,
260
+ wickUpColor: themeConfig.upColor,
261
+ });
262
+
263
+ if (mainSeries && candlestickData.length > 0) {
264
+ // ★ F2/F7: K线类型数据预处理 + whitespace注入
265
+ let displayData: any[] = [...candlestickData];
266
+
267
+ switch (currentSeriesType) {
268
+ case SeriesType.HeikinAshi:
269
+ displayData = transformToHeikinAshi(displayData);
270
+ break;
271
+ case SeriesType.LineBreak:
272
+ displayData = transformToLineBreak(displayData, 3);
273
+ break;
274
+ case SeriesType.VolumeCandlestick:
275
+ displayData = transformVolumeCandlestickData(result.data);
276
+ break;
277
+ // 其他类型直接使用原始 candlestickData
278
+ }
279
+
280
+ // 注入未来 whitespace 时间点
281
+ const lastTime = displayData[displayData.length - 1].time as number;
282
+ const futureWhitespace = generateFutureWhitespace(lastTime, currentPeriod, 100);
283
+ const combinedData: any[] = [...displayData, ...futureWhitespace];
284
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
285
+ (mainSeries as any).setData(combinedData);
286
+ }
287
+
288
+ // 添加成交量序列
289
+ const volumeSeries = seriesManager.addOverlaySeries(
290
+ SeriesType.Histogram,
291
+ {
292
+ color: 'rgba(38, 166, 154, 0.35)',
293
+ priceFormat: { type: 'volume' },
294
+ },
295
+ 'volume',
296
+ );
297
+ // ★ Req1:保存 volumeSeriesId
298
+ volumeSeriesIdRef.current = volumeSeries as unknown as string | null;
299
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
300
+ const volumeSeriesApi = volumeSeries as any;
301
+
302
+ if (volumeSeries && volumeData.length > 0) {
303
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
304
+ volumeSeriesApi.setData(volumeData);
305
+ chart.priceScale('volume').applyOptions({
306
+ scaleMargins: { top: 0.8, bottom: 0 },
307
+ borderVisible: false,
308
+ textColor: 'rgba(209, 212, 220, 0.3)',
309
+ });
310
+ }
311
+
312
+ // ★ F3: 替换 fitContent — 避免whitespace拉宽初始视图,精确控制可见范围
313
+ const totalDataBars = candlestickData.length;
314
+ const visibleBars = 80;
315
+ const futurePreviewBars = 15;
316
+ chart.timeScale().setVisibleLogicalRange({
317
+ from: Math.max(0, totalDataBars - visibleBars),
318
+ to: totalDataBars + futurePreviewBars,
319
+ });
320
+
321
+ // Pass candle data to DrawingAdapter for magnet snap
322
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
323
+ drawingAdapterRef.current?.setCandleData(candlestickData as any);
324
+
325
+ // 数据重载后,更新DrawingAdapter的series引用(防御性修复:clearAll后series引用过期)
326
+ if (drawingAdapterRef.current && mainSeries) {
327
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
328
+ drawingAdapterRef.current.updateSeries(mainSeries as any);
329
+ }
330
+
331
+ // ★ F1: 再次检查 generation — 防止 addMainSeries 后被另一个 loadData 超越
332
+ if (myGeneration !== loadGenerationRef.current) {
333
+ console.log('[QLChartPanel] loadData generation expired after addMainSeries, discarding', { myGeneration, current: loadGenerationRef.current });
334
+ return;
335
+ }
336
+
337
+ setDataLoaded(true);
338
+
339
+ // ★ Req1:数据加载完成后初始化 RealtimeDataFeed
340
+ if (mainSeries) {
341
+ const feed = new RealtimeDataFeed();
342
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
343
+ feed.attach(mainSeries as any, volumeSeriesApi ?? null);
344
+ realtimeFeedRef.current = feed;
345
+ }
346
+
347
+ // ★ 任务3-2.1:DataManager 绑定到 chart — 启用拖动加载 + 定时获取
348
+ const dm = dataManagerRef.current;
349
+ const chartApi = chartManager.getChart();
350
+ if (dm && chartApi && mainSeries) {
351
+ // 同步全量数据到 DataManager
352
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
353
+ dm.setAllData(candlestickData as any);
354
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
355
+ dm.attachToChart(chartApi, mainSeries as any, {
356
+ pairId,
357
+ product,
358
+ period: currentPeriod,
359
+ });
360
+ // 启动定时获取(默认5秒)
361
+ dm.startRealtimeFetch(5000);
362
+ }
363
+
364
+ // chart 和 series 就绪回调
365
+ if (onChartReady) {
366
+ onChartReady(chart, mainSeries as ISeriesApi<LWCSeriesType> || null);
367
+ }
368
+ } catch (err) {
369
+ // ★ F1: 过期调用的错误也不处理
370
+ if (myGeneration !== loadGenerationRef.current) return;
371
+
372
+ const msg = err instanceof Error ? err.message : 'Failed to load chart data';
373
+ setError(msg);
374
+ console.error('[QLChartPanel]', err);
375
+ } finally {
376
+ // ★ F1: 只有最新 generation 才更新 loading 状态
377
+ if (myGeneration === loadGenerationRef.current) {
378
+ setLoading(false);
379
+ }
380
+ }
381
+ // eslint-disable-next-line react-hooks/exhaustive-deps
382
+ }, [chartManager, seriesManager, config.fetchFn, pairId, product, currentPeriod, currentSeriesType]);
383
+
384
+ // 数据加载
385
+ useEffect(() => {
386
+ loadData();
387
+ }, [loadData]);
388
+
389
+ // ★ 任务3-2.1:DataManager detach 清理(period 变化或组件卸载时)
390
+ useEffect(() => {
391
+ return () => {
392
+ dataManagerRef.current?.detach();
393
+ };
394
+ }, [currentPeriod]);
395
+
396
+ // ★ Req1:注册外部实时数据回调
397
+ useEffect(() => {
398
+ if (!dataLoaded || !config.realtimeSubscribe) return;
399
+
400
+ const result = config.realtimeSubscribe({
401
+ pairId,
402
+ product,
403
+ period: currentPeriod,
404
+ onUpdate: (data) => realtimeFeedRef.current?.push(data),
405
+ });
406
+
407
+ // 返回 unsubscribe
408
+ if (typeof result === 'function') {
409
+ return result;
410
+ }
411
+ // eslint-disable-next-line react-hooks/exhaustive-deps
412
+ }, [dataLoaded, pairId, product, currentPeriod, config.realtimeSubscribe]);
413
+
414
+ // 指标渲染 - 监听 activeIndicators 变化
415
+ // 重构:副图指标使用LWC v5原生pane系统,不再使用PaneChartPanel
416
+ useEffect(() => {
417
+ const renderer = rendererRef.current;
418
+ if (!renderer) return;
419
+
420
+ // ★ V4 需求4:复合 signature diff(id:visible:params),任何变化都触发重渲染
421
+ const currentSignature = activeIndicators
422
+ .map((i) => `${i.id}:${i.visible ? '1' : '0'}:${JSON.stringify(i.params)}`)
423
+ .sort()
424
+ .join(',');
425
+ if (currentSignature === prevIndicatorIdsRef.current) return;
426
+ prevIndicatorIdsRef.current = currentSignature;
427
+
428
+ // 清除旧的指标渲染
429
+ renderer.clearAll();
430
+
431
+ // ★ V6 需求1: clearAll 后清除 DrawingAdapter 的 pane series 映射
432
+ // clearAll 删除了所有副图 pane,旧 series 已失效
433
+ if (drawingAdapterRef.current) {
434
+ drawingAdapterRef.current.clearPaneSeriesMap();
435
+ // 重置到主图 pane
436
+ drawingAdapterRef.current.resetToMainPane();
437
+ }
438
+
439
+ // 重新渲染所有活跃指标
440
+ for (const indicator of activeIndicators) {
441
+ if (!indicator.visible) continue;
442
+
443
+ // 使用 MockIndicatorService 生成 mock 数据(过渡期,后端就绪后替换)
444
+ const mockData = MockIndicatorService.generate({ name: indicator.name, count: 100 });
445
+ setIndicatorData(indicator.name, {
446
+ name: indicator.name,
447
+ params: indicator.params,
448
+ data: mockData,
449
+ });
450
+
451
+ try {
452
+ const seriesIds = renderer.renderIndicator(indicator, mockData);
453
+ indicator.seriesIds = seriesIds;
454
+
455
+ // ★ V5 需求1: 注册副图 pane series 到 DrawingAdapter
456
+ const paneIndex = renderer.getPaneIndexByIndicatorId(indicator.id);
457
+ if (paneIndex > 0) {
458
+ const paneSeries = renderer.getPaneSeries(paneIndex);
459
+ if (paneSeries && drawingAdapterRef.current) {
460
+ drawingAdapterRef.current.registerPaneSeries(paneIndex, paneSeries);
461
+ }
462
+ }
463
+ } catch (err) {
464
+ console.error(`[QLChartPanel] Failed to render indicator ${indicator.name}:`, err);
465
+ }
466
+ }
467
+ }, [activeIndicators, setIndicatorData]);
468
+
469
+ // Bug②修复:panel mousedown事件在capture阶段可靠切换activePanelId
470
+ // 使用 panelId ?? 'panel_0' 确保单面板模式也正确设置 activePanelId
471
+ const effectivePanelId = panelId ?? 'panel_0';
472
+
473
+ useEffect(() => {
474
+ const container = containerRef.current;
475
+ if (!container) return;
476
+
477
+ const handleMouseDown = () => {
478
+ const state = useChartStore.getState();
479
+ if (state.activePanelId !== effectivePanelId) {
480
+ state.setActivePanel(effectivePanelId);
481
+ }
482
+ };
483
+
484
+ // capture阶段确保优先触发
485
+ container.addEventListener('mousedown', handleMouseDown, true);
486
+ return () => {
487
+ container.removeEventListener('mousedown', handleMouseDown, true);
488
+ };
489
+ }, [containerRef, effectivePanelId]);
490
+
491
+ const handlePanelClick = useCallback(() => {
492
+ useChartStore.getState().setActivePanel(effectivePanelId);
493
+ }, [effectivePanelId]);
494
+
495
+ const activePanelId = useChartStore((s) => s.activePanelId);
496
+ const isActive = activePanelId === effectivePanelId;
497
+
498
+ // 切换活跃面板时清除绘图选中状态(避免跨面板属性编辑混乱)
499
+ useEffect(() => {
500
+ if (!isActive && useDrawingStore.getState().selectedDrawingId) {
501
+ useDrawingStore.getState().selectDrawing(null);
502
+ }
503
+ }, [isActive]);
504
+
505
+ return (
506
+ <div className={`qlchart-container ${isActive ? 'active-panel' : ''}`} data-panel-id={panelId} onClick={handlePanelClick}>
507
+ {showToolbar && (
508
+ <QLToolbar
509
+ period={currentPeriod}
510
+ seriesType={currentSeriesType}
511
+ theme={theme}
512
+ chartManager={chartManager}
513
+ />
514
+ )}
515
+ {/* 指标标签栏 */}
516
+ {activeIndicators.length > 0 && (
517
+ <div className="qlchart-indicator-tags">
518
+ {activeIndicators.map((ind) => (
519
+ <IndicatorTag key={ind.id} config={ind} />
520
+ ))}
521
+ </div>
522
+ )}
523
+ <div className="qlchart-chart-area" style={{
524
+ display: 'flex',
525
+ flexDirection: 'column',
526
+ flex: 1,
527
+ }}>
528
+ <div className="qlchart-panel" style={{ flex: 1, position: 'relative' }}>
529
+ {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
530
+ <div ref={containerRef as any} className="qlchart-chart-wrapper" />
531
+
532
+ {/* 功能菜单 */}
533
+ <ChartFunctionMenu chart={chartManager?.getChart() ?? null} />
534
+
535
+ {/* 绘图工具栏:仅单面板时在此渲染 */}
536
+ {isSinglePanel && <DrawingModule />}
537
+
538
+ {/* 绘图属性栏:单面板 或 多面板的活跃面板时渲染 */}
539
+ {(isSinglePanel || isActive) && (
540
+ <DrawingPropertyPanel
541
+ onStyleChange={handleDrawingStyleChange}
542
+ onDelete={(id) => drawingAdapterRef.current?.removeDrawing(id)}
543
+ />
544
+ )}
545
+ <ChartContextMenu
546
+ containerRef={containerRef}
547
+ recentTools={recentTools}
548
+ onToolSelect={handleContextMenuToolSelect}
549
+ onDeleteDrawing={handleDeleteDrawing}
550
+ hasSelectedDrawing={!!selectedDrawingId}
551
+ />
552
+ {loading && <div className="qlchart-loading">加载中...</div>}
553
+ {error && <div className="qlchart-error">{error}</div>}
554
+ </div>
555
+ {/* 副图指标通过LWC v5原生pane系统在主chart内渲染,不再需要独立Panel */}
556
+ </div>
557
+ </div>
558
+ );
559
+ }
560
+ );
@@ -0,0 +1,90 @@
1
+ import { createContext, useContext, useEffect, useRef, type ReactNode } from 'react';
2
+ import type { ThemePreset, SeriesType, IndicatorListResponse, IndicatorSearchQuery, IndicatorTemplate } from '../types/index.js';
3
+ import { migrateLegacyDrawings } from '../plugins/drawing/migration.js';
4
+
5
+ /** ★ Req1:实时K线数据格式(秒级时间戳,与LWC一致) */
6
+ export interface RealtimeCandle {
7
+ time: number;
8
+ open: number;
9
+ high: number;
10
+ low: number;
11
+ close: number;
12
+ volume?: number;
13
+ }
14
+
15
+ /** ★ Req1:实时数据订阅函数 */
16
+ export interface RealtimeSubscribeFn {
17
+ (params: {
18
+ pairId: string;
19
+ product: string;
20
+ period: string;
21
+ onUpdate: (data: RealtimeCandle) => void;
22
+ }): (() => void) | void;
23
+ }
24
+
25
+ /** QLChart 配置 */
26
+ export interface QLChartConfig {
27
+ /** API基础URL */
28
+ apiBaseUrl?: string;
29
+ /** 默认主题 */
30
+ defaultTheme?: ThemePreset;
31
+ /** 默认周期 */
32
+ defaultPeriod?: string;
33
+ /** 默认序列类型 */
34
+ defaultSeriesType?: SeriesType;
35
+ /** 数据获取函数 */
36
+ fetchFn?: (params: Record<string, unknown>) => Promise<unknown>;
37
+ /** ★ Req1:是否启用数据缓存(默认true) */
38
+ cacheEnabled?: boolean;
39
+ /** ★ Req1:缓存TTL(毫秒,默认300000 = 5分钟) */
40
+ cacheTTL?: number;
41
+ /** ★ Req1:实时数据订阅(WebSocket场景) */
42
+ realtimeSubscribe?: RealtimeSubscribeFn;
43
+ // ★ 任务3-2.2:指标总览API(注入式)
44
+ /** 指标列表查询函数(注入式,未注入时 fallback 到 BUILTIN_INDICATORS) */
45
+ indicatorListFn?: () => Promise<IndicatorListResponse>;
46
+ /** 指标搜索函数(注入式,用于指标面板搜索) */
47
+ indicatorSearchFn?: (query: IndicatorSearchQuery) => Promise<IndicatorTemplate[]>;
48
+ }
49
+
50
+ const QLChartContext = createContext<QLChartConfig>({});
51
+
52
+ /** 配置Provider
53
+ *
54
+ * 支持两种使用方式:
55
+ * 1. 散装属性(推荐):`<QLChartProvider fetchFn={fn} cacheEnabled />`
56
+ * 2. config对象(向后兼容):`<QLChartProvider config={{ fetchFn: fn }} />`
57
+ */
58
+ export function QLChartProvider({
59
+ config,
60
+ children,
61
+ ...restProps
62
+ }: {
63
+ config?: QLChartConfig;
64
+ children: ReactNode;
65
+ } & Partial<QLChartConfig>) {
66
+ const migratedRef = useRef(false);
67
+
68
+ // 一次性迁移旧版LocalStorage绘图数据
69
+ useEffect(() => {
70
+ if (migratedRef.current) return;
71
+ migratedRef.current = true;
72
+ migrateLegacyDrawings();
73
+ }, []);
74
+
75
+ // 合并 config prop 和散装属性,散装属性优先
76
+ const mergedConfig: QLChartConfig = { ...(config ?? {}), ...restProps };
77
+
78
+ return (
79
+ <QLChartContext.Provider value={mergedConfig}>
80
+ {children}
81
+ </QLChartContext.Provider>
82
+ );
83
+ }
84
+
85
+ /** 获取配置 */
86
+ export function useQLChartConfig(): QLChartConfig {
87
+ return useContext(QLChartContext);
88
+ }
89
+
90
+ export { QLChartContext };