@gadmin2n/schematics 0.0.72 → 0.0.74

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 (173) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.js +2 -0
  3. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/job.prisma +62 -0
  4. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/system.prisma +0 -21
  5. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/workflow.prisma +171 -0
  6. package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/AgendaJob.ts +60 -0
  7. package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/Event.ts +1 -1
  8. package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/WorkflowEventOutbox.ts +62 -0
  9. package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/WorkflowNodeInstance.ts +62 -0
  10. package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/WorkflowNodeType.ts +62 -0
  11. package/dist/lib/application/files/gadmin2-game-angle-demo/server/.env +5 -0
  12. package/dist/lib/application/files/gadmin2-game-angle-demo/server/package.json +5 -4
  13. package/dist/lib/application/files/gadmin2-game-angle-demo/server/prisma.config.ts +14 -7
  14. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/index.ts +4 -0
  15. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/permissions.ts +49 -3
  16. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-node-types.ts +746 -0
  17. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflows.ts +786 -0
  18. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/agenda/agenda.controller.ts +6 -0
  19. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/agenda/agenda.service.ts +79 -0
  20. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/agendaJob/agendaJob.controller.spec.ts +20 -0
  21. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/agendaJob/agendaJob.controller.ts +145 -0
  22. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/agendaJob/agendaJob.module.ts +10 -0
  23. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/{canvas/canvas.service.spec.ts → agendaJob/agendaJob.service.spec.ts} +71 -65
  24. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/agendaJob/agendaJob.service.ts +83 -0
  25. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/index.ts +2 -1
  26. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/temporal.module.ts +9 -0
  27. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/temporal.service.ts +100 -0
  28. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-execution.dto.ts +19 -0
  29. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-export.dto.ts +43 -0
  30. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-export.service.ts +317 -0
  31. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-node-type.controller.ts +16 -0
  32. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-node-type.service.ts +13 -0
  33. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.ts +220 -0
  34. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.dto.ts +82 -0
  35. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.module.ts +16 -0
  36. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.ts +505 -0
  37. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowEventOutbox/workflowEventOutbox.controller.spec.ts +22 -0
  38. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowEventOutbox/workflowEventOutbox.controller.ts +147 -0
  39. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowEventOutbox/workflowEventOutbox.module.ts +10 -0
  40. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowEventOutbox/workflowEventOutbox.service.spec.ts +356 -0
  41. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowEventOutbox/workflowEventOutbox.service.ts +110 -0
  42. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeInstance/workflowNodeInstance.controller.spec.ts +22 -0
  43. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeInstance/workflowNodeInstance.controller.ts +216 -0
  44. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeInstance/workflowNodeInstance.module.ts +10 -0
  45. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeInstance/workflowNodeInstance.service.spec.ts +356 -0
  46. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeInstance/workflowNodeInstance.service.ts +168 -0
  47. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeType/workflowNodeType.controller.spec.ts +22 -0
  48. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeType/workflowNodeType.controller.ts +199 -0
  49. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeType/workflowNodeType.module.ts +10 -0
  50. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeType/workflowNodeType.service.spec.ts +348 -0
  51. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowNodeType/workflowNodeType.service.ts +106 -0
  52. package/dist/lib/application/files/gadmin2-game-angle-demo/server/yarn.lock +579 -1082
  53. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/README.md +278 -0
  54. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/config/development-sql.yaml +5 -0
  55. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/docker-compose.yml +25 -0
  56. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/package.json +13 -0
  57. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/sql/create-event-trigger.sql +87 -0
  58. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/.env +7 -0
  59. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/SANDBOX.md +122 -0
  60. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/package-lock.json +4285 -0
  61. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/package.json +28 -0
  62. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/__tests__/activities/code-execute.test.ts +44 -0
  63. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/__tests__/activities/http-request.test.ts +87 -0
  64. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/__tests__/helpers.test.ts +225 -0
  65. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/__tests__/node-type-consistency.test.ts +101 -0
  66. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/code-execute.ts +51 -0
  67. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/db-execute.ts +85 -0
  68. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/db-query.ts +35 -0
  69. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/http-request.ts +54 -0
  70. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/index.ts +6 -0
  71. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/reporting.ts +62 -0
  72. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/send-notification.ts +47 -0
  73. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/config.ts +13 -0
  74. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/condition.ts +101 -0
  75. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/context.ts +58 -0
  76. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/graph.ts +184 -0
  77. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/helpers.ts +133 -0
  78. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/node-types.ts +57 -0
  79. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/types.ts +77 -0
  80. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/index.ts +36 -0
  81. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/outbox-poller.ts +226 -0
  82. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/workflows/dsl-workflow.ts +411 -0
  83. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/tsconfig.json +19 -0
  84. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/vitest.config.ts +8 -0
  85. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/yarn.lock +1905 -0
  86. package/dist/lib/application/files/gadmin2-game-angle-demo/web/package-lock.json +17555 -0
  87. package/dist/lib/application/files/gadmin2-game-angle-demo/web/package.json +5 -2
  88. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/App.tsx +1 -0
  89. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/sider.tsx +5 -1
  90. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/title.tsx +1 -1
  91. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.tsx +63 -0
  92. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/dev-shell/DevShell.tsx +91 -2
  93. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/list.tsx +48 -2
  94. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/show.tsx +43 -2
  95. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/common.json +14 -9
  96. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/common.json +14 -9
  97. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/agenda/index.tsx +309 -56
  98. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/agenda/show.tsx +1 -3
  99. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/agendaJob/create.tsx +108 -0
  100. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/agendaJob/edit.tsx +124 -0
  101. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/agendaJob/index.tsx +4 -0
  102. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/agendaJob/list.tsx +245 -0
  103. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/agendaJob/show.tsx +70 -0
  104. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasListPage.tsx +0 -1
  105. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasPage.tsx +160 -2
  106. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasToolbar.tsx +120 -148
  107. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CodeFloatWindow.tsx +74 -181
  108. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/LivePreview.tsx +15 -13
  109. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/canvasConfigRegistry.tsx +2 -2
  110. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/canvasContextMenuRegistry.tsx +338 -3
  111. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/canvasDefaults.ts +18 -17
  112. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/components/BarChartDataSourceModal.tsx +10 -4
  113. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/components/LineChartDataSourceModal.tsx +10 -4
  114. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/components/{ChartViewerConfigModal.tsx → MultiChartConfigModal.tsx} +30 -18
  115. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/components/MultiChartDataSourceModal.tsx +427 -0
  116. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/components/NumCardDataSourceModal.tsx +10 -4
  117. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/components/PromptModal.tsx +6 -14
  118. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/components/RadarChartDataSourceModal.tsx +10 -4
  119. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/components/TableDataSourceModal.tsx +10 -4
  120. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/components/canvasModalProps.ts +24 -0
  121. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/demos.ts +45 -63
  122. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/CustomNode.tsx +99 -0
  123. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/ExportModal.tsx +87 -0
  124. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/FlowRenderer.tsx +322 -0
  125. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/ImportModal.tsx +175 -0
  126. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodeEditModal.tsx +60 -0
  127. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodePropertyPanel.tsx +1150 -0
  128. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/RunWorkflowModal.tsx +101 -0
  129. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/StatusCards.tsx +198 -0
  130. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/VersionPanel.tsx +81 -0
  131. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/editor.tsx +566 -0
  132. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/hooks/useWorkflowAgent.ts +224 -0
  133. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/index.tsx +524 -0
  134. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instance-detail.tsx +343 -0
  135. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instances.tsx +243 -0
  136. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/components/CreateNodeInstanceModal.tsx +363 -0
  137. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/components/DynamicConfigForm.tsx +154 -0
  138. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/components/NodeInstanceForm.tsx +176 -0
  139. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/create.tsx +77 -0
  140. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/edit.tsx +112 -0
  141. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/index.tsx +305 -0
  142. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/show.tsx +282 -0
  143. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/show.tsx +469 -0
  144. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/types.ts +92 -0
  145. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflowEventOutbox/create.tsx +111 -0
  146. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflowEventOutbox/edit.tsx +127 -0
  147. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflowEventOutbox/index.tsx +4 -0
  148. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflowEventOutbox/list.tsx +254 -0
  149. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflowEventOutbox/show.tsx +74 -0
  150. package/dist/lib/application/files/gadmin2-game-angle-demo/web/yarn.lock +1501 -1199
  151. package/package.json +1 -1
  152. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/app.controller.spec.ts +0 -22
  153. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/BarChart/index.tsx +0 -896
  154. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/ChartSwitcher/index.tsx +0 -219
  155. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/ChartViewer/index.tsx +0 -159
  156. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/Filter/index.tsx +0 -192
  157. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/LineChart/index.tsx +0 -1034
  158. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/NumCard/NumCard.module.css +0 -8
  159. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/NumCard/index.tsx +0 -509
  160. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/NumLineCard/index.tsx +0 -66
  161. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/PieChart/index.tsx +0 -552
  162. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/RadarChart/index.tsx +0 -263
  163. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/Section/index.tsx +0 -35
  164. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/Table/index.tsx +0 -207
  165. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/TreemapChart/index.tsx +0 -382
  166. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/WorldMap/index.tsx +0 -135
  167. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/chart-constants.ts +0 -53
  168. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/icon/InfoIcon.tsx +0 -8
  169. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/icon/index.ts +0 -1
  170. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/map/config.ts +0 -31
  171. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/map/nameMap.json +0 -9
  172. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/map/world.geo.json +0 -39349
  173. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/canvas/metric-info-tooltip/index.tsx +0 -19
@@ -1,1034 +0,0 @@
1
- import React, { useMemo, useEffect } from 'react';
2
- import { Spin, Empty } from 'antd';
3
- import * as echarts from 'echarts';
4
- import { useECharts } from '@/hooks/useECharts';
5
- import { LEGEND_BASE, GRID_VERTICAL, GRID_DUAL_Y } from '../chart-constants';
6
-
7
- // ─── Types ────────────────────────────────────────────────────────────────────
8
-
9
- export type LineChartVariant =
10
- | 'line'
11
- | 'stackLine' // 向后兼容别名 → line
12
- | 'stackArea' // 向后兼容别名 → line + stacked + area
13
- | 'simpleLine' // 向后兼容别名 → line + smooth + area + showGrid=false
14
- | 'mulAreaLine'
15
- | 'dualYAxis';
16
-
17
- /** line / simpleLine 单系列数据 */
18
- export type LineDataSingle = { name: string; value: number | null }[];
19
-
20
- /** stackLine 多系列数据 */
21
- export interface LineDataMulti {
22
- xData: string[];
23
- series: {
24
- name: string;
25
- data: (number | null)[];
26
- [key: string]: any;
27
- }[];
28
- }
29
-
30
- /**
31
- * mulAreaLine 复合面积折线图数据(BG/云厂商 etc.)
32
- * outerKeyList: 外层 key(决定面积色),innerKeyList: 内层 key(决定虚线色)
33
- * series[].name = "outerKey-innerKey",series[].areaColor = outerKey色,series[].lineColor = innerKey色
34
- */
35
- export interface LineDataMulAreaLine extends LineDataMulti {
36
- outerKeyList?: string[];
37
- innerKeyList?: string[];
38
- originData?: any;
39
- }
40
-
41
- /**
42
- * dualYAxis 双 Y 轴混合图(折线 + 柱状)数据。
43
- * yData[0] 对应 Y 轴左侧,yData[1] 对应 Y 轴右侧。
44
- * series[].yAxisIndex 决定数据挂在哪条 Y 轴,series[].type 可为 "line" | "bar"。
45
- */
46
- export interface LineDataDualYAxis {
47
- xData: string[];
48
- yData: [
49
- { name: string; unit: string; max?: number },
50
- { name: string; unit: string; max?: number },
51
- ];
52
- series: {
53
- name: string;
54
- data: (number | null)[];
55
- type: 'line' | 'bar';
56
- yAxisIndex: 0 | 1;
57
- [key: string]: any;
58
- }[];
59
- }
60
-
61
- export type LineChartData =
62
- | LineDataSingle
63
- | LineDataMulti
64
- | LineDataMulAreaLine
65
- | LineDataDualYAxis;
66
-
67
- /** 标注线配置:在指定 Y 轴值处绘制水平红色参考线 */
68
- export interface MarkLineConfig {
69
- value: number;
70
- /** 线条颜色,默认 #fd0100 */
71
- color?: string;
72
- }
73
-
74
- export interface LineChartProps {
75
- // ── 核心 ──────────────────────────────────────────────────────────────────
76
- /** 折线数据,line / simpleLine 用 LineDataSingle,stackLine 用 LineDataMulti,dualYAxis 用 LineDataDualYAxis */
77
- data: LineChartData;
78
- /** 视觉变体:line = 单系列;stackLine = 多系列;stackArea = 多系列面积堆叠(每条线带颜色填充);simpleLine = 迷你趋势线(无轴标签);dualYAxis = 双 Y 轴混合图 */
79
- variant?: LineChartVariant;
80
- /**
81
- * 图表标题,渲染在图表上方。
82
- * - 字符串:套默认样式(居中、12px、bold、#515151)
83
- * - ReactNode:直接渲染,样式由调用方控制
84
- * height 只指图表区域高度,标题额外占空间。
85
- */
86
- title?: React.ReactNode;
87
- /** Y 轴单位,line / stackLine 显示在 tooltip 和 Y 轴 label 中(dualYAxis 使用 yData 中的 unit) */
88
- unit?: string;
89
-
90
- // ── 布局 ──────────────────────────────────────────────────────────────────
91
- /** 图表容器高度,默认 300 */
92
- height?: number | string;
93
- /** 是否显示图例,多系列默认 true,单系列默认 false */
94
- legendShow?: boolean;
95
-
96
- // ── 配置覆盖 ──────────────────────────────────────────────────────────────
97
- /**
98
- * 透传至 ECharts option 的配置(yAxis、xAxis、grid 等),与基准配置浅合并。
99
- * simpleLine 支持 seriesProps 字段(覆盖 series 级配置,如主题色 lineStyle / areaStyle)。
100
- */
101
- chartProps?: Record<string, any>;
102
-
103
- // ── 高级功能 ──────────────────────────────────────────────────────────────
104
- /**
105
- * 仅对 variant="line" 生效。
106
- * 开启后 label 只在 x 轴可见刻度对应的数据点上显示(每隔一个刻度显示一次),
107
- * 避免密集数据时标签重叠。
108
- */
109
- smartLabel?: boolean;
110
-
111
- /**
112
- * 智能 Y 轴模式(默认 false)。
113
- * 开启后自动分析数据最小值/最大值,计算合理的轴范围(两侧加 30% padding),
114
- * 让折线起伏清晰可见,而不是从 0 起始。
115
- * 适用于 variant="line" / "stackLine"。
116
- * 若数据集内所有值相同,或已通过 chartProps.yAxis.min/max 手动指定范围,则 smartYAxis 无效。
117
- */
118
- smartYAxis?: boolean;
119
-
120
- /**
121
- * 标注线配置,在 Y 轴指定值处绘制水平参考线(红色虚线)。
122
- * 适用于 variant="stackLine",如带宽利用率 80% 阈值、网络延迟 100ms 阈值。
123
- */
124
- markLine?: MarkLineConfig;
125
-
126
- // ── 样式开关 ──────────────────────────────────────────────────────────────
127
- /** 是否堆叠(多系列时生效),默认 false */
128
- stacked?: boolean;
129
- /** 是否显示面积填充,默认 false */
130
- area?: boolean;
131
- /** 是否显示网格线(Y轴 splitLine),默认 true */
132
- showGrid?: boolean;
133
- /** 是否显示数据点标记(symbol),默认 false */
134
- showDots?: boolean;
135
- /** 是否显示坐标点数据标签(需 showDots 开启),默认 false */
136
- showLabel?: boolean;
137
- /** 是否使用平滑拟合曲线,默认 false */
138
- smooth?: boolean;
139
- /** 是否显示横纵坐标轴(轴标签+刻度),默认 true */
140
- showAxis?: boolean;
141
-
142
- // ── 状态 ──────────────────────────────────────────────────────────────────
143
- /** 显示 Spin 加载态 */
144
- loading?: boolean;
145
- /** 强制显示空态。未传时根据数据是否为空自动判断 */
146
- empty?: boolean;
147
-
148
- // ── 样式 / 测试 ──────────────────────────────────────────────────────────
149
- style?: React.CSSProperties;
150
- className?: string;
151
- /** data-testid */
152
- testId: string;
153
- /**
154
- * 图表实例就绪回调,回传 ECharts 实例(销毁时传 null)。
155
- * 用于 PieChart ↔ LineChart 联动(如 useChartLink)。
156
- */
157
- onChartReady?: (instance: import('echarts').ECharts | null) => void;
158
- /**
159
- * ECharts 事件绑定(如 click、mouseover),与 useECharts onEvents 对应。
160
- */
161
- onEvents?: Record<
162
- string,
163
- (params: any, chart: import('echarts').ECharts) => void
164
- >;
165
- }
166
-
167
- // ─── 公共常量 ─────────────────────────────────────────────────────────────────
168
-
169
- // LEGEND_BASE, GRID_VERTICAL, GRID_DUAL_Y 来自 ../chart-constants
170
- const DEFAULT_GRID = { left: 60, right: 60 };
171
-
172
- // ─── 空态判断 ─────────────────────────────────────────────────────────────────
173
-
174
- function checkEmpty(data: LineChartData, variant: LineChartVariant): boolean {
175
- if (
176
- variant === 'stackLine' ||
177
- variant === 'stackArea' ||
178
- variant === 'mulAreaLine' ||
179
- variant === 'dualYAxis'
180
- ) {
181
- const d = data as LineDataMulti;
182
- return !d?.xData?.length && !d?.series?.length;
183
- }
184
- // variant='line' 可能是单系列(数组)或多系列(对象)
185
- if (variant === 'line') {
186
- if (Array.isArray(data)) {
187
- return !(data as LineDataSingle)?.length;
188
- }
189
- const d = data as LineDataMulti;
190
- return !d?.xData?.length && !d?.series?.length;
191
- }
192
- return !(data as LineDataSingle)?.length;
193
- }
194
-
195
- // ─── Smart Y Axis ─────────────────────────────────────────────────────────────
196
-
197
- /**
198
- * 根据数据值自动计算合理的 Y 轴 min/max,避免从 0 起始导致起伏不可见。
199
- * 逻辑:找到数据最小/最大值,在两侧各加 30% padding,再取美观整数边界。
200
- */
201
- function computeSmartYAxis(
202
- values: (number | null)[],
203
- ): { min: number; max: number } | null {
204
- const nums = values.filter(
205
- (v): v is number => typeof v === 'number' && isFinite(v),
206
- );
207
- if (nums.length < 2) return null;
208
-
209
- const dataMin = Math.min(...nums);
210
- const dataMax = Math.max(...nums);
211
- const range = dataMax - dataMin;
212
-
213
- if (range === 0) return null; // 所有值相同,让 ECharts 自动处理
214
-
215
- // 两侧各加 30% padding
216
- const padding = range * 0.3;
217
- const rawMin = dataMin - padding;
218
- const rawMax = dataMax + padding;
219
-
220
- // 按 range 量级取整,保证轴刻度美观
221
- const mag = Math.pow(10, Math.floor(Math.log10(range)));
222
- const niceMag = mag >= 1 ? mag : 1;
223
-
224
- const min = Math.floor(rawMin / niceMag) * niceMag;
225
- const max = Math.ceil(rawMax / niceMag) * niceMag;
226
-
227
- return { min, max };
228
- }
229
-
230
- // ─── markLine ECharts 配置构建 ─────────────────────────────────────────────────
231
-
232
- function buildMarkLineData(markLine?: MarkLineConfig) {
233
- if (!markLine) return undefined;
234
- return {
235
- silent: true,
236
- symbol: 'none',
237
- label: { show: false },
238
- lineStyle: {
239
- color: markLine.color ?? '#fd0100',
240
- type: 'solid',
241
- },
242
- data: [{ yAxis: markLine.value }],
243
- };
244
- }
245
-
246
- // ─── Unified Line Option 构建 ────────────────────────────────────────────────
247
-
248
- function isMultiData(
249
- data: LineDataSingle | LineDataMulti,
250
- ): data is LineDataMulti {
251
- return !Array.isArray(data) && 'xData' in (data as any);
252
- }
253
-
254
- function buildUnifiedLineOption(
255
- data: LineDataSingle | LineDataMulti,
256
- unit: string,
257
- chartProps: Record<string, any>,
258
- options: {
259
- stacked?: boolean;
260
- area?: boolean;
261
- showGrid?: boolean;
262
- showDots?: boolean;
263
- showLabel?: boolean;
264
- smooth?: boolean;
265
- showAxis?: boolean;
266
- smartLabel?: boolean;
267
- smartYAxis?: boolean;
268
- markLine?: MarkLineConfig;
269
- },
270
- ): echarts.EChartsCoreOption {
271
- const {
272
- yAxis,
273
- xAxis,
274
- grid = {},
275
- legend = {},
276
- tooltip = {},
277
- seriesProps = {},
278
- } = chartProps;
279
- const {
280
- stacked,
281
- area,
282
- showGrid = true,
283
- showDots,
284
- showLabel,
285
- smooth,
286
- showAxis = true,
287
- smartLabel,
288
- smartYAxis,
289
- markLine,
290
- } = options;
291
- const markLineData = buildMarkLineData(markLine);
292
-
293
- if (isMultiData(data)) {
294
- // ── 多系列模式 ──
295
- const allValues = data?.series?.flatMap((s) => s.data) ?? [];
296
- const smartY =
297
- smartYAxis && !yAxis?.min && !yAxis?.max
298
- ? computeSmartYAxis(allValues)
299
- : null;
300
-
301
- return {
302
- tooltip: {
303
- trigger: 'axis' as const,
304
- valueFormatter: (value: number) => `${value}${unit}`,
305
- ...tooltip,
306
- },
307
- legend: {
308
- ...LEGEND_BASE,
309
- data: data?.series?.map((item) => item?.name),
310
- ...legend,
311
- },
312
- xAxis: {
313
- type: 'category' as const,
314
- boundaryGap: false,
315
- axisTick: { show: false },
316
- axisLine: { show: false },
317
- data: data?.xData,
318
- axisLabel: { show: showAxis, fontSize: '10px', hideOverlap: true },
319
- ...xAxis,
320
- },
321
- grid: { ...GRID_VERTICAL, ...grid },
322
- yAxis: {
323
- position: 'left' as const,
324
- type: 'value' as const,
325
- axisLabel: {
326
- show: showAxis,
327
- fontSize: '10px',
328
- formatter: (value: number) => `${value}${unit}`,
329
- },
330
- axisLine: { show: false },
331
- splitLine: { show: showGrid },
332
- ...(smartY ?? {}),
333
- ...yAxis,
334
- },
335
- series: data?.series?.map(
336
- ({
337
- name,
338
- data: seriesData,
339
- color,
340
- lineStyle,
341
- areaStyle: seriesAreaStyle,
342
- stack,
343
- }) => ({
344
- type: 'line' as const,
345
- smooth: smooth ?? false,
346
- showSymbol: showDots ?? false,
347
- label: {
348
- show: !!(showDots && showLabel),
349
- position: 'top' as const,
350
- fontSize: 10,
351
- },
352
- name,
353
- data: seriesData,
354
- ...(stacked
355
- ? { stack: stack ?? 'stack' }
356
- : stack !== undefined
357
- ? { stack }
358
- : {}),
359
- ...(color !== undefined && { color }),
360
- ...(lineStyle !== undefined && { lineStyle }),
361
- ...(area
362
- ? {
363
- areaStyle: { color: color ?? null, ...(seriesAreaStyle ?? {}) },
364
- }
365
- : seriesAreaStyle !== undefined
366
- ? { areaStyle: seriesAreaStyle }
367
- : {}),
368
- ...(stacked && area
369
- ? {
370
- itemStyle: { color: color ?? null },
371
- lineStyle: { color: color ?? null, ...(lineStyle ?? {}) },
372
- emphasis: {
373
- focus: 'series' as const,
374
- blurScope: 'global' as const,
375
- },
376
- }
377
- : {}),
378
- ...seriesProps,
379
- ...(markLineData !== undefined && { markLine: markLineData }),
380
- }),
381
- ),
382
- };
383
- }
384
-
385
- // ── 单系列模式 ──
386
- const smartY =
387
- smartYAxis && !yAxis?.min && !yAxis?.max
388
- ? computeSmartYAxis(data.map((d) => d?.value))
389
- : null;
390
-
391
- const labelFormatter = smartLabel
392
- ? function (params: any) {
393
- return params.dataIndex % 3 === 0 ? `${params.value}${unit}` : '';
394
- }
395
- : `{c}${unit}`;
396
-
397
- return {
398
- legend: { ...LEGEND_BASE, show: false },
399
- tooltip: {
400
- trigger: 'axis' as const,
401
- formatter: (ctx: any) => {
402
- if (ctx[0]) {
403
- const { name, data: val, marker } = ctx[0];
404
- return val !== null
405
- ? `${marker || ''}${name}: ${val}${unit}`
406
- : `${name}`;
407
- }
408
- return '';
409
- },
410
- ...tooltip,
411
- },
412
- xAxis: {
413
- type: 'category' as const,
414
- axisTick: { show: false },
415
- axisLabel: {
416
- show: showAxis,
417
- fontSize: 10,
418
- interval: 0,
419
- hideOverlap: true,
420
- margin: 15,
421
- },
422
- data: data.map((item) => item?.name),
423
- ...xAxis,
424
- },
425
- yAxis: {
426
- position: 'left' as const,
427
- type: 'value' as const,
428
- axisLabel: { show: showAxis, fontSize: 10 },
429
- splitLine: { show: showGrid },
430
- ...(smartY ?? {}),
431
- ...yAxis,
432
- },
433
- grid: { ...DEFAULT_GRID, bottom: 30, top: 30, ...grid },
434
- series: [
435
- {
436
- data: data.map((item) => item?.value),
437
- type: 'line' as const,
438
- smooth: smooth ?? false,
439
- showSymbol: showDots ?? false,
440
- label: {
441
- show: !!(showDots && showLabel),
442
- position: 'top' as const,
443
- fontSize: 10,
444
- formatter: labelFormatter,
445
- },
446
- ...(area
447
- ? {
448
- areaStyle: {
449
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
450
- { offset: 0, color: 'rgba(56, 137, 243, 0.5)' },
451
- { offset: 1, color: 'rgba(56, 137, 243, 0)' },
452
- ]),
453
- },
454
- }
455
- : {}),
456
- ...seriesProps,
457
- },
458
- ],
459
- };
460
- }
461
-
462
- // ─── GCD helpers for dualYAxis ────────────────────────────────────────────────
463
-
464
- function multipleArrayAdd(...arrays: (number | null)[][]): number[] {
465
- const length = arrays[0].length;
466
- const result: number[] = [];
467
- for (let i = 0; i < length; i++) {
468
- let sum = 0;
469
- for (const arr of arrays) {
470
- sum += arr[i] ?? 0;
471
- }
472
- result.push(sum);
473
- }
474
- return result;
475
- }
476
-
477
- function gcdFunc(a: number, b: number): number {
478
- a = Math.abs(a);
479
- b = Math.abs(b);
480
- const c = 5;
481
- while (b !== 0) {
482
- const temp = b;
483
- b = a % b;
484
- a = temp;
485
- }
486
- let result = a;
487
- let temp = c;
488
- while (temp !== 0) {
489
- const tempValue = temp;
490
- temp = result % temp;
491
- result = tempValue;
492
- }
493
- return result;
494
- }
495
-
496
- function getHighestDigitAndOrder(num: number) {
497
- num = Math.ceil(num);
498
- const order = Math.pow(10, String(num).length - 1);
499
- const highestDigit = Math.ceil(num / order);
500
- return { highestDigit, order };
501
- }
502
-
503
- function getMaxValue(
504
- series: LineDataDualYAxis['series'],
505
- yAxisIndex: number,
506
- ): number {
507
- const seriesArr = series.filter((s) => s.yAxisIndex === yAxisIndex);
508
- if (seriesArr.length && seriesArr.every((s) => s.type === 'bar')) {
509
- const arr = multipleArrayAdd(...seriesArr.map((s) => s.data));
510
- return Math.max(...arr, 0);
511
- }
512
- return Math.max(
513
- ...seriesArr
514
- .flatMap((s) => s.data)
515
- .filter((v): v is number => typeof v === 'number'),
516
- 0,
517
- );
518
- }
519
-
520
- // ─── Option 构建函数 ──────────────────────────────────────────────────────────
521
-
522
- // buildLineOption, buildStackLineOption, buildStackAreaOption 已合并为 buildUnifiedLineOption(见上方)
523
-
524
- function buildMulAreaLineOption(
525
- data: LineDataMulAreaLine,
526
- unit: string,
527
- chartProps: Record<string, any>,
528
- ): echarts.EChartsCoreOption {
529
- const { grid = {} } = chartProps;
530
- const {
531
- series = [],
532
- xData = [],
533
- outerKeyList = [],
534
- innerKeyList = [],
535
- } = data;
536
-
537
- // Sort series: by outerKeyList order then innerKeyList order (mirrors getMulStackLineOption)
538
- const outkeyGroup = series.reduce((acc: any, cur: any) => {
539
- const outerKey = cur?.name?.split('-')?.[0] || cur?.name;
540
- acc[outerKey] ? acc[outerKey].push(cur) : (acc[outerKey] = [cur]);
541
- return acc;
542
- }, {});
543
- let sortedSeries: any[] = outerKeyList.map((outerKey: string) => {
544
- return (outkeyGroup[outerKey] || []).sort((a: any, b: any) => {
545
- const aInner = a?.name?.split('-')?.[1] || '';
546
- const bInner = b?.name?.split('-')?.[1] || '';
547
- return innerKeyList.indexOf(aInner) - innerKeyList.indexOf(bInner);
548
- });
549
- });
550
- sortedSeries = sortedSeries.flat();
551
- // 补上不在 outerKeyList(小份额)里的 series,避免丢失
552
- const includedNames = new Set(sortedSeries.map((s: any) => s.name));
553
- const remainder = series.filter((s: any) => !includedNames.has(s.name));
554
- sortedSeries = [...sortedSeries, ...remainder];
555
- if (sortedSeries.length === 0) sortedSeries = series;
556
-
557
- return {
558
- tooltip: { show: false },
559
- legend: { ...LEGEND_BASE, show: false },
560
- grid: { ...DEFAULT_GRID, top: 30, bottom: 45, ...grid },
561
- xAxis: {
562
- type: 'category' as const,
563
- boundaryGap: false,
564
- data: xData,
565
- axisTick: { show: false },
566
- axisLine: { show: false },
567
- axisLabel: { fontSize: '10px', hideOverlap: true },
568
- },
569
- yAxis: {
570
- position: 'left' as const,
571
- type: 'value' as const,
572
- axisLabel: {
573
- formatter: (value: number) => value.toLocaleString(),
574
- fontSize: '10px',
575
- },
576
- axisLine: { show: false },
577
- splitNumber: 5,
578
- },
579
- series: sortedSeries.map((item: any, index: number) => {
580
- const [outerKey, innerKey] = (item?.name || '').split('-');
581
- const areaColor = item?.areaColor ?? null;
582
- const lineColor = item?.lineColor ?? null;
583
- return {
584
- name: item?.name,
585
- type: 'line' as const,
586
- stack: 'stack',
587
- data: item?.data,
588
- smooth: false,
589
- symbol: 'none',
590
- triggerLineEvent: true, // 让面积/线条区域可触发 mouseover 等标准事件
591
- lineStyle: {
592
- type: 'dashed' as const,
593
- dash: [5 + index, 5 + index],
594
- color: lineColor,
595
- width: 1,
596
- opacity: 0.6,
597
- },
598
- areaStyle: {
599
- color: areaColor,
600
- opacity: 0.6,
601
- },
602
- };
603
- }),
604
- };
605
- }
606
-
607
- // buildSimpleLineOption 已废弃,simpleLine 功能由 buildUnifiedLineOption + smooth + area 覆盖
608
-
609
- function buildDualYAxisOption(
610
- data: LineDataDualYAxis,
611
- chartProps: Record<string, any>,
612
- ): echarts.EChartsCoreOption {
613
- const { grid = {} } = chartProps;
614
- const { xData, yData, series } = data;
615
- const [y0, y1] = yData || [];
616
-
617
- const maxY0val = getMaxValue(series, 0);
618
- const maxY1val = getMaxValue(series, 1);
619
-
620
- const { highestDigit: highestDigitY0, order: order0 } =
621
- getHighestDigitAndOrder(maxY0val);
622
- const { highestDigit: highestDigitY1, order: order1 } =
623
- getHighestDigitAndOrder(maxY1val);
624
-
625
- const gcdNum = gcdFunc(highestDigitY0, highestDigitY1);
626
- const gcdMax = Math.max(highestDigitY0 / gcdNum, highestDigitY1 / gcdNum);
627
- const max = Math.max(highestDigitY0, highestDigitY1);
628
- let max0 = gcdMax * order0 > maxY0val ? gcdMax * order0 : max * order0;
629
- let max1 = gcdMax * order1 > maxY1val ? gcdMax * order1 : max * order1;
630
- max0 = y0?.max ?? max0;
631
- max1 = y1?.max ?? max1;
632
-
633
- const y0SeriesCount = series.filter((s) => s.yAxisIndex === 0).length;
634
-
635
- return {
636
- tooltip: {
637
- trigger: 'axis' as const,
638
- formatter: (params: any) => {
639
- let result = `${params[0].name}<br/>`;
640
- params.forEach((item: any, index: number) => {
641
- const isY0 = index < y0SeriesCount;
642
- const unit = isY0 ? (y0?.unit ?? '') : (y1?.unit ?? '');
643
- const unitStr = unit ? `${unit}` : '';
644
- result += `${item.marker} ${item.seriesName}: ${item.value?.toLocaleString?.() ?? item.value}${unitStr}<br/>`;
645
- });
646
- return result;
647
- },
648
- },
649
- grid: { ...GRID_DUAL_Y, ...grid },
650
- legend: { ...LEGEND_BASE },
651
- xAxis: [
652
- {
653
- type: 'category' as const,
654
- data: xData,
655
- boundaryGap: false,
656
- axisTick: { show: false },
657
- axisLine: { show: false },
658
- axisLabel: {
659
- fontSize: 10,
660
- interval: 0,
661
- hideOverlap: true,
662
- margin: 15,
663
- },
664
- },
665
- ],
666
- yAxis: [
667
- {
668
- type: 'value' as const,
669
- alignTicks: false,
670
- name: y0?.name + (y0?.unit ? `(${y0?.unit})` : ''),
671
- axisLabel: {
672
- fontSize: 10,
673
- formatter: (value: number) =>
674
- `${value?.toLocaleString?.() ?? value}${y0?.unit ?? ''}`,
675
- },
676
- max: max0,
677
- min: 0,
678
- },
679
- {
680
- type: 'value' as const,
681
- alignTicks: false,
682
- name: y1?.name + (y1?.unit ? `(${y1?.unit})` : ''),
683
- axisLabel: {
684
- fontSize: 10,
685
- formatter: (value: number) => `${value}${y1?.unit ?? ''}`,
686
- },
687
- max: max1,
688
- min: 0,
689
- },
690
- ],
691
- series,
692
- };
693
- }
694
-
695
- // ─── 组件 ─────────────────────────────────────────────────────────────────────
696
-
697
- export const LineChart: React.FC<LineChartProps> = ({
698
- data,
699
- variant = 'line',
700
- title = '',
701
- unit = '',
702
- height = 300,
703
- legendShow: legendShowProp,
704
- chartProps = {},
705
- smartLabel = false,
706
- smartYAxis = false,
707
- markLine,
708
- stacked: stackedProp,
709
- area: areaProp,
710
- showGrid: showGridProp,
711
- showDots: showDotsProp,
712
- showLabel: showLabelProp,
713
- smooth: smoothProp,
714
- showAxis: showAxisProp,
715
- loading = false,
716
- empty,
717
- style,
718
- className,
719
- testId,
720
- onChartReady,
721
- onEvents,
722
- }) => {
723
- const isEmpty = empty ?? checkEmpty(data, variant);
724
-
725
- const titleNode = title ? (
726
- typeof title === 'string' ? (
727
- <div
728
- style={{
729
- textAlign: 'center',
730
- fontSize: 13,
731
- fontWeight: 'bold',
732
- color: '#515151',
733
- marginTop: 12,
734
- marginBottom: 4,
735
- }}
736
- >
737
- {title}
738
- </div>
739
- ) : (
740
- title
741
- )
742
- ) : null;
743
-
744
- const mergedOption = useMemo(() => {
745
- // 向后兼容别名
746
- let effectiveVariant = variant;
747
- let stacked = stackedProp;
748
- let area = areaProp;
749
- let smooth = smoothProp;
750
- let showAxis = showAxisProp;
751
- if (variant === 'stackLine') {
752
- effectiveVariant = 'line';
753
- } else if (variant === 'stackArea') {
754
- effectiveVariant = 'line';
755
- stacked = stacked ?? true;
756
- area = area ?? true;
757
- } else if (variant === 'simpleLine') {
758
- effectiveVariant = 'line';
759
- smooth = smooth ?? true;
760
- area = area ?? true;
761
- showAxis = showAxis ?? false;
762
- }
763
-
764
- switch (effectiveVariant) {
765
- case 'line':
766
- return buildUnifiedLineOption(
767
- data as LineDataSingle | LineDataMulti,
768
- unit,
769
- chartProps,
770
- {
771
- stacked,
772
- area,
773
- showGrid: showGridProp,
774
- showDots: showDotsProp,
775
- showLabel: showLabelProp,
776
- smooth,
777
- showAxis,
778
- smartLabel,
779
- smartYAxis,
780
- markLine,
781
- },
782
- );
783
- case 'dualYAxis':
784
- return buildDualYAxisOption(data as LineDataDualYAxis, chartProps);
785
- case 'mulAreaLine':
786
- return buildMulAreaLineOption(
787
- data as LineDataMulAreaLine,
788
- unit,
789
- chartProps,
790
- );
791
- default:
792
- return buildUnifiedLineOption(
793
- data as LineDataSingle | LineDataMulti,
794
- unit,
795
- chartProps,
796
- {
797
- stacked,
798
- area,
799
- showGrid: showGridProp,
800
- showDots: showDotsProp,
801
- showLabel: showLabelProp,
802
- smooth,
803
- showAxis,
804
- smartLabel,
805
- smartYAxis,
806
- markLine,
807
- },
808
- );
809
- }
810
- }, [
811
- data,
812
- variant,
813
- unit,
814
- chartProps,
815
- smartLabel,
816
- smartYAxis,
817
- markLine,
818
- stackedProp,
819
- areaProp,
820
- showGridProp,
821
- showDotsProp,
822
- showLabelProp,
823
- smoothProp,
824
- showAxisProp,
825
- ]);
826
-
827
- // legendShow 覆盖
828
- const finalOption = useMemo(() => {
829
- if (legendShowProp === undefined) return mergedOption;
830
- return {
831
- ...mergedOption,
832
- legend: { ...(mergedOption as any).legend, show: legendShowProp },
833
- };
834
- }, [mergedOption, legendShowProp]);
835
-
836
- // ── mulAreaLine 分组高亮:hover 子类时同父类其他子类稍淡,其他父类全透明 ──
837
- const mulSeriesMetaRef = React.useRef<
838
- { name: string; areaColor: any; lineColor: any }[]
839
- >([]);
840
- const mulActiveRef = React.useRef<string | null>(null);
841
- if (variant === 'mulAreaLine') {
842
- const mulData = data as LineDataMulAreaLine;
843
- mulSeriesMetaRef.current =
844
- mulData.series?.map((s) => ({
845
- name: s.name,
846
- areaColor: s.areaColor ?? null,
847
- lineColor: s.lineColor ?? null,
848
- })) ?? [];
849
- }
850
-
851
- const applyMulOpacity = React.useCallback(
852
- (chart: any, hoveredName: string | null) => {
853
- const meta = mulSeriesMetaRef.current;
854
- if (!meta.length || !chart) return;
855
-
856
- if (!hoveredName) {
857
- chart.setOption({
858
- series: meta.map((s) => ({
859
- areaStyle: { color: s.areaColor, opacity: 0.6 },
860
- lineStyle: { color: s.lineColor, opacity: 0.6 },
861
- })),
862
- });
863
- return;
864
- }
865
-
866
- const outerKey = hoveredName.split('-')[0];
867
- chart.setOption({
868
- series: meta.map((s) => {
869
- const sOuterKey = s.name.split('-')[0];
870
- let opacity: number;
871
- if (s.name === hoveredName) {
872
- opacity = 0.9;
873
- } else if (sOuterKey === outerKey) {
874
- opacity = 0.35;
875
- } else {
876
- opacity = 0.05;
877
- }
878
- return {
879
- areaStyle: { color: s.areaColor, opacity },
880
- lineStyle: { color: s.lineColor, opacity },
881
- };
882
- }),
883
- });
884
- },
885
- [],
886
- );
887
-
888
- const { domRef, chartRef } = useECharts({ option: finalOption, onEvents });
889
-
890
- // 监听 ECharts 标准事件 + 外部 highlight/downplay(sunburst 联动)
891
- // 必须在 useECharts 之后,保证 chartRef.current 已初始化
892
- useEffect(() => {
893
- if (variant !== 'mulAreaLine' || !chartRef.current) return;
894
- const chart = chartRef.current;
895
-
896
- const onMouseOver = (params: any) => {
897
- const name = params.seriesName;
898
- if (name && name !== mulActiveRef.current) {
899
- mulActiveRef.current = name;
900
- applyMulOpacity(chart, name);
901
- }
902
- };
903
-
904
- const onMouseOut = () => {
905
- if (mulActiveRef.current !== null) {
906
- mulActiveRef.current = null;
907
- applyMulOpacity(chart, null);
908
- }
909
- };
910
-
911
- // 外部 dispatchAction highlight(如 sunburst 联动)
912
- const onHighlight = (params: any) => {
913
- const batch = params.batch ?? [params];
914
- const meta = mulSeriesMetaRef.current;
915
- let targetName: string | null = null;
916
-
917
- for (const item of batch) {
918
- if (item.seriesName) {
919
- targetName = item.seriesName;
920
- break;
921
- }
922
- if (item.seriesIndex != null && meta[item.seriesIndex]) {
923
- targetName = meta[item.seriesIndex].name;
924
- break;
925
- }
926
- }
927
-
928
- if (targetName && targetName !== mulActiveRef.current) {
929
- mulActiveRef.current = targetName;
930
- applyMulOpacity(chart, targetName);
931
- }
932
- };
933
-
934
- const onDownplay = () => {
935
- if (mulActiveRef.current !== null) {
936
- mulActiveRef.current = null;
937
- applyMulOpacity(chart, null);
938
- }
939
- };
940
-
941
- chart.on('mouseover', onMouseOver);
942
- chart.on('mouseout', onMouseOut);
943
- chart.on('highlight', onHighlight);
944
- chart.on('downplay', onDownplay);
945
-
946
- return () => {
947
- chart.off('mouseover', onMouseOver);
948
- chart.off('mouseout', onMouseOut);
949
- chart.off('highlight', onHighlight);
950
- chart.off('downplay', onDownplay);
951
- };
952
- }, [variant, finalOption, applyMulOpacity]);
953
-
954
- // 回调 ECharts 实例(onChartReady)
955
- useEffect(() => {
956
- if (onChartReady) {
957
- onChartReady(chartRef.current);
958
- }
959
- }, [chartRef.current]);
960
-
961
- // 组件卸载时通知 null
962
- useEffect(() => {
963
- return () => {
964
- if (onChartReady) onChartReady(null);
965
- };
966
- }, []);
967
-
968
- // 数据到来后强制 resize,确保容器已有实际尺寸时图表正确渲染
969
- useEffect(() => {
970
- if (!isEmpty && chartRef.current) {
971
- requestAnimationFrame(() => {
972
- chartRef.current?.resize();
973
- });
974
- }
975
- }, [isEmpty, mergedOption]);
976
-
977
- return (
978
- <div
979
- style={{
980
- position: 'relative',
981
- width: '100%',
982
- height,
983
- ...(titleNode ? { display: 'flex', flexDirection: 'column' } : {}),
984
- ...style,
985
- }}
986
- >
987
- {titleNode}
988
- <div
989
- style={{
990
- position: 'relative',
991
- width: '100%',
992
- ...(titleNode ? { flex: 1, minHeight: 0 } : { height: '100%' }),
993
- }}
994
- >
995
- <div
996
- ref={domRef}
997
- className={className}
998
- style={{ position: 'absolute', inset: 0 }}
999
- data-testid={testId}
1000
- />
1001
- {isEmpty && !loading && (
1002
- <div
1003
- style={{
1004
- position: 'absolute',
1005
- inset: 0,
1006
- display: 'flex',
1007
- justifyContent: 'center',
1008
- alignItems: 'center',
1009
- }}
1010
- >
1011
- <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="NO DATA" />
1012
- </div>
1013
- )}
1014
- {loading && (
1015
- <div
1016
- style={{
1017
- position: 'absolute',
1018
- inset: 0,
1019
- display: 'flex',
1020
- justifyContent: 'center',
1021
- alignItems: 'center',
1022
- background: 'rgba(255,255,255,0.6)',
1023
- zIndex: 1,
1024
- }}
1025
- >
1026
- <Spin />
1027
- </div>
1028
- )}
1029
- </div>
1030
- </div>
1031
- );
1032
- };
1033
-
1034
- export default LineChart;