st-comp 0.0.277 → 0.0.279

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 (319) hide show
  1. package/README.md +19 -19
  2. package/es/ChartLayout.cjs +1 -1
  3. package/es/ChartLayout.js +1 -1
  4. package/es/ConfigProvider.cjs +1 -1
  5. package/es/ConfigProvider.js +3 -3
  6. package/es/Contextmenu.cjs +1 -1
  7. package/es/Contextmenu.js +7 -7
  8. package/es/CustomFunction.cjs +2 -2
  9. package/es/CustomFunction.js +514 -308
  10. package/es/FactorWarning.cjs +1 -1
  11. package/es/FactorWarning.js +37 -37
  12. package/es/HeatMap.cjs +1 -1
  13. package/es/HeatMap.js +7 -7
  14. package/es/Kline.cjs +1 -1
  15. package/es/Kline.js +18 -18
  16. package/es/KlineBasic.cjs +1 -1
  17. package/es/KlineBasic.js +93 -93
  18. package/es/KlineConfig.cjs +1 -1
  19. package/es/KlineConfig.js +17 -17
  20. package/es/KlineNew.cjs +1 -1
  21. package/es/KlineNew.js +14 -14
  22. package/es/KlinePlus.cjs +4 -4
  23. package/es/KlinePlus.js +760 -656
  24. package/es/LinearLegend.cjs +1 -1
  25. package/es/LinearLegend.js +1 -1
  26. package/es/Map.cjs +1 -1
  27. package/es/Map.js +1 -1
  28. package/es/MonacoEditor.cjs +1 -1
  29. package/es/MonacoEditor.js +18 -18
  30. package/es/Pagination.cjs +1 -1
  31. package/es/Pagination.js +76 -76
  32. package/es/PasswordPrompt.cjs +1 -1
  33. package/es/PasswordPrompt.js +2 -2
  34. package/es/Pie.cjs +2 -2
  35. package/es/Pie.js +14 -14
  36. package/es/Table.cjs +1 -1
  37. package/es/Table.js +22 -22
  38. package/es/TreeMap.cjs +1 -1
  39. package/es/TreeMap.js +4 -4
  40. package/es/User.cjs +1 -1
  41. package/es/User.js +73 -73
  42. package/es/VarSelectDialog.cjs +2 -2
  43. package/es/VarSelectDialog.js +21 -21
  44. package/es/VarietyAiHelper.cjs +4 -4
  45. package/es/VarietyAiHelper.js +139 -138
  46. package/es/VarietyAutoComplete.cjs +1 -1
  47. package/es/VarietyAutoComplete.js +16 -16
  48. package/es/VarietySearch.cjs +4 -4
  49. package/es/VarietySearch.js +861 -857
  50. package/es/{VarietySelect-6a166293.js → VarietySelect-41daac7b.js} +10 -10
  51. package/es/{VarietySelect-610903f0.cjs → VarietySelect-d58eee29.cjs} +1 -1
  52. package/es/VarietyTextCopy.cjs +2 -2
  53. package/es/VarietyTextCopy.js +25 -25
  54. package/es/VirtualTable.cjs +1 -1
  55. package/es/VirtualTable.js +6 -6
  56. package/es/{_initCloneObject-152b4f4e.js → _initCloneObject-5e73c9c8.js} +2 -2
  57. package/es/{_initCloneObject-76e116d0.cjs → _initCloneObject-a5832933.cjs} +1 -1
  58. package/es/aiTools-229609fb.cjs +2 -0
  59. package/es/aiTools-ce2f9281.js +115 -0
  60. package/es/aiTools.js +179 -176
  61. package/es/{config-provider-4c784baf.cjs → config-provider-06ea6b42.cjs} +1 -1
  62. package/es/{config-provider-7a3aca55.js → config-provider-88b637a8.js} +3 -3
  63. package/es/{dropdown-987c47fb.cjs → dropdown-4ce4c3e2.cjs} +1 -1
  64. package/es/{dropdown-01e09205.js → dropdown-72fbc15d.js} +2 -2
  65. package/es/{el-autocomplete-342ae823.js → el-autocomplete-6cafcdf1.js} +5 -5
  66. package/es/{el-autocomplete-16f66b34.cjs → el-autocomplete-e0857c5b.cjs} +1 -1
  67. package/es/{el-button-13dc915f.cjs → el-button-613e0bb2.cjs} +1 -1
  68. package/es/{el-button-314171a0.js → el-button-94d3de38.js} +3 -3
  69. package/es/{el-checkbox-5899681f.js → el-checkbox-6d4cb393.js} +3 -3
  70. package/es/{el-checkbox-bdf64663.cjs → el-checkbox-e90762df.cjs} +1 -1
  71. package/es/{el-dialog-0ef95df6.cjs → el-dialog-02942e22.cjs} +1 -1
  72. package/es/{el-dialog-86a3eec2.js → el-dialog-5e30161f.js} +4 -4
  73. package/es/{el-form-item-77ef6784.cjs → el-form-item-44f060d8.cjs} +1 -1
  74. package/es/{el-form-item-cda4d93c.js → el-form-item-9e8969ea.js} +5 -5
  75. package/es/{el-input-fa1445ad.js → el-input-342b2def.js} +13 -13
  76. package/es/{el-input-89222943.cjs → el-input-9a9a7f7f.cjs} +1 -1
  77. package/es/{el-input-number-d3338e5e.cjs → el-input-number-65c847d6.cjs} +1 -1
  78. package/es/{el-input-number-8f6dbf74.js → el-input-number-c22b803a.js} +35 -35
  79. package/es/{el-loading-84dde1a8.cjs → el-loading-8179b497.cjs} +1 -1
  80. package/es/{el-loading-9b671203.js → el-loading-bcc51e3d.js} +1 -1
  81. package/es/{el-menu-item-535fef90.js → el-menu-item-339baf6d.js} +21 -21
  82. package/es/{el-menu-item-4531fd0e.cjs → el-menu-item-83186444.cjs} +1 -1
  83. package/es/{el-message-box-621ed2f1.js → el-message-box-035b699c.js} +9 -9
  84. package/es/{el-message-box-f5717070.cjs → el-message-box-c62d3217.cjs} +1 -1
  85. package/es/{el-overlay-23de4324.js → el-overlay-3e17604b.js} +3 -3
  86. package/es/{el-overlay-151708cb.cjs → el-overlay-8f1da9e8.cjs} +1 -1
  87. package/es/{el-popconfirm-b8aa490e.cjs → el-popconfirm-4e32ec1b.cjs} +1 -1
  88. package/es/{el-popconfirm-bcff0aa5.js → el-popconfirm-f8a86e2c.js} +4 -4
  89. package/es/{el-popper-43ec37f4.cjs → el-popper-a4f3ae4d.cjs} +1 -1
  90. package/es/{el-popper-03988662.js → el-popper-fbd0dcea.js} +1 -1
  91. package/es/{el-segmented-db3a86be.js → el-segmented-56bd58a8.js} +2 -2
  92. package/es/{el-segmented-79a1367c.cjs → el-segmented-833ac177.cjs} +1 -1
  93. package/es/{el-select-4bd0010d.js → el-select-44fa547d.js} +8 -8
  94. package/es/{el-select-ec04e7ce.cjs → el-select-70806166.cjs} +1 -1
  95. package/es/{el-table-column-35e47229.cjs → el-table-column-3e2cdfca.cjs} +1 -1
  96. package/es/{el-table-column-7af2bec7.js → el-table-column-b74a857d.js} +9 -9
  97. package/es/{el-tag-bef9e83d.js → el-tag-66c83156.js} +2 -2
  98. package/es/{el-tag-5d5608e0.cjs → el-tag-9c2f329f.cjs} +1 -1
  99. package/es/{el-text-f998902c.js → el-text-034bd3f4.js} +1 -1
  100. package/es/{el-text-abf10fdf.cjs → el-text-6b2374bb.cjs} +1 -1
  101. package/es/index-00f6ca75.cjs +1 -0
  102. package/es/{index-a00e0534.cjs → index-185792a7.cjs} +1 -1
  103. package/es/{index-d1b554ac.js → index-1e2a38ab.js} +2 -2
  104. package/es/{index-0986e373.cjs → index-3153c1db.cjs} +1 -1
  105. package/es/{index-936e7dec.js → index-44a382a5.js} +2 -2
  106. package/es/{index-ce21e2ce.js → index-5216d5f5.js} +5 -5
  107. package/es/{index-7ed0999e.js → index-53cc4e43.js} +720 -716
  108. package/es/{index-cfcd60ca.cjs → index-5c0dd943.cjs} +1 -1
  109. package/es/{index-cfd00899.cjs → index-870b6c9d.cjs} +1 -1
  110. package/es/{index-a83ec4b7.js → index-8c2d7e05.js} +1 -1
  111. package/es/{index-8901a38c.cjs → index-94131fd7.cjs} +30 -30
  112. package/es/{index-db846633.cjs → index-9d16cea8.cjs} +1 -1
  113. package/es/{index-f3f6ee27.js → index-a33b2fb8.js} +178 -178
  114. package/es/{index-2d7ac156.cjs → index-a9246d17.cjs} +17 -17
  115. package/es/{index-60c69dd1.cjs → index-ac8dc53d.cjs} +1 -1
  116. package/es/{index-102edf6c.js → index-c5f2d372.js} +2 -2
  117. package/es/{index-6290ae0f.js → index-c6584658.js} +2 -2
  118. package/es/{index-5c44d71a.js → index-eb96e074.js} +149 -119
  119. package/es/{index-5ed42e9c.js → index-f034b910.js} +2 -2
  120. package/es/{index-c58e90e9.cjs → index-f58b2920.cjs} +1 -1
  121. package/es/{python-6cc6ec1b.js → python-8a1664e6.js} +18 -18
  122. package/es/{python-71f59893.cjs → python-a3db9620.cjs} +1 -1
  123. package/es/style.css +1 -1
  124. package/es/{use-form-common-props-02be4928.cjs → use-form-common-props-ac666630.cjs} +1 -1
  125. package/es/{use-form-common-props-76a09d89.js → use-form-common-props-ed72e914.js} +11 -11
  126. package/es/{use-global-config-31b3ec57.cjs → use-global-config-23472471.cjs} +1 -1
  127. package/es/{use-global-config-0f43458b.js → use-global-config-ee406d58.js} +2 -2
  128. package/es/{validator-ea292506.cjs → validator-366dbf39.cjs} +1 -1
  129. package/es/{validator-0e037618.js → validator-c60a8956.js} +1 -1
  130. package/es/{zh-cn-1227eb8a.js → zh-cn-b59a0910.js} +1 -1
  131. package/es/{zh-cn-0edc7234.cjs → zh-cn-c21d12d8.cjs} +1 -1
  132. package/lib/aiTools.js +179 -176
  133. package/lib/bundle.js +1 -1
  134. package/lib/bundle.umd.cjs +230 -230
  135. package/lib/{index-2a269748.js → index-62fa175a.js} +30149 -29800
  136. package/lib/{python-41da18e5.js → python-4a48fa39.js} +1 -1
  137. package/lib/style.css +1 -1
  138. package/package.json +53 -44
  139. package/auto-imports.d.ts +0 -13
  140. package/components.d.ts +0 -59
  141. package/es/aiTools-a3b095f6.js +0 -113
  142. package/es/aiTools-f36136a4.cjs +0 -2
  143. package/es/index-476c4459.cjs +0 -1
  144. package/index.html +0 -13
  145. package/packages/ChartLayout/index.ts +0 -8
  146. package/packages/ChartLayout/index.vue +0 -63
  147. package/packages/ConfigProvider/index.ts +0 -8
  148. package/packages/ConfigProvider/index.vue +0 -66
  149. package/packages/Contextmenu/index.ts +0 -8
  150. package/packages/Contextmenu/index.vue +0 -134
  151. package/packages/CustomFunction/components/ManageDialog.vue +0 -706
  152. package/packages/CustomFunction/index.ts +0 -8
  153. package/packages/CustomFunction/index.vue +0 -200
  154. package/packages/FactorWarning/index.ts +0 -8
  155. package/packages/FactorWarning/index.vue +0 -754
  156. package/packages/FactorWarning/tools.js +0 -10
  157. package/packages/HeatMap/index.ts +0 -8
  158. package/packages/HeatMap/index.vue +0 -141
  159. package/packages/Kline/components/Contextmenu/index.vue +0 -110
  160. package/packages/Kline/components/Tips/index.vue +0 -40
  161. package/packages/Kline/componentsNew/KlineSlide/index.vue +0 -155
  162. package/packages/Kline/componentsNew/KlineSub/index.vue +0 -297
  163. package/packages/Kline/componentsNew/KlineTips/index.vue +0 -66
  164. package/packages/Kline/componentsNew/KlineUtils/index.vue +0 -84
  165. package/packages/Kline/componentsNew/Tips/index.vue +0 -33
  166. package/packages/Kline/formatKlineData.ts +0 -109
  167. package/packages/Kline/images/buy.svg +0 -1
  168. package/packages/Kline/images/pen.png +0 -0
  169. package/packages/Kline/images/sell.svg +0 -1
  170. package/packages/Kline/images/t.svg +0 -1
  171. package/packages/Kline/index.ts +0 -16
  172. package/packages/Kline/index.vue +0 -891
  173. package/packages/Kline/option.ts +0 -539
  174. package/packages/Kline/type.d.ts +0 -219
  175. package/packages/Kline/utils.ts +0 -682
  176. package/packages/KlineBasic/api.js +0 -58
  177. package/packages/KlineBasic/components/AverageTips/index.vue +0 -33
  178. package/packages/KlineBasic/components/Contextmenu/index.vue +0 -105
  179. package/packages/KlineBasic/components/KlineSub/index.vue +0 -332
  180. package/packages/KlineBasic/components/KlineTips/index.vue +0 -99
  181. package/packages/KlineBasic/components/Tips/index.vue +0 -56
  182. package/packages/KlineBasic/index.ts +0 -8
  183. package/packages/KlineBasic/index.vue +0 -839
  184. package/packages/KlineBasic/utils.js +0 -258
  185. package/packages/KlineConfig/config.js +0 -46
  186. package/packages/KlineConfig/index.ts +0 -8
  187. package/packages/KlineConfig/index.vue +0 -356
  188. package/packages/KlineNew/components/KlineSlide/index.vue +0 -156
  189. package/packages/KlineNew/components/KlineSub/index.vue +0 -335
  190. package/packages/KlineNew/components/KlineTips/index.vue +0 -96
  191. package/packages/KlineNew/components/KlineUtils/index.vue +0 -341
  192. package/packages/KlineNew/components/Tips/index.vue +0 -33
  193. package/packages/KlineNew/index.ts +0 -14
  194. package/packages/KlinePlus/components/SliderChart.vue +0 -208
  195. package/packages/KlinePlus/components/Tips.vue +0 -36
  196. package/packages/KlinePlus/images/buy.svg +0 -1
  197. package/packages/KlinePlus/images/sell.svg +0 -1
  198. package/packages/KlinePlus/images/t.svg +0 -1
  199. package/packages/KlinePlus/index.ts +0 -8
  200. package/packages/KlinePlus/index.vue +0 -1067
  201. package/packages/KlinePlus/utils.js +0 -771
  202. package/packages/LinearLegend/index.ts +0 -8
  203. package/packages/LinearLegend/index.vue +0 -112
  204. package/packages/Map/China.json +0 -1
  205. package/packages/Map/index.ts +0 -8
  206. package/packages/Map/index.vue +0 -147
  207. package/packages/MonacoEditor/index.ts +0 -8
  208. package/packages/MonacoEditor/index.vue +0 -1141
  209. package/packages/MonacoEditor/tools.js +0 -18
  210. package/packages/Pagination/index.ts +0 -8
  211. package/packages/Pagination/index.vue +0 -87
  212. package/packages/PasswordPrompt/index.ts +0 -8
  213. package/packages/PasswordPrompt/index.vue +0 -267
  214. package/packages/Pie/index.ts +0 -8
  215. package/packages/Pie/index.vue +0 -148
  216. package/packages/Table/components/Button/index.vue +0 -61
  217. package/packages/Table/components/Formatter/index.vue +0 -26
  218. package/packages/Table/index.d.ts +0 -0
  219. package/packages/Table/index.ts +0 -8
  220. package/packages/Table/index.vue +0 -133
  221. package/packages/TreeMap/index.ts +0 -8
  222. package/packages/TreeMap/index.vue +0 -186
  223. package/packages/User/components/EditInfoDialog/Mobile.vue +0 -214
  224. package/packages/User/components/EditInfoDialog/index.vue +0 -161
  225. package/packages/User/components/EditPasswordDialog.vue +0 -168
  226. package/packages/User/components/SetPasswordDialog.vue +0 -160
  227. package/packages/User/index.ts +0 -8
  228. package/packages/User/index.vue +0 -117
  229. package/packages/VarSelectDialog/Mock.js +0 -495
  230. package/packages/VarSelectDialog/VarietySelect.vue +0 -78
  231. package/packages/VarSelectDialog/index.ts +0 -8
  232. package/packages/VarSelectDialog/index.vue +0 -323
  233. package/packages/VarietyAiHelper/images/like.png +0 -0
  234. package/packages/VarietyAiHelper/images/like_no.png +0 -0
  235. package/packages/VarietyAiHelper/index.ts +0 -8
  236. package/packages/VarietyAiHelper/index.vue +0 -910
  237. package/packages/VarietyAutoComplete/index.ts +0 -8
  238. package/packages/VarietyAutoComplete/index.vue +0 -89
  239. package/packages/VarietySearch/components/AddTag/index.vue +0 -268
  240. package/packages/VarietySearch/components/CommonIndicator/IndicatorTags.vue +0 -52
  241. package/packages/VarietySearch/components/CommonIndicator/index.vue +0 -697
  242. package/packages/VarietySearch/components/CommonIndicator/tools.js +0 -260
  243. package/packages/VarietySearch/components/CompositeOrder/index.vue +0 -260
  244. package/packages/VarietySearch/components/FactorScreen/FactorDescription.vue +0 -70
  245. package/packages/VarietySearch/components/FactorScreen/index.vue +0 -1158
  246. package/packages/VarietySearch/components/FactorScreen/tools.js +0 -34
  247. package/packages/VarietySearch/config.js +0 -539
  248. package/packages/VarietySearch/index.ts +0 -8
  249. package/packages/VarietySearch/index.vue +0 -688
  250. package/packages/VarietyTextCopy/index.ts +0 -8
  251. package/packages/VarietyTextCopy/index.vue +0 -136
  252. package/packages/VirtualTable/index.ts +0 -8
  253. package/packages/VirtualTable/index.vue +0 -241
  254. package/packages/index.ts +0 -59
  255. package/public/aiTools.js +0 -176
  256. package/public/talib.wasm +0 -0
  257. package/public/vite.svg +0 -1
  258. package/src/App.vue +0 -140
  259. package/src/assets/vue.svg +0 -1
  260. package/src/components/Dialog/index.vue +0 -16
  261. package/src/components/Kline/index.vue +0 -21
  262. package/src/components/Table/index.vue +0 -27
  263. package/src/main.ts +0 -17
  264. package/src/pages/ChartLayout/index.vue +0 -22
  265. package/src/pages/ConfigProvider/index.vue +0 -9
  266. package/src/pages/Contextmenu/index.vue +0 -46
  267. package/src/pages/CustomFunction/index.vue +0 -13
  268. package/src/pages/FactorWarning/index.vue +0 -203
  269. package/src/pages/HeatMap/index.vue +0 -128
  270. package/src/pages/Kline/index.vue +0 -12
  271. package/src/pages/KlineBasic/api.js +0 -48
  272. package/src/pages/KlineBasic/index.vue +0 -366
  273. package/src/pages/KlineBasic/indicator.js +0 -114
  274. package/src/pages/KlineConfig/index.vue +0 -20
  275. package/src/pages/KlineNew/components/KlineAction/components/ContextMenu/index.vue +0 -91
  276. package/src/pages/KlineNew/components/KlineAction/components/YearChart/index.vue +0 -532
  277. package/src/pages/KlineNew/components/KlineAction/images/pen.png +0 -0
  278. package/src/pages/KlineNew/components/KlineAction/index.vue +0 -716
  279. package/src/pages/KlineNew/components/KlineAction/mockApi/index.js +0 -13
  280. package/src/pages/KlineNew/components/KlineAction/utils.js +0 -571
  281. package/src/pages/KlineNew/components/KlineBasic/index.vue +0 -336
  282. package/src/pages/KlineNew/components/KlineBasic/utils.js +0 -46
  283. package/src/pages/KlineNew/components/KlineNew/index.vue +0 -316
  284. package/src/pages/KlineNew/components/KlineNew/utils.js +0 -31
  285. package/src/pages/KlineNew/components/KlineSlide/index.vue +0 -448
  286. package/src/pages/KlineNew/components/KlineSlide/utils.js +0 -78
  287. package/src/pages/KlineNew/components/KlineSlideNew/index.vue +0 -430
  288. package/src/pages/KlineNew/components/KlineSlideNew/utils.js +0 -78
  289. package/src/pages/KlineNew/index.vue +0 -92
  290. package/src/pages/KlinePlus/KlineDialog/MultiCycle.vue +0 -312
  291. package/src/pages/KlinePlus/KlineDialog/api.js +0 -108
  292. package/src/pages/KlinePlus/KlineDialog/index.vue +0 -582
  293. package/src/pages/KlinePlus/KlineDialog/indicator.js +0 -109
  294. package/src/pages/KlinePlus/KlineDialog/tools.js +0 -86
  295. package/src/pages/KlinePlus/index.vue +0 -30
  296. package/src/pages/KlinePlus/tradeLogMock.js +0 -1927
  297. package/src/pages/LinearLegend/index.vue +0 -92
  298. package/src/pages/Map/index.vue +0 -62
  299. package/src/pages/MonacoEditor/index.vue +0 -63
  300. package/src/pages/Pagination/index.vue +0 -24
  301. package/src/pages/PasswordPrompt/index.vue +0 -12
  302. package/src/pages/Pie/index.vue +0 -36
  303. package/src/pages/Table/index.vue +0 -117
  304. package/src/pages/TreeMap/index.vue +0 -542
  305. package/src/pages/User/index.vue +0 -24
  306. package/src/pages/VarSelectDialog/index.vue +0 -16
  307. package/src/pages/VarietyAiHelper/index.vue +0 -20
  308. package/src/pages/VarietyAutoComplete/index.vue +0 -19
  309. package/src/pages/VarietySearch/api.js +0 -70
  310. package/src/pages/VarietySearch/index.vue +0 -467
  311. package/src/pages/VarietyTextCopy/index.vue +0 -24
  312. package/src/pages/VirtualTable/demo.json +0 -6377
  313. package/src/pages/VirtualTable/index.vue +0 -79
  314. package/src/router/index.ts +0 -9
  315. package/src/router/routes.ts +0 -132
  316. package/src/style.css +0 -79
  317. package/src/vite-env.d.ts +0 -9
  318. package/tsconfig.json +0 -30
  319. package/tsconfig.node.json +0 -10
@@ -1,1067 +0,0 @@
1
- <!-- 单品种单周期K线业务组件 -->
2
- <script setup>
3
- import dayjs from "dayjs";
4
- import * as echarts from "echarts";
5
- import { stMath, debounce, addResizeListener } from "st-func";
6
- import { ref, watch, computed, onMounted, onUnmounted, inject } from "vue";
7
- import { loadKlineConfig, checkTimeInterval, getSubOptions, mergeklineData, handleMarkPointTradeLog, handleNetPositionLine, handleTradeIncomeRateLine, normalizeToKlineTimeByMatch } from "./utils.js";
8
- import Tips from "./components/Tips.vue";
9
- import SliderChart from "./components/SliderChart.vue";
10
-
11
- const { round, formatValue } = stMath;
12
- const { request } = inject("stConfig"); // 组件库全局配置
13
-
14
- const loading = ref(false);
15
- const isHover = ref(false);
16
- const props = defineProps({
17
- varietyCode: { type: String, required: true },
18
- varietyStock: { type: Number, required: true }, // 品种市场类型 0-期货, 1-股票, 2-期权
19
- indicatorStore: { type: Object, required: true }, // 指标配置Store
20
- userKlineConfig: { type: Object, required: true }, // 用户自定义的K线配置
21
-
22
- // 交互选项
23
- cycle: { type: String, required: true },
24
- sellBuy: { type: Number, default: 1 },
25
- rightType: { type: [Number, null], default: null }, // 复权类型
26
- klineType: { type: [Number, null], default: null }, // 合约类型
27
- incomeType: { type: Number, default: 0 }, // 收益计算类型 0-单利, 1-复利
28
- initTimeRange: { type: Array, required: true },
29
- mainIndicator: { type: String, required: true },
30
-
31
- // 绘制数据
32
- tradeLog: { type: Array, default: () => [] }, // 成交数据
33
- netPositionData: { type: Array, default: () => [] }, // 净值数据
34
- tradeIncomeRateData: { type: Array, default: () => [] }, // 成交收益率数据
35
-
36
- // 强制定位高亮(非必填)
37
- positionTime: { type: [String, null], default: null },
38
- // 同步刷选范围(非必填)
39
- syncBrushRange: { type: Object, default: () => ({ cycleList: [], startTime: null, endTime: null }) },
40
-
41
- // 业务系统相关
42
- deleteFirstNumber: { type: Number, default: 1 }, // 绩效标记
43
- withoutPermission: { type: Number, default: 0 }, // 三方标记(不需要token授权)
44
- });
45
-
46
- // 加载标记
47
- let isLoadNew = false;
48
- let isloadAllNew = false;
49
- let isLoadHistory = false;
50
- let isloadAllHistory = false;
51
-
52
- let resizeRo = null;
53
- let subChartIns = null;
54
- let mainChartIns = null;
55
- const mainChartRef = ref(null);
56
- const subChartRef = ref(null);
57
- const sliderChartRef = ref(null);
58
-
59
- // K线数据
60
- const klineData = ref({
61
- data: [],
62
- mainIndicator: [],
63
- subIndicator: [],
64
- time: [],
65
- });
66
- const activeIndex = ref(0);
67
- const screenTimeRange = ref([]); // 当屏首末时间
68
- const contextmenuKlineTime = ref(null); // 右键点击的那一根K线时间
69
-
70
- // 指标数据
71
- const subIndicator = ref("VOL");
72
- const mainTips = computed(() => {
73
- const itemData = klineData.value.data[activeIndex.value];
74
- if (!itemData) {
75
- return [
76
- { label: "开", value: "-" },
77
- { label: "高", value: "-" },
78
- { label: "低", value: "-" },
79
- { label: "收", value: "-" },
80
- { label: "额", value: "-" },
81
- { label: "涨跌", value: "-" },
82
- ];
83
- }
84
- const result = [
85
- { label: "开", value: itemData[0] },
86
- { label: "高", value: itemData[3] },
87
- { label: "低", value: itemData[2] },
88
- { label: "收", value: itemData[1] },
89
- ];
90
- // 特殊: 交易总额
91
- itemData[4] !== null && result.push({ label: "额", value: formatValue(itemData[4]) });
92
- // 特殊: 涨跌幅 公式:收盘价 - 昨收 / (昨收绝对值)
93
- let diffColor;
94
- if (itemData[6] > 0) {
95
- diffColor = "red";
96
- } else if (itemData[6] < 0) {
97
- diffColor = "green";
98
- }
99
- result.push({ label: "涨跌", value: `${round(itemData[6])}%`, color: diffColor });
100
- return result;
101
- });
102
- const mainIndicatorTips = computed(() => {
103
- return (
104
- klineData.value?.mainIndicator?.map((item) => {
105
- return { label: item.key, value: round(item.data[activeIndex.value]), color: item.color };
106
- }) || []
107
- );
108
- });
109
- const subIndicatorTips = computed(() => {
110
- return klineData.value?.subIndicator?.map((item) => ({ label: item.key, color: item.color, value: item.data[activeIndex.value] || "-" })) || [];
111
- });
112
-
113
- // 图表: 初始化
114
- const initChart = () => {
115
- // 检查DOM元素是否存在
116
- if (!mainChartRef.value) {
117
- console.warn("[K线] 主图DOM元素不存在,取消初始化");
118
- return false;
119
- }
120
- if (mainChartIns) return true;
121
-
122
- try {
123
- // 主图
124
- mainChartIns = echarts.init(mainChartRef.value);
125
- mainChartIns.on(
126
- "highlight",
127
- debounce((params) => {
128
- if (params.dataIndex) {
129
- activeIndex.value = params.dataIndex;
130
- } else {
131
- activeIndex.value = params.batch?.[0].dataIndex ?? -1;
132
- }
133
- }, 10),
134
- );
135
- mainChartIns.on(
136
- "datazoom",
137
- debounce(async () => {
138
- const { loadCheckCount } = loadKlineConfig;
139
- const { startValue, endValue } = mainChartIns.getOption()?.dataZoom[0] ?? {};
140
- // 当前开始索引 < 阈值边界, 触发加载更多数据
141
- if (isLoadHistory === false && isloadAllHistory === false && startValue < loadCheckCount) await getMoreData("history");
142
- // 当前结束索引 > 阈值边界, 触发加载更多数据
143
- if (isLoadNew === false && isloadAllNew === false && endValue > klineData.value.time.length - loadCheckCount) await getMoreData("new");
144
- getScreenTimeRange();
145
- drawScreenMaxPrice();
146
- }),
147
- );
148
- mainChartIns.on("globalout", () => {
149
- if (mainChartIns) {
150
- const option = mainChartIns.getOption();
151
- activeIndex.value = option.dataZoom[0].endValue;
152
- }
153
- });
154
- mainChartIns.getZr().on("contextmenu", () => {
155
- contextmenuKlineTime.value = klineData.value?.time[activeIndex.value];
156
- });
157
- window.addEventListener("keydown", handleKeyDownEvent);
158
-
159
- // 副图
160
- if (props.userKlineConfig.enable_subChart && subChartRef.value) {
161
- subChartIns = echarts.init(subChartRef.value);
162
- echarts.connect([mainChartIns, subChartIns]);
163
- }
164
-
165
- // DOM缩放
166
- if (mainChartRef.value) {
167
- resizeRo = addResizeListener(mainChartRef.value);
168
- resizeRo.listen(() => {
169
- requestAnimationFrame(() => {
170
- mainChartIns?.resize();
171
- subChartIns?.resize();
172
- sliderChartRef.value?.resize();
173
- });
174
- });
175
- }
176
-
177
- return true;
178
- } catch (error) {
179
- console.error("[st-klinePlus]: 初始化异常:", error);
180
- return false;
181
- }
182
- };
183
- // 图表: 获取数据 [首屏]
184
- const getMainData = async ({ startTime, endTime }) => {
185
- // 加载状态初始化
186
- isLoadHistory = false;
187
- isloadAllHistory = false;
188
- isLoadNew = false;
189
- isloadAllNew = false;
190
- try {
191
- loading.value = true;
192
- const params = {
193
- varietyCode: props.varietyCode,
194
- cycle: props.cycle, // 周期
195
- right: props.varietyStock === 1 ? props.rightType : 0, // 复权方式
196
- contractType: props.varietyStock ? null : props.klineType, // 合约类型
197
- mainIndicatorList: props.indicatorStore.getIndicatorParams(props.mainIndicator),
198
- subIndicator: subIndicator.value,
199
- deleteFirstNumber: props.deleteFirstNumber,
200
- withoutPermission: props.withoutPermission,
201
- };
202
- // Echarts如果一屏如果正好展示了所有数据, 是无法缩放触发加载更多的
203
- // 所以在请求中需要对开始和结束时间进行延伸, 但是首屏的展示时间定位还是会以传入的开始结束时间为准
204
- if (["1", "2", "3", "4", "5"].includes(props.cycle)) {
205
- Object.assign(params, {
206
- startTime: dayjs(startTime).subtract(1, "day").format("YYYY-MM-DD HH:mm:ss"),
207
- endTime: dayjs(endTime).add(1, "day").format("YYYY-MM-DD HH:mm:ss"),
208
- });
209
- } else if (["6", "7"].includes(props.cycle)) {
210
- Object.assign(params, {
211
- startTime: dayjs(startTime).subtract(50, "day").format("YYYY-MM-DD HH:mm:ss"),
212
- endTime: dayjs(endTime).add(50, "day").format("YYYY-MM-DD HH:mm:ss"),
213
- });
214
- } else {
215
- Object.assign(params, {
216
- startTime: dayjs(startTime).subtract(10, "month").format("YYYY-MM-DD HH:mm:ss"),
217
- endTime: dayjs(endTime).add(10, "month").format("YYYY-MM-DD HH:mm:ss"),
218
- });
219
- }
220
- const { body } = await request.post("/middleLayer/kline/getKline", params);
221
- klineData.value = body ?? { data: [], mainIndicator: [], subIndicator: [], time: [] };
222
-
223
- // 找到匹配对应传入的开始, 结束时间的数据索引, 绘制图表
224
- let startValue = -1;
225
- let endValue = -1;
226
- for (let index = 0; index < klineData.value.time.length; index++) {
227
- const day = dayjs(klineData.value.time[index]).format("YYYY-MM-DD");
228
- if (startValue !== -1 && endValue !== -1) break;
229
- if (startValue === -1) {
230
- if (new Date(day) === new Date(startTime)) startValue = index;
231
- else if (new Date(day) > new Date(startTime)) startValue = index - 1;
232
- }
233
- if (endValue === -1) {
234
- if (new Date(day) === new Date(endTime)) endValue = index;
235
- else if (new Date(day) > new Date(endTime)) endValue = index - 1;
236
- }
237
- }
238
- if (endValue === -1) endValue = klineData.value.time.length - 1;
239
- // const startValue = klineData.value.time.findIndex((item) => new Date(item) >= new Date(startTime));
240
- // const endValue = klineData.value.time.findIndex((item) => new Date(item) >= new Date(endTime));
241
- draw({
242
- startValue: startValue === -1 ? 0 : startValue,
243
- endValue: endValue === -1 ? klineData.value.time.length - 1 : endValue,
244
- });
245
- } finally {
246
- loading.value = false;
247
- }
248
- };
249
- // 图表: 获取数据 [更多]
250
- const getMoreData = async (type) => {
251
- const { loadAddCount } = loadKlineConfig;
252
- switch (type) {
253
- case "history": {
254
- isLoadHistory = true;
255
- const params = {
256
- varietyCode: props.varietyCode,
257
- cycle: props.cycle,
258
- right: props.varietyStock === 1 ? props.rightType : 0, // 复权方式
259
- contractType: props.varietyStock ? null : props.klineType,
260
- endTime: klineData.value.time[0],
261
- limit: loadAddCount,
262
- mainIndicatorList: props.indicatorStore.getIndicatorParams(props.mainIndicator),
263
- subIndicator: subIndicator.value,
264
- deleteFirstNumber: props.deleteFirstNumber,
265
- withoutPermission: props.withoutPermission,
266
- };
267
- const { body } = await request.post("/middleLayer/kline/getKline", params);
268
- // 合并数据
269
- klineData.value = mergeklineData(body, klineData.value);
270
- // 判断是否加载完全部的历史数据
271
- if (body.data.length < loadAddCount) {
272
- console.log("[K线] 获取更多数据: 左侧已全部获取完毕, 关闭");
273
- isloadAllHistory = true;
274
- }
275
- isLoadHistory = false;
276
- break;
277
- }
278
- case "new": {
279
- isLoadNew = true;
280
- const params = {
281
- varietyCode: props.varietyCode,
282
- cycle: props.cycle, // 周期
283
- right: props.varietyStock === 1 ? props.rightType : 0, // 复权方式
284
- contractType: props.varietyStock ? null : props.klineType, // 合约类型
285
- startTime: klineData.value.time[klineData.value.time.length - 1], // 开始时间
286
- limit: loadAddCount, // 查询K线数量
287
- mainIndicatorList: props.indicatorStore.getIndicatorParams(props.mainIndicator),
288
- subIndicator: subIndicator.value,
289
- deleteFirstNumber: props.deleteFirstNumber,
290
- withoutPermission: props.withoutPermission,
291
- };
292
- const { body } = await request.post("/middleLayer/kline/getKline", params);
293
- // 合并数据
294
- klineData.value = mergeklineData(klineData.value, body);
295
- // 判断是否加载完全部的新数据
296
- if (body.data.length < loadAddCount) {
297
- console.log("[K线] 获取更多数据: 右侧已全部获取完毕, 关闭");
298
- isloadAllNew = true;
299
- }
300
- isLoadNew = false;
301
- break;
302
- }
303
- }
304
-
305
- // 绘制图表 (更新载入更多数据后的定位索引)
306
- const { startValue, endValue } = mainChartIns.getOption()?.dataZoom[0] ?? {};
307
- const klineDataTime = mainChartIns.getOption()?.xAxis[0].data;
308
- const newStartValue = klineData.value.time.findIndex((item) => new Date(item) >= new Date(klineDataTime[startValue]));
309
- const newEndValue = klineData.value.time.findIndex((item) => new Date(item) >= new Date(klineDataTime[endValue]));
310
- draw({
311
- startValue: newStartValue === -1 ? 0 : newStartValue,
312
- endValue: newEndValue === -1 ? klineData.value.time.length - 1 : newEndValue,
313
- });
314
- };
315
- // 图表: 获取数据 [当屏首末时间范围]
316
- const getScreenTimeRange = () => {
317
- if (!klineData.value.time.length) return (screenTimeRange.value = []);
318
- const { time } = klineData.value;
319
- const { startValue, endValue } = mainChartIns.getOption()?.dataZoom[0] ?? {};
320
- const startTime = time[startValue];
321
- const endTime = time[endValue];
322
- if (["6", "7", "8"].includes(props.cycle)) {
323
- screenTimeRange.value = [dayjs(startTime).format("YYYY-MM-DD"), dayjs(endTime).format("YYYY-MM-DD")];
324
- } else {
325
- screenTimeRange.value = [startTime, endTime];
326
- }
327
- };
328
-
329
- // 图表: 绘制(主流程)
330
- const draw = (params = { startValue: 0, endValue: 0 }) => {
331
- const isInitialized = initChart();
332
- // 确保图表存在
333
- if (!isInitialized || !mainChartIns) {
334
- return console.warn("[st-klinePlus]: 图表未准备就绪, 跳过绘制");
335
- }
336
- // 检查数据是否存在
337
- if (!klineData.value.time.length) {
338
- return console.warn("[st-klinePlus]: 图表无数据, 跳过绘制");
339
- }
340
-
341
- const { maxValueSpan } = loadKlineConfig;
342
- const { time, data, mainIndicator } = klineData.value;
343
-
344
- // 如果超过了最大根数限制, 要求endValue不变, startValue进行变换
345
- const startValue = params.endValue - params.startValue > maxValueSpan ? params.endValue - maxValueSpan : params.startValue;
346
- const endValue = params.endValue;
347
-
348
- // 配置: 指标线
349
- const indicatorSeries = mainIndicator.map((item) => {
350
- return {
351
- name: item.key,
352
- type: "line",
353
- silent: true,
354
- symbol: "none",
355
- data: item.data,
356
- lineStyle: {
357
- width: item.width || 1,
358
- },
359
- itemStyle: {
360
- color: item.color,
361
- },
362
- };
363
- });
364
- // 配置数据: 净值曲线
365
- const { netPositionLineData } = handleNetPositionLine(props.netPositionData, props.cycle);
366
- // 配置数据: 成交收益率曲线
367
- const { tradeIncomeRateLineData } = handleTradeIncomeRateLine(props.tradeIncomeRateData, props.cycle, props.incomeType, time);
368
- // 配置数据: 成交点位 + 连线
369
- const { tradePointData, tradeLineData } = handleMarkPointTradeLog(props.tradeLog, props.cycle, props.sellBuy, time, data, props.userKlineConfig);
370
-
371
- /**
372
- * @description: 渲染图表
373
- */
374
- mainChartIns.setOption(
375
- {
376
- animation: false,
377
- grid: {
378
- top: "40px",
379
- left: "60px",
380
- right: "20px",
381
- bottom: "20px",
382
- },
383
- dataZoom: [
384
- {
385
- type: "inside",
386
- startValue,
387
- endValue,
388
- minValueSpan: loadKlineConfig.minValueSpan,
389
- maxValueSpan: loadKlineConfig.maxValueSpan,
390
- },
391
- ],
392
- tooltip: {
393
- trigger: "axis",
394
- appendToBody: true,
395
- confine: true,
396
- axisPointer: {
397
- type: "cross",
398
- label: {
399
- rich: {},
400
- formatter: (data) => {
401
- const { axisDimension, value } = data;
402
- if (axisDimension === "x") {
403
- if (["6", "7", "8"].includes(props.cycle)) {
404
- return dayjs(value).format("YYYY-MM-DD");
405
- } else {
406
- return value;
407
- }
408
- } else {
409
- return String(round(value));
410
- }
411
- },
412
- },
413
- },
414
- formatter: (params) => {
415
- if (!params?.length) return null;
416
- let body = "";
417
- params.forEach((item) => {
418
- // 资产持仓净值
419
- if (item.seriesName === "netPosition" && item.data !== null) {
420
- body += `<div>资产持仓净值: ${item.data[1]}</div>`;
421
- }
422
- // 成交数据
423
- if (item.componentSubType === "candlestick") {
424
- // 买卖点
425
- if (props.sellBuy === 0) {
426
- // 买卖点渲染逻辑
427
- const sellBuy = tradePointData.filter((i) => i?.coord[0] === item.axisValue);
428
- let buy = 0;
429
- let sell = 0;
430
- sellBuy.forEach((i) => {
431
- const { tradeType, amount } = i.customData;
432
- if (tradeType === "买") {
433
- buy += amount;
434
- }
435
- if (tradeType === "卖") {
436
- sell += amount;
437
- }
438
- });
439
- if (buy) body += `<div>买: ${buy}</div>`;
440
- if (sell) body += `<div>卖: ${sell}</div>`;
441
- }
442
- // 开平点
443
- else {
444
- // 开平点渲染逻辑
445
- tradePointData.forEach((i) => {
446
- if (i?.coord[0] === item.axisValue) {
447
- const { tradeType, amount, profitAndLoss, openPriceAll, closePriceAll } = i.customData;
448
- let tooltip = `<div>${tradeType}: ${amount}手</div>`;
449
- // 开仓-tooltip: 开仓价
450
- if (tradeType.includes("开")) {
451
- tooltip += `<div>开仓价: ${(openPriceAll / amount)?.toFixed(2)}</div>`;
452
- }
453
- // 平仓-tooltip: 平仓价, 盈亏, 盈亏比率
454
- if (tradeType.includes("平")) {
455
- const openPrice = (openPriceAll / amount)?.toFixed(2);
456
- const closePrice = (closePriceAll / amount)?.toFixed(2);
457
- tooltip += `<div>平仓价: ${closePrice}</div>`;
458
- tooltip += `<div>盈亏: ${profitAndLoss?.toFixed(2)}</div>`;
459
- tooltip += `<div>盈亏比率: ${(((closePrice - openPrice) / openPrice) * 100)?.toFixed(2)} %</div>`;
460
- }
461
- body += tooltip;
462
- }
463
- });
464
- }
465
- }
466
- });
467
- if (!body) return null;
468
- let time = params[0].axisValue;
469
- if (["6", "7", "8"].includes(props.cycle)) time = dayjs(time).format("YYYY-MM-DD");
470
- return `
471
- <div>
472
- <span style="font-weight: bold;">${time}</span>
473
- ${body}
474
- </div>
475
- `;
476
- },
477
- },
478
- xAxis: {
479
- show: true,
480
- type: "category",
481
- data: time,
482
- splitLine: {
483
- show: false,
484
- },
485
- axisLabel: {
486
- formatter: (value) => {
487
- if (["6", "7", "8"].includes(props.cycle)) {
488
- return dayjs(value).format("YYYY-MM-DD");
489
- } else {
490
- return value;
491
- }
492
- },
493
- },
494
- },
495
- yAxis: [
496
- {
497
- type: "value",
498
- axisLine: {
499
- show: true,
500
- },
501
- splitLine: {
502
- show: true,
503
- lineStyle: {
504
- type: "dotted",
505
- color: "#333",
506
- },
507
- },
508
- min: (value) => {
509
- const { min, max } = value;
510
- const interval = Math.abs((max - min) / 10);
511
- return round(min - interval);
512
- },
513
- max: (value) => {
514
- const { min, max } = value;
515
- const interval = Math.abs((max - min) / 10);
516
- return round(max + interval);
517
- },
518
- },
519
- {
520
- show: false,
521
- min: (value) => {
522
- const { min, max } = value;
523
- const interval = Math.abs((max - min) / 10);
524
- return min - interval;
525
- },
526
- max: (value) => {
527
- const { min, max } = value;
528
- const interval = Math.abs((max - min) / 10);
529
- return max + interval;
530
- },
531
- },
532
- ],
533
- series: [
534
- // K线
535
- {
536
- type: "candlestick",
537
- name: "kLine",
538
- data,
539
- markPoint: { data: [...tradePointData] },
540
- markLine: { data: [...tradeLineData] },
541
- itemStyle: {
542
- color: "transparent",
543
- color0: "#00FFFF",
544
- borderColor: "#FF0000",
545
- borderColor0: "#00FFFF",
546
- borderWidth: 1,
547
- },
548
- },
549
- // 净值曲线
550
- {
551
- type: "line",
552
- name: "netPosition",
553
- data: netPositionLineData,
554
- symbol: "none",
555
- yAxisIndex: 1,
556
- connectNulls: true,
557
- itemStyle: {
558
- color: "#666666",
559
- },
560
- lineStyle: {
561
- width: 2,
562
- },
563
- },
564
- // 交易收益率曲线
565
- {
566
- type: "line",
567
- name: "tradeIncomeRate",
568
- data: tradeIncomeRateLineData,
569
- symbol: "none",
570
- yAxisIndex: 1,
571
- connectNulls: false,
572
- itemStyle: {
573
- color: "#666666",
574
- },
575
- lineStyle: {
576
- width: 2,
577
- },
578
- },
579
- // 指标线
580
- ...indicatorSeries,
581
- ],
582
- toolbox: {
583
- show: false,
584
- },
585
- brush: {
586
- xAxisIndex: "all",
587
- brushLink: "all",
588
- transformable: false,
589
- outOfBrush: {
590
- colorAlpha: 2,
591
- },
592
- brushStyle: {
593
- color: "rgba(255,255,255,0.1)",
594
- borderColor: "rgba(255,255,255,0.4)",
595
- },
596
- },
597
- },
598
- true,
599
- );
600
- if (props.userKlineConfig.enable_subChart) {
601
- subChartIns.setOption(getSubOptions(klineData.value, startValue, endValue), true);
602
- }
603
-
604
- /**
605
- * @description: 善后处理
606
- */
607
- {
608
- getScreenTimeRange();
609
- drawScreenMaxPrice();
610
- activeIndex.value = endValue;
611
- }
612
-
613
- /**
614
- * @description: 数据框选: brush
615
- * 由于brush配置中, 如果有一边的X轴时间不在已载入数据中, 整个都会失效, 所以这里使用的是匹配算法
616
- */
617
- {
618
- const brushAreas = [];
619
- // 1. 成交范围 (灰白色)
620
- if (props.userKlineConfig.enable_tradeLogBrush && tradePointData.length) {
621
- const [startTime, endTime] = [tradePointData[0].coord[0], tradePointData.at(-1).coord[0]];
622
- const coordRange = normalizeToKlineTimeByMatch(time, [startTime, endTime], props.cycle);
623
- brushAreas.push({
624
- brushId: "开平仓范围",
625
- brushType: "lineX",
626
- xAxisIndex: 0,
627
- coordRange,
628
- });
629
- }
630
- // 2. 同步刷选范围 (蓝色)
631
- if (props.syncBrushRange.cycleList.includes(props.cycle)) {
632
- const { startTime, endTime } = props.syncBrushRange;
633
- const coordRange = normalizeToKlineTimeByMatch(time, [startTime, endTime], props.cycle);
634
- brushAreas.push({
635
- brushId: "同步刷选范围",
636
- brushType: "lineX",
637
- xAxisIndex: 0,
638
- coordRange,
639
- brushStyle: {
640
- fill: "rgba(64, 158, 255, 0.2)",
641
- stroke: "rgba(64, 158, 255, 0.8)",
642
- lineWidth: 1,
643
- },
644
- });
645
- }
646
- // 3. 强制定位高亮 (紫色)
647
- if (props.positionTime) {
648
- const [startTime, endTime] = [dayjs(props.positionTime).format("YYYY-MM-DD 00:00:00"), dayjs(props.positionTime).format("YYYY-MM-DD 23:59:59")];
649
- const coordRange = normalizeToKlineTimeByMatch(time, [startTime, endTime], props.cycle);
650
- brushAreas.push({
651
- brushId: "强制定位高亮",
652
- brushType: "lineX",
653
- xAxisIndex: 0,
654
- coordRange,
655
- brushStyle: {
656
- fill: "rgba(217, 179, 255, 0.2)",
657
- stroke: "rgba(217, 179, 255, 0.8)",
658
- lineWidth: 1,
659
- },
660
- });
661
- }
662
- mainChartIns.dispatchAction({
663
- type: "brush",
664
- areas: brushAreas,
665
- });
666
- }
667
- };
668
- // 图表: 绘制(当屏最高价)
669
- const drawScreenMaxPrice = () => {
670
- if (!props.userKlineConfig.enable_showScreenMaxPrice || !klineData.value.time.length) return;
671
-
672
- const { data } = klineData.value;
673
- const originOption = mainChartIns.getOption();
674
- const { startValue, endValue } = originOption?.dataZoom[0] ?? {};
675
-
676
- let maxPrice = 0;
677
- let maxPriceIndex = startValue;
678
-
679
- for (let i = startValue; i <= endValue; i++) {
680
- if (Number(data[i][3]) >= maxPrice) {
681
- maxPrice = Number(data[i][3]);
682
- maxPriceIndex = i;
683
- }
684
- }
685
-
686
- const filterSeries = originOption.series?.filter((item) => item.name !== "maxPrice") || [];
687
- const dataLength = endValue - startValue;
688
- const positionInView = (maxPriceIndex - startValue) / dataLength;
689
- let position = "right";
690
- let formatter = `←${maxPrice}`;
691
-
692
- if (positionInView > 0.7) {
693
- position = "left";
694
- formatter = `${round(maxPrice)}→`;
695
- } else {
696
- position = "right";
697
- formatter = `←${round(maxPrice)}`;
698
- }
699
-
700
- // 关键:只更新 series,不更新其他配置
701
- mainChartIns?.setOption(
702
- {
703
- series: [
704
- ...filterSeries,
705
- {
706
- type: "line",
707
- name: "maxPrice",
708
- data: [],
709
- lineStyle: { opacity: 0 },
710
- markPoint: {
711
- symbol: "circle",
712
- symbolSize: 1,
713
- itemStyle: {
714
- color: "transparent",
715
- borderColor: "transparent",
716
- },
717
- z: 100,
718
- zlevel: 10,
719
- label: {
720
- show: true,
721
- position,
722
- formatter,
723
- color: "#fff",
724
- fontSize: 12,
725
- padding: [4, -4],
726
- },
727
- data: [
728
- {
729
- name: "最高点",
730
- coord: [maxPriceIndex, maxPrice],
731
- },
732
- ],
733
- },
734
- },
735
- ],
736
- },
737
- {
738
- notMerge: false, // 合并模式
739
- replaceMerge: [], // 不替换任何配置
740
- },
741
- );
742
- };
743
-
744
- // 拖拽轴: 拖拽回调
745
- const handleSliderChange = (params) => {
746
- const [startTime, endTime] = checkTimeInterval({
747
- startTime: params.startTime,
748
- endTime: params.endTime,
749
- cycle: props.cycle,
750
- });
751
- getMainData({ startTime, endTime });
752
- };
753
- // 键盘控制 [↑↓←→缩放平移]
754
- const handleKeyDownEvent = ({ code, ctrlKey }) => {
755
- if (!mainChartIns) return;
756
- if (!(ctrlKey || isHover.value)) return;
757
- const { xAxis, dataZoom } = mainChartIns.getOption();
758
- const { data: xAxisData } = xAxis?.[0] ?? { data: [] };
759
- let { startValue, endValue } = dataZoom?.[0] ?? {};
760
- if (!xAxisData?.length) return;
761
- // 键位cb
762
- switch (code) {
763
- // ↑ 放大
764
- case "ArrowUp": {
765
- if (endValue - startValue < 5) return;
766
- const diff = Math.floor((endValue - startValue) / 2) + 1;
767
- startValue = startValue + diff;
768
- if (endValue - startValue < 5) startValue = endValue - 4;
769
- break;
770
- }
771
- // ↓ 缩小
772
- case "ArrowDown": {
773
- const diff = Math.min(500, endValue - startValue);
774
- startValue = startValue - diff - 1;
775
- break;
776
- }
777
- // ← 左移
778
- case "ArrowLeft": {
779
- if (startValue > 0) {
780
- startValue -= 1;
781
- endValue -= 1;
782
- }
783
- if (activeIndex.value > 0) {
784
- activeIndex.value -= 1;
785
- }
786
- break;
787
- }
788
- // → 右移
789
- case "ArrowRight": {
790
- if (endValue < xAxisData.length - 1) {
791
- startValue += 1;
792
- endValue += 1;
793
- }
794
- if (isHover.value && activeIndex.value < xAxisData.length - 1) {
795
- activeIndex.value += 1;
796
- }
797
- break;
798
- }
799
- }
800
- // 1. 更新图表当屏位置
801
- mainChartIns.dispatchAction({
802
- type: "dataZoom",
803
- startValue,
804
- endValue,
805
- });
806
- // 2. 更新图表指示器位置
807
- mainChartIns.dispatchAction({
808
- type: "updateAxisPointer",
809
- seriesIndex: 0,
810
- dataIndex: isHover.value ? activeIndex.value : null,
811
- });
812
- // 3. 更新图表高亮索引
813
- mainChartIns.dispatchAction({
814
- type: "highlight",
815
- dataIndex: isHover.value ? activeIndex.value : endValue,
816
- });
817
- };
818
-
819
- onMounted(() => {
820
- const [startTime, endTime] = props.initTimeRange;
821
- getMainData({ startTime, endTime });
822
- });
823
- // 监控: [品种, 初始时间范围] => 重加载K线 (缩放初始化)
824
- watch(
825
- () => [props.varietyCode, props.initTimeRange],
826
- () => {
827
- const [startTime, endTime] = props.initTimeRange;
828
- getMainData({ startTime, endTime });
829
- },
830
- { deep: true },
831
- );
832
- // 监控: [周期选项] => 重加载K线 (缩放不变+校验时间间隔)
833
- watch(
834
- () => [props.cycle],
835
- () => {
836
- const { startValue, endValue } = mainChartIns.getOption()?.dataZoom[0] ?? {};
837
- const [startTime, endTime] = checkTimeInterval({
838
- startTime: klineData.value.time[startValue],
839
- endTime: klineData.value.time[endValue],
840
- cycle: props.cycle,
841
- type: "middle",
842
- });
843
- getMainData({ startTime, endTime });
844
- },
845
- { deep: true },
846
- );
847
- // 监控: [主指标, 副指标, 指标配置, 复权选项, 常用选项] => 重加载K线 (缩放不变)
848
- watch(
849
- () => [props.mainIndicator, subIndicator.value, props.indicatorStore?.filterIndicator, props.indicatorStore?.customIndicator, props.rightType, props.klineType],
850
- () => {
851
- const { startValue, endValue } = mainChartIns.getOption()?.dataZoom[0] ?? {};
852
- const [startTime, endTime] = [klineData.value.time[startValue], klineData.value.time[endValue]];
853
- getMainData({ startTime, endTime });
854
- },
855
- { deep: true },
856
- );
857
- // 监控: [成交点类型, 成交数据, 净值数据, 收益计算类型, 成交收益率数据] => 重绘K线 (缩放不变)
858
- watch(
859
- () => [props.sellBuy, props.tradeLog, props.netPositionData, props.incomeType, props.tradeIncomeRateData],
860
- () => {
861
- const { startValue, endValue } = mainChartIns.getOption()?.dataZoom[0] ?? {};
862
- draw({ startValue, endValue });
863
- },
864
- { deep: true },
865
- );
866
- onUnmounted(() => {
867
- // 解绑
868
- mainChartIns?.off("highlight");
869
- mainChartIns?.off("globalout");
870
- mainChartIns?.off("datazoom");
871
- mainChartIns?.getZr()?.off("contextmenu");
872
- window.removeEventListener("keydown", handleKeyDownEvent);
873
- // 销毁
874
- mainChartIns?.dispose();
875
- subChartIns?.dispose();
876
- resizeRo?.dispose();
877
- resizeRo = null;
878
- });
879
- defineExpose({
880
- screenTimeRange,
881
- contextmenuKlineTime,
882
- reDraw: () => {
883
- const { startValue, endValue } = mainChartIns.getOption()?.dataZoom[0] ?? {};
884
- draw({ startValue, endValue });
885
- },
886
- });
887
- </script>
888
-
889
- <template>
890
- <div
891
- class="kline-plus"
892
- v-loading="loading"
893
- @mousemove="isHover = true"
894
- @mouseout="isHover = false"
895
- >
896
- <!-- 主图 -->
897
- <div class="main-chart">
898
- <div class="indicator">
899
- <Tips :data="mainTips" />
900
- <Tips :data="mainIndicatorTips" />
901
- </div>
902
- <template v-if="userKlineConfig.enable_showScreenTimeRange">
903
- <span class="screen-time-range"> {{ screenTimeRange[0] }} - {{ screenTimeRange[1] }} </span>
904
- </template>
905
- <!-- K线 -->
906
- <div
907
- ref="mainChartRef"
908
- class="chart"
909
- />
910
- </div>
911
- <!-- 副图 -->
912
- <template v-if="userKlineConfig.enable_subChart">
913
- <div class="sub-chart">
914
- <div class="indicator">
915
- <div class="title">
916
- <span>{{ subIndicator }}</span>
917
- <el-select
918
- v-model="subIndicator"
919
- style="width: 100px; margin-right: 4px; height: 25px"
920
- size="small"
921
- class="element-dark"
922
- popper-class="element-dark"
923
- >
924
- <el-option
925
- v-for="(item, index) in indicatorStore.subIndicatorList"
926
- :key="index"
927
- :label="item.label"
928
- :value="item.value"
929
- />
930
- </el-select>
931
- </div>
932
- <Tips :data="subIndicatorTips" />
933
- </div>
934
- <div
935
- ref="subChartRef"
936
- class="chart"
937
- />
938
- </div>
939
- </template>
940
- <!-- 拖拽轴 -->
941
- <template v-if="userKlineConfig.enable_sliderChart">
942
- <div class="slider-chart">
943
- <SliderChart
944
- ref="sliderChartRef"
945
- :screenTimeRange="screenTimeRange"
946
- :varietyCode="varietyCode"
947
- :varietyStock="varietyStock"
948
- :rightType="rightType"
949
- :klineType="klineType"
950
- :deleteFirstNumber="props.deleteFirstNumber"
951
- :withoutPermission="props.withoutPermission"
952
- @change="handleSliderChange"
953
- />
954
- </div>
955
- </template>
956
- <!-- 空数据 -->
957
- <el-empty
958
- v-if="!klineData.time.length"
959
- class="empty"
960
- description="暂无数据"
961
- />
962
- </div>
963
- </template>
964
-
965
- <style lang="scss" scoped>
966
- .kline-plus {
967
- width: 100%;
968
- height: 100%;
969
- position: relative;
970
- color-scheme: dark;
971
- display: flex;
972
- flex-direction: column;
973
- // 主图
974
- .main-chart {
975
- width: 100%;
976
- height: calc((100% - 40px) * 0.8);
977
- background-color: black;
978
- flex: 1;
979
- .indicator {
980
- height: 55px;
981
- box-sizing: border-box;
982
- padding-left: 10px;
983
- padding-top: 4px;
984
- }
985
- .screen-time-range {
986
- color: white;
987
- font-size: 12px;
988
- line-height: 12px;
989
- position: absolute;
990
- right: 10px;
991
- top: 40px;
992
- }
993
- .chart {
994
- width: 100%;
995
- height: calc(100% - 55px);
996
- box-sizing: border-box;
997
- }
998
- }
999
- // 副图
1000
- .sub-chart {
1001
- width: 100%;
1002
- height: calc((100% - 40px) * 0.2);
1003
- .indicator {
1004
- padding-left: 10px;
1005
- display: flex;
1006
- align-items: center;
1007
- .title {
1008
- display: flex;
1009
- align-items: center;
1010
- span {
1011
- font-size: 12px;
1012
- line-height: 24px;
1013
- color: white;
1014
- }
1015
- :deep(.el-select) {
1016
- width: 16px !important;
1017
- height: 16px !important;
1018
- margin-left: 4px;
1019
- .el-select__icon {
1020
- margin-left: 0;
1021
- color: black;
1022
- width: 16px;
1023
- height: 16px;
1024
- }
1025
- .el-select__wrapper {
1026
- border-radius: 50%;
1027
- background: #ccc;
1028
- padding: 0;
1029
- gap: 0;
1030
- min-height: 12px;
1031
- line-height: 12px;
1032
- }
1033
- }
1034
- }
1035
- }
1036
- .chart {
1037
- width: 100%;
1038
- height: calc(100% - 24px);
1039
- }
1040
- }
1041
- // 拖拽轴
1042
- .slider-chart {
1043
- width: 100%;
1044
- height: 40px;
1045
- }
1046
- // 空数据
1047
- .empty {
1048
- position: absolute;
1049
- top: 0;
1050
- left: 0;
1051
- width: 100%;
1052
- height: 100%;
1053
- padding: 0;
1054
- background-color: rgb(0, 0, 0);
1055
- --el-empty-fill-color-0: var(--el-color-black);
1056
- --el-empty-fill-color-1: #4b4b52;
1057
- --el-empty-fill-color-2: #36383d;
1058
- --el-empty-fill-color-3: #1e1e20;
1059
- --el-empty-fill-color-4: #262629;
1060
- --el-empty-fill-color-5: #202124;
1061
- --el-empty-fill-color-6: #212224;
1062
- --el-empty-fill-color-7: #1b1c1f;
1063
- --el-empty-fill-color-8: #1c1d1f;
1064
- --el-empty-fill-color-9: #18181a;
1065
- }
1066
- }
1067
- </style>